std::string与std::string_view
std::string
std::string是一个类,实际上是basic_string模板类的一个实例,这个类支持<cstring>
中提供的许多功能,还能自动管理内存分配。string类在std名称空间的<string>
头文件中定义。
尽管string是一个类,但是可以当做一个内置类型来使用,把string想象为简单的类型更容易发挥string的作用,通过运算符重载的神奇作用。c++的string使用起来比C字符串更加容易。比如,给string重载+运算符,以表示“字符串串联;重载+=,表示追加一个字符串。
string a {"12"};
string b {"34"};
string c;
c = a + b; // c是“1234”
a += b; // a是”1234“
c与c++字符串比较
C风格的字符串不能通过==运算符进行比较。例如:
char *a {"12"};
char b[] {"12"};
if (a == b) {} // error
if (strcmp(a, b) == 0) {} // OK
此外C字符串也无法通过<、<=、>=、>进行比较,因此需要通过strcmp()根据字符串的返回值-1,0,1来判断,这样会产生非常笨拙,可读性低的代码,比较容易出错。
在c++中的string类,操作符(==、!=、和<等)都被重载了,这些运算符操作真正的字符串字符,单独的字符可以通过方括号运算符[]访问。
在c++string中还提供了compare()方法,它的行为类似于strcmp()并且具有类似的返回类型。如下:
string a {"12"};
string b {"34"};
auto result {a.compare(b)};
result > 0; // a>b
result < 0; // a<b
result = 0; // a=b
这里的compare()和strcmp()一样,有很多返回值,所以必须记住这些返回值的含义,比如对于想要比较两个字符串相等就做某事,很容易写出if (a.compare(b)) {do somthing};
compare()相等等于0,所以此行代码正好写反了。那么如果直接使用==来直接比较两个字符串就清晰明了的多。
还可以用c++20的三向比较运算符来比较字符串。如:
auto result { a <=> b};
if (is_lt(result)) {cout << "less" << endl;}
if (is_gt(result)) {cout << "greater" << endl;}
if (is_eq(result)) {cout << "equal" << endl;}
而且string类能够自动处理内存需求,因此不会在产生内存溢出的情况了。
与C风格字符串兼容
为达到兼容的目的,还可以应用string类的c_str()方法获得一个表示C风格字符串的const char指针。不过,一旦string执行任何内存重分配或者string对象被销毁了,返回的这个const指针就失效了。应该在使用之前调用这个方法,以便于它准确反映当前的内容。永远不要重函数返回中返回在基于栈的string上调用c_str()的结果。
还有一个data()方法,在c++14及更早的版本中,始终与c_str()一样返回const char *。从c++17开始,在非const字符串上调用时,data()返回char *。
string的基本操作
- substr(pos, len):返回从给定的位置(pos)开始的给定长度(len)的子字符串。
- find(str):如果找到给定的字符串,返回它的位置;如没有找到,返回string::npos。
- replace(pos, len, str):将字符串的一部分(给定开始位置和长度)替换为另一个字符串。
- starts_with(str)/ends_with(str):如果一个字符串以给定的字串开始或者结尾,则返回true。
注意:从c++20开始,std::string是constexpr类,这意味着string可以用于在编译期执行操作。并且可以用于constexpr函数和类的实现。
string字面量
源代码中的字符串字面量通常解释为const char *。使用用户自定义的标准字面量s可以把字符串字面量解释为string。例如:
auto string1 {"hello world"}; // string1 is const char*
auto string2 {"hello world"s}; // string2 is std::string
标准用户定义字面量s在std::literals::string_literals名称空间中定义。但是,string_literals和literals名称空间都是所谓的内联名称空间。
std::vector和字符串的CTAD(类模板参数推导)
std::vector支持类模板参数推导,允许编译器根据初始化列表自动推导vector的类型,对字符串vector使用CTAD时必须小心。例如:
vector names {"john", "sam", "joe"};
推导出来的类型是vector<const char*>,而不是vector <string>
这是一个很容易犯的错误,可能会导致代码出现一些奇怪的行为,甚至崩溃。这取决于之后对vector的处理方式。
如果需要一个vector<string>
,可以使用std::string的字面量。
vector names {"john"s, "sam"s, "joe"s};
std::string_view
在c++17之前,为接受只读字符串的函数在选择形参的时候往往比较纠结。如果选择const char *的话,那么当程序员使用string的时候,则必须调用c_str()或者data()来获取const char *,这样做的话,函数就失去了string良好的面向对象的方面及其良好的辅助用法。那么如果把形参改为const string&呢?这样的话就始终需要string。例如,如果传递一个字面值字面量,编译器将默认创建一个临时字符串对象(字符串的副本),并且将该对象传递给函数,因为会增加一点开销。所以,有时候程序员会编写同一个函数的多个重载版本,一个接受const char *,另一个接受const string &,但是显然,这不是最优的解法。
在c++17中,通过引入std::string_view类解决了所有问题,std::string_view类是std::basic_string_view类模版的实例化,在<string_view>
中定义。string_view基本上就是const string&的简单替代品,但是不会产生开销。它从不复制字符串,string_view支持与string类似的接口。例外就是缺少c_str,但data()是可以使用的。另外,string_view还添加了remove_prefix(size_t)和remove_suffix(size_t)函数,前者是将起始指针后移给定的偏移量来收缩字符串,后者是将结尾的指针倒退给定的偏移量来收缩字符串。
string_view和string使用方式基本上是一样的,如下代码,extractExtension() 函数提取给定的文件扩展名(包含点符号)并返回。
string_view extractExtension(string_view filename)
{
return filename.substr(filename.rfind('.'));
}
int main()
{
// 注意下面代码中R是c++17引入的原始字符串字面量写法,format是c++20引入的写法,详情可以点击本文下方链接学习
string filename {R"(c:\temp\my file.ext)"};
cout << format("c++ string: {}", extractExtension(filename)) << endl;
const char *cString {R"(c:\temp\my file.ext)"};
cout << format("c string: {}", extractExtension(filename)) << endl;
cout << format("literal: {}", extractExtension(R"(c:\temp\my file.ext)")) << endl;
return 0;
}
输出结果:
c++ string: .ext
c string: .ext
literal: .ext
在对于extractExtension()函数的调用,没有一次进行了复制。参数中的filename只是指针和长度,该函数的返回类型也是如此。这些都十分高效。
注意:在每当函数需要将只读字符串作为一个参数的时候,可以使用std::string_view替代const string&或者const char *
无法从sring_view隐式构建一个string。要么使用显示的string构造函数,要么使用string_view::data()成员。例如:
void func(const string& args) {/*...*/}
//不能用下面的方式调用
func(extractExtension("my file.ext")); // 不能从string_view隐士转换到string
可以通过下面的两种方式
func(extractExtension("my file.ext").data());
func(string{extractExtension("my file.ext")});
和上面的原因相同,同样无法连接一个string和一个string_view。
string str {"hello"};
string_view sv {"world"};
auto result {str + sv}; // ERROR
auto result {str + sv.data()}; // yes
//或者使用append()函数
string result2 {str};
result2.append(sv.data(), sv.size());
警告:返回字符串的函数应该返回const string& 或者string的类型,不应该返回string_view。因为这样可能带来使返回的string_view无效的风险。例如当它指向的字符串需要重新分配时。
string_view不应该用于保存一个临时字符串,因为可能会出现未定义的行为,主要取决于编译器和编译设置。
c++17字符串字面量链接https://blog.csdn.net/weixin_48617416/article/details/129655630?spm=1001.2014.3001.5501
c++20std::format链接https://blog.csdn.net/weixin_48617416/article/details/129700418?spm=1001.2014.3001.5501