提起 C++ 中的 string,想必不少人都有过这样的感触:初学时惊叹于它功能的丰富与便捷,总忍不住感慨这份 “开箱即用” 的神奇;深入学习后才发现,这份看似简单的 “神奇” 背后,藏着动态内存管理、拷贝控制、运算符重载等层层精巧的底层设计。
std::string的使用 点击可以看到标准库中string的主要功能。
目录
第一步:开一个string.h的头文件
1. 代码结构分析
1.构造函数:
浅拷贝:
深拷贝:
核心思路:
优势:
延伸:拷贝赋值运算符也可以用这个写法
2.析构函数:
3.常用成员函数:
1.iterator(迭代器)
2.常调用的函数或简单的函数
补充:
4.较复杂的成员函数 string.cpp
1.实现,无注释版:
2.代码注释版
5.运算符重载实现字符串比较和输入输出功能
1.比较运算符重载
2.流运算符重载
关键实现细节说明
6.完整代码
string.h
string.cpp
接下来,我们来讲怎么实现string
第一步:开一个string.h的头文件

1. 代码结构分析
#pragma once
#include <iostream>
#include <assert.h>
using namespace std;
namespace bit
{
class string
{
private:
char* _str = nullptr; // 指向堆上的字符数组
size_t _size = 0; // 当前有效字符个数(不含'\\0')
size_t _capacity = 0; // 当前可存储的最大字符数(不含'\\0')
};
}
- _str:指向动态分配的字符数组,是字符串的实际存储位置。
- _size:记录当前字符串的有效长度,避免每次都调用 strlen 计算。
- _capacity:记录当前分配的内存容量,用于实现动态扩容,避免频繁分配内存。
- 使用 namespace bit ,可以避免与标准库 std::string 冲突。
基于这个框架,可以逐步实现以下核心功能:
构造函数:
- 默认构造:string()
- 从 C 字符串构造:string(const char* s)
- 拷贝构造:string(const string& s)
- 移动构造:string(string&& s)
析构函数:
- 释放 _str 指向的堆内存,避免内存泄漏。
运算符重载:
- 拷贝赋值:string& operator=(const string& s)
- 移动赋值:string& operator=(string&& s)
- 下标访问:char& operator[](size_t pos)
- 流输出:friend ostream& operator<<(ostream& os, const string& s)
常用成员函数:
- size() / length():返回 _size
- c_str():返回 _str
- push_back(char ch):在末尾添加字符
- append(const string& s):追加字符串
- resize(size_t n) / reserve(size_t n):调整大小和容量
1.构造函数:
浅拷贝:
/*string()
:_str(new char[1]{'\\0'})
,_size(0)
,_capacity(0)
{}*/
// 短小频繁调用的函数,可以直接定义到类里面,默认是inline
// 代替string().优化写法
string(const char* str = "") // " "中间默认是\\0
{
_size = strlen(str);
// _capacity不包含\\0
_capacity = _size;
_str = new char[_capacity + 1]; // 需要额外开一个空间存放"\\0"
strcpy(_str, str);
}
深拷贝:
// 深拷贝问题
// s2(s1)
/*string(const string& s)
{
_str = new char[s._capacity + 1];
strcpy(_str, s._str);
_size = s._size;
_capacity = s._capacity;
}*/
//优化写法
// 辅助函数:交换两个对象的所有资源
void swap(string& s)
{
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
// 拷贝构造函数(现代写法)
string(const string& s)
{
string tmp(s._str); // 1. 先构造一个临时对象tmp,它是s的副本
swap(tmp); // 2. 把当前对象(this)和tmp交换
}
核心思路:
优势:
- 代码极简:复用了已有构造函数,避免了重复的内存分配和拷贝逻辑。
- 异常安全:如果构造 tmp 时抛出异常(如内存不足),当前对象 this 保持初始的空状态,不会处于半构造的无效状态。
延伸:拷贝赋值运算符也可以用这个写法
// s1 = s3;
//string& operator=(const string& s)
//{
//if (this != &s)
//{
////string tmp(s._str);
//string tmp(s);
//swap(tmp);
//}
//return *this;
//}
// s1 = s3;
string& operator=(string tmp)// 注意:参数是值传递,会自动调用拷贝构造
{
swap(tmp);// 直接和临时对象交换
return *this;
}
- 参数 s 是一个值传递的副本,函数结束后会自动析构,释放掉 this 原来的旧资源。
- 这是 C++ 中实现强异常安全拷贝赋值的经典方案。
2.析构函数:
~string()
{
if (_str)
{
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
}
释放 _str 指向的堆内存,避免内存泄漏。
3.常用成员函数:
1.iterator(迭代器)
public:
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;
}
2.常调用的函数或简单的函数
//注意:以下都是成员函数,在class string {} 内部
//若string s1,以下是用法 s1.成员函数();
//s1.c_str;
const char* c_str() const
{
return _str;
}
//s1.clear();
void clear()
{
_str[0] = '\\0';
_size = 0;
}
//s1.size();
size_t size() const
{
return _size;
}
size_t capacity() const
{
return _capacity;
}
char& operator[](size_t pos)
{
assert(pos < _size);
return _str[pos];
}
const char& operator[](size_t pos) const
{
assert(pos < _size);
return _str[pos];
}
对于常调用的函数或简单的函数,可以直接在头文件中定义
短小频繁调用的函数,可以直接定义到类里面,默认是inline
补充:
内联函数与头文件定义
在头文件中直接定义简单函数时,通常会使用inline关键字来避免多重定义错误。内联函数建议满足以下条件:
- 函数体简短(通常不超过10行)
- 调用频率高
- 不含递归或复杂控制流
4.较复杂的成员函数 string.cpp
只声明不实现:复杂函数的实现全部放在 .cpp 文件中,头文件只保留声明,减少编译依赖;
//string.h
void reserve(size_t n);
void push_back(char ch);
void append(const char* str);
string& operator+=(char ch);
string& operator+=(const char* str);
void insert(size_t pos, char ch);
void insert(size_t pos, const char* str);
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);
string substr(size_t pos = 0, size_t len = npos);
1.实现,无注释版:
这里要注意:在string.h中有默认参数,在string.cpp中不要再写。如下文的 find.
#include"string.h"
namespace bit
{
const size_t string::npos = -1;
void string::reserve(size_t n)
{
if (n > _capacity)
{
//cout << "reserve:" << n << endl;
char* tmp = new char[n + 1];
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
void string::push_back(char ch)
{
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
_str[_size] = ch;
++_size;
_str[_size] = '\\0';
}
string& string::operator+=(char ch)
{
push_back(ch);
return *this;
}
void string::append(const char* str)
{
size_t len = strlen(str);
if (_size + len > _capacity)
{
// 大于2倍,需要多少开多少,小于2倍按2倍扩
reserve(_size + len > 2 * _capacity ? _size + len : 2 * _capacity);
}
strcpy(_str + _size, str);
_size += len;
}
string& string::operator+=(const char* str)
{
append(str);
return *this;
}
void string::insert(size_t pos, char ch)
{
assert(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;
}
void string::insert(size_t pos, const char* s)
{
assert(pos <= _size);
size_t len = strlen(s);
if (_size + len > _capacity)
{
// 大于2倍,需要多少开多少,小于2倍按2倍扩
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] = s[i];
}
_size += len;
}
void string::erase(size_t pos, size_t len)
{
assert(pos < _size);
if (len >= _size – pos)
{
_str[pos] = '\\0';
_size = pos;
}
else
{
for (size_t i = pos + len; i <= _size; i++)
{
_str[i – len] = _str[i];
}
_size -= len;
}
}
size_t string::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 string::find(const char* str, size_t pos)
{
assert(pos < _size);
const char* ptr = strstr(_str + pos, str);
if (ptr == nullptr)
{
return npos;
}
else
{
return ptr – _str;
}
}
string string::substr(size_t pos, size_t len)
{
assert(pos < _size);
// len大于剩余字符长度,更新一下len
if (len > _size – pos)
{
len = _size – pos;
}
string sub;
sub.reserve(len);
for (size_t i = 0; i < len; i++)
{
sub += _str[pos + i];
}
return sub;
}
2.代码注释版
#include"string.h"
namespace bit
{
// 定义静态成员npos,表示无效位置或未找到
const size_t string::npos = -1;
// 调整字符串容量至至少n个字符(不包括结尾的'\\0')
void string::reserve(size_t n)
{
if (n > _capacity)
{
// 分配新内存(多1字节用于存储'\\0')
char* tmp = new char[n + 1];
// 复制原内容到新内存
strcpy(tmp, _str);
// 释放旧内存
delete[] _str;
_str = tmp;
_capacity = n;
}
}
// 在字符串末尾追加字符
void string::push_back(char ch)
{
// 容量不足时扩容(初始为0则扩容至4)
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
// 写入字符并更新大小和结尾'\\0'
_str[_size] = ch;
++_size;
_str[_size] = '\\0';
}
// 重载+=运算符(字符版本)
string& string::operator+=(char ch)
{
push_back(ch);
return *this;
}
// 追加C风格字符串
void string::append(const char* str)
{
size_t len = strlen(str);
// 容量不足时扩容(按需或2倍扩容策略)
if (_size + len > _capacity)
{
reserve(_size + len > 2 * _capacity ? _size + len : 2 * _capacity);
}
// 追加内容并更新大小
strcpy(_str + _size, str);
_size += len;
}
// 重载+=运算符(字符串版本)
string& string::operator+=(const char* str)
{
append(str);
return *this;
}
// 在指定位置插入字符
void string::insert(size_t pos, char ch)
{
assert(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;
}
// 在指定位置插入C风格字符串
void string::insert(size_t pos, const char* s)
{
assert(pos <= _size);
size_t len = strlen(s);
// 容量不足时扩容
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] = s[i];
}
_size += len;
}
// 删除从pos开始的len个字符
void string::erase(size_t pos, size_t len)
{
assert(pos < _size);
// 若删除范围超过字符串尾部,则截断到pos位置
if (len >= _size – pos)
{
_str[pos] = '\\0';
_size = pos;
}
else
{
// 向前移动剩余字符覆盖被删除部分
for (size_t i = pos + len; i <= _size; i++)
{
_str[i – len] = _str[i];
}
_size -= len;
}
}
// 从pos位置开始查找字符ch
size_t string::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;
}
// 从pos位置开始查找子串str
size_t string::find(const char* str, size_t pos)
{
assert(pos < _size);
// 使用strstr函数查找子串
const char* ptr = strstr(_str + pos, str);
if (ptr == nullptr)
{
return npos;
}
else
{
return ptr – _str;
}
}
// 提取从pos开始长度为len的子串
string string::substr(size_t pos, size_t len)
{
assert(pos < _size);
// 调整len不超过剩余字符数
if (len > _size – pos)
{
len = _size – pos;
}
// 创建子串对象并预分配空间
string sub;
sub.reserve(len);
// 逐个字符复制
for (size_t i = 0; i < len; i++)
{
sub += _str[pos + i];
}
return sub;
}
}
5.运算符重载实现字符串比较和输入输出功能
#pragma once
#include<iostream>
#include<assert.h>
using namespace std;
namespace bit
{
class string
{
//……
};
bool operator<(const string& s1, const string& s2);
bool operator<=(const string& s1, const string& s2);
bool operator>(const string& s1, const string& s2);
bool operator>=(const string& s1, const string& s2);
bool operator==(const string& s1, const string& s2);
bool operator!=(const string& s1, const string& s2);
ostream& operator<<(ostream& out, const string& s);
istream& operator>>(istream& in, string& s);
}
1.比较运算符重载
bool operator<(const string& s1, const string& s2)
{
// 使用strcmp比较两个字符串的C风格字符串形式
// 返回s1是否按字典序小于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)
{
// 相等运算使用strcmp比较C风格字符串
return strcmp(s1.c_str(), s2.c_str()) == 0;
}
bool operator!=(const string& s1, const string& s2)
{
// 不等运算通过否定==运算实现
return !(s1 == s2);
}
2.流运算符重载
ostream& operator<<(ostream& out, const string& s)
{
// 遍历字符串的每个字符并输出到输出流
for (auto ch : s)
{
out << ch;
}
return out; // 返回输出流以支持链式调用
}
istream& operator>>(istream& in, string& s)
{
s.clear(); // 清空目标字符串
const int N = 256; // 缓冲区大小
char buff[N]; // 字符缓冲区
int i = 0; // 缓冲区索引
char ch;
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; // 返回输入流以支持链式调用
}
关键实现细节说明
比较运算符实现特点 所有比较运算符都基于strcmp函数实现,确保与C风格字符串比较行为一致。通过运算符间的逻辑组合减少重复代码,如>和>=分别通过否定<=和<实现。
流运算符实现特点 输出运算符直接遍历字符串字符,简单高效。输入运算符采用缓冲技术处理长字符串,避免频繁内存分配。使用get()而非>>读取字符,确保能捕获空格字符作为输入终止符。
缓冲区管理 输入运算符使用固定大小缓冲区(256字节)分批处理输入,平衡内存使用和性能。当缓冲区接近满时及时清空,防止溢出同时减少字符串拼接操作。
6.完整代码
string.h
#pragma once
#include<iostream>
#include<assert.h>
using namespace std;
namespace bit
{
class string
{
public:
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;
}
/*string()
:_str(new char[1]{'\\0'})
,_size(0)
,_capacity(0)
{}*/
// 短小频繁调用的函数,可以直接定义到类里面,默认是inline
//代替string()
string(const char* str = "")
{
_size = strlen(str);
// _capacity不包含\\0
_capacity = _size;
_str = new char[_capacity + 1];
strcpy(_str, str);
}
// 深拷贝问题
// s2(s1)
/*string(const string& s)
{
_str = new char[s._capacity + 1];
strcpy(_str, s._str);
_size = s._size;
_capacity = s._capacity;
}*/
void swap(string& s)
{
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
// s2(s1)
// 现代写法
string(const string& s)
{
string tmp(s._str);
swap(tmp);
}
// s2 = s1
// s1 = s1
/*string& operator=(const string& s)
{
if (this != &s)
{
delete[] _str;
_str = new char[s._capacity + 1];
strcpy(_str, s._str);
_size = s._size;
_capacity = s._capacity;
}
return *this;
}*/
// s1 = s3;
//string& operator=(const string& s)
//{
//if (this != &s)
//{
////string tmp(s._str);
//string tmp(s);
//swap(tmp);
//}
//return *this;
//}
// s1 = s3;
string& operator=(string tmp)
{
swap(tmp);
return *this;
}
~string()
{
if (_str)
{
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
}
const char* c_str() const
{
return _str;
}
void clear()
{
_str[0] = '\\0';
_size = 0;
}
size_t size() const
{
return _size;
}
size_t capacity() const
{
return _capacity;
}
char& operator[](size_t pos)
{
assert(pos < _size);
return _str[pos];
}
const char& operator[](size_t pos) const
{
assert(pos < _size);
return _str[pos];
}
/*void copy_on_write()
{
if (count > 1)
{
深拷贝
}
}*/
void reserve(size_t n);
void push_back(char ch);
void append(const char* str);
string& operator+=(char ch);
string& operator+=(const char* str);
void insert(size_t pos, char ch);
void insert(size_t pos, const char* str);
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);
string substr(size_t pos = 0, size_t len = npos);
private:
//char _buff[16];
char* _str = nullptr;
size_t _size = 0;
size_t _capacity = 0;
//static const size_t npos = -1;
static const size_t npos;
/*static const int N = 10;
int buff[N];*/
};
bool operator<(const string& s1, const string& s2);
bool operator<=(const string& s1, const string& s2);
bool operator>(const string& s1, const string& s2);
bool operator>=(const string& s1, const string& s2);
bool operator==(const string& s1, const string& s2);
bool operator!=(const string& s1, const string& s2);
ostream& operator<<(ostream& out, const string& s);
istream& operator>>(istream& in, string& s);
}
string.cpp
#include"string.h"
namespace bit
{
const size_t string::npos = -1;
void string::reserve(size_t n)
{
if (n > _capacity)
{
//cout << "reserve:" << n << endl;
char* tmp = new char[n + 1];
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
void string::push_back(char ch)
{
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
_str[_size] = ch;
++_size;
_str[_size] = '\\0';
}
string& string::operator+=(char ch)
{
push_back(ch);
return *this;
}
void string::append(const char* str)
{
size_t len = strlen(str);
if (_size + len > _capacity)
{
// 大于2倍,需要多少开多少,小于2倍按2倍扩
reserve(_size + len > 2 * _capacity ? _size + len : 2 * _capacity);
}
strcpy(_str + _size, str);
_size += len;
}
string& string::operator+=(const char* str)
{
append(str);
return *this;
}
void string::insert(size_t pos, char ch)
{
assert(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;
}
void string::insert(size_t pos, const char* s)
{
assert(pos <= _size);
size_t len = strlen(s);
if (_size + len > _capacity)
{
// 大于2倍,需要多少开多少,小于2倍按2倍扩
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] = s[i];
}
_size += len;
}
void string::erase(size_t pos, size_t len)
{
assert(pos < _size);
if (len >= _size – pos)
{
_str[pos] = '\\0';
_size = pos;
}
else
{
for (size_t i = pos + len; i <= _size; i++)
{
_str[i – len] = _str[i];
}
_size -= len;
}
}
size_t string::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 string::find(const char* str, size_t pos)
{
assert(pos < _size);
const char* ptr = strstr(_str + pos, str);
if (ptr == nullptr)
{
return npos;
}
else
{
return ptr – _str;
}
}
string string::substr(size_t pos, size_t len)
{
assert(pos < _size);
// len大于剩余字符长度,更新一下len
if (len > _size – pos)
{
len = _size – pos;
}
string sub;
sub.reserve(len);
for (size_t i = 0; i < len; i++)
{
sub += _str[pos + i];
}
return sub;
}
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 strcmp(s1.c_str(), s2.c_str()) == 0;
}
bool operator!=(const string& s1, const string& s2)
{
return !(s1 == s2);
}
ostream& operator<<(ostream& out, const string& s)
{
for (auto ch : s)
{
out << ch;
}
return out;
}
istream& operator>>(istream& in, string& s)
{
s.clear();
const int N = 256;
char buff[N];
int i = 0;
char ch;
//in >> ch;
ch = in.get();
while (ch != ' ' && ch != '\\n')
{
buff[i++] = ch;
if (i == N – 1)
{
buff[i] = '\\0';
s += buff;
i = 0;
}
//in >> ch;
ch = in.get();
}
if (i > 0)
{
buff[i] = '\\0';
s += buff;
}
return in;
}
}
网硕互联帮助中心



评论前必须登录!
注册