文章目录
//获取代码
wget https://labfile.oss.aliyuncs.com/courses/1166/mySTL.zip
//解压文件到Code目录
unzip -q mySTL.zip -d ./Code/
1、C++ STL 简介
STL 介绍
你将学习到 c++ template ,异常处理 ,并回顾数据库的部分知识 ,初步掌握 STL 开发 ,避免重复制造轮子。
将学习到的知识点:
- 模板编程
- 泛型编程
- STL 常用组件
- lambda 表达式
- 异常处理
- 内存处理
- 部分数据结构
- 部分算法
STL 的原名是“Standard Template Library”,翻译过来就是标准模板库。STL 是 C++ 标准库的一个重要组成部分,STL 实现了常用的数据结构和算法 ,蕴含其间的泛型编程和代码复用
的思想深刻的影响了编程习惯,像微积分延长天文学家寿命一样,STL 延长了程序员的寿命。
STL 由算法,容器,迭代器,适配器,仿函数(函数对象),空间适配器六大部件组成
。
+ g++ main.cpp -std=c++11
+ g++ main.cpp -std=c++14
容器
这里的容器首先是一个模板类,在类中实现对数据的操作,而包含这样的类的实现就叫一个容器。STL 有许多这样的容器,它们包括:
向量(vector),列表(list),队列(queue),双端队列(deque),优先队列(Priority queue),集合(set),多种集合(multiset),映射(map),多重映射(multimap)。
算法
数据结构加算法等于程序,如果说容器实现了数据结构
的话,那么算法就是 STL 的灵魂
,STL 的算法是一种通用的算法,并不依赖于特定的数据结构和对象 。
这样的好处是不用针对每种情况编写特定的代码,而是给出一种通用的做法,是代码复用的一种实现方法,模板编程则是泛型编程的基础
。
迭代器
让我们演示一个简单的函数: add(int &a ,int &b) ,它传入两个引用,然后执行加法操作,可以看到它依赖于 int 这个特定的类型,而且暴露了这个函数的内部结构不利于对底层的隔离和封装。
那么 STL 是怎么解决这个问题的呢?
他们使用了迭代器(对指针的一种泛化)。迭代器底层是由指针实现的,是容器和算法的桥梁。STL 里大多数容器都实现了自己的迭代器,我们可以使用迭代器来完成对容器的访问。后面我们会详细讲到迭代器的种类,性质,使用,实现。
适配器
学习过数据结构的同学大都知道,数据结构不是独立的,部分数据结构是可以相互转换的。比如栈和队列可以互相实现。
当我们需要一个碗的时候我们不一定重新制造,我们可以把瓶子的上部去掉。同样的道理,当我们需要队列(queue)的时候,也可以用双端队列(deque)去实现。而 queue 就叫做适配器。
STL 有三种基本容器 vector,deque,list。有用基本容器扩展的适配器 queue,stack 等。适配器主要有容器适配器,迭代器适配器,函数适配器,它们的作用范围不同,意思大致一致。
仿函数
仿函数又叫做函数对象
,其本质是类的对象
,一种可回调机制,在类中重载了()运算符,使对象在用()时呈现出函数的特性,所以叫做仿函数。叫仿函数体现了它的作用,叫函数对象体现其本质,大家喜欢叫什么都可以。
而为什么需要仿函数呢?
因为 STL 没有也不可能将所有东西都包含到函数中,而程序是对现实的模拟,现实又是最复杂的,一个sort()
,你要 <
,我要 >
。如何协调呢?我们可以定义自己需要的仿函数,定制自己的操作。
空间配置器
c++ 的一大魅力就是对底层的操作,你像一个魔法师一样,挥舞着魔杖操纵着底层的各种资源。当然一个不好,程序也崩给你看。
而空间配置器就是 STL 自己的“内存池”
。完成对内存的申请,释放,维护。配置器有两个部分:一级空间配置器,二级空间配置器。
本次课程不会过度讲解配置器,感兴趣的同学可以去看一下另外一个课程:c++ 实现高性能内存池。
2、template 编程和迭代器粗解
模板编程
模板编程是 STL 的基石,也是 c++11 的核心特性之一。模板是相对于编译器而言,顾名思义就是向编译器提供一个处理事务的模板,以后需要处理的东西,如果都是这个事务类型,那么统统用这个模板处理。
模板的基本语法如下:
template <typename/class T>
template 告诉编译器,接下来是一个模板 ,typename 和 class 都是关键字,在这里二者可以互用没有区别。在< >中 T 叫做模板形参,一旦模板被实例化,T 也会变成具体的类型。接下来,我们看一个例子。
模板函数
template <typename T>
T add(const T lva ,const T rva)
{
T a ;
a = lva + rva ;
return a;
}
- 这是一个模板函数的简单实例,所有模板函数在开始都需要 template 语句,以告诉编译器这是一个模板和参数等必要信息,当然里面的 T 可以取任意你喜欢的名字 ,模板参数个数也是任意更换的。
- 还要提醒的一点是:template <typename T1 ,typename T2 = int> 函数模板是支持默认参数的,T1 、T2 顺序在默认情况下是可以任意的,不用严格按照从右到左的顺序。
然后就是使用了,我们可以写出add(1,2) 这样的函数,也可以写出add(2.5,4.6)这样的函数,向 add 函数提供参数时,编译器会自动分析参数的类型,然后将所有用到 T 定义的换成相对性的类型,以上的两个函数在编译期间会生成
:
int add(const int lva ,const int rva)
{
int a ;
a = lva + rva ;
return a;
}
double add(const double lva ,const double rva)
{
double a ;
a = lva + rva ;
return a;
}
这样的两个具体函数
。如果我们使用add(1,2.0)是会报错的,编译器无法找到add(int,double)。
类模板和成员模板
- 类模版
c++11 不仅支持对函数的模板化,也支持对类的模板,下面来看基本的语法是怎样的:
template <class T>
class Myclass
{
T a;
public:
T add(const T lva ,const T rva);
};
template <class T>
T Myclass<T>::add(const T lva, const T rva)
{
a = lva + rva;
return a;
}
这是一个简单并且典型的类模板,在程序中给出模板并不能使用它,还必须实例化,比如:
Myclass<int> A; //用 int 实例化一个类 A
Myclass<double> B; //用 double 实例化一个类 B
当程序编译到这里时就会按照我们给出的类型,声明两组类和两组类函数
。注意,在这里我们一定要显式给出类型 T
。
类模板不像是函数模板 ,函数模板会根据参数推断类型。 当然类模板也支持默认参数,但是类模板必须严格从右往左默认化。
- 成员模板
模板的使用范围是广泛的,不仅可以用作函数模板,类模板,还可以用作 class ,struct ,template class 的成员。而要实现 STL 这是我们必须掌握和使用的特性。我们先看一个简单的例子,用上面的类改编而来:
template <class T>
class Myclass
{
public:
T a;
template <typename type_1 , typename type_2>
type_1 add(const type_1 lva ,const type_2 rva);
};
template <class T>
template <typename type_1,typename type_2>
type_1 Myclass<T>::add(const type_1 lva, const type_2 rva)
{
a = lva + rva;
return a;
}
在类的声明中使用了一个嵌套的模板声明。且通过作用域运算符 :: 指出 add 是类的成员,
需要注意的一点,有些编译器不支持模板成员
,而有些编译器不支持在类外定义
。
我们默认大家的编译器都支持。模板如此强大,甚至允许我们在模板类中再建立模板类:
template <class T>
class Myclass
{
public:
T a;
template <typename type_1 , typename type_2>
type_1 add(const type_1 lva ,const type_2 rva);
template <class type_3>
class Myclass_2; // 声明放在这里,具体定义放在类外进行。
Myclass_2<T> C; // 定义一个Myclass_2 类 A。使用 T 进行实例化
};
template <class T>
template <typename type_1,typename type_2>
type_1 Myclass<T>::add(const type_1 lva, const type_2 rva)
{
a = lva + rva;
return a;
}
template <class T>
template <class type_3>
class Myclass<T>::Myclass_2
{
public:
type_3 value;
type_3 sub(const type_3 a , const type_3 b) {
vlaue = a - b;}
};
模板类中的静态成员
我们知道,在类中定义的静态成员是存储在静态区中,被所有类对象共享,并不属于某一个类所有。
同样的在模板类中的静态成员也不会被复制多份,而是被同类实例化的类对象共享
,比如所有 int 和所有 double 的类对象,享有相互独立的静态变量。
也可以说是编译器生成了 int 和 double 两个版本的类定义。
typename 和 class
typename和class是模板中经常使用的两个关键词 ,在模板定义的时候没有什么区别。
以前用的是 class,后来 c++ 委员会加入了 typename。因为历史原因,两个是可以通用的。
对有些程序员来说,在定义类模板的时候,常常使用 class 作为关键字,增加代码可读性。其它则用 typename,上面的代码大都遵循这样的标准,但是并无强制规定。
但是如果二者没有差别,为什么还要加入 typename 呢?
c++标准委员会不会增加无用的特性,让我们来看一个例子:
class Myclass{
public:
Myclass();
typedef int test; //定义类型别名
}
template <class T>
class Myclass2{
public:
Myclass2();
T::test *a // 声明一个指向T::test类型的指针。
// typename T::test * a
}
以上的代码没有全部写完,大家觉得编译器能够过吗?
答案是不能,因为在 c++ 中,允许我们在类中定义一个类型别名,且使用的时候和类名访问类成员
的方法一样。
这样编译器在编译的时候就会产生二义性,它根本不知道这是一个类型还是别名,所以我们加上 typename 显式说明出来。
当然如果这里没有二义性,比如Myclass ::test * a ,加上 typename 是会报错的。
此外,在 class 的 STL 底层还有一个特性,用于保留模板参数,但是在 c++17 中已经舍弃。
3、迭代器
迭代器(iterator)是一种对象,它能够用来遍历标准模板库容器中的部分或全部元素,每个迭代器对象代表容器中的确定的地址。
迭代器修改了常规指针的接口,所谓迭代器是一种概念上的抽象
:那些行为上像迭代器的东西都可以叫做迭代器。
然而迭代器有很多不同的能力,它可以把抽象容器和通用算法有机的统一起来
。
迭代器基本分为五种,输入输出迭代器,前向逆向迭代器,双向迭代器和随机迭代器。
简单概括:迭代器是一种检查容器内元素并遍历元素的可带泛型数据类型
。
下面,我们新建头文件Iterator.h
是头文件,用来实现我们的迭代器,这里的代码需要引用到系统头文件#include <cstddef>
,它主要用于定义一些类型。接下来我们定义 5 种迭代器的类型,将其写入Iterator.h
文件中:
struct input_iterator_tag{
};//返回输入迭代器
struct output_iterator_tag{
};//返回输出迭代器
struct forward_iterator_tag :public input_iterator_tag {
};//返回前向迭代器
struct bidirectional_iterator_tag :public forward_iterator_tag {
};//返回双向迭代器
struct random_access_iterator_tag :public bidirectional_iterator_tag {
};//返回随机迭代器
//获取代码
wget https://labfile.oss.aliyuncs.com/courses/1166/mySTL.zip
unzip -q mySTL.zip -d ./Code/
输入迭代器
通过对输入迭代器解除引用,它将引用对象,而对象可能位于集合中。通常用于传递地址。
template<class T, class Distance>
struct input_iterator {
typedef input_iterator_tag iterator_category;//返回类型
typedef T value_type;//所指对象类型
typedef Distance difference_type;//迭代器间距离类型
typedef T* pointer;//操作结果类型
typedef T& reference;//解引用操作结果类型
};
输出迭代器
该类迭代器和输入迭代器极其相似,也只能单步向前迭代元素,不同的是该类迭代器对元素只有读的权力。通常用于返回地址。
struct output_iterator{
typedef output_iterator_tag iterator_category;
typedef void value_type;
typedef void difference_type;
typedef void pointer;
typedef void reference;
};
前向迭代器
前向迭代器可以在一个正确的区间中进行读写操作,它拥有输入迭代器的所有特性,和输出迭代器的部分特性,以及单步向前迭代元素的能力。通常用于遍历。
template <class T, class Distance> struct forward_iterator{
typedef forward_iterator_tag iterator_category;
typedef T value_type;
typedef Distance difference_type;
typedef T* pointer;
typedef T& reference;
};
双向迭代器
该类迭代器是在前向迭代器的基础上提供了单步向后迭代元素的能力,前向迭代器的高级版。
template <class T, class Distance> struct bidirectional_iterator{
typedef bidirectional_iterator_tag iterator_category;
typedef T value_type;
typedef Distance difference_type;
typedef T* pointer;
typedef T& reference;
};
随机迭代器
该类迭代器能完成上面所有迭代器的工作,它自己独有的特性就是可以像指针那样进行算术计算,而不是仅仅只有单步向前或向后迭代。
template <class T, class Distance> struct random_access_iterator{
typedef random_access_iterator_tag iterator_category;
typedef T value_type;
typedef Distance difference_type;
typedef T* pointer;
typedef T& reference;
};
迭代器辅助函数
迭代器的实现,我们这里主要会使用到两个函数来辅助完成操作,分别是 advace 函数和 distance 函数。这两个函数是写到Algorithm.h
文件中的。
- advance 函数: 用于迭代器前移,增加迭代的位置。可用于定向访问到迭代器的某个变量。
template<class InputIterator, class Distance>
void _advance(InputIterator& it, Distance n, input_iterator_tag){
assert(n >= 0);
while (n--){
//当n大于0,迭代器前移n位
++it;
}
}
void advance(InputIterator& it, Distance n){
typedef typename iterator_traits<InputIterator>::iterator_category iterator_category;
_advance(it, n, iterator_category());
}
- distance 函数: 用于计算迭代器间距离
template<class InputIterator>
typename iterator_traits<InputIterator>::difference_type
_distance(InputIterator first, InputIterator last, input_iterator_tag){
typename iterator_traits<InputIterator>::difference_type dist = 0;//初始化距离
while (first++ != last){
//当首地址不等于尾地址,距离增加
++dist;
}
return dist;//返回迭代器间距离
}
template<class Iterator>
typename iterator_traits<Iterator>::difference_type
distance(Iterator first, Iterator last){
typedef typename iterator_traits<Iterator>::iterator_category iterator_category;
return _distance(first, last, iterator_category());
}
实例测试
完成上面迭代器的实现后,我们现在在 Test 目录下新建文件 iteratortest.cpp,用于测试 Iterator 的功能。这里的测试我们需要借助到 vector 容器。关于Vector 容器的内容,我们将在后面的实验中给大家讲解,这里我们直接使用 Vector.h的内容。该文件可以通过下载课程源码找到。
#include <iostream>
#include "Iterator.h"
#include "Vector.h"
int main()
{
mySTL::vector<int> vec;
for(int i = 0;i < 10;i++)
vec.push_back(i);
mySTL::vector<int>::iterator it = vec.begin();
mySTL::vector<int>::iterator end = vec.end();
std::cout<<"The value of vector:";
for(;it != vec.end();it++)
std::cout<<*it<<" ";
std::cout<<std::endl;
//advance使用
it = vec.begin();
std::cout<<"After advance 3:";
mySTL::advance(it,3);
std::cout << *it << " "<<std::endl;
//distance使用
std::cout<<"The distance of position 3 to the end:";
std::cout<<mySTL::distance(it,end)<<std::endl;
return 0;
}
4、函数对象(仿函数)
函数对象概述
函数对象
是重载函数调用操作符
的类的对象
。即函数对象是行为类似函数的对象
,又称仿函数
,是一个能被当做普通函数来调用的对象
。
函数对象与函数指针相比,有两个优点:
- 第一是编译器可以内联执行函数对象的调用;
- 第二是函数对象内部可以保持状态。
STL 中的众多算法,非常依赖于函数对象处理容器的元素。所以 STL 预定义了许多函数对象、谓词和适配器。
预定义和辅助函数对象
我们可以在 Functional.h 中预定义一些函数对象,以方便在以后的实验中直接调用。
首先在 include 目录下创建 Functional.h。
- unary_function: 作为
一元函数对象
的基类,只定义了参数和返回值的类型
template<class T>
struct unary_function {
typedef T argumant_type;
typedef T result_type;
};
- binary_function:作为
二元函数
基类,只定义了参数和返回值的类型
template<class T>
struct binary_function {
typedef T first_argument_type;
typedef T second_argument_type;
typedef T result_type;
};
- less:用于返回较小值
template<class T>
struct less{
typedef T first_argument_type;
typedef T second_argument_type;
typedef bool result_type;
result_type operator()(const first_argument_type& x, const second_argument_type& y){
return x < y;
}
};
- equal_to: 判断是否相等
template<class T>
struct equal_to{
typedef T first_argument_type;
typedef T second_argument_type;
typedef bool result_type;
result_type operator()(const first_argument_type& x, const second_argument_type& y){
return x == y;
}
};
- identity: 验证同一性
template <class T>
struct identity : public unary_function<T> {
const T& operator()(const T& x) const {
return x;} //函数调用操作符
};
- select1st: 返回键值,在 map 中会用到
template <class T>
struct select1st : public unary_function<T, typename T::first_type> {
const typename T::first_type& operator()(const T& x) const {
return x.first;}
适配器
函数对象适配器
本质上任然是一个函数;函数对象适配器提供了对函数对象或者普通函数的操作,使其能够根据我们的需求来修改函数对象或者普通函数的功能。
使用函数对象适配器的步骤:
-
(1)首先让自定义的函数对象 public 继承一个父类。这里有两个选择:binary_function 和 unary_function。如果有两个参数选择前者。
-
(2)定义一个函数对象作为参数传入
函数对象适配器
。常见的函数对象适配器有:- 绑定适配器 bind1st bind2nd (bind1st 绑定第一个参数, bind2nd 绑定第二个参数)
- 取反适配器 not1 not2 (not1 作用于一元函数对象,not2 作用于二元函数对象)
- 普通函数适配器 ptr_fun
- 作用于类中方法的适配器
mem_fun
mem_fun_ref
-
(3)加 const
实例测试
我们在上面讲了预定义函数对象和适配器,在下面我们就来演示预定义函数和适配器的搭配。我们在 Test 文件夹下建立 functionaltest.cpp,
#include <iostream>
#include "Vector.h"
#include "Functional.h"
using namespace std;
class compare:public binary_function<int,int,bool>{
//用于接收两个参数
public:
bool operator()(int i, int num) const {
return i > num;
}
};
class comparetonum:public unary_function<int,bool>{
//用于接收一个参数
public:
bool operator()(int i) const {
return i > 5;
}
};
void print(int i,int j)//普通函数对象
{
if (i > j){
cout << i << " ";
}
}
int main(){
mySTL::vector <int> vec;
for (int i = 0; i < 10; i++)
{
vec.push_back(i + 1);
}
mySTL::vector<int>::iterator it = find_if(vec.begin(), vec.end(), bind2nd(compare(),6));//找出大于6的第一个数
if (it == vec.end())
{
cout << "cannot find the number!" << endl;
}
else
{
cout << "find num: " << *it << endl;
}
mySTL::vector<int>::iterator rit = find_if(vec.begin(), vec.end(), not1(comparetonum())); //取反适配器的用法,找出小于5的第一个数
if (rit == vec.end())
{
cout << "cannot find the number!" << endl;
}
else
{
cout << "find num: " << *rit << endl;
}
mySTL::vector<int> vec1;
for (int i = 0; i < 10; i++)
{
vec1.push_back(i);
}
cout<<"The num larger than 5: ";
mySTL::for_each(vec1.begin(), vec1.end(), bind2nd(ptr_fun(print),5)); //使用ptr_fun将普通函数转换为函数对象,然后给函数对象绑定参数。
cout << endl;
return 0;
}
执行命令:
g++ functionaltest.cpp -std=c++11 -o functionaltest -I ../include
总结
- 函数对象是重载了“()”操作符的普通类对象,因此从语法上讲,函数对象与普通的函数行为类似。
- 函数对象的优点是可以在内部修改而不改动外部接口,所以非常方便,节省了不少开发时间。
5、算法
lambda 表达式定制操作
泛型算法中的定制操作
很多算法都会比较输入序列中的元素以达到排序的效果,通过定制比较动作,可以控制算法按照编程者的意图工作。 这里我们用排序算法举例。
- 普通排序算法,这样的排序算法只能按照从小到大排序,很多时候是不适用的。
template<class RandomIterator>
void sort(RandomIterator first, RandomIterator last){
if (first >= last || first + 1 == last)
return;
if (last - first <= 20)//区间长度小于等于20的采用冒泡排序更快
return bubble_sort(first, last, pred);
auto mid = mid3(first, last - 1, pred);
auto p1 = first, p2 = last - 2;
while (p1 < p2){
while (pred(*p1, mid) && (p1 < p2)) ++p1;
while (!pred(*p2, mid) && (p1 < p2)) --p2;
if (p1 < p2){
swap(*p1, *p2);
}
}
swap(*p1, *(last - 2));//将作为哨兵的mid item换回原来的位置
sort(first, p1, pred);
sort(p1 + 1, last, pred);
}
- 排序算法的定制操作
普通排序算法只能由小到大排序,并不智能。而排序算法的定制操作,此函数多了一个类型 BinaryPredicate,可以用来定制规则(如从大到小),增加了实用性。
template<class RandomIterator, class BinaryPredicate>
void sort(RandomIterator first, RandomIterator last, BinaryPredicate pred){
if (first >= last || first + 1 == last)
return;
if (last - first <= 20)//区间长度小于等于20的采用冒泡排序更快
return bubble_sort(first, last, pred);
auto mid = mid3(first, last - 1, pred);
auto p1 = first, p2 = last - 2;
while (p1 < p2){
while (pred(*p1, mid) && (p1 < p2)) ++p1;
while (!pred(*p2, mid) && (p1 < p2)) --p2;
if (p1 < p2){
swap(*p1, *p2);
}
}
swap(*p1, *(last - 2));//将mid item换回原来的位置
sort(first, p1, pred);
sort(p1 + 1, last, pred);
}
谓词
- 谓词相当于一个动作(将要干什么),比如有一个需求,希望从大到小排序,则可以先定义一个谓词(函数)。
bool comp(const int& v1,const int& v2)
{
return v1 > v2;
}
- 将这个函数传递给 sort 算法,就可以按照从大到小排序。
sort(v.begin(),v.end(),comp);
lambda 表达式
前面的例子中,定义了一个函数传递给 sort 算法。如果这个函数可以重复使用还好,如果只是使用一次的话就显得比较麻烦,而且浪费了空间。
这种情况下就可以使用 lamada 表达式。lamada 表达式相较于谓词,它没有定义函数(没有函数名)
。
sort(v.begin(),v.end(),[]comp(const int& v1,const int& v2){
return v1 > v2;});//这种没有定义函数的指定动作(谓词)的方式就是lambda表达式。
STL 算法
算法部分主要包含在头文件 “Algorithm.h” 、 "Functional.h” 和 “numeric.h ” 中。
- "Algorithm.h"是所有 STL 头文件中最大的一个,其中常用到的功能范围涉及到比较、 交换、查找、遍历操作、复制、修改、反转、排序、合并等等。
- "Functional.h”中则定义了一些模板类,用以声明函数对象。
- “Numeric.h ” 则包含一些数值运算操作,这个会在 STL 数值算法中讲到。
STL 算法大大小小差不多有 70+,下面列举一些我们实验需要常用的。
首先在 include 目录下创建 Algorithm.h。
- find: 利用底层元素的等于操作符,对指定范围内的元素与输入值进行比较,当匹配时,结束搜索,返回该元素的一个迭代器。用于查找。
template <class InputIterator, class T>
InputIterator find(InputIterator first, InputIterator last, const T& val){
for (; first != last; ++first){
if (*first == val)
break;
}
return first;
}
- sort: 以升序重新排列指定范围内的元素,重载版本使用自定义的比较操作。用于排序。
template<class RandomIterator>
void sort(RandomIterator first, RandomIterator last){
return sort(first, last, less<typename iterator_traits<RandomIterator>::value_type>());
}
template<class RandomIterator, class BinaryPredicate>
void sort(RandomIterator first, RandomIterator last, BinaryPredicate pred){
if (first >= last || first + 1 == last)
return;
if (last - first <= 20)//区间长度小于等于20的采用冒泡排序更快
return bubble_sort(first, last, pred);
auto mid = mid3(first, last - 1, pred);
auto p1 = first, p2 = last - 2;
while (p1 < p2){
//快速排序
while (pred(*p1, mid) && (p1 < p2)) ++p1;
while (!pred(*p2, mid) && (p1 < p2)) --p2;
if (p1 < p2){
swap(*p1, *p2);
}
}
swap(*p1, *(last - 2));//将作为哨兵的mid item换回原来的位置
sort(first, p1, pred);
sort(p1 + 1, last, pred);
}
- swap: 交换存储在两个对象中的值。
template <class RandomAccessIterator, class Compare>
void pop_heap(RandomAccessIterator first, RandomAccessIterator last, Compare comp){
mySTL::swap(*first, *(last - 1));
if (last - first >= 2)
mySTL::down(first, last - 2, first, comp);
}
- up:上溯算法,用于从下向上遍历
template<class RandomAccessIterator, class Compare>
//heap上溯算法
static void up(RandomAccessIterator first, RandomAccessIterator last,
RandomAccessIterator head, Compare comp){
if (first != last){
int index = last - head;
auto parentIndex = (index - 1) / 2;
for (auto cur = last; parentIndex >= 0 && cur != head; parentIndex = (index - 1) / 2){
auto parent = head + parentIndex;//get parent
if (comp(*parent, *cur))
mySTL::swap(*parent, *cur);
cur = parent;
index = cur - head;
}
}
}
- down:下降算法,用于从上向下遍历
template<class RandomAccessIterator, class Compare>
//heap下降算法
static void down(RandomAccessIterator first, RandomAccessIterator last,
RandomAccessIterator head, Compare comp){
if (first != last){
auto index = first - head;
auto leftChildIndex = index * 2 + 1;
for (auto cur = first; leftChildIndex < (last - head + 1) && cur < last; leftChildIndex = index * 2 + 1){
auto child = head + leftChildIndex;//get the left child
if ((child + 1) <= last && *(child + 1) > *child)//cur has a right child
child = child + 1;
if (comp(*cur, *child))
mySTL::swap(*cur, *child);
cur = child;
index = cur - head;
}
}
}
- copy: 复制算法,常用于赋值
template<>
inline char *copy(char *first, char *last, char *result){
auto dist = last - first;
memcpy(result, first, sizeof(*first) * dist);
return result + dist;
}
set 算法
STL 中有关 set 给出了四种算法,分别是交集,并集, 差集,对称差集。但是此处的 set 不同于数学中的集合。数学中的集合允许元素以任意次数、任意次序出现,但此处的不允许元素重复出现,而且所有元素按序出现。这四种算法处理的结构也是有序的。
- 交集 set_intersection
交集用于求出两个不同集合中的相同元素。
template<class InputIterator,class OutputIterator>
OutputIt set_intersection(InputIterator first1, InputIterator last1,
InputIterator first2, InputIterator last2,
OutputIterator d_first){
while (first1 != last1 && first2 != last2) {
if (*first1 < *first2) {
++first1;
}
else {
if (!(*first2 < *first1)) {
*d_first++ = *first1++;
}
++first2;
}
}
return d_first;
}
- 并集 set_union
将两个集合合并到一起,相同的元素只在并集中出现一次。
template<class InputIterator,class OutputIterator>
OutputIt set_union(InputIterator first1, InputIterator last1,
InputIterator first2, InputIterator last2,
OutputIterator d_first)
{
for (; first1 != last1; ++d_first) {
if (first2 == last2)
return std::copy(first1, last1, d_first);
if (*first2 < *first1) {
*d_first = *first2++;
}
else {
*d_first = *first1;
if (!(*first1 < *first2))
++first2;
++first1;
}
}
return std::copy(first2, last2, d_first);
}
- 差集 set_difference
在集合 1 中出现而没有在集合 2 出现的元素
template<class InputIterator, class OutputIterator>
OutputIt set_difference(InputIterator first1, InputIterator last1,
InputIterator first2, InputIterator last2,
OutputIterator d_first)
{
while (first1 != last1) {
if (first2 =