C++ --- string

发布于:2025-05-25 ⋅ 阅读:(25) ⋅ 点赞:(0)

简介

在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类型


网站公告

今日签到

点亮在社区的每一天
去签到