为了解决std::string初始化(或拷贝)成本高昂的问题,C++17引入了std::string_view。std::string_view提供对现有字符串(C风格字符串、std::string、或另一个std::string_view)的只读访问,而无需进行拷贝。当想要有效地处理和操作字符串而不修改它们时,通常使用std::string_view。
int test_string_view_copy()
{
std::string str1{ "china" };
std::string_view strv1{ "beijing" };
// str2和str1的数据存放在完全不同的两个位置(副本); strv2和strv1的数据都指向同一个指针地址,没有额外的拷贝(副本)
std::string str2{ str1 };
std::string_view strv2{ strv1 };
std::cout << "str2: " << str2 << ", strv2: " << strv2 << "\n"; // str2: china, strv2: beijing
str1.clear();
strv1 = {}; //strv1 = strv1.substr(0, 0); //strv1.remove_prefix(strv1.size()); // 注意它们的区别
// clear str1完全不会影响到str2; "clear" strv1也不会影响到strv2,因为strv2指向的指针地址没有变,对strv1 "clear"操作并不会影响到它之前指向的指针地址数据
std::cout << "str2: " << str2 << ", strv2: " << strv2 << "\n"; // str2: china, strv2: beijing
return 0;
}
std::string为模板类std::basic_string<char>,而std::string_view为模板类std::basic_string_view<char>。如下,其中CharT为character type。
template<class CharT,
class Traits = std::char_traits<CharT>,
class Allocator = std::allocator<CharT> >
class basic_string;
template<class CharT,
class Traits = std::char_traits<CharT> >
class basic_string_view;
using string = basic_string<char>;
using string_view = basic_string_view<char>;
模板类std::basic_string的声明参考: https://en.cppreference.com/w/cpp/header/string
模板类std::basic_string_view的声明参考:https://en.cppreference.com/w/cpp/header/string_view
std::basic_string有析构函数,而std::basic_string_view没有析构函数。
std::string_view优点:
1.轻便:主要用于提供字符串的视图(view),使std::string_view拷贝字符串的过程非常高效,永远不会创建字符串的任何副本,不像std::string会效率低下且导致内存开销。std::string_view不拥有字符串数据,它仅提供对现有字符串的视图或引用(view or reference)。这使得它适合需要访问或处理字符串而无需内存分配或重新分配开销的场景.
2.轻量高效:由于std::string_view不管理内存,因此它具有最小的内存占用。分配或拷贝std::string_view既快速又便宜,因为它只涉及拷贝字符串的引用、长度和起始位置。
3.更好的性能:std::string_view比const std::string&更好,因为它消除了在字符串的最开头有一个std::string对象的约束,因为std::string_view由两个元素组成:第一个是const char*,指向数组的起始位置,第二个是size。
4.std::string_view提供了std::string提供的成员函数的子集,主要集中于只读操作。虽然你可以访问字符并执行搜索,但无法修改std::string_view的内容。这种不变性确保了操作的安全性,并保证所查看的字符串保持不变。
注意:
1.与C字符串和std::string在字符串末尾需要字符串终止符('\0')不同,std::string_view不需要空终止符来标记字符串的结尾。因为它记录了字符串的长度。
2.从概念上讲,std::string_view只是字符串的视图,不能用于实际修改字符串。创建std::string_view无需拷贝数据。
3.std::string_view可以使用许多不同类型的字符串进行初始化:C格式字符串、std::string、或另一个std::string_view。C风格字符串和std::string都会隐式转换为std::string_view。但std::string_view不会隐式转换为std::string:因为std::string会拷贝初始值(这是昂贵的),所以C++不允许将std::string_view隐式转换为std::string。这是为了防止意外地将std::string_view参数传递给std::string参数,以及无意中在可能不需要此类副本的情况下创建昂贵的副本。如果需要,可以有两个选择:
(1).使用std::string_view初始值显式创建std::string;
(2).使用static_cast将现有的std::string_view转换为std::string.
以下为测试代码:注意注释说明
int test_string_view_functions()
{
// unlike std::string, std::string_view has full support for constexpr
constexpr std::string_view strv{ "china beijing" };
// substr
auto strv1 = strv.substr(6);
std::cout << "strv1: " << strv1 << std::endl; // strv1: beijing
//strv1[0] = 'B'; // 无论strv是不是constexpr, strv1都不能作修改
char str1[]{ "beijing" };
std::string_view strv2{ str1 };
std::cout << "strv2: " << strv2 << std::endl; // strv2: beijing
str1[0] = 'B';
std::cout << "strv2: " << strv2 << std::endl; // strv2: Beijing
std::cout << "at 2: " << strv2.at(2) << ", back: " << strv2.back() << "\n"; // at 2: i, back: g
strv2.remove_prefix(3);
std::cout << "remove_prefix 3: " << strv2 << "\n"; // remove_prefix 3: jing
strv2.remove_suffix(2);
std::cout << "remove_suffix 2: " << strv2 << "\n"; // remove_suffix 2: ji
std::string str{};
strv.copy(str.data(), strv.size()); // 注意str并非完整的std::string对象,str.size()为0
std::cout << "str: " << str.data() << "\n"; // str: china beijing note:是str.data()而不能是str
// 注意以下两条语句的差异, str.size()为0
std::cout << "strv.compare(str.data()): " << strv.compare(str.data()) << "\n"; // strv.compare(str.data()): 0
std::cout << "strv.compare(str): " << strv.compare(str) << "\n"; // windows: strv.compare(str): 1 linux: strv.compare(str): 13
auto found = strv.find(strv2);
std::cout << "found: " << found << "\n"; // found: 9
std::cout << "strv rfind: " << strv.rfind("i") << "\n"; // strv rfind: 10
std::cout << "strv find_first_of: " << strv.find_first_of("i") << "\n"; // strv find_first_of: 2
std::cout << "strv find_last_of: " << strv.find_last_of("i") << "\n"; // strv find_last_of: 10
std::cout << "strv find_last_not_of: " << strv.find_last_not_of("in") << "\n"; // strv find_last_not_of: 12
std::cout << "strv find_first_not_of: " << strv.find_first_not_of("in", 1) << "\n"; // strv find_first_not_of: 1
// std::string_view不需要空终止符来标记字符串的结尾,因为它记录了字符串的长度
char name[]{ 'c', 'h', 'i', 'n', 'a' };
std::string_view strv3{ name, std::size(name) };
std::cout << "strv3: " << strv3 << "\n"; // strv3: china
std::cout << "name: " << name << "\n"; // windows: name: china烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫蘐?宋 linux: name: chinaBeijing
// std::string_view to std::string: std::string_view不会隐式转换为std::string
std::string str2{ strv }; // explicit conversion
std::cout << "str2: " << str2 << "\n"; // str2: china beijing
// std::string_view to C-style string: std::string_view -> std::string -> C-style string
auto str3{ str2.c_str() };
std::cout << "str3 length: " << strlen(str3) << "\n"; // str3 length: 13
// std::string_view just only a view
auto addr = [] {
std::string str_csdn{ "https://blog.csdn.net/fengbingchun/" };
std::string_view strv_csdn{ str_csdn };
std::cout << "strv csdn: " << strv_csdn << "\n"; // strv csdn: https://blog.csdn.net/fengbingchun/
return strv_csdn;
};
std::string_view strv4{ addr() };
std::cout << "strv4 csdn: " << strv4 << "\n"; // windows: strv4 csdn: 葺葺葺葺葺葺葺葺葺葺葺葺葺葺葺葺葺 linux: strv4 csdn: ▒(a▒▒\▒▒G▒cn.net/fengbingchun/
// literals for std::string_view: 可以通过在双引号字符串文字后面使用sv后缀来创建类型为std::string_view的C风格字符串常量
using namespace std::string_literals; // access the s suffix
using namespace std::string_view_literals; // access the sv suffix
std::cout << "foo\n"; // no suffix is a C-style string literal
std::cout << "goo\n"s; // s suffix is a std::string literal
std::cout << "moo\n"sv; // sv suffix is a std::string_view literal
return 0;
}
执行结果如下图所示:
GitHub:https://github.com/fengbingchun/Messy_Test