C和指针这本经典著作,最近开始学习,然后想与大家共享下,希望共同进步!网络的力量是强大的,会碰撞出思想的火花来!
第一章 快速上手
1、要从逻辑上删除一段C代码,更好的办法是使用#if指令:
#if 0
statements
#endif
在 #if 和 #endif之间的程序段就可以有效地从程序中去除,即使这段代码之间原先存在注释也无妨
2、关于scanf返回值的问题
scanf函数,与printf函数一样,都被定义在stdio.h里,因此在使用scanf函数时要加上#include<stdio.h>。它是格式输入函数,即按用户指定的格式从键盘上把数据输入到指定的变量之中,其关键字最末一个字母f即为“格式”(format)之意。
scanf()函数返回成功赋值的数据项数,读到文件末尾出错时则返回EOF。
3、当数组名作为实参时,传给函数的实际上是一个指向数组起始位置的指针,也就是数组在内存中的地址。
4、为什么ch被声明为整型,但是又用来读取字符的原因?
例如:
int ch;
while( (ch = getchar()) != EOF && ch != '\n');
EOF是一个整型值,它的位数比字符类型要多,把ch声明为整型可以防止从输入读取的字符意外地被解释为EOF。但同时,这也意味着接收字符的ch必须足够大,足以容纳EOF,这就是ch使用整型值的原因。尽管char类型变量的目的是为了让它们容纳字符型的值,但字符在本质上是小整型值
5、数组做参数的时候是以引用的方式传递的,即地址传递。而标量和常量都是传值调用,被调用的函数无法修改调用函数以传值形式传递给它的参数,然而当被调用函数修改数组参数的其中一个元素时,调用函数所传递的数组就会被实际修改。
当数组名作为实参时,传给函数的实际上是一个指向数组起始位置的指针,也就是数组在内存中的地址。正因为实际传递的是一个指针而不是一份数组的拷贝,才使数组名作为参数时具备了传址调用的语义。函数可以按照操纵指针的方式来操纵实参,也可以像使用数组名一样用下标来引用数组的元素。
但是,由于它的传址调用语义,如果函数修改了形参数组的元素,它实际上将修改实参数组的对应元素。
6、字符串是以一串NUL字节结尾的字符。NUL作为字符串终止符,它本身并不被看作字符串的一部分。字符串常量就是源程序中被双引号括起来的一串字符。
7、使用scanf函数应该注意:使用所有格式码(除了%c之外)时,输入值之前的空白(空格、制表符、换行符等)会被跳过,值后面的空白表示该值的结束,因此,用%s格式码输入字符串时,中间不能包含空白。
8、编译器通常不对数组下标的有效性进行检查。(是因为下标引用可以作为任意的指针,而不仅仅是数组名)
9、while( gets( input ) != NULL){}
你认为这段代码可能会出现什么问题?
因为gets没有指定输入字符的大小,会发生内存越界,堆栈溢出
第二章 基本概念
1、ANSI C的任何一种实现中,存在两种不同的环境:
第一种是翻译环境:在这个环境里,源代码被转换为可执行的机器指令
第二种是执行环境:用于实际执行代码
两种环境不必在同一台机器上
交叉编译器:就是在一台机器上运行,但它所生成的可执行代码运行于不同类型的机器上。
2、翻译
翻译有几个步骤组成:
首先,组成一个程序的若干个源文件通过编译过程分别转换为目标代码;
然后,各个目标文件由链接器捆绑在一起,形成一个单一而完整的可执行程序;
链接器同时也会引入标准C函数库中任何被该程序所用到的函数,而且它也可以搜索程序个人的程序库,将其中需要使用的函数也链接到程序中。
3、编译
首先由预处理器处理,这个阶段,预处理器在源代码上执行一些文本操作。
然后,源代码经过解析,判断它的语句的意思。第2个阶段是产生绝大多数错误和警告的信息的地方。随后,产生目标代码。
目标代码是机器指令的初步形式,用于实现程序的语句。如果我们在编译程序的命令行中加入进行优化的选项,优化器就会对目标代码进一步进行处理,使他的效率更高
4、编译并链接一个完全包含于一个源文件的C程序,中间会产生*.o的目标文件,但它在链接过程完成后会被删除。
5、编译并链接几个C文件,当源文件超过一个时,目标文件便不会删除,这就允许你对程序进行修改后,只对那些进行改动的源文件进行重新编译。
在UNIX系统中,C编译器被称为cc,
cc program.c这个命令会产生一个称为a.out的可执行程序,中间会产生program.o的目标程序,但是链接完成后就被删除
cc main.c sort.c lookup.c----->cc -o name main.o sort.o lookup.o意思是可以是链接器把可执行程序保存在name文件中,而不是a.out
6、执行
首先、程序必须载入到内存中,在宿主环境中,这个任务由操作系统完成,那些不是存储在堆栈中的尚未初始化的变量将在这个时候得到 初始值。在独立环境中,程序的载入必须由手工安排,也可能是通过把可执行代码置入只读内存中来完成。
然后,程序执行便开始。通常一个小型的启动程序与程序链接在一起,它负责处理一系列日常事务,如收集命令行参数以便使程序能够访问它们。接着,便调用main函数
然后,便开始执行程序代码;程序将使用一个运行时堆栈,它用于存储函数的局部变量和返回地址;程序同时也可以使用静态内存,存储静态内存中的变量在程序的整个执行过程中将一直保留他们的值。
最后一个阶段:程序的终止;正常终止就是main函数返回;或者是用户终止执行;或者是执行过程中出现错误而自行中断。
7、标识符的长度没有限制,但是标准允许编译器忽略第31个字符以后的字符。
虽然一个源文件可以包含超过一个的函数,但是每个函数都必须完整的出现在同一个源文件中。
绝大部分注释都是成块出现的,这样他们从视觉上在代码中很突出,读者就可以很容易找到和跳过他们。
总结:
一个C程序的源代码保存在一个或多个源文件中,但是一个函数只能完整的出现在同一个源文件中。把相关的函数放在同一个文件内是一种好策略。每个源文件都分别编译,产生对应的目标文件。然后,目标文件被链接到一起,形成可执行程序。编译和最终运行程序的机器有可能相同,有可能不同。
程序必须载入到内存中才能执行。在宿主式环境中,这个任务由操作系统完成。在自由式环境中,程序常常永久存储在ROM中;经过初始化的静态变量在程序执行前能获得他们的值。你的程序执行的起点是main函数。绝大多数环境使用堆栈来存储局部变量和其他数据。