C++ --- string
简介
在C++中,std::string
是标准模板库(STL)提供的字符串类,用于表示和操作字符序列。它是C风格字符串(char*
或 char[]
)的现代化替代品,具有更安全、更便捷的特性。
不过string类面世比STL早,当中很多定义的功能对比真正的STL中提供的其他类来说即繁杂又重复,不过其思想是符合STL的。
STL的六大组件:
学前必知:
通过string类生成的对象的成员变量有三个,一个是指向底层数组的指针,一个siz字符串的大小,一个capacity字符串的容量。
数组内存储的字符串和C语言是一样的,末尾都会有一个\0的存在。
1、构造函数
(1)默认构造
//默认的:(1) string();
string s1; //创建一个空字符s1,后续可以手动初始化
s1 = "abcdefg";
string s2 = "abcdefg"; //上述两步合并
之所以能写成赋值的这种形式,是因为string类重载了赋值符号即operator = ,所以才会出现赋值形式的构造。
(2)拷贝构造
//拷贝:(2) string(const string & str);
string s4("abc");
string s5(s4);
//string s5 = s4; //此写法也可
(3)部分拷贝 — 截取字符串
//截取字符串:(3) string(const string & str, size_t pos, size_t len = npos);
//pos --- 截取的起始位置
//npos --- 截取的长度
string s6(s1, 1, 5); //输出bcdef
//string s6(s1, -1, 5); //程序直接崩溃
string s7(s1, 1, 10); //输出bcdefg
监视窗口观察:
1)当截取的起始位置不是一个正确的值时,此时运行程序,程序会直接崩溃。
2)当截取的长度大于字符串本身的长度时,默认输出从截取位置之后的整个字符串,并不会报错。
(4)拷贝来自C语言方式的字符串(字符数组)
//拷贝来自C语言方式的字符串:(4) string(const char* s);
char ch2[5] = "abc";
string s8(ch2); //这里ch2就已经是地址了
cout << s8 << endl;
string s3("abcdefg"); //这样才是常写的形式
(5)部分拷贝来自C语言方式的字符串(字符数组)
//部分拷贝来自C语言方式的字符串:(5) string (const char* s, size_t n);
char ch3[10] = "abcdefgh";
string s9(ch3,5);
cout << s9 << endl;
监视窗口观察:
(6)使用n个c字符填充整个字符串
//使字符串充满n个C字符:(6) string (size_t n, char c);
string s10(10, 'n');
监视窗口观察:
上述构造方法中(1),(2),(4)用得较多,其他几个用得少。
2、迭代器(主流的遍历方式)
迭代器是STL中一个新的概念,虽说概念较新,但是其功能就是遍历对象中的元素。
STL中的容器都有自己的迭代器,但是基础使用上都差不多,所以知识可迁移性很强。
这里只介绍它的使用,不介绍其底层机理。
迭代器是嵌套在string类中的类类型,使用方式就是容器名称 : : iterator 迭代器名称 = 对象.begin()或者对象.end(),string类中就是string : : iterator 迭代器名称 = 对象.begin()或者对象.end(),迭代器名称可以随便取,但是通常方便记忆就取了it,iterator的前两个字母。
想要获取迭代器指向的内容,语法就和指针相似,解引用迭代器名称即可。
上面说到迭代器的功能是遍历对象中的元素,回到string类这里,我们之前就已经学习过一种遍历字符串的方式,一个for循环使用[ ]加下标来遍历。
2.1字符串经典遍历和修改的方式
示例代码如下:
//string类的经典遍历方式:[ ]加下标
string s2("hello world");
for (int i = 0; i < s2.size(); i++)
{
cout << ++s2[i] << " ";
}
cout<<endl;
运行结果:
为什么s2对象能使用数组的形式使用[ ]来确定元素?
是因为string类中将[ ]给重载了:
传递一个pos位置,其返回此位置上的元素值的引用。
s2.size(),是string类中定义的之中确定字符串长度的方法,在此类中还有一个length(),也是确定字符串长度的方法(此方法用的少)。
2.2使用迭代器遍历和修改字符串
示例代码如下:
//2、迭代器
//获取正向迭代器
//begin() --- 获取起始位置的迭代器
//end() --- 获取最后一个位置的下一个位置的迭代器
//其区间是左闭右开区间
string s1("hello world");
string::iterator it1 = s1.begin();
string::iterator it2 = s1.end();
//for形式
for (it1 = s1.begin(); it1 != it2; ++it1)
{
//++(*it1);
cout << *it1 << " "; //打印其指向的内容,形式上就是指针的模样
}
cout << endl;
//while形式
while (it1 != it2)
{
cout << *it1 << " ";
++it1;
}
cout << endl;
运行结果如下:
这里的begin是返回对象首元素位置的迭代器,end是返回首元素最后一个元素的下一个位置的迭代器,其区间表示为:[begin,end),是一个左闭右开区间,在C/C++语言内凡是存在区间的基本上都是左闭右开区间。
既然有正向迭代器也就有反向迭代器:
//获取反向迭代器
//rbegin() --- 获取最后一个位置的迭代器
//rend() --- 获取起始位置的前一个位置的迭代器
//其区间是左开右闭区间
string s2("hello world");
string::reverse_iterator r_it1 = s2.rbegin();
string::reverse_iterator r_it2 = s2.rend();
for (r_it1; r_it1 != r_it2; r_it1++)
{
cout << *r_it1;
}
cout << endl;
运行结果:
这里的rbegin是获取最后一个位置的迭代器,rned是获取起始位置的前一个位置的迭代器,对其++是从最后一个元素位置向前遍历,和正向迭代器是相反的操作。
也有对于const修饰的对象的常量迭代器:
//获取常量迭代器
//cbegin() --- 获取起始位置的迭代器
//cend() --- 获取最后一个位置的下一个位置的迭代器
//区间同样是左闭右开区间
const string s3("hello world");
string::const_iterator c_it1 = s3.cbegin();
string::const_iterator c_it2 = s3.cend();
for (c_it1; c_it1 != c_it2; c_it1++)
{
cout << *c_it1;
}
cout << endl;
常量反向迭代器:
//获取常量反向迭代器
const string s4("hello world");
string::const_reverse_iterator c_r_it1 = s3.crbegin();
string::const_reverse_iterator c_r_it2 = s3.crend();
上述迭代器的用法只是众多迭代器使用方式的一种,之后其他的使用方式在后面的容器里再学习。
2.3使用范围for遍历对象(C++11支持的新特性)
在讲解范围for之前要先了解auto,这是一个自动识别类型的关键字。
示例代码如下:
// auto关键字 --- 自动识别类型
auto x = 10; // 这里auto就识别成int类型
auto y = 3.14; // 这里auto就识别成double类型
int& m = x;
// auto z = m; // 这里的z是对于引用m的值的赋值,不是m的引用
auto& z = m; // 正确写法
m++; // 是引用则z,m ,x都会改变
// 指针 --- 下面两种写法都可以
auto px1 = &x; //灵活性更高,当 & 删除时,则识别为int类型
auto* px2 = &x; //较为固定,只能识别指针类型
调试窗口观察:
范围for示例代码:
// 范围for --- 支持各种STL容器
// 语法auto 对象名 :需要遍历的对象名
// 其底层就是迭代器
// 是将 *迭代器对象拷贝给auto对象
// 并且自动判断(!=),自动迭代(++)
string s4("hello world");
//这种写法是只能遍历
//当然也能直接指定类型:
//for(char e : s4)
for (auto e : s4)
{
cout << e << " ";
}
cout << endl;
//这种写法才能修改
for (auto& e : s4)
{
cout << ++e << " ";
}
cout << endl;
//特殊用法,数组也能使用范围for遍历
char arr[10] = "abcdefghi";
for (auto e : arr)
{
cout << e << " ";
}
cout << endl;
总结:对于string类,for循环加 [ ] 加下标遍历方式更加常用,不过对于其他STL容器,迭代器是更加主流的遍历方式,不过上述三种的遍历方式都是灵活的去使用,并不是死板的。
3、常见,常用方法或重载
3.1查询大小和容量管理
(1)size(),length()
功能:size()和length()都是string类查询对象大小的方法,不过更多使用的是size(),因为length()先出现,而为了规范性,和其他容器的查询大小方法一致,后续就添加了size()。
示例代码如下:
// 1.查询对象大小
// size() , length() ,功能上相同
string s1("hello world!!");
// 结果一致 --- 13
cout << s1.size() << endl;
cout << s1.length() << endl;
常用于for循环的结束条件。
(2)capacity()
功能:capacity()是一个查看对象容量大小的一个接口。
示例代码如下:
//(2)capacity()
// 查询对象的容量
string s2("hello world");
string s3;
cout << s2.capacity(); //起始是15
cout << s3.capacity() << endl; //即使是空对象,capacity也是初始为15
// 查看扩容机制 --- 几倍扩容 --- 除了第一次为两倍,后续均为1.5倍扩容
string s4;
size_t cy = s4.capacity();
cout << cy << endl;
for (int i = 0; i < 100; i++)
{
s4.push_back('L');
if (s4.capacity() != cy)
{
cy = s4.capacity();
cout << cy << endl;
}
}
查看扩容机制的运行结果:
15到31,两倍左右;31到47,约1.5倍;47到70,约1.5倍;后续一样。
(3)reserve()
功能:将字符串容量调整为计划的大小,长度最多为n个字符
示例代码如下:
// 请求将字符串容量调整为计划的大小,长度最多为n个字符。
string s5("hello world"); //capacity = 15
// 用法一:扩容(最常用的)
s5.reserve(100);
cout << s5.capacity() << endl; //capacity = 111
// 用法二:缩容 --- 这是一个不具约束力的请求,也就是说可能会缩容,或者不缩容
s5.reserve(30);
cout << s5.capacity() << endl; //capacity = 111
// 此接口的n值不会影响字符串的长度
s5.reserve(3);
cout << s5.capacity() << endl; //capacity = 111
最常用的用法是提前给定容量大小,减少反复扩容的操作,提高效率,本质上是一个空间换时间的操作。
优化上述capacity中扩容机制的代码:
string s4;
s4.reserve(100); //这样一来直接开到111容量,可以减少后续的扩容操作
size_t cy = s4.capacity();
cout << cy << endl;
for (int i = 0; i < 100; i++)
{
s4.push_back('L');
if (s4.capacity() != cy)
{
cy = s4.capacity();
cout << cy << endl;
}
}
(4)shrink_to_fit()
功能:这是一个纯粹的缩容接口,将对象的capacity缩容到size大小缩容在string类里面是一个不具有约束里的请求。
示例代码如下:
// 这是一个纯粹的缩容接口
string s6;
s6.reserve(100);
cout << "缩容之前:" << s6.capacity() << endl;
s6.shrink_to_fit();
cout << "缩容之后:" << s6.capacity() << endl;
运行结果:
缩容机制(图解):
基于上述机制,缩容的代价是很大的,要少用此接口,故缩容操作是一个不具约束力的请求。
(5)resize()
功能:更改size或者capacity的大小
示例代码如下:
// void resize(size_t n); //此重载改变size大小,若大于size或者capacity,则字符串后续会被填充进空字符(\0)
// void resize(size_t n, char c); //此重载改变size大小,若大于size或者capacity,则字符串后续会被填充进给定的c字符
// 1、当 n > capacity(大于容量了故也大于size) --- 将size设定到目标大小并且进行扩容操作
string s7("hello world");
cout << "resize之前的size:" << s7.size() << endl; // 11
cout << "resize之前的capacity:" << s7.capacity() << endl; // 15
s7.resize(30,'x');
s7.resize(30); //此写法就是将x替换成了\0,这一个看不见的字符
cout << s7 << endl;
cout << "resize之后的size:" << s7.size() << endl; // 30
cout << "resize之后的capacity:" << s7.capacity() << endl; // 31
// 2、当 capacity > n > size --- 和一一样的功能
// 3、当 n < size --- 只保留给定的n个字符,多的字符全部删除
string s8("hello world");
cout << "resize之前的size:" << s8.size() << endl; // 11
cout << "resize之前的capacity:" << s8.capacity() << endl; // 15
s8.resize(5);
cout << s8 << endl; //hello
cout << "resize之后的size:" << s8.size() << endl; // 5
cout << "resize之后的capacity:" << s8.capacity() << endl; // 15
运行结果:
3.2增
(1)operator += - - - 用得最多的
功能:可以添加字符串,字符或者string类的对象。
示例代码如下:
// 2.增
// 增加一个string类对象(1): string& operator+= (const string & str);
// 增加一个字符串(2): string& operator+= (const char* s);
// 增加一个字符(3): string& operator+= (char c);
string s2;
string s3("2025/5/19 ");
s2 += s3; //(1)的用法
s2 += "hello"; //(2)的用法
s2 += "_"; //(2)的用法
s2 += '\n'; //(3)的用法
s2 += "world"; //(2)的用法
cout << s2 << endl;
运行结果:
(2)operator +
功能:连接两个string类对象,并返回一个新串
示例代码如下:
// 连接两个string类对象,并返回一个新串
string s5("aaa");
string s6("bbb");
cout << "s5 + s6:" << s5 + s6 << endl;
运行结果:
(3)push_back()
功能:可以将单个字符添加到字符串末尾
示例代码如下:
//(2)push_back()
// 将单个字符添加到字符串末尾
string s4("hello");
s4.push_back(' ');
s4.push_back('w');
s4.push_back('o');
s4.push_back('r');
s4.push_back('l');
s4.push_back('d');
cout << s4 << endl;
运行结果:
(4)insert()
功能:在指定位置插入字符或者字符串
示例代码如下:
// 任意位置插入元素
// 插入一个string对象(1): string& insert(size_t pos, const string & str);
// 插入一个子串(2): string& insert(size_t pos, const string & str, size_t subpos, size_t sublen);
// 插入一个C风格的字符串(3): string & insert(size_t pos, const char* s);
// 插入一个字符串的前n个字符(4): string& insert(size_t pos, const char* s, size_t n);
// 插入n个c字符(5): string& insert(size_t pos, size_t n, char c);
string s7("abgh");
string s8("1234");
cout << s7.insert(2, s8) << endl; // (1) --- ab1234gh
cout << s7.insert(2, s8, 2, 4) << endl; // (2) --- ab34gh
cout << s7.insert(2, "cdef") << endl; // (3) --- abcdefgh
cout << s7.insert(2, "xyz", 2) << endl; // (4) --- abxygh
cout << s7.insert(2, 5, 'x') << endl; // (5) --- abxxxxxgh
总结:
insert最常用的重载是(1),(3),(5),并且要谨慎使用(不是不安全),而是insert底层涉及数据挪动。
(5)append - - - 用的少这里只介绍最实用的几个重载
功能:可以将字符或者字符串添加到字符串末尾
示例代码如下:
// 可以将字符或者字符串添加到字符串末尾
//插入一个string对象(1): string& append(const string & str);
//插入一个字符串(2): string & append(const char* s);
//插入n个字符(3): string& append(size_t n, char c);
string s9;
string s10("hello");
s9.append(s10); // (1) --- hello
s9.append(" world"); // (2) --- hello world
s9.append(3, '!'); // (3) --- hello world!!!
3.3删
(1)erase()
功能:删除指定位置之后的n个字符,也需谨慎使用,底层同样涉及数据挪动,效率低下。
示例代码如下:
// 删除指定位置之后的n个字符
string s1("aaabbbcccddd");
s1.erase(3, 6); // aaaddd
// 也可以不指定第二个参数len ,这样就是使用的缺省值npos,而npos是无符号整数的最大值,用了-1表示
// 这样的用法就相当于自第三个位置之后所有的数据全部都删除
s1.erase(3); // aaa
(2)pop_back()
功能:删除字符串末尾的一个字符
示例代码如下:
// 删除字符串末尾的一个字符
string s2("abcde?");
s2.pop_back(); // abcde
s2.pop_back(); // abcd
s2.pop_back(); // abc
(3)clear()
功能:清空整个字符串
示例代码如下:
// 清空整个字符串
string s3("hello shsh>>?");
s3.clear(); // 变为空值: ""
3.4改
replace()
功能:修改从pos位置开始,长度为n的字符或者字符串,它的效率也是很低的,谨慎使用,原因基本一致(erase和insert)
示例代码如下:
// 4.改 --- 或者替换
// (1)replace
// 从pos位置开始,插入n个长度的string类对象(1): string& replace(size_t pos, size_t len, const string & str);
// string类对象的部分(2): string& replace(size_t pos, size_t len, const string & str,size_t subpos, size_t sublen);
// subpos --- pos位置开始
// sublen --- 修改的长度
// C语言风格字符串(3): string & replace(size_t pos, size_t len, const char* s);
// C语言风格字符串的一部分(4): string& replace(size_t pos, size_t len, const char* s, size_t n);
// 使用n个字符替换指定目标字符(串)(5): string& replace(size_t pos, size_t len, size_t n, char c);
string s1("aaabbbccceee");
string s2("ddd");
cout << s1.replace(9, 3, s2) << endl; //(1) --- aaabbbcccddd
cout << s1.replace(9, 3, s2, 0, 2) << endl; //(2) --- aaabbbcccdd
cout << s1.replace(9, 3, "eee") << endl; //(3) --- aaabbbccceee
cout << s1.replace(9, 3, "eee", 1) << endl; //(4) --- aaabbbccce
cout << s1.replace(9, 3, 3, 'n') << endl; //(5) --- aaabbbcccnnn
其中常用的为(3)
3.5查
(1)empty
功能:检查字符串是否为空,若为空,则返回0(false),反之返回1(true)
示例代码如下:
//(1)empty()
// 查看(判断)字符串是否为空,若不为空则返回0(false),反之返回1(true)
string s4("hello shsh>>?");
bool b1 = s4.empty();
s4.clear();
bool b2 = s4.empty();
cout << b1 << endl; //0
cout << b2 << endl; //1
(2)operator[ ]
功能:返回第pos位置上的元素,并且该重载会检查数组越界,若出现数组越界操作,则会直接程序崩溃。
示例代码如下:
//(2)operator[]
// 返回第pos位置的数据
string s2("hello world!!");
cout << s2[0] << endl; //h
cout << s2[0]++ << endl; //i --- 也可以进行修改操作
cout << s2[7] << endl; //o
cout << s2[12] << endl; //!
cout << s2 << endl; //iello world!!
// 该重载会检查数组越界 --- 直接会发生程序崩溃
//s2[14];
// 普通数组不会程序崩溃,因为对于C/C++编译器来说检查普通数组的越界操作是一个影响效率的操作
int arr[10] = { 0 };
arr[20];
(3)at()
功能:功能和operator[ ]一样,不过处理错误时是抛异常,而非检查返回值
示例代码如下:
//(3)at()
// 功能和operator[ ]一样,不过处理错误时是抛异常,而非检查返回值
string s3("123456");
s3.at(0)++;
cout << s3 << endl; //23456
cout << s3.at(5) << endl; //6
try
{
//at接口的越界操作
cout << s3.at(10) << endl; //抛异常
}
catch(exception& e)
{
cout << e.what();
}
运行结果:
invalid string position此异常即为字符串位置无效。
(4)front()与back()
功能:返回字符串的起始位置、末尾位置的元素,这两操作都不能对空对象进行操作,会直接导致程序崩溃
示例代码如下:
//(4)front()
// 返回字符串的起始字符,若对于空对象进行此操作会导致程序崩溃
string s4("12345");
cout << s4.front() << endl; //1
//s4.clear(); //对于空对象进行此操作会导致程序崩溃
//cout << s4.front();
//(5)back()
// 返回字符串的末尾字符,同理进行对空对象的操作会导致程序崩溃
string s5("12345");
cout << s5.back() << endl; //5
//s5.clear(); //不能对空对象进行此操作
//cout << s5.back();
(5)find()和rfind()
功能:find()是查询指定的字符返回其下标、或者查询字符串返回起始位置的下标。rfind()是反向查询字符或者字符串,并返回其下标。
示例代码如下:
// 查询字符串: size_t find(const char* s, size_t pos = 0) const;
// 查询字符: size_t find(char c, size_t pos = 0) const;
// 第二个参数pos就是从串的哪一个位置开始查询,没有指定则使用缺省值0,即从头开始查询
// 若没有找到指定的字符或者字符串,则返回重载的全局静态成员常量npos,这是一个非常大的数
// 返回指定字符或者字符串(起始位置)的下标
string s6("hello world!!");
size_t pos1 = s6.find('h'); // 这里返回 h 的下标:0
size_t pos2 = s6.find("!!"); // 这里返回第一个!号的下标:11
size_t pos3 = s6.find('h',1); // 这里返回npos,即非常大的数
cout << pos1 << endl;
cout << pos2 << endl;
cout << pos3 << endl;
运行结果:
(6)substr()
功能:提取子串,从pos位置开始,提取len个字符,其区间是 [ ) 式。
示例代码如下:
// string substr (size_t pos = 0, size_t len = npos) const;
// pos --- 指定从哪个位置开始查找,若不指定则使用缺省值0,即从头开始查找
// len --- 指定的提取子串的长度,若不指定则使用缺省值npos,即提取直到字符串末尾
// 返回值是一个string类型
// 若给定的 pos 非法(pos > size()),则会抛出异常 --- 超出范围异常
//
// 查询子串
string s7("123abc!!");
string str1 = s7.substr(); // 都不指定则提取整个字符串
string str2 = s7.substr(3); // --- abc!!
string str3 = s7.substr(0, 3); // --- 123
string str4 = s7.substr(6, 2); // --- !!
cout << "str1 = " << str1 << endl;
cout << "str2 = " << str2 << endl;
cout << "str3 = " << str3 << endl;
cout << "str4 = " << str4 << endl;
try
{
string str5 = s7.substr(10); //这里 pos > s7.size()
}
catch(const exception& e)
{
cout << e.what() << endl;
}
运行结果:
3.5其他
(1)c_str()和data()
功能:返回底层指向的数组的指针
示例代码如下:
// 返回底层指向数组的指针
// 返回值都是const char*
string s1("hello world");
const char* pstr1 = s1.c_str();
const char* pstr2 = s1.data();
在某些使用C语言的函数的情况下会使用。
(2)重载全局的各种关系运算符
==、!=、<、<=、>、>=
其比较方式和C语言的strcmp函数相同,依次比较对应位置上的字符的ASCII值
返回值均是bool类型