🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉
1、什么是STL
STL(standard template libaray-标准模板库):是C++标准库的重要组成部分,不仅是一个可复用的组件库,而且 是一个包罗数据结构与算法的软件框架。
1.1、STL的六大组成
2、string
2.1、string文档介绍
默认成员函数
迭代器
容量相关的
C语言中:string
串
字符数组,可以扩容,可以增删改查。(更符合的信息都是字符串存的,比如身份证,名字,地址**...**)
数据和方法分离(空间和使用)
C++中:string
2.2、string的构造
提供了7个
2.3、string类对象的常见构造
就前3个常用,其余的不常用。
带参构造、无参构造、拷贝构造
关于npos
size_t表示无符号整形,所以npos为FFFF FFFFH
2.4、string类对象的容量操作
知识点
operator[]
底层:
一个指针,一个大小,一个容量(用来扩容)。
2.5、我们如何遍历自定义类型的字符串呢?
方法1:size( ) + operator[ ]
这里自定义类型s1可以像数组一样访问,而且访问越界有报错。
cppvoid test_string3() { string s1("hello world"); //计算s1大小 cout << s1.size() << endl; //访问字符串 // 方法1:operator[] //自定义类型s1,可以像数组一样访问 //可读,可写,可修改 //可读 for (size_t i = 0; i < s1.size(); i++) { //写全 //cout << s1.operator[i] << " "; cout << s1[i] << " "; } cout << endl; s1[0] = 'x'; cout << endl; //可修改 for (size_t i = 0; i < s1.size(); i++) { s1[i]++; } }
放法2:迭代器
先看代码👇👇👇
cppvoid test_string4() { string s1("hello world"); //iterator定义在类域里,只能在类域中搜索。 string::iterator it1 = s1.begin(); while (it1 != s1.end()) { cout << *it1 << " "; ++it1; } cout << endl; }
代码中出现begin( ) 和 end( )
如何理解呢?
解析:
我们可以把begin和end看作指针 ,**begin指第一个字符,end指最后一个字符的下一个位置。**结合代码,begin把第一个字符的地址给给it1,然后解引用it1取到值,++it1走到下一个位置,知道遇到end()就停止。
注意:
\0是表示字符,不是有效字符
[ begin,end ) 为左闭右开区间,
对比方法一和方法二:
方法一只适合下标访问,空间必须是连续的,所以只适用于string;
方法二迭代器是主流的访问方式,
举例链表使用迭代器访问
总结:
begin( ):任何容器返回第一个数据位置的iterator
end( ):任何容器返回最后数据的下一个位置的iterator
方法3:范围for(所有容器都支持)
cpp for (auto e : s1) { cout << e << " "; } cout << endl;
自动取s1里的值,赋值给e,自动++,自动判断结束。
底层角度,就是迭代器
注意:
这里是*it赋值给给e,赋值拷贝,并不会影响数据。
2.6迭代器iterator
普通迭代器 和const迭代器
我们先看一下const迭代器
如图,这里是一个const string,就不能用之前那种方式遍历了,
因为const string 是只读的,这里我们调用
修改代码:👇👇👇
总结:
iterator 可读可写
const_iterator 只读
普通的迭代是给普通的string用的
const的是给const对象用的,保护string不能修改数据,可以遍历
这里我们再深究一下:
为什么const_iterator这洋写,而不是const iterator
因为const iterator 保证的是迭代器本身不能写
而const_operator 保证的是迭代器指向的数据不能写
反向迭代器
均只读
代码:👇👇👇
cppstring s2("hello world"); string::reverse_iterator it2 = s2.rbegin(); while (it2 != s2.rend()) { cout << *it2 << " "; ++it2; } cout << endl;
底层重载operator++来实现自定义类型的++。
迭代器总结
共有4中迭代器
iterator const_iterator reverse_iterator const_reverse_iterator
差异就在读和写部分
iterator 可读可写
const_iterator只读
reverse_iterator 只读
const_reverse_iterator 只读
2.7string插入字符
push 和 append
插入字符
但这两种方式都不常用
还是喜欢用重载运算符
2.7.1assign
assign:赋值字符串,但赋值通常用operator=,运算符重载。
这里我们浅浅了解一下,熟悉一下看文档。
2.7.2insert、erase和repalce
insert插入
string的insert慎用,
因为时间复杂度很大,效率低。
库中没有给出头插一个字符怎某写,
这里我们这样:👇👇👇
cppchar ch = 'y'; cin >> ch; s2.insert(0, 1, ch);//头插一个 cout << s2 << endl;
insert的使用:👇👇👇
cppvoid test_string8() { string s1("hello world"); cout << s1 << endl; s1.assign("11111"); cout << s1 << endl; string s2("hello world"); s2.insert(0, "xxxx"); cout << s2 << endl; char ch = 'y'; cin >> ch; s2.insert(0, 1, ch);//头插一个 cout << s2 << endl; s2.insert(s2.begin(), 'y'); cout << s2 << endl; s2.insert(s2.begin(), s1.begin(), s1.end());//把s1的头到尾,插入s2 cout << s2 << endl; }
erase和replace
erase:
慎用,效率低。
replace:
把我的一部分替换
erase的使用👇👇👇
cppvoid test_string9() { string s1("hello world"); cout << s1 << endl; s1.erase(0, 1);//去掉第一个字符 cout << s1 << endl; s1.erase(5, 100);//去掉5 - 100,后边没有也不报错, cout << s1 << endl; //replace效率不高,慎用,和insert类似,需要挪动数据 string s2("hello world"); s2.replace(5, 1, "%20"); cout << s2 << endl; }
说到替换,那把所有空格都替换怎某实现呢?
代码👇👇👇
cpp//把空格全部替换成%20 //方法一:循环替换,但时间复杂度大 string s3("hello world hello bit"); for (size_t i = 0; i < s3.size();) { if (s3[i] == ' ') { s3.replace(i, 1, "%20"); i += 3;//跳过%20这三个字符 } else { i++; } } cout << s3 << endl; //方法二:以空间换时间, string s4("hello world hello bit"); string s5; for (auto ch : s4) { if (ch != ' ') { s5 += ch; } else { s5 += "%20"; } } cout << s5 << endl;
这里提供了俩方法:
1.循环替换
2.另辟蹊径
我们这里不推荐方法一循环替换,为什么呢?
因为时间复杂度非常大,方法二以时间换空间。
看到这里,我们小试牛刀,练练手。
小试牛刀
.仅仅反转字母(双指针)
.字符串中的第一个唯一一个字符(映射)
.验证回文串(回文:对称),题目:只判断字母
2.7.3max_size()
字符串的最大长度(用的不多),因为类型开不出这样大的空间
2.7.4reserve和resize
reserve改变capacity
resize改变size,也可以改变capacity
reserve:扩容(提前开好空间,不适用于缩容)
在vs中不能缩容(看编译器)
cppstring s1("11111111111"); string s2("1111111111111111111111111111111"); cout << s1.capacity() << endl; s1.reserve(100); cout << s1.capacity() << endl; s1.reserve(20); cout << s1.capacity() << endl;
reserve的使用
cppvoid TesePushBack() { string s; s.reserve(200); s[100] = 'x'; }
开了空间是不能直接赋值的,因为[ ]在底层调用operator [ ],会调用size,而reverse只改变capacity,所以这时还需要初始化size
resize的使用
cppvoid test_string11() { string s1; s1.resize(5); s1[4] = '3'; s1[3] = '4'; s1[2] = '5'; s1[1] = '6'; s1[0] = '7'; }
resize不会填充前面已有数据,会在后面补
cppstring s2("hello world"); s2.resize(20, 'x');
2.7.5 at
越界,异常可以捕获。
cppstring s2("hello world"); //s2[30]; //s2.at(30); try { s2.at(30); } catch (const exception& e) { cout << e.what() << endl; }
s2[30] 直接报错,
s2.at(30)会捕获异常
operator是暴力型的,at是温柔型的。
2.7.6 c_str()
文件读取
cpp#define _CRT_SECURE_NO_WARNINGS 1 string file("Test.cpp"); FILE* fout = fopen(file.c_str(), "r");//获取底层指向char的那个指针。 char ch = fgetc(fout); while (ch != EOF) { cout << ch; ch = fgetc(fout); }
2.7.7 find()和rfind()
find()
查找字符
这里掌握第一个和第四个
第一个:查找一个striing,pos位置
第四个:从pos位开始找,一个字符
find() : 正在查找rfind():倒着查找
3.vs和g++下string结构的说明
3.1vs下
注意:下述结构是在32位平台下进行验证,32位平台下指针占4个字节。
vs下string的结构 string总共占28个字节,内部结构稍微复杂一点,先是有一个联合体,联合体用来定义string中字。符串的存储空间:
当字符串长度小于16时,使用内部固定的字符数组来存放
当字符串长度大于等于16时,从堆上开辟空间
这种设计也是有一定道理的,大多数情况下字符串的长度都小于16,那string对象创建好之后,内部已经有了16个字符数组的固定空间,不需要通过堆创建,效率高。
其次:还有一个size_t字段保存字符串长度,一个size_t字段保存从堆上开辟空间总的容量
最后:还有一个指针做一些其他事情。
3.2g++下
G++下,string是通过写时拷贝实现的,string对象总共占4个字节,内部只包含了一个指针,该指针将来指向一块堆空间,
内部包含了如下字段:
·空间总大小
·字符串有效长度
·引用计数
指向堆空间的指针,用来存储字符串。
🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉
last but not least,创作不易,望读者三连三连三连支持💖
重要的事情说三遍💖