C++ STL string 类:常用接口 + auto + 范围 for全攻略,字符串操作效率拉满

🔥个人主页:小张同学

🎬作者简介:C++研发方向学习者

📖个人专栏: 《C语言》《数据结构》《C++深度剖析:从入门到深耕》

⭐️人生格言:无视中断,不弃热枕,方得坚持之道。


前言:

在 C 语言中处理字符串时,手动管理内存、应对缓冲区溢出等问题常常让人头疼。C++ 标准库的string类正是为解决这些痛点而来,它让字符串操作更安全、便捷。本文将从string类的优势讲起,带你快速掌握它的核心用法与实用技巧,帮你更高效地处理字符串场景。


目录

一、为什么要学string类

[1.1 C语言字符串的痛点:](#1.1 C语言字符串的痛点:)

[1.2 string类的优势:](#1.2 string类的优势:)

二、了解string类、auto和范围for

[2.1 简单了解string类:](#2.1 简单了解string类:)

[2.2 auto和范围for:](#2.2 auto和范围for:)

[2.2.1 auto关键字:](#2.2.1 auto关键字:)

[2.2.2 范围for:](#2.2.2 范围for:)

三、string类的常用接口说明

[3.1 string类对象的常见构造:](#3.1 string类对象的常见构造:)

[3.2 string类对象的容量操作:](#3.2 string类对象的容量操作:)

[3.3 string类对象的访问及遍历操作:](#3.3 string类对象的访问及遍历操作:)

[3.3.1 下标访问:](#3.3.1 下标访问:)

[3.3.2 迭代器遍历:](#3.3.2 迭代器遍历:)

[3.3.3 范围for遍历:](#3.3.3 范围for遍历:)

[3.4 string类对象的修改操作:](#3.4 string类对象的修改操作:)

[3.4.1 尾部追加:perator+= / push_back / append](#3.4.1 尾部追加:perator+= / push_back / append)

[3.4.2 插入和删除:insert 和 erase 在指定位置插入和删除](#3.4.2 插入和删除:insert 和 erase 在指定位置插入和删除)

[3.4.3 内容清空:clear(只清有效字符)](#3.4.3 内容清空:clear(只清有效字符))

[3.4.4 字符串查找:find() 找字符/子串](#3.4.4 字符串查找:find() 找字符/子串)

[3.4.5 字符串替换:replace() 修改指定位置内容](#3.4.5 字符串替换:replace() 修改指定位置内容)

四、其他string常用场景的一些实用接口和技巧

[4.1 整行输入:getline()读取带空格的字符串:](#4.1 整行输入:getline()读取带空格的字符串:)

[4.2 子串截取:substr()从指定位置取指定长度](#4.2 子串截取:substr()从指定位置取指定长度)

[4.3 C字符转换:c_str () 适配C语言库函数](#4.3 C字符转换:c_str () 适配C语言库函数)

[4.4 空串判断:empty () 高效判空](#4.4 空串判断:empty () 高效判空)

[4.5 补充示例:(涉及到几个接口的综合使用)](#4.5 补充示例:(涉及到几个接口的综合使用))


一、为什么要学string类

1.1 C语言字符串的痛点:

  • 内存需手动管理,易出现内存泄漏、野指针,且无法动态扩容;
  • 无边界检查,str 系列库函数极易造成缓冲区溢出、越界访问;
  • 强依赖'\0'结束符 ,丢失 / 覆盖后会乱读内存,也无法存储含'\0'的字符串;
  • 数据与操作分离(违背 OOP),无封装,需手动调用独立的 str 库函数;
  • 基础操作繁琐 ,无**+** 拼接、**==**比较等直观运算符,常用功能需手动实现;
  • 指针赋值为浅拷贝,多指针指向同一块内存,易因修改 / 释放导致程序异常。

1.2 string类的优势:

  • 内存自动管理:无需手动 malloc/free,底层自动分配、扩容、释放,彻底规避内存泄漏、野指针问题;
  • 内置边界安全校验:操作自动检查缓冲区,必要时先扩容再执行,at () 方法越界抛异常,从根源防止缓冲区溢出;
  • 摆脱 '\0' 依赖:通过内部长度变量维护实际长度,可存储含 '\0' 的字符串,鲁棒性高,同时支持 c_str () 兼容 C 语言;
  • OOP 封装设计:将字符串数据和操作方法封装为整体,符合面向对象思想,调用直观,无需手动传递底层数据;
  • 操作极简高效:重载 +、+=、==、[] 等常用运算符,拼接 / 比较 / 访问像基本数据类型一样简单,内置 substr、find 等常用方法,无需手动实现;
  • 拷贝安全无风险:默认实现深拷贝,多个对象数据相互独立,修改 / 释放互不影响,C++11 还支持移动语义,大幅提升拷贝效率。

C++ string 类通过自动内存管理、内置边界检查、封装数据与操作、重载常用运算符、默认深拷贝,从根源规避了以上所有问题,开发更高效、程序更健壮。并且在OJ中,有关字符串的题目基本以string类的形式出现,而且在常规工作中,为了简单、方便、快捷,基本都使用string类,很少有人去使用C库中的字符串操作函数。


二、了解string类、auto和范围for

2.1 简单了解string类:

参考文档:string - C++ Reference

这里我们就不过多介绍了,大家自行搜索了解。

注意:使用string类时,必须包含#include头文件以及using namespace std;

cpp 复制代码
#include<iostream>
#include<string>
using namespace std;

2.2 auto和范围for:

我们在这里补充2个C++11的小语法,方便我们后面的学习。

2.2.1 auto关键字:

  • 在早期C/C++中auto的含义是:使用**auto修饰的变量,是具有自动存储器的局部变量,后来这个不重要了。**C++11中,标准委员会变废为宝,赋予了auto全新的含义即:auto不再是一个存储类型指示符,而是作为一个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期****推导而得
  • auto声明指针类型时,用autoauto*没有任何区别,但用auto声明引用类型时则必须加**&**
  • 当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际****只对第一个类型进行推导,然后用推导出来的类型定义其他变量
  • auto****不能作为函数的参数,可以做返回值,但是建议谨慎使用
  • auto****不能直接用来声明数组

下面用代码给大家具体说明一下:

cpp 复制代码
int func1()
{
	return 10;
}
// 不能做参数
//void func2(auto a) //编译报错
//{
//}
// 可以做返回值,但是建议谨慎使用
auto func3()
{
	return 3;
}

int main()
{
	//C++11
	int i = 0;
	//通过初始化表达式值类型自动推荐对象类型
	auto j = i;
	auto k = 10;
	auto m = 1.1;
	auto n = 'a';
	auto ret = func1();

	cout << typeid(i).name() << endl;
	cout << typeid(j).name() << endl;
	cout << typeid(k).name() << endl;
	cout << typeid(m).name() << endl;
	cout << typeid(n).name() << endl;
	cout << typeid(ret).name() << endl;

	// 编译报错:rror C3531: "e": 类型包含"auto"的符号必须具有初始值设定项
	// auto e;

	auto p1 = &i;//也是指针类型
	auto* p2 = &i;//指定一定是指针
	cout << typeid(p1).name() << endl;
	cout << typeid(p2).name() << endl;
	cout << p1 << endl;
	cout << p2 << endl;

	//引用
	int& r1 = i;
	auto r2 = r1;//r2不是int&引用,是int
	auto& r3 = r1;//r3是int&引用,引用必须指定&
	cout << &r1 << endl;
	cout << &r2 << endl;
	cout << &i << endl;
	cout << &r3 << endl;

	auto aa = 1, bb = 2;
	// 编译报错:error C3538: 在声明符列表中,"auto"必须始终推导为同一类型
	//auto cc = 3, dd = 4.0;
	
	// 编译报错:error C3318: "auto []": 数组不能具有其中包含"auto"的元素类型
	//auto array[] = { 4, 5, 6 };
	return 0;
}

这里大家可能觉得auto的用处不是很大,下面我们来看看auto的用武之地:

cpp 复制代码
#include<iostream>
#include <string>
#include <map>
using namespace std;
int main()
{
	std::map<std::string, std::string> dict = { { "apple", "苹果" },{ "orange",
	"橙子" }, {"pear","梨"} };
	// auto的用武之地
	//std::map<std::string, std::string>::iterator it = dict.begin();
	auto it = dict.begin();
	while (it != dict.end())
	{
		cout << it->first << ":" << it->second << endl;
		++it;
	}
	return 0;
}
cpp 复制代码
//利用auto简化迭代器对于函数的使用
void test_1()
{
	string s1("hello world");
	cout << s1 << endl;

	//string::iterator it1 = s1.begin();
	//利用auto进行优化:
	auto it1 = s1.begin();
	while (it1 != s1.end())
	{
		//(*it1)++;//修改
		cout << (*it1) << " ";
		++it1;
	}
	cout << endl;

	//还有查找也可以:
	//string::iterator ret = find(s1.begin(), s1.end(), 'd');
	//利用auto进行优化
	auto ret = find(s1.begin(),s1.end(),'d');
	if (ret != s1.end())
	{
		cout << "找到了" << endl;
	}
	//list也是一样可以这样使用
}

int main()
{
	test_1();
	return 0;
}

2.2.2 范围for:

  • 对于一个有范围的集合而言,由程序员来说明循环的范围是多余的,有时候还会容易犯错误。因此 C++11 中引入了基于范围的for循环。for循环后的括号由冒号""分为两部分:第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围,自动迭代,自动取数据,自动判断结束。

  • 范围for可以作用到数组和容器对象上进行遍历。

  • 范围for的底层很简单,容器遍历实际就是替换为迭代器,这个从汇编层也可以看到。

  • 自动取容器数据赋值,自动迭代++、自动判断结束------被形象地称为**"语法糖"**。

  • 范围for通常配合auto使用,更简便,但不是必须适配auto关键字使用。

下面用代码给大家具体说明一下:

cpp 复制代码
void test_for()
{
	string s1("hello world");
	cout << s1 << endl;

	//C++11
	//范围for,自动取容器数据赋值,自动迭代++,自动判断结束
	//其实底层还是迭代器,这个看反汇编可以发现

	//for (auto i : s1)
	//也可以:
	for (char i : s1)
	{
		cout << i << " ";
	}
	cout << endl;

	for (auto& j : s1)//其实可以直接使用&,可以修改
	{
		j--;
	}
	cout << s1 <<  endl;

	for (const auto& k : s1)
	{
		//k++;//只能读不能改
		cout << k << ' ';
	}
	cout << endl;

	//支持迭代器的容器,都可以使用范围for
	//数组也支持,这里先使用一点C风格
	int a[10] = { 1,2,3 };
	for (auto e : a)
	{
		cout << e << ' ';
	}
	cout << endl;
}

int main()
{
	test_for();
	return 0;
}

三、string类的常用接口说明

3.1 string类对象的常见构造:

这里给大家介绍4种常用的构造:

|----------------------------------|-----------------------------------------------|
| **(constructor)**函数名称 | 功能说明 |
| string()(重点) | 构造空的 string 类对象,即空字符串 |
| string(const char* s) (重点) | C-string 来构造 string 类对象 |
| string(size_t n, char c) | string 类对象中包含 n 个字符 c |
| string(const string&s) (重点) | 拷贝构造函数 |

cpp 复制代码
// 1. 空字符串构造(默认构造)
string s1;  // s1是空串,底层已初始化,不用手动加'\0'

// 2. C字符串构造(最常用,把char*转成string)
string s2("hello world");  // s2 = "hello world"

// 3. 重复字符构造(创建n个相同字符的字符串)
string s3(5, 'a');  // s3 = "aaaaa"(5个'a')

// 4. 拷贝构造(用已有的string创建新对象)
string s4(s2);  // s4 = "hello world"(和s2内容一样)

下面是文档中较为官方的介绍:详见string::string - C++ Reference

下面我用代码具体说明一下:

cpp 复制代码
#include<iostream>
#include<string>
using namespace std;
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);//从s2下标为0的位置拷贝5个过去构造s4;
	cout << s4 << endl;
	//pos位置一直拷贝到结尾
	//1.写一个超过s2长度的
	string s5(s2, 6, 15);
	cout << s5 << endl;
	//2.直接不写,默认使用缺省值npos
	string s6(s2, 6);
	cout << s6 << endl;
	
	string s7("hello world", 7);//取前7个
	cout << s7 << endl;

	string s8(10, 'x');//取10个x
	cout << s8 << endl;

	string s9 = "xxxxxx";//这样也可以
	cout << s9 << endl;
}

string的析构函数知道即可,不用我们自己实现和调用,会自动生成并且调用。

3.2 string类对象的容量操作:

|-------------------|--------------------------------------------------|
| 函数名称 | 功能说明 |
| size (重点) | 返回字符串有效字符长度( 不含结尾的 '\0' )(基本所有容器通用) |
| length | 返回字符串有效字符长度( 不含结尾的 '\0' )(不通用) |
| capacity | 返回底层已分配的空间大小(能存多少字符,不含 '\0') |
| empty (重点) | 检测字符串是否为空串,是返回 true ,否则返回 false |
| clear (重点) | 只清空有效字符,不会释放空间 |
| reserve (重点) | 为字符串预留空间,若空间小于已有空间不会改变有效元素个数 |
| shrink_to_fit | 进行缩容,可以改变有效元素个数 |
| resize (重点) | 将有效字符的个数改成 n 个,多出的空间用字符 c 填充 |

代码示例:

cpp 复制代码
void TestCapacity()
{
	string s1;
	//s1.reserve(200);//确定要插入多少时,可以提前扩容,避免频繁扩容,提高效率
	size_t old = s1.capacity();
	cout << old << endl;
	for (size_t i = 0; i < 200; i++)
	{
		s1.push_back('x');
		if (old != s1.capacity())
		{
			cout << s1.capacity() << endl;
			old = s1.capacity();
		}
	}
	cout << endl << endl;

}
void test_string2()
{
	string s0;
	cout << s0.size() << endl;

	string s1("hello world");
	cout << s1.max_size() << endl;//字符串所能达到的最大长度,了解下即可
	cout << s1.size() << endl;//不包含结尾的\0,基本所有容器通用
	cout << s1.length() << endl;//也不包含结尾的\0,不通用,只在string中
	cout << s1.capacity() << endl;//存储实际有效字符的个数,不包含结尾的\0

	s1.clear();//内容被清理,空间不会清理
	cout << s1.size() << endl;
	cout << s1.capacity() << endl << endl;

	//测试空间增容
	TestCapacity();

	//reserve最好只用来增容
	string s2("hello world");
	cout << s2.size() << endl;
	cout << s2.capacity() << endl;

	s2.reserve(20);//会开的比20大
	cout << s2.size() << endl;
	cout << s2.capacity() << endl;

	//s2.reserve(5);//不同编译器不一样,vs上不会缩容,g++上会缩容
	s2.shrink_to_fit();//这个可以实现缩容,但是一般不会用,代价比较大
	cout << s2.size() << endl;
	cout << s2.capacity() << endl;

	string s3(s2);
	cout << s3 << endl;

	// < 当前对象的size时,相当于保留前n个,删除后面的数据
	s3.resize(5);
	cout << s3 << endl;
	// > 当前对象的size时,插入数据
	s3.resize(10, 'x');
	cout << s3 << endl;
	s3.resize(15);//用0来填充多出的元素空间
	cout << s3 << endl;
	s3.resize(30, 'y');
	cout << s3 << endl;
}

int main()
{
	test_string2();
	return 0;
}

注意:

1. size()与length()方法底层实现原理完全相同,引入size()的原因是为了与其他容器的接口保持一致,一般情况下基本都是用size()。

2. clear()只是将string中有效字符清空,不改变底层空间大小。

3. resize(size_t n) 与 resize(size_t n, char c)都是将字符串中有效字符个数改变到n个,不同的是当字符个数增多时:resize(n)用'\0'来填充多出的元素空间,resize(size_t n, char c)用字符c来填充多出的元素空间。注意:resize在改变元素个数时,如果是将元素个数增多,可能会改变底层容量的大小,如果是将元素个数减少,底层空间总大小不变。

4. reserve(size_t res_arg=0):为string预留空间,不改变有效元素个数,当reserve的参数小于string的底层空间总大小时,reserver不会改变容量大小。

3.3 string类对象的访问及遍历操作:

|------------------------|---------------------------------------------------------------|
| 函数名称 | 功能说明 |
| operator[ ] (重点) | 返回 pos 位置的字符, const string 类对象调用 |
| begin + end | begin 获取一个字符的迭代器 + end 获取最后一个字符下一个位 置的迭代器 |
| rbegin + rend | begin 获取一个字符的迭代器 + end 获取最后一个字符下一个位 置的迭代器 |
| 范围 for | C++11 支持更简洁的范围 for 的新遍历方式 |

3.3.1 下标访问:

和数组下标访问逻辑类似,支持读和写,注意下标从0开始:

代码示例:

cpp 复制代码
void test_string3()
{
	string s1("hello world");
	cout << s1 << endl;

	cout << s1[0] << endl;
	s1[0] = 'x';//可以直接使用下标来访问修改,类似于上面那样
	cout << s1[0] << endl;
	cout << s1 << endl;

	//相比于数组这个越界有严格的检查
	//s1[12];//断言
	s1.at(12);//抛异常,这里at的使用简单看看就行,at不常用

}

int main()
{
	try
	{
		test_string3();
	}
	catch (const exception& e)
	{
		cout << e.what() << endl;
	}
	return 0;
}

下标访问的实现逻辑与下方代码类似:

cpp 复制代码
class string
{
public:
	char& operator[] (size_t pos)
	{
		return _str[pos];
	}
private:
	char* _str;
	size_t _size;
	size_t _capacity;
};

3.3.2 迭代器遍历:

迭代器是STL容器的一个通用遍历方式,begin() 指向第一个字符,end() 指向最后一个字符的下一位。

cpp 复制代码
//下标遍历,迭代器
void test_string3()
{
	string s1("hello world");
	cout << s1 << endl;

	//下标+[ ]
	//遍历or修改
	for (size_t i = 0; i < s1.size(); i++)
	{
		s1[i]++;
	}
	cout << s1 << endl;

	//迭代器
	//行为像指针一样的东西
	//1.常规使用
	string::iterator it1 = s1.begin();
	while (it1 != s1.end())
	{
		//(*it1)-;//修改
		cout << *it1 << " ";
		++it1;
	}

	//相对于下标+[]来说,迭代器更加通用,我们这里再来看看在链表中的使用
	list<int> l1;
	l1.push_back(1);
	l1.push_back(2);
	l1.push_back(3);
	l1.push_back(4);
	list<int>::iterator lit1 = l1.begin();
	while (lit1 != l1.end())
	{
		cout << *lit1 << " ";
		++lit1;
	}
	cout << endl;

	//迭代器的其它使用形式
	Print(s1);
}
void Print(const string& s)
{
	//2.const版本
	//const string::iterator it1=s.cbegin();
	//上面这样使用是不对的,const不应该用来修饰整个迭代器,这样都遍历不了了,而是修饰指向的对象
	string::const_iterator it1= s.cbegin();//这里使用cbegin和begin的都可以
	while (it1 != s.cend())
	{
		//*it1 = 'x';不能修改
		cout << *it1 << " ";
		++it1;
	}
	cout << endl;

	//3.reverse版本,加上const一起演示,逆序输出
	string::const_reverse_iterator it2 = s.rbegin();//这里使用rbegin
	while (it2 != s.rend())
	{
		//*it2 = 'x';//不能修改
		cout << *it2 << " ";
		++it2;
	}
	cout << endl;
}

int main()
{
	test_string3();
	return 0;
}

3.3.3 范围for遍历:

这个在前面以及讲过了,这里就不过多介绍了。

cpp 复制代码
void test_string4()
{
	string s1("hello world");
	cout << s1 << endl;
 
	//C++11
	//范围for,自动取容器数据赋值,自动迭代++,自动判断结束
	//其实底层还是迭代器,这个看反汇编可以发现
	
	//for (auto ch : s1)//其实可以直接使用&,可以修改
	for(auto& ch:s1)
	{
		ch -= 1;
	}
 
	for (const auto& ch : s1)
	{
		cout << ch << ' ';//只能读不能改
	}
	cout << endl;
 
	//支持迭代器的容器,都可以使用范围for
	//数组也支持,这里先使用一点C风格
	int a[10] = { 1,2,3 };
	for (auto e : a)
	{
		cout << e << ' ';
	}
	cout << endl;
}
int main()
{
	test_string4();
}

3.4 string类对象的修改操作:

|------------------------------|---------------------------------------------------------------|
| 函数名称 | 功能说明 |
| push_back | 在字符串后尾插字符 c |
| append | 在字符串后追加一个字符串 |
| operator+= ( 重点) | 在字符串后追加字符串 str |
| c_str (重点) | 返回 C 格式字符串 |
| find + npos (重点) | 从字符串 pos 位置开始往后找字符 c ,返回该字符在字符串中的 位置 |
| rfind | 从字符串 pos 位置开始往前找字符 c ,返回该字符在字符串中的 位置 |
| substr | str 中从 pos 位置开始,截取 n 个字符,然后将其返回 |

注意:

1. 在string尾部追加字符时,s.push_back(c) / s.append(1, c) / s += 'c'三种的实现方式差 不多,一般情况下string类的+=操作用的比较多,+=操作不仅可以连接单个字符,还可 以连接字符串。

2. 对string操作时,如果能够大概预估到放多少字符,可以先通过reserve把空间预留 好。

3.4.1 尾部追加:perator+= / push_back / append

  • operator+=:支持追加单个字符和字符串,比 push_back 和 append 更加灵活
  • push_back:仅支持单个字符,功能单一,适合明确追加单个字符的场景;
  • append:支持字符串、子串,适合需要追加部分内容的场景。

下面用代码说明一下使用:

cpp 复制代码
void test_string5()
{
	string s1("hello world");
	s1.push_back('#');//尾插一个字符
	s1.append("hello tengxun");//尾插一个字符串
	cout << s1 << endl;

	s1.append(10, 'x');//尾插10个x
	cout << s1 << endl;

	//还可以配着迭代器使用
	string s3;
	string s2(" apple hello!");
	//我不想要空格和!
	s3.append(s2.begin() + 1, s2.end() - 1);
	//s3.append(++s2.begin(), --s2.end());
	cout << s3 << endl;

	//其实我们直接使用+=更加方便
	string s4("hello world");
	s4 += ' ';            // 追加字符
	s4 += "hello tengxun";// 追加字符串
	cout << s4 << endl;

	//为什么不把 + 重载为成员的而是全局,因为这样可以不用一定把成员变量写在左边
	cout << s4 + "xxxx" << endl;
	cout << "xxxx" + s4 << endl;//全局这样就可以,但是成员函数就不可以

	//assign:为字符串赋予一个新值,替换其当前内容,没有直接赋值好用
	s4 = "xxx";
	cout << s4 << endl;
	s4.assign("yyyyy");
	cout << s4 << endl;
}

int main()
{
	test_string5();
	return 0;
}

3.4.2 插入和删除:insert 和 erase 在指定位置插入和删除

下面我们用代码来实际使用一下:

cpp 复制代码
void test_string6()
{
	string s1("hello world");
	//上面都是尾插,这里实现一个头插
	s1.insert(0, "xxxxx");
	cout << s1 << endl;
	s1.insert(0, "x");//头插x
	cout << s1 << endl;

	//但是头插一个字符必须这样写
	s1.insert(0, 1, '*');
	cout << s1 << endl;

	//还可以在指定位置插入
	s1.insert(5, 1, '*');//第5个位置插入一个*
	cout << s1 << endl;

	//迭代器版本
	s1.insert(s1.begin(), '&');
	cout << s1 << endl;

	//删除
	string s2("hello world");
	s2.erase(0, 1);//位置0,删除长度1
	cout << s2 << endl;

	s2.erase(s2.begin());//头删
	cout << s2 << endl;

	s2.erase(5, 2);//指定位置开始删除2个
	cout << s2 << endl << endl;

	//没给的话就全删掉
	s2.erase(5);//这里应该也是默认npos
	cout << s2 << endl;
}

int main()
{
	test_string6();
	return 0;
}

3.4.3 内容清空:clear(只清有效字符)

cpp 复制代码
string s = "hello";
s.clear();  // s变成空串,但底层容量不变
cout << s.size();  // 输出0(有效字符数为0)

3.4.4 字符串查找:find() 找字符/子串

find() 从左往右找字符或者子串,返回第一次出现的下标;没有找到就返回 string :: npos(这个戴代表一个很大的数,代表"不存在")

下面我们用代码来实际使用一下:

cpp 复制代码
int main()
{
    string s = "hello world";
    // 1. 找字符'w'
    size_t pos1 = s.find('w');
    if (pos1 != s.npos)
    {
        cout << "找到了,位置为" << pos1 << endl;// 输出6(s[6]是'w')
    }

    //2. 找子串"orld"
    size_t pos2 = s.find("orld");
    if (pos2 != s.npos)
    {
        cout << "找到了,位置为" << pos2 << endl;// 输出7(s[7]是"orld")
    }

    // 3. 从下标3开始找字符'l'
    s = "1 hello 1 world";
    size_t pos3 = s.find('1', 3);// 从第3位(h开始)往后找
    if (pos3 != s.npos)
    {
        cout << "找到了,位置为" << pos3 << endl;// 输出8(s[8]是'l')
    }

    return 0;
}

3.4.5 字符串替换:replace() 修改指定位置内容

下面我们用代码来实际使用一下:

cpp 复制代码
void test_string7()
{
	string s3("hello world");
	s3.replace(5, 1, "&&&");//把5这个位置的1个替换成&&&
	cout << s3 << endl;

	s3.replace(5, 3, "*");//从5开始的三个替换成*
	cout << s3 << endl;

	//我们再来看看怎么把所有空格都替换成%%
	string s4("hello            world");
	cout << s4 << endl;
	//s4.replace(5, 12, "%&%");//仅限知道有多少个空格
	//cout << s4 << endl;

	size_t pos = s4.find(' ');
	while (pos != s4.npos)
	{
		s4.replace(pos, 1, "%%");
		//找到下一个空格
		pos = s4.find(' ', pos + 2);
	}
	cout << s4 << endl;
}
int main()
{
	test_string7();
	return 0;
}

但是replace的效率不是很高,要频繁移动数据,所以实际使用不多。

我们可以将上面的**"把所有空格都替换成%%"**的这个问题优化一下:

cpp 复制代码
string s6;
s6.reserve(s5.size());
for (auto s : s5)
{
	if (s != ' ')
	{
		s6 += s;
	}
	else {
			s6 += "%%";
	}
}
s5 = s6;
cout << s5 << endl;

四、其他string常用场景的一些实用接口和技巧

4.1 整行输入:getline()读取带空格的字符串:

在平常的使用中,如果使用 cin>>string 读取字符串时,遇到空格就会停止。而 getline() 能读取一整行的内容,包括空格。默认回车结束,也可以自己指定结束的字符

比如我们下面这个题就必须使用getline,直接用cin是不行的,无法读取完整:

题目链接:字符串最后一个单词的长度_牛客题霸_牛客网

cpp 复制代码
#include <iostream>
#include <string>
using namespace std;
 
int main() {
	string str;
	// cin >> str;//这个不行
	getline(cin, str);
	//getline(cin, str, '#');//指定碰到#结束
 
 
	size_t pos = str.rfind(' ');
	if (pos != str.size())
	{
		cout << str.size() - (pos + 1) << endl;
	}
	else
	{
		cout << str.size() << endl;
	}
}

4.2 子串截取:substr()从指定位置取指定长度

cpp 复制代码
int main()
{
	string s = "hello world";
	// 1. 从位置6开始,取5个字符
	string sub1 = s.substr(6, 5);  // sub1 = "world"
	cout << sub1 << endl;

	// 2. 从位置0开始,取5个字符
	string sub2 = s.substr(0, 5);  // sub2 = "hello"
	cout << sub2 << endl;

	// 3. 从位置6开始,取到末尾
	string sub3 = s.substr(6);     // sub3 = "world"
	cout << sub1 << endl;

	return 0;
}

4.3 C字符转换:c_str () 适配C语言库函数

我们在一些特殊的场景下需要使用C语言的char*(比如printf输出),用c_str ()把string转换成const char*;

cpp 复制代码
#include <cstring>
int main()
{
	string s = "hello";
	// 1. printf输出(printf不直接支持string)
	printf("s = %s\n", s.c_str());  // 输出:s = hello

	// 2. 调用C库函数strlen(需要包含<cstring>)
	size_t len = strlen(s.c_str());  // len = 5
}

4.4 空串判断:empty () 高效判空

判断字符串是否为空,优先用empty(),比size()==0更高效(empty()直接返回标志位,size()==0可能要计算)

cpp 复制代码
int main()
{
	string s;
	if (s.empty())// 推荐
    {
		cout << "s是空串";
	}
	// 不推荐:if (s.size() == 0)
}

4.5 补充示例:(涉及到几个接口的综合使用)

cpp 复制代码
int main()
{
	string filename("Test.cpp");
	FILE* fout = fopen(filename.c_str(), "r");
	if (fout)
	{
		cout << "打开文件成功" << endl;
	}

	//string suffix = filename.substr(4, 4);
	string file("Test.tar.zip");
	size_t pos = file.rfind('.');
	if (pos != string::npos)
	{
		string suffix = file.substr(pos);
		cout << suffix << endl;
	}

	string url = "https://legacy.cplusplus.com/reference/string/string/rfind/";
	size_t i1 = url.find(':');
	if (i1 != string::npos)
	{
		string protocol = url.substr(0, i1);
		cout << protocol << endl;

		size_t i2 = url.find('/', i1+3);
		if (i2 != string::npos)
		{
			string domain = url.substr(i1+3, i2-(i1+3));
			cout << domain << endl;

			string uri = url.substr(i2 + 1);
			cout << uri << endl;
		}
	}

	std::string str("Please, replace the vowels in this sentence by asterisks.");
	std::size_t found = str.find_first_not_of("abcdefg");
	while (found != std::string::npos)
	{
		str[found] = '*';
		found = str.find_first_not_of("aeiou", found + 1);
	}

	std::cout << str << '\n';
	cout << (str < url) << endl;

	cout << str + "xxxx" << endl;
	cout << str + url << endl;
	cout << "xxxx" + str << endl;

	// C++11以后,传值返回对象效率都很不错
	string ret = str + "xxxx";

	cin >> url >> str;
	cout << url << endl;
	cout << str << endl;

	return 0;
}

本篇博客的完整原代码:

小张同学的CPP仓库------gitee.com


往期回顾:

C++ 模板初级:函数 / 类模板 + 实例化 + 匹配原则全讲透,自此告别重复 C++ 代码-CSDN博客

C/C++ 内存管理:从 malloc/free到new/delete,原理区别全讲透,程序再也不崩溃-CSDN博客

C++ 类和对象(五):初始化列表、static、友元、内部类等7大知识点全攻略-CSDN博客


结语:

本文从基础概念到常用接口,再到实用技巧,系统梳理了string类的核心知识。string类是 C++ 处理字符串的利器,熟练运用它能显著提升编程效率。建议大家结合示例动手实践,在项目中打磨技能,让string类成为你代码里的得力工具。如果文章对你有帮助的话,欢迎评论,点赞,收藏加关注,感谢大家的支持。

相关推荐
万岳科技系统开发1 小时前
食堂采购系统源码库存扣减算法与并发控制实现详解
java·前端·数据库·算法
xyq20241 小时前
Matplotlib 绘图线
开发语言
小镇敲码人2 小时前
探索CANN框架中TBE仓库:张量加速引擎的优化之道
c++·华为·acl·cann·ops-nn
wWYy.2 小时前
数组快排 链表归并
数据结构·链表
张登杰踩2 小时前
MCR ALS 多元曲线分辨算法详解
算法
m0_694845572 小时前
tinylisp 是什么?超轻量 Lisp 解释器编译与运行教程
服务器·开发语言·云计算·github·lisp
平安的平安2 小时前
面向大模型算子开发的高效编程范式PyPTO深度解析
c++·mfc
June`2 小时前
muduo项目排查错误+测试
linux·c++·github·muduo网络库
春日见2 小时前
如何创建一个PR
运维·开发语言·windows·git·docker·容器