C++之STL

Posted ElevHe

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++之STL相关的知识,希望对你有一定的参考价值。

面向对象三大特性:封装、继承、多态

封装:复用性高的模块抽象出来,进行整理,作为一个整体,提高了代码的复用性

继承:子类继承父类,把父类中所有的属性和行为都获得,不用再次声明,也提高了代码的复用  

多态:一个函数名称有多个接口,由于对象不同,父类指针指向子类对象,对象创建的不同,调用一个接口获得的内容不同,会产生不同的形态 

STL 即标准模板库,从广义上分为:容器(container)  算法(algorithm)  迭代(iterator)

 容器算法之间通过迭代器进行无缝连接

 

STL 六大组件

容器、算法、迭代器、仿函数、适配器(配接器)、空间配置器

1、容器:各种数据结构,如vector、list、deque、set、map等,用来存放数据

2、算法:各种常用算法,如sort、find、copy、for_each等

3、迭代器:扮演容器与算法之间的胶合剂

4、仿函数:行为类似函数,可作为算法的某种策略

5、适配器:用来修饰容器或者仿函数或迭代器接口的东西

6、空间配置器:负责空间的配置与管理

 

一、容器

分为序列式容器关联式容器

序列式容器:强调值的排序,其中每个元素都有其固定的位置

关联式容器:二叉树结构,各元素间没有严格物理意义上的顺序关系

 

二、算法

算法分为质变算法与非质变算法

质变算法:运算过程中会更改区间内元素内容。如:拷贝,替换,删除等

非质变算法:运算过程中不改变区间内元素内容。如:查找、遍历、寻找极值等

 

三、迭代器

提供一种方法,使之能够依序寻访某个容器所含的各个元素,而又不会暴露该容器的内部表达方式

每个容器都有自己的专属迭代器。

迭代器种类:

1.输入迭代器2.输出迭代器3.前向迭代器4.双向迭代器5.随机访问迭代器

常用4、5

四、仿函数

仿函数(Functor)又称为函数对象(Function Object)是一个能行使函数功能的类。

通过重载括号运算符实现

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

STL 组件

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

STL之string认识_字符串

string

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

认识string

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

STL之string认识_编译器_02

字符编码表

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

STL之string认识_字符串_03

为了编码属于各自国家的字符表,中国编出了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;

STL之string认识_字符串_04

string 是模板码

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

STL之string认识_字符串_05

string 的底层

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

int main()

string s("hello");
return 0;

STL之string认识_字符串_06

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

使用string

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

#include <iostream>
#include <string>

using std::string;

int main()

std::string s1; // 或者直接放出
string s2;
return 0;

构造函数

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

STL之string认识_迭代器_07

构造函数

函数说明

string()

构造一个空字符串

string(const string& str)

拷贝构造,深拷贝

string(const char* s)

通过一个字符串来构造

string(size_t n, char c)

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

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

string()

int main()

string s;
return 0;

STL之string认识_编译器_08

string(const char* s)

int main()

string s("hello string");
return 0;

STL之string认识_迭代器_09

string(size_t n, char c)

int main()

string s(10,a);
return 0;

STL之string认识_迭代器_10

string(const string& str)

int main()

string s1("hello");
string s2(s1);
return 0;

STL之string认识_字符串_11

长度 & 容量

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

size() & length()

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

STL之string认识_编译器_12

int main()

string s("hello");
cout << s.size() << endl;
cout << s.length() << endl;
return 0;

STL之string认识_编译器_13

capacity()

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

int main()

string s("hello");
cout << s.capacity() << endl;
return 0;

STL之string认识_迭代器_14

max_size()

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

int main()

string s;
cout << s.max_size() << endl;
return 0;

STL之string认识_字符串_15

operator[\\] & at()

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

int main()

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

STL之string认识_编译器_16

越界访问

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

int main()

string s("hello");
if (s[5] == \\0)

cout << "s[5] == \\\\0 " << endl;

return 0;

STL之string认识_迭代器_17

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

int main()

string s("hello");
s[16];
return 0;

STL之string认识_迭代器_18

遍历string

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

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

### 下标访问

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

STL之string认识_字符串_19

int main()

string s("hello");
for (size_t i = 0; i < s.size(); i++)

cout << s[i] << " ";

cout << endl;
return 0;

STL之string认识_编译器_20

迭代器访问

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

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

STL之string认识_迭代器_21

正向迭代器

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

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

STL之string认识_字符串_22

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

int main()

string s("hello");
string::iterator it = s.begin();
while (it != s.end())

cout << *it << " ";
it++;

cout << endl;

return 0;

STL之string认识_迭代器_23

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

int main()

string s("hello");
string::iterator it = s.begin();
while (it != s.end())

(*it)++;
cout << *it << " ";
it++;

cout << endl;

return 0;

STL之string认识_字符串_24

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

STL之string认识_迭代器_25

这个迭代器的主要应用是给那些不想修改的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;

STL之string认识_迭代器_26

反向迭代器

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

STL之string认识_迭代器_27

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

int main()

string str("hello");
string::reverse_iterator rit = str.rbegin();
while (rit != str.rend())

cout << *rit << " ";
rit++;

cout << endl;
return 0;

STL之string认识_迭代器_28

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

int main()

string str("hello");
string::const_reverse_iterator rit = str.crbegin();
while (rit != str.crend())

cout << *rit << " ";
rit++;

cout << endl;
return 0;

STL之string认识_字符串_29

范围 for

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

int main()

string str("hello");
for (auto ch : str)

cout << ch << " ";

cout << endl;
return 0;

STL之string认识_迭代器_30

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

STL之string认识_迭代器_31

STL之string认识_字符串_32

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;

STL之string认识_迭代器_33

string 扩容机制

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

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

STL之string认识_字符串_34

reserve()

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

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

STL之string认识_编译器_35

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

int main()

string s;
s.reserve(100);
return 0;

STL之string认识_迭代器_36

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

N > capacity

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

int main()

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

STL之string认识_迭代器_37

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;

STL之string认识_编译器_38

resize()

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

STL之string认识_迭代器_39

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;

STL之string认识_编译器_40

void resize (size_t n)

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

STL之string认识_编译器_41

情况一

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;

STL之string认识_编译器_42

情况二

int main()

string s;
s.push_back(a);
s.push_back(b);
s.push_back(b);
s.resize(7);

return 0;

STL之string认识_编译器_43

这里我就给一个总结,所谓的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;

STL之string认识_编译器_44

clear()

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

int main()

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

STL之string认识_编译器_45

append()

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

STL之string认识_编译器_46

append()

函数说明

string& append (const string& str);

尾插一个string对象

string&a

以上是关于C++之STL的主要内容,如果未能解决你的问题,请参考以下文章

浅谈 C++之 STL

STL之string认识

C++之STL

C++ STL 之 常用算法

C++中STL学习笔记——容器之vector

C++中STL学习笔记——容器之vector

(c)2006-2024 SYSTEM All Rights Reserved IT常识