Coursera课程笔记----C++程序设计----Week8

C++程序设计

标准模板库 STL-1(week 8)

STL概述(Standard Template Library)

C++重用

  • C++语言的核心优势之一就是便于软件的重用
  • C++中有两个方面体现重用:
    1. 面向对象的思想:继承和多态,标准类库
    2. 泛型程序设计(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就是两个迭代器

容器概述

  • 可以用于存放各种类型的数据(基本类型的变量、对象等)的数据结构,都是类模版,分为三种:

    1. 顺序容器:vector,deque,list
    2. 关联容器:set,multiset,map,multimap
    3. 容器适配器: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时,在缺省的情况下,以下三个说法等价

  1. x比y小
  2. 表达式“x<y”为真
  3. 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的情况下,以下三种说法等价:

  1. x小于y
  2. op(x,y)返回的值为true
  3. 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;
}
posted @ 2020-07-02 23:58  maimai_d  阅读(126)  评论(0编辑  收藏  举报