将capacity容量缩至合适,一般不会缩小到和size
一样大,可能会比size大一点。
int main() { string s2("Hello C++!"); cout << "最初的s2.size():" << s2.size() << endl; cout << "最初的s2.capacity():" << s2.capacity() << endl; s2.reserve(100);//扩容 cout << "reserve(100)后的s2.size():" << s2.size() << endl; cout << "reserve(100)后的s2.capacity():" << s2.capacity() << endl; s2.shrink_to_fit();//缩容 cout << "缩容后的s2.size():" << s2.size() << endl; cout << "缩容后的s2.capacity():" << s2.capacity() << endl; return 0; }
2.3 与对象访问及遍历有关的操作
int main() { string s1("Hello C++!");//普通对象 for (size_t i = 0; i < s1.size(); i++) { cout << s1[i]; } cout << endl << "修改后:"; for (size_t i = 0; i < s1.size(); i++) { cout << ++s1[i];//因为返回值是引用所以可以用[]对其进行修改 } cout << endl; const string s2("Hello World!"); for (size_t i = 0; i < s2.size(); i++) { cout << s2[i]; } cout << endl; return 0; }
小Tips:因为operator[]的返回值是一个引用,所以可以通过[]加下标的方式去访问和修改string类对象,和内置类型的数组不同,这里的s1[i]本质上是去调用函数,而内置类型的数组使用[]本质是解引用。如果发生越界访问,程序会直接报错,at接口和operator[]接口的功能类似,只不过at接口在发生越界访问的时候会抛出异常。
📖迭代器
迭代器是一种抽象的设计概念,它提供一种方法,使之能够依序巡访某个容器所含的各种元素,而又无需暴露该容器的内部表述方式。迭代器是一种行为类似指针的对象,它是容器与算法的桥梁,算法需要去访问容器中的数据,但是容器的数据都是私有的,并且有多种容器,针对不同的容器,某一算法的具体实现可能不同,例如对链表逆置和对顺序表逆置,具体过程当然是不同的,但是迭代器的出现却将它们统一了起来,对于一个算法,无论是什么容器,只需要将它的迭代器区间传过来即可,算法只使用统一的逻辑。因此任何容器的迭代器类型都用iterator来表示,它是一个类的内置类型,通过typedef得到,关于迭代器更具体地内容我将在后续模拟实现的文章中为给大家分享,今天我们只需要知道如何使用即可。
📖begin、end
int main() { string s1("Hello C++!");//普通对象 string::iterator it = s1.begin(); while (it < s1.end()) { cout << *it;//解引用迭代器 it++;//++迭代器,让迭代器向后走 } cout << endl; it = s1.begin(); while (it < s1.end()) { ++(*it);//通过迭代器去修改 it++; } it = s1.begin(); while (it < s1.end()) { cout << *it; it++; } cout << endl; return 0; }
从上面的代码可以看出,一个迭代器对象和一个指针类型的变量十分相似,都可以通过*解引用,并且都可以++,还可以解引用后去修改。但本质上对迭代器的这些操作都是通过运算符重载来实现的,具体实现我将在后续文章中为大家介绍。
小Tips:迭代器区间永远都是左闭右开,迭代器类型作为类的内置类型可以直接通过类名::iterator访问,例如:string::iterator就表示string类里面的迭代器类型。普通迭代器可读可写,const迭代器限制的是其指向的内容,只能读不能写,而const迭代器本身可以修改。
📖rbegin、rend
int main() { string s1("Hello C++!");//普通对象 string::reverse_iterator it = s1.rbegin(); while (it < s1.rend()) { cout << *it;//解引用迭代器 it++;//++迭代器,让迭代器向后走 } cout << endl; return 0; }
小Tips:reverse_iterator一般被叫做反向迭代器,因为它可以倒着去遍历。
📖范围for
int main() { string s1("Hello C++!");//普通对象 for (auto it : s1) { cout << it; } cout << endl; for (auto& it : s1)//用引用就可以进行修改 { it--; } for (auto it : s1) { cout << it; } cout << endl; return 0; }
小Tips:范围for就是基于迭代器实现的,在底层范围for会转化成正向迭代器。换言之,一个容器如果不支持迭代器,那它必定也不支持范围for。
2.4 与对象修改有关的操作
赋值运算符重载是string类的一个默认成员函数,该函数有三个重载形式,如下图所示:
int main() { string s1("Hello C++!"); string s2("你好,C++!"); cout << s2 << endl;//原始的s2 s2 = s1;//string类对象 cout << s2 << endl;//第一次赋值后的s2 s2 = "春人."; cout << s2 << endl;//第二次赋值后的s2 s2 = 'a'; cout << s2 << endl;//第三次赋值后的s2 return 0; }
将一个字符c追加到string类对象的末尾,它的长度增加1。
int main() { string s1("Hello C++!"); cout << "追加前:" << s1 << endl; s1.push_back('s'); cout << "追加后:" << s1 << endl; return 0; }
append是在源字符串的后面进行追加操作的成员函数,它有七种重载实现形式,如下图所示:
int main() { string s1("Hello C++!"); string s2("aaaa"); cout << "追加前:" << s2 << endl; s2.append(s1); cout << "追加一个string对象:" << s2 << endl; s2 = "aaaa"; s2.append(s1, 6, 3); cout << "追加一个string对象的一部分:" << s2 << endl; s2 = "aaaa"; s2.append("你好"); cout << "追加一个C类型的字符串:" << s2 << endl; s2 = "aaaa"; s2.append("Hello!", 2); cout << "追加一个C类型字符串的前两个字符:" << s2 << endl; s2 = "aaaa"; s2.append(5, 'b'); cout << "追加五个字符b:" << s2 << endl; s2 = "aaaa"; s2.append(s1.begin()+2, s1.begin()+4); cout << "追加一个迭代器区间:" << s2 << endl; return 0; }
通过重载运算符+=实现追加,该运算符重载有三种重载实现形式,如下图所示:
int main() { string s1("Hello C++!"); string s2("aaaa"); cout << "追加前:" << s2 << endl; s2 += s1; cout << "追加一个string类对象:" << s2 << endl; s2 = "aaaa"; s2 += "bcde"; cout << "追加一个C类型的字符串:" << s2 << endl; s2 = "aaaa"; s2 += 'o'; cout << "追加一个字符:" << s2 << endl; return 0; }
小Tips:除了上面介绍的一些常用的字符串修改接口外,还有一些不太常用的,例如:assign(内容替换)、insert(指定位置插入)、erase(删除)、replace(部分替换)、swap(交换两个字符串)。它们的使用方法都大同小异。
2.5 与查找有关的接口
该接口的返回值类型是const char*,即返回一个C格式的字符串,该接口起到桥梁作用。
int main() { string s2("Hello C++!"); const char* str = s2.c_str(); cout << str << endl; return 0; }
📖find
从字符串的pos位置开始往后查找字符或字符串,返回其在当前字符串中的位置。
int main() { string s1("https://www.csdn.net/?spm=1011.2124.3001.4476"); size_t pos1 = s1.find("csdn"); cout << pos1 << endl; size_t pos2 = s1.find("www.csdn.net", 7, 3); cout << pos2 << endl; return 0; }
小Tips:一般在没有找到的情况下会返回npos,即整型最大值。
在字符串pos位置开始往前查找字符或字符串,返回其在当前字符串中的位置。
int main() { std::string str("The sixth sick sheik's sixth sheep's sick."); std::string key("sixth"); std::size_t found = str.rfind(key); if (found != std::string::npos) str.replace(found, key.length(), "seventh"); std::cout << str << '\n'; return 0; }
📖substr (size_t pos = 0, size_t len = npos)
在源字符串中,从pos位置开始,截取n个字符,以string的形式返回。
int main() { string s1("https://www.csdn.net/?spm=1011.2124.3001.4476"); size_t pos1 = s1.find("csdn"); string s2 = s1.substr(pos1, 8); cout << s2 << endl; return 0; }
小Tips:除了上面介绍的一些常用接口,还有一些不常用的,比如:find_first_of(在字符串中搜索与其参数中指定的任何字符匹配的第一个字符)、find_last_of(查找最后一个匹配的)、find_first_not_of(查找第一个不匹配的)、find_last_not_of(查找最后一个不匹配的)。
2.6 string类的非成员函数
有些运算符重载函数存在竞争左操作数的问题,所以它们写在string类的外面,是类的非成员函数,主要有下面几种:
函数 | 功能说明 |
operator+ | 尽量少用,因为是传值返回,会进行深拷贝导致效率降低 |
operator>> | 输入运算符重载 |
operator<< | 输出运算符重载 |
relational operators | 大小比较运算符重载 |
getline | 获取一行字符串 |
小Tips:operator>>和getline的区别在于,前者遇到空格' '和换行\n会截止,而后者默认只有遇到换行\n才截止,因此当我们需要从键盘读取一个含有空格的字符串是,只能用getline。
2.7 与类型转换有关的接口
📖string类型转成其他内置类型
📖to_string将内置类型转成string类型
# 三、编码问题
我们平时使用的 string 本质上是通过对类模板 basic_string 用字符型 char 实例化得到的。
搞成模板的原因是为了兼容其他字符类型字符串的管理。即字符不只有 char 类型,还有 wchar_t 、char16_t、char32_t等,它们的区别在于存储一个字符所需要的字节数不同。这些不同类型的字符本质上和编码有关,我们目前C/C++中接触最多的是 ASCII 编码,它是用一个字节来存储一个字符。编码产生的本质是,计算机底层只认识二进制01序列,并不认识这些字符,因此要将字符存到计算机里面就需要建立一个字符与二进制01的对应关系,这个这关关系也被叫做映射表,也就是我们所说的编码表,而 ASCII 表是老美搞出来表示它们国家常用字符的映射表,大多是一些字母和符号。为了表示我们国家的文字,我们国家出台了 gbk 编码,此外国际上还有 unicode 编码(万国码)。
🎁结语:
今天的分享到这里就结束啦!如果觉得文章还不错的话,可以三连支持一下,您的支持就是春人前进的动力!