【C++】 String 核心用法:构造、遍历、容量与优化技巧

📌 相关专栏

很高兴你点开这篇文章✨

这里会持续更新我喜欢的内容,关注我,一起慢慢变好呀

👍 点赞 ⭐ 收藏 💬 评论


文章目录

  • 前言
  • [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;
}
  1. 欢迎留言交流
  2. 期待你的评论与建议
  3. 留下你的想法吧

🐶 🐾 ✨ 🐾 🐶


谢谢你看到这里呀

如果喜欢这篇内容,点个关注,下次更新不迷路✨

👍 点赞 ⭐ 收藏 💬 评论