标签:c语言
之前写过一篇C的语言的角落,介绍了一些C中的一些非常用特性(http://blog.csdn.net/yang_yulei/article/details/34557625),最近又整理了一些边角的知识,特开此文。
switch语句中的case
(case 关键词可以放在if-else或者是循环当中)
switch (a)
{
    case 1:;
      // ...
      if (b==2)
      {
        case 2:;
        // ...
      }
      else case 3:
      {
        // ...
        for (b=0;b<10;b++)
        {
          case 5:;
          // ...
        }
      }
      break;
 
    case 4:
}
指定初始化(C99)
在C99之前,你只能按顺序初始化一个结构体。在C99中你可以这样做:
struct Foo {
    int x;
    int y;
    int z;
};
Foo foo = {.z = 3, .x = 5};
这段代码首先初始化了foo.z,然后初始化了foo.x. foo.y 没有被初始化,所以被置为0。
这一语法同样可以被用在数组中。以下三行代码是等价的:
int a[5] = {[1] = 2, [4] = 5};
int a[]   = {[1] = 2, [4] = 5};
int a[5] = {0, 2, 0, 0, 5};
受限指针(C99)
关键字restrict仅对指针有用,修饰指针,表明要修改这个指针所指向的数据区的内容,仅能通过该指针来实现,此关键字的作用是使编译器优化代码,生成更高效的汇编代码。
例如:
int foo (int* x, int* y)
{
    *x = 0;
    *y = 1;
    return *x;
}
很显然函数foo()的返回值是0,除非参数x和y的值相同。可以想象,99%的情况下该函数都会返回0而不是1。然而编译起必须保证生成100%正确的代码,因此,编译器不能将原有代码替换成下面的更优版本:
int f (int* x, int* y)
{
    *x = 0;
    *y = 1;
    return 0;
}
int f (int *restrict x, int *restrict y)
{
    *x = 0;
    *y = 1;
    return 0;
}void *memcpy( void * restrict dest ,const void * restrict src,sizi_t n) 这是一个很有用的内存复制函数,由于两个参数都加了restrict限定,所以两块区域不能重叠,即 dest指针所指的区域,不能让别的指针来修改,即src的指针不能修改. 相对应的别一个函数 memmove(void *dest,const void
 * src,size_t)则可以重叠。
静态数组索引(C99)
void f(int a[static 10]) {
    /* ... */
}
你向编译器保证,你传递给f 的指针指向一个具有至少10个int 类型元素的数组的首个元素。我猜这也是为了优化;例如,编译器将会假定a 非空。编译器还会在你尝试要将一个可以被静态确定为null的指针传入或是一个数组太小的时候发出警告。
void f(int a[const]) {
    /* ... */
}
你不能修改指针a.,这和说明符int * const a.作用是一样的。然而,当你结合上一段中提到的static 使用,比如在int a[static const 10] 中,你可以获得一些使用指针风格无法得到的东西。
多字符常量
int x = ‘ABCD‘ ;
这会把x的值设置为0×41424344(或者0×44434241,取决于大小端)我们一般的小端机上,低位存在低字节处,DCBA依次从低字节到高字节排列。
这只是一种看起来比较炫酷的写法,一般没什么用。
关于EOF
EOF是初学者比较困惑的一个东西,算不上生僻的知识点,但容易造成误解,出错,所以在这里也总结一下。
EOF是end of file的缩写,表示”文字流”(stream)的结尾。这里的”文字流”,可以是文件(file),也可以是标准输入(stdin)。
比如,下面这段代码就表示,如果不是文件结尾,就把文件的内容复制到屏幕上。
int c;
while ((c = fgetc(fp)) != EOF) {
  putchar (c);
}很自然地,我就以为,每个文件的结尾处,有一个叫做EOF的特殊字符,读取到这个字符,操作系统就认为文件结束了。int c;
while (!feof(fp)) {
    c = fgetc(fp);
    do something;
}但是,这样写也有问题。fgetc()读取文件的最后一个字符以后,C语言的feof()函数依然返回0,表明没有到达文件结尾;只有当fgetc()向后再读取一个字符(即越过最后一个字符),feof()才会返回一个非零值,表示到达文件结尾。int c ;
while ((c = fgetc(fp)) != EOF) {
    do something;
}
if (feof(fp)) {
    printf("\n End of file reached.");
} else {
  printf("\n Something went wrong.");
}
位域(位段)
位域也不算是很冷僻的知识点,但关于其内存对齐的问题,还是有些小的细节需要注意。
所谓“位域”是把一个字节中的二进制位划分为几个不同的区域, 并说明每个区域的位数。每个域有一个域名,允许在程序中按域名进行操作。
位域列表的形式为: 类型说明符 位域名:位域长度;
一个位域必须存储在同一个字节中,不能跨两个字节。如一个字节所剩空间不够存放另一位域时,应从下一单元起存放该位域。
例如:
struct A
{
    char a:6 ;
    char b:5 ;
    char c:5 ;
};
// sizeof(struct A) 为3,而不是2。
0位段
长度为0的位段,其作用是使下一个位段从下一个存储单元开始存放。(下个存储单元的大小是其位段类型的大小)
例如:
struct bs
{
    unsigned a:4;
    unsigned  :0;   // 空域
    unsigned b:4;   // 从下一存储单元开始存放
    unsigned c:4;
};
// sizeof(struct bs) 为8,若没有0位段则是4
关于内存排布
先举个栗子:
struct foo {
    char a;
    int  b:1;
    int   :0;
    int  c:7;
    char d;
    char e:4;
    char f:6;
    char g:6;
} ;位段的压缩存储规则是:
1. 如果相邻位域字段的类型相同,且其位宽之和小于类型的sizeof大小,则后面的字段将紧邻前一个字段存储,直到不能容纳为止;
2. 如果相邻位域字段的类型相同,但其位宽之和大于类型的sizeof大小,则后面的字段将从新的存储单元开始,其偏移量为其类型大小的整数倍;
3. 如果相邻的位域字段的类型不同,则各编译器的具体实现有差异,VC6采取不压缩方式,Dev-C++采取压缩方式;
4. 如果位域字段之间穿插着非位域字段,则不进行压缩;
5. 整个结构体的总大小为最宽基本类型成员大小的整数倍。
sizeof(struct foo) 为12
下面慢慢解释:char a占一个字节,b占1bit,gcc采用压缩方式,即把a和b压缩在一个int单元里, 其后是0位段,故其前面是一个存储单元,a和b占4字节。
之后又是int c,故先开一个4字节,然后把后面的位段填充进去,但后面是一个完整的char,这个完整的char是不能放到位段中的,故int c占4字节,char d占1字节。
其后的e,f,g都是位段,注意,位段是不能跨越边界的(以其数据类型的大小为边界),故e,f,g各占1字节,而不是合占2字节。 故sizeof(struct foo) 为12。
简单图解:
|---a(8)---|-b(1)-|---------------------(23)----------------------|
|---c(7)---|-------------------------(25)--------------------------|
|---d(8)---|---e(4)---|--(4)--|---f(6)---|-(2)-|---g(6)---|-(2)-|
注意:
位段最好不要跨字节,我在C教材中看到过警示:不要使位段超过8位,但定义多位的位段(我曾定义过64位的位段,用于提取double型变量的各位)仍然可行,可以编译以及运行,但是是跨字节之后,其排列顺利就会受到大小端的影响,故最好不要让位段超过8位。
标签:c语言
原文地址:http://blog.csdn.net/yang_yulei/article/details/46337583