STL学习笔记

STL学习笔记

1. STL初识

1.1 STL的诞生

  • 长久以来,软件界一直希望建立一种可重复利用的东西
  • C++的面向对象编程和泛型编程思想,目的就是复用性的提升
  • 大多数情况下,数据结构和算法都未能有一套标准,导致被迫从事大量重复工作
  • 为了建立数据结构和算法的一套标准,诞生了STL

1.2 STL的基本概念

  • STL(Standard Template Library, 标准模板库)
  • STL从广义上分为:容器(container)算法(algorithm)迭代器(iterator)
  • 容器和算法之间通过迭代器进行无缝连接

1.3 STL的六大组件

STL大体分为六大组件,分别是:容器,算法,迭代器,仿函数,适配器(配接器),空间配置器

  1. 容器:各种数据结构,如vector、list、deque、set、map等,用来存放数据
  2. 算法:各种常用的算法,如sort、find、copy、for_each等
  3. 迭代器:扮演了容器与算法之间的胶合剂
  4. 仿函数:行为类似函数,可作为算法的某种策略
  5. 适配器:一种用来修饰容器或者仿函数或迭代器接口的东西
  6. 空间适配器:负责空间的配置与管理

1.4 STL中容器、算法、迭代器

容器就是将运用最广泛的一些数据结构实现出来

常用的数据结构:数组、链表、树、栈、队列、集合、映射表等

这些容器分为序列式容器关联式容器两种:

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

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

有限的步骤,解决逻辑或者数学上的问题,这一门学科我们叫做算法(Algorithms)

算法分为:质变算法和非质变算法

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

​ 非质变算法:是指运算过程中不会更改区间内的元素的内容,例如查找,计数,遍历,寻求极值等

迭代器:容器和算法之间的胶合剂

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

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

迭代器使用非常类似指针,初学阶段我们可以先理解迭代器为指针

2. STL常用容器

2.1 string容器

2.1.1 string基本概念

本质:

  • string是c++风格的字符串,而string本质上是一个类

string和char*区别:

  • char* 是一个指针
  • string是一个类,类内部封装了char,管理这个字符串,是一个char*型的容器

特点:

  • string类内部封装了很多成员方法
  • 例如:查找find,拷贝copy,删除delete,替换replace,插入insert
  • string管理char*所分配的内存,不用担心复制越界和取值越界等,由类内部进行负责
2.1.2 string常用API

构造函数原型:

string();
string(const char* s);
string(const string& str);
string(int n, char c);   //使用n个字符c初始化

赋值函数原型:

string& operator=(const char* s);
string& operator=(const string& s);
string& operator=(char c);
string& assign(const char* s);
string& assign(const char* s, int n); //把字符串s的前n个字符赋给当前的字符串
string& assign(const string& s);
string& assign(int n, char c);  //用n个字符c赋给当前字符串

字符串拼接函数原型:

string& operator+=(const char* s);
string& operator+=(const char c);
string& operator+=(const string& s);
string& append(const char* s);
string& append(const char* s, int n);
string& append(const string& s);
string& append(const string* s, int pos, int n); //字符串从pos开始的n个字符连接到当前字符串末尾

查找和替换函数原型

int find(const string& str, int pos = 0) const;
int find(const char* s, int pos = 0) const;
int find(const char* s, int pos, int n) const;  //从pos位置查找s的前n个字符第一次位置
int find(const char c, int pos = 0) const; 
int rfind(const string& str, int pos = npos);  //查找str的最后一次出现位置,从pos开始查找
int rfind(const char* s, int pos = npos);  //查找str的最后一次出现位置,从pos开始查找
int rfind(const char*& s, int pos, int n);  //从pos位置查找s的前n个字符最后一次位置
int rfind(const char c, int pos = 0);   //查找字符c的最后一次出现位置
string& replace(int pos, int n, const string& str);
string& replace(int pos, int n, const char* s);

比较函数原型

/*
compare函数在>时返回 1,<时返回 -1,==时返回 0。
比较区分大小写,比较时参考字典顺序,排越前面的越小。
大写的A比小写的a小。
*/
int compare(const string &s) const;//与字符串s比较
int compare(const char *s) const;//与字符串s比较

子串函数原型

string substr(int pos = 0, int n = npos) const;  //返回由pos开始的n个字符组成的字符串

插入和删除函数原型

string& insert(int pos, const char* s);
string& insert(int pos, const string& str);
string& insert(int pos, int n, char c);  //在指定位置插入n个字符c
string& erase(int pos = 0, int n = npos)  //删除从pos开始的n个字符
2.1.3 string和c-style字符串转换
//char* 转 string 
char* s = "it";
string str(s);
//string 转 char*
string str = "it";
const char* cstr = str.c_str();
在c++中存在一个从const char*到string的隐式类型转换,却不存在从一个string对象到C_string的自动类型转换。对于string类型的字符串,可以通过c_str()函数返回string对象对应的C_string.
通常,程序员在整个程序中应坚持使用string类对象,直到必须将内容转化为char*时才将其转换为C_string.

2.2 vector容器

2.2.1 vector容器基本概念

功能:

  • vector数据结构和数组非常相似,也称为单端数组

vector与普通数组区别:

  • 不同之处在于数组是静态空间,而vector可以动态扩展

动态扩展:
并不是在原空间之后续接新空间,而是找更大的内存空间,然后将原数据拷贝到新空间,最后释放原空间

在这里插入图片描述

2.2.2 vector常用API

构造函数原型

vector<T> v; //采用模板实现类实现,默认构造函数
vector(v.begin(), v.end());//将v[begin(), end())区间中的元素拷贝给本身。
vector(n, elem);//构造函数将n个elem拷贝给本身。
vector(const vector &vec);//拷贝构造函数。

//例子 使用第二个构造函数 我们可以...
int arr[] = {2,3,4,1,9};
vector<int> v1(arr, arr + sizeof(arr) / sizeof(int)); 

//这样得到的v1的数据为 2,3,4,1,9

赋值函数原型

assign(beg, end);//将[beg, end)区间中的数据拷贝赋值给本身。  参数实际上也是两个指针
assign(n, elem);//将n个elem拷贝赋值给本身。
vector& operator=(const vector  &vec);//重载等号操作符
swap(vec);// 将vec与本身的元素互换。

容器空间函数原型

size();//返回容器中元素的个数
empty();//判断容器是否为空
resize(int num);//重新指定容器的长度为num,若容器变长,则以默认值填充新位置。如果容器变短,则末尾超出容器长度的元素被删除。
resize(int num, elem);//重新指定容器的长度为num,若容器变长,则以elem值填充新位置。如果容器变短,则末尾超出容器长>度的元素被删除。
capacity();//容器的容量
reserve(int len);//容器预留len个元素长度,预留位置不初始化,元素不可访问。

数据存储函数原型

at(int idx); //返回索引idx所指的数据,如果idx越界,抛出out_of_range异常。
operator[];//返回索引idx所指的数据,越界时,运行直接报错
front();//返回容器中第一个数据元素
back();//返回容器中最后一个数据元素

插入和删除函数原型

insert(const_iterator pos, int count,ele);//迭代器指向位置pos插入count个元素ele.
push_back(ele); //尾部插入元素ele
pop_back();//删除最后一个元素
erase(const_iterator start, const_iterator end);//删除迭代器从start到end之间的元素
erase(const_iterator pos);//删除迭代器指向的元素
clear();//删除容器中所有元素
2.2.3 vector小demo

1. 巧用swap,收缩内存空间

vector<int> v;
for (int i = 0; i < 100000;i ++)
{
	v.push_back(i);	
}

cout << "capacity:" << v.capacity() << endl;
cout << "size:" << v.size() << endl;

//此时 通过resize改变容器大小
v.resize(10);

cout << "capacity:" << v.capacity() << endl;
cout << "size:" << v.size() << endl;

//容量没有改变
vector<int>(v).swap(v);

cout << "capacity:" << v.capacity() << endl;
cout << "size:" << v.size() << endl;
vector<int>(v)  
//实际上是一个通过vector拷贝构造函数得到的一个匿名对象,
//这个匿名对象通过调用swap函数与v交换元素,从而收缩了v的空间容量   

2. 利用reserve预留空间
功能描述:

  • 减少vector在动态扩展容量时的扩展次数

函数原型:

reserve(int len);
//容器预留len个元素长度,预留位置不初始化,元素不可访问
vector<int> v;

//预先开辟空间
v.reserve(100000);

int* p = NULL;
int count = 0;
for (int i = 0; i < 100000;i ++)
{
	v.push_back(i);
	if (p != &v[0])
    {
		p = &v[0];
		count++;
	}
}

cout << "count:" << count << endl;
由于vector容器动态扩展的原理是:先拷贝原有数据,重新分配新的空间并将原有数据复制到新的空间,释放原来空间
所以可以利用指针p的指向来判断vector容器扩展了多少次空间

2.3 deque容器

2.3.1 deque容器基本概念

功能:

  • 双端数组,可以对头端进行插入删除操作

deque与vector区别:

  • vector对于头部的插入删除效率低,数据量越大,效率越低
  • deque相对而言,对头部的插入删除速度比vector快
  • vector访问元素时的速度会比deque快,这和两者内部实现有关

在这里插入图片描述
deque内部工作原理:
deque内部有个中控器,维护每段缓冲区中的内容,缓冲区中存放真实数据
中控器维护的是每个缓冲区的地址,使得使用deque时像一片连续的空间
在这里插入图片描述

2.3.2 deque常用API

构造函数原型

deque<T> d;//默认构造形式
deque(beg, end);
//构造函数将[beg, end)区间中的元素拷贝给本身。
//begin和and本质是指针,可为迭代器
deque(n, elem);//构造函数将n个elem拷贝给本身。
deque(const deque &deq);//拷贝构造函数。

赋值函数原型

assign(beg, end);//将[beg, end)区间中的数据拷贝赋值给本身。
assign(n, elem);//将n个elem拷贝赋值给本身。
deque& operator=(const deque &deq); //重载等号操作符 
swap(deq);// 将deq与本身的元素互换

空间函数原型

deque.size();//返回容器中元素的个数
deque.empty();//判断容器是否为空
deque.resize(num);//重新指定容器的长度为num,若容器变长,则以默认值填充新位置。如果容器变短,则末尾超出容器长度的元素被删除。
deque.resize(num, elem); //重新指定容器的长度为num,若容器变长,则以elem值填充新位置,如果容器变短,则末尾超出容器长度的元素被删除。

双端插入和删除函数原型

push_back(elem);//在容器尾部添加一个数据
push_front(elem);//在容器头部插入一个数据
pop_back();//删除容器最后一个数据
pop_front();//删除容器第一个数据

数据存取函数原型

at(idx);//返回索引idx所指的数据,如果idx越界,抛出out_of_range。
operator[];//返回索引idx所指的数据,如果idx越界,不抛出异常,直接出错。
front();//返回第一个数据。
back();//返回最后一个数据

插入和删除函数原型

insert(pos,elem);//在pos位置插入一个elem元素的拷贝,返回新数据的位置。
insert(pos,n,elem);//在pos位置插入n个elem数据,无返回值。
insert(pos,beg,end);//在pos位置插入[beg,end)区间的数据,无返回值。
clear();//移除容器的所有数据
erase(beg,end);//删除[beg,end)区间的数据,返回下一个数据的位置。
erase(pos);//删除pos位置的数据,返回下一个数据的位置。

排序函数原型

sort(iterator beg, iterator end)//对beg和end区间内元素进行排序
//排序默认规则是从小到大 即升序
//要加入Algorithm头文件

2.4 stack容器

2.4.1 stack基本概念

概念: stack是一种先进后出的数据结构,它只有一个出口
在这里插入图片描述

栈中只有最顶端的元素才可以被外界访问,因此不允许遍历

栈中进入数据称为 — 入栈 push
栈中弹出数据成为 — 出栈 pop

2.4.2 stack常用API

stack构造函数

stack<T> stkT;//stack采用模板类实现, stack对象的默认构造形式: 
stack(const stack &stk);//拷贝构造函数

stack赋值操作

stack& operator=(const stack &stk);//重载等号操作符

stack数据存取操作

push(elem);//向栈顶添加元素
pop();//从栈顶移除第一个元素
top();//返回栈顶元素

stack大小操作

empty();//判断堆栈是否为空
size();//返回堆栈的大小

2.5 queue容器

2.5.1 queue容器基本概念

概念: queue容器是一种先进先出的数据结构,它有两个出口
在这里插入图片描述
队列容器允许从一端新增元素,从另一端移除元素
队列中只有队头和队尾才可以被外界使用,因此队列不允许有遍历行为
队列中进数据称为 — 入队
队列中出数据称为 — 出队

2.5.2 queue常用API

queue构造函数

queue<T> queT;//queue采用模板类实现,queue对象的默认构造形式:
queue(const queue &que);//拷贝构造函数

queue赋值操作

queue& operator=(const queue &que);//重载等号操作符

queue数据存取操作

push(elem);//往队尾添加元素
pop();//从队头移除第一个元素
back();//返回最后一个元素
front();//返回第一个元素

queue大小操作

empty();//判断队列是否为空
size();//返回队列的大小

2.6 list容器

2.6.1 list基本概念

功能:
将数据进行链式存储

链表(list)是一种物理存储单元上非连续的存储结构,数据元素的逻辑顺序是通过链表中的指针链接实现的

链表的组成:链表由一系列结点组成

结点的组成:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域

STL中的链表是一个双向循环链表

在这里插入图片描述
由于链表的存储方式并不是连续的内存空间,因此链表中的迭代器只支持前移和后移,属于双向迭代器

list的优点:

  • 采用动态存储分配,不会造成内存浪费和溢出
  • 链表执行插入和删除操作十分方便,修改指针指向即可,不需要移动大量元素

list的缺点:

  • 链表虽然灵活,但是空间(指针域)和时间(遍历)额外耗费比较大
  • List有一个重要的性质,插入和删除操作都不会造成原有list迭代器的失效,这在vector中是不成立的(vector的插入操作可能造成记忆体重新配置,导致原有的迭代器全部失效)

总结:STL中list和vector是两个最常用的容器,各有优缺点

2.6.2 list常用API

构造函数原型

list<T> lstT;//list采用采用模板类实现,对象的默认构造形式:
list(beg,end);//构造函数将[beg, end)区间中的元素拷贝给本身。
list(n,elem);//构造函数将n个elem拷贝给本身。
list(const list &lst);//拷贝构造函数。

元素插入和删除操作

push_back(elem);//在容器尾部加入一个元素
pop_back();//删除容器中最后一个元素
push_front(elem);//在容器开头插入一个元素
pop_front();//从容器开头移除第一个元素
insert(pos,elem);//在pos位置插elem元素的拷贝,返回新数据的位置。
insert(pos,n,elem);//在pos位置插入n个elem数据,无返回值。
insert(pos,beg,end);//在pos位置插入[beg,end)区间的数据,无返回值。
clear();//移除容器的所有数据
erase(beg,end);//删除[beg,end)区间的数据,返回下一个数据的位置。
erase(pos);//删除pos位置的数据,返回下一个数据的位置。
remove(elem);//删除容器中所有与elem值匹配的元素。

大小操作

size();//返回容器中元素的个数
empty();//判断容器是否为空
resize(num);//重新指定容器的长度为num
//若容器变长,则以默认值填充新位置
//如果容器变短,则末尾超出容器长度的元素被删除
resize(num, elem);//重新指定容器的长度为num
//若容器变长,则以elem值填充新位置
//如果容器变短,则末尾超出容器长度的元素被删除

赋值操作

assign(beg, end);//将[beg, end)区间中的数据拷贝赋值给本身。
assign(n, elem);//将n个elem拷贝赋值给本身。
list& operator=(const list &lst);//重载等号操作符
swap(lst);//将lst与本身的元素互换。

数据存取操作

front();//返回第一个元素。
back();//返回最后一个元素。

反转排序操作

reverse();//反转链表,比如lst包含1,3,5元素,运行此方法后,lst就包含5,3,1元素。
sort(); //list排序

关于sort排序算法:

  • 所有不支持随机访问的迭代器,都不可以使用标准算法
  • 不支持随机访问的迭代器,内部会提供一些相应算法
  • list容器提供的sort算法支持升序和降序两种排序方法

代码实现

list<int>L1;
L1.push_back(20);
L1.push_back(10);
L1.push_back(40);
L1.push_back(50);
L1.push_back(30);

//升序
L1.sort();

//降序
bool myCompare(int v1, int v2)
{
	return v1 > v2;
}
L1.sort(myCompare);

2.7 set/ multiset 容器

2.7.1 set基本概念

简介:

  • 所有元素都会在插入时自动被排序

本质:

  • set/multiset属于关联式容器,底层结构是用二叉树实现
2.7.2 set常用API

set构造和赋值函数

set<T> st;//set默认构造函数:
set(const set &st);//拷贝构造函数
set& operator=(const set &st);//重载等号操作符

set大小和交换

size();//返回容器中元素的数目
empty();//判断容器是否为空
swap(st);//交换两个集合容器

set插入和删除

insert(elem);//在容器中插入元素。
clear();//清除所有元素
erase(pos);//删除pos迭代器所指的元素,返回下一个元素的迭代器。
erase(beg, end);//删除区间[beg,end)的所有元素 ,返回下一个元素的迭代器。
erase(elem);//删除容器中值为elem的元素。

set查找和统计

find(key);//查找键key是否存在,若存在,返回该键的元素的迭代器;若不存在,返回set.end();
count(key);//查找键key的元素个数
2.7.3 pair对组的创建

功能描述:

  • 成对出现的数据,利用对组可以返回两个数据

两种创建方式:

  • pair<type, type> p(value1, value2);
  • pair<type, type> p = make_pair(value1, value2);

使用案例

pair<string, int>p1("Tom", 20);
cout << "姓名:" << p1.first << " 年龄:" << p1.second << endl;

pair<string, int>p2 = make_pair("Jerry", 18);
cout << "姓名:" << p2.first << " 年龄:" << p2.second << endl;
2.7.4 set和multiset区别

insert函数返回值

set<int>s;
pair<set<int>::iterator, bool> ret = s.insert(10);
if (ret.second)
	cout << "第一次插入成功!" << endl;
else
	cout << "第一次插入失败!" << endl;

ret = s.insert(10);
if (ret.second)
	cout << "第二次插入成功" << endl;
else
	cout << "第二次插入失败" << endl;
	
//multiset
multiset<int>ms;
ms.insert(10);
ms.insert(10);
for (multiset<int>::iterator it = ms.begin(); it != ms.end(); it++)
{
	cout << *it << ' ';
}
cout << endl;
2.7.5 set容器排序

学习目标:

  • set容器默认排序规则为从小到大,掌握如何改变排序规则

主要技术点:

  • 利用仿函数,可以改变排序规则

示例

class Mycompare
{
	bool operator()(int v1, int v2)
	{
		return v1 > v2;
	}
};

set<int>s;
s.insert(10);
s.insert(50);
s.insert(40);
s.insert(30);
s.insert(20);

for (set<int>::iterator it = s.begin(); it != s.end(); it++)
{
	cout << *it << ' ';
}
cout << endl;

2.8 map/ multimap容器

2.8.1 map基本概念

简介:

  • map中所有元素都是pair
  • pair中第一个元素为key(键值),起到索引作用2222,第二个元素为value(实值)
  • 所有元素都会根据元素的key自动排序

本质:

  • map/mutilmap属于关联式容器,底层结构是用二叉树实现

优点:

  • 可以根据key值快速找到value值

map和multimap区别:

  • map不允许容器中有重复key值元素
  • multimap允许容器中有重复key值元素
2.8.2 map常用API

map构造和赋值函数

map<T1, T2> mapTT;//map默认构造函数: 
map(const map &mp);//拷贝构造函数
map& operator=(const map &mp);//重载等号操作符

map空间和交换操作

size();//返回容器中元素的数目
empty();//判断容器是否为空
swap(mp);//交换两个集合容器

map插入数据的四种方法

map.insert(...); //往容器插入元素,返回pair<iterator,bool>
map<int, string> mapStu;
// 第一种 通过pair的方式插入对象
mapStu.insert(pair<int, string>(3, "小张"));
// 第二种 通过pair的方式插入对象
mapStu.inset(make_pair(-1, "校长"));
// 第三种 通过value_type的方式插入对象
mapStu.insert(map<int, string>::value_type(1, "小李"));
// 第四种 通过数组的方式插入值
mapStu[3] = "小刘";
mapStu[5] = "小王";

map删除操作

clear();//删除所有元素
erase(pos);//删除pos迭代器所指的元素,返回下一个元素的迭代器。
erase(beg,end);//删除区间[beg,end)的所有元素 ,返回下一个元素的迭代器。
erase(keyElem);//删除容器中key为keyElem的对组。

map查找和统计操作

find(key);//查找键key是否存在,若存在,返回该键的元素的迭代器;/若不存在,返回map.end();
count(keyElem);//返回容器中key为keyElem的对组个数。对map来说,要么是0,要么是1。对multimap来说,值可能大于1。

最后

判断一个容器的迭代器是否双向或者是否支持跳跃访问的技巧

vector<int>v;
vector<int>::iterator it = v.begin();
it++;
it--;
//上述两行代码都不报错说明迭代器是双向的
it = it + 1;
//上面的代码不报错说明迭代器支持跳跃访问

参考文章链接:【C/C++】STL详解

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值