C语言原则:一切工作自己负责。
C语言哲学:语言中的任何部分都不应该通过隐藏的运行程序来实现。
1.当执行一个运算时,如果它的一个运算符是有符号的,而另一个运算符是无符号的,那么C语言会隐式地将有符号参数强制转换为无符号数,并假设这两个数是非负数,来执行这个 运算。
例如:if( -1 < 0u) 这条语句中,由于0被定义成了无符号的数,表达式就等价于2^32-1 < 0,可以看到语句的判断结果就和原来的本意完全不一样了。
2.为什么采用下面的方式定义,而不是直接#define int_min -2147483648
#define int_max 2147483647
#define int_min -int_max-1
为什么要特意把32位int的最小值常量写成-2147483647-1而不是-2147483648是因为编译器遇到-X这样的常量是先获得X的值与类型,然后再对其取负,而对于32位以上机器的所有int类型都容不下2147483648这么大,所以会再寻找更合适的数据类型来表示,而寻找合适类型这步在不同的C语言版本,不同的平台,不同的表示格式都有不同的适配顺序,这就导致了如果直接写-2147483648可能会是unsigned的,可能会是long还可能会是long long型的,故用-2147483647-1的形式来消弭此歧义。
int dt1 = ( -2147483648 < 0),int dt2=( 0x80000000<0)
这里的dt1和dt2都是1,无论在何种机器上,这里存在一个隐式转化,转为int型数据。
3.众所周知,C语言中,所有非0 的数表示为真,0表示为假。那么null呢?
例如:
if (null)
printf("nihao");
这里的打印不会执行。因为,null的ASII值就是0,所以null此处就相当于0。
4.容易混淆的const
const并不能把变量变成常量!它仅仅表示这个符号不能被赋值。但它不能防止通过程序的内部(甚至是外部)的方法来修改这个值。const最有用之处就在用它来限定函数的形参,这样该函数将不会修改实参指针所指的数据。
5.switch 语句
标准C语言规定至少允许一条switch语句有257个case标签(为了允许switch满足一个8bit字符的所有情况,即256种情况,再加上EOF)
在switch语句的左花括号和第一个case之间可以增加一些变量声明,从而进行一些局部存储的分配。但没有必要为这些变量赋初值,因为它不会被执行,程序从后面的case开始执行。
所有的case都是可选的,任何形式的语句包括带标签的语句都是允许的。这里就会出现一些问题,例如:
如果把default打错 了,编译器是不会报错的。
注意:break语句事实上跳出的是最近的那层循环语句或switch语句。
6.字符串的连接
旧风格:
printf("nihao \
fecheng gaoxing \
jiandao ni ");
新风格:
printf(“nihao”
"fecheng gaoxing"
" jiandao ni" );
即新风格下会用一连串相邻的字符串常量代替,在编译时自动合并。除最后一个字符串外,其他字符串末尾的'\0’都被自动删除。当然,这种特性又可能会引入新的问题,例如:
char *p[] = {
"color",
"big disk",
"cray" //此处少写一个逗号
"nimei",
"nimhao",//这里的逗号存在与否没有什么意义
};
缺少逗号的一行会和后面的字符串合并,导致最后数组的个数比预期的少一个。
7.C语言运算符优先级存在的问题
1) "."的优先级高于"*",“->”操作符用于消除这个问题。例如:*p.f。
其实际结果是对p取f偏移,作为指针,然后做解除引用的操作,等价于*(p.f)
2)函数()高于“*”。int *fp() 实际结果是相当于 int *( fp() )。
3)"=="和"!="高于位操作符。如(val & mask !=0)等价于 val&(mask != 0)
4)"=="和"!="高于赋值符。如 c = getchar() !=EOF 等价于 c=(getchar() != EOF)
5)算术运算高于移位运算。如 msb<< 4+lsb 等价于msb <
6)逗号的优先级是所有运算符中最低的。
8.与空格有关的Bug
1)“\”字符可用于一些字符的转义,但如何在“\”和需要转义的字符间留上一两个空格就会出现问题。
2)z=y+++x;
这句话根据ANSI C规定的“maximal munch strategy”,编译器将选取能组成最长字符序列的方案。故上面的代码被解析为z=y++ +x;
z=y+++++x;
按照上面的说明,应解析为 z=y++ ++ +x;但实际上编译器会报错。
3)ratio = *x/*y。本意是2个指针指向的数据做除法。但编译器会报错。编译时会把y前面的*号作为需要转义的字符处理, 故需要在/和y前面的*号间添加一个空格。
9.typedef详解
普通声明表示“这个名字是一个指定类型的变量”,而typedef关键字并不创建一个变量,而是宣称“这个名字是指定类型的同义词”。
typedef int *ptr; // ptr 是 “指向int的指针” 类型。
typedef int (fun)() //fun是“指向返回值为int的函数的指针” 类型。
typedef int arr[5] //arr是“长度为5的int型数组” 类型。
例如: ptr a;// a为指向int型的指针。 arr b;//b为有5个元素的int型数组。
typedef和#define的区别:
1)可以用其他类型说明符对宏类型名进行扩展,但对typedef所定义的类型名却不能这么做。
#define peach int
unsigned peach i; //正确
typedef int banana;
unsigned banana i; //错误
2)连续几个变量的声明中,用typedef定义的类型能够保证声明中所有的变量均为同一种类型,而宏定义的类型则不行。
# define prt int*
prt a, b; //b的类型是int型
10. “x++”、" x+1" 、"++x"不能作为左值使用
网上较为统一的说法如下:
“需要了解编译器。++x实际上返回的是x,可以被寻址,所以++x可以作为左值;而x++实际上是x还没有做++的操作,它是一个引用临时变量的表达式,不能寻址,所以x++是右值”。
但在codeblocks上编译时,“++x”也不能作为左值,根据临时变量这一特点,又测试了“x+1”,同样也不能作为左值。可见这里的区别应该还是与编译器相关。编译器可能认为“++x”是不合理的左值而去掉。
11.为什么在free一个指针的时候,一般后面都要把这个指针置为NULL
如果一个指针在free之后不置为NULL的话,这样的指针就叫做“野指针”。free函数不会将指针置为NULL,实际上,free不对指针的值做任何操作,而只是试图改变指针指向的一片连续的存储器空间的状态。如果这片存储器空间是malloc或其它兼容方式(例如POSIX库函数strdup)分配过来的,那么会释放这片空间,释放的空间可以之后再次被分配。
而我通过实际的编程发现:若不将指针置为NULL,之后调用这个指针,会发现这个指针被分配的地址已经发生改变,而其指向的值仍未发生改变(这就是隐患)。指针地址改变,说明操作系统已经将这个指针重新分配了。 而如果将这个指针置为NULL的话,同样调用这个指针所指向的值,则编译器会提示出错,且不会打印该指针指向的值。
因此,保险起见,在每一个free()的操作后,都应该加上把该指针置为NULL的语句。
12. sizeof('A')等于多少?
等于4,或者是你机器上int型的长度。字符常量的类型是int,根据提升规则,它由char转换为int。K&R中提到:“在表达式中,每个char都被转换为int,所有的float都转换为double,由于函数参数也是一个表达式,所以当参数传递给函数时也会发生类型转换,即,char和short转换为int,float转换为double”
注意:这个特性称为类型提升。类型提升会在做四则运算、参数传递中发生。这也是为什么单个的printf()格式符字串%d能适用于几个不同类型:short、char或int。但是如果使用了函数原型(即在函数名后的括号内给出参数类型),缺省参数提升就不会发生。
13. 2种方式定义字符串数组时,使用sizeof表达式得到的结果不同
代码段:
void main()
{
char c[] = {'a','b','c'};
char p[] = "abc";
char q[3];
printf("%d\n",q);
printf("%s,%d,%d\n",p, sizeof(p), &p);
printf("%s,%d,%d\n",c, sizeof(c), &c);
}结果截图:
1)如果使用大括号的形式初始化字符数组,使用sizeof表达式,结果就是实际的字符数;而如果直接用一个字符串赋给字符数组,使用sizeof表达式就会比实际的字符数多一个,这是因为在赋值的过程中,把字符串最后的“\0”也一起赋给了字符数组。(其实也越界打印了p[3]和c[3],都是“\0”)
2)可以看见这三个数组的位置是连续放在一起的,代码从上到下,分配的位置从高到低。可见堆栈是从高地址向低地址生长。
14.指针数组和数组指针的区别
指针数组:用于存储指针的数组,数组元素都是指针
数组指针:指向数组的指针
int* a[4] 指针数组,数组a中的元素都为int型指针(注意:[]优先级高于*)
int (*a)[4] 数组指针,指向数组a的指针
15.C语言中的类型
C语言中要么是组合类型(如数组、结构等,由相同的更小的元素组成),要么是标量类型(scaler type,具有一个特性,每个值都是原子值,即并非有其他类型组成)。
数值类型继承了标量类型的所有特性,并增加了记录算术量。
整数类型继承了数值类型的所有特性,且都是整数。
16. 不一样的结构体初始化
struct
{
unsigned int type : 4;
unsigned int dup : 1;
unsigned int qos : 2;
unsigned int retain : 1;
} bits;这里面每一行都比平常我们看到的结构体定义多一个冒号加一个数字。这里表示的意思就是只取前面对应的值的低多少位。例如type,应该是32位,冒号后面是数字4,则代表只取type的低四位数据,这样,值肯定就会和type的值有出入。对于笔者来说,这是一个比较新的用法
17.无限循环和死循环
无限循环是程序设计者有意为之,死循环则是程序错误
for( ; ; ) 与while(true)的区别
1).前者效率高,优化时会将for循环的两个分号省略,而while中还需要判断。当然,这还得看编译器。有的编译器上,二者在效率上也没有明显差别
2).while中判断条件一直为真,有些工具软件如PC-Lint会判错
总之,二者的区别很大程度上由编译器来决定(个人理解)。
18需要区别的变量声明
a) 一个整型数(An integer)
b)一个指向整型数的指针( A pointer to an integer)
c)一个指向指针的的指针,它指向的指针是指向一个整型数( A pointer to a pointer to an intege)r
d)一个有10个整型数的数组( An array of 10 integers)
e) 一个有10个指针的数组,该指针是指向一个整型数的。(An array of 10 pointers to integers)
f) 一个指向有10个整型数数组的指针( A pointer to an array of 10 integers)
g) 一个指向函数的指针,该函数有一个整型参数并返回一个整型数(A pointer to a function that takes an integer as an argument and returns an integer)
h)一个有10个指针的数组,该指针指向一个函数,该函数有一个整型参数并返回一个整型数( An array of ten pointers to
functions that take an integer argument and return an integer )
答案是:
a) int a; // An integer
b) int *a; // A pointer to an integer
c) int **a; // A pointer to a pointer to an integer
d) int a[10]; // An array of 10 integers
e) int *a[10]; // An array of 10 pointers to integers
等价于int *(a[10]);
f) int (*a)[10]; // A pointer to an array of 10 integers
g) int (*max_function)(int a); // A pointer to a function a that takes an integer argument and returns an integer
h) int (*a[10])(int); // An array of 10 pointers to functions that take an integer argument and return an integer
19关键字static的作用是什么?
在C语言中,关键字static有三个明显的作用:
1)一旦声明为静态变量,在编译时刻开始永远存在,不受作用域范围约束,但是如果是局部静态变量,则此静态变量只能在局部作用域内使用,超出范围不能使用,但是它确实还占用内存,还存在.
2)
在模块内(但在函数体外),一个被声明为静态的变量可以被模块内所用函数访问,但不能被模块外其它函数访问。它是一个本地的全局变量。
3)在模块内,一个被声明为静态的函数只可被这一模块内的其它函数调用。那就是,这个函数被限制在声明它的模块的本地范围内使用。
20.关键字const有什么含意?
总结:1)只读。2)使用关键字const也许能产生更紧凑的代码。3)使编译器很自然地保护那些不希望被改变的参数,防止其被无意的代码修改
21.树的高度和深度
树的深度是从根节点开始(其深度为1)自顶向下逐层累加的,而高度是从叶节点开始(其高度为1)自底向上逐层累加的。虽然树的深度和高度一样,但是具体到树的某个节点,其深度和高度是不一样的。我的理解是:非根非叶结点的深度是从根节点数到它的,高度是从叶节点数到它的。