3.5.2 字符串类型
使用char类型的变量我们可以表示单个字符,那么,我们又该如何表示拥有多个字符的字符串呢? 我们注意到,一个字符串是由多个字符串连起来形成的。很自然地,一种最简单直接的方法就是利用数组(一种数据组织管理方式,它将多个相同类型的数据元素组织起来,形成一个数据序列以便于访问。更多可以参考后文3.6小节对数组的介绍)来保存一个字符串中的各个字符,最后用一个特殊字符‘\0’表示字符串的结束,以此来将多个char类型的字符数据串联成字符串。例如:
// 定义一个字符数组,用以保存字符串
char msg[];
// 将各个字符依次保存到数组相应位置上
msg[] = 'G'; // 第一个字符,保存到数组的第一个位置,后面的以此类推
msg[] = 'o';
msg[] = 'o';
msg[] = 'd';
// 在最后的位置保存一个‘\0’,表示字符串结束
msg[] = '\0';
// 输出msg数组中的字符串
cout<<msg<<endl;
用字符数组表示字符串的方式虽然简单可行,可是却有诸多使用上的不便。在C++中,我们更多地是用STL(Standard Template Library,标准模板库,是利用模板技术实现的一套函数库,其中提供了一些常用的容器和算法以便于对数据进行处理。这里的字符串类型 string就是由它定义的一种用以表示字符串的数据类型。关于STL,在稍后的第8章中我们还将详细介绍)中定义好的字符串类型string来表示字符串。这种字符串类型实质上是对一个char类型字符数组的包装,在保存字符串数据的同时还增加了一些对字符串的常用操作,比如获取字符串长度,查找特定字符等,这样字符串的处理就方便多了。就像char和wchar_t相互对应处理不同范围的字符一样,与string对应的,C++还提供了可以处理wchar_t类型字符的wstring字符串类型。例如:
#include <iostream>
#include <string> // string类型所在的头文件 using namespace std; int main()
{
// 定义一个string类型变量,表示英文字符串
string strEn = "Good Morning!";
// 定义一个wstring类型变量,表示中文字符串
wstring strChs = L"陕A-82103";
// 用cout输出string类型字符串
// 用wcout输出wstring类型字符串
cout<<strEn; wcout.imbue( locale ( "chs" ) ); // 设置区域 wcout<<strChs<<endl; return ;
}
可以看到,因为string和wstring编码方式的不同,不同的字符串在输出的时候需要采用不同的方式。对于wstring类型的字符串变量,在输出的时候需要使用wcout对象,并且需要用imbue()函数设定字符的编码方式。另外,这里值得指出的一点是,英文字符串不仅可以用string类型表示,也可以使用wstring类型表示,而中文字符串则只能使用wstring类型表示。
string类型不仅包装了字符数组,可以存储字符串中的各个字符,同时还提供了很多跟字符串相关的操作,例如,可以获得一个字符串的长度,或者在字符串中查找某个字符等等,极大地方便了对字符串的处理。例如:
// 定义一个字符串变量,用于保存用户输入的用户名
string strName = ""; cout<<"请输入用户名:"<<endl;
// 获取用户输入的字符串,并保存到strName变量
cin>>strName;
// 通过string类型的length()函数获取strName的长度
// 并判断其长度是否小于6(字符串中是否少于6个字符)
if(strName.length() < )
{
// 如果小于,则进行错误提示
cout<<"错误:用户名至少包含6个字符"<<endl;
}
在上面这段代码中,我们首先定义了一个string类型的变量strName,用以保存用户输入的用户名字符串,然后用cin获取用户输入的字符串并保存到strName变量。接着再利用string类型的length()函数获取字符串的长度,也就是strName中的字符个数。最后用if条件结构将其与我们要求的字符串长度6进行比较,如果不符合条件则进行错误提示。
知道更多:auto类型变量——根据初始值推断真实的数据类型
在前面的章节中,我们介绍了C++中的多种数据类型:有表示整数的int类型也有表示浮点数的float类型;有表示单个字符的char类型也有表示字符串的string类型,这些意义不同用途各异的数据类型为我们定义变量来表示现实世界中的数据提供了丰富的选择。但是,这些数据类型在使用上有一个共同的要求,那就是在定义变量表示数据时,我们必须先要知道所要表示的数据是什么类型,到底是一个小数呢还是一串字符,然后才能据此确定到底是该使用float呢还是string。可是在开发实践中,有时候我们并不能非常容易地确定一个变量应该具有的数据类型。比如,将某个复杂表达式作为初始值赋值给一个新定义的变量时,我们往往很难确定这个表达式的数据类型,从而无法确定变量应有的数据类型。为了解决这个问题,C++11为我们提供了auto关键字,使用它作为某个变量定义的数据类型,编译器会根据这个变量的初始值,自动推断出这个变量合理的数据类型而无需我们人为指定。例如:
auto x = ; // 使用整数7对变量x进行初始化,x被推断为int类型
auto y = 1.982; // 使用浮点数1.982对变量y进行初始化,y被推断为double类型 Handler GetHandler();
// 使用GetHandler()函数的返回值对变量handler进行初始化
// handler被推断为Handler类型
auto handler = GetHandler();
这里我们在定义变量x的时候,并没有指定其具体的数据类型,而是使用auto做为代替。这样,编译器在编译这段代码时,会根据7这个初始值自动推断x的实际数据类型为int。同样的道理,使用浮点数1.982进行初始化的变量y会被编译器自动推断为double类型;而最后的一个变量handler会被初始化为GetHandler()函数的返回值类型Handler。虽然auto关键字会根据初始值自动推断变量的数据类型,但是,它的使用并不需要花费额外的编译时间。有好处而又没有额外的花费,auto关键字就像商场的免费大赠送,这样的大便宜谁不喜欢呢?
实际上,可以把auto关键字看成是一个变量定义中的数据类型占位符,它占据了原来应该是具体数据类型的位置。而在编译的时候,编译器会根据这个变量的初始值,推断出这个变量应有的具体数据类型,然后替换掉auto关键字,就成为一个普通的带有具体数据类型的变量定义了。用auto关键字定义变量的形式跟一般的定义变量的形式并无二异,唯一的差别之处在于,用auto关键字定义变量时,变量必须有初始值:
auto 变量名 = 初始值表达式; // 赋值形式
// 或
auto 变量名{初始值表达式}; // 初始化列表形式
这样,这个初始值表达式计算结果的数据类型将被编译器推断为变量的数据类型。
通常在定义变量时,如果我们很难准确地推断它的数据类型,或者是这个变量的数据类型难于书写,就可以使用auto作为变量的数据类型来定义变量,而真正的数据类型就交由编译器去根据变量的初始值推断得到好了。做这种苦力活,电脑要比人脑快多了。这样做不仅省去了我们自己推断数据类型的麻烦,避免了可能的人为错误,同时也可以达到简化代码的目的。例如:
template <typename T>
// 数据类型vector<T>之后的“&”符号,表示其后所定义的变量是一个引用
// 引用是C++中一种访问数据的特殊方式,在稍后的7.1小节中我们将详细介绍
void printall(const vector<T>& v)
{
// 根据v.begin()的返回值类型自动推断变量it的数据类型
for (auto it = v.begin(); it != v.end(); ++it)
cout << *it << endl;
}
为了表示同样的意义,如果没有auto关键字帮忙,我们不得不写成下面这种繁琐的形式:
template <typename T>
void printall(const vector<T>& v)
{
for (typename vector<T>::const_iterator it = v.begin();
it != v.end(); ++it)
cout << *it << endl;
}
除了简化代码之外,auto关键字有时候甚至能够帮助我们完成一些在C++11之前不可能完成的任务,成为一种必需。比如,在模板函数中,当一个变量的数据类型依赖于模板参数时,如果不使用auto关键字,将根本无法确定变量的数据类型,因为我们根本无法提前预知用户使用何种数据类型作为模板参数来调用这个模板函数,从而也就无法确定这个变量的数据类型。但是使用auto关键字之后,一切难题都将迎刃而解。例如:
template <typename T,typename U>
void mul(const T& t,const U& u)
{
// ...
// 用auto关键字做数据类型,编译器将根据u和t的实际数据类型,
// 自动推断变量tmp的数据类型
auto tmp = t*u; // ...
}
在这里,变量tmp的数据类型应该与模板参数T和U相乘结果的数据类型相同,也就是依赖于T和U的数据类型。对于程序员来说,在编写这个模板函数的时候,模板参数T和U的类型尚未确定,这样变量tmp的类型也就无法确定。所以,我们用auto关键字作为占位符,占据数据类型的位置,而真正的数据类型,则留待编译器在最终编译的时候,根据具体给定的模板参数T和U的类型而推断得到。这样,就把一件原来不可能的事情变成了可能。
使用auto关键字,可以根据变量的初始值自动推断其数据类型,这样就极大地方便了复杂数据类型变量的定义。但是,这种方式好是好,却有一个缺点,那就是每次推断得到的数据类型只能在定义变量的时候使用一次,无法保留下来继续使用。好不容易推断得到的数据类型只能使用一次,这就显得有点不够低碳环保了。而有时候,我们也需要这个推断得到的数据能够保留下来,从而可以重复使用以定义相同类型的多个变量。为了弥补这个缺点,C++11还提供了一个decltype关键字。它的使用语法形式如下:
typedef decltype(表达式) 用户数据类型;
其中,decltype(表达式)是这个表达式的推断数据类型(declared type),也就是这个表达式计算结果的数据类型。而typedef则是将这个数据类型定义为用户自定义的数据类型,换句话说,也就是为这个推断数据类型取一个名字,从而可以把它作为一个新的数据类型,用在定义变量、创建对象等任何需要数据类型的地方。例如,我们可以用decltype关键字改写上面的例子:
template <typename T,typename U>
void mul(const T& t,const U& u)
{
// ...
// 用decltype得到t*u的数据类型,
// 并用typedef关键字将其定义成一个新的数据类型M
typedef decltype(t*u) M;
// 用这个新的数据类型M定义指针变量(表示变量或函数地址的变量),创建M类型对象
M* tmp = nullptr;
tmp = new M; // ...
}
auto和decltype的作用有些相似,都可以推断某个表达式的具体数据类型。但是,两者的使用还是稍有差别。如果我们仅仅是想根据初始值确定一个变量合适的数据类型,那么auto是最佳人选。而只有当我们需要推断某个表达式的数据类型,并将其作为一种新的数据类型重复使用(比如,定义多个相同类型变量)或者单独使用(比如,作为函数的返回值类型)时,我们才真正需要用到decltype。
[Chen1]确认