C++17 std::string_view:性能与便捷的完美结合

C++17 std::string_view:性能与便捷的完美结合

AI 生成笔记

引言

在C++编程中,字符串处理是一项常见而基础的任务。长久以来,我们主要依赖std::string和字符数组(C风格字符串)来处理文本数据。然而,这些方式各有局限:std::string进行拷贝操作成本高,而C风格字符串则缺乏安全性和便利性。

C++17引入了一个令人兴奋的新工具:std::string_view。这个轻量级的非拥有型字符串视图类为字符串处理带来了革命性的改变。本文将由浅入深地探讨string_view的各个方面,展示其如何提升代码的性能和可读性。

什么是string_view?

std::string_view(定义在<string_view>头文件中)是一个轻量级的、只读的、非拥有型的字符序列视图。简单地说,它是对已存在字符串的一个"窗口"或"视图",不拥有字符串数据本身。

其核心特点包括:

  • 非拥有型:不拥有它所指向的数据,不负责内存管理
  • 只读:不能修改底层数据
  • 轻量级:本质上只包含指针和长度,拷贝成本极低
  • 兼容性:可以从C风格字符串、std::string或字符数组创建

基础用法

让我们从简单的示例开始:

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

int main() {
    // 从字面量创建
    std::string_view sv1 = "Hello, world!";
    
    // 从std::string创建
    std::string str = "C++ Programming";
    std::string_view sv2 = str;
    
    // 输出
    std::cout << "sv1: " << sv1 << std::endl;
    std::cout << "sv2: " << sv2 << std::endl;
    
    // 获取长度
    std::cout << "Length of sv1: " << sv1.length() << std::endl;
    std::cout << "Size of sv1: " << sv1.size() << std::endl;
    
    // 检查是否为空
    std::cout << "Is sv1 empty? " << (sv1.empty() ? "Yes" : "No") << std::endl;
    
    return 0;
}

性能优势:避免不必要的拷贝

string_view最显著的优势是避免了不必要的字符串拷贝。考虑以下场景:

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

// 使用std::string作为参数
void processString(const std::string& s) {
    // 处理字符串...
}

// 使用std::string_view作为参数
void processStringView(std::string_view sv) {
    // 处理字符串...
}

int main() {
    const char* cstr = "This is a rather long string used for demonstration";
    
    // 测量使用std::string的性能
    auto start1 = std::chrono::high_resolution_clock::now();
    for (int i = 0; i < 1000000; ++i) {
        processString(cstr); // 隐式转换为std::string,会分配内存
    }
    auto end1 = std::chrono::high_resolution_clock::now();
    
    // 测量使用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();
    
    std::chrono::duration<double, std::milli> duration1 = end1 - start1;
    std::chrono::duration<double, std::milli> duration2 = end2 - start2;
    
    std::cout << "std::string time: " << duration1.count() << " ms\n";
    std::cout << "std::string_view time: " << duration2.count() << " ms\n";
    
    return 0;
}

在上面的例子中,使用string_view可能比string快数倍,尤其是在处理大量字符串时。

字符串操作

string_view提供了丰富的字符串操作功能,无需修改底层数据:

#include <iostream>
#include <string_view>

int main() {
    std::string_view sv = "Hello, C++17 World!";
    
    // 截取子串
    std::string_view sub1 = sv.substr(7, 6); // "C++17"
    std::cout << "Substring: " << sub1 << std::endl;
    
    // 查找
    size_t pos = sv.find("World");
    if (pos != std::string_view::npos) {
        std::cout << "Found 'World' at position: " << pos << std::endl;
    }
    
    // 移除前缀和后缀
    std::string_view sv2 = sv;
    sv2.remove_prefix(7);  // 移除"Hello, "
    std::cout << "After remove_prefix: " << sv2 << std::endl;
    
    sv2.remove_suffix(7);  // 移除" World!"
    std::cout << "After remove_suffix: " << sv2 << std::endl;
    
    return 0;
}

深入理解:生命周期管理

使用string_view时,最关键的问题是理解和管理生命周期。因为string_view不拥有数据,所以必须确保在string_view使用期间,底层数据保持有效:

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

// 危险:返回引用了局部变量的string_view
std::string_view dangerous() {
    std::string local = "Temporary string";
    return local;  // 危险!返回的view引用了即将销毁的对象
}

// 安全:string_view作为参数
void safe(std::string_view sv) {
    std::cout << sv << std::endl;
}

int main() {
    // 危险用法
    // std::string_view sv = dangerous();  // sv引用已销毁的数据
    // std::cout << sv << std::endl;  // 未定义行为
    
    // 安全用法
    std::string s = "Permanent string";
    std::string_view sv = s;
    safe(sv);  // 传递view是安全的,因为s在此范围内有效
    
    return 0;
}

实际应用案例

案例1:提高函数接口性能

#include <iostream>
#include <string_view>
#include <vector>
#include <algorithm>

// 使用string_view作为搜索函数的参数
bool containsSubstring(std::string_view text, std::string_view substring) {
    return text.find(substring) != std::string_view::npos;
}

int main() {
    const char* cstr = "C++ language features";
    std::string str = "Modern C++ programming";
    
    // 可以无缝处理不同类型的字符串
    if (containsSubstring(cstr, "language")) {
        std::cout << "cstr contains 'language'" << std::endl;
    }
    
    if (containsSubstring(str, "Modern")) {
        std::cout << "str contains 'Modern'" << std::endl;
    }
    
    return 0;
}

案例2:高效的文本解析

#include <iostream>
#include <string_view>
#include <vector>

// 分割字符串函数,返回string_view的vector
std::vector<std::string_view> split(std::string_view str, char delimiter) {
    std::vector<std::string_view> result;
    
    size_t start = 0;
    size_t end = str.find(delimiter);
    
    while (end != std::string_view::npos) {
        result.push_back(str.substr(start, end - start));
        start = end + 1;
        end = str.find(delimiter, start);
    }
    
    result.push_back(str.substr(start));
    return result;
}

int main() {
    std::string text = "apple,banana,cherry,date,elderberry";
    
    // 分割字符串,不产生额外的内存分配
    std::vector<std::string_view> fruits = split(text, ',');
    
    for (const auto& fruit : fruits) {
        std::cout << "Fruit: " << fruit << std::endl;
    }
    
    return 0;
}

案例3:配置解析器

#include <iostream>
#include <string>
#include <string_view>
#include <unordered_map>
#include <fstream>

class ConfigParser {
private:
    std::unordered_map<std::string, std::string> configData;

public:
    bool parse(const std::string& filename) {
        std::ifstream file(filename);
        if (!file) return false;
        
        std::string line;
        while (std::getline(file, line)) {
            std::string_view lineView = line;
            
            // 忽略空行和注释
            if (lineView.empty() || lineView[0] == '#') continue;
            
            // 查找分隔符
            size_t pos = lineView.find('=');
            if (pos == std::string_view::npos) continue;
            
            // 提取键和值
            std::string_view key = lineView.substr(0, pos);
            std::string_view value = lineView.substr(pos + 1);
            
            // 去除前后空白
            key = trimView(key);
            value = trimView(value);
            
            // 存储配置
            configData[std::string(key)] = std::string(value);
        }
        return true;
    }
    
    std::string getValue(const std::string& key, const std::string& defaultValue = "") const {
        auto it = configData.find(key);
        return (it != configData.end()) ? it->second : defaultValue;
    }

private:
    // 去除字符串视图前后的空白
    std::string_view trimView(std::string_view sv) {
        const auto start = sv.find_first_not_of(" \t");
        if (start == std::string_view::npos) return "";
        
        const auto end = sv.find_last_not_of(" \t");
        return sv.substr(start, end - start + 1);
    }
};

int main() {
    ConfigParser parser;
    
    // 假设有一个配置文件
    // 这里为了示例简化,我们模拟创建一个配置文件
    {
        std::ofstream config("config.ini");
        config << "# Configuration file\n"
               << "server_ip = 192.168.1.1\n"
               << "port = 8080\n"
               << "max_connections = 100\n";
    }
    
    if (parser.parse("config.ini")) {
        std::cout << "Server IP: " << parser.getValue("server_ip") << std::endl;
        std::cout << "Port: " << parser.getValue("port") << std::endl;
        std::cout << "Max Connections: " << parser.getValue("max_connections") << std::endl;
        std::cout << "Timeout: " << parser.getValue("timeout", "30") << std::endl;  // 使用默认值
    }
    
    return 0;
}

案例4:高效的日志系统

#include <iostream>
#include <string_view>
#include <chrono>
#include <iomanip>
#include <sstream>

enum class LogLevel {
    DEBUG,
    INFO,
    WARNING,
    ERROR
};

class Logger {
private:
    LogLevel minLevel;
    
public:
    Logger(LogLevel level = LogLevel::INFO) : minLevel(level) {}
    
    void setLevel(LogLevel level) {
        minLevel = level;
    }
    
    void log(LogLevel level, std::string_view message) {
        if (level < minLevel) return;
        
        // 获取当前时间
        auto now = std::chrono::system_clock::now();
        auto nowTime = std::chrono::system_clock::to_time_t(now);
        
        std::stringstream ss;
        ss << std::put_time(std::localtime(&nowTime), "%Y-%m-%d %H:%M:%S");
        
        // 输出日志前缀
        std::cout << "[" << ss.str() << "] ";
        
        switch (level) {
            case LogLevel::DEBUG:   std::cout << "[DEBUG] "; break;
            case LogLevel::INFO:    std::cout << "[INFO] "; break;
            case LogLevel::WARNING: std::cout << "[WARNING] "; break;
            case LogLevel::ERROR:   std::cout << "[ERROR] "; break;
        }
        
        std::cout << message << std::endl;
    }
    
    // 方便的包装函数
    void debug(std::string_view message) { log(LogLevel::DEBUG, message); }
    void info(std::string_view message) { log(LogLevel::INFO, message); }
    void warning(std::string_view message) { log(LogLevel::WARNING, message); }
    void error(std::string_view message) { log(LogLevel::ERROR, message); }
};

int main() {
    Logger logger(LogLevel::DEBUG);
    
    // 使用string_view高效传递日志消息
    logger.debug("Initializing application...");
    logger.info("Application started");
    logger.warning("Configuration file not found, using defaults");
    
    const char* errorMsg = "Failed to connect to database";
    logger.error(errorMsg);
    
    return 0;
}

高级主题:string_view与constexpr

C++17的一个强大特性是增强的constexpr支持。string_view可以与constexpr完美配合,实现编译期字符串处理:

#include <iostream>
#include <string_view>

// 编译期计算字符串长度
constexpr size_t compiletime_length(std::string_view sv) {
    return sv.length();
}

// 编译期检查字符串是否以特定前缀开始
constexpr bool starts_with(std::string_view str, std::string_view prefix) {
    return str.substr(0, prefix.size()) == prefix;

### 回答问题 #### 1. 使用 `std::string_view` 改进之前的代码 `std::string_view` 是 C++17 引入的一个轻量级字符串视图类,它允许我们以只读的方式访问字符串数据,而无需复制底层数据。这可以显著提高性能,尤其是在处理大量字符串时。 以下是使用 `std::string_view` 改进后的代码: ```cpp #include <iostream> #include <string> #include <string_view> // 基类:Publication class Publication { protected: std::string_view title; // 使用 string_view std::string_view publisher; // 使用 string_view double price; public: // 构造函数 Publication(std::string_view t = "", std::string_view p = "", double pr = 0.0) : title(t), publisher(p), price(pr) {} // 获取方法 std::string_view getTitle() const { return title; } std::string_view getPublisher() const { return publisher; } double getPrice() const { return price; } }; // 派生类:Book class Book : public Publication { private: std::string_view ISBN; // 使用 string_view std::string_view author; // 使用 string_view public: // 构造函数 Book(std::string_view t = "", std::string_view p = "", double pr = 0.0, std::string_view is = "", std::string_view a = "") : Publication(t, p, pr), ISBN(is), author(a) {} // 获取方法 std::string_view getISBN() const { return ISBN; } std::string_view getAuthor() const { return author; } }; // 派生类:Magazine class Magazine : public Publication { private: char frequency; std::string_view editor; // 使用 string_view public: // 构造函数 Magazine(std::string_view t = "", std::string_view p = "", double pr = 0.0, char f = 'M', std::string_view e = "") : Publication(t, p, pr), frequency(f), editor(e) {} // 获取方法 char getFrequency() const { return frequency; } std::string_view getEditor() const { return editor; } }; ``` **解释:** - 使用了 `std::string_view` 替代 `std::string`,避免了不必要的字符串拷贝。 - `std::string_view` 提供了对字符串的只读访问,因此在需要频繁传递字符串的情况下,性能会有显著提升。 - 注意:`std::string_view` 的生命周期依赖于其引用的原始字符串,因此在设计时需要确保原始字符串在 `std::string_view` 使用期间不会被销毁。 --- #### 2. 使用 `std::string_view` 改进 `Library_Book` 类 以下是改进后的 `Library_Book` 类实现: ```cpp #include <iostream> #include <string> #include <string_view> // 图书馆书籍类:Library_Book class Library_Book : public Book { private: int libraryNumber; // 图书馆用户的六位编号 std::string_view borrowDate; // 使用 string_view public: // 构造函数 Library_Book(std::string_view t = "", std::string_view p = "", double pr = 0.0, std::string_view is = "", std::string_view a = "", int ln = 0, std::string_view bd = "") : Book(t, p, pr, is, a), libraryNumber(ln), borrowDate(bd) {} // 获取方法 int getLibraryNumber() const { return libraryNumber; } std::string_view getBorrowDate() const { return borrowDate; } // 打印借阅信息 void printBorrowDetails() const { std::cout << "Title: " << getTitle() << std::endl; std::cout << "Author: " << getAuthor() << std::endl; std::cout << "Library Number: " << libraryNumber << std::endl; std::cout << "Borrow Date: " << borrowDate << std::endl; } }; ``` **解释:** - 在 `Library_Book` 中,`borrowDate` 被替换为 `std::string_view`,以减少不必要的字符串拷贝。 - 其他部分保持不变,但通过使用 `std::string_view`,我们可以更高效地处理字符串数据。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值