Coursera课程笔记----C++程序设计----Week8
C++程序设计
标准模板库 STL-1(week 8)
STL概述(Standard Template Library)
C++重用
- C++语言的核心优势之一就是便于软件的重用
- C++中有两个方面体现重用:
- 面向对象的思想:继承和多态,标准类库
- 泛型程序设计(generic programming)的思想:模版机制、标准模版库STL
泛型程序设计
- 简单地说就是使用模版的程序设计法
- 讲一些常用的数据结构(链表,数组,二叉树)(类模版)和算法(排序,查找)(函数模版)写成模版,以后则不论数据结构里放的是什么对象,算法针对什么样的对象,则都不必重新实现数据结构,重新编写算法
- 标准模版库(Standard Template Library)就是一些常用数据结构和算法的模版的集合
- 有了STL,不必再写大多的标准数据结构和算法,并且可获得非常高的性能
STL中的基本概念
-
容器:可容纳各种数据类型的通用数据结构,是类模版
-
迭代器:可用于依次存取容器中元素,类似于指针
-
算法:用来操作容器中的元素的函数模版
- sort()来对一个vector中的数据进行排序
- find()来搜索一个list中的对象
算法本身与他们操作的数据类型无关,因此他们可以在从简单数组道高度复杂容器的任何数据结构上使用
int array[100];
//该数组就是容器,而int* 类型的指针变量就可以作为迭代器,sort算法可以作用于该容器上,对其进行排序
sort(array,array+70);//将前70个元素排序,array和array+70就是两个迭代器
容器概述
-
可以用于存放各种类型的数据(基本类型的变量、对象等)的数据结构,都是类模版,分为三种:
- 顺序容器:vector,deque,list
- 关联容器:set,multiset,map,multimap
- 容器适配器:stack,queue,priority_queue
-
对象被插入容器中时,被插入的是对象的一个复制品。许多算法,比如排序、查找,要求对容器中的元素进行比较,有的容器本身就是排序的,所以,放入容器的对象所属的类,往往还应该重载==和<运算符
顺序容器简介
-
容器并非排序的,元素的插入位置同元素的值无关
-
有vector,deque,list三种
-
vector 头文件<vector>
向量,动态数组,元素在内存连续存放,随机存取任何元素都能在常数时间完成。在尾端增删元素具有较佳的性能(大部分情况下是常数时间,因为通常会预留一些存储空间,所以时间复杂度是常数;偶尔会因为空间不够而变成o(n) )。
-
duque 头文件<deque>
双向队列,元素在内存连续存放。随机存取任何元素都能在常数时间完成(但次于vector,因为需要判断是否越界,是否需要重置到头部)。在两端增删元素具有较佳的性能(大部分情况下是常数时间,只有在需要重新分配存储空间的时候才会变成o(n))
-
list 头文件<list>
双向链表。元素在内存不连续存放。在任何位置增删元素都能在常数时间完成。不支持随机存取。
-
关联容器简介
-
元素是排序的
-
插入任何元素,都按相应的排序规则来确定其位置
-
在查找时具有非常好的性能(排序的目的)
-
通常以平衡二叉树的方式实现,插入和检索的时间都是o(log(N))
-
set/multiset 头文件<set>
set 即集合。set中不允许相同元素,multiset中允许相同的元素
-
map/multimap 头文件<map>
map与set的不同在于map中存放的元素有且仅有两个成员变量,一个名为first,一个名为second,map根据first值对元素进行从小到大的排序,并可快速的根据first来检索元素。
map同multimap的区别在于是否允许相同first值的元素
-
容器适配器简介
-
stack:头文件<stack>
栈。是项的有限序列,并满足序列中被删除、检索和修改的项职能是最近插入序列的项(栈顶的项)。后进先出。
-
queue 头文件<queue>
队列。插入只可以在尾部进行,删除、检索和修改只允许从头部进行。先进先出
-
priority_queue 头文件<queue>
优先级队列,最高优先级元素总是第一个出列
顺序容器和关联容器中都有的成员函数
- begin 返回指向容器中第一个元素的迭代器
- end 返回指向容器中最后一个元素后面的位置的迭代器
- rbegin 返回指向容器中最后一个元素的迭代器
- rend 返回指向容器中第一个元素前面的位置的迭代器
- erase 从容器中删除一个或几个元素
- clear 从容器中删除所有元素
顺序容器的常用成员函数
- front 返回容器中第一个元素的引用
- back 返回容器中最后一个元素的引用
- push_back 在容器末尾增加新元素
- pop_back 删除容器末尾的元素
- erase 删除迭代器指向的元素(可能会让迭代器失效),或删除一个区间,返回被删除元素后面的那个元素的迭代器
迭代器
-
用于指向顺序容器和关联容器中的元素
-
迭代器用法和指针类似
-
有const和非const两种
-
通过迭代器可以读取它指向的元素
-
通过非const迭代器还能修改其指向的元素
-
定义一个容器类的迭代器的方法:
容器类名::iterator/const_iterator 变量名;
-
访问一个迭代器指向的元素
* 迭代器变量名
-
迭代器上可以执行++操作,从而使其指向容器中的下一个元素。但如果迭代器到达了容器中的最后一个元素的后面,此时再使用它,就会出错,类似于使用NULL或未初始化的指针一样。
-
迭代器类别
- 随机访问:vector、deque
- 双向:list、set/multiset、map/multimap
- 不支持迭代器:stack\queue\priority_queue
- 有的算法,例如sort, binary_search需要通过随机访问迭代器来访问容器中的元素,那么list以及关联容器就不支持该算法!
-
遍历vector(deque)
vector<int> v(100); int i; for(i = 0; i < v.size(); i++) cout<< v[i]; //根据下标随机访问 vector<int>::const_iterator ii; for(ii = v.begin(); ii != v.end(); ii++) // !=,<都可 cout << *ii;
-
遍历list(所用的是双向迭代器)
list<int> v; list<int>::const_iterator ii; for(ii = v.begin();ii != v.end(); ++ii) cout << * ii; //错误的做法,双向迭代器不支持<,list没有[]成员函数 for( ii = v.begin();ii<v.end();ii++) cout << * ii; for(int i = 0; i < v.size();i++) cout << v[i];
算法简介
-
算法就是一个个函数模版,大多数在<algorithm>中定义
-
STL中提供能在各个容器中通用的算法,比如查找,排序等
-
算法通过迭代器来操纵容器中的元素。许多算法可以对容器中的一个局部区间进行操作,因此需要两个参数,一个是起始元素的迭代器,一个是终止元素的后面的一个元素的迭代器。比如,排序和查找。
-
有的算法返回一个迭代器,比如find()算法,在容器中查找一个元素,并返回一个指向该元素的迭代器
-
算法可以处理容器,也可以处理普通数组
-
示例:find() 通常用于实现顺序查找
template<class Init, class T> Init find(Init first, Init last, const T& val);
- first 和 last 这两个参数都是容器的迭代器,它们给出了容器中的查找区间起点和终点[first,last)
- 含义为find在[first,last)查找等于val的元素
- 用==运算符判断相等
- 函数返回值是一个迭代器。如果找到,则该迭代器指向被找到的元素。如果找不到,则该迭代器等于last
- 时间复杂度o(n)
STL容器中“大”“小”的概念
-
关联容器内部的元素是从小到大排序的
-
有些算法要求其操作的区间是从小到大排序的,称为“有序区间算法”(binary_search)
-
有些算法会对区间进行从小到大排序,称为“排序算法”
-
还有一些其他算法会用到“大”,“小”的概念
使用STL时,在缺省的情况下,以下三个说法等价
- x比y小
- 表达式“x<y”为真
- y比x大
STL中“相等”的概念
- 有时“x和y相等”等价于“x==y为真”
- 有时“x和y相等”等价于“x小于y和y小于x同时为假”(binary search)
顺序容器Vector
简介
-
可变长的动态数组(相较于静态数组而言)
-
必须包含头文件 #include <vector>
-
支持随机访问迭代器
- 根据下标随机访问某个元素,所需时间为常数
- 在尾部添加速度很快
- 在中间插入慢
-
所有STL算法 都能对vector操作
常用成员函数
-
构造函数初始化
成员函数 作用 vector(); 无参构造函数,将容器初始化成空的 vector(int n); 将容器初始化成有n个元素 vector(int n, const T& val); 假定元素类型是T,将容器初始化成有n个元素,每个元素的值都是val vector(iterator first, iterator last); 将容器初始化为与别的容器上区间为[first,last)一致的内容 -
其他常用函数
成员函数 作用 void pop_back(); 删除容器末尾的元素 void push_back(const T & val); 将val添加到容器末尾 int size(); 返回容器中元素的个数 T & front(); 返回容器中第一个元素的引用 T & back(); 返回容器中最后一个元素的引用
使用范例
//例1
#include <iostream>
#include <vector>
using namespace std;
int main()
{
int i;
int a[5] = {1,2,3,4,5};
vector<int> v(5);
cout<<v.end()-v.begin()<<endl;//输出5
for(i = 0; i < v.size();i++) v[i] = i;//可以用方括号直接访问
v.at(4) = 100;//将第5个元素赋值为100
for(i = 0; i < v.size();i++)
cout<< v[i] << ","; // 0,1,2,3,100
cout << endl;
vector<int> v2(a,a+5);//构造函数
v2.insert(v2.begin()+2,13);//在begin()+2位置插入13
for(i = 0; i < v2.size();i++)//1,2,13,3,4,5
cout<<v2.at(i)<<",";
}
二维动态数组
vector< vector<int>> v(3);
//v有3个元素
//没个元素都是vector<int>容器
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<vector<int>> v(3);
for(int i=0; i < v.size();++i)
for(int j=0; j < 4; ++j)
v[i].push_back(j);
for(int i = 0; i < v.size();++i)
{
for(int j = 0; j < v[i].size(); ++j)
cout << v[i][j] << " ";
cout << endl;
}
return 0;
}
List容器
简介
- 本质是双向链表 #include<list>
- 因此在任何位置插入or删除都是常数时间
- 不支持根据下标随机存取元素
- 具有所有顺序容器都有的成员函数
额外支持的成员函数
成员函数 | 作用 |
---|---|
push_front | 在链表最前面插入 |
pop_front | 删除链表最前面的元素 |
sort | 排序(list不支持STL的算法sort) |
remove | 删除和指定值相等的所有元素 |
unique | 删除所有和前一个元素相同的元素 |
merge | 合并两个链表并清空被合并的链表 |
reverse | 颠倒链表 |
splice | 在指定位置前面插入另一链表中的一个或多个元素,并在另一链表中删除被插入的元素 |
sort函数
- list容器的迭代器不支持完全随机访问,因此不能用标准库中sort函数对它进行排序
- list自己的sort成员函数
list<T> classname;
classname.sort(compare); //compare函数可以自己定义
classname.sort(); //无参数版本,按<排序
- list容器只能使用双向迭代器,因此不支持>/</[]/随机移动(+2等操作不行,+1/++可以)
#include<list>
#include<iostream>
#include<algorithm>
using namespace std;
class A { //定义类A,并以友元重载<,==,<<
private:
int n;
public:
A( int n_) {n = n_;}
friend bool operator< (const A & a1, const A & a2);
friend bool operator== (const A & a1, const A & a2);
friend ostream & operator << (ostream & o, const A & a);
};
bool operator < (const A & a1, const A & a2)
{
return a1.n < a2.n
}
bool operator== (const A & a1, const A & a2)
{
return a1.n == a2.n
}
ostream & operator << (ostream & o, const A & a)
{
o << a.n;
return o;
}
//定义函数模版PrintList,打印列表中的对象
template <class T>
void PrintList(const list<T> & lst)
{
int tmp = lst.size();
if( tmp > 0){
typename list<T>::const_iterator i;
i = lst.begin();
for(i = lst.begin();i!=lst.end();i++)
cout << *i << ",";
}
}
//与其他顺序容器不同,list容器只能使用双向迭代器
//因此不支持大于/小于比较运算符,[]运算符和随机移动
//typename用来说明 list<T>::const_iterator 是个类型
//在VS中不写也可以
deque容器
简介
- 双向队列
- 必须包含头文件 #include <deque>
- 所有适用于vector的操作,都适用于deque
- deque还有push_front(将元素插入到容器头部)和pop_front(删除头部的元素)操作
函数对象
简介
- 若一个类重载了运算符"()",则该类的对象就成为函数对象
class CMyAverage{//函数对象类
public:
double operator()(int a1, int a2, int a3)
{
return (double)(a1+a2+a3)/3;
}
};
CMyAverage average;//函数对象
cout << average(3,2,3); //average.operator()(3,2,3)
//输出2.66667
函数对象的应用
//Dev C++中的Accumulate源代码1
template <typename _InputIterator, typename _Tp>
_Tp accumulate(_InputIterator __first, _InputIterator __last, _Tp __init)
{
for(;__first != __last; ++__first)
__init = __init + *__first;
return __init;
}
//typename 等价于 class
//Dev C++中的Accumulate源代码2
template <typename _InputIterator, typename _Tp,typename _BinaryOperation>
_Tp accumulate(_InputIterator __first, _InputIterator __last, _Tp __init, _BinaryOperation __binary_op)
{
for(;__first != __last; ++__first)
__init = __binary_op(__init, *__first);
return __init;
}
//调用accumulate时,和__binary_op对应的实参可以是一个函数或函数对象
函数对象的应用示例
#include <iostream>
#include <vector>
#include <algorithm>
#include <numeric>
#include <functional>
using namespace std;
int sumSquares(int total, int value)
{
return total + value * value;
}
template <class T>
void PrintInterval(T first, T last)
{//输出区间[first,last)中的元素
for(;first!=last;++first)
cout<<*first<<" ";
cout<<endl;
}
template<class T>
class SumPowers
{
private:
int power;
public:
SumPowers(int p):power(p){}
const T operator()(const T & total, const T & value)
{//计算value的power次方,加到total上
T v = value;
for(int i = 0; i < power - 1 ; ++i)
v = v * value;
return total + v;
}
};
int main()
{
const int SIZE = 10;
int a1[] = {1,2,3,4,5,6,7,8,9,10};
vector<int> v(a1,a1+SIZE);
cout << "1.";
PrintInterval(v.begin(),v.end());
int result = accumulate(v.begin(),v.end(),0,SumSquares);//🌟
cout << "2.平方和" << result << endl;
result = accumulate(v.begin(),v.end(),0,SumPowers<int>(3));//🌛
cout << "3.立方和" << result << endl;
result = accumulate(v.begin(),v.end(),0,SumPowers<int>(4));
cout << "4.四次方和" << result << endl;
return 0;
}
//🌟句实例化出
int accumulate(vector<int>::iterator first, vector<int>::iterator last, int init, int(*op)(int,int))
{
for(;first != last; ++first)
init = op(init, *first);
return init;
}
//🌛句实例化出
int accumulate(vector<int>::iterator first, vector<int>::iterator last, int init, SumPowers<int> op)
{
for(;first != last; ++first)
init = op(init, *first);
return init;
}
STL中的函数对象类模版
以下模版可以用来生成函数对象
- equal_to
- greater
- less
头文件:<functional>
greater函数对象类模版
template <class T>
struct greater: public binary function <T, T, bool>{
bool operator()(const T& x, const T& y) const{
return x > y;
}
};
greater的应用
list有两个sort成员函数
-
void sort();
将list中的元素按"<"规定的比较方法升序排列
-
template <class Compare>
void sort(Compare op);
将list中的元素按op规定的比较方法升序排列,即要比较x,y大小时,看op(x,y)的返回值,为true则认为x小于y
#include <list>
#include <iostream>
using namespace std;
class MyLess{
public:
bool operator()(const int & c1, const int & c2)
{
return (c1 % 10) < (c2 % 10);
}
};
template <class T>
void Print(T first, T last)
{
for(;first != last;++first)
cout << * first << ",";
}
int main()
{
const int SIZE = 5;
int a[SIZE] = {5,21,14,2,3};
list<int> lst(a,a+SIZE);
lst.sort(MyLess());
Print(lst,begin(),lst.end());//21,2,3,14,5
cout << endl;
lst.sort(greater<lint>());//greater<int>()是个对象
Print(lst,begin(),lst.end());//21,14,5,3,2
cout << endl;
}
在STL中使用自定义的“大”“小”关系
关联容器盒STL中许多算法,都是可以用函数或函数对象自定义比较器的。在自定义了比较器op的情况下,以下三种说法等价:
- x小于y
- op(x,y)返回的值为true
- y大于x
例题
- 写出MyMax模版
#include <iostream>
#include <iterator>
using namespace std;
class MyLess{
public:
bool operator()(int a1, int a2) {
if((a1 % 10) < (a2 % 10))
return true;
else
return false;
}
};
bool MyCompare(int a1, int a2)
{
if((a1 % 10) < (a2 % 10))
return false;
else
return true;
}
int main()
{
int a[] = {35,7,13,19,12};
cout << *MyMax(a,a+5,MyLess()) << endl; //19
cout << *MyMax(a,a+5,MyCompare << endl; //12
return 0;
}
template <class T, class Pred>
T MyMax(T first, T last, Pred myless)
{
T tmpMax = first;
for(;first !=last;++first)
if(myless(*tmpMax,*first))
tmpMax = first;
return tmpMax;
};
习题
练习1
#include <iostream>
#include <iterator>
#include <set>
using namespace std;
int main() {
int a[] = {8,7,8,9,6,2,1};
set<int> v(a,a+7);
// 在此处补充你的代码
ostream_iterator<int> o(cout," ");
copy( v.begin(),v.end(),o);
return 0;
}
练习2 List
#include <iostream>
#include <iterator>
#include <list>//双向链表
#include <vector>
#include <string>
using namespace std;
list<int>& FindList(vector< list<int> >& l, int id)
{
int tmp = l.size();
vector< list<int> >::iterator i;
i = l.begin();
return *(i + id - 1);
}
int main()
{
int n;
cin >> n;
vector< list<int> > a;
for (int i = 0; i < n; i++)
{
string s;
cin >> s;
if(s == "new")
{
int id;
cin >> id ;
a.push_back(list<int>());//这个答案是默认id不会跳着设定……
}
else if(s == "add")
{
int id, num;
cin >> id >> num;
list<int>& temp = FindList(a, id);
temp.push_back(num);
temp.sort();//将list中的元素按"<"规定的比较方法升序排列
}
else if(s == "merge")
{
int id1, id2;
cin >> id1 >> id2;
list<int>& temp1 = FindList(a, id1);
list<int>& temp2 = FindList(a, id2);
temp1.sort();
temp2.sort();
temp1.merge(temp2);
}
else if(s == "unique")
{
int id;
cin >> id;
list<int>& temp = FindList(a, id);
temp.sort();
temp.unique();
}
else if(s == "out")
{
int id;
cin >> id;
list<int>& temp =FindList(a, id);
temp.sort();
if (temp.size() > 0)
{
list<int>::iterator i;
for (i = temp.begin(); i != temp.end(); i++)
{
cout << *i << " ";
}
}
cout << endl;
}
}
return 0;
}