C++中字符串的介绍
字符串是个特殊的字符数组。与普通字符数组不同的是,字符串在结尾处有一个字符\0”,表示字符串的结束。在C++中字符串有特殊的初始化方式和专门的处理函数。本节将介绍字符串的特性,在下一节中将介绍专门用于处理字符串的函数。
.1.1?什么是字符串
在C++语言中,没有专门的字符串类型,一个字符串,其实就是一个字符数组。不过并不是数组中的所有字符都是字符串的一部分,字符串是以字符\0”表示字符串的结束的。所以在字符数组中,所有在字符\0之前的字符才是字符串中的有效字符。
字符串也有字面常量,其形式是用双引号包围起来一串字符。例如:
"Hello?World";?
前面讲到的字符,用的是单引号,字符串用的是双引号,这点不要弄反了。"a"是一个字符串,而不是一个字符,虽然这个字符串只有一个有效字符。
上面定义了一个字符串,其存储形式如图.1所示。? 图.1?字符串的存储形式 每一个字符串,后面都有一个不可见的字符\0”,这个字符是字符串的结束标志。因此上面的字符串有12个字符。双引号表示其中的内容是一个字符串,字符串中并不包括双引号。
前面讲字符类型的时候讲过转义字符,比如字符\n”表示换行。字符串中可以包含转义字符,例如:
"Hello\nWorld";?
如果输出上面的字符串,则会分两行输出。""表示只有空格字符的字符串,而""也是一个字符串,这个字符串只有一个结束标志。字符串还可以分多行定义,例如:
"Hello"?
"World";?
上面的定义相当于"HelloWorld",编译器会自动把分行定义的字符串连接起来。需要注意的是,只有在结束的时候才可以有语句结束的分号";",前面都不能有。
字符串的结束标志不需要显式地提供,编译器会自动在后面追加。.1.2?定义字符串
用双引号包围的字符串是字符串类型的字面常量,不可以修改。而作为变量的字符串,其实就是字符数组。定义一个字符串其实就是定义一个字符数组,例如:
char?name[10];??????????//?定义一个名字是name的字符串?
也可以定义二维的或者多维的字符数组,例如:
char?names[10][10];?????//?定义一个二维字符数组,通常称这样的数组为字符串数组?
.1.3?字符串的初始化
同普通数组一样,字符数组也允许在定义的时候直接初始化,例如:
char?name[10]?=?{''J'',?''a'',?''s'',?''o'',?''n''};?
同样,如果为数组的所有元素提供了初始值,则字符数组的长度可以省略,例如:
char?name[]?=?{''J'',?''a'',?''s'',?''o'',?''n''};?
这时数组长度被自动定义为5。
还可以用字符串来作为字符数组的初始值,因此还可以这么写:
char?name[]?=?"Jason";?
上面的语句定义了一个长度为6的字符数组,其最后的一个字符为\0”。在这种初始化方式下,不需要花括号"{"和"}"。
通常用上面的方法对字符数组进行初始化,而不用单个字符的方式。
.1.4?操作字符串
字符串作为一个字符数组,同样可以使用下标的方法来操作每一个字符,不过字符串有一些普通数组没有的特性。
前面在介绍数组的时候提到过,cin和cout不可以直接操作数组,但是字符数组是个特例。可以直接用C++的标准输入cin输入整个数组的元素,用标准输出cout输出整个字符数组。下面对一个输入的字符串进行反转,其算法是:第一个字符和最后一个字符交换,第二个字符和倒数第二个字符交换,依此类推。假定字符串为abcdefg,那么其交换过程示意如下:
a???------>??g?
b???------>??f?
c???------>??e?
d???保持不变?
交换之后就成了gfedcba。
交换到字符d时,也就是字符串一半的时候,这时的结果已经是gfedcba,交换已经完成了,如果继续再交换下去,则又会恢复成交换之前的样子。
该实例的具体代码如示例代码6.4所示。
示例代码6.4
#include??
using?namespace?std;?????????????????????????????//?使用名称空间std?int?main(int?argc,?char?argv[])??????????????//?主函数?
{?
????char?buffer[128];???????????????????????????//?字符数组?
????cin>>buffer;????????????????????????????????//?输入字符串?
????int?len?=?(int)strlen(?buffer?);????????????//?取得字符串的长度?
????for(?int?i?=?0;?i? ????{?
????????char?temp?=?buffer[i];??????????????????//?交换两个字符?
????????buffer[i]?=?buffer[len?-?i?-?1];?
????????buffer[len?-i?-?1]?=?temp;?
????}?
????cout?<<"反转之后的字符串为:"??< ????return?EXIT_SUCCESS;????????????????????????????//?主函数返回?
}?
建立一个控制台工程,并将上述代码复制到源文件中,编译并运行,其结果如图6.10所示。
? 图.2?字符串反转结果 对于buffer[0],其对应的要交换的为buffer[len-1],对于第i个字符buffer[i],其需要交换的为buffer[len-i-1]。最后来看循环结束的条件:如果字符串的长度len为偶数,最后一对需要交换的是buffer[len/2-1]和buffer[len/2];如果字符串的长度len为奇数,buffer[len/2]是中间字符,不需要交换,最后一对需要交换的字符是buffer[len/2-1]和buffer[len/2+1]。因此用条件i 也可以定义二维的字符数组,也就是字符串的数组。例如下面定义可以存储多个人名的数组:
char?names[10][40];?
同普通的二维数组一样,字符串数组在定义的时候也可以初始化:
char?names[10][40]?=?
{?
????"Tom",?
????"Mary",?
????"Jacky",?
????"Jason"?
};?
上面定义了一个长度为10的字符串数组,并给前4个进行了初始化,剩余部分用空字符串来填充。
不能遗忘两个字符串中间的","。
虽然可以对字符数组用字符串进行初始化,可以直接输入、输出字符串,但是在使用字符数组的时候还是有很多不便,比如不能直接对字符数组进行赋值、不能比较两个字符数组的大小等。因此C++提供了一些函数来处理字符串,下面介绍几个常用的函数。
.2.1字符串复制函数strcpy
字符串是一个字符数组,因此它就有数组的局限性,不能用一个字符串给另外一个字符串赋值,例如:
char?name[10];?
name?=?"Jason";?????????//?错误,不可以这样直接赋值?
为了方便实现上面的功能,C++标准库中提供了strcpy函数,其定义格式为:
strcpy(?字符数组名1,?字符数组名2?)?
函数把字符数组2中的字符串复制到字符数组1中,串结束标志\0也一同复制。使用例子如下:
char?str1[128],str2[]?=?"Hello?World";?
strcpy(?str1,?str2?);???????//?把str2复制到str1?
程序员需要自己保证str1的长度足够容纳str2的内容,如果str2的长度超过了str1的长度,可能会导致系统崩溃。
其中第二个参数可以直接是一个字符串,因此也可以这样使用:
char?str[128];?
strcpy(?str,?"Hello?World"?);???//?直接给str赋值?
在下面的例子中需要用户输入两个字符串,然后把这两个字符串的内容互换,程序如示例代码.1所示。
#include??using?namespace?std;???????????????????????//?使用名称空间std?int?main(int?argc,?char?argv[])????????????//?主函数?
{?
????cout<<"请依次输入两个字符串:"< ????char?str1[128],str2[128];???????//?字符串变量?
????cin>>str1;??????????????//?输入?
????cin>>str2;??
????cout<<"第一个字符串为:"< ????cout<<"第二个字符串为:"< ????cout< ????char?temp[128];?????????????//?临时字符串变量?
????strcpy(?temp,?str1?);???????//?交换两个字符串?
????strcpy(?str1,?str2?);?
????strcpy(?str2,?temp?);?????cout<<"交换之后的字符串为:"< ????cout<<"第一个字符串为:"< ????cout<<"第二个字符串为:"< ????system("PAUSE");????????????????????????//?等待用户反应?
????return?EXIT_SUCCESS;????????????????????//?主函数返回?
}?
建立一个控制台工程,并将上述代码复制到源文件中,编译并运行,其结果如图.1所示。
? 图.1?交换字符串结果 strcpy函数复制的是字符\0之前的内容(包括这个结束标志),不是整个字符数组的内容。.2.2计算字符串长度函数strlen
对于字符数组,可以通过sizeof来得到其定义的长度,但是这个长度对于字符串是没有意义的。因为通常想要得到的是其有效内容的长度,也就是在字符\0之前的字符的个数。在C++标准库中提供了函数strlen,可以实现这个功能。strlen函数的格式如下:
strlen(?字符数组名?)?
strlen将字符串的实际长度作为函数的返回值。使用例子如下:
char?str[128]?=?"Hello?World";?
int?len?=?strlen(?str?);????????????????//?取得字符串的长度?
上面的len值为11,包括里面的空格,但是不包括里面的字符串结束标志。下例中需要用户输入一个字符串,然后用strlen函数计算其长度,程序如示例代码.2所示。
#include??
using?namespace?std;?int?main()?
{?
????cout<<"请输入一个字符串:"< ????char?str[128];?
????cin>>str;?
????cout<<"字符串的长度为:"< ????return?0;?
}?
建立一个控制台工程,并将上述代码复制到源文件中,编译并运行,其结果如图.2所示。
? 图.2?使用strlen函数示例结果 .2.3字符串连接函数strcat
对于两个整数a和b,下面的代码结果是把两个整数相加:
int?a?=?3;?
int?b?=?5;?
int?c?=?a?+?b;?
对于字符串,其加法的意义应该是把两个字符串连接起来,对于下面两个定义:
char?str1[128]?=?"Hello?World";?
char?str2[128]?=?"C++?Programming";?
如果想把两个字符串连接起来,直接用+是不可以的,在C++标准库中提供了一个可以实现这个功能的函数,这个函数就是strcat。函数格式如下:
strcat?(?字符数组名1,?字符数组名2?)?
函数把字符数组2中的字符串连接到字符数组1中字符串的后面,并删去字符串1后的串标志\0。本函数返回值是字符数组1的首地址。使用例子如下:
char?str1[128]?=?"Hello?World";?
char?str2[128]?=?"C++?Programming";?
strcat(?str1,?str2?);???????????????????//?连接字符串?
上面的代码把str2连接到str1的后面,现在str1为"HelloWorldC++Programming"。下例需要用户输入两个字符串,然后把它们连接起来,程序如示例代码.3所示。
#include??
using?namespace?std;????????????????????????//?使用名称空间std?int?main(int?argc,?char?argv[])????????//?主函数?
{?
????cout<<"请输入两个字符串:"< ????char?str1[128];?????????????????????????????//?字符串变量?
????char?str2[128];?
????char?buffer[128];?????cin>>str1>>str2;????????????????????????????//?输入字符串?
????strcpy(?buffer,?str1?);?????????????????????//?复制?
????strcat(?buffer,?str2?);?????????????????????//?连接?
????cout<<"新生成的字符串为:"< ????????return?EXIT_SUCCESS;????????????????????????????//?主函数返回?
}?
建立一个控制台工程,并将上述代码复制到源文件中,编译并运行,其结果如图.3所示。
? 图.3?strcat函数使用示例结果 .2.4字符串比较函数strcmp
对于两个字符串,其比较是有意义的,但是字符数组不能直接进行比较,因此在C++标准库中提供了对字符串进行比较的函数,函数格式如下:
strcmp(?字符数组名1,字符数组名2?)?
函数按照ASCII码顺序比较两个数组中的字符串,并由函数返回值返回比较结果:
字符串1=字符串2,返回值=0。
字符串2>字符串2,返回值>0。
字符串1<字符串2,返回值<0。
当两个字符串相等的时候,返回值为0,因此,在比较两个字符串是否相等的时候,应当像下面的代码这么写:
if(?strcmp(?str1,?str2?)?==?0?)?????//?判断两个字符串是否相同?
而不能这么写:
if(?strcmp(?str1,?str2?)?)??????????//?错误的写法,结果跟希望正好相反?
下例需要用户输入两个字符串,然后对它们进行比较,程序如示例代码.4所示。
#include??
using?namespace?std;????????????????????????//?使用名称空间std?int?main(int?argc,?char?argv[])?????????????//?主函数?
{?
????cout<<"请输入两个字符串:"< ????char?str1[128];?????????????????????????????//?字符串变量?
????char?str2[128];?????cin>>str1>>str2;????????????????????????????//?输入字符串?
????int?res?=?strcmp(?str1,?str2?);?????????????//?比较?
????cout<<"比较的结果:"< ????cout< ????if(?res?==?0?)?cout<<"==";?
????if(?res?>?0?)?cout<<">";?
????if(?res?0?)?cout<<"<";?
????cout< ????system("PAUSE");????????????????????????
????//?等待用户反应?
????return?EXIT_SUCCESS;???????????????????
????//?主函数返回?
}?
建立一个控制台工程,并将上述代码复制到源文件中,编译并运行,其结果如图.4所示。
? 图.4?使用strcmp函数示例结果 在C++的标准库中提供了一个string类。使用这个类,可以直接进行赋值、比较、计算长度和连接等操作。.3.1汉字编码方式的介绍
对英文字符的处理,7位ASCII码字符集中的字符即可满足使用需求,且英文字符在计算机上的输入及输出也非常简单,因此,英文字符的输入、存储、内部处理和输出都可以只用同一个编码(如ASCII码)。
而汉字是一种象形文字,字数极多(现代汉字中仅常用字就有六、七千个,总字数高达5万个以上),且字形复杂,每一个汉字都有“音、形、义”三要素,同音字、异体字也很多,这些都给汉字的的计算机处理带来了很大的困难。要在计算机中处理汉字,必须解决以下几个问题:首先是汉字的输入,即如何把结构复杂的方块汉字输入到计算机中去,这是汉字处理的关键;其次,汉字在计算机内如何表示和存储?如何与西文兼容?最后,如何将汉字的处理结果从计算机内输出?
为此,必须将汉字代码化,即对汉字进行编码。对应于上述汉字处理过程中的输入、内部处理及输出这三个主要环节,每一个汉字的编码都包括输入码、交换码、内部码和字形码。在计算机的汉字信息处理系统中,处理汉字时要进行如下的代码转换:输入码→交换码→内部码→字形码。
(1)输入码:作用是利用它和现有的标准西文键盘结合来输入汉字。输入码也称为外码。主要归为四类:
a)数字编码:数字编码是用等长的数字串为汉字逐一编号,以这个编号作为汉字的输入码。例如,区位码、电报码等都属于数字编码。
b)拼音码:拼音码是以汉字的读音为基础的输入办法。
c)字形码:字形码是以汉字的字形结构为基础的输入编码。例如,五笔字型码(王码)。
d)音形码:音形码是兼顾汉字的读音和字形的输入编码。
(2)交换码:用于汉字外码和内部码的交换。交换码的国家标准代号为GB2312-80。
(3)内部码:内部码是汉字在计算机内的基本表示形式,是计算机对汉字进行识别、存储、处理和传输所用的编码。内部码也是双字节编码,将国标码两个字节的最高位都置为“1”,即转换成汉字的内部码。
(4)字形码:字形码是表示汉字字形信息(汉字的结构、形状、笔划等)的编码,用来实现计算机对汉字的输出(显示、打印)。
1.3.2VC/C++中汉字的编码方式
VC/C++正是采用了GB2312内部码作为汉字的编码方式,因此VC/C++中的各种输入输出方法,如cin/wcin,cout/wcout,scanf/wsanf,printf/wprintf……都是基于GB2312的,如果汉字的内码不是这种编码方式,那么利用上述各种方法就不会正确的解析汉字。
仔细观察ASCII字符表,从第161个字符开始,后面的字符并不经常为用户所使用,负值也未使用。GB2312编码方式充分利用这一特性,将161-255(-95~-1)之间的数值空间作为汉字的标识码。既然255-161=94不能满足汉字容量的要求,就将每两个字符并在一块(即一个汉字占两个字节),显然,9494=8836基本上已经满足了常用汉字个数的要求。计算机处理字符时,当连续处理到两个大与160(或-95~-1)的字节时,就认为这两个字节存放了一个汉字字符。可以用下面的Demo程序来模拟VC/C++中输出汉字字符的过程:
unsignedcharinput[50];
cin>>input;
intflag=0;
for(inti=0;i<50;i++)
{
if(input[i]>0xa0&&input[i]!=0)
{
if(flag==1)
{
cout<<"chinesecharacter" }
else
{
flag++;
}
}
elseif(input[i]==0)
{
break;
}
else
{
cout<<"englishcharacter"<}
}
输入:Hello中国(“中国”对应的GB2312内码为:214208,185250)
输出:englishcharacter
englishcharacter
englishcharacter
englishcharacter
englishcharacter
chinesecharacter
chinesecharacter
VC/C++中的英文字符仍然采用ASCII编码方式。可以设想,其他国家程序员利用VC/C++编写程序输入本国字符时,VC/C+++则会采用该国的字符编码方式来处理这些字符。
问题又产生了,韩国的VC/C++程序在中国的VC/C++上运行时,如果没有相应的内码库,则对韩语字符的显示有可能出现乱码。我个人猜测,VC安装程序中应该带有不同国家的内码库,这样一来肯定会占用很大的空间。如果所有的国家使用统一的编码方式,且所有的程序设计语言和开发工具都支持这种编码方式该多好!而现实中,确实已经有这种编码方式了,且许多新的语言也都支持这种编码方式,如Java、C#等,它就是下面的Unicode编码
1.3.3新的内码标准——Unicode
Unicode(统一码、万国码、单一码)是一种在计算机上使用的字符编码。它为每种语言中的每个字符设定了统一并且唯一的二进制编码,以满足跨语言、跨平台进行文本转换、处理的要求。1990年开始研发,1994年正式公布。随着计算机工作能力的增强,Unicode也在面世以来的十多年里得到普及。最新版本的Unicode是2005年3月31日推出的Unicode4.1.0。
Unicode编码系统可分为编码方式和实现方式两个层次。
编码方式:Unicode的编码方式与ISO10646的通用字符集(UniversalCharacterSet,UCS)概念相对应,目前的用于实用的Unicode版本对应于UCS-2,使用16位的编码空间。也就是每个字符占用2个字节。这样理论上一共最多可以表示216个字符。基本满足各种语言的使用。实际上目前版本的Unicode尚未填充满这16位编码,保留了大量空间作为特殊使用或将来扩展。
实现方式:Unicode的实现方式不同于编码方式。一个字符的Unicode编码是确定的。但是在实际传输过程中,由于不同系统平台的设计不一定一致,以及出于节省空间的目的,对Unicode编码的实现方式有所不同。Unicode的实现方式称为Unicode转换格式(UnicodeTranslationFormat,简称为UTF)。如,UTF-8编码,这是一种变长编码,它将基本7位ASCII字符仍用7位编码表示,占用一个字节(首位补0)。而遇到与其他Unicode字符混合的情况,将按一定算法转换,每个字符使用1-3个字节编码,并利用首位为0或1进行识别。
Java与C#语言都是采用Unicode编码方式,在这两种语言中定义一个字符,在内存中存放的就是这个字符的两字节Unicode码。如下所示:
chara=''我'';=>内存中存放的Unicode码为:25105
1.3.4内码的相互转换
(1)VC中的实现方法
利用Windows系统提供的API:::MultiByteToWideChar和::WideCharToMultiByte
::MultiByteToWideChar:实现当前码到Unicode码的转换;
::WideCharToMultiByte:实现Unicode码到当前码的转换;
(2)Java中的实现方法
StringvcString=newString(JavaString.getBytes("UTF-8"),"gb2312");
Java的编码应该是UTF-8
VC中的MutiByteCharaterSet
这种方式以按字节为单位存放字符,即如果一个字符码为两字节,则在内存中占两字节,字符码为一字节,就占一字节。例如,字符串“中国abc”的编码为:中(0xd6、0xd0)、国(0xb9、0xfa)、a(0x61)、b(0x62)、c(0x63)、(0x00),就存为如下方式
|
|