概述
这是一个完整的自定义字符串类 yyq::string 的实现,模仿 C++ 标准库中的 std::string。代码分为头文件 string.h 和实现文件 string.cpp,采用了现代 C++ 的编程风格和优化技巧。本文将全面分析该实现,不遗漏任何细节。
目录
概述
一、整体架构与设计
1.1 命名空间与头文件保护
1.2 类成员变量设计
二、构造函数与拷贝控制
2.1 构造函数
2.2 现代拷贝构造(拷贝交换技法)
2.3 赋值运算符(拷贝交换技法)
2.4 析构函数
三、访问接口与迭代器
3.1 基本访问接口
3.2 索引操作符
3.3 迭代器支持
四、内存管理策略
4.1 reserve函数
4.2 扩容策略
五、字符串操作实现
5.1 添加操作
5.2 插入操作
插入单个字符:
插入字符串:
5.3 删除操作
六、查找与子串操作
6.1 查找功能
6.2 子串操作
七、比较运算符
八、流操作符
8.1 输出运算符
8.2 输入运算符
8.3 getline函数
九、代码中的注释与优化
9.1 注释的替代实现
9.2 被注释的旧版本
十、优化建议
10.1 性能优化
10.2 功能扩展
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声明
-
可考虑添加自定义分配器支持
十一、代码亮点总结
拷贝交换技法:异常安全且简洁的拷贝控制
完整迭代器支持:与STL无缝集成
合理的内存管理:平衡性能与内存使用
全面的运算符重载:提供自然的使用语法
流操作符集成:完全融入C++ IO系统
防御性编程:assert检查、nullptr处理
教学价值:注释展示了多种实现方式
十二、完整性与学习价值
这个yyq::string实现涵盖了字符串类的所有核心功能:
-
✅ 构造函数与析构函数
-
✅ 拷贝控制(Rule of Three)
-
✅ 内存管理(扩容策略)
-
✅ 基本操作(增删改查)
-
✅ 运算符重载(比较、赋值、流)
-
✅ 迭代器支持
-
✅ 与C字符串兼容
虽然与标准库的std::string相比还有差距,但它作为一个教学实现,完美展示了C++类设计、资源管理、运算符重载等核心概念,是学习C++面向对象编程的优秀范例。

