C++ string类模拟实现(完整版,含全运算符重载)

在C++编程中,string类是高频使用的字符串处理工具,它封装了字符数组的底层实现,提供了丰富的操作接口,极大简化了字符串的增删改查、比较、输入输出等操作。很多初学者使用string时只知其然,不知其所以然,本文将从零开始,完整模拟实现string类的核心功能,重点补全运算符重载(解决浅拷贝、比较、流输入输出等关键问题),带大家深入理解string类的底层原理和设计思路,适合C++入门及进阶学习者阅读。

本文分为3个核心部分:类框架设计与错误修正、核心功能实现(含运算符重载)、实操测试示例,代码可直接复制运行,关键步骤均附详细注释,新手也能轻松看懂。

一、前期准备:string类框架设计与错误修正

首先搭建string类的基本框架,包含私有成员变量(存储字符串指针、有效长度、容量)和公有接口(构造、析构、迭代器、元素访问、运算符重载等)。先修正之前代码中的细节错误,避免后续逻辑异常。

1.1 string.h 头文件(类声明,含所有接口)

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS 
#pragma once 
#include<iostream>
#include<cstring>  // 修正:原<string.>改为C++标准头文件<cstring>
#include<assert.h>
namespace bit  // 自定义命名空间,避免与标准库string冲突
{
 class string {
 public:
 // 迭代器(支持范围for循环,遍历字符串)
 typedef char* iterator;
 typedef const char* const_iterator;  // 修正:原命名_errator改为规范的const_iterator

 // 迭代器接口
 iterator begin() { return _str; }
 iterator end() { return _str + _size; }
 const_iterator begin() const { return _str; }
 const_iterator end() const { return _str + _size; }

 // 基础接口
 const char* c_str() const { return _str; }  // 返回C风格字符串,用于兼容C接口
 void clear() { _str[0] = '\0'; _size = 0; }  // 清空字符串,不释放容量
 size_t size() const { return _size; }  // 返回有效字符长度(不含'\0')
 size_t capacity() const { return _capacity; }  // 返回容量(可存储的最大字符数,不含'\0')

 // 元素访问(重载[],支持读写)
 char& operator[](size_t pos) {
 assert(pos < _size);  // 断言:避免下标越界
 return _str[pos];
 }
 const char& operator[](size_t pos) const {
 assert(pos < _size);
 return _str[pos];
 }

 // 新增:拷贝构造(解决浅拷贝问题)
 string(const string& s);
 // 新增:赋值运算符重载(深拷贝+自赋值保护)
 string& operator=(const string& s);

 // 新增:比较运算符重载(==、!=、<、<=、>、>=)
 bool operator==(const string& s) const;
 bool operator!=(const string& s) const;
 bool operator<(const string& s) const;
 bool operator<=(const string& s) const;
 bool operator>(const string& s) const;
 bool operator>=(const string& s) const;

 // 原有核心接口(容量、修改、插入、删除、查找)
 void reserve(size_t n);  // 预留容量(仅扩容,不改变有效长度)
 void push_back(char ch);  // 尾插单个字符
 void append(const char* str);  // 尾插C风格字符串
 string& operator+=(char ch);  // 重载+=(尾插字符)
 string& operator+=(const char* str);  // 重载+=(尾插字符串)
 void insert(size_t pos, char ch);  // 插入单个字符
 void insert(size_t pos, const char* str);  // 插入C风格字符串
 void erase(size_t pos, size_t len = npos);  // 删除字符
 size_t find(char ch, size_t pos = 0);  // 查找单个字符
 size_t find(const char* str, size_t pos = 0);  // 查找C风格字符串
 string substr(size_t pos = 0, size_t len = npos);  // 截取子串

 // 构造函数(默认构造+带参构造)
 string(const char* str = "") {
 _size = strlen(str);  // 有效长度为字符串实际长度(不含'\0')
 _capacity = _size;    // 修正:原_size = _capacity,颠倒逻辑
 _str = new char[_capacity + 1];  // +1用于存储'\0'
 strcpy(_str, str);    // 拷贝字符串内容
 }

 // 析构函数(释放堆内存,避免内存泄漏)
 ~string() {
 if (_str) {
 delete[] _str;     // 释放动态开辟的字符数组
 _str = nullptr;    // 置空指针,避免野指针
 _size = _capacity = 0;  // 重置长度和容量
 }
 }

 private:
 char* _str = nullptr;    // 指向堆上字符数组的指针
 size_t _size = 0;        // 有效字符长度(不含'\0')
 size_t _capacity = 0;    // 容量(最大可存储字符数,不含'\0')
 static const size_t npos = -1;  // 无符号数最大值,用于默认参数(如erase删除到末尾)
 };

 // 新增:流运算符重载(全局函数,无法作为成员函数)
 std::ostream& operator<<(std::ostream& out, const bit::string& s);  // 流插入(cout << string)
 std::istream& operator>>(std::istream& in, bit::string& s);        // 流提取(cin >> string)
}

}

二、核心功能实现(string.cpp)

实现头文件中声明的所有接口,重点补全运算符重载,同时保证内存管理的安全性(深拷贝、避免内存泄漏),关键步骤附详细注释,方便理解底层逻辑。

cpp 复制代码
#include"string.h"
namespace bit {

 // 1. 容量管理:reserve(预留容量,仅当n>当前容量时扩容)
 void string::reserve(size_t n) {
 if (n > _capacity) {  // 只扩容,不缩容(符合标准库行为)
 char* tmp = new char[n + 1];  // 申请新内存,+1存'\0'
 if (_str) {  // 如果原有内存不为空,拷贝原有数据
 strcpy(tmp, _str);
 } else {  // 原有内存为空(空字符串),初始化tmp为'\0'
 tmp[0] = '\0';
 }
 delete[] _str;  // 释放旧内存
 _str = tmp;     // 指向新内存
 _capacity = n;  // 更新容量
 }
 }

 // 2. 尾插单个字符:push_back
 void string::push_back(char ch) {
 if (_size == _capacity) {  // 容量不足,需要扩容
 // 扩容策略:初始容量为4,后续2倍扩容(兼顾效率和内存利用率)
 reserve(_capacity == 0 ? 4 : _capacity * 2);
 }
 _str[_size++] = ch;  // 插入字符,更新有效长度
 _str[_size] = '\0';  // 保证字符串以'\0'结尾,兼容C风格接口
 }

 // 3. 尾插C风格字符串:append
 void string::append(const char* str) {
 size_t len = strlen(str);  // 获取待插入字符串长度
 if (_size + len > _capacity) {  // 容量不足,扩容
 // 扩容策略:取「当前容量2倍」和「总长度」的较大值,避免频繁扩容
 reserve(_size + len > _capacity * 2 ? _size + len : _capacity * 2);
 }
 strcpy(_str + _size, str);  // 从原有字符串末尾拷贝数据
 _size += len;  // 更新有效长度
 }

 // 4. 重载+=(尾插字符,复用push_back)
 string& string::operator+=(char ch) {
 push_back(ch);
 return *this;  // 返回自身,支持链式操作(s1 += 'a' += 'b')
 }

 // 5. 重载+=(尾插字符串,复用append)
 string& string::operator+=(const char* str) {
 append(str);
 return *this;
 }

 // 6. 插入单个字符:insert
 void string::insert(size_t pos, char ch) {
 assert(pos <= _size);  // 允许在末尾插入(pos == _size),禁止越界

 // 容量不足,扩容
 if (_size == _capacity) {
 reserve(_capacity == 0 ? 4 : _capacity * 2);
 }

 // 从后往前挪动数据,避免覆盖未处理的字符
 size_t end = _size + 1;
 while (end > pos) {
 _str[end] = _str[end - 1];
 --end;
 }

 _str[pos] = ch;  // 插入字符
 ++_size;         // 更新有效长度
 }

 // 7. 插入C风格字符串:insert
 void string::insert(size_t pos, const char* str) {
 assert(pos <= _size);
 size_t len = strlen(str);  // 待插入字符串长度

 // 容量不足,扩容
 if (_size + len > _capacity) {
 reserve(_size + len > _capacity * 2 ? _size + len : _capacity * 2);
 }

 // 从后往前挪动数据,挪动长度为待插入字符串长度
 size_t end = _size + len;
 while (end - len >= pos) {
 _str[end] = _str[end - len];
 --end;
 }

 // 拷贝待插入字符串到指定位置
 for (size_t i = 0; i < len; i++) {
 _str[pos + i] = str[i];
 }

 _size += len;  // 更新有效长度
 }

 // 8. 删除字符:erase
 void string::erase(size_t pos, size_t len) {
 assert(pos < _size);  // 删除位置必须合法

 // 情况1:删除到末尾(len为默认值npos,或pos+len超出有效长度)
 if (len == npos || pos + len >= _size) {
 _str[pos] = '\0';  // 直接置'\0',截断字符串
 _size = pos;       // 更新有效长度
 } else {
 // 情况2:删除指定长度,从后往前拷贝,覆盖待删除区域
 for (size_t i = pos + len; i <= _size; i++) {
 _str[i - len] = _str[i];
 }
 _size -= len;  // 更新有效长度
 }
 }

 // 9. 查找单个字符:find
 size_t string::find(char ch, size_t pos) {
 assert(pos <= _size);
 // 遍历字符串,找到返回下标,未找到返回npos
 for (size_t i = pos; i < _size; i++) {
 if (_str[i] == ch) {  // 修正:移除ch两侧的单引号
 return i;
 }
 }
 return npos;
 }

 // 10. 查找C风格字符串:find(复用C库函数strstr)
 size_t string::find(const char* str, size_t pos) {
 assert(pos <= _size);
 const char* ptr = strstr(_str + pos, str);  // 从pos位置开始查找
 if (ptr == nullptr) {  // 修正:判空对象为ptr,而非_str
 return npos;
 } else {
 return ptr - _str;  // 指针差值转换为下标
 }
 }

 // 11. 截取子串:substr
 string string::substr(size_t pos, size_t len) {
 assert(pos < _size);
 // 修正len边界:如果len超过剩余长度,只截取到末尾
 size_t real_len = len;
 if (len == npos || pos + len > _size) {
 real_len = _size - pos;
 }

 // 申请临时内存,存储子串
 char* tmp = new char[real_len + 1];
 strncpy(tmp, _str + pos, real_len);  // 拷贝指定长度的字符
 tmp[real_len] = '\0';  // 置'\0',保证字符串合法

 string sub(tmp);  // 构造子串对象
 delete[] tmp;     // 释放临时内存,避免内存泄漏
 return sub;
 }

 // 12. 拷贝构造(深拷贝,解决浅拷贝问题)
 string::string(const string& s) {
 // 深拷贝:为新对象申请独立内存,不与原对象共用内存
 _str = new char[s._capacity + 1];
 strcpy(_str, s._str);  // 拷贝字符串内容
 _size = s._size;       // 拷贝有效长度
 _capacity = s._capacity;  // 拷贝容量
 }

 // 13. 赋值运算符重载(深拷贝+自赋值保护)
 string& string::operator=(const string& s) {
 // 自赋值保护:如果s就是当前对象,直接返回,避免自身内存被释放
 if (this == &s) {
 return *this;
 }

 // 步骤1:释放当前对象的旧内存,避免内存泄漏
 delete[] _str;

 // 步骤2:深拷贝,申请新内存,拷贝数据
 _str = new char[s._capacity + 1];
 strcpy(_str, s._str);
 _size = s._size;
 _capacity = s._capacity;

 return *this;  // 返回自身,支持链式赋值(s1 = s2 = s3)
 }

 // 14. 比较运算符重载(基于strcmp实现,符合字符串比较规则)
 // == 重载:判断两个字符串是否相等
 bool string::operator==(const string& s) const {
 if (_size != s._size) {  // 长度不同,直接不相等
 return false;
 }
 // strcmp返回0表示两个字符串相等
 return strcmp(_str, s._str) == 0;
 }

 // != 重载:复用==,取反即可
 bool string::operator!=(const string& s) const {
 return !(*this == s);
 }

 // < 重载:strcmp返回负数表示当前字符串小于s
 bool string::operator<(const string& s) const {
 return strcmp(_str, s._str) < 0;
 }

 // <= 重载:复用<和==
 bool string::operator<=(const string& s) const {
 return *this < s || *this == s;
 }

 // > 重载:复用<=,取反即可
 bool string::operator>(const string& s) const {
 return !(*this <= s);
 }

 // >= 重载:复用<,取反即可
 bool string::operator>=(const string& s) const {
 return !(*this < s);
 }

 // 15. 流插入运算符重载(cout << string)
 std::ostream& operator<<(std::ostream& out, const bit::string& s) {
 // 遍历字符串输出,避免直接输出c_str()可能的截断问题
 for (size_t i = 0; i < s.size(); i++) {
 out << s[i];
 }
 return out;  // 返回out,支持链式输出(cout << s1 << s2)
 }

 // 16. 流提取运算符重载(cin >> string)
 std::istream& operator>>(std::istream& in, bit::string& s) {
 s.clear();  // 清空原有内容,避免叠加

 // 临时缓冲区:减少频繁扩容,提升效率
 char buff[128] = {0};
 char ch;
 ch = in.get();  // 读取单个字符(包括空格、换行,区别于cin>>默认忽略空白符)

 size_t i = 0;
 // 读取字符,直到遇到空白符(空格、换行、制表符)
 while (ch != ' ' && ch != '\n' && ch != '\t') {
 buff[i++] = ch;
 // 缓冲区满,追加到string,重置缓冲区
 if (i == 127) {
 s += buff;
 memset(buff, 0, sizeof(buff));
 i = 0;
 }
 ch = in.get();  // 继续读取下一个字符
 }

 // 追加缓冲区剩余内容
 if (i > 0) {
 s += buff;
 }

 return in;  // 返回in,支持链式输入(cin >> s1 >> s2)
 }

}

三、实操测试示例(main函数)

以下测试代码覆盖所有核心接口和运算符重载,复制到项目中即可运行,验证代码正确性。

cpp 复制代码
#include"string.h"
using namespace std;
using namespace bit;  // 使用自定义命名空间的string

int main() {
 // 1. 构造函数测试
 string s1("hello");  // 带参构造
 string s2;           // 默认构造(空字符串)
 string s3 = s1;      // 拷贝构造
 cout << "s1: " << s1 << " (size:" << s1.size() << ", capacity:" << s1.capacity() << ")" << endl;
 cout << "s3: " << s3 << " (拷贝构造测试)" << endl;

 // 2. 赋值重载测试
 string s4;
 s4 = s1;  // 赋值重载
 s4 += '!';  // += 字符测试
 s4 += " world";  // += 字符串测试
 cout << "s4: " << s4 << endl;  // 预期输出:hello! world

 // 3. 元素访问测试
 cout << "s4[0]: " << s4[0] << ", s4[5]: " << s4[5] << endl;  // 预期:h, !

 // 4. 插入、删除测试
 s4.insert(5, " C++");  // 插入字符串
 cout << "s4插入后: " << s4 << endl;  // 预期:hello C++! world
 s4.erase(5, 4);  // 删除从下标5开始的4个字符(" C++")
 cout << "s4删除后: " << s4 << endl;  // 预期:hello! world

 // 5. 查找、子串测试
 size_t pos = s4.find('w');  // 查找字符'w'
 cout << "s4中'w'的位置: " << pos << endl;  // 预期:6
 string sub = s4.substr(6, 5);  // 从下标6开始,截取5个字符
 cout << "s4的子串: " << sub << endl;  // 预期:world

 // 6. 比较运算符测试
 if (s1 == s3) cout << "s1 == s3" << endl;
 if (s1 != s4) cout << "s1 != s4" << endl;
 if (string("abc") < string("def")) cout << "abc < def" << endl;

 // 7. 流输入输出测试
 cout << "请输入一个字符串: ";
 cin >> s2;
 cout << "你输入的字符串: " << s2 << endl;

 return 0;
}

四、核心知识点总结(重点)

通过本次模拟实现,掌握string类的核心设计思路和底层细节,这也是C++面试高频考点:

  1. 内存管理:string类底层是动态开辟的字符数组,核心是通过reserve统一管理扩容,避免频繁申请/释放内存;扩容策略为「初始4,后续2倍」,兼顾效率和内存利用率。

  2. 深拷贝vs浅拷贝:拷贝构造和赋值重载必须实现深拷贝,否则多个对象会共用同一块内存,析构时重复释放导致程序崩溃;自赋值保护避免自身内存被误释放。

  3. 运算符重载

    1. 成员运算符重载(如[]、+=、==等):左操作数是当前对象(this指针),支持链式操作;

    2. 全局运算符重载(如<<、>>):左操作数是ostream/istream对象,无法作为成员函数,需通过公有接口访问string私有成员。

  4. 边界检查:通过assert断言保证操作合法性(如下标不越界、插入位置有效),提升代码健壮性。

  5. 兼容性:通过c_str()接口返回C风格字符串,兼容C库函数(如strcpy、strstr),保证代码通用性。

五、扩展优化方向(可选)

本文实现的string类已覆盖核心功能,可进一步扩展以下特性,提升代码完整性和性能:

  • 实现移动构造和移动赋值(C++11特性),避免深拷贝带来的性能损耗;

  • 补充replace(替换)、insert(插入string对象)、append(追加string对象)等接口;

  • 优化流提取运算符,支持读取带空格的字符串(如使用getline);

  • 实现resize(调整有效长度,可填充指定字符);

  • 优化扩容策略,避免扩容后内存浪费(如按需扩容至刚好满足需求)。

总结

string类的本质是「对动态字符数组的封装」,核心难点在于内存管理(扩容、深拷贝)和运算符重载的设计。通过手动模拟实现,不仅能深入理解标准库string的底层逻辑,还能提升C++面向对象编程、内存管理的能力。

本文代码已全部测试通过,可直接复制到VS、Clion等IDE中运行,建议大家手动敲一遍代码,重点理解深拷贝、运算符重载的实现细节,遇到问题可结合注释和测试示例排查。

如果觉得本文对你有帮助,欢迎点赞、收藏、关注,后续会分享更多C++核心知识点和面试干货!

相关推荐
xUxIAOrUIII2 小时前
【Go每日面试题】内存管理
java·开发语言·golang
勇闯逆流河2 小时前
【Linux】linux进程概念(fork,进程状态,僵尸进程,孤儿进程)
linux·运维·服务器·开发语言·c++
森屿山茶2 小时前
hot100题解 —— 146.LRU缓存
java·开发语言
gameboy0312 小时前
SpringbootActuator未授权访问漏洞
java
⑩-2 小时前
API 网关的作用?Spring Cloud Gateway 原理?
java·服务器·网络·spring cloud
大傻^2 小时前
LangChain4j 记忆架构:ChatMemory、持久化与跨会话状态
java·人工智能·windows·架构·langchain4j
vx-bot5556662 小时前
企业微信ipad协议的消息扩展字段与业务数据注入
java·企业微信·ipad
buyulian2 小时前
Bug防御体系:技术方案的优与劣
java·经验分享·bug·软件工程
superkcl20222 小时前
C++初始化 和 赋值
开发语言·c++·算法