目录
一、常见bug:“烫烫烫烫烫烫”
我想程序员刚开始接触字符串或字符数组时,可能都会遇到“烫烫烫烫烫烫”乱码bug。其实这是由一些不好的编程习惯导致的。例如:
#include<stdio.h> int main() { char str[20],ch; int i=0; for(i=0;(ch=getchar())!='\n'&&i<20;i++) str[i]=ch; printf("%s",str); return 0; }
1.解决办法
解决办法有两个,一个是在字符串结尾加上’\0’,例如:
另一个是在声明字符数组时初始化:
2.原理
为什么这两个方法可行?
首先这与输出方式有关,你使用了%s格式化输出,说明str存储的应该是一个字符串,而C语言中字符串应该以’\0’结尾,但你没有。第一种解决办法手动将字符串的下一字符设为’\0’,输出函数遇到\0就停止了,自然不会输出乱码。
第二种解决办法初始化了栈区中字符数组的存储空间,使其(二进制位)全为0。当你用printf(“%s”,str)输出的时候,输出函数遇到0000 0000(注意\0用二进制表示就是0000 0000)也停止了。
为什么会输出“烫烫烫烫烫烫”?
“烫”常见于Windows编译环境下。因为vs编译器在分配内存时,对于未赋予初值的栈空间,会使用0xCC填充。因为0xCC已经超出ASCII码的表示范围,编译器会认为这是一个宽字符,从而把两个字节连在一起识别。当两个0xCC连在一起时就形成了0xCCCC,正好对于GBK中“烫”的编码。
我们可以写一段代码验证一下:
数组str占10个字节,每个“烫”占两字节,刚好输出5个“烫”
总结一下,我们在平时写程序的过程中,要养成给变量设置初值的习惯。在涉及字符串的操作时,要注意字符串是否以\0为结尾。
如果你想对字符串有更全面的了解,可以看看下面的内容。
二、字符串定义
1.字符串存储
字符串即一串字符,以\0为结尾。\0用十六进制表示就是0x00,用二进制表示就是0000 0000。C程序中字符串有两种存储方式,一种是字符串常量,另一种是字符串变量。
2.字符串常量
1) 字符串常量存储在常量区,其类型通常为char *。在程序执行期间常量的值不可改变,但可以通过指针获取其值。例如:
#include <stdio.h> int main() { char * str= "Hello World!\n";//常量字符串 printf("%s",str); return 0; }
注意虽然我们没有在字符串的结尾加上\0,但是编译器把字符串常量存储到常量区时,会自动将字符串常量之后的二进制位置为0,相当于在字符串的结尾加上\0了。
2) 此外还有一种实现字符串常量的方式——define,例如:
#include <stdio.h> #define STRING "Hello!\n" int main() { printf("%s",STRING); return 0; }
注意#define的实质是宏替换,在程序预编译时就对代码进行了替换,其值不会存储在常量区。
3.字符串变量
C语言没有定义string类型,需要用户自己实现字符串类型。通常我们使用char类型数组来存储字符串变量,并使用\0作为字符串的结尾。
//下面是两种给字符串赋初值的方法: #include <stdio.h> int main() { char str1[10] = "Hello!"; //{"Hello!"} char str2[10] = {'H','e','l','l','o','!','\0'}; printf("str1=%s\nstr2=%s\n",str1,str2); return 0; }
如果你是用” ”给字符串赋初值,例如char str1[10] = "Hello!",编译器会在字符串结尾自动加上\0。
如果你是一个字符一个字符地给字符串赋初值,那么你需要手动在字符串结尾加上'\0',例如char str2[10] = {'H','e','l','l','o','!','\0'},否则在以字符串格式输出时就会出错。
三、如何操作字符串
1.字符串变量赋值
1) 数组声明时赋值
在给字符串赋初值的时候,可以使用下面两种赋值方式:
char str1[10] = "Hello!"; //{"Hello!"}
char str2[10] = {'H','e','l','l','o','!','\0'};
但是在数组声明语句之外的地方,不能使用上面两种赋值方式,否则编译器会报错(如下图)。主要是因为” ”以及{ }赋值这两种方式都只能在数组定义的时候使用。
2) 其他情况赋值
可以对字符数组的每个元素逐个赋值,最后以’\0’结尾:
#include <stdio.h> int main() { char str1[10],str2[10]; //str1赋值: str1[0]='H';str1[1]='e';str1[2]='l';str1[3]='l';str1[4]='o';str1[5]='\0'; //str2赋值: for(int i=0;i<10;i++) str2[i]=str1[i]; printf("str1=%s\nstr2=%s",str1,str2); return 0; }
也可以使用strcpy函数实现字符串的赋值,记得加上string.h头文件:
#include <stdio.h> #include <string.h> int main() { char str1[10]="Hello!\n",str2[10]={0}; strcpy(str2,str1);//将字符串str1赋值给str2 printf("%s",str2); return 0; }
2.输入
1) 使用scanf函数
#include <stdio.h> int main() { char str[10]; scanf("%s",str); printf("%s\n",str); return 0; }
注意,scanf函数读入字符串遇到空格或回车就停止读入了,因此不能读入空格:
2) 使用gets函数
#include <stdio.h> int main() { char str[10]; gets(str); printf("%s\n",str); return 0; }
注意gets函数遇到回车才停止读入,遇到空格不停止,因此可以读入空格:
3) 逐字符读入
注意,使用scanf或gets读入字符串,它们会自动在字符串的结尾添加'\0';而用逐字符读入字符串的方式必须在字符串的结尾手动添加'\0'
#include <stdio.h> int main() { char str[20]; int i=0; for(i=0;(str[i]=getchar())!='\n'&&i<20;i++); str[i]='\0';//手动添加'\0' printf("%s",str); return 0; }
3.输出
1) printf以%s格式输出
printf("%s",str);
2) puts函数
puts(str);
注意puts函数会在输出完字符串后自动换行,而printf不会
3) 逐字符输出
#include<stdio.h> int main() { char str[20]="Hello World!\n"; for(int i=0;i<20&&str[i]!='\0';i++) printf("%c",str[i]); }
4.计算字符串长度
#include<stdio.h> int main() { char str[20]="0123456789!\n"; int length=0; for(length=0;length<20&&str[length]!='\0';length++);//计算字符串str长度 printf("length=%d",length); } //字符串的结束符\0一般不计入字符串长度
5.字符串在函数间的传递
C语言字符串在函数间的传递通常是以指针形式传递的,即传递数组的首地址(字符数组名),例如:
#include<stdio.h> #define N 20 int len(char s[]){//计算字符串s的长度 int length=0; for(length=0;length<N&&s[length]!='\0';length++); return length; } int main() { char str[N]="Hello World!\n"; printf("%d",len(str)); return 0; }
其中函数参数的声明部分下面这几种写法是等价的:
//int len(char s[]);
//int len(char s[N]);
//int len(char * s);
注意,因为字符数组是以指针形式传递的,所以如果在子函数中修改了字符串,在返回到主函数时这些修改也会一并保留下来。例如:
#include<stdio.h> #define N 20 void reverse(char s[N]){//翻转字符串s int i=0,j=0; char m; for(j=0;j<N&&s[j]!='\0';j++);//计算字符串s的长度 for(j=j-1;i<j;i++,j--){ m=s[i]; s[i]=s[j]; s[j]=m; } printf("reverse() s:%s\n",s);//翻转后,字符串s的值 return; } int main() { char str[N]="0"; scanf("%s",str); //读入字符串str reverse(str); //翻转字符串str printf("main() str:%s\n",str);//执行reverse函数后,main函数str的值 return 0; }
这里主函数的str和子函数reverse的s是同一个字符数组,它们占用同一个存储空间。
6.总结
对字符串的操作,归根结底是对字符数组的操作。因此除了上面提到的方法外,我们可以通过对字符数组各个元素的操作,来实现对字符串更加复杂的操作。在涉及字符串的操作时,我们要注意字符数组是否越界,以及字符串的下一字符是否为\0。
四、string头文件常用函数
string头文件定义了一些操纵字符串和数组的函数。下面简单讲几个常用的:
1.计算字符串长度strlen
size_t strlen(char *s);
求字符串的长度,从字符串的首地址开始到遇到第一个'\0'停止计数,例如:
#include<stdio.h> #include<string.h> #define N 30 int main(){ char str1[N]="There is an apple!"; printf("%d\n",strlen(str1)); return 0; }
2.复制字符串strcpy
char * strcpy ( char * destination, char * source );
将字符串source的值复制给destination,例如:
#include<stdio.h> #include<string.h> #define N 30 int main(){ char str1[N]="There is an apple!"; char str2[N]="0"; strcpy(str2,str1);//将字符串str1的值复制给str2 printf("%s\n",str2); return 0; }
3.复制前n个字符strncpy
char * strncpy ( char * destination, char * source, size_t num );
将字符串source的最多前num个字符复制到字符数组destination中,当num不大于字符串source的长度时,需要初始化destination或在destination字符串结尾追加\0。例如:
#include<stdio.h> #include<string.h> int main(){ char str1[19]="There is an apple!";//数组str1长度为19,其存储的字符串长度为18 char str2[18]="0"; strncpy(str2,str1,10);//将字符串str1的前10个字符复制给str2 printf("%s\n",str2); return 0; }
如果num大于字符串source的长度,strncpy会将字符串source的’\0’一起拷贝给destination。
4.字符串比较strcmp
int strcmp ( char *str1, char *str2);
比较ASCII码,str1>str2,返回值> 0;两串相等,返回0;str1<str2,返回值<0。例如:
#include<stdio.h> #include<string.h> int main(){ char str1[20]="apple"; char str2[20]="bpple"; if(strcmp(str1,str2)>=0) printf("str1>=str2\n"); else printf("str1<str2\n"); return 0; }
5.比较前n个字符strncpy
char * strncpy ( char *str1, char *str2, size_t num);
比较str1、str2前num个字符的ASCII码,例如:
#include<stdio.h> #include<string.h> int main(){ char str1[20]="apple b"; char str2[20]="apple a"; if(strncmp(str1,str2,5)==0) printf("str1==str2\n"); else printf("str1<>str2\n"); return 0; }
6.字符串拼接strcat
char * strcat ( char *destination, char *source);
将source的内容追加到destination的末尾,例如:
#include<stdio.h> #include<string.h> int main(){ char str1[20]="an "; char str2[20]="apple"; strcat(str1,str2); printf("%s\n",str1); return 0; }