C++ string_view


std::string_view 是C++17引入的一个轻量级、非拥有的字符串视图类,定义于 <string_view> 头文件中。它提供了一种高效且安全的方式来处理字符串数据,而无需复制字符串内容

1、 基本概念

std::string_view 本质上是对一个字符串序列的引用,它并不拥有字符串数据,只是持有指向字符串数据的指针和长度信息。这使得它在处理字符串时避免了不必要的内存分配和复制操作,提高了程序的性能。

主要用途

  • 函数参数传递:当函数需要处理字符串但不需要拥有字符串所有权时,可以使用 std::string_view 作为参数,这样可以避免不必要的字符串复制。
  • 临时字符串处理:在处理临时字符串或者子字符串时,使用 std::string_view 可以避免创建新的 std::string 对象,从而提高效率。

示例代码

以下是一个简单的示例,展示了 std::string_view 的基本用法:

#include <iostream>
#include <string_view>

// 接受 std::string_view 作为参数的函数
void printStringView(std::string_view sv) {
    std::cout << "String view content: " << sv << std::endl;
}

int main() {
    // 从 C 风格字符串创建 std::string_view
    const char* cstr = "Hello, World!";
    std::string_view sv1(cstr);

    // 从 std::string 创建 std::string_view
    std::string str = "Hello, C++!";
    std::string_view sv2(str);

    // 调用函数打印 std::string_view 内容
    printStringView(sv1);
    printStringView(sv2);

    return 0;
}

代码解释

  1. 包含头文件:包含 <iostream> 用于输入输出,<string_view> 用于使用 std::string_view
  2. 定义函数:定义了一个接受 std::string_view 作为参数的函数 printStringView,用于打印字符串视图的内容。
  3. 创建 std::string_view
    • 从 C 风格字符串创建 std::string_viewstd::string_view sv1(cstr);
    • std::string 创建 std::string_viewstd::string_view sv2(str);
  4. 调用函数:调用 printStringView 函数打印字符串视图的内容。

注意事项

  • 生命周期管理:由于 std::string_view 不拥有字符串数据,它所引用的字符串数据必须在 std::string_view 的生命周期内保持有效。否则,访问 std::string_view 可能会导致未定义行为。
  • 不可修改性std::string_view 是只读的,不能直接修改其内容。如果需要修改字符串,应该使用 std::string

常用成员函数

  • length()size():返回字符串视图的长度。
  • data():返回指向字符串数据的指针。
  • substr():返回一个新的 std::string_view,表示原字符串视图的子字符串。
  • starts_with()ends_with():检查字符串视图是否以指定的前缀或后缀开头或结尾。
  • find():查找指定子字符串或字符的第一次出现位置。

以下是一个使用这些成员函数的示例:

#include <iostream>
#include <string_view>

int main() {
    std::string_view sv = "Hello, World!";

    // 获取字符串视图的长度
    std::cout << "Length: " << sv.length() << std::endl;

    // 获取子字符串
    std::string_view sub = sv.substr(0, 5);
    std::cout << "Substring: " << sub << std::endl;

    // 检查前缀
    if (sv.starts_with("Hello")) {
        std::cout << "Starts with 'Hello'" << std::endl;
    }

    // 查找字符
    size_t pos = sv.find(',');
    if (pos != std::string_view::npos) {
        std::cout << "Comma found at position: " << pos << std::endl;
    }

    return 0;
}

通过使用 std::string_view,可以在不复制字符串数据的情况下高效地处理字符串,从而提高程序的性能和效率。

2、对比string

在实际应用里,std::string_view 相较于 std::string 存在不少优势,下面从性能、灵活性和安全性等方面展开详细介绍。

性能优势

避免内存分配与复制

std::string 会拥有字符串数据,在创建、赋值或者传递时,通常需要进行内存分配与数据复制操作。而 std::string_view 只是对已有字符串数据的引用,不拥有数据,所以不会产生额外的内存分配和复制开销。

下面是一个简单示例,能体现出两者在性能上的差异:

#include <iostream>
#include <string>
#include <string_view>
#include <chrono>

// 使用 std::string 作为参数的函数
void processString(const std::string& str) {
    // 模拟处理操作
    std::cout << str.length() << std::endl;
}

// 使用 std::string_view 作为参数的函数
void processStringView(std::string_view sv) {
    // 模拟处理操作
    std::cout << sv.length() << std::endl;
}

int main() {
    const char* cstr = "This is a long string for testing performance.";

    // 测试 std::string 的性能
    auto start1 = std::chrono::high_resolution_clock::now();
    for (int i = 0; i < 1000000; ++i) {
        processString(cstr);
    }
    auto end1 = std::chrono::high_resolution_clock::now();
    auto duration1 = std::chrono::duration_cast<std::chrono::milliseconds>(end1 - start1).count();

    // 测试 std::string_view 的性能
    auto start2 = std::chrono::high_resolution_clock::now();
    for (int i = 0; i < 1000000; ++i) {
        processStringView(cstr);
    }
    auto end2 = std::chrono::high_resolution_clock::now();
    auto duration2 = std::chrono::duration_cast<std::chrono::milliseconds>(end2 - start2).count();

    std::cout << "Time taken by std::string: " << duration1 << " ms" << std::endl;
    std::cout << "Time taken by std::string_view: " << duration2 << " ms" << std::endl;

    return 0;
}

在这个示例中,processString 函数接收 std::string 类型的参数,每次调用时都会进行字符串复制;而 processStringView 函数接收 std::string_view 类型的参数,不会进行复制操作。通过计时可以发现,使用 std::string_view 能显著提升性能。

轻量级数据结构

std::string_view 一般只包含一个指向字符串数据的指针和一个长度信息,其内存开销较小。相比之下,std::string 除了存储字符串数据外,还需要额外的空间来管理内存,如存储容量信息、分配器等。

灵活性优势

支持多种字符串源

std::string_view 可以从多种类型的字符串源创建,包括 std::string、C 风格字符串(const char*)以及字符数组等。这使得它在处理不同类型的字符串时更加灵活。

#include <iostream>
#include <string>
#include <string_view>

void printStringView(std::string_view sv) {
    std::cout << sv << std::endl;
}

int main() {
    std::string str = "Hello, std::string!";
    const char* cstr = "Hello, C-style string!";
    char arr[] = {'H', 'e', 'l', 'l', 'o', '\0'};

    printStringView(str);
    printStringView(cstr);
    printStringView(arr);

    return 0;
}
方便进行子字符串操作

std::string_view 提供了 substr 等成员函数,能够方便地获取原字符串的子字符串,而且不会复制子字符串的数据,效率较高。

#include <iostream>
#include <string_view>

int main() {
    std::string_view sv = "Hello, World!";
    std::string_view sub = sv.substr(0, 5);
    std::cout << sub << std::endl;
    return 0;
}

安全性优势

只读特性

std::string_view 是只读的,不能直接修改其内容。这有助于避免意外修改字符串数据,提高程序的安全性。如果需要修改字符串,应该使用 std::string

#include <iostream>
#include <string_view>

int main() {
    std::string_view sv = "Read-only string";
    // 以下代码会导致编译错误,因为 std::string_view 是只读的
    // sv[0] = 'W'; 
    return 0;
}

综上所述,在只需要对字符串进行读取操作、不需要拥有字符串所有权的场景中,使用 std::string_view 可以提高程序的性能和灵活性,同时增强代码的安全性。

### C++ 中 `std::string_view` 与 `std::string` 的性能比较 在C++中,`std::string_view` 和 `std::string` 各有其适用场景和性能特征。以下是两者的主要区别及其对性能的影响: --- #### 1. **内存分配** - **`std::string`**: 每次创建新的 `std::string` 对象时,都会涉及动态内存分配(除非启用了短字符串优化 SSO)。这意味着即使只是传递或返回一个简单的子串,也可能触发额外的内存分配和拷贝操作[^3]。 - **`std::string_view`**: 不会拥有自己的缓冲区,而是直接引用现有的字符串数据。这消除了不必要的内存分配开销,特别是在只需要读取而不修改字符串内容的情况下[^2]。 ```cpp // 示例:std::string 的内存分配成本 std::string original = "A very long string that we only need to read."; std::string substring = original.substr(0, 5); // 创建新对象并可能引发内存分配 ``` ```cpp // 示例:std::string_view 避免了内存分配 std::string original = "A very long string that we only need to read."; std::string_view view(original); auto substring_view = view.substr(0, 5); // 不涉及任何内存分配 ``` --- #### 2. **拷贝代价** - **`std::string`**: 当作为参数传递到函数中时,默认行为是按值传递,即每次调用都需要进行深拷贝(除非显式使用引用)[^1]。 - **`std::string_view`**: 只需保存指向原字符串的指针以及长度信息即可完成传递,几乎无任何显著开销[^2]。 ```cpp void processString(std::string str) {} // 参数传入会导致深拷贝 processString("Some large text..."); ``` ```cpp void processStringView(std::string_view sv) {} // 几乎零成本传递 processStringView("Some large text..."); ``` --- #### 3. **只读操作** 对于那些仅仅需要执行查找、匹配或其他形式的只读访问的任务而言,采用 `std::string_view` 能够带来明显的速度提升,因为它避免了一切关于所有权管理和副本生成的操作[^1]。 ```cpp bool containsExample(const std::string& str) { return str.find("example") != std::string::npos; } // 如果频繁调用此函数,则会产生较多的小型临时字符串对象 bool containsExampleSV(const std::string_view sv) { return sv.find("example") != std::string_view::npos; } // 更加高效的方式处理输入数据流中的模式检测等问题 ``` --- #### 4. **可变性限制** 需要注意的是,由于 `std::string_view` 并不实际持有数据的所有权,所以无法对其进行任何形式的写入更改。如果确实存在这样的需求,则仍应回归至传统的 `std::string` 类型上来实现相应的逻辑功能[^3]。 --- ### 实际测试对比 假设有一个包含大量重复字符串解析工作的应用程序,在这种环境下切换为广泛运用 `std::string_view` 很可能会观察到整体吞吐量有所提高的同时降低了GC压力或者减少了页面交换频率等情况的发生概率[^2]。 然而具体数值上的差异还会受到诸多因素影响,例如目标平台架构特性、编译器优化级别设置等等,因此建议针对特定业务负载自行开展基准测量活动以便得出更为精确结论。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值