从零实现一个简化版string 类 —— 深入理解std::string的底层设计

从零实现一个简化版 string 类 ------ 深入理解 std::string 的底层设计

说明:本文聚焦 string实现细节设计理由 ,代码使用一个教学用的 mini_string,并配有完整实现与测试用例。不讲 API 用法,而是解释"为什么要这么实现"。

文章目录

  • [从零实现一个简化版 `string` 类 ------ 深入理解 `std::string` 的底层设计](#从零实现一个简化版 string 类 —— 深入理解 std::string 的底层设计)
    • [1. 引言](#1. 引言)
    • [2. 基础设计 ------ 为什么要有 `size` 和 `capacity`](#2. 基础设计 —— 为什么要有 sizecapacity)
    • [3. 构造与析构 ------ RAII 思想的体现](#3. 构造与析构 —— RAII 思想的体现)
    • [4. 拷贝赋值 ------ 避免浅拷贝与资源泄漏](#4. 拷贝赋值 —— 避免浅拷贝与资源泄漏)
    • [5. 容量管理 ------ `reserve`、`shrink_to_fit` 与扩容策略](#5. 容量管理 —— reserveshrink_to_fit 与扩容策略)
    • [6. 元素访问 ------ 性能与安全的取舍(`[]` vs `at()`)](#6. 元素访问 —— 性能与安全的取舍([] vs at()))
    • [7. 修改操作 ------ 为什么 `insert` / `erase` 都是 O(n)](#7. 修改操作 —— 为什么 insert / erase 都是 O(n))
    • [8. 查找与子串 ------ 朴素实现的理由与复杂度](#8. 查找与子串 —— 朴素实现的理由与复杂度)
    • [9. 迭代器 ------ 直接返回指针的简单而有效](#9. 迭代器 —— 直接返回指针的简单而有效)
    • [10. 小结与思考题](#10. 小结与思考题)
    • [11. 完整代码实现](#11. 完整代码实现)

1. 引言

在 C++ 开发中,std::string 就像"空气"一样无处不在:拼接日志、处理配置、解析数据...... 我们随手就能写 s.push_back('a')s.append("hello"),却很少追问为什么这些操作有特定的性能特征。

本文不讲"如何用 string",而讲为什么要这样设计 string 。我们用教学版 mini_string 来逐步展示 std::string 的核心实现思想:内存布局、扩容策略、拷贝/移动语义、迭代器规则等。读完你应当能回答:为什么 push_back 在大多数情况下是 O(1)?为什么 substr 会拷贝内存?为什么短字符串优化(SSO)能显著提升性能?

2. 基础设计 ------ 为什么要有 sizecapacity

最朴素的字符串只需要一个 char* 指针,并在末尾加 '\0'。但这会带来三个核心问题:

  1. 获取长度低效size() 需要遍历到 '\0',每次 O(n)。
  2. 频繁 realloc :每次追加若重新分配内存,会导致大量 new/delete,性能大幅下降。
  3. 易错且不兼容 :忘记 '\0' 会导致未定义行为,且不能良好与 C API 交互。

为了解决这些问题,mini_string 采用三个成员:

cpp 复制代码
char*  _data;      // 指向动态分配的连续内存,末尾有 '\0'
size_t _size;      // 当前逻辑长度(不含 '\0')
size_t _capacity;  // 分配的容量(通常包含为 '\0' 预留的那 1 字节)

设计思想

  • _size:快速得到长度(O(1)),避免频繁遍历。
  • _capacity:记录已分配空间,避免小步扩容。
  • _data 保持连续并以 '\0' 结尾,兼容 C 风格字符串(c_str())。

这个模型是 std::string 的核心:既保证性能(常数时间的 size()、摊还常数的 push_back()),又保证兼容性

3. 构造与析构 ------ RAII 思想的体现

C++ 推荐 RAII(Resource Acquisition Is Initialization):对象一旦创建就拥有并管理资源,析构时释放资源。

mini_string 的构造/析构要满足以下目标:

  • 默认构造 :保证对象是合法的、能调用 c_str() 的(哪怕是空串)。
  • 从 C 字符串构造 :能从 const char* 初始化。
  • 拷贝构造:保证深拷贝,两个对象不会共享同一内存。
  • 析构:释放动态分配的内存,避免泄漏。

示例:

cpp 复制代码
// 默认构造:保证 _data 非空,且 _data[0] == '\0'
mini_string::mini_string()
    : _data(new char[1]), _size(0), _capacity(1) {
    _data[0] = '\0';
}

// 用 C 字符串构造
mini_string::mini_string(const char* s) {
    _size = strlen(s);
    _capacity = _size + 1;
    _data = new char[_capacity];
    memcpy(_data, s, _capacity); // 包含 '\0'
}

// 拷贝构造:深拷贝
mini_string::mini_string(const mini_string& other) {
    _size = other._size;
    _capacity = other._capacity;
    _data = new char[_capacity];
    memcpy(_data, other._data, _capacity);
}

// 析构:释放资源
mini_string::~mini_string() {
    delete[] _data;
    _data = nullptr;
    _size = _capacity = 0;
}

为什么要深拷贝?

如果使用浅拷贝(仅复制指针),两个对象会指向同一内存;其中一对象析构时会释放内存,另一个对象将变成野指针,导致严重错误。


4. 拷贝赋值 ------ 避免浅拷贝与资源泄漏

构造函数之外,赋值运算符(operator=)也要注意资源管理。编译器默认生成的是成员逐一复制(浅拷贝),这在管理动态内存时不可接受。

正确做法是:

  • 先检测自赋值 if (this != &other)
  • 释放当前对象的旧资源(delete[] _data)。
  • 根据 other 的大小分配新内存并拷贝。
  • 返回 *this 的引用以支持链式赋值。

参考实现:

cpp 复制代码
mini_string& mini_string::operator=(const mini_string& other) {
    if (this != &other) {
        delete[] _data;
        _size = other._size;
        _capacity = other._capacity;
        _data = new char[_capacity];
        memcpy(_data, other._data, _capacity);
    }
    return *this;
}

关键点 :自我检测避免 a = a 导致释放自身内存后再访问;先释放再分配可以在内存受限时出错(可进一步改进为 copy-and-swap 技法以提高异常安全性),但教学版已足够说明核心问题。

5. 容量管理 ------ reserveshrink_to_fit 与扩容策略

问题 :如果每次 push_back 都分配新的内存(按需扩容 1 字节),时间复杂度会非常高(大量 new/delete 与拷贝)。

策略 :采用倍增(doubling)策略,使扩容次数降到对数级,从而将 push_back摊还复杂度降为 O(1)

关键操作:

  • reserve(new_cap):如果 new_cap 大于当前 _capacity,则分配新空间并拷贝数据。通常调用者在已知最终大小时会用 reserve 预分配,避免多次扩容。
  • shrink_to_fit():将 _capacity 缩小到 _size + 1(保留 '\0')。注意:这会导致内存重分配并使迭代器失效,因此应谨慎使用(通常在内存紧张时使用)。

实现要点(教学版):

cpp 复制代码
void mini_string::reserve(size_t new_cap) {
    if (new_cap > _capacity) {
        char* new_data = new char[new_cap];
        memcpy(new_data, _data, _size + 1); // 含 '\0'
        delete[] _data;
        _data = new_data;
        _capacity = new_cap;
    }
}

void mini_string::shrink_to_fit() {
    if (_capacity > _size + 1) {
        char* new_data = new char[_size + 1];
        memcpy(new_data, _data, _size + 1);
        delete[] _data;
        _data = new_data;
        _capacity = _size + 1;
    }
}

为什么 clear() 不释放内存?
clear() 只是把 _size 设为 0 并确保 '\0',不释放缓冲区。这样如果用户随后再次追加,能复用已分配的内存,减少分配次数,提高性能。只有在需要回收内存时才调用 shrink_to_fit()

6. 元素访问 ------ 性能与安全的取舍([] vs at()

字符串应提供两类访问接口:

  • operator[]:直接返回 _data[i],不检查越界,性能最快。适合性能敏感但确信索引正确的场景。
  • at():检查边界,越界抛出 std::out_of_range,更安全但略慢。

实现示例:

cpp 复制代码
char& mini_string::at(size_t i) {
    if (i >= _size) throw std::out_of_range("index out of range");
    return _data[i];
}

此外提供 front() / back() / c_str() 等便利函数。设计上给使用者"选择权":性能优先或安全优先由使用者决定。

7. 修改操作 ------ 为什么 insert / erase 都是 O(n)

关键约束:字符串必须保持连续内存 ,以保证 c_str() 和迭代器(指针)有效。因此:

  • 插入字符需要把插入点之后的数据整体向后移动(memmove / 手写循环),位置越靠前移动长度越大。
  • 删除字符需要把删除点之后的数据整体向前移动。

这注定了 insert / erase 的时间复杂度为 O(n)。

实现:

cpp 复制代码
void mini_string::push_back(const char ch) {
    if (_size + 1 > _capacity) {
        size_t new_cap = _capacity == 0 ? 4 : 2 * _capacity;
        reserve(new_cap);
    }
    _data[_size] = ch;
    ++_size;
    _data[_size] = '\0';
}

void mini_string::insert(size_t pos, const char ch) {
    assert(pos <= _size);
    if (_size + 1 > _capacity) reserve(_capacity == 0 ? 4 : 2*_capacity);
    // 从后向前移动一个位置
    size_t i = _size;
    while (i > pos) {
        _data[i] = _data[i - 1];
        --i;
    }
    _data[pos] = ch;
    ++_size;
    _data[_size] = '\0';
}

void mini_string::erase(size_t pos, size_t len) {
    assert(pos <= _size);
    if (pos == _size) return;
    if (len > _size - pos) {
        _size = pos;
    } else {
        size_t i = pos + len;
        while (i < _size) {
            _data[i - len] = _data[i];
            ++i;
        }
        _size -= len;
    }
    _data[_size] = '\0';
}

8. 查找与子串 ------ 朴素实现的理由与复杂度

find():教学实现采用朴素子串匹配(strncmp 或逐字符比较),时间复杂度 O(n·m)。虽然理论上 KMP 等算法在某些模式集下更优,但标准库在实现上并不一定统一采用 KMP,因为:

  • KMP 在构建部分匹配表时也有成本,且在很多短模式/短文本场景下朴素算法更快。
  • 实际应用中查找多是短子串或不频繁调用,朴素足够且实现简单。

substr():返回一个新 mini_string,必须分配新内存并拷贝子串,复杂度 O ( l e n ) O(len) O(len)。

实现:

cpp 复制代码
size_t mini_string::find(const char* target_str) const {
    size_t len = strlen(target_str);
    if (len == 0) return 0;
    for (size_t i = 0; i + len <= _size; ++i) {
        if (strncmp(_data + i, target_str, len) == 0) return i;
    }
    return npos;
}

mini_string mini_string::substr(size_t pos, size_t len) const {
    assert(pos < _size);
    if (pos + len > _size) len = _size - pos;
    mini_string result;
    result.reserve(len);
    for (size_t i = 0; i < len; ++i) result.push_back(_data[pos + i]);
    return result;
}

9. 迭代器 ------ 直接返回指针的简单而有效

因为内部存储就是连续的 char 数组,char* 自然满足随机访问迭代器的要求,所以直接返回 begin()/end()_data_data + _size 是最简洁高效的选择:

cpp 复制代码
char* begin() { return _data; }
char* end()   { return _data + _size; }

这样既能与 <algorithm> 无缝配合,也避免了额外的迭代器类型实现成本。

10. 小结与思考题

通过实现 mini_string,我们会更清晰地理解:

  • 为什么 std::string 要同时维护 _size_capacity(性能与可用性权衡)。

  • 为什么 push_back 能做到摊还 O(1):倍增策略减少 realloc

  • 为什么插入/删除要搬移大量数据(连续内存带来的代价)。

思考题(练手)

  1. append(const char*) 中的 reserve 改为 reserve(_size + len + 1)(为 '\0' 留位),你能解释差别吗?
  2. 尝试把 substr 的实现改为使用 memcpy(直接分配并拷贝一次),与 push_back 循环版在性能上有什么差别?为什么?
  3. 如果你要为 mini_string 添加移动构造与移动赋值,如何实现以保证 noexcept?为什么 noexcept 很重要(特别是容器在移动元素时)?

11. 完整代码实现

c++ 复制代码
// ======== mini_string.h ========
#include <iostream>
#include <cassert>
using namespace std;

// 基本定义 
class mini_string {
private:
	char* _data; // 字符数据
	size_t _size;// 实际字符数
	size_t _capacity; // 容量 不含'\0'
public:
	static const size_t npos;
	// ======= 构造&析构 ========
	mini_string(); // 默认构造
	mini_string(const char* str); // 用C格式字符串构造
	mini_string(const mini_string& str); // 拷贝构造
	~mini_string(); // 析构

	// ======= 赋值操作 ========
	mini_string& operator=(const mini_string& str);

	// ======= 元素访问 ========
	char& operator[](size_t i) { return _data[i]; } // 可以修改
	const char& operator[](size_t i) const { return _data[i]; } // 不能修改
	char& at(size_t i);
	char& front() { return _data[0]; } // 字符串首位索引
	char& back() { return _data[_size - 1]; } // 字符串末尾索引
	const char* c_str() const { return _data; } // 返回C格式字符串

	// ======= 容量操作 ========
	size_t size() const { return _size; }
	size_t capacity() const { return _capacity; }
	void clear() { _size = 0,_data[0] = '\0'; }
	bool empty() const { return _size == 0; }

	void reserve(size_t new_cap);
	void shrink_to_fit();

	// ======= 查找操作 ========
	size_t find(const char target_char) const; // 查找字符串中有无目标字符
	size_t find(const char* target_str) const; // 查找字符串中有无目标字符串
	mini_string substr(size_t pos, size_t len) const; // 截取子串

	// ======= 修改操作 ========
	void push_back(const char ch); // 尾插
	mini_string& append(const char* str); // 结尾追加一个C格式字符串
	mini_string& append(const mini_string& str); // 结尾追加一个mini_string类字符串
	void erase(size_t pos, size_t len); //从pos位置开始删除len个长度字符
	void insert(size_t pos, const char ch);// 在pos位置之后插入一个字符
	void insert(size_t pos, const char* str);// 在pos位置之后插入一个字符串
	mini_string& operator+=(const mini_string& str);  // 针对mini_string对象
	mini_string& operator+=(const char* str);         // 针对C风格字符串
	mini_string& operator+=(const char ch);                 // 针对单个字符

	// ======= 迭代器 ========
	typedef char* iterator;
	typedef char* const_iterator;

	iterator begin() { return _data; }
	iterator end() { return _data + _size; }
	/*const_iterator begin(){ return _data; }
	const_iterator  end() { return _data + _size; }*/

	// ======= 运算符重载 =========
	bool operator==(const mini_string& str) const { return strcmp(_data, str._data) == 0; }
	bool operator!=(const mini_string& str) const { return !(*this == str._data); }
	bool operator>(const mini_string& str) const { return !(*this <= str._data); }
	bool operator>=(const mini_string& str) const { return !(*this < str._data); }
	bool operator<(const mini_string& str) const { return strcmp(_data, str._data) < 0; }
	bool operator<=(const mini_string& str) const { return *this < str._data || *this == str._data; }
};

// ======= 流操作 =========
istream& operator>>(istream& is,  mini_string& str);
ostream& operator<<(ostream& os, const mini_string& str);

// ======== mini_string.cpp ========

#include "mini_string.h"
const size_t mini_string::npos = -1;
// ======= 构造&析构 ========
// 默认构造:分配 1 字节,存放 '\0'
mini_string::mini_string()
    : _data(new char[1])
    , _size(0)
    , _capacity(1)
{
    _data[0] = '\0';
}

// 用 C 字符串构造
mini_string::mini_string(const char* s) {
    _size = strlen(s);
    _capacity = _size + 1;
    _data = new char[_capacity];
    // 拷贝s到_data
    memcpy(_data, s, _capacity);
}

// 拷贝构造:深拷贝
mini_string::mini_string(const mini_string& str) {
    _size = str._size;
    _capacity = str._capacity;
    _data = new char[_capacity];
    memcpy(_data, str._data, _capacity);
}

// 析构函数:统一释放
mini_string::~mini_string() {
    delete[] _data;
    _data = nullptr;
    _size = _capacity = 0;
}

// ======= 元素访问 ========
// 访问指定位置的字符
char& mini_string::at(size_t i) {
    if (i >= _size) throw std::out_of_range("index out of range");
    return _data[i];
}

// ======= 赋值操作 ========
mini_string& mini_string::operator=(const mini_string& str) {
    // 防止自赋值
    if (this != &str) {
        delete[] _data;
        _size = str._size;
        _capacity = str._capacity;
        _data = new char[_capacity];

        memcpy(_data, str._data, _capacity);
    }
    return *this;
}

// ======= 容量操作 ========
void mini_string::reserve(size_t new_cap) {
    // 只有传递的新容量够大才扩容
    if (new_cap > _capacity) {
        // 开一个新容量的空间 释放旧空间 指向新空间
        char* tmp = new char[new_cap];
        memcpy(tmp, _data, _size + 1);
        delete[] _data;
        _data = tmp;
        _capacity = new_cap;
    }
}
// 缩小容量
void mini_string::shrink_to_fit() {
    // 这个1是为了多存'\0'
    if (_capacity > _size + 1 ) {
        // 开一个新容量的空间 释放旧空间 指向新空间
        char* tmp = new char[_size + 1];
        memcpy(tmp, _data, _size + 1);
        delete[] _data;
        _data = tmp;
        _capacity = _size + 1;
    }
}

// ======= 修改操作 ========

void  mini_string::erase(size_t pos, size_t len) {
    // 边界检查
    assert(pos <= _size);
    // 如果pos在末尾,不用操作
    if (pos == _size) return;
    // 如果len足够长 有多少删多少
    if (len > _size - pos) {
        _data[pos] = '\0';
        _size = pos;
    }
    else {
    // 老老实实挪动
        size_t i = pos + len;        
        while (i < _size) {
            _data[i - len] = _data[i];
            ++i;
        }
        _size -= len;
    }
    _data[_size] = '\0';// 一定不要忘了字符串结尾
}
void  mini_string::insert(size_t pos, const char ch) {
    // 边界检查
    assert(pos <= _size);
    // 先扩容
    if (_size + 1 >= _capacity) {
        size_t new_cap = _capacity == 0 ? 4 : 2 * _capacity;
        reserve(new_cap);
    }

    size_t i = _size; 
    while (i > pos) {
        _data[i] = _data[i - 1];
        --i;
    }
    _data[pos] = ch;
    ++_size;            // 先更新长度
    _data[_size] = '\0';// 结尾符位置为新的_size
}

void  mini_string::insert(size_t pos, const char* str) {
    // 边界检查
    assert(pos <= _size);
    size_t len = strlen(str);
    if (_size + len + 1 > _capacity) reserve(_size + len + 1);
    
    // 将 [pos, _size) 区间的字符向后移动 len 位
    size_t i = _size;
    while (i > pos) {
        _data[i + len - 1] = _data[i -1];
        --i;
    }
    // 把目标字符串拷贝从pos位置开始拷贝
    memcpy(_data + pos, str, len);
    _data[_size + len] = '\0';
    _size += len;
}

void  mini_string::push_back(const char ch) {
    // 先扩容
    if (_size + 1 >= _capacity) {
        size_t new_cap = _capacity == 0 ? 4 : 2 * _capacity;
        reserve(new_cap);
    }

    _data[_size] = ch;  // 插入新字符
    ++_size;            // 先更新长度
    _data[_size] = '\0';// 此时_size已+1
}

mini_string& mini_string::append(const char* str) {
    size_t len = strlen(str);
    if (_size + len + 1 > _capacity) reserve(_size + len + 1);

    memcpy(_data + _size, str, len);
    _size += len;
    _data[_size] = '\0';

    return *this; // 返回当前对象的引用,无拷贝
}

mini_string& mini_string::append(const mini_string& str) {
    size_t len = str.size();
    if (_size + len + 1 > _capacity) reserve(_size + len + 1);

    memcpy(_data + _size, str.c_str(), len);
    _size += len;
    _data[_size] = '\0';

    return *this; // 返回当前对象的引用,而非拷贝
}

mini_string& mini_string::operator+=(const char ch) {
    push_back(ch);
    return *this;
}
mini_string& mini_string::operator+=(const char* str) {
    append(str);
    return *this;
}
mini_string& mini_string::operator+=(const mini_string& str) {
    append(str);
    return *this;
}
// ======= 查找操作 ========
size_t mini_string::find(const char target_char) const {
    for (size_t i = 0; i < _size; i++)
    {
        if (_data[i] == target_char) return i;
    }
    return npos;
}
size_t mini_string::find(const char* target_str) const {
    size_t len = strlen(target_str);
    if (len == 0) return 0;
    for (size_t i = 0; i + len <= _size; ++i) {
        if (strncmp(_data + i, target_str, len) == 0) return i;
    }
    return npos;
}

mini_string mini_string::substr(size_t pos, size_t len) const {
    // 越界判断
    assert(pos < _size);

    // 计算实际长度:不能超过剩余字符数
    if (pos + len > _size) {
        len = _size - pos;
    }

    // 用已有的构造函数来完成拷贝
    mini_string result;
    result.reserve(len + 1);   // 提前分配内存,避免多次扩容
    for (size_t i = 0; i < len; ++i) {
        result.push_back(_data[pos + i]);
    }

    return result;
}

// ======= 流操作 =========
ostream& operator<<(std::ostream& os, const mini_string& str) {
    for (size_t i = 0; i < str.size(); i++)
    {
        os << str[i];
    }
    return os;
}
istream& operator>>(istream& is, mini_string& str) {
    str.clear();
    char ch;

    // 跳过前导空格
    while (is.get(ch) && (ch == ' ' || ch == '\n')) {
        // 什么都不做
    }

    if (!is) return is;

    // 读取单词,直到空格或换行
    do {
        str.push_back(ch);
    } while (is.get(ch) && ch != ' ' && ch != '\n');

    return is;
}


// ======== test_mini_string.cpp ========
#include "mini_string.h"

// ======= 辅助打印函数 ========
static void print_header(const char* title) {
    cout << "===================== " << title << " =====================\n";
}

// ======= 构造 & 赋值 测试 ========
void test_constructors_and_assignment() {
    print_header("构造 & 赋值 测试");

    // 默认构造
    mini_string s0;
    cout << "s0 (default) size=" << s0.size() << " c_str=\"" << s0.c_str() << "\"\n";
    assert(s0.size() == 0);
    assert(strcmp(s0.c_str(), "") == 0);

    // C 字符串构造
    mini_string s1("Hello");
    cout << "s1 (from C-string) size=" << s1.size() << " c_str=\"" << s1.c_str() << "\"\n";
    assert(s1.size() == 5);
    assert(strcmp(s1.c_str(), "Hello") == 0);

    // 拷贝构造
    mini_string s2(s1);
    cout << "s2 (copy of s1) size=" << s2.size() << " c_str=\"" << s2.c_str() << "\"\n";
    assert(s2.size() == s1.size());
    assert(strcmp(s2.c_str(), s1.c_str()) == 0);

    // 赋值操作
    mini_string s3;
    s3 = s1;
    cout << "s3 (assigned from s1) c_str=\"" << s3.c_str() << "\"\n";
    assert(strcmp(s3.c_str(), "Hello") == 0);

    // 自赋值(不应崩溃)
    s1 = s1;
    cout << "s1 after self-assign: \"" << s1.c_str() << "\"\n";
    assert(strcmp(s1.c_str(), "Hello") == 0);

    cout << "构造与赋值测试通过\n\n";
}

// ======= 元素访问 测试 ========
void test_element_access() {
    print_header("元素访问 测试");

    mini_string s("abc");
    // operator[]
    assert(s[0] == 'a' && s[1] == 'b' && s[2] == 'c');

    // 修改通过 operator[]
    s[0] = 'x';
    cout << "after s[0] = 'x': " << s.c_str() << "\n";
    assert(strcmp(s.c_str(), "xbc") == 0);

    // front/back
    assert(s.front() == 'x');
    assert(s.back() == 'c');

    // at() 越界检查
    bool threw = false;
    try {
        char ch = s.at(100); // 预计抛出
        (void)ch;
    }
    catch (const std::out_of_range& e) {
        threw = true;
        cout << "at() 越界抛出: " << e.what() << "\n";
    }
    assert(threw);

    cout << "元素访问测试通过\n\n";
}

// ======= 容量 操作 测试 ========
void test_capacity_operations() {
    print_header("容量 操作 测试");

    mini_string s("12345");
    size_t old_cap = s.capacity();
    cout << "初始 size=" << s.size() << " capacity=" << old_cap << "\n";

    // reserve 扩容
    s.reserve(64);
    cout << "reserve(64) 后 capacity=" << s.capacity() << "\n";
    assert(s.capacity() >= 64);

    // clear & empty
    s.clear();
    cout << "clear 后 size=" << s.size() << " empty=" << (s.empty() ? "true" : "false") << "\n";
    assert(s.size() == 0 && s.empty());

    // 注意:shrink_to_fit 的实现可能与标准不一致,测试只保证不会崩溃且字符串正确
    s = mini_string("hello");
    size_t cap_before = s.capacity();
    s.reserve(100);
    s.shrink_to_fit(); // 调用以确保不会崩溃(实现细节可能不同)
    cout << "调用 shrink_to_fit() 后 size=" << s.size() << " capacity=" << s.capacity() << "\n";
    assert(strcmp(s.c_str(), "hello") == 0);

    cout << "容量操作测试通过\n\n";
}

void test_modification_operations() {
    print_header("修改操作 测试");

    // 1) push_back 与 += char
    mini_string s0 = "Today";
    const char ch_back = ',';
    s0 += ch_back;
    cout << s0 << endl;

    // 2) append C-string
    const char* str_back = "happy!";
    s0 += str_back;
    cout << s0 << endl;


    // 3) operator+=(mini_string)
    const mini_string s1 = "I like it!";
    s0 += s1;
    cout << s0 << endl;

    // 4) insert 单字符(开头)
    s0.insert(0, 'p');
    cout << s0 << endl;

    // 5) erase(删除已知范围)
    s0.erase(0, 3);
    cout << s0 << endl;
   
    // 6) clear
    s0.clear();
    cout << s0 << endl;

    cout << "修改操作测试通过\n\n";
}

void test_find_and_substr() {
    print_header("查找 & 截取 测试");

    // 测试字符串初始化
    mini_string s = "hello world hello";
    cout << "测试字符串: " << s << endl;

    // 1) 查找单个字符
    char target_char = 'w';
    size_t p1 = s.find(target_char);
    cout << "find('" << target_char << "') => " << p1 << "\n";
    assert(p1 != mini_string::npos);

    // 2) 查找子字符串
    const char* target_str = "hello";
    size_t p2 = s.find(target_str);
    cout << "find(\"" << target_str << "\") => " << p2 << "\n";
    assert(p2 == 0);

    // 3) 查找空字符串
    const char* empty_str = "";
    size_t p3 = s.find(empty_str);
    cout << "find(\"" << empty_str << "\") => " << p3 << " (expect 0)\n";
    assert(p3 == 0);

    // 4) 正常截取子字符串
    size_t pos1 = 6, len1 = 5;
    mini_string sub = s.substr(pos1, len1);
    cout << "substr(" << pos1 << "," << len1 << ") => \"" << sub.c_str() << "\"\n";
    assert(strcmp(sub.c_str(), "world") == 0);

    // 5) 截取超长长度的子字符串
    size_t pos2 = 6, len2 = 999;
    mini_string sub2 = s.substr(pos2, len2);
    cout << "substr(" << pos2 << "," << len2 << ") => \"" << sub2.c_str() << "\"\n";
    assert(strcmp(sub2.c_str(), "world hello") == 0);

    cout << "查找与截取测试通过\n\n";
}

// ======= 迭代器 & 范围 for 测试 ========
void test_iterators_and_rangefor() {
    print_header("迭代器 & 范围 for 测试");

    mini_string s("iter");
    // 通过迭代器读取
    string built;
    for (mini_string::iterator it = s.begin(); it != s.end(); ++it) {
        built.push_back(*it);
    }
    cout << "iterator read => \"" << built << "\"\n";
    assert(built == "iter");

    // 范围 for
    built.clear();
    for (auto ch : s) built.push_back(ch);
    cout << "range-for => \"" << built << "\"\n";
    assert(built == "iter");

    cout << "迭代器测试通过\n\n";
}

// ======= 其它边界 & 综合测试 ========
void test_misc_and_edge_cases() {
    print_header("其它边界 & 综合测试");

    // 空字符串 append / insert / erase
    mini_string s;
    s.append("");
    assert(s.size() == 0);
    s.insert(0, ""); // 插入空串
    assert(s.size() == 0);
    s.erase(0, 0);
    assert(s.size() == 0);

    // 长字符串拼接(触发多次扩容)
    mini_string longstr;
    const char* chunk = "0123456789";
    for (int i = 0; i < 100; ++i) longstr.append(chunk);
    cout << "longstr size=" << longstr.size() << "\n";
    assert(longstr.size() == (size_t)10 * 100);

    cout << "其它边界测试通过\n\n";
}

int main() {
    cout << "=== mini_string 单元测试开始 ===\n\n";

    test_constructors_and_assignment();
    test_element_access();
    test_capacity_operations();
    test_modification_operations();
    test_find_and_substr();
    test_iterators_and_rangefor();
    test_misc_and_edge_cases();

    cout << "全部测试通过 \n";
    return 0;
}
相关推荐
hope_wisdom2 小时前
C/C++数据结构之栈基础
c语言·数据结构·c++··stack
ajassi20003 小时前
开源 C++ QT Widget 开发(十四)多媒体--录音机
linux·c++·qt·开源
劲镝丶4 小时前
malloc概述
c语言·开发语言·c++
努力努力再努力wz5 小时前
【C++进阶系列】:万字详解红黑树(附模拟实现的源码)
java·linux·运维·c语言·开发语言·c++
cccyi75 小时前
C/C++类型转换
c++
枫fengw5 小时前
9.8 C++
开发语言·c++
JCBP_6 小时前
QT(3)
开发语言·汇编·c++·qt·算法
XFF不秃头6 小时前
力扣刷题笔记-三数之和
c++·笔记·算法·leetcode
Pafey6 小时前
VS2022 + Qt5.9 中文乱码/项目设置utf-8编码
c++·qt·中文乱码