C++ string类的操作
1. 为什么需要string类?
C字符串的缺陷:
-
手动管理内存:易引发越界访问、内存泄漏
-
不符合OOP思想:数据与操作分离
string类的优势:
-
封装字符序列,自动管理内存
-
提供丰富的成员方法(如
append
、find
) -
无缝集成STL算法(如
sort
、reverse
)
2. string类接口
2.1 构造与初始化
构造函数 | 作用 | 示例 |
---|---|---|
string() | 创建空字符串 | string s1; |
string(const char* s) | C字符串初始化 | string s2("hello"); |
string(const string& str) | 拷贝构造 | string s3(s2); |
string(size_t n, char c) | 填充n个字符c | string s4(5, 'x'); |
string(const char* s, size_t n) | 取C字符串前n个字符 | string s5("world", 3); |
string(const string& str, size_t pos, size_t len) | 从str的pos位置取len个字符 | string s6(s2, 1, 3); |
#include<iostream>
#include<string>
#include<list>
#include<algorithm>using namespace std;void test_string()
{//常用string s1;//string()string s2("hello world");//string(const char* s)string s3(s2);//string(const string& str),Constructs a copy of str.//不常用,了解string s4(s2, 3, 5);//string(const string& str, size_t pos, size_t len = npos)string s5(s2, 3);string s6(s2, 3, 30);string s7("hello world", 5);//string(const char* s, size_t n)string s8(10, 'x');//string(size_t n, char c)cout << s1 << endl;cout << s2 << endl;cout << s3 << endl;cout << s4 << endl;cout << s5 << endl;cout << s6 << endl;cout << s7 << endl;cout << s8 << endl;}
常用的构造
- string s1;//string(),构建一个长度为0的空字符串
- string s2("hello world");//string(const char* s),//复制指针"hello world"(s)指向地址的字符串,初始化s2
- string s3(s2);//string(const string& str),Constructs a copy of str.使用字符串s2(str)构造s3
不常用的构造
- string s4(s2, 3, 5);//string(const string& str, size_t pos, size_t len = npos).从s2(str)的下标为3的位置处(size_t pos)拷贝5个字符长度(size_t len = npos)构造s4(string)
- string s5(s2, 3);//string(const string& str, size_t pos, size_t len = npos).从s2(str)的下标为3的位置处(size_t pos),拷贝到s2结束
- string s6(s2, 3, 30);//string(const string& str, size_t pos, size_t len = npos).从s2(str)的下标为3的位置处(size_t pos),拷贝30个字符长度,在这里30个字符已经超出数组长度,只会拷贝到s2结束
- string s7("hello world", 5);//string(const char* s, size_t n),从"hello world"(s)指针处开始拷贝5个字符(size_t n)
- string s8(10, 'x');//string(size_t n, char c),拷贝10个"x"初始化s8
构造时可能涉及到隐式类型转换
//隐式类型转换
string s9 = "hello world";
const string& s10 = "hello world";
- 在string s9 = "hello world";中,"hello world";被构造成一个临时的string对象,再用这个临时的string对象拷贝构造s9,但编译器会优化为“hello world”直接构造s9。
- 在const string& s10 = "hello world";中,"hello world";构造的临时对象具有常性,不能被修改,所以引用时需要使用const。
2.2 容量操作
方法 | 作用 |
---|---|
s.size() | 返回有效字符长度(同length() ) |
s.capacity() | 返回总空间大小 |
s.reserve(size_t n) | 分配字符串长度最大为n |
s.resize(size_t n, char c) | 调整有效字符数为n,多出的空字符用"c"代替,超出n的删除 |
2.3 访问与遍历
方式 | 语法 | 特点 |
---|---|---|
下标[] | s[i] | 越界触发assert |
正向迭代器 | for(auto it=s.begin(); it!=s.end(); ++it) | 兼容STL算法 |
反向迭代器 | for(auto rit=s.rbegin(); rit!=s.rend(); ++rit) | 逆序访问 |
范围for | for(auto& ch : s) | 操作方便 |
2.3.1访问
class string
{
public:char& operator[](size_t i){assert(i < _size);//越界检查return _str[i];}
private:char* _str;size_t _size;size_t _capacity;
};
- char& operator[] (size_t pos);//可修改访问位置上的元素
- const char& operator[] (size_t pos) const;//不可修改访问位置上的元素
void test_string3()
{string s1("hello world");s1[0] = 'x';// assert(i < _size)会强制检查是否越界//s1[20];const string s2("hello world");// 不能修改//s2[0] = 'x';
}
- 在s1[0] = 'x';中,s1没有用const修饰,可以修改s1的内容,这时调用char& operator[] (size_t pos);使用[]访问元素,并支持修改。
- 在s2[0] = 'x';中,s2被const修饰,不可以修改s2的内容,这时调用const char& operator[] (size_t pos) const;,使用[]访问元素,不支持修改。
2.3.2 遍历
//const string s1("hello world");使用const修饰的字符串,支持遍历不支持修改
string s1("hello world");
方式1:下标 + []
//正序遍历
for (size_t i = 0; i < s1.size(); i++)
{s1[i] = s1[i] + 1;cout << s1[i] << " ";//类似遍历数组
}//逆序遍历
for (size_t i = s1.size() - 1; i >= 0; i--)
{cout << s1[i] << " ";//类似遍历数组if(i == 0){break;}
}
遍历方式与数组遍历相似。
使用下标+[ ]遍历、修改元素:其中s1[i] = s1[i] + 1; ,这里将s1的每个元素向后移动一位。
方式2:正迭代器string::iterator和逆迭代器reverse_iterator
string::iterator it1 = s1.begin();
(*(it1 + 1)) = 'o';//使用迭代器修改元素
while(it1 != s1.end())//将it1类似等价于指针来用
{cout << *it1 << " ";it1++;
}//迭代器逆序
string::reverse_iterator it2 = s1.rbegin();
while (it2 != s1.rend())
{cout << *it2 << " ";++it2;
}
迭代器遍历类似指针遍历
- begin:任何容器返回第一个数据位置的iterator。string::iterator it1 = s1.begin()返回开始位置,类似函数调用返回第一个位置的指针,it1指向s1的h位置=》*(it1)==h,it1++会使it1向尾端移动。
- end:任何容器返回最后数据的下一个位置iterator。s1.end()返回结束位置下一个位置的迭代器,=》*(end())==\0。
- rbegin:任何容器返回最后一个数据位置的iterator。string::reverse_iterator it2 = s1.rbegin();返回最后一个数据位置(d的位置)存到it2中,要说明的是it2++会使it2朝着字符串的首字符移动。
- rend:任何容器返回第一个数据位置的iterator。s1.rend()返回首个元素的位置('h')。
使用迭代器修改元素:(*(it1 + 1)) = 'o';将下标为1的元素修改为'o'
方式3:for+auto
for (auto& e : s1)
{e = 'o';cout << e << " ";
}
遍历方式:自动取s1中的值给e,自动往后++,自动判断结束。
e='o',将s1给e的值修改为'o';
2.4 插入与删除
操作 | 方法 | 关键特性 |
---|---|---|
尾部追加 | push_back(char c) | 单字符高效 |
append(...)/operator+= | 支持字符串/子串/填充 | |
任意位置 | insert(size_t pos, ...) | 可能触发扩容 |
replace(size_t pos, len, ...) | 可覆盖原字符(len>0时) | |
删除 | erase(size_t pos, len) | 删除[pos, pos+len)区间 |
erase(iterator pos) | 删除迭代器位置字符 |
2.4.1 插入
string s1("hello world");
方式1:s1.push_back(char c);将字符c插入到字符串s1的末尾,string::push_back - C++ Reference
s1.push_back('x');
方式2:append,尾插,string::append - C++ Reference
string s2("gogogo");
s1.append(s2);//string& append(const string& str),将string对象s1(str)拷贝到s1尾部
s1.append(s2,1,3);//string& append(const string& str,size_t subpos, size_t sublen),将string对象(str)的第1个位置(subpos)开始拷贝3个字符长度(sublen)到s1的尾部
s1.append(" yyyyyy!!");//string& append(const char* s),s指向以'\0'终止点字符串,将指针s指向字符串拷贝到s1尾部
s1.append(" yyyyyy!!", 3);//string& append(const char* s, size_t n),将指针s指向的字符串前n个字符拷贝到s1尾部
s1.append(3, 'c');//string& append(size_t n, char c),连续追加3个(n个)字符c(char c)到s1的尾部
方式3:+=,尾插,string::operator+= - C++ Reference
string s2("111111");
s1 += s2;//string& operator += (const string& str),将string对象s2拷贝到s1的尾部
s1 += 'y';//string& operator += (char c),将一个字符'y'(char c)追加到s1尾部
s1 += "zzzzzzzz";//string& operator += (const char* s),s指向以'\0'终止点字符串,将s复制到s1的尾部
方式4:+,返回一个新构造的字符串对象。operator+ (string) - C++ Reference
string s1 = "hello";
string s2 = "hello11";string ret1 = s1 + s2;//string operator+ (const string& lhs, const string& rhs),在字符s1(lhs)后面串联s2(rhs),构造一个新的字符串ret1.
方式5:insert,任意位置插入,string::insert - C++ Reference
string s2("hello world");
string s1(" happy happy");
s2.insert(s2.size(), s1);//string& insert(size_t pos, const string& str),在s2的s2.size()位置处(pos)插入string对象s1(str)
s2.insert(s2.size(), s1, 3, 2);//string& insert(size_t pos, const string& str, size_t subpos, size_t sublen),string对象s1(str)从下标3(subpos)开始持续2字符长度(sublen),插入到s2的s2.size()位置处(pos),s2.insert(s2.end(), s1.begin(), s1.end());//void insert(iterator p, InputIterator first, InputIterator last),将[first, last)即(s1.begin(), s1.end())范围的字符序列拷贝到s2.end()位置处(p)char ch = 'y';
s2.insert(0, 2, ch);//string& insert(size_t pos, size_t n, char c),将2个(size_t n)ch字符(char c)插入到s2的0位置处(size_t pos)
方式6:replace,任意位置插入(可以覆盖原字符串上的字符,若选择覆盖0字符,与插入效果类似),string::replace - C++ Reference
string s2("hello world");
s2.replace(1, 0, "%20");//string& replace (size_t pos, size_t len, const char* s),"%20"(s)指向的字符串以'\0'解尾,在下标尾1的位置处(pos),插入"%20"(char* s),覆盖s2中的0个字符(len)
s2.replace(1, 0, "happy",2);//string& replace (size_t pos, size_t len, const char* s, size_t n),"happy"(s)指向的字符串以'\0'解尾,在下标尾1的位置处(pos),插入"happy"(char* s)的前2个字符(n),覆盖s2中的0个字符(len)string s1("happy happy");
s2.replace(0, 0, s1);//string& replace (size_t pos, size_t len, const string& str),在s2的0下标(pos)处,插入string对象s1(str),并覆盖s2中的0个字符(len)s2.replace(0, 0, s1, 0, 5);//string& replace (size_t pos, size_t len, const string& str,size_t subpos, size_t sublen),,将string对象s1(str)从下标0(subpos)开始连续5个元素长度(sublen),拷贝到s2的0下标(pos)处,并覆盖s2中的0个字符(len)。s2.replace(0, 0, 3, 'o');//string& replace(size_t pos, size_t len, size_t n, char c),s2的下标0位置处(pos)覆盖0个字符长度(len),插入3个(n)'o'字符(char c)
2.4.2 删除
方式1: erase删除,string::erase - C++ Reference
string s2("hello world");
s2.erase(0, 2);//string& erase (size_t pos = 0, size_t len = nops),从0位置处(pos)连续删除两个字符(npos)s2.erase(s2.begin()+1);//iterator erase (iteror p),在s2.begin()+1位置处(p),删除一个元素s2.erase(s2.begin()+1, s2.end()-1);//iterator erase (iterator first, iterator last),删除[s2.begin()+1, s2.end()-1)之间的元素,左闭右开
方式2:replace删除
使用replace删除的语法与插入的类似,只不过将插入的字符替换为空,eg:
s2.replace(1, 1, "");//string& replace(size_t pos, size_t len, const char* s),在""为空,使用空,从下标1位置处(pos)开始覆盖3个字符(len)长度。
2.5 改变容量
方法 | 行为 | 示例 |
---|---|---|
reserve(n) | 扩容:当n > capacity() 时重新分配 | s.reserve(100); |
不缩容:n < capacity() 时保留空间 | s.reserve(10); (VS特性) | |
resize(n, c) | n < size() :截断多余字符 | s.resize(5); |
n > size() :填充c 至长度n | s.resize(10, '!'); |
方式1:reverse,预设字符串长度,提前开好空间。改变capacity,不会改变字符串的实际size
string s1("111111111");
cout << "s1的capacity:" << s1.capacity() << endl;
s1.reserve(100);
cout << "s1的capacity:" << s1.capacity() << endl;s1.reserve(20);//vs编译器下默认不缩容
cout << s1.capacity() << endl;
方式2:resize,调整字符串长度。
s2.resize(10,'1');//void resize(size_t n, char c),在10个空间中(n)用字符'1'填充(char c)
s2.resize(20);//void resize(size_t n),开辟20个空间
在resize(size_t n)和resize(size_t n, char c)中,如果n小于当前字符串长度,则删除n之后的所有字符。如果n大于当前字符串长度,在末尾自动插入字符填充长度至n,如果指定了char c,则填充c。
2.6 查找、提取元素
方法 | 作用 | 典型场景 |
---|---|---|
find(char c) | 返回字符首次出现位置 | url.find(':') |
rfind(char c) | 返回字符末次出现位置 | file.rfind('.') (取后缀) |
substr(pos, len) | 提取子串[pos, pos+len) | s.substr(0, 5) |
string url("https://legacy.cplusplus.com/reference/string/string/find/");
string file("string.cpp.zip");
2.6.1 查找
方式1:find,查找第一次出现匹配项的位置。string::find - C++ Reference
size_t pos1 = url.find(':');//size_t find(char c, size_t pos = 0) const;从下标pos开始,查找并返回第一次出现':'的位置(char c)
方式2:rfind,查找最后一次出现匹配项的位置。string::rfind - C++ Reference
size_t pos = file.rfind('.');//size_t rfind(char c, size_t pos = npos) const.从npos位置(末端查找)查找第一次出现字符'.'(char c)的位置。
2.6.2 提取
方式:substr,提取某段区间的元素.string::substr - C++ Reference
//string substr (size_t pos = 0, size_t len = npos) const
string url1 = url.substr(0, pos1 - 0);//提取url中下标[0,pos1-0]之间的元素
string suffix = file.substr(pos);//提取file中下标[0,pos]之间的元素
3. 总结
string类通过封装复杂性与暴露确定性,在C++中实现了安全性与效率的平衡。
设计目标 | 实现机制 | 价值 |
---|---|---|
内存自治 | RAII自动管理 + 动态扩容 | 彻底避免内存泄漏/越界 |
操作安全 | 边界检查 + 异常安全 | 稳定可靠的字符串操作 |
接口统一 | 迭代器兼容STL + 运算符重载(+ , [] 等) | 无缝结合标准库算法 |
性能优化 | reserve() 预分配减少碎片 | 高效处理动态增长数据 |