c++常用stl容器及技巧

1 vector

1.1 说明

  • vector是向量类型,可以容纳许多类型的数据,因此也被称为容器
  • (可以理解为动态数组,是封装好了的类)
  • 进行vector操作前应添加头文件#include <vector>

1.2 基本函数实现

 vector<int> v( N , i );建立一个可变长度数组v,内部元素类型为int,可变数组最初有N给元素,初始值为i,( N , i )可省略(默认为0)。

1)v.push_back( a );将元素a插入数组v末尾,并增加数组长度。

2)v.size()返回数组v的长度。

3)v.clear():来清空vector中的元素个数

4)v.resize( n,m):重新定义数组大小为n,新增部分初始为m,m可省略。若原大小大与n,则删除多余信息。

5)vector<int>: :iterator it:定义一个名字叫作it的迭代器。

6)v.begin():返回数组v的首元素末尾的下一个元素的指针(迭代器)。

7)v.end():返回数组v首元素末尾的下一个元素的指针(迭代器)。类似与空指针,无元素。

8)v.erase(v.begin()+2):将v[2]删除        v.earse(v.begin()+1,v.begin()+3);   将v[1].v[2]删除

9)多维可变数组:vector< vector<int> > v(注意尖括号里的空格,以免被认为是移位运算符而编译错误。

2 stack

2.1 说明

C++ Stack(堆栈) 是一个容器类的改编,为程序员提供了堆栈的全部功能,——也就是说实现了一个先进后出(FILO)的数据结构。

c++ stl栈stack的头文件为:

#include <stack>

2.2 基本函数实现

#include <stack>

1)stack \<int> s: 建立一个栈s,其内部元素类型是int

2)s.push(a):将元素a压进栈s

3)s.pop():将s的栈顶元素弹出

4)s.top():查询s的栈顶元素

5)s.size():查询s的元素个数

6)s.empty():查询s是否为空

3 queue

3.1 说明

queue是一种容器转换器模板,可使用队列类。在 STL 中主要则是实现了一个先进先出的容器。

c++ stl队列queue的头文件为:

#include< queue>

3.2 基本函数实现

1)queue<int> q:建立一个内部元素类型为int的队列q

2)q.empty():判断队列是否为空

3)q.size():返回队列长度

4)q.push(a):对于queue,在队尾压入一个新元素a

5)q.front():返回队首元素的值,但不删除该元素

6)q.back():返回队尾元素的值,但不删除该元素

7)q.top():返回具有最高优先级的元素值,但不删除该元素

4 list

4.1 说明

List是stl实现的双向链表,与向量(vectors)相比, 它允许快速的插入和删除,但是随机访问却比较慢。使用时需要添加头文件

#include <list>

4.2 基本函数实现

定义和初始化

list<int>lst1;          //创建空list

list<int> lst2(5);       //创建含有5个元素的list

list<int>lst3(3,2);  //创建含有3个元素的list

list<int>lst4(lst2);    //使用lst2初始化lst4

list<int>lst5(lst2.begin(),lst2.end());  //同lst4

常用操作函数

list \<int> a

list\<int>::iterator it; 定义一个名为it的迭代器(指针)

it++;it--                    ;迭代器指向前一个和后一个元素

a.insert(it,x)         ;在迭代器it的前插入元素x

a.erase(it)               ;删除it指针(迭代器)的元素

注意:erase执行后会将it迭代器删除,即it迭代器失效,若要继续使用,则需注意备份

for(it=a.begin();it!=a.end();i++)  遍历链表

a.pop_back()           ;删除最后一个元素 

a.pop_front()           ;删除第一个元素 

a.push_back(x) 在list的末尾添加一个元素 x

a.push_front(x) 在list的头部添加一个元素 x

a.front() 				  ;返回第一个元素

a.begin() 				 ;返回指向第一个元素的迭代器 

a.end()			 		;返回末尾的迭代器

a.clear() 				  ;删除所有元素 

a.empty() 			   ;如果list是空的则返回true 

a.remove(x)            ;删除列表容器中删除与x相等的元素

remove()函数作为列表list容器的成员函数,可以从列表容器中删除与x相等的元素,同时会减小列表容器的大小,其减小的数量等于被删除的元素的个数

5 set

5.1 说明

set的本质是红黑树(一种比较优秀的平衡二叉树)

注意:

1、set中的元素都是排好序的

2、set集合中没有重复的元素

5.2 基本函数实现

set <int> ds;		//建立一个名字叫ds的,元素类型为int的集合
begin();            // 返回指向第一个元素的迭代器
end();              // 返回指向迭代器的最末尾处(即最后一个元素的下一个位置)
clear();            // 清除所有元素
empty();            // 如果集合为空,返回true,否则返回false
erase(x);           //删除集合中的x元素,否则什么都不干
erase(it);			//删除集合中地址it的元素
size();				//集合中元素的数目
find();				//返回一个指向被查找到元素的迭代器
insert(x);			//在集合中插入元素x,如果元素已经存在则什么都不做
lower_bound();		//返回指向大于(或等于)某值的第一个元素的迭代器
upper_bound();		//返回大于某个值元素的迭代器
set <int> ::iterator it;//迭代器it

上文中提到的“地址”实际上是对应元素的迭代器。lower_bound返回的迭代器,可以对其++找到后继元素的迭代器,也可以–找到前继元素的迭代器。需要注意指向元素的迭代器,如果已经是begi(),则不能–,如果是end(),则不能++

6 map

6.1 说明

1.map简介

map是STL的一个关联容器,它提供一对一的hash。

  • 第一个可以称为关键字(key),每个关键字只能在map中出现一次;
  • 第二个可能称为该关键字的值(value);

map以模板(泛型)方式实现,可以存储任意类型的数据,包括使用者自定义的数据类型。Map主要用于资料一对一映射(one-to-one)的情況,map內部的实现自建一颗红黑树,这颗树具有对数据自动排序的功能。

2.map的功能

自动建立key - value的对应。key 和 value可以是任意你需要的类型,包括自定义类型

6.2 基本函数实现

3.使用map

#include<map>

map<A, B> ds;

建立一个名字为ds,下标类型为A,元素类型为B的映射表,例如map<string,int>就是将一个string映射到int的映射表。

ds[A]=B:就是把数组下标为A的位置值变成B,下标为任意类型

ds[A]:访问这个数组下标为A的元素,比如可以进行cout<<ds[“!!!”]<<endl;的操作

这样就定义了一个用int作为索引,并拥有相关联的指向string的指针.

4.map的基本操作函数:

begin();         		//返回指向map头部的迭代器
clear();        		//删除所有元素
end();					//反悔映射表中最后一个元素的下一个元素的地址。这个很少直接单独使用,而是配合其他操作,用于确定某个元素的存在
swap();					//交换两个map
find(x);				//查询元素x映射表中的地址,不存在则返回end()
empty();				//如果映射表是空的,则返回1,否则返回0
size();					//返回映射表中元素的个数
erase(A);				//删除这个“数组”中下标为A的元素。注意:访问使用ds[A]访问数组下标为A的元素时,如果这个下标对应的元素不存在,则自动创建下标为A,值为默认值(例如,所以元素类型默认值是0,string字符串是空字符串)的元素。

7 priority_queue

7.1 说明

1. priority_queue简介

C ++中的优先是STL中的派生容器,它仅考虑最高优先级元素。队列遵循FIFO策略,而优先队列根据优先级弹出元素,即,优先级最高的元素首先弹出。

2. 与普通队列queue的区别

  • 优先队列中,队列中的每个元素都与某个优先级相关联
  • 优先队列中具有最高优先级的元素将被首先删除
  • 如果存在多个具有相同优先级的元素,则将考虑该元素在队列中的顺序。

7.2 基本用法

3. 定义

priority_queue<int> variable_name;

其模板声明带有三个参数

priority_queue<Type, Container, Functional>

Type为数据类型,Container为保存数据的容器,Functional为元素比较方式

注意:

  • Container必须是用数组实现的容器,比如 vector, deque. 默认使用的是vector

  • 比较方式默认使用 operator<,所以如果把后面两个参数省略的话,优先队列就是大顶堆,队头元素最大

    • priority_queue(),默认按照从小到大排列。所以top()返回的是最大值而不是最小值
    • 使用greater<>后,数据从大到小排列,top()返回的就是最小值而不是最大值
  • 使用greater<>后,数据从大到小排列,top()返回的就是最小值而不是最大值

priority_queue<int, greater<>> pq;//这是错误的
priority_queue<int,vector<int> , greater<>> pq;//这是对的
 
//升序队列
priority_queue <int,vector<int>,greater<int> > q;
//降序队列
priority_queue <int,vector<int>,less<int> >q;
//greater和less是std实现的两个仿函数(就是使⼀个类的使⽤看上去像⼀个函数。其实现就是类中实现⼀个operator(),这个类就有了类似函数的⾏为,就是⼀个仿函数类了)
  • pair的⽐较,先⽐较第⼀个元素,第⼀个相等⽐较第⼆个

4. 常用成员函数

push()			// 将新元素插入优先队列
emplace()		// 在优先队列顶部插入一个新元素(c++11)
pop()			// 将优先级最高的元素从队列中删除
top()			// 寻址优先队列的最顶层的元素
size()			// 返回优先队列的大小
empty()			// 验证队列是否为空。基于验证,返回队列的状态
swap()			// 将优先队列的元素与具有相同类型和大小的另一个队列交换(c++11)

8 pair

8.1 说明

pair是将2个数据组合成一组数据,当一个函数需要返回2个数据的时候,可以选择pair。pair的实现是一个结构体,主要的两个成员变量是first second 因为是使用struct不是class,所以可以直接使用pair的成员变量。

8.2 基本用法

1. 定义

pair类型定义在#include <utility>头文件中

pair将一对值(T1和T2)组合成一个值

pair<T1, T2> p1;            //创建一个空的pair对象(使用默认构造),它的两个元素分别是T1和T2类型,采用值初始化。
pair<T1, T2> p1(v1, v2);    //创建一个pair对象,它的两个元素分别是T1和T2类型,其中first成员初始化为v1,second成员初始化为v2。
make_pair(v1, v2);          // 以v1和v2的值创建一个新的pair对象,其元素类型分别是v1和v2的类型。
p1 < p2;                    // 两个pair对象间的小于运算,其定义遵循字典次序:如 p1.first < p2.first 或者 !(p2.first < p1.first) && (p1.second < p2.second) 则返回true。
p1 == p2;                  // 如果两个对象的first和second依次相等,则这两个对象相等;该运算使用元素的==操作符。
p1.first;                   // 返回对象p1中名为first的公有数据成员
p1.second;                 // 返回对象p1中名为second的公有数据成员

2. 创建和初始化

pair<string, string> anon;        // 创建一个空对象anon,两个元素类型都是string
pair<string, int> word_count;     // 创建一个空对象 word_count, 两个元素类型分别是string和int类型
pair<string, vector<int> > line;  // 创建一个空对象line,两个元素类型分别是string和vector类型

创建时赋初始值

pair<string, string> author("James","Joy");    // 创建一个author对象,两个元素类型分别为string类型,并默认初始值为James和Joy。
pair<string, int> name_age2(name_age);    // 拷贝构造初始化

typedef简化声明

typedef pair<string,string> Author;
Author proust("March","Proust");
Author Joy("James","Joy");

3. make_pair

pair<int, double> p1;
p1 = make_pair(1, 1.2);

cout << p1.first << p1.second << endl;
 
//output: 1 1.2

4. 通过tie获取pair元素值

在某些情况函数会以pair对象作为返回值时,可以直接通过std::tie进行接收。比如:

std::pair<std::string, int> getPreson() {
    return std::make_pair("Sven", 25);
}
 
int main(int argc, char **argv) {
    std::string name;
    int ages;
 
    std::tie(name, ages) = getPreson();
 
    std::cout << "name: " << name << ", ages: " << ages << std::endl;
 
    return 0;
}

9 unordered_map

9.1 说明

  • unordered_map是一个将key和value关联起来的容器,它可以高效的根据单个key值查找对应的value。
  • key值应该是唯一的,key和value的数据类型可以不相同。
  • unordered_map存储元素时是没有顺序的,只是根据key的哈希值,将元素存在指定位置,所以根据key查找单个value时非常高效,平均可以在常数时间内完成。
  • unordered_map查询单个key的时候效率比map高,但是要查询某一范围内的key值时比map效率低。
  • 可以使用[]操作符来访问key值对应的value值。

9.2 常用方法

与map类似。

9.3 c++中map与unordered_map的区别

1. 效率与内存

  • 运行效率方面:unordered_map最高,而map效率较低但 提供了稳定效率和有序的序列。
  • 占用内存方面:map内存占用略低,unordered_map内存占用略高,而且是线性成比例的。

2. 头文件

  • map: #include < map >
  • unordered_map: #include < unordered_map >

3. 内部实现原理

  • map: map内部实现了一个红黑树,该结构具有自动排序的功能,因此map内部的所有元素都是有序的,红黑树的每一个节点都代表着map的一个元素,因此,对于map进行的查找,删除,添加等一系列的操作都相当于是对红黑树进行这样的操作,故红黑树的效率决定了map的效率
  • unordered_map: unordered_map内部实现了一个哈希表,因此其元素的排列顺序是杂乱的,无序的

4. 优缺点比较

map

  • 优点有序性

  • 缺点:空间占用率高

  • 适用处:对于那些有顺序要求的问题,用map会更高效一些。

unordered_map

  • 优点:内部实现了哈希表,因此其查找速度是常量级别的。

  • 缺点:哈希表的建立比较耗费时间

  • 适用处:对于查找问题,unordered_map会更加高效一些,因此遇到查找问题,常会考虑一下用unordered_map

注意

  • 对于unordered_map或者unordered_set容器,其遍历顺序与创建该容器时输入元素的顺序是不一定一致的,遍历是按照哈希表从前往后依次遍历的

10 string

10.1 常用构造函数

  • string str——构造空的string类对象,即空字符串
  • string str(str1)——str1 和 str 一样
  • string str(“ABC”)——等价于 str=“ABC”
  • string str(“ABC”,strlen)——等价于 “ABC” 存入 str 中,最多存储 strlen 个字节
  • string str(“ABC”,stridx,strlen)——等价于 “ABC” 的stridx 位置,作为字符串开头,存到str中,最多存储 strlen 个字节
  • string str(srelen,‘A’)——存储 strlen 个 ‘A’ 到 str 中

10.2 常用成员函数

  • str.length()——求字符串长度
  • str.size()——和 length() 一样
  • str.capacity()——获取容量,包含了不用增加内存就能使用的字符数
  • str.resize(10)——设置当前 str 的大小为10,若大小大与当前串的长度,\0 来填充
  • str.resize(10,char c)——设置当前 str 的大小为10,若大小大与当前串的长度,字0符c 来填充
  • str.reserve(10)——设置str的容量 10,不会填充数据
  • str.swap(str1)——交换 str1 和 str 的字符串
  • str.push_back(‘A’)——在str末尾添加一个字符 ‘A’ ,参数必须是字符形式
  • str.append(“ABC”)——在str末尾添加一个字符串 “ABC”,参数必须是字符串形式
  • str.erase(2)——删除 下标2 的位置开始,之后的全删除
  • str.erase(2,1)——删除 下标2 的位置开始,之后的 1个 删除
  • str.clear()——删除 str 所有
  • str.replace(2,4,“abcd”)——从 下标2 的位置,替换 4个字节 ,为"abcd"
  • str.empty()——判空

assign 清空字符串

  • str.assign(“ABC”)——清空字符串,并设置为 “ABC”
  • str.assign(“ABC”,2)——清空字符串,并设置为"AB",保留两个字符
  • str.assign(“ABC”,1,1)——清空字符串,设置为 “ABC” 中的从 位置1 开始,保留 1个 字符
  • str.assign(5,‘A’)——清空字符串,然后字符串设置为 5个 ‘A’

insert 插入

  • str.insert(2,3,‘A’)——在str下标为2的位置添加 3个 字符’A’

  • str.insert(2,“ABC”)——在str下标为2的位置添加 字符串 “ABC”

  • str.insert(2,“ABC”,1)——在str下标为2的位置添加 字符串 “ABC” 中 1个 字符

  • str.insert(2,“ABC”,1,1)——在str下标为2的位置添加 字符串 “ABC” 中从位置 1 开始的 1 个字符
    注:上个函数参数中加粗的 1 ,可以是 string::npos,这时候最大值,从 位置1 开始后面的全部字符

  • str.insert( iterator pos, size_type count, CharT ch )——在 str 中,迭代器指向的 pos位置 插入 count个 字符 ch
    s4.insert(++str1.begin(),2,‘a’); 结果:s4:ABCD -> AaaBCD

  • str.insert( iterator pos, InputIt first, InputIt last )——在 str 中,pos位置 插入 str1 的 开始位置 到 结束为止
    s4.insert(s4.begin(),str1.begin(),str1.end()); 结果:s4:ABCD str1:abc -> abcABCD

substr() 复制子字符串

substr()是C++函数,主要功能是复制子字符串,要求从指定位置开始,并具有指定的长度。如果没有指定长度_Count或_Count+_Off超出了源字符串的长度,则子字符串将延续到源字符串的结尾。

语法

substr(size_type _Off = 0,size_type _Count = npos)

参数

_Off——所需的子字符串的起始位置。字符串中第一个字符的索引为 0,默认值为0。
_Count——复制的字符数目
返回值——一个子字符串,从其指定的位置开始

一种构造string的方法
形式 : s.substr(pos, len)
返回值: string,包含s中从pos开始的len个字符的拷贝。pos的默认值是0,len的默认值是s.size() - pos,即不加参数会默认拷贝整个s
异常 :若pos的值超过了string的大小,则substr函数会抛出一个out_of_range异常;若pos+n的值超过了string的大小,则substr会调整n的值,只拷贝到string的末尾。

用法

string s(“student12”);
string x=s.substr()                 //默认时的长度为从开始位置到尾
string y=s.substr(5)               //获得字符串s中 从第5位开始到尾的字符串
string z=s.substr(5,3);         //获得字符串s中 从第5位开始的长度为3的字符串

find() 查找

是一个字符或字符串查找函数,该函数有唯一的返回类型,即string::size_type,即一个无符号整形类型,可能是整数也可能是长整数。如果查找成功,返回按照查找规则找到的第一个字符或者子串的位置;如果查找失败,返回string::npos,即-1(当然打印出的结果不是-1,而是一个很大的数值,那是因为它是无符号的)。

string::npos静态成员常量:是对类型为size_t的元素具有最大可能的值。当这个值在字符串成员函数中的长度或者子长度被使用时,该值表示“直到字符串结尾”。作为返回值他通常被用作表明没有匹配。

string::npos是这样定义的:static const size_type npos = -1;

因为string::size_type描述的是size,故需为无符号整数型类别。因为缺省配置为size_t作为size_type,于是-1被转换为无符号整数类型,npos也就成为了该类别的最大无符号值。不过实际值还是取决于size_type的实际定义类型,即无符号整型(unsigned int)的-1与无符号长整型(unsigned long)的-1是不同的

语法

find(str,position)
  • str:是要找的元素
  • position:字符串中的某个位置,表示从从这个位置开始的字符串中找指定元素(不填第二个参数,默认从字符串的开头进行查找)
  • 返回值为目标字符的位置(第一个字符位置为0),当没有找到目标字符时返回npos

延伸用法

  • 找到目标字符第一次出现和最后一次出现的位置
string s = "hello world!";
cout <<s.find_first_of("l") << endl;//第一次出现的位置
cout << s.find_last_of("l") << endl;//最后一次出现的位置
结果为:
2
9
  • 反向查找
string s = "hello world!";
cout << s.rfind("l") << endl;//即从后往前第一次出现"l"的位置
结果为:9

当正向查找与反向查找得到的位置不相同说明子串不唯一

  • 查找所有子串在母串中出现的位置
//查找s 中flag 出现的所有位置。
	string s("hello world!");
    string flag="l";
    int position=0;
    int i=1;
    while((position=s.find(flag,position))!=string::npos)
    {
        cout<<"position  "<<i<<" : "<<position<<endl;
        position++;
        i++;
    }
结果为:
  position  1 : 2
  position  2 : 3
  position  3 : 9

11 一些技巧

11.1 push和emplace的区别

对内置数据类型

stack<int> s1;
s1.push(1);				//直接插入数据
int a=2;
s1.push(a);				//变量a插入,这两种操作对于内置数据类型都可以

对自定义数据类型

class Person
{
    public:
    	int age;
    	string name;
};
stack<Person> s2
    

s2.push(18, "Tom");			//这种做法是错误的

//若想用push进行插入,只能先将这个对象构造出来,再将这个对象插入
s2.push(Person(18, "Tom"));

Person p(18, "Tom");
s2.push(p);

//上述错误的push操作可以用emplace来完成
s2.emplace(20, "Bob");		//传入20,"Bob"后,它会自己调用一遍Person的构造函数

queue<pair<int, int> > q;
//push得先构造对象
q.push(make_pair<int, int>(0, 0));
//emplace则更简便
q.emplace(1, 1);

总结:

  • emplace相当于初始调用了一遍构造函数(要注意初始容器的构造函数,如vector(a, b)表示初始大小为a,初始赋值为b)
  • push能做的,emplace都能做
  • push要把传入的对象先构造好,再复制过去插入;而emplace则可以自己拿到构造对象所需的元素构造出来,直接插入即可
  • emplace相比于push省去了复制这步,即使用emplace这种操作会更节省内存,且更简便

11.2 STL容器中的first与second

C++ STL里的容器,比如map、uordered_map中经常会出现如

map<string, string> oneMap;
for(auto &it:oneMap){
	cout<<it->first<<": "<<it->second<<endl;
}

代码段中的first或second用法,这是因为map中的每个元素都对应一组<key, value>键值对(pair),键值对中的第一个成员称为first,第二个成员称为second.

12 auto

用法就类似于C#中的var关键字,在C++11标准的语法中,auto被定义为自动推断变量的类型。编程时常常需要把表达式的值赋给变量,这就要求声明变量时清楚的知道表达式的类型。然而有些情况是声明的变量的类型我们并不知道,比如在模板编程时。为了解决这个问题,C++11引入了auto类型说明符,用它来让编译器替我们去分析表达式所属的类型。auto可以在声明变量的时候根据变量初始值的类型自动为此变量选择匹配的类型

12.1 注意事项

  • auto的自动类型推断发生在编译期,所以使用auto并不会造成程序运行时效率的降低

  • auto声明的变量必须要初始化,否则编译器不能判断变量的类型。这类似于const关键字。

  • auto不能被声明为返回值,auto不能作为形参,auto不能被修饰为模板参数。

  • 如果初始化表达式是引用,则去除引用语义。

    int a = 10;
    int &b = a;
     
    auto c = b;//c的类型为int而非int&(去除引用)
    auto &d = b;//此时c的类型才为int&
     
    c = 100;//a =10;
    d = 100;//a =100;
    
  • 如果初始化表达式为const或volatile(或者两者兼有),则除去const/volatile语义。如果auto关键字带上&号,则不去除const语意。

    const int a1 = 10;
    auto  b1= a1; //b1的类型为int而非const int(去除const)
    const auto c1 = a1;//此时c1的类型为const int
    b1 = 100;//合法
    c1 = 100;//非法
    
    const int a2 = 10;
    auto &b2 = a2;//因为auto带上&,故不去除const,b2类型为const int
    b2 = 10; //非法
    //这是因为如果去掉了const,则b2为a2的非const引用,通过b2可以改变a2的值,则显然是不合理的。
    

12.2 主要用法

  • 用于代替冗长复杂、变量使用范围专一的变量声明

    例如,如果 il 是一个std::initializer_list<double>对象,则可将下述代码:

    for (std::initializer_list<double>::iterator p = il.begin(); p != il.end(); ++p)
    

    替换为如下代码:

    for (auto p = il.begin(); p != il.end(); ++p)
    
  • 在定义模板函数时,用于声明依赖模板参数的变量类型

    template <typename _Tx,typename _Ty>
    void Multiply(_Tx x, _Ty y)
    {
        auto v = x*y;
        std::cout << v;
    }
    

    若不使用auto变量来声明v,那这个函数就难定义啦,不到编译的时候,谁知道x*y的真正类型是什么呢?

  • 模板函数依赖于模板参数的返回值。

    当模板函数的返回值依赖于模板的参数时,我们依旧无法在编译代码前确定模板参数的类型,故也无从知道返回值的类型,这时我们可以使用auto。格式如下所示。

    template <typename _Tx, typename _Ty>
    auto multiply(_Tx x, _Ty y)->decltype(_Tx*_Ty)
    {
        return x*y;
    }
    

    decltype操作符用于查询表达式的数据类型,也是C++11标准引入的新的运算符,其目的也是解决泛型编程中有些类型由模板参数决定,而难以表示它的问题。
    auto在这里的作用也称为返回值占位,它只是为函数返回值占了一个位置,真正的返回值是后面的decltype(_Tx*_Ty)。为何要将返回值后置呢?如果没有后置,则函数声明时为:

    decltype(_Tx*_Ty)multiply(_Tx x, _Ty y)
    

    而此时_Tx,_Ty还没声明,编译无法通过。

13 accumulate

accumulate定义在#include<numeric>中,作用有两个,一个是累加求和,另一个是自定义类型数据的处理

语法

accumulate(first, last, init)
  • first,last指定要累加的元素范围

  • init累加的初值

  • 返回值是一个数的类型

用法

int arr[]={1, 2, 3, 4, 5};
vector<int> vec(&arr[0], &arr[5])
int sum = accumulate(vec.begin() , vec.end() , 0);  //sum=15

accumulate函数将它的一个内部变量设置为指定的初始值,然后在此初值上累加输入范围内所有元素的值。accumulate算法返回累加的结果,其返回类型就是其第三个实参的类型。

可以使用accumulate把string型的vector容器中的元素连接起来

string sum = accumulate(v.begin() , v.end() , string(" "));  

这个函数调用的效果是:从空字符串开始,把vec里的每个元素连接成一个字符串。

14 deque

介绍:

双向队列。上一个的队列,只能够删除队首,在队尾插入。而双向队列,能够支持队首和队尾删除和插入的操作。

常用于滑动窗口等问题。

使用的时候需要加上头文件:

#include<deque>

初始化:

deque<int> q;

函数调用:

q.push_back(); q.push_front(); q.size(); q.front(); q.back(); q.pop_back(); q.pop_front(); q.empty(); 的时间复杂度均为 O ( 1 ) O(1) O(1)

	deque<int> q;
	q.push_back(50); //在队列尾巴插入元素 50
	q.push_back(666); //在队列尾巴插入元素 666
	q.push_front(10); //在队列头部插入元素 10
	q.push_back(5201314); //在队列尾巴插入元素 5201314
	q.push_back(2333); //在队列尾巴插入元素 2333
 
	cout << q.size() << endl; //返回双向队列中的元素个数。
	//输出结果为 5
	for (int i = 0; i < q.size(); i++) cout << q[i] << " \n"[i == q.size() - 1];
	//输出结果为 10 50 666 5201314 2333
 
	cout << q.front() << " " << q.back() << endl;
	//返回队列头部元素   和   返回队列尾巴元素
	//输出结果为 10 2333
 
	q.pop_front(); q.pop_back();
	//删除队首元素 和 删除队尾元素
	for (int i = 0; i < q.size(); i++) cout << q[i] << " \n"[i == q.size() - 1];
	//输出结果为 50 666 5201314
 
	if (q.empty()) cout << "空" << " ";
	else cout << "非空" << " ";
	q.clear(); //清空双向队列
	if (q.empty()) cout << "空" << endl;
	else cout << "非空" << endl;
	//如果是空双向队列,则返回值为真。
	//如果双向队列中含有元素,则返回值为假。
	//输出结果为 非空 空
 
	q.push_back(10); q.push_back(20); q.push_back(30); q.push_back(40);
	q.push_back(50); q.push_back(60); q.push_back(70); q.push_back(80);
	for (int i = 0; i < q.size(); i++) cout << q[i] << " \n"[i == q.size() - 1];
	//输出结果为 10 20 30 40 50 60 70 80
	q.erase(q.begin() + 2);
	//删除迭代器所在的元素
	for (int i = 0; i < q.size(); i++) cout << q[i] << " \n"[i == q.size() - 1];
	//输出结果为 10 20 40 50 60 70 80
	q.erase(q.begin() + 3, q.begin() + 6);
	//删除两个迭代器之间的元素,包括前面的那个元素,不包括后面的那个元素。
	for (int i = 0; i < q.size(); i++) cout << q[i] << " \n"[i == q.size() - 1];
	//输出结果为 10 20 40 80

15 max_element和min_element

介绍

时间复杂度 O ( n ) O(n) O(n),相当于一个for循环

max_element用于从范围[first, last)中获取最大值的元素。

min_element用于从范围[first, last)中获取最小值的元素。

包含在 **algorithm **库中,语法如下(C++17起)

max_element

语法

#include<algorithm>
template< class ExecutionPolicy, class ForwardIt, class Compare >
template< class ForwardIt, class Compare >
constexpr ForwardIt max_element(ForwardIt first, ForwardIt last, Compare comp );

参数

first,last:定义要检验范围的向前迭代器

comp:比较函数对象

返回值

指向范围 [first, last) 中最大元素的迭代器。若范围中有多个元素等价于最大元素,则返回指向首个这种元素的迭代器。若范围为空则返回 last

min_element

语法

#include<algorithm>
template< class ForwardIt, class Compare >
constexpr ForwardIt min_element( ForwardIt first, ForwardIt last, Compare comp );

参数

first,last:定义要检验范围的向前迭代器

comp:比较函数对象

返回值

指向范围 [first, last) 中最小元素的迭代器。若范围中有多个元素等价于最小元素,则返回指向首个这种元素的迭代器。若范围为空则返回 last

16 lower_bound和upper_bound

lower_bound( )和upper_bound( )都是利用二分查找的方法在一个排好序的数组中进行查找的。

lower_bound

函数原型

原型1template <class ForwardIterator, class T>
ForwardIterator lower_bound (ForwardIterator first, ForwardIterator last,  const T& val);
原型2template <class ForwardIterator, class T, class Compare>
ForwardIterator lower_bound (ForwardIterator first, ForwardIterator last, const T& val, Compare comp);

模板参数

  • ForwardIterator就是一个迭代器,vector< int > v,v数组的首元素就是 v.begin()
  • T&val , 就是一个T类型的变量
  • Compare 就是一个比较器,可以传仿函数对象,也可以传函数指针

参数

first,last:迭代器在排序序列的起始位置和终止位置,使用的范围是[first,last).包括first到last位置中的所有元素

val:在[first,last)下,也就是区分(找到大于等于val值的位置,返回其迭代器)

comp:主要针对于原型二,传一个函数对象,或者函数指针,按照它的方式来比较

返回值

返回一个迭代器,指向第一个大于等于val的位置

作用

前提是有序的情况下,lower_bound返回指向第一个值大于等于val的位置。(通过二分查找)

例子

  • 原型一 例1
vector<int> v= {3,4,1,2,8};
//先排序
sort(v.begin(),v.end()); // 1 2 3 4 8
// 定义两个迭代器变量 
vector<int>::iterator iter1;
vector<int>::iterator iter2; 

iter1 = lower_bound(v.begin(),v.end(),3);//迭代器指向3
iter2 = lower_bound(v.begin(),v.end(),7);//迭代器指向8(因为第一个大于等于8)

cout << iter1 - v.begin() << endl; //下标 2
cout << iter2 - v.begin() << endl; //下标 4 
system("pause");

总结:需要注意的是如果例子中(val >= 8),那么迭代器就会指向last位置,也就是数组尾元素的下一个,不管val多大,迭代器永远指向尾元素的下一个位置

  • 原型二 例1
vector<int> v = { 3, 4, 1, 2, 8 };
// 无序数组,不用sort排序,下面调用lower_bound排序

// 定义两个迭代器变量 
vector<int>::iterator iter1;
vector<int>::iterator iter2;

//less<int>() 是建小堆,排升序。(greater<int>() 是建大堆,排降序)
//所以数组 v = {1,2,3,4,8} 
//我们找第一个比1大的,那肯定就是首元素1了
//我们找第一个比8大的,没有,所以就指向数组最后一个元素8了
iter1 = lower_bound(v.begin(), v.end(), 1, less<int>());//
iter2 = lower_bound(v.begin(), v.end(), 8, less<int>());//

cout << iter1 - v.begin() << endl; //下标 所以就是 0
cout << iter2 - v.begin() << endl; //下标 所以就是 4
system("pause");

我们原型2的第四个参数(比较器)可以用来排序,再来获取大于等于val值的迭代器

总结:我们原型2的第四个参数(比较器)可以用来排序,再来获取大于等于val值的迭代器

  • 原型二 例2 仿函数传参
typedef struct Student
{
	int _id;  //学号
	int _num; //排名
	Student(int id, int num)
		:_id(id)
		, _num(num)
	{}
}Stu;

struct CompareV
{
	bool operator() (const Stu& s1,  const Stu& s2)//  排名升序
	{	
		return s1._num < s2._num;
	}
};

int main()
{
	vector<Stu> vS = { { 101, 34 }, { 103, 39 }, { 102, 35 } };
	//CompareV()排完序后是这个样子
	//101 34
	//102 35
    //103 39
	auto iter = lower_bound(vS.begin(), vS.end(), Stu(200,33), CompareV());
	cout << iter - vS.begin() << endl; //我们就找到了按仿函数排序(找排名比33大的位置 就是0)
	system("pause");
}

底层实现

int lower_bound(vector<int>& nums, int x) {
	int left = 0;
	int right = nums.size() - 1;

	while (left <= right) {
		int mid = left +(right - left) / 2;
		if (x > nums[mid]) {
			left = mid + 1;
		}
		else {
			right = mid - 1;	
		}
	}
	return left;
}

upper_bound

用法和上面类似。只是把lower_bound的大于等于换成大于。仿函数等等全是相同的用法

底层实现

int upper_bound(vector<int>& nums, int x) {
	int left = 0;
	int right = nums.size() - 1;

	while (left <= right) {
		int mid = left +(right - left) / 2;
		if (x >= nums[mid]) {       //这里是大于等于
			left = mid + 1;
		}
		else {
			right = mid - 1;	
		}
	}
	return left;
}
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值