在C++中,没有std::string_view之前,对于函数中需要传入只读字符串的情形,总是让人感到两难。如果传const char*吧,对于拥有了std::string类的情况,就需要调用c_str()或者data()函数来获得该指针,这看着就像开倒车,走回头路,一点也不面向对象;我们再看另一难,我的前面的文章也讲过,不传左边的指针,那就传右面的引用呗,为了体现面向对象,那就传const string&好了,你看着好像是完美了是吧,但你知道计算机是怎么做的吗?计算机为了传入const string&,总是需要一个string对象 ,传字符串文本到函数中是吧,编译器会悄没声地生成一个临时的string对象包含一个原有字符串文本的拷贝,这样的话是不是也不是我们想要的呢,毕竟出现了资源上的浪费。所以就有人对类的同样功能的方法(函数),对不同的传参const string&或const char *写出了不同的重载方法(函数),这样,怎么说呢,不能说不对,但不完美,也不优雅。
那怎么办呢,优化呗,从两难中的哪个难优化呢,指针的那个肯定不是一个好的选择,那就从const string&入手吧,传入引用肯定是好的,只是要解决不再出现资源上的浪费,也就是悄没声的再拷贝一份原有字符串的问题解决一下就可以了,好的,如此我们的std:string_view类就横空出世了,它完美地解决了这个问题,不再拷贝了。std:string_view提供了成员函数contains()、data()、remove_prefix(size_t)、remove_suffix(size_t),后两个成员函数根据偏移量从头向后或从尾向前移动指针缩小字符串,与std::string一样,std::string_view用nullptr构造也会报编译错误。说了这么多,大家可能还是没有直观印象,学习最好的方式就是,眼见为实,知识+实践,好咧,来实例:
import std;
using namespace std;
string extractExtension(string_view filename)
{
return string{ filename.substr(filename.rfind('.')) };
}
int main()
{
string filename{ R"(c:\temp\my file.ext)" };
println("C++ string: {}", extractExtension(filename));
const char* cString{ R"(c:\temp\my file.ext)" };
println("C String: {}", extractExtension(cString));
println("Literal: {}", extractExtension(R"(c:\temp\my file.ext)"));
const char* raw{ "Hello.txt" };
size_t length{ 9 };
println("Raw: {}", extractExtension({ raw,length }));
println("Raw: {}", extractExtension(string_view{ raw, length }));
return 0;
}
这个实例,就是构造了一个函数,该函数传入了一个std::string_view类,然后,利用不同的方式传参,我们先看结果,再做解析:
C++ string: .ext
C String: .ext
Literal: .ext
Raw: .txt
Raw: .txt
首先,该结果达到了我们的目的,我们简单解释一下:先解释一下extractExtension(),该函数调用rfind()从右往左找'.',substr()返回一个std:string_view类,然后传给string构造类,返回函数调用者。这里面都是值传递,函数返回的string类的构造成本最低。下面来看main()主函数,其对extractExtension()函数的调用,从根本上说,就是传递了一个指向字符串常量的指针和该字符串的长度,不管怎么调用,其根本上就是这么回事,还比较高效。从最后的两个对extractExtension()的调用可以看出,也可以用指向字符串常量的指针和数值来构造std::string_view类,这里需要注意的是,当传入的长度与字符串的实际长度不一致时,如果长度值小于实际长度,则以数值为准,截取数值长度的字符串,也就是可以解决字符串不心NUL(\0)的问题;如果长度值大于实际长度,则系统会自动计算字符串的长度,忽略传入的长度值,有兴趣的同学可以自己尝试修改,看看是不是这么回事。
不能隐式地从std::string_view构造std::string,这是为了防止意外地不必要的发生拷贝行为,这个动作是禁止的。想从std::string_view构造std::string类,像以上例子中,显示地调用std::string构造器就可以了。还有,不能将std::string_view与std::string进行连接,编译器会报错,如下的示例是无法编译通过的:
string str{ "Hello" };
string_view sv{ " world" };
auto result{ str + sv };
这时候,编译器会报错:E0349 没有与这些操作数匹配的 "+" 运算符。
如何修改,有两种方式,一种是将string_view显示地构造为string,如下:
string str{ "Hello" };
string_view sv{ " world" };
//auto result{ str + sv };
auto result1{ str + string{sv} };
另外一种是使用append()方法,如下:
string str{ "Hello" };
string_view sv{ " world" };
string result{ str };
result.append(sv.data(), sv.size());
这里需要多说一句,std::string_view类是有data()成员函数的,这个在前面已经说过了,不再赘述。
好了,结束之前再说两点吧,一是不要滥用std::string_view,在函数返回值中不要用,因为正常应该用const string&或者string,否则可能会破坏string_view,引起地址重新分配;第二,在类中的const string&或者string_view数据成员要确保在类的生命周期其处于存活状态,强烈建议用string存储。各位同学辛苦。