【C++初阶】九、STL容器中的string类(上)
=========================================================================
相关代码gitee自取:
=========================================================================
接上期:
【C++初阶】八、初识模板(泛型编程、函数模板、类模板)-CSDN博客
=========================================================================
目录
补充 -- operator= :(赋值“=”运算符重载函数)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
一 . 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+end 和 rbegin+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; }