《C专家编程》总结
《C专家编程》总结
开始读《C专家编程》之前,有一个很担心的问题:94年出的讲语言的书,在现在(201*)还有多少是适用的。因此,一边读,一边用VS201*做实验。最后发现大部分内容都还在用。读完后,觉得最精彩的部分有二:一是讲解如何理解声明,二是深入地讲解数组名与指针。下文是将看书过程中所做的笔记进行的整理。
p.s:以下代码均在VS201*测试过
1.使用无符号数时要特别注意(不推荐使用无符号数)
当无符号数与有符号数在同一条表达式中出现时,有符号数会被转换为无符号数。e.g:
intfeng=-1;
unsignedintyan=5;
boolresult=(fengvoid(*signal(intsig,void(*func)(int)))(int);//signal是一个函数,该函数接收一个int,一个函数指针,并返回一个函数指针
5.左值与右值
左值通常表示存储结果的地方(地址),其值在编译时可知右值通常表示地址的内容,其值通常要到运行时才知道
6.指针与数组名不等同的情况(定义为数组,却声明为指针,或者反过来)
前提知识(假设有定义:intarray[10],*ptr;):
a.使用数组名下标访问(如:array[1]),会直接将数组名的地址加上偏移值作为变量的地址(即array[1]的地址)
b.使用指针下标访问(如:ptr[1]),会先取指针指向的内容,然后将这个内容加上偏移值作为变量的地址(即ptr[1]的地址)
不等同的原因:
当定义为数组,却声明为指针时,相当于用指针下标访问的方法来解析一个数组名下标,即先取数组第0号元素的内容,然后将这个内容加上偏移值作为变量的地址,从而访问了不该访问的东西。反之亦然。7.指针与数组等同的情况
a.编译器将表达式中的数组名当作指向该数组第0号元素的指针,下标当作指针的偏移量,即array[i]会被当作*(array+i)
b.编译器将函数参数声明中的数组名当作指向该数组第0号元素的指针,即在函数内部得到的是指针,而不是数组名
基于a情况,可知这条谣言是假的(至少在一维数组中一定不成立):用指针迭代数组比用下标迭代数组更快
基于b情况,可解释为什么在传递数组后,不能用以下方法计算数组长度
intArrayLength(intarr[]){
returnsizeof(arr)/sizeof(arr[0]);//返回值必定是1,因为此时的arr是一个指针,而不是数组名}
注意b情况的将数组改写为指针并不是递归定义的,e.g:
实参charzero[10][10]被改写为char(*zero)[10],这里将数组的数组改写为数组的指针
实参char*zero[10]被改写为char**zero,这里将指针数组改写为指针的指针
实参cahr(*zero)[10]不改变,因为此时的zero是指针,而不是数组8.interposition
interposition指用户定义的函数取代库中声明完全相同的函数,注意这不是指重载,而是指下面这种:
voidzero();//userdefinedfunctionvoidzero();//libraryfunction
出现interposition时,要特别注意以下情况:
voidzero();//userdefinedfunctionintmain(){
zero();//调用用户定义的函数zero,而不是库函数zero
FengYan();//假设这是另一个库函数,并且函数内调用库函数zero,此时由于interposition,变成调用用户定义的zeroreturn0;}
备注:
出现interposition时,在VS201*会出现warning:inconsistentdlllinkage9.堆栈段作用
a.存储局部变量
b.函数调用时,存储有关的维护信息
c.用作暂时存储区。e.g:计算一个很长的表达式时,会把部分结果先压到堆栈中
扩展阅读:C语言要点总结
《C和指针》《C专家编程》《C陷阱与缺陷》《C语言编程要点》
《编程精粹--Microsoft编写优质无错C程序秘诀》
总结
说明:总结的知识点主要源于上面的4本书,《编程精粹--Microsoft编写优质无错C程序秘诀》这本书未做总结,该书有清晰版的pdf格式的电子版。
--wuliming--201*-04-25
wuliming_sc@163.comwuliming_sc@qq.com
目录
字符与字符串的区别(c缺陷与陷阱1.5节)....................................................................................................................................4指针与数组1(c缺陷与陷阱3.1节)................................................................................................................................................5指针与数组2(c和指针.P141.)......................................................................................................................................................6指针和数组的相同与不同(c专家编程.P199.).................................................................................................................................8用malloc为字符串分配存储空间时的注意事项(c缺陷与陷阱3.2节)........................................................................................10字符串常量(c和指针.P269.)........................................................................................................................................................12用字符串常量初始化指针和数组(c专家编程.P87.)....................................................................................................................13二维数组下标操作的相关概念(c和指针.P156.)...........................................................................................................................14指向一维、二维数组的指针(c和指针.P158.)...............................................................................................................................17array_name和&array_name的异同........................................................................................................................................18数组作为函数的参数时,不能通过sizeof运算符得到该数组的大小............................................................................................18用strlen()求字符串的长度(c和指针.P159.)...............................................................................................................................19‘char**’和‘constchar**’的兼容性问题(c专家编程.P19.)..........................................................................................22空指针相关的问题(c缺陷与陷阱3.5节)......................................................................................................................................23NULL和NUL的区别..................................................................................................................................................................24未初始化的指针和NULL指针的区别(c和指针.P95.)..................................................................................................................25理解函数的声明(c缺陷与陷阱2.1节).........................................................................................................................................27函数参数的传值调用(c和指针.P122.).........................................................................................................................................29函数指针(c和指针.P260.)...........................................................................................................................................................31作为函数参数的多维数组(c和指针.P159.)..................................................................................................................................32强制类型转换相关概念(c专家编程.P187.)..................................................................................................................................34malloc()、calloc()、realloc()...................................................................................................................................................36常见的动态内存错误(c和指针.P223.).........................................................................................................................................37在程序退出main()函数之后,还有可能执行一部分代码吗?........................................................................................................38总线错误和段错误相关概念(c专家编程.P157.)...........................................................................................................................39怎样判断一个字符是数字、字母或其它类别的符号?....................................................................................................................40怎样将数字转换为字符串?............................................................................................................................................................42怎样将字符串转换为数字?............................................................................................................................................................44字符串拷贝和内存拷贝函数..........................................................................................................................................................47字符串和内存数据比较函数..........................................................................................................................................................49连接字符串的函数........................................................................................................................................................................51查找字符/字符串的函数...............................................................................................................................................................52qsort()函数.............................................................................................................................................................................58bsearch()函数.............................................................................................................................................................................59lsearch(线性搜索)...................................................................................................................................................................60lfind(线性搜索).......................................................................................................................................................................60srand(设置随机数种子)..........................................................................................................................................................61rand(产生随机数)...................................................................................................................................................................61什么是标准预定义宏?...................................................................................................................................................................62断言assert(表达式)相关概念(c和指针.P342.)........................................................................................................................63连接运算符“##”和字符串化运算符"#"的作用..........................................................................................................................63注释掉一段代码的方法.................................................................................................................................................................66Typedef相关概念.......................................................................................................................................................................67=不同于==(c缺陷与陷阱1.1节)........................................................................................................................................69词法分析中的“贪心法”(c缺陷与陷阱1.3节).......................................................................................................................70运算符的优先级问题(c缺陷与陷阱2.2节)..............................................................................................................................72
变量的存储类型及初始化相关概念(c和指针.P43.)......................................................................................................................73左值和右值相关的概念(c和指针.P79.)........................................................................................................................................75变量的值和类型相关的概念(c和指针.P92.).................................................................................................................................76怎样删去字符串尾部的空格..........................................................................................................................................................77怎样删去字符串头部的空格..........................................................................................................................................................78怎样打印字符串的一部分.............................................................................................................................................................79结构的自引用(c和指针.P198.)....................................................................................................................................................80结构的存储分配(c和指针.P206.)................................................................................................................................................81边界计算与不对称边界(C缺陷与陷阱3.6节)..............................................................................................................................83整数溢出(C缺陷与陷阱3.9节)....................................................................................................................................................84返回整数的getchar函数(C缺陷与陷阱5.1节).........................................................................................................................85更新顺序文件(C缺陷与陷阱5.2节)............................................................................................................................................86随机数的相关概念(c和指针.P328.).............................................................................................................................................87用递归和迭代两种办法解fibonacci............................................................................................................................................88
字符与字符串的区别(c缺陷与陷阱1.5节)
#includeintmain(){
charch="abcdefghijklmnopqrstuvwxyz";charstr[]="abcdefghijklmnopqrstuvwxyz";printf("-----%c-----\\n%s\\n",ch,str);
return0;}
编译该程序可以通过,但是会产生警告;输出结过为:-----z-----
Abcdefghijklmnopqrstuvwxyz//在Dev-C++4.9.9.2编译环境中可以通过,但是在VC.0中通不过
指针与数组1(c缺陷与陷阱3.1节)
c语言中的数组值得注意的地方有以下两点:
1、c语言中只有一维数组,而且数组的大小必须在编译期间就作为一个常数确定下来(C99标准允许变长数组,GCC编译器中实现了变长数组)。然而,c语言中数组的元素可以是任何类型的对象,当然也可以是另外一个数组。这样,要仿真出一个多维数组就不是一件难事。
2、对于一个数组,我们只能够做两件事:确定该数组的大小,以及获得指向该数组下标为0的元素的指针。其他有关数组的操作,哪怕它们乍看上去是以数组下标进行运算的,实际上都是通过指针进行的。换句话说,任何一个数组下标运算都等同于一个对应的指针运算,因此我们完全可以依据指针行为定义数组下标的行为。
现在考虑下面的例子:inti;int*p;
intcalendar[12][31];
上面声明的calendar是一个数组,该数组拥有12个数组类型的元素,其中的每个元素都是一个拥有31个整型元素的数组。因此,sizeof(calendar)的值是:31×12×sizeof(int)。
考虑一下,calendar[4]的含义是什么?因为calender是一个有着12个数组类型元素的数组,它的每个数组类型元素又是一个有着31个整型元素的数组,所以calendar[4]是calendar数组的第5个元素,是calendar数组中12个有着31个整型元素的数组之一。因此,calendar[4]的行为也表现为一个有着31个整型元素的数组的行为。例如,sizeof(calendar[4])的结果是:31×sizeof(int)。
又如,p=calendar[4];这个语句使指针p指向了数组calendar[4]中下标为0的元素。因为calendar[4]是一个数组,我们可以通过下标的形式来指定这个数组中的元素:i=calendar[4][7],这个语句也可以写成下面这样而表达的意思保持不变:i=*(calendar[4]+7),还可以进一步写成:i=*(*(calendar+4)+7)。
下面我们再看:p=calendar;这个语句是非法的,因为calendar是一个二维数组,即“数组的数组”,在此处的上下文中使用calendar名称会将其转换为一个指向数组的指针。而p是一个指向整型变量的指针,两个指针的类型不一样,所以是非法的。显然,我们需要一种声明指向数组的指针的方法:intcalendar[12][31];int(*monthp)[31];monthp=calendar;
int(*monthp)[31]语句声明的*monthp是一个拥有31个整型元素的数组,因此,monthp就是一个指向这样的数组的指针。monthp指向数组calendar的第一个元素。
指针与数组2(c和指针.P141.)
1、数组的名的值是一个指针常量,不能试图将一个地址赋值给数组名;
2、当数组名作为sizeof操作符的操作数时,sizeof(arrayname)返回的是整个数组的长度,而不是指向
数组的指针的长度;
3、当数组名作为单目操作符&的操作数,取一个数组名的地址所产生的是一个指向数组的指针,而不是一个指
向某个指针常量值的指针。
4、指针和数组并不总是相等的。为了说明这个概念,请考虑下面这两个声明:
inta[5];int*b;
a和b能够互换吗?它们都具有指针值,它们都可以进行间接访问和下标操作。但是,它们还是有很大的区别的:声明一个数组时,编译器将根据声明所指定的元素数量为数组保留内存空间,然后再创建数组名,它的值是一个常量,指向这段空间的起始位置。声明一个指针变量时,编译器只为指针本身保留内存空间,它并不为任何整型值分配内存空间。而且,指针变量并未被初始化为指向任何现有的内存空间,如果它是一个自动变量,它甚至根本不会被初始化。把这两个声明用图的方法表示,可以发现它们之间存在显著的不同:ab?因此,上述声明后,表达式*a是完全合法的,但表达式*b却是非法的。*b将访问内存中某个不确定的位
置,或者导致程序终止。另一方面,表达式b++可以通过编译,但是a++却不能,因为a的值是一个常量。
#include
intmain(){
//注意sizeof(num)的长度应该为10*4=40intnum[]={0,1,2,3,4,5,6,7,8,9};printf("sizeof(num)=%d\\n",sizeof(num));//注意sizeof(str)的长度应该为11,包括字符串后面的"\\0"charstr[]="0123456789";printf("sizeof(str)=%d\\n",sizeof(str));//注意sizeof(str1)的长度应该为10,不包括字符串后面的"\\0",但是,最好将字符串的最后一个字符设定为空charstr1[]={"0","1","2","3","4","5","6","7","8","9"};printf("sizeof(str1)=%d\\n",sizeof(str1));//&num的类型为"int(*)[10]",表示的是一个指向长度为10的整形数组的指针int(*ptoint)[10]=#printf("sizeof(ptoint)=%d,(*ptoint)[9]=%d\\n",sizeof(ptoint),(*ptoint)[9]);//&str的类型为"char(*)[11]",表示的是一个指向长度为11的字符数组的指针,注意str数组的长度是11,而不是10char(*ptostr)[11]=&str;printf("sizeof(ptostr)=%d,(*ptostr)[9]=%c\\n",sizeof(ptostr),(*ptostr)[9]);//由于p指向的是数组num[5],所以对下标取负值后,不会超出数组的正常取值范围//该例子也说明了为什么下标检查在c语言中是一项困难的任务:下标引用可以作用于任意的指针,而不仅仅是数组名//作用于指针的下标引用的有效性即依赖于该指针当时恰好指向什么内容,也依赖于下标的值int*p=num+5;printf("p[-1]=%d,p[0]=%d,p[1]=%d\\n",p[-1],p[0],p[1]);//下面的表达式中,"num[5]和5[num]"的值是一样的,把它们转换成对等的间接访问表达式,它们都等同于*(num+2)//"5[num]"这个古怪的表达式之所以可行,缘于C实现下标的方法。对编译器来说,这两种形式并无差别//但是,决不应该编写形如"5[num]"的表达式,因为它会大大的影响程序的可读性printf("num[5]=%d,5[num]=%d\\n",num[5],5[num]);getchar();
}
return0;
输出结果为:
指针和数组的相同与不同(c专家编程.P199.)
在实际应用中,数组和指针可以互换的情形要比两者不可互换的情形更为常见。让我们分别考虑“声明”和“使用”这两种情况。声明本身还可以进一步分为3种情况:外部数组的声明;
数组的定义(定义是声明的一种特殊情况,它分配内存空间,并可能提供一个初始值);函数参数的声明;
extern,如externchara[];不能改写为指针的形式
声明定义,如chara[10];不能改写为指针的形式数组
函数的参数,可以随意选择数组在表达式中使用的形式或者指针的形式
如c=a[i],可以随意选择数组形式或者是指针形式
也既是:作为函数参数时、在语句或表达式中使用数组时,我们可以采用数组或者指针的任何一种形式,除此之外的其他情况下,指针和数组不要互换。下面就数组和指针相同的情况做详细的说明:规则1、表达式中的数组名被编译器当作一个指向该数组第一个元素的指针。
假如我们声明:inta[10];int*p=a;
就可以通过一下任何一种方式来访问a[i]:
p[i]*(p+i)*(a+i)
事实上,可以采用的方法很多。对数组的引用如a[i]在编译时总是被编译器改写成*(a+i)的形式,C语言标准要求编译器必须具备这个概念性的行为。
编译器自动把下标值的步长调整到数组元素的大小。如果整型数的长度是4个字节,那么a[i+1]和a[i]在内存中的距离就是4。对起始地址执行加法操作之前,编译器会负责计算每次增加的步长。这就是为什么指针总是有类型限制,每个指针只能指向一种类型的原因所在,因为编译器需要知道对指针进行解除引用操作时应该取几个字节,以及每个下标的步长应取几个字节。规则2、下标总是和指针的偏移量相同。
把数组下标作为指针加偏移量是c语言从BCPL(C语言的祖先)继承过来的技巧。在人们的常规思维中,在运行时增加对c语言下标的范围检查是不切实际的。因为取下标操作只是表示将要访问该数组,但并不保证一定要访问。而且程序员完全可以使用指针来访问数组,从而绕过下标操作符。在这种情况下,数组下标范围检测并不能检测所有对数组的访问的情况。事实上,下标范围检测被认为不值得加入到c语言当中。
还有一个说法是,在编写数组算法时,使用指针比使用数组更有效率。这个颇为人们所接收的说法在通常情况下是错误的。使用现代的产品质量优化的编译器,一维数组和指针引用所产生的代码并不具有显著的差别。不管怎样,数组下标是定义在指针的基础上,所以优化器常常可以把它转化为更有效率的指针表达式,并生成相同的机器指令。
规则3、在函数参数的声明中,数组名被编译器当作指向该数组第一个元素的指针。
在函数形参定义这个特殊情况下,编译器必须把数组形式改写成指向数组第一个元素的指针形式。编译器只
向函数传递数组的地址,而不是整个数组的拷贝。这种转换意味着在声明函数的时候,以下三种形式都是合法的(同时无论实参是数组还是真的指针也都是合法的):
my_function(int*turnip){}my_function(intturnip[]){}my_function(intturnip[200]){}
用malloc为字符串分配存储空间时的注意事项(c缺陷与陷阱3.2节)
11
字符串常量(c和指针.P269.)
当一个字符串常量出现在表达式中时,它的值是指针常量。编译器把该字符串的一份拷贝存储在内存的某个位置,并存储一个指向第一个字符的指针。我们可以对字符串常量进行下标引用、间接访问以及指针运算。“xyz”+1
字符串常量实际上是个指针,这个表达式计算“指针值加上1”的值。它的结果也是个指针,指向字符串中的第二个字符y*“xyz”
对一个指针执行间接访问操作时,其结果就是指针所指向的内容。字符串常量的类型是“指向字符的指针”,所以这个间接访问的结果就是它所指向的字符:x。注意表达式的结果并不是整个字符串,而只是它的第一个字符。“xyz”[2]
同样可以推断出上面这个表达式的值就是字符z。
#include
//接受一个无符号整型值,把它转换成字符,并打印出来
//如果是打印16进值的数,可以用这种方法:putchar("0123456789ABCDEF"[value%16])voidbinary_to_ascii(unsignedlongvalue){
unsignedlongquotient;quotient=value/10;if(quotient!=0)binary_to_ascii(quotient);putchar("0123456789"[value%10]);}
intmain(){
//字符串常量实际上是个指针,这个表达式计算"指针值加上1"的值。它的结果也是个指针,
//指向字符串中的第二个字符:yprintf("%s\\n","xyz"+1);
//对一个指针执行间接访问操作时,其结果就是指针所指向的内容。
//字符串常量的类型是"指向字符的指针",所以这个间接访问的结果就是它所指向的字符:xprintf("%c\\n",*"abcdefg");
//同样可以推断出上面这个表达式的值就是字符zprintf("%c\\n","abcdefg"[3]);
binary_to_ascii(1234567);
getchar();
return0;}
用字符串常量初始化指针和数组(c专家编程.P87.)
定义指针时,编译器并不为指针所指的对象分配空间,它只是分配指针本身的空间,除非在定义时同时赋给指针一个字符串常量进行初始化。例如,下面的定义创建一个字符串常量(为其分配内存):char*p=“breadfruit”;
注意只有对字符串常量才是如此。不能指望为浮点数之类的变量分配空间,如:float*pip=3.14;/*错误,无法通过编译*/
在ANSIC中,初始化指针时所创建的字符串常量被定义为只读。如果试图通过指针修改这个字符串值,程序会出现未定义的行为。在有些编译器中,字符串常量被存放在只允许读取的文本段中,以防止它被修改。
数组也可以用字符串常量进行初始化:chara[]=“gooseberry”;
与指针相反,由字符串常量初始化的数组是可以修改的。比如下面的语句:strncpy(a,“black”,5);将数组的值修改为“blackberry”。
#include#include
intmain(void){
char*p="thisisaexample";
//char*pi=3.14;//这样定义是错误的,无法通过编译
//p[0]="T";//修改该字符串常量时,编译是没问题,但是运行时会出现异常
chara[]="gooseberry";strncpy(a,"black",5);
printf("%s\\n",p);printf("%s\\n",a);
return0;}
二维数组下标操作的相关概念(c和指针.P156.)
141516
指向一维、二维数组的指针(c和指针.P158.)
17array_name和&array_name的异同
前者是指向数组中第一个元素的指针,后者是指向整个数组的指针。chara[MAX];/*arrayofMAXcharacters*/char*p=a;/*p为指向数组的指针*/
char*pa=&a;/*该语句是不正确的,pa的类型为"char*",而&a的类型为"char(*)[MAX]’*/char(*pb)[MAX]=&a;/*该语句是正确的,pb的类型为"char(*)[MAX]"*/
#include
voidmain(){
chara[5]={"a","b","c","d","\\0"};char*p=a;
//运行下面这句后,vc6.0提示的错误为:cannotconvertfrom"char(*)[5]"to"char*",&a的类型应该是指向一个数组的指针//char*pa=&a;
//所以,应该定义一个指向相同类型和大小的数组的指针来获得“&a”的值char(*point_to_str)[5];point_to_str=&a;
printf("%d\\n%d\\n",&p,&point_to_str);printf("%s\\n%s\\n",p,point_to_str);}
运行结果为:12450441245040abcdabcd
数组作为函数的参数时,不能通过sizeof运算符得到该数组的大小
当把数组作为函数的参数时,你无法在程序运行时通过数组参数本身告诉函数该数组的大小,因为函数的数组参数相当于指向该数组第一个元素的指针。这意味着把数组传递给函数的效率非常高,也意味着程序员必须通过某种机制告诉函数数组参数的大小。为了告诉函数数组参数的大小,人们通常采用以下两种方法:第一种方法是将数组和表示数组大小的值一起传递给函数,例如memcpy()函数就是这样做的:memcpy(dest,source,length);
第二种方法是引入某种规则来结束一个数组,例如在C语言中字符串总是以ASCII字符NUL("\\0")结束,而一个指针数组总是以空指针结束。请看下述函数,它的参数是一个以空指针结束的字符指针数组,这个空指针告诉该函数什么时候停止工作:
voidprintMany(char*strings[]){
inti=0;
while(strings[i]!=NULL){
puts(strings[i++]);}}
C程序员经常用指针来代替数组下标,因此大多数C程序员通常会将上述函数编写得更隐蔽一些:voidprintMany(char*strings[]){
while(*strings){
puts(*strings++);}}
尽管你不能改变一个数组名的值,但是strings是一个数组参数,相当于一个指针,因此可以对它进行自增运算,并且可以在调用puts()函数时对strings进行自增运算。
用strlen()求字符串的长度(c和指针.P159.)
库函数strlen的原型为:size_tstrlen(charconst*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
intmain(){
charstr1[]="0123456789";charstr2[]="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)=%d\\n",strlen(str1));printf("strlen(str2)=%d\\n",strlen(str2));
printf("(strlen(str1)-strlen(str2)>=0)表达式的值为:%d\\n",strlen(str1)-strlen(str2)>=0);printf(""strlen(str1)-strlen(str2)"的结果是无符号类型,无符号数不可能是负值,所以该条件永远成立\\n");}//注意:sizeof()和strlen()取得的值是不相等的//sizeof()求得的长度包括字符串末尾的那个空字符"\\0"//strlen()求得的长度不包括字符串末尾的空字符printf("sizeof(str1)=%d\\nstrlen(str1)=%d\\n",sizeof(str1),strlen(str1));
getchar();return0;}
‘char**’和‘constchar**’的兼容性问题(c专家编程.P19.)
有时候必须非常专注的阅读ANSIC标准才能找到某个问题的答案。一位销售工程师把下面的代码作为测试例子发给SUN的编译器小组。
#include
voidfoo(constchar**P){}
intmain(intargc,char**argv){
foo(argv);
return0;}
在VC6.0下编译这段代码,编译器会发出警告:
cannotconvertparameter1from"char**"to"constchar**"
提交代码的工程师想知道为什么会产生类似的警告,他认为,实参char*s与形参constchar*p应该是相容的,标准库中所有的字符串处理函数都是这样的。那么,为什么实参char**argv与形参constchar**P实际上不能相容呢?答案是肯定的,它们并不相容。现在我们回顾一下标准中有关简单赋值的部分,它位于ANSIC第6.3.16.1节,描述了下列约束条件:要使上述赋值形式合法,必须满足下列条件之一:
两个操作数都是指向有限定符或无限定符的相容类型的指针,左边指针所指向的类型必须具有右边指针所指向类型的全部限定符。
正是这个条件,使得函数调用中实参char*能够与形参constchar*匹配。它之所以合法,是因为在下面的代码中:char*cp;
constchar*cpp;cpp=cp;
左操作数是一个指向有const限定符的char的指针;右操作数是一个指向没有限定符的char的指针;
char类型和char类型是相容的,左操作数所指向的类型具有右操作数所指向类型的限定符(无),再加上自身的限定符const(注意反过来不能赋值)。
标准第6.3.16.1节没有说明char**实参与constchar**形参是否相容。标准6.1.2.5节中讲述实例的部分声称:constfloat*类型并不是一个有限定符的类型,它的类型是“指向一个具有const限定符的float类型的指针”,也就是说const限定符是修饰指针所指向的类型,而不是指针。类似地,constchar**也是一个没有限定符的指针类型,它的类型是“指向有const限定符的char类型的指针的指针”。由于char**和constchar**都是没有限定符的指针类型,但它们所指向的类型不一样(前者指向char*,后者指向constchar*),因此它们是不相容的。因此类型为char**的实参和类型为constchar**的形参是不相容的,编译器会产生一条诊断信息。
空指针相关的问题(c缺陷与陷阱3.5节)
#include#includeintmain(){char*p=NULL;if(p==(char*)0){printf("pisanullpoint\\n");}else{printf("pisnotanullpoint\\n");}//该语句不会引起编译错误,但是运行时会出现异常if(strcmp(p,(char*)0)==0){printf("can"tdereferencep\\n");}
//该语句不会引起编译错误,但是运行时会出现异常printf("%d",*p);
getchar();return0;}
NULL和NUL的区别
NULL是在头文件中专门为空指针定义的一个宏。NUL是ASCII字符集中第一个字符的名称,它对应于一个零值。C语言中没有NUL这样的预定义宏。注意:在ASCII字符集中,数字0对应于十进制值48,不要把数字0和"\\0"(NUL)的值混同起来。
NULL可以被定义为(void*)0,而NUL可以被定义为"\\0"。NULL和NUL都可以被简单地定义为0,这时它们是等价的,可以互换使用,但这是一种不可取的方式。为了使程序读起来更清晰,维护起来更容易,你在程序中应该明确地将NULL定义为指针类型,而将NUL定义为字符类型。
对指针进行解引用操作可以获得它的值。从定义来看,NULL指针并未指向任何东西。因此,对一个NULL指针进行解引用操作是非法的。在对指针进行解引用操作之前,必须确保它并非NULL指针。
未初始化的指针和NULL指针的区别(c和指针.P95.)
未初始化的指针
NULL指针
26理解函数的声明(c缺陷与陷阱2.1节)
2728
函数参数的传值调用(c和指针.P122.)
#include
charga[]="abcdefghijklm";
voidmy_array_func(charca[10]){
//&ca相当于一个指向字符数组的指针的地址
char**pp=&ca;
printf("&ca=%#x\\n",&ca);printf("ca=%#x\\n",ca);
printf("&(ca[0])=%#x\\n",&(ca[0]));printf("&(ca[1])=%#x\\n",&(ca[1]));
printf("sizeof(ca)=%d\\n\\n",sizeof(ca));}
voidmy_pointer_func(char*pa){
//&pa相当于一个指向字符数组的指针的地址
char**pp=&pa;
printf("&pa=%#x\\n",&pa);printf("pa=%#x\\n",pa);
printf("&(pa[0])=%#x\\n",&(pa[0]));printf("&(pa[1])=%#x\\n",&(pa[1]));
printf("sizeof(pa)=%d\\n\\n",sizeof(pa));}
intmain(){
//&ga相当于一个指向字符数组的指针的地址char(*pp)[14]=&ga;
printf("&ga=%#x\\n",&ga);printf("ga=%#x\\n",ga);
printf("&(ga[0])=%#x\\n",&(ga[0]));printf("&(ga[1])=%#x\\n",&(ga[1]));
printf("sizeof(ga)=%d\\n\\n",sizeof(ga));my_array_func(ga);my_pointer_func(ga);getchar();
return0;}
//摘自《c专家编程》p216页,做了部分修改
运行结果为:
从结果可以看出,数组参数的地址和数组参数的第一个元素的地址是不一样的,比如&ca和ca的值。
函数指针(c和指针.P260.)
31作为函数参数的多维数组(c和指针.P159.)
#include
intfunc1(int*vec){printf("一维数组做为参数:通过"intfunc1(int*vec)"的形式调用函数\\n");return0;}
intfunc2(intvec[]){printf("一维数组做为参数:通过"intfunc2(intvec[])"的形式调用函数\\n");return0;}
voidfunc3(intmat[3][10]){
printf("二维数组做为参数:通过"voidfunc3(intmat[3][10])"的形式调用函数\\n");
return;}
voidfunc4(intmat[][10]){
printf("二维数组做为参数:通过"voidfunc4(intmat[][10])"的形式调用函数\\n");return;}
voidfunc5(int(*mat)[10]){
printf("二维数组做为参数:通过"voidfunc5(int(*mat)[10])"的形式调用函数\\n");return;}
voidfunc6(int**p){
printf("二维数组做为参数:通过"voidfunc6(int**p)"的形式调用函数\\n");return;}
intmain(){
intvector[10]={1,2,3,4,5,6,7,8,9,10};func1(vector);func2(vector);intmatrix[3][10]={{1,2,3,4,5,6,7,8,9,10},{1,2,3,4,5,6,7,8,9,10},{1,2,3,4,5,6,7,8,9,10}};func3(matrix);func4(matrix);func5(matrix);//将matrix参数传递给函数"voidfunc6(int**p)"会出现异常//func6(matrix);
getchar();return0;}
只有当二维数组是一个指向字符串的指针数组时,函数的声明才可以采取char**my_array的形式。这是因为字符串和指针都有一个显示的越界值(分别是NUL和NULL),可以作为结束标记。至于其它的类型,并没有一种类似的通用且可靠的值,所以并没有一种内置的方法知道何时到达数组某一维的结束位置。即使是指向字符串的指针数组,通常也需要一个计数参数argc,以纪录字符串的数量。
#include#include
voidmy_func(char**p){
while(*p!=NULL)printf("%s\\n",*p++);}
intmain(){
char*p[]={"one","two","three","four","five",NULL};
my_func(p);
return0;}
强制类型转换相关概念(c专家编程.P187.)
强制类型转换(cast)这个术语从C语言一诞生就开始使用,即用于类型转换,也用于消除类型歧义。可以很容易地把某种类型的数据强制转换为基本类型的数据:在括号里写上新类型的名称,然后把它们放在需要转化类型的表达式的前面。在强制转换一个更为复杂的类型时,可以采取如下的方法:1、2、3、
作为一个例子,程序员经常发现他们需要强制类型转换以便使用qsort()库函数。这个库函数接收4个参数,其中一个是指向比较函数的指针,qsort()函数的声明如下:
voidqsort(void*buf,size_tnum,size_tsize,int(*comp)(constvoid*ele1,constvoid*ele2));
一个对象的声明,它的类型就是想要转换的结果类型;
删去标识符以及任何类似extern之类的存储限定符,并把剩余的内容放在一对括号里面;把第二步产生的内容放在需要进行类型转换的对象的左边。
当调用qsort函数时,可以向它传递一个你所喜欢的比较函数。你的比较函数将接收实际的数据类型而不是void*参数,就像下面这样:
intintcompare(constint*i,constint*j){return*i-*j;}
这个函数并不与qsort()的comp()参数完全匹配,所以要进行强制类型转换。假定有一个数组a,它具有10个元素,需要对它们进行排序,根据上面列出的3个步骤,可以发现对qsort()的调用将会是下面这个样子:qsort(a,10,sizeof(a[0]),(int(*)(constvoid*,constvoid*))intcompare);
#include#include
//对整型数进行排序
intintcompare(constint*i,constint*j){
return*i-*j;}
//对double类型的数进行排序
intdoublecompare(constdouble*i,constdouble*j){
if(*i>*j)return1;elseif(*i<*j)return-1;elsereturn0;}
intmain(){
intbase[]={3,102,5,-2,98,52,18,56,38,70};inti,len=sizeof(base)/sizeof(base[0]);for(i=0;i printf("\\n"); qsort(base,len,sizeof(base[0]),(int(*)(constvoid*,constvoid*))intcompare);for(i=0;i printf("%d,",base[i]); printf("\\n\\n"); doublebase1[]={1.2,2.6,3.4,8.6,14.3,0.2,7.9,1.6,9.2,10.8,5.55};intlen1=sizeof(base1)/sizeof(base1[0]);for(i=0;i printf("%5.2f,",base1[i]);} printf("\\n"); qsort(base1,len1,sizeof(base1[0]),(int(*)(constvoid*,constvoid*))doublecompare);for(i=0;i printf("%5.2f,",base1[i]); return0;} malloc()、calloc()、realloc() 函数malloc()和calloc()都可以用来分配动态内存空间,但两者稍有区别。malloc()函数有一个参数,即要分配的内存空间的大小: void*malloc(size_tsize); calloc()函数有两个参数,分别为元素的数目和每个元素的大小,两个参数的乘积就是要分配的空间的大小:void*calloc(size_tnumElements,size_tsizeOfElement);如果调用成功,函数malloc()和calloc()都将返回所分配的内存空间的首地址。 malloc()函数和calloc()函数的主要区别是前者不能初始化所分配的内存空间,而后者能。如果由malloc()函数分配的内存空间原来没有被使用过,则其中的每一位可能都是0;反之,如果这部分内存空间曾经被分配、释放和重新分配,则其中可能遗留各种各样的数据。也就是说,使用malloc()函数的程序开始时(内存空间还没有被重新分配)能正常运行,但经过一段时间后(内存空间已被重新分配)可能会出现问题。 calloc()函数会将所分配的内存空间中的每一位都初始化为零,也就是说,如果你是为字符类型或整数类型的元素分配内存,那么这些元素将保证会被初始化为零;如果你是为指针类型的元素分配内存,那么这些元素通常(但无法保证)会被初始化为空指针;如果你是为实数类型的元素分配内存,那么这些元素可能(只在某些计算机中)会被初始化为浮点型的零。 为了明确是为一个数组分配内存空间,有些程序员会选用calloc()函数。但是,除了是否初始化所分配的内存空间这一点之外,绝大多数程序员认为以下两种函数调用方式没有区别:calloc(numElements,sizeOfElement);malloc(numElements*sizeOfElement); 需要解释的一点是,理论上(按照ANSIC标准)指针的算术运算只能在一个指定的数组中进行,但是在实践中,即使C编译程序或翻译器遵循这种规定,许多C程序还是冲破了这种限制。因此,尽管malloc()函数并不能返回一个数组,它所分配的内存空间仍然能供一个数组使用(对realloc()函数来说同样如此,尽管它也不能返回一个数组)。总之,当你在calloc()函数和malloc()函数之间作选择时,你只需考虑是否要初始化所分配的内存空间,而不用考虑函数是否能返回一个数组。 realloc函数用于修改一个原先已经分配的内存块的大小。使用这个函数,你可以使一块内存扩大或者缩小。如果它用于扩大一个内存块,那么这块内存原先的内容依然保留,新增加的内存添加到原先内存块的后面,新内存并未以任何方法进行初始化。如果它用于缩小一个内存块,该内存块尾部的部分内存便被拿掉,剩余部分内存的原先内容依然保留。如果原先的内存块无法改变大小,realloc将分配另外一块正确大小的内存,并把原先那块内存的内容复制到新的块上,因此,在使用realloc之后,就不能再使用指向旧内存的指针,而是应该改用realloc所返回的新指针。 常见的动态内存错误(c和指针.P223.) 在使用动态内存分配的程序中,常常会出现许多错误。这些错误包括对NULL指针进行解引用操作、对分配的内存进行操作时越过边界、释放并非动态分配的内存、试图释放一块动态分配的内存的一部分以及一块动态内存被释放之后还继续使用它。以下是一些需要注意的事项: 1、在请求动态内存分配时,要检查所请求的内存是否成功分配。 2、操作内存时,不要超过动态分配的内存的边界。对分配的内存之外的区域进行访问可能会破坏别的数据,产生一些莫名其妙的很难发现的bug。 3、传递给free的指针必须是一个从malloc、calloc、realloc函数返回的指针。 4、动态分配的内存必须整块一起释放,不允许释放一块动态分配的内存的一部分(realloc函数可以缩小一块动态分配的内存,有效地释放它尾部的部分内存)。 在程序退出main()函数之后,还有可能执行一部分代码吗? 可以,但这要借助C库函数atexit()。利用atexit()函数可以在程序终止前完成一些—清理‖工作如果将指向一组函数的指针传递给atexit()函数,那么在程序退出main()函数后(此时程序还未终止)就能自动调用这组函数。在使用atexit()函数时你要注意这样两点: 第一:由atexit()函数指定的要在程序终止前执行的函数要用关键字void说明,并且不能带参数;第二:由atexit()函数指定的函数在入栈时的顺序和调用atexit()函数的顺序相反,即它们在执行时遵循后进先出(LIFO)的原则。 #include#include voidmy_exit1(void){ printf("my_exit1()function!\\n");} voidmy_exit2(void){ printf("my_exit2()function!\\n");} voidmain(){ atexit(my_exit1);atexit(my_exit2); printf("now,eixtthisprogram...\\n");} 输出结果为: now,eixtthisprogram...my_exit2()function!my_exit1()function! 总线错误和段错误相关概念(c专家编程.P157.) 在UNIX上编程时,经常会遇到如下两个常见的运行时错误:buserror (总线错误) segmentationfault(段错误) 总线错误 总线错误几乎都是由于未对齐的读或写造成的。它之所以称为总线错误,是因为出现未对齐的内存访问请求时,被堵塞的组件就是地址总线。对齐的意思就是数据项只能存储在地址是数据项大小的整数倍的内存位置上。在现代的计算机架构中,尤其是RISC架构,都需要数据对齐,因为与任意的对齐有关的额外逻辑会使整个内存系统更大且更慢。通过迫使每个内存访问局限在一个cache行或一个单独的页面内,可以极大地简化如cache控制器或内存管理单元这样的硬件。 我们表达“数据项不能跨越页面或cache边界”规则的方法多少有些间接,因为我们用地址对齐这个术语来陈述这个问题,而不是直截了当说是禁止内存跨页访问,但它们说的是同一回事。例如,访问一个8字节的double数据时,地址只允许是8的整数倍。所以一个double数据可以存储于地址24、8008、32768,但不能存储于地址1006,页和cache的大小是经过精心设计的,这样只要遵守对齐规则就可以保证一个原子数据项不会跨越一个页或cache块的边界。 段错误 段错误通常是由于解除引用一个未初始化或非法值的指针引起的。以发生频率为序,最终可能导致段错误的常见编程错误是: 1、坏指针错误:在指针赋值之前就用它来引用内存;或者向库函数传递一个坏指针(如果调试器显示系统程序中出现了段错误,很可能并不是系统程序引起的段错误,问题可能就出现在自己的代码中);或者指针被释放后还继续访问它的内容。 2、改写错误:越过数组边界写入数据,在动态分配的内存空间以外写入数据,或改写一些堆管理数据结构(在动态分配的内存之前的区域写入数据就很容易发生这种情况)。 3、指针释放引起的错误:释放同一块内存两次,或释放一块未曾使用malloc分类的内存,或释放一个无效的指针。一个极为常见的与释放内存有关的错误就是在for(p=start;p;p=p->next)这样的循环中迭代一个链表,并在循环体内使用free(p)这样的语句。这样,在下一次循环迭代时,程序就会对已经释放的指针进行解除引用操作,从而导致不可预料的结果。 怎样判断一个字符是数字、字母或其它类别的符号? 在头文件ctype.h中定义了一批函数,它们可用来判断一个字符属于哪一类别。下面列出了这些函数:-----------------------------------------------------------------------------函数字符类别返回非零值的字符 -----------------------------------------------------------------------------isdigit()十进制数0--9 isxdigit()十六进制数0--9,af,或A--Fisalnum()字母数字符号0--9,a--Z,或A--Zisalpha()字母az或A--Zislower()小写字母azisupper()大写字母A--Z isspace()空白符空格符,水平制表符,垂直制表符,换行符,换页符,或回车符isgraph()非空白字符任何打印出来不是空白的字符(ASCII码从21到7E)isprint()可打印字符 所有非空白字符,加上空格符 ispunct()标点符除字母数字符号以外的所有非空白字符 iscntrl()控制字符除可打印字符外的所有字符(ASCII码从00到1F,加上7F)------------------------------------------------------------------------------调用上述这些宏而不是自己编写测试字符类别的程序有三点好处:首先,这些宏运算速度快,因为它们的实现方式通常都是利用位屏蔽技术来检查一个表,所以即使是进行一项相当复杂的检查,也比真正去比较字符的值要快得多。其次,这些宏都是正确的。如果你自己编写一个测试程序,你很容易犯逻辑上或输入上的错误,例如引入了一个错误的字符(或漏掉了一个正确的字符)。第三,这些宏是可移植的。信不信由你,并非所有的人都使用同样的含PC扩充字符的ASCII字符集。也许今天你还不太在意,但是,当你发现你的下一台计算机使用的是Unicode字符集而不是ASCII字符集,你就会庆幸自己原来没有按照字符集中的字符值来编写程序。 其他字符转换函数: isascii(测试字符是否为ASCII码字符) intisascii(intc); 检查参数c是否为ASCII码字符,也就是判断c的范围是否在0到127之间。若参数c为ASCII码字符,则返回TRUE,否则返回NULL(0)。 toascii(将整型数转换成合法的ASCII码字符) inttoascii(intc); toascii()会将参数c转换成7位的unsignedchar值,第八位则会被清除,此字符即会被转成ASCII码字符。将转换成功的ASCII码字符值返回。 tolower(将大写字母转换成小写字母) inttolower(intc); 若参数c为大写字母则将该对应的小写字母返回。返回转换后的小写字母,若不须转换则将参数c值返回。 toupper(将小写字母转换成大写字母) inttoupper(intc); 若参数c为小写字母则将该对映的大写字母返回。返回转换后的大写字母,若不须转换则将参数c值返回。 以isalmun为例,说明这些函数的用法[剩余的其他函数跟它类似]:intisalnum(intc) 检查参数c是否为英文字母或阿拉伯数字,若参数c为字母或数字,则返回TRUE,否则返回NULL。此为宏定义,非真正函数。 #include#includeintmain(){ charstr[]="123c@#FDsP[e?"; for(inti=0;str[i]!=0;i++){ if(isalnum(str[i])) printf("%cisanalphanumericcharacter\\n",str[i]);} return0;} 怎样将数字转换为字符串? C语言提供了几个标准库函数,可以将任意类型(整型、长整型、浮点型等)的数字转换为字符串。以下是用itoa() #include#include intmain(){ intnum=435443435;charstr[30]; itoa(num,str,10); printf("Thenumber"num"is%dandthestring"str"is%s.\\n",num,str);getchar();return0;} 函数将整数转换为字符串的一个例子: itoa()函数有3个参数:第一个参数是要转换的数字,第二个参数是要写入转换结果的目标字符串,第三个参数是转移数字时所用的基数。在上例中,转换基数为10。 ----------------------------------------------------------函数名作用 ----------------------------------------------------------itoa()将整型值转换为字符串ltoa()将长整型值转换为字符串ultoa()将无符号长整型值转换为字符串 ---------------------------------------------------------- 请注意,上述函数与ANSI标准是不兼容的。能将整数转换为字符串而且与ANSI标准兼容的方法是使用sprintf()函数,请看下例: #include#include intmain(){ intnum=123456;charstr[50]; sprintf(str,"%d",num); printf("Thenumber"num"is%dandthestring"str"is%s.\\n",num,str); getchar();return0;} gcvt(将浮点型数转换为字符串,取四舍五入) char*gcvt(doublenumber,size_tndigits,char*buf); gcvt()用来将参数number转换成ASCII码字符串,参数ndigits表示显示的位数。gcvt()与ecvt()和fcvt()不同的地方在于,gcvt()所转换后的字符串包含小数点或正负符号。若转换成功,转换后的字符串会放在参数buf指针所指的空间。该函数返回一字符串指针,此地址即为buf指针。 ecvt():将双精度浮点型值转换为字符串,转换结果中不包含十进制小数点fcvt():以指定位数为转换精度,其余同ecvt() #include#include intmain(){ doublea=123.45546;doubleb=-1234.56;charptr[20]={0}; gcvt(a,7,ptr); printf("avalue=%s\\n",ptr);char*p=gcvt(b,5,ptr);printf("bvalue=%s\\n",p);} 怎样将字符串转换为数字? 下列函数可以将字符串转换为数字: ------------------------------------------------------------------------函数名 作用 ------------------------------------------------------------------------atof()将字符串转换为双精度浮点型值atoi()将字符串转换为整型值atol()将字符串转换为长整型值 strtod()将字符串转换为双精度浮点型值,并报告不能被转换的所有剩余数字strtol()将字符串转换为长整值,并报告不能被转换的所有剩余数字strtoul()将字符串转换为无符号长整型值,并报告不能被转换的所有剩余数字 ------------------------------------------------------------------------atof(将字符串转换成浮点型数)doubleatof(constchar*nptr); atof()会扫描参数nptr字符串,跳过前面的空格字符,直到遇上数字或正负符号才开始做转换,而再遇到非数字或字符串结束时("\\0")才结束转换,并将结果返回。参数nptr字符串可包含正负号、小数点或E(e)来表示指数部分,如123.456或123e-2。该函数返回转换后的浮点型数。atof()与使用strtod(nptr,(char**)NULL)结果相同。atoi(将字符串转换成整型数)intatoi(constchar*nptr); atoi()会扫描参数nptr字符串,跳过前面的空格字符,直到遇上数字或正负符号才开始做转换,而再遇到非数字或字符串结束时("\\0")才结束转换,并将结果返回。该函数返回转换后的整型数。atoi()与使用strtol(nptr,(char**)NULL,10);结果相同。atol(将字符串转换成长整型数)longatol(constchar*nptr); atol()会扫描参数nptr字符串,跳过前面的空格字符,直到遇上数字或正负符号才开始做转换,而再遇到非数字或字符串结束时("\\0")才结束转换,并将结果返回。该函数返回转换后的长整型数。atol()与使用strtol(nptr,(char**)NULL,10);结果相同。 #include#include intmain(){ char*a0="-1000";char*b0="456"; intc0=atoi(a0)+atoi(b0); printf("atoi:c0=%d\\n",c0); char*a1="-100.23";char*b1="200e-2"; floatc1=atof(a1)+atof(b1);printf("atof:c1=%f\\n",c1); char*a2="4563453";char*b2="4563431237"; longc2=atol(a2)+atol(b2);printf("atol:c2=%ld\\n",c2);} strtod(将字符串转换成浮点数) doublestrtod(constchar*nptr,char**endptr); strtod()会扫描参数nptr字符串,跳过前面的空格字符,直到遇上数字或正负符号才开始做转换,到出现非数字或字符串结束时("\\0")才结束转换,并将结果返回。若endptr不为NULL,则会将遇到不合条件而终止的nptr中的字符指针由endptr传回。参数nptr字符串可包含正负号、小数点或E(e)来表示指数部分。如123.456或123e-2。该函数返回转换后的浮点型数。strtol(将字符串转换成长整型数) longintstrtol(constchar*nptr,char**endptr,intbase); strtol()会将参数nptr字符串根据参数base来转换成长整型数。参数base范围从2至36,或0。参数base代表采用的进制方式,如base值为10则采用10进制,若base值为16则采用16进制等。当base值为0时则是采用10进制做转换,但遇到如"0x"前置字符则会使用16进制做转换。一开始strtol()会扫描参数nptr字符串,跳过前面的空格字符,直到遇上数字或正负符号才开始做转换,再遇到非数字或字符串结束时("\\0")结束转换,并将结果返回。若参数endptr不为NULL,则会将遇到不合条件而终止的nptr中的字符指针由endptr返回。该函数返回转换后的长整型数,否则返回ERANGE并将错误代码存入errno中(ERANGE指定的转换字符串超出合法范围) strtoul(将字符串转换成无符号长整型数) unsignedlongintstrtoul(constchar*nptr,char**endptr,intbase); strtoul()会将参数nptr字符串根据参数base来转换成无符号的长整型数。参数base范围从2至36,或0。参数base代表采用的进制方式,如base值为10则采用10进制,若base值为16则采用16进制数等。当base值为0时则是采用10进制做转换,但遇到如"0x"前置字符则会使用16进制做转换。一开始strtoul()会扫描参数nptr字符串,跳过前面的空格字符串,直到遇上数字或正负符号才开始做转换,再遇到非数字或字符串结束时("\\0")结束转换,并将结果返回。若参数endptr不为NULL,则会将遇到不合条件而终止的nptr中的字符指针由endptr返回。该函数返回转换后的长整型数,否则返回ERANGE并将错误代码存入errno中(ERANGE指定的转换字符串超出合法范围) #include#includeintmain(){ chara[]="1000000000abcde";charb[]="1000000000abcde";charc[]="ffffOPQRST";char**p; printf("a=%d,p=%s\\n",strtol(a,p,10),*p);printf("b=%d,p=%s\\n",strtol(b,p,2),*p);printf("c=%d,p=%s\\n",strtol(c,p,16),*p);} 输出结果为: a=1000000000,p=abcdeb=512,p=abcdec=65535,p=OPQRST 字符串拷贝和内存拷贝函数 strcpy(拷贝字符串) 定义函数:char*strcpy(char*dest,constchar*src); strcpy()函数只能拷贝字符串。strcpy()函数将源字符串src的每个字节拷贝到目的字符串dest中,src字符串末尾的"\\0"也被拷贝过去。strcpy()函数返回参数dest的起始地址。如果参数dest所指的内存空间不够大,可能会造成缓冲溢出的错误情况(程序员必须保证目标字符数组的空间足够容纳需要复制的字符串。如果src字符串比dest字符串长,多余的字符仍将被复制,它们将覆盖原先存储于dest数组后面的内存空间的值),在编写程序时请特别留意,或者用strncpy()来取代。如果参数src和dst在内存中出现重叠,其结果是未定义的。 strncpy(拷贝字符串) 定义函数:char*strncpy(char*dest,constchar*src,size_tn); strncpy()会将参数src字符串拷贝前n个字符至参数dest所指的地址。函数返回参数dest的字符串起始地址。 注意n的取值范围,不要超过src和dest的长度。 #include#include intmain(){ chara1[30]="string(1)";charb1[]="STRING(2)"; printf("beforestrcpy():%s\\n",a1); printf("afterstrcpy():%s\\n",strcpy(a1,b1)); chara2[30]="string(1)";charb2[]="STRING(2)"; printf("beforestrncpy():%s\\n",a2); printf("afterstrncpy():%s\\n",strncpy(a2,b2,6));} memcpy(拷贝内存内容) 定义函数:void*memcpy(void*dest,constvoid*src,size_tn); memcpy()用来拷贝src所指的内存内容前n个字节到dest所指的内存地址上。与strcpy()不同的是,memcpy()会完整的复制n个字节,不会因为遇到字符串结束"\\0"而结束。memcpy()函数可以拷贝任意类型的数据。memcpy()函数返回指向dest的指针。指针src和dest所指的内存区域不可重叠。在拷贝字符串时,通常都使用strcpy()函数;在拷贝其它数据(例如结构)时,通常都使用memcpy()函数。memmove(拷贝内存内容) 定义函数:void*memmove(void*dest,constvoid*src,size_tn); memmove()与memcpy()一样都是用来拷贝src所指的内存内容前n个字节到dest所指的地址上。不同的是,当src和dest所指的内存区域重叠时,memmove()仍然可以正确的处理,不过执行效率上会比使用memcpy()略慢些。该函数返回指向dest的指针。 #includeintmain(){ chara[30]="string(1)";charb[]="string(2)"; printf("beforestrcpy():%s\\n",a); printf("afterstrcpy():%s\\n",strcpy(a,b)); a[30]="string(1)";b[]="string(2)"; printf("beforestrncpy():%s\\n",a); printf("afterstrncpy():%s\\n",strncpy(a,b,6));} memccpy(拷贝内存内容) 定义函数:void*memccpy(void*dest,constvoid*src,intc,size_tn);memccpy()用来拷贝src所指的内存内容前n个字节到dest所指的地址上。与memcpy()不同的是,memccpy()会在复制时检查参数c是否出现,若是则返回dest中值为c的下一个字节地址。该函数返回指向dest中值为c的下一个字节指针。返回值为NULL表示在src所指内存前n个字节中没有值为c的字节。 #include#includeintmain(){ chara[]="string(a)";charb[]="string(b)";char*p; p=(char*)memccpy(a,b,"k",sizeof(b)); if(p==NULL){ //注意p为NULL的情况,这时不能读取p所指的地方的内容printf("thereturnpointerofmymccpyisnull!\\n");}else{ printf("memccpy():%s,*p=%c\\n",a,*p);}} bcopy(拷贝内存内容) 定义函数:voidbcopy(constvoid*src,void*dest,intn); bcopy()与memcpy()一样都是用来拷贝src所指的内存内容前n个字节到dest所指的地址,不过参数src与dest在传给函数时是相反的位置。建议使用memcpy()取代。 字符串和内存数据比较函数 strcmp(比较字符串) intstrcmp(constchar*s1,constchar*s2); strcmp()用来比较参数s1和s2字符串。字符串大小的比较是以ASCII码表上的顺序来决定,此顺序亦为字符的值。strcmp()首先将s1第一个字符值减去s2第一个字符值,若差值为0则再继续比较下个字符,若差值不为0则将差值返回。例如字符串"Ac"和"ba"比较则会返回字符"A"(65)和"b"(98)的差值(-33)。若参数s1和s2字符串相同则返回0。s1若大于s2则返回大于0的值。s1若小于s2则返回小于0的值。 strcoll(采用目前区域的字符排列次序来比较字符串)intstrcoll(constchar*s1,constchar*s2); strcoll()会依环境变量LC_COLLATE所指定的字符排列次序来比较s1和s2字符串。若参数s1和s2字符串相同则返回0,s1若大于s2则返回大于0的值,s1若小于s2则返回小于0的值。若LC_COLLATE为"POSIX"或"C",则strcoll()与strcmp()作用完全相同。 strcasecmp(忽略大小写比较字符串) intstrcasecmp(constchar*s1,constchar*s2); strcasecmp()用来比较参数s1和s2字符串,比较时会自动忽略大小写的差异。若参数s1和s2字符串相同则返回0,s1长度大于s2长度则返回大于0的值,s1长度若小于s2长度则返回小于0的值。 strncasecmp(忽略大小写比较字符串) intstrncasecmp(constchar*s1,constchar*s2,size_tn); strncasecmp()用来比较参数s1和s2字符串前n个字符,比较时会自动忽略大小写的差异。若参数s1和s2字符串相同则返回0。s1若大于s2则返回大于0的值,s1若小于s2则返回小于0的值。 strcmpi()或stricmp():对两个字符串进行大小写不敏感的比较。strncmp():对两个字符串的一部分进行大小写敏感的比较。strnicmp():对两个字符串的一部分进行大小写不敏感的比较。 #include#include intmain(){ char*a="aBcDeF";char*b="AbCdEf";char*c="aacdef";char*d="ABCDEF"; printf("strcmpi(a,b):%d\\n",strcmpi(a,b));printf("stricmp(a,b):%d\\n",stricmp(a,b));printf("strcmp(a,d):%d\\n",strcmp(a,d)); printf("strncmp(a,b,3):%d\\n",strncmp(a,b,3));printf("strnicmp(a,b,3):%d\\n",strnicmp(a,b,3)); getchar();return0;} memcmp(比较内存内容) intmemcmp(constvoid*s1,constvoid*s2,size_tn); memcmp()用来比较s1和s2所指的内存区间前n个字符。字符串大小的比较是以ASCII码表上的顺序来决定。memcmp()首先将s1第一个字符值减去s2第一个字符的值,若差为0则再继续比较下个字符,若差值不为0则将差值返回。例如,字符串"Ac"和"ba"比较则会返回字符"A"(65)和"b"(98)的差值(-33)。若参数s1和s2所指的内存内容都完全相同则返回0值。s1若大于s2则返回大于0的值。s1若小于s2则返回小于0的值。 bcmp(比较内存内容) intbcmp(constvoid*s1,constvoid*s2,intn); bcmp()用来比较s1和s2所指的内存区间前n个字节,若参数n为0,则返回0。若参数s1和s2所指的内存内容都完全相同则返回0值,否则返回非零值。建议使用memcmp()取代。 #include#includeintmain(){ char*a="aBcDeF";char*b="AbCdEf";char*c="aacdef";char*d="aBcDeF"; printf("memcmp(a,b):%d\\n",memcmp((void*)a,(void*)b,6));printf("memcmp(a,c):%d\\n",memcmp((void*)a,(void*)c,6));printf("memcmp(a,d):%d\\n",memcmp((void*)a,(void*)d,6));} 友情提示:本文中关于《《C专家编程》总结》给出的范例仅供您参考拓展思维使用,《C专家编程》总结:该篇文章建议您自主创作。 来源:网络整理 免责声明:本文仅限学习分享,如产生版权问题,请联系我们及时删除。
《《C专家编程》总结》由互联网用户整理提供,转载分享请保留原作者信息,谢谢!
链接地址:http://www.bsmz.net/gongwen/748323.html