我们的第一个“Hello World”程序使用cout输出了
"Hello World"
这一句话其实就是字符串。字符串就是连续的字符“串”起来的一段文本,可以看成是若干有序的字符的集合。通过前面的学习,可知char数组就是若干有序的字符的集合,所以可以使用char数组来存储字符串。
一、字符串常量
回顾“Hello World”程序代码:cout<<"Hello World";
,语句中的"Hello World"
就是一个字符串常量。字符串常量书写的时候用英文双引号引起来,例如:"Hello World"
、"I love programming"
、"In me\nthe tiger sniffs the rose."
、"A"
都是字符串常量。要注意"A"
和'A'
的区别,"A"
是只包含一个字符的字符串,'A'
是一个字符。
二、使用字符数组存储字符串
首先来回顾普通字符数组的用法。普通字符数组就是char类型的数组,数组元素的类型是char类型。
#include<iostream> using namespace std; int main() { char s[6]; //输入6个char存储到字符数组中 for(int i=0;i<=5;i++) cin>>s[i]; //输出字符数组中的元素 for(int i=0;i<=5;i++) cout<<s[i]; cout<<endl; //对字符数组中的元素进行重新赋值 s[0] = 'z'; s[2] = s[1]; s[3]++; for(int i=0;i<=5;i++) cout<<s[i]; return 0; }
那么如何保存字符串变量呢?例如输入一个字符串,应该如何存储?字符串就是连续的字符“串”起来的一段文本,可以看成是若干有序的字符的集合,所以可以使用char数组来存储字符串。
例如语句 char str[6] = "Hello";
,将字符串常量"Hello"
存储到了长度为6的字符数组str中。语句执行后,字符数组str每个元素的情况如下:
下标 | 0 | 1 | 2 | 3 | 4 | 5 |
元素 | 'H' | 'e' | 'l' | 'l' | 'o' | '\0' |
可见字符串的每个字符按照顺序依次存储到字符数组中,特殊地是,存储字符串的字符数组末尾会自动加上一个“尾巴”——表示字符串结束的特殊字符'\0'
(空字符,ASCII码值为0),这也是为什么"Hello"
只有5个字符,却至少要用长度为6的字符数组来存储。这是C/C++的自动处理机制,C/C++是靠空字符'\0'
来识别保存到字符数组中的字符串的结束位置。可以通过一个程序来测试:
#include<iostream> using namespace std; int main() { char str[6] = "Hello"; cout<<str<<endl; //输出存储在字符数组中的字符串 str[3] = '\0'; //一句“恶意”代码 cout<<str<<endl; return 0; }
程序的运行结果如下:
Hello Hel
执行了str[3] = '\0';
语句后,str数组元素如下:
下标 | 0 | 1 | 2 | 3 | 4 | 5 |
元素 | 'H' | 'e' | 'l' | '\0' | 'o' | '\0' |
这个时候再用cout输出存储在字符数组str中的字符串,因为C/C++是靠空字符'\0'
来识别保存到字符数组中的字符串的结束位置,所以会认为存储在str中的字符串是"Hel"
,程序最后输出的内容也正是"Hel"
。
通过上面的分析可知:
- 可以使用字符数组存储字符串;
- 存储时,从字符数组的 0 号下标开始依次存储字符串的每个字符。C/C++会自动在字符数组末尾的地方加上空字符
'\0'
,注意这是运行时自动实现的,不需要我们写代码去实现! - 正因为如此,定义存储字符串的字符数组时,数组的长度至少是要存储的字符串的长度+1。如果开的char数组很大,依次存储完字符串的所有字符后,字符数组后面的元素都是空字符
'\0'
。
三、输入输出字符串
使用字符数组存储字符串,可以直接使用cin将输入的字符串存入到字符数组中,此时也会自动在数组的末尾添加上空字符'\0'
。也可以直接使用cout输出存储在字符数组中的字符串,会自动依次输出字符数组中的字符直到遇到'\0'
为止(不会输出'\0'
)。
例如我们要输入一句不包含空格、长度不超过10的英语单词,要将单词的字母全部大写,可以这样处理:
#include<iostream> using namespace std; int main() { char str[11]; //输入的字符串长度不超过10,存储字符串的字符数组长度至少为11 cin>>str; cout<<str<<endl; for(int i=0;str[i]!='\0';i++){ //使用循环遍历字符数组存储字符串的元素 //小写字母转大写 if(str[i]>='a' && str[i]<='z') str[i] -= 'a'-'A'; cout<<str[i]; } return 0; }
如果输入的字符串长度小于10,例如输入"Hello"
,那么字符数组内部的存储形式如下:
下标 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
元素 | 'H' | 'e' | 'l' | 'l' | 'o' | '\0' | '\0' | '\0' | '\0' | '\0' | '\0' |
因为'\0'
相当于整数0,所以还可以将遍历存储在字符数组中的字符串写成:
#include<iostream> using namespace std; int main() { char str[11]; //输入的字符串长度不超过10,存储字符串的字符数组长度至少为11 cin>>str; cout<<str<<endl; for(int i=0;str[i];i++){ //循环条件直接用s[i] //小写字母转大写 if(str[i]>='a' && str[i]<='z') str[i] -= 'a'-'A'; } cout<<str<<endl; //前面循环中已经将char数组中的小写字母改成了大写字母 return 0; }
关于字符串更多的输入输出方式,后面的章节会详细介绍。
四、字符串和字符数组的区别
- 字符数组就是元素类型为char的数组;
- 字符串可以存储在字符数组中。例如定义字符数组时赋初值为常量字符串、通过cin直接将字符串输入到字符数组、通过cout直接输出字符数组时,字符数组都会被当成字符串处理。存储的时候会自动在字符数组末尾添加上一个表示字符串结束的空字符
'\0'
。
操作 | 字符数组 char s[100]; | 用来存储字符串的字符数组 char s[101]; |
赋值 | 对数组某个元素赋值,例如s[0] = 'a'; | 可以直接在定义时将字符数组赋初值为一个字符串常量,例如char s[101] = "Hello World"; 当然也可以像左边那样对“存储字符串的字符数组”的某个元素赋值 |
输入 | 输入到数组某个元素,例如cin>>s[i]; | 可以直接输入一个字符串,例如cin>>s; 当然也可以像左边那样输入到“存储字符串的字符数组”某个元素 |
输出 | 输出数组某个元素,例如cout<<s[i]; | 可以直接输出一个字符串,例如cout<<s; 当然也可以像左边那样输出“存储字符串的字符数组”某个元素 |
五、字符串函数
其实应用程序中处理字符串的操作远远多于处理数字,就像cmath中有很多用来实现数学计算的函数一样,C/C++中提供了字符串处理函数来简化对字符串的基本处理,要使用这些函数,首先要引入cstring头文件。
1.strlen字符串长度
可以使用strlen函数求字符串的长度,参阅下面的程序:
#include<iostream> #include<cstring> using namespace std; char str[1001]; int main() { cin>>str; cout<<strlen(str)<<endl; int n = strlen(str); for(int i=0;i<n;i++){ cout<<str[i]; } return 0; }
使用strlen函数求出字符串的长度,-1则是字符串最后一个元素的下标,这样可以实现反向遍历字符串:
#include<iostream> #include<cstring> using namespace std; char str[1001]; int main() { cin>>str; for(int i=strlen(str)-1;i>=0;i--){ cout<<str[i]; } return 0; }
2.strcpy拷贝字符串
使用strcpy(s1,s2);
可以将字符串s2的内容拷贝到s1中,注意这里没有返回值,是直接改变字符串s1的内容。例如要交换两个字符串的值,不能像数字那样使用交换器,可以使用strcpy来拷贝字符串:
#include<iostream> #include<cstring> using namespace std; int main() { char s1[11] = "Hello"; char s2[11] = "World"; cout<<s1<<" "<<s2<<endl; char tmp[11]; strcpy(tmp,s1); strcpy(s1,s2); strcpy(s2,tmp); cout<<s1<<" "<<s2<<endl; return 0; }
3.strcat字符串拼接
使用strcat(s1,s2);
可以将字符串s2的内容拼接到s1的末尾,注意这里没有返回值,是直接改变字符串s1的内容。
#include<iostream> #include<cstring> using namespace std; int main() { char s1[21] = "Hello"; char s2[21] = "World"; cout<<s1<<endl; strcat(s1,s2); cout<<s1<<endl; return 0; }
4.strcmp字符串比较
strcmp(s1,s2);
函数返回使用字典顺序比较字符串s1、s2的结果,如果s1==s2,返回值是0;如果s1>s2,返回值大于0;如果s1<s2,返回值小于0。所谓字典顺序就是英语字典里单词的排列顺序,也和字符串中字符的ASCII值有关系。
#include<iostream> #include<cstring> using namespace std; int main() { cout<<strcmp("hello","hello")<<endl; cout<<strcmp("a","A")<<endl; //"a">"A" cout<<strcmp("he","hello")<<endl; //"he"<"hello" cout<<strcmp("H","h")<<endl; //"H"<"h" cout<<strcmp("Hello","h")<<endl; //"Hello"<"h" cout<<strcmp("H","hello")<<endl; //"H"<"hello" return 0; }
来看一段程序代码:
#include<iostream> #include<cstring> using namespace std; int main() { char choice[11]; cin>>choice; //依次处理字符串中的char,大写字母转换成小写 for(int i=0;choice[i]!='\0';i++){ if(choice[i]>='A' && choice[i]<='Z'){ choice[i] += 'a'-'A'; } } if(strcmp(choice,"yes")==0){ cout<<"you inputed yes"; } return 0; }
程序将输入的字符串全部转换成小写后再判断和"yes"
是否相等。运行程序,分别输入"YES"
、"Yes"
、"yes"
甚至是"yEs"
、"yES"
之类能够表示"yes"
的字符串,if语句的条件都成立。
注意体会并掌握上面处理策略包含的“标准化”思想。
5.memset初始化函数
memset是计算机中C/C++语言初始化函数。作用是将某一块内存中的内容全部设置为指定的值,这个函数通常为新申请的内存做初始化工作。我们一般用来将数组的元素全部赋值为同一个值,memset函数的处理速度优于用循环重复赋值。
#include<iostream> #include<cstring> using namespace std; int main() { int a[5]; cout<<sizeof(a)<<endl; //sizeof(a)计算数组a的字节长度,这里结果是20 memset(a,0,sizeof(a)); //将int数组a的每个元素赋值为0 for(int i=0;i<5;i++) cout<<a[i]<<" "; cout<<endl; memset(a,-1,sizeof(a)); //将int数组a的每个元素赋值为-1 for(int i=0;i<5;i++) cout<<a[i]<<" "; cout<<endl; memset(a,1,sizeof(a)); //注意:这里并不能将int数组a的每个元素赋值为1 for(int i=0;i<5;i++) cout<<a[i]<<" "; cout<<endl; return 0; }
程序运行结果如下:
20 0 0 0 0 0 -1 -1 -1 -1 -1 16843009 16843009 16843009 16843009 16843009
分析结果可知:memset(a,1,sizeof(a));
并没有达到预期的将数组a的所有元素赋值为1的效果。原因是memset是以字节为单位对连续内存空间赋值的,int是4个字节,每个int元素的每个字节会被赋值成0000001
,那么每个int元素会被赋值为二进制数 00000001000000010000000100000001
,也就是十进制数16843009
。所以使用memset函数对int或者long long类型数组元素批量赋值,一般赋值成0或者-1能够达到效果,赋值为255也会被初始化为-1,其他值不能达到效果。对于int 或者long long类型数组,使用memset初始化,如果初始化成127,那么数组的每个元素会是接近 int 或long long类型上限的正整数;如果初始化成128,那么数组的每个元素会是接近 int 或long long类型下限的负整数;
如果我们将上面程序的int数组修改成char数组,那么能够将数组的元素批量赋值成-128~127范围内的任意整数,因为char正好是一个字节:
#include<iostream> #include<cstring> using namespace std; int main() { char a[5]; //char数组,每个元素一个字节 memset(a,0,sizeof(a)); for(int i=0;i<5;i++) cout<<(int)a[i]<<" "; cout<<endl; memset(a,-128,sizeof(a)); for(int i=0;i<5;i++) cout<<(int)a[i]<<" "; cout<<endl; memset(a,127,sizeof(a)); for(int i=0;i<5;i++) cout<<(int)a[i]<<" "; cout<<endl; return 0; }
*6.memcpy内存拷贝函数
memcpy是C和C++使用的内存拷贝函数,函数使用方法是memcpy(destin,source,n);
函数的功能是从源内存地址的起始位置开始拷贝若干个字节到目标内存地址中,即从源source中拷贝n个字节到目标destin中。与strcpy不同的是,strcpy只能复制字符串,而memcpy可以复制任意内容,例如任意类型数组、整型、结构体、类等。
#include<iostream> #include<cstring> using namespace std; int main() { int a[10] = {1,2,3,4,5,6,7,8,9,10}; int b[10]; memcpy(b,a,sizeof(a)); for(int i=0;i<10;i++) cout<<b[i]<<" "; return 0; }
注意:有一些字符串函数,例如实现大小写转换的strlwr、strupr,不是标准C库函数,只能在Dev C++中使用,在竞赛标准的 Linux gcc 环境下不能使用。