深入解析string:从设计思想到完整实现

概述

这是一个完整的自定义字符串类 yyq::string 的实现,模仿 C++ 标准库中的 std::string。代码分为头文件 string.h 和实现文件 string.cpp,采用了现代 C++ 的编程风格和优化技巧。本文将全面分析该实现,不遗漏任何细节。

目录

概述

一、整体架构与设计

[1.1 命名空间与头文件保护](#1.1 命名空间与头文件保护)

[1.2 类成员变量设计](#1.2 类成员变量设计)

二、构造函数与拷贝控制

[2.1 构造函数](#2.1 构造函数)

[2.2 现代拷贝构造(拷贝交换技法)](#2.2 现代拷贝构造(拷贝交换技法))

[2.3 赋值运算符(拷贝交换技法)](#2.3 赋值运算符(拷贝交换技法))

[2.4 析构函数](#2.4 析构函数)

三、访问接口与迭代器

[3.1 基本访问接口](#3.1 基本访问接口)

[3.2 索引操作符](#3.2 索引操作符)

[3.3 迭代器支持](#3.3 迭代器支持)

四、内存管理策略

[4.1 reserve函数](#4.1 reserve函数)

[4.2 扩容策略](#4.2 扩容策略)

五、字符串操作实现

[5.1 添加操作](#5.1 添加操作)

[5.2 插入操作](#5.2 插入操作)

插入单个字符:

插入字符串:

[5.3 删除操作](#5.3 删除操作)

六、查找与子串操作

[6.1 查找功能](#6.1 查找功能)

[6.2 子串操作](#6.2 子串操作)

七、比较运算符

八、流操作符

[8.1 输出运算符](#8.1 输出运算符)

[8.2 输入运算符](#8.2 输入运算符)

[8.3 getline函数](#8.3 getline函数)

九、代码中的注释与优化

[9.1 注释的替代实现](#9.1 注释的替代实现)

[9.2 被注释的旧版本](#9.2 被注释的旧版本)

十、优化建议

[10.1 性能优化](#10.1 性能优化)

[10.2 功能扩展](#10.2 功能扩展)

[10.3 边界情况处理](#10.3 边界情况处理)

十一、代码亮点总结

十二、完整性与学习价值


一、整体架构与设计

1.1 命名空间与头文件保护

cpp

复制代码
namespace yyq
{
    class string { ... };
}
  • 使用自定义命名空间 yyq 避免与标准库冲突

  • #pragma once 防止头文件重复包含

  • _CRT_SECURE_NO_WARNINGS 1 禁用VS的安全警告

1.2 类成员变量设计

cpp

复制代码
private:
    char* _str = nullptr;      // 动态分配的字符数组
    size_t _size = 0;          // 当前字符串长度(不含'\0')
    size_t _capacity = 0;      // 当前容量(不含'\0')
    static const size_t npos;  // 静态常量,表示无效位置

这种三成员设计是字符串类的经典实现,体现了RAII原则。

二、构造函数与拷贝控制

2.1 构造函数

cpp

复制代码
string(const char* str = "")
{
    _size = strlen(str);
    _capacity = _size;
    _str = new char[_capacity + 1];
    strcpy(_str, str);
}
  • 使用空字符串作为默认参数

  • 精确分配内存(长度+1用于'\0')

2.2.1 构造函数

cpp

复制代码
string(const char* str = "")
{
    _size = strlen(str);
    _capacity = _size;
    _str = new char[_capacity + 1];
    strcpy(_str, str);
}
  • 使用空字符串作为默认参数

  • 精确分配内存(长度+1用于'\0')

  • 注意:空字符串""的长度为0,仍会分配1字节存储'\0'

2.2.2 现代拷贝构造(拷贝交换技法)

cpp

复制代码
string(const string& s)
{
    string temp = s._str;  // 调用构造函数
    swap(temp);            // 交换资源
}

void swap(string& s)
{
    std::swap(_str, s._str);
    std::swap(_size, s._size);
    std::swap(_capacity, s._capacity);
}

这种实现简洁优雅,利用了已存在的构造函数和高效的swap操作。

2.3 赋值运算符(拷贝交换技法)

cpp

复制代码
string& operator=(string temp)
{
    swap(temp);
    return *this;
}

这是异常安全的实现,参数为值传递自动调用拷贝构造,解决了自赋值问题。

2.4 析构函数

cpp

复制代码
~string()
{
    if (_str)
    {
        delete[] _str;
        _str = nullptr;
        _size = _capacity = 0;
    }
}

检查_str是否为nullptr再删除,是良好的防御性编程。

三、访问接口与迭代器

3.1 基本访问接口

cpp

复制代码
const char* c_str()const { return _str; }
size_t size()const { return _size; }
size_t capacity()const { return _capacity; }
void clear() { _str[0] = '\0'; _size = 0; }

提供了一组基本的const成员函数,支持常对象调用。

3.2 索引操作符

cpp

复制代码
char& operator[](size_t pos)
{
    assert(pos < size());
    return _str[pos];
}

const char& operator[](size_t pos)const
{
    assert(pos < size());
    return _str[pos]; 
}

提供了非常量和常量两个版本,支持读写访问和只读访问。

3.3 迭代器支持

cpp

复制代码
typedef char* iterator;
typedef const char* const_iterator;

iterator begin() { return _str; }
iterator end() { return _str + _size; }
const_iterator begin()const { return _str; }
const_iterator end()const { return _str + _size; }

完整的迭代器支持,使类能与STL算法配合,支持范围for循环。

四、内存管理策略

4.1 reserve函数

cpp

复制代码
void reserve(size_t n)
{
    if (n > _capacity)
    {
        char* temp = new char[n + 1];
        strcpy(temp, _str);
        delete[] _str;
        _str = temp;
        _capacity = n;
    }
}

只扩容不缩容,先分配新内存再释放旧内存,保证异常安全。

4.2 扩容策略

cpp

复制代码
// push_back中的扩容
reserve(_capacity == 0 ? 4 : 2 * _capacity);

// append中的扩容
reserve(_size + len > 2 * _capacity ? _size + len : 2 * _capacity);

采用vector风格的扩容策略:初始容量0→4,后续2倍扩容或按需扩容。

五、字符串操作实现

5.1 添加操作

cpp

复制代码
void push_back(char ch)
{
    if (_size == _capacity)
        reserve(_capacity == 0 ? 4 : 2 * _capacity);
    _str[_size] = ch;
    ++_size;
    _str[_size] = '\0';
}

void append(const char* str)
{
    size_t len = strlen(str);
    if (_size + len > _capacity)
        reserve(_size + len > 2 * _capacity ? _size + len : 2 * _capacity);
    strcpy(_str + _size, str);
    _size += len;
}

string& operator+=(char ch) { push_back(ch); return *this; }
string& operator+=(const char* str) { append(str); return *this; }

提供两种添加方式,并重载了+=运算符,支持链式调用。

5.2 插入操作

插入单个字符:

cpp

复制代码
void insert(size_t pos, char ch)
{
    assert(pos <= _size);
    if (_size == _capacity)
        reserve(_capacity == 0 ? 4 : 2 * _capacity);
    
    size_t end = _size + 1;
    while (end > pos)
    {
        _str[end] = _str[end - 1];
        --end;
    }
    _str[pos] = ch;
    ++_size;
}
插入字符串:

cpp

复制代码
void insert(size_t pos, const char* str)
{
    assert(pos <= _size);
    size_t len = strlen(str);
    if (len == 0) return;  // 空字符串优化
    
    if (_size + len > _capacity)
        reserve(_size + len > 2 * _capacity ? _size + len : 2 * _capacity);
    
    size_t end = _size + len;
    while (end > pos + len - 1)
    {
        _str[end] = _str[end - len];
        --end;
    }
    
    for (size_t i = 0; i < len; i++)
        _str[pos + i] = str[i];
    
    _size += len;
}

插入操作实现了从后向前移动元素的算法,确保数据正确性。

5.3 删除操作

cpp

复制代码
void erase(size_t pos, size_t len)
{
    assert(pos < _size);
    if (len >= _size - pos)  // 删除到结尾
    {
        _str[pos] = '\0';
        _size = pos;
        return;
    }
    
    size_t end = pos + len;
    while (end <= _size)  // 包含'\0'
    {
        _str[end - len] = _str[end];
        ++end;
    }
    _size -= len;
}

提供了从指定位置删除指定长度字符的功能,支持删除到结尾的特殊情况。

六、查找与子串操作

6.1 查找功能

cpp

复制代码
size_t find(char ch, size_t pos)
{
    assert(pos < _size);
    for (size_t i = pos; i < _size; i++)
        if (_str[i] == ch) return i;
    return npos;
}

size_t find(const char* str, size_t pos)
{
    assert(pos < _size);
    const char* ptr = strstr(_str + pos, str);
    return ptr ? ptr - _str : npos;
}
  • 字符查找:简单线性查找

  • 字符串查找:利用C库函数strstr提高效率

6.2 子串操作

cpp

复制代码
string substr(size_t pos, size_t len)
{
    assert(pos < _size);
    if (len > _size - pos)  // 调整长度避免越界
        len = _size - pos;
    
    string str;
    str.reserve(len);
    for (size_t i = 0; i < len; i++)
        str += _str[pos + i];  // 逐个添加(可优化为批量拷贝)
    
    return str;
}

返回新字符串对象,自动调整长度防止越界。

七、比较运算符

cpp

复制代码
bool operator<(const string& s1, const string& s2)
    { return strcmp(s1.c_str(), s2.c_str()) < 0; }
bool operator==(const string& s1, const string& s2)
    { return strcmp(s1.c_str(), s2.c_str()) == 0; }
bool operator<=(const string& s1, const string& s2)
    { return s1 < s2 || s1 == s2; }
bool operator>(const string& s1, const string& s2)
    { return !(s1 <= s2); }
bool operator>=(const string& s1, const string& s2)
    { return !(s1 < s2); }
bool operator!=(const string& s1, const string& s2)
    { return !(s1 == s2); }

只实现<==,其他运算符基于这两个实现,符合C++标准库的设计理念。

八、流操作符

8.1 输出运算符

cpp

复制代码
ostream& operator<<(ostream& out, const string& s)
{
    for (auto ch : s)  // 使用范围for循环(依赖迭代器)
        out << ch;
    return out;
}

简洁优雅,充分利用了迭代器支持。

8.2 输入运算符

cpp

复制代码
istream& operator>>(istream& in, string& s)
{
    s.clear();
    const int N = 256;
    char buff[N];
    int i = 0;
    
    char ch = in.get();  // 使用get(),不跳过空白符
    
    while (ch != ' ' && ch != '\n')
    {
        buff[i++] = ch;
        if (i == N - 1)  // 缓冲区快满
        {
            buff[i] = '\0';
            s += buff;   // 追加到字符串
            i = 0;       // 重置缓冲区
        }
        ch = in.get();
    }
    
    if (i > 0)  // 处理缓冲区剩余内容
    {
        buff[i] = '\0';
        s += buff;
    }
    
    return in;
}

使用缓冲区减少内存分配次数,正确处理空格和换行符作为分隔符。

8.3 getline函数

cpp

复制代码
istream& getline(istream& in, string& s)
{
    // 与operator>>类似,但只以换行符为结束
    while (ch != '\n') { ... }
}

专门处理整行输入,不以空格作为分隔符。

九、代码中的注释与优化

9.1 注释的替代实现

代码中包含多个注释掉的实现,展示了不同的算法思路:

cpp

复制代码
// 注释中的insert替代实现
//size_t end = _size;
//while (end >= pos)
//{
//    _str[end + 1] = _str[end];
//    --end;
//}

这些注释提供了教学价值,展示了算法设计的多样性。

9.2 被注释的旧版本

cpp

复制代码
// 传统拷贝构造函数
//string(const string& s)
//{
//    _str = new char[s._capacity + 1];
//    strcpy(_str, s._str);
//    _size = s.size();
//    _capacity = s._capacity;
//}

展示了传统实现方式,与现代实现形成对比。

十、优化建议

10.1 性能优化

  • substr中逐个字符添加可优化为批量拷贝

  • 某些情况下可考虑使用memmove代替循环移动字符

  • 添加移动构造函数和移动赋值运算符(C++11)

  • 可添加resize方法控制大小

10.2 功能扩展

  • 缺少pop_back方法

  • 缺少与数值类型的转换方法

  • 缺少大小写转换等功能

  • 可添加find_first_of等更丰富的查找功能

10.3 边界情况处理

  • 构造函数接收nullptr时应处理

  • 某些方法可添加noexcept声明

  • 可考虑添加自定义分配器支持

十一、代码亮点总结

  1. 拷贝交换技法:异常安全且简洁的拷贝控制

  2. 完整迭代器支持:与STL无缝集成

  3. 合理的内存管理:平衡性能与内存使用

  4. 全面的运算符重载:提供自然的使用语法

  5. 流操作符集成:完全融入C++ IO系统

  6. 防御性编程:assert检查、nullptr处理

  7. 教学价值:注释展示了多种实现方式

十二、完整性与学习价值

这个yyq::string实现涵盖了字符串类的所有核心功能:

  • ✅ 构造函数与析构函数

  • ✅ 拷贝控制(Rule of Three)

  • ✅ 内存管理(扩容策略)

  • ✅ 基本操作(增删改查)

  • ✅ 运算符重载(比较、赋值、流)

  • ✅ 迭代器支持

  • ✅ 与C字符串兼容

虽然与标准库的std::string相比还有差距,但它作为一个教学实现,完美展示了C++类设计、资源管理、运算符重载等核心概念,是学习C++面向对象编程的优秀范例。

相关推荐
格林威2 小时前
工业相机图像高速存储(C++版):内存映射文件(MMF)零拷贝方案,附海康相机实战代码!
开发语言·c++·数码相机·计算机视觉·视觉检测·工业相机·海康相机
8Qi82 小时前
LeetCode热题100--189
c语言·数据结构·c++·算法·leetcode
灰色小旋风2 小时前
力扣第八题C++ 字符串转换整数
c++·算法·leetcode
melonbo2 小时前
C++ 中用于模块间通信的设计模式
开发语言·c++·设计模式
王的宝库2 小时前
Go 语言基础进阶:指针、init、匿名函数/闭包、defer
开发语言·go
Qt程序员2 小时前
Linux .so 动态库:底层原理
linux·c++·内核·动态库
灰色小旋风2 小时前
力扣第十题C++正则表达式匹配
c++·leetcode·正则表达式
乌萨奇也要立志学C++2 小时前
【Linux】线程池(一)C++ 手写线程池:基于策略模式实现高性能日志模块
linux·c++·策略模式
lihaihui19912 小时前
Linux C++知识梳理
linux·c++