C++ 字符串类型解析: std::string
、std::string_view
与 C-style 字符串的关系
- C-style 字符串 (
const char*
):传统的字符数组,通过\0
结尾来表示字符串。 std::string
:C++ 标准库提供的字符串类,封装了字符数组并提供丰富的字符串操作。std::string_view
:C++17 引入的字符串视图,轻量级,不拥有数据,只是对现有字符串数据的引用。
1. C-style 字符串 (const char*
)
C-style 字符串是最基础的字符串表示方式,通常通过 const char*
来表示。
例如:
const char* c_str = "Hello, World!";
C-style 字符串的特点是它没有内建的内存管理和丰富的操作方法,通常需要手动管理内存。
适用于与 C 语言兼容的函数,或者在性能要求非常高的场合。
2. std::string
— C++ 标准库字符串
std::string
是 C++ 中常用的字符串类型,它封装了 C-style 字符串,提供了丰富的操作方法。
例如:
std::string str = "Hello, World!";
str += " Welcome to C++!";
std::cout << str.size(); // 输出字符串长度
std::string
自动管理内存,提供了方便的字符串操作方法,如拼接、查找、替换等。
它的缺点是可能带来一定的性能开销,特别是在频繁的内存分配和复制的场合。
3. std::string_view
— 字符串视图
std::string_view
是 C++17 引入的轻量级字符串视图,它只是对现有字符串数据的引用,不拥有数据。
例如:
std::string_view view = "Hello, World!";
std::string_view
适用于需要性能优化,避免不必要的内存拷贝的场合。它非常轻量,但需要确保它所引用的数据在视图使用期间有效。
三者之间的转换关系
- 从
const char*
转换为std::string
:
你可以直接使用std::string
的构造函数或赋值操作来完成。
const char* c_str = "Hello, World!";
std::string str(c_str);
或者:
std::string str = c_str;
std::string
会复制 const char*
指向的数据,因此它会拥有该数据的副本。
- 从
std::string
转换为std::string_view
:
std::string_view
不拥有数据,而是引用已有数据。你可以通过构造函数将std::string
转换为std::string_view
。
std::string str = "Hello, World!";
std::string_view view(str);
注意,std::string_view
引用的数据需要在 std::string_view
使用期间有效。
- 从
std::string_view
转换为std::string
:
如果你需要一个拥有数据的std::string
,可以将std::string_view
转换为std::string
。
std::string_view view = "Hello, World!";
std::string str(view);
- 从
const char*
转换为std::string_view
:
可以直接通过std::string_view
的构造函数将const char*
转换为视图。
const char* c_str = "Hello, World!";
std::string_view view(c_str);
- 从
std::string_view
转换为const char*
:
你可以通过std::string_view
的data()
方法获取原始的 C-style 字符串指针。
std::string_view view = "Hello, World!";
const char* c_str = view.data();
总结
在 C++ 中,选择正确的字符串类型和合理的转换方式,不仅能提升程序的性能,也能减少内存管理的复杂性。理解这三者之间的关系是高效编写 C++ 代码的基础。
std::string
适用于需要对字符串进行修改和动态内存管理时。std::string_view
适用于避免不必要的字符串拷贝,提升性能的场合。const char*
用于与 C 语言兼容,或在性能要求极高的情况下使用。
C++ 中优先使用 std::string_view
(按值传递)而不是 const std::string&
- 使用
const std::string&
(会发生拷贝):
当我们使用const std::string&
作为函数参数时,传递给函数的字符串会进行拷贝,尤其在传递临时字符串字面量时。
-
对于非临时对象(如 std::string large_str = “Hello”;):const std::string& 会避免拷贝,只是传递该对象的引用,因此没有发生拷贝。
-
对于临时对象(如字符串字面量 “Hello”):编译器会将临时字符串字面量转换为一个 std::string 对象,然后传递其引用。这时,虽然是传引用,但由于 std::string 对象本身是临时创建的,仍然需要将临时对象创建并传递。因此,临时对象的创建本身涉及到一次拷贝。因为字面量本身不是 std::string 对象,而是一个字符数组。
示例代码:
#include <iostream>
#include <string>
void print_string(const std::string& str) {
std::cout << "String: " << str << std::endl;
}
int main() {
std::string large_str = "Hello, this is a large string that will be passed to a function!";
// 使用 const std::string& 参数传递
print_string(large_str);
print_string("Temporary string literal"); // 这里也会发生拷贝
return 0;
}
- 使用
std::string_view
(避免拷贝):
使用std::string_view
可以避免不必要的内存拷贝,std::string_view
不会拷贝数据,它只会引用现有的字符串数据。
示例代码:
#include <iostream>
#include <string_view> // 需要包含 string_view 头文件
void print_string(std::string_view str_view) {
std::cout << "String: " << str_view << std::endl;
}
int main() {
std::string large_str = "Hello, this is a large string that will be passed to a function!";
// 使用 std::string_view 参数传递
print_string(large_str); // 这里没有拷贝,仅仅引用 large_str 的数据
print_string("Temporary string literal"); // 这里没有拷贝,直接引用字面量数据
return 0;
}
性能差异:
- 使用
std::string_view
避免了不必要的内存拷贝,尤其是在传递临时字符串字面量时。 - 相比之下,
const std::string&
在传入字符串字面量时,会导致std::string
对象的拷贝。 - 对于大字符串,
std::string_view
可以显著提高性能。
何时不使用 std::string_view
:
- 当你需要修改字符串内容时,
std::string_view
不能修改数据。 - 需要保证
std::string_view
引用的字符串在视图生命周期内有效,避免悬空引用。
总结:
使用 std::string_view
可以提升性能,避免内存拷贝,特别是传递临时字符串字面量和大型字符串时。
但要注意它的生命周期管理问题,在合适的场景下选择使用它。
使用string_view产生悬垂引用案例
#include <iostream>
#include <string>
#include <string_view>
std::string_view askForName()
{
std::cout << "What's your name?\n";
// 使用 std::string
std::string name{};
std::cin >> name;
std::string_view view{name};
std::cout << "Hello " << view << '\n';
// 返回 view 时,name 会销毁,导致悬垂引用
return view;
}
int main()
{
// `askForName` 返回一个悬垂引用
std::string_view view{askForName()};
// `view` 现在指向已经销毁的 `name` 字符串
// 访问它会导致未定义行为
std::cout << "Your name is " << view << '\n'; // 未定义行为:悬垂引用
return 0;
}
- askForName() 函数:
- askForName 函数内部创建了一个局部变量 name,它存储用户输入的名字。
- 然后,name 被转换成 std::string_view 类型的 view,并返回。
- 然而,当 askForName 函数返回时,name 变量会离开作用域并销毁。
- 此时,view 仍然持有对 name 字符串数据的引用。产生悬垂引用