日常开发中,字符串处理是最常见操作之一。C++提供了std::string
和char*
两种字符串类型。然而,在某些场景下,它们可能会带来性能问题或设计上的局限性。为了解决这些问题,C++17 引入了 std::string_view
。
std::string
和char*
存在瑕疵,才引入的std::string_view
。那std::string_view
解决了std::string
和char*
的什么问题呢
std::string
被传递给函数时,通常会发生一次深拷贝操作,即复制整个字符串内容。这一操作对于较大的字符串来说,可能会导致显著的性能开销。std::string
可能会重新分配内存以适应新的内容,这种重新分配会带来额外的性能开销。char*
的安全性问题:char*
本身并不包含有关字符串长度的任何信息,因此开发人员必须依赖字符串结尾的空字符('\0'
)来确定字符串的结束位置。这种做法容易引发字符串越界、内存访问错误等问题。代码示例
#include <iostream>
#include <string>
//1. 函数传参
void need_copy_with_non_const_string(std::string str) {
std::cout << "Processing: " << str << std::endl;
}
//2. std::string 会触发内存的重新分配
int need_realloc
{
std::string str = "Initial String";
str += " with more data"; // 修改字符串,可能导致重新分配内存
std::cout << str << std::endl;
return0;
}
int main() {
std::string large_str = "This is a large string that might be copied.";
need_copy_with_non_const_string(large_str); // 传递字符串时,发生了复制
need_realloc(); // 修改字符串,可能导致重新分配内存
return0;
}
注意: 在上述代码中,当 std::string
被传递给 process_string
函数时,整个字符串的数据会被复制到该函数的局部变量中。 当然,该问题并非不可解,可以通过以下方式进行优化:
const char*
传递:使用 const char*
作为参数类型,可以避免不必要的复制。std::string_view
作为 C++17 引入的一种轻量级的新型字符串视图类,仅持有一个指向字符串数据的指针和一个表示字符串长度的整数。其具有如下优势:
std::string_view
避免了不必要的内存复制,提高了性能。std::string_view
避免了内存分配与释放,减少了内存开销。std::string_view
提供了字符串的长度信息,避免了字符串越界问题。具体代码示例如下:
//1. 函数传参
#include <iostream>
#include <string_view>
void process_string(std::string_view str) { // 传递字符串视图,不复制字符串
std::cout << "Processing: " << str << std::endl;
}
int main() {
std::string large_str = "This is a large string that might be copied.";
process_string(large_str); // 直接传递视图,避免复制
return0;
}
//2. 避免内存分配与释放
int main() {
constchar* cstr = "This is a C-string";
std::string_view view(cstr); // 创建字符串视图,避免内存分配
std::cout << "String View: " << view << std::endl;
return0;
}
//3. 增强安全性
int main() {
constchar* cstr = "Hello, World!";
std::string_view view(cstr); // 使用 string_view,避免了 char* 的长度问题
std::cout << "String View: " << view << ", Length: " << view.size() << std::endl;
return0;
}
std::string_view
提供了许多有用的接口,以下是其中一些常用的接口:
// 构造函数
std::string_view(constchar* str, size_t count); // 从字符数组创建
std::string_view(conststd::string& str); // 从 std::string 创建
// 成员函数
size_t size() const noexcept; // 返回字符串长度
size_t length() const noexcept; // 返回字符串长度
bool empty() const noexcept; // 判断字符串是否为空
const char* data() const noexcept; // 返回指向字符串数据的指针
cost_reference operator[](size_t pos) const; // 访问指定位置的字符
const_reference at(size_t pos) const; // 访问指定位置的字符,带边界检查
const_reference front() const; // 返回第一个字符
const_reference back() const; // 返回最后一个字符
void remove_prefix(size_t n); // 移除前 n 个字符
void remove_suffix(size_t n); // 移除后 n 个字符
int compare(std::string_view other) const noexcept; // 比较两个 string_view
//查找一族
size_t find(char ch, size_t pos = 0) const noexcept; // 查找字符 ch
size_t find(const char* str, size_t pos = 0) const noexcept; // 查找字符串 str
size_t find(std::string_view str, size_t pos = 0) const noexcept; // 查找字符串 str
size_t rfind(char ch, size_t pos = npos) const noexcept; // 从后向前查找字符 ch
size_t find_first_of(char ch, size_t pos = 0) const noexcept; // 查找第一个匹配的字符
size_t find_first_not_of(char ch, size_t pos = 0) const noexcept; // 查找第一个不匹配的字符
size_t find_last_of(char ch, size_t pos = npos) const noexcept; // 从后向前查找第一个匹配的字符
size_t find_last_not_of(char ch, size_t pos = npos) const noexcept; // 从后向前查找第一个不匹配的字符
bool _Starts_with(std::string_view prefix) constnoexcept; // 判断是否以 prefix 开头
尽管 std::string_view
提供了许多优势,但在使用时仍然需要小心。string_view 并不负责管理其所指向的数据的生命周期,因此string_view 存续期间其持有的字符串的有效性需要开发者自行保证。
#include <iostream>
#include <string_view>
void print_view(std::string_view view) {
std::cout << "String View: " << view << std::endl;
}
int main() {
std::string str = "Hello, World!";
std::string_view view(str); // 从 std::string 创建 view
str.clear(); // 清空 std::string
print_view(view); // 此时 view 变为悬空指针,未定义行为
return0;
}
在上面的代码中,std::string_view
引用的 std::string
被清空后,std::string_view
变成了一个悬空指针,访问它将导致未定义行为。因此,在使用 std::string_view
时,必须确保其引用的原始数据在整个生命周期内有效。
std::string_view
作为 C++17 引入的一个新特性,极大地优化了字符串处理的性能,尤其是在频繁传递和操作字符串时。通过避免不必要的内存复制和分配,std::string_view
提供了一种高效的方式来操作字符串数据。然而,std::string_view
不负责内存管理,使用时需要小心数据的生命周期和悬空指针问题。通过合理运用 std::string_view
,可以在确保性能的同时,提高程序的安全性和灵活性。