c和指针

来源:互联网 发布:淘宝代卖要多少钱 编辑:程序博客网 时间:2024/06/05 22:41

1.      要从逻辑上删除一段C代码,更好的方法是使用#if 指令。

2.      预处理指令:预处理器读入源代码,根据预处理指令对其进行修改,然后把修改过的源代码递交给编译器。

3.      所有传递给函数的参数都是按值传递的。地址传递也相当值传递,因为地址本身也可以作为一个特殊的“值”,所以地址传递也是一种特殊的值传递。只是为了强调其特殊性,故称之为“地址传递”。

4.      NUL 是ASCII字符集中’\0’字符的名字,它的字节模式为全0。              

NULL指一个其值为0的指针。它们都是整型值。其值也相同,所以他们可以互换使用。然而,还是应当使用适当的常量,因为他能告诉阅读程序的人不仅使用0这个值,而且告诉他们使用这个值的目的。

5.      符号NULL在头文件stdio.h中定义,另一方面,并不存在预定义的符号NUL,所以如果你想使用它而不是字符常量’\0’,你必须自行定义。

6.      在使用形参时,如果该参数不能被修改,尽量使用const修饰,这可以很明显的显示这个意图。

7.      一个C程序的源代码可以保存在一个或多个源文件中,但一个函数只能完整地出现在同一个源文件中,把相关的函数放在同一个文件内是好的策略。

8.      缺省的int究竟是16位还是32位,或者是其他值,则由编译器决定。通常这个选择的缺省值是这个机器最为自然的(高效)的位数。

9.      当可移植问题比较严重时,字符是否为有符号就会带来两难的境地,最佳妥协方案就是把存储于char型变量限制在singed char 和unsigned char的交集内,这可以获得最大程度的可移植性,同时又不牺牲效率。并且,只有当char变量显示声明为signed或unsigned时,才对它执行算术运算。

10.  十进制整型字面值可能是int、long、或unsigned long。在缺省情况下,它是最短类型但能完整容纳这个值。

11.  C标准要求float 类型至少要能精确表示到小数点后6位,并且整数部分的表示范围至少要达到10^-37 – 10^+37。float一般是 32位的。C 标准规定double类型的整数部分的最小表示范围和 float 一样,都是 10^-37到 10^+37,但是它要求double 类型的小数部分至少要能精确到小数点后 10 位。double通常是 64位的。

12.  浮点数字面值在缺省情况之下都是double 类型的,除非它的后面跟一个L或l表示它是long double 类型的值,或者跟一个F或f表示它是一个float类型的值。

13.  signed 关键字一般只用于char类型,因为其他整型类型在缺省情况下都是有符号的。

14.  C语言编译器一般并不检查程序对数组下标的引用是否在数组的合法范围内。如果下标值是从那些已知的正确的值计算而来,那么就无需检查它的值,如果一个用作下标的值是根据某种方法从用户输入的数据产生而来的,那么在使用它之前必须进行检测,确保他们位于有效的范围之内。

15.  int const  *p:一个指向整型常量的指针,你可以修改指针的值,但你不能修改它所指向的值;

16.  int *const  p :一个指向整型常量的指针。此时指针是常量,它的值无法修改,但你可以修改它所指向的整型的值。

17.  const常量只能用于允许使用变量的地方。

18.  有三个地方可以用于存储变量:普通内存、运行时堆栈、硬件寄存器。凡是在任何代码块之外声明的变量总是存储于静态内存中,也就是不属于堆栈的内存,这类变量称为静态变量。在代码内部声明的变量的缺省存储类型是自动的,也就是说它存储于堆栈中,称为自动变量。但是如果给在代码块内部声明的变量加上关键字static,可以使他的存储类型从自动变为静态。

19.  修改变量的存储类型并不能修改该变量的作用域。函数的形参不能修饰为静态,因为实参总是在堆栈中传递给函数,用于支持递归。

20.  关键字register可以用于自动变量的声明,提示它们应该存储于机器的硬件存储器而不是内存中,这类变量称为寄存器变量。

21.  除非你对自动变量进行显式初始化,否则当自动变量创建时,他们的值总是垃圾。

22.  如果整除运算的任意一方为负数,运算的结果由编译器定义的。

标准函数库的算术运算头文件中的div函数:

div_t  div (intnumerator,int denominator);

div函数把它的第二个参数除以第一个参数,产生商和余数,用一个div_t结构返回。这个结构包含下面两个字段:

int  quot;  //商

intr  rem;  //余数

但这两个字段并不一定以这个顺序出现。如果不能整除,商将是所有小雨代数商的整数中最靠近它的那个整数。注意‘/’操作符的除法运算结果并未精确定义。当/操作符的任何一个操作数为负而不能整除时,到底商是最大的那个小于等于代数商的整数还是最小的那个大于等于代数商的整数,这取决于编译器。

23.  标准说明无符号值执行的所有移位操作都是逻辑移位,但对于有符号值,到底采用逻辑移位还是算术移位取决于编译器。所以有符号的移右位操作程序是不可移植的。

24.  a<< -5:类似于这类的移位行为是未定义的,所以它的值由编译器决定。然而很少有编译器设计者会清楚的说明如果发生这类情况将会怎样,所以他的结果是不可测的。

25.  ++ 或 – 操作符要求只能作用于一个左值。

26.  前缀形式的++:操作数的值被增加,表达式的值就是操作数增加后的值;

后缀形式的++:操作数的值仍被增加,但表达式的值是操作数增加前的值。

27.  前缀和后缀形式的增值操作符都复制一份变量值的拷贝。用于周围表达式的值正是这份拷贝。前缀操作符在进行复制之前增加变量得值,后缀操作符在进行复制之后才增加变量的值。

28.  关系操作符:>、<</SPAN>、>=、<=、!=、==    :这些操作符产生的结果都是一个整型值,而不是布尔值。如果两端的操作数符合操作符指定的关系,表达式的结果是1,如果不符合,表达式的结果是0.

29.  &&操作符的做操作数总是首先进行求值,如果它的值为真,然后就对又操作数进行求值。如果左操作数为假,那么右操作数便不再进行求值。

30.  当float型值转换为整型值时,小数部分被舍弃(不是四舍五入)。如果浮点数的值过于庞大,无法容纳于整型值中,那么其结果将是未定义的。

31.  左值意味着一个位置,而右值意味着一个值。

32.  两个相邻的操作符的执行顺序油它们的优先级决定,如果它们的优先级相同,他们的执行顺序由它们的结合性决定。除此之外,编译器可以自由决定使用任何顺序对表达式进行求值,只要他不违背逗号,&&,|| 和 ?: 操作符的限制。

33.  不能简单地通过观察一个值的‘位’来判断它的类型。为了判断值得类型(以及它的值),你必须观察程序中这个值的使用方式。

即,不能通过一个值每个位中0和1的排列来判断一个值得类型及其值。

比如:01100111011011000110111101100010 ,可以认为它是1个32位整数(1735159650),可以认为它是2个16位整数(26476和28514),可以认为它是浮点数(1.116533*10^24),也可以它是4个字符(glob)。

该例选自《C与指针》6.2小节,值和类型。

34.  对指针执行加法或减法运算之后如果结果是指针所指的位置在数组第一个元素的前面或在数组最后一个元素的后面,那么其效果就是未定义的。让指针指向数组最后一个元素后面的位置是合法的,但对这个指针执行间接访问可能会失败。

35.  只有当两个指针都指向同一个数组中的元素时,才允许从一个指针减去另一个指针,否则其结果未定义。

36.  标准允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针进行比较,但不允许与指向数组第1个元素之前的那个内存位置的指针进行比较。

37.  声明一个指针变量并不会自动分配任何内存。对指针进行间接访问之前,指针必须进行初始化:或者使它指向现有内存,或者给它分配动态内存。对未初始化的指针变量执行间接访问操作是非法的。

38.  NULL指针就是不指向任何东西的指针。它可以赋值给一个指针,用于表示这个指针并不指向任何值。对NULL指针执行间接访问操作的后果因编译器而异,两个常见的后果分别是返回内存位值零的值以及终止程序。

39.  函数的定义就是函数体的实现。函数体就是一个代码块,它在函数被调用时执行。

40.  当程序调用一个无法见到返回值类型的函数时,编译器便认为该函数返回一个整数值。对于那些并不返回整型值的函数,这种认定可能会引起错误。

41.  数组名相当于一个指针,但不是指针,它的类型是“指向元素类型的指针”。这个值是指针常量,而不是指针变量。在两个场合下,数组名并不用指针常量来表示:当数组名作为sizeof操作符或单目操作符&的操作数时。Sizeof返回整个数组的长度,而不是指向数组的指针的长度。取一个数组名的地址所产生的是一个指向数组的指针,而不是一个指向某个指针常量值的指针。

42.  C的数组的下表引用和间接访问表达式是一样的。

int array[10];

int *ap = array+2;

ap[-1]:ap指向第3个元素(下标值为2),所以使用偏移量-1则得到它的前一个元素,也就是array[1]。

43.  假定指针和下标这两种方法都是正确的,下标绝不会比指针更有效率,但指针有时会比下表更有效率。

44.  但是,值得记住的是,效率并不是唯一的因素,通常代码的简洁性更为重要。

45.  自动变量的位置位于堆栈中,则执行流每次进入它们所在的代码块时,这类变量每次所处的内存位置可能并不相同,所以自动变量在缺省情况下是为初始化的。如果自动变量的声明中给出了初始值,每次当执行流进入自动变量声明所在的作用域时,变量就被一条隐式的赋值语句初始化。这条隐式的赋值语句和普通的赋值语句一样需要时间和空间来执行。

46.  如果数组为自动变量,初始化列表中可能有很多值,这就可能产生许多条赋值语句,对于那些庞大的数组,它的初始化时间及空间的消耗都是很可观的。如果这不是必须的,就把数组声明为static,这样数组的初始化只需在程序开始前执行一次。

47.  下标引用的优先级高于间接访问。

48.  只要有可能,函数的指针形参都应该声明为const。

49.  strlen的结果是个无符号数。

50.  strcmp比较的结果如果想等则为0,不等的话,标准并没有说明一个具体值。它只是说明第一个字符串大于第二个字符串就返回一个大于零的值,如果第一个字符串小于第二个字符串就返回一个小于零的值。

51.  strncpy把源字符串的字符复制到目标数组。它总是正好向dst写入len个字符,如果strlen(src)的值小于len,dst数组就用额外的NUL字符填充,如果strlen(src)的值大雨或等于len,那么只有len个字符被复制到dst中。这时,它的结果将不会以NUL字节结尾。

strncpy的原型:char *strncpy( char *dst,char const *src,size_tlen );

52.  strncat总是在结果字符串后面添加一个NUL字符。

53.  char *strtok(char *str,charconst *sep);   sep参数是个字符串,定义了用作分隔符的字符集合。第一个参数是个字符串,它包含零个或多个由sep字符串中的一个或多个分隔符分隔的标记。strtok找到str的下一个标记,并将其用NUL结尾,然后返回一个指向这个标记的指针。

54.  字符分类函数(包含于头文件 ctype.h),每个分类函数接受一个包含字符值的整型参数。函数测试这个字符并返回一个整型值,表示真或假。注意,标准并没有指定任何特定值,所以有可能返回任何非零值。

55.  使用字符字符分类和转换函数可以提高函数可移植性。直接测试或操纵字符将会降低程序的可移植性,

例如: if (ch >= ‘A’&& ch<= ‘Z’)  //测试字符ch是否是一个大写字符

这条语句在ASCII字符集的机器上能够运行,但在使用EBCDIC字符集的机器上将会失败。

if ( isupper(ch) )   //这条语句无论机器使用什么字符集,它都能顺利运行。

56.  malloc所分配的是一块连续内存。同时,malloc实际分配的内存有可能比你请求的稍微多一点,但是这个行为是由编译器定义的,所以你不能指望它肯定会分配比你的请求更多的内存。

57.  如果内存池是空的,或者它的可用内存无法满足你的请求,在种情况下,malloc向操作系统请求,要求得到更多的内存,并在这块新的内存上执行分配任务。如果操作系统无法向malloc提供更多的内存,malloc就返回一个NULL指针。因此,对每个从malloc返回的指针都进行检查,确保它并非NULL是非常重要的。

58.  realloc函数用于修改一个原先已经分配的内存块的大小。如果它用于扩大一个内存块,那么这块内存原先的内容依然保留,新增的内存添加到原先内存块的后面,新内存并未以任何方法进行初始化。如果用于缩小一个内存块,该内存块尾部的部分内存便被拿走剩余部分内存的原先内容依然保留。如果原先的内存块无法改变大小,realloc将分配另一块正确大小的内存,并把原先那块内存的内容复制到新的块上。因此,在使用realloc之后,你就不应该使用指向旧内存的指针,而是使用realloc所返回的指针。

59.  常见的动态内存错误:对NULL指针进行间接访问操作、对分配的内存进行操作时越过边界、释放并非动态分配的内存、试图释放一块动态分配的内存的一部分以及一块动态内存被释放之后被继续使用。

60.  动态内存分配最常见的错误就是忘记检查所请求的内存是否分配成功。

61.  动态内存分配的第二大错误来源是操作内存时超出了分配内存的边界。

62.  不要访问已经被free函数释放了的内存。假定你对一个指向动态分配的内存的指针进行了复制,且这个指针的拷贝分布于程序各处,你无法保证当你使用其中一个时它所指向的内存是不是已经被另一个指针释放。所以,不用的指针立即赋空(NULL)。

63.  分配的内存在使用完毕后不释放将引起内存泄漏。

64.  字符串常量的类型是“指向字符的指针”。所以表达式 *”xyz”的值是x。

65.  使用宏比使用函数在程序的规模和速度方面更胜一筹,因为用于调用和从函数返回的代码很可能比实际执行这个工作的代码更大。

66.  宏与类型无关,一个宏可以处理各种类型的数据。

67.  宏的不利之处在于每次使用宏,一份宏定义代码的拷贝都将插入到程序中。除非宏非常短,否则使用宏可能会大幅度增加程序的长度。

68.  避免用#define指令定义可以用函数实现的很长序列的代码。

69.  在那些对表达式求值的宏中,每个宏参数出现的地方都应该加上括号,并且在整个宏定义的两边也加上括号。

70.  对于宏来说,只要合适就应该使用文件包含,不必担心它的额外花销。

71.  fgetc 和 fputc都是真正的函数,但getc、getchar、和putchar都是通过#define指令定义的宏。

72.  int fscanf(FILE *stream,charconst *format,……);

int scanf( char const *format,……);

int sscanf( char const *string,char const*format,……);

 

int fprintf( FILE *stream,char const*format,……);

int printf( char const *format,……);

int sprintf( char *buffer,char const*format,……);

 

为了能让这些函数正常运行,指针参数的类型必须是对应格式的正确类型。函数无法检验它们的指针参数是否为正确类型,所以函数就假定他们是正确的,于是继续执行并使用它们,如果指针参数的类型是不正确的,那么结果值就会是垃圾,而相邻的变量有可能在处理过程中被改写。所以保证它们相互匹配是程序员的责任

73.  char *fgets (char *buffer, intbuffer_size, FILE *stream);

fgets从指定的stream读取字符并把它们复制到buffer中。当它读取一个换行符并存储到缓冲区之后就不再读取。如果缓冲区内存储的字符数达到buffer_size -1个时它也停止读取。虽然如此,但是并不会出现数据丢失的情况,因为下一次调用fgets将从流的下一个字符开始读取。

74.  fgets函数如果在任何字符读取前就达到了文件尾,缓冲区就未进行修改,fgets函数返回一个NULL指针。否则,fgets返回它的第一个参数。

75.  char *fputs ( char const*buffer,FILE *stream);

向指定的文件写入一个字符串。

这个字符串是逐字写入的:如果它不包含一个换行符,就不会写入换行符;如果它包含了好几个换行符,所有的换行符都会被写入。

如果写入时出现了错误,fputs返回常量值EOF,否则它将返回一个非负值。

76.  remov函数删除一个指定的文件,如果当remove被调用时文件处于打开状态,其结果是未定义的,取决于编译器。

77.  rename函数用于改变一个文件的名字,从oldname改为newname。如果已经有一个名为newname的文件存在,其结果取决于编译器。如果这个函数失败,文件仍然可以用原来的名字进行访问。

78.  不要忘记检查fopen函数的返回值。不要忘记使用fclose函数将打开的文件它关闭。

79.  int abs( int value);

abs函数返回它的参数的绝对值。如果其结果不能用一个整数表示,这个行为是未定义的。labs用于执行相同的任务,但它的作用的对象是长整型。

80.  int rand(void);

void srand (unsigned int seed);

rand返回一个范围在0和RAND_MAX(至少为32767)之间的伪随机数。当它重复调用时,函数返回这个范围内的其他数。

但是只调用这个函数,在产生一次随机数序列之后,如果调用相同的程序,产生的随机数就是上次产生的随机数序列,除非产生比这个序列更多的随机数,才会在以前的序列后面出现新的随机数。

int main()

{

   int i=0;

   while(i<10)

   {

       printf("%d\n",rand());

       i++;

   }

 

   return 0;

}

运行一次之后,产生10个随机数,然后不管运行多少次,都是该10个数;把while(i<10)改成while(i<20)后,程序会在上次10个数字后面再添加10个随机数。

 

为了避免这种程序每次运行时获得相同的随机数序列,我们调用srand函数。它用它的参数值对随机数发生器进行初始化。一个常用的技巧是用时间作为随机数产生器的种子。

81.  使用数学运算函数时,不要忘记包含math.h头文件。

82.  如果一个函数的参数不在该函数的定义域内,称为定义域错误。例如:sqrt(-5.0);因为负值的平方根是未定义的。当出现一个定义域错误时,函数返回一个由编译器定义的错误值,并且在errno中存储EDOM这个值。

83.  头文件errno.h是C语言C标准函式库里的标头档,定义了通过错误码回报错误资讯的宏:errno宏定义为一个int型态的左值,包含任何函式使用errno功能所产生的上一个错误码。只有当一个库函数失败时,errno才会被设置。当函数成功运行时,errno的值不会被修改。这意味着我们不能通过测试errno的值来判断是否有错误存在。反之,只有当被调用的函数提示有错误发生时检查errno的值才有意义。

84.  time函数返回当前的日期和时间。

time_t time ( time_t *returned_value);

如果参数是一个非NULL的指针,时间值也将通过这个指针进行存储。如果机器无法提供当前的日期和时间,或者时间值太大,无法用time_t变量表示,函数就返回-1。