在C语言中,字符串表示为字符的数组。字符串中的最后一个字符是空字符(’\0’),这样,操作字符串的代码就知道在哪里结束,官方将这个空字符定义为NUL。C++包含一些来自C语言的字符串操作函数,它们在<cstring>头文件中定义。通常,这些函数直接操作内存分配。C++提供了一个得到极大改善的字符串概念,并作为标准库的一部分提供了这个字符串的实现。在C++中,std::string是一个类(实际上是basic_string模板类的一个实例),这个类支持<cstring>中提供的许多功能,还能自动管理内存分配。本篇博文就以使用字符串为话题展开,总结我们常用的字符串类型,希望对大家有一点帮助。
C风格字符串
前面说过,C风格字符串就是以’\0’为结束标志的字符串,实际是以数组的形式来存储的。下面我们用几个C字符串函数举几个栗子吧!
#define_CRT_SECURE_NO_WARNINGS //关闭安全性检测
#include<iostream>
#include<string>
#include<cassert>
#include<cstring>
#include<cstdio>
usingnamespacestd;
//字符串拷贝函数的内部实现
char *Mystrcpy(char *strDest,const char *strSrc)
{
//检验参数的合法性
assert(NULL != strDest && NULL != strSrc);
//保存目标字符串的起始位置,以便最后返回
char *strAddr = strDest;
//完成拷贝操作
while( '\0' != (*strDest++ = *strSrc++) )
{
NULL;
}
//返回目标字符串的起始位置
return strAddr;
}
int main()
{
const char *str = "C风格字符串";
size_t size = strlen(str);
//str[0]= 'K'; /*编译器会捕捉到任何写入只读内存的企图*/
//size大小为11,strlen计算的是实际字符串的大小,不包括字符串结束标志'\0'
printf("The length of \"%s\" is %d\n", str,size);
//为保证pStr能保存str中内容,必须在size的基础上加1,以便有位置来存储字符串结束标志'\0'
char *pStr = new char[size+ 1];
//使用C字符串函数完成字符串拷贝
strcpy(pStr,str);
printf("The content of pStr is \"%s\"\n",pStr);
//释放动态申请内存
delete[] pStr;
//将指针变量置空,避免野指针
pStr = nullptr;
pStr = newchar[20];
//使用自定义函数完成字符串拷贝操作
Mystrcpy(pStr,"Hello ");
//strcat字符串拼接,从pStr的结束标志'\0'处开始拼接str(包括str的'\0')
strcat(pStr,str);
printf("The content of pStr is \"%s\"\n",pStr);
//释放动态申请内存
delete[] pStr;
//将指针变量置空,避免野指针
pStr = nullptr;
return 0;
}
程序运行结果:
注意到程序第一行的宏定义了吗?相信大家在编写程序时,使用到C库函数时,在编译时经常会报如下警告,这可能会令有“NO ERROR,NO WARNING”编程习惯的小伙伴很不爽。
看到这张图是不是有一种似曾相识的感觉呢!这主要是因为C库函数关于字符串的操作函数都是不安全的,它们的安全性都有程序猿们来负责,这无疑是给程序猿增添了很多的麻烦,特别是处理一些内存溢出的问题。上面的警告是因为编译器的安全检查导致的,这似乎是程序员们的福音。如果你习惯了使用C库函数,又觉得这些WARNING令你很不爽,你可以尝试博主的做法(在源文件的第一行加上”#define_CRT_SECURE_NO_WARNINGS”来关闭编译器的安全性检查)。如果你还是觉有点不舒服,那么你可以尝试在所有有警告的函数后面加上“_s”,如strcpy_s。 “s”是safe的简写,这些函数都是做过安全处理的,大家放心使用就可以了。这就是C风格字符串的内容,下面让我们进入C++字符串吧!
C++字符串
为了理解C++string类的必要性,我们先来看看C风格字符串的优势和劣势吧
优势:
①很简单,底层使用了基本的字符类型和数组结构
②量级轻,如果使用得当,只会占用所需内存
③很低级,因为可以按操作原始内存的方式轻松操作和复制字符串
④能够很好地被C程序猿所理解
劣势:
①为了模拟字符串数据类型,需要付出很多努力
②使用难度大,而且很容易产生难以找到的内存bug
③没有使用C++面向对象的特性
④要求程序猿了解底层的实现形式
尽管string是一个类,但是可以把string当做内建类型来使用。事实上,把string想象成简单类型更容易发挥string的作用。通过运算符的重载,C++的string使用起来比C风格字符串容易得多。下面我们就写个简单的程序用一下C++string吧!
#include<iostream>
#include<string>
using namespace std;
int main()
{
string str1 = "C++string";
//operator=完成拷贝功能,相当于C库函数中的strcpy,操作是不是简单多啦
string str2 = str1;
cout<<"The content of str2 is "<<"\""<< str2 << "\""<< endl;
str1 = "Hello ";
//operater+和operator+=都可以完成字符串拼接功能
str1 += str2;//或str1 = str1 + str2;
cout << "The content of str1 is " << "\"" << str1 << "\"" << endl;
//为了达到兼容的目的,还可以使用c_str()方法获得一个表示C风格字符串的const字符指针
const char *pStr = str1.c_str();
cout << "The content of pStr is " << "\"" << pStr << "\"" << endl;
//将字符串转换为数值
string str3 = "100";
int value = 0;
value = stoi(str3,NULL, 8);
cout<<"value is "<< value << endl;
//将数值转换为字符串
value = 12345;
str3 = to_string(value);
cout << "The content of str3 is " << "\"" << str3 << "\"" << endl;
return0;
}
程序运行结果:
前面说过,在C++中,std::string是一个类(实际上是basic_string模板类的一个实例),这个类支持<cstring>中提供的许多功能,还能自动管理内存分配。string类在std名称空间的<string>头文件中定义。当string操作需要扩展string时,string类能够自动处理内存需求,因此不再会出现内存溢出的情况了。string重载了“+”和“+=”运算符,方便字符串之间的拼接;重载了“==”、“!=”、“<”、“<=”、“>”、“>=”运算符,方便字符串之间的比较;重载了“=”运算符,方便字符串之间的拷贝;……
为了达到兼容的目的,还可以用于string类的c_str()方法获得一个表示C风格字符串的const字符指针。不过,一旦string执行了任何内存重分配或string对象的销毁操作,这个返回的const指针就失效了。永远不要从函数中返回在基于堆栈的string上调用c_str()的结果。
std::string字面量
源程序中的字符串字面量通常解释为constchar*。在字符串后面加上“s”可以把字符串字面量解释为std::string(这是C++14新特性)。如:
auto str = “std::string字面量”s;
注意,有可能你的编译器不支持上述操作,因为std::string字面量是C++14新特性,只有支持C++14新特性的编译器才支持上述定义哦。
原始字符串字面量
原始字符串字面量(raw string literal)是可以横跨多行代码的字符串字面量,不需要转义嵌入的双引号,像“\t”“\n”等转义序列不再按照转义序列的方式来处理了,而是按照普通文件的方式来处理。
#include<iostream>
#include<string>
using namespace std;
int main()
{
//原始字符串字面量(raw string literal)是可以横跨多行代码的字符串字面量
string str = R"( '\n \t
"原始字符串字面量\v" ' )";
//普通字符串字面量需要借助"\"来实现可以横跨多行代码的字符串字面量
string str1 = "hello \
world\n";
cout << "The content of str is " << "\"" << str << "\"" << endl;
cout << "The content of str1 is " << "\"" << str1 << "\"" << endl;
//str= R"( "()" )"; /*原始字符串字面量中不能包含“"(”和“)"”*/
str = R"+( "()" )+";/*修改后就可以包含“"(”和“)"”*/
cout << "The content of str is " << "\"" << str << "\"" << endl;
return0;
}
程序运行结果:
注意,原始字符串字面量(rawstring literal)是可以横跨多行代码的字符串字面量,而普通字符串字面量需要借助“\”来实现可以横跨多行代码的字符串字面量。在原始字符串字面量中,不需要转义嵌入的双引号,像“\t”“\n”等转义序列不再按照转义序列的方式来处理了,而是按照普通文件的方式来处理。
如果需嵌入“"(”和“)"”,则需要使用扩展的原始字符串字面量语法,如下所示:
R”d-char-sequence(r-char-sequence)d-char-sequence”
r-char-sequence是实际的原始字符串。d-char-sequence是可选的分隔符序列,原始字符串首尾的分隔符序列应该一致。分隔符序列最多能有16个字符。应该选择未出现在原始字符串字面量中的序列作为分隔符序列。
上面的程序中:
str = R"("()" )";
改为:str = R"+("()" )+";// d-char-sequence为“ + ”,r-char-sequence为“ ”()” ”
在操作数据库查询字符串和正则表达式等字符串时,原始字符串字面量可以令程序的编写更加方便。
非标准字符串
许多C++程序猿都不使用C++风格的字符串,有这么几个原因:一是一些程序猿不是不知道有C++string类型,而是因为它并不总是C++规范的一部分。二是一些程序猿发现,C++string没有提供他们需要的行为,所有开发了自己的字符串类型。也许最常见的原因是,开发框架和操作系统有自己的表达字符串的方式。例如Microsoft MFC中的CString类,它常常用于兼容或解决遗留的问题。在C++开始项目时,提前确定团队如何表示字符串是非常重要的。
如果你想进一步了解CString的用法,请查阅MSDN。