在现代编程语言中,字符串处理是一个核心功能。C++语言在早期版本中对字符串的处理并不尽如人意,尤其是在性能和易用性方面。随着C++17标准的发布,引入了std::string_view,这是一个重要的改进,它提供了一种更高效的方式来处理字符串数据。本文将详细探讨std::string_view的功能、用法以及在实际编程中如何正确使用它。
std::string是C++中一个非常常用的字符串类,它提供了对字符数组的封装,使得字符串的存储、修改、迭代和显示变得更加方便。例如,可以轻松地比较字符串的前缀或后缀,或者提取子字符串。但是,std::string在某些情况下也存在一些不足,比如在进行子字符串操作时可能会产生不必要的内存分配。
std::string str = "My str";
std::string prefix = "My ";
if (str.compare(0, prefix.size(), prefix) == 0) {
std::cout << str.substr(prefix.size());
}
在上面的例子中,substr函数会返回一个新的std::string实例,这在某些情况下是不必要的,因为它并不修改原始字符串。为了避免这种情况,可以直接通过索引访问字符串中的字符。
for (size_t i = prefix.size(); i < str.size(); ++i) {
std::cout << str[i];
}
C++17引入了std::string_view,它是一个轻量级的字符串视图,允许查看已经分配的连续内存区域,而不需要为子字符串分配新的std::string实例。这不仅避免了C风格的字符串比较语法,还减少了不必要的内存分配。
std::string str = "My str";
std::string prefix = "My ";
std::string_view str_v = str;
if (str_v.substr(0, prefix.size()) == prefix) {
std::cout << str_v.substr(prefix.size());
}
使用std::string_view,可以更简洁地编写代码,同时提高性能。例如,可以轻松地检查字符串是否以特定的前缀或后缀开始或结束,而不需要进行任何字符串分配。
bool validate(std::string_view str) {
std::string start = "lstart", stop = "lstop";
return str.substr(0, start.size()) == start &&
str.substr(str.size() - stop.size()) == stop;
}
std::string_view实际上是一个结构体,它包含指向字符缓冲区起始位置的指针和大小信息。这些信息在构造函数中传递,并在substr函数中提取到新实例。从std::string实例构造std::string_view时,实际上是使用std::string::operator basic_string_view,然后从std::string_view构造std::string_view。
std::string_view还可以从char*实例或char*和size_t参数构造。这意味着,如果只需要查看和分析编译时字符串(这些字符串存储在二进制源代码中,因此它们的地址可以用于std::string_view),可以直接将它们分配给std::string_view实例,而不是首先构造std::string实例。
std::string_view str = "My str";
std::string_view prefix = "My ";
if (str.substr(0, prefix.size()) == prefix) {
std::cout << str.substr(prefix.size());
}
在C++20和C++23中,std::string_view和std::string对象引入了新的成员函数,如starts_with和ends_with(非常适合上述例子),以及C++23中的contains成员函数。
std::string_view str = "My str";
std::string_view prefix = "My ";
if (str.starts_with(prefix)) {
std::cout << str.substr(prefix.size());
}
所有上述函数都可以在constexpr上下文中使用或定义。由于std::string_view不分配任何新数据,它是编译时编程的开放窗口。
constexpr std::string_view str = "My str";
constexpr std::string_view prefix = "My ";
if (str.starts_with(prefix)) {
std::cout << str.substr(prefix.size());
}
std::string_view旨在在分析字符串时提供更好的性能。然而,性能和安全性之间总是存在权衡,当处理std::string_view时,这种权衡尤为明显。
规则#1:永远不要返回std::string_view。返回std::string_view可能会导致不安全的内存访问,因为返回的std::string_view指向的内存在析构函数中被释放,这意味着返回的std::string_view现在指向已释放的内存。
std::string_view func() {
std::string str;
std::cin >> str;
return str;
}
规则#2:小心空终止符。如前所述,不推荐使用空终止符,使用std::string_view时应始终牢记这一点。
std::string_view str = "my cool str";
str.remove_prefix(str.find("\n"));
str.remove_suffix(str.size() - str.rfind("\n"));
std::cout << str;
// "cool" - OK
std::cout << str.data();
// "cool str"
const char* get() {
return new char[]{"my new str"};
}
{
std::string_view str = get();
// Here we can call: delete str.data()
str.remove_prefix(1);
// 内存泄漏!
// delete str.data() // 无效调用。
// 指针不指向分配部分的头部。
}