STL之string学习

网友投稿 231 2022-08-23

STL之string学习

写在前面

我们总算是接触到C++的STL了,这是C++模块中很重要的一部分,可以这么说,如果你学C++没有学过STL,那么你的C++很大概率是残缺的,我们学习STL主要学习两个层次,第一个是可以使用,第二个是知道它们的底层,至于更高层次的,现在的我还没有达到,就不和大家分享了。我们先来简单的认识一下STL,有一个大致的了解,今天主要的内容时string。

什么是STL

STL(standard template libaray-标准模板库):是 C++ 标准库的重要组成部分,不仅是一个可复用的组件库,而且是一个包罗数据结构与算法的软件框架。我之前和大家分享过数据结构的知识,也和大家用C语言写过,但是它们的可用性有点低,你在写OJ题时,使用C语言,你会发现有的时候还要自己实现数据结构,大佬们也是有这样的困扰,所以出现STL,使用的便是泛型编程,这也是我们前面谈模板的原因。

STL版本

我们学习STL共有三个版本,里面的功能都大致一样。

原始版本 Alexander Stepanov、Meng Lee 在惠普实验室完成的原始版本,这是最初的STLP. J. 版本 继承HP版本,被微的VS系列使用,代码可读性底SGI版本 继承自HP版 本。被GCC(Linux)采用,可移植性好,阅读性非常高。我们后面学习STL要阅读部分源代码,主要参考的就是这个版本。

STL 组件

STL的六大组件,大家直接看着图片吧,后面的内容都会涉及到,这里大家先来认识一下。

string

我们先来认识一下什么是string,简单来说,string在C语言里我们认识的字符串,在OJ中,有关字符串的题目基本以string类的形式出现,而且在常规工作中,为了简单、方便、快捷,基本都使用string类,很少有人去使用C库中的字符串操作函数 。由于我们比较常用,所以大佬们就把这个给变成了模板类了,某种意义上来说,string不属于STL,它的出现要早的多。

认识string

先看看STL里面关于string的分类,我们可能会感到疑惑,这里怎么会有四种string,我们要学习的是哪一种,SLT不是模板吗,为何这里string看着不像…这些问题我们一个一个解决.

字符编码表

在谈为何会有四种string之前?我们需要谈谈什么是字符编码表.我们都知道在计算机世界中,它们只认识0和1,但是现实世界中,我们是有类似汉语,英语的语言的,我们该如何把现实世界和计算机世界给联系起来呢?这就是字符编码表的作用.最初的字符编码表是由美国提出的,叫做ASCII码,是不支持中文的.

为了编码属于各自国家的字符表,中国编出了GBK,使用的是16位的二进制,但是各国还没有一个统一的标准,这时候国际组织ISO制定了Unicode,也叫万国码.

我们已经知道了字符表的历史,所谓的四种string就是为了支持不同位的字符,是的,我们不是只有char这一个字符类型,我们主要学习的就似乎string,他们四个用法大概差不多,学会了一个,另外几个查查文档就可以了.

int main(){ cout << "string : " << sizeof(char) << endl; cout << "u16string : " << sizeof(char16_t) << endl; cout << "u32string : " << sizeof(char32_t) << endl; cout << "wstring : " << sizeof(wchar_t) << endl; return 0;}

string 是模板码

是的,我们看到的string是typedef过的,也就是它是参数为char类型的模板.

string 的底层

string的底层是一个可以动态开辟的字符数组,这一点大家一定要记住,后面我们模拟实现的时候就是按照这个来的.

int main(){ string s("hello"); return 0;}

我们来真正的看看这个数组,避免出现问题.

使用string

现在我们就可以使用string,我们使用的时候需要包头文件,而且还需要放出std里面的string,少说多做,我们看看就明白了.

#include #include using std::string;int main(){ std::string s1; // 或者直接放出 string s2; return 0;}

构造函数

现在我们就可以正式接触string里面的内容了,注意C98中string里面存在七个构造函数,但是我们不是每一个都常用,这里我来简绍的有四个.

构造函数

函数说明

string()

构造一个空字符串

string(const string& str)

拷贝构造,深拷贝

string(const char* s)

通过一个字符串来构造

string(size_t n, char c)

构造一个含有 n 个 c字符的字符串

我们现在分别来演示一下.

string()

int main(){ string s; return 0;}

string(const char* s)

int main(){ string s("hello string"); return 0;}

string(size_t n, char c)

int main(){ string s(10,'a'); return 0;}

string(const string& str)

int main(){ string s1("hello"); string s2(s1); return 0;}

长度 & 容量

我们知道了string底层是一个动态开辟的数组,那么string支持了计算我们string对象的有效长度和容量的函数我们直接看看吧.

size() & length()

这两个都是计算string对象的有效长度的,唯一的区别就是函数名不同罢了,length()是最初的方法,我们字符串的有效长度可以形容为length,但是后面的哈希表等就有点说不过去了,所以增加了size()这个函数,为了保持命名的规范性,仅此而已.

int main(){ string s("hello"); cout << s.size() << endl; cout << s.length() << endl; return 0;}

capacity()

计算是当前对象的容量,也就是数组的长度.

int main(){ string s("hello"); cout << s.capacity() << endl; return 0;}

max_size()

这个函数就是我们字符串最大能开多长,一般是没有人用的,这里我就提一下就可以了.

int main(){ string s; cout << s.max_size() << endl; return 0;}

operator[] & at()

string重载了[]这运算符,可以支持下标访问,这里的at()的作用和[]作用是一样的,只不过是早期版本,功能上没有什么区别.

int main(){ string s("hello"); cout << s[1] << endl; cout << s.at(1) << endl; return 0;}

越界访问

我们访问的如果是有效字符的下一个位置,返回的是一个\0,大家现在还不知道底层,但是我们知道空字符串里面是包含一个\0 的,这是为了兼容C语言.

int main(){ string s("hello"); if (s[5] == '\0') { cout << "s[5] == \\0 " << endl; } return 0;}

但是如果我们越界访问了,VS编译器会出现报错.

int main(){ string s("hello"); s[16]; return 0;}

遍历string

遍历string我们存在三种方法,这三种方法也是我们经常用到的。

使用 下标使用 迭代器 这个我们重点谈使用 范围for

### 下标访问

string这个类是重载了[]这个运算符的,再加上string底层是一个数组,它是支持随机访问的,

int main(){ string s("hello"); for (size_t i = 0; i < s.size(); i++) { cout << s[i] << " "; } cout << endl; return 0;}

迭代器访问

或许你还对迭代器有点疑惑,不知道它是什么,这样吧,我简单的说一下,所谓的迭代器就是我们遍历STL最常用的方法.是的,你没有看错,不是所有的STL底层都是连续的空间,也就是说使用下标访问是不具有普遍性的.

string中的迭代器就是一个原生指针,其中迭代器又分为四种.我们来看看吧.

正向迭代器

这里我直接用两种方式来访问,一个是const修饰的,一个可修改的.

下面的是正向迭代器指向的地方,注意end()指向的有效字符的下一个位置.

可以修改迭代器指向的内容

int main(){ string s("hello"); string::iterator it = s.begin(); while (it != s.end()) { cout << *it << " "; it++; } cout << endl; return 0;}

如果我们想要修改迭代器指向的内容,这里就可以知道了.

int main(){ string s("hello"); string::iterator it = s.begin(); while (it != s.end()) { (*it)++; cout << *it << " "; it++; } cout << endl; return 0;}

如果我们不想修改迭代器指向的数据,可以直接调用const修饰的迭代器

这个迭代器的主要应用是给那些不想修改的string,我们看看应用.

void func(const string& str){ string::const_iterator it = str.cbegin(); while (it != str.cend()) { cout << *it << " "; it++; } cout << endl;}int main(){ string s("hello"); func(s); return 0;}

反向迭代器

谈完了正向的迭代器,这里就要谈谈什么是反向迭代器,它的作用也是遍历string,只不过是反这遍历的.

这个迭代器是可以修改的,这里就不修改了.

int main(){ string str("hello"); string::reverse_iterator rit = str.rbegin(); while (rit != str.rend()) { cout << *rit << " "; rit++; } cout << endl; return 0;}

现在还有可以反向的const修饰的迭代器,我们用用就可以了,具体的就不谈了.

int main(){ string str("hello"); string::const_reverse_iterator rit = str.crbegin(); while (rit != str.crend()) { cout << *rit << " "; rit++; } cout << endl; return 0;}

范围 for

范围 for就比较简单了,但是有一个缺陷,就是一次肯定遍历完

int main(){ string str("hello"); for (auto ch : str) { cout << ch << " "; } cout << endl; return 0;}

它看着比较高大上,实际上底层也是对迭代器的复用,这里的复用的迭代器是可以修改的那种

push_back()

string支持尾部插入一个字符,如果容量不够,编译器会自动扩容.

int main(){ string s; s.push_back('a'); s.push_back('b'); s.push_back('c'); s.push_back('d'); cout << s << endl; return 0;}

string 扩容机制

我们知道了string的底层是一个动态数组,我们现在想看看它是如何扩容的.

从这里我们可以看出,不同编译器的扩容规则是不一样的,VS下是1.5倍扩容,g++是2倍扩,而且刚开始VS就开辟了15个空间,g++没有开

reserve()

大家都知道,扩容是代价的,有的时候需要再次开辟空间和进行数组的拷贝,如果我们要是知道总共开辟的空间,那么提前开辟好不是更好吗,这就是这个函数的作用.

只改变容量不改变 size,也就是你可以理解为它是只扩容.

注意,reserve()函数开辟的空间不一定和我们要的一样,需要内存对齐的,但是大差不差.

int main(){ string s; s.reserve(100); return 0;}

但是这里就有两个疑问了,如果我们的N大于现在存在的存在的容量会怎么样,小于又会怎么样?这里我们来讨论一下.

N > capacity

这个和上面的一样,会自动扩容,只改变capacity,不改变size.

int main(){ string s("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); int size = s.size(); cout << size << endl; s.reserve(s.capacity() + 20); return 0;}

N < capacity

这种情况如果发生,不会出现任何变化.

int main(){ string s("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); cout << "原始 size " << s.size() << endl; cout << "原始 capacity() " << s.capacity() << endl; s.reserve(15); cout << "size " << s.size() << endl; cout << "capacity() " << s.capacity() << endl; return 0;}

resize()

这个函数的作用从名字上面就可以看出它是重置size,也就是说,我们重新指定尾插数据的地方,如果空间不够,编译器会自动扩容.但是这里有两个函数,本质上是一个缺省函数.

int main(){ string s; cout << "size " << s.size() << endl; cout << "capacity() " << s.capacity() << endl; s.resize(20); cout << "size " << s.size() << endl; cout << "capacity() " << s.capacity() << endl; return 0;}

void resize (size_t n)

这个是重置 N个空间,从开始的那里开始数,数到下标为N的地方,这个及其以后都重置的空间变成\0.这里分为三种情况.这里我们就来讨论两种共情况,第三种就是扩容的情况.

情况一

int main(){ string s; s.push_back('a'); s.push_back('b'); s.push_back('b'); s.push_back('b'); s.push_back('b'); s.resize(2); return 0;}

情况二

int main(){ string s; s.push_back('a'); s.push_back('b'); s.push_back('b'); s.resize(7); return 0;}

这里我就给一个总结,所谓的resize就是把有效元素的最后一个位置的后一个位置给改变,改成下标是N,凡是和原来的有效位置之间的距离,都给我变成\0.

void resize (size_t n, char c)

这个就更加简单了,没有什么可以说的,也符合上面的两种规则,也符合不够的扩容规则.就是把默认的\0改成我们想要的字符,这里就不解释其他的情况了.

int main(){ string s; s.push_back('a'); s.push_back('b'); s.resize(20,'1'); cout << s << endl; return 0;}

clear()

清理有效字符,VS下不会进行缩容,看编译器自己的选择.

int main(){ string s("hello"); s.reserve(100); s.clear(); // 只改变 size 缩容不缩容看编译器 return 0;}

append()

我们发现,push_back尾插是可以的,但是它支持插入一个字符,我们想要插入一个字符串,可不可以,string也提供了这个接口.我们把最长用的的几个个大家简绍一下.

append()

函数说明

string& append (const string& str);

尾插一个string对象

string& append (const char* s);

尾插一个字符串

template string& append (InputIterator first, InputIterator last);

使用迭代器尾插

我直接演示一下这几个函数吧,用法还是比较简单的,这里新增的迭代器尾插是我们没有见识的过的,这里单独用一下.

int main(){ string s("hello"); s.append("qkj"); cout << s << endl; string s2("你好啊"); s.append(so2); cout << s << endl; return 0;}

string& append (InputIterator first, InputIterator last)

这个还是挺容易的,它是尾插first到last的内容,没有什么神奇,到后面几个博客,我们会专门谈谈迭代器.

int main(){ string s("hello"); string s2("你好啊"); s.append(s2.begin(),s2.end()); cout << s << endl; return 0;}

operator+=

相对于上面的append,这个才是我们经常用的,而且更容易理解.

这个我们会经常用.

int main(){ string s("hello"); s += "aaaa"; cout << s << endl; s += 'c'; cout << s << endl; string s2("hahahah"); s += s2; cout << s << endl; return 0;}

insert()

一般情况下,我们是不用这个函数的,都是使用的尾插,但是为了让大家了解一下,这里都给大家拿出来,给大家简绍一下比较常用的.

insert()

函数说明

string& insert (size_t pos, const string& str);

在pos位置插入一个字符串

string& insert (size_t pos, const char* s);

iterator insert (iterator p, char c);

大家会用就可以了.

int main(){ string s1("hello"); string s2(" qkj"); s1.insert(5, s2); cout << s1 << endl; s1.insert(2, "xxxxxx"); cout << s1 << endl; string::iterator it = s1.begin(); s1.insert(it + 2, '1'); cout << s1 << endl; return 0;}

erase()

这个是删除下标从pos往后数len个字符,如果不传字符就删除 npos个,这里的npos是一个静态常量,数据很大.

int main(){ string s1("hello"); s1.erase(1,3); cout << s1 << endl; return 0;}

至于那个传迭代器的就不和大家演示了.

swap()

string里面是存在swap函数的,当然我们std中的那个也可以用,只不过需要发生深拷贝,效率有点低.

int main(){ string s1("hello"); string s2("qkj"); cout << s1 << endl; cout << s2 << endl; s1.swap(s2); cout << s1 << endl; cout << s2 << endl; return 0;}

c_str()

这个函数返回就是这个数组的指针,在工作中,有的是不接容string的指针,这里就要string里面的指针,现在可能还没有什么用处.

int main(){ string s1("hello"); const char* str = s1.c_str(); cout << str << endl; return 0;}

find()

find()是查找string里面是不是有我们想要的字符或者字符串,默认是从下标0开始的,返回的是字符的下标或者是字符串的的头位置,找不的话就是返回npos.

int main(){ string s1("hello"); size_t pos = s1.find('e', 3); // 从 下标 3 开始 if (pos != string::npos) { cout << s1[pos] << endl; } else { cout << "没有找到" << endl; } return 0;}

int main(){ string s1("hello"); size_t pos = s1.find("lo", 2); // 从 下标 3 开始 if (pos != string::npos) { cout << "找到了" << endl; } else { cout << "没有找到" << endl; } return 0;}

还有一个rfind()函数,这个是从string对象后面来查找,这里就不和大家分享了.

substr()

这个可以说字符串截断的函数,从pos位置开始截,len个字符,这里也是一个缺省函数.

int main(){ string s1("hello"); string s2 = s1.substr(1, 2); cout << s2 << endl; return 0;}

getline()

我们都知道,>>运算符遇到空格就结束了,这里对于string也是一样的.

int main(){ string s; cin >> s; cout << s << endl; return 0;}

这个函数不是string特有的,在std里面封者.

int main(){ string s; std::getline(cin,s); cout << s << endl; return 0;}

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:devops学习(一) 搭建gitlab代码仓库
下一篇:蒙牛全面启动品牌升级战略,开展全方位品牌营销行动!(蒙牛品牌战略分析)
相关文章

 发表评论

暂时没有评论,来抢沙发吧~