文章目录
1、C风格字符串和C++字符串区别
我们编写的每个应用程序都会使用某种类型的字符串,使用C语言时,没有太多选择,只能使用普通的以’\0’结尾的字符数组表示的字符串。这种表达方式会导致许多问题,例如会导致安全漏洞的缓冲区溢出,C++标准库包含一个安全易用的std::string类,这个类符合RALL思想,没有这些缺点。
2、C++std::string类
C++提供了一个得到极大改善的字符串概念,并作为标准库的一部分提供了这个字符串的实现。在C++中,std::string是一个类(实际上是basic_string模板类的一个实例),这个类支持< cstring >中提供的许多功能,还能自动管理内存分配。
2.1、使用std::string类
C++的string类使用起来比C风格字符串更容易,以为它内部封装了许多运算符重载,如下是测试代码:
#include<iostream>
#include<string>
int main()
{
std::string str1 = "hello";
std::string str2 = " world";
auto str3 = str1 + str2;
std::cout << str3 << std::endl;
return 0;
}
2.2、字符串比较
在C++的string类中,操作符(==、!=、<、>等)都被重载了,这些运算符可以操作真正的字符串的字符,单独的字符可以通过方括号运算符[]访问。
C++string类另外提供了一个compare()方法,它的行为类似于C语言的strcmp()具有类似的返回类型,下面是测试代码:
#include<iostream>
#include<string>
int main()
{
std::string a = "12";
std::string b = "34";
auto ret = a.compare(b);
if (ret < 0)
{
std::cout << "a<b" << std::endl;
}
else if (ret > 0)
{
std::cout << "a>b" << std::endl;
}
else if (ret == 0)
{
std::cout << "a==b" << std::endl;
}
return 0;
}
测试代码的结果:
2.3、字符串与数字相互转化
很多情况下我们输出某条包含数字的语句时都需要将数字转化为字符串才可以将语句输出,如下是std标准库中封装的数字转化为字符串接口:
string to_string (int val);
string to_string (long val);
string to_string (long long val);
string to_string (unsigned val);
string to_string (unsigned long val);
string to_string (unsigned long long val);
string to_string (float val);
string to_string (double val);
string to_string (long double val);
下面是std::to_string的接口的案例:
#include<iostream>
#include<string>
int main()
{
int num = 42;
auto str = std::string("I have ") + std::to_string(42) + std::string("yuan");
std::cout << str << std::endl;
return 0;
}
上面讲了数字转化为字符串,如下是字符串转化为数字的接口:
接下来是字符串转化为数字的案例:
#include<iostream>
#include<string>
int main()
{
//字符串转化为整数
std::string str1 = "42";
auto num = std::stoi(str1);
std::cout << num << std::endl;
//字符串转化为浮点型
std::string str2 = "3.14";
auto dot = std::stof(str2);
std::cout << str2 << std::endl;
return 0;
}
注意:std::stoi/stof/stod/stoll等,它们的第二个参数是size_t类型的pos指的是非数字部分的位置,用于将非数字部分过滤掉,第三个参数是int 类型的base用于进行进制的转化,它默认是十进制。
2.4、字符串的截取操作
std::string提供了一个截取字符串的接口,它会截取从pos位置开始,长度为len的子字符串,原字符串不会发生改变,它的原型如下:
string substr(size_t pos=0,size_t len=-1)const
如果原字符串剩余部分长度不足len,则返回长度小于len的子字符串而不会出错。如果pos超出原字符串的范围,则会抛出std::out_of_range的异常,如下是字符串截取的案例:
#include<iostream>
#include<string>
int main()
{
std::string str = "helloSakura";
std::cout << "从第2个字符开始截取长度为4的字符串:" << str.substr(2, 4) << std::endl;
std::cout << "从第2个字符开始截取长度为99的字符串:" << str.substr(2, 99) << std::endl;
std::cout << "从第2个字符开始截取到末尾的字符串:" << str.substr(2) << std::endl;
std::cout << "从头开始截取长度为5的字符串:" << str.substr(0, 5) << std::endl;
//throw out_of_range
//std::cout << "从第100个字符开始截取长度为5的字符串:" << str.substr(100, 5) << std::endl;
return 0;
}
测试代码的运行结果:
2.5、字符串的查找
当我们要查找某一个字符串的子串时,可以通过string类中的find()方法查找,如下是find()方法的原型:
find(str,pos):如果找到了给定的子串,则返回它的位置;如果没有找到,则返回string::npos,如果给定了pos位置,则会从pos位置开始查找给定str,而不是从最开始位置查找str。
如下是find()的寻找子字符的测试例子:
#include<iostream>
#include<string>
int main()
{
std::string str = "helloSakura";
std::cout << "查找h字符的结果:" << str.find('h') << std::endl;
std::cout << "查找e字符的结果:" << str.find('e') << std::endl;
std::cout << "查找l字符的结果:" << str.find('l') << std::endl;
std::cout << "查找o字符的结果:" << str.find('o') << std::endl;
std::cout << "查找a字符的结果:" << str.find('a',2) << std::endl;
return 0;
}
测试代码的结果:
如下是find()的应用案例:计算子字符串出现次数
#include<iostream>
#include<string>
size_t stringCount(const std::string& str, const std::string& sub)
{
size_t num=0, pos = 0;
while (true)
{
pos = str.find(sub, pos);
if (pos == str.npos)
{
break;
}
num++;
pos += sub.size();
}
return num;
}
int main()
{
std::cout << stringCount("hellolldsdsallwds", "ll") << std::endl;
return 0;
}
测试代码的结果:
2.6、寻找集合内的任意字符
find_first_of是string类中查找在字符串中第一个与指定字符串中的某个字符匹配的字符,返回它的位置。它的原型如下:
寻找字符串集合内的任意字符的测试代码:
#include<iostream>
#include<string>
int main()
{
std::string str = "helloSakura!!";
auto it = str.find_first_of("lSa");
std::cout << it << std::endl;
return 0;
}
测试代码的结果:
2.7、替换子字符串
replace()是string类中用来替换一段子字符串的接口,它的原型为:
如下是replace()的代码测试:
#include<iostream>
#include<string>
int main()
{
std::string str = "make Sakura happy";
str.replace(5, 6, "Naruto");
std::cout << str << std::endl;
return 0;
}
测试代码的结果:
下面使用replace()批量替换字符串案例:
#include<iostream>
#include<string>
void replace_allStr(std::string& str, const std::string& from, const std::string& to)
{
size_t pos = 0;
while (true)
{
pos = str.find(from, pos);
if (pos == str.npos)
break;
str.replace(pos, from.size(), to);
pos += from.size();
}
}
int main()
{
std::string str = "hello Sakura,and good luck for Sakura!!";
replace_allStr(str, "Sakura", "Naruto");
std::cout << str << std::endl;
return 0;
}
测试代码的结果:
3、std::string_view
在C++17之前,为接收只读字符串的函数选择形参类型一直是一件进退两难的事情。若使用std::string,则必须调用c_str()或则data()来获取const char*。更糟糕的是,函数将失去std::string良好的面向对象的方面和良好的API。
在C++17中,通过引入std::string_view类解决了所有问题,std::string_view类是std::basic_string_view类模板的实例化。string_view基本就是const string&的简单替代品,但不会产生开销。它不复制字符串,string_view支持与std::string类似的接口。一个例外是string_view缺少c_str()的接口,但data()可用。另外string_view添加了remove_prefix(size_t)和remove_suffix(size_t)方法,前者将起始指针前移给定的偏移量来收缩字符串,后者则是将结尾指针倒退给定的偏移量来收缩字符串。
如下是string_view简单的使用案例:
#include<iostream>
#include<string>
#include<string_view>
int main()
{
std::string str1 = "hello";
//深拷贝
std::string str2 = str1;
//弱引用
std::string_view sv1 = str1;
//浅拷贝
std::string_view sv2 = sv1;
//打印未修改的测试结果
std::cout << "str1:" << str1 << std::endl;
std::cout << "str2:" << str2 << std::endl;
std::cout << "sv1:" << sv1 << std::endl;
std::cout << "sv2:" << sv2 << std::endl;
//分隔符
std::cout << "-----------------------------------------" << std::endl;
//修改str1的第一个字符
str1[0] = 'H';
//打印修改后的结果
std::cout << "str1:" << str1 << std::endl;
std::cout << "str2:" << str2 << std::endl;
std::cout << "sv1:" << sv1 << std::endl;
std::cout << "sv2:" << sv2 << std::endl;
return 0;
}
测试代码的结果:
注意:str2是对str1的深拷贝,因为调用了std::string的拷贝构造函数,所以str1被修改时,str2仍然不变。sv1和sv2都是指向str1的弱引用,所以str1被修改时,sv1和sv2都被改变。
3.1、string_view和string的运算符操作
当我们将string_view类型的常量弱引用类型的字符串和string类型的字符串进行相加(运算符+)操作时会出错,必须要先将string_view转化为const char*,也就是调用data()接口,测试代码如下:
#include<iostream>
#include<string>
#include<string_view>
int main()
{
std::string str1 = "hello";
std::string_view sv1 = " world";
//使用+号运算符时,必须将string_view转化为const char*
auto it = str1 + sv1.data();
//使用append追加字符串不会出错
auto it2 = str1.append(sv1);
std::cout << it2 << std::endl;
return 0;
}
3.2、string_view的字符串收缩
之前讲到了string_view除了可以使用string中的接口以外,自身也添加了两个新的接口,remove_prefix()和remove_suffix(),这两个接口用于实现字符串的收缩,下面是简单的使用这两个接口的案例:
#include<iostream>
#include<string>
#include<string_view>
int main()
{
std::string str1 = "hello Sakura";
std::string_view sv1 = str1;
//从前向后收缩1个字符
sv1.remove_prefix(1);
std::cout << "sv1:" << sv1 << std::endl;
//从后向前收缩一个字符
sv1.remove_suffix(1);
std::cout << "sv1:" << sv1 << std::endl;
return 0;
}
测试代码的结果: