神奇的C语言

      固然下面列出来的几点都是C的基础用法,只不过是这些用法可能平时不会被注意。因此不少东西第一次看到的时候,可能会以为很怪异,可是细细想一想就能很好的理解,也就能更好的清楚C语言的一些特性。可是在具体的编码过程中,我仍是但愿都能老老实实规规矩矩的。由于程序员不须要太多棱角,把代码写得规范整洁比耍小聪明要重要得多。下面我列举了5个例子说明一些问题,若是你是老手看到这些就一笑而过吧,若是是新手,我相信仍是会获得一些启发的。程序员

      1. #和##在宏中的做用,以及带参宏,参数的传递问题。数组

      2. 结构体中域的偏移位置的计算问题。函数

      3. 结构体的定义以及初始化的用法。测试

      4. 数组和指针在运算中的等价关系。优化

      5. 数组在栈中的“变异”。编码

1. 例子:spa

    #include <stdio.h>
    
    #define _mir(a) #a
    int main()
    {
        char * s = _mir(
            struct _st{
                int a;
                int b;
                int c;
            };
        );
        printf( "%s\n", s );
        return 0;
    }

输出:指针

    struct _st{ int a; int b; int c; };

说明:blog

A) 预编译中#是将右边的参数转成一个字符串,##是将左右两边的参数链接成一个字符串。例子是#的用法。
B) 宏当中的参数实际上是以逗号(,)分隔的,其余的字符其实都被当作同一个参数,可是换行和空白其实被处理过了,使参数在同一个行中。有兴趣的本身多作些测试吧,这个用法能够用于要写包含特殊字符的字符串,省得要写不少的转义字符(\),可是中间不能有逗号,呵呵~内存

2. 例子:

    #include <stdio.h>
    
    struct _st{
        int a;
        int b;
        int c;
    };
    
    void main()
    {
        printf( "%d\n", &((struct _st*)0)->b );
    }

输出:

    4

 说明:

A) &((struct _st*)0)->b 的做用是输出_st结构体中b的偏移。为何用0当成指针呢,其实很好理解:若是一个_st结构体的地址是0,那么b的地址其实就是b在结构体中的偏移。
B) 其实按理,若是先作((struct _st*)0)->b运算,那么程序确定异常,因此编译器仍是作了优化的,具体编译器怎么作的,我也没深究。

3. 例子:

    #include <stdio.h>
    
    struct _st{
            int a : 1;
            int b : 1;
            int c : 1;
    }s = {
            .c = 1,
            .b = 0,
            .a = 0
    };
    
    void main()
    {
            printf( "%d %d %d %d\n", sizeof(s), s.a, s.b, s.c );
    }

 输出:

    4 0 0 -1

 说明:

A) 在结构体的初始化时,能够指定域进行初始化,如例子中的.c = 1,顺序能够颠倒,这样作的好处就是可读性较强,对于大结构的初始化,在阅读时很方便。缺点就是低版本的编译器可能不支持。
B) 在结构体的声明中,能够指定域的大小,如例子中的int a : 1; 说明a只暂用一个bit,充分展现了C对二进制处理反面的亲和力。
C) 为何s.c输出是-1,而不是1,其实很简单,由于0xFFFFFFFF表示的是-1,那么一个1bit大小的变量,全部位上面都是1,那么它也表示-1。因此编码的过程当中,有符号和无符号混用实际上是很危险的一件事情。
4. 例子:

    #include <stdio.h>
    void main()
    {
        int i;
        char a[10]="hello";
        
        0[a] = 'x';
        printf( "%s\n", a );
    
        for( i=0; i<10; i++ )
            printf( "%c", (rand()%10)["0123456789"] );
        printf( "\n" );
       
    }

输出:

    xello
    1740948824

说明:

A) 0[a] = 'x';是什么玩意儿?若是写成a[0]='x';其实你就明白是什么意思了,可是说白了,a[0]和0[a]在编译器看来是同样的。由于数组在作[]运行时,实际上是作指针的加法运行:a[0]等价于*(a+0)。因此0[a]也就等价于*(0+a)是彻底正确的。
B) 循环中功能是输出一个10位的随机数。其实也等价于"0123456789"[rand()%10]。这里"0123456789"的类型是char*,因此指针也支持[]运算,由于[]运算其实就是加法运算。

5. 例子:

    #include <stdio.h>
        
    void func( char a[10] )
    {
        printf( "%d %d\n", &a, &a[0] );
    }
    
    void main()
    {
        char a[10];
        printf( "%d %d\n", &a, &a[0] );
        func( a );
    }

 输出:

    1638208 1638208
    1638192 1638208

说明:

A) 为何两行的结果会不同?在通常状况下,按个人理解,一个数组a,&a和&a[0]的值是同样的。可是当a在形参当中时就不同了。例子中,func函数中的a,其实a变量是在func函数的栈当中,在func内部,a其实已经被转化成char *a,因此&a是表示指针变量a在栈中的地址,而&a[0]表示的是指针指向的内存空间的第一个元素的地址,其实也就是调用者传入的数组的第一个元素的地址。不知道我说明白了没有!! B) 这个可能比较难理解,关键是明白一点,在数组做为形参时,是被转换成指针看待的。

相关文章
相关标签/搜索