黑马程序员C++提高编程

目录

C++提高编程

1. 模板

1.1 模板的概念

1.2 函数模板

1.2.1 函数模板语法

1.2.2 函数模板注意事项

1.2.3 函数模板案例

1.2.3 普通函数与函数模板的区别

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

1.2.6 模板的局限性

1.3 类模板

1.3.1 类模板语法

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

1.3.3 类模板中成员函数创建时机

1.3.4 类模板对象做函数参数

1.3.5 类模板与继承

1.3.6 类模板成员函数类外实现

1.3.7 类模板份文件编写

1.3.8 类模板与友元

1.3.9 类模板案例

2. STL初识

2.1 STL的诞生

2.2STL的基本概念

2.3STL六大组件

2.4STL中容器、算法、迭代器

2.5容器算法迭代器初识

2.5.1 vector存放内置数据类型

2.5.2 Vector中存放自定义数据类型

2.5.3Vector容器嵌套容器

3 STL常用容器

3.1 string容器

3.1.1string 基本概念

3.1.2string构造函数

3.1.3string赋值操作

3.1.4string字符串拼接

3.1.5string查找和替换

3.1.6string字符串比较

3.1.7 string 字符存取

3.1.8 string插入和删除

3.1.9 string子串

3.2vector容器

3.2.1vector基本概念

3.1.2 vector构造函数

3.2.3 vector赋值操作

3.2.4 vector容量和大小

3.2.5 vector 插入和删除

3.2.6 vector 数据存取

3.2.7 vector 互换容器

3.2.8 vector预留空间

3.3 deque 容器

3.3.1 deque 容器的基本概念

3.3.2 deque构造函数

3.3.3 deque赋值操作

3.3.4 deque 大小操作

3.3.5 deque 插入和删除

3.3.6 deque 数据存取

3.3.7 deque 排序

3.4案例-评委打分

3.4.1 案例描述

3.4.2 实现步骤

3.5 stack 容器

3.5.1 stack容器基本概念

3.5.2 stack 常用接口

3.6 queue 容器

3.6.1 queue基本概念

3.6.2 queue 常用接口

3.7 list 容器

3.7.1 list基本概念

3.7.2 list构造函数

3.7.3 list赋值和交换

3.7.4 list 大小操作

3.7.5 list插入和删除

3.7.6 list数据存取

3.7.7 list 反转和排序

3.7.8排序案例

3.8 set/multiset 容器

3.8.1 set 基本概念

3.8.2 set 构造和赋值

3.8.3 set 大小和交换

3.8.4 set插入和删除

3.8.5 set 查找和统计

3.8.6 set和multiset区别

3.8.7 pair对组创建

3.8.8 set 容器排列

3.9 map/multimap 容器

3.9.1 map 基本概念

3.9.2 map构造和赋值

3.9.3 map 大小和交换

3.9.4 map插入和删除

3.9.5 map 查找和统计

3.9.6 map 容器排序

3.10 案例-员工分组

3.10.1 案例描述

3.10.2 实现步骤

4 STL-函数对象(仿函数)

4.1 函数对象

4.1.1 函数对象概念

4.1.2 函数对象使用

4.2 谓词

4.2.1 谓词概念

4.2.2 一元谓词

4.2.3 二元谓词

4.3 内建函数对象

4.3.1 内建函数对象意义

4.3.2 算术仿函数

4.3.3 关系仿函数

4.3.4 逻辑仿函数

5 STL-常用算法

5.1 常用遍历算法

5.1.1 for_each

5.1.2 transform

5.2 常用查找算法

5.2.1 find

5.2.2 find_if

5.2.3 adjacent_find (adjacent 相邻)

5.2.4 binary_search (二分查找法)

5.2.5 count

5.2.6 count_if

5.3 常用排序算法

5.3.1 sort

5.3.2 random_shuffle

5.3.3 merge

5.3.4 reverse

5.4 常用拷贝和替换算法

5.4.1 copy

5.4.2 replace

5.4.3 replace_if

5.4.4 swap

5.5 常用算术生成算法

5.5.1 accumulate

5.5.2 fill

5.6 常用集合算法

5.6.1 set_intersection

5.6.2 set_union

5.6.2 set_difference


C++提高编程

  • 本阶段主要针对C++泛型编程和STL技术详细讲解, 探讨C++更深层的使用

1. 模板

1.1 模板的概念

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

例如生活中的模板, 一寸照片模板, PPT模板

模板的特点:

  • 模板不可以直接使用, 他只是一个框架

  • 模板的通用并不是万能的

1.2 函数模板

  • C++另一种编程思想称为泛型编程, 主要利用的技术就是模板

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

1.2.1 函数模板语法

函数模板作用:

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

语法:

template<typename T>
函数声明或定义

解释:

template----声明创建模板

typename----表明其后面的符号是一种数据类型, 也可以写成class

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

示例:

#include<iostream>
using namespace std;
​
//函数模板
​
//交换两个整型函数
void swapInt(int &a,int &b) {
    int temp = a;
    a = b;
    b = temp;
}
​
//交换两个浮点型函数
void swapDouble(double& a, double& b) {
    double temp = a;
    a = b;
    b = temp;
}
​
//函数模板
template<typename T>//声明一个模板,告诉编译器后面的代码中紧跟着的T不要报错,  T是一个通用数据类型
void mySwap(T& a, T& b) {
    T temp = a;
    a = b; 
    b = temp;
}
​
void test01() {
    int a = 10;
    int b = 20;
​
    //swapInt(a, b);
    
    //利用函数模板交换
    //两种方式使用函数模板
​
    //1. 自动类型推导
    mySwap(a, b);
​
    //2. 显示指定类型
    mySwap<int>(a, b);
    
​
    
    cout << "a=" << a << endl;
    cout << "b=" << b << endl;
​
    double c = 1.1;
    double d = 2.2;
​
    swapDouble(c, d);
​
    cout << "c=" << c << endl;
    cout << "d=" << d << endl;
}
​
int main() {
    test01();
    return 0;
}

总结:

  • 函数模板利用关键字 template

  • 使用函数模板有两种方式:自动类型推导和显式指定类型

  • 模板的目的是为了提高复用性, 将类型参数化

1.2.2 函数模板注意事项

注意事项:

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

  • 模板必须要确定出T的数据类型, 才可以使用

示例:

#include<iostream>
using namespace std;

//注意事项:

template<class T>//typename 可以替换为 class
void mySwap(T& a, T& b) {
	T temp = a;
	a = b;
	b = temp;
}
//1. 自动类型推导, 必须推导出一致的数据类型T, 才可以使用
void test01() {
	int a = 10;
	int b = 20;
	char c = 'c';
	mySwap(a, b);//正确
	//mySwap(a, c);//错误,推导不出一致的T类型

	cout << "a=" << a << endl;
	cout << "b=" << b << endl;
	}


//2. 模板必须要确定出T的数据类型, 才可以使用
template<class T>
void func() {
	cout << "func() 的调用" << endl;
}

void test02() {
	//func();//错误,模板必须要确定出T的数据类型, 才可以使用
	func<int>();
}

int main() {
	test01();
	test02();
	return 0;
}

1.2.3 函数模板案例

案例描述:

  • 利用函数模板封装一个排序的函数, 可以对不同数据数组进行排序

  • 排序规则从大到小, 排序算法为选择排序

  • 分别利用char数组int数组进行测试

示例:

#include<iostream>
using namespace std;

//实现通用 对数组进行排序的函数
//规则 从大到小
//算法 选择排序
//测试 char 数组 int 数组

//交换函数模板
template<class T>
void mySawp(T& a, T& b) {
	T temp = a;
	a = b; b = temp;
}

//排序算法
template<class T>
void mySort(T arr[], int len) {
	for (int i = 0; i < len; i++) {
		int max = i;//认定最大值的下标是i
		for (int j = i + 1; j < len; j++) {
			//认定的最大值 比 遍历出的数值 要小, 说明j下标的元素才是真正的最大值
			if (arr[max] < arr[j]) {
				max = j;//更新最大值下标
			}
		}
		if(max!=i){
			mySawp(arr[max], arr[i]);
			/*T temp = arr[max];
			arr[max] = arr[i];
			arr[i] = temp;*/
		}
	}
}

//提供打印函数模板
template<class T>
void printArray(T arr[], int len) {
	for (int i = 0; i < len; i++) {
		cout << arr[i] << " ";
	}
	cout << endl;
}

void test01() {
	//测试char数组
	char charArr[] = "badcfe";
	int num = sizeof(charArr) / sizeof(char);
	mySort(charArr, num);
	printArray(charArr, num);

}

void test02() {
	//测试int数组
	int intArr[] = { 7,5,1,3,9,2,4,6,8 };
	int num = sizeof(intArr) / sizeof(int);
	mySort(intArr, num);
	printArray(intArr, num);
}
int main() {
	test01();
	test02();
	return 0;
}

1.2.3 普通函数与函数模板的区别

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

  • 普通函数调用时可以发生自动类型转换(隐式类型转换)

  • 函数模板调用时, 如果利用自动类型推导, 不会发生隐式类型转换

  • 如果利用显示指定类型的方式, 可以发生隐式类型转换

示例:

#include<iostream>
using namespace std;

///**普通函数与函数模板的区别:

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

//普通函数
int myAdd01(int a, int b) {
	return a + b;
}

//函数模板
template<class T>
T myAdd02(T a, T b) {
	return a + b;
}

void test01() {
	int a = 10;
	int b = 20;
	char c = 'c';//a--97, c-99
	cout << myAdd01(a, c) << endl;//将字符'c'隐式转化为整型

	//自动类型推导
	//cout << myAdd02(a, b) << end;; //报错, 不会发生隐式类型转换

	//显示指定类型
	cout << myAdd02<int>(a, c) << endl;// 显示指定类型, 可以发生隐式类型转换
}

int main() {
	test01();
	return 0; 
}

总结:建议使用显示指定类型的方式, 调用h函数模板, 因为可以自己确定通用类型T

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

调用规则:

  1. 如果函数模板和普通函数都可以实现, 优先调用普通函数

  2. 可以通过空模板参数列表来强制调用函数模板

  3. 函数模板也可以发生重载

  4. 如果函数模板可以产生更好的匹配, 优先调用函数模板

示例:

#include<iostream>
using namespace std;

//调用规则:

//1. 如果函数模板和普通函数都可以实现, 优先调用普通函数
//2. 可以通过空模板参数列表来强制调用函数模板
//3. 函数模板也可以发生重载
//4. 如果函数模板可以产生更好的匹配, 优先调用函数模板

void myPrint(int a, int b){
	cout << "调用的是普通函数" << endl;
}

template<class T>
void myPrint(T a, T b) {
	cout << "调用的是函数模板" << endl;
}

template<class T>
void myPrint(T a, T b,T c) {//函数模板也可以发生重载
	cout << "调用的是重载的函数模板" << endl;
}


void test01() {
	int a = 10;
	int b = 20;
	//myPrint(a, b); //如果函数模板和普通函数都可以实现, 优先调用普通函数

	//可以通过空模板参数列表来强制调用函数模板
	// myPrint<>(a, b);

	//myPrint(a, b, 10);

	//如果函数模板可以产生更好的匹配, 优先调用函数模板
	char c1 = 'a';
	char c2 = 'b';
	myPrint(c1, c2);//普通函数也可以调用, 但是需要进行一次类型转换, 不是更好的匹配
}

int main() {
	test01();
	return 0;
}

总结:既然提供了函数模板, 最好就不要提供普通函数, 否则容易出现二义性

1.2.6 模板的局限性

局限性:

  • 模板的通用不是万能的

例如:

template<class T>
void f(T a,T b){
	a = b;	
}

在上述代码中提供的赋值操作, 如果传入的a和b是数组, 就无法实现了

再例如:

template<class T>
void f(T a,T b){
    if(a>b){
        ...
    }
}

在上述代码中提供的赋值操作, 如果传入的a和都是数组,就无法实现了

因此C++为了解决这个问题, 提供函数的重载, 可以为这些特定的类型提供具体化的模板

示例:

#include<iostream>
using namespace std;

//模板局限性:
//模板的通用不是万能的,有些特定的数据类型需要用具体化方式做特殊实现

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

	string m_Name;
	int m_Age;
};

//对比两个数据是否相等函数
template<class T>
bool myCompare(T& a, T& b) {
	if (a == b) {
		return true;
	}
	else {
		return false;
	}
}

//利用具体化Person的版本实现代码,  具体化优先调用
template<>bool myCompare(Person &p1, Person &p2) {
	if (p1.m_Name == p2.m_Name && p1.m_Age == p2.m_Age) {
		return true;
	}
	else {
		return false;
	}
}
void test01() {
	int a = 10;
	int b = 20;

	bool ret = myCompare(a, b);
	cout << ret << endl;
	if (ret) {
		cout << "a==b" << endl;
	}
	else {
		cout << "a!=b" << endl;
	}
}

void test02() {
	Person p1("Tom", 10);
	Person p2("Tom", 10);

	bool ret = myCompare(p1, p2);
	if (ret) {
		cout << "p1==p2" << endl;
	}
	else {
		cout << "p1!=p2" << endl;
	}
}

int main() {
	//test01();
	test02();
	return 0;
}

总结:

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

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

1.3 类模板

1.3.1 类模板语法

类模板作用:

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

语法:

template<typename T>
类

解释:

template----声明创建模板

typename----表明其后面的符号是一种数据类型, 也可以写成class

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

示例:

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

//类模板
template<class NameType,class AgeType>
class Person {
public: 
	Person(NameType name,AgeType age){
		this->m_Name = name;
		this->m_Age = age;
	}

	void showPerson() {
		cout << "name:" << this->m_Name << "  age:" << this->m_Age << endl;

	}

	NameType m_Name;
	AgeType m_Age;
};

void test01() {
	Person<string, int>p1("孙悟空",999);
	p1.showPerson();
}

int main() {
	test01();
	return 0;
}

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

类模板与函数模板的区别主要有两种:

  1. 类模板没有自动类型推导的使用方式

  2. 类模板在模板参数列表中可以有默认参数

示例:

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

//类模板与函数模板的区别
template<class NameType,class AgeType=int>//2. 类模板在模板参数列表中可以有默认参数
class Person {
public:
	Person(NameType name, AgeType age) {
		this->m_Name = name;
		this->m_Age = age;
	}

	void showPerson() {
		cout << "name:" << this->m_Name << " age:" << this->m_Age << endl;
	}

	NameType m_Name;
	AgeType m_Age;
};

//1. 类模板没有自动类型推导的使用方式
void test01() {
	//Person p("孙悟空", 1000);//报错, 无法用自动类型推导
	Person<string, int>p("孙悟空", 1000);
	p.showPerson();
}

//2. 类模板在模板参数列表中可以有默认参数
void test02() {
	Person<string>p("猪八戒", 900);
	p.showPerson();
}

int main() {
	test01();
	test02();
	return 0;
}

1.3.3 类模板中成员函数创建时机

类模板中成员函数和普通类中成员函数创建时机是有区别的:

  • 普通类中成员函数一开始就可以创建

  • 类模板中的成员函数在调用时才创建

示例:

#include<iostream>
using namespace std;

//类模板中成员函数和普通类中成员函数创建时机是有区别的:

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

class Person1 {
public:
	void showPerson1() {
		cout << "Person1 show" << endl;
	}
};

class Person2 {
public:
	void showPerson2() {
		cout << "Person2 show" << endl;
	}
};

template<class T>
class MyClass {
public:
	T obj;

	//类模板中的成员函数
	void func1() {
		obj.showPerson1();
	}

	void func2() {
		obj.showPerson2();
	}//没有报错, 类模板中的成员函数并不是一开始就创建的, 而是在模板调用时再生成
};

void test01() {
	MyClass<Person1>m;
	m.func1();
	m.func2();//编译出错, 说明函数调用才会去创建成员函数
}

int main() {
	test01();
	return 0;
}

总结:类模板中的成员函数并不是一开始就创建的, 而是在模板调用时再生成

1.3.4 类模板对象做函数参数

学习目标: 类模板实例化的对象, 向函数传参的方式

一共有三种传入方式:

  1. 指定传入的类型: 直接显示对象的数据类型

  2. 参数模板化: 将对象中的参数变为模板进行传递

  3. 整个类模板化: 将这个对象类型 模板化进行传递

示例:

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

//类模板实例化的对象做函数参数 

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

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

	T1 m_Name;
	T2 m_Age;
};

//1. 指定传入的类型 : 直接显示对象的数据类型
void printPerson1(Person<string, int>&p) {
	p.showPerson();
}

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

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

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

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

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


int main() {
	test01();
	test02();
	test03();
	return 0;
}

总结:

  1. 查看编译器推导的T类型

cout << "T的类型为:" << typeid(T).name()<< endl;

  1. 通过类模板创建的对象,, 有三种方式向函数中进行传参

  2. 使用比较广泛的时第一种; 指定传入的类型

1.3.5 类模板与继承

当类模板碰到继承时, 需要注意以下几点:

  • 当子类继承的父类是一个类模板时, 子类在声明的时候, 要指定出父类中的类型

  • 如果不指定, 编译器无法给予子类内存分配

  • 如果想灵活指定出父类中T的类型, 子类也需变为类模板

示例:

#include<iostream>
using namespace std;

//类模板与继承

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

template<class T>
class Base {
	T m;
};
//class Son :public Base {//错误, 必须要知道父类中的T类型, 才能继承给子类
class Son :public Base<int>{//正确
	
};

void test01() {
	Son s1;
}

//如果想灵活指定出父类中T的类型, 子类也需变为类模板
template<class T1, class T2>
class Son2 :public Base<T2> {
public:
	Son2() {
		cout << "T1的类型为:" << typeid(T1).name() << endl;
		cout << "T2的类型为:" << typeid(T2).name() << endl;
	}
	T1 obj;
};

void test02() {
	Son2<int, char>S2;
}

int main() {
	//test01(); 
	test02();
	return 0;
}

1.3.6 类模板成员函数类外实现

学习目标: 能够掌握模板中的成员函数类外实现

示例:

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

//类模板成员函数类外实现
template<class T1,class T2>
class Person {
public:
	Person(T1 name, T2 age); //类内只写函数声明
	//{
	//	this->m_Name = name;
	//	this->m_Age = age;
	//}

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

	T1 m_Name;
	T2 m_Age;
};

//构造函数的类外实现
template<class T1,class T2>
Person<T1, T2>::Person(T1 name, T2 age) {
	this->m_Name = name;
	this->m_Age = age;
}


//成员函数类外实现
template<class T1,class T2>
void Person<T1, T2>::showPerson() {
	cout << "姓名:" << this->m_Name << " 年龄: " << this->m_Age << endl;
}

void test01() {
	Person<string, int>p("Tom", 20);
	p.showPerson();
}

int main() {
	test01();
	return 0;
}

总结:类模板中成员函数类外实现时, 需要加上模板参数列表

1.3.7 类模板份文件编写

(类很多, 分文件编写)

学习目标: 掌握类模板成员函数分文件编写产生的问题以及解决方式

问题:

  • 类模板中成员函数创建时期是在调用阶段, 导致分文件便携式链接不到

解决:

  • 方式1: 直接包含.cpp源文件

  • 方式2: 将声明和实现写到同一个文件中, 并更改后缀名为.hpp, .hpp是约定的名字, 并不是强制

示例:

person.hpp中 代码:

#pragma
#include<iostream>
using namespace std;

#include<string>

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

	void showPerson();

	T1 m_Name;
	T2 m_Age;
};


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

template<class T1, class T2>
void Person<T1, T2>::showPerson() {
	cout << "姓名:" << this->m_Name << "  年龄:" << this->m_Age << endl;
}

类模板分文件编写.cpp中代码

#include<iostream>
using namespace std;

//第一种解决方式:  直接包含 源文件
//#include"person.cpp"

//第二种解决方式:  将.h和.cpp中的内容写到一起,将后缀名改为.hpp
#include"person.hpp"


//#include<string>

//类模板份文件编写问题以及解决
//
//template<class T1,class T2>
//class Person {
//public: 
//	Person(T1 name, T2 age);
//
//	void showPerson();
//
//	T1 m_Name;
//	T2 m_Age;
//};

//template<class T1, class T2>
//Person<T1, T2>::Person(T1 name, T2 age) {
//	this->m_Name = name;
//	this->m_Age = age;
//}
//
//template<class T1, class T2>
//void Person<T1, T2>::showPerson() {
//	cout << "姓名:" << this->m_Name << "  年龄:" << this->m_Age << endl;
//}


void test01() {
	Person<string, int>p("Jerry", 18);
	p.showPerson();
}

int main() {
	test01();
	return 0;
}

总结:主流解决方式是第二种, 将类模板成员函数写到一起, 并将后缀名改为.hpp

1.3.8 类模板与友元

学习目标: 掌握类模板配合友元函数的类内和类外实现

全局函数类内实现: 直接再类内声明友元即可

全局函数类外实现: 需要提前让编译器知道全局函数的存在

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

//通过全局函数 打印Person信息  直接在类内声明友元即可

//提前让编译器知道Person类的存在
template<class T1, class T2>
class Person;

//类外实现
template<class T1, class T2>
void printPerson2(Person<T1, T2>p) {
	cout << "类外实现---姓名: " << p.m_Name << " 年龄:" << p.m_Age << endl;
}

template<class T1,class T2>
class Person {
	//全局函数 类内实现
	friend void printPerson(Person<T1, T2>p) {
		cout << "姓名: " << p.m_Name << " 年龄:" << p.m_Age << endl;
	}

	//全局函数 类外实现
	//加空模板参数列表, 告诉编译器这是模板函数
	//如果全局函数 是类外实现,  需要让编译器提前知道这个函数的存在
	friend void printPerson2<>(Person<T1, T2>p);

public:
	Person(T1 name,T2 age) {
		this->m_Name = name;
		this->m_Age = age;
	}

private:
	T1 m_Name;
	T2 m_Age;
};


//1. 全局函数 类内实现
void test01() {
	Person<string, int>p("Tom", 18);
	printPerson(p);
}

//2. 全局函数类外实现   需要提前让编译器知道全局函数的存在
void test02() {
	Person<string, int>p("Jerry", 20);
	printPerson2(p);
}
int main() {
	//test01();
	test02();
	return 0;
}

总结:建议全局函数做类内实现, 用法简单, 而且编译器可以直接识别

1.3.9 类模板案例

案例描述: 实现一个通用的数组类, 要求如下:

  • 可以对内置数据类型以及自定义数据类型的数据进行存储

  • 将数组中的数据存储到堆区

  • 构造函数中可以传入数组的容量

  • 提供对应的拷贝构造函数以及operator=防止浅拷贝问题

  • 提供尾插法和尾删法对数组中的数据进行增加和删除

  • 可以通过下标的方式访问数组中的元素

  • 可以获取数组中当前元素个数和数组的容量

示例:

myArray.h中代码

//自己的通用的数组类
#pragma once
#include<iostream>
using namespace std;

template<class T>
class MyArray {
public:
	//有参构造, 参数是 容量
	MyArray(int capacity) {
		this->m_Capacity = capacity;
		this->m_Size = 0;
		this->pAddress = new T[this->m_Capacity];

		//cout << "MyArray的构造函数调用" << endl;
	}
	
	//拷贝构造
	MyArray(const MyArray& arr) {
		this->m_Capacity = arr.m_Capacity;
		this->m_Size = arr.m_Size;
		//this->pAddress = arr.pAddress;//系统提供的拷贝构造--浅拷贝
		
		this->pAddress = new T[arr.m_Capacity+4];//深拷贝

		//将arr中的数据都拷贝过来
		for (int i = 0; i < this->m_Size; i++) {
			this->pAddress[i] = arr.pAddress[i];
		}

		//cout << "MyArray的拷贝函数调用" << endl;
	}

	//operator= 防止浅拷贝问题
	MyArray& operator=(const MyArray& arr) {
		//先判断原来堆区是否有数据,  如果有,先释放
		if (this->pAddress != NULL) {
			//cout << "MyArray的oprator=函数调用" << endl;
			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[arr.m_Capacity];
		for (int i = 0; i < this->m_Size; i++) {
			this->pAddress[i] = arr.pAddress[i];
		}
		return *this;
	}

	//尾插法
	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--;
	}

	//通过下标的方式访问数组中的数据
	//这是我们自己写的数组,编译器不认识[],要重载中括号[]
	T& operator[](int index) {
		return this->pAddress[index];
	}

	//返回数组的容量
	int getCapacity() {
		return this->m_Capacity;
	}

	//返回数组大小
	int getSize() {
		return this->m_Size;
	}

	//析构函数
	~MyArray(){
		if (this->pAddress != NULL) {
			delete[] this->pAddress;
			this->pAddress = NULL;
		}
		//cout << "MyArray的析构函数调用" << endl;
	}

private:
	T* pAddress;//指针指向堆区开辟的真实数组  

	int m_Capacity;//容量  

	int m_Size;//大小  


};

类模板案例-数组类封装.cpp中函数

#include<iostream>
using namespace std;
#include"MyArray.hpp"
#include<string>
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 << endl;

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

	MyArray<int>arr2(arr1);

	cout << "arr2的打印输出为: " <<endl;
	printIntArray(arr2);
	cout << endl;

	//尾删
	arr2.Pop_Back();

	cout << "arr2尾删后的打印输出为: " <<endl;
	printIntArray(arr2);
	cout << endl;

	cout << "arr2的容量为: " << arr2.getCapacity() << endl;
	cout << "arr2的大小为: " << arr2.getSize() << 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("孙权", 22);
	Person p2("曹操", 22);
	Person p3("刘备", 21);
	Person p4("关羽", 20);
	Person p5("张飞", 19);

	//将数据插入到数组中
	arr.Push_Back(p1);
	arr.Push_Back(p2);
	arr.Push_Back(p3);
	arr.Push_Back(p4);
	arr.Push_Back(p5);

	//打印数组
	printPersonArray(arr);

	//输出容量
	cout << "arr1的容量为: " << arr.getCapacity() << endl;
	
	//输出大小
	cout << "arr1的大小为: " << arr.getSize() << endl;

}
int main() {
	//test01();
	test02();
	return 0;
}

2. STL初识

2.1 STL的诞生

  • 长久以来,软件界一直希望建立一种可重复利用的东西。

  • C++的面向对象泛性编程思想,目的就是复用性的提升

  • 大多数情况下,数据结构和算法都未能有一套标准,导致被迫从事大量重复工作。

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

2.2STL的基本概念

  • STL(standard template library,标准模板库

  • STL从广义上分为:容器算法迭代器

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

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

2.3STL六大组件

STL分为6大组件分别是:容器算法迭代器仿函数适配器配接器)、空间配置器

  1. 容器:各种常用的数据结构:如Vector 、list、 deque、 set、 map等,用来存放数据

  2. 算法:各种常用的算法:如sort、find、copy、for_each等

  3. 迭代器:扮演了容器与算法之间的胶合剂。

  4. 仿函数:行为类似函数,可作为算法的某种策略。

  5. 适配器:一种用来修饰容器或者仿函数或者迭代器接口的东西。

  6. 空间配置器:负责空间的配置与管理。

2.4STL中容器、算法、迭代器

容器:置物之所也

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

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

这些容器分为序列式容器关联式容器

序列式容器:强调值的排序序列,容器中的每个元素均有固定的位置。

关联式容器:二叉树结构,各元素之间没有严格的物理上的顺序关系。

(同样一组数据在序列式容器中按规定的顺序排列,而在关联式容器中可能会打乱顺序,按照某种特定的顺序排列)

算法:问题之解法也

有限的步骤,解决逻辑或数学上的问题。这一门学科我们叫做算法(Algorithms)

算法分为:质变算法非质变算法

质变算法:是指运算过程中会更改区间内的元素的内容,例如拷贝、替换、删除等等。

非质变算法:是指运算过程中不会更改区间内的元素内容例如查找、计数、遍历、寻找极值等等

迭代器:容器与算法之间的胶合剂

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

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

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

迭代器使用非常类似于指针。初学者阶段,我们先理解迭代器为指针。

迭代器种类:

种类功能支持运算
输入迭代器对数据的只读访问只读,支持++、==、!=
输出迭代器对数据的只写访问只写,支持++
前向迭代器读写操作、并能向前推进迭代器读写,支持++、==、!=
双向迭代器读写操作、并能向前推进迭代器读写,支持++、--
随机访问迭代器读写操作、可以以跳跃的方式访问任意数据,是功能最强的迭代器读写,支持++、--、[n]、-n、<、<=、>、>=

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

2.5容器算法迭代器初识

STL中最常用的容器为vector,可以理解为数组。下面我们将学习如何向这个容器中插入数据并遍历这个容器。

2.5.1 vector存放内置数据类型

容器:vetor

算法:for_each

迭代器:vetor<int>::iterator

示例

#include<iostream>
using namespace std;
#include <vector>
#include<algorithm>//标准算法头文件

void myPrint(int val) {
	cout << val << endl;
}
void test01() {
//创建vector容器对象,并且通过模板参数指定容器中存放的数据的类型
	vector<int>v;//创建了一个vector容器,相当于数组,向容器中放数据
	v.push_back(10);
	v.push_back(20);
	v.push_back(30);
	v.push_back(40);//向容器中插入数据

//通过迭代器访问容器中的数据
//vector<int>(相当于类型)作用域下的iterator(迭代器的名称)
	vector<int>::iterator itBegin = v.begin();//v.begin是起始迭代器(类似于指针),指向容器中第一个元素,v.rbegin()指向容器最后一个元素
    vector<int>::iterator itEnd = v.end();//v.end是结束迭代器,指向容器中最后一个元素的下一个位置
	//第一种遍历方式:
	while (itBegin != itEnd) {
		cout << *itBegin << endl;
		itBegin++;
	}
	//第二种遍历方式:
	for (vector<int>::iterator it = v.begin(); it != v.end(); it++) {
		cout << *it << endl;
	}
	//第三种遍历方式:利用STL提供的遍历三法
	for_each(v.begin(), v.end(), myPrint);
}
int main() {
	test01();
	return 0;

2.5.2 Vector中存放自定义数据类型

示例:

#include<iostream>
using namespace std;
#include<vector>
#include<algorithm>

//Vector中存放自定义数据类型
class Person
{
public:
	Person(string name, int age)
	{
		this->m_Name = name;
		this->m_Age = age;
	}
	string m_Name;
	int m_Age;
};
void test01() {
	vector<Person>v ;
	//存放对象
	Person p1 ("aaa", 10);
	Person p2 ("bbb", 20);
	Person p3 ("ccc", 30);
	Person p4 ("ddd", 40);
	Person p5 ("eee", 50);

	//向容器中添加数据
	v.push_back(p1);
	v.push_back(p2);
	v.push_back(p3);
	v.push_back(p4);
	v.push_back(p5);

	//遍历容器中的数据
	for (vector<Person>::iterator it = v.begin(); it != v.end(); it++) {
		cout << "姓名:" << (*it).m_Name << "  年龄:" << (*it).m_Age << endl;
		//it 是一个指针,解引用(*it)解出来的数据类型就是尖括号里的,即Person类型 需要拿他的属性出来,用“.”
		cout << "姓名:" << it->m_Name << "  年龄:" << it->m_Age << endl;
		//it 是一个指针,可以直接通过“->”得到其属性
	}
}

void test02() {
	vector<Person*>v;//该容器中放的是数据的地址
	//存放对象指针
	Person p1("aaa", 10);
	Person p2("bbb", 20);
	Person p3("ccc", 30);
	Person p4("ddd", 40);
	Person p5("eee", 50);

	//向容器中添加数据
	v.push_back(&p1);
	v.push_back(&p2);
	v.push_back(&p3);
	v.push_back(&p4);
	v.push_back(&p5);

	//遍历容器中的数据
	for (vector<Person*>::iterator it = v.begin(); it != v.end(); it++) {
		cout<<"姓名:"<<(*it)->m_Name << "  年龄:" << (*it)->m_Age << endl;
		//(*it)解出来的是Person类型的指针,通过指针得其属性,用“->”
	}
}
int main(){
	test01();
	test02();
	return 0;
}

2.5.3Vector容器嵌套容器

示例:

#include<iostream>
using namespace std;
#include<vector>
#include<algorithm>

//容器嵌套容器
void test01() {
	vector<vector<int>>v;

	//创建几个小容器
	vector<int>v1;//v1容器中存放的是1 2 3 4
	vector<int>v2;//v1容器中存放的是2 3 4 5
	vector<int>v3;//v1容器中存放的是3 4 5 6
	vector<int>v4;//v1容器中存放的是4 5 6 7

	//向小容器中添加数据
	for (int i = 0; i < 4; i++) {
		v1.push_back(i + 1);
		v2.push_back(i + 2);
		v3.push_back(i + 3);
		v4.push_back(i + 4);
	}

	//将小容器插入到大的容器vector v中
	v.push_back(v1);
	v.push_back(v2);
	v.push_back(v3);
	v.push_back(v4);

	//通过大容器,把所用数据遍历一遍
	for ( vector<vector<int>>::iterator it = v.begin(); it != v.end(); it++) {
		//(*it)-----容器 vector<int>
		for (vector<int>::iterator vit = (*it).begin(); vit != (*it).end(); vit++) {
			cout << *vit << " ";
		}
		cout << endl;
	}
}
int main(){
	test01();
	return 0;
}

3 STL常用容器

3.1 string容器

3.1.1string 基本概念

本质:

  • string是C++风格的字符串,而string本质上是一个类

string和char*的区别:

  • char*是一个指针

  • string是一个类,类内部封装了char,管理这个字符串,是一个char型的容器

特点:

string类内部封装了许多成员方法

例如:查找find、拷贝copy、删除delete、替换replace、插入insert

string管理char*所分配的内存,不用安心复制越界和取值越界等,由类内部进行负责

3.1.2string构造函数

构造函数原型:

  • string(); //创建一个空的字符串 例如:string str;

    string(const char* s); //传C语言的字符串初始化C++的字符串

  • string(const string& str); //使用一个string对象初始化另一个string,相当于copy

  • string(int n,char c); //使用n个字符c初始化

示例:

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

void test01(){
	string s1;//默认构造
	const char* str = "Hello World!";

	string s2(str);
	cout << "s2= " << s2 << endl;

	string s3(s2);
	cout << "s3= " << s3 << endl;

	string s4(10, 'a');
	cout << "s4= " << s4 << endl;
}

int main(){
	test01();
	return 0;
}

总结:string的多种构造方式没有可比性,灵活使用

3.1.3string赋值操作

功能描述:给string字符串进行赋值

赋值的函数类型;

  • string& operator=(const char* s); //char*类型字符串赋值给当前字符串 operator---算子

  • string& operator=(const string &s); //把字符串s赋给当前字符串

  • string& operator=(char c); //字符赋值给当前字符串

  • string& assign(const char* s); //把字符串s赋给当前字符串 assign--分配

  • string& assign(const char* s,int n); //把字符串s的前n个字符赋给当前的字符串

  • string& assign(const string &s); //把字符串s赋给当前字符串

  • string& assign(int n,char c); //用n个字符串c赋给当前字符串

    示例:

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

void test01() {
	string str1;
	str1 = "Hello World!";
	cout << "str1= " << str1 << endl;

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

	string str3;
	str3 = 'c';
	cout << "str3=" << str3 << endl;

	string str4;
	str4.assign("Hello C++!");
	cout << "str4=" << str4 << endl;

	string str5;
	str5.assign("Hello C++", 5);
	cout << "str5=" << str5 << endl;

	string str6;
	str6.assign(str5);
	cout << "str6=" << str6 << endl;

	string str7;
	str7.assign(10,'w');
	cout << "str7=" << str7 << endl;
}

int main() {
	test01();
	return 0;
}

总结:string的赋值方式很多,operator这种方式比较实用

3.1.4string字符串拼接

函数原型:

  • string& operator+=(const char* str); //重载+=操作符

  • string& operator+=(const char c); //重载+=操作符

  • string& operator+=(const string& str); //重载+=操作符

  • string& append(const char* s); //把字符串s连接到当前字符串结尾 append---附加

  • string& append(const char* s,int n); //把字符串s的前n个字符连接到当前的字符串结尾

  • string& append(const string &s); // 同operator+=(const string& str);

  • string& append(const string &s,int pos,int n); //字符串s从pos开始的n个字符连接到字符串结尾

示例:

void test01() {
	string str1;
	str1 = "我";
	str1 += "爱学习。";
		cout << "str1 = " << str1 << endl;

	string str2 ="LOL DNF";
	str2 += str1;
	cout << "str2= " << str2 << endl;

	string str3="I ";
	str3.append("love ");
	cout << "str3=" << str3 << endl;

	str3.append("C++!", 3);
	cout << "str3=" << str3 << endl;

	str3.append(str1);
	cout << "str3=" << str3 << endl;

	str3.append(str2, 0, 3);
	cout << "str3=" << str3 << endl;
}

3.1.5string查找和替换

功能描述:

  • 查找(find or rfind):查找指定字符是否存在

    • find 从左向右查找,rfind 从右向左查找

  • 替换(replace):在指定位置替换字符串

示例:

void(){
    //查找
    string str1="abcaecdesadeawag";
    int pos=str1.find("de");//查到的是第一个字符第一次出现的位置
    int pos1=str1.rfind("de");//查到的是第一个字符最后一次出现的位置
    if(pos==-1){ //如果未找到字符串的话,则自动输出-1
        cout<<"未找到字符串"<<endl;
    }else{
         cout<<"找到字符串,pos= "<<pos<<endl;//输出6,d第一次出现的位置是6
    }
    str1.find("adea",int n,int pos=x)//从x位置查找adea的前n个字符串出现的位置
    
    //替换
    string str2="abcdefg";
    str2.replace(1,3,"111111");//替换 从1号位置起的3个字符为111111
    cout<<"str2= "<<str2<<endl;//输出str2=a111111efg
    
}  

3.1.6string字符串比较

比较方式:

  • 按字符的ASCLL码进行对比

相等 返回 0

大于 返回 1

小于 返回 -1

函数原型:

  • int compare(const string &s) const;

  • int compare(const char *s) const;//与字符串s进行比较.

示例:

string str1="hello";
string str2="hello";
if(str1.cmpare(str2)==0){
    cout<<"str1 等于 str2"<<endl;
}

3.1.7 string 字符存取

string中单个字符存取方式有两种

  • char& operator[](int n);//通过 []

  • char& at(int n); //通过at

示例:

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

str[0]='w';//修改单个字符
str.at(1)='o';//修改单个字符
cout<<str<<endl;

3.1.8 string插入和删除

  • 插入:insert()

  • 删除:erase()

下标都是从0开始

示例:

string str="hello world";
str.insert(1,"111");//将字符111插入到第1个位置 输出 h111ello world
str.insert(x,n,char c);//从第x个位置插入字符串c的n个字符
str.erase(1,3);//从第一个位置起,删除3个字符 输出为 ho world

3.1.9 string子串

从字符串中获取想要的子串

函数原型:

  • string substr(int pos=0,int n=npos) const;//返回由pos开始的n个字符组成的字符串

string str="hello world";
string subStr=str.substr(1,3);
cout<<"subStr = "<<subStr<<endl;//输出结果 ell

//实用操作
void test(){
    string email="zhangsan@sina.com";
    //从邮箱地址中 获取 用户名信息
    int pos=email.find('@');
    string usrName=email.substr(0,pos);
}

总结:灵活运用求字串功能,可以在实际开发中获取有效信息

3.2vector容器

3.2.1vector基本概念

功能:

  • vector数据结构和数组十分相似,也称单端数组

vector与普通数组的区别

  • 数组是静态空间,而vector可以动态扩展

动态扩展:

  • 并不是在原空间之后续接新空间,而是找更大的内存空间,然后将原数据拷贝到新空间,释放原空间

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

3.1.2 vector构造函数

四种构造方式

示例:

#include <vector>
#include <iostream>
using namespace std;
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);
	printVector(v3);

	//拷贝构造
	vector<int>v4(v3);
	printVector(v4);
}
int main()
{
	test01();
	return 0;
}

3.2.3 vector赋值操作

给vector容器进行赋值

  • v.assign()

示例:

#include <vector>
#include <iostream>
using namespace std;
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);

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

	//assign区间赋值,前闭后开
	vector<int>v3;
	v3.assign(v1.begin(), v1.end());
	printVector(v3);

	//saaign n个elem方式赋值
	vector<int>v4;
		v4.assign(10,100);
	printVector(v4);
}
int main()
{
	test01();
	return 0;
}

3.2.4 vector容量和大小

功能描述:

  • 对vector容器的容量和大小操作

函数原型:

  • empty(); //判断容器是否为空

  • capacity(); //容器的容量(>=元素个数)

  • size(); //返回容器中元素的个数

  • resize(int num); //重新指定容器的长度为num,若容器指定值使其变长,则以默认值(0)填充新位置

    //若容器变短,则末尾超出容器长度的元素被删除

  • resize(int num,elem); //重新指定容器的长度为num,若容器变长,则以elem值填充新位置

    //若容器变短,则末尾超出容器长度的元素被删除

3.2.5 vector 插入和删除

示例:

#include <vector>
#include <iostream>
using namespace std;
void printVector(vector<int>& v) {
	for (vector<int>::iterator it = v.begin(); it != v.end(); it++) {
		cout << *it << " " ;
	}
	cout << endl;
}
//for (int i = 0; i < 10; i++) {
//	v1.push_back(i);
//}
//printVector(v1);
void test01() {
	vector<int>v;
	//尾插
	v.push_back(10);
	v.push_back(20);
	v.push_back(30);
	v.push_back(40);
	v.push_back(50);
	printVector(v);//遍历
	//尾删
	v.pop_back();
	printVector(v);
	//插入---第一个参数是迭代器
	v.insert(v.begin(), 100);
	printVector(v);

	v.insert(v.begin(), 2, 1000);//在第一个位置插入2个1000
	printVector(v);

	//删除---第一个参数是迭代器
	v.erase(v.begin()+i);//删除第i个元素
	printVector(v);

	v.erase(v.begin(), v.end());//类似清空
	printVector(v);

	//清空
	v.clear();
	printVector(v);
}
int main()
{
	test01();
	return 0;
}

3.2.6 vector 数据存取

  • v[i]

  • v.at(i)

  • v.front()

  • v.back()

示例:

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

void test01() {
	vector<int>v;
	for (int i = 0; i < 10; i++) {
		v.push_back(i);
	}
	//利用[]来访问数组中元素
	for (int i = 0; i < v.size(); i++) {
		cout << v[i] << " ";
	}
	cout << endl;

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

	//获取第一个元素
	cout << "第一个元素为:" << v.front() << endl;

	//获取最后一个元素
	cout << "最后一个元素为:" << v.back() << endl;
}
int main()
{
	test01();
	return 0;
}

3.2.7 vector 互换容器

实现两个容器内元素互换

函数原型:

  • swap(vec);//将vec与本身元素互换

示例:

#include<iostream>
using namespace std;
#include<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);
	}
	cout << "交换前的元素:" << endl;
	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);
}

//实际用途
//巧用swap可以收缩内存空间
void test02() {
	vector<int>v;
	for (int i = 0; i < 100000; i++) {
		v.push_back(i);
	}
	cout << "v的容量是:" << v.capacity() << endl;//13w多
	cout << "v的大小为:" << v.size() << endl;//10w

	v.resize(3);
	cout << "v的容量是:" << v.capacity() << endl;//容量还是13w多
	cout << "v的大小为:" << v.size() << endl;//大小为3,造成了空间浪费

	//巧用swap可以收缩内存空间
	vector<int>(v).swap(v);
//	vector<int>(v) 是一个匿名对象(假设是容器x),并按照v容器对x进行初始化操作,所以x的大小、容量都为3
//  .swap(v) 表示容器交换。匿名对象的特性:在上一语句执行完毕,系统自动回收匿名对象的空间
	cout << "v的容量是:" << v.capacity() << endl;//容量变为3
	cout << "v的大小为:" << v.size() << endl;//大小为3
}
int main() {
	test01();
	test02();
	return 0;
}

总结:swap可以使两个容器交换,可以达到实用的收缩内存效果

3.2.8 vector预留空间

功能描述:

  • 减少vector在动态扩展时的扩展次数

函数原型:

  • reserve(int len);//容器预留len个元素长度,预留位置不初始化,元素不可访问。

#include<iostream>
using namespace std;
#include<vector>
void test01() {
	vector<int>v1;
	//v1.reserve(100000);
	int* p = NULL;
	int num=0;//统计开辟空间的次数
	for (int i = 0; i < 100000; i++) {
		v1.push_back(i);
		if (p != &v1[0]) {
			p = &v1[0];
			num++;
		}
	}
	cout << num << endl;//输出30,即将10w个数压入容器,共开辟了30次空间,较为麻烦。
//使用reserve预留空间,即可一次性压入数据,不会出现压满空间后,重新分配空间,复制数据到新的空间里的情况
}

int main() {
	test01();
	return 0;
}

总结:如果数据量较大,可以一开始就利用reserve预留空间

3.3 deque 容器

3.3.1 deque 容器的基本概念

功能:

  • 是双端数组,可以对头端进行插入删除操作

deque与vector的区别:

  • vector对于头部的插入删除效率低,数据越大,效率越低

  • deque相对而言,对于头部的插入删除速度比vector快

  • vector访问元素的速度回避deque快,这和两者内部实现有关

3.3.2 deque构造函数

#include<iostream>
using namespace std;
#include<deque>
void printDeque(const deque<int>& d) {//对deque容器限制只读,不能改写其中数据,防止for循环内改写数据
	for (deque<int>::const_iterator it = d.begin(); it != d.end(); it++) {//使用“不呢个修改的”迭代器
	//*it=100;//容器中数据不能修改
		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);
	//n个elem构造
	deque<int>d3(10, 100);
	printDeque(d3);
	//拷贝构造
	deque<int>d4(d3);
	printDeque;
}
int main() {
	test01();
	return 0;
}

3.3.3 deque赋值操作

类vector赋值

  1. "="赋值

  2. assign区间赋值 d.assign()

  3. assign n个elem赋值 d.(10,100)

3.3.4 deque 大小操作

类vector,但deque无容量概念

  • d.empty(); //判断容器是否为空

  • d.size(); //返回容器中元素的个数

  • d.resize(int num); //重新指定容器的长度为num,若容器指定值使其变长,则以默认值(0)填充新位置

    //若容器变短,则末尾超出容器长度的元素被删除

  • d.resize(int num,elem); //重新指定容器的长度为num,若容器变长,则以elem值填充新位置

    //若容器变短,则末尾超出容器长度的元素被删除

3.3.5 deque 插入和删除

函数原型:

两端插入操作:

  • push_bank(elem);//尾部插入一个元素

  • push_front(elem);//头部

  • pop_back(elem);//尾部删除一个元素

  • pop_front(elem);//头部

指定位置操作:插入和删除提供的位置全都是迭代器

  • insert(pos,elem); //在pos位置插入一个元素的拷贝,返回新数据的位置

  • insert(pos,n,elem); //在pos位置插入n个elem,无返回值

  • insert(pos,beg,end); //在pos位置插入区间内的数据,无返回值

  • clear(); //清空容器中所有数据

  • erase(beg,end); //删除区间内的数据,返回下一个数据的位置

  • erase(pos); //删除pos位置的数据,返回下一个数据位置

3.3.6 deque 数据存取

  • v.[i] //利用[]来访问数组中元素

  • v.at(i) //利用at方式访问元素

  • v.front()//获取第一个元素

  • v.back() //获取最后一个元素

3.3.7 deque 排序

利用算法实现对容器进行排序

算法:使用算法函数需包含标准算法头文件(algorithm)

  • sort(iterator beg,iterator end)//对(beg,end)区间内的元素进行排序

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);
}
int main(){
    sort(d1.begin(),d1.end());//默认从小到大排序
    //对于支持随机访问的迭代器的容器,都可以利用sort算法直接对其排序
    //vector、deque都支持使用sort算法
}

3.4案例-评委打分

3.4.1 案例描述

  • 有5名选手ABCDE,10名评委分别对没一名选手进行打分,去除最高分,去除最低分,取平均值

3.4.2 实现步骤

  1. 创建五名选手(类),放到vector中

  2. 遍历vector容器,取出来每名选手,执行for循环,将10位评委的打分存到deque容器(可以对头端尾端进行操作)中

  3. sort算法对deque容器中的分数进行排序,去除最低和最高分

  4. 遍历deque容器,累加得分

  5. 获取平均值

示例代码:

#include<iostream>
using namespace std;
#include<deque>
#include<vector>
#include<algorithm>
#include<string>
#include<ctime>

//选手类
class Person {
public:
	Person(string name, int score) {
		this->m_Name = name;
		this->m_Score = score;
	}
	string m_Name;//姓名
	int m_Score;//平均分

};
void createPerson(vector<Person>& v) {
	string nameSeed = "ABCDE";//名称的种子
	for (int i = 0; i < 5; i++) {
		string name = "选手";
		name += nameSeed[i];
		int score = 0;//平均分初始化
		Person p(name, score);
		v.push_back(p);//将创建的person对象放到容器中
	}
}
void setScore(vector<Person>& v) {
	for (vector<Person>::iterator it = v.begin(); it != v.end(); it++) {

		//将评委的分数放入到deque容器中
		deque<int>d;
		for (int i = 0; i < 10; i++) {
			int score = rand() % 41 + 60;//0~100的随机数
			d.push_back(score);
		}

		//测试
		cout << "选手:" << it->m_Name << " 打分:";
		for (deque<int>::iterator dit = d.begin(); dit != d.end(); dit++) {
			cout << *dit << " ";
		}
		cout << endl;

		sort(d.begin(), d.end());//排序
		d.pop_back();
		d.pop_front();//删除最低和最高分

		//取平均分
		int sum = 0;
		for (deque<int>::iterator dit = d.begin(); dit != d.end(); dit++) {
			sum +=*dit;//*
		}
		int avg = sum / d.size();
		//将平均分赋给到选手身上
		it->m_Score = avg;//it就是人的指针
	}
}
void showScore(vector<Person>&v) {
	for (vector<Person>::iterator it = v.begin(); it != v.end(); it++) {
		cout << "姓名:" << it->m_Name << "  平均分:" << it->m_Score << endl;
	}
}
int main() {
	//随机数种子
	srand((unsigned int)time(NULL));
    //1、创建五名选手
	vector<Person>v;//存放选手的容器
	createPerson(v);
	
	//测试
	/*for (vector<Person>::iterator it = v.begin(); it != v.end(); it++) {
		cout << "姓名:"<<(*it).m_Name << "分数:" << (*it).m_Score << endl;
	}*/

	//2、给五名选手打分
	setScore(v);
	
	//3、显示最后得分
	showScore(v);

	return 0;
}

3.5 stack 容器

3.5.1 stack容器基本概念

概念:stack()是一种先进后出(First In Last Out,FILO)的数据结构,他只有一个出口

栈中只有栈顶元素可以被访问,因此栈不允许被遍历

3.5.2 stack 常用接口

函数构造:

  1. 无参构造--stack<int>s;

  2. 拷贝构造--stack<int>s(s1);

赋值操作:

  • ”=“赋值--s=s1;

数据存取:

  • 入栈--push()

  • 出栈--pop()

  • 取栈顶元素--top()

  • 返回栈顶元素,不在堆栈中删除它--peek()

大小操作:

  • 判断栈是否为空--empty()

  • 栈的大小--size()

示例:

void test01(){
    stack<int>s;
    
    //入栈
    s.push(10);
    s.push(20);
    s.push(30);
    s.push(40);
    
    cout<<"栈的大小:"<<s.size()<<endl;
    
    //栈不为空,查看栈顶,执行出栈操作
    while(!empty()){
        
        //查看栈顶元素
        cout<<"栈顶元素是:"<<s.top<<endl;
        
        //出栈
        s.pop();
    }
}

3.6 queue 容器

3.6.1 queue基本概念

概念:Queue(队列)是一种先进先出(First In First Out,FIFO)的数据结构,它有两个出口

队列容器允许从一端新增数据,从另一端移除数据

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

3.6.2 queue 常用接口

函数构造:

  1. 无参构造--queue<int>q;

  2. 拷贝构造--queue<int>q(q1);

赋值操作:

  • ”=“赋值--```q=q1;

数据存取:

  • 入队--push()

  • 出队--pop()

  • 返回最后一个元素--back()

  • 返回第一个元素--front()

大小操作:

  • 判断栈是否为空--empty()

  • 队列的大小--size()

3.7 list 容器

3.7.1 list基本概念

功能:对数据进行链式存储

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

链表的组成:由一系列结点组成

结点的组成:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域

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

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

list的优点:

  • 采用动态存储分配,不会造成内存浪费和溢出(若vector中有十万个元素,则容器容量可能达到十三万)

  • 链表执行插入和删除操作十分方便,修改指针即可,不需要要移动大量元素

list的缺点:

  • 空间(指针域)和时间(遍历)额外耗费大

list的一个重要性质:插入和删除操作都不会造成原有list迭代器的失效,这在vector是不成立的(若vector容器已满,再插入元素时,则会重新开辟空间,迭代器失效)

总结:STL中ListVector是最常被使用的容器,各有优缺点

3.7.2 list构造函数

  1. 默认构造

  2. 区间构造

  3. n个elem构造

  4. 拷贝构造

#include<iostream>
using namespace std;
#include<list>
void printList(const list<int>& l) {
	for (list<int>::const_iterator it = l.begin(); it != l.end(); it++) {
		cout << *it << " ";
	}
	cout << endl;
}
void test01() {

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

	//遍历容器
	printList(L1);

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

	//n个elem方式进行构造
	list<int>L3(10, 100);
	printList(L3);

	//拷贝构造
	list<int>L4(L3);
	printList(L4);
}
int main() {
	test01();
	return 0;
}

3.7.3 list赋值和交换

给list容器进行赋值

  • l.assign();

给list容器进行

  • swap(lst);//将lst与本身元素互换

示例:

#include <list>
#include <iostream>
using namespace std;
void printList(list<int>& l) {
	for (list<int>::iterator it = l.begin(); it != l.end(); it++) {
		cout << *it << " ";
	}
	cout << endl;
}
void test01() {
	list<int>l1;
	for (int i = 0; i < 10; i++) {
		l1.push_back(i);
	}
	printList(l1);

	//赋值 operator=
	list<int>l2;
	l2 = l1;
	printList(l2);

	//assign区间赋值,前闭后开
	list<int>l3;
	l3.assign(l1.begin(), l1.end());
	printList(l3);

	//saaign n个elem方式赋值
	list<int>l4;
	l4.assign(10, 100);
	printList(l4);
}
void test02() {
	list<int>l1;
	for (int i = 0; i < 10; i++) {
		l1.push_back(i);
	}
	cout << "交换前的元素:" << endl;
	printList(l1);

	list<int>l2;
	for (int i = 10; i > 0; i--) {
		l2.push_back(i);
	}
	printList(l2);
	cout << "交换后:" << endl;
	l1.swap(l2);
	printList(l1);
	printList(l2);
}
int main()
{
	//test01();
	test02();
	return 0;
}

3.7.4 list 大小操作

  • empty(); //判断容器是否为空

  • capacity(); //容器的容量(>=元素个数)

  • size(); //返回容器中元素的个数

  • resize(int num); //重新指定容器的长度为num,若容器指定值使其变长,则以默认值(0)填充新位置

    //若容器变短,则末尾超出容器长度的元素被删除

  • resize(int num,elem); //重新指定容器的长度为num,若容器变长,则以elem值填充新位置

    //若容器变短,则末尾超出容器长度的元素被删除

3.7.5 list插入和删除

函数原型:

两端插入操作:

  • push_bank(elem);//尾部插入一个元素

  • push_front(elem);//头部插入一个元素

  • pop_back(elem);//尾部删除一个元素

  • pop_front(elem);//头部删除一个元素

指定位置操作:插入和删除提供的位置全都是迭代器

  • insert(pos,elem); //在pos位置插入一个元素的拷贝,返回新数据的位置

  • insert(pos,n,elem); //在pos位置插入n个elem,无返回值

  • insert(pos,beg,end); //在pos位置插入区间内的数据,无返回值

  • clear(); //清空容器中所有数据

  • erase(beg,end); //删除区间内的数据,返回下一个数据的位置

  • erase(pos); //删除pos位置的数据,返回下一个数据位置

  • remove(); //移除容器中所有与elem值匹配的元素

3.7.6 list数据存取

  • l.front()//获取第一个元素

  • l.back() //获取最后一个元素

不可以使用[]或at方式访问元素:list本质是链表,不是用联系线性空间存储数据,迭代器也是不支持随机访问的

list<int>::iterator it=l.begin();
it++;//正确,支持双向
it=it+1;//错误,不支持随机访问

3.7.7 list 反转和排序

函数原型:

  • reverse(); //反转链表

  • sort(); //链表排序

#include<iostream>
using namespace std;
#include<list>
void printList(const list<int>& l) {
	for (list<int>::const_iterator it = l.begin(); it != l.end(); it++) {
		cout << *it << " ";
	}
	cout << endl;
}
bool myCompare(int v1,int v2) {
	//降序 就让第一个 > 第二个数
	return v1 > v2;
}
void test01() {
	list<int>L1;
	L1.push_back(10);
	L1.push_back(30);
	L1.push_back(40);
	L1.push_back(20);
	L1.push_back(50);
	cout << "反转前链表元素;";
	printList(L1);

	//反转链表
	L1.reverse();
	cout << "反转后链表元素;";
	printList(L1);

	//排序
	L1.sort();//默认从小到大升序排序
	cout << "排序后链表元素;";
	printList(L1);

	//从大到小降序排列
	cout << "降序后链表元素;";
	L1.sort(myCompare);
	printList(L1);
}
int main() {
	test01();
	return 0;
}

3.7.8排序案例

案例描述:将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 comparePerson(Person& p1, Person& p2) {
	//按年龄升序
	if (p1.m_Age == p2.m_Age) {
		//年龄相同,按照身高降序
		return p1.m_Height > p2.m_Height;
	}
	else {
		return p1.m_Age < p2.m_Age;
	}
}
void test01() {
	list<Person>L;
	Person p1("刘备", 35, 175);
	Person p2("曹操", 45, 176);
	Person p3("赵云", 33, 180);
	Person p4("关羽", 35, 177);
	Person p5("张飞", 30, 174);
	Person p6("孙权", 35, 176);
	
	//插入数据
	L.push_back(p1);
	L.push_back(p2);
	L.push_back(p3);
	L.push_back(p4);
	L.push_back(p5);
	L.push_back(p6);

	for (list<Person>::iterator it = L.begin(); it != L.end(); it++) {
		cout << "姓名:"<<(*it).m_Name << " 年龄:"<<(*it).m_Age<<" 身高:"<<it->m_Height<<endl;
	}

	//排序
	cout << "-----------------------------" << endl;
	cout << "排序后:" << endl;
	L.sort(comparePerson);
	for (list<Person>::iterator it = L.begin(); it != L.end(); it++) {
		cout << "姓名:" << (*it).m_Name << " 年龄:" << (*it).m_Age << " 身高:" << it->m_Height << endl;
	}
}
int main() {
	test01();
	return 0;
}

总结:

  • 对于自定义数据类型,必须要指定排序规则否则编译器不知道如何排序

  • 高级排序只是在排序规则上再进行一次逻辑规则制定,并不复杂

3.8 set/multiset 容器

3.8.1 set 基本概念

简介:

  • 所有元素都会在插入时自动被排序

  • 都使用<set>头文件

本质:

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

3.8.2 set 构造和赋值

函数构造:

  1. 默认构造--set<int>st;

  2. 拷贝构造--set<int>st(st1);

函数赋值:

  1. “=”赋值---s1=s2;

插入数据:只有 insert 方式

s1.insert(10);

3.8.3 set 大小和交换

函数原型:

  • empty(); //判断容器是否为空

  • size(); //返回容器中元素的个数

  • swap(st); //将lst与本身元素互换

3.8.4 set插入和删除

函数原型:

  • insert(elem); //在容器中插入一个元素

  • clear(); //清空容器中所有数据

  • erase(beg,end); //删除区间内的数据,返回下一个元素的迭代器

  • erase(pos); //删除pos位置的数据,返回下一个元素的迭代器

  • erase(elem); //删除容器中值为elem的元素

3.8.5 set 查找和统计

函数原型:

  • find(key); //查找key是否存在,若存在,返回该键的迭代器,若不存在,返回set.end();

  • count(key);//统计key的元素个数

示例:

#include<iostream>
#include<set>
using namespace std;
void test01() {
    set<int>s;
    s.insert(10);
    s.insert(20);
    s.insert(30);
    s.insert(40);

    set<int>::iterator pos=s.find(30);
    if (pos != s.end()) {
        cout << "找到元素:" << *pos << endl;
    }
    else {
        cout << "未找到元素" << endl;
    }
}
void test02() {
    set<int>s;
    s.insert(10);
    s.insert(30);
    s.insert(30);
    s.insert(40);
    //统计,对于set,个数只能是1或0
    int num = s.count(30);
    cout << "num=" << num << endl;
}
int main() {
    test01();
    test02();
    return 0;
}

3.8.6 set和multiset区别

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

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

3.8.7 pair对组创建

功能描述:

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

两种创建方式:

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

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

示例:

#include<iostream>
#include<algorithm>
using namespace std;
void test01() {
    //第一种方式:
    pair<string, int>p("Tom", 20);
    cout << "姓名:" << p.first << " 年龄:" << p.second << endl;

    //第一种方式:
    pair<string, int>p2=make_pair("Jerry", 18);
    cout << "姓名:" << p2.first << " 年龄:" << p2.second << endl;
}
int main() {
    test01();
    return 0;
}

3.8.8 set 容器排列

  • set 自动按从小到大排序,改变排序规则

主要技术点:

  • 利用仿函数,可以改变排序规则

示例一:set存放内置数据类型

#include<iostream>
#include<set>
using namespace std;
class MyCompare {
public:
    bool operator()(int v1,int v2) {//重载小括号、参数列表
        return v1>v2;
    }
};
void test01() {
    set<int>s1;
    s1.insert(10);
    s1.insert(30);
    s1.insert(20);
    s1.insert(40);
    s1.insert(50);
    for (set<int>::iterator it = s1.begin(); it != s1.end(); it++) {
        cout << *it << " ";
    }
    cout << endl;

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

示例一:set存放自定义数据类型

3.9 map/multimap 容器

3.9.1 map 基本概念

简介:

  • map中所有元素都是pair

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

  • 所有元素都会根据元素的键值自动排序

本质:

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

优点:

  • 可以根据key值快速找到value值

map和multimap区别:

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

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

3.9.2 map构造和赋值

函数构造:

  1. 默认构造--map<int>mp;

  2. 拷贝构造--map<int>mp(mp1);

函数赋值:

  1. “=”赋值---mp1=mp2;

示例:

#include<iostream>
using namespace std;
#include<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;
    }
}
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);
    map<int, int>m2(m);//拷贝构造
    printMap(m2);

    //赋值
    map<int, int>m3;
    m3 = m;
}
int main() {
    test01();
    return 0;
}

3.9.3 map 大小和交换

函数原型:

  • empty(); //判断容器是否为空

  • size(); //返回容器中元素的个数

  • swap(st); //将lst与本身元素互换

3.9.4 map插入和删除

函数原型:

  • insert(elem); //在容器中插入一个元素

  • clear(); //清空容器中所有数据

  • erase(beg,end); //删除区间内的数据,返回下一个元素的迭代器

  • erase(pos); //删除pos位置的数据,返回下一个元素的迭代器

  • erase(key); //删除容器中键值为key的元素

示例:

#include<iostream>
using namespace std;
#include<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;
    }
}
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());
    printMap(m);
    
    m.erase(3);//按照key删除
    printMap(m);
    
   // m.erase(m.begin()+1,m.end());
    m.clear();
    printMap(m);
}
int main() {
    test01();
    return 0;
}

3.9.5 map 查找和统计

函数原型:

  • find(key); //查找key是否存在,若存在,返回该键的迭代器,若不存在,返回set.end();

  • count(key);//统计key的元素个数,要么是0,要么是1

3.9.6 map 容器排序

  • map 自动按照key值从小到大排序,改变排序规则

主要技术点:

  • 利用仿函数,可以改变排序规则

3.10 案例-员工分组

3.10.1 案例描述

  • 公司今天招聘了10个员工(ABCDEFGHIJ)10名员工进入公司之后,需要指派员工在哪个部门工作

  • 员工信息有:姓名、工资组成。部门分为策划、美术、研发。

  • 随机给10名员工分配部门和工资。

  • 通过multimap进行信息的插入。Key(部门编号)value(员工)。

  • 分部门显示员工信息。

3.10.2 实现步骤

  1. 创建10名员工放到vector容器中。

  2. 遍历vector容器,取出每个员工进行随机分组。

  3. 分组后,将员工部门编号作为Key,具体员工作为value放入multimap容器中。

  4. 分部门显示员工信息。

示例:

#include<iostream>
using namespace std;
#include<map>
#include<vector>
#include<string>
#include<ctime>
#define CEHUA 0
#define MEISHU 1
#define YANFA 2
class Worker {
public:
    Worker(string name, int salary) {
        this->m_Name = name;
        this->m_Salary = salary;
    }
    string m_Name;
    int m_Salary;
};
void creatWorker(vector<Worker>&v) {
    string nameSeed = "ABCDEFGHIJ";
    for (int i = 0; i < 10; i++) {
        string name = "员工";
        name += nameSeed[i];
        int salary =rand()%10001+10000;
        Worker p(name, salary);
        v.push_back(p);
    } 
}
void setGroup(vector<Worker>&v, multimap<int, Worker>& m) {
    for (vector<Worker>::iterator it = v.begin(); it != v.end(); it++) {
        //产生随机部门编号
        int deptId =rand()%3;//编号有:0 1 2

        //将员工插入到分组中
        //key是部门编号,value是具体的员工
        m.insert(make_pair(deptId, *it));
    }
}
void showWorkerByGroup(multimap<int, Worker>& m) {
    cout << "策划部门:" << endl;
    multimap<int, Worker>::iterator pos = m.find(CEHUA);
    int count = m.count(CEHUA);//统计策划部门人数
    int index = 0;
    for (; pos !=m.end()&&index<count; pos++,index++) {
        cout << "姓名:" << (*pos).second.m_Name << " 工资:" << pos->second.m_Salary << endl;
    }
    cout << "---------------------------------------------" << endl;;
    cout << "美术部门:" << endl;
    pos = m.find(MEISHU);
    count = m.count(MEISHU);//统计策划部门人数
    index = 0;
    for (; pos != m.end()&&index < count; pos++,index++) {
        cout << "姓名:" << (*pos).second.m_Name << " 工资:" << pos->second.m_Salary << endl;
    }
    cout << "---------------------------------------------"<<endl;;
    cout << "研发部门:" << endl;
    pos = m.find(YANFA);
    count = m.count(YANFA);//统计策划部门人数
    index = 0;
    for (; pos != m.end()&&index < count; pos++,index++) {
        cout << "姓名:" << (*pos).second.m_Name << " 工资:" << pos->second.m_Salary << endl;
    }
}


int main() {
    srand((unsigned int)time(NULL));
    //1、创建员工
    vector<Worker>v;
    creatWorker(v);

    //测试
    /*for (vector<Worker>::iterator it = v.begin(); it != v.end(); it++) {
        cout << "姓名:" << it->m_Name << " 工资:" << it->m_Salary << endl;
    }*/

    //2、员工分组
    multimap<int, Worker>m;
    setGroup(v,m);
    
    //3、分组显示员工
    showWorkerByGroup(m);
    return 0;
}

4 STL-函数对象(仿函数)

4.1 函数对象

4.1.1 函数对象概念

概念:

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

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

本质:

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

4.1.2 函数对象使用

特点:

  • 函数对象在使用时, 可以像普通函数那样调用, 可以有参数, 可以有返回值

  • 函数对象超出普通函数的概念, 函数对象可以有自己的状态

  • 函数对象可以作为参数传递

示例:

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

//函数对象 (仿函数)

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

class MyAdd {
public:
	int operator()(int v1, int v2) {
		return v1 + v2;
	}
};

//1. 函数对象在使用时, 可以像普通函数那样调用, 可以有参数, 可以有返回值
void test01() {
	MyAdd myAdd;//myAdd 就是一个函数对象
	cout<<myAdd(10, 10) << endl;
}

//2. 函数对象可以有自己的状态
class MyPrint {
public:
	MyPrint() {
		this->count = 0;
	}
	void operator()(string test) {
		cout << test << endl;
		this->count++;
	}

	int count;//内部自己状态

};

void test02() {
	MyPrint myPrint;
	myPrint("Hello World!");
	myPrint("Hello World!");
	myPrint("Hello World!");

	cout << "myPrint调用的次数为:" << myPrint.count << endl;
}

//3. 函数对象可以作为参数传递 
void doPrint(MyPrint& mp,string test) {
	mp(test);
}

void test03() {
	MyPrint myPrint;
	doPrint(myPrint, "Hello C++");
}

int main() {
	//test01();
	//test02();
	test03();
	return 0;
}

4.2 谓词

4.2.1 谓词概念

概念:

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

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

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

4.2.2 一元谓词

示例:

#include<iostream>
using namespace std;
#include<vector>
#include<algorithm>

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

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

class GreaterFive {
public:
	bool operator()(int val) {//一元谓词
		return val > 5;//val>5时 返回true,否则返回false
	}
};

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

	//查找容器中  有没有大于5的数字
	vector<int>::iterator it = find_if(v.begin(), v.end(), GreaterFive());//GreaterFive()是一个匿名对象
	if (it == v.end()) {
		cout << "未找到" << endl;
	}
	else {
		cout << "找到了大于5的数字为:" << *it << endl;
	}
}


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


int main() {
	test01();
	return 0;
}

算法:find_if();

4.2.3 二元谓词

示例:

#include<iostream>
using namespace std;
#include<vector>
#include<algorithm>

//二元谓词
class MyCompare {
public:
	bool operator()(int val1, int val2) {
		return val1 > val2;
	}
};

void test01() {
	vector<int>v;
	v.push_back(10);
	v.push_back(40);
	v.push_back(50);
	v.push_back(20);
	v.push_back(30);
 
	sort(v.begin(), v.end());
	for (vector<int>::iterator it = v.begin(); it != v.end(); it++) {
		cout << *it << " ";
	}
	cout << endl;

	//使用函数对象  改变算法策略, 变为从大到小排序
	sort(v.begin(), v.end(), MyCompare());

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

int main() {
	test01();
	return 0;
}

sort();算法

4.3 内建函数对象

4.3.1 内建函数对象意义

概念:

  • STL内建了一些函数对象

分类:

  • 算数仿函数

  • 关系仿函数

  • 逻辑仿函数

用法:

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

4.3.2 算术仿函数

功能描述:

  • 实现四则运算

  • 其中negate是一元运算, 其他都是二元运算

仿函数原型:

  • template<class T> T plus<T> // 加法仿函数

  • template<class T> T minus<T> // 减法仿函数

  • template<class T> T multiplies<T> // 乘法仿函数

  • template<class T> T divides<T> // 除法仿函数

  • template<class T> T modulus<T> // 取模仿函数

  • template<class T> T negate<T> // 取反仿函数

示例:

#include<iostream>
using namespace std;
#include<functional>//内建函数对象头文件

//内建函数对象  算数仿函数

//negate 一元仿函数 取反仿函数(负数)
void test01() {
	negate<int>n;

	cout << n(50) << endl;
}

//plus 二元仿函数  加法
void test02() {
	plus<int>p;
	cout << p(10, 20) << endl;
}

int main() {
	test01();
	test02();
	return 0;
}

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

4.3.3 关系仿函数

功能描述:

  • 实现关系对比

仿函数原型:

  • template<class T> bool equal_to<T> //等于

  • template<class T> bool not_equal<T> // 不等于

  • template<class T> bool greater<T> //大于

  • template<class T> bool greater_equal<T> // 大于等于

  • template<class T> bool less<T> //小于

  • template<class T> bool less_equal<T> // 小于等于

示例:

#include<iostream>
using namespace std;
#include<vector>
#include<functional>
#include<algorithm>

//内建函数对象_关系仿函数
//大于 greater

class MyCompare {
public:
	bool operator()(int v1,int v2) {
		return v1 > v2;
	}
};

void test01() {
	vector<int>v;
	v.push_back(10);
	v.push_back(30);
	v.push_back(50);
	v.push_back(20);
	v.push_back(40);

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

	//降序
	//sort(v.begin(), v.end(), MyCompare());
	//greater<int>是内建函数对象
	sort(v.begin(), v.end(), greater<int>());
	for (vector<int>::iterator it = v.begin(); it != v.end(); it++) {
		cout << *it << " ";
	}
	cout << endl;
}

int main() {
	test01();
	return 0;
}

4.3.4 逻辑仿函数

功能描述:

  • 实现逻辑运算

仿函数原型:

  • template<class T> bool logical_and<T> //逻辑与

  • template<class T> bool logical_or<T> // 逻辑或

  • template<class T> bool logical_not<T> //逻辑非

示例:

#include<iostream>
using namespace std;
#include<vector>
#include<functional>
#include<algorithm>

//内建函数对象_逻辑仿函数
//逻辑非  logical_not

void test01() {
	vector<bool>v;
	v.push_back(true);
	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 << " ";
	}
	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 << " ";
	}
	cout << endl;
}

int main() {
	test01();
	return 0;
}

总结:逻辑仿函数实际应用较少,了解即可

5 STL-常用算法

概述:

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

  • <algorithm> 是所有STL头文件中最大的一个, 范围设计到 比较, 交换, 查找, 遍历操作, 复制, 修改等

  • <numeric>体积很小, 只包括几个在序列上面进行简单数学运算的模板函数

  • <functional> 定义了一些模板类, 用以声明函数对象

5.1 常用遍历算法

算法简介:

  • for_each //遍历容器

  • transform //搬运容器到另一个容器中

5.1.1 for_each

功能描述:

  • 实现遍历容器

函数原型:

  • for_each(iterator beg,iterator end,_func);

//遍历算法 遍历容器元素

//beg 开始迭代器

//end 结束迭代器

//_func 函数或函数对象(使用普通函数时不带函数名后的括号)

示例:

 #include<iostream>
using namespace std;
#include<vector>
#include<algorithm>

//常用遍历算法 for_each

//普通函数
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;
}

int main() {
	test01();
	return 0;
}

总结: for_each在实际开发中是最常用的遍历算法, 需要熟练掌握

5.1.2 transform

功能描述:

  • 搬运容器到另一个容器

函数原型:

  • transform(iterator beg1,itrator end1,itrator beg2,_func);

//beg1 原容器开始迭代器

//end1 原容器结束迭代器

//beg2 目标容器开始迭代器

//_func 函数或者函数对象

示例:

#include<iostream>
using namespace std;
#include<vector>
#include<algorithm>

//常用遍历算法 transform

class Transform {
public:
	int operator()(int v) {
		return v+10;
	}
};

class MyPrint {
public:
	void operator()(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(), MyPrint());
	cout << endl;
}

int main() {
	test01();
	return 0;
}

总结: 搬运的目标容器必须要提前开辟空间, 否则无法正常搬运

5.2 常用查找算法

算法简介:

  • find //查找元素

  • find_if //按条件查找

  • adjacent_find //查找相邻重复元素

  • binary_search //二分查找法

  • count //统计元素个数

  • count_if //按条件统计元素个数

5.2.1 find

功能描述:

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

函数原型:

  • find(iterator beg, iterator end, value );

//按值查找元素,找到返回指定位置迭代器,找不到返回结束迭代器位置

//beg 开始迭代器

//end结束迭代器

//value 查找的元素

示例:

#include<iostream>
using namespace std;
#include<vector>
#include<algorithm>
#include<string>

//常用查找算法
//find

//查找 内置数据类型
void test01() {
	vector<int>v;
	for (int i = 0; i < 10; i++) {
		v.push_back(i);
	}

	//查找 容器中 是否有 5 这个元素
	vector<int>::iterator it=find(v.begin(), v.end(), 5);
	if (it == v.end()) {
		cout << "没有找到!" << endl;
	}
	else {
		cout << "找到:" << *it << endl;
	}
}

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

	//重载 == 底层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("aaa", 10);
	Person p2("bbb", 20);
	Person p3("ccc", 30);
	Person p4("ddd", 40);
	Person p5("eee", 50);

	//放到容器中
	v.push_back(p1);
	v.push_back(p2);
	v.push_back(p3);
	v.push_back(p4);
	v.push_back(p5);

	Person pp("bbb", 20);
	vector<Person>::iterator it=find(v.begin(), v.end(), pp);

	if (it == v.end()) {
		cout << "没有找到!" << endl;
	}
	else {
		cout << "找到元素 姓名:" << it->m_Name << " " << "年龄:" << it->m_Age << endl;
	}
}

int main() {
	//test01();
	test02();
	return 0;
}

总结: 利用find可以在容器中找指定的元素, 返回值是迭代器

5.2.2 find_if

功能描述:

  • 按条件查找元素

函数原型:

  • find_if(iterator beg, iterator end, _Pred );

//按值查找元素,找到返回指定位置迭代器,找不到返回结束迭代器位置

//beg 开始迭代器

//end结束迭代器

//_Pred 函数或者谓词(返回bool类型的仿函数)

示例:

#include<iostream>
using namespace std;
#include<vector>
#include<algorithm>
#include<string>

//常用查找算法 find_if

//1. 查找内置数据类型
class GreaterFive {
public:
	bool operator()(int val){
		return val > 5;
	}
};

void test01() {
	vector<int>v;
	for (int i = 0; i < 10; i++) {
		v.push_back(i);
	}
	vector<int>::iterator it = find_if(v.begin(), v.end(), GreaterFive());
	if (it == v.end()) {
		cout << "没有找到" << endl;
	}
	else {
		cout << "找到大于5的数:" <<*it<< endl;
	}
}

//2. 查找自定义数据类型
class Person {
public:
	Person(string name,int age) {
		this->m_Name = name;
		this->m_Age = age;
	 }
	string m_Name;
	int m_Age;
};

class Greater20 {
public:
	bool operator()(Person& p) {
		return p.m_Age > 20;
	}
};

void test02() {
	vector<Person>v;

	//创建数据
	Person p1("aaa", 10);
	Person p2("bbb", 20);
	Person p3("ccc", 30);
	Person p4("ddd", 40);

	v.push_back(p1);
	v.push_back(p2);
	v.push_back(p3);
	v.push_back(p4);

	//找年龄大于20的人
	vector<Person>::iterator it=find_if(v.begin(), v.end(), Greater20());
	if (it == v.end()) {
		cout << "没有找到" << endl;
	}
	else {
		cout << "找到大于20的人:  姓名: " <<it->m_Name<<"  年龄: "<<it->m_Age << endl;
	}
}

int main() {
	//test01();
	test02();
	return 0;
}

5.2.3 adjacent_find (adjacent 相邻)

功能描述:

  • 查找相邻重复元素

函数原型:

  • adjacent_find(iterator beg, iterator end);

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

//beg 开始迭代器

//end结束迭代器

示例:

#include<iostream>
using namespace std;
#include<vector>
#include<algorithm>

//常用查找算法 adjacent_find

void test01() {
	vector<int>v;
	v.push_back(0);
	v.push_back(2);
	v.push_back(0);
	v.push_back(3);
	v.push_back(1);
	v.push_back(0);
	v.push_back(3);
	v.push_back(3);

	vector<int>::iterator pos=adjacent_find(v.begin(), v.end());
	if (pos == v.end()) {
		cout << "未找到相邻重复元素" << endl;
	}
	else {
		cout << "找到相邻重复元素,第一个相邻重复元素位置是: " << *pos << endl;
	}
}

int main() {
	test01();
	return 0;
}

5.2.4 binary_search (二分查找法)

功能描述:

  • 查找指定元素是否存在

函数原型:

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

//查找指定元素, 查到, 返回true, 否则false

//注意: 在无序序列中不可使用

//beg 开始迭代器

//end 结束迭代器

//value 查找的元素

示例:

#include<iostream>
using namespace std;
#include<vector>
#include<algorithm>

//常用查找算法  binary_search

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

	//查找容器中是否有元素 9
	//注意: 容器必须是有序的序列. 如果是无序序列, 结果未知
	bool ret = binary_search(v.begin(), v.end(), 9);

	if (ret == true) {
		cout << "找到了元素" << endl;
	}
	else {
		cout << "未找到" << endl;
	}
	
}

int main() {
	test01();
	return 0;
}

5.2.5 count

功能描述:

  • 统计元素个数

函数原型:

  • count(iterator beg, iterator end,value);

//统计元素出现的次数

//beg 开始迭代器

//end 结束迭代器

//value 统计的元素

示例:

#include<iostream>
using namespace std;
#include<vector>
#include<algorithm>
#include<string>

//常用查找算法 count

//1. 统计内置数据类型

void test01() {
	vector<int>v;
	v.push_back(10);
	v.push_back(40);
	v.push_back(30);
	v.push_back(40);
	v.push_back(20);
	v.push_back(40);
	
	cout<<"40出现的次数为:"<< count(v.begin(), v.end(), 40)<<endl;
}

//1. 统计自定义数据类型

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

	bool operator==(const Person& p) {//防止修改P的年龄, 底层要求必须加const
		if (this->m_Age == p.m_Age) {
			return true;
		}
		else {
			return false;
		}
	}

	string m_Name;
	int m_Age;
};

void test02() {
	vector<Person>v;
	Person p1("刘备", 35);
	Person p2("关羽", 35);
	Person p3("张飞", 35);
	Person p4("赵云", 30);
	Person p5("马超", 34);

	//将人员插入到容器中
	v.push_back(p1);
	v.push_back(p2);
	v.push_back(p3);
	v.push_back(p4);
	v.push_back(p5);

	Person p("诸葛亮", 35);

	int num = count(v.begin(), v.end(), p);	
	cout << "和诸葛亮同岁的人员个数为: " << num << endl;
}
int main() {
	//test01();
	test02();
	return 0;
}

总结: 统计自定义数据类型时, 需要配合重载 operator==

5.2.6 count_if

功能描述:

  • 按条件统计元素个数

函数原型:

  • count(iterator beg, iterator end,_Pred);

//按条件统计元素出现的次数

//beg 开始迭代器

//end 结束迭代器

//_Pred 谓词

示例:

#include<iostream>
using namespace std;
#include<vector>
#include<algorithm>

//常用查找算法_count_if

//1. 统计内置数据类型
class Greater20 {
public:
	bool operator()(int val) {
		return val > 20;
	}
};
void test01() {
	vector<int>v;
	v.push_back(10);
	v.push_back(20);
	v.push_back(40);
	v.push_back(30);
	v.push_back(20);
	v.push_back(40);
	
	int num = count_if(v.begin(), v.end(), Greater20());
	cout << "大于20的元素个数为: " << num << endl;
}

//2. 统计自定义数据类型
class Person {
public:
	Person(string name, int age) {
		this->m_Name = name;
		this->m_Age = age;
	}

	string m_Name;
	int m_Age;
};

class AgeGreater20 {
public:
	bool operator()(const Person& p) {
		return p.m_Age > 20;
	}
};

void test02() {
	vector<Person>v;
	Person p1("刘备", 35);
	Person p2("关羽", 35);
	Person p3("张飞", 35);
	Person p4("赵云", 40);
	Person p5("马超", 20);

	v.push_back(p1);
	v.push_back(p2);
	v.push_back(p3);
	v.push_back(p4);
	v.push_back(p5);

	//统计年龄大于20岁人员个数
	int num = count_if(v.begin(), v.end(), AgeGreater20());
	cout << "大于20岁的人员个数为: " << num << endl;
}
int main() {
	//test01();
	test02();
	return 0;
}

5.3 常用排序算法

算法简介:

  • sort //对容器内元素进行排序

  • random_shuffle //shuffle(洗牌) 洗牌, 指定范围内的元素随机调整次序

  • merge //merge(合并) 容器元素合并, 并存储到另一容器中

  • reverse //反转指定范围的元素

5.3.1 sort

功能描述:

  • 对容器内元素进行排序

函数原型:

  • sort(iterator beg, iterator end, _Pred );

//beg 开始迭代器

//end结束迭代器

//_Pred 谓词(可以填, 可以不填, 不填默认从小到大排序 )

示例:

#include<iostream>
using namespace std;
#include<algorithm>
#include<vector>
#include<functional>//内建函数对象头文件

//常用排序算法 sort
void myPrint(int val) {
	cout << val << " ";
}
void test01() {
	vector<int>v;
	v.push_back(10);
	v.push_back(50);
	v.push_back(30);
	v.push_back(20);
	v.push_back(40);

 	//利用sort进行升序
	sort(v.begin(), v.end());
	for_each(v.begin(), v.end(), myPrint);
	cout << endl;

	//改变为 降序
	sort(v.begin(),v.end(),greater<int>());//greater<>()是内建函数对象
	for_each(v.begin(), v.end(), myPrint);
	cout << endl;
}

int main() {
	test01();
	return 0;
}

总结:sort属于开发中最常用的算法之一, 需熟练掌握

5.3.2 random_shuffle

功能描述:

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

函数原型:

  • random_shuffle(iterator beg, iterator end);

//指定范围内的元素随机调整次序

//beg 开始迭代器

//end结束迭代器

示例:

#include<iostream>
using namespace std;
#include<vector>
#include<algorithm>
#include<ctime>

//常用排序算法 random_shuffle
void myPrint(int val) {
	cout << val << " ";
}
void test01() {
	srand((unsigned int)time(NULL));

	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);
	cout << endl;
}

int main() {
	test01();
	return 0;
}

总结: random_shuffle 洗牌算法比较实用, 使用时记得加随机数种子

5.3.3 merge

功能描述:

  • 两个容器元素合并, 并存储到另一个容器中

函数原型:

  • random_shuffle(iterator beg1, iterator end1, iterator beg2, iterator end2, iterator dest);

//注意:两个容器必须是有序的

//beg1 容器1开始迭代器

//end1 容器1结束迭代器

//beg2 容器2开始迭代器

//end2 容器2结束迭代器

//dest 目标容器开始迭代器

示例:

#include<iostream>
using namespace std;
#include<vector>
#include<algorithm>

//常用排序算法 merge
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 + 1);
	}

	//目标容器
	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;
}

int main() {
	test01();
	return 0;
}

5.3.4 reverse

功能描述:

  • 将容器内的元素进行反转

函数原型:

  • reverse(iterator beg, iterator end);

//反转指定范围的元素

//beg 容器开始迭代器

//end 容器结束迭代器

示例:

#include<iostream>
using namespace std;
#include<vector>
#include<algorithm>

//常用排序算法_ reverse

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

void test01() {
	vector<int>v;
	v.push_back(10);
	v.push_back(20);
	v.push_back(30);
	v.push_back(40);
	v.push_back(50);
	
	cout << "反转前: " << endl;
	for_each(v.begin(), v.end(), myPrint);
	cout << endl;

	cout << "反转后: " << endl;
	reverse(v.begin(), v.end());
	for_each(v.begin(), v.end(), myPrint);
}

int main() {
	test01();
	return 0;
}

5.4 常用拷贝和替换算法

算法简介:

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

  • replace //将容器内指定范围就元素修改为新元素

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

  • swap //互换两个容器内的元素

5.4.1 copy

功能描述:

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

函数原型:

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

//beg 开始迭代器

//end结束迭代器

//iterator dest 目标容器的起始迭代器

示例:

#include<iostream>
using namespace std;
#include<vector>
#include<algorithm>

//常用拷贝和替换算法 copy

class myPrint {
public:
	void operator()(int val) {
		cout << val << " " ;
	}
};
void test01() {
	vector<int>v1;
	for (int i = 0; i < 10; i++) {
		v1.push_back(i);
	}

	vector<int>v2;
	v2.resize(v1.size());
	copy(v1.begin(), v1.end(), v2.begin());

	for_each(v2.begin(), v2.end(), myPrint());
	cout << endl;
}

int main() {
	test01();
	return 0;
}

总结:利用copy算法在拷贝时, 目标容器记得提前开辟空间

5.4.2 replace

功能描述:

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

函数原型:

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

//将区间内 旧元素 替换成 新元素

//beg 开始迭代器

//end结束迭代器

//oldvalue 旧元素

//newvalue 新元素

示例:

#include<iostream>
using namespace std;
#include<vector>
#include<algorithm>

//常用拷贝和替换算法 repalce

void myPrint(int val) {
	cout << val << " ";
}
void test01() {
	vector<int>v;
	v.push_back(20);
	v.push_back(30);
	v.push_back(50);
	v.push_back(30);
	v.push_back(40);
	v.push_back(20);
	v.push_back(10);
	v.push_back(20);

	cout << "替换前: " << endl;
	for_each(v.begin(), v.end(), myPrint);
	cout << endl;

	replace(v.begin(), v.end(), 20, 60);
	cout << "替换后: " << endl;
	for_each(v.begin(), v.end(), myPrint);
	cout << endl;

}

int main() {
	test01();
	return 0;
}

5.4.3 replace_if

功能描述:

  • 将容器内满足条件的元素, 替换成指定元素

函数原型:

  • replace_if(iterator beg, iterator end, _Pred, newvalue );

//按条件替换元素, 满足条件的替换成指定元素

//beg 开始迭代器

//end结束迭代器

//_Pred 谓词

//newvalue 新元素

示例:

#include<iostream>
using namespace std;
#include<vector>
#include<algorithm>

//常用拷贝和替换算法 repalce_if

class myPrint {
public:
	void operator()(int val) {
		cout << val << " ";
	}
};

class Greater30 {
public:
	bool operator()(int val) {
		return val >= 30;
	}
};

void test01() {
	vector<int>v;
	v.push_back(20);
	v.push_back(30);
	v.push_back(50);
	v.push_back(30);
	v.push_back(40);
	v.push_back(20);
	v.push_back(10);
	v.push_back(20);
	
	cout << "替换前: " << endl;
	for_each(v.begin(), v.end(), myPrint());
	cout << endl;

	//将大于等于30的替换为90
	replace_if(v.begin(), v.end(),Greater30() , 90);
	cout << "替换后: " << endl;
	for_each(v.begin(), v.end(), myPrint());
	cout << endl;

}

int main() {
	test01();
	return 0;
}

总结: replace_if 按条件查找, 可以利用仿函数灵活筛选满足的条件

5.4.4 swap

功能描述:

  • 互换两个容器的元素

函数原型:

  • swap(container c1, container c2);

//互换两个容器的元素

//c1 容器1

//c2 容器2

#include<iostream>
using namespace std;
#include<vector>
#include<algorithm>

//常用拷贝和替换算法 swap

class myPrint {
public:
	void operator()(int val) {
		cout << val << " ";
	}
};

class Greater30 {
public:
	bool operator()(int val) {
		return val >= 30;
	}
};

void test01() {
	vector<int>v1;
	vector<int>v2;

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

	cout << "交换前: " << endl;
	for_each(v1.begin(), v1.end(), myPrint());
	cout << endl;
	for_each(v2.begin(), v2.end(), myPrint());
	cout << endl;

	cout << "-------------------------" << endl;
	//将大于等于30的替换为90
	swap(v1,v2);

	cout << "替换后: " << endl;
	for_each(v1.begin(), v1.end(), myPrint());
	cout << endl;
	for_each(v2.begin(), v2.end(), myPrint());
	cout << endl;

}

int main() {
	test01();
	return 0;
}

总结:swap 交换容器时, 注意交换的容器要同种类型

5.5 常用算术生成算法

注意:

  • 算术生成算法属于小型算法, 使用时包含头文件为 #include<numeric>

算法简介:

  • accumulate //计算容器元素累计总和

  • fill //向容器内添加元素

5.5.1 accumulate

功能描述:

  • 计算区间内 容器元素累计总和

函数原型:

  • accumulate(iterator beg, iterator end, value);

//计算容器元素累计总和

//beg 开始迭代器

//end 结束迭代器

//value 起始值

示例:

#include<iostream>
using namespace std;
#include<vector>
#include<numeric>

//常用的算术生成算法
void test01() {
	vector<int>v;
	for (int i = 0; i <= 100; i++) {
		v.push_back(i);
	}

	int total = accumulate(v.begin(), v.end(), 0);
	cout << "total = " << total << endl;
}

int main() {
	test01();
	return 0;
}

5.5.2 fill

功能描述:

  • 向容器中填充指定的元素

函数原型:

  • fill(iterator beg, iterator end, value);

//向容器中填充指定的元素

//beg 开始迭代器

//end 结束迭代器

//value 填充的值

示例:

#include<iostream>
using namespace std;
#include<vector>
#include<numeric>
#include<algorithm>

//常用算术生成算法 fill
void myPrint(int val) {
	cout << val << " ";
}
void test01() {
	vector<int>v;
	v.resize(10);//默认填充10个0

	//后期重新填充
	fill(v.begin(), v.end(), 100);

	for_each(v.begin(), v.end(), myPrint);
	cout << endl;
}

int main() {
	test01();
	return 0;
}

5.6 常用集合算法

算法简介:

  • set_intersection //求两个容器的交集

  • set_union //求两个容器的并集

  • set_difference //求两个容器的差集

5.6.1 set_intersection

功能描述:

  • 求两个容器的交集

函数原型:

  • set_intersection(iterator beg1, iterator end1, iterator begin2, iterator end2,iterator dest);

//求两个集合的交集

//注意: 两个集合必须是有序序列

//beg1 容器1开始迭代器

//end1 容器1结束迭代器

//beg2 容器2开始迭代器

//end2 容器2结束迭代器

//dest 目标容器开始迭代器

示例:

#include<iostream>
using namespace std;
#include<vector>
#include<algorithm>

//常用的集合算法 set_intersection
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;
	//目标容器需要提前开辟空间
	//最特殊情况 大容器包含小容器, 开辟空间, 取小容器的size即可
	vTarget.resize(min(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);
	cout << endl;
}

int main() {
	test01();
	return 0;
}

总结:

  • 求交集的两个集合必须是有序序列

  • 目标容器开辟空间需要从两个容器中取小值

  • set_intersecton返回值即是交集中最后一个元素的位置

5.6.2 set_union

功能描述:

  • 求两个容器的并集

函数原型:

  • set_union(iterator beg1, iterator end1, iterator begin2, iterator end2,iterator dest);

//求两个集合的并集

//注意: 两个集合必须是有序序列

//beg1 容器1开始迭代器

//end1 容器1结束迭代器

//beg2 容器2开始迭代器

//end2 容器2结束迭代器

//dest 目标容器开始迭代器

示例:

#include<iostream>
using namespace std;
#include<vector>
#include<algorithm>

//常用的集合算法 set_union
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_union(v1.begin(), v1.end(), v2.begin(), v2.end(), vTarget.begin());
	
	for_each(vTarget.begin(), itEnd, myPrint);
}

int main() {
	test01();
	return 0;
}

5.6.2 set_difference

功能描述:

  • 求两个容器的差集(两个容器中不属于交集的部分)

函数原型:

  • set_difference(iterator beg1, iterator end1, iterator begin2, iterator end2,iterator dest);

//求两个集合的并集

//注意: 两个集合必须是有序序列

//beg1 容器1开始迭代器

//end1 容器1结束迭代器

//beg2 容器2开始迭代器

//end2 容器2结束迭代器

//dest 目标容器开始迭代器

示例:

#include<iostream>
using namespace std;
#include<vector>
#include<algorithm>


//常用的集合算法 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;
	//最特殊情况 两个容器没有交集, 取两个容器中大的size作为目标容器开辟空间
	vTarget.resize(max(v1.size(), v2.size()));

	cout << "v1和v2的差集为: " ;

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

	cout << "v2和v1的差集为: ";

	itEnd = set_difference(v2.begin(), v2.end(), v1.begin(), v1.end(), vTarget.begin());

	for_each(vTarget.begin(), itEnd, myPrint);
	cout << endl;
}

int main() {
	test01();
	return 0;
}

@#include<iostream> using namespace std;

void test01() {

}

int main() { test01(); return 0; }

  • 17
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值