string类
在C++编程中,string类是处理字符串的核心工具,但想要真正掌握它,模拟实现是最好的方式。下面这篇文章我将以自己模拟实现的完整代码,从底层原理到核心函数的实现,带你一步步掌握string类的使用,理解它的设计思想和使用细节。
string类的基础认知
string本质上是C++对动态字符数组的封装,它不仅封装了字符数组的储存(char *),还管理了其容量(capasity)和大小(size),并提供了丰富的接口(增删改查,比较等)。
- 自动管理内存,无需手动分配 / 释放(避免内存泄漏、越界);
- 支持动态扩容,长度可灵活调整;
- 重载了 +=、==、+等运算符,语法更直观;
- 提供大量成员函数,覆盖字符串增删改查、拼接、比较等场景。
为什么要学习string类?
很多初学者会疑惑:为什么不直接用 C 语言的str系列函数?核心原因有两点:
- c语言中str库函数中的函数,虽然提供对字符串的修改操作,但是无法检测越界访问,容易产生越界修改并不自知。
- 而C++string类重载了[ ],,越界访问时会直接终止程序并提示出现错误的位置,从根源上避免了越界访问的问题。
string类的使用
1. 头文件与命名空间
使用string前必须包含头文件,且由于string位于std命名空间,有两种使用方式:
cpp
// 方式1:引入头文件+使用std命名空间(新手推荐)
#include <iostream>
#include <string>
using namespace std;
// 方式2:显式指定std::(工程开发更规范)
#include <iostream>
#include <string>
// 使用时写 std::string
2.字符串的创建与初始化
string支持多种初始化方式
cpp
#include <iostream>
#include <string>
using namespace std;
int main() {
// 1. 空字符串
string s1;
// 2. 用常量字符串初始化
string s2 = "Hello C++";
// 3. 拷贝初始化
string s3 = s2;
// 4. 用指定字符重复n次初始化
string s4(5, 'a'); // s4 = "aaaaa"
// 5. 截取其他字符串的部分初始化(起始位置,长度)
string s5(s2, 6, 3); // 从s2的第6位(索引从0开始)取3个字符,s5 = "C++"
cout << "s1: " << s1 << endl; // 空
cout << "s2: " << s2 << endl; // Hello C++
cout << "s3: " << s3 << endl; // Hello C++
cout << "s4: " << s4 << endl; // aaaaa
cout << "s5: " << s5 << endl; // C++
return 0;
}
3.获取长度与判空
- size()/length():返回字符串长度(字符数,两者功能完全一致);
- empty():判断字符串是否为空,空返回true,非空返回false;
- capacity():返回字符串当前分配的内存容量(字节数,一般无需关注)。
cpp
string s = "Hello";
cout << "长度:" << s.size() << endl; // 输出5
cout << "是否为空:" << s.empty() << endl; // 输出0(false)
s.clear(); // 清空字符串
cout << "清空后是否为空:" << s.empty() << endl; // 输出1(true)
4.字符串拼接
支持+=运算符和append()成员函数,前者更简洁,后者支持更灵活的拼接(如截取部分字符串拼接)。
cpp
string s1 = "Hello";
string s2 = " World";
// 方式1:+= 运算符(推荐)
s1 += s2;
cout << s1 << endl; // 输出 Hello World
// 方式2:append() 函数
string s3 = "Hi";
s3.append(" C++", 0, 3); // 拼接" C++"的前3个字符(" C+")
cout << s3 << endl; // 输出 Hi C+
5.字符串比较
支持==、!=、<、>、<=、>=运算符,按ASCII 码值逐字符比较;也可使用compare()成员函数,返回值:
0 :两字符串相等;
正数 :当前字符串大于目标字符串;
负数:当前字符串小于目标字符串。
cpp
string s1 = "Apple";
string s2 = "Banana";
string s3 = "Apple";
cout << (s1 == s3) << endl; // 输出1(true)
cout << (s1 > s2) << endl; // 输出0(false,'A'的ASCII码小于'B')
// compare() 用法
int res = s1.compare(s2);
cout << res << endl; // 输出负数(s1 < s2)
6.字符访问
- 下标运算符[]:直接访问指定位置的字符(无越界检查,越界会导致未定义行为);
- at()成员函数:访问指定位置的字符(有越界检查,越界抛出out_of_range异常,更安全)。
cpp
string s = "Hello";
cout << s[1] << endl; // 输出e
cout << s.at(2) << endl; // 输出l
// s[10]; // 越界,程序崩溃(无提示)
// s.at(10); // 越界,抛出std::out_of_range异常(可捕获)
7.查找与替换
- find():从左到右查找指定字符 / 字符串,返回首次出现的索引; 未找到返回string::npos(一个特殊的无符号常量,表示 "不存在");
- rfind():从右到左查找,返回最后一次出现的索引;
- replace():替换字符串中指定范围的字符。
cpp
string s = "Hello World, Hello C++";
// 查找 "Hello" 首次出现的位置
size_t pos = s.find("Hello");
if (pos != string::npos) {
cout << "首次出现位置:" << pos << endl; // 输出0
}
// 从索引7开始查找 "Hello"
pos = s.find("Hello", 7);
if (pos != string::npos) {
cout << "第二次出现位置:" << pos << endl; // 输出13
}
// 替换:从索引0开始,5个字符替换为"Hi"
s.replace(0, 5, "Hi");
cout << s << endl; // 输出 Hi World, Hello C++
8.截取子串
substr(pos, len):从索引pos开始,截取len个字符;若len省略,则截取到字符串末尾。
cpp
string s = "Hello World";
string sub = s.substr(6, 5); // 从索引6开始截取5个字符
cout << sub << endl; // 输出 World
string sub2 = s.substr(0, 5); // 截取前5个字符
cout << sub2 << endl; // 输出 Hello
9.插入与删除
- insert(pos, str):在索引pos处插入字符串str;
- erase(pos,len):从索引pos处删除len个字符;若len省略,删除到末尾。
cpp
string s = "Hello";
// 插入
s.insert(5, " C++"); // 在索引5处插入" C++"
cout << s << endl; // 输出 Hello C++
// 删除
s.erase(5, 4); // 从索引5处删除4个字符(" C++")
cout << s << endl; // 输出 Hello
10.与 C 语言字符数组的转换
有时需要将string转换为 C 风格字符数组(如调用 C 语言接口),可使用:
- c_str():返回const char*(只读,不能修改);
- data():C++11 后与c_str()功能一致,返回const char*;
- copy():将string内容拷贝到字符数组中(可修改)。
cpp
string s = "Hello";
// 转换为const char*
const char* c_str = s.c_str();
cout << c_str << endl; // 输出 Hello
// 拷贝到字符数组(可修改)
char arr[10] = {0};
s.copy(arr, 5, 0); // 从s的索引0开始,拷贝5个字符到arr
cout << arr << endl; // 输出 Hello
11.容量管理
reserve(size_t n)
请求将字符串容量调整为计划大小,如果n大于当前字符串容量,该函数会使其容量增加至n(或更大),如果n小于当前字符串容量,字符串容量不会改变。仅扩容,不缩容
此函数对字符串长度没有任何影响,改变的是capacity,不会改变size。
cpp
#include <iostream>
#include <string>
using namespace std;
int main() {
string s = "Hello";
cout << "初始状态:" << endl;
cout << "size: " << s.size() << ", capacity: " << s.capacity() << endl;
// 输出:size: 5, capacity: 15
// 预留能容纳100个字符的空间
s.reserve(100);
cout << "\nreserve(100)后:" << endl;
cout << "size: " << s.size() << ", capacity: " << s.capacity() << endl;
// 输出:size: 5, capacity: 111(编译器可能预留略多,保证≥100)
// 预留更小的容量,无效果
s.reserve(50);
cout << "\nreserve(50)后:" << endl;
cout << "size: " << s.size() << ", capacity: " << s.capacity() << endl;
// 输出:size: 5, capacity: 111
return 0;
}
resize(size_t n , char c = '\0' )
将字符串的长度调整为n个字符的长度
如果n大于当前字符串长度,则扩容到n个字符,大于n的部分用c来初始化;
如果n小于当前字符串长度,则保留前n个字符,删除其余字符。
cpp
#include <iostream>
#include <string>
using namespace std;
int main() {
string s = "Hello";
cout << "初始状态:" << endl;
cout << "size: " << s.size() << ", capacity: " << s.capacity() << ", 内容:" << s << endl;
// 输出:size: 5, capacity: 15(不同编译器capacity值可能不同), 内容:Hello
// 情况1:n < 当前size → 截断
s.resize(3);
cout << "\nresize(3)后:" << endl;
cout << "size: " << s.size() << ", capacity: " << s.capacity() << ", 内容:" << s << endl;
// 输出:size: 3, capacity: 15, 内容:Hel
// 情况2:n > 当前size → 扩展,用指定字符填充
s.resize(6, '!');
cout << "\nresize(6, '!')后:" << endl;
cout << "size: " << s.size() << ", capacity: " << s.capacity() << ", 内容:" << s << endl;
// 输出:size: 6, capacity: 15, 内容:Hel!!!
return 0;
}
string类的完整模拟实现代码
1.头文件string.h
cpp
#include<iostream>
#include<assert.h>
using namespace std;
namespace Mystring
{
class string
{
public:
//迭代器定义
typedef char* iterator;
typedef const char* const_iterator;
//运算符重载
char& operator[](size_t index);
const char& operator[](size_t index)const;
//迭代器相关
iterator begin()
{
return _str;
}
iterator end()
{
return _str + _size;
}
const_iterator begin() const
{
return _str;
}
const_iterator end() const
{
return _str + _size;
}
//构造函数
string(const char* str = "")
{
_size = strlen(str);
_capacity = _size;
_str = new char [_capacity + 1];//+1储存'\0'
strcpy(_str, str);
}
//拷贝构造(现代写法:利用临时对象交换)
string(const string& s)
{
string tmp(s._str);//创建临时对象
swap(tmp);//交换临时对象和当前对象的资源
}
//赋值运算符重载(值传递+交换)
string& operator=(string tmp)
{
swap(tmp);
return *this;
}
//析构函数
~string()
{
if (_str)
{
delete[] _str;
_size = 0;
_capacity = 0;
_str = nullptr;//避免野指针
}
}
//基础工具函数
void clear()//清空字符串
{
_str[0] = '\0';
_size = 0;
}
void swap(string& s)//交换两个string对象的资源
{
std::swap(_str, s._str);
std::swap(_capacity, s._capacity);
std::swap(_size, s._size);
}
const char* str()const
{
return _str;
}
size_t size()const
{
return _size;
}
size_t capacity()const
{
return _capacity;
}
bool empty()const
{
return _size == 0;
}
//容量管理
void resize(size_t n, char c ='\0');// resize:调整字符串长度
void reserve(size_t n);//reserve:调整容量(仅扩容,不缩容)
//增删改查
void push_back(char c);//尾插一个字符
void append(const char* str);//追加一个字符串
// 返回c在string中第一次出现的位置
size_t find(char c, size_t pos=0) const;
// 返回子串s在string中第一次出现的位置
size_t find(const char* s, size_t pos=0) const;
// 在pos位置上插入字符c/字符串str,并返回该字符的位置
string& insert(size_t pos, char c);
string& insert(size_t pos, const char* str);
// 删除pos位置上的元素,并返回该元素的下一个位置
string& erase(size_t pos, size_t len = npos);
string& operator+=(char ch);
string& operator+=(const char* str);
string substr(size_t pos, size_t len=npos);
private:
size_t _size = 0;//有效字符长度(不包含'\0')
size_t _capacity=0;//容量大小(不包含'\0')
char* _str = nullptr;//储存字符串的动态数组
static const size_t npos;//静态常量,标识无效位置
};
//运算符重载
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)
cpp
#include "string.h"
namespace Mystring
{
// 静态常量初始化
const size_t string::npos = -1;
//resize:调整字符串长度
void string::resize(size_t n, char c )
{
if (n < _size)//如果n小于当前字符串长度,则保留前n个字符,删第n个字符之外的字符
{
_str[n] = '\0';
_size = n;
}
else//如果n大于当前字符串长度,则扩容到n个字符,大于_size的部分用字符c来初始化
{
char* tmp = new char[n + 1];
strcpy(tmp, _str);
_capacity = n;
delete[]_str;
_str = tmp;
for (size_t i = _size; i < n; i++)
{
_str[i] = c;
}
_size = n;
_str[n] = '\0';
}
}
//调整容量,仅扩容不缩容
void string::reserve(size_t n)
{
if (n > _capacity)
{
char* tmp = new char[n + 1];
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
// 查找c在string中第一次出现的位置
size_t string::find(char c, size_t pos) const
{
assert(pos < _size);
size_t i = pos;
while (i < _size)
{
if (_str[i] == c)
{
return i;
}
i++;
}
return npos;
}
// 查找子串s在string中第一次出现的位置
size_t string::find(const char* s, size_t pos) const
{
assert(pos < _size);
char* tmp = strstr(_str + pos, s);
if (tmp == nullptr)
{
return npos;
}
else
{
return tmp - _str;
}
}
//尾插字符c
void string::push_back(char c)
{
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : 2 * _capacity);
}
_str[_size] = c;
_size++;
_str[_size] = '\0';
}
//尾插字符串str
void string::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;
}
// 在pos位置上插入字符c/字符串str,并返回该字符的位置
string& string::insert(size_t pos, char c)
{
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] = c;
_size++;
return *this;
}
//指定位置插入字符串str
string& string::insert(size_t pos, const char* str)
{
assert(pos <= _size);
size_t len = strlen(str);
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 (int i = 0; i < len; i++)
{
_str[pos + i] = str[i];
}
_size += len;
return *this;
}
// 删除pos位置上的元素,并返回该元素的下一个位置
string& 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;
}
return *this;
}
//截取子串
string string::substr(size_t pos,size_t len)
{
assert(pos < _size);
if (len > _size - pos)
{
len = _size - pos;
}
string sub;
sub.reserve(len);
for (int i = 0; i < len; i++)
{
sub += _str[pos + i];
}
return sub;
}
//+=字符ch
string& string::operator+=(char ch)
{
push_back(ch);
return *this;
}
//+=字符串str
string& string::operator+=(const char* str)
{
append(str);
return *this;
}
//重载运算符[]
char& string::operator[](size_t index)
{
assert(index < _size);
return _str[index];
}
const char& string::operator[](size_t index)const
{
assert(index < _size);
return _str[index];
}
bool operator<(const string& s1, const string& s2)
{
return strcmp(s1.str(), s2.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 || s1 == s2;
}
bool operator==(const string& s1, const string& s2)
{
return strcmp(s1.str(), s2.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];
char ch;
int i = 0;
ch = in.get();
// 读取字符,直到空格/换行
while (ch != ' ' && ch != '\n')
{
buff[i++] = ch;
// 缓冲区满时追加到string
if (i == N - 1)
{
buff[i] = '\0';
s += buff;
i = 0;
}
ch = in.get();
}
// 追加剩余字符
if (i > 0)
{
buff[i] = '\0';
s += buff;
}
return in;
}
}
3.测试示例
cpp
#include "string.h"
using namespace Mystring;
int main()
{
// 构造函数测试
string s1("hello");
cout << "s1: " << s1 << endl; // 输出:hello
// 拷贝构造测试
string s2 = s1;
cout << "s2: " << s2 << endl; // 输出:hello
// reserve测试
s1.reserve(10);
cout << "s1 capacity: " << s1.capacity() << endl; // 输出:10
// resize测试
s1.resize(8, 'x');
cout << "s1: " << s1 << endl; // 输出:helloxxx
// 尾插测试
s1.push_back('!');
cout << "s1: " << s1 << endl; // 输出:helloxxx!
// 查找测试
size_t pos = s1.find('x');
cout << "x的位置:" << pos << endl; // 输出:5
// 插入测试
s1.insert(5, "yy");
cout << "s1: " << s1 << endl; // 输出:helloyyxxx!
// 删除测试
s1.erase(5, 2);
cout << "s1: " << s1 << endl; // 输出:helloxxx!
// 比较运算符测试
cout << (s1 == s2 ? "相等" : "不相等") << endl; // 输出:不相等
// 输入测试
string s3;
cin >> s3;
cout << "输入的s3:" << s3 << endl;
return 0;
}
掌握string类的使用,能大幅提升 C++ 字符串处理的效率和代码可读性,是 C++ 入门的必备技能。建议结合实际场景多练习,比如字符串分割、格式化、替换等,加深对string类的理解。