std::string_view 是 C++17 引入的 轻量级字符串"只读视图"。
它不拥有字符串,只是"指向一段连续字符"的 (ptr, length) 二元组。
核心目的:
避免创建临时 std::string、减少拷贝,提高性能。
cpp
void f(std::string_view s);
你可以传:
-
std::string -
const char* -
字面量
"hello" -
char[]
并且不用分配内存、不拷贝内容。
内部原理
如果你去翻源码(我这里放MSVC的实现源码),你会发现它的关键点就在这两个成员变量里
cpp
using string_view = basic_string_view<char>;
template <class _Elem, class _Traits>
class basic_string_view {
public:
using const_pointer = const _Elem*;
using size_type = size_t;
private:
const_pointer _Mydata;
size_type _Mysize;
};
说人话就是
cpp
class string_view {
const char* data_; // 指向外部的字符串内容
size_t size_; // 长度
};
-
没有 '\0' 终止符的要求
-
没有内存所有权
-
不会自动管理被观察数据的生命周期
它仅仅是一个"窗口",与实际的存储分离。(不知道读者是否用过pytorch,这就很像Tensor里storage和view的关系)
所以:
cpp
std::string_view sv = some_string.substr(...); // OK,zero-copy
但是:
cpp
std::string_view sv;
{
std::string s = "abc";
sv = s; // sv 指向 s 的内部 storage
} // s 析构 → sv 悬空
==> 悬空引用。
string_view 的主要 API
构造函数
cpp
constexpr string_view() noexcept;//空视图,data=nullptr, size=0。
constexpr string_view(const char* s);//空视图,data=nullptr, size=0。
constexpr string_view(const char* s, size_t count);//从指针 + 长度构造,不需要 '\0'。
constexpr string_view(const std::string& str) noexcept;//引用 str.data(),str.size()。
数据访问
cpp
constexpr const char* data() const noexcept;
constexpr size_t size() const noexcept;
constexpr bool empty() const noexcept;
constexpr const char& operator[](size_t pos) const;
constexpr const char& at(size_t pos) const;
constexpr const char& front() const;
constexpr const char& back() const;
//还有begin,end等等迭代器,和string一样
字符串操作
cpp
//不分配,不复制,仍然只是 view。
constexpr string_view substr(size_t pos = 0, size_t count = npos) const;
//负 → 小,正 → 大。
constexpr int compare(string_view sv) const noexcept;
//find 系列
size_t find(string_view sv, size_t pos = 0) const noexcept;
size_t find(char c, size_t pos = 0) const noexcept;
size_t rfind(...);
size_t find_first_of(...);
size_t find_last_of(...);
size_t find_first_not_of(...);
size_t find_last_not_of(...);
修改 view 本身
不改变底层字符,只是移动窗口。
cpp
constexpr void remove_prefix(size_t n);
constexpr void remove_suffix(size_t n);
例子:
cpp
std::string_view sv = "hello";
sv.remove_prefix(1); // "ello"
sv.remove_suffix(2); // "el"
转换回 string
cpp
std::string s = std::string(view); // 复制!
这会真的 copy 内容。
string_view 使用注意事项
悬空引用
cpp
std::string_view sv;
{
std::string tmp = "abc";
sv = tmp; // 指向 tmp.data()
} // tmp销毁 → sv悬空
指向临时字符串也有问题
cpp
std::string_view sv = std::string("abc"); // 临时对象立即销毁
指向 vector<char> resize 后失效
底层存储可能重新分配。
不保证 null-terminated
你不能把 string_view.data() 当 C 字符串用。
cpp
printf("%s", sv.data()); // 如果现在不是 '\0' 结尾 → UB
与const string &的区别
其实看到api也大概能感受到了,string_view作为窗口,仅仅起到观察的效果,不拥有资源。并且这个窗口本身也是可以进行修改的。
所以如果仅仅是一次性的引用string,那么这两者几乎没区别
但是如果使用时需要调整,比如需要取substr,如果使用const string&,就会额外拷贝一个string出来。但是如果使用string_view,那么仅仅是新开一个窗口观察,开销更小。
当然,由于string_view是只读的,如果需要修改字符串,还是需要string&的