C语言编程总结.pdf
C C 和指针和指针 C C 专家编程专家编程 C C 陷阱与缺陷陷阱与缺陷 C C 语言编程要点语言编程要点 编程精粹编程精粹-MicrosoftMicrosoft 编写优质无错编写优质无错 C C 程序秘诀程序秘诀 总 结 说明:总结的知识点主要源于上面的 4 本书,编程精粹-Microsoft 编写优质无错C 程序秘诀这本书未做总结,该书有清晰版的 pdf 格式的电子版。-wuliming -2007-04-25 wuliming_ 指针和数组相关概念*字符与字符串的区别 指针与数组 1 指针与数组 2 指针和数组的相同与不同 用 malloc 为字符串分配存储空间时的注意事项 作为常数的数组声明(c 缺陷与陷阱 3.3 节.在其它部分有包含该节的知识点,了解 or 略过)字符串常量 用字符串常量初始化指针和数组 二维数组下标操作的相关概念 指向一维、二维数组的指针 array_name 和&array_name 的异同 数组作为函数的参数时,不能通过 sizeof 运算符得到该数组的大小 用 strlen()求字符串的长度 char*和 const char*的兼容性问题 空指针相关的问题 NULL 和 NUL 的区别 未初始化的指针和 NULL 指针的区别 理解函数的声明 函数参数的传值调用 函数指针 作为函数参数的多维数组 强制类型转换相关概念 可变参数相关问题 malloc()、calloc()、realloc()在程序退出 main()函数之后,还有可能执行一部分代码吗?总线错误和段错误相关概念 数字和字符串之间转换相关的函数*怎样判断一个字符是数字、字母或其它类别的符号?怎样将数字转换为字符串?怎样将字符串转换为数字?字符串以及内存操作相关函数*字符串拷贝和内存拷贝函数:strcpy strncpy memcpy memmove memccpy bcopy 字符串和内存数据比较函数:strcmp strcasecmp strncasecmp memcmp strcoll bcmp 连接字符串的函数:strcat strncat 查找字符/字符串的函数:strstr strchr strrchr memchr 其它相关的函数:index rindex strlen strdup memset bzero strspn strcspn strpbrk strtok 数据结构及算法相关函数 qsort()bsearch()lsearch(线性搜索)lfind(线性搜索)srand(设置随机数种子)rand(产生随机数)OTHER*什么是标准预定义宏?断言 assert(表达式)相关概念 连接运算符“#”和字符串化运算符#有什么作用?注释掉一段代码的方法注释掉一段代码的方法 Typedef 相关概念=不同于=词法分析中的“贪心法”运算符的优先级问题 变量的存储类型及初始化相关概念 左值和右值相关的概念 变量的值和类型相关的概念 怎样删去字符串尾部的空格?怎样删去字符串头部的空格?怎样打印字符串的一部分?结构的自引用 结构的存储分配 边界计算与不对称边界 整数溢出 返回整数的 getchar 函数 更新顺序文件 随机数的相关概念 用递归和迭代两种办法解 fibonacci 字符与字符串的区别字符与字符串的区别(c 缺陷与陷阱缺陷与陷阱 1.5 节节)#include int main()char ch=abcdefghijklmnopqrstuvwxyz;char str=abcdefghijklmnopqrstuvwxyz;printf(-%c-n%sn,ch,str);return 0;编译该程序可以通过,但是会产生警告;输出结过为:编译该程序可以通过,但是会产生警告;输出结过为:-z-Abcdefghijklmnopqrstuvwxyz /在在 Dev-C+4.9.9.2 编译环境中可以通过,但是在编译环境中可以通过,但是在 VC.0 中通不过中通不过 指针与数组指针与数组 1(c 缺陷与陷阱缺陷与陷阱 3.1 节节)c 语言中的数组值得注意的地方有以下两点:语言中的数组值得注意的地方有以下两点:1、c语言中只有一维数组语言中只有一维数组,而且数组的大小必须在编译期间就作为一个常数确定下来而且数组的大小必须在编译期间就作为一个常数确定下来(C99 标准允许变长数组,标准允许变长数组,GCC 编译器中实现了变长数组编译器中实现了变长数组)。然而,。然而,c 语言中数组的元语言中数组的元素可以是任何类型的对象,当然也可以是另外一个数素可以是任何类型的对象,当然也可以是另外一个数组。这样,要仿真出一个多维数组就不是一件难事。组。这样,要仿真出一个多维数组就不是一件难事。2、对于一个数组,我们只能够做两件事:确定该数组的大小,以及获得指向该数组下标为、对于一个数组,我们只能够做两件事:确定该数组的大小,以及获得指向该数组下标为 0 的元素的指针。的元素的指针。其他有关数组的操作,哪怕它们乍看上去是以数组下标进行运算的,实际上都是通过指针进行的。换句话说,其他有关数组的操作,哪怕它们乍看上去是以数组下标进行运算的,实际上都是通过指针进行的。换句话说,任何一个数组下标运算都等同于一个对应的指针运算,因此我们完全可以依据指针行为定义数组下标的行为。任何一个数组下标运算都等同于一个对应的指针运算,因此我们完全可以依据指针行为定义数组下标的行为。现在考虑下面的例子:int i;int*p;int calendar1231;上面声明的 calendar 是一个数组,该数组拥有该数组拥有 12 个数组类型的元素,其中的每个元素都是一个拥有个数组类型的元素,其中的每个元素都是一个拥有 31 个整个整型元素的数组。型元素的数组。因此,sizeof(calendar)的值是:3112sizeof(int)。考虑一下,calendar4的含义是什么?因为 calender 是一个有着 12 个数组类型元素的数组,它的每个数组类型元素又是一个有着 31 个整型元素的数组,所以 calendar4是 calendar 数组的第 5 个元素,是 calendar数组中 12 个有着 31 个整型元素的数组之一。因此,calendar4的行为也表现为一个有着 31 个整型元素的数组的行为。例如,sizeof(calendar4)的结果是:31sizeof(int)。又如,p=calendar4;这个语句使指针 p 指向了数组 calendar4中下标为 0 的元素。因为 calendar4是一个数组,我们可以通过下标的形式来指定这个数组中的元素:i=calendar47,这个语句也可以写成下面这样而表达的意思保持不变:i=*(calendar4+7),还可以进一步写成:,还可以进一步写成:i=*(*(calendar+4)+7)。下面我们再看:p=calendar;这个语句是非法的,因为 calendar 是一个二维数组,即“数组的数组”,在此处的上下文中使用 calendar 名称会将其转换为一个指向数组的指针。而 p 是一个指向整型变量的指针,两个指针的类型不一样,所以是非法的。显然,我们需要一种声明指向数组的指针的方法。int calendar1231;int(*monthp)31;monthp=calendar;int(*monthp)31 语句声明的*monthp 是一个拥有 31 个整型元素的数组,因此,monthp 就是一个指向这样的数组的指针。monthp 指向数组 calendar 的第一个元素。HERE 指针与数组指针与数组 2(c 和指针和指针.P141.)1、数组的名的值是一个指针常量,不能试图将一个地址赋值给数组名;2、当数组名作为 sizeof 操作符的操作数时,sizeof(arrayname)返回的是整个数组的长度,而不是指向数组的指针的长度;3、当数组名作为单目操作符&的操作数,取一个数组名的地址所产生的是一个指向数组的指针,而不是一个指向某个指针常量值的指针。4、指针和数组并不总是相等的。为了说明这个概念,请考虑下面这两个声明:int a5;int*b;a 和 b 能够互换吗?它们都具有指针值,它们都可以进行间接访问和下标操作。但是,它们还是有很大的区别的:声明一个数组时,编译器将根据声明所指定的元素数量为数组保留内存空间,然后再创建数组名,它的值是一个常量,指向这段空间的起始位置。声明一个指针变量时,编译器只为指针本身保留内存空间,它并不为任何整型值分配内存空间。而且,指针变量并未被初始化为指向任何现有的内存空间,如果它是一个自动变量,它甚至根本不会被初始化。把这两个声明用图的方法表示,可以发现它们之间存在显著的不同:a b?因此,上述声明后,表达式*a 是完全合法的,但表达式*b 却是非法的。*b 将访问内存中某个不确定的位置,或者导致程序终止。另一方面,表达式 b+可以通过编译,但是 a+却不能,因为 a 的值是一个常量。#include int main()/注意注意 sizeof(num)的长度应该为的长度应该为 10*4=40 int num=0,1,2,3,4,5,6,7,8,9;printf(sizeof(num)=%dn,sizeof(num);/注意注意 sizeof(str)的长度应该为的长度应该为 11,包括字符串后面的包括字符串后面的0 char str=0123456789;printf(sizeof(str)=%dn,sizeof(str);/注意注意 sizeof(str1)的长度应该为的长度应该为 10,不包括字符串后面的不包括字符串后面的0,但是,最好将字符串的最后一个字符设定为空,但是,最好将字符串的最后一个字符设定为空 char str1=0,1,2,3,4,5,6,7,8,9;printf(sizeof(str1)=%dn,sizeof(str1);/&num 的类型为的类型为int(*)10,表示的是一个指向长度为,表示的是一个指向长度为 10 的整形数组的指针的整形数组的指针 int(*ptoint)10=#printf(sizeof(ptoint)=%d,(*ptoint)9=%dn,sizeof(ptoint),(*ptoint)9);/&str 的类型为的类型为char(*)11,表示的是一个指向长度为,表示的是一个指向长度为 11 的字符数组的指针,注意的字符数组的指针,注意 str 数组的长度是数组的长度是 11,而不是,而不是 10 char(*ptostr)11=&str;printf(sizeof(ptostr)=%d,(*ptostr)9=%cn,sizeof(ptostr),(*ptostr)9);/由于由于 p 指向的是数组指向的是数组 num5,所以对下标取负值后,不会超出数组的正常取值范围,所以对下标取负值后,不会超出数组的正常取值范围 /该例子也说明了为什么下标检查在该例子也说明了为什么下标检查在 c 语言中是一项困难的任务:下标引用可以作用于任意的指针,而不仅仅是数语言中是一项困难的任务:下标引用可以作用于任意的指针,而不仅仅是数组名组名 /作用于指针的下标引用的有效性即依赖于该指针当时恰好指向什么内容,也依赖于下标的值作用于指针的下标引用的有效性即依赖于该指针当时恰好指向什么内容,也依赖于下标的值 int*p=num+5;printf(p-1=%d,p0=%d,p1=%d n,p-1,p0,p1);/下面的表达式中,下面的表达式中,num5和和 5num的值是一样的,把它们转换成对等的间接访问表达式,它们都等同于的值是一样的,把它们转换成对等的间接访问表达式,它们都等同于*(num+2)/5num这个古怪的表达式之所以可行,缘于这个古怪的表达式之所以可行,缘于 C 实现下标的方法。对编译器来说,这两种形式并无差别实现下标的方法。对编译器来说,这两种形式并无差别 /但是,决不应该编写形如但是,决不应该编写形如5num的表达式,因为它会大大的影响程序的可读性的表达式,因为它会大大的影响程序的可读性 printf(num5=%d,5num=%d n,num5,5num);getchar();return 0;输出结果为:指针和数组的相同与不同指针和数组的相同与不同(c 专家编程专家编程.P199.)在实际应用中,数组和指针可以互换的情形要比两者不可互换的情形更为常见。让我们分别考虑“声明”和“使用”这两种情况。声明本身还可以进一步分为 3 种情况:外部数组的声明;数组的定义(定义是声明的一种特殊情况,它分配内存空间,并可能提供一个初始值);函数参数的声明;extern,如 extern char a;不能改写为指针的形式 声明 定义,如 char a10;不能改写为指针的形式 数组 函数的参数,可以随意选择数组 在表达式中使用 的形式或者指针的形式 如 c=ai,可以随意选择数组 形式或者是指针形式 也既是:作为函数参数时、在语句或表达式中使用数组时,我们可以采用数组或者指针的任何一种形式,除此作为函数参数时、在语句或表达式中使用数组时,我们可以采用数组或者指针的任何一种形式,除此之外的其他情况下,指针和数组不要互换之外的其他情况下,指针和数组不要互换。下面就数组和指针相同的情况做详细的说明:规则规则 1、表达式中的数组名被编译器当作一个指向该数组第一个元素的指针。、表达式中的数组名被编译器当作一个指向该数组第一个元素的指针。假如我们声明:int a10;int*p=a;就可以通过一下任何一种方式来访问 ai:pi *(p+i)*(a+i)事实上,可以采用的方法很多。对数组的引用如 ai 在编译时总是被编译器改写成*(a+i)的形式,C 语言标准要求编译器必须具备这个概念性的行为。编译器自动把下标值的步长调整到数组元素的大小。如果整型数的长度是 4 个字节,那么 ai+1和 ai在内存中的距离就是 4。对起始地址执行加法操作之前,编译器会负责计算每次增加的步长。这就是为什么指针总是有类型限制,每个指针只能指向一种类型的原因所在,因为编译器需要知道对指针进行解除引用操作时应该取几个字节,以及每个下标的步长应取几个字节。规则规则 2、下标总是和指针的偏移量相同。、下标总是和指针的偏移量相同。把数组下标作为指针加偏移量是 c 语言从 BCPL(C 语言的祖先)继承过来的技巧。在人们的常规思维中,在运行时增加对 c 语言下标的范围检查是不切实际的。因为取下标操作只是表示将要访问该数组,但并不保证一定要访问。而且程序员完全可以使用指针来访问数组,从而绕过下标操作符。在这种情况下,数组下标范围检测并不能检测所有对数组的访问的情况。事实上,下标范围检测被认为不值得加入到 c 语言当中。还有一个说法是,在编写数组算法时,使用指针比使用数组更有效率。这个颇为人们所接收的说法在通常情况下是错误的。使用现代的产品质量优化的编译器,一维数组和指针引用所产生的代码并不具有显著的差别。不管怎样,数组下标是定义在指针的基础上,所以优化器常常可以把它转化为更有效率的指针表达式,并生成相同的机器指令。规则规则 3、在函数参数的声明中,数组名被编译器当作指向该数组第一个元素的指针。、在函数参数的声明中,数组名被编译器当作指向该数组第一个元素的指针。在函数形参定义这个特殊情况下,编译器必须把数组形式改写成指向数组第一个元素的指针形式。编译器只向函数传递数组的地址,而不是整个数组的拷贝。这种转换意味着在声明函数的时候,以下三种形式都是合法的(同时无论实参是数组还是真的指针也都是合法的):my_function(int*turnip)my_function(int turnip)my_function(int turnip200)用用 malloc 为字符串分配存储空间时的注意事项为字符串分配存储空间时的注意事项(c 缺陷与陷阱缺陷与陷阱 3.2 节节)作为常数的数组声明作为常数的数组声明(c 缺陷与陷阱缺陷与陷阱 3.3 节节.在其它部分有包含该节的知识点,了解在其它部分有包含该节的知识点,了解 or 略过略过)字符串常量字符串常量(c 和指针和指针.P269.)当一个字符串常量出现在表达式中时,它的值是指针常量。编译器把该字符串的一份拷贝存储在内存的某个位当一个字符串常量出现在表达式中时,它的值是指针常量。编译器把该字符串的一份拷贝存储在内存的某个位置,并存储一个指向第一个字符的指针。我们可以对字符串常量进行下标引用、间接访问以及指针运算。置,并存储一个指向第一个字符的指针。我们可以对字符串常量进行下标引用、间接访问以及指针运算。“xyz”+1“xyz”+1 字符串常量实际上是个指针,这个表达式计算“指针值加上 1”的值。它的结果也是个指针,指向字符串中的第二个字符 y*“xyz”“xyz”对一个指针执行间接访问操作时,其结果就是指针所指向的内容。字符串常量的类型是“指向字符的指针”,所以这个间接访问的结果就是它所指向的字符:x。注意表达式的结果并不是整个字符串,而只是它的第一个字符。“xyzxyz”22 同样可以推断出上面这个表达式的值就是字符 z。#include /接受一个无符号整型值,把它转换成字符,并打印出来接受一个无符号整型值,把它转换成字符,并打印出来/如果是打印如果是打印 16 进值的数,可以用这种方法:进值的数,可以用这种方法:putchar(0123456789ABCDEF value%16 )void binary_to_ascii(unsigned long value)unsigned long quotient;quotient=value/10;if(quotient!=0)binary_to_ascii(quotient);putchar(0123456789 value%10 );int main()/字符串常量实际上是个指针,这个表达式计算字符串常量实际上是个指针,这个表达式计算指针值加上指针值加上 1的值。它的结果也是个指针,的值。它的结果也是个指针,/指向字符串中的第二个字符:指向字符串中的第二个字符:y printf(%sn,xyz+1);/对一个指针执行间接访问操作时,其结果就是指针所指向的内容。对一个指针执行间接访问操作时,其结果就是指针所指向的内容。/字符串常量的类型是字符串常量的类型是指向字指向字符的指针符的指针,所以这个间接访问的结果就是它所指向的字符:,所以这个间接访问的结果就是它所指向的字符:x printf(%cn,*abcdefg);/同样可以推断出上面这个表达式的值就是字符同样可以推断出上面这个表达式的值就是字符 z printf(%cn,abcdefg3);binary_to_ascii(1234567);getchar();return 0;用字符串常量初始化指针和数组用字符串常量初始化指针和数组(c 专家编程专家编程.P87.)定义指针时,编译器并不为指针所指的对象分配空间,它只是分配指针本身的空间,除非在定义时同时赋给指针一个字符串常量进行初始化。例如,下面的定义创建一个字符串常量(为其分配内存):char*p=“breadfruit”;注意只有对字符串常量才是如此。不能指望为浮点数之类的变量分配空间,如:float*pip=3.14;/*错误,无法通过编译*/在 ANSI C 中,初始化指针时所创建的字符串常量被定义为只读。如果试图通过指针修改这个字符串值,程序会出现未定义的行为。在有些编译器中,字符串常量被存放在只允许读取的文本段中,以防止它被修改。数组也可以用字符串常量进行初始化:char a=“gooseberry”;与指针相反,由字符串常量初始化的数组是可以修改的。比如下面的语句:strncpy(a,“black”,5);将数组的值修改为“blackberry”。#include#include int main(void)char*p=this is a example;/char*pi=3.14;/这样定义是错误的,无法通过编译这样定义是错误的,无法通过编译 /p0=T;/修改该字符串常量时,编译是没问题,但是运行时会出现异常修改该字符串常量时,编译是没问题,但是运行时会出现异常 char a=gooseberry;strncpy(a,black,5);printf(%sn,p);printf(%sn,a);return 0;二维数组下标操作的相关概念二维数组下标操作的相关概念(c 和指针和指针.P156.)指向一维、二维数组的指针指向一维、二维数组的指针(c 和指针和指针.P158.)array_name 和和&array_name 的异同的异同 前者是指向数组中第一个元素的指针,后者是指向整个数组的指针前者是指向数组中第一个元素的指针,后者是指向整个数组的指针。char aMAX;/*array of MAX characters*/char*p=a;/*p 为指向数组的指针*/char*pa=&a;/*该语句是不正确的,pa 的类型为char*,而&a 的类型为char(*)MAX*/char(*pb)MAX=&a;/*该语句是正确的,pb 的类型为char(*)MAX*/#include void main()char a5=a,b,c,d,0;char*p=a;/运行下面这句后,vc6.0 提示的错误为:cannot convert from char(*)5 to char*,&a 的类型应该是指向一个数组的指针/char*pa=&a;/所以,应该定义一个指向相同类型和大小的数组的指针来获得“&a”的值 char(*point_to_str)5;point_to_str=&a;printf(%dn%dn,&p,&point_to_str);printf(%sn%sn,p,point_to_str);运行结果为:1245044 1245040 abcd abcd 数组作为函数的参数数组作为函数的参数时,时,不能不能通过通过 sizeofsizeof 运算符运算符得到该得到该数组的大小数组的大小 不可以。当把数组作为函数的参数时,你无法在程序运行时通过数组参数不可以。当把数组作为函数的参数时,你无法在程序运行时通过数组参数本身告诉函数该数组的大小,因为函本身告诉函数该数组的大小,因为函数的数组参数相当于指向该数组第一个元素的指针。这意味着把数组传递给函数的效率非常高,也意味着程序数的数组参数相当于指向该数组第一个元素的指针。这意味着把数组传递给函数的效率非常高,也意味着程序员必须通过某种机制告诉函数数组参数的大小。员必须通过某种机制告诉函数数组参数的大小。为了告诉函数数组参数的大小,人们通常采用以下两种方法:第一种方法是将数组和表示数组大小的值一起传递给函数第一种方法是将数组和表示数组大小的值一起传递给函数,例如 memcpy()函数就是这样做的:memcpy(dest,source,length);第二种方法是引入某种规则来结束一个数组,例如在第二种方法是引入某种规则来结束一个数组,例如在 C C 语言中字符串总是以语言中字符串总是以 ASCIIASCII 字符字符 NUL(NUL(0)0)结束,而一个结束,而一个指针数组总是以空指针数组总是以空指针结束。指针结束。请看下述函数,它的参数是一个以空指针结束的字符指针数组,这个空指针告诉该函数什么时候停止工作:void printMany(char*strings)int i=0;while(stringsi!=NULL)puts(stringsi+);C 程序员经常用指针来代替数组下标,因此大多数 C 程序员通常会将上述函数编写得更隐蔽一些:void printMany(char*strings)while(*strings)puts(*strings+);尽管你不能改变一个数组名的值,但是 strings 是一个数组参数,相当于一个指针,因此可以对它进行自增运算,并且可以在调用 puts()函数时对 strings 进行自增运算。用用 strlen()求字符串的长度求字符串的长度(c 和指针和指针.P159.)库函数库函数 strlen 的原型为:的原型为:size_t strlen(char const*string);strlen 返回一个类型为返回一个类型为 size_t 的值。这个类型是在头文件的值。这个类型是在头文件 stddef.h 中定义的,它是一个无符号整型类型中定义的,它是一个无符号整型类型。在表达式中使用无符号数可能导致不可预期的结果。例如,下面两个表达式看起来是相等的:if(strlen(str1)=strlen(str2)if(strlen(str1)-strlen(str2)=0)但事实上它们是不相等的,第 1 条语句会按照预想的那样工作,但第 2 条语句的结果将永远是真的。strlen 的结果是无符号数,所以操作符=左边的表达式也将是无符号数,而无符号数决不可能是负的。表达式中如果同时包含了无符号数和有符号数,可能会产生奇怪的结果。和上面的一对语句一样,下面两条语句并不相等,原因相同。if(strlen(str1)=10)if(strlen(str1)-10=0)如果将 strlen 的结果值强制转换成 int,就可以消除这个问题。类似的,sizeof()的返回类型也是的返回类型也是 size_t,和strlen()一样,也存在类似的现象(sizeof()是一个运算符,不是函数)。对无符号类型的建议:对无符号类型的建议:尽量不要在代码里面使用无符号类型,以免增加不必要的复杂性。尤其是,不要仅仅因为无符号数不存在负值尽量不要在代码里面使用无符号类型,以免增加不必要的复杂性。尤其是,不要仅仅因为无符号数不存在负值而用它来表示数量。而用它来表示数量。尽量使用像尽量使用像 int 这样的有符号类型,这样在涉及升级混合类型的复杂细节时,不必担心边界情况。这样的有符号类型,这样在涉及升级混合类型的复杂细节时,不必担心边界情况。对于返回类型为无符号的函数对于返回类型为无符号的函数(strlen()、sizeof(),最好将结果转换成整型(,最好将结果转换成整型((int)strlen()、(int)sizeof()),这样可以避免出现比较微妙的),这样可以避免出现比较微妙的 bug(在 java 里面,就没有无符号数)。#include#include#include int main()char str1=0123456789;char str2=abcdefghijk;/sizeof()的返回类型也是无符号类型,无符号类型的运算结果也被转换成无符号类型,不可能为负的返回类型也是无符号类型,无符号类型的运算结果也被转换成无符号类型,不可能为负 /(int)sizeof(str1)(int)sizeof(str2)0 这个表达式将这个表达式将得到预期结果得到预期结果 if(sizeof(str1)-sizeof(str2)0)printf(sizeof(str1)-sizeof(str2)的计算结果是无符号型的,不可能为负的计算结果是无符号型的,不可能为负n);if(strlen(str1)=strlen(str2)printf(strlen 的返回值为无符号整型类型,把两个无符号整型类型做比较,会得到预期的结果的返回值为无符号整型类型,把两个无符号整型类型做比较,会得到预期的结果n);if(strlen(str1)-strlen(str2)=0)printf(strlen(str1)=%dn,strlen(str1);printf(strlen(str2)=%dn,strlen(str2);printf(strlen(str1)-strlen(str2)=0)表达式的值为:表达式的值为:%dn,strlen(str1)-strlen(str2)=0);printf(strlen(str1)-strlen(str2)的结果是无符号类型,无符号数不可能是负值,所以该条件永远成立的结果是无符号类型,无符号数不可能是负值,所以该条件永远成立n);/注意:注意:sizeof()和和 strlen()两个函数取得的值是不相等的两个函数取得的值是不相等的 /sizeof()求得的长度包括字符串末尾的那个空字符求得的长度包括字符串末尾的那个空字符0 /strlen()求得的长度不包括字符串末尾的空字符求得的长度不包括字符串末尾的空字符 printf(sizeof(str1)=%dn strlen(str1)=%dn,sizeof(str1),strlen(str1);getchar();return 0;char*和和 const char*的兼容性问题的兼容性问题(c 专家编程专家编程.P19.)有时候必须非常专注的阅读 ANSI C 标准才能找到某个问题的答案。一位销售工程师把下面的代码作为测试例子发给 SUN 的编译器小组。#include void foo(const char*P)int main(int argc,char*argv)foo(argv);return 0;在在 VC6.0 下编译这段代码,编译器会发出警告下编译这段代码,编译器会发出警告:cannot convert parameter 1 from char*to const char*提交代码的工程师想知道为什么会产生类似的警告,他认为,实参 char*s 与形参 const char*p 应该是相容的,标准库中所有的字符串处理函数都是这样的。那么,为什么实参 char*argv 与形参 const char*P实际上不能相容呢?答案是肯定的,它们并不相容。现在我们回顾一下标准中有关简单赋值的部分,它位于 ANSI C 第 6.3.16.1 节,描述了下列约束条件:要使上述赋值形式合法,必须满足下列条件之一:两个操作数都是指向有限定符或无限定符的相容类型的指针,左边指针所指向的类型必须具有右边指针 所指向类型的全部限定符。正是这个条件,使得函数调用中实参 char*能够与形参 const char*匹配。它之所以合法,是因为在下面的代码中:char*cp;const char*cpp;cpp=cp;左操作数是一个指向有 const 限定符的 char 的指针;右操作数是一个指向没有限定符的 char 的指针;char 类型和 char 类型是相容的,左操作数所指向的类型具有右操作数所指向类型的限定符(无),再加上自身的限定符 const(注意反过来不能赋值)。标准第 6.3.16.1 节没有说明 char*实参与 const char*形参是否相容。标准 6.1.2.5 节中讲述实例的部分声称:const float*类型并不是一个有限定符的类型,它的类型是“指向一个具有 const 限定符的 float 类型的指针”,也就是说 const 限定符是修饰指针所指向的类型,而不是指针。类似地,const char*const char*也是一个没也是一个没有限定有限定符的指针类型,它的类型是“指向有符的指针类型,它的类型是“指向有 constconst 限定符的限定符的 charchar 类型的指针的指针”。类型的指针的指针”。由于由于 char*char*和和 const const char*char*都是没有限定符的指针类型,但它们所指向的类型不一样(前者指向都是没有限定符的指针类型,但它们所指向的类型不一样(前者指向 char*char*,后者指向,后者指向 const char*const char*),因此它们是不相容的因此它们是不相容的。因此类型为 char*的实参和类型为 const char*的形参是不相容的,编译器会产生一条诊断信息。备注:解释的有些牵强,目前记住结果就可以了 空指针相关的问题空指针相关的问题(c 缺陷与陷阱缺陷与陷阱 3.5 节节)#include#include int main()char*p=NULL;if(p=(char*)0)printf(p is a null pointn);else printf(p is not a null pointn);/该语句不会引起编译错误,但是运行时会出现异常 if(strcmp(p,(char*)0)=0)printf(cant dereference pn);/该语句不会引起编译错误,但是运行时会出现异常 printf(%d,*p);getchar();return 0;NULL 和和 NUL 的区别的区别 NULLNULL 是在是在stddefh头文件中专门为空指针定义的一个宏。头文件中专门为空指针定义的一个宏。NULNUL 是是 ASCIIASCII 字符集中第一个字符的名称,它对应字符集中第一个字符的名称,它对应于一个零值。于一个零值。C C 语言中没有语言中没有 NULNUL 这样的预定义宏。注意这样的预定义宏。注意:在在 ASCIIASCII 字符集中,数字字符集中,数字 0 0 对应于十进制值对应于十进制值 4848,不要,不要把数字把数字 0 0 和和 0(NUL)0(NUL)的值混同起来。的值混同起来。NULL 可以被定义为可以被定义为(void*)0,而,而 NUL 可以被定义为可以被定义为0。NULL 和 NUL 都可以被简单地定义为 0,这时它们是等价的,可以互换使用,但这是一种不可取的方式。为了使程序读起来更清晰,维护起来更容易,你在程序中应该明确地将 NULL 定义为指针类型,而将 NUL 定义为字符类型。对指针进行解引用操作可以获得它的值。从定义来看,NULL 指针并未指向任何东西。因此,对一个 NULL 指针进行解引用操作是非法的。在对指针进行解引用操作之前,必须确保它并非 NULL 指针。未初始化的指针和未初始化的指针和 NULL 指针指针的区别的区别(c 和指针和指针.P95.)未初始化的指针未初始化的指针 NULLNULL 指针指针 理解函数的声明理解函数的声明(c(c 缺陷与陷阱缺陷与陷阱 2.12.1 节节)函数参数的传值调用函数参数的传值调用(c(c 和指针和指针.P.P122.122.)#include char ga=abcdefghijklm;void my_array_func(char ca10)/&ca 相当于一个相当于一个指向字符数组的指针的地址指向字符数组的指针的地址 char*pp=&ca;printf(&ca=%#x n,&ca);printf(ca=%#x n,ca);printf(&(ca0)=%#x n,&(ca0);printf(&(ca1)=%#x n,&(ca1);printf(sizeof(ca)=%d nn,sizeof(ca);void my_pointer_func(char*pa)/&pa 相当于一个指向字符数组的指针的地址相当于一个指向字符数组的指针的地址 char*pp=&pa;printf(&pa=%#x n,&pa);printf(pa=%#x n,pa);printf(&(pa0)=%#x n,&(pa0);printf(&(pa1)=%#x n,&(pa1);printf(sizeof(pa)=%d nn,sizeof(pa);int main()/&ga 相当于一个指向字符数组的指针的地址相当于一个指向字符数组的指针的地址 char(*pp)14=&ga;printf(&ga=%#x n,&ga);printf(ga=%#x n,ga);printf(&(ga0)=%#x n,&(ga0);printf(&(ga1)=%#x n,&(ga1);printf(sizeof(ga)=%d nn,sizeof(ga);my_array_func(ga);my_pointer_func(ga);getchar();return 0;/摘自摘自c 专家编程专家编程p216 页,做了部分修改页,做了部分修改 运行结果为:从结果可以看出,数组参数的地址和数组参数的第一个元