概述
这是一个完整的自定义字符串类 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声明 -
可考虑添加自定义分配器支持
十一、代码亮点总结
-
拷贝交换技法:异常安全且简洁的拷贝控制
-
完整迭代器支持:与STL无缝集成
-
合理的内存管理:平衡性能与内存使用
-
全面的运算符重载:提供自然的使用语法
-
流操作符集成:完全融入C++ IO系统
-
防御性编程:assert检查、nullptr处理
-
教学价值:注释展示了多种实现方式
十二、完整性与学习价值
这个yyq::string实现涵盖了字符串类的所有核心功能:
-
✅ 构造函数与析构函数
-
✅ 拷贝控制(Rule of Three)
-
✅ 内存管理(扩容策略)
-
✅ 基本操作(增删改查)
-
✅ 运算符重载(比较、赋值、流)
-
✅ 迭代器支持
-
✅ 与C字符串兼容
虽然与标准库的std::string相比还有差距,但它作为一个教学实现,完美展示了C++类设计、资源管理、运算符重载等核心概念,是学习C++面向对象编程的优秀范例。