0. 引言
在性能敏感的应用中(如网络库、系统编程等),频繁的std::strring字符串拷贝会带来性能开销,而C++17 字符串string_view可以减少拷贝。
因为我们项目规定只能使用C++14,在之前的C++编程:使用 mmap 和 std::string_view 优化配置文件的读取与解析这篇文章中实际使用的是自己实现的string_view。
本文将参考开源库muduo的StringPiece.h 实现基于 C++11 实现的的 StringView
类,以避免不必要的内存分配和字符串拷贝。
适用场景:
- 需要高效处理大文本(如日志分析)。
- 频繁传递字符串,但无需修改其内容。
- 需要替代 C++17 的
std::string_view
的场景。
1. 完整代码
#include <algorithm> // for std::min, std::max
#include <cstring> // for std::strlen, std::memcmp
#include <iostream>
#include <stdexcept>
#include <string> // for std::string
class StringView {
private:
const char* data_ = nullptr; // 指向外部字符串
std::size_t size_ = 0; // 字符串长度
static constexpr std::size_t npos = static_cast<std::size_t>(-1); // 定义 npos
public:
// 默认构造函数
StringView() noexcept = default;
// 从 C 风格字符串构造
explicit StringView(const char* str) : data_(str), size_(str ? std::strlen(str) : 0) {
}
// 从 C 风格字符串构造,并指定长度
StringView(const char* str, std::size_t len) : data_(str), size_(str ? len : 0) {
}
// 从 std::string 构造
explicit StringView(const std::string& str) noexcept : data_(str.data()), size_(str.size()) {
}
// 获取字符串长度
std::size_t size() const noexcept {
return size_;
}
// 检查是否为空
bool empty() const noexcept {
return size_ == 0;
}
// 获取字符串数据
const char* data() const noexcept {
return data_;
}
// 获取字符(带异常安全性检查)
const char& operator[](std::size_t index) const {
if (!data_ || index >= size_) {
throw std::out_of_range("Index out of range");
}
return data_[index];
}
// 获取子视图(改进异常处理和边界检查)
StringView substr(std::size_t pos, std::size_t len = npos) const {
if (pos > size_) {
throw std::out_of_range("Position out of range");
}
len = std::min(len, size_ - pos); // 修正 len
return StringView(data_ + pos, len);
}
// 比较两个 StringView
int compare(const StringView& other) const noexcept {
std::size_t len = std::min(size_, other.size_);
int result = std::memcmp(data_, other.data_, len);
return result != 0 ? result : static_cast<int>(size_ - other.size_);
}
// 检查是否以指定前缀开头
bool starts_with(const StringView& prefix) const noexcept {
return size_ >= prefix.size_ && std::memcmp(data_, prefix.data_, prefix.size_) == 0;
}
// 检查是否以指定后缀结尾
bool ends_with(const StringView& suffix) const noexcept {
return size_ >= suffix.size_ && std::memcmp(data_ + size_ - suffix.size_, suffix.data_, suffix.size_) == 0;
}
// 检查是否包含指定子字符串
bool contains(const StringView& sub) const noexcept {
if (sub.size_ > size_) return false;
for (std::size_t i = 0; i <= size_ - sub.size_; ++i) {
if (std::memcmp(data_ + i, sub.data_, sub.size_) == 0) {
return true;
}
}
return false;
}
// 输出字符串(增加空指针检查)
friend std::ostream& operator<<(std::ostream& os, const StringView& sv) {
if (sv.data_ && sv.size_ > 0) {
os.write(sv.data_, sv.size_);
}
return os;
}
// 操作符重载:==, !=, <, <=, >, >=
bool operator==(const StringView& other) const noexcept {
return size_ == other.size_ && compare(other) == 0;
}
bool operator!=(const StringView& other) const noexcept {
return !(*this == other);
}
bool operator<(const StringView& other) const noexcept {
return compare(other) < 0;
}
bool operator<=(const StringView& other) const noexcept {
return compare(other) <= 0;
}
bool operator>(const StringView& other) const noexcept {
return compare(other) > 0;
}
bool operator>=(const StringView& other) const noexcept {
return compare(other) >= 0;
}
};
2. 实现原理
以下是主要实现点的解释:
-
数据成员:
data_
:指向外部字符串的指针。size_
:表示字符串的长度,避免使用std::strlen
重复计算。
-
构造函数:
- 支持从 C 风格字符串(
const char*
)或指定长度构造。 nullptr
字符串处理为长度 0,确保安全。
- 支持从 C 风格字符串(
-
基本操作:
- 提供
substr
创建子字符串视图,并通过边界检查避免越界。 starts_with
、ends_with
和contains
用于高效地检查前缀、后缀和子串。
- 提供
-
性能优化:
- 比较操作基于
std::memcmp
,支持按字节快速比较子串。 operator<<
直接输出字符串内容,兼容标准输出流。
- 比较操作基于
-
安全性:
- 处理了空指针、越界访问等潜在问题,符合 C++ 标准的异常安全性。
3. 测试程序
以下是测试 StringView
功能的示例程序,涵盖构造、访问、修改和比较操作。
int main() {
const char* text = "Hello, world!";
StringView sv1(text); // 整个字符串
StringView sv2(text, 5); // 前 5 个字符
std::string str = "Hello, OpenAI!";
StringView sv3(str); // 从 std::string 构造
std::cout << "sv1: " << sv1 << ", size: " << sv1.size() << "\n";
std::cout << "sv2: " << sv2 << ", size: " << sv2.size() << "\n";
std::cout << "sv3: " << sv3 << ", size: " << sv3.size() << "\n";
std::cout << "sv1 starts with 'Hello': " << sv1.starts_with(StringView("Hello")) << "\n";
std::cout << "sv1 ends with 'world!': " << sv1.ends_with(StringView("world!")) << "\n";
std::cout << "sv1 contains 'world': " << sv1.contains(StringView("world")) << "\n";
try {
auto sv4 = sv1.substr(7, 5);
std::cout << "sv4 (substr): " << sv4 << "\n";
} catch (const std::out_of_range& e) {
std::cout << "Error: " << e.what() << "\n";
}
std::cout << "Comparison (sv1 vs sv2): " << (sv1 == sv2) << "\n";
return 0;
}
4. 输出结果
运行上述测试程序后,输出类似以下内容:
sv1: Hello, world!, size: 13
sv2: Hello, size: 5
sv3: Hello, OpenAI!, size: 14
sv1 starts with 'Hello': 1
sv1 ends with 'world!': 1
sv1 contains 'world': 1
sv4 (substr): world
Comparison (sv1 vs sv2): 0