C和指针:第三,四章
第三章
1. 变量的三个属性——作用域,链接属性和存储类型。这三个属性决定了一个变量的“可视性”(也就是可以在什么地方使用) 和“生命周期”(它的值可以保持多久)。
2. C语言4种基本类型:整形,浮点型,指针和聚合类型。所有其他类型都是从这4种基本类型的某种组合派生而来。
3. 整形包括:字符,短整形,整形和长整形,它们都分为有符号(signed)和无符合(unsigned)两种。
4. 编译器的limits.h头文件说明了各种不同整形类型的特点和取值范围。
5. 缺省的char要么是signed char, 要么是unsigned char,这取决于编译器。只有当程序使用char变量值位于signed char和unsigned char之间,程序才是可移植的。显式的将char 变量声明为signed或unsigned,可提高程序的可移植性。但有些编译器对signed或unsigned处理起来会更快,如果硬将类型转换,效率可能受损。
6. 整数的八进制以0开头,十六进制以0x开头。当一个字面值用于某个特定环境中,将它写成十六进制或八进制值更为合适,因为这种写法更清晰地显示了这个值的特殊本质。
7. 使用字符常量所产生的总是正确的值,所以它能提高程序的可移植性。
8. 枚举(enumerated)类型就是指它的值为符号常量,而不是字面值的类型。虽然看上去是符号常量,但实际是以整形存储的。
9. 浮点数字面值在缺省情况下是double类型,除非它的后面跟一个L或 l 表示它是一个long double类型,或是跟一个F或f表示它是一个float类型的值。
10. 指针变量是一个存储内存地址的变量。
11. 当一个字符串常量出现于一个表达式中,表达式所使用的值就是这些字符所存储的地址,而不是这些字符本身。因此,你可以把字符串常量赋给一个“指向字符的指针”,但你不能把字符串常量赋值给一个字符数组。
12. 一声明语句要声明几个指针变量应写为: int *a, *b, *c; 而不是 int *a, b, c;
13. 函数如果不显式的声明返回值的类型,它默认返回整形。
14. 指针常量分两种,一是指针变量和它指向的实体。如:
int const *p; // 可以修改指针的指向,但不能修改指向的值
int * const p; // 可以修改指向的值,但不能修改指针的指向
int const * const p; // 不能修改指针的指向,也不能修改指针指向的值
15. 编译器有4种不同类型的作用域——文件作用域,函数作用域,代码块作用域和原型作用域。
1). 代码块作用域,可被当前代码块中的所有语句访问。当代码块处理嵌套时,声明于内层代码块的标识符的作用域只能被内层代码块内的语句访问。如果内层代码块的一个标识符与外层代码块的一个标识符同名,对于内层而言外层的那个同名标识符将被隐藏,即外层的同名标识符无法在内层代码块中被访问。
我们应当尽量避免在嵌套的代码块中定义相同的变量名,并没有很好的理由使用这捉技巧,它只会在程序的调试或维护期间引起混乱。
2). 文件作用域,任何代码块之外声明的标识符都具有文件作用域,这些标识符从它们声明之处起直到它所在的源文件结尾处都是可以访问的。文件中定义的函数名就具有文件作用域,因为函数本身并不属于任何代码块。
3). 原型作用域只适用于在函数原型中声明的参数名。在函数原型中(与函数的定义不同),参数的名字并非必需,但如果出现参数名,你可以随意给它们取任何名字,不必与函数定义中的形参名匹配,也不必与函数实际调用时所传递的实参匹配。
4). 函数作用域,它只适用于语句标签,即goto语句(尽量不用goto语句)。它的作用域
16. 链接属性有三种:外部(external),内部(internal)和无(none)。没有链接属性的标识符总是被当作单独的个体;内部链接属性在同个源文件内的所有声明中都指同一个实体,但位于不同源文件的多个声明则分属不同的实体;外部链接属性标识符不论声明多少次,位于几个源文件都表示同一个实体。如:
typedef char *a;
int b;
int c (int d)
{
int e;
int f (int g); // 是函数调用,不是函数定义,因为不能看作是与函数c 的嵌套
}
缺省情况下,以上标识符中,b, c, f的链接属性为external,其余标识符则为none。因此,如果另一个源文件也包含了标识符b的类似声明并调用函数b,它们实际上访问的都是这个源文件所定义的实体。f的链接之所是external是因为它是个函数名,在这个源文件中调用函数f,实际上将链接到其他源文件所定义的函数。
17. 关键字extern 和 static 可以修改标识符的链接属性,如果某个变量具有external链接属性,在前面加上static关键字可以将链接属性改为internal。如:
static int b; // b将成为这个源文件所私有,如果在其它源文件中也有一个b变量,那么所引用的是另一个不同的变量
还可以把函数也声明为static,如:
static int c (int d) // 可防止被其他源文件调用
static只对缺少链接属性为external的声明才有改变链接属性的效果。
当extern关键字用于源文件中一个标识符的第一次声明时,它指定该标识符具有external链接属性,但如果第一次声明指定了链接属性,第二次的声明并不会改变该标识符的链接属性。如:
static int i;
int func()
{
int j;
extern int k; // 让人更明白i 的意图
extern int i; // i 的链接属性并没有改变,仍是internal
}
18. 变量的存储类型决定变量何时创建,何时销毁以及它的值保持多久。有三个地方可以用于存储变量:普通内存,运行时堆栈,硬件寄存器。变量缺省存储类型取决于它的声明位置。
1). 凡是在任何代码块之外的声明变量总是存储于静态内存中,也就是不属于堆栈的内存,这类变量称为静态(static)变量。这类变量,你无法为它们指定其他存储类型,它在程序运行之前创建,在程序整个执行期间始终存在。它始终保原先的值,除非你给它赋一个不同的值或程序结束。
2). 在代码块内部声明的变量缺省类型是自动的(automatic),也就是说它存储于堆栈中,称为自动(auto)变量。自动变量在程序执行到声明自动变量的代码块时,自动变量才被创建,当程序的执行流离开该代码块时,自动销毁。每次调用代码块时,变量都将重新创建。而每次的内存地址和原先的地址可能相同,也可能不同。
3). 对于在代码块内部声明的变量,可以加上关键字static,使它的存储类型从自动变为静态。注意修改变量的存储类型,并不表示修改变量的作用域,它仍然只能在代码块内按名字访问。
4). 函数的形参不能声明为静态,因为实参总是在堆栈中传递给函数,用于支持递归。
5). register可用于自动变量的声明,提示编译器该变量应存储于机器的硬件寄存器而不是内存中,这类变量称为寄存器变量。你应把使用频率最高的变量声明为寄存器变量。如果把指针声明为寄存器变量,程序的效率将能得到提高,尤其是那些频繁执行间接访问操作的指针。寄存器变量的创建和销毁和自动变量相同。此外,寄存器变量不一定能申请成功,这会有编译器及实际CPU中寄存器的使用情况有关。
19. 初始化:自动变量和静态变量的初始化存在一个重要的差别:静态变量的初始化中,可以把执行程序文件想要初始化的值放在当程序执行时变量将会使用的位置。如果不显式地指定其初始值,默认将初始化为0。自动变量没有缺省的初始值,如果变量不初始化值,它的值总是垃圾。
20. static 在不同的上下文件环境中,具有不同的意思,主要有两个作用:一是,改变标识符的链接属性;二是,改变变量的存储类型。
21. 作用域,链接属性和存储类型总结表:
变量类型 | 声明的位置 | 是否存于堆栈 | 作用域 | 如果声明为static |
全局 | 所有代码块之外 | 否 | 从声明处到文件尾 | 不允许从其他源文件访问 |
局部 | 代码块起始处 | 是 | 整个代码块 | 变量不存储于堆栈中,它的值在程序整个执行期一直保持 |
形式参数 | 函数头部 | 是 | 整个函数 | 不允许 |
第4章
1. C没有专门的“赋值语句”,赋值是通过表达式语句进行的。如:
x = y + 3;
ch = getchar();
以上实际上是表达式语句,而不是赋值语句。
2. 代码块:
{
declarations
statements
}
代码块允许你在语法要求只出现一条语句的地方使用多条语句。
3. if 语句
if (expression)
statement
else
statement
expression 可以是任何能产生整形结果的表达式——0表示假,非0表示真。
当if嵌套时,就会出现“悬空的else”问题,解决方法可以用花括号来控制对应关系。
4. while 语句
while (expression)
statement
1). break 和 continue
break语句执行后,程序执行循环结束后的那条语句。continue 语句执行后,循环体内的剩余语句不再执行,而是立即开始下轮的循环,决定是否继续执行循环。
2). 有时,while语句在条件表达式中就可以完成整个语句的任务,于是循环体就无事可做,在这种情况下,循环体可以用空语句来表示。单独用一行来表示一条空语句是比较好的做法,如:
while ((ch = getchar()) != EOF && ch != ‘\n’)
; // 清楚的显示了循环体是空的
5. for 语句
for (expression1; expression2; expression3)
statement // 是循环体
expression1 是初始化部分,只在循环开始的第一次执行;expression2是条件判断部分,循环体每次执行前都要执行一次;expression3是步长,它在循环体每次执行完毕,在条件表达式执行之前执行。这三个表达式都可选。
for 语句中的break和continue作用与在while语句中的作用一样。
如果将for 语句改成while 语句是:
expression1;
while (expression2)
{
statement
expression3;
}
for循环有一个风格上的优势,它把用于操作循环的表达式收集在一起,放在同一个地点,便于寻找。当循环体比较庞大时,这个优点更为突出。
6. do 语句
do
statement
while (expression);
如果你的循环体至少要执行一次,就选择do。不要忘记while 条件表达式后面的分号。
7. switch 语句
switch (expression)
statement
尽管switch语句体内可以使用单一的语句也合法,但这样做显得毫无意义,实际使用的switch语句会是:
switch (expression)
{
statement-list
}
1). 97% 的case标签后都有一条break语句,除非你特意不加上去。
2). case标签只能对整形数值判断,不能对浮点数判断。
3). continue 语句在switch语句中,没有任何效果。
4). C没有任何的简便方法可以指定某个范围值,所以该范围内的每个值都必须以单独的case标签给出。
5). 每个switch语句中只能出现一条default子名,它可以出现在语句列表的任何位置。
8. goto 语句
标签符:
goto 标签符;
要使用goto语句,你必须在希望跳转的语句前加上语句标签,语句标签就是标识符后加个冒号。包含这些标签的goto语句可以出现在同一个函数中的任何位置。
goto的一个应用是跳出多层嵌套的循环。