std::string_view 是 C++17 引入的一个非拥有型(non-owning)字符串引用 :它内部只存「一个字符指针 + 一个长度」,不分配内存、不复制内容、也不负责释放。所以 setString(std::string_view value) 的含义是------「我要读一段字符,但这段字符的所有权还在调用方那里」。
它解决什么问题
在 C++17 之前,写一个只读字符串参数的函数通常要在两种写法里选:
cpp
void setString(const std::string& value); // 传字面量/char* 时要构造临时 std::string(一次堆分配+拷贝)
void setString(const char* value); // 丢掉长度信息,且不能接受 std::string 以外的容器片段
换成 string_view 后,两种调用都零拷贝、零分配:
cpp
setString("hello"); // 字面量,直接指向 .rodata,不构造 string
setString(std::string("world")); // 临时 string 只提供指针和长度
setString(buf, 5); // 从 C 数组取前 5 个字符
setString(s.substr(3, 4)); // 取子串,仍然不分配内存
对 std::string、char 数组、const char*、std::filesystem::path 等都能隐式转换过来,所以它是很好的「只读输入参数」类型。同时它带长度,能安全表示含 \0 的数据,find/substr/compare 这些操作也不产生新分配。
使用它的三个硬约束
1. 不能悬垂------它不延长任何对象的生命周期
cpp
std::string_view bad() {
std::string s = "temp";
return s; // 错:s 析构后 view 指向已释放内存
}
void f() {
std::string_view sv = std::string("abc"); // 错:临时 string 立刻销毁
}
规则很简单:string_view 的生命周期必须短于它所指向的那段字符。
2. 不保证以 '\0' 结尾
sv.data() 只是指向首字符,sv[sv.size()] 是越界(C++23 起 operator[] 才有 assert 之外的明确行为,但仍不保证有终止符)。所以要传给 strlen、fopen、printf("%s") 这类 C 接口时,得先落地成真正的字符串:
cpp
std::string owned(sv); // 复制一份,保证 null 结尾
::fopen(owned.c_str(), "r");
3. 别拿它当成员变量或全局变量
类里存 std::string_view 成员几乎总是隐患(除非是明确的「借用」语义,且文档写清生命周期依赖,比如某些 span 风格的观察者)。setString 内部如果要保存这个值,应该转成 std::string 存起来:
cpp
void setString(std::string_view value) {
m_value = std::string(value); // 明确复制,之后与调用方解耦
}
顺带几个细节
- 按值传是对的 :
string_view通常 16 字节(指针+长度),按值传比const std::string&更便宜,不需要写成const std::string_view&。 - 它是只读视图 :通过它修改内容属于未定义行为(
data()返回const char*),要改就转成std::string或用std::span<char>。 - 和
std::span是同一思路 :span是「任意连续区间」的视图,string_view是专门给字符用的、额外带字符串语义(substr、find、比较运算符)。 std::string可以隐式转string_view,反之不行 :std::string sv = some_view;需要显式构造std::string(sv),这是刻意的------避免悄悄产生分配。- 异步/回调场景慎用 :把
string_view存进 lambda 或线程任务里,很容易在真正执行时已经悬垂;这种场合应传std::string(或shared_ptr<const std::string>)。
一句话总结:std::string_view 让 setString 能接受任何形式的字符数据而不产生拷贝,代价是函数只借用、不拥有,因此它绝不能把这段字符留到调用结束之后再用。
言归正传:
通过一个表格单元格类来实现
cpp
import std;
import spreadsheet_cell;
int main()
{
SpreadsheetCell myCell, anotherCell;
myCell.setValue(6);
anotherCell.setString("12.3");
std::println("cell 1:{}", myCell.getValue());
std::println("anothercell 2:{}", anotherCell.getValue());
auto mycell2{std::make_unique<SpreadsheetCell>()};
mycell2->setValue(6);
std::println("mysell2 :{}", mycell2->getValue());
SpreadsheetCell mycell3(3);
std::println("myshell 3: {}", mycell3.getValue());
return 0;
}
cpp
export module spreadsheet_cell;
import std;
export class SpreadsheetCell//表格中的单个单元的元素?
{
public:
SpreadsheetCell(double initisValue);
SpreadsheetCell() {};
SpreadsheetCell(std::string_view initialValue);
SpreadsheetCell(const SpreadsheetCell& src);
SpreadsheetCell& operator=(const SpreadsheetCell&rhs);
void setValue(double value);
double getValue() const;
void setString(std::string_view value);
std::string getString() const;
private:
std::string doubleToString(double value) const;
double stringToDouble(std::string_view value) const;
double m_value{ 0 };
};
void SpreadsheetCell::setValue(double value) {
m_value = value;
}
double SpreadsheetCell::getValue() const {
return m_value;
}
void SpreadsheetCell::setString(std::string_view value) {
m_value = stringToDouble(value);
}
std::string SpreadsheetCell::getString() const {
return doubleToString(m_value);
}
std::string SpreadsheetCell::doubleToString(double value) const {
return std::to_string(value);
}
double SpreadsheetCell::stringToDouble(std::string_view value) const//
{
double num{ 0 };
std::from_chars(value.data(), value.data() + value.size(), num);
return num;
}
SpreadsheetCell::SpreadsheetCell(double initisValue)
{
setValue(initisValue);
}
SpreadsheetCell::SpreadsheetCell(std::string_view initialValue)
{
setString(initialValue);
}
SpreadsheetCell::SpreadsheetCell(const SpreadsheetCell& src)
{
m_value = src.getValue();
}
SpreadsheetCell& SpreadsheetCell::operator=(const SpreadsheetCell& rhs)
{
// TODO: 在此处插入 return 语句
if (this == &rhs)
{
return *this;
}
m_value = rhs.m_value;
return *this;
}