Bootstrap

C++基础语法三

1. 模板

模板就是建立通用的模具,大大提高复用性

特点:

  • 模板不可以直接使用,它只是一个框架
  • 模板的通用并不是万能的

C++提供两种模板机制:函数模板和类模板

1.1 函数模板

函数模板作用:建立一个通用函数,其函数返回值类型和形参类型可以不具体制定,用一个虚拟的类型来代表

语法:

template <typename T> 或 template <class T>

函数声明或定义

解释: 

template:声明创建模板(关键字)

typename:表明其后面的符号是一种数据类型,可以用class代替(关键字)

T:通用的数据类型,名称可以替换,通常为大写字母

如实现两个数交换的函数模板:

template<typename T>
void Swap(T& a, T& b) {
    T temp = a;
    a = b;
    b = temp;
}

两种方式使用函数模板

1.自动类型推导        Swap(a, b);

2.显示指定类型        Swap<int>(a, b);

函数模板注意事项:

  • 自动类型推导,必须推导出一致的数据类型T,才可以使用
  • 模板必须要确定出T的数据类型,才可以使用

确定T的数据类型:

如定义一个函数模板  template <typename T>   void func () { },func()中没有形参,无法确定T,直接调用func();会报错,可以确定函数的数据类型,正常调用为func<int>( );   

普通函数和函数模板的区别:

  • 普通函数调用时可以发生自动类型转换(隐式类型转换)
  • 函数模板调用时,如果利用自动类型推导,不会发生隐式类型转换
  • 如果利用显示指定类型的方式,可以发生隐式类型转换

普通函数与函数模板的调用规则:

  • 如果函数模板和普通函数都可以实现,优先调用普通函数
  • 可以通过空模板参数列表来强制调用函数模板   函数名称 < > (形参);
  • 函数模板也可以发生重载
  • 如果函数模板可以产生更好的匹配,优先调用函数模板

模板也拥有局限性,即模板的通用性并不是万能的,即无法直接实现数组的比对,如a=b;因此,为了解决这种问题,提供模板的重载,可以为这些特定的类型提供具体化的模板

利用具体化的模板,可以解决自定义类型的通用化

学习模板并不是为了写模板,而是在STL能够运用系统提供的模板 

1.2 类模板

类模板作用:建立一个通用类,类中的成员数据类型可以不具体制定,用一个虚拟的类型来代表

语法:

template <typename T> 或 template <class T>

解释: 

template:声明创建模板(关键字)

typename:表明其后面的符号是一种数据类型,可以用class代替(关键字)

T:通用的数据类型,名称可以替换,通常为大写字母

如定义一个人的类模板

template <class NameType, class AgeType>  //成员中需要几种类型就定义几种类型

class Person{

public:

        Person(){

        this.m_Name=name;

        this.m_Age=age;

}

        NameType m_Name;

        AgeType m_Age;

}

后续创建时  Person<string,int> p1("姓名",999);

类模板与函数模板的区别:

  • 类模板没有自动类型推导的使用方式
  • 类模板在模板参数列表中可以有默认参数

类模板在模板参数列表中可以有默认参数,如template <class NameType, class AgeType = int>,在后面定义时 Person<string> p1("姓名",999) 可以省去第二种数据类型

类模板中成员函数和普通类中成员函数创建时机:

  • 普通类中的成员函数一开始就可以创建
  • 类模板中的成员函数在调用时才创建

类模板对象做函数参数,一共有三种传入方式:

  • 指定传入的类型 - 直接显示对象的数据类型(最常用)
  • 参数模板化 - 将对象中的参数变为模板进行传递
  • 整个类模板化 - 将这个对象类型模板化进行传递

查看类模板中的成员类型,使用 typeid(T).name() 可以查看

例子(传入方式):

#include<iostream>
using namespace std;

template<class T1,class T2>
class Person {
public:
	Person(T1 name, T2 age) {
		m_Name = name;
		m_Age = age;
	}

	void showPerson() {
		cout << "姓名:" << m_Name << " " << "年龄:" << m_Age << endl;
	}

	T1 m_Name;
	T2 m_Age;
};


//1.指定传入类型
void printPerson1(Person<string, int>& p) {
	p.showPerson();
}

void test01() {
	Person<string, int> p("孙悟空", 999);
	printPerson1(p);
}

//2.参数模板化
template<class T1,class T2>
void printPerson2(Person<T1, T2>& p) {
	p.showPerson();
	cout << "T1的类型为: " << typeid(T1).name() << endl;
	cout << "T2的类型为: " << typeid(T2).name() << endl;
}


void test02() {
	Person<string, int> p("猪八戒", 989);
	printPerson2(p);
}

//3.整个类模板化
template<class T>
void printPerson3(T& p) {
	p.showPerson();
	cout << "T的类型为: " << typeid(T).name() << endl;
}

void test03() {
	Person<string, int> p("唐僧", 98);
	printPerson3(p);
}

int main() {

	test01();
	test02();
	test03();
	system("pause");
	return 0;
}

当类模板碰到继承时,需要注意:

  • 当子类继承的父类是一个类模板时,子类在声明时,要指定出父类中T的类型
  • 如果不指定,编译器无法给子类分配内存
  • 如果想灵活指定出父类中T的类型,子类也需变为类模板

例如:

template<class T>
class Base {
    T m;
};

class Son :public Base<int> {       //指定出父类中T的类型

};

template<class T1,class T2>
class Son1 :public Base<T2> {     // 子类也变为类模板
    T1 obj;
};

.h文件和.cpp文件中的内容可以写在一起,后缀名为.hpp,约定俗称为类模板文件

类模板与友元:

  • 全局函数类内实现 - 直接在类内声明友元即可
  • 全局函数类外实现 - 需要提前让编译器知道全局函数的存在

类模板案例:实现一个通用数组类,要求为:

  • 可以对内置数据类型以及自定义数据类型的数据进行存储
  • 将数组中的数据存储到堆区
  • 构造函数中可以传入数组的容量
  • 提供对应的拷贝构造函数以及operator=防止浅拷贝问题
  • 提供尾插法和尾删法对数组中的数据进行增加和删除
  • 可以通过下标的方式访问数组中的元素
  • 可以获取数组中当前元素个数和数组的容量

实现:

#include<iostream>
using namespace std;

template<class T>
class MyArray {
public:

	MyArray(int capacity) {
		//cout << "有参构造函数" << endl;
		this->m_Capacity = capacity;
		this->m_Size = 0;
		this->Paddress = new T[this->m_Capacity];
	}

	MyArray(const MyArray& arr) {
		//cout << "拷贝构造函数" << endl;
		this->m_Capacity = arr.m_Capacity;
		this->m_Size = arr.m_Size;
		this->Paddress = new T[this->m_Capacity];
		for (int i = 0; i < this->m_Size; i++) {
			this->Paddress[i] = arr.Paddress[i];
		}
	}

	MyArray& operator = (const MyArray & arr){
		//cout << "=运算符重载" << endl;
		if (this->Paddress != NULL) {
			delete[] this->Paddress;
			this->m_Capacity = 0;
			this->m_Size = 0;
		}

		this->m_Capacity = arr.m_Capacity;
		this->m_Size = arr.m_Size;
		this->Paddress = new T[this->m_Capacity];
		for (int i = 0; i < this->m_Size; i++) {
			this->Paddress[i] = arr[i];
		}

		return *this;
		
	}

	T& operator[](int index) {

		return this->Paddress[index];
	}


	void Push_Back(const T& val) {

		if (this->m_Capacity == this->m_Size) {
			return;
		}
		this->Paddress[this->m_Size] = val;//在数组末尾插入数据
		this->m_Size++;
	}

	void Pop_Back() {

		if (this->m_Size == 0) {
			return;
		}

		this->m_Size--;
	}

	int GetCapacity() {

		return this->m_Capacity;
	}

	int GetSize() {

		return this->m_Size;
	}


	~MyArray() {
		//cout << "析构函数" << endl;
		if (this->Paddress != NULL) {
			delete[] this->Paddress;
			this->Paddress = NULL;
			this->m_Capacity = 0;
			this->m_Size = 0;
		}
	}

private:
	T* Paddress;
	int m_Capacity;
	int m_Size;
};

void printIntArray(MyArray<int>& arr) {

	for (int i = 0; i < arr.GetSize(); i++) {

		cout << arr[i] << " ";
	}
	cout << endl;
}

void test01() {

	MyArray<int> arr1(5);
	for (int i = 0; i < 5; i++) {
		arr1.Push_Back(i);
	}
	cout << "arr1的打印输出为:" << endl;
	printIntArray(arr1);

	cout << "arr1的容量为:" << arr1.GetCapacity() << endl;
	cout << "arr1的大小为:" << arr1.GetSize() << endl;

	cout << "_____________________________" << endl;

	MyArray<int> arr2(arr1);
	arr2.Pop_Back();
	cout << "arr2的打印输出为:" << endl;
	printIntArray(arr2);

	cout << "arr2的容量为:" << arr2.GetCapacity() << endl;
	cout << "arr2的大小为:" << arr2.GetSize() << endl;

	cout << "_____________________________" << endl;

	/*MyArray<int> arr2(arr1);

	MyArray<int> arr3(100);
	arr3 = arr1;*/
}

class Person {
public:

	Person() {}
	Person(string name, int age) {
		this->m_Name = name;
		this->m_Age = age;
	}
	string m_Name;
	int m_Age;
};


void printPersonArray(MyArray<Person> & arr) {

	for (int i = 0; i < arr.GetSize(); i++) {

		cout << "姓名:" << arr[i].m_Name << "  " << "年龄:" << arr[i].m_Age << endl;
	}

}

void test02() {

	MyArray<Person> arr(10);
	Person p1("Tom", 99);
	Person p2("Coy", 93);
	Person p3("Li", 85);
	Person p4("VOU", 103);
	Person p5("CCC", 166);

	arr.Push_Back(p1);
	arr.Push_Back(p2);
	arr.Push_Back(p3);
	arr.Push_Back(p4);
	arr.Push_Back(p5);

	printPersonArray(arr);

	cout << "arr的容量为:" << arr.GetCapacity() << endl;
	cout << "arr的大小为:" << arr.GetSize() << endl;

}

int main() {

	test01();
	test02();

	system("pause");
	return 0;
}

2. STL - 初识

C++的面向对象(封装,继承,多态)泛型编程(模板)思想,目的就是复用性的提升

为了建立数据结构和算法的一套标准,诞生了STL

2.1 STL基本概念

STL:Standard Template Library,标准模板库

STL从广义上分为:容器(container),算法(algorithm),迭代器(iterator)

容器和算法之间通过迭代器进行无缝连接

STL几乎所有的代码都采用了模板类或者模板函数

2.2 STL六大组件

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

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

2.2.1 容器

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

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

容器分为序列式容器和关联式容器

  • 序列式容器:强调值得排序,序列式容器中的每个元素均有固定的位置
  • 关联式容器:二叉树结构,各元素之间没有严格的物理上的顺序关系

2.2.2 算法

有限的步骤,解决逻辑或数学上的问题,叫做算法

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

  • 质变算法:是指运算过程中会更改区间内的元素的内容,如拷贝,替换,删除等
  • 非质变算法:是指运算过程中不会更改区间内的元素内容,如查找,计数,遍历,寻找极值等

2.2.3 迭代器

算法要通过迭代器才能访问容器中的元素

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

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

迭代器使用非常类似指针

常用的容器中迭代器种类为双向迭代器和随机访问迭代器

  • 双向迭代器:功能为读写操作,并能向前和向后操作,支持运算为读写,支持++,--
  • 随机访问迭代器:功能为读写操作,可以以跳跃的方式访问任意数据,功能最强的迭代器,支持运算为读写,支持++,--,[n],-n,<,<=,>,>=

3. STL- 常用容器

3.1 string容器

string本质上是一个类,类内部封装了char*,是一个char*型的容器。string管理char*所分配的内存,不用担心复制越界和取值越界等,由类内部进行负责

string内部封装了很多成员方法,例如查找find,拷贝copy,删除delete,替换replace,插入insert

3.1.1 string构造函数

举例四种初始化方法:

void test01() {

    string s1; //默认构造

    const char* str = "hello world";
    string s2(str);

    string s3(s2);

    string s4(10, 'c');  //使用n个字符c初始化

3.1.2 string赋值操作

一种是等号的赋值方式,一种是assign的赋值方式,赋值例子如下:

有一种方法为assign()方法

void test01() {

    string str1;
    str1 = "hello world";
    
    string str2;
    str2 = str1;
    cout << str2 << endl;

    string str3;
    str3 = 'a';
    cout << str3 << endl;

    string str4;
    str4.assign("hello C++");
    cout << str4 << endl;
    
    string str5;
    str5.assign("hello C", 5);  //获取字符串的前5个字符并赋给当前字符串,即str5为hello
    cout << str5 << endl;

    string str6;
    str6.assign(str5);

    string str7;
    str7.assign(10, 'w');   //用n个字符c赋给当前字符串
    cout << str7 << endl;

3.1.3 string字符串拼接

实现在字符串末尾拼接字符串,拼接例子如下:

有一种方法为append()方法

void test01() {

    string str1="我";
    str1 += "爱study";
    cout << str1 << endl;

    str1 += ':';

    string str2 = "lol";
    str1 += str2;
    cout << str1 << endl;

    string str3 = "I";
    str3.append(" love ");  //把字符串love连接到当前字符串结尾
    cout << str3 << endl;
    
    str3.append("youabcde", 4); //把字符串game abcde的前4个字符连接到当前字符串结尾
    cout << str3 << endl;

    str3.append(str2);
    cout << str3 << endl;

    str3.append(str2, 0, 2);  // 参数2表示的是从哪个位置开始截取,参数3表示的是截取字符个数
    cout << str3 << endl;

3.1.4 string查找和替换

查找:查找指定字符串是否存在

替换:在指定的位置替换字符串

查找的例子如下:

查找的方法为find()方法,rfind()方法

void test01() {

    //find()方法
    string str1="abcdefgjkloputde";
    int pos = str1.find("de");
    if (pos == -1) {  // find方法,若返回-1,代表字符串中没有找到想要的字符串
        cout << "未找到" << endl;
    }
    else {
        //pos = 3;
        cout << pos << endl;  // find方法,索引位置从0开始,返回找到的第一个字符的位置

    }
    
    //rfind方法
    pos = str1.rfind("de");
    cout << pos << endl; // pos = 14;

find()方法和rfind()方法的区别为:find方法是从左往右查找,rfind方法是从右往左查找,也可以理解为find是查找字符串第一次出现的位置,rfind是查找字符串最后一次出现的位置

替换的例子如下:

替换的方法为replace()方法

void test02() {

    string str1 = "abcdefg";
    str1.replace(1, 3,"1111");  // 从str1的1号字符起,3个字符替换为"1111"
    cout << str1 << endl; // str1 = a1111efg

}

3.1.5 string字符串比较

字符串比较是按字符的ASCII码进行对比,相等返回0,大于返回1,小于返回-1

字符串比较的例子如下:

比较的方法为compare()方法

void test01() {

    string str1 = "xello";
    string str2 = "hello";
    if (str1.compare(str2) == 0) {
        cout << "相等" << endl;
    }
    else if (str1.compare(str2) > 0) {
        cout << "str1 大于 str2 " << endl;
    }
    else {
        cout << "str1 小于 str2 " << endl;
    }

3.1.6 string字符存取

存取列子如下:

存取的方法为at()方法

void test01() {

    string str = "hello";
    
    //通过 [] 访问单个字符
    for (int i = 0; i < str.size(); i++) {
        cout << str[i] << " ";
    }

    cout << endl;

    //通过 at 方式访问单个字符
    for (int i = 0; i < str.size(); i++) {
        cout << str.at(i) << " ";
    }

    cout << endl;

    //修改单个字符
    str[0] = 'x';

    cout << str << endl;

    str.at(1) = 'x';
    cout << str << endl;

3.1.7 string插入和删除

对字符串进行插入和删除字符操作,例子如下:

插入字符串使用insert()方法,删除使用erase()方法,插入和删除的起始下标都是从0开始

void test01() {

    string str = "hello";
    
    str.insert(1, "111"); // 从哪个位置插入什么字符串
    cout << str << endl; //str=h111ello

    str.erase(1, 3); // 从哪个位置开始,删多少个字符
    cout << str << endl;

3.1.8 string子串

功能为:从字符串中获取想要的子串

获取子串的例子如下:

获取子串的方法为substr()方法

void test01() {

    string str = "abcdef";

    string subStr = str.substr(1, 3); //从1号位置开始截取3个字符
    cout << subStr << endl; // bcd

//实用操作
void test02() {

    string email = "[email protected]";
    //从邮件地址中获取用户名信息
    int pos = email.find("@");
    string userName = email.substr(0, pos);
    cout << userName << endl;
}

3.2 vector容器

使用vector容器要包含头文件 #include<vector>

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

vector与普通数组的区别在于:数组是静态空间,而vector可以动态扩展

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

vector容器中所用到的方法:

vector容器的迭代器是支持随机访问的迭代器

3.2.1 vector构造函数

void printVector(vector<int> &v) {

    for (vector<int>::iterator it = v.begin(); it != v.end(); it++) {
        cout << *it << " ";
    }
    cout << endl;
}

void test01() {

    vector<int> v1; // 默认构造  无参构造
    for (int i = 0; i < 10; i++) {
        v1.push_back(i);
    }
    printVector(v1);

    //通过区间方式进行构造
    vector<int> v2(v1.begin(), v1.end());
    printVector(v2);

    //n个elem方式构造
    vector<int> v3(10, 100); //添加10个100
    printVector(v3);

    //拷贝构造
    vector<int> v4(v3);
    printVector(v4);
}

3.2.2 vector赋值操作

赋值操作可以使用等号赋值,可以使用assign()方法赋值

void test01() {

    vector<int> v1; // 默认构造  无参构造
    for (int i = 0; i < 10; i++) {
        v1.push_back(i);
    }
    printVector(v1);

    //赋值 opoerator=
    vector<int> v2;
    v2 = v1;
    printVector(v2);

    //assign
    vector<int> v3;
    v3.assign(v1.begin(), v1.end());
    printVector(v3);

    //n个elem 方式赋值
    vector<int> v4;
    v4.assign(10, 100);
    printVector(v4);
}

3.2.3 vector容量和大小

利用empty()方法判断容器是否为空

利用capacity()方法判断容器的容量

利用size()方法返回容器中元素的个数

利用resize(int num)方法重新指定容器的长度为num,若容器变长,则以默认值0填充新位置;如果容器变短,则末尾超出容器长度的元素被删除

利用resize(int num,elem)方法重新指定容器的长度为num,若容器变长,则以elem值填充新位置;如果容器变短,则末尾超出容器长度的元素被删除

void test01() {

    vector<int> v1; // 默认构造  无参构造
    for (int i = 0; i < 10; i++) {
        v1.push_back(i);
    }
    printVector(v1);

    if (v1.empty()) {
        cout << "v1为空" << endl;
    }
    else {
        cout << "v1不为空" << endl;
        cout << v1.capacity() << endl;
        cout << v1.size() << endl;

    }

    //重新指定大小 resize
    v1.resize(15);
    printVector(v1); //0,1,2,3,4,5,6,7,8,9,0,0,0,0,0

    v1.resize(5);
    printVector(v1); //0,1,2,3,4

}

3.2.4 vector插入和删除

利用push_back(ele)方法实现在尾部插入元素ele

利用pop_back()方法实现删除最后一个元素

利用insert(const_iterator pos,ele)方法实现向迭代器指向位置pos插入元素ele

利用insert(const_iterator pos,int count,ele)方法实现向迭代器指向位置pos插入count个元素ele

利用erase(const_iterator pos)方法实现删除迭代器指向的元素

利用erase(const_iterator start,const_iterator end)方法实现删除迭代器从start到end之间的元素

利用clear()方法删除容器中所有元素

const_iterator代表的是迭代器

void test01() {

    vector<int> v1; 
    //尾插法
    v1.push_back(10);
    v1.push_back(20);
    v1.push_back(30);
    v1.push_back(40);
    v1.push_back(50);
    printVector(v1);

    //尾删法
    v1.pop_back();
    printVector(v1);

    //插入  insert的第一个参数是迭代器
    v1.insert(v1.begin(), 100); //v1.begin()指向的是容器中的第一个元素的位置,在第一个位置前插入100
    printVector(v1);

    v1.insert(v1.begin(), 2, 1000); //增加两个1000
    printVector(v1);

    //删除
    v1.erase(v1.begin());
    printVector(v1); // 删除了一个1000

    //清空
    v1.erase(v1.begin(), v1.end());
    // v1.clear();

    printVector(v1);
}

3.2.5 vector数据存取

利用at(int idx)方法返回索引idx所指的数据

利用operator[ ] 返回索引idx所指的数据

利用front()方法返回容器中第一个数据元素

利用back()方法返回容器中最后一个数据元素

void test01() {

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

    //利用[]方式访问数组中的元素
    for (int i = 0; i < v1.size(); i++) {
        cout << v1[i] << " ";
    }
    cout << endl;

    //利用at方式访问元素
    for (int i = 0; i < v1.size(); i++) {
        cout << v1.at(i) << " ";
    }
    cout << endl;

    //获取第一个元素
    cout << v1.front() << endl;

    //获取最后一个元素
    cout << v1.back() << endl;
}

3.2.6 vector互换容器

实现两个容器内元素进行互换,利用swap(vec)方法将vec与本身的元素互换

void test01() {

    vector<int> v1; 
    for (int i = 0; i < 10; i++) {
        v1.push_back(i);
    }
    printVector(v1);

    vector<int> v2;
    for (int i = 10; i > 0; i--) {
        v2.push_back(i);
    }
    printVector(v2);

    cout << "交换后" << endl;
    v1.swap(v2);
    printVector(v1);
    printVector(v2);
}

//实际用途:巧用vector可以收缩内存空间
void test02() {

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

    v.resize(3);
    cout << v.capacity() << endl;
    cout << v.size() << endl;

    //巧用swap收缩内存
    vector<int>(v).swap(v);
    cout << v.capacity() << endl;
    cout << v.size() << endl;
}


3.2.7 vector预留空间

目的是减少vector在动态扩展容量时的扩展次数,利用reserve(int len)方法实现,即容器预留len个元素长度,预留位置不初始化,元素不可访问

void test01() {

    vector<int> v; 
    //利用reserve预留空间
    v.reserve(100000);
    int num = 0;//统计开辟次数
    int* p = NULL;
    for (int i = 0; i < 100000; i++) {
        v.push_back(i);

        if (p != &v[0]) {
            p = &v[0];
            num++;
        }
    }
    cout << num << endl; //num=1;若不使用reserve方法,则num=30
}

3.3 deque容器

使用时需要加头文件 #include <deque>

deque容器也叫做双端数组,可以对头端进行插入删除操作

deque与vector区别:

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

deque容器中所用到的方法:

deque内部工作原理:

  • deque内部有个中控器,维护每段缓冲区中的内容,缓冲区中存放真实数据
  • 中控器维护的是每个缓冲区的地址,使得使用deque时像一片连续的内存空间

deque容器的迭代器也是支持随机访问的

3.3.1 deque构造函数

void printDeque(const deque<int> &d) {

    for (deque<int>::const_iterator it = d.begin(); it != d.end(); it++) {
        cout << *it << " ";
    }
    cout << endl;
}

void test01() {

    deque<int> d1;
    for (int i = 0; i < 10; i++) {
        d1.push_back(i);
    }
    printDeque(d1);

    deque<int> d2(d1.begin(), d1.end());
    printDeque(d2);

    deque<int> d3(10, 100);
    printDeque(d3);

    deque<int> d4(d3);
    printDeque(d4);
}

3.3.2 deque赋值操作

利用等号赋值或者使用assign()方法进行赋值

void test01() {

    deque<int> d1;
    for (int i = 0; i < 10; i++) {
        d1.push_back(i);
    }
    printDeque(d1);

    //等号赋值
    deque<int> d2;
    d2 = d1;

    printDeque(d2);

    //assign赋值
    deque<int> d3;
    d3.assign(d1.begin(), d1.end());
    d3.assign(10, 100);

    printDeque(d3);
}

3.3.3 deque大小操作

利用empty()方法判断容器是否为空

利用size()方法返回容器中元素的个数

利用resize(num)方法重新指定容器的长度为num,若容器变长,则以默认值0填充新位置;如果容器变短,则末尾超出容器长度的元素被删除

利用resize(int num,elem)方法重新指定容器的长度为num,若容器变长,则以elem值填充新位置;如果容器变短,则末尾超出容器长度的元素被删除

写法和vector类似

3.3.4 deque插入和删除

两端插入操作:

利用push_back(elem)方法实现在尾部插入元素elem

利用push_front(elem)方法实现在头部插入元素elem

利用pop_back()方法实现删除最后一个元素

利用pop_front()方法实现删除第一个元素

指定位置操作:

利用insert(const_iterator pos,ele)方法实现向迭代器指向位置pos插入元素ele,返回新数据的位置

利用insert(const_iterator pos,int count,ele)方法实现向迭代器指向位置pos插入count个元素ele,无返回值

利用insert(const_iterator start pos,const_iterator start beg,const_iterator start end)方法实现在pos位置插入[beg,end)区间的数据,无返回值

利用erase(const_iterator pos)方法实现删除迭代器指向的元素,返回下一个数据的位置

利用erase(const_iterator start,const_iterator end)方法实现删除迭代器从start到end之间的元素,返回下一个数据的位置

利用clear()方法删除容器中所有元素

void test01() {

    deque<int> d1;
    //尾插
    d1.push_back(10);
    d1.push_back(20);
    //头插
    d1.push_front(100);
    d1.push_front(200);
    printDeque(d1);
    //尾删
    d1.pop_back();
    printDeque(d1);
    //头删
    d1.pop_front();
    printDeque(d1);
}
void test02() {

    deque<int> d2;
    d2.push_back(10);
    d2.push_back(20);
    d2.push_front(100);
    d2.push_front(200);
    printDeque(d2);

    //insert方式
    d2.insert(d2.begin(), 1000);
    printDeque(d2);

    d2.insert(d2.begin(), 2,10000);
    printDeque(d2);

    d2.insert(d2.begin(), d2.begin(), d2.end());
    printDeque(d2);

    //删除
    deque<int>::iterator it = d2.begin();
    it++;
    d2.erase(it);
    printDeque(d2);

    d2.clear();
    printDeque(d2);
}

3.3.5 deque数据存取

利用at(int idx)方法返回索引idx所指的数据

利用operator[ ] 返回索引idx所指的数据

利用front()方法返回容器中第一个数据元素

利用back()方法返回容器中最后一个数据元素

写法和vector容器类似

3.3.6 deque排序

利用sort(iterator beg,iterator end)方法实现对beg和end区间内元素进行排序,需要利用sort算法,要引入头文件#include<algorithm>

void test01() {

    deque<int> d1;
    //尾插
    d1.push_back(10);
    d1.push_back(20);
    d1.push_back(30);
    //头插
    d1.push_front(100);
    d1.push_front(200);
    d1.push_front(300);
    printDeque(d1);

    sort(d1.begin(),d1.end()); // 默认排序算法为从小到大排序
    printDeque(d1);
}

对于支持随机访问的迭代器的容器,都可以利用sort算法直接对其进行排序

3.4 stack容器

使用时需要加头文件 #include <stack>

stack是一种先进后出的数据结构,它只有一个出口

栈中只有顶端的元素才可以被外界使用,因此栈不允许有遍历行为

栈中进入数据称为入栈,push

栈中弹出数据称为出栈,pop

3.4.1 stack常用接口

利用push(elem)方法实现向栈顶添加元素

利用pop()方法实现从栈顶移除第一个元素

利用top()方法实现返回栈顶元素

利用empty()方法判断堆栈是否为空

利用size()方法返回栈的大小

void test01() {

    stack<int> s;
    //入栈
    s.push(10);
    s.push(20);
    s.push(30);
    s.push(40);
    //只要栈不为空,就查看栈顶,并执行出栈操作
    while (!s.empty()) {
        //查看栈顶元素
        cout << s.top() << endl;
        //出栈
        s.pop();
    }
    cout << s.size() << endl;
}

3.5 queue容器

使用时需要加头文件 #include <queue>

queue是一种先进先出的数据结构,它有两个出口

队列容器允许从一端新增元素,从另一端移除元素

队列中只有对头和队尾才可以被外界使用,因此队列不允许有遍历行为

队列中进数据称为入队,push

队列中出数据称为出队,pop

3.5.1 queue常用接口

利用push(elem)方法实现向队尾添加元素

利用pop()方法实现从对头移除第一个元素

利用back()方法返回最后一个元素

利用front()方法返回第一个元素

利用empty()方法判断队列是否为空

利用size()方法返回队列的大小

class Person {
public:
    Person(string name,int age) {
        this->m_age = age;
        this->m_name = name;
    }
    int m_age;
    string m_name;
};
void test01() {
    Person p1("aa", 30);
    Person p2("bb", 40);
    Person p3("cc", 35);
    Person p4("dd", 38);

    queue<Person> q;
    //入对
    q.push(p1);
    q.push(p2);
    q.push(p3);
    q.push(p4);
    cout << q.size() << endl;

    while (!q.empty()) {
        //查看对头
        cout << q.front().m_age << " " << q.front().m_name << endl;
        //查看队尾
        cout << q.back().m_age << " " << q.back().m_name << endl;
        //出队
        q.pop();
    }
    cout << q.size() << endl;
}

3.6 list容器

使用时需要加头文件 #include <list>

list的功能是将数据进行链式存储

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

链表由一系列结点组成,结点由两部分组成,一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域 

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

链表中所包含的方法:

由于链表的存储方式并不是连续的内存空间,因此链表list中的迭代器只支持前移和后移,属于双向迭代器

list的优点:

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

list的缺点:

  • 链表灵活,但是空间(指针域)和时间(遍历)额外耗费较大

list有一个重要的性质,插入操作和删除操作都不会造成原有list迭代器的失效,这在vector是不成立的

3.6.1 list构造函数

void test01() {

    list<int> L1;//默认构造
    //添加数据
    L1.push_back(10);
    L1.push_back(20);
    L1.push_back(30);
    L1.push_back(40);

    //区间方式构造
    list<int> L2(L1.begin(),L1.end());

    //拷贝构造
    list<int> L3(L2);

    //n个elem
    list<int> L4(10, 1000);
}

3.6.2 list赋值和交换

利用assign()方法或等号赋值的方式进行赋值,利用swap()方法实现list与本身元素互换

void test01() {

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

    list<int> L2;
    L2.assign(L1.begin(), L1.end()); 

    list<int> L3;
    L3 = L1;  //等号赋值

    list<int> L4;
    L4.assign(10, 100);

    //交换
    L1.swap(L4);
    printList(L1);
}

3.6.3 list大小操作

利用empty()方法判断容器是否为空

利用size()方法返回容器中元素的个数

利用resize(num)方法重新指定容器的长度为num,若容器变长,则以默认值0填充新位置;如果容器变短,则末尾超出容器长度的元素被删除

利用resize(int num,elem)方法重新指定容器的长度为num,若容器变长,则以elem值填充新位置;如果容器变短,则末尾超出容器长度的元素被删除

写法和vector,deque的类似

3.6.4 list插入和删除

利用push_back(elem)方法实现在尾部插入元素elem

利用push_front(elem)方法实现在头部插入元素elem

利用pop_back()方法实现删除最后一个元素

利用pop_front()方法实现删除第一个元素

利用insert(const_iterator pos,ele)方法实现向迭代器指向位置pos插入元素ele,返回新数据的位置

利用insert(const_iterator pos,int count,ele)方法实现向迭代器指向位置pos插入count个元素ele,无返回值

利用insert(const_iterator start pos,const_iterator start beg,const_iterator start end)方法实现在pos位置插入[beg,end)区间的数据,无返回值

利用erase(const_iterator pos)方法实现删除迭代器指向的元素,返回下一个数据的位置

利用erase(const_iterator start,const_iterator end)方法实现删除迭代器从start到end之间的元素,返回下一个数据的位置

利用clear()方法删除容器中所有元素

利用remove(elem)方法删除容器中所有与elem值匹配的元素

写法和deque类似

3.6.5 list数据存取

利用front()方法返回容器中第一个数据元素

利用back()方法返回容器中最后一个数据元素

不可以用 [ ] 和 at 方式访问list容器中的元素,原因是list本质是链表,不是用连续线性空间存储数据,迭代器也是不支持随机访问的

void test01() {

    list<int> L1;
    L1.push_back(10);
    L1.push_back(20);
    L1.push_front(100);
    L1.push_front(200);

    cout << L1.front() << endl;
    cout << L1.back() << endl;

    //验证迭代器是否支持随机访问
    list<int>::iterator it = L1.begin();
    it++;  //支持双向
    it--;
    //it = it + 1;  //报错,不支持随机访问

}

3.6.6 list反转和排序

利用reverse()方法实现反转链表,将容器中的元素反转

利用sort()方法实现对链表中的数据的排序,使用时包含头文件#include<algorithm>

//实现降序
bool myCompare(int v1, int v2) {
    return v1 > v2;
}

void test01() {

    list<int> L1;
    L1.push_back(10);
    L1.push_back(20);
    L1.push_front(100);
    L1.push_front(200);
    L1.push_front(50);
    printList(L1);

    L1.reverse(); //反转
    printList(L1);

    L1.sort(); //升序   sort是成员函数
    printList(L1);

    L1.sort(myCompare); //降序
    printList(L1);
}

所有不支持随机访问迭代器的容器,不可以用标准算法,内部会提供对应一些算法

案列:将Person自定义数据类型进行排序,Person中属性有姓名,年龄,身高,排序规则为按年龄进行升序,如果年龄相同按照身高进行降序

#include<iostream>
using namespace std;
#include<list>

class Person {
public:
	Person(string name, int age, int height) {

		this->m_name = name;
		this->m_age = age;
		this->m_height = height;
	}

	string m_name;
	int m_age;
	int m_height;
};

bool compare_age_height(Person &p1,Person &p2){
	
	if (p1.m_age == p2.m_age) {
		return p1.m_height > p2.m_height;
	}
	return p1.m_age < p2.m_age;
}

void printList(list<Person>& L) {
	for (list<Person>::iterator it = L.begin(); it != L.end(); it++) {
		cout << "姓名:" << it->m_name << " 年龄:" << it->m_age << " 身高:" << it->m_height <<endl;
	}
	cout << endl;
}

void test01() {

	list<Person> L;

	Person p1("aa", 20, 181);
	Person p2("bb", 20, 184);
	Person p3("cc", 25, 175);
	Person p4("dd", 26, 173);
	Person p5("ee", 18, 172);
	Person p6("ff", 18, 190);

	L.push_back(p1);
	L.push_back(p2);
	L.push_back(p3);
	L.push_back(p4);
	L.push_back(p5);
	L.push_back(p6);

	cout << "排序前:" << endl;
	printList(L);

	cout << "排序后:" << endl;
	L.sort(compare_age_height);
	printList(L);

}


int main() {

	test01();

	system("pause");
	return 0;
}

3.7 set/multiset容器

使用时包含头文件 #include<set>,既可以使用set也可以使用multiset

set容器特点:所有元素都会在插入时自动被排序

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

set和multiset区别:

  • set不允许容器中有重复的元素
  • multiset允许容器中有重复的元素

3.7.1 set构造和赋值

只能利用insert()方法进行数据的插入
void test01() {

    set<int> s1;
    s1.insert(10);
    s1.insert(40);
    s1.insert(30);
    s1.insert(20);
    s1.insert(30);
    printSet(s1); // 结果为 10,20,30,40

    //拷贝构造
    set<int> s2(s1);
    printSet(s2);

    //赋值
    set<int>s3;
    s3 = s2;
    printSet(s3);
}

3.7.2 set大小和交换

利用size()方法返回容器中元素的数目

利用empty()方法判断容器是否为空

利用swap(st)方法交换两个集合容器

void test01() {

    set<int> s1;
    s1.insert(10);
    s1.insert(40);
    s1.insert(30);
    s1.insert(20);
    s1.insert(30);
    printSet(s1); // 结果为 10,20,30,40
    //大小
    cout << s1.size() << endl;
    set<int> s2;
    s2.insert(100);
    s2.insert(200);
    //交换两个容器
    s1.swap(s2);
    cout << s1.size() << endl;  // 2
    printSet(s1); // 100,200
}

3.7.3 set插入和删除

利用insert(elem)方法在容器中插入元素elem

利用erase(const_iterator pos)方法实现删除迭代器指向的元素,返回下一个元素的迭代器

利用erase(const_iterator start,const_iterator end)方法实现删除迭代器从start到end之间的元素,返回下一个元素的迭代器

利用erase(elem)方法删除容器中值为elem的元素

利用clear()方法删除容器中所有元素

方法和前面容器的类似

3.7.4 set查找和统计

利用find(key)方法查找key是否存在,若存在,返回该键的元素的迭代器;若不存在,返回set.end(),即返回元素结束的位置

利用count(key)方法统计key的元素个数

void test01() {

    set<int> s1;
    s1.insert(10);
    s1.insert(40);
    s1.insert(30);
    s1.insert(20);
    s1.insert(30);
    printSet(s1); // 结果为 10,20,30,40
    
    //统计个数
    cout << s1.count(30) << endl;  // 1

    //查找
    set<int>::iterator pos = s1.find(40);
    if (pos != s1.end()) {
        cout << "找到元素为:" << *pos << endl;
    }
    else {
        cout << "未找到" << endl;
    }

}

3.7.5 set和multiset区别

区别:

  • set不可以插入重复数据,而multiset可以
  • set插入数据的同时会返回插入结果,表示插入是否成功
  • multiset不会检测数据,因此可以插入重复数据

void test01() {

    set<int> s1;
    pair<set<int>::iterator, bool> ret = s1.insert(10);
    if (ret.second) {  // ret的第二个元素
        cout << "第一次插入成功" << endl;
    }
    else {
        cout << "第一次插入失败" << endl;
    }

    pair<set<int>::iterator, bool> ret1 = s1.insert(10);
    if (ret1.second) {
        cout << "第二次插入成功" << endl;
    }
    else {
        cout << "第二次插入失败" << endl;
    }

    multiset<int> ms;
    ms.insert(10);
    ms.insert(10);

    printSet(ms); // 10,10
}

3.7.6 pair对组创建

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

两种创建方式:

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

访问对组中第一个元素调用first,访问第二个元素调用second

void test01() {

    //第一种方式
    pair<string, int> p("Tom", 20);
    cout << "姓名:" << p.first << " 年龄:" << p.second << endl;

    //第二种方式
    pair<string, int> p2 = make_pair("Jerry", 30);
    cout << "姓名:" << p2.first << " 年龄:" << p2.second << endl;
}

3.7.7 set容器排序

set容器默认排序规则为从小打大,可以利用仿函数,改变排序规则

set存放内置数据类型,修改排序规则:

class MyCompare {   // 实现降序
public:
    bool operator()(int v1, int v2) const{
        return v1 > v2;
    }
};

void test01() {

    set<int> s1;
    s1.insert(10);
    s1.insert(50);
    s1.insert(20);
    s1.insert(40);
    s1.insert(30);
    printSet(s1); //10 20 30 40 50

    set<int, MyCompare> s2;
    s2.insert(10);
    s2.insert(50);
    s2.insert(20);
    s2.insert(40);
    s2.insert(30);
    for (set<int, MyCompare>::iterator it = s2.begin(); it != s2.end(); it++) {
        cout << *it << " "; //50 40 30 20 10
    }

    cout << endl;
}

set存放自定义数据类型,修改排序规则(自定义数据类型,都会指定排序规则)

class Person {
public:
    Person(string name, int age) {
        this->m_name = name;
        this->m_age = age;
    }
    string m_name;
    int m_age;
};

class MyCompare {
public:
    bool operator()(const Person &p1, const Person &p2) const{
        return p1.m_age>p2.m_age;
    }
};

void test01() {

    set<Person,MyCompare> s;
    Person p1("aa", 24);
    Person p2("bb", 28);
    Person p3("cc", 25);
    Person p4("dd", 21);

    s.insert(p1);
    s.insert(p2);
    s.insert(p3);
    s.insert(p4);

    for (set<Person,MyCompare>::iterator it = s.begin(); it != s.end(); it++) {
        cout << "姓名:" << it->m_name << " 年龄:" << it->m_age << endl;
    }

    cout << endl;
}

3.8 map/multimap容器

使用时包含头文件 #include<map>

map中所有元素都是pair(对组)

pair中第一个元素为key(键值),起到索引作用,第二个元素为value(实值)

所有元素都会根据元素的键值自动排序,可以根据键值快速找到value值

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

map和multimap区别:

  • map不允许容器中有重复的key值元素
  • multimap允许容器中有重复key值元素

3.8.1 map构造和赋值

void printMap(map<int, int>& m) {
    for (map<int, int>::iterator it = m.begin(); it != m.end(); it++) {
        cout << "key: " << it->first << " value: " << it->second <<endl;
    }
    cout << endl;
}

访问对组中第一个元素调用first,访问第二个元素调用second

void test01() {

    map<int, int> m;
    m.insert(pair<int,int> (1, 10));
    m.insert(pair<int, int>(2, 20));
    m.insert(pair<int, int>(4, 40));
    m.insert(pair<int, int>(3, 30));
    printMap(m); //会进行自动排序

    //拷贝构造
    map<int, int>m2(m);

    //赋值
    map<int, int>m3;
    m3 = m2;

}

3.8.2 map大小和交换

利用size()方法返回容器中元素的数目

利用empty()方法判断容器是否为空

利用swap(st)方法交换两个集合容器

和set容器写法类似

3.8.3 map插入和删除

利用insert(elem)方法在容器中插入元素elem

利用erase(const_iterator pos)方法实现删除迭代器指向的元素,返回下一个元素的迭代器

利用erase(const_iterator start,const_iterator end)方法实现删除迭代器从start到end之间的元素,返回下一个元素的迭代器

利用erase(key)方法按照key值删除对组,如m.erase(3),若存在(3,30),则删除该对组

利用clear()方法删除容器中所有元素

void test01() {

    map<int, int> m;
    //插入 第一种
    m.insert(pair<int,int>(1, 10));
    //插入 第二种
    m.insert(make_pair(2, 20));
    //插入 第三种
    m.insert(map<int, int>::value_type(3, 30));
    //插入 第四种
    m[4] = 40;  //[]不建议插入,用途可以利用key访问到value
    printMap(m);

    //删除
    m.erase(m.begin());
    m.erase(3);
    printMap(m);
}

3.8.4 map查找和统计

利用find(key)方法查找key是否存在,若存在,返回该键的元素的迭代器;若不存在,返回set.end(),即返回元素结束的位置

利用count(key)方法统计key的元素个数

均通过键值key进行操作

使用方法和set容器类似

3.8.5 map排序

map容器默认排序规则为 按照key值进行从小到大排序,可以利用仿函数,改变排序规则

class myCompare {
public:
    bool operator()(int v1, int v2) const {
        //降序
        return v1 > v2;
    }
};

void printMap(map<int, int, myCompare>& m) {
    for (map<int, int, myCompare>::iterator it = m.begin(); it != m.end(); it++) {
        cout << "key: " << it->first << " value: " << it->second <<endl;
    }
    cout << endl;
}

void test01() {

    map<int, int, myCompare> m;
    m.insert(make_pair(2, 20));
    m.insert(make_pair(1, 40));
    m.insert(make_pair(3, 80));
    printMap(m);
}

4. STL - 函数对象

4.1 函数对象

重载函数调用操作符的类,其对象常称为函数对象

函数对象使用重载的()时,行为类似函数调用,也叫仿函数

函数对象(仿函数)是一个类,不是一个函数

函数对象的使用特点:

  • 函数对象在使用时,可以像普通函数那样调用,可以有参数,可以有返回值
  • 函数对象超出普通函数的概念,函数对象可以有自己的状态
  • 函数对象可以作为参数传递

4.2 谓词

返回bool类型的仿函数称为谓词

如果operator()接受一个参数,叫一元谓词

如果operator()接受两个参数,叫二元谓词

class GreaterFive {
public:
    bool operator()(int val) { //bool类型  一个参数,叫做一元谓词
        return val > 5;

    }
};

void test01() {

    vector<int> v;
    for (int i = 0; i < 10; i++) {
        v.push_back(i);
    }
    //查找容器中,有无大于5的数字    GreaterFive()为匿名函数对象
    vector<int>::iterator it = find_if(v.begin(), v.end(), GreaterFive());
    if (it == v.end()) {
        cout << "未找到" << endl;
    }
    else {
        cout << "大于5的数字为:" << *it << endl;
    }
}

4.3 内建函数对象

STL内建了一些函数对象,分为算数仿函数,关系仿函数,逻辑仿函数

这些仿函数所产生的对象,用法和一般函数完全相同

使用内建函数对象,需要引入头文件 #include<functional>

4.3.1 算数仿函数

功能为实现四则运算

其中negate(取反)是一元运算,其他都是二元运算

加法 plus      减法 minus        乘法multiplies       除法divides    取模modulus    取反negate 

void test01() {

    negate<int> n;
    cout << n(50) << endl; // -50

    plus<int> p;
    cout << p(10, 20) << endl; // 30

}

4.3.2 关系仿函数

功能为实现关系对比

等于equal_to       不等于not_equal_to     大于greater     大于等于greater_equal

小于less    小于等于less_equal

void test01() {

    vector<int> v;
    for (int i = 0; i < 5; i++) {
        v.push_back(i);
    }
    sort(v.begin(), v.end(), greater<int>());     // 实现了降序,提供内建函数对象greater<int>()
    for (vector<int>::iterator it = v.begin(); it != v.end(); it++) {
        cout << *it << " ";
    }
    cout << endl;
}

4.3.3 逻辑仿函数

功能为实现逻辑运算

逻辑与logical_and        逻辑或logical_or           逻辑非logical_not

void test01() {

    vector<bool> v;
    v.push_back(true);
    v.push_back(false);
    v.push_back(true);
    v.push_back(false);
    for (vector<bool>::iterator it = v.begin(); it != v.end(); it++) {
        cout << *it << " ";   // 1 0 1 0
    }
    cout << endl;
    //利用逻辑非,将容器v搬运到容器v2中,并执行取反操作
    vector<bool> v2;
    v2.resize(v.size()); 

    transform(v.begin(), v.end(), v2.begin(), logical_not<bool>());

    for (vector<bool>::iterator it = v2.begin(); it != v2.end(); it++) {
        cout << *it << " ";   // 0 1 0 1
    }
    cout << endl;
}

5. STL - 常用算法

算法主要是由头文件<algorithm> <functional> <numeric> 组成

  • <algorithm>是所有STL头文件中最大的一个,范围涉及到比较,交换,查找,遍历操作,复制,修改等
  • <numeric>体积很小,只包括几个在序列上面进行简单数学运算的模板函数
  • <functional>定义了一些模板类,用以声明函数对象

5.1 常用遍历算法

使用时包含头文件 #include<algorithm>

for_each:遍历容器

transform:搬运容器到另一个容器中

5.1.1 for_each

for_each ( iterator beg, iterator end, _func)   //  遍历算法,遍历容器元素

参数解释:

  • beg为开始迭代器
  • end为结束迭代器
  • _func为函数名或者函数对象

//普通函数
void print01(int val) {
    cout << val << " ";
}

//仿函数
class print02 {
public:
    void operator()(int val) {
        cout << val << " ";

    }
};

void test01() {

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

    for_each (v.begin(), v.end(), print01);  //普通函数名
    cout << endl;

    for_each(v.begin(), v.end(), print02()); //函数对象(类名+小括号)
    cout << endl;
}

5.1.2 transform

transform ( iterator beg1, iterator end1, iterator beg2, _func); //搬运容器到另一个容器中

参数解释:

  • beg1为源容器开始迭代器
  • end1为源容器结束迭代器
  • beg2为目标容器开始迭代器
  • _func为普通函数名或者函数对象

class Transform {
public:
    int operator()(int val) {
        return val+100;  //可以进行一些操作
    }
};

void print01(int val) {
    cout << val << " ";
}

void test01() {

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

    vector<int> vTarget; // 目标容器
    vTarget.resize(v.size()); //目标容器需要提前开辟空间
    transform(v.begin(), v.end(), vTarget.begin(), Transform());
    for_each(vTarget.begin(), vTarget.end(), print01);
    cout << endl;
}

5.2 常用查找算法

使用时包含头文件 #include<algorithm>

find  查找元素

find_if  按条件查找元素

adjacent_find  查找相邻重复元素

binary_search  二分查找法

count  统计元素个数

count_if  按条件统计元素个数

5.2.1 find

功能为查找指定元素,找到返回指定元素的迭代器,找不到返回结束迭代器end()

find ( iterator beg, iterator end, value);

其中beg为开始迭代器,end为结束迭代器,value为查找的元素

//查找内置数据类型
void test01() {
    vector<int> v;
    for (int i = 0; i < 10; i++) {
        v.push_back(i);
    }
    vector<int>::iterator it  = find(v.begin(), v.end(), 6);
    if (it == v.end()) {
        cout << " 未找到 " << endl;
    }
    else {
        cout << *it << " ";
    }
    cout << endl;
}

class Person {
public:
    Person(string name, int age) {
        this->m_age = age;
        this->m_name = name;
    }
    //重载==,让find知道如何对比Person数据类型
    bool operator==(const Person& p) {
        if (this->m_name == p.m_name && this->m_age == p.m_age) {
            return true;
        }
        else {
            return false;
        }
    }

    string m_name;
    int m_age;
};

//查找自定义数据类型
void test02() {

    vector<Person> v;
    Person p1("aa", 34);
    Person p2("bb", 39);
    Person p3("cc", 30);
    Person p4("dd", 27);
    v.push_back(p1);
    v.push_back(p2);
    v.push_back(p3);
    v.push_back(p4);

    vector<Person>::iterator it = find(v.begin(), v.end(), p2);
    if (it == v.end()) {
        cout << " 未找到 " << endl;
    }
    else {
        cout << it->m_name << " + "<<it->m_age<<endl;
    }
    cout << endl;
}

5.2.2 find_if

功能为按值返回查找元素,找到返回指定元素的迭代器,找不到返回结束迭代器end()

find_if ( iterator beg, iterator end, _Pred);

其中beg为开始迭代器,end为结束迭代器,_Pred为函数名或者谓词(返回bool类型的仿函数)

5.2.3 adjacent_find

功能为查找相邻重复元素,返回相邻元素的第一个位置的迭代器

adjacent_find ( iterator beg, iterator end);

其中beg为开始迭代器,end为结束迭代器

加入vector容器中存放的元素为 0,2,0,3,1,4,3,3,则使用该算法返回的是3,3中的第一个3的位置,因为0元素之间不相邻,第一个3元素和后面的也不相邻

5.2.4 binary_search

功能为查找指定的元素,查到返回true,否则返回false,在无序序列中不可用

bool binary_search ( iterator beg, iterator end, value);

其中beg为开始迭代器,end为结束迭代器,value为查找的元素

5.2.5 count

功能为统计元素出现次数

count ( iterator beg, iterator end, value);

其中beg为开始迭代器,end为结束迭代器,value为统计的元素

class Person {
public:
    Person(string name, int age) {
        this->m_age = age;
        this->m_name = name;
    }
    //重载==
    bool operator==(const Person& p) {
        if (this->m_age == p.m_age) {
            return true;
        }
        else {
            return false;
        }
    }

    string m_name;
    int m_age;
};

//查找自定义数据类型
void test01() {

    vector<Person> v;
    Person p1("aa", 34);
    Person p2("bb", 34);
    Person p3("cc", 30);
    Person p4("dd", 27);
    Person p5("cc", 34);
    v.push_back(p1);
    v.push_back(p2);
    v.push_back(p3);
    v.push_back(p4);

    int it = count(v.begin(), v.end(), p5);
    cout << it << endl;
}

5.2.6 count_if

功能为按条件统计元素出现次数

count_if ( iterator beg, iterator end, _Pred);

其中beg为开始迭代器,end为结束迭代器,_Pred为谓词(返回bool类型的仿函数)

5.3 常用排序算法

使用时包含头文件 #include<algorithm>

sort  对容器内元素进行排序

random_shuffle  洗牌,指定范围内的元素随机调整次序

merge  容器元素合并,并存储到另一容器中

reverse  反转指定范围元素

5.3.1 sort

sort ( iterator beg, iterator end, _Pred);

其中beg为开始迭代器,end为结束迭代器,_Pred为谓词。

_Pred默认不写,则是升序排序,添加后可以改变顺序变为降序排序

5.3.2 random_shuffle 

random_shuffle ( iterator beg, iterator end);

其中beg为开始迭代器,end为结束迭代器

void myPrint(int val) {
    cout << val << " ";
}

void test01() {

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

    random_shuffle(v.begin(), v.end());
    for_each(v.begin(), v.end(), myPrint);
}

5.3.3 merge

功能为容器元素合并,并存储到另一容器中,注意两个容器必须是有序的

merge ( iterator beg1, iterator end1, iterator beg2, iterator end2,  iterator dest);

其中beg1为容器1开始迭代器,end1为容器1结束迭代器,beg2为容器2开始迭代器,end2为容器2结束迭代器,dest为目标容器开始迭代器

void myPrint(int val) {
    cout << val << " ";
}

void test01() {
    vector<int> v1;
    vector<int> v2;
    for (int i = 0; i < 10; i++) {
        v1.push_back(i);
        v2.push_back(i + 2);
    }
    vector<int> vTarget;
    vTarget.resize(v1.size()+v2.size());  //开辟空间
    merge(v1.begin(), v1.end(), v2.begin(), v2.end(), vTarget.begin());
    for_each(vTarget.begin(),vTarget.end(),myPrint);
    cout << endl;
}

5.3.4 reverse

reverse ( iterator beg, iterator end);

其中beg为开始迭代器,end为结束迭代器

5.4 常用拷贝和替换算法

使用时包含头文件 #include<algorithm>

copy  容器内指定范围的元素拷贝到另一容器中

replace  将容器内指定范围的旧元素改为新元素

replace_if  容器内指定范围满足条件的元素替换为新元素

swap  互换两个容器的元素

5.4.1 copy

copy ( iterator beg, iterator end, iterator dest);

其中beg为容器开始迭代器,end为容器结束迭代器,dest为目标容器开始迭代器

拷贝时要注意开辟空间 v2.resize(v1.size());

5.4.2 replace

replace ( iterator beg, iterator end, oldvalue, newvalue);

其中beg为容器开始迭代器,end为容器结束迭代器,oldvalue为旧元素,newvalue为新元素

会替换区间内满足条件的所有元素,如容器中的数据为20,3,4,5,20,6。若替换元素20为2,则容器中的数据变为2,3,4,5,2,6

5.4.3 replace_if

replace_if ( iterator beg, iterator end, _pred, newvalue);

其中beg为容器开始迭代器,end为容器结束迭代器,_pred为谓词,newvalue为替换的新元素

5.4.4 swap

swap ( container c1, container c2);

其中c1为容器1,c2为容器2

5.5 常用算术生成算法

使用时包含头文件 #include<numeric>

accumulate  计算容器元素累计总和

fill  向容器中添加元素,利用fill可以将容器区间内元素填充为指定的值

5.5.1 accumulate

accumulate ( iterator beg, iterator end, value);

其中beg为容器开始迭代器,end为容器结束迭代器,value为起始值

void test01() {
    vector<int> v;
    for (int i = 0; i <= 100; i++) {
        v.push_back(i);
    }
    int total = accumulate(v.begin(), v.end(), 1000);  // 1000为起始累加值
    cout << total << endl;  //6050 = 1000 + 1 +2 +...+100
}

5.5.2 fill

fill ( iterator beg, iterator end, value);

其中beg为容器开始迭代器,end为容器结束迭代器,value为起始值

void test01() {
    vector<int> v;
    v.resize(10);
    fill(v.begin(), v.end(), 100);  //10个0全部替换为了10个100
    for_each(v.begin(), v.end(), myPrint);
}

5.6 常用集合算法

set_intersection  求两个容器的交集

set_union  求两个容器的并集

set_difference  求两个容器的差集

5.6.1 set_intersection 

set_intersection ( iterator beg1, iterator end1, iterator beg2, iterator end2,  iterator dest );

其中beg1为容器1开始迭代器,end1为容器1结束迭代器,beg2为容器2开始迭代器,end2为容器2结束迭代器,dest为目标容器开始迭代器

注意事项:

  • 求交集的两个集合必须是有序序列
  • 目标容器开辟空间需要从两个容器中取小值   min (v1.size(), v2.size());
  • set_intersection返回值即是交集中最后一个元素的位置

5.6.2 set_union

set_union ( iterator beg1, iterator end1, iterator beg2, iterator end2,  iterator dest );

其中beg1为容器1开始迭代器,end1为容器1结束迭代器,beg2为容器2开始迭代器,end2为容器2结束迭代器,dest为目标容器开始迭代器

注意事项:

  • 求并集的两个集合必须是有序序列
  • 目标容器开辟空间需要两个容器相加
  • set_union返回值即是并集中最后一个元素的位置

5.6.3 set_difference

set_difference ( iterator beg1, iterator end1, iterator beg2, iterator end2,  iterator dest );

其中beg1为容器1开始迭代器,end1为容器1结束迭代器,beg2为容器2开始迭代器,end2为容器2结束迭代器,dest为目标容器开始迭代器

注意事项:

  • 求差集的两个集合必须是有序序列
  • 目标容器开辟空间需从两个容器取较大值
  • set_difference返回值即是差集中最后一个元素的位置

集合算法的例子:

void myPrint(int val) {
    cout << val << " ";
}

void test01() {
    vector<int> v1;
    vector<int> v2;
    for (int i = 0; i < 10; i++) {
        v1.push_back(i);
        v2.push_back(i+5);
    }
    vector<int> vTarget;
    vTarget.resize(v1.size() + v2.size());

    vector<int>::iterator itEnd =  set_intersection(v1.begin(), v1.end(), v2.begin(), v2.end(), vTarget.begin());
    for_each(vTarget.begin(), itEnd, myPrint); //使用itEnd避免了出现填充0的情况
    cout << endl;

    vector<int>::iterator itEnd1 = set_union(v1.begin(), v1.end(), v2.begin(), v2.end(), vTarget.begin());
    for_each(vTarget.begin(), itEnd1, myPrint);
    cout << endl;

    vector<int>::iterator itEnd2 = set_difference(v1.begin(), v1.end(), v2.begin(), v2.end(), vTarget.begin());
    for_each(vTarget.begin(), itEnd2, myPrint);
    cout << endl;
}

;