C语言的角落——C之非常用特性(一)

本文搜集整理了一些之前博客中没有提到的,C语言不常用的特性,算是对C系列的最后一次补充。

对C语言有兴趣的朋友可以浏览一下,查漏补缺。

变长参数列表

<stdarg.h> 头文件定义了一些宏,当函数参数未知时去获取函数的参数

变量:typedef  va_list

宏:

va_start()

va_arg()

va_end()

va_list类型通过stdarg宏定义来访问一个函数的参数表,参数列表的末尾会用省略号省略 
(va_list用来保存va_start,va_end所需信息的一种类型。为了访问变长参数列表中的参数,必须声明va_list类型的一个对象 )

我们通过初始化(va_start)类型为va_list的参数表指针,并通过va_arg来获取下一个参数。

【例子:】

求任意个整数的最大值

#include <stdio.h>
#include <stdarg.h>

int maxint(int n, ...)         /* 参数数量由非变长参数n直接指定 */
{
    va_list ap;
    int     i, arg, max;

    va_start(ap, n);           /* ap为参数指针,首先将其初始化为最后一个具名参数, 以便va_arg获取下一个省略号内参数 */
    for (i = 0; i < n; i++) {
        arg = va_arg(ap, int); /* 类型固定为int, 按照给定类型返回下一个参数 */
        if (i == 0)            
            max = arg;           
        else {
            if (arg > max)
                max = arg;
        }
    }
    va_end(ap);
    return max;
}

void main()
{
    printf("max = %d
", maxint(5, 2, 6, 8, 11, 7));   
}

可变长数组

历史上,C语言只支持在编译时就能确定大小的数组。程序员需要变长数组时,不得不用malloc或calloc这样的函数为这些数组分配存储空间,且涉及到多维数组时,不得不显示地编码,用行优先索引将多维数组映射到一维的数组。

ISOC99引入了一种能力,允许数组的维度是表达式,在数组被分配的时候才计算出来

#include <stdio.h>

int 
main(void)
{
    int n, i ;

    scanf("%d", &n) ; 

    int array[n] ; 
    for (; i<n; i++)
    {
        array[i] = i ;
    }

    for (i=0; i<n; i++)
    {
        printf("%d,", array[i]) ;
    }

    return 0;
}

注意:

如果你需要有着变长大小的临时存储,并且其生命周期在变量内部时,可考虑VLA(Variable Length Array,变长数组)。但这有个限制:每个函数的空间不能超过数百字节。因为C99指出边长数组能自动存储,它们像其他自动变量一样受限于同一作用域。即便标准未明确规定,VLA的实现都是把内存数据放到栈中。VLA的最大长度为SIZE_MAX字节。考虑到目标平台的栈大小,我们必须更加谨慎小心,以保证程序不会面临栈溢出、下个内存段的数据损坏的尴尬局面。

case支持范围取值(gcc扩展特性) MinGW编译通过

#include <stdio.h>

int  main(void)
{
    int i=0; 
    scanf("%d", &i) ;

    switch(i)
    {
     case 1 ... 9: putchar("0123456789"[i]);   
     case "A" ... "Z":    //do something
     } 

     return 0;
}

非局部跳转setjmp和longjmp

在C中,goto语句是不能跨越函数的,而执行这类跳转功能的是setjmp和longjmp。这两个对于处理发生在深层嵌套函数调用中的出错情况是非常有用的。

此即为:非局部跳转。非局部指的是,这不是由普通C语言goto语句在一个函数内实施的跳转,而是在栈上跳过若干调用帧,返回到当前函数调用路径的某个函数中。

       当使用的是十六进制转换说明符x或X时,在输出数据前面加上前缀0x或0X

0(零)     在打印的数据前面加上前导0

逆向打印参数(POSIX扩展语法)

printf("%4$d %3$d %2$d %1$d", 1, 2, 3, 9);      //将会打印9 3 2 1

格式化输入scanf

扫描集(实用)

一个字符序列可以用一个扫描集(Scanset)来输入。扫描集是位于格式控制字符串中,以百分号开头、用方括号[]括起来的一组字符。

寻找与扫描集中的字符相匹配的字符。一旦找到匹配的字符,那么这个字符将被存储到扫描集对应的实参(即指向一个字符数组的指针)中。只有遇到扫描集中没有包含的字符时,扫描集才会停止输入字符。

如果输入流中的第一个字符就不能与扫描集中包含的字符相匹配,那么只有空操作符被存储到字符数组中。

(如果输入的字符属于方括号内字符串中某个字符,那么就提取该字符;如果一经发现不属于就结束提取。该方法会自动加上一个""到已经提取的字符后面。)

【例如】

char str[512] ;

printf(“Enter string: ”) ;

scanf(“%[aeiou]”, str) ;

程序使用扫描集[aeiou]在输入流中寻找元音字符,直到遇到非元音字符。

我们还可以用缩写a-z表示abcd….xyz字母集。

scanf(“%[a-z]”, str) ;

同理,也可以用缩写0-9  缩写A-Z。

想只取字母,那就可以写成 %[A-Za-z]

对于字符串"abDEc123"如果想按照字母和数字读到两个字符串中就应该是 "%[a-zA-Z]%[0-9]",buf1,buf2 ;

逆向扫描集

逆向扫描集还可以用来扫描那些没有出现在扫描集中的字符。创建一个逆向扫描集的方法是,在方括号内扫描字符前面加一个“脱字符号”(^)。这个符号将使得那些没有出现在扫描集中的字符被保存起来。只有遇到了逆向扫描集中包含的字符时,输入才会停止。(即取其后字符们的补集作为扫描集)

scanf(“%[^aeiou]”, str) ;

即接受输入流中的非元音字符。

用这种方法还可以解决scanf的输入中不能有空格的问题。只要用

scanf("%[^ ]",str); 就可以了。很神奇吧。

【注意】

[]内的字符串可以是1或更多字符组成。空字符集(%[])是违反规定的,可导致不可预知的结果。%[^]也是违反规定的。

指定域宽

我们可以在scanf函数的转换说明符中指定域宽来从输入流中读取特定数目的字符。

【例】

scanf(“%2d%d”, &x, &y) ;

程序从输入流中读取一系列连续的数字,然后,将其前两位数字处理为一个两位的整数,将剩余的数字处理成另外一个整数。

赋值抑制字符

。赋值抑制字符使得scanf函数从输入流中读取任意类型的数据,并将其丢弃,而不是将其赋值给一个变量。如果你想忽略掉某个输入,使用在% 后使用

%[^=] 前面带 号表示不保存变量。跳过符合条件的字符串。

char s[]="notepad=1.0.0.1001";

char szfilename [32] = "" ;

int i = *sscanf( s, "%[^=]", szfilename )**;

// szfilename=NULL,因为没保存

int i =*sscanf( s, "%[^=]=%s", szfilename )**;

// szfilename=1.0.0.1001

所有对%s起作用的控制,都可以用于%[],比如"%[^ ]%c"就表示跳过一行,"%-20[^ ]"就表示读取 前20个字符。 

把扫描集、赋值抑制符和域宽等综合使用,可实现简单的正则表达式那样的分析字符串的功能。

scanf的返回值是读入数据的个数;
比如scanf("%d%d",&a,&b);读入一个返回1,读入2个返回2,读入0个返回0;读入错误返回EOF即-1

顺便提一句,你应该非常小心的使用scanf 因为它可能会是你的输入缓冲溢出!通常你应该使用fgets 和sscanf 而不是仅仅使用scanf,使用fgets 来读取一行,然后用sscanf 来解析这一行,就像上面演示的一样。

数据类型对应字节数

程序运行平台
     不同的平台上对不同数据类型分配的字节数是不同的。
     个人对平台的理解是CPU+OS+Compiler,是因为: 
     1、64位机器也可以装32位系统(x64装XP); 
     2、32位机器上可以有16/32位的编译器(XP上有tc是16位的,其他常见的是32位的); 
     3、即使是32位的编译器也可以弄出64位的integer来(int64)。 
     以上这些是基于常见的wintel平台,加上我们可能很少机会接触的其它平台(其它的CPU和OS),所以个人认为所谓平台的概念是三者的组合。 
     虽然三者的长度可以不一样,但显然相互配合(即长度相等,32位的CPU+32位的OS+32位的Compiler)发挥的能量最大。 
     理论上来讲 我觉得数据类型的字节数应该是由CPU决定的,但是实际上主要由编译器决定(占多少位由编译器在编译期间说了算)。

常用数据类型对应字节数可用如sizeof(char),sizeof(char*)等得出

32位编译器:

     char :1个字节
     char*(即指针变量): 4个字节(32位的寻址空间是2^32, 即32个bit,也就是4个字节。同理64位编译器)
     short int : 2个字节
     int:  4个字节
     unsigned int : 4个字节
     float:  4个字节
     double:   8个字节
     long:   4个字节
     long long:  8个字节
     unsigned long:  4个字节

 64位编译器:

     char :1个字节
     char*(即指针变量): 8个字节
     short int : 2个字节
     int:  4个字节
     unsigned int : 4个字节
     float:  4个字节
     double:   8个字节
     long:   8个字节
     long long:  8个字节
     unsigned long:  8个字节

文章导航