【C++初阶】九、STL容器中的string类(上)

=========================================================================

相关代码gitee自取:

C语言学习日记: 加油努力 (gitee.com)

=========================================================================

接上期:

【C++初阶】八、初识模板(泛型编程、函数模板、类模板)-CSDN博客

=========================================================================

目录

[一 . STL简介](#一 . STL简介)

什么是STL

STL的版本

[HP 原始版本:](#HP 原始版本:)

[P.J. 版本:](#P.J. 版本:)

[RW 版本:](#RW 版本:)

[SGI 版本:](#SGI 版本:)

STL的六大组件

图示:


[二 . string类](#二 . string类)

C语言中的字符串

C++标准库中的string类

string类(了解)

总结:

string类的常用接口说明(重点)

string类对象的常见构造函数:

[图示 -- 第1个构造函数:](#图示 -- 第1个构造函数:)

[图示 -- 第2、3个构造函数:](#图示 -- 第2、3个构造函数:)

[图示 -- 第4、5个构造函数:](#图示 -- 第4、5个构造函数:)

[图示 -- 第6、7个构造函数:](#图示 -- 第6、7个构造函数:)

[补充 -- operator= :(赋值"="运算符重载函数)](#补充 -- operator= :(赋值“=”运算符重载函数))

string类对象的容量操作:

使用注意事项:

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

[operator[] :(下标运算符"[ ]"重载函数)](#operator[] :(下标运算符"[ ]"重载函数))

[begin + end :(通过迭代器进行遍历)](#begin + end :(通过迭代器进行遍历))

[rbegin + rend :(通过迭代器进行反向遍历)](#rbegin + rend :(通过迭代器进行反向遍历))

[范围for循环 -- 完成字符串遍历:](#范围for循环 -- 完成字符串遍历:)


本篇博客相关代码:

[Test.cpp文件 -- C++文件:](#Test.cpp文件 -- C++文件:)

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

一 . STL简介

什么是STL

STL(standard template libaray -- 标准模板库):

是 C++标准库 的重要组成部分,不仅是一个可复用的组件库,
而且是一个包罗数据结构和算法的软件框架


STL的版本

HP 原始版本:

Alexander Stepanov、Meng Lee 在惠普实验室完成的原始版本,
本着开源精神,他们声明允许任何人任意运用、拷贝、修改、传播、商业使用这些代码
无需付费,唯一的条件就是也需要像原始版本一样做开源使用。
HP版本的STL -- 所有STL实现版本的始祖


P.J. 版本:

P. J. Plauger 开发,继承自HP版本,
被 Windows Visual C++(VS系列)采用,不能公开或修改。
缺陷:可读性比较低,符号命名比较怪异


RW 版本:

由 Rouge Wage公司 开发,继承自HP版本,被 C++ Builder 采用,
不能公开或修改,可读性一般


SGI 版本:

由 Silicon Graphics Computer Systems,inc公司开发,继承自HP版本。
被 GCC(Linux)采用,可移植性好,可公开、可修改甚至贩卖,
从命名风格和编程风格上看,阅读性非常高


STL的六大组件

  • STL的六大组件分别为:
    仿函数 、算法 、迭代器 、空间配置器 、容器 、配接器
图示:
  • 其中空间配置器也叫内存池,容器可以简单理解成数据结构,配接器也可叫适配器,
    容器(数据结构)存储数据需要大量的空间,
    如果频繁向堆申请大量的内存空间,效率会有点低,
    所以STL中有了空间配置器(内存池),专门给容器(数据结构)提供内存空间,
    内存空间的初始化工作则交给定位new完成

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

二 . string类

C语言中的字符串

C语言中,字符串是以 '\0' 结尾的一些字符的集合,为了操作方便,
C语言标准库中提供了一些str系列的库函数,但是这些库函数与字符串是分离开的,
不太符合OOP(面向对象程序设计)的思想,而且底层空间需要用户自己管理,
稍不留神可能还会越界访问


C++标准库中的string类

string类(了解)

  • string类严格来说不是STL容器,但从归类的角度来说,
    其实可以把字符串看成是数据结构中的串,一个专门管理字符的串。
    因为string产生得比STL早,所以在C++文档中被纳入到了标准库中,而不是STL中
  • string字符串是表示字符序列的类,虽然我们通常叫做string类,
    但string其实是一个模板类的一个实例
  • 标准的字符串类提供了对此类对象的支持,其接口类似于标准字符容器的接口,
    但添加了专门用于操作单字节字符字符串的设计特性
  • string类使用char作为其字符类型,使用它的默认char_traits和分配器类型
    (关于模板的更多学习,可参阅:C++ string类模板
  • string类是basic_string模板类的一个示例,它使用char来实例化basic_string模板类,
    并用 char_traits 和 allocator 作为 basic_string 的默认参数
    (关于模板的更多学习,可参阅:
    C++ string类模板** )**
  • 注意:
    string类独立于所使用的编码来处理字节,
    如果用来处理多字节或变长字符(如UTF-8)的序列,
    string类的所有成员(如长度或大小)以及它的迭代器,
    将仍然按照字节而不是实际编码的字符来操作
总结:
  • string是表示字符串的字符串类
  • 该类的接口和常规容器的接口基本相同,再添加了一些用来操作string的常规操作
  • string类在底层实际是 basic_string模板类 的别名(引用),
    使用了 typedef basic_string<char, char_traits, allocator> string; 重命名为了string
  • 不能操作多字节或者变长字符的序列
  • 在使用string类时,必须包含 #include<string>头文件 以及 using namespace std;
    (也可以不用完全展开,展开string对应的那部分即可)

---------------------------------------------------------------------------------------------

string类的常用接口说明(重点)

string类对象的常见构造函数:
constructor)构造函数名称 对应功能说明
string(); (重点) 创建一个字符串对象, 但没有使用字符串初始化
string (const char* s); (重点) 创建一个字符串对象, 并使用常量字符串初始化
string (const string& str); (重点) 创建一个字符串对象, 并使用另一个字符串对象 进行初始化
string (const string& str, size_t pos, size_t len = npos); 拷贝**++str++** 字符串中++pos++ 下标 开始的++len++个字符, 拷贝给创建的字符串对象
string (const char* s, size_t n); 拷贝一个++常量字符串++ 的 ++n++个长度的字符, 拷贝给创建的字符串对象
string (size_t n, char c); 填充++n++ 个++c++字符到 创建的字符串对象中
template <class InputIterator> string (InputIterator first, InputIterator last); 涉及迭代器, 不懂,以后再说
图示 -- 第1个构造函数:
图示 -- 第2、3个构造函数:
图示 -- 第4、5个构造函数:
图示 -- 第6、7个构造函数:
**补充 -- operator= :

(赋值"="运算符重载函数)**

  • string类对象还可以通过赋值"="运算符重载函数(operator=)实现初始化
  • operator= 有三种实现:
    第1种: string& operator= (const string& str); -- string字符串类对象赋值
    第2种: string& operator= (const char* s); -- 直接写出字符串进行赋值
    第3种: string& operator= (char c); -- 单个字符进行赋值

对应图示**:**


string类对象的容量操作:
函数名称 功能说明
size (重点) 返回字符串有效字符大小(长度)
length 返回字符串有效字符长度
capacity 实际能够存储的有效字符个数
empty (重点) 检测字符串是否为空串,是返回true,否则返回false
clear (重点) 清空字符串有效字符
reserve (重点) 为字符串预留空间,可以进行扩容操作 (只影响容量,不会影响数据)
resize (重点) 将有效字符的个数分割成n个,多出的空间可用字符c填充 (即影响容量,也影响数据)
使用注意事项:
  • size() 与 length() 方法底层实现原理完全相同,
    引入 size() 的原因是为了与其它容器的接口保持一致,
    一般情况下基本都是用 size()
    对应图示:

  • clear() 方法只是将string字符串中的有效字符清空,
    不改变底层空间大小

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

图示 -- resize :

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

图示 -- reserve :

图示 -- reserve增容 :


string类对象的访问即遍历操作:
函数名称 功能说明
operator[] (重点) 返回pos下标位置的字符,const string类对象也有对应const调用
begin + end begin:获取第一个字符的迭代器 end:获取最后一个字符下一个位置的迭代器
rbegin + rend being + end 是正向迭代器(正向遍历),分为const和非const版本, rbegin + rend 是反向迭代器(反向遍历),也分为const和非const版本
范围for循环 C++11支持更简洁的范围for循环的新遍历方式
**operator[] :

(下标运算符"[ ]"重载函数)**

  • string类的 "operator[]" 有两个实现:
    第1种: char& operator[] (size_t pos);
    第2种: const char& operator[] (size_t pos) const;
  • string类实现了 "operator[]" 后,
    string类对象就可以像数组通过下标一样访问字符串中的字符了

图示 -- 第1种实现**:**

**begin + end :

(通过迭代器进行遍历)**

  • 迭代器iterator定义在类域中,但它不是内部类,是一个类型,
    现在刚接触迭代器的情况下,可以认为迭代器的用法和指针类似
  • 迭代器的 begin() 和 end() 方法,形成的区间是"左闭右开"的,
    对于string类对象,begin() 可以理解成指向字符串首字符的指针,
    end() 可以理解成指向最后一个有效字符的下一位字符的指针

图示 -- 使用迭代器遍历字符串:

图示 -- 迭代器实现operator[]的第2种实现:

**rbegin + rend :

(通过迭代器进行反向遍历)**

  • 和 begin+end 类似,只不过 rbegin+rend 是反向遍历
  • begin+endrbegin+rend各自都有两种实现**:**
    非const版本const版本

图示 -- 使用迭代器反向遍历字符串:

范围for循环 -- 完成字符串遍历:
  • 范围for循环底层是通过迭代器实现的,
    是C++11支持的更简洁的范围for循环的新遍历方式

对应图示:

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

本篇博客相关代码:

Test.cpp文件 -- C++文件:

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS 1

//包含IO流头文件:
#include<iostream>
//完全展开std命名空间:
using namespace std;

//包含string类头文件:
#include<string>

#include<vector>
#include<list>

//template<class T>
//T* func(int n)
//{
//	return new T[n];
//}
//
主函数:
//int main()
//{
//	func<double> (10);
//	
//	return 0;
//}



string类的默认成员函数:
主函数:
//int main()
//{
//	//string:
//	/*
//	* string是一个类模板,
//	* typedef basic_string<char> string;
//	* 原名是叫 basic_string<char> 的类模板,
//	* 被typedef重命名为string
//	*/
//
//	//string类的默认成员函数 -- 构造函数:
//	//string类中有7种构造函数:
//	/*
//	*(最常用)1:无参的string -- string(); 
//	*(最常用)2:string (const char* s);
//	* 3:string (const string& str);
//	* 4:string (const string& str, size_t pos, size_t len = npos);
//	* 5:string (const char* s, size_t n);
//	* 6:string (size_t n, char c);
//	* 7:template <class InputIterator> string (InputIterator first, InputIterator last);
//	*/
//
//	//(最常用)1:无参的string -- string(); 
//	string s1;
//
//
//	//(最常用)2:string (const char* s);
//	string s2("hello world");
//	/*
//	* s:接收一个字符串
//	* 创建一个字符串类,
//	* 并使用C语言的常量字符串的地址进行初始化,
//	* s2就存储着这段常量字符串
//	*/
//
//
//	//赋值"=",调用拷贝构造初始化:
//	string s3 = s2;
//	/*
//	* 该写法相当于4中的:string s3(s2);
//	*/
//	
//
//	//3:string (const string& str);
//	string s4(s3);
//	/*
//	* str:接收string字符串类对象
//	* 拷贝str字符串对象,
//	* 该写法相当于3中的:string s4 = s3;
//	* 这种方法也是调用拷贝构造函数进行初始化的
//	*/
//
//	/*
//	* 因为string类重载了 流插入/流提取:
//	*	operator << / operator >>
//	* 所以是可以直接使用 输出流/输入流 的
//	*/
//	cout << "构造函数1:" << s1 << endl;
//	cout << "构造函数2:" << s2 << endl;
//	cout << "赋值"=":" << s3 << endl;
//	cout << "构造函数3:" << s4 << endl;
//
//
//	//4:string (const string& str, size_t pos, size_t len = npos);
//	/*
//	* str:接收string字符串类对象
//	* pos:下标
//	* len:长度
//	* npos:一个整型静态const无符号的变量,值为-1,
//	*		因为无符号,-1会是整型的最大值,
//	*		所以如果不对len初始化的话,len的缺省值就是很大的值,
//	*		所以会拷贝很长的字符串,即拷贝pos后的所有字符了
//	*		(len不给默认拷贝str中pos下标后的所有字符)
//	* 
//	* 这个string类函数的功能是拷贝字符串str的一部分,
//	* 从pos下标开始,拷贝len长度的字符串
//	*/
//	string s5(s2, 1, 5);
//	/*
//	* 从s2字符串的第1个字符开始,
//	* (s2:"hello world")
//	* 往后拷贝5个字符长度的字符串,
//	* 拷贝结果存放在字符串s5中。
//	* (s5:"ello ")
//	* 注:空格也算一个字符;
//	*     len如果超过str的长度,则str结尾为止
//	*/
//	cout << "构造函数4:" << s5 << endl;
//	
//
//	//5:string (const char* s, size_t n);
//	/*
//	* s:字符串指针
//	* n:在s字符串中拷贝字符长度
//	*/
//	string s6("hello world", 5);
//	/*
//	* 拷贝"hello world"中的前5个字符,
//	* 拷贝到s6中初始化s6
//	*/
//	cout << "构造函数5:" << s6 << endl;
//
//
//	//6:string (size_t n, char c);
//	/*
//	* n:填充的字符个数
//	* c:要填充的字符
//	* 使用n个c字符来填充字符串字符串
//	*/
//	string s7(10, 'x');
//	/*
//	* 使用10个'x'来填充s7
//	*/
//	cout << "构造函数6:" << s7 << endl;
//
//
//	//7:template <class InputIterator> string (InputIterator first, InputIterator last);
//	/*
//	* 这个string类构造函数因为涉及迭代器的内容,
//	* 所以等了解了迭代器再来了解该类构造函数
//	*/
//
//
//
//
//
//	//string类的默认成员函数 -- 析构函数:
//	/*
//	* string类的析构函数:
//	* string字符串类为了支持扩容,
//	* 其字符数组是动态开辟的,
//	* 动态开辟的空间使用后要进行释放,
//	* 其释放工作就是由析构函数负责的,
//	* 而析构函数一般是自动调用的
//	*/
//	
//
//	//string类的默认成员函数 -- 赋值"="运算符重载函数:
//	//string::operator= (string类赋值"="运算符重载函数)
//	/*
//	* 第1种: string& operator= (const string& str);
//	* 第2种: string& operator= (const char* s);
//	* 第3种: string& operator= (char c);
//	* 
//	* 第1种是支持string字符串类对象进行赋值;
//	* 第2种是支持字符串(直接写出字符串)进行赋值;
//	* 第3种是支持单个字符进行赋值
//	*/
//	//string类第1种赋值方法:
//	s1 = s2; //string字符串类对象进行赋值
//	cout << "赋值=运算符重载函数1:" << s1 << endl;
//
//	//string类第2种赋值方法:
//	s1 = "world"; //字符串(直接写出字符串)进行赋值
//	cout << "赋值=运算符重载函数2:" << s1 << endl;
//
//	//string类第3种赋值方法:
//	s1 = 'x'; //单个字符进行赋值
//	cout << "赋值=运算符重载函数3:" << s1 << endl;
//	
//	
//	return 0;
//}


namespace ggdpz
//防止和std中的string命名冲突:
{
	//string类(自己的):
	class string
	{
	private: //私有成员变量:

		char* _str; //字符数组(字符串)指针
		size_t _size; //字符数组大小(长度)
		size_t _capacity; //字符数组容量

		/*
		* 可以简单想象string类的私有成员变量
		* 就是这几个
		*/
	};
}


//string类的遍历和访问:
int main()
{
	string s1("hello world");
	/*
	* string本质是个字符数组,
	* 只不过通过类封装在一起,
	* 如果想要遍历string字符串的话有两种方法:
	* 
	* 遍历的第1种方法:下标 + []
	* 我们访问数组的时候会使用到方括号"[]",
	* string的底层是数组实现的,所以会对"[]"进行重载,
	* 即operator[],使用string类的operator[]后,
	* 就可以像访问数组一样访问字符串(字符数组)了
	*/

	//string类的成员函数 -- 下标"[]"运算符重载函数:
	//string::operator[]
	/*
	* 第1种: char& operator[] (size_t pos);
	* 第2种: const char& operator[] (size_t pos) const;
	* 
	* pos:访问string字符串对象pos下标的
	* char&:访问pos下标字符后返回该字符的引用("别名"),
	*		 如果是普通对象则可以修改该字符
	* 
	* 第2种是第1种的重载版本
	*/

	//要获取string类对象的长度有两种方法:
	//第一种方法:size()
	cout << s1.size() << endl; 
	//第二种方法:length()
	cout << s1.length() << endl; 
	/*
	* size() 和 length() 都是返回字符串对象s1的长度,
	* 至于同个功能取两个名字,是因为历史发展的关系,
	* 对于字符串,长度其实使用length()会更合理,
	* 但由于string产生得比STL早,STL出来前string只有length(),
	* 当STL出来后,对像set(树)这种数据结构length(长度)就不太合适了,
	* STL设置接口的时候又需要一定的统一性,length又不能统一使用,
	* 所以又设置了size(大小),所以string类中又有了size(),
	* 之后计算string类时使用size()即可,按照STL的标准来
	* 
	* size() 和 length() 计算string类时不会计算"\0"
	*/
	
	//第一种遍历方法:使用for循环遍历字符串:
	for (size_t i = 0; i < s1.size(); i++)
		//s1.size() 就可以返回string类对象s1的长度
	{
		/*
		* 第1种: char& operator[] (size_t pos);
		*/
		//遍历打印string类对象s1的字符:
		cout << s1[i] << " ";
		//cout << s1.operator[](i) << " "; //等于上面的代码
		/*
		* 这里遍历string类时使用了下标运算符"[]",
		* 让string类可以像遍历数组一样被遍历,
		* 实际string字符串类对象s1调用了下标"[]"运算符重载函数,
		* s1[i] 即 s1.operator[](i)
		*/
	}
	cout << endl; //换行


	/*
	* 使用下标符[]可以读数据,
	* 还可以用它来写数据,跟数组类似,
	* 因为operator[]调用后返回的是char&,
	* 是一个引用,所以字符串使用[]可以直接写(修改)数据
	*/
	s1[0] = 'x';
	cout << s1 << endl;


	//逆置string字符串:
	int begin = 0; //字符串左边界(下标)
	int end = s1.size() - 1; //字符串右边界(下标)
	/*
	* 右边界即字符串最后一个字符下标,
	* 使用size()获得字符串长度,
	* 字符串长度 - 1,即最后一个字符下标
	*/

	while (begin < end) 
		/*
		* begin < end,
		* 说明字符串中还有字符能够逆置
		*/
	{
		//1、通过创建临时变量实现两值交换:
		/*
		* //创建临时变量存储左边界:
		* char tmp = s1[begin];
		*
		* //将右边界字符赋给左边界字符:
		* s1[begin] = s1[end];
		*
		* //将左边界字符赋给右边界字符:
		* s1[end] = tmp;
		*/

		//2、通过C++自带的swap交换函数实现两值交换:
		swap(s1[begin], s1[end]);
		/*
		* C++自带交换函数swap,
		* 因为C++中有了函数模板,
		* 所以不用考虑实际类型的问题,
		* 从而实现了通用的swap交换函数,
		* 使用swap函数需要包含<utility>头文件,
		* 但是这里该头文件间接包含了,
		* 所以不用再显式写出来
		*/

		//进行迭代:
		++begin; //调整左边界
		--end; //调整右边界
	}

	cout << s1 << endl;



	//第二种遍历方法:使用迭代器iterator遍历字符串:
	string::iterator it = s1.begin();
	/*
	* iterator定义在类域中,但它不是内部类,是一个类型,
	* 现在还不熟悉迭代器,它的用法类似指针,
	* 但迭代器不一定是指针
	* 
	* it可以理解成指向字符串(字符数组)首字符的指针,
	* begin() 和 end() 迭代器区间是"左闭右开"的,
	* begin()可以理解成指向字符串首字符的指针,
	* end()可以理解成指向最后一个有效字符的下一位字符('/0')的指针,
	*/
	while (it != s1.end())
	{
		*it += 1; //通过迭代器也可以写(修改)数据
		/*
		* it一开始指向字符串首字符,
		* *it解引用指针后就可以修改该位置的字符了,
		*/
		cout << *it << " "; //打印当前it指针的字符
		++it; //调整it指针位置

		/*
		* it实际可能是指针,也可能不是指针,
		* 可以发现iterator迭代器实际运作方式,
		* 就像是用指针的方式进行字符串遍历访问和修改
		*/

		/*
		* 下标运算符[],只能底层有一定连续的情况下使用,
		* 所以不是所有容器都能够支持。
		* 真正访问容器最方便主流的就是迭代器
		* (链式结构、树形、哈希结构 只能使用迭代器)
		* 
		* 而且各类容器调用迭代器的方式都是相同,
		* 会调用一个容器的迭代器,
		* 其它容器的迭代器也就会使用了
		*/
	}

	cout << endl;


	//对于字符串的逆置,在C++算法中也有,
	//直接调用即可:
	reverse(s1.begin(), s1.end());
	/*
	* 该算法名叫reverse,
	* 使用时传 开始 和 结束 的位置即可,
	* 无论什么容器,只要传迭代器区间给reverse,
	* 就可以实现逆置
	* 
	* 所以迭代器还可以配合算法使用(nb)
	* C++算法也是泛型编程,
	* 不是针对某个容器的迭代器实现的,
	* 函数模板,针对各个容器的迭代器实现
	*/
	cout << s1 << endl;

	return 0;
}


//迭代器:
int main()
{
	//vector类:
	vector<int> v;

	//尾插入数据:
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);

	//vector可以使用类遍历,
	//也可以使用迭代器遍历:
	vector<int>::iterator vit = v.begin();
	/*
	* begin() 会获取第一个位置的迭代器(左闭),
	* 迭代器的区间都是左闭右开的,
	* end() 会获取最后一个数据的下一个位置(右开)
	*/

	while (vit != v.end())
		//begin() 还未到 end() :
	{
		//解引用vit获取当前位置数据:
		cout << *vit << " ";
		++vit; //调整指针
	}

	cout << endl; //换行
	
	//reverse 也可以实现vector的逆置:
	reverse(v.begin(), v.end());



	//list类:
	list<double> lt;

	//尾插入数据:
	lt.push_back(1.1);
	lt.push_back(2.1);
	lt.push_back(3.1);
	lt.push_back(4.1);

	//使用迭代器遍历list:
	list<double>::iterator lit = lt.begin();

	while (lit != lt.end())
		//begin() 还未到 end() :
	{
		//解引用vit获取当前位置数据:
		cout << *lit << " ";
		++lit; //调整指针
	}

	cout << endl; //换行

	//reverse 也可以实现list的逆置:
	reverse(lt.begin(), lt.end());

	return 0;
}



int main()
{
	/*
	* 第2种: const char& operator[] (size_t pos) const;
	* 这个重载构造函数的隐藏this指针是用const修饰的,
	* 该函数主要是为了解决参数匹配的问题
	*/
	string s1{ "hello world" }; //非const对象
	const string s2{ "hello world" }; //const对象
	s1[0] = 'x'; //非const对象s1调用"[]"
	s2[1] = 'x'; //const对象s2调用"[]"
	/*
	* 非const对象s1调用的"[]"运算符重载函数:
	* 第1种: char& operator[] (size_t pos);
	* 
	* const对象s2调用的"[]"运算符重载函数:
	* 第2种: const char& operator[] (size_t pos) const;
	* (const:只读,不能修改)
	* 
	* s1也可以调用"第2种",非const调用const,
	* "可读可写" 变成 "只读",访问权限缩小是允许的,
	* 虽然都可以调用"第2种",但是"第2种"的返回值是const char&,
	* 非const对象调用后返回"只读"的引用(别名)就不合适,
	* 所以还需要实现"第1种" 
	*/

	/*
	* 对于const的string对象s2,因为"只读",
	* 所以不能像s1非const对象那样使用迭代器:
	*/
	//string::iterator it = s2.begin(); //编译错误

	//应该是const_iterator("只读")而不是iterator("可读可写"):
	string::const_iterator it = s2.begin();
	while (it != s2.end())
	{
		//*it += 1; //const对象数据无法被修改
		cout << *it << " ";
		++it; //迭代器it本身是可以修改的
	}
	cout << endl;
	/*
	* string类的迭代器中的 begin / end,
	* 其实现时也有两个版本:
	* 第1种:iterator begin();
	* 第2种:const_iterator begin() const;
	* 
	* 其中"第2种"const版本中,
	* 它是名字为:const_iterator,
	* 而不是在iterator前直接加const修饰,
	* 
	* 
	* 
	* const_iterator it 和 const iterator it:
	* 
	* const_iterator it 本质是修饰迭代器指向的数据,
	* 即 *it 不能被修改
	* 
	* const iterator it 修饰的是迭代器本身,
	* 迭代器本身不能被修改,即 it 不能被修改,
	* 要进行遍历的话,得调整it(it++),
	* 所以不能直接在iterator前加const进行修饰
	*/



	//容器都能支持范围for循环:
	for (auto e : s1)
		//依次取容器对象s1放入e中进行循环遍历:
	{
		cout << e << " ";
	}
	cout << endl;


	return 0;
}



一般在传参才会用到const对象:
(让对象在函数中使用时不会被改变)
//void func(const string& s)
//{
//	//第二种:const对象版本:
//	string::const_reverse_iterator it = s.rbegin();
//	//这里反向迭代器的const版本的类型很长,所以可以用auto进行省略:
//	auto it = s.rbegin();
//	
//	//进行反向遍历:
//	while (it != s.rend())
//	{
//		//"只读"
//		//*it1 = 'x'; //报错,无法'写'
//
//		//打印当前字符串s1中it指针指向的字符:
//		cout << *it << " ";
//		//调整it指针:
//		++it;
//	}
//	cout << endl; //换行
//}
//
//
//int main()
//{
//	//string类对象:
//	string s1("hello world");
//	/*
//	* string类对象的三种遍历方式:
//	* 1、下标 + [] :只适用于底层连续的容器
//	* 2、迭代器 :yyds,能配合算法使用
//	* 3、范围for :看似好用,实际是依靠迭代器实现的
//	*/
//
//	/*
//	* string的反向迭代器:std::string::rbegin
//	* 
//	* 第一种:reverse_iterator rbegin();
//	* 第二种:const_reverse_iterator rbegin() const;
//	* (rend 和 rbegin 类似)
//	* 
//	* 对于没有下标的情况,如链表,如果要进行倒着遍历,
//	* 就可以使用其反向迭代器,掌握了迭代器,
//	* 就掌握了所有容器的遍历、访问、修改
//	*/
//	  
//	//第一种:非const版本:
//	string::reverse_iterator it1 = s1.rbegin();
//	//进行反向遍历:
//	while (it1 != s1.rend())
//	{
//		//"可读可写"
//		//*it1 = 'x'; //'写'
//		
//		//打印当前字符串s1中it指针指向的字符:
//		cout << *it1 << " ";
//		//调整it指针:
//		++it1;
//	}
//	cout << endl; //换行
//
//	/*
//	* 打印:"d l r o w  o l l e h",
//	* 即实现了s1:"h e l l o  w o r l d"的反向遍历
//	*/
//
//
//	//第二种:const对象版本:
//	func(s1);
//	/*
//	* func函数接收const对象,
//	* s1不是const对象,传过去后权限缩小,
//	* "可读可写" 变成 "只读"
//	*/
//
//	return 0;
//}



//int main()
//{
//	//std::string::max_size
//	//(返回字符串可以达到的最大长度)
//
//	//无参字符串对象:
//	string s1;
//
//	//有参字符串对象:
//	string s2("hello world");
//
//	cout << s1.max_size() << endl;
//	cout << s2.max_size() << endl;
//	/*
//	* 无论是s1的无参,还是s2的有参,
//	* 打印两者的"max_size()"时,
//	* (32位系统)都是"214783647"(2^31),
//	* 即整型最大值的一半,
//	* 也就是说此时这两个字符串能达到的最大长度为2^31,
//	* 这是在VS2022上,不同编译器可能不同,
//	* 不同系统也不同(x64/x86),
//	* 实际也不一定就开了2^31个字符的空间
//	*/
//
//
//
//	//std::string::reserve
//	/*
//	* (为字符串预留空间,可以进行扩容操作)
//	* 注意和revserve进行区分,
//	* reserve:保留
//	* reverse:反转、逆置
//	*/
//	s1.reserve(s1.max_size());
//	//保留max_size个空间
//
//	//std::string::capacity
//	//(实际能够存储的有效字符个数)
//	cout << s1.capacity() << endl; //"15"
//	cout << s2.capacity() << endl; //"15"
//	/*
//	* string s1;
//	* string s2("hello world");
//	*
//	* s1 和 s2 的capacity打印时都是"15",
//	* 即实际可存储的有效字符个数为15,
//	* 但实际是有16个空间的,有一个空间给了"\0",
//	* "\0"不算有效字符,而是标识字符,
//	* 所以capacity打印时为"15"
//	*/
//
//
//
//	//std::string::resize
//	//(将有效字符的个数分割成n个,多出的空间用字符c填充)
//	/*
//	* reserve:只影响容量,不会影响数据;
//	* resize:即影响容量,也影响数据
//	* 
//	* resize有两种实现:
//	* 1、void resize(size_t n);
//	* 2、void resize(size_t n, char c);
//	* 
//	* resize的使用分三种情况:
//	*/
//
//	string s1("hello world");
//	//起始字符串大小(长度)-- 11
//	cout << s1.size() << endl;
//	//起始容量 -- 15
//	cout << s1.capacity() << endl; 
//
//	//第一种情况:resize分割数 > capacity
//	//	(即影响容量,又影响数据)
//	//size为11,capacity为15,分割数为100:
//	s1.resize(100); 
//	
//	//分割后字符串大小 -- 100
//	cout << s1.size() << endl; 
//	//分割后容量 -- 111
//	cout << s1.capacity() << endl; 
//	/*
//	* 当 resize分割数 > capacity 时,
//	* 分割后capacity容量会增容到至少比分割数大,
//	*(这里 capacity--15 就变成至少比 分割数--100 大的111)
//	* 
//	* 而字符串数据 size 从15变成了100,
//	* 那其余"85"的数据是什么数据呢?
//	* 
//	* 这里的resize是第一种实现:void resize(size_t n);
//	* 使用resize这种实现,且 resize分割数 > capacity 时,
//	* 多出的数据就会用 空字符(初识字符)--'/0' 插入,
//	* 这里的'/0'就不是标识字符了,而是有效字符了,
//	* '/0'是哪种字符,取决于'/0'是否在size范围中,
//	* 这里增容后,size从15扩大到100并用'/0'插入充当多余数据,
//	* 此时"数据'/0'"就不是结束符而是有效数据了
//	* 
//	* 如果是第二种实现:void resize(size_t n, char c);
//	* 相同情况下,多出的数据就会用传过来的 字符c 插入
//	* 
//	* resize分割数 > capacity ---- "扩容 + 尾插"
//	*/
//
//
//	string s2("hello world");
//	//起始字符串大小(长度)-- 11
//	cout << s2.size() << endl;
//	//起始容量 -- 15
//	cout << s2.capacity() << endl;
//
//	//第二种情况:size < n < capacity
//	//(只会改变字符串大小size)
//	//size为11,capacity为15,分割数n为12:
//	s2.resize(12);
//
//	//分割后字符串大小 -- 12
//	cout << s2.size() << endl;
//	//分割后容量 -- 15
//	cout << s2.capacity() << endl;
//	/*
//	* 在VS中,reserve不会缩容,resize也不会缩容,
//	* 所以容量还是15,不会改变,
//	* 字符串数据size还是和第一种情况类似,
//	* size < 分割数n,数据size就增加到b,
//	* 并用'/0'或'字符c'插入充当多余数据,
//	*(插入的数据取决于resize是哪种实现)
//	* 
//	*(g++ 和 VS 的resize在这种情况下是一样的)
//	* size < n < capacity ---- "尾插(容量足够)"
//	*/
//
//
//	string s3("hello world");
//	//起始字符串大小(长度)-- 11
//	cout << s3.size() << endl;
//	//起始容量 -- 15
//	cout << s3.capacity() << endl;
//
//	//第三种情况:分割数n < size
//	//(只会改变字符串大小size)
//	//size为11,capacity为15,分割数n为5:
//	s3.resize(5);
//
//	//分割后字符串大小 -- 5
//	cout << s3.size() << endl;
//	//分割后容量 -- 15
//	cout << s3.capacity() << endl;
//	/*
//	* 和第二种情况一样,
//	* 因为capacity容量足够,
//	* 所以不会改变空间大小,
//	* 只对数据进行分割
//	*(g++中和VS也是一样的)
//	* 
//	* 分割数n < size ---- "删除数据,保留分割数n个"
//	*/
//
//	/*
//	* 总结:
//	* resize一定会对数据大小size进行操作,
//	* 对容量capacity可能会进行增容操作
//	*(g++ 和 VS 两个主流平台中)
//	* 在第一种情况中可以 增加数据,
//	* 在第三种情况中可以 删除数据
//	* 所以resize的作用就是:
//	* 1、插入数据(如果空间不够还会扩容)
//	* 2、删除数据
//	* 更多场景下是用于开空间并初始化
//	*/
//}

//int main()
//{
//	//reserve 和 capacity 的使用:
//
//	//无参字符串对象:
//	string s1;
//	//有参字符串对象:
//	string s2("hello world");
//
//	//使用reserve进行空间预留:
//	s1.reserve(500);
//	/*
//	* 如果我们知道大概需要多少空间,
//	* 则可以使用reserve提前开好空间,
//	* 不需要进行后面的扩容操作,
//	* 打印结果:"15、511"
//	* 容量直接一次性扩容到足够存储500个有效字符
//	* (实际512个空间)
//	*/
//
//	//通过capacity检车string的扩容机制:
//	size_t old = s1.capacity(); //s1此时容量
//	cout << old << endl; 
//
//	for (size_t i = 0; i < 100; i++)
//	{
//		//循环依次,尾插一个字符:
//		s1.push_back('x');
//
//		if (old != s1.capacity())
//			/*
//			* 如果 old 和当前容量不同,
//			* 说明old容量被扩容了
//			*/
//		{
//			//打印扩容后容量:
//			cout << s1.capacity() << endl;
//			//更新old容量:
//			old = s1.capacity();
//		}
//	}
//	/*
//	* 如果没有使用reserve进行空间预留,则会进行扩容操作:
//	* 
//	* 打印结果:"15、31、47、70、105"
//	* 容量从15变成31在变成47......
//	* 从整体来说,是按当前容量的1.5倍进行扩容
//	* 
//	* 不同编译器容量和扩容操作不同:
//	* VS -- "15、31、47、70、105" -- 1.5倍扩容
//	* gcc -- "0、1、2、4、8、16......" -- 2倍扩容
//	*/
//
//	/*
//	* reserve在VS上只会增容;
//	* 
//	* 在g++上除了增容,还可以缩容,
//	* 但缩容不会影响数据,只会影响空间,
//	* 即最多缩容到现存数据大小的空间
//	*(缩容不会删除数据,最小缩到size)
//	*/
//}


int main()
{
	
	return 0;
}
相关推荐
放逐者-保持本心,方可放逐3 分钟前
node.js 入门级基础应用
开发语言·node.js
顾北川_野15 分钟前
android 默认关闭增强型4GLTE开关;去掉VT视频通话功能及菜单
android·开发语言
TPCloud39 分钟前
快速利用c语言实现线性表(lineList)
c语言·开发语言·线性表·linelist
如意.7591 小时前
【C++】—— map 与 set 深入浅出:设计原理与应用对比
开发语言·c++
起名字真南1 小时前
【C++】深入理解自定义 list 容器中的 list_iterator:迭代器实现详解
数据结构·c++·windows·list
容器( ु⁎ᴗ_ᴗ⁎)ु.。oO1 小时前
java中的定时器
java·开发语言
m0_547486661 小时前
一道C语言关于距离的期末题及答案
c语言·开发语言
蹊黎1 小时前
C++模版初阶
开发语言·c++
山川尔尔_1 小时前
JS手写-this绑定实现
开发语言·javascript·ecmascript
DongGei1 小时前
c++多线程
c++