📌 相关专栏
-
【C++ 专栏】
📌 相关文章推荐
很高兴你点开这篇文章✨
这里会持续更新我喜欢的内容,关注我,一起慢慢变好呀
👍 点赞 ⭐ 收藏 💬 评论
文章目录
- 前言
- [1. string 的多种构造方式](#1. string 的多种构造方式)
- [2. 下标访问与 operator\[\]](#2. 下标访问与 operator[])
- [3. 范围 for 遍历:修改与只读](#3. 范围 for 遍历:修改与只读)
- [4. auto 关键字与类型推导陷阱](#4. auto 关键字与类型推导陷阱)
- [5. 迭代器遍历(正向、反向、只读)](#5. 迭代器遍历(正向、反向、只读))
- [6. 容量操作:size, length, capacity](#6. 容量操作:size, length, capacity)
- [7. 清空字符串:clear()](#7. 清空字符串:clear())
- [8. 判空:empty()](#8. 判空:empty())
- [9. 调整有效字符个数:resize()](#9. 调整有效字符个数:resize())
- [10. 预分配空间:reserve() 避免频繁扩容](#10. 预分配空间:reserve() 避免频繁扩容)
- 本文所有的代码
- [🐾 test.cpp](#🐾 test.cpp)
前言
C++ 标准库中的 std::string 是我们处理字符串的利器。它的接口丰富,既兼容 C 风格字符串,又提供了动态内存管理、各种遍历方式等高级特性。然而,正因为功能繁多,对于和我一样的初学者来说是很容易混淆构造函数、迭代器类型、容量变化规则等细节。
🐾 接下来就让我们一起来了解了解吧!
🐶 🐾 ✨ 🐾 🐶
1. string 的多种构造方式
string 类提供了多种重载的构造函数,灵活适应不同场景。
cpp
string s1; // 空字符串
string s2("hello world"); // 从 C 字符串构造
string s3(s2); // 拷贝构造,全部拷贝
string s4(s2, 0, 5); // 从 s2 的位置 0 开始拷贝 5 个字符 → "hello"
string s5(s2, 6, 15); // 即使超过末尾也不越界,拷贝到结束 → "world"
string s6(s2, 6); // 省略长度,从下标 6 拷贝到末尾 → "world"
string s7("hello world", 6); // 拷贝前 6 个字符 → "hello "
string s8(10, 'x'); // 10 个 'x' → "xxxxxxxxxx"
注意 : 部分拷贝时,指定的长度若超过源字符串长度,会安全地拷贝到末尾;string s7("hello world",6) 这种写法是拷贝前 6 个字符,注意包含空格。
🐶 🐾 ✨ 🐾 🐶
2. 下标访问与 operator\[\]
operator[ ] 返回字符的引用,因此可以读取或修改指定位置的字符。
cpp
string s1("hello world");
s1[0] = 'x'; // 修改第一个字符为 'x'
cout << s1[0] << endl; // 输出 'x'
cout << s1.size() << endl; // 返回有效字符个数(不含 '\0')
cout << s1.length() << endl; // 效果与 size() 相同
注意 :size() 和 length() 完全等价,推荐使用 size() 以保持与其它容器一致。
try-catch 捕获异常,但 operator\[\] 通常不检查越界(越界是未定义行为)。若需安全访问,可使用 at() 成员函数(会抛出 out_of_range)。
cpp
int main()
{
//try块:执行可能抛出异常的代码->test_string2()
try
{
test_string2();
}
//catch:捕获并处理try块中抛出的异常,/
//e.what()用于输出异常的描述信息
catch (const exception& e)
{
cout << e.what() << endl;
}
return 0;
}
🐶 🐾 ✨ 🐾 🐶
3. 范围 for 遍历:修改与只读
范围 for 是 C++11 提供的简洁遍历语法。
cpp
string s1("hello world");
// 可修改版本:使用引用
for (auto& ch : s1)
{
ch -= 1; // 每个字符 ASCII 码减1
}
// 只读版本:使用 const 引用
for (const auto& ch : s1)
{
cout << ch << ' '; // 只能读,不能修改
}
注意 :auto& ch 推导为 char&,因此可以修改原字符串;const auto& 则保证只读。范围 for 也适用于数组,未初始化的元素会补 0。
🐶 🐾 ✨ 🐾 🐶
4. auto 关键字与类型推导陷阱
auto 可以自动推导类型,但有一些细节需要留意。
cpp
int i = 0;
auto j = i; // j 是 int
auto k = 10; // k 是 int
auto p1 = &i; // p1 是 int*
auto* p2 = &i; // 显式指明指针,p2 也是 int*
int& r1 = i;
auto r2 = r1; // r2 是 int(auto 会忽略引用)
auto& r3 = r1; // r3 是 int&(必须加 & 才能保留引用)
🐾 应用示例:与 std::find 配合使用。
cpp
string s1("hello world");
auto ret1 = find(s1.begin(), s1.end(), 'x');
if (ret1 != s1.end())
{
cout << "找到了!";
}
🐾 auto 简化了迭代器类型书写,但注意 auto 会丢弃 const 和引用属性,如需保留请显式添加 const / &。
🐶 🐾 ✨ 🐾 🐶
5. 迭代器遍历(正向、反向、只读)
迭代器是 STL 容器的通用访问方式,string 也完整支持。
cpp
void Print(const string& s)
{
// 只读正向迭代器
string::const_iterator it1 = s.cbegin();
while (it1 != s.cend())
{
cout << *it1 << " ";
++it1;
}
// 只读反向迭代器(逆序遍历)
string::const_reverse_iterator it2 = s.rbegin();
while (it2 != s.rend())
{
cout << *it2 << " ";
++it2;
}
}
🐾 普通迭代器(可修改)示例:
cpp
string s1("hello world");
string::iterator it = s1.begin();
while (it != s1.end())
{
*it = *it + 1; // 修改字符
++it;
}
注意 :begin() 指向第一个字符,end() 指向最后一个字符的下一个位置。反向迭代器 rbegin() 指向最后一个字符,rend() 指向首字符之前。迭代器适用于所有容器(如代码中的 list<int>),这是下标遍历做不到的。
🐶 🐾 ✨ 🐾 🐶
6. 容量操作:size, length, capacity
- size() / length() :返回字符串中有效字符的个数。
- capacity() :返回当前为字符串分配的内存空间大小(可以容纳的字符数,不包含 '\0')。
cpp
string s6("Hello world");
cout << s6.size() << endl; // 11
cout << s6.capacity() << endl; // 通常大于等于 size(VS 中会做对齐,如 15)
string s7;
cout << s7.capacity() << endl; // 初始容量可能很小(如 15 或 0,由实现定义)
🐾 capacity() 总是大于等于 size(),当字符串增长超过容量时,会自动重新分配更大的内存(通常以指数增长或对齐方式)。
🐶 🐾 ✨ 🐾 🐶
7. 清空字符串:clear()
clear() 将字符串的有效字符个数变为 0,但不会释放已分配的内存。
cpp
string s1("Hello world");
s1.clear();
cout << s1.size() << endl; // 0
cout << s1.capacity() << endl; // 与 clear 之前相同(例如 15)
🐾 若想释放多余内存,可以结合 shrink_to_fit()(C++11)或使用空字符串进行 swap。
🐶 🐾 ✨ 🐾 🐶
8. 判空:empty()
empty() 返回字符串是否为空(即 size() == 0)。
cpp
string s1("hello");
string s2;
cout << s1.empty() << endl; // 0 (false)
cout << s2.empty() << endl; // 1 (true)
🐶 🐾 ✨ 🐾 🐶
9. 调整有效字符个数:resize()
resize(n, char) 将字符串的有效字符个数改为 n:
- 若 n < 原大小,则截断到前 n 个字符。
- 若 n > 原大小,则用 char 填充多出的位置(默认填充 '\0')。
cpp
string s9("hehe world");
s9.resize(4); // 截断为 "hehe"
cout << s9.size(); // 4
cout << s9.capacity(); // 容量不会缩小(VS 下仍为 15)
s9.resize(20, 'a'); // 扩展为 20 个字符,新增部分用 'a' 填充
cout << s9.size(); // 20
cout << s9.capacity(); // 可能增长到 31(对齐后)
重要 :resize 增加元素时可能引起容量变化;减少元素时容量不会下降,避免频繁的内存重分配。
🐶 🐾 ✨ 🐾 🐶
10. 预分配空间:reserve() 避免频繁扩容
reserve(n) 手动为字符串预留至少 n 个字符的空间,但不改变有效字符个数。
cpp
string s;
s.reserve(2000); // 提前预留 2000 字节
for (int i = 0; i < 2000; ++i)
s += 'a'; // 循环中不会多次扩容
string s1("hello world");
cout << s1.capacity(); // 初始容量(VS 中为 15)
s1.reserve(20); // n > 当前容量 → 扩容(至少到 20,实际可能为 31)
cout << s1.capacity(); // 31
cout << s1.size(); // 仍然是 11(未改变内容)
s1.reserve(5); // n < 当前容量 → 不会缩容
cout << s1.capacity(); // 依然是 31
建议 :如果预先知道字符串最终长度,使用 reserve 可以避免多次内存分配和拷贝,显著提升效率。
🐶 🐾 ✨ 🐾 🐶
本文所有的代码
🐾 test.cpp
cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include<string>
#include<iostream>
#include<algorithm> //find函数需要
#include<list>
using namespace std;
//1.
void test_string1()
{
//构造函数
string s1;
string s2("hello world");
string s3(s2);//全部拷贝
cout << s1 << endl;
cout << s2 << endl;
cout << s3 << endl;
string s4(s2, 0, 5);//部分拷贝(从位置0开始拷贝到位置5):hello
cout << s4 << endl;//hello
string s5(s2, 6, 15);//就算没有15个字符,也不用担心越界,直接拷贝到末尾就结束:hello world
cout << s5 << endl;//hello world
string s6(s2, 6);//没写要取多少个字符,则从第六个位置开始取到末尾:world
cout << s6 << endl;//'world'
string s7("hello world",6);//拷贝前6位
cout << s7 << endl;//'hello '
string s8(10, 'x');//10个x
cout << s8 << endl;//xxxxxxxxxx
s7 = "xxxxxx";//修改s7的值,重新赋值
cout << s7 << endl;//xxxxxx
}
int main()
{
test_string1();
}
//
//
//
//2.
////operator[]:下标遍历访问
void test_string2()
{
string s1("hello world");
cout << s1 << endl;
s1[0] = 'x'; //将下表为0的元素改成x;
cout << s1 << endl;//hello world
cout << s1[0] << endl;//xelllo world
cout << s1.size() << endl;//6
cout << s1.length() << endl;//6
}
int main()
{
//try块:执行可能抛出异常的代码->test_string2()
try
{
test_string2();
}
//catch:捕获并处理try块中抛出的异常,/
//e.what()用于输出异常的描述信息
catch (const exception& e)
{
cout << e.what() << endl;
}
return 0;
}
//3.
//////////////范围for遍历///////////////////////////////////////////////////////////
void test_string3()
{
string s1("hello world");
cout << s1 << endl;
////可修改版本
////要想可修改 ,就要用&引用->auto& ch:引用,可以修改原字符串
//auto& ch:推导为char*,因为s1字符串里的元素是char型
for (auto& ch : s1) //依次把s1里的每个字符拿出来
{
//hello world 每个字符的ASCLL码减1,变为 gdhhn vnqkc
ch -= 1; //每个字符的ASCll码减1
}
////只读版本
////const->只能读,不能改
for (const auto& ch : s1)
{
cout << ch << ' ' << endl;//只读不能改
}
cout << endl;
////用在数组上
int a[10] = { 1,2,3 };
for (auto e : a)
{
//因为数组未初始化完成,所以后面的自动补0
cout << e << ' ';//输出1,2,3,0,0,0,0,0,0,0
}
cout << endl;
}
int main()
{
test_string3();
}
//4.
/////////////////迭代器对于函数的使用,auto///////////////////////////
///auto会根据右边的值自动推变量的类型
void test_string4()
{
string s1("helo world");
cout << s1 << endl;
cout << endl;
//string::iterator ret1=find(s1.begin(),s1.end(),'x');
//利用auto优化
//find(起始迭代器,结束迭代器,要找的值)
//
//返回值:找到了->指向该元素的迭代器,打印
// 没找到->返回end(),也就是不打印
auto ret1 = find(s1.begin(), s1.end(), 'x');
if (ret1 != s1.end())
{
cout << "找到了x!" << endl ;//因为这里的字符串没有'x',所以不会输出这句话
}
int i = 0; //i是int
auto j = i; //j是int
auto k = 10; //k是int
auto p1 = &i; //p1是int*
//auto*强调一定是指针
auto* p2 = &i; //明确写成指针,是int*
cout << p1 << endl;
cout << p2 << endl;
cout << endl;
//引用
int& r1 = i; //r1是i的引用,即r1是i的别名
auto r2 = r1; //r2不是int&引用,是int,是新变量,因为auto会忽略引用
auto& r3 = r1; //r3是int&引用,即r3是r1的别名,要保留引用,必须➕&
//&i,&r1,&r3是同一个地址,&r2是新的地址
cout << &r1 << endl;
cout << &r2 << endl;
cout << &i << endl;
cout << &r3 << endl;
}
int main()
{
test_string4();
}
//5.
///////////////迭代器遍历(适配所有容器)////////////////////////////////////////
//begin()指向第一个字符
//end()指向最后一个字符的下一位
//
void Print(const string& s)
{
//只读迭代器:const_iterator
//const string::iterator:修饰迭代器本身,迭代器就不能移动了(几乎没用
//string::const_iterator:修饰迭代器指定的对象,迭代器可移动,但元素不能修改(只读遍历)
//cbegin():返回指向字符串s首元素的迭代器
// cend():返回末尾元素的下一位置的迭代器
//it*:解引用访问元素
string::const_iterator it1 = s.cbegin();
while (it1 != s.cend())
{
cout << *it1 << " ";
++it1;
}
cout << endl;;
//反向只读迭代器:const_reverse_iterator
//逆序遍历:rbegin()指向最后一个元素,rend()指向首元素之前的位置
//++it2:逻辑上是向前移动(从尾到头遍历)
//const_reverse_iterator:既保证逆序,又保证元素只读
string::const_reverse_iterator it2 = s.rbegin();
while (it2 != s.rend())
{
cout << *it2 << ' ';
++it2;
}
cout << endl << endl;;
}
///////下标遍历
void test_string5()
{
string s1("hello world");
cout <<"s1:"<<" " << s1 << endl;
for (size_t i = 0; i < s1.size(); i++)
{
s1[i]++; // s1[i]:下标+[]遍历--->对取出的字符进行修改(ASCLL码+1)
//'h'变为'i'....
}
cout<<"修改后的s1:" <<" " << s1 << endl; //输出修改后的字符串
//////普通迭代器遍历
cout << "普通迭代器遍历:" ;
string::iterator it1 = s1.begin();
//s1.begin();返回指向第一个字符的迭代器
//s1.end():返回指向最后一个字符的下一个位置的迭代器
while (it1 != s1.end()) //判断是否遍历到末尾
{
cout << *it1 << " "; //解引用,访问当前字符(类似指针的*p)
++it1; //让迭代器移动到下一个字符
}
cout << endl;
/////lit<int>:双向链表容器,存储int类型数据
cout << "遍历lit:";
list<int> lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
/////遍历lit
list<int>::iterator lit = lt.begin();
while (lit != lt.end())
{
cout << *lit << " ";
++lit;
}
cout << endl;
cout << "调用Print():";
Print(s1);
}
int main()
{
test_string5();
}
//6.
////容量操作---size(),length(),capacity()
void test_string6()
{
//size(),length():返回字符串的有效长度
string s6("Hello world");
cout << s6.size() << endl;
cout << s6.length() << endl << endl;
//capacitiy():返回空间大小
string s7;//空字符串
cout << s6.capacity() << endl;
cout << s7.capacity() << endl;
//在vs中大多数情况容量都是比字符串本身长度要大,因为会进行对齐
}
int main()
{
test_string6();
return 0;
}
//7.
////清空有效字符->clear()
void test_string7()
{
string s1("Hello world");
s1.clear();
cout << s1.size() << endl;
cout << s1.capacity()<< endl;
//clear()只是把string中有效的字符清空,不会改变空间容量
}
int main()
{
test_string7();
return 0;
}
//8.
////判空:empty()
//
void test_string8()
{
//emoty():检测字符串是否为空串,是就返回ture->1,不是就返回false->0
string s1("hello world");
string s2;//空字符串
cout << s1.empty() << endl;
cout << s2.empty()<< endl;
}
int main()
{
test_string8();
return 0;
}
//9
////调整有效字符->resize()
void test_string9()
{
//resize:将有效字符的个数改成n个,多出的空间用字符c填充->resize(n,'c')
//注意:resize在改变元素个数时,如果将元素个数增多,可能会改变底层容量(capacity)的大小
////////////////////////////////如果将元素个数减少,底层空间容量大小不会
string s9("hehe world");
s9.resize(4); //有效字符个数改成4个:hehe
string::iterator ch = s9.begin(); //遍历ch,打印
while (ch != s9.end())
{
cout << *ch;
ch++;
}
cout << endl;
cout << s9.size() << endl; //4
cout << s9.capacity() << endl; //15:编译器在底层预分配空间时,会多预留一些空间,避免频繁扩容
s9.resize(20,'a'); //多出的空间用字符c填充->resize(n,'c')
for (size_t i = 0; i < s9.size(); i++)
{
cout << s9[i];
}
cout << endl;
cout << s9.size()<<endl; //20
cout << s9.capacity() << endl<<endl;//31
}
int main()
{
test_string9();
return 0;
}
//10.
////预分配空间(避免频繁扩容)->reserve()
void test_string10()
{
//reserve:为字符串预留空间,不改变有效元素个数(预分配空间)
//作用:知道某个字符串的大概长度,就可以先用reserve()预分配空间,避免频繁扩容
string s;
s.reserve(2000); //已知要存2000个字符,提前预分配
//后续拼接2000个字符,就不会频繁扩容
for (int i = 0; i < 2000; ++i)
{
s += 'a';
}
string s1("hello world"); //11
cout << s1.size() << endl; //11
cout << s1.capacity() << endl; //初始分配15
s1.reserve(20); //预留20(n>capacity-15),要至少扩容到20,不会改变有效元素的个数
cout << s1.size() << endl; //11
cout << s1.capacity() << endl; //31(已扩容)
s1.reserve(5); //n<capacity,不会缩容
cout << s1.size() << endl; //11
cout << s1.capacity() << endl; //31
}
int main()
{
test_string10();
return 0;
}
- 欢迎留言交流
- 期待你的评论与建议
- 留下你的想法吧
🐶 🐾 ✨ 🐾 🐶

谢谢你看到这里呀
如果喜欢这篇内容,点个关注,下次更新不迷路✨
👍 点赞 ⭐ 收藏 💬 评论
