1、
C99标准以前的C要求在一个代码块的开始处集中声明变量,遵循这条规则的好处是把所有的变量声明放在一起,会更易于了解程序多要做的事情。C99标准则允许把变量声明分散放置,这样的好处是可以在准备为变量赋值之前声明变量,这样就不会忘记给变量赋值。
2、
编译器将C语言源代码编译成机器语言代码,放在一个目标代码文件中,而后链接器将启动代码(不同擦操作系统的启动代码不同)、库代码(程序中所使用到的,在头文件中所声明的变量及函数在库文件中的源代码)和目标代码链接起来,生成可执行代码,即exe文件。
3、
printf()中的说明符(占位符)决定数据的显示方式,例如:在int为32位的系统中,无符号数3000000000和有符号数-129496296在内存中的表示方法是一样的,但是在printf()中用%u说明符会打印出3000000000,二用%d说明符则会打印出-129496296。
4、
对于float类型的变量,printf()中的说明符可以用%f或%lf,而scanf()中的说明符则只能用%f;
对于double类型的变量,printf()中的说明符可以用%f或%lf,而scanf()中的说明符则只能用%lf;
对于long double类型的变量,printf()中的说明符可以用%f或%lf,而scanf()中的说明符则只能用%lf。
5、
C语言中,把char类型的长度定义为一个字节,所以,在char类型长为16(一般为8),double类型长为64的系统中,sizeof将报告double类型有4字节长。
6、
printf()函数什么时候真正把输出传送给屏幕?首先,printf语句将输出传递给一个被称为缓冲区(buffer)的中介存储区。缓冲区中的内容再不断地被传递给屏幕。标准C规定在以下几种情况下将缓冲区内容传给屏幕:缓冲区满的时候、遇到换行符的时候以及需要输入的时候。将缓冲区内容传送给屏幕或文件成为刷新缓冲区。
7、
sizeof()函数以字节为单位给出数据的大小,strlen()函数以字符为单位给出字符串的长度。而一个字符只占用一个字节,那是不是这两个函数应用到同一个字符串时可以得到相同的结果呢?事实并非如此,例如,对于一个数组长度为40的char型数组,假设其中实际保存的字符个数为6,则用sizeof读取出的长度为40,而用strlen()函数读出的长度为6(数组的第7个单元放置空字符,即‘\0’,strlen()函数在这里停止计数)。再比如,对于字符串,strlen()函数读取的是双引号中最后一个非空白字符前的字符的个数(包含该非空白字符),包括其中的标点、空白字符等字符,而sizeof()函数读取的是双引号中的所有字符的个数,包括最后的空白字符。一般用strlen()函数获取一个字符串的长度(不包含标示终止的空字符)。
8、
C语言中规定,sizeof()返回size_t类型的值,这是一个无符整数类型,但它不是一个新类型,而是在C的头文件中通过typedef将其创建为unsigned int 或unsigned long的别名。
9、
空白字符与空字符的区别:空白字符是指在屏幕上不会显示出来的字符(如:空格、制表符tab、回车换行等),而空字符是指‘\0’,或者是字符的编码值为0的字符,它是非打印字符。C的字符串存储时通常以空字符结束,该字符的存在意味着数组的单元数必须至少比要存储的字符数多1。
10、
printf()的返回值为所打印的字符的数目,如果有输出错误,它会返回一个负数。scanf()函数返回成功读入的项目的个数,如果它没有读取任何项目(当它期望一个数字而你却键入一个非数字的字符串时就会发生这种情况),它会返回0,而当它检测到“文本结尾”,它会返回EOF(在stdio.h中,它被定义为-1)。
11、
printf()语句过长,要在多行放置一个打印语句的三种方法:
方法1、使用多个printf()语句。
方法2、用反斜线符号(/)和回车键的组合结束第一行,这样下一行必须从行的最左边开始。
方法3、采用字符串连接的方法,它是ANSI
C的新方法。如果在一个用双引号引起来的字符串后面跟有另一个用双引号引起来的字符串,而且二者之间仅用空白字符隔开,那么C会把该组合当做一个字符串处理。
12、
printf()和scanf()中的*修饰符:
一、在printf()中,假设你不想实现指定字段宽度,而是希望由程序来制定该值,那么可以在字段宽度部分使用*代替数字,但是这样的话,你也必须使用一个参数告诉函数字段宽度应该是什么,也就是说,如果转换说明符是%*d,那么参数列表中应该包含一个*的值和一个d的值。
二、在scanf()中,*则提供截然不同的服务,当把它放在%和说明符字母之间时,它会是函数跳过相应的输入项目。
13、
深入理解scanf()的输入:首先应明白空白字符对于sacnf()如何处理输入起着至关重要的作用,除了在%c模式下外,在读取输入时,scanf()会跳过空白字符直到第一个非空白的字符处,然后会一直读取字符,直到遇到空白字符,或遇到一个不符合正在读取的类型的字符,或达到指定的字段长度(由三者中最先满足的那一个终止输入)而停止读取,并将停止处的字符作为下一个输入字符(空白字符除外)。scanf()在%c模式下读取字符的时候,则不会跳过空白字符,而是直接读取第一个输入的字符。scanf()在%s模式下读取字符串的时候,由于遇到单词间的空格键而停止读取,故只能读取到第一个单词,而不是整个字符串语句。
14、C99标准为整数及浮点数除法规定了“趋零截尾”的规则,因此“/”计算的结果应该遵循趋近0并去掉小数点的原则,尤其在有负数的时候。也因此,对于负数的取模运算(%),得到的模的正负性与第一个操作数的正负性相同。
15、
字符数组与字符串:一般来说,字符数组就是元素都被赋予字符值的数组,如果字符数组包含了“\0”,那么字符数组的内容就构成一个字符串,其中空字符标志着字符串的结尾。
16、C99标准为逻辑运算符增加了可选择的拼写法,and可以代替&&,or可以代替||,not可以代替!
17、 在使用getchar()函数读取字符时,由于最终要输入回车符来发送输入,因此往往要用到fflush(stdin)来刷新输入缓冲区,或者使用如下语句来跳过输入行的剩余部分(包括最后输入的回车)。
while(getchar()!=’\n’)
continue;
同样,由于scanf()函数在读取非法字符时会停在那里,并把该字符放回输入,下次读取的时候依然从该字符读取,这样便永远不会超过这个非法字符,那么往往也需要上述两种方法来解决这个问题。
18、
对于非缓冲输入,一般在按下要求的键之后,会立即发送输入,而对于缓冲输入,一般当按下回车时,才会发送输入。
19、
缓冲分为两种:完全缓冲I/O和行缓冲I/O。对于完全缓冲输入来说,缓冲区满时被清空(内容被发送至目的地),这种类型的缓冲通常出现在文本输入中,缓冲区的大小取决于系统,512字节和4096字节是常见的值;对于行缓冲输入来说,遇到一个换行字符时将清空缓冲区。键盘输入是标准的行缓冲,因此按下回车键时将清空缓冲区。
20、
读取字符时scanf()与getchar()的区别:scanf()在遇到换行符时,会将其停留在输入队列中,下次读取时会跳过该换行符,而getchar()则不会跳过换行符。如果混合使用scanf()和getchar(),那么当调用getchar()之前scanf()恰好在输入中留下了一个换行符时,将会产生问题,运用刷新输入缓冲区或跳过输入行剩余部分等技巧可以解决这类问题。
21、
在大多数系统内部,指针由一个无符号整数表示,但这并不表示可以把指针看做是整数类型。ANSI C专门为指针提供了%p输出格式。
22、C保证为数组分配内存空间时,指向数组之后的第一个位置的指针是也合法的,但是对该位置上的值不做任何保证。,它可能被系统的其他程序占用,也可能是垃圾值。
23、
无论在何种情况下,int
*ar都表示ar是指向int的指针。形式int
ar[]也可以表示ar是执行int的指针,但只有在声明形式参量时才可以这样使用。另外在C语言中,两个表达式ar[i]和*ar(i+1)的意义是等价的,而且不管ar是一个数组名还是一个指针变量,这两个表达式都可以工作。然而只有当ar是一个指针变量时,才可以使用ar++这样的表达式。
24、
使用指针时一定要注意,不能对未初始化的指针取值。
25、const int *p
与int * const
p的不同是,前者定义指针p所指向的int值为常量,不可改变,而p的值是可以改变的,后者定义指针p的值为常量,即其指向不可改变,而*p的值是可以被改变的。
26、const指针(这种形式:const int
*p)不能赋给非const指针,但是非const指针赋给const指针是允许的,这样的赋值有一个前提:只能进行一层间接运算。
27、
函数是通过指针来处理多维数组的,在编译时,编译器会把数组符号转换为指针符号,编译器在转换时需要知道指针所指向的对象数据的大小。
28、
由于在处理二维数组函数时,数组的列只能被预置在函数内部,因此C99标准引入了变长数组(VLA)。这里的“变”不是指创建数组后,可以修改其大小,而是指其维数大小可以由变量指定。例如如下声明:
Int sum(int rows,int cols,int
ar[rows][cols]),这里前两个参量是ar数组的行数和列数。注意:参数列表中,rows和cols的声明需要早于对数组ar的声明。
当然我们也可以通过这样传递参数来解决以上问题,通过形如Int
sum(int **ar,int rows,int cols)的形式传递参数,只要在函数内部为arr数组分配动态内存即可。
29、
字符串常量属于静态存储类。静态存储是指如果在一个函数中使用字符串常量,即使多次调用了这个函数,该字符串在程序的整个运行中只存储一份。字符串的整个引号中的内容作为指向该字符串存储位置的指针。
30、char
*arr[4]与char
arr[4][10]的区别:前者表示的是一个指向char的指针的数组,其中存放4个地址,而后者是一个char数组的数组,其中存放4个完整的字符串数组。
31、
空指针(NULL)与空字符(\0)的区别:空指针是一个地址,而空字符是一个char类型的数据对象。数值上二者都可以用0表示,但是它们的概念不同。
32、gets()、puts()和fgets()、fputs():在读取字符串时,gets()会把最后的换行符舍去,并为未字符串加上空字符,但是不检查预留存储区是否能容纳实际输入的数据,fgets()会指定最大读入的字符数,但是会读取换行符。puts()显示字符串时会自动在其后添加一个换行符,另外,puts()在遇到空字符时才会停下来,所以应该保证空字符的存在,fputs()并不为输出自动添加换行符。gets()和puts()一起使用,它们一般面向键盘屏幕输入输出,fgets()和fputs()一起使用,它们一般面向文件输入输出。
33、
传统上,具有代码块作用域的变量都必须在代码块的开始处声明,C99标准放宽了这一规则,允许在一个代码块的任何位置声明变量,一个新的可能是变量声明可以出现在for循环的控制部分。
34、 一个C变量具有下列链接之一:外部链接、内部链接、空连接。具有代码块作用域或者函数原型作用域的变量具有空链接,意味着它们是由其定义所在的代码块或者函数原型所私有的,具有文件作用域的变量可能有内部或外部链接,这要取决于在外部定义中是否使用了static说明符,若使用了,则具有内部链接,若没有使用,则具有外部链接。一个具有外部链接的变量可以在一个多文件程序的任何地方使用,一个具有内部链接的变量可以在一个文件的任何地方使用。
35、 一个C变量有以下两种存储时期之一:静态存储时期和自动存储时期。如果一个变量具有静态存储时期,它在程序执行期间将一直存在,具有文件作用域的变量具有静态存储时期,具有代码块作用域的变量一般情况下具有自动存储时期,在程序进入定义这些变量的代码块时,将为这些变量分配内存,当退出该代码块时,分配的内存将被释放。另外,static关键字表明链接类型,并非存储时期
36、C使用作用域、链接和存储时期等定义了5种存储类:自动变量、寄存器变量、具有代码块作用域的静态变量、具有外部链接的静态变量、具有内部链接的静态变量。
一、自动变量,它具有自动存储时期、代码块作用域和空链接,可以用auto关键字声明,也可以不声明,自动变量需要显式地初始化,而不能自动初始化为某些默认值。
二、寄存器变量,多是存放在一个寄存器而非内存中,所以无法获得其地址,它具有自动存储时期、代码块作用域和空链接,通过register说明符声明,如未经初始化,它的值是不定的。
三、具有代码块作用域的静态变量,它具有代码块作用域、空连接,却有静态存储时期,也就是说,当包含这些变量的函数完成工作时,他们并不消失,且对它们的值的改变会一直保存下来,其内存空间当程序结束时才会被释放,在代码块内通过static关键字声明。另外,如果不显式地对该静态变量初始化,他们将被初始化为默认值0,仅在编译时被初始化一次。
四、具有外部链接的静态变量,它具有文本作用域、外部链接和静态存储时期,这一类的变量被称为外部变量。把变量的定义声明放在所有函数之外,即创建了一个外部变量。可以在使用外部变量的函数中通过使用extern关键字再次声明它,如果变量是在别的文件中定义的,使用extern来声明该变量就是必须的。如果不对外部变量显式地初始化,它将自动被赋初值0,这一原则也适用于外部定义的数组元素,另外,只可以用常量表达式来初始化文件作用域变量,且一个外部变量只仅在编译时进行一次初始化,而且一定是在变量被定义时进行。
五、具有内部链接的静态变量,它具有文本作用域、内部链接和静态存储时期,通过使用static说明符在所有函数外部进行定义,只能在本文件中使用,仅在编译时初始化一次,若未明确初始化,将被初始化为0。
37、alloca向栈申请内存,因此无需释放,malloc、calloc等申请的内存均在堆中,区别是,malloc申请的内存中的内容没有被初始化,而calloc申请的内存中的内容被初始化为0或空指针(无法保证),浮点型的0(某些系统中)等,realloc对给定的指针所指的空间进行扩大或缩小。
38、getc()和putc()在输入输出字符时需要传入一个文件指针做外参数,从而确定输入输出的文件名称。getc(stdin)和getchar()等价,在源代码中,一般后者通过前者实现,putc(ch,stdout)和putchar(ch)等价,在源代码中,一般后者通过前者实现。
39、
printf()将打印输入到屏幕,sprintf()将打印输入到指定的字符串中,它的第一个参数为目标字符串的首地址,fprintf()则将打印输入到指定的文件中,它的第一个参数是一个文件指针,但该参数为stdout时,与printf()等价。
40、和数组不同,一个结构体的名字不是该结构的地址,必须使用&运算符。另外,现在的C允许把一个结构赋值给另一个结构,即使该结构中有一个成员是数组也能完成赋值,但是对数组却不能这样做。
41、如果必须写一个与结构有关的函数,应该用结构体指针作为参数还是用结构体作为参数并返回值呢?前者执行效率高,但是缺少对数据的保护,后者对数据有保护,但是执行效率慢。通常,程序员为了追求效率而是用结构指针作为函数参数,当需要保护数据、防止意外改变数据时,对指针使用const限定词。而传递结构体值是处理小型结构最常用的方法。
42、如果在结构体中要存储字符串,使用字符数组好,还是用指向字符的指针比较好呢?要使用字符数组来存储。因为字符数组中的字符串变量是存储在结构体中的,而指向字符的指针仅在结构体中存储字符串的地址,字符串则存储在编译器存储字符串常量的任何地方,如果对该指针未初始化而直接用scanf()输入的话,该字符串将可能被放在任何地方,这是很危险的,当然如果使用malloc()进行动态分配的话,也可以使用指向字符的指针来存储字符串。
43、联合(union)是一个能在同一个存储空间里(但不同时)存储不同类型数据的数据类型。联合的初始化有3中选择:可以把一个联合初始化为同类型的另一个联合;可以初始化联合的第一个元素;或者,按照C99标准,可以使用一个指定初始化项目。不能在定义联合体的时候对它初始化,不能用联合体变量名作为函数参数。
44、枚举类型使用enum关键字声明代表整数常量的符号名称,enum常量是int型的,因此在使用int类型的任何地方都可以使用它,但不能将一个整数直接赋给一个枚举变量,应先进行强制类型转化才能赋值。enum中的第一个常量的默认值是0,依次递增,由于是常量,因此不能对它们赋值,如果要指定值,只能在声明枚举类时指定枚举元素的值。另外:C的某些枚举属性不能延至C++中,例如,C允许枚举变量使用运算符++,而C++不允许,要想在C++下也能工作,就要把用enum关键字声明的变量改用int声明。
45、预处理器不会进行计算,只是按照指令进行文本替换操作,在编译时才会进行计算。在宏中不要使用增减运算符,如++。
46、atexit()和exit():atexit()使用函数指针作为参数,用来注册函数(应该是不接手机任何参数的void函数),将这些函数放入注册列表中(ANSI C保证这个列表至少可放置32个函数),最后,调用exit()函数时,按照先进后出的顺序执行这些函数。exit()函数在执行了atexit()指定的函数后,精做一些自身清理工作,最后终止程序。注意:main()终止时会隐式地调用exit(),而且最main()以外的函数中使用exit()也会终止程序。