C语言.docx
《C语言.docx》由会员分享,可在线阅读,更多相关《C语言.docx(17页珍藏版)》请在冰点文库上搜索。
C语言
1.不用库函数实现字符串的操作
(1)strcpy函数
⒈strcpy的实现代码
char*strcpy(char*strDest,constchar*strSrc)
{char*strDestCopy=strDest;//[3]
if((NULL==strDest)||(NULL==strSrc))//[1]
throw"Invalidargument(s)";//[2]
while((*strDest++=*strSrc++)!
='\0');//[4]
returnstrDestCopy;}
错误的做法:
[1]
(A)不检查指针的有效性,说明答题者不注重代码的健壮性。
(B)检查指针的有效性时使用((!
strDest)||(!
strSrc))或(!
(strDest&&strSrc)),说明答题者对C语言中类型的隐式转换没有深刻认识。
在本例中char*转换为bool即是类型隐式转换,这种功能虽然灵活,但更多的是导致出错概率增大和维护成本升高。
所以C++专门增加了bool、true、false三个关键字以提供更安全的条件表达式。
(C)检查指针的有效性时使用((strDest==0)||(strSrc==0)),说明答题者不知道使用常量的好处。
直接使用字面常量(如本例中的0)会减少程序的可维护性。
0虽然简单,但程序中可能出现很多处对指针的检查,万一出现笔误,编译器不能发现,生成的程序内含逻辑错误,很难排除。
而使用NULL代替0,如果出现拼写错误,编译器就会检查出来。
[2]
(A)returnnewstring("Invalidargument(s)");,说明答题者根本不知道返回值的用途,并且他对内存泄漏也没有警惕心。
从函数中返回函数体内分配的内存是十分危险的做法,他把释放内存的义务抛给不知情的调用者,绝大多数情况下,调用者不会释放内存,这导致内存泄漏。
(B)return0;,说明答题者没有掌握异常机制。
调用者有可能忘记检查返回值,调用者还可能无法检查返回值(见后面的链式表达式)。
妄想让返回值肩负返回正确值和异常值的双重功能,其结果往往是两种功能都失效。
应该以抛出异常来代替返回值,这样可以减轻调用者的负担、使错误不会被忽略、增强程序的可维护性。
[3]
(A)忘记保存原始的strDest值,说明答题者逻辑思维不严密。
[4]
(A)循环写成while(*strDestCopy++=*strSrc++);,同[1](B)。
(B)循环写成while(*strSrc!
='\0')*strDest++=*strSrc++;,说明答题者对边界条件的检查不力。
循环体结束后,strDest字符串的末尾没有正确地加上'\0'。
⒉返回strDest的原始值使函数能够支持链式表达式,增加了函数的“附加值”。
同样功能的函数,如果能合理地提高的可用性,自然就更加理想。
链式表达式的形式如:
intiLength=strlen(strcpy(strA,strB));
又如:
char*strA=strcpy(newchar[10],strB);
返回strSrc的原始值是错误的。
其一,源字符串肯定是已知的,返回它没有意义。
其二,不能支持形如第二例的表达式。
其三,为了保护源字符串,形参用const限定strSrc所指的内容,把constchar*作为char*返回,类型不符,编译报错。
在上面的语句中,循环语句
while((*strDestCopy++=*strSrc++)!
='\0');
较难理解,可以把这句理解为以下操作。
第一种:
while
(1){chartemp;temp=*strDestCopy=*strSrc;strDestCopy++;strSrc++;if('\0'==temp)break;}
第二种:
while(*strSrc!
='\0')
{
*strDestCopy=*strSrc;
strDestCopy++;
strSrc++;
}
*strDestCopy=*strSrc++;
也即:
while(*strSrc!
='\0')
{
*strDestCopy++=*strSrc++;
}
*strDestCopy=‘\0’;
(2)strcmp函数
intstrcmp(constchar*str1,constchar*str2)
{
/*不可用while(*str1++==*str2++)来比较,当不相等时仍会执行一次++,return返回的比较值实际上是下一个字符。
应将++放到循环体中进行。
*/
while(*str1==*str2)
{
if(*str1=='\0')
return0;
str1++;
str2++;
}
return*str1-*str2;
}
(3)strcat函数
//将源字符串加const,表明其为输入参数
char*strcat(char*strDest,constchar*strSrc)
{
//后文returnaddress,故不能放在assert断言之后声明address
char*address=strDest;
assert((strDest!
=NULL)&&(strSrc!
=NULL));//对源地址和目的地址加非0断言
while(*strDest)//是while(*strDest!
=’\0’)的简化形式
{
//若使用while(*strDest++),则会出错,因为循环结束后strDest还会执行一次++,
//那么strDest将指向'\0'的下一个位置。
/所以要在循环体内++;因为要是*strDest最后指
//向该字符串的结束标志’\0’。
strDest++;
}
while(*strDest++=*strSrc++)
{
NULL;//该循环条件内可以用++,
}//此处可以加语句*strDest=’\0’;无必要
returnaddress;//为了实现链式操作,将目的地址返回
}
(4)strlen()函数与sizeof的区别
strlen(char*)函数求的是字符串的实际长度,它求得方法是从开始到遇到第一个'\0',如果你只定义没有给它赋初值,这个结果是不定的,它会从aa首地址一直找下去,直到遇到'\0'停止。
charaa[10];cout<charaa[10]={'\0'};cout<charaa[10]="jun";cout<而sizeof()返回的是变量声明后所占的内存数,不是实际长度,此外sizeof不是函数,仅仅是一个操作符,strlen是函数。
sizeof(aa)返回10
inta[10];sizeof(a)返回40(根据语言int型c是两个字节c++是四个java是两个)
⒈sizeof操作符的结果类型是size_t,它在头文件中typedef为unsigned int类型。
该类型保证能容纳实现所建立的最大对象的字节大小。
⒉sizeof是操作符(关键字),strlen是函数。
⒊sizeof可以用类型做参数,strlen只能用char*做参数,且必须是以''\0''结尾的。
sizeof还可以用函数做参数,比如:
shortf();
printf("%d\n",sizeof(f()));
输出的结果是sizeof(short),即2。
⒋数组做sizeof的参数不退化,传递给strlen就退化为指针了。
⒌大部分编译程序在编译的时候就把sizeof计算过了是类型或是变量的长度这就是sizeof(x)可以用来定义数组维数的原因
charstr[20]="0123456789";
inta=strlen(str);//a=10;
intb=sizeof(str);//而b=20;
6.strlen的结果要在运行的时候才能计算出来,是用来计算字符串的长度,不是类型占内存的大小。
7.sizeof后如果是类型必须加括弧,如果是变量名可以不加括弧。
这是因为sizeof是个操作符不是个函数。
⒏当适用了于一个结构类型时或变量,sizeof返回实际的大小,
当适用一静态地空间数组,sizeof归还全部数组的尺寸。
sizeof操作符不能返回动态地被分派了的数组或外部的数组的尺寸
⒐数组作为参数传给函数时传的是指针而不是数组,传递的是数组的首地址,
如:
fun(char[8])
fun(char[])
都等价于fun(char*)
在C++里参数传递数组永远都是传递指向数组首元素的指针,编译器不知道数组的大小
如果想在函数内知道数组的大小,需要这样做:
进入函数后用memcpy拷贝出来,长度由另一个形参传进去
fun(unsigedchar*p1,intlen){unsignedchar*buf=newunsignedchar[len+1]memcpy(buf,p1,len);}
我们能常在用到sizeof和strlen的时候,通常是计算字符串数组的长度
看了上面的详细解释,发现两者的使用还是有区别的,从这个例子可以看得很清楚:
charstr[20]="0123456789";
inta=strlen(str);//a=10;>>>>strlen计算字符串的长度,以结束符0x00为字符串结束。
intb=sizeof(str);//而b=20;>>>>sizeof计算的则是分配的数组str[20]所占的内存空间的大小,不受里面存储的内容改变。
上面是对静态数组处理的结果,如果是对指针,结果就不一样了
char*ss="0123456789";
sizeof(ss)结果4>>>>ss是指向字符串常量的字符指针,sizeof获得的是一个指针的值所占的空间,应该是长整型的,所以是4
sizeof(*ss)结果1>>>>*ss是第一个字符其实就是获得了字符串的第一位'0'所占的内存空间,是char类型的,占了1位
strlen(ss)=10>>>>如果要获得这个字符串的长度,则一定要使用strlen
sizeof返回对象所占用的字节大小.//正确
strlen返回字符个数.//正确
在使用sizeof时,有一个很特别的情况,就是数组名到指针蜕变,
charArray[3]={'0'};
sizeof(Array)==3;
char*p=Array;
strlen(p)==1;//sizeof(p)结果为4
在传递一个数组名到一个函数中时,它会完全退化为一个指针
看完以上你是否很清楚sizeof和strlen的区别了呢?
还不明白的话,我们看下面几个例子:
第一个例子
char*ss="0123456789";
sizeof(ss)结果4===》ss是指向字符串常量的字符指针
sizeof(*ss)结果1===》*ss是第一个字符
大部分编译程序在编译的时候就把sizeof计算过了是类型或是变量的长度
这就是sizeof(x)可以用来定义数组维数的原因
charstr[20]="0123456789";
inta=strlen(str);//a=10;
intb=sizeof(str);//而b=20;
charss[]="0123456789";
sizeof(ss)结果11===》ss是数组,计算到\0位置,因此是10+1
sizeof(*ss)结果1===》*ss是第一个字符
charss[100]="0123456789";
sizeof(ss)结果是100===》ss表示在内存中的大小100×1
strlen(ss)结果是10===》strlen是个函数,内部实现是用一个循环计算到\0之前为止
intss[100]="0123456789";
sizeof(ss)结果400===》ss表示在内存中的大小100×4
strlen(ss)错误===》strlen的参数只能是char*且必须是以'\0'结尾的
charq[]="abc";
charp[]="a\n";
sizeof(q),sizeof(p),strlen(q),strlen(p);
结果是4332
第二个例子
classX{inti;intj;chark;};Xx;
cout<cout<第三个例子
charszPath[MAX_PATH]
如果在函数内这样定义,那么sizeof(szPath)将会是MAX_PATH,但是将szPath作为虚参声明时(voidfun(charszPath[MAX_PATH])),sizeof(szPath)却会是4(指针大小)
还有一位网友的说明也很好:
其实理解sizeof只需要抓住一个要点:
栈
程序存储分布有三个区域:
栈、静态和动态。
能够从代码直接操作的对象,包括任何类型的变量、指针,都是在栈上的;动态和静态存储区是靠栈上的指针来间接操作的。
sizeof操作符,计算的是对象在栈上的投影体积;记住这个就很多东西都很清楚了。
charconst*static_string="Hello";
sizeof(static_string)是sizeof一个指针,所以在32bitsystem是4
charstack_string[]="Hello";
sizeof(stack_string)是sizeof一个数组,所以是6*sizeof(char)
char*string=newchar[6];
strncpy(string,"Hello",6");
sizeof(string)是sizeof一个指针,所以还是4。
和第一个不同的是,这个指针指向了动态存储区而不是静态存储区。
不管指针指向的内容在什么地方,sizeof得到的都是指针的栈大小
C++中对引用的处理比较特殊;sizeof一个引用得到的结果是sizeof一个被引用的对象的大小;所以
structO{inta,b,c,d,e,f,g,h;};intmain(){O&r=*newO;cout<r引用的是整个的O对象而不是指向O的指针,所以sizeofr的结果和sizeofO完全相同。
下面几种实现strlen函数的源代码大家参考
例1
1
2
3
4
5
6
7
8
9
#include
#include
Typedefunsignedintu_int;
u_intMystrlen(constchar*str)
{
u_inti;
assert(str!
=NULL);
for(i=0;str[i]!
='\0';i++);
returni;
}
例2
1
2
3
4
5
6
7
intstrlen(constchar*str)
{
assert(str!
=NULL);
intlen=0;
while((*str++)!
='\0')
len++;
returnlen;
}
例3
1
2
3
4
5
6
intstrlen(constchar*str)
{
assert(str);
constchar*p=str;
while(*p++!
=NULL);
returnp-str-1;
}
例4
1
2
3
4
5
6
7
intstrlen(constchar*str)
{
assert(str);
if(*str==NULL)
return0;
else
return(1+strlen(++str));
}
例5
1
2
3
4
5
6
/***strlen-Findthelengthofastring*@s:
Thestringtobesized*/
size_tstrlen(constchar*s)
{
constchar*sc;
for(sc=s;*sc!
='\0';++sc)/*nothing*/;
returnsc-s;
}
2.内存的操作
(1)malloc()函数
原型为externvoid*malloc(unsignedintnum_bytes)。
动态内存分配,包含在malloc.h或者stdlib.h(vc++6.0)中。
分配长度为num_bytes字节的内存块。
如果分配成功则返回指向被分配内存的指针(此存储区中的初始值不确定),否则返回空指针NULL。
当内存不再使用时,应使用free()函数将内存块释放。
函数返回的指针一定要适当对齐,使其可以用于任何数据对象。
[1]malloc()函数与new的区别
从本质上来说,malloc(Linux上具体实现可以参考manmalloc,glibc通过brk()&mmap()实现)是libc里面实现的一个函数,如果在sourcecode中没有直接或者间接include过stdlib.h,那么gcc就会报出error:
‘malloc’wasnotdeclaredinthisscope。
如果生成了目标文件(假定动态链接malloc),如果运行平台上没有libc(Linux平台,手动指定LD_LIBRARY_PATH到一个空目录即可),或者libc中没有malloc函数,那么会在运行时(Run-time)出错。
new则不然,是c++的关键字,它本身不是函数。
new不依赖于头文件,c++编译器就可以把new编译成目标代码(g++4.6.3会向目标中插入_Znwm这个函数,另外,编译器还会根据参数的类型,插入相应的构造函数)。
在使用上,malloc和new至少有两个不同:
new返回指定类型的指针,并且可以自动计算所需要大小。
比如:
1
2
3
int*p;
p=newint;
//返回类型为int*类型(整数型指针),分配大小为sizeof(int);
或:
1
2
3
int*parr;
parr=newint[100];
//返回类型为int*类型(整数型指针),分配大小为sizeof(int)*100;
而malloc则必须要由我们计算字节数,并且在返回后强行转换为实际类型的指针。
1
2
3
4
5
6
7
int*p;
p=(int*)malloc(sizeof(int)*128);
//分配128个(可根据实际需要替换该数值)整型存储单元,
//并将这128个连续的整型存储单元的首地址存储到指针变量p中
double*pd=(double*)malloc(sizeof(double)*12);
//分配12个double型存储单元,
//并将首地址存储到指针变量pd中
第一、malloc函数返回的是void*类型。
对于C++,如果你写成:
p=malloc(sizeof(int));则程序无法通过编译,报错:
“不能将void*赋值给int*类型变量”。
所以必须通过(int*)来将强制转换。
而对于C,没有这个要求,但为了使C程序更方便的移植到C++中来,建议养成强制转换的习惯。
第二、函数的实参为sizeof(int),用于指明一个整型数据需要的大小。
在Linux中可以有这样:
malloc(0),这是因为Linux中malloc有一个下限值16Bytes,注意malloc(-1)是禁止的;
但是在某些系统中是不允许malloc(0)的。
在规范的程序中我们有必要按照这样的格式去使用malloc及free:
1
2
3
4
5
6
7
8
9
10
type*p;
if(NULL==(p=(type*)malloc(sizeof(type))))
/*请使用if来判断,这是有必要的*/
{
perror("error...");
exit
(1);
}
.../*其它代码*/
free(p);
p=NULL;/*请加上这句*/
malloc也可以达到new[]的效果,申请出一段连续的内存,方法无非是指定你所需要内存大小。
比如想分配100个int类型的空间:
1
2
int*p=(int*)malloc(sizeof(int)*100);
//分配可以放得下100个整数的内存空间。
另外有一点不能直接看出的区别是,malloc只管分配内存,并不能对所得的内存进行初始化,所以得到的一片新内存中,其值将是随机的。
除了分配及最后释放的方法不一样以外,通过malloc或new得到指针,在其它操作上保持一致。
(2)calloc()函数
函数原型:
void*calloc(size_tn,size_tsize);
功能:
在内存的动态存储区中分配n个长度为size的连续空间,函数返回一个指向分配起始地址的指针;如果分配不成功,返回NULL。
跟malloc的区别:
calloc在动态分配完内存后,自动初始化该内存空间为零,而malloc不初始化,里边数据是随机的垃圾数据。
用法:
void*calloc(size_tn,size_tsize);
一般使用后要使用free(起始地址的指针)对内存进行释放,不然内存申请过多会影响计算机的性能,以至于得重启电脑。
如果使用过后不清零,还可以使用指针对该块内存进行访问。
头文件:
stdlib.h或malloc.h
编写一个calloc(n,size),返回一个指向n个大小为size的对象的指针,用malloc(size)写
void *calloc(size_t num, size_t size)
{
void *ptr = null;
size_t nbytes = 0;
nbytes = num*size;
ptr = malloc(nbytes);
if(ptr!
=null){ memset(ptr, 0×0,nbytes); }
return ptr;
}
void*memset(void*s,intch, size_t n);
函数解释:
将s中前n个字节(typedefunsignedintsize_t)用ch替换并返回s。
memset:
作用是在一段内存块中填充某个给定的值,它是对较大的结构体或数组进行清零操作的一种最快方法[1] 。
(3)realloc()函数
realloc原型是externvoid*reall