终于到了STL了,c++标准模板库,这个才是c++和c的最大区别,也是c++的终极boss。刷c++这个副本刷到这里,也快结束了,这个这个副本的最大的一个boss。打败了这个boss就可以获得技能书了。(c++技能书)

8.1 STL理论基础

8.1.1 STL概念

STL(standard Template library,标准模板库),是惠普实验室开发的一系列软件统称。现在只要出现在c++中,但是引入c++之前该技术已经存在很久了。

STL从广义上分:容器(container)算法(algorithm)迭代器(iterator),容器和算法之间通过迭代器进行无缝连接。STL几乎所有代码都使用模板类和模板函数,这比传统的由函数和类组成的库提高了更好的代码重用机会。

STL具有高可重用性,高性能,高移植性,跨平台的优点:

  • 高可重用性(几乎所有代码都是使用类模板和函数模板)
  • 高性能(如map可以高效地从十万条记录里面查出指定的记录)
  • 高移植性()
  • 跨平台

下面的容器介绍都是从这个网站学习过来的,要想看详细信息,去这个网站学习学习。

http://c.biancheng.net/view/343.html">​​http://c.biancheng.net/view/343.html​​​

8.1.2 容器

容器(container)用于存放数据的类模板。可变长数组、链表、平衡二叉树等数据结构在 STL 中都被实现为容器。

程序员使用容器时,即将容器类模板实例化为容器类时,会指明容器中存放的元素是什么类型的。

容器中可以存放基本类型的变量,也可以存放对象。对象或基本类型的变量被插入容器中时,实际插入的是对象或变量的一个复制品。

顺序容器

顺序容器有以下三种:可变长动态数组 vector、双端队列 deque、双向链表 list。

它们之所以被称为顺序容器,是因为元素在容器中的位置同元素的值无关,即容器不是排序的。将元素插入容器时,指定在什么位置(尾部、头部或中间某处)插入,元素就会位于什么位置。

关联容器

关联容器有以下四种:set、multiset、map、multimap。关联容器内的元素是排序的。插入元素时,容器会按一定的排序规则将元素放到适当的位置上,因此插入元素时不能指定位置。

#include <vector>
int main(int argc, char **argv)
{
//定义一个容器v
vector<int> v;

//往容器添加数据
v.push_back(10);
v.push_back(20);
v.push_back(30);
v.push_back(40);

//容器的操作方法,不仅仅只有一个,有很多个,可以看看我昨天转载的博客
//里面就有这些方
/
return 0;
}

8.1.3 迭代器

要访问顺序容器和关联容器中的元素,需要通过“迭代器(iterator)”进行。迭代器是一个变量,相当于容器和操纵容器的算法之间的中介。迭代器可以指向容器中的某个元素,通过迭代器就可以读写它指向的元素。从这一点上看,迭代器和指针类似。

迭代器按照定义方式分成以下四种。


  1. 正向迭代器,定义方法如下:
    容器类名::iterator 迭代器名;



  2. 常量正向迭代器,定义方法如下:
    容器类名::const_iterator 迭代器名;



  3. 反向迭代器,定义方法如下:
    容器类名::reverse_iterator 迭代器名;



  4. 常量反向迭代器,定义方法如下:
    容器类名::const_reverse_iterator 迭代器名;



迭代器的功能分类

不同容器的迭代器,其功能强弱有所不同。容器的迭代器的功能强弱,决定了该容器是否支持 STL 中的某种算法。例如,排序算法需要通过随机访问迭代器来访问容器中的元素,因此有的容器就不支持排序算法。

常用的迭代器按功能强弱分为输入、输出、正向、双向、随机访问五种,这里只介绍常用的三种。


  1. 正向迭代器。假设 p 是一个正向迭代器,则 p 支持以下操作:++p,p++,*p。此外,两个正向迭代器可以互相赋值,还可以用==和!=运算符进行比较。



  2. 双向迭代器。双向迭代器具有正向迭代器的全部功能。除此之外,若 p 是一个双向迭代器,则–p和p–都是有定义的。–p使得 p 朝和++p相反的方向移动。



  3. 随机访问迭代器。随机访问迭代器具有双向迭代器的全部功能。若 p 是一个随机访问迭代器,i 是一个整型变量或常量,则 p 还支持以下操作:
    p+=i:使得 p 往后移动 i 个元素。
    p-=i:使得 p 往前移动 i 个元素。
    p+i:返回 p 后面第 i 个元素的迭代器。
    p-i:返回 p 前面第 i 个元素的迭代器。
    p[i]:返回 p 后面第 i 个元素的引用。



此外,两个随机访问迭代器 p1、p2 还可以用 <、>、<=、>= 运算符进行比较。p1<p2的含义是:p1 经过若干次(至少一次)++操作后,就会等于 p2。其他比较方式的含义与此类似。

对于两个随机访问迭代器 p1、p2,表达式p2-p1也是有定义的,其返回值是 p2 所指向元素和 p1 所指向元素的序号之差(也可以说是 p2 和 p1 之间的元素个数减一)。

表1:不同容器的迭代器的功能

容器

迭代器功能

vector

随机访问

deque

随机访问

list

双向

set / multiset

双向

map / multimap

双向

stack

不支持迭代器

queue

不支持迭代器

priority_queue

不支持迭代器

下面写个简单的例子:

#include <vector>
int main(int argc, char **argv)
{
vector<int> v; //定义一个容器v

//往容器添加数据
v.push_back(10);
v.push_back(20);
v.push_back(30);
v.push_back(40);

//使用迭代器遍历
//1.直接用迭代器遍历
for(vector<int>::iterator it = v.begin(); it != v.end(); it++) {
cout << *it << " "; //*it 就代表着是迭代器里面的内容,迭代器就像一个指针
}
cout << endl;

for(int i=0; i<v.size(); i++) {
cout << v[i] << " "; //可以像数组那样遍历
}
cout << endl;
//2.先使用stl函数for_each,这个函数功能等下讲

return 0;
}

这里我学习写的遍历就两种把,其他的看卡上面的链接,很多内容的,还有迭代器的辅助函数,不能抄太多是吧,你们自己去看。

8.1.4 算法

STL 提供能在各种容器中通用的算法(大约有70种),如插入、删除、查找、排序等。算法就是函数模板。算法通过迭代器来操纵容器中的元素。

许多算法操作的是容器上的一个区间(也可以是整个容器),因此需要两个参数,一个是区间起点元素的迭代器,另一个是区间终点元素的后面一个元素的迭代器。例如,排序和查找算法都需要这两个参数来指明待排序或待查找的区间。

这里我自己找了个函数举例:(终于不要担心说抄袭了,哈哈哈)

//我在algorithm头文件中找到一个比较简单执行迭代器模板函数
//并且我很高兴,可以看到这个模板,不过那些多余的东西,我也看不懂,不过可以直接忽略
//_Frist _Last 是传递迭代器的,开始指针,和结束指针。
//_Func 传递的是操作函数,函数内部不做处理,需要传递一个回调函数处理

// FUNCTION TEMPLATE for_each
template <class _InIt, class _Fn>
_CONSTEXPR20 _Fn for_each(_InIt _First, _InIt _Last, _Fn _Func) { // perform function for each element [_First, _Last)
_Adl_verify_range(_First, _Last);
auto _UFirst = _Get_unwrapped(_First);
const auto _ULast = _Get_unwrapped(_Last);
for (; _UFirst != _ULast; ++_UFirst) {
_Func(*_UFirst);
}

return _Func;
}

下面是使用这个函数模板:

#include <vector>
#include <algorithm>
int main(int argc, char **argv)
{

vector<int> v; //定义一个容器v

//往容器添加数据
v.push_back(10);
v.push_back(20);
v.push_back(30);
v.push_back(40);

//使用迭代器遍历
//1.直接用迭代器遍历
for(vector<int>::iterator it = v.begin(); it != v.end(); it++) {
cout << *it << " "; //*it 就代表着是迭代器里面的内容,迭代器就像一个指针
}
cout << endl;

for(int i=0; i<v.size(); i++) {
cout << v[i] << " "; //可以像数组那样遍历
}
cout << endl;


//2.先使用stl函数for_each,这个函数功能等下讲
vector<int>::iterator i_begin = v.begin();
vector<int>::iterator i_end = v.end();
//之前忽略了一点,模板函数不需要填<>里的参数,编译器会根据参数,自己生产一个新的函数,然后提高匹配调用
for_each<>(i_begin, i_end, show);
for_each(i_begin, i_end, show);

return 0;
}

8.1.5 练习一下迭代器

1.容器存放person类的指针,并且遍历出来

2.容器嵌套容器 一个容器作为另一个容器的元素(这个没写完)

using namespace std;

class Person
{
public:

Person(char *name, int age)
{
//this->name = name;
strcpy(this->name, name);
this->age = age;
}

void show() {
cout << "名字" << name << "年龄" << age << endl;
}

char name[64];
int age;
private:
};

//家族类
class Family
{
public:
char name[60];
private:
};

/**
* @brief 创建test对象
* @param
* @retval
*/
#include <vector>
#include <algorithm>
int main(int argc, char **argv)
{
//这是存储一个普通的类对象
vector<Person> v;

Person p1("mama", 40);
Person p2("me", 22);
Person p3("lisi", 22);
Person p4("zhaowu", 22);

v.push_back(p1);
v.push_back(p2);
v.push_back(p3);
v.push_back(p4);

for(vector<Person>::iterator it = v.begin(); it != v.end(); it++){
(*it).show();
}

//下面是存储类对象的指针
vector<Person*> p_v;

Person *p_p1 = new Person("mama", 40);
Person *p_p2 = new Person("me", 22);
Person *p_p3 = new Person("lisi", 22);
Person *p_p4 = new Person("zhaowu", 22);

p_v.push_back(p_p1);
p_v.push_back(p_p2);
p_v.push_back(p_p3);
p_v.push_back(p_p4);

for(vector<Person*>::iterator it = p_v.begin(); it != p_v.end(); it++){
(*it)->show();
}

delete p_p1;
delete p_p2;
delete p_p3;
delete p_p4;

//容器嵌套容器 一个容器作为另一个容器的元素
vector<vector<Person>> v3;



return 0;
}

8.2 string容器

8.2.1 string的特性

我们讲的第一个容器是string,就是字符串的容器,我们在c语言中,描述字符串都是用字符数组或者char* 来表示字符串,下面来看看char *和string的比较:

  1. char *是一个指针,string是一个类
  2. string封装了很多方法。(越用越爽的方法)
  3. 不用考虑内存释放和越界。(都是string管理和维护)

8.2.2 string构造函数

#include <string>
int main(int argc, char **argv)
{
string s1;
string s2(10, 'a');
string s3("hello c++");
string s4(s3); //拷贝构造

cout << s1 << endl;
cout << s2 << endl;
cout << s3 << endl;
cout << s4 << endl;


return 0;
}

看代码就懂,多说无益

c++学习笔记(八、STL基础、string、vector)_c++

8.2.3 string赋值语句

int main(int argc, char **argv)
{
string s1;
string s2(10, 'a');

s1 = "hello c++";
cout << s1 << endl;
s1 = s2;
cout << s1 << endl;
s1 = 'a';
cout << s1 << endl;
//成员方法
s1.assign("jj");
cout << s1 << endl;

//cout << s1 << endl;
// cout << s2 << endl;
// cout << s3 << endl;
// cout << s4 << endl;


return 0;
}

string把=操作符重载了,可以直接赋值。

c++学习笔记(八、STL基础、string、vector)_STL基础_02

8.2.4 string取值语句

#include <string>
int main(int argc, char **argv)
{
string s1;
string s2(10, 'a');

s1 = "hello c++";

for(int i=0; i<s1.size(); i++) {
cout << s1[i] << " ";
}
cout << endl;

for(int i=0; i<s1.size(); i++) {
cout << s1.at(i) << " ";
}
cout << endl;


//cout << s1 << endl;
// cout << s2 << endl;
// cout << s3 << endl;
// cout << s4 << endl;


return 0;
}

取值操作的时候,c++也把[]操作符重载了,所以可以直接用[]取值,at这个是函数方法取值,这两个取值的区别就是:

[]取值 如果访问越界了 就直接挂了

at方式取值,访问越界,会抛异常out_of_range

8.2.5 string拼接

#include <string>
int main(int argc, char **argv)
{
string s1;
string s2(10, 'a');

s1 = "hello";

s1 += "c++";
cout << s1 << endl;
s1 += s2;
cout << s1 << endl;

s1.append("haha");
cout << s1 << endl;

string s3 = s1 + s2; //可以两个字符串相加
cout << s3 << endl;

return 0;
}

结果:

c++学习笔记(八、STL基础、string、vector)_string_03

下面是拼接的函数集:

c++学习笔记(八、STL基础、string、vector)_c++_04

8.2.6 string查找和替换

#include <string>
int main(int argc, char **argv)
{
string s1;
string s2(10, 'a');

s1 = "hello c++";

int pos = s1.find("c++");
cout << pos << endl;

return 0;
}

函数集:

c++学习笔记(八、STL基础、string、vector)_string_05

8.2.7 string比较操作

不写了偷懒。

c++学习笔记(八、STL基础、string、vector)_string_06

8.2.8 string获取子字符串

c++学习笔记(八、STL基础、string、vector)_vector_07

8.2.9 string插入和删除

c++学习笔记(八、STL基础、string、vector)_vector_08

想不到一个string字符串这么多函数,不过用法都挺简单的,我们看下一容器。

8.3 vector容器

8.3.1 vector介绍

vector 是顺序容器的一种。vector 是可变长的动态数组,支持随机访问迭代器,所有 STL 算法都能对 vector 进行操作。要使用 vector,需要包含头文件 vector。

vector容器是单口容器,push_back pop_back都是在末端操作,这样就减少容器内部数据的移动,提高效率。

vector动态增长的原理,当原来空间不足的时候,要重新申请一个比原来空间的大小X2的空间,然后把原来的数据拷贝到新的空间里,释放原理空间的内存,基本所有的动态数组都是这样实现扩容。(Java的也是)

8.3.2 vector构造函数

void show(vector<int>& v) {
for(vector<int>::iterator it = v.begin(); it != v.end(); it++) {
cout << *it << " ";
}
cout << endl;
}

#include <vector>
int main(int argc, char **argv)
{
//vector构造函数
vector<int> v1; //默认构造
int arr[] = {10, 20, 30, 40};
vector<int> v2(arr, arr + sizeof(arr)/sizeof(arr[0]));
vector<int> v3(v2.begin(), v2.end());
vector<int> v4(v3);

show(v1);
show(v2);
show(v3);
show(v4);

return 0;
}

上面写了几种构造方法,结果:

c++学习笔记(八、STL基础、string、vector)_c++_09

下面是函数集:

c++学习笔记(八、STL基础、string、vector)_string_10

8.3.3 vector常用赋值操作

void show(vector<int>& v) {
for(vector<int>::iterator it = v.begin(); it != v.end(); it++) {
cout << *it << " ";
}
cout << endl;
}

#include <vector>
int main(int argc, char **argv)
{
//vector赋值操作
vector<int> v1;
int arr[] = {10, 20, 30, 40};
vector<int> v2(arr, arr + sizeof(arr)/sizeof(arr[0]));
vector<int> v3;
int arr1[] = {100, 200, 300, 400};
vector<int> v4(arr1, arr1 + sizeof(arr1)/sizeof(arr1[0]));
//成员方法
v1.assign(v2.begin(), v2.end());
//重载=操作符
v3 = v2;
//v4和v1交换
v4.swap(v1);

show(v1);
show(v2);
show(v3);
show(v4);

return 0;
}

感觉都差不多,swap方式等下会有妙用,底层实现的交换,其实只是交换了两个指向内存的指针而已,结果:

c++学习笔记(八、STL基础、string、vector)_STL_11

函数集:

c++学习笔记(八、STL基础、string、vector)_string_12

8.3.4 vector大小操作

这个操作就不写了,看看就懂了,哈哈哈。

c++学习笔记(八、STL基础、string、vector)_c++_13

8.3.5 vector数据存储

这几个跟string差不多,不写了,vector也支持直接[]操作。

c++学习笔记(八、STL基础、string、vector)_STL基础_14

8.3.6 vector插入和删除

void show(vector<int>& v) {
for(vector<int>::iterator it = v.begin(); it != v.end(); it++) {
cout << *it << " ";
}
cout << endl;
}

#include <vector>
int main(int argc, char **argv)
{
//vector赋值操作
vector<int> v1;
int arr[] = {10, 20, 30, 40};
vector<int> v2(arr, arr + sizeof(arr)/sizeof(arr[0]));

//添加操作
v2.push_back(55); //直接插到末尾

v2.insert(v2.begin(), 33); //这个按位置插入,这个这个位置是迭代器的指针
v2.insert(v2.end(), 77); //尾插

v2.insert(v2.begin() + 2, 99); //vector支持随机访问

//支持下标操作的,一般都支持随机访问
//也就是迭代器可以直接 +2 +3 -2 -4操作

show(v2);

//删除操作
v2.pop_back(); //尾部删除

v2.erase(v2.begin()); //指定位置删除,这个是删除头

show(v2);

v2.erase(v2.begin() + 2, v2.end()); //删除一个区域

show(v2);

v2.clear();

show(v2);

return 0;
}

上面代码都把操作过了一遍,下面是运行的结果:

c++学习笔记(八、STL基础、string、vector)_STL_15

函数集:

c++学习笔记(八、STL基础、string、vector)_STL_16

8.3.7 巧用swap收缩vector空间

vector容器在我们添加元素的时候,是容量是不断增长的,如果我们删除元素之后,容量会不会减少呢?下面我们看看例子:

#include <vector>
int main(int argc, char **argv)
{
vector<int> v1;

//不断的增长元素
for(int i=0; i<10000; i++) {
v1.push_back(i);
}

cout << "size = " << v1.size() << endl;
cout << "capacity = " << v1.capacity() << endl;

//突然减少元素
v1.resize(10);

cout << "size = " << v1.size() << endl;
cout << "capacity = " << v1.capacity() << endl;


return 0;
}

结果会怎么样呢?

c++学习笔记(八、STL基础、string、vector)_STL_17

很不幸的说,元素减少,容量不会减少,这样的话,好像浪费了很多的空间,不过我们也有巧门,来减少容量。

//收缩空间
vector<int>(v1).swap(v1);

cout << "size = " << v1.size() << endl;
cout << "capacity = " << v1.capacity() << endl;

就加了这一句代码,就可以成功的减少了空间,我们先来看结果:

c++学习笔记(八、STL基础、string、vector)_string_18

是不是空间减少了,原理呢?

其实也挺简单的,就是可能自己想不到,vector(v1).swap(v1);这句代码,我们先分开

vector(v1) 这一部分是说明要申请一个vector的匿名对象,然后调用拷贝构造函数,用v1做模板,重新生成一个和v1一样的匿名对象,然后这个匿名对象和真正的v1进行交换,也就是真正的v1的指针已经指向了匿名对象的那块内存了,匿名对象的指针指向1万多的空间的内存,然后匿名对象没有被使用,就被释放掉了,跟着匿名对象被释放的是那1万多的内存,这时候的v1的内存就是新开辟的那块,所以用这种方法,确实很巧妙。

8.3.8 reserve申请空间

resize 和 reserve区别:

reserve是容器预留的空间,但在空间内不真正创建元素对象,所以在没有添加新的对象之前,不能引用容器内的元素。

resize 是改变容器的大小,且在创建对象,调用这个函数之后,就可以引用容器内的对象了。

因为在上面我们不断的添加元素的时候,容器会因为容量不足,进而不断申请内存,申请完内存之后,又要拷贝,拷贝完了,又要释放掉原来的内存,这样一折腾,是不是感觉效率很低。所以如果我们知道了容器大概要存储的元素个数,那么我们就可以用reserve预留空间,而不用在添加的时候去折腾。 调用方法看上面的大小操作。