string类和string_view类

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的基本操作

  1. substr(pos, len):返回从给定的位置(pos)开始的给定长度(len)的子字符串。
  2. find(str):如果找到给定的字符串,返回它的位置;如没有找到,返回string::npos。
  3. replace(pos, len, str):将字符串的一部分(给定开始位置和长度)替换为另一个字符串。
  4. 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

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值