定义方式
字符数组
char str[]={'h','e','l','l','o'};或者char str[]="hello";
数组方式定义且初始化,开的空间是合法空间,访问的时候,可通过下标法访问。因为访问的都是字符变量,所以可以修改字符数组的内容。
strlen(str)值是5,而sizeof(str)值是6,说明字符串初始化时候,系统会自动在输入的字符串最后添加\0表示字符串的结尾。由此要注意:为字符串开空间时记得多开一个空间用于存放\0,不然\0越界可能会发生一些奇怪的错误
strlen和sizeof区别
strlen求字符串长度,传参传字符串名字,会从首地址数起,直到找到\0,记录大小时不计入\0。sizeof求空间大小,传参类型所占的空间有多大,值就有多大。
字符数组strlen求有效字符串长度,sizeof求整个数组大小,指针字符串strlen求有效字符串长度,sizeof求的是这个字符串首字符地址的大小
字符串指针
char *str="hello";
指针方式定义且初始化,是将常量区这片字符串的地址直接赋值给指针,访问的时候,通过指针偏移取内容访问。因为是该指针指向的是常量区的地址,这片地址的值是不允许修改的。所以如果想直接通过指针取内容修改字符串内容,会segmentation fault段错误。
注意字符数组和字符串指针两者定义方式区别就在于空间的类型不同,前者是字符串变量,后者是字符串常量。数组方式是开一片合法地址空间,然后往空间里面赋值,这些空间里面的值可以自定义。指针方式并非另外开了一片空间,而是直接指向常量区的地址,该地址里面本来就有一些常量字符串,这些字符串只能使用,不能修改。
如果想通过指针方式定义字符串,而且允许修改,定义时候可以:
第一种:
char p[size]; char *str=p;scanf("%s",str);
第一种方式是开一片合法空间在栈区,size值为常量,该空间大小固定,然后用str指向该空间,再把str直接当字符串使用。
第二种:
char *str=(char *)malloc(size);scanf("%s",str);
第二种方式是动态开辟一片空间在堆区,size值可以是变量,空间大小还可以通过realloc来扩大之前malloc开辟的空间,str指向该空间,再把str当字符串使用。
指针方式定义字符串,指针名本质上是字符串首字符的地址,但是该指针名可以当做字符串的名字使用,所以访问的时候,可以直接通过字符串名字输出,也可以通过指针取内容方式依次取出每个字符输出。
#include<stdio.h>
int main(){
char *str="123abc";
for(int i=0;i<6;i++){
printf("%c",*(str+i));//str本质是地址
}
puts("");
printf("%s\n",str); //str可以当字符串名使用
printf("%p\n",str); //str本质是地址
puts(str); //str当字符串名使用
}
相关API
malloc
malloc传参直接传开辟空间大小,开辟空间不赋初值,开辟不成功return NULL;
calloc
calloc传参第一个参数是空间个数,第二个参数是没一个空间大小,开辟空间自动赋初值0,开辟不成功return NULL;
realloc
realloc额外扩大malloc或者calloc开辟的空间,第一个参数传空间首地址,第二个参数传扩容的大小,扩容成功且原地址后额外空间足够大则返回原地址,若原地址后额外空间小于需要扩容的空间,则会新找合适大小的空间,将原数据拷贝到新空间,再返回新空间的地址,原空间会自动释放。若内存已无足够大小空间,则扩容失败return NULL。
memset
memset会为一段连续的空间统一赋值,第一个参数是目标地址,第二个参数是赋值内容,第三个参数是赋值的空间数量。
gets
gets函数可以完全取代scanf("%s",str),可以直接输入带空格的字符串,而且使用gets函数系统会将最后敲的换行符从缓冲区丢弃,以致于多次gets函数之间不需要吸收回车清空缓存区
但是需要注意gets可以读取任意多少字符,需要让赋值地址的空间足够大,不然很容易造成内容泄露,发生一些奇怪的错误。
strcpy和strncpy
strcpy可以拷贝整个字符串到新的地址,并且末尾追加\0,第一个参数的目标字符串,第二个参数是源字符串,要保证目标字符串的空间大于源字符串的空间。strncpy多第三个参数n,拷贝的字符串是前n个字符,不会追加\0需要手动添加,如果n大于整个字符串的长度,多余的部分会以\0填充
memcpy
memcpy可以拷贝任意内容,不只是字符串,而且有第三个参数n规定拷贝空间的大小。
strcmp和strncmp
strcmp按字典序通过字符ascii码大小比较两个字符串,第一个参数是第一个字符串,第二个参数是第二个字符串,若第一个字符串大于第二个字符串return 1
相等return 0,小于return -1。strncmp多第三个参数n,比较两字符串的前n个字符。
strcat和strncat
strcat用于将源字符串拼接到目标字符串尾部,第一个参数是目标字符串,第二个参数是源字符串,调用后原目标字符串尾部的\0会被源字符串的首字符替代,调用完成后目标字符串的尾部会追加\0。strncat多第三个参数n,将源字符串的前n个字符拼接到目标字符串尾部。
strstr
strstr用于判断源字符串是否是目标字符串的子串,第一个参数是目标字符串,第二个参数是源字符串,如果是return目标字符串第一次出现源字符串的指针,如果不是return NULL;
strchr
strstr用于判断源字符是否是目标字符串的子字符,第一个参数是目标字符串,第二个参数是源字符,如果是return目标字符串第一次出现源字符的指针,如果不是return NULL;
strtok
strtok用于分割目标字符串,第一个参数是目标字符串,第二个参数是分割字符串,分割字符串中的每一个字符都可以作为目标字符串的分隔符。第一次调用传入目标字符串,分割成功return 分割出来的字符串的首地址,如果不可分割则return NULL。而后多次调用传入第一个参数为NULL,可以继续分割目标字符串,注意分割操作会修改目标字符串的内容,如果不想修改目标字符串,可以拷贝一份。
一些注意事项
对于自加,i++,先用再加,先运算完整个表达式再自加,一个语句分号前都是运算表达式。
对于断言assert(expresstion),括号中写程序正常运行的条件,只要程序运行到assert时,expression表达式的值为false,即不满足条件了,assert函数想stderr打印一条错误信息,再调用abort终止程序运行。可以在合适的地方设置断言,防止程序运行过程中,遇到一些隐患可以及时停止。比如规定输入小于40个字节大小的字符串,可以在获取输入后,设置断言,检查字符串的空间大小,超过40字节则程序停止运行。
作业案例:自己实现常用字符串API
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<string.h>
char* Mystrcpy(char *des,char *srs){
assert(des!=NULL&&srs!=NULL);
char *backup=des;
while(*backup++=*srs++); //就算while循环{}没有内容;不能少
//src最后的\0先赋值给backup,backup再判断,为假,不执行{}的代码。但是++运算符已经运行了,最后backup仍然会自加。
return des;
}
char* Mystrncpy(char *des,char *srs,int count){
assert(des!=NULL&&srs!=NULL);
char *backup=des;
while((*backup++=*srs++)&&count>0){ //i++,i的值会在自加所在的语句运行完之后再自加
count--;
}
if(count>0){
while(count>0){
count--;
*backup++='\0';
}
}
*(backup-1)='\0';
return des;
}
int Mystrcmp(char *des,char* srs){
assert(des!=NULL&&srs!=NULL);
int res=0;
while(!(res=(*des++)-(*srs++))&&*des&&*srs);
if(res>0)res=1;
if(res<0)res=-1;
return res;
}
int Mystrncmp(char *des,char* srs,int size){
assert(des!=NULL&&srs!=NULL);
int res=0;
while(!(res=(*des++)-(*srs++))&&*des&&*srs&&size>1)size--;
if(res>0)res=1;
if(res<0)res=-1;
return res;
}
char* Mystrcat(char *des,char* srs){
assert(des!=NULL&&srs!=NULL);
char *backup=des;
while(*backup){ //backup为\0时,就不自加了,如果++写在()里面,backup='\0'之后仍会自加;
backup++;
}
while((*backup++)=(*srs++));
*backup='\0';
return des;
}
char* Mystrncat(char *des,char* srs,int count){
assert(des!=NULL&&srs!=NULL);
char *backup=des;
Mystrncpy(backup+strlen(des),srs,count);
return des;
}
int Mystrstr(char* des,char* srs){
assert(des!=NULL&&srs!=NULL);
int srslen=strlen(srs);
int res=0;
int cnt=1;
if(!srslen){
return -999;
}
while(*des!='\0'){
if(*des==*srs){
res=Mystrncmp(des,srs,srslen);
if(res==0){
return cnt;
}
}
des++;
cnt++;
}
return -1;
}
char* Mystrchr(char *des, char c){
assert(des!=NULL);
while(*des!='\0'&&*des!=c)des++;
if(!(*des))return NULL;
return des;
}
char* Mystrtok(char *srs,char *delim){
assert(delim!=NULL);
char*p =(char *)malloc(3);
Mystrcpy(p,"\nexit\0");
static char *remain;
int len=strlen(delim);
char de[100];
for(int i=0;i<len;i++){
de[i]=*delim;
delim++;
}
char *tok;
if(srs!=NULL){
tok=srs;
while(*tok!='\0'){
for(int i=0;i<len;i++){
if(*tok==de[i]){
*tok='\0';
remain=tok+1;
// printf("srs退出时remain地址为%p\n",remain);
return srs;
}
}
tok++;
}
// printf("srs退出时remain地址为%p\n",remain);
remain=NULL;
return NULL;
}
if(srs==NULL){
char *new=remain;
tok=remain;
if(remain==NULL){
return NULL;
}
if(*remain=='\0'){
return p;
}
while(*tok!='\0'){
for(int i=0;i<len;i++){
if(*tok==de[i]){
*tok='\0';
remain=tok+1;
// printf("NULL退出时remain内容为%c 地址为%p\n",*remain,remain);
// printf("NULL退出时new内容为%c 地址为%p \n",*new,new);
return new;
}
}
tok++;
// printf("累加时tok内容为%c 地址为%p \n",*tok,tok);
}
// printf("最后一次退出时tok为%c \n",*tok);
// printf("最后一次退出时tok地址为%p \n",tok);
remain=tok;
return new;
}
}
int main(){
char *str1=(char *)calloc(201,sizeof(char)); //防止内存越界多留一个为了存\0不然会有很多意想不到的错误
char *str2=(char *)calloc(201,sizeof(char));
char *backup=(char *)malloc(402*sizeof(char));
int size;
char c;
puts("请输入2个小于200个长度的字符串str1和str2");
gets(str1);
gets(str2);
assert(strlen(str1)<=200&&strlen(str2)<=200);
puts("调用strcpy");
Mystrcpy(backup,str1);
printf("复制str1给backup:%s\n",backup);
Mystrcpy(backup,str2);
printf("复制str2给backup:%s\n",backup);
puts("\n调用strncpy");
printf("请输入复制的大小,程序会裁剪此大小的str1和str2赋值给backup\n");
scanf("%d",&size);
Mystrncpy(backup,str1,size);
printf("复制str1的前%d个数据为:%s\n",size,backup);
Mystrncpy(backup,str2,size);
printf("复制str2的前%d个数据为:%s\n",size,backup);
puts("\n调用strcmp\nstr1比较str2");
printf("结果为 %d\n",Mystrcmp(str1,str2));
puts("\n调用strncmp\n请输入比较的大小,程序会裁剪此大小的str1和str2并做比较");
scanf("%d",&size);
printf("结果为 %d\n",Mystrncmp(str1,str2,size));
puts("\n调用strcat\n将str2拼接到str1上");
Mystrcpy(backup,str1);
Mystrcat(backup,str2);
printf("拼接之后的结果为 %s",backup);
puts("\n调用strncat\n请输入裁剪的大小,程序会裁剪此大小的str2拼接到str1上");
scanf("%d",&size);
fflush(stdin);
Mystrcpy(backup,str1);
Mystrncat(backup,str2,size);
printf("结果为 %s\n",backup);
puts("\n调用strstr");
printf("请输入一个字符串,程序会判断该字符串是不是str1的子串\n");
char *strstr1=(char *)calloc(200,sizeof(char));
scanf("%s",strstr1);
getchar();
fflush(stdin);
if(Mystrstr(str1,strstr1)==-1)printf("该字符串不是str1的子串\n");
else{
if(Mystrstr(str1,strstr1)==-999){
printf("该字符串是个空字符串,是str1的子串\n");
}
else{
printf("该字符串是str1的子串,位置在str1的第%d个字符处\n",Mystrstr(str1,strstr1));
}
}
puts("\n调用strchr");
printf("请输入一个字符,程序会判断该字符是否存在于str1中\n");
scanf("%c",&c);
fflush(stdin);
if(Mystrchr(str1,c)==NULL)printf("该字符不存在于str1中\n");
else{
printf("该字符存在于str1中,输出该字符所在的字符串为%s\n",Mystrchr(str1,c));
}
puts("\n调用strtok");
char *delim=(char*)calloc(40,sizeof(char));
printf("请输入可以分割的字符串,程序会分割str1\n");
scanf("%s",delim);
printf("%s\n",Mystrtok(str1,delim));
char *pp;
for(int i=2;i<1000;i++){
pp=Mystrtok(NULL,delim);
if(pp==NULL){
printf("没有可以分割的内容\n");
break;
}
if(*pp!='\0'&&Mystrcmp(pp,"\nexit\0")!=0)
printf("%s\n",pp);
if(Mystrcmp(pp,"\nexit\0")==0)
{
printf("分割结束\n");
break;
}
}
}