第二部分《C和指针》
第六章 指针
1. C语言中,值的类型是通过值的使用方法隐式地确定的。编译器能够保证值的声明和值的使用之间的关系是适当的,从而帮助我们确定值的类型。比如,如果使用的是整型算术指令,这个值就被解释为整数;如果使用的是浮点型算术指令,这个值就被解释为浮点数。由此得出的重要结论是:不能单一地通过检查一个值的位来判断它的类型,为了判断类型(以及它的值),必须观察程序中这个值的使用方式。
2. 声明一个指针变量并不会自动分内存。在对指针执行间接访问前,指针必须进行初始化并且是非NULL指针:或者使它指向现有内存,或者给它动态分配内存。对所有指针变量进行显式的初始化是种好做法。如果已知道指针将被初始化为什么地址,就把它初始化为该地址,否则就把它初始化为NULL。
3. C语言的指针算术运算只限于两种:指针 +/- 整数 和 指针 +/- 指针。
a. “指针 +/- 整数”:当一个指针和一个整数量执行算术运算时,整数在执行运算前始终会根据指针所指向类型的大小进行调整。比如对指向整数的指针加3,则增加到指针的值为12(3*4)。
b. “指针1 +/- 指针2”:只有当“指针1”和“指针2”指向同一数组时,该形式才合法。减法运算的值是两个指针在内存中的距离(以数组元素的长度为单位,不是以字节为单位)。
4. 对指针执行关系运算也是有限制的,可以用“>”、“<”、“>=”、“<=”,前提是他们都指向同一个数组中的元素。
5. ANSI标准允许指向数组元素的指针与指向数组元素最后一个元素后面那个内存位置的指针进行比较,但不允许与指向第一个元素之前的那内存位置的指针进行比较。虽然后者大多数编译器支持,但是应该尽量避免使用,因为标准并不保证它可行。
6. 越界指针和指向未知值的指针是两个常见的错误根源。当使用指针运算时,必须非常小心,确信运算的结果将指向有意义的东西。
7. 编程练习源码及出现问题总结:
练习6.3里main函数第一行原本是这么写的:
-
char *string = "Hello, Pointer!";
这样的话是字符串常量,不能修改所存内容,但是按照题目要求是反向排列数据,必须修改内容,所以这样是不行的。编译没错,运行报段错误。如下修改即可:
-
char string[] = "Hello, Pointer!";
第七章 函数
1. 使用原型最方便且最安全的方法是把原型置于一个单独的文件中,当其它源文件需要这个函数的原型时,就是用#include包含该文件。原型告诉编译器参数的数量和每个参数的类型及返回值类型,参数名不是必需的,但是加上参数名可以给函数调用者提供有用的信息。
2. C函数所有参数均以“传值调用”方式进行传递,这意味着函数将获得参数值的一份拷贝。这样,函数可以放心修改这份拷贝值,而不必担心修改调用程序传递给它的参数。数组名实际上是一个指针, 传递给函数的就是这个指针的一份拷贝。函数没办法判断数组参数的长度,所以如果函数需要这个值,必须作为参数显示地传递给函数。
3. C函数通过运行时堆栈支持递归函数的实现。当函数被调用时,它的变量的空间是创建于运行时堆栈上的。以前调用的函数的变量仍保留在堆栈上,但它们被新函数的变量所掩盖,因此是不能被访问的。所以与循环相比有一点不同就是,递归调用会保存一些信息,也就是保存在堆栈中的变量值,有时这些信息非常有用。递归函数调用将涉及一些运行时开销---参数必须压到堆栈中,为局部变量分配内存空间,寄存器的值必须保存等,当递归函数每次调用返回时,上述操作必须还原,恢复成原来的样子。迭代实现往往比递归实现效率更高,虽然代码可读性可能稍微差一些。当你使用递归方式实现一个函数之前,请先问问你自己使用递归带来的好处是否抵得上它的代价。而且你必须小心:这个代价可能比初看上去要大得多。
第八章 数组
1. 数组名的值是一个指针常量,也就是数组第一个元素的地址。注意,这个值是指针常量,而不是指针变量。只有在两种场合下,数组名并不用指针常量来表示---数组名作为sizeof操作符或单目操作符&的操作数时。
2. 除了优先级外,下标引用和间接访问完全相同。
3. 关于指针的效率:
当你根据某个固定数目的增量在一个数组中移动时,使用指针变量将比使用下标产生效率更高的代码。为了对下标表达式求值,编译器在程序中插入指令,取得方括号里的值,并把它与数组类型的长度(字节数,比如整型是四字节)想乘。对于固定长度的移动,与固定数字相乘的运算在编译时完成,在运行时所需指令少一些。
声明为寄存器变量的指针通常比位于静态内存和堆栈中的指针效率更高。
4. 作为函数参数的数组名与一般数组名的区别:
前者是指针变量,后者是指针常量。前者只分配指针内存,后者分配一个数组内存。
5. 静态和动态初始化:
存储于静态内存的数组只初始化一次,在程序开始运行之前。如果数组未被初始化,数组元素的初始值将会被自动设置为零。当这个文件载入内存准备运行时,初始化后的数组值和程序指令一样也被载入到内存中。因此,程序运行时,静态数组已经被初始化完成。
自动变量位于运行时堆栈中,如果自动变量的声明中给出了初始值,每次当执行流进入自动变量所在的作用域时,变量就会被一条隐式的赋值语句初始化。
当数组的初始化局部于一个函数(或者代码块)时,就应该仔细考虑下,在程序的执行流每次进入该函数(或代码块)时,每次都对数组进行重新初始化是不是值得。如果答案是否定的,就把数组声明为static,这样数组的初始化只需在程序开始前执行一次。
第十章 结构和联合
1. 数组元素与结构成员的区别:
a. 结构成员可以不同类型,数组元素同一类型;
b. 结构成员通过名字访问,数组元素通过下标访问;
c. 结构成员间可以有用于满足边界对齐的填充内存,数组元素间无间隙;
d. 向函数传递结构参数,是把整个结构的一份拷贝传给函数(低效的,不提倡这种用法,应该把指向结构的指针传递给函数!),而向函数传递数组,是把指向数组的指针的拷贝传递给函数。
2. 数组名与结构名的区别:
结构变量属于标量类型,可以像其它标量那样执行相同的操作,相同类型的结构变量相互之间可以赋值。数组名不是指针,但是它的值是指向数组第一个元素的指针。对于指向结构的指针px, *px的结果是整个结构。对于指向数组的指针py,*py的结果是数组第一个元素(对于一维数组)。
阅读(1640) | 评论(0) | 转发(0) |