1.假设hi和low是两个整数,他们的值介于0到15之间,r是一个八位整数,想要r的低四位和low一致,高四位和hi一致,很自然会这样写:
r = hi《4+low;很不幸这样是错误的,因为加法的运算级别要比移位运算符高,因此实际上相当于,r=hi《(4+low);
所以正确的写法是,r=(hi《4)+low或者r = hi《4 | low;
2.int calendar[12][31]可以看做拥有12个元素的数组,其中每个元素都是一个拥有31个整型元素的数组,这12个元素每一个calendar【X】都是所在31个元素数组的首地址,于是四月七号可以表示为*(calendar[4]+7)或者*(*(calendar+4)+7)或者calendar[4][7];
3.空指针并非空字符串。当常数零被转换为指针使用时,这个指针绝对不能解除引用(dereference).换句话说,当我们把0赋给一个指针变量时,绝对不能企图使用该指针所指向的内存中的存储内容。
下面的写法是合法的:
if(p == (char*) 0) …… 但是写成下面这样就是非法的: if(strcmp(p,(char *)0) == 0)…… 原因是库函数的实现中会包括查看它的指针参数所指向的内存中的内容的操作。
4.边界计算与不对称边界。
循环的写法推荐写成下面的方式:
int a[10],i; for(i = 0;i<10;i++) a[i] = 0;
而不是写成下面这样:
int a[10],i; for(i = 0;i<=9;i++) a[i] = 0;
对于指针bufptr,我们考虑“不对称边界”的偏好,让它指向缓冲区中第一个未被占用的字符,而不是指向缓冲区中最后一个已占用的字符,于是我们这样编写语句:
*bufptr++ = c;
当指针bufptr与&buffer[0]相等时,缓冲区存放的内容为空,因此初始化时声明缓冲区为空可以这样写:
bufptr = &buffer;或者更简洁的写成 bufptr = buffer;
所以任何时候缓冲区中已存放的字符数都是bufptr-buffer,未占用的字符数为N-(bufptr-buffer);
现在编写函数bufwrite:
void bufwrite(char *p,int n) { while(--n >>= 0) { if(bufptr == &buffer[N]) flushbuffer(); *bufptr++ = *p++; } }
函数bufwrite有两个参数,第一个参数是一个指针,指向将要写入缓冲区的第一个字符;第二个参数是一个整数,代表将要写入缓冲区的字 符数。假定我们可以调用函数flushbuffer来吧缓冲区里的内容写出来,而且函数flushbuffer会重置指针bufptr,使其指向缓冲区的起始位置。 注:在大多数c语言实现中,--你>=0至少与等效的n-->0一样快,甚至在某些c实现中还要快。
代码中出现了bufptr与&buffer[N]的比较,而buffer 【N】元素是不存在的!数组buffer的元素下标从0到N-1,所以我们用
if(bufptr == &buffer[N])
等效代替了
if(bufptr > &buffer[N-1])
原因是我们坚持遵循“不对称边界的原则”:我们要比较指针bufptr与缓冲区后第一个字符的地址,实际上我们并不需要引用这个元素,只需要引用这个元素的地址。
5.C语言中只有四个运算符(&&、||、?:和,)存在规定的求值顺序。
&&和||首先对左侧的操作数求值,只有在需要的时候才对右侧的操作数求值。运算符?:有三个操作数:在a?b:c中,操作数a首先被求值,根据a的值再求操作数b或c的值。而逗号运算符,首先对左侧操作数求值,然后该值被丢弃,再对右侧操作数求值。
C语言中其他所有运算符对其操作数求值的顺序是未定义的。特别的,赋值运算符并不保证任何求值顺序。
6.按位运算符&,|和~对操作数的处理方式是将其视作一个二进制的位序列,分别对其每个位进行操作。运算符&&和运算符||在左侧操作数的值能够确定最终结果时根本不会对右侧操作数求值。
7.extern关键字是一个对外部变量的引用而不是定义,static修饰符可以把变量的作用域限制在一个源文件内,对于其他文件是不可见的。我们可以在多个源文件中定义同名的函数g,只要所有的函数g都被定义为static,或者仅仅只有一个函数g不是static。
8.如果任何一个函数在调用它的每个文件中,都在第一次被调用之前进行了声明或者定义,那么就不会有任何与返回值类型相关的麻烦。如果一个函数在被定义或者声明之前被调用,那么它的返回值类型就默认为整型。
9.
#include<stdio.h> main() { int i; char c; for(i = 0;i <5;i++) { scanf("%d",&c); printf("%d ",i); } printf("/n"); }
表面上,这个程序从标准输入设备上读入5个数,在标准输出上写五个数:0 1 2 3 4
实际上,这个程序并不一定得到上面的结果。在某个编译器上,输出可能是:
0 0 0 0 0 0 1 2 3 4
问题的关键在于,这里的c被声明为char类型,而不是int类型。当程序要求scanf读入一个整数,应该传递给他指向整数的指针。而程序中scanf函数得到的却是一个指向字符的指针,scanf并不能分辨这种情况,他只是将这个指向字符的指针作为指向整数的指针接收,并且在指针指向的位置存储一个整数。因为整数所占的存储空间要大于字符所占的存储空间,所以字符附近的内存将被覆盖。
字符c附近的内存中存储的内容是由编译器决定的,本例中它存放的是整数i的低端部分。因此,每次读入一个数值到c时,都会将i的低端部分覆盖为0,而i的高端部分本来就是0,相当于i每次都重新设置为0,循环将一直进行。当达到文件的结束位置后,scanf函数不再试图读入新的数值到c。这时,i才可以正常的递增,最后终止循环。
10.在微处理器中数据总是按照高位优先的方式存放,而在内存中,会因为厂商的不同而不同。
big endian:最低地址存放高位字节,称为高位优先。内存从最低地址开始,按顺序存放,高位数字先写,所有处理器都是按照这个顺序存放数据。
little endian:最低地址存放低位字节,可称为低位优先。内存从最低地址开始,顺序存放。
11.宏中的参数最好不要有++,否则会产生很多副作用。
12.最好用类型定义来定义新的类型,而不实用宏来定义。例如:
# define T1 struct foo*;
typedef struct foo *T2;
T1和T2从概念上完全相同,都是指向结构foo的指针,的你是如果用来声明多个变量,就会有问题:
T1 a,b;
T2 a,b;
第一个被拓展为struct foo * a,b;
这样a被定义为一个指向结构体的指针,而b被定义为一个结构体。
13.将基本数据类型定义成为新的数据类型使移植更加修改。
14.因为一个字符串常量可以用来表示一个字符数组,所以在数组名出现的地方都可以用字符串常量来替换:
((char)(n%10)+'0') 可以用"0123456789"[n%10]来代替
15.大小写字母互换的宏
#define _toupper(c) ((c)+'A'-'a')
#define _tolower(c) ((c)+'a'-'A')