1.C语言采用编译模式而不是解释模式,引入类型系统,每个变量在使用前必须先声明。

2.C语言排斥强类型,它允许程序员需要时可以在不同类型的对象间赋值。

3.C语言的基本数据类型直接与底层硬件对应。

4.表达式中的数组名可以看作是指针,但是数组和指针不是在任何情况下都是等效的。

5.register关键字:这个关键字能给编译器提供线索,就是程序中的哪些变量属于热门(经常被使用),这样就可以把它们存放到寄存器中。使用register关键字简化了编译器,却把包袱丢给了程序员。让编译器在使用各个变量时自动处理寄存器的分配工作显然比一经声明就把这类变量在生命期内始终保留在寄存器里要好。

6.C预处理所实现的功能:
字符串替换:(宏定义的常量)
头文件包含
通用代码模板的扩展:(宏定义的语句)

7.宏最好只用于命名常量,并为一些适当的结构提供简洁的记法。宏名应该大写,这样很容易与函数调用区分开来。

8.参数与原型不匹配:
编译如下代码:

1
2
3
4
5
void foo(const char **p) { }
int main(int argc,char **argv){
foo(argv);
return 0;
}

出现:
warning: passing argument 1 of ‘foo’ from incompatible pointer type [-Wincompatible-pointer-types]
note: expected ‘const char **‘ but argument is of type ‘char **‘
因为const char **与char **并不相容,在ANSI C标准第6.3.2.2节中讲述约束条件的小节中有这么一句话:

每个实参都应该具有自己的类型,这样它的值就可以赋值给与它所对应的形参类型的对象(该对象的类型不能含有限定符)

这就是说参数传递过程类似于赋值。要使上述的赋值形式合法,必须满足下列条件之一:
a.两个操作数都是指向有限定符或无限定符的相容类型的指针。
b.左边指针所指向的类型必须具有右边指针所指向类型的全部限定符。
正是因为条件b,使得下面的代码是合法的:

1
2
3
char *cp;
const char *ccp;
ccp = cp;

分析:
a.左操作数是一个指向有const限定符的char指针。
b.右操作数是一个指向没有限定符的char指针。
c.char类型与char类型是相容的,左操作数所指向的类型具有右操作数所指向类型的限定符(无),再加上自身的限定符(const)。
d.反过来赋值,编译器会警告:
warning: assignment discards ‘const’ qualifier from pointer target type [-Wdiscarded-qualifiers]

8.const float *类型并不是一个有限定符的类型——它的类型是“指向一个具有const限定符的float类型的指针”,也就是说const限定符是修饰指针所指向的类型,而不是指针本身。

9.关键字const并不能把变量变成常量。在一个符号前加上const限定符只是表示这个符号不能被赋值。也就是说它的值对于这个符号来说是只读的(但它并不能防止通过程序的内部或者外部的方法来修改这个值)。const最有用之处就是用它来限定函数的形参,这样该函数将不会修改实参指针所指向的数据,但其他的函数却可能会修改它。
const可以用在数据上,如:

1
2
3
4
const int limit = 10;
const int * limitp = &limit ;
int i = 27;
limitp = &i;

这段代码表示limitp是一个指向常量整型的指针。这个指针不能用于修改这个整型数,但是在任何时候,这个指针本身的值却可改变。这样,它就指向了不同的地址。const和*的组合通常只用于在数组形式的参数中模拟传值调用。它声称“我给你一个指向它的指针,但你不能修改它。”
这个约定类似于void *的用法,尽管在理论上void *可以用于任何情形,但通常被限制于把指针从一种类型转换为另一种类型。
改变const变量的值:

1
2
3
4
const int limit = 10;
int *limitp = &limit ;
*limitp = 20;
printf("%d",limit);

编译时警告:
int *limitp = &limit ;
warning: initialization discards ‘const’ qualifier from pointer target type [-Wdiscarded-qualifiers]
输出结果为:
20
而这样写:

1
2
3
4
const int limit = 10;
const int *limitp = &limit ;
*limitp = 20;
printf("%d",limit);

编译出错:
*limitp = 20;
error: assignment of read-only location ‘*limitp’

10.算术转换:

整型升级:

char,short int或者int型位段,包括它们的有符号或无符号类型,以及枚举类型,可以使用在需要int或unsigned int的表达式中。如果int可以完整表示源类型的所有值,那么该源类型的值就转换为int,否则转换为unsigned int。

寻常算术转换:

许多操作数类型为算术类型的双目运算符会引发转换,并以类似的方式产生结果类型。它的目的是产生一个普通类型,同时也是运算符结果的类型。
首先,如果其中一个操作数的类型是long double,那么另一个操作数也被转换为long double。
其次,如果其中一个操作数的类型是double,那么另一个操作数也被转换为double。
再次,其中一个操作数的类型是float,那么另一个操作数也被转换为float。
否则,两个操作数进行整型升级,执行下面的规则:
如果其中一个操作数的类型是unsigned long int,那么另一个操作数也被转换为unsigned long int。
其次,如果其中一个操作数的类型是long int,而另一个操作数的类型是unsigned int,如果long int能够完整表示unsigned int的值,那么unsigned int类型操作数被转换为long int,如果long int不能完整表示unsigned int的值,那么两个操作数都被转换为unsigned long int。
再次,如果其中一个操作数的类型是long int,那么另一个操作数也被转换为long int。
再再次,如果其中一个操作数的类型是unsigned int,那么另一个操作数也被转换为unsigned int。
如果以上情况都不属于,那么两个操作数都为int。
浮点操作数和浮点表达式的值可以用比类型本身所要求的更大的精度和更广的范围来表示,而它的类型并不因此改变。

大致意思是:

当执行算术运算时,操作数的类型如果不同,就会发生转换。数据类型一般朝着浮点精度更高,长度更长的方向转换,整型数如果转换为signed不会丢失信息,就转换为signed,否则转换为unsigned。

一个微妙的bug:

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>

int array[] = {23,34,12,17,204,99,16};
#define TOTAL_ELEMENTS (sizeof(array)/sizeof(array[0]))

int main(){
int d=-1,x=0;
if(d <= TOTAL_ELEMENTS -2)
x = array[d+1];
printf("%d",x);
return 0;
}

编译输出警告:
if(d <= TOTAL_ELEMENTS -2)
warning: comparison between signed and unsigned integer expressions [-Wsign-compare]
执行结果输出:
0
原因:
TOTAL_ELEMENTS所定义的值是unsigned int类型(因为sizeof()的返回类型是无符号数)。
if语句在signed int和unsigned int之间测试相等性,所以d被升级为unsigned int类型,-1转换成unsigned int的结果是一个非常大的正整数,导致表达式的值为假。
要修正这个问题,只要对TOTAL_ELEMENTS进行强制类型转换即可:

1
if(d <= (int)TOTAL_ELEMENTS -2)

此外,使用

1
#define TOTAL_ELEMENTS (sizeof(array)/sizeof(array[0]))

而不是

1
#define TOTAL_ELEMENTS (sizeof(array)/sizeof(int))

的好处在于前者可以在不修改#define语句的情况下改变数组的基本类型(比如,把int变成char)。

11.对无符号类型的建议:
a.尽量不要在代码中使用无符号类型,以免增加不必要的复杂性。尤其是,不要仅仅因为无符号数不存在负值而用它来表示数量。
b.尽量使用像int那样的有符号类型,这样在涉及升级混合类型的复杂细节时,不必担心边界情况(如-1被升级为非常大的正数)。
c.只有在使用位段和二进制掩码时,才可以用无符号数。应该在表达式中使用强制类型转换,使操作数均为有符号数或者无符号数,这样就不必由编译器来选择结果的类型。