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

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

相关代码gitee自取

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

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

接上期

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

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

                     

目录

一 . STL简介

什么是STL

STL的版本

HP 原始版本:

P.J. 版本:

RW 版本:

SGI 版本:

STL的六大组件

图示:


二 . string类

C语言中的字符串

C++标准库中的string类

string类(了解)

总结:

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

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

图示 -- 第1个构造函数:

图示 -- 第2、3个构造函数:

图示 -- 第4、5个构造函数:

图示 -- 第6、7个构造函数:

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

string类对象的容量操作:

使用注意事项:

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

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

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

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

范围for循环 --  完成字符串遍历:


本篇博客相关代码:

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

         

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

             

一 . STL简介

什么是STL

                    

STLstandard template libaray -- 标准模板库):

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

                     

                     


                    

STL的版本

                   

HP 原始版本:

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

                          

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

                       

P.J. 版本:

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

                          

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

                       

RW 版本:

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

                          

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

                       

SGI 版本:

 Silicon Graphics Computer Systemsinc公司开发继承自HP版本
GCCLinux采用可移植性好可公开可修改甚至贩卖
命名风格编程风格上看阅读性非常

                     

                     


                    

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);填充nc字符
创建的字符串对象中
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 + endbegin获取第一个字符迭代器
end获取最后一个字符下一个位置迭代器
rbegin + rendbeing + 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++文件:

#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;
}