三、C++提高

一、模板

  • c++的另一种编程思想称为泛型思想,主要利用的就是模板
  • c++提供两种模板机制:函数模板和类模板

1、模板的概念

概念:建立通用的模具,大大提高复用性

特点:

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

2、函数模板

2.1、函数模板语法

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

语法:

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

解释:

  • template:声明创建模板
  • typename:表面后面的符号是一种数据类型,这里也可以写class,效果是一样的
  • T:通用的数据类型,可以是任何数据类型(T这个名字是我们自己命名的,可以改为别的)

注意:给模板语法传值的时候有两种传值方式

  1. 自动类型推导:编译器会自动识别传入的数据类型
  2. 显示指定类型:我们在调用函数前加上传入的数据类型

示例:

#include <iostream>
using namespace std;

// 模板语法,T表示一个虚拟类型,根据传入的值会有不同的表现
template<typename T>
void my_swap(T &a, T &b)
{
	cout << "交换前:a = " << a << "\tb = " << b << endl;
	T temp = a;
	a = b;
	b = temp;
	cout << "交换后:a = " << a << "\tb = " << b << endl;
}

void test()
{
	int a = 10;
	int b = 20;
	// 1、自动类型推导
	my_swap(a, b);

	double c = 1.2;
	double d = 3.1;
	// 2、显示指定类型
	my_swap<double>(c, d);
}

int main()
{
	test();
	
	system("pause");
	return 0;
}

2.2、函数模板注意事项

注意:

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

总结:使用模板时必须确定出通用数据类型,并且能够推导出一致的类型

示例:

#include <iostream>
using namespace std;

// 1、自动类型推导,推导出来的数据类型必须是一致的
template<typename T>
void func1(T a, T b)
{
	cout << a << endl;
	cout << b << endl;
}

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

void test()
{
	// 1、自动类型推导,推导出来的数据类型必须是一致的
	int a = 20;
	int b = 10;
	char c = 'c';
	func1(a, b); // 正确:推导出来的数据类型一致
	func1(a, c);  // 报错:推导出的数据类型不一致

	// 2、模板必须要确定出T的数据类型,才可以使用
	func2();  // 报错:模板不能独立使用,必须要确定T的数据类型
	func2<int>();  // 正确:利用显示指定数据类型的方式,给t一个类型才可以使用模板
}

int main()
{
	test();
	
	system("pause");
	return 0;
}

2.3、函数模板的案例

案例描述:

  • 利用函数模板封装一个排序的函数,可以对不同数据类型数组进行排序
  • 排序规则从大到小,排序算法为选择排序
  • 分别利用char数组和int数组进行测试

示例:

#include <iostream>
using namespace std;

// 交换位置的模板函数
template<typename T>
void my_swap(T& a, T& b)
{
	T temp = a;
	a = b;
	b = temp;
}

// 利用选择排序的方式降序排列数组元素的模板函数
template<typename T>
void my_sort(T arr[], int len)
{
	for (int i = 0; i < len; i++)
	{
		// 假设当前索引为i的元素最大
		int max = i;
		for (int j = i + 1; j < len; j++)
		{
			// 当假设的最大值小于另一个值时
			if (arr[max] < arr[j])
			{
				// 更换假设的最大值的下标为j
				max = j;
			}
		}
		// 循环结束后最大值的下标不等于我们假设的最大值时,交换他们的位置
		if (max != i)
		{
			my_swap(arr[max], arr[i]);
		}
	}
}

// 打印数组元素的模板函数
template<typename T>
void print_arr(T arr[], int len)
{
	for (int i = 0; i < len; i++)
	{
		cout << arr[i] << "  ";
	}
	cout << endl;
}

void test()
{
	char char_arr[] = "wsdcfrtghuik";
	int len = sizeof(char_arr) / sizeof(char);
	my_sort(char_arr, len);
	print_arr(char_arr, len);
}

void test2()
{
	int int_arr[] = { 2,1,4,6,6,8,3,58,34,3,32 };
	int len = sizeof(int_arr) / sizeof(int);
	my_sort(int_arr, len);
	print_arr(int_arr, len);
}

int main()
{
	test();
	test2();

	system("pause");
	return 0;
}

2.4、普通函数与函数模板的区别

区别:

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

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

示例

#include <iostream>
using namespace std;

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

// 函数模板
template<typename T>
int add2(T a, T b)
{
	return a + b;
}

void test()
{
	int a = 10;
	int b = 20;
	char c = 'c'; // 字符c对应的ASCII码为99
	// 1、普通函数调用时可以发生自动类型转换(隐式类型转换)
    // 编译器会将char类型的c隐式转换为int类型
	cout << add1(a, c) << endl;  // 打印结果:109

	// 2、函数模板调用时,如果利用自动类型推导,不会发生隐式类型转换
	cout << add2(a, c) << endl;  // 会报错,无法转换
	
	// 3、如果利用显示指定类型的方式,可以发生隐式类型转换
	cout << add2<int>(a, c) << endl;  // 打印结果:109
}

int main()
{
	test();

	system("pause");
	return 0;
}

2.5、普通函数与函数模板重载时的调用规则

规则:

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

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

示例

#include <iostream>
using namespace std;

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

template<typename T>
void func(T a, T b)
{
	cout << "模板函数调用" << endl;
}

template<typename T>
void func(T a, T b, T c)
{
	cout << "模板函数重载调用" << endl;
}

void test()
{
	int a = 10;
	int b = 20;
	int c = 30;
	// 1、如果普通函数和模板函数都可以实现,优先调用普通函数
	func(a, b);

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

	// 3、模板函数可以发生重载
	func<>(a, b, c);

	// 4、如果模板函数可以产生更好的匹配,优先调用函数模板
	// 虽然普通函数可以隐式转换char类型为int类型,但是模板函数不需要转换
	// 所以模板函数比普通函数产生更好的匹配
	char d1 = 'a';
	char d2 = 'a';
	func(d1, d2);
}

int main()
{
	test();

	system("pause");
	return 0;
}

2.6、具体化模板

模板是有一定局限性的

例如:

该代码如果传入两个数组的话就无法实现

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

再例如:

该代码如果传入自定义类型的数据也无法实现

template<typename T>
void f(T a, T b)
{
    if(a != b)
    {
        cout<< "a和b并不相等" << endl;
    }
}

因此c++提供了模板的重载,可以为某些特定的类型提供具体化的模板

语法:在重载的函数模板前加上template<>

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

示例:

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

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

	string name;
	int age;
};

template<typename T>
void func(T a, T b)
{
	if (a != b)
	{
		cout << "false" << endl;
		return;
	}
	cout << "true" << endl;
}

// 提供具体化的模板,与上面的模板发生重载,当传入的类型是Person时自动调用具体化的Person模板
template<> void func(Person p1, Person p2)
{
	if (p1.age == p2.age && p1.name == p2.name)
	{
		cout << "true" << endl;
		return;
	}
	cout << "false" << endl;
}

void test()
{
	int a = 10;
	int b = 20;
	Person p1(10, "张三");
	Person p2(45, "王五");
	Person p3(10, "张三");

	// 根据传入的类型不同调用不同的模板
	func(a, b);
	func(p1, p2);
}

int main()
{
	test();

	return 0;
}

3、类模板

3.1、类模板语法与使用

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

语法:

template<typename T>
类

解释:

  • template:声明创建模板
  • typename:表面后面的符号是一种数据类型,这里也可以写class,效果是一样的
  • T:通用的数据类型,可以是任何数据类型(T这个名字是我们自己命名的,可以改为别的)

注意:

  1. template后面的尖括号中可以添加多个虚拟数据类型

    如:template<class 虚拟类型1, class 虚拟类型2,...>

  2. 使用类模板只能使用显示指定类型的方法

    如:自定义数据类型<数据类型1, 数据类型2, 数据类型3...> 对象名();

示例

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

// 建立一个通用类,类中的成员数据类型可以不具体指定,用一个虚拟的类型来代表
// 尖括号中可以定义多个虚拟数据类型
template<class type_name, class type_age>
class Person
{
public:
	Person(type_name name, type_age age)
	{
		this->m_name = name;
		this->m_age = age;
	}

	void show()
	{
		cout << "name:" << this->m_name << "  age:" << this->m_age << endl;
	}

	// 成员属性用虚拟类型指定
	type_name m_name;
	type_age m_age;
};

void test()
{
	// 这里的尖括号表示:模板参数列表,告诉摸版传入的数据的类型
	Person<string, int> p("奥特曼", 99);
	p.show();
}

int main()
{
	test();

	return 0;
}

3.2、类模板与函数模板的区别

区别:

  1. 类模板没有自动推类型导的使用方式,必须使用显示指定类型
  2. 类模板在模板参数列表中可以有默认参数

示例

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

// 建立一个通用类,类中的成员数据类型可以不具体指定,用一个虚拟的类型来代表
// 尖括号中可以定义多个虚拟数据类型,可以有默认参数
template<class type_name, class type_age = int>
class Person
{
public:
	Person(type_name name, type_age age)
	{
		this->m_name = name;
		this->m_age = age;
	}

	void show()
	{
		cout << "name:" << this->m_name << "  age:" << this->m_age << endl;
	}

	// 成员属性用虚拟类型指定
	type_name m_name;
	type_age m_age;
};

// 1、类模板没有自动推类型导的使用方式,必须使用显示指定类型
void test1()
{
	Person p("奥特曼", 99);  // 报错,自动推导不出来
}

// 2、类模板在模板参数列表中可以有默认参数
void test2()
{
	// template<class type_name, class type_age = int>
	// 给模板参数形参列表中第二个虚拟类型赋予了一个默认的数据类型
	// 所以传参的时候只用传第一个虚拟类型传值,第二个可传可不传,传了就以手动传的为主
	Person<string>p("孙悟空", 999);
	p.show();
}

int main()
{
	test1();
	test2();

	return 0;
}

3.3、类模板中成员函数的创建时机

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

示例

#include<iostream>
using namespace std;

class Person1
{
public:
	void show_person1()
	{
		cout << "Person1下的函数调用" << endl;
	}
};

class Person2
{
public:
	void show_person2()
	{
		cout << "Person2下的函数调用" << endl;
	}
};

template<class T>
class Myclass
{
public:
	// 调用Person1中的成员函数
	void func1(){obj.show_person1();}

	// 调用Person2中的成员函数
	void func2(){obj.show_person2();}

	T obj;
};

void test()
{
	// 在模板参数中传入一个类对象
	Myclass<Person1> m;
	m.func1();  // 传入的是Person1的对象,所以可以成功调用
	m.func2();  // 报错:传入的是Person1的对象,show_person2不是Person1的成员,所以调用失败

	/*由上可知,模板函数是调用时才创建的,根据传入的数据类型不同返回不同的结果*/
}

int main()
{
	test();

	return 0;
}

3.4、类模板对象做函数参数

作用:类模板实例化出来的对象,作为参数给函数传参

一共有三种传参方式:

  1. 指定传入类型:直接显示对象的数据类型
  2. 参数模板化:将对象中的参数变为模板进行传递
  3. 整个类模板化:将这个对象类型模板化进行传递

注意:使用比较广泛的是第一种,直接把对象的数据类型全部告诉他

示例

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

template<class T>
class Person
{
public:
	T name;
	Person(T name)
	{
		this->name = name;
	}
	void show()
	{
		cout << "姓名:" << this->name <<  endl;
	}
};

// 1、指定传入类型:直接显示对象的数据类型
void func1(Person<string>&p)
{
	p.name = "张三";
	p.show();
}

// 2、参数模板化:将对象中的参数变为模板进行传递
template<class T>
void func2(Person<T>& p)
{
	p.name = "李四";
	p.show();
}

// 3、整个类模板化:将这个对象类型模板化进行传递
template<class T>
void func3(T &p)
{
	p.name = "王五";
	p.show();
}

void test()
{
	Person<string> p("法外狂徒");
	func1(p);
	func2(p);
	func3(p);
}

int main()
{
	test();

	return 0;
}

3.5、类模板与继承

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

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

示例:

当子类继承的父类是一个类模板时要指定父类中T的类型

#include <iostream>
using namespace std;

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

// class Son :public Base   报错:要指定父类中T的类型
class Son :public Base<int>  // 正确
{

};

int main()
{
	

	return 0;
}

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

#include <iostream>
using namespace std;

template<class T>
class Base
{
public:
	T a;

	void base()
	{
		cout << "父类中的a的数据类型为:" << typeid(a).name() << endl;
	}
};

// T1指定的是子类中成员函数a的数据类型,T2指定父类中T的类型
template<class T1, class T2>
class Son :public Base<T2>
{
public:
	T1 a;

	void son()
	{
		cout << "子类中的a的数据类型为:" << typeid(a).name() << endl;
	}
};

void test()
{
	// 根据上述代码可知:int是子类中a的数据类型,char是父类中的a数据类型
	Son<int, char> s;
	s.base();
	s.son();

	// 打印结果
	/*父类中的a的数据类型为:char
	子类中的a的数据类型为:int*/
}

int main()
{
	test();

	return 0;
}

3.6、类模板成员函数的类外实现

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

示例

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

// 类模板
template<class T1, class T2>
class Person
{
public:
	T1 m_age;
	T2 m_name;

	// 构造函数声明
	Person(T1 age, T2 name);
	// 普通成员函数声明
	void show_person();
};

// 构造函数类外实现
// 需要加上模板语法和模板参数列表:Person<T1, T2>
template<class T1, class T2>
Person<T1, T2>::Person(T1 age, T2 name)
{
	this->m_age = age;
	this->m_name = name;
}
// 普通成员函数类外实现
// 需要加上模板语法和模板参数列表:Person<T1, T2>
template<class T1, class T2>
void Person<T1, T2>::show_person()
{
	cout << "age:" << this->m_age << endl;
	cout << "name:" << this->m_name << endl;
}

void test()
{
	Person<int, string> p(12, "炸三");
	p.show_person();
}

int main()
{
	test();

	return 0;
}

3.7、类模板分文件编写

出现的问题:

我们在main文件中只导入了头文件,也就是只有函数的声明,而类模板中成员函数的创建是在调用阶段,所以调用函数时链接不到函数的实现

解决方式:

  • 方式一:直接包含写函数实现的 .cpp 文件
  • 方式二:将 .h 和 .cpp 的内容写到一起,并且将后缀名改为 .hpp

注意:主流的解决方法是第二种,后缀名为 .hpp的文件默认为类模板文件

示例

解决方式一

main文件

#include <iostream>
#include <string>

// 直接导入源文件
#include "01.cpp"
using namespace std;

void test()
{
	Person<int, string> p(12, "炸三");
	p.show_person();
}

int main()
{
	test();

	return 0;
}

头文件:01.h

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

// 类模板
template<class T1, class T2>
class Person
{
public:
	T1 m_age;
	T2 m_name;

	// 构造函数声明
	Person(T1 age, T2 name);
	// 普通成员函数声明
	void show_person();
};

源文件:01.cpp

#include "01.h"

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

template<class T1, class T2>
void Person<T1, T2>::show_person()
{
	cout << "age:" << this->m_age << endl;
	cout << "name:" << this->m_name << endl;
}

解决方式二:

main文件

#include <iostream>
#include <string>

// 把类模板成员写到一起,并将后缀名改为.hpp
#include "01.hpp"
using namespace std;

void test()
{
	Person<int, string> p(12, "炸三");
	p.show_person();
}

int main()
{
	test();

	return 0;
}

类模板文件:01.hpp

#pragma once
#include <iostream>
#include <string>
using namespace std;
// 把声明和实现写在一起
// 类模板
template<class T1, class T2>
class Person
{
public:
	T1 m_age;
	T2 m_name;

	// 构造函数
	Person(T1 age, T2 name) 
	{
		this->m_age = age;
		this->m_name = name;
	}
	// 普通成员函数
	void show_person()
	{
		cout << "age:" << this->m_age << endl;
		cout << "name:" << this->m_name << endl;
	}
};

3.8、类模板与友元

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

模板类内实现全局函数 - 直接在类内友元即可

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

template<class T1, class T2>
class Person
{
	// 类模板内实现全局函数,直接声明友元即可
	template<class T1, class T2>
	friend void print_person(Person<T1, T2>& p)
	{
		cout << "姓名:" << p.m_name << "  年龄:" << p.m_age << endl;
	}

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

private:
	T1 m_age;
	T2 m_name;
};

void test() 
{
	Person<int, string> p(22, "张三");

	print_person(p);
}

int main()
{
	test();

	return 0;
}

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

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

// 3、因为函数中需要Person类型的参数,所以又要在函数声明之前告诉编译器这个类的存在
template<class T1, class T2>
class Person;

// 2、让编译器提前看到这个函数的存在,确保可以运行
template<class T1, class T2>
void print_person(Person<T1, T2>& p)
{
	cout << "姓名:" << p.m_name << "  年龄:" << p.m_age << endl;
}

template<class T1, class T2>
class Person
{
	// 全局函数  类外实现
	// 1、这个全局函数要先让编译器知道,所以在这个类的上面写函数的实现
	template<class T1, class T2>
	friend void print_person(Person<T1, T2>& p);
	// 如果不想再写一遍template模板声明,可以用空模板参数列表 如:
	// friend void print_person<>(Person<T1, T2>& p);
    // 直接用这行代码将上面没有注释两行代码替换掉,效果是一样的

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

private:
	T1 m_age;
	T2 m_name;
};

void test() 
{
	Person<int, string> p(22, "张三");

	print_person(p);
}

int main()
{
	test();

	return 0;
}

3.9、类模板案例

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

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

示例

类模板文件:01.hpp

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

template<class T>
class MyArr
{
public:
	// 构造函数
	MyArr(int capcity)
	{
		// 初始化数组容量和长度
		this->m_capcity = capcity;
		this->m_size = 0;
		// 在堆区开辟数组,大小为用户传进来的容量
		this->p_arrs = new T[this->m_capcity];
	}

	// 拷贝构造函数
	MyArr(const MyArr& arr)
	{
		// 把数组大小和容量拷贝一份
		this->m_capcity = arr.m_capcity;
		this->m_size = arr.m_size;

		// 重新在堆区开辟新的数组,并且将旧数组中的元素拷贝到新数组中,做深拷贝
		this->p_arrs = new T[arr.m_capcity];
		for (int i = 0; i < arr.m_size; i++)
		{
			this->p_arrs[i] = arr.p_arrs[i];
		}
	}

	// operator=运算符重载,防止浅拷贝带来的问题
	void operator=(const MyArr& arr)
	{
		// 判断当前数组是否为空,不为空要释放堆区的数组
		if (this->p_arrs != NULL)
		{
			delete[] this->p_arrs;
			this->p_arrs = NULL;
		}

		// 拷贝旧数组的容量和大小
		this->m_capcity = arr.m_capcity;
		this->m_size = arr.m_size;
		// 深拷贝旧数组中的元素
		this->p_arrs = new T[arr.m_capcity];
		for (int i = 0;i < arr.m_size; i++)
		{
			this->p_arrs[i] = arr.p_arrs[i];
		}
	}

	// operator[]运算符重载,可以使用索引的方式访问数组中的数据
	// 用引用接收方便对数组元素的值进行修改
	T& operator[](int index)
	{
		return this->p_arrs[index];
	}

	// 尾部追加数据
	void add_ele(T ele)
	{
		// 数组容量已经满时不能再追加元素
		if (this->m_capcity == this->m_size)
		{
			cout << "数组已满" << endl;
			return;
		}
		this->p_arrs[this->m_size] = ele;
		// 更新数组大小
		this->m_size++;
	}

	// 尾部删除数据
	void delete_ele()
	{
		// 数组中没有元素时无法删除
		if (this->m_size == 0)
		{
			cout << "数组为空" << endl;
			return;
		}
		// 做逻辑删除,数组大小减一,用户就访问不到最后一个数组的位置了,对于用户来说就是删除了
		this->m_size--;
	}

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

	// 返回数组容量
	int arr_capcity()
	{
		return this->m_capcity;
	}

	// 析构函数
	~MyArr()
	{
		// 释放堆区数组
		if (this->p_arrs != NULL)
		{
			delete[] this->p_arrs;
			this->p_arrs = NULL;
		}
	}

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

	// 数组容量
	int m_capcity;
	
	// 数组大小
	int m_size;
};

main文件

#include <iostream>
#include "01.hpp"
using namespace std;

void print_arr(MyArr<int>& arr)
{
	for (int i = 0; i < arr.arr_size(); i++)
	{
		cout << arr[i] << " " ;
	}
	cout<< endl;
}

// 内置数据类型功能测试
void test1()
{
	MyArr<int> arr1(9);  // 创建数组
	for (int i = 0; i < arr1.arr_capcity(); i++)
	{
		// 尾部插入法测试
		arr1.add_ele(i);
	}

	MyArr<int> arr2(arr1);   // 拷贝构造测试

	MyArr<int> arr3(100);   // operator=测试
	arr3 = arr1;

	print_arr(arr1);
	print_arr(arr2);
	print_arr(arr3);

	// 尾部删除法测试
	arr1.delete_ele();
	print_arr(arr1);

	// 通过下标的方式访问数组中的元素
	cout << arr1[0] << endl;
	// 下标使用引用接收,可以直接进行修改操作
	arr1[0] = 100;
	cout << arr1[0] << endl;
	print_arr(arr1);

	// 可以获取数组中当前元素个数和数组容量
	cout << "容量:" << arr1.arr_capcity() << endl;
	cout << "大小:" << arr1.arr_size() << endl;
}

// 自定义数据类型测试
class Person
{
public:
	int age;
	string name;

	Person(){}

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

void print_arr(MyArr<Person> arr)
{
	for (int i = 0; i < arr.arr_size(); i++)
	{
		cout << "姓名:" << arr[i].name << "  年龄:" << arr[i].age << endl;
	}
}

void test2()
{
	Person p1(12, "张三");
	Person p2(32, "历史");
	Person p3(33, "网课");
	Person p4(45, "但是");

	MyArr<Person> arr1(9);  // 创建数组

	// 尾部追加测试
	arr1.add_ele(p1);
	arr1.add_ele(p2);
	arr1.add_ele(p3);
	arr1.add_ele(p4);


	MyArr<Person> arr2(arr1);   // 拷贝构造测试arr

	MyArr<Person> arr3(100);   // operator=测试
	arr3 = arr1;

	print_arr(arr1);
	print_arr(arr2);
	print_arr(arr3);

	// 尾部删除法测试
	arr1.delete_ele();
	print_arr(arr1);

	// 通过下标的方式访问数组中的元素
	cout << arr1[0].name << endl;
	// 下标使用引用接收,可以直接进行修改操作
	arr1[0] = p2;
	cout << arr1[0].name << endl;
	print_arr(arr1);

	// 可以获取数组中当前元素个数和数组容量
	cout << "容量:" << arr1.arr_capcity() << endl;
	cout << "大小:" << arr1.arr_size() << endl;
}

int main()
{
	test1();
	test2();

	system("pause");
	return 0;
}

二、STL初识

1、STL的诞生

  • 长久以来,软件界一直希望建立一种可重复利用的东西
  • C++的面向对象和泛型编程思想,目的就是复用性的提升
  • 大多数情况下,数据结构和算法都未能有一套标准,导致从事大量重复工作
  • 为了建立数据结构和算法的一套标准,诞生了STL

2、STL基本概念

  • STL(Standard Template Library)标准模板库
  • STL从广义上分为:容器(container)、算法(algorithm)、迭代器(iterator)
  • 容器和算法之间通过迭代器进行无缝衔接
  • STL几乎所有的代码都采用了模板类或模板函数

3、STL六大组件

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

  1. 容器:各种数据结构,如vector、list、deque、set、map等,用来存放数据
  2. 算法:各种常用的算法,如sort、find、copy、for_each等
  3. 迭代器:容器与算法之间的胶合剂
  4. 仿函数:行为类似函数可以作为算法的某种策略
  5. 适配器:用来修饰容器或者仿函数或迭代器的接口
  6. 空间配置器:负责空间的配置与管理

4、STL中的容器、算法、迭代器简介

容器

置物之所也

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

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

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

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

算法

问题之解法也

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

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

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

迭代器

容器与算法之间的粘合剂

提供一种方法,使之能依序寻访某个容器所含的各个元素,而又无需暴露该容器的内部表示方式,每个容器都有自己专属的迭代器

迭代器的使用非常类似于指针,初学阶段可以先理解迭代器为指针

迭代器种类

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

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

5、容器算法迭代器初识

了解STL中容器、算法、迭代器的概念后,我们就可以使用代码来感受STL的魅力

STL中最常用的容器为vector,可以理解为数组

5.1、vector存放内置数据类型

容器:vector

算法:for_each

迭代器:vector<int>::iterator

示例

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

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

int main()
{
	// 创建vector容器对象,并且通过模板参数指定容器中存放的数据类型
	vector<int> v;

	for (int i = 0; i < 9; i++)
	{
		// 向容器中存放数据:push_back
		v.push_back(i);
	}

	// 第一种遍历vector容器中数据的方式
	// v.bengin()返回迭代器,这个迭代器指向容器中第一个数据
	// v.end()返回迭代器,返回容器中最后一个元素的下一个位置
	// vector<int>::iterator  拿到vector<int>这种容器的迭代器类型
    // 可以把vector<int>::iterator当成数据类型,跟int、char类型一样
	vector<int>::iterator p_begin = v.begin();
	vector<int>::iterator p_end = v.end();
	while (p_begin != p_end)
	{
        // 对迭代器解引用后就可以拿到迭代器指向的数据
		cout << *p_begin << endl;
		p_begin++;
	}

	// 第二种遍历方式
	for (vector<int>::iterator it = v.begin(); it != v.end(); it++)
	{
		cout << *it << endl;
	}

	// 第三种遍历方式
	// for_each是STL提供的标准遍历算法,要加头文件:<algorithm>
	// 需要三个参数:起始迭代器,结束迭代器,一个函数名(这个函数是我们自己写的函数,该函数形参类型与迭代器类型保持一致)
	// for_each会帮我们把迭代器解引用后再传给函数名对应的函数中,由我们来控制最后的输出内容
	for_each(v.begin(), v.end(), print_arr);

	/*
	// for_each源码解析
	for_each(_InIt _First, _InIt _Last, _Fn _Func) 
	{ 
		_Adl_verify_range(_First, _Last);
		// 判断传入的起始迭代器是否符合规范,并将形参名改为:_UFirst
		auto _UFirst      = _Get_unwrapped(_First);

		// 判断传入的结束迭代器是否符合规范,并将形参名改为:_ULast
		const auto _ULast = _Get_unwrapped(_Last);

		// 遍历的本质就是for循环,与上面的第二种遍历方式一样
		for (; _UFirst != _ULast; ++_UFirst) 
		{
			// 将迭代器解引用,再传入我们自己写的函数,由我们控制最后的输出
			_Func(*_UFirst);
		}

		return _Func;
	}
	*/

	system("pause");
	return 0;
}

5.2、vector存放自定义数据类型

存放自定义数据类型的本体

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

class Person
{
public:
	string name;
	int age;

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

void show_print(const Person& p)
{
	cout << "name:" << p.name << "  age:" << p.age << endl;
}

int main()
{
	Person p1("111", 11);
	Person p2("222", 22);
	Person p3("333", 33);
	Person p4("444", 44);
	vector<Person> v;

	// 尾插法添加数据
	v.push_back(p1);
	v.push_back(p2);
	v.push_back(p3);
	v.push_back(p4);

	// 第一种遍历方式
	vector<Person>::iterator p_begin = v.begin();
	vector<Person>::iterator p_end = v.end();
	while (p_begin != p_end)
	{
		// p_begin是一个迭代器,用法与指针一样,所以可以解引用了后用  .  的方式取值,也可以直接使用 ->  符号取值
		cout << "name:" << (*p_begin).name << "  age:" << (*p_begin).age << endl;
		cout << "name:" << p_begin->name << "  age:" << p_begin->age << endl;
		p_begin++;
	}

	// 第二种遍历方式
	for (vector<Person>::iterator it = v.begin(); it != v.end(); it++)
	{
		// it是一个迭代器,用法与指针一样,所以可以解引用了后用  .  的方式取值,也可以直接使用 ->  符号取值
		cout << "name:" << (*it).name << "  age:" << (*it).age << endl;
		cout << "name:" << it->name << "  age:" << it->age << endl;
	}


	// 第三种遍历方式
	for_each(v.begin(), v.end(), show_print);

	system("pause");
	return 0;
}

存放自定义数据类型的地址

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

class Person
{
public:
	string name;
	int age;

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

void show_print(const Person* p)
{
	cout << "name:" << p->name << "  age:" << p->age << endl;
}

int main()
{
	Person p1("111", 11);
	Person p2("222", 22);
	Person p3("333", 33);
	Person p4("444", 44);
	vector<Person*> v;

	// 尾插法添加数据
	v.push_back(&p1);
	v.push_back(&p2);
	v.push_back(&p3);
	v.push_back(&p4);

	// 第一种遍历方式
	vector<Person*>::iterator p_begin = v.begin();
	vector<Person*>::iterator p_end = v.end();
	while (p_begin != p_end)
	{
		// p_begin是一个迭代器,用法与指针一样,所以可以解引用了后用  .  的方式取值,也可以直接使用 ->  符号取值
		cout << "name:" << (*(*p_begin)).name << "  age:" << (*(*p_begin)).age << endl;
		cout << "name:" << (*p_begin)->name << "  age:" << (*p_begin)->age << endl;
		p_begin++;
	}

	// 第二种遍历方式
	for (vector<Person*>::iterator it = v.begin(); it != v.end(); it++)
	{
		// it是一个迭代器,用法与指针一样,所以可以解引用了后用  .  的方式取值,也可以直接使用 ->  符号取值
		cout << "name:" << (*(*it)).name << "  age:" << (*(*it)).age << endl;
		cout << "name:" << (*it)->name << "  age:" << (*it)->age << endl;
	}


	// 第三种遍历方式
	// 迭代器类型为<Person*>,函数的形参也要是<Perosn*>类型的,与迭代器尖括号内的数据类型保持一致
	for_each(v.begin(), v.end(), show_print);

	system("pause");
	return 0;
}

5.3、vector容器嵌套容器

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

int main()
{
	// 创建大容器
	vector<vector<int>> v;

	// 创建小容器
	vector<int> v1;
	vector<int> v2;
	vector<int> v3;
	vector<int> v4;

	// 先往小容器中添加元素
	for (int i = 10; i < 19; i++)
	{
		v1.push_back(i + 1);
		v2.push_back(i + 3);
		v3.push_back(i + 5);
		v4.push_back(i + 7);
	}

	// 再把小容器装到大容器中
	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++)
	{
		for (vector<int>::iterator vit = it->begin(); vit != it->end(); vit++)
		{
			cout << *vit << " ";
		}
		cout << endl;
	}

	system("pause");
	return 0;
}

三、迭代器

1、概述

要访问顺序容器和关联容器中的元素,需要通过“迭代器(iterator)”进行。迭代器是一个变量,相当于容器和操纵容器的算法之间的中介。迭代器可以指向容器中的某个元素,通过迭代器就可以读写它指向的元素。从这一点上看,迭代器和指针类似。

2、迭代器类型

  1. 正向迭代器

    容器类名::iterator 迭代器名;
    
  2. 常量正向迭代器

    容器类名::const_iterator 迭代器名;
    
  3. 反向迭代器

    容器类名::reverse_iterator 迭代器名;
    
  4. 常量反向迭代器

    容器类名::const_reverse_iterator 迭代器名;
    

3、迭代器种类及容器支持的迭代器种类

迭代器种类

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

不同容器支持的迭代器

容器 迭代器功能
vector 随机访问
deque 随机访问
list 双向
set / multiset 双向
map / multimap 双向
stack 不支持迭代器
queue 不支持迭代器
priority_queue 不支持迭代器

4、迭代器的简单使用

通过迭代器可以读取他指向的元素,通过非常量迭代器还可以修改其指向的元素

迭代器的自增和自减

  • 对于正向迭代器进行自增操作时,会指向后一个元素;自减会指向前一个元素
  • 对于反向迭代器进行自增操作时,会指向前一个元素;自减会指向后一个元素

示例:

#include <iostream>
#include <vector>
using namespace std;
int main()
{
    vector<int> v;  //v是存放int类型变量的可变长数组,开始时没有元素
    for (int n = 0; n<5; ++n)
    {
        v.push_back(n);  //push_back成员函数在vector容器尾部添加一个元素
    }
    
    vector<int>::iterator i;  //定义正向迭代器
    // 用正向迭代器遍历容器
    for (i = v.begin(); i != v.end(); ++i)
    { 
        cout << *i << " ";  //*i 就是迭代器i指向的元素
        *i *= 2;  //每个元素变为原来的2倍
    }
    cout << endl;
    
    //用反向迭代器遍历容器
    for (vector<int>::reverse_iterator j = v.rbegin(); j != v.rend(); ++j)
    {
        cout << *j << " ";
    }
    return 0;
}

不同容器的迭代器,其功能强弱有所不同。容器的迭代器的功能强弱,决定了该容器是否支持 STL 中的某种算法。例如,排序算法需要通过随机访问迭代器来访问容器中的元素,因此有的容器就不支持排序算法。

常用的迭代器按功能强弱分为输入、输出、正向、双向、随机访问五种,这里只介绍常用的三种。

  1. 正向迭代器。假设 p 是一个正向迭代器,则 p 支持以下操作:++p,p++,*p。此外,两个正向迭代器可以互相赋值,还可以用==!=运算符进行比较。

  2. 双向迭代器。双向迭代器具有正向迭代器的全部功能。除此之外,若 p 是一个双向迭代器,则--pp--都是有定义的。--p使得 p 朝和++p相反的方向移动。

  3. 随机访问迭代器。随机访问迭代器具有双向迭代器的全部功能。若 p 是一个随机访问迭代器,i 是一个整型变量或常量,则 p 还支持以下操作:

    • p+=i:使得 p 往后移动 i 个元素。
    • p-=i:使得 p 往前移动 i 个元素。
    • p+i:返回 p 后面第 i 个元素的迭代器。
    • p-i:返回 p 前面第 i 个元素的迭代器。
    • p[i]:返回 p 后面第 i 个元素的引用。

    此外,两个随机访问迭代器 p1、p2 还可以用 <、>、<=、>= 运算符进行比较。p1<p2的含义是:p1 经过若干次(至少一次)++操作后,就会等于 p2。其他比较方式的含义与此类似。

    对于两个随机访问迭代器 p1、p2,表达式p2-p1也是有定义的,其返回值是 p2 所指向元素和 p1 所指向元素的序号之差(也可以说是 p2 和 p1 之间的元素个数减一)。

5、迭代器的辅助函数

STL 中有用于操作迭代器的三个函数模板,它们是:

  • advance(p, n):使迭代器 p 向前或向后移动 n 个元素。
  • distance(p, q):计算两个迭代器之间的距离,即迭代器 p 经过多少次 + + 操作后和迭代器 q 相等。如果调用时 p 已经指向 q 的后面,则这个函数会陷入死循环。
  • iter_swap(p, q):用于交换两个迭代器 p、q 指向的值。

要使用上述模板,需要包含头文件 algorithm。下面的程序演示了这三个函数模板的 用法。

#include <list>
#include <iostream>
#include <algorithm> //要使用操作迭代器的函数模板,需要包含此文件
using namespace std;
int main()
{
    int a[5] = { 1, 2, 3, 4, 5 };
    list <int> lst(a, a+5);
    list <int>::iterator p = lst.begin();
    advance(p, 2);  //p向后移动两个元素,指向3
    cout << "1)" << *p << endl;  //输出 1)3
    
    advance(p, -1);  //p向前移动一个元素,指向2
    cout << "2)" << *p << endl;  //输出 2)2
    
    list<int>::iterator q = lst.end();
    q--;  //q 指向 5
    cout << "3)" << distance(p, q) << endl;  //输出 3)3
    iter_swap(p, q); //交换 2 和 5
    cout << "4)";
    
    for (p = lst.begin(); p != lst.end(); ++p)
    {
     	cout << *p << " ";   
    }
    return 0;
}

// 打印结果
/*
1) 3
2) 2
3) 3
4) 1 5 3 4 2
*/

四、STL - 常用容器

1、string容器

1.1、string基本概念

本质:string是c++风格的字符串,而string的本质是一个类

**string和char * 的区别: **

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

特点:

  • string类中封装了很多成员方法

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

  • string管理char*所分配的内存空间,不用担心复制越界和取值越界等,类内部完成这些

1.2、string构造函数

函数原型

  • string(); // 创建一个空的字符串,例如:string 变量名
  • string(const char * s); // 使用字符串s初始化
  • string(const string & str); // 使用一个string对象初始化另一个string对象
  • string(int n, char c); // 使用n个字符c初始化

示例

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

void test()
{
	// 1、string(); // 创建一个空的字符串,例如:`string 变量名`
	string s1;  // 默认构造

	// 2、string(const char* s); //  使用字符串s初始化
	const char* str = "hello word";
	string s2(str);  // 有参构造
	cout << s2 << endl;

	// 3、string(const string & str); //  使用一个string对象初始化另一个string对象
	string s3(s2);  // 拷贝构造
	cout << s3 << endl;

	// 4、string(int n, char c); //  使用n个字符c初始化
	string s4(10, 'a');
	cout << s4 << endl;

	// 打印结果
	/*
	hello word
	hello word
	aaaaaaaaaa
	请按任意键继续. . .
	*/
}

int main()
{
	test();

	system("pause");
	return 0;
}

1.3、string赋值操作

函数原型

  • string& operator=(const char* s); // char*类型字符串赋值给当前的字符串
  • string& operator=(const string &s); // 把字符串s赋值给当前的字符串
  • string& operator=(char c); // 把字符赋值给当前的字符串
  • string& assign(const char * s); // 把字符s赋值给当前的字符串
  • 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>
#include <string>
using namespace std;

void test()
{
	// string& operator=(const char* s); `        //  char*类型字符串赋值给当前的字符串
	string s1;
	s1 = "hello word";
	cout << s1 << endl;

	// string& operator=(const string & s); `        //  把字符串s赋值给当前的字符串
	string s2;
	s2 = s1;
	cout << s2 << endl;

	// string& operator=(char c); `      //  把字符赋值给当前的字符串
	string s3;
	s3 = 'a';
	cout << s3 << endl;

	// string& assign(const char* s); `      //  把字符s赋值给当前的字符串
	string s4;
	s4.assign("hello c++");
	cout << s4 << endl;

	// string& assign(const char* s, int n); `      //  把字符串s的前n个字符赋值给当前字符串
	string s5;
	s5.assign("hello c++", 5);
	cout << s5 << endl;

	// string& assign(const string & s); `      //  把字符串s赋值给当前的字符串
	string s6;
	s6.assign(s5);
	cout << s6 << endl;

	// string& assign(int n, char c); `      //  用n个c字符赋值给当前字符串
	string s7;
	s7.assign(5, 'w');
	cout << s7 << endl;

	// 打印结果
	/*
	hello word
	hello word
	a
	hello c++
	hello
	hello
	wwwww
	请按任意键继续. . .
	*/
}

int main()
{
	test();

	system("pause");
	return 0;
}

1.4、string字符串拼接

函数原型

  • string& operator+=(const char* str); // 拼接char*类型字符串
  • string& operator+=(const char c); // 拼接字符类型c
  • string& operator+=(const string & str); // 拼接string对象
  • string& append(const char * str); // 拼接char*类型字符串
  • string& append(const char * str, int n); // 拼接str字符串中的前n个字符
  • string& append(const string & str); // 拼接string对象
  • string& append(const string & str, int n, int len); // 从字符串str的第n个位置开始,拼接len个字符

示例

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

void test()
{
	// string& operator+=(const char* str); `//  拼接char*类型字符串
	string str1 = "床前";
	str1 += "明月光";
	cout << str1 << endl;

	// string& operator+=(const char c); `//  拼接字符类型c
	str1 += ',';
	cout << str1 << endl;

	// string& operator+=(const string & str); `//  拼接string对象
	string str2 = "疑是地上霜。";
	str1 += str2;
	cout << str1 << endl;

	// string& append(const char* str); `//  拼接char*类型字符串
	string str3 = "我";
	str3.append("没k");
	cout << str3 << endl;

	// string& append(const char* str, int n); `//  拼接str字符串中的前n个字符
	str3.append(", ! : .", 1);
	cout << str3 << endl;

	// string& append(const string & str); `//  拼接string对象
	string str4 = "布鲁biu布鲁biu";
	str3.append(str4);
	cout << str3 << endl;

	// string& append(const string & str, int n, int len); `//  从字符串str的第n个位置开始,拼接len个字符
	str3.append(", ! : .", 2, 1);
	cout << str3 << endl;


	// 打印结果
	/*
	床前明月光
	床前明月光,
	床前明月光,疑是地上霜。
	我没k
	我没k,
	我没k,布鲁biu布鲁biu
	我没k,布鲁biu布鲁biu!
	请按任意键继续. . .
	*/
}

int main()
{
	test();

	system("pause");
	return 0;
}

1.5、string查找和替换

函数原型:

  • int find(const string & str, int pos = 0) const; // 从pos开始找字符串str第一次出现的位置
  • int find(const char * s, int pos = 0) const; // 从pos开始找字符串s第一次出现的位置
  • int find(const char * s, int pos, int n) const; // 从pos开始找字符串s的前n个字符第一次出现的位置
  • int find(const char c, int pos = 0) const; // 从pos开始找字符c第一次出现的位置
  • int rfind(const string & str, int pos = npos) const; // 从pos开始找字符串str最后一次出现的位置
  • int rfind(const char * s, int pos = npos) const; // 从pos开始找字符串s出现的最后一次位置
  • int rfind(const char * s, int pos, int n) const; // 从pos开始找字符串s的前n个字符最后一次出现的位置
  • int rfind(const char c, int pos = 0) const; // 从pos开始找字符c最后一次出现的位置
  • string & replace(int pos, int n, const string & str); // 替换从pos开始的n个字符为字符串str
  • string & replace(int pos, int n, const char * s); // 替换从pos开始的n个字符为字符串s

总结:

  • find查找时从左往右,frind从右往左找
  • find/rfind找到字符串后返回查找的第一个字符位置,找不到返回-1
  • replace在替换时,指定从第几个字符开始,替换多少个字符,替换那个字符

示例

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


// 查找
void test1()
{
	string str = "abcdefgde";
	int pos;
	// int find(const string & str, int pos = 0) const; `        //  从pos开始找字符串str第一次出现的位置
	string s = "de";
	pos = str.find(s, 0);
	cout << pos << endl;

	// int find(const char* s, int pos = 0) const; `        //  从pos开始找字符串s第一次出现的位置
	pos = str.find("de", 0);
	cout << pos << endl;

	// int find(const char* s, int pos, int n) const; `        //  从pos开始找字符串s的前n个字符第一次出现的位置
	pos = str.find("bcde", 0, 2);
	cout << pos << endl;

	// int find(const char c, int pos = 0) const; `        //  从pos开始找字符c第一次出现的位置
	pos = str.find('f', 0);
	cout << pos << endl;

	// int rfind(const string & str, int pos = npos) const; `        //  从pos开始往前找字符串str最后一次出现的位置
	pos = str.rfind(s, 10);
	cout << pos << endl;

	// int rfind(const char* s, int pos = npos) const; `        //  从pos开始往前找字符串s出现的最后一次位置
	pos = str.rfind("de", 10);
	cout << pos << endl;

	// int rfind(const char* s, int pos, int n) const; `        //  从pos开始往前找字符串s的前n个字符最后一次出现的位置
	pos = str.rfind("efg", 10, 2);
	cout << pos << endl;

	// int rfind(const char c, int pos = 0) const; `        //  从pos开始找字符c最后一次出现的位置
	pos = str.rfind('d');
	cout << pos << endl;

}


// 替换
void test2()
{
	string str = "abcdefg";
	// string & replace(int pos, int n, const string & str); `        //  替换从pos开始的n个字符为字符串str
	string s = "bbbbb";
	str.replace(1, 3, s);
	cout << str << endl;

	// string & replace(int pos, int n, const char* s); `        //  替换从pos开始的n个字符为字符串s
	str.replace(1, 3, "eeeee");
	cout << str << endl;

}

int main()
{
	test1();
	test2();

	// 打印结果
	/*
	3
	3
	1
	5
	7
	7
	4
	7
	abbbbbefg
	aeeeeebbefg
	请按任意键继续. . .
	*/

	system("pause");
	return 0;
}

1.6、string字符串比较

比较的方式:按字符串的ASCII码逐个进行对比

函数原型:

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

返回值:

  • =:返回 0
  • >:返回 1
  • <:返回 -1

总结:字符串的比较主要用于比较两个字符串是否相等,判断大小的意义并不大

示例

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


// 查找
void test1()
{
	string str1 = "hello word";
	// int & compare(const string & str) const; `        //  与字符串str进行比较
	string str2 = "hello";
	if (str1.compare(str2) == 0)
	{
		cout << "str1 = str2" << endl;
	}
	else if (str1.compare(str2) == 1)
	{
		cout << "str1 > str2" << endl;
	}
	else if (str1.compare(str2) == -1)
	{
		cout << "str1 < str2" << endl;
	}

	// int & compare(const char* s) const; `        //  与字符串s进行比较
	if (str1.compare("hello word") == 0)
	{
		cout << "str1 = hello word" << endl;
	}
	else if (str1.compare("hello word") == 1)
	{
		cout << "str1 > hello word" << endl;
	}
	else if (str1.compare("hello word") == -1)
	{
		cout << "str1 < hello word" << endl;
	}

	// 打印结果
	/*
	str1 > str2
	str1 = hello word
	请按任意键继续. . .
	*/

}

int main()
{
	test1();

	system("pause");
	return 0;
}

1.7、string字符长度

int size(const string & str);

示例

string str = "hello";
int i = str.size();
cout << i << endl;

1.8、string字符存取

函数原型:

  • char& operator[](int n); // 通过 [] 的方式获取单个字符
  • char& at(int n); // 通过at方法获取单个字符

示例:

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


// 查找
void test1()
{
	string str = "hello word";
	// char& operator[](int n); `      //  通过 [] 的方式获取单个字符
	cout << str[0] << endl;
	str[0] = 'a';
	cout << str << endl;

	// char& at(int n); `      //  通过at方法获取单个字符
	cout << str.at(0) << endl;
	str.at(0) = 'q';
	cout << str << endl;

	//
	/*
	h
	aello word
	a
	qello word
	请按任意键继续. . .
	*/
}

int main()
{
	test1();

	system("pause");
	return 0;
}

1.9、string插入和删除

函数原型:

  • string& insert(int pos, const char* s); // 从索引pos开始,插入字符串s
  • string& insert(int pos, const string& str); // 从索引pos开始,插入字符串str
  • string& insert(int pos, int n, char c); // 从索引pos开始,插入n个字符c
  • string& erase(int pos, int n = nops); // 从索引pos开始,删除n个字符

示例

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


// 查找
void test1()
{
	string str = "hello word";
	// string& insert(int pos, const char* s); `        //  从索引pos开始,插入字符串s
	str.insert(0, "aaa");
	cout << str << endl;

	// string& insert(int pos, const string & str); `        //  从索引pos开始,插入字符串str
	string s = "bbb";
	str.insert(0, s);
	cout << str << endl;

	// string& insert(int pos, int n, char c); `        //  从索引pos开始,插入n个字符c
	str.insert(0, 5, 'g');
	cout << str << endl;

	// string& erase(int pos, int n = nops); `        //  从索引pos开始,删除n个字符
	str.erase(0, 11);
	cout << str << endl;

	// 打印结果
	/*
	aaahello word
	bbbaaahello word
	gggggbbbaaahello word
	hello word
	请按任意键继续. . .
	*/
}

int main()
{
	test1();

	system("pause");
	return 0;
}

1.10、string子串

作用:从字符串中获取想要的一段字符串或字符

函数原型:string& substr(int pos, int n = npos) const; // 从所以pos开始,截取n个字符

示例:

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


// 查找
void test1()
{
	// string& substr(int pos, int n = npos) const; `        //  从所以pos开始,截取n个字符
	string str = "hello word";
	string s = str.substr(0, 5);
	cout << s << endl;

	// 打印结果
	/*
	hello
	请按任意键继续. . .
	*/
}

int main()
{
	test1();

	system("pause");
	return 0;
}

2、vector容器

2.1、vector容器基本概念

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

image

vector与普通数组的区别:

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

动态扩展:

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

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

2.2、vector构造函数

函数原型:

  • vector<T> v; // 采用模板实现类实现,默认构造函数
  • vector(v.begin(), v.end()); // 将v[ begin(), end() ]区间中的元素拷贝给本身
  • vector(int n, elem); // 拷贝n个elem给本身
  • vector(const vector &vec); // 拷贝构造函数

示例

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

// 打印整形vector容器中的元素
void print_vector(vector<int>& v)
{
	for (vector<int>::iterator it = v.begin(); it != v.end(); it++)
	{
		cout << *it << " ";
	}
	cout << endl;
}

void test1()
{
	// vector<T > v; `//  采用模板实现类实现,默认构造函数
	vector<int> v1;
	for (int i = 0; i < 10; i++)
	{
		v1.push_back(i);
	}
	print_vector(v1);

	// vector(v.begin(), v.end()); `//  将v[ begin(), end() ]区间中的元素拷贝给本身
	vector<int> v2(v1.begin(), v1.end());
	print_vector(v2);

	// vector(n, elem); `//  拷贝n个elem给本身
	vector<int> v3(9, 7);
	print_vector(v3);

	// vector(const vector & vec); `//  拷贝构造函数
	vector<int> v4(v3);
	print_vector(v4);

	// 打印结果
	/*
	0 1 2 3 4 5 6 7 8 9
	0 1 2 3 4 5 6 7 8 9
	7 7 7 7 7 7 7 7 7
	7 7 7 7 7 7 7 7 7
	请按任意键继续. . .

	*/
}

int main()
{
	test1();

	system("pause");
	return 0;
}

2.3、vector赋值

函数原型:

  • vector& operator=(const vector & vec); // 重载等号赋值操作
  • assign(v.beg, v.end); // 将v[ begin(), end() ]区间中的元素拷贝赋值给本身
  • assign(int n, elem); // 将n个elem拷贝赋值给本身

示例

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

// 打印整形vector容器中的元素
void print_vector(vector<int>& v)
{
	for (vector<int>::iterator it = v.begin(); it != v.end(); it++)
	{
		cout << *it << " ";
	}
	cout << endl;
}

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

	// vector& operator=(const vector & vec); `//  重载等号赋值操作
	vector<int> v1;
	v1 = v;
	print_vector(v1);

	// assign(v.beg, v.end); `//   将v[ begin(), end() ]区间中的元素拷贝赋值给本身
	vector<int> v2;
	v2.assign(v.begin(), v.end());
	print_vector(v2);

	// assign(int n, elem); `//  将n个elem拷贝赋值给本身
	vector<int> v3;
	v3.assign(10, 8);
	print_vector(v3);

	// 打印结果
	/*
	0 1 2 3 4 5 6 7 8 9
	0 1 2 3 4 5 6 7 8 9
	8 8 8 8 8 8 8 8 8 8
	请按任意键继续. . .
	*/
}

int main()
{
	test1();

	system("pause");
	return 0;
}

2.4、vector容量和大小

语法:

  • empty(); // 判断容器是否为空:返回true为空,返回false不为空
  • capacity(); // 返回容器的容量
  • size(); // 返回容器中的元素个数
  • resize(int num); // 重新指定容器的元素个数为num。若num大于容器本身的元素个数,则默认用0填充新的位置;若num小于容器本身的元素个数,则删除超出容器元素个数的元素
  • resize(int num, elem); // 重新指定容器的元素个数为num。若num大于容器本身的元素个数,则默认用elem填充新的位置;若num小于容器本身的元素个数,则删除超出容器元素个数的元素
  • shrink_to_fit(); // 将内存减少到等于当前元素实际所使用的大小。

注意:使用resize时,指定的元素个数小于容器的元素个数,会删除超出容器元素个数的元素,注意他只是删除了容器中的元素,并没有改变容器的长度。例如原本的容器有100,改变容器大小后他还是100,除非超过了指定的元素个数超过了容器长度

示例

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

// 打印整形vector容器中的元素
void print_vector(vector<int>& v)
{
	for (vector<int>::iterator it = v.begin(); it != v.end(); it++)
	{
		cout << *it << " ";
	}
	cout << endl;
}

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

	// empty(); `//  判断容器是否为空:返回true为空,返回false不为空
	if (v.empty())
	{
		cout << "容器为空" << endl;
	}
	else
	{
		cout << "容器不为空" << endl;
	}

	// capacity(); `//  返回容器的容量
	int i = v.capacity();
	cout << "容器的容量为:" << i << endl;

	// size(); `//  返回容器中的元素个数
	i = v.size();
	cout << "容器的元素个数为:" << i << endl;

	// `resize(int num); `//  重新指定容器的长度为num。若num大于容器本身的长度,则默认用0填充新的位置;若num小于容器本身的长度,则删除超出容器长度的元素
	v.resize(15);
	print_vector(v);
	// 若num小于容器本身的长度,则删除超出容器长度的元素
	v.resize(5);
	print_vector(v);

	// resize(int num, elem); `//  重新指定容器的长度为num。若num大于容器本身的长度,则默认用elem填充新的位置;若num小于容器本身的长度,则删除超出容器长度的元素
	v.resize(10, 100);
	print_vector(v);

    // shrink_to_fit(); //  将内存减少到等于当前元素实际所使用的大小
	cout << "使用shrink_to_fit之前的容器总长度:" << v.capacity() << endl;
	cout << "使用shrink_to_fit之前的容器元素个数:" << v.size() << endl;
	v.shrink_to_fit();
	cout << "使用shrink_to_fit之后的容器总长度:" << v.capacity() << endl;
	cout << "使用shrink_to_fit之后的容器元素个数:" << v.size() << endl;


	// 打印结果
	/*
	容器不为空
	容器的容量为:13
	容器的元素个数为:10
	0 1 2 3 4 5 6 7 8 9 0 0 0 0 0
	0 1 2 3 4
	0 1 2 3 4 100 100 100 100 100
	使用shrink_to_fit之前的容器总长度:13
    使用shrink_to_fit之前的容器元素个数:10
    使用shrink_to_fit之后的容器总长度:10
    使用shrink_to_fit之后的容器元素个数:10
	请按任意键继续. . .
	*/
}

int main()
{
	test1();

	system("pause");
	return 0;
}

2.5、vector插入和删除

函数原型:

  • push_back(ele); // 尾部插入元素ele
  • pop_back(); // 删除最后一个元素
  • insert(const_iterator pos, ele); // 向迭代器指向的位置pos插入元素ele
  • insert(coust_iterator pos, int count, ele); // 向迭代器指向的位置pos插入count个ele元素
  • erase(const_iterator pos); // 把迭代器pos指向的元素删除
  • erase(const_iterator start, const_iterator end); // 把迭代器start指向的元素和迭代器end指向的元素的之间的元素删除(包括start,不包括end)
  • clear(); // 清空容器中的所有元素

示例:

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

// 打印整形vector容器中的元素
void print_vector(vector<int>& v)
{
	for (vector<int>::iterator it = v.begin(); it != v.end(); it++)
	{
		cout << *it << " ";
	}
	cout << endl;
}

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

	// push_back(ele); // 尾部插入元素ele
	v.push_back(100);
	print_vector(v);

	// pop_back(); // 删除最后一个元素
	v.pop_back();
	print_vector(v);

	// insert(const_iterator pos, ele); // 向迭代器指向的位置pos插入元素ele
	v.insert(v.begin(), 99);
	print_vector(v);

	// insert(coust_iterator pos, int count, ele); // 向迭代器指向的位置pos插入count个ele元素
	v.insert(v.begin(), 3, 77);
	print_vector(v);

	// erase(const_iterator pos); // 把迭代器pos指向的元素删除
	v.erase(v.begin());
	print_vector(v);

	// erase(const_iterator start, const_iterator end); // 把迭代器start指向的元素和迭代器end指向的元素的之间的元素删除(包括start,不包括end)
	v.erase(v.begin(), v.end());
	print_vector(v);

	// clear(); // 清空容器中的所有元素
	v.push_back(100);
	print_vector(v);
	v.clear();
	print_vector(v);

	// 打印结果
	/*
	0 1 2 3 4 5 6 7 8 9 100
	0 1 2 3 4 5 6 7 8 9
	99 0 1 2 3 4 5 6 7 8 9
	77 77 77 99 0 1 2 3 4 5 6 7 8 9
	77 77 99 0 1 2 3 4 5 6 7 8 9

	100

	请按任意键继续. . .
	*/
}

int main()
{
	test1();

	system("pause");
	return 0;
}

2.6、vector数据存取

函数原型:

  • at(int idx); // 返回索引idx指向的元素
  • operator[](int idx); // 返回索引idx指向的元素
  • front(); // 返回容器中第一个数据元素
  • back(); // 返回容器中最后一个数据元素

注意:上面四个方法的返回值都是本身,可以直接用 = 来赋值或者修改数据

示例

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

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

	// at(int idx); //  返回索引idx指向的元素
	for (int i = 0; i < v.size(); i++)
	{
		cout << v.at(i) << " ";
	}
	cout << endl;
	
	// operator[](int idx); //  返回索引idx指向的元素
	for (int i = 0; i < v.size(); i++)
	{
		cout << v[i] << " ";
	}
	cout << endl;
	
	// front(); //  返回容器中第一个数据元素
	cout << "容器中第一个数据元素为:" << v.front() << endl;
	
	// back(); //  返回容器中最后一个数据元素
	cout << "容器中最后一个数据元素为:" << v.back() << endl;

	// 打印结果
	/*
	0 1 2 3 4 5 6 7 8 9
	0 1 2 3 4 5 6 7 8 9
	容器中第一个数据元素为:0
	容器中最后一个数据元素为:9
	请按任意键继续. . .

	*/
}

int main()
{
	test1();

	system("pause");
	return 0;
}

2.7、vector互换容器

作用:

  • 两个容器内的数据进行交换
  • 收缩内存大小的效果

函数原型:swap(vec); // 将vec容器中的元素与自身的元素进行交换

示例:

两个容器内的数据进行交换

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

// 打印整形vector容器中的元素
void print_vector(vector<int>& v)
{
	for (vector<int>::iterator it = v.begin(); it != v.end(); it++)
	{
		cout << *it << " ";
	}
	cout << endl;
}

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

	vector<int> v2;
	for (int i = 0; i < 10; i++)
	{
		v2.push_back(i+10);
	}
	cout << "交换前:" << endl;
	cout << "v1:";
	print_vector(v1);
	cout << "v2:";
	print_vector(v2);

	// swap(vec); //  将vec容器中的元素与自身的元素进行交换
	v1.swap(v2);
	cout << "交换后:" << endl;
	cout << "v1:";
	print_vector(v1);
	cout << "v2:";
	print_vector(v2);


	// 打印结果
	/*
	交换前:
	v1:0 1 2 3 4 5 6 7 8 9
	v2:10 11 12 13 14 15 16 17 18 19
	交换后:
	v1:10 11 12 13 14 15 16 17 18 19
	v2:0 1 2 3 4 5 6 7 8 9
	请按任意键继续. . .

	*/
	
}

int main()
{
	test1();

	system("pause");
	return 0;
}

收缩内存大小的效果

// vector<int>相当于我们创建了一个匿名对象
// vector<int>(v):匿名对象调用拷贝构造复制了容器v的元素,但是不会复制v容器的长度
// 所以这个匿名对象的容器长度和元素个数是一致的
// 再调用swap方法,交换匿名对象创建的容器和v容器,此时匿名对象创建的容器长度变为13万,v容器的长度变为5
// 该行结束后匿名对象容器被系统回收,也就达成了收缩容器的目的
#include <iostream>
#include <vector>
using namespace std;

void test1()
{
	vector<int> v;
	for (int i = 0; i < 100000; i++)
	{
		v.push_back(i);
	}
	// vector会自动扩充容器长度,方便后面操作数据
	cout << "容器长度为:" << v.capacity() << endl;
	cout << "容器元素个数为:" << v.size() << endl;

	// 当我们使用resize方法缩短容器中的元素个数后,容器的长度不会改变,因此造成了内存浪费
	v.resize(5);
	cout << "容器长度为:" << v.capacity() << endl;
	cout << "容器元素个数为:" << v.size() << endl;

	// vector<int>相当于我们创建了一个匿名对象
	// vector<int>(v):匿名对象调用拷贝构造复制了容器v的元素,但是不会复制v容器的长度
	// 所以这个匿名对象的容器长度和元素个数是一致的
	// 再使用swap方法,交换匿名对象创建的容器和v容器,此时匿名对象创建的容器长度变为13万,v容器的长度变为5
	// 该行结束后匿名对象容器被系统回收,也就达成了收缩容器的目的
	vector<int>(v).swap(v);
	cout << "容器长度为:" << v.capacity() << endl;
	cout << "容器元素个数为:" << v.size() << endl;
}

int main()
{
	test1();
	// 打印结果
	/*
	容器长度为:138255
	容器元素个数为:100000
	容器长度为:138255
	容器元素个数为:5
	容器长度为:5
	容器元素个数为:5
	请按任意键继续. . .
	*/

	system("pause");
	return 0;
}

2.8、vector预留空间

作用:减少在vector在动态扩展容量时的扩展次数

语法:reserve(int len); // 容器预留len个元素长度,预留位置没有初始化,不可访问

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

示例:

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

void test1()
{
	vector<int> v;
	// 统计vector动态扩展的次数
	int num = 0;
	// 定义一个空指针
	int* p = NULL;
	for (int i = 0; i < 100000; i++)
	{
		v.push_back(i);

		// &v[0]表示当前容器的首地址
		// vector扩展容器长度时会创建新的内存空间,并把旧的容器中的数据拷贝过来
		// 所以当容器首地址变化时,说明容器进行了一次扩展
		// 让指针p指向容器首地址,当首地址变化时,让p重新指向首地址,并让num自增,记录扩展的次数
		if (p != &v[0])
		{
			p = &v[0];
			num++;
		}
	}
	// 键入十万个数据vector容器动态扩展了30次,频率太高了,因此我们可以使用reserve方法,提前告诉编译器容器的总长度,只需要扩展一次就可以放下数据
	cout << "没有使用reserve方法时动态扩展的次数:"<< num << endl;

	// reserve(int len); `        //  容器预留len个元素长度,预留位置没有初始化,不可访问
	vector<int> v1;
	v1.reserve(100000);
	int num1 = 0;
	int* p1 = NULL;
	for (int i = 0; i < 100000; i++)
	{
		v1.push_back(i);
		if (p1 != &v1[0])
		{
			p1 = &v1[0];
			num1++;
		}
	}
	cout << "使用reserve方法时动态扩展的次数:" << num1 << endl;

	// 打印结果
	/*
	没有使用reserve方法时动态扩展的次数:30
	使用reserve方法时动态扩展的次数:1
	请按任意键继续. . .
	*/
}

int main()
{
	test1();

	system("pause");
	return 0;
}

2.9、vector不可写操作

我们在用函数打印输出的时候,需要把vector容器作为参数传给函数,要节省内存空间所以使用引用的方式传值,为了防止误操作修改容器中的数据要加上const

此时迭代器的类型也要跟着改变为自读迭代器:vector<int>::const_iterator

示例

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

void print_vector(const vector<int>& v)
{
	// vector<int>::itetator it  这样写会报错,无法转换
	for (vector<int>::const_iterator it = v.begin(); it != v.end(); it++)
	{
		//*it = 100;  // 报错:无法修改
		cout << *it << " ";
	}
	cout << endl;
}

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

int main()
{
	test1();

	system("pause");
	return 0;
}

2.10、vector方法

iterator(迭代器)

名称 描述
begin 返回指向容器第一个元素的迭代器
end 返回指向容器最后一个元素所在位置的后一个位置的迭代器
rbegin 返回指向容器最后一个元素的迭代器
rend 返回指向容器第一个元素所在位置的前一个位置的迭代器
cbegin 与begin()功能一致,不过加了const属性,不可修改元素
cend 与end()功能一致,不过加了const属性,不可修改元素
crbegin 与rbegin()功能一致,不过加了const属性,不可修改元素
crend 与rend()功能一致,不过加了const属性,不可修改元素

capacity(容量)

名称 描述
size 返回容器中元素个数
capacity 返回容器最大可容纳的元素个数
max_size 返回最大可容纳的元素个数。这个值非常大,一般是2^32-1
empty 判断容器是否为空,为空返回true,不为空返回false
resize 改变容器中元素个数,少则删除,多则用值填充
reserve 增加容器的总容量,为vector预留空间,在要插入的数据过大时使用
shrink_to_fit 使容器总长度等于容器中的元素个数

element access(元素访问)

名称 描述
operator[] 使用[] 的方式访问元素,与数组一样
at 与[]的方式一样,通过索引的访问元素
front 返回第一个元素
back 返回最后一个元素
data 返回指向容器第一个元素的指针

modifiers(修改器)

名称 描述
push_back 尾部插入元素
pop_back 删除最后一个元素
insert 插入元素
erase 删除元素
clear 清空容器中的元素,size = 0,容器容量不变
swap 交换两个容器中的元素
assign 用新元素替换原有内容
emplace 插入元素,和insert实现原理不同,速度更快
emplace_back 在容器尾部生成一个元素。和 push_back() 的区别是,该函数直接在容器尾部构造元素,省去了复制移动元素的过程。

3、deque容器

3.1、deque基本概念

作用:

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

  • deque容器支持随机访问迭代器

deque和vector的区别

  • vector对于头部的插入删除效率低,数据量越大,效率越低
  • deque相对而言,对头部的插入删除速度会比vector快
  • 而vector访问元素的速度会比deque快,这与两者内部实现有关

image

deque内部工作原理:

deque内部有个中控器,维护每段缓冲区中的内容,缓冲区中存放真实数据

中控器维护的是每个缓冲区的地址,使得使用deque时像一片连续的内存空间

image

3.2、deque构造函数

函数原型

  • deque<T> deqT; // 默认构造形式
  • deque(iterator::begin, iterator::end); // 将 [begin, end] 区间中的元素拷贝给本身
  • deque(int n, elem); // 将n个elem元素拷贝给本身
  • deque(const deque& deq); // 拷贝构造

总结:deque容器与vector容器的构造方式几乎一致,灵活使用即可

示例:

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

// 打印整形容器中的数据
void print_deque(const deque<int>& v)
{
	for (deque<int>::const_iterator it = v.begin(); it != v.end(); it++)
	{
		cout << *it << " ";
	}
	cout << endl;
}

void test1()
{
	deque<int> d;
	for (int i = 0; i < 10; i++)
	{
		d.push_back(i);
	}
	// deque<T > deqT; //  默认构造形式
	deque<int> d1;
	print_deque(d1);

	// deque(begin, end); //  将 [begin, end] 区间中的元素拷贝给本身
	deque<int> d2(d.begin(), d.end());
	print_deque(d2);

	// deque(int n, elem); //  将n个elem元素拷贝给本身
	deque<int> d3(10, 100);
	print_deque(d3);

	// deque(const deque & deq);//  拷贝构造
	deque<int> d4(d);
	print_deque(d4);

	// 打印结果
	/*
	
	0 1 2 3 4 5 6 7 8 9
	100 100 100 100 100 100 100 100 100 100
	0 1 2 3 4 5 6 7 8 9
	请按任意键继续. . .
	*/

}

int main()
{
	test1();

	system("pause");
	return 0;
}

3.3、deque赋值操作

函数原型:

  • deque& operator=(const deque & deq); // 等号赋值
  • assign(iterator::begin, iterator::end); // 将[begin, end] 区间中的数据拷贝赋值给本身
  • assign(int n, elem); // 将n个elem元素拷贝赋值给本身

总结:deque容器与vector容器的构造方式几乎一致,灵活使用即可

示例:

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

// 打印整形容器中的数据
void print_deque(const deque<int>& v)
{
	for (deque<int>::const_iterator it = v.begin(); it != v.end(); it++)
	{
		cout << *it << " ";
	}
	cout << endl;
}

void test1()
{
	deque<int> d;
	for (int i = 0; i < 10; i++)
	{
		d.push_back(i);
	}
	// deque& operator=(const deque & deq); `        // 等号赋值
	deque<int>d1;
	d1 = d;
	print_deque(d1);

	// assign(begin, end); `        //  将[begin, end] 区间中的数据拷贝赋值给本身
	deque<int>d2;
	d2.assign(d.begin(), d.end());
	print_deque(d2);

	// assign(int n, elem); `        //  将n个elem元素拷贝赋值给本身
	deque<int>d3;
	d3.assign(10, 99);
	print_deque(d3);

	// 打印结果
	/*
	0 1 2 3 4 5 6 7 8 9
	0 1 2 3 4 5 6 7 8 9
	99 99 99 99 99 99 99 99 99 99
	请按任意键继续. . .
	*/

}

int main()
{
	test1();

	system("pause");
	return 0;
}

3.3、deque元素数量

注意:deque容器没有容器容量这个概念,他可以无限的扩展

函数原型:

  • empty(); // 判断容器是否为空:返回true为空,返回false不为空
  • size(); // 返回容器中的元素个数
  • resize(int num); // 重新指定容器的元素个数为num。若num大于容器本身的元素个数,则默认用0填充新的位置;若num小于容器本身的元素个数,则删除超出容器元素个数的元素
  • resize(int num, elem); // 重新指定容器的元素个数为num。若num大于容器本身的元素个数,则默认用elem填充新的位置;若num小于容器本身的元素个数,则删除超出容器元素个数的元素
  • shrink_to_fit(); // 将内存减少到等于当前元素实际所使用的大小。

示例:

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

// 打印整形容器中的数据
void print_deque(const deque<int>& v)
{
	for (deque<int>::const_iterator it = v.begin(); it != v.end(); it++)
	{
		cout << *it << " ";
	}
	cout << endl;
}

void test1()
{
	deque<int> d;
	for (int i = 0; i < 10; i++)
	{
		d.push_back(i);
	}
	//  `empty(); `        //  判断容器是否为空:返回true为空,返回false不为空
	if (d.empty())
	{
		cout << "容器为空" << endl;
	}
	else {
		cout << "容器不为空" << endl;
	}

	//  `size(); `        //  返回容器中的元素个数
	cout << "容器中的元素个数为:" << d.size() << endl;

	//  `resize(int num); `        //  重新指定容器的元素个数为num。若num大于容器本身的元素个数,则默认用0填充新的位置;若num小于容器本身的元素个数,则删除超出容器元素个数的元素
	d.resize(15);
	print_deque(d);
	d.resize(5);
	print_deque(d);

	//  `resize(int num, elem); `        //  重新指定容器的元素个数为num。若num大于容器本身的元素个数,则默认用elem填充新的位置;若num小于容器本身的元素个数,则删除超出容器元素个数的元素
	d.resize(10, 99);
	print_deque(d);
	d.resize(5);
	print_deque(d);

	//  `shrink_to_fit(); `        //   将内存减少到等于当前元素实际所使用的大小。 
	d.shrink_to_fit();


	// 打印结果
	/*
	容器不为空
	容器中的元素个数为:10
	0 1 2 3 4 5 6 7 8 9 0 0 0 0 0
	0 1 2 3 4
	0 1 2 3 4 99 99 99 99 99
	0 1 2 3 4
	请按任意键继续. . .
	*/

}

int main()
{
	test1();

	system("pause");
	return 0;
}

3.4、deque插入删除

函数原型:

两端操作

  • push_back(elem); // 在容器尾部添加元素elem
  • push_front(elem); // 在容器头部添加元素elem
  • pop_back(); // 删除尾部元素
  • pop_front(); // 删除头部元素

指定位置操作(pos是一个迭代器)

  • insert(iterator::pos, elem); // 在pos位置插入一个元素位置, 返回新数据位置
  • insert(iterator::pos, int n, elem) // 在pos位置插入n个elem数据,无返回值
  • insert(iterator::pos, iterator::beg, iterator::end) // 在pos位置插入[beg, end] 区间的数据,无返回值
  • clear(); // 清空容器中所有的元素
  • erase(iterator::beg, iterator::end); // 删除[beg, end] 区间的元素,返回下一个元素的位置
  • erase(iterator::pos); // 删除pos位置的元素,返回下一个元素的位置

示例

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

// 打印整形容器中的数据
void print_deque(const deque<int>& v)
{
	for (deque<int>::const_iterator it = v.begin(); it != v.end(); it++)
	{
		cout << *it << " ";
	}
	cout << endl;
}

void test1()
{
	deque<int> d;
	for (int i = 0; i < 5; i++)
	{
		d.push_back(i);
	}

	// 两端操作
	//  `push_back(elem); `        //  在容器尾部添加元素elem
	d.push_back(100);
	print_deque(d);

	//  `push_front(elem); `        //  在容器头部添加元素elem
	d.push_front(99);
	print_deque(d);

	//  `pop_back(); `        //  删除尾部元素
	d.pop_back();
	print_deque(d);

	//  `pop_front(); `        //  删除头部元素
	d.pop_front();
	print_deque(d);

	


	// 指定位置操作(pos是一个迭代器)
	//  `insert(pos, elem); `        //  在pos位置插入一个元素位置, 返回新数据位置
	d.insert(d.begin(), 11);
	print_deque(d);

	//  `insert(pos, int n, elem)`        //  在pos位置插入n个elem数据,无返回值
	d.insert(d.begin(), 3, 7);
	print_deque(d);

	//  `insert(pos, beg, end)`        //  在pos位置插入[beg, end] 区间的数据,无返回值
	d.insert(d.begin(), d.begin(), d.end());
	print_deque(d);

	//  `clear(); `        //  清空容器中所有的元素
	d.clear();
	print_deque(d);

	//  `erase(beg, end); `        //  删除[beg, end] 区间的元素,返回下一个元素的位置
	for (int i = 0; i < 10; i++)
	{
		d.push_back(i);
	}
	// 迭代器位置向后偏移3个元素的位置
	deque<int>::iterator it = d.begin();
	it += 3;
	d.erase(it, d.end());
	print_deque(d);

	//  `erase(pos); `        //  删除pos位置的元素,返回下一个元素的位置
	d.erase(d.begin());
	print_deque(d);

	// 打印结果
	/*
	0 1 2 3 4 100
	99 0 1 2 3 4 100
	99 0 1 2 3 4
	0 1 2 3 4
	11 0 1 2 3 4
	7 7 7 11 0 1 2 3 4
	7 7 7 11 0 1 2 3 4 7 7 7 11 0 1 2 3 4

	0 1 2
	1 2
	请按任意键继续. . .
	*/

}

int main()
{
	test1();

	system("pause");
	return 0;
}

3.5、deque数据存取

函数原型

  • operator[](int idx); // 通过 [] 的方式访问元素,返回idx索引指向的数据
  • at(int idx); // 返回索引idx指向的数据
  • front(); // 返回容器中第一个数据元素
  • back(); // 返回容器中最后一个数据元素

示例

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

void test1()
{
	deque<int> d;
	for (int i = 0; i < 5; i++)
	{
		d.push_back(i);
	}

	// operator[](int idx); `        //  通过  []  的方式访问元素,返回idx索引指向的数据
	cout << "通过[]的方式返回数据:" << d[2] << endl;

	//  `at(int idx); `        //  返回索引idx指向的数据
	cout << "通过at()方法返回数据:" << d.at(2) << endl;

	//  `front(); `        //  返回容器中第一个数据元素
	cout << "容器中第一个数据元素:" << d.front() << endl;

	//  `back(); `        //  返回容器中最后一个数据元素
	cout << "容器中最后一个数据元素:" << d.back() << endl;

	// 打印结果
	/*
	通过[]的方式返回数据:2
	通过at()方法返回数据:2
	容器中第一个数据元素:0
	容器中最后一个数据元素:4
	请按任意键继续. . .
	*/

}

int main()
{
	test1();

	system("pause");
	return 0;
}

3.6、deque排序操作

利用一个算法sort对容器进行排序

语法:sort(iterator::beg, iterator::end) // 对beg和end区间的元素进行排序

注意:

  • sort算法仅可以对拥有随机访问迭代器的容器进行排序,如:vector、deque
  • 默认升序排序

示例:

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

// 打印整形容器中的数据
void print_deque(const deque<int>& v)
{
	for (deque<int>::const_iterator it = v.begin(); it != v.end(); it++)
	{
		cout << *it << " ";
	}
	cout << endl;
}

void test1()
{
	deque<int> d;
	for (int i = 10; i < 19; i++)
	{
		d.push_front(i - 2);
		d.push_back(i);
	}
	cout << "排序前:" << endl;
	print_deque(d);
	sort(d.begin(), d.end());
	cout << "排序后:" << endl;
	print_deque(d);
	

	// 打印结果
	/*
	排序前:
	16 15 14 13 12 11 10 9 8 10 11 12 13 14 15 16 17 18
	排序后:
	8 9 10 10 11 11 12 12 13 13 14 14 15 15 16 16 17 18
	请按任意键继续. . .
	*/

}

int main()
{
	test1();

	system("pause");
	return 0;
}

3.7、deque所有方法

函数成员 函数功能
begin() 返回指向容器中第一个元素的迭代器。
end() 返回指向容器最后一个元素所在位置后一个位置的迭代器,通常和 begin() 结合使用。
rbegin() 返回指向最后一个元素的迭代器。
rend() 返回指向第一个元素所在位置前一个位置的迭代器。
cbegin() 和 begin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改元素。
cend() 和 end() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改元素。
crbegin() 和 rbegin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改元素。
crend() 和 rend() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改元素。
size() 返回实际元素个数。
max_size() 返回容器所能容纳元素个数的最大值。这通常是一个很大的值,一般是 232-1,我们很少会用到这个函数。
resize() 改变实际元素的个数。
empty() 判断容器中是否有元素,若无元素,则返回 true;反之,返回 false。
shrink _to_fit() 将内存减少到等于当前元素实际所使用的大小。
at() 使用经过边界检查的索引访问元素。
front() 返回第一个元素的引用。
back() 返回最后一个元素的引用。
assign() 用新元素替换原有内容。
push_back() 在序列的尾部添加一个元素。
push_front() 在序列的头部添加一个元素。
pop_back() 移除容器尾部的元素。
pop_front() 移除容器头部的元素。
insert() 在指定的位置插入一个或多个元素。
erase() 移除一个元素或一段元素。
clear() 移出所有的元素,容器大小变为 0。
swap() 交换两个容器的所有元素。
emplace() 在指定的位置直接生成一个元素。
emplace_front() 在容器头部生成一个元素。和 push_front() 的区别是,该函数直接在容器头部构造元素,省去了复制移动元素的过程。
emplace_back() 在容器尾部生成一个元素。和 push_back() 的区别是,该函数直接在容器尾部构造元素,省去了复制移动元素的过程。

4、案例:评委打分

案例描述

五个选手ABCDE,10个评委分别为每一名选手打分,去掉最高分,去掉最低分,取平均值即为选手的成绩

案例实现

实现:

  • 创建五个选手放在vector容器中
  • 遍历vector容器,取出每一个选手,执行for循环开始10次打分,并把打的分放到deque容器中
  • 使用sort算法排序,去掉最大值和最小值
  • deque容器遍历一遍,累加总分
  • 获取平均分

示例

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

// 选手类
class Person
{
public:
	// 最终成绩
	int score;
	// 姓名
	string name;

	// 构造函数,成绩初识为0
	Person(string name, int score = 0)
	{
		this->name = name;
		this->score = score;
	}
};

// 创建选手到vector容器中,person_size是参赛选手的人数
void create_person(vector<Person>& v, int person_size)
{
	// 选手姓名容器
	string names = "ABCDE";
	for (int i = 0; i < person_size; i++)
	{
		string name = "选手:";
		// 拼接选手姓名容器中的第i个字符
		name += names[i];

		// 构造函数创建选手
		Person p(name);
		v.push_back(p);
	}
}

// 展示参赛选手的信息
void show_person(const vector<Person>& v)
{
	for (vector<Person>::const_iterator it = v.begin(); it != v.end(); it++)
	{
		cout << it->name << "  最终成绩为:" << it->score << endl;
	}
}

// 评委打分,set_sizse是评委人数
void set_score(vector<Person>& v, int set_size)
{
	// 开始循环vector容器中的选手
	for (vector<Person>::iterator it = v.begin(); it != v.end(); it++)
	{
		// 创建deque容器,存放十个评委给当前选手的分数
		deque<int> d;
		cout << it->name << "的得分为:" << endl;
		for (int i = 0; i < set_size; i++)
		{
			// 随机数:60~100
			int score = rand() % 41 + 60;

			// 把第i个评委的成绩放入deque容器中
			d.push_back(score);
			cout << score << " ";
		}
		cout << endl;
		
		// 排序deque容器
		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;
		}
		// 把平均分赋值给当前选手
		it->score = sum / d.size();
	}
}

int main()
{
	// 随机数种子
	srand((unsigned int)time(NULL));
	// 参赛选手人数
	int person_size = 5;
	// 评委人数
	int set_size = 10;
	// 创建放参赛选手的容器
	vector<Person> v;

	// 创建参赛选手
	create_person(v, person_size);
	// 评委开始打分
	set_score(v, set_size);
	// 展示最终成绩
	show_person(v);

	system("pause");
	return 0;
}

5、stack容器

5.1、stack基本概念

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

image

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

栈中进入数据称为:入栈(push)

栈中弹出数据称为:出栈(pop)

生活中栈的例子:弹夹装子弹

5.2、stack常用接口

函数原型:

构造函数:

  • stack<T> stk; // 默认构造
  • stack(const stack & stk); // 拷贝构造

赋值操作:

  • stack& operator=(const stack & stk); // 重载等号操作符

数据存取:

  • push(elem); // 向栈顶添加一个元素
  • pop(); // 从栈顶删除一个元素
  • top(); // 返回栈顶元素

大小操作:

  • empty(); // 判断容器是否为空
  • size(); // 返回容器中的元素个数

示例:

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

/*
**构造函数:**

1 `stack<T> stk;`        //  默认构造
2 `stack(const stack & stk);`        //  拷贝构造

**赋值操作:**

3 `stack& operator=(const stack & stk);`        //  重载等号操作符

**数据存取:**

4 `posh(elem);`        //  向栈顶添加一个元素
5 `pop();`        //  从栈顶删除一个元素
6 `top();`        //  返回栈顶元素

**大小操作:**

7 `empty();`        //  判断容器是否为空
8 `size();`        //  返回容器中的元素个数
*/

void test()
{
	// 1  默认构造
	stack<int> s;

	// 4  添加元素
	s.push(10);
	s.push(20);
	s.push(30);

	// 6  返回栈顶的元素;  3  重载等号操作符为栈顶元素赋值
	s.top() = 100;

	// 8  返回容器中元素个数
	cout << "容器中的元素个数为:" << s.size() << endl;

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

	// 7  判断容器是否为空
	while (!s.empty())
	{
		// 6  返回栈顶的元素
		cout << "当前栈顶的元素为" << s.top() << endl;

		// 5  删除栈顶元素
		s.pop();
	}

	// 8  返回容器中元素个数
	cout << "容器中的元素个数为:" << s.size() << endl;

	// 打印结果
	/*
	容器中的元素个数为:3
	当前栈顶的元素为100
	当前栈顶的元素为20
	当前栈顶的元素为10
	容器中的元素个数为:0
	请按任意键继续. . .
	*/
}

int main()
{
	test();

	system("pause");
	return 0;
}

6、queue容器

6.1、queue概念

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

image

  • 队列允许从一端添加数据,从另一端移除数据
  • 队列中只有对头和队尾才可以被外界使用,因此队列不允许有遍历行为
  • 队列中进数据称为:入队push
  • 队列中出数据称为:出队pop

6.2、queue常用接口

函数原型:

构造函数:

  • queue<T> stk; // 默认构造
  • queue(const queue& que); // 拷贝构造

赋值操作:

  • queue& operator=(const queue& que); // 重载等号操作符

数据存取:

  • push(elem); // 往队头添加一个元素
  • pop(); // 从队尾删除一个元素
  • back(); // 返回最后一个元素
  • front(); // 返回第一个元素

大小操作:

  • empty(); // 判断容器是否为空
  • size(); // 返回容器中的元素个数

示例:

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

/*
**构造函数:**

1 `queue<T> stk;`        //  默认构造
2 `queue(const queue& que);`        //  拷贝构造

**赋值操作:**

3 `queue& operator=(const queue& que);`        //  重载等号操作符

**数据存取:**

4 `push(elem);`        //  往队头添加一个元素
5 `pop();`        //  从队尾删除一个元素
6 `back();`        //  返回最后一个元素
7 `front();`      //  返回第一个元素

**大小操作:**

8 `empty();`        //  判断容器是否为空
9 `size();`        //  返回容器中的元素个数
*/

void test()
{
	// 1  默认构造
	queue<int> q;

	// 2  拷贝构造
	queue<int> q1(q);
	for (int i = 0; i < 4; i++)
	{
		cout << "第" << i + 1 << "次循环添加数据" << endl;

		// 4  往队头添加数据
		q.push(i);

		// 7  返回第一个元素
		cout << "此时队头为:" << q.front() << endl;

		// 6  返回最后一个元素
		cout << "此时队尾为:" << q.back() << endl;
	}

	// 3  重载等号操作符进行赋值
	q.front() = 99;
	cout << "修改后的队头为:" << q.front()<< endl;

	// 9  容器中的元素个数
	cout << "队列中的元素个数:" << q.size() << endl;
	int i = 1;

	// 8  判断容器是否为空
	while (!q.empty())
	{
		cout << "准备开始第" << i << "次循环删除数据" << endl;

		// 7  返回第一个元素
		cout << "此时队头为:" << q.front() << endl;

		// 6  返回最后一个元素
		cout << "此时队尾为:" << q.back() << endl;

		// 5  从队尾删除一个元素
		q.pop();
		i++;
	}

	// 9  容器中的元素个数
	cout << "队列中的元素个数:" << q.size() << endl;

	// 打印结果
	/*
	第1次循环添加数据
	此时队头为:0
	此时队尾为:0
	第2次循环添加数据
	此时队头为:0
	此时队尾为:1
	第3次循环添加数据
	此时队头为:0
	此时队尾为:2
	第4次循环添加数据
	此时队头为:0
	此时队尾为:3
	修改后的队头为:99
	队列中的元素个数:4
	准备开始第1次循环删除数据
	此时队头为:99
	此时队尾为:3
	准备开始第2次循环删除数据
	此时队头为:1
	此时队尾为:3
	准备开始第3次循环删除数据
	此时队头为:2
	此时队尾为:3
	准备开始第4次循环删除数据
	此时队头为:3
	此时队尾为:3
	队列中的元素个数:0
	请按任意键继续. . .
	*/
}

int main()
{
	test();

	system("pause");
	return 0;
}

7、list容器

7.1、list基本概念

作用:将数据进行链式存储

链表:

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

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

单向循环链表结构图

可以对任意位置进行快速插入或删除元素

例如下图:想在0x01后面插入一个地址为0x05的结点,我们只需要把地址为0x01的指针域指向的地址改为0x05,在把0x05的指针域指向的地址赋值为0x02即可。删除一个元素同样的道理,修改指针域指向的地址即可

image

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

  • 双向循环链表的指针域中比单向列表多了一个指向上一个结点的地址,因此可以前后查看数据

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

image

list的优点:

  • 采用动态存储分配,不会造成内存浪费和溢出
  • 链表执行插入和删除操作非常方便,修改指针域中的指针即可,不需要移动大量元素
  • 有一个重要的特性:插入和删除操作都不会造成原有的list迭代器失效,这在vector是不成立的

list的缺点:

  • 链表非常灵活,但是空间占用大(比vector多了一个指针域)
  • 遍历时间较长(不是一个连续的内存空间)

总结:STL中list和vector是两个最常用的容器,各有优缺点

7.2、list构造函数

list构造方式与其他容器构造方式基本一致

函数原型:

  • list<T> lst; // 默认构造
  • list(iterator::beg, iterator::end); // 将区间 [beg, end] 之间的元素拷贝给本身
  • list(const list & lst); // 拷贝构造
  • list(int n, elem); // 拷贝n个elem元素给本身

示例:

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

// 打印整形list容器中的数据
void print_list(const list<int>& l)
{
	for (list<int>::const_iterator it = l.begin(); it != l.end(); it++)
	{
		cout << *it << " ";
	}
	cout << endl;
}

void test()
{
	// list<T > lst; `        //  默认构造
	list<int> l1;
	l1.push_back(10);
	l1.push_back(20);
	l1.push_back(30);
	print_list(l1);

	// list(beg, end); `        //  将区间 [beg, end] 之间的元素拷贝给本身
	list<int> l2(l1.begin(), l1.end());
	print_list(l2);

	// list(const list & lst); `        //  拷贝构造
	list<int> l3(l1);
	print_list(l3);

	// list(int n, elem); `        //  拷贝n个elem元素给本身
	list<int> l4(10, 999);
	print_list(l4);


	// 打印结果
	/*
	10 20 30
	10 20 30
	10 20 30
	999 999 999 999 999 999 999 999 999 999
	请按任意键继续. . .
	*/
}

int main()
{
	test();
	
	system("pause");
	return 0;
}

7.3、list赋值和交换

函数原型

  • operator=(const list & lst); // 重载等号操作符
  • assign(iterator::beg, iterator::end); // 将区间 [beg, end] 之间的元素赋值给本身
  • assign(int n, elem); // 将n个elem元素赋值给本身
  • swap(list & lst); // 交换本身和lst的元素

示例:

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

// 打印整形list容器中的数据
void print_list(const list<int>& l)
{
	for (list<int>::const_iterator it = l.begin(); it != l.end(); it++)
	{
		cout << *it << " ";
	}
	cout << endl;
}

void test()
{
	list<int> l;
	l.push_back(10);
	l.push_back(20);
	l.push_back(30);

	// operator=(const list & lst); `        //  重载等号操作符
	list<int> l1;
	l1 = l;
	print_list(l);

	// assign(beg, end); `        //  将区间 [beg, end] 之间的元素赋值给本身
	list<int> l2;
	l2.assign(l.begin(), l.end());
	print_list(l2);

	// assign(int n, elem); `        //  将n个elem元素赋值给本身
	list<int> l3;
	l3.assign(5, 888);
	print_list(l3);

	// swap(list & lst); `        //  交换本身和lst的元素
	cout << "交换前:" << endl;
	cout << "l1:";
	print_list(l1);
	cout << "l3:";
	print_list(l3);
	l1.swap(l3);
	cout << "交换前:" << endl;
	cout << "l1:";
	print_list(l1);
	cout << "l3:";
	print_list(l3);

	// 打印结果
	/*
	10 20 30
	10 20 30
	888 888 888 888 888
	交换前:
	l1:10 20 30
	l3:888 888 888 888 888
	交换前:
	l1:888 888 888 888 888
	l3:10 20 30
	请按任意键继续. . .
	*/
}

int main()
{
	test();
	
	system("pause");
	return 0;
}

7.4、list大小操作

函数原型:

  • empty(); // 判断容器是否为空:返回true为空,返回false不为空
  • size(); // 返回容器中的元素个数
  • resize(int num); // 重新指定容器的元素个数为num。若num大于容器本身的元素个数,则默认用0填充新的位置;若num小于容器本身的元素个数,则删除超出容器元素个数的元素
  • resize(int num, elem); // 重新指定容器的元素个数为num。若num大于容器本身的元素个数,则默认用elem填充新的位置;若num小于容器本身的元素个数,则删除超出容器元素个数的元素

示例:

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

// 打印整形list容器中的数据
void print_list(const list<int>& l)
{
	for (list<int>::const_iterator it = l.begin(); it != l.end(); it++)
	{
		cout << *it << " ";
	}
	cout << endl;
}

void test()
{
	list<int> l;
	l.push_back(10);
	l.push_back(20);
	l.push_back(30);

	// empty(); `        //  判断容器是否为空:返回true为空,返回false不为空
	if (l.empty())
	{
		cout << "list容器为空" << endl;
	}
	else
	{
		cout << "list容器不为空" << endl;
	}

	// size(); `        //  返回容器中的元素个数
	cout << "list容器中的元素个数为:" << l.size() << endl;

	// resize(int num); `        //  重新指定容器的元素个数为num。若num大于容器本身的元素个数,则默认用0填充新的位置;若num小于容器本身的元素个数,则删除超出容器元素个数的元素
	l.resize(5);
	print_list(l);
	l.resize(2);
	print_list(l);

	// resize(int num, elem); `        //  重新指定容器的元素个数为num。若num大于容器本身的元素个数,则默认用elem填充新的位置;若num小于容器本身的元素个数,则删除超出容器元素个数的元素
	l.resize(10, 99);
	print_list(l);
	l.resize(1, 99);
	print_list(l);

	// 打印结果
	/*
	list容器不为空
	list容器中的元素个数为:3
	10 20 30 0 0
	10 20
	10 20 99 99 99 99 99 99 99 99
	10
	请按任意键继续. . .
	*/
}

int main()
{
	test();
	
	system("pause");
	return 0;
}

7.5、插入和删除

函数原型:

  • 两端操作

    • push_back(elem); // 在容器尾部添加元素elem
    • push_front(elem); // 在容器头部添加元素elem
    • pop_back(); // 删除尾部元素
    • pop_front(); // 删除头部元素
  • 指定位置操作(pos是一个迭代器)

    • insert(iterator::pos, elem); // 在pos位置插入一个元素位置, 返回新数据位置
    • insert(iterator::pos, int n, elem) // 在pos位置插入n个elem数据,无返回值
    • insert(iterator::pos, iterator::beg, iterator::end) // 在pos位置插入[beg, end] 区间的数据,无返回值
    • clear(); // 清空容器中所有的元素
    • erase(iterator::beg, iterator::end); // 删除[beg, end] 区间的元素,返回下一个元素的位置
    • erase(iterator::pos); // 删除pos位置的元素,返回下一个元素的位置
    • remove(elem); // 删除容器中所有与elem值匹配的元素

示例:

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

// 打印整形list容器中的数据
void print_list(const list<int>& l)
{
	for (list<int>::const_iterator it = l.begin(); it != l.end(); it++)
	{
		cout << *it << " ";
	}
	cout << endl;
}

void test()
{
	list<int> l;

	// 两端操作
	//  `push_back(elem); `        //  在容器尾部添加元素elem
	l.push_back(10);
	l.push_back(20);
	l.push_back(30);
	print_list(l);

	//  `push_front(elem); `        //  在容器头部添加元素elem
	l.push_front(40);
	print_list(l);

	//  `pop_back(); `        //  删除尾部元素
	l.pop_back();
	print_list(l);

	//  `pop_front(); `        //  删除头部元素
	l.pop_front();
	print_list(l);


	//  指定位置操作(pos是一个迭代器)
	//  `insert(pos, elem); `        //  在pos位置插入一个元素位置, 返回新数据位置
	l.insert(l.begin(), 90);
	print_list(l);

	//  `insert(pos, int n, elem)`        //  在pos位置插入n个elem数据,无返回值
	l.insert(l.begin(), 3, 70);
	print_list(l);

	//  `insert(pos, beg, end)`        //  在pos位置插入[beg, end] 区间的数据,无返回值
	l.insert(l.begin(), l.begin(), l.end());
	print_list(l);

	//  `clear(); `        //  清空容器中所有的元素
	list<int> l1(l);
	l1.clear();
	print_list(l1);

	//  `erase(beg, end); `        //  删除[beg, end] 区间的元素,返回下一个元素的位置
	list<int> l2(l);
	l2.erase(l2.begin(), l2.end());
	print_list(l2);

	//  `erase(pos); `        //  删除pos位置的元素,返回下一个元素的位置
	l.erase(l.begin());
	print_list(l);

	//  `remove(elem); `        //  删除容器中所有与elem值匹配的元素
	l.remove(70);
	print_list(l);


	// 打印结果
	/*
	10 20 30
	40 10 20 30
	40 10 20
	10 20
	90 10 20
	70 70 70 90 10 20
	70 70 70 90 10 20 70 70 70 90 10 20


	70 70 90 10 20 70 70 70 90 10 20
	90 10 20 90 10 20
	请按任意键继续. . .
	*/
}

int main()
{
	test();
	
	system("pause");
	return 0;
}

7.6、list数据存取

list存储数据时不是使用连续的内存空间,迭代器不支持随机访问,所以只能访问头尾

想访问其他位置的元素,只能通过迭代器进行自增或自减一个一个向前访问过来(也就是只能用 ++ 、-- 这两个运算符,使用 + 、- 是不行的)

函数原型:

  • front(); // 返回第一个元素
  • back(); // 返回最后一个元素

示例:

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

void test()
{
	list<int> l;
	l.push_back(10);
	l.push_back(20);
	l.push_back(30);

	// `front(); `        //  返回第一个元素
	cout << "容器中第一个元素:" << l.front() << endl;

	//  `back(); `        //  返回最后一个元素
	cout << "容器中最后一个元素:" << l.back() << endl;

	list<int>::iterator it = l.begin();
	it++;  // 后一个元素的迭代器
	it--;  // 前一个元素的迭代器
	it += 1;  // 报错:没有重载+运算符

	// 打印结果
	/*
	容器中第一个元素:10
	容器中最后一个元素:30
	请按任意键继续. . .
	*/
}

int main()
{
	test();
	
	system("pause");
	return 0;
}

7.7、list反转和排序

函数原型:

  • reverse(); // 反转链表
  • sort(); // 链表排序

注意:list不支持随机访问迭代器,所以不能用全局的 sort 排序算法,但是list内部提供了排序的成员函数,直接使用即可

sort成员函数默认升序排序,想要降序排序,写一个回调函数制定排序规则,再传进去即可

示例:

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

// 打印整形list容器中的数据
void print_list(const list<int>& l)
{
	for (list<int>::const_iterator it = l.begin(); it != l.end(); it++)
	{
		cout << *it << " ";
	}
	cout << endl;
}

bool my_sort(int v1, int v2)
{
	// 想要降序排列,让第一个数大于第二个数
	// 想要升序排列,让第一个数小于第二个数
	return v1 > v2;
}

void test()
{
	list<int> l;
	l.push_back(4);
	l.push_back(2);
	l.push_back(8);
	l.push_back(1);
	l.push_back(7);

	// `reverse(); `        //  反转链表
	cout << "反转前:" << endl;
	print_list(l);
	cout << "反转后:" << endl;
	l.reverse();
	print_list(l);

	//  `sort(); `        //  链表排序
	cout << "排序前:" << endl;
	print_list(l);
	cout << "排序后:" << endl;
	// 升序排序
	l.sort();
	print_list(l);
	// 降序排序,与遍历的算法使用方式类似,需要自己写一个函数,定义规则,然后在把函数名传给函数
	l.sort(my_sort);
	print_list(l);


	// 打印结果
	/*
	反转前:
	4 2 8 1 7
	反转后:
	7 1 8 2 4
	排序前:
	7 1 8 2 4
	排序后:
	1 2 4 7 8
	8 7 4 2 1
	请按任意键继续. . .
	*/
}

int main()
{
	test();
	
	system("pause");
	return 0;
}

7.8、list所有可用的成员函数

成员函数 功能
begin() 返回指向容器中第一个元素的双向迭代器。
end() 返回指向容器中最后一个元素所在位置的下一个位置的双向迭代器。
rbegin() 返回指向最后一个元素的反向双向迭代器。
rend() 返回指向第一个元素所在位置前一个位置的反向双向迭代器。
cbegin() 和 begin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改元素。
cend() 和 end() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改元素。
crbegin() 和 rbegin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改元素。
crend() 和 rend() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改元素。
empty() 判断容器中是否有元素,若无元素,则返回 true;反之,返回 false。
size() 返回当前容器实际包含的元素个数。
max_size() 返回容器所能包含元素个数的最大值。这通常是一个很大的值,一般是 232-1,所以我们很少会用到这个函数。
front() 返回第一个元素的引用。
back() 返回最后一个元素的引用。
assign() 用新元素替换容器中原有内容。
emplace_front() 在容器头部生成一个元素。该函数和 push_front() 的功能相同,但效率更高。
push_front() 在容器头部插入一个元素。
pop_front() 删除容器头部的一个元素。
emplace_back() 在容器尾部直接生成一个元素。该函数和 push_back() 的功能相同,但效率更高。
push_back() 在容器尾部插入一个元素。
pop_back() 删除容器尾部的一个元素。
emplace() 在容器中的指定位置插入元素。该函数和 insert() 功能相同,但效率更高。
insert() 在容器中的指定位置插入元素。
erase() 删除容器中一个或某区域内的元素。
swap() 交换两个容器中的元素,必须保证这两个容器中存储的元素类型是相同的。
resize() 调整容器的大小。
clear() 删除容器存储的所有元素。
splice() 将一个 list 容器中的元素插入到另一个容器的指定位置。
remove(val) 删除容器中所有等于 val 的元素。
remove_if() 删除容器中满足条件的元素。
unique() 删除容器中相邻的重复元素,只保留一个。
merge() 合并两个事先已排好序的 list 容器,并且合并之后的 list 容器依然是有序的。
sort() 通过更改容器中元素的位置,将它们进行排序。
reverse() 反转容器中元素的顺序。

7.9、list排序案例

案例描述:对自定义数据类型排序,按照年龄进行升序排序,如果年龄相同,则根据身高降序排序

总结:

  • 对于自定义数据类型,必须要指定排序规则(写一个回调函数或仿函数),否则编译器不知道按照什么进行排序
  • 高级排序,只是在排序时再进行一次逻辑判断

示例:

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

class Person
{
public:
	string name;
	int age;
	int height;

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

// 打印容器中数据
void print_list(const list<Person>& l)
{
	for (list<Person>::const_iterator it = l.begin(); it != l.end(); it++)
	{
		cout << "姓名:" << it->name << "  年龄:" << it->age << "  身高:" << it->height << endl;
	}
}

// 回调函数,制定排序规则
bool my_sort(Person &p1, Person&p2)
{
	// 当年龄一样时按照身高降序排序
	if (p1.age == p2.age)
	{
		return p1.height > p2.height;
	}
	// 按照年龄的升序排序
	return p1.age < p2.age;
}

void test()
{
	Person p1("张三", 18, 170);
	Person p2("李四", 23, 170);
	Person p3("王五", 34, 180);
	Person p4("刘六", 34, 170);
	Person p5("宋七", 23, 180);
	Person p6("杨八", 23, 175);

	list<Person> l;
	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;
	print_list(l);
	cout << "------------------------" << endl;
	cout << "排序后:" << endl;
	l.sort(my_sort);
	print_list(l);


	// 打印结果
	/*
	排序前:
	姓名:张三  年龄:18  身高:170
	姓名:李四  年龄:23  身高:170
	姓名:王五  年龄:34  身高:180
	姓名:刘六  年龄:34  身高:170
	姓名:宋七  年龄:23  身高:180
	姓名:杨八  年龄:23  身高:175
	------------------------
	排序后:
	姓名:张三  年龄:18  身高:170
	姓名:宋七  年龄:23  身高:180
	姓名:杨八  年龄:23  身高:175
	姓名:李四  年龄:23  身高:170
	姓名:王五  年龄:34  身高:180
	姓名:刘六  年龄:34  身高:170
	请按任意键继续. . .
	*/
}

int main()
{
	test();
	
	system("pause");
	return 0;
}

8、set和multiset容器

8.1、set基本概念

  • 所有元素都会在插入时自动排序
  • set和multiset都属于关联式容器,底层结构使用二叉树进阶的红黑树实现

set和multiset的区别:

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

注意:

  • 使用set和multiset容器只需要导入一个头文件 #include <set>
  • 插入自定义数据类型时,需要使用仿函数制定set的排序规则,否则set不知道按照什么排序,就会报错

8.2、set构造和赋值

构造:

  • set<T> s; // 默认构造
  • set(const set & s); // 拷贝构造

赋值:

  • set& operator=(const set & s); // 重载等号操作符

示例

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

// 打印容器中数据
void print_set(const set<int>& s)
{
	for (set<int>::const_iterator it = s.begin(); it != s.end(); it++)
	{
		cout << *it << " ";
	}
	cout << endl;
}

void test()
{
	/*构造:*/
	// `set<T > s; `        //  默认构造
	set<int> s1;
	s1.insert(1);
	s1.insert(2);
	s1.insert(3);
	print_set(s1);

	//  `set(const set & s); `        // 拷贝构造 
	set<int>s2(s1);
	print_set(s2);


	/* 赋值:*/
	// `set& operator=(const set & s); `        //  重载等号操作符
	set<int> s3;
	s3 = s1;
	print_set(s3);

	// 打印结果
	/*
	1 2 3
	1 2 3
	1 2 3
	请按任意键继续. . .
	*/
}

int main()
{
	test();
	
	system("pause");
	return 0;
}

8.3、set大小和交换

函数原型:

  • size(); // 返回容器中的元素个数
  • empty(); // 判断容器是否为空
  • swap(s); // 交换两个容器中的元素

注意:不支持resize,重新指定容器元素个数

示例

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

// 打印容器中数据
void print_set(const set<int>& s)
{
	for (set<int>::const_iterator it = s.begin(); it != s.end(); it++)
	{
		cout << *it << " ";
	}
	cout << endl;
}

void test()
{
	set<int> s1;
	s1.insert(1);
	s1.insert(2);
	s1.insert(3);

	set<int> s2;
	s2.insert(10);
	s2.insert(20);
	s2.insert(30);
	// `size(); `        //  返回容器中的元素个数
	cout << "s1中的元素个数为:" << s1.size() << endl;

	//  `empty(); `        //  判断容器是否为空
	if (s1.empty())
	{
		cout << "s1为空" << endl;
	}
	else 
	{
		cout << "s1不为空" << endl;
	}

	//  `swap(s); `        //  交换两个容器中的元素
	cout << "交换前:" << endl;
	cout << "s1:";
	print_set(s1);
	cout << "s2:";
	print_set(s2);
	s1.swap(s2);
	cout << "交换后:" << endl;
	cout << "s1:";
	print_set(s1);
	cout << "s2:";
	print_set(s2);

	// 打印结果
	/*
	s1中的元素个数为:3
	s1不为空
	交换前:
	s1:1 2 3
	s2:10 20 30
	交换后:
	s1:10 20 30
	s2:1 2 3
	请按任意键继续. . .
	*/
}

int main()
{
	test();
	
	system("pause");
	return 0;
}

8.4、set插入和删除

函数原型:

  • inser(elem); // 在容器中添加数据elem
  • erase(iterator::pos) // 删除迭代器pos指向的数据
  • erase(elem); // 删除容器中值为elem的元素,返回下一个元素的迭代器
  • erase(iterator::beg, iterator::end); // 删除区间 [beg, end] 之间的数据,返回下一个元素的迭代器
  • clear(); // 清空容器中的所有数据

注意:

  • 添加元素只有一种方式:insert,没有尾插、头插等等

  • 添加元素时,会对容器中的数据自动排序

  • 不能添加重复的元素,添加了重复的元素不会报错,就跟没添加一样,石沉大海

  • 根据迭代器指向的位置删除元素时,是按照容器排序后的位置删,不是按照添加元素时的顺序

示例:

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

// 打印容器中数据
void print_set(const set<int>& s)
{
	for (set<int>::const_iterator it = s.begin(); it != s.end(); it++)
	{
		cout << *it << " ";
	}
	cout << endl;
}

void test()
{
	set<int> s;
	// `inser(elem); `        //  在容器中添加数据elem
	s.insert(2);
	s.insert(4);
	s.insert(1);
	s.insert(6);
	s.insert(5);
	s.insert(3);
	print_set(s);  // 容器会自动对添加进来的数据进行排序
	s.insert(1);  // 添加重复的数据时不会报错,也不会添加成功,跟什么都没发生一样
	print_set(s);

	//  `erase(iterator::pos)`        //  删除迭代器pos指向的数据
	s.erase(s.begin());  // 根据排序后的位置删,不是按照添加元素的顺序删
	print_set(s);

	//  `erase(elem); `        //  删除容器中值为elem的元素
	s.erase(4);
	print_set(s);

	//  `erase(iterator::beg, iterator::end); `        //  删除区间 [beg, end] 之间的数据
	set<int>::iterator it = s.begin();
	it++;  // 要写在外面,不能写在括号内
	s.erase(it, s.end());
	print_set(s);

	//  `clear(); `        //  清空容器中的所有数据
	s.clear();
	print_set(s);

	// 打印结果
	/*
	1 2 3 4 5 6
	1 2 3 4 5 6
	2 3 4 5 6
	2 3 5 6
	2

	请按任意键继续. . .
	*/
}

int main()
{
	test();
	
	system("pause");
	return 0;
}

8.5、set查找和统计

函数原型

  • find(key); // 查找元素key是否存在,若存在,返回该键的迭代器;不存在,返回set.end()
  • count(key); // 统计值为key的元素个数

示例

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

void test()
{
	set<int> s;
	s.insert(2);
	s.insert(4);
	s.insert(1);
	s.insert(6);
	s.insert(5);
	s.insert(3);
	// `find(key); `        //  查找元素key是否存在,若存在,返回该键的迭代器;不存在,返回set.end()
	set<int>::iterator it = s.find(4);
	if (it == s.end())
	{
		cout << "元素不存在" << endl;
	}
	else
	{
		cout << "元素存在:" << *it << endl;
	}

	//  `count(key); `        //  统计值为key的元素个数
	cout << "元素4在容器中的个数:" << s.count(4) << endl;
	cout << "元素40在容器中的个数:" << s.count(40) << endl;

	// 打印结果
	/*
	元素存在:4
	元素4在容器中的个数:1
	元素40在容器中的个数:0
	请按任意键继续. . .
	*/
}

int main()
{
	test();
	
	system("pause");
	return 0;
}

8.6、set和multiset的区别

区别: set不可以插入重复数据,而multiset可以

  • set在添加数据的同时会返回添加结果,表示是否添加成功

  • multiset不会检测数据,因此可以重复添加

multiset容器中insert的返回值

只返回添加数据时的位置,没有检测是否重复的这个过程

iterator insert(value_type&& _Val) 
{
    return iterator(_Emplace(_STD move(_Val)).first, _Get_scary());
}

set容器中insert的返回值

查看insert方法的源码可知

pair<iterator, bool> insert(value_type&& _Val)
{
    const auto _Result = _Emplace(_STD move(_Val));
    return {iterator(_Result.first, _Get_scary()), _Result.second};
}

set添加数据时是有返回值类型的:pair<iterator, bool>,叫对组,对组中第一个返回值是一个迭代器,表示数据添加到那个地方了,第二个返回值是bool类型,表示数据是否添加成功

对组:返回的数据是成对出现的

总结:

  • 需要重复插入数据时用multiset
  • 不允许插入重复的数据时用set
  • 这两个容器除了添加数据时有区别外,其他使用都是一致的

示例:

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

void test()
{
	set<int> s;

	// 使用set变量接收insert的返回值pair
	pair<set<int>::iterator, bool> set = s.insert(10);

	// 想要获取到对组中的第二个返回值,使用second方法即可
	if (set.second)
	{
		cout << "数据添加成功" << endl;
	}
	else
	{
		cout << "数据添加失败" << endl;
	}

	set = s.insert(10);  // 第二次添加失败
	// 想要获取到对组中的第二个返回值,使用second方法即可
	if (set.second)
	{
		cout << "数据添加成功" << endl;
	}
	else
	{
		cout << "数据添加失败" << endl;
	}


	// 打印结果
	/*
	数据添加成功
	数据添加失败
	请按任意键继续. . .
	*/
}

int main()
{
	test();
	
	system("pause");
	return 0;
}

8.7、pair对组创建

对组的作用:成对出现的数据,利用对组可以同时返回两个不同类型的数据

两种创建方式:

  • pair<type, type> p (value1, value2); // type为要返回的数据类型,value为两个数据类型赋初值
  • pair<type, type> p = make_pair(value1, value2);

获取对组中的值:

  • p.first // 获取对组中第一个数据
  • p.second // 获取对组中第二个数据

示例:

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

void test()
{
	// 第一种创建对组的方式
	pair<string, int> p1("张三", 28);
    // first获取对组中第一个数据,second获取对组中第二个数据
	cout << "姓名:" << p1.first << "  年龄:" << p1.second << endl;

	// 第二种创建对组的方式
	pair<string, int> p2 = make_pair("李四", 22);
	cout << "姓名:" << p2.first << "  年龄:" << p2.second << endl;

	// 打印结果
	/*
	姓名:张三  年龄:28
	姓名:李四  年龄:22
	请按任意键继续. . .
	*/
}

int main()
{
	test();
	
	system("pause");
	return 0;
}

8.8、set改变排序规则

set容器默认从小到大排序,我们可以利用仿函数改变set的排序规则

第一步

  • 写一个类,类中重载()操作符,并且使用bool值类型接收
  • 形参列表中需要两个值,值类型与set容器的数据类型一致,例如:容器类型为set<int>,那么函数声明为:bool operator()(int val1, int val2);
  • 如果想要降序排序,就return形参列表中第一个数大于第二个数
  • 如果想要降序排序,就return形参列表中第一个数小于第二个数
  • 函数运行时报c3848就在函数的括号后面加一个const

示例

class MySort
{
public:
	// 返回值是bool类型,括号中两个值的数据类型与set容器的数据类型一致
	bool operator()(int val1, int val2)const  // vs2019版本括号后面记得加const,否则报c3848
	{
		// 想要降序排列就让第一个数大于第二个数,反之亦然
		return val1 > val2;
	}
};

第二步

  • 想要改变排序规则,在使用构造函数创建对象时就要指定排序规则
  • set模板参数列表中可以传两个个值,一个是指定set容器的数据类型,还一个是传仿函数,也就是把上一步写的类名传进来
  • 这样添加数据时他就会根据仿函数中的排序规则来排序

示例

// 要改变排序规则,需要在添加数据之前就指定
// set中可以传两个参数,一个是指定数据类型,一个是写有仿函数的类名
set<int, MySort> s;

一:set容器改变内置数据类型的排序方式

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

class MySort
{
public:
	// 返回值是bool类型,括号中两个值的数据类型与set容器的数据类型一致
	bool operator()(int val1, int val2)const  // vs2019版本括号后面记得加const,否则报03848
	{
		// 想要降序排列就让第一个数大于第二个数,反之亦然
		return val1 > val2;
	}
};

void test()
{
	// 要改变排序规则,需要在添加数据之前就指定
	// set中可以传两个参数,一个是指定数据类型,一个是写有仿函数的类名
	set<int, MySort> s1;
	s1.insert(1);
	s1.insert(2);
	s1.insert(3);
	s1.insert(4);

	// 迭代器的类型也是要加上类名
	for (set<int, MySort>::iterator it = s1.begin(); it != s1.end(); it++)
	{
		cout << *it ;
	}
	cout << endl;
}

int main()
{
	test();
	
	system("pause");
	return 0;
}

二:set容器改变自定义数据类型的排序方式

对于自定义数据类型,set必须指定排序规则才可以添加数据,否则set不知道该怎么排序

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

class Person
{
public:
	int age;
	string name;

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

class MySort
{
public:
	// 返回值是bool类型,括号中两个值的数据类型与set容器的数据类型一致
	bool operator()(Person val1, Person val2)const  // 针对vs2019版 括号后面记得加const,且不能使用引用传值,否则报03848
	{
		// 想要降序排列就让第一个数大于第二个数,反之亦然
		return val1.age > val2.age;
	}
};

void test()
{
	Person p1(12, "张三");
	Person p2(44, "李四");
	Person p3(22, "王五");
	Person p4(43, "六六");
	// 要改变排序规则,需要在添加数据之前就指定
	// set中可以传两个参数,一个是指定数据类型,一个是写有仿函数的类名
	set<Person, MySort> s1;
	s1.insert(p1);
	s1.insert(p2);
	s1.insert(p3);
	s1.insert(p4);

	// 迭代器的类型也是要加上类名
	for (set<Person, MySort>::iterator it = s1.begin(); it != s1.end(); it++)
	{
		cout << "姓名:" << it->name << "  年龄:" << it->age << endl;
	}
	cout << endl;
}

int main()
{
	test();
	
	system("pause");
	return 0;
}

8.9、set常用的成员函数

成员方法 功能
begin() 返回指向容器中第一个(注意,是已排好序的第一个)元素的双向迭代器。如果 set 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。
end() 返回指向容器最后一个元素(注意,是已排好序的最后一个)所在位置后一个位置的双向迭代器,通常和 begin() 结合使用。如果 set 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。
rbegin() 返回指向最后一个(注意,是已排好序的最后一个)元素的反向双向迭代器。如果 set 容器用 const 限定,则该方法返回的是 const 类型的反向双向迭代器。
rend() 返回指向第一个(注意,是已排好序的第一个)元素所在位置前一个位置的反向双向迭代器。如果 set 容器用 const 限定,则该方法返回的是 const 类型的反向双向迭代器。
cbegin() 和 begin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的元素值。
cend() 和 end() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的元素值。
crbegin() 和 rbegin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的元素值。
crend() 和 rend() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的元素值。
find(val) 在 set 容器中查找值为 val 的元素,如果成功找到,则返回指向该元素的双向迭代器;反之,则返回和 end() 方法一样的迭代器。另外,如果 set 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。
lower_bound(val) 返回一个指向当前 set 容器中第一个大于或等于 val 的元素的双向迭代器。如果 set 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。
upper_bound(val) 返回一个指向当前 set 容器中第一个大于 val 的元素的迭代器。如果 set 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。
equal_range(val) 该方法返回一个 pair 对象(包含 2 个双向迭代器),其中 pair.first 和 lower_bound() 方法的返回值等价,pair.second 和 upper_bound() 方法的返回值等价。也就是说,该方法将返回一个范围,该范围中包含的值为 val 的元素(set 容器中各个元素是唯一的,因此该范围最多包含一个元素)。
empty() 若容器为空,则返回 true;否则 false。
size() 返回当前 set 容器中存有元素的个数。
max_size() 返回 set 容器所能容纳元素的最大个数,不同的操作系统,其返回值亦不相同。
insert() 向 set 容器中插入元素。
erase() 删除 set 容器中存储的元素。
swap() 交换 2 个 set 容器中存储的所有元素。这意味着,操作的 2 个 set 容器的类型必须相同。
clear() 清空 set 容器中所有的元素,即令 set 容器的 size() 为 0。
emplace() 在当前 set 容器中的指定位置直接构造新元素。其效果和 insert() 一样,但效率更高。
emplace_hint() 在本质上和 emplace() 在 set 容器中构造新元素的方式是一样的,不同之处在于,使用者必须为该方法提供一个指示新元素生成位置的迭代器,并作为该方法的第一个参数。
count(val) 在当前 set 容器中,查找值为 val 的元素的个数,并返回。注意,由于 set 容器中各元素的值是唯一的,因此该函数的返回值最大为 1。

8.10、multiset常用的成员函数

成员方法 功能
begin() 返回指向容器中第一个(注意,是已排好序的第一个)元素的双向迭代器。如果 multiset 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。
end() 返回指向容器最后一个元素(注意,是已排好序的最后一个)所在位置后一个位置的双向迭代器,通常和 begin() 结合使用。如果 multiset 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。
rbegin() 返回指向最后一个(注意,是已排好序的最后一个)元素的反向双向迭代器。如果 multiset 容器用 const 限定,则该方法返回的是 const 类型的反向双向迭代器。
rend() 返回指向第一个(注意,是已排好序的第一个)元素所在位置前一个位置的反向双向迭代器。如果 multiset 容器用 const 限定,则该方法返回的是 const 类型的反向双向迭代器。
cbegin() 和 begin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的元素值。
cend() 和 end() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的元素值。
crbegin() 和 rbegin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的元素值。
crend() 和 rend() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的元素值。
find(val) 在 multiset 容器中查找值为 val 的元素,如果成功找到,则返回指向该元素的双向迭代器;反之,则返回和 end() 方法一样的迭代器。另外,如果 multiset 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。
lower_bound(val) 返回一个指向当前 multiset 容器中第一个大于或等于 val 的元素的双向迭代器。如果 multiset 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。
upper_bound(val) 返回一个指向当前 multiset 容器中第一个大于 val 的元素的迭代器。如果 multiset 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。
equal_range(val) 该方法返回一个 pair 对象(包含 2 个双向迭代器),其中 pair.first 和 lower_bound() 方法的返回值等价,pair.second 和 upper_bound() 方法的返回值等价。也就是说,该方法将返回一个范围,该范围中包含所有值为 val 的元素。
empty() 若容器为空,则返回 true;否则 false。
size() 返回当前 multiset 容器中存有元素的个数。
max_size() 返回 multiset 容器所能容纳元素的最大个数,不同的操作系统,其返回值亦不相同。
insert() 向 multiset 容器中插入元素。
erase() 删除 multiset 容器中存储的指定元素。
swap() 交换 2 个 multiset 容器中存储的所有元素。这意味着,操作的 2 个 multiset 容器的类型必须相同。
clear() 清空 multiset 容器中所有的元素,即令 multiset 容器的 size() 为 0。
emplace() 在当前 multiset 容器中的指定位置直接构造新元素。其效果和 insert() 一样,但效率更高。
emplace_hint() 本质上和 emplace() 在 multiset 容器中构造新元素的方式是一样的,不同之处在于,使用者必须为该方法提供一个指示新元素生成位置的迭代器,并作为该方法的第一个参数。
count(val) 在当前 multiset 容器中,查找值为 val 的元素的个数,并返回。

9、map和multimap容器

9.1、map基本概念

本质:

  • map/multimap属于关联式容器,底层结构使用二叉树进阶的红黑树实现

简介:

  • map/multimap中所有元素都是pair(键值对、对组)
  • pair中第一个元素为key(键),起到索引作用,第二个元素为value(值)
  • 所有元素都会根据元素的键自动排列

优点:

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

map/multimap的区别:

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

9.2、map构造和赋值

构造:

  • map<T> m; // 默认构造
  • map(const map& m); // 拷贝构造

赋值:

  • map& operator=(const map& m); // 重载等号操作符

示例:

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

// 打印map容器中的数据
void print_map(const map<int, int>& m)
{
	for (map<int, int>::const_iterator it = m.begin(); it != m.end(); it++)
	{
		cout << "key = " << (*it).first << "  value = " << it->second << endl;
	}
}

void test()
{
	/*构造:*/
	// `map<T > m; `        //  默认构造
	map<int, int> m;
	m.insert(pair<int, int>(1, 34));
	m.insert(pair<int, int>(4, 23));
	m.insert(pair<int, int>(2, 325));
	m.insert(pair<int, int>(3, 34));
	cout << "m:" << endl;
	print_map(m);

	//  `map(const map & m); `        // 拷贝构造 
	map<int, int> m1(m);
	cout << "m1:" << endl;
	print_map(m1);

	/* 赋值:*/
	// `map& operator=(const map & m); `        //  重载等号操作符
	map<int, int> m2;
	m2 = m;
	cout << "m2:" << endl;
	print_map(m2);

	// 打印结果
	/*
	m:
	key = 1  value = 34
	key = 2  value = 325
	key = 3  value = 34
	key = 4  value = 23
	m1:
	key = 1  value = 34
	key = 2  value = 325
	key = 3  value = 34
	key = 4  value = 23
	m2:
	key = 1  value = 34
	key = 2  value = 325
	key = 3  value = 34
	key = 4  value = 23
	请按任意键继续. . .
	*/
}

int main()
{
	test();

	system("pause");
	return 0;
}

9.3、map大小和交换

函数原型:

  • size(); // 返回容器中的元素个数
  • empty(); // 判断容器是否为空
  • swap(m); // 交换两个容器中的元素

示例

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

// 打印容器中的数据
void print_map(const map<int, int>& m)
{
	for (map<int, int>::const_iterator it = m.begin(); it != m.end(); it++)
	{
		cout << "key = " << it->first << "  value = " << it->second << endl;
	}
	cout << endl;
}

void test()
{
	map<int, int>m1;
	m1.insert(pair<int, int>(1, 23));
	m1.insert(pair<int, int>(2, 23));
	m1.insert(pair<int, int>(3, 23));
	m1.insert(pair<int, int>(4, 23));
	// `size(); `        //  返回容器中的元素个数
	cout << "m1的元素个数为:" << m1.size() << endl;

	//  `empty(); `        //  判断容器是否为空
	if (m1.empty())
	{
		cout << "容器为空" << endl;
	}
	else
	{
		cout << "容器不为空" << endl;
	}

	//  `swap(m); `        //  交换两个容器中的元素
	map<int, int>m2;
	m2.insert(pair<int, int>(5, 55));
	m2.insert(pair<int, int>(6, 44));
	m2.insert(pair<int, int>(7, 44));
	m2.insert(pair<int, int>(8, 78));
	cout << "交换前:" << endl;
	cout << "m1:" << endl;
	print_map(m1);
	cout << "m2:" << endl;
	print_map(m2);
	m1.swap(m2);
	cout << "交换后:" << endl;
	cout << "m1:" << endl;
	print_map(m1);
	cout << "m2:" << endl;
	print_map(m2);

	// 打印结果
	/*
	m1的元素个数为:4
	容器不为空
	交换前:
	m1:
	key = 1  value = 23
	key = 2  value = 23
	key = 3  value = 23
	key = 4  value = 23

	m2:
	key = 5  value = 55
	key = 6  value = 44
	key = 7  value = 44
	key = 8  value = 78

	交换后:
	m1:
	key = 5  value = 55
	key = 6  value = 44
	key = 7  value = 44
	key = 8  value = 78

	m2:
	key = 1  value = 23
	key = 2  value = 23
	key = 3  value = 23
	key = 4  value = 23

	请按任意键继续. . .
	*/

}

int main()
{
	test();

	system("pause");
	return 0;
}

9.4、map插入和删除

函数原型

  • inser(pair); // 在容器中添加键值对pair
  • erase(iterator::pos) // 删除迭代器pos指向的数据
  • erase(key); // 删除容器中键为key的元素,返回下一个元素的迭代器
  • erase(iterator::beg, iterator::end); // 删除区间 [beg, end] 之间的数据,返回下一个元素的迭代器
  • clear(); // 清空容器中的所有数据

insert方法三种可接收的形参和使用 [] 的方式添加数据

  1. m.inser( map<type1, type2>(value1, value2) );
  2. m.inser( make_pair(value1, value2) );
  3. m.inser( map<type1, type2>::value_type(value1, value2) );
  4. m[key] = value; // 不建议使用这个方式添加数据,因为在添加数据时,如果中括号里面的key在容器中已经存在,那么新的value值会覆盖掉旧的value值,导致修改数据。这个方式是用来查看key对应的value值的(multimap没有重载 [] 操作符,无法使用)

注意:

  • 添加元素只有一种方式:insert,没有尾插、头插等等

  • 添加元素时,会根据key值自动排序

  • 不能添加重复的key,重复添加的话新数据会覆盖旧数据,跟上面使用中括号的方式添加数据时可能会修改数据是一个道理

  • 根据迭代器指向的位置删除元素时,是按照容器排序后的位置删,不是按照添加元素时的顺序

示例:

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

// 打印容器中的数据
void print_map(const map<int, int>& m)
{
	for (map<int, int>::const_iterator it = m.begin(); it != m.end(); it++)
	{
		cout << "key = " << it->first << "  value = " << it->second << endl;
	}
	cout << "打印完毕"<< endl;
}

void test()
{
	map<int, int>m;
	// `inser(pair); `        //  在容器中添加键值对pair
	// 第一种添加数据的方式:
	m.insert(pair<int, int>(1, 23));
	// 第二种添加数据的方式:
	m.insert(make_pair(2, 33));
	// 第三种添加数据的方式:
	m.insert(map<int, int>::value_type(3, 31));
	// 第四种添加数据的方式:
	m[4] = 43;  // 不建议使用该方法添加数据,可能会误操作修改数据。该方法主要用来查看key对应的value值
	print_map(m);

	//  `erase(iterator::pos)`        //  删除迭代器pos指向的数据
	m.erase(m.begin());
	print_map(m);

	//  `erase(key); `        //  删除容器中键为key的元素,返回下一个元素的迭代器
	m.erase(2);
	print_map(m);

	//  `erase(iterator::beg, iterator::end); `        //  删除区间 [beg, end] 之间的数据,返回下一个元素的迭代器
	m.erase(m.begin(), m.end());
	print_map(m);

	//  `clear(); `        //  清空容器中的所有数据
	m.insert(pair<int, int>(1, 23));
	m.insert(pair<int, int>(2, 23));
	m.insert(pair<int, int>(3, 23));
	m.clear();
	print_map(m);



	// 打印结果
	/*
	key = 1  value = 23
	key = 2  value = 33
	key = 3  value = 31
	key = 4  value = 43
	打印完毕
	key = 2  value = 33
	key = 3  value = 31
	key = 4  value = 43
	打印完毕
	key = 3  value = 31
	key = 4  value = 43
	打印完毕
	打印完毕
	打印完毕
	请按任意键继续. . .
	*/

}

int main()
{
	test();

	system("pause");
	return 0;
}

9.5、map查找和统计

函数原型

  • find(key); // 查找元素key是否存在,若存在,返回该键的迭代器;不存在,返回set.end()
  • count(key); // 统计值为key的元素个数
  • operator[](key); // 通过中括号的方式查看括号中key对应的value(multimap没有重载 [] 操作符,无法使用)(找不到对应的key时会在容器中创建一个出来,value值初始化为0)

示例:

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

// 打印容器中的数据
void print_map(const map<int, int>& m)
{
	for (map<int, int>::const_iterator it = m.begin(); it != m.end(); it++)
	{
		cout << "key = " << it->first << "  value = " << it->second << endl;
	}
	cout << "打印完毕"<< endl;
}

void test()
{
	map<int, int>m;
	for (int i = 0; i < 5;i++)
	{
		m.insert(make_pair(i, i + 10));
	}
	// `find(key); `        //  查找元素key是否存在,若存在,返回该键的迭代器;不存在,返回set.end()

	if (m.find(2) != m.end()) 
	{
		cout << "该key值存在" << endl;
	}
	else
	{
		cout << "该key值不存在" << endl;
	}

	//  `count(key); `        //  统计值为key的元素个数
	cout << "key = 2的元素有" << m.count(2) << "个" << endl;
	cout << "key = 6的元素有" << m.count(6) << "个" << endl;

	//  `operator[](key); `        //  通过中括号的方式查看括号中key对应的value(multimap没有重载  []  操作符,无法使用)
	cout << "key = 2的value = " << m[2] << endl;
	cout << "key = 6的value = " << m[6] << endl;  // 找不到key等于6的元素时会在容器中创建一个出来,value值初始化为0


	// 打印结果
	/*
	该key值存在
	key = 2的元素有1个
	key = 6的元素有0个
	key = 2的value = 12
	key = 6的value = 0
	请按任意键继续. . .
	*/

}

int main()
{
	test();

	system("pause");
	return 0;
}

9.6、map容器排序

map容器默认从小到大排序,我们可以利用仿函数改变set的排序规则

第一步

  • 写一个类,类中重载()操作符,并且使用bool值类型接收
  • 形参列表中需要两个值,值类型与map容器模板列表中的第一个数据类型一致,也就是与key的数据类型一致,因为他是根据key来排序的,例如:容器类型为map<int, string>,那么函数声明为:bool operator()(int val1, int val2);
  • 如果想要降序排序,就return形参列表中第一个数大于第二个数
  • 如果想要降序排序,就return形参列表中第一个数小于第二个数
  • 函数运行时报c3848就在函数的括号后面加一个const

示例

class MySort
{
public:
	// 返回值是bool类型,括号中两个值的数据类型与map容器的key的数据类型保持一致
	bool operator()(int val1, int val2)const  // vs2019版本括号后面记得加const,否则报c3848
	{
		// 想要降序排列就让第一个数大于第二个数,反之亦然
		return val1 > val2;
	}
};

第二步

  • 想要改变排序规则,在使用构造函数创建对象时就要指定排序规则
  • map模板参数列表中可以传三个值,前两个是指定key和value的数据类型,还一个是传仿函数,也就是把上一步写的类名传进来
  • 这样添加数据时他就会根据仿函数中的排序规则来排序

示例

// 要改变排序规则,需要在添加数据之前就指定
// map模板参数列表中可以传三个值,前两个是指定key和value的数据类型,还一个是传仿函数
map<int, int, MySort> m;

一:map容器改变内置数据类型的排序方式

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

class MySort
{
public:
	// 返回值是bool类型,括号中两个值的数据类型与map容器key的数据类型一致
	bool operator()(int val1, int val2)const  // vs2019版本括号后面记得加const,否则报c3848
	{
		// 想要降序排列就让第一个数大于第二个数,反之亦然
		return val1 > val2;
	}
};

void test()
{
	// 要改变排序规则,需要在添加数据之前就指定
	map<int, int, MySort> m;
	m.insert(make_pair(3, 34));
	m.insert(make_pair(2, 34));
	m.insert(make_pair(5, 22));
	m.insert(make_pair(1, 33));
	m.insert(make_pair(4, 37));

	// 迭代器的类型也是要加上类名
	for (map<int, int, MySort>::iterator it = m.begin(); it != m.end(); it++)
	{
		cout << "key = " << it->first << "value = " << it->second << endl;
	}
}

int main()
{
	test();

	system("pause");
	return 0;
}

二:map容器改变自定义数据类型的排序方式

对于自定义数据类型,map必须指定排序规则才可以添加数据,否则set不知道该怎么排序

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

class Person
{
public:
	int age;
	string name;

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

class MySort
{
public:
	// 返回值是bool类型,括号中两个值的数据类型与map容器key的数据类型一致
	bool operator()(Person val1, Person val2)const  // vs2019版本括号后面记得加const,否则报c3848
	{
		// 想要降序排列就让第一个数大于第二个数,反之亦然
		return val1.age > val2.age;
	}
};

void test()
{
	// 按照年龄大小降序排序
	Person p1(21, "张三");
	Person p2(33, "李四");
	Person p3(23, "王五");
	Person p4(31, "刘六");
	Person p5(25, "杨七");

	// 要改变排序规则,需要在添加数据之前就指定
	map<Person, int, MySort> m;
	m.insert(pair<Person, int>(p1, 1));
	m.insert(pair<Person, int>(p2, 2));
	m.insert(pair<Person, int>(p3, 3));
	m.insert(pair<Person, int>(p4, 4));
	m.insert(pair<Person, int>(p5, 5));

	// 迭代器的类型也是要加上类名
	for (map<Person, int, MySort>::iterator it = m.begin(); it != m.end(); it++)
	{
		cout << "key_name = " << it->first.name << "  key_age = " << it->first.age << "  value = " << it->second << endl;
	}

	// 打印结果
	/*
	key_name = 李四  key_age = 33  value = 2
	key_name = 刘六  key_age = 31  value = 4
	key_name = 杨七  key_age = 25  value = 5
	key_name = 王五  key_age = 23  value = 3
	key_name = 张三  key_age = 21  value = 1
	请按任意键继续. . .
	*/
}

int main()
{
	test();

	system("pause");
	return 0;
}

9.7、map常用成员方法

成员方法 功能
begin() 返回指向容器中第一个(注意,是已排好序的第一个)键值对的双向迭代器。如果 map 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。
end() 返回指向容器最后一个元素(注意,是已排好序的最后一个)所在位置后一个位置的双向迭代器,通常和 begin() 结合使用。如果 map 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。
rbegin() 返回指向最后一个(注意,是已排好序的最后一个)元素的反向双向迭代器。如果 map 容器用 const 限定,则该方法返回的是 const 类型的反向双向迭代器。
rend() 返回指向第一个(注意,是已排好序的第一个)元素所在位置前一个位置的反向双向迭代器。如果 map 容器用 const 限定,则该方法返回的是 const 类型的反向双向迭代器。
cbegin() 和 begin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的键值对。
cend() 和 end() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的键值对。
crbegin() 和 rbegin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的键值对。
crend() 和 rend() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的键值对。
find(key) 在 map 容器中查找键为 key 的键值对,如果成功找到,则返回指向该键值对的双向迭代器;反之,则返回和 end() 方法一样的迭代器。另外,如果 map 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。
lower_bound(key) 返回一个指向当前 map 容器中第一个大于或等于 key 的键值对的双向迭代器。如果 map 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。
upper_bound(key) 返回一个指向当前 map 容器中第一个大于 key 的键值对的迭代器。如果 map 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。
equal_range(key) 该方法返回一个 pair 对象(包含 2 个双向迭代器),其中 pair.first 和 lower_bound() 方法的返回值等价,pair.second 和 upper_bound() 方法的返回值等价。也就是说,该方法将返回一个范围,该范围中包含的键为 key 的键值对(map 容器键值对唯一,因此该范围最多包含一个键值对)。
empty() 若容器为空,则返回 true;否则 false。
size() 返回当前 map 容器中存有键值对的个数。
max_size() 返回 map 容器所能容纳键值对的最大个数,不同的操作系统,其返回值亦不相同。
operator[] map容器重载了 [] 运算符,只要知道 map 容器中某个键值对的键的值,就可以向获取数组中元素那样,通过键直接获取对应的值。
at(key) 找到 map 容器中 key 键对应的值,如果找不到,该函数会引发 out_of_range 异常。
insert() 向 map 容器中插入键值对。
erase() 删除 map 容器指定位置、指定键(key)值或者指定区域内的键值对。后续章节还会对该方法做重点讲解。
swap() 交换 2 个 map 容器中存储的键值对,这意味着,操作的 2 个键值对的类型必须相同。
clear() 清空 map 容器中所有的键值对,即使 map 容器的 size() 为 0。
emplace() 在当前 map 容器中的指定位置处构造新键值对。其效果和插入键值对一样,但效率更高。
emplace_hint() 在本质上和 emplace() 在 map 容器中构造新键值对的方式是一样的,不同之处在于,使用者必须为该方法提供一个指示键值对生成位置的迭代器,并作为该方法的第一个参数。
count(key) 在当前 map 容器中,查找键为 key 的键值对的个数并返回。注意,由于 map 容器中各键值对的键的值是唯一的,因此该函数的返回值最大为 1。

9.8、multimap常用成员方法

和 map 容器相比,multimap 未提供 at() 成员方法,也没有重载 [] 运算符。这意味着,map 容器中通过指定键获取指定指定键值对的方式,将不再适用于 multimap 容器。其实这很好理解,因为 multimap 容器中指定的键可能对应多个键值对,而不再是 1 个。

成员方法 功能
begin() 返回指向容器中第一个(注意,是已排好序的第一个)键值对的双向迭代器。如果 multimap 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。
end() 返回指向容器最后一个元素(注意,是已排好序的最后一个)所在位置后一个位置的双向迭代器,通常和 begin() 结合使用。如果 multimap 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。
rbegin() 返回指向最后一个(注意,是已排好序的最后一个)元素的反向双向迭代器。如果 multimap 容器用 const 限定,则该方法返回的是 const 类型的反向双向迭代器。
rend() 返回指向第一个(注意,是已排好序的第一个)元素所在位置前一个位置的反向双向迭代器。如果 multimap 容器用 const 限定,则该方法返回的是 const 类型的反向双向迭代器。
cbegin() 和 begin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的键值对。
cend() 和 end() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的键值对。
crbegin() 和 rbegin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的键值对。
crend() 和 rend() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的键值对。
find(key) 在 multimap 容器中查找首个键为 key 的键值对,如果成功找到,则返回指向该键值对的双向迭代器;反之,则返回和 end() 方法一样的迭代器。另外,如果 multimap 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。
lower_bound(key) 返回一个指向当前 multimap 容器中第一个大于或等于 key 的键值对的双向迭代器。如果 multimap 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。
upper_bound(key) 返回一个指向当前 multimap 容器中第一个大于 key 的键值对的迭代器。如果 multimap 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。
equal_range(key) 该方法返回一个 pair 对象(包含 2 个双向迭代器),其中 pair.first 和 lower_bound() 方法的返回值等价,pair.second 和 upper_bound() 方法的返回值等价。也就是说,该方法将返回一个范围,该范围中包含的键为 key 的键值对。
empty() 若容器为空,则返回 true;否则 false。
size() 返回当前 multimap 容器中存有键值对的个数。
max_size() 返回 multimap 容器所能容纳键值对的最大个数,不同的操作系统,其返回值亦不相同。
insert() 向 multimap 容器中插入键值对。
erase() 删除 multimap 容器指定位置、指定键(key)值或者指定区域内的键值对。
swap() 交换 2 个 multimap 容器中存储的键值对,这意味着,操作的 2 个键值对的类型必须相同。
clear() 清空 multimap 容器中所有的键值对,使 multimap 容器的 size() 为 0。
emplace() 在当前 multimap 容器中的指定位置处构造新键值对。其效果和插入键值对一样,但效率更高。
emplace_hint() 在本质上和 emplace() 在 multimap 容器中构造新键值对的方式是一样的,不同之处在于,使用者必须为该方法提供一个指示键值对生成位置的迭代器,并作为该方法的第一个参数。
count(key) 在当前 multimap 容器中,查找键为 key 的键值对的个数并返回。

10、案例:员工分组

案例描述

  • 公司招募了10名员工(ABCDEFGHIJ)
  • 员工信息有:姓名、工资;部门分为:策划、美术、研发
  • 随机给10名员工分配部门和工资
  • 通过multimap进行信息插入,key:部门编号,value:员工对象
  • 分部门展示员工信息

案例实现

  1. 创建10名员工,放到vector容器中
  2. 遍历vector容器,取出每个员工,进行随机分组
  3. 分组后将员工部门编号作为key,具体员工对象为value,放入到multimap容器中
  4. 分部门展示员工信息

实现:

#include<iostream>
#include<string>
#include<map>
#include<vector>
#include<ctime>
using namespace std;
// 定义宏常量 0:策划,1:美术,2:研发
#define CH 0
#define MS 1
#define YF 2

// 员工类
class Work
{
public:
	string name;
	int salary;
};

// 创建员工对象并放到vector容器中
void create_work(vector<Work>& v_work, int size_work)
{
	// 姓名种子
	string names = "ABCDEFGHIJ";

	// 循环添加员工
	for (int i = 0; i < size_work; i++)
	{
		// 员工姓名,拼接字符串
		string name = "员工:";
		name += names[i];

		// 随机生产工资:9999`19999
		int salary = rand() % 10000 + 10000;  // 9999 ~ 19999

		// 创建员工对象,并添加员工属性:姓名和工资
		Work w;
		w.name = name;
		w.salary = salary;

		// 把创建好的员工插入到vector容器中
		v_work.push_back(w);
	}
}

// 给员工分配部门
void grouping_work(const vector<Work>& v_work, multimap<int, Work>& m_work)
{
	// 循环取出每一个员工
	for (vector<Work>::const_iterator it = v_work.begin(); it != v_work.end(); it++)
	{
		// 随机生成0~2的数,作为部门编号
		int d_id = rand() % 3;  // 0 ~ 2

		// 把员工添加到multimap容器中,key为部门编号,value为员工对象
		m_work.insert(pair<int, Work>(d_id, *it));
	}
}

// 打印一个部门的员工信息
void show_department(const multimap<int, Work>& m_work, int d_id)
{
	// 获取当前部门编号的起始迭代器
	multimap<int, Work>::const_iterator it = m_work.find(d_id);
	// 获取当前部门的人数
	int count = m_work.count(d_id);

	// 因为multimap容器会自动排序,所以部门一样的员工在容器中是连续的,只需要让迭代器自增到部门人数上限为止
	for (int i = 0; i < count; i++, it++)
	{
		cout << it->second.name << "  工资" << it->second.salary << endl;
	}
	cout << endl;
}

// 打印所以部门员工信息
void show_work(const multimap<int, Work>& m_work)
{
	cout << "策划部门:" << endl;
	show_department(m_work, CH);
	cout << "美术部门:" << endl;
	show_department(m_work, MS);
	cout << "研发部门:" << endl;
	show_department(m_work, YF);
}

int main()
{
	// 随机数种子
	srand((unsigned int) time(NULL));
	// 员工人数
	int work_size = 10;
	// 创建vector容器存放员工对象
	vector<Work> v_work;
	// 添加员工到vector容器中
	create_work(v_work, work_size);

	// 创建multimap容器
	multimap<int, Work> m_work;
	// 员工分部门
	grouping_work(v_work, m_work);

	// 展示所有部门的员工信息
	show_work(m_work);

	system("pause");
	return 0;
}

五、STL - 函数对象

1、函数对象的概念

概念:

  • 重载函数调用操作符的类,其对象常称为函数对象
  • 函数对象使用重载的()时,行为类似函数调用,也叫仿函数

本质:

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

2、函数对象的使用

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

总结:

函数对象的写法非常灵活,没有固定写法

可以作为像普通函数那样调用,可以有参数,可以有返回值

#include <iostream>
using namespace std;

class My_add
{
public:
	// 重载小括号运算符
	int operator()(int a, int b)
	{
		return a + b;
	}
};

void test()
{
	My_add add;
	// 可以像普通函数一样调用
	cout << add(12, 22) << endl;
}

int main()
{
	test();

	system("pause");
	return 0;
}

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

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

class My_print
{
public:
	My_print()
	{
		// 初始化属性
		this->count = 0;
	}

	// 重载小括号运算符
	// 函数对象可以有自己的状态,比如记录该函数对象被调用了几次
	void operator()(string str)
	{
		cout << str << endl;
		this->count++;
	}

	// 记录函数对象被调用了多少次
	int count;
};

void test()
{
	My_print print;

	print("hello c++");
	print("hello c++");
	print("hello c++");
	print("hello c++");
	cout << "函数对象被调用了" << print.count << "次" << endl;
}

int main()
{
	test();

	system("pause");
	return 0;
}

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

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

class My_print
{
public:
	My_print()
	{
		// 初始化属性
		this->count = 0;
	}

	// 重载小括号运算符
	// 函数对象可以有自己的状态,比如记录该函数对象被调用了几次
	void operator()(string str)
	{
		cout << str << endl;
		this->count++;
	}

	// 记录函数对象被调用了多少次
	int count;
};

// 函数对象作为函数参数传递
void to_print(My_print& my, string str)
{
	my(str);
}

void test()
{
	My_print print;
	to_print(print, "你好,世界");
}

int main()
{
	test();

	system("pause");
	return 0;
}

3、谓词

3.1、谓词概念

  • 返回bool类型的仿函数称为谓词
  • 如果operator()接收一个参数,那么称为一元谓词
  • 如果operator()接收两个参数,那么称为二元谓词

3.2、一元谓词

示例

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

class My_class
{
public:
	// 一元谓词,重载小括号的返回值为bool类型,接收一个参数
	bool operator()(int a)
	{
		return a > 5;
	}
};

void test()
{
	// 创建一个容器并存值
	vector<int> v;
	for (int i = 0; i < 9; i++)
	{
		v.push_back(i);
	}

	// 创建一个vector容器的int类型的迭代器
	vector<int>::iterator it;
	My_class m;
	// find_if算法:查找符合要求的数据(判断条件是一元谓词return后跟的条件)
	// 返回值类型是一个迭代器。找到符合要求的数据后就返回该数据的迭代器,未找到就返回结束位置的迭代器
	// 需要的三个参数为:开始查找位置的迭代器,结束位置的迭代器,一元谓词(接收一个参数的仿函数)
	// 第三个参数可以使用匿名对象,也可以自己创建一个有名对象了传进去
	it = find_if(v.begin(), v.end(), My_class());

	// 返回的迭代器等于接收迭代器,说明没有找到符合要求的数据
	if (it == v.end())
	{
		cout << "在该数组中没有找到大于5的数" << endl;
	}
	// 否则就找到了符合要求的数据,并返回该数的迭代器
	else
	{
		cout << "找到了比5大的数,该数为:" << *it << endl;
	}
}

int main()
{
	test();

	system("pause");
	return 0;
}

3.3、二元谓词

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

class My_sort
{
public:
	// 二元谓词,重载小括号,返回值为bool类型,接收两个参数
	bool operator()(int val1, int val2)
	{
		return val1 > val2;
	}
};

void test()
{ 
	vector<int> v;
	v.push_back(3);
	v.push_back(5);
	v.push_back(2);
	v.push_back(1);
	v.push_back(4);

	// 升序排序
	sort(v.begin(), v.end(), My_sort());

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

int main()
{
	test();

	system("pause");
	return 0;
}

4、内建函数对象

4.1、内建函数对象意义

分类:

  • 算术运算符
  • 关系运算符
  • 逻辑运算符

用法:

  • 这些仿函数产生的对象,用法和一般函数完全一致
  • 使用内建函数对象,需要引入头文件#include<functional>

4.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> // 取反仿函数

使用:

// 以加法为例:
plus<数据类型> 对象名;
对象名(数据1, 数据2);

示例:

加减乘除和取模运算的方法都是一样的,只是换一下函数名,所以只展示加法仿函数和取反仿函数

#include<iostream>
// 使用内建仿函数需要先导入头文件
#include<functional>
using namespace std;

void test()
{
	// 加法仿函数
	// 与容器使用类似,先创建一个对象出来,指定要运算的类型
	plus<int> p;
	// 直接像使用函数一样使用,利用小括号传值,除了negate仿函数外其他算术运算符都需要两个参数
	cout << p(10, 20) << endl;
	// 打印结果:30
}

void test2()
{
	// 取反仿函数
	negate<int> n;
	cout << n(-50) << endl;
	// 打印结果:50
}

int main()
{
	test();
	test2();

	system("pause");
	return 0;
}

4.3、关系仿函数

大于仿函数使用频率最高

仿函数原型:

  • template<class T> bool equal_to<T> // 等于
  • template<class T> bool not_equal_to<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> // 小于等于

使用:

// 以大于仿函数为例:
greater<数据类型> 对象名;
对象名(数据1, 数据2);
// 返回值是0或1,满足条件返回1, 不满足返回0

注意:

  • 关系仿函数都是二元谓词,因为两个数要作对比

  • 在排序算法中,源码默认就是使用的小于仿函数,所以默认升序排序;当我们要降序排序时,就可以传一个大于仿函数,所以大于仿函数是使用频率较高的

排序算法源码:

// sort函数有两个重载

// 第一个重载
// 需要三个参数:起始迭代器,结束迭代器,二元谓词(自定义排序的方式)
template <class _RanIt, class _Pr>
_CONSTEXPR20 void sort(const _RanIt _First, const _RanIt _Last, _Pr _Pred) { // order [_First, _Last)
    _Adl_verify_range(_First, _Last);
    const auto _UFirst = _Get_unwrapped(_First);
    const auto _ULast  = _Get_unwrapped(_Last);
    _Sort_unchecked(_UFirst, _ULast, _ULast - _UFirst, _Pass_fn(_Pred));
}

// 第二个重载
// 只需要两个参数:起始迭代器,结束迭代器
// 通过源码可以看到他是调用了第一个重载的函数,并把小于仿函数当作第三个参数传进去,所以sort默认升序排序
template <class _RanIt>
_CONSTEXPR20 void sort(const _RanIt _First, const _RanIt _Last) { // order [_First, _Last)
    _STD sort(_First, _Last, less<>{});
}

示例:

#include<iostream>
// 使用内建仿函数需要先导入头文件
#include<functional>
#include<algorithm>
#include<vector>
using namespace std;

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

void test()
{
    // 创建容器
	vector<int> v;
	v.push_back(3);
	v.push_back(5);
	v.push_back(1);
	v.push_back(4);
	v.push_back(2);

	cout << "使用自己写的二元谓词进行降序排序:" << endl;
	sort(v.begin(), v.end(), My_sort());
	for (vector<int>::iterator it = v.begin(); it != v.end(); it++)
	{
		cout << *it << " ";
	}
	cout << endl;

	cout << "使用内建的大于仿函数进行降序排序:" << endl;
	// 该方法与上面的方法实现过程是一样的,只不过这是系统提供的,不用自己写
	sort(v.begin(), v.end(), greater<int>());
	for (vector<int>::iterator it = v.begin(); it != v.end(); it++)
	{
		cout << *it << " ";
	}
	cout << endl;


	// 打印结果
	/*
	使用自己写的二元谓词进行降序排序:
	5 4 3 2 1
	使用内建的大于仿函数进行降序排序:
	5 4 3 2 1
	请按任意键继续. . .
	*/
}

int main()
{
	test();

	system("pause");
	return 0;
}

4.4、逻辑仿函数

实际开发中基本用不到,了解即可

作用:实现逻辑运算

函数原型

  • template<class T> bool logical_and<T> // 逻辑与
  • template<class T> bool logical_or<T> // 逻辑或
  • template<class T> bool logical_not<T> // 逻辑非

注意:

  • 逻辑与和逻辑或是二元谓词
  • 逻辑非是一元谓词

使用:

logical_and<数据类型> 变量名;
变量名(数据1, 数据2);

示例:

#include<iostream>
// 使用内建仿函数需要先导入头文件
#include<functional>
#include<algorithm>
#include<vector>
using namespace std;

void test()
{
	vector<bool> v;
	v.push_back(true);
	v.push_back(true);
	v.push_back(false);
	v.push_back(true);

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

	cout << "对容器中的值取反" << endl;
	logical_not<bool> n;
	for (vector<bool>::iterator it = v.begin(); it != v.end(); it++)
	{
		cout << n(*it) << endl;
	}

	// 打印结果
	/*
	1
	1
	0
	1
	对容器中的值取反
	0
	0
	1
	0
	请按任意键继续. . .
	*/
}

int main()
{
	test();

	system("pause");
	return 0;
}

六、STL - 常用算法

1、概述

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

2、常用遍历算法

  • for_each // 遍历容器
  • transform // 搬运容器中的数据到另一个容器中

2.1、for_each:遍历容器

作用:遍历容器中所有数据

函数原型:for_each(_InIt _First, _InIt _Last, _Fn _Func)

参数解释:三个必要的参数:起始迭代器、结束迭代器、函数或函数对象(函数返回值:void;函数体代码:根据自己的情况,自定义数据打印输出的形式)

函数源码:

template <class _InIt, class _Fn>
_CONSTEXPR20 _Fn for_each(_InIt _First, _InIt _Last, _Fn _Func) { // perform function for each element [_First, _Last)
    _Adl_verify_range(_First, _Last);
    auto _UFirst      = _Get_unwrapped(_First);
    const auto _ULast = _Get_unwrapped(_Last);
    // 使用for循环,起始迭代器自增,当起始迭代器等于结束迭代器时结束循环
    for (; _UFirst != _ULast; ++_UFirst) {
        // 将迭代器解引用后作为参数传给我们提供的回调函数或仿函数中
        _Func(*_UFirst);
    }

    return _Func;
}

示例:

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

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

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

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

	// 第一种方法:第三个参数传递一个函数地址(回调函数)
	for_each(v.begin(), v.end(), print1);
	cout << endl;

	// 第二种方法:第三个参数传递一个函数对象(仿函数)
	for_each(v.begin(), v.end(), Print2());
	cout << endl;
}

int main()
{
	test();

	system("pause");
	return 0;
}

2.2、transform:搬运容器数据到另一个容器中

作用:搬运容器数据到另一个容器中

函数原型:transform(const _InIt _First, const _InIt _Last, _OutIt _Dest, _Fn _Func)

参数解释:四个必要参数:原容器起始迭代器、原容器结束迭代器、目标容器起始迭代器、函数或函数对象(函数返回值类型:与容器类型保持一致;函数体:可以对数据进行逻辑修改后再传入新的容器,也可以直接返回,不作修改)

注意:目标容器一定要事先预留好存放数据的位置,否则程序会崩溃(例如:vector容器使用resize方法预先留好存放数据的位置)

函数源码:

template <class _InIt, class _OutIt, class _Fn>
_CONSTEXPR20 _OutIt transform(const _InIt _First, const _InIt _Last, _OutIt _Dest, _Fn _Func) {
    // transform [_First, _Last) with _Func
    _Adl_verify_range(_First, _Last);
    auto _UFirst      = _Get_unwrapped(_First);
    const auto _ULast = _Get_unwrapped(_Last);
    auto _UDest       = _Get_unwrapped_n(_Dest, _Idl_distance<_InIt>(_UFirst, _ULast));
    // for循环原容器和目标容器
    for (; _UFirst != _ULast; ++_UFirst, (void) ++_UDest) {
        // 把原容器中的数据作为参数,传给我们提供的函数,再把通过函数处理后的数据赋值给目标容器
        *_UDest = _Func(*_UFirst);
    }

    _Seek_wrapped(_Dest, _UDest);
    return _Dest;
}

示例:

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

// 打印容器中的数据
void my_print(vector<int>& v)
{
	for (vector<int>::iterator it = v.begin(); it != v.end(); it++)
	{
		cout << *it << " ";
	}
	cout << endl;
}

class Transform
{
public:
	// 返回值类型必须与容器类型保持一致
	int operator()(int val)
	{
		// 我们可以对原容器中的数据进行一定的逻辑操作后再传入目标容器中
		return val + 100;
	}
};

int my_transform(int val)
{
	// 也可以不对原容器中的数据进行任何操作,直接传入目标容器中
	return val;
}

void test()
{
	// 原容器
	vector<int> v1;
	// 目标容器
	vector<int> v2;

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

	// 注意:目标容器一定要事先预留好存放数据的位置,否则程序会崩溃
	v2.resize(v1.size());

	// 第一种方法:第四个参数传入函数对象
	transform(v1.begin(), v1.end(), v2.begin(), Transform());
	my_print(v2);

	// 第二种方法:第四个参数传入一个普通函数的地址
	transform(v1.begin(), v1.end(), v2.begin(), my_transform);
	my_print(v2);


}

int main()
{
	test();

	system("pause");
	return 0;
}

3、常用查找算法

  • find // 查找元素
  • find_if // 按条件查找元素
  • adjacent_find // 查找相邻的重复元素
  • binary_search // 二分查找法
  • count // 统计元素个数
  • count_if // 按条件统计元素个数

3.1、find:查找指定元素

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

函数原型:find(_InIt _First, const _InIt _Last, const _Ty& _Val)

参数解释:三个必须参数:起始迭代器、结束迭代器、要查找的元素

返回值:是一个迭代器

注意:当我们要查找的是自定义类型数据时,需要我们在类中重载 “==” 操作符,且形参列表要加上const修饰

函数源码:

template <class _InIt, class _Ty>
_NODISCARD _CONSTEXPR20 _InIt find(_InIt _First, const _InIt _Last, const _Ty& _Val) { // find first matching _Val
    _Adl_verify_range(_First, _Last);
    _Seek_wrapped(_First, _Find_unchecked(_Get_unwrapped(_First), _Get_unwrapped(_Last), _Val));
    return _First;
}

函数实现逻辑的源码:

template <class _InIt, class _Ty>
_NODISCARD constexpr _InIt _Find_unchecked1(_InIt _First, const _InIt _Last, const _Ty& _Val, false_type) {
    // find first matching _Val
    // for循环容器,判断容器中的每一个数据是否与要查找的值一致
    // 当我们要查找的是自定义类型数据时,需要我们在类中重载==操作符,且形参列表要用const修饰
    for (; _First != _Last; ++_First) {
        if (*_First == _Val) {
            break;
        }
    }

    return _First;
}

find查找内置数据类型

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

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

	// find找到想要的数据时返回该数据的迭代器,没有找到时返回结束迭代器end()
	vector<int>::iterator it = find(v.begin(), v.end(), 5);
	if (it == v.end())
	{
		cout << "容器中没有5这个元素" << endl;
	}
	else
	{
		cout << "容器中有5这个元素" << endl;
	}
}

int main()
{
	test();

	system("pause");
	return 0;
}

find查找自定义数据类型

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

// 创建自定义数据类型
class Person
{
public:
	Person(string name, int age)
	{
		this->name = name;
		this->age = age;
	}

	// 通过查看find源码可知,他是for循环容器中的每一个数据与我们传入的对象进行比较,而自定义类型编译器不知道要如何比较,所以要重载==运算符
	// 注意:一定要加const修饰,否则数据类型不对
	bool operator==(const Person& p)
	{
		if (this->age == p.age && this->name == p.name)
		{
			return true;
		}
		return false;
	}

	string name;
	int age;
};

// find查找自定义数据类型
void test()
{
	// 往vector容器中添加5个Person类型的对象
	string str_name = "abcdef";
	vector<Person> v;
	for (int i = 0; i < 5; i++)
	{
		string names = "姓名";
		string name = names + str_name[i];
		Person p(name, i + 20);
		v.push_back(p);
	}

	// 创建一个Person对象,判断容器中是否有与该对象一模一样的数据
	Person p1("姓名a", 20);

	// find找到想要的数据时返回该数据的迭代器,没有找到时返回结束迭代器end()
	vector<Person>::iterator it = find(v.begin(), v.end(), p1);
	if (it == v.end())
	{
		cout << "没有找到与p1一模一样的人" << endl;
	}
	else
	{
		cout << "找到了与p1一模一样的人" << endl;
	}
}

int main()
{
	test();

	system("pause");
	return 0;
}

3.2、find_if:根据条件查找元素

作用:查找符合要求的数据(判断条件由谓词内的函数体代码决定)

返回值类型:返回值类型是一个迭代器。找到符合要求的数据后就返回该数据的迭代器,未找到就返回结束位置的迭代器器

函数原型:find_if(_InIt _First, const _InIt _Last, _Pr _Pred)

参数解释:需要三个必须的参数:开始查找位置的迭代器、结束位置的迭代器、_Prde函数或者谓词

函数源码:

find_if(_InIt _First, const _InIt _Last, _Pr _Pred) { // find first satisfying _Pred
    _Adl_verify_range(_First, _Last);
    auto _UFirst      = _Get_unwrapped(_First);
    const auto _ULast = _Get_unwrapped(_Last);
    // for循环容器中的每一个元素
    for (; _UFirst != _ULast; ++_UFirst) {
        // 把容器中每一个元素作为参数传给回调函数
        // 当回调函数返回值为真时,返回容器中该元素的迭代器
        if (_Pred(*_UFirst)) {
            break;
        }
    }

    _Seek_wrapped(_First, _UFirst);
    return _First;
}

查找内置数据类型

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

class My_find
{
public:
	// 一元谓词,函数体内写查找条件
	bool operator()(int val)
	{
		// 传入的数大于5
		return val > 5;
	}
};

void test()
{
	// 创建vector容器并添加9个数
	vector<int> v;
	for (int i = 0; i < 9; i++)
	{
		v.push_back(i);
	}

	// 找到大于5的数,返回值是迭代器类型
	// 找到符合条件的元素时返回该元素的迭代器,否则返回结束迭代器
	vector<int>::iterator it = find_if(v.begin(), v.end(), My_find());
	if (it == v.end())
	{
		cout << "没有找到大于5的数" <<endl ;
	}
	else
	{
		cout << "找到了大于5的数" << endl;
	}
}

int main()
{
	test();

	system("pause");
	return 0;
}

查找自定义数据类型

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

// 创建自定义类型
class Person
{
public:
	Person(string name, int age)
	{
		this->name = name;
		this->age = age;
	}

	string name;
	int age;
};

class My_find
{
public:
	// 一元谓词,函数体内写查找条件
	bool operator()(Person& p)
	{
		// 年龄大于20岁时返回真
		return p.age > 20;
	}
};

void test()
{
	// 创建Person类型的vector容器,并添加5个Person对象
	vector<Person> v;
	for (int i = 0; i < 5; i++)
	{
		string str_name = "abcde";
		string names = "姓名";
		string name = names + str_name[i];
		Person p(name, i + 18);
		v.push_back(p);
	}

	// 找到年龄大于20岁的人,返回值是迭代器类型
	// 找到符合条件的元素时返回该元素的迭代器,否则返回结束迭代器
	vector<Person>::iterator it = find_if(v.begin(), v.end(), My_find());
	if (it == v.end())
	{
		cout << "没有年龄大于20岁的人";
	}
	else
	{
		cout << "找到了年龄大于20岁的人,姓名为:" << it->name << "  年龄为:" << it->age << endl;
	}
}

int main()
{
	test();

	system("pause");
	return 0;
}

3.3、adjacent_find:查找相邻重复的元素

该函数有两个重载:

  • adjacent_find(const _FwdIt _First, const _FwdIt _Last)
    • 作用:查找 2 个连续相等的元素
    • 参数解释:两个参数:起始迭代器、结束迭代器
  • adjacent_find(const _FwdIt _First, _FwdIt _Last, _Pr _Pred)
    • 作用:查找 2 个连续满足 pred 规则的元素
    • 参数解释:三个参数:起始迭代器、结束迭代器、 函数或者函数对象( 包含 2 个参数且返回值类型为 bool 的函数 )

返回值:迭代器,找到符合条件的元素返回该元素的迭代器,没有找到返回结束迭代器

注意: adjacent_find() 函数的底层使用的是 == 运算符来判断连续 2 个元素是否相等。这意味着,如果指定区域内的元素类型为自定义的类对象或者结构体变量时,需要先对 == 运算符进行重载,然后才能使用此函数。

实现函数逻辑的源码:

template <class ForwardIterator>
ForwardIterator adjacent_find (ForwardIterator first, ForwardIterator last)
{
    if (first != last)
    {
        ForwardIterator next=first; ++next;
        while (next != last) {
            if (*first == *next)     // 或者 if (pred(*first,*next)), 对应第二种语法格式
                return first;
        ++first; ++next;
        }
    }
    return last;
}

操作内置数据类型

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

// adjacent_find函数第三个参数可以传入函数对象,在类内重载小括号,自己制定查找规则
class My_find
{
public:
	// 返回值类型是bool值,接收两个形参(二元谓词)
	bool operator()(int val1, int val2)
	{
		// 自己指定查找规则,不一定非是判断相邻的两个数是否相等
		// 相邻且第二个数是第一个数两倍的元素
		return val1 * 2 == val2;
	}
};

// adjacent_find函数的三个参不仅可以传入函数对象,也可以传一个函数的地址
// 返回值类型为bool类型,接收两个形参
bool my_find(int val1, int val2)
{
	// 自己制定查找规则
	// 相邻且第二个数比第一个数大10的元素
	return val1 + 10 == val2;
}

void test()
{
	// 创建vector容器
	vector<int> v;
	v.push_back(2);
	v.push_back(4);
	v.push_back(11);
	v.push_back(21);
	v.push_back(1);
	v.push_back(1);

	// adjacent_find函数的重载之一:只要两个参数,传入起始迭代器和结束迭代器即可
	vector<int>::iterator it = adjacent_find(v.begin(), v.end());
	if (it == v.end())
	{
		cout << "没有找到相邻且相等的元素" << endl;
	}
	else
	{
		cout << "找到了相邻且相等的元素:" << *it << endl;
	}


	// adjacent_find函数的重载之二:需要三个参数,传入起始迭代器和结束迭代器,第三个参数传入一个函数对象,自己制定查找规则
	it = adjacent_find(v.begin(), v.end(), My_find());
	if (it == v.end())
	{
		cout << "没有找到相邻且第二个数是第一个数两倍的元素" << endl;
	}
	else
	{
		cout << "找到了相邻且第二个数是第一个数两倍的元素:" << *it << endl;
	}


	// adjacent_find函数的重载之二:需要三个参数,传入起始迭代器和结束迭代器,第三个参数传入函数地址,自己制定查找规则
	it = adjacent_find(v.begin(), v.end(), my_find);
	if (it == v.end())
	{
		cout << "没有找到相邻且第二个数比第一个数大10的元素" << endl;
	}
	else
	{
		cout << "找到了相邻且第二个数比第一个数大10的元素:" << *it << endl;
	}
}

int main()
{
	test();

	system("pause");
	return 0;
}

操作自定义数据类型

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

// 创建自定义数据类型
class Person
{
public:
	Person(string name, int age)
	{
		this->name = name;
		this->age = age;
	}
	
	// adjacent_find的底层使用的是 == 运算符来判断连续 2 个元素是否相等。这意味着,如果指定区域内的元素类型为自定义的类对象或者结构体变量时,需要先对 == 运算符进行重载,然后才能使用此函数。 
	bool operator==(const Person& p)
	{
		if (this->name == p.name && this->age == p.age)
		{
			return true;
		}
		return false;
	}

	string name;
	int age;
};

// adjacent_find函数第三个参数可以传入函数对象,在类内重载小括号,自己制定查找规则
class My_find
{
public:
	// 返回值类型是bool值,接收两个形参(二元谓词)
	bool operator()(const Person& p1, const Person& p2)
	{
		// 自己指定查找规则,不一定非是判断相邻的两个数是否相等
		// 找到两个相邻的对象,后一个对象的年龄比前一个对象的年龄大一岁
		return (p1.age + 1) == p2.age;
	}
};

// adjacent_find函数的三个参不仅可以传入函数对象,也可以传一个函数的地址
// 返回值类型为bool类型,接收两个形参
bool my_find(const Person& p1, const Person& p2)
{
	// 自己制定查找规则
	// 找两个相邻的对象,前一个对象的年龄小于后一个对象的年龄
	return p1.age < p2.age;
}

void test()
{
	// 创建vector容器,并加入5个Person对象
	vector<Person> v;
	string std = "abcde";
	string std_name = "姓名";
	for (int i = 0; i < 5; i++)
	{
		string name = std_name + std[i];
		Person p(name, i + 20);
		v.push_back(p);
	}

	// adjacent_find函数的重载之一:只要两个参数,传入起始迭代器和结束迭代器即可
	vector<Person>::iterator it = adjacent_find(v.begin(), v.end());
	if (it == v.end())
	{
		cout << "没有相邻且完全一样的人的情况" << endl;
	}
	else
	{
		cout << it->name << "与相邻的后一个人完全一样" << endl;
	}


	// adjacent_find函数的重载之二:需要三个参数,传入起始迭代器和结束迭代器,第三个参数传入一个函数对象,自己制定查找规则
	it = adjacent_find(v.begin(), v.end(), My_find());
	if (it == v.end())
	{
		cout << "没有相邻且后一个人比前一个人大一岁的情况" << endl;
	}
	else
	{
		cout << it->name << "的后一位比当前这个人大一岁" << endl;
	}


	// adjacent_find函数的重载之二:需要三个参数,传入起始迭代器和结束迭代器,第三个参数传入函数地址,自己制定查找规则
	it = adjacent_find(v.begin(), v.end(), my_find);
	if (it == v.end())
	{
		cout << "没有前一个人的年龄小于后一个人的年龄的情况" << endl;
	}
	else
	{
		cout << it->name << "比后一个人的年龄小" << endl;
	}
}

int main()
{
	test();

	system("pause");
	return 0;
}

3.4、binary_search:查找指定区域是否包含目标

其底层原理是采用二分查找算法

使用二分查找的前提是有序序列
    例如:{5,21,13,19,37,75,56,64,88 ,80,92}这个列表中,使用二分法查找找到21
1、需要先排序{5,13,19,21,37,56,64,75,80,88,92}
2、再找到列表里最中间数据56,让他与我们的目标数据相比较
3、发现比21大,且此列表是升序排序,所以直接排除56及以后的数据,查找537之间的数据,以此重复上面12两步即可找到目标数据
    
lower_bound()
upper_bound()
equal_range()
binary_search()
这四个函数底层都是采用二分查找法

作用:查找指定区域内是否包含某个目标

注意:

  • 该函数底层实现是二分法,所以指定的区域内必须要按一定规则排序
  • binary_search默认序列排序顺序是从小到大,所以不指定排序规则时区域内的数据要按照从小到大的顺序排序
  • 如果区域内的数据是从大到小排序或自定义的数据类型,就需要传入第四个参数,写明指定区域内的排序规则

例如:

在降序列表中{92,88,80,75,64,56,37,21,19,13,5}找到21

当使用binary_search()函数来查找,且不传入第四个参数,也就是排序规则,使用函数默认的排序规则时
1、找列表里最中间的数:56
2、因为binary_search()函数默认区域内的排序规则是升序排序,所以发现2156小时,会直接排除56及以后的所有数据,我们的目标数据直接被排除在外了,因此最后返回的结果是false

总结:二分法查找时用的查找规则,要和查找区域的排序规则一样

总结:二分法查找时用的查找规则,要和查找区域的排序规则一样

总结:二分法查找时用的查找规则,要和查找区域的排序规则一样

重要的事说三遍!!!

该函数有两个重载:

  • 第一个重载

注意:

函数原型:binary_search(_FwdIt _First, _FwdIt _Last, const _Ty& _Val)

参数解释:需要三个参数

  1. _First:起始迭代器

  2. _Last:结束迭代器

  3. _val:要查找的值

返回值:bool类型;找到val时返回true,否则返回false

注意:此函数重载仅适用于区域内的降序排序时使用

底层实现:

template <class ForwardIterator, class T>
bool binary_search (ForwardIterator first, ForwardIterator last, const T& val)
{
    first = std::lower_bound(first,last,val);
    return (first!=last && !(val<*first));
}

lower_bound函数简介

在指定区域内查找不小于目标值的第一个元素。也就是说,使用该函数在指定范围内查找某个目标值时,最终查找到的不一定是和目标值相等的元素,还可能是比目标值大的元素。

两种语法格式

//在 [first, last) 区域内查找不小于 val 的元素
ForwardIterator lower_bound (ForwardIterator first, ForwardIterator last,const T& val);

//在 [first, last) 区域内查找第一个不符合 comp 规则的元素
ForwardIterator lower_bound (ForwardIterator first, ForwardIterator last,const T& val, Compare comp);
  • 第二个重载

函数原型:binary_search(_FwdIt _First, _FwdIt _Last, const _Ty& _Val, _Pr _Pred)

参数解释:需要四个参数

  1. _First:起始迭代器

  2. _Last:结束迭代器

  3. _val:要查找的值

  4. _Pred:根据_Pred指定的规则,查找区域内是否包含_val

    • 该参数可以是普通函数,也可以是函数对象
    • 该参数的返回值要为bool类型
    • 该参数要可接收两个形参,如果使用的是函数对象,记得在形参前加上const修饰

返回值:bool类型;找到_val时返回true,否则返回false

注意:二分法查找时用的查找规则,要和查找区域的排序规则一样

底层实现:

template<class ForwardIt, class T, class Compare>
bool binary_search(ForwardIt first, ForwardIt last, const T& val, Compare comp)
{
    first = std::lower_bound(first, last, val, comp);
    return (!(first == last) && !(comp(val, *first)));
}

lower_bound函数简介

在指定区域内查找不小于目标值的第一个元素。也就是说,使用该函数在指定范围内查找某个目标值时,最终查找到的不一定是和目标值相等的元素,还可能是比目标值大的元素。

两种语法格式

//在 [first, last) 区域内查找不小于 val 的元素
ForwardIterator lower_bound (ForwardIterator first, ForwardIterator last,const T& val);
//在 [first, last) 区域内查找第一个不符合 comp 规则的元素
ForwardIterator lower_bound (ForwardIterator first, ForwardIterator last,const T& val, Compare comp);

查找内置数据类型

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

// 制定排序规则,通过函数对象的方式
class My_sort
{
public:
	bool operator()(int val1, int val2)
	{
		return val1 > val2;
	}
};

// 制定排序规则,通过普通函数的方式
bool my_sort(int val1, int val2)
{
	return val1 > val2;
}

void test()
{
	// 创建容器
	vector<int> v;
	for (int i = 0; i < 9; i++)
	{
		// 随机生成0~9的数
		int age = rand() % 10;
		v.push_back(i);
	}

	int find;
	cout << "输入要查找的元素:";
	cin >> find;

	// 对容器中的元素按照年龄降序排序
	sort(v.begin(), v.end(), My_sort());  // sort(v.begin(), v.end(), my_sort);  也可用普通函数的方式
	// 使用二分法查找数据时,保持查找时用的查找规则,和要查找区域的排序规则一致
	if (binary_search(v.begin(), v.end(), find, My_sort()))  // binary_search(v.begin(), v.end(), pp, my_sort)  也可用普通函数的方式
	{
		cout << "找到了为:" << find << "的元素" << endl;
	}
	else
	{
		cout << "没有找到为:" << find << "的元素" << endl;
	}
}

int main()
{
	test();

	system("pause");
	return 0;
}

查找自定义数据类型

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

// 自定义类型
class Person
{
public:
	Person(string name, int age)
	{
		this->name = name;
		this->age = age;
	}
	string name;
	int age;
};

// 制定排序规则,通过函数对象的方式
class My_sort
{
public:
	bool operator()(const Person& p1, const Person& p2)
	{
		return p1.age > p2.age;
	}
};

// 制定排序规则,通过普通函数的方式
bool my_sort(const Person& p1, const Person& p2)
{
	return p1.age > p2.age;
}

void test()
{
	// 创建容器
	vector<Person> v;
	for (int i = 0; i < 9; i++)
	{
		// 随机生成20~29的数子作为年龄
		int age = rand() % 10 + 20;
		string names = "我是";
		string std = to_string(i);
		names += std;
		Person p(names, age);
		v.push_back(p);
	}

	int find;
	cout << "输入要查找的年龄:";
	cin >> find;
	Person pp("NULL", find);

	// 对容器中的元素按照年龄降序排序
	sort(v.begin(), v.end(), my_sort);  // sort(v.begin(), v.end(), My_sort());  也可用函数对象的方式
	// 使用二分法查找数据时,保持查找时用的查找规则,和要查找区域的排序规则一致
	if (binary_search(v.begin(), v.end(), pp, my_sort))  // binary_search(v.begin(), v.end(), pp, My_sort())  也可用函数对象的方式
	{
		cout << "找到了年龄为:" << find << "的人" << endl;
	}
	else
	{
		cout << "没有找到年龄为:" << find << "的人" << endl;
	}
}

int main()
{
	test();

	system("pause");
	return 0;
}

3.5、count:统计元素个数

作用:统计给定的区域内某个元素的个数

函数原型:count(const _InIt _First, const _InIt _Last, const _Ty& _Val)

参数解释:

  • First:起始迭代器
  • Last:结束迭代器
  • Val:要统计的值

返回值:整型,找到的元素个数

注意:统计自定义变量时要重载 == 运算符,且返回值为bool值,形参列表加const修饰

函数源码:

template <class _InIt, class _Ty>
_NODISCARD _CONSTEXPR20 _Iter_diff_t<_InIt> count(const _InIt _First, const _InIt _Last, const _Ty& _Val) {
    // count elements that match _Val
    _Adl_verify_range(_First, _Last);
    auto _UFirst               = _Get_unwrapped(_First);
    const auto _ULast          = _Get_unwrapped(_Last);
    
    // 初始化count = 0
    _Iter_diff_t<_InIt> _Count = 0;

    // for循环给定区域内的每一个元素
    for (; _UFirst != _ULast; ++_UFirst) {
        // 判断区域内每个元素是否等于要统计的值
        if (*_UFirst == _Val) {
            // 结果为true则count自增
            ++_Count;
        }
    }

    // 将count返回
    return _Count;
}

统计内置数据类型

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

void test()
{
	// 创建容器
	vector<int> v;
	for (int i = 0; i < 9; i++)
	{
		// 随机生成0~5的数
		int n = rand() % 5;
		v.push_back(n);
	}

	// 统计该容器中有多少个2
	int num = count(v.begin(), v.end(), 2);
	cout << "值为2的数有:" << num << "个" << endl;
}

int main()
{
	test();

	system("pause");
	return 0;
}

统计自定义数据类型

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

// 创建自定义数据类型
class Person
{
public:
	Person(string name, int age)
	{
		this->name = name;
		this->age = age;
	}

	// 根据源码可知:count函数内部通过 == 运算符把容器中每一个元素与要统计的元素进行比对
	// 所以要重载 == 运算符,且返回值要为bool,形参列表用const修饰
	bool operator==(const Person& p)
	{
		return p.age == this->age;
	}

	string name;
	int age;
};

void test()
{
	// 创建容器
	vector<Person> v;
	for (int i = 0; i < 9; i++)
	{
		// 随机生成0~5的数
		int n = rand() % 5 + 20;
		Person p("name", n);
		v.push_back(p);
	}

	Person p("name", 21);
	// 统计年龄等于21的有多少人
	int num = count(v.begin(), v.end(), p);
	cout << "年龄等于21的有:" << num << "个人" << endl;
}

int main()
{
	test();

	system("pause");
	return 0;
}

3.6、count_if:按条件统计元素个数

作用:根据我们指定的条件来统计元素个数

函数原型:count_if(_InIt _First, _InIt _Last, _Pr _Pred)

参数解释:

  • First:起始迭代器
  • Last:结束迭代器
  • Pred:一元谓词,自己制定的统计规则

返回值类型:整形,找到符合规则的元素个数

函数源码:

template <class _InIt, class _Pr>
_NODISCARD _CONSTEXPR20 _Iter_diff_t<_InIt> count_if(_InIt _First, _InIt _Last, _Pr _Pred) {
    // count elements satisfying _Pred
    _Adl_verify_range(_First, _Last);
    auto _UFirst               = _Get_unwrapped(_First);
    const auto _ULast          = _Get_unwrapped(_Last);
    // cout统计元素个数
    _Iter_diff_t<_InIt> _Count = 0;
    
    // for循环指定区域的所有元素
    for (; _UFirst != _ULast; ++_UFirst) {
        // 把元素传入一元谓词中,判断返回结果
        if (_Pred(*_UFirst)) {
            // 返回的结果为true则cout自增,表示找到了符合规则的元素
            ++_Count;
        }
    }

    // 将找到元素个数返回
    return _Count;
}

统计内置数据类型

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

// 定义统计规则,统计所有大于用户输入的元素个数
class Age_gr
{
public:
	// 获取用户输入的数字
	Age_gr(int val)
	{
		this->val = val;
	}

	// 一元谓词
	bool operator()(int val)
	{
		// 当元素大于用户输入的数时就返回true
		return val > this->val;
	}

	int val;
};

void test()
{
	// 创建容器
	vector<int> v;
	for (int i = 0; i < 9; i++)
	{
		v.push_back(i);
	}

	// 获取用户输入的数
	int val;
	cout << "输入要查找的数";
	cin >> val;

	// 统计容器中大于用户输入的数的元素个数
	int num = count_if(v.begin(), v.end(), Age_gr(val));
	cout << "大于" << val << "的有:" << num << "个数" << endl;
}

int main()
{
	test();

	system("pause");
	return 0;
}

统计自定义数据类型

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

// 创建自定义数据类型
class Person
{
public:
	Person(string name, int age)
	{
		this->name = name;
		this->age = age;
	}

	string name;
	int age;
};

// 定义统计规则,统计所有年龄大于用户输入的年龄的元素个数
class Age_gr
{
public:
	// 获取用户输入的年龄
	Age_gr(int age)
	{
		this->age = age;
	}

	// 一元谓词
	bool operator()(const Person& p)
	{
		// 当元素的年龄大于用户输入的年龄时就返回true
		return p.age > this->age;
	}

	int age;
};

void test()
{
	string names[5] = { "刘备", "关羽", "张飞", "吕布", "赵信" };
	// 创建容器
	vector<Person> v;
	for (int i = 0; i < 5; i++)
	{
		// 随机生成0~5的数
		int n = rand() % 10 + 20;
		Person p(names[i], n);
		v.push_back(p);
	}

	// 获取用户输入的年龄
	int age;
	cout << "输入要查找的年龄";
	cin >> age;

	// 统计容器中年龄大于用户输入的年龄的元素个数,通过构造函数将用户输入的年龄传入进去
	int num = count_if(v.begin(), v.end(), Age_gr(age));
	cout << "年龄大于" << age<< "的有:" << num << "个人" << endl;
}

int main()
{
	test();

	system("pause");
	return 0;
}

4、常用排序算法

  • sort // 对容器内元素进行排序
  • random_shuffle // 把容器中的数据顺序全部随机打乱
  • merge // 容器元素合并,并存储到另一个容器中
  • reverse // 反转容器中的元素

4.1、sort:对容器内元素排序

功能:对容器中的数据进行排序

需要的参数(该函数有两个重载):

  • 第一个重载,需要三个参数:起始迭代器,结束迭代器,二元谓词(自定义排序的方式)
  • 第二个重载,只需要两个参数:起始迭代器,结束迭代器
    • 通过源码可以看到他是调用了第一个重载的函数,并把内建的小于仿函数当作第三个参数传进去,所以sort默认升序排序

注意:对自定义数据类型排序需要自定义排序方式

函数源码:

// sort函数有两个重载

// 第一个重载
// 需要三个参数:起始迭代器,结束迭代器,二元谓词(自定义排序的方式)
template <class _RanIt, class _Pr>
_CONSTEXPR20 void sort(const _RanIt _First, const _RanIt _Last, _Pr _Pred) { // order [_First, _Last)
    _Adl_verify_range(_First, _Last);
    const auto _UFirst = _Get_unwrapped(_First);
    const auto _ULast  = _Get_unwrapped(_Last);
    _Sort_unchecked(_UFirst, _ULast, _ULast - _UFirst, _Pass_fn(_Pred));
}

// 第二个重载
// 只需要两个参数:起始迭代器,结束迭代器
// 通过源码可以看到他是调用了第一个重载的函数,并把内建的小于仿函数当作第三个参数传进去,所以sort默认升序排序
template <class _RanIt>
_CONSTEXPR20 void sort(const _RanIt _First, const _RanIt _Last) { // order [_First, _Last)
    _STD sort(_First, _Last, less<>{});
}

示例

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

// 自定义函数对象
class My_sort
{
public:
	bool operator()(int val1, int val2)
	{
		return val1 > val2;
	}
};

// 普通函数
bool my_sort(int val1, int val2)
{
	return val1 < val2;
}

// 打印容器中的数据
void my_print(const vector<int>& v)
{
	for (vector<int>::const_iterator it = v.begin(); it != v.end(); it++)
	{
		cout << *it << " ";
	}
	cout << endl;
}

void test()
{
	// 创建容器
	vector<int> v;
	for (int i = 0; i < 9; i++)
	{
		int num = rand() % 30;
		v.push_back(num);
	}
	cout << "未排序前:" << endl;
	my_print(v);

	sort(v.begin(), v.end());
	cout << "使用sort函数的默认排序时:" << endl;
	my_print(v);

	sort(v.begin(), v.end(), My_sort());
	cout << "使用自定义函数对象的方式指定排序规则为从大到小排序时:" << endl;
	my_print(v);

	sort(v.begin(), v.end(), my_sort);
	cout << "使用普通函数的方式指定排序规则为从小到大排序时:" << endl;
	my_print(v);

	// 记得包含头文件 #include<functional>
	sort(v.begin(), v.end(), greater<int>());
	cout << "使用内建函数对象的方式指定排序规则为从大到小排序时:" << endl;
	my_print(v);

}

int main()
{
	test();

	system("pause");
	return 0;
}

4.2、random_shuffle:对容器中的元素随机排序

函数原型:

template <class RandomAccessIterator>
void random_shuffle (RandomAccessIterator first, RandomAccessIterator last);

template <class RandomAccessIterator, class RandomNumberGenerator>
void random_shuffle (RandomAccessIterator first, RandomAccessIterator last, RandomNumberGenerator&& gen);

需要参数:

first 和 last 分别是要打乱的元素范围的开始和结束迭代器。

在上述两个 random_shuffle() 的版本中,第一个版本使用默认的随机数发生器,即函数 rand(),该函数可以获取一个 0~32767 之间的任意整数。第二个版本可以使用用户提供的随机数发生器,要求该发生器接受一个整数 N 作为参数,并返回区间 [0, N) 中的随机数。

注意:

  • 使用该函数时最好搭配随机数种子,这样他每一次的排序都会不同,如:srand((unsigned int)time (NULL)); 记得包含头文件:#include<ctime>
  • 自定义数据类型和内置的数据类型使用方法是一样的

第一个重载示例

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

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

void my_print(const Person& p)
{
	cout << "姓名:" << p.name << " 年龄:" << p.age << endl;
}

void test()
{
	srand((unsigned int)time (NULL));
	string str_name = "abcdefghi";
	vector<Person> v;
	for (int i = 0; i < 9; i++)
	{
		string names = "姓名";
		string name = names + str_name[i];
		Person p(name, i + 20);
		v.push_back(p);
	}
	cout << "没有打乱前:" << endl;
	for_each(v.begin(), v.end(), my_print);

	// 随机打乱容器中元素的排序
	random_shuffle(v.begin(), v.end());


	cout << "打乱后:" << endl;
	for_each(v.begin(), v.end(), my_print);
}

int main()
{
	test();

	system("pause");
	return 0;
}

第二个重载示例

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

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

// 自定义随机数生成器函数
class RandomGenerator
{
public:
	int operator()(int n) 
	{
		return rand() % n;
	}
};

void my_print(const Person& p)
{
	cout << "姓名:" << p.name << " 年龄:" << p.age << endl;
}

void test()
{
	srand((unsigned int)time (NULL));
	string str_name = "abcdefghi";
	vector<Person> v;
	for (int i = 0; i < 9; i++)
	{
		string names = "姓名";
		string name = names + str_name[i];
		Person p(name, i + 20);
		v.push_back(p);
	}
	cout << "没有打乱前:" << endl;
	for_each(v.begin(), v.end(), my_print);

	// 随机打乱容器中元素的排序
	random_shuffle(v.begin(), v.end(), RandomGenerator());


	cout << "打乱后:" << endl;
	for_each(v.begin(), v.end(), my_print);
}

int main()
{
	test();

	system("pause");
	return 0;
}

4.3、merge:合并两个有序序列

作用:把两个有相同排序规则的有序序列容器中的元素合并到目标容器中,合并后的目标容器也会按照同样的排序规则排序

注意:一定要预留好目标容器的空间,用以存放合并的元素

该函数有两个重载

  • 第一个重载:以默认的升序作为排序规则

函数原型:

OutputIterator merge (InputIterator1 first1, InputIterator1 last1,
                      InputIterator2 first2, InputIterator2 last2,
                      OutputIterator result);

参数解释:

  • first1:第一个容器的起始迭代器
  • last1:第一个容器的结束迭代器
  • first2:第二个容器的起始迭代器
  • last2:第二个容器的结束迭代器
  • result:目标容器的起始迭代器

注意: 使用第一个重载时,[first1, last1) 和 [first2, last2) 指定区域内的元素必须支持 < 小于运算符

  • 第二个重载:以自定义的 comp 规则作为排序规则

函数原型:

OutputIterator merge (InputIterator1 first1, InputIterator1 last1,
                      InputIterator2 first2, InputIterator2 last2,
                      OutputIterator result, Compare comp);

参数解释:

  • first1:第一个容器的起始迭代器
  • last1:第一个容器的结束迭代器
  • first2:第二个容器的起始迭代器
  • last2:第二个容器的结束迭代器
  • result:目标容器的起始迭代器
  • comp:用户自定义的排序规则,二元谓词,用普通函数的方式还是函数对象的方式皆可

注意: 使用第二个重载时,[first1, last1) 和 [first2, last2) 指定区域内的元素必须支持 comp 排序规则内的比较运算符。

示例1:第一种重载

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


void test()
{
	vector<int> v1;
	vector<int> v2;
	vector<int> v3;
	
	for (int i = 0; i < 9; i++)
	{
		v1.push_back(i);
		v2.push_back(i+1);
	}
	// 注意:一定要预留足够的空间存放元素
	v3.resize(v1.size() + v2.size());

	// 合并v1和v2两个容器中的元素到v3中,且排序规则也一致
	merge(v1.begin(), v1.end(), v2.begin(), v2.end(), v3.begin());

	// 打印容器中的元素
	for (vector<int>::iterator it = v3.begin(); it != v3.end(); it++)
	{
		cout << *it << " ";
	}
	cout << endl;
}

int main()
{
	test();

	system("pause");
	return 0;
}

示例2:第二种重载

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

// 定义排序规则,也可以使用函数对象的方式
bool my_sort(int val1, int val2)
{
	return val1 > val2;
}

void test()
{
	vector<int> v1;
	vector<int> v2;
	vector<int> v3;
	
	for (int i = 9; i >0; i--)
	{
		v1.push_back(i);
		v2.push_back(i+1);
	}
	// 注意:一定要预留足够的空间存放元素
	v3.resize(v1.size() + v2.size());

	// 合并v1和v2两个容器中的元素到v3中,且排序规则也一致
	merge(v1.begin(), v1.end(), v2.begin(), v2.end(), v3.begin(), my_sort);

	// 打印容器中的元素
	for (vector<int>::iterator it = v3.begin(); it != v3.end(); it++)
	{
		cout << *it << " ";
	}
	cout << endl;
}

int main()
{
	test();

	system("pause");
	return 0;
}

4.4、reverse:反转容器中元素的顺序

函数原型:reverse(RandomAccessIterator first, RandomAccessIterator last)

需要的参数:起始迭代器和结束迭代器

示例:

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


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

	// 打印容器中的元素
	cout << "反转前:" << endl;
	for (vector<int>::iterator it = v.begin(); it != v.end(); it++)
	{
		cout << *it << " ";
	}
	cout << endl;

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

int main()
{
	test();

	system("pause");
	return 0;
}

5、常用拷贝和替换算法

  • copy // 将指定范围的元素拷贝到另一个容器中
  • replace // 将容器中指定范围的旧元素改为新元素
  • replace_if // 将容器内指定范围内满足条件的元素替换为新元素
  • swap // 互换两个容器的元素

5.1、copy:拷贝元素到另一个容器中

函数原型:copy(iterator begin, iterator end, iterator dest);

参数:

  • begin:起始迭代器
  • end:结束迭代器
  • dest:目标容器起始迭代器

注意:目标容器预留好存放元素的空间

示例:

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


void test()
{
	vector<int> v;
	vector<int> v1;
	
	for (int i = 0; i < 9; i++)
	{
		v.push_back(i);
	}
	// 目标容器记得预留足够的空间
	v1.resize(v.size());

	// 拷贝
	copy(v.begin(), v.end(), v1.begin());

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

int main()
{
	test();

	system("pause");
	return 0;
}

5.2、replace:替换范围指定的元素

作用:替换指定范围内的旧元素为新元素

函数原型:replace(iterator begin, iterator end, type old_value, type new_vlaue)

参数:

  • begin:起始迭代器
  • end:结束迭代器
  • old_value:要被替换的元素
  • new_value:要替换的元素

注意:替换自定义数据类型时要重载 == 操作符,形参列表使用const修饰

函数源码:

template <class _FwdIt, class _Ty>
_CONSTEXPR20 void replace(const _FwdIt _First, const _FwdIt _Last, const _Ty& _Oldval, const _Ty& _Newval) {
    // replace each matching _Oldval with _Newval
    _Adl_verify_range(_First, _Last);
    auto _UFirst      = _Get_unwrapped(_First);
    const auto _ULast = _Get_unwrapped(_Last);
    
    // 循环容器中每一个元素
    for (; _UFirst != _ULast; ++_UFirst) {
        // 当容器中的元素等于要替换的值时判断为true
        if (*_UFirst == _Oldval) {
            // 把该元素赋值为新的值
            *_UFirst = _Newval;
        }
    }
}

内置数据类型的示例:

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


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

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

	// 把指定范围内值为2的元素替换为值为200的元素
	replace(v.begin(), v.end(), 2, 200);
	cout << "替换后:" << endl;
	for (vector<int>::iterator it = v.begin(); it != v.end(); it++)
	{
		cout << *it << " ";
	}
	cout << endl;
}

int main()
{
	test();

	system("pause");
	return 0;
}

自定义数据类型的示例

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

class Person
{
public:
	Person(int age)
	{
		this->age = age;
	}

	bool operator==(const Person& p)
	{
		return this->age == p.age;
	}

	int age;
};

void test()
{
	vector<Person> v;
	
	for (int i = 0; i < 9; i++)
	{
		Person p(i + 20);
		v.push_back(p);
	}

	cout << "替换前:" << endl;
	for (vector<Person>::iterator it = v.begin(); it != v.end(); it++)
	{
		cout << it->age << " ";
	}
	cout << endl;

	Person p1(33);
	Person p2(20);
	// 把指定范围内值为年龄为20的元素替换为值为年龄为33的元素
	replace(v.begin(), v.end(), p2, p1);
	cout << "替换后:" << endl;
	for (vector<Person>::iterator it = v.begin(); it != v.end(); it++)
	{
		cout << it->age << " ";
	}
	cout << endl;
}

int main()
{
	test();

	system("pause");
	return 0;
}

5.3、replace_if:按条件替换指定范围的元素

作用:按条件替换指定范围内的元素

函数原型:repalce_if(iterator begin, iterator end, pr pred, type new_value)

参数解释:

  • begin:起始迭代器
  • end:结束迭代器
  • Pred:一元谓词
  • new_value:要替换的新元素

函数源码:

template <class _FwdIt, class _Pr, class _Ty>
_CONSTEXPR20 void replace_if(const _FwdIt _First, const _FwdIt _Last, _Pr _Pred, const _Ty& _Val) {
    // replace each satisfying _Pred with _Val
    _Adl_verify_range(_First, _Last);
    auto _UFirst      = _Get_unwrapped(_First);
    const auto _ULast = _Get_unwrapped(_Last);
    
    
    for (; _UFirst != _ULast; ++_UFirst) {
        if (_Pred(*_UFirst)) {
            *_UFirst = _Val;
        }
    }
}

示例:内置数据类型和自定义数据类型使用方法是一样的,不用重载符号

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

class Person
{
public:
	Person(int age)
	{
		this->age = age;
	}

	int age;
};

// 普通函数或者函数对象都可以
bool my_re(const Person& p)
{
	return p.age > 25;
}

void test()
{
	vector<Person> v;
	
	for (int i = 0; i < 9; i++)
	{
		Person p(i + 20);
		v.push_back(p);
	}

	cout << "替换前:" << endl;
	for (vector<Person>::iterator it = v.begin(); it != v.end(); it++)
	{
		cout << it->age << " ";
	}
	cout << endl;

	Person p(33);
	// 把指定范围内值为年龄大于25的元素替换为值为年龄为33的元素
	replace_if(v.begin(), v.end(), my_re, p);
	cout << "替换后:" << endl;
	for (vector<Person>::iterator it = v.begin(); it != v.end(); it++)
	{
		cout << it->age << " ";
	}
	cout << endl;
}

int main()
{
	test();

	system("pause");
	return 0;
}

5.4、swap:互换两个容器中的所有元素

注意:一定要是两个相同类型的容器,例如两个vector容器

函数原型:swap(container c1, container c2)

参数解释:

  • c1:第一个容器
  • c2:第二个容器

示例:

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

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

void test()
{
	vector<int> v1;
	vector<int> v2;
	
	for (int i = 0; i < 9; i++)
	{
		v1.push_back(i);
		v2.push_back(i + 100);
	}

	cout << "交换前----------------------------------" << endl;
	cout << "v1: ";
	my_print(v1);
	cout << "v2: ";
	my_print(v2);

	cout << "交换后-----------------------------------" << endl;
	// 交互两个容器中的元素
	swap(v1, v2);
	cout << "v1: ";
	my_print(v1);
	cout << "v2: ";
	my_print(v2);
}

int main()
{
	test();

	system("pause");
	return 0;
}

6、常用算术生成算法

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

  • accumulate // 累加
  • fill // 向容器中添加元素

6.1、accumulate:计算容器内所以元素的总和

accumulate定义在#include<numeric>中,作用有两个,一个是累加求和,另一个是自定义类型数据的处理

1、累加求和

accumulate函数将它的一个内部变量设置为指定的初始值,然后在此初值上累加输入范围内所有元素的值。accumulate算法返回累加的结果,其返回类型就是其第三个实参的类型

可以使用accumulate把string型的vector容器中的元素连接起来:

string sum = accumulate(v.begin() , v.end() , string(" "));  

这个函数调用的效果是:从空字符串开始,把vector里的每个元素连接成一个字符串。

函数原型:Accumulate(_InIt _First, _InIt _Last, _Ty _Val)

参数解释:

  • first:起始迭代器
  • last:结束迭代器
  • val:累加的初值(例如容器中有{1,2,3}三个数,累加初值为5,那么结果为:5+1+2+3)

示例:

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

void test()
{
	vector<int> v;
	
	for (int i = 0; i < 9; i++)
	{
		v.push_back(i + 100);
	}

	// 计算容器内所有元素的总和
	int num = accumulate(v.begin(), v.end(), 0);

	cout << num << endl;
}

int main()
{
	test();

	system("pause");
	return 0;
}

2、自定义数据类型的处理

函数原型:Accumulate(_InIt _First, _InIt _Last, _Ty _Val, _Fn2 _Func)

参数解释:

  • first:起始迭代器
  • last:结束迭代器
  • val:累加的初值(例如容器中有{1,2,3}三个数,累加初值为5,那么结果为:5+1+2+3)
  • func:回调函数
    • 返回值类型:与第三个参数保持一致
    • 形参列表:接收两个参数(第一个参数类型与accumulate函数的第三个参数保持一致,第二个参数类型与容器内元素的数据类型保持一致)

函数源码:

template<class _InIt, class _Ty,  class _Fn2> 
inline _Ty _Accumulate(_InIt _First, _InIt _Last, _Ty _Val, _Fn2 _Func)  
{   // return sum of _Val and all in [_First, _Last), using _Func  
    
    // for循环容器中的每一个元素
    for (; _First != _Last; ++_First)  
        // 把初值变量指向的值和容器中的一个元素传入回调函数中,再把函数返回的值重新赋值给初值变量
        _Val = _Func(_Val, *_First);  
    return (_Val);  
}  

示例

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

class Person
{
public:
	Person(int age)
	{
		this->age = age;
	}
	int age;
};

int my_sum(int a, const Person& p2)
{
	return a + p2.age;
}

void test()
{
	vector<Person> v;
	
	for (int i = 0; i < 9; i++)
	{
		v.push_back(i + 20);
	}

	// 计算容器内所有元素的年龄的总和
	int num = accumulate(v.begin(), v.end(), 0, my_sum);

	cout << num << endl;
}

int main()
{
	test();

	system("pause");
	return 0;
}

6.2、fill:替换范围内所有的元素

作用:把指定范围内的所有元素都替换为指定的元素

函数原型:fill(iterator begin, iterator end, type value)

参数解释:

  • 起始迭代器
  • end:结束迭代器
  • value:要替换的元素

示例:

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


void test()
{
	vector<int> v;

	for (int i = 0; i < 9; i++)
	{
		v.push_back(i);
	}

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

	// 把指定范围内的元素全部替换为值为200的元素
	fill(v.begin(), v.end(),  200);
	cout << "替换后:" << endl;
	for (vector<int>::iterator it = v.begin(); it != v.end(); it++)
	{
		cout << *it << " ";
	}
	cout << endl;
}

int main()
{
	test();

	system("pause");
	return 0;
}

7、常用集合算法

  • set_intersection // 求两个容器的交集
  • set_union // 求两个容器的并集
  • set_difference // 求两个容器的差集

需要包含头文件:#include<algorithm>

7.0、三个函数的使用方式与条件基本一致,所以只详细写一个交集

注意:

  • 求差集时注意谁是谁的差集,目标容器预留位置大小为较大的容器容量
  • 并集目标容器预留位置大小为两个容器容量相加
  • 交集预留位置大小为较小的容器容量

7.1、set_intersection:交集

作用:把两个有相同排序规则的容器的交集元素放到一个新的容器(目标容器)中,且目标容器的排序规则与两个旧容器的排序规则一致

注意:

  • 求交集的容器必须为有序序列,如果使用第一个重载,则容器内排序规则必须为升序
  • 如果两个容器排序规则不为升序或自定义数据类型,就要使用第二个重载,并且把容器的排序规则使用普通函数或者函数对象法的方式传入
  • 目标容器记得预留位置,预留位置大小为较小的容器容量
  • 如果想只打印所有的交集元素,就使用set_intersection函数返回的迭代器作为结束迭代器

该函数有两个重载

  • 第一个重载:适用于内置数据类型,且容器排序规则是升序

函数原型:set_intersection(iterator begin1, iterator end1, iterator begin2, iterator end2, iterator dest)

参数解释:

  • begin1:第一个容器的起始迭代器
  • end1:第一个容器的结束迭代器
  • begin2:第二个容器的起始迭代器
  • end2:第二个容器的结束迭代器
  • dest:目标容器的起始迭代器

返回值:目标容器最后一个元素的迭代器

函数源码:

template <class InputIterator1, class InputIterator2, class OutputIterator>
OutputIterator set_intersection (
    InputIterator1 first1, InputIterator1 last1,
    InputIterator2 first2, InputIterator2 last2,
    OutputIterator result
)
{
    
  // 无限循环,直到两个迭代器的起始迭代器都等于结束迭代器时结束循环
  while (first1!=last1 && first2!=last2)
  {
    // 比对两个容器的起始迭代器指向的元素
      
    // 当第一个容器的起始迭代器指向的元素大于第二个容器的起始迭代器指向的元素时,第一个容器的起始迭代器进行自增,然后再进行循环
    if (*first1<*first2) ++first1;
      
    // 当第二个容器的起始迭代器指向的元素大于第一个容器的起始迭代器指向的元素时,第二个容器的起始迭代器进行自增,然后再进行循环
    else if (*first2<*first1) ++first2;
      
    // 直到两个起始迭代器指向的元素相等时
    else {
      // 说明找到了两个容器中相交的地方,把该元素赋值给目标容器的起始迭代器
      *result = *first1;
      // 再把三个容器的起始迭代器都进行自增,然后再进行循环
      ++result; ++first1; ++first2;
    }
  }
  // 返回值是目标容器最后添加的一个元素的后一个位置,类似结束迭代器
  return result;
}
  • 第二个重载:适用于自定义数据类型或容器排序规则不为升序的

函数原型:set_intersection(iterator begin1, iterator end1, iterator begin2, iterator end2, iterator dest, pr pred)

参数解释:

  • begin1:第一个容器的起始迭代器
  • end1:第一个容器的结束迭代器
  • begin2:第二个容器的起始迭代器
  • end2:第二个容器的结束迭代器
  • dest:目标容器的起始迭代器
  • pred:排序规则

返回值:目标容器最后一个元素的迭代器

注意:自定义数据类型使用该函数需要有一个默认构造,即使里面什么也不写

函数源码:

template <class _InIt1, class _InIt2, class _OutIt, class _Pr>
_CONSTEXPR20 _OutIt set_intersection(
    _InIt1 _First1, _InIt1 _Last1, _InIt2 _First2, _InIt2 _Last2, _OutIt _Dest, _Pr _Pred) {
    // AND sets [_First1, _Last1) and [_First2, _Last2)
    _Adl_verify_range(_First1, _Last1);
    _Adl_verify_range(_First2, _Last2);
    auto _UFirst1      = _Get_unwrapped(_First1);
    const auto _ULast1 = _Get_unwrapped(_Last1);
    auto _UFirst2      = _Get_unwrapped(_First2);
    const auto _ULast2 = _Get_unwrapped(_Last2);
    _DEBUG_ORDER_SET_UNWRAPPED(_InIt2, _UFirst1, _ULast1, _Pred);
    _DEBUG_ORDER_SET_UNWRAPPED(_InIt1, _UFirst2, _ULast2, _Pred);
    auto _UDest = _Get_unwrapped_unverified(_Dest);
    
    // 逻辑与第一个重载的源码基本类似,只不过判断条件变为自己提供的规则,不是默认的大于
    while (_UFirst1 != _ULast1 && _UFirst2 != _ULast2) {
        if (_DEBUG_LT_PRED(_Pred, *_UFirst1, *_UFirst2)) {
            ++_UFirst1;
        } else if (_Pred(*_UFirst2, *_UFirst1)) {
            ++_UFirst2;
        } else {
            *_UDest = *_UFirst1;
            ++_UDest;
            ++_UFirst1;
            ++_UFirst2;
        }
    }

    _Seek_wrapped(_Dest, _UDest);
    return _Dest;
}

内置数据类型,且升序排序示例:

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

// 函数对象的方式,自定义打印规则
class My_print
{
public:
	void operator()(int val)
	{
		cout << val << " ";
	}
};

// 打印容器中的所有元素
void print_vector(vector<int>& v)
{
	for_each(v.begin(), v.end(), My_print());
	cout << endl;
}

void test()
{
	vector<int> v1;
	vector<int> v2;
	for (int i = 0; i < 10; i++)
	{
		v1.push_back(i);
		v2.push_back(i + 5);
	}
	cout << "求下面两个容器的交集:" << endl;
	print_vector(v1);
	print_vector(v2);

	vector<int> v3;
	// 扩充目标容器,假设最特殊的情况:一个容器完全包含另一个容器所有的值,所以扩容的空间为:两个容器中较小的那个
	v3.resize(v1.size() < v2.size() ? v1.size() : v2.size());

	// 返回目标容器最后一个元素的迭代器位置
	vector<int>::iterator it_end = set_intersection(v1.begin(), v1.end(), v2.begin(), v2.end(), v3.begin());

	// 想要只打印所有交集元素,就使用函数返回的迭代器作为结束迭代器
	cout << "两个容器的交集为:" << endl;
	for (vector<int>::iterator it = v3.begin(); it != it_end; it++)
	{
		cout << *it << " ";
	}
	cout << endl;
}

int main()
{
	test();

	system("pause");
	return 0;
}

自定义数据类型或者排序规则不为升序排序示例

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

// 创建自定义数据
class Person
{
public:
	// 需要提供默认构造
	Person(){}
	Person(string name, int age)
	{
		this->name = name;
		this->age = age;
	}

	string name;
	int age;
};

// 函数对象的方式,自定义打印规则
class My_print
{
public:
	void operator()(const Person& p)
	{
		cout << "姓名:" << p.name << "  年龄:" << p.age << endl;
	}
};

// 函数对象的方式,自定义排序规则
class My_sort
{
public:
	bool operator()(const Person& p1, const Person& p2)
	{
		return p1.age > p2.age;
	}
};

// 打印容器中的所有元素
void print_vector(vector<Person>& v)
{
	for_each(v.begin(), v.end(), My_print());
	cout << endl;
}

void test()
{
	vector<Person> v1;
	vector<Person> v2;
	string names = "abcdefghijklmn";
	string str = "姓名-";
	for (int i = 0; i < 10; i++)
	{
		int age1 = rand() % 10 + 20;
		int age2 = rand() % 10 + 20;
		Person p1(str + names[i], age1);
		Person p2(str + names[i], age2);
		v1.push_back(p1);
		v2.push_back(p2);
	}
	cout << "求下面两个容器的交集:" << endl;
	sort(v1.begin(), v1.end(), My_sort());
	sort(v2.begin(), v2.end(), My_sort());
	// 先对容器中的元素进行排序
	print_vector(v1);
	print_vector(v2);

	vector<Person> v3;
	// 扩充目标容器,假设最特殊的情况:一个容器完全包含另一个容器所有的值,所以扩容的空间为:两个容器中较小的那个
	v3.resize(v1.size() < v2.size() ? v1.size() : v2.size());

	// 最后一个参数写两个旧容器的排序规则,目标容器内的元素也会按照该排序规则排序
	// 返回目标容器最后一个元素的迭代器位置
	vector<Person>::iterator it_end = set_intersection(v1.begin(), v1.end(), v2.begin(), v2.end(), v3.begin(), My_sort());

	// 想要只打印所有交集元素,就使用函数返回的迭代器作为结束迭代器
	cout << "两个容器的交集为:" << endl;
	for (vector<Person>::iterator it = v3.begin(); it != it_end; it++)
	{
		cout << "姓名:" << it->name << "  年龄:" << it->age << endl;
	}
	cout << endl;

	// 打印结果
	/*
	求下面两个容器的交集:
	姓名:姓名-c  年龄:29
	姓名:姓名-d  年龄:28
	姓名:姓名-j  年龄:27
	姓名:姓名-f  年龄:25
	姓名:姓名-i  年龄:25
	姓名:姓名-b  年龄:24
	姓名:姓名-e  年龄:22
	姓名:姓名-a  年龄:21
	姓名:姓名-g  年龄:21
	姓名:姓名-h  年龄:21

	姓名:姓名-d  年龄:28
	姓名:姓名-a  年龄:27
	姓名:姓名-g  年龄:27
	姓名:姓名-j  年龄:26
	姓名:姓名-f  年龄:25
	姓名:姓名-c  年龄:24
	姓名:姓名-e  年龄:24
	姓名:姓名-i  年龄:22
	姓名:姓名-h  年龄:21
	姓名:姓名-b  年龄:20

	两个容器的交集为:
	姓名:姓名-d  年龄:28
	姓名:姓名-j  年龄:27
	姓名:姓名-f  年龄:25
	姓名:姓名-b  年龄:24
	姓名:姓名-e  年龄:22
	姓名:姓名-a  年龄:21

	请按任意键继续. . .
	*/
}

int main()
{
	test();

	system("pause");
	return 0;
}

七、演讲比赛小项目

1、项目简介

  • 学校举行一场演讲比赛,一共12个人参加,比赛共两轮,第一轮为淘汰赛,第二轮为决赛
  • 每名选手都有对应的编号,如10001~10012
  • 比赛方式:分组比赛,每组六人
  • 第一轮分为两个小组,整体按照选手编号进行抽签后顺序演讲
  • 十个评委分别给每名选手打分,去除最高分和最低分,求得平均数为本轮成绩
  • 当小组演讲结束后,淘汰组内排名最后的三明选手,前三名晋级,进入下一轮的比赛
  • 第二轮为决赛,前三名胜出
  • 每轮比赛展示选手信息

2、创建入口文件

创建源文件:main.cpp

#include<iostream>
using namespace std;

int main()
{
	system("pause");
	return 0;
}

3、创建管理类

功能

  • 提供菜单界面与用户交互
  • 对演讲比赛流程进行控制
  • 与文件的读写交互

创建头文件和源文件:Admin

头文件

#pragma once
#include<iostream>
using namespace std;

class Admin
{
public:
	// 构造函数
	Admin();

	// 析构函数
	~Admin();
};

源文件

#include "admin.h"

Admin::Admin() {};

Admin::~Admin() {};

4、菜单功能

作用:展示菜单,与用户交互

在管理类中声明并实现该功能

// 菜单功能
void Admin::show_menu()
{
	cout << "*****欢迎来到演讲比赛现场*****" << endl;
	cout << "-----------------------------" << endl;
	cout << "=====1、开始进行演讲比赛=====" << endl;
	cout << "=====2、查看往期比赛记录=====" << endl;
	cout << "=====3、清空往期比赛记录=====" << endl;
	cout << "=====4、退出演讲比赛程序=====" << endl;
	cout << "-----------------------------" << endl;
}

在main函数中测试该功能

#include<iostream>
using namespace std;
#include "admin.h"

int main()
{
	Admin admin;
	admin.show_menu();

	system("pause");
	return 0;
}

5、退出功能

在管理员类中实现退出功能

// 退出功能
void Admin::exit_system()
{
	cout << "欢迎下次使用!!!" << endl;
	exit(0);
}

main函数中接收用户输入,返回不同功能

int main()
{
	Admin admin;
	// 接收用户输入
	int choice;
	while(true)
	{
		// 展示菜单
		admin.show_menu();

		cout << "输入想操作的功能的编码:";
		cin >> choice;
		switch (choice)
		{
		case 1:
			// 开始演讲比赛
			break;
		case 2:
			// 查看往期比赛记录
			break;
		case 3:
			// 清空比赛记录
			break;
		case 4:
			// 退出当前程序
			admin.exit_system();
			break;
		default:
			// 输入其他数字时清屏
			system("cls");
			break;
		}
	}

	system("pause");
	return 0;
}

6、创建选手类

类中包含:姓名和分数

添加头文件speaker.h

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

class Speaker
{
public:
	// 姓名
	string name;

	// 分数,浮点型,避免出现分数一样的情况,数组类型,因为有两轮比赛
	double scores[2];
};

7、比赛容器

在管理员类中添加比赛用到的容器

// 该容器存放所有参赛选手的编号
vector<int> v1;

// 该容器存放第一轮晋级的选手编号
vector<int> v2;

// 该容器存放胜出的前三名选手
vector<int> v3;

// 存放编号以及对应的具体选手
map<int, Speaker> speaker;

// 该属性记录比赛轮数
int index;

在管理员类中添加初始化函数

// 初始化属性函数
void Admin::init_stats()
{
	// 初始化容器为空
	this->v1.clear();
	this->v2.clear();
	this->v3.clear();
	this->speaker.clear();

	// 初始化比赛轮数
	this->index = 1;
}

在构造函数中调用初始化属性函数

// 构造函数
Admin::Admin() 
{
	// 调用初始化属性函数
	init_stats();
};

8、创建选手功能

在管理员类中添加create_speaker函数

// 创建参赛选手功能
void Admin::create_speaker()
{
	string names = "ABCDEFGHIJKL";
	for (int i = 0; i < names.size(); i++)
	{
		// 创建参赛选手对象
		Speaker sp;
		// 生成参赛选手姓名
		string name = "姓名";
		name += names[i];
		sp.name = name;
		// 初始化参数选手两轮比赛的分数
		sp.scores[0] = 0;
		sp.scores[1] = 0;

		// 把选手编号放到存放所有参赛选手编号的容器中
		this->v1.push_back(i + 10001);

		// 在map容器中添加元素,把编号作为key,具体的选手作为value
		this->speaker.insert(make_pair(i + 10001, sp));
	}
}

在构造函数中调用

// 构造函数
Admin::Admin() 
{
	// 调用初始化属性函数
	init_stats();

	// 调用创建选手的功能
	create_speaker();
};

9、开始比赛的函数

在管理员类中添加flow_speacker函数,先做一个伪实现

// 控制比赛流程
void Admin::flow_speacker()
{
	// 第一轮抽签
	
	// 第一轮比赛

	// 第一轮比赛结束

	// 第二轮抽签

	// 第二轮比赛
}

1、抽签(随机分组)

在管理员类中添加draw_lots函数

// 抽签
void Admin::draw_lots()
{
	// 随机数种子
	srand((unsigned int)time(NULL));

	// 判断这是第几轮比赛
	if (this->index == 1)
	{
		// 第一轮抽签
		cout << "==========第" << this->index << "轮抽签开始==========" << endl;
		// 随机打乱容器中的元素排序
		random_shuffle(this->v1.begin(), this->v1.end());

		// distance函数可以返回两个迭代器之间隔了几个元素
		int num = distance(this->v1.begin(), this->v1.end());

		// 循环打印第一组的选手编号,因为平均分为两组,所以结束迭代器自减num/2个数
		cout << "-----第一组选手:-----" << endl;
		int count1 = 1;
		for (vector<int>::iterator it = this->v1.begin(); it != this->v1.end() - num / 2; it++)
		{
			cout << "第" << count1 << "个演讲的选手:" << *it << endl;
			count1++;
		}
		cout << endl;

		// 循环打印第二组的选手编号,因为平均分为两组,所以起始迭代器自增num/2个数
		cout << "-----第二组选手:-----" << endl;
		int count2 = 1;
		for (vector<int>::iterator it = this->v1.begin() + num / 2; it != this->v1.end(); it++)
		{
			cout << "第" << count2 << "个演讲的选手:" << *it << endl;
			count2++;
		}
		cout << endl;
		cout << "抽签结束,按下任意键开始淘汰赛!!!!" << endl;
		cout << endl;
	}
	else
	{
		// 第二轮抽签
		cout << "==========第" << this->index << "轮抽签开始==========" << endl;
		// 随机打乱容器中的元素排序
		random_shuffle(this->v2.begin(), this->v2.end());

		// 循环打印容器中的元素
		int count = 1;
		for (vector<int>::iterator it = this->v2.begin(); it != this->v2.end(); it++)
		{
			cout << "第" << count << "个演讲的选手:" << *it << endl;
			count++;
		}
		cout << endl;
		cout << "抽签结束,按下任意键开始决赛!!!!" << endl;
		cout << endl;
	}
	system("pause");
}

2、评委打分

在管理员类中添加mark函数

// 评委打分
void Admin::mark(vector<int>::iterator it, vector<int>::iterator it_end, int index)
{
	// 函数参数:起始迭代器、结束迭代器、比赛论次

	// 记录这是第几个参赛选手
	int count = 1;
	// 临时容器:参赛选手的总成绩作为key,编号作为value,利用内建函数对象调整容器的排序规则为降序排序
	multimap<double, int, greater<double>> mm;

	// 形参接收起始迭代器和结束迭代器,循环范围内的元素
	for (; it != it_end; it++)
	{
		// 创建deque容器用了存放评委打的分数
		deque<double> d;

		cout << endl;
		cout << "第-" << count << "-位参赛选手 << " << *it << " >> 的打分如下:" << endl;
		count++;

		// 总共有十个评委打分,去掉最高分和最低分再求平均值
		for (int i = 0; i < 10; i++)
		{
			// 随机400~1000之间的数,再除以十,就可以得到40~100的随机小数,10后面加上.f表示这是小数不是整数,相除时就会保留小数部分
			double num = (rand() % 401 + 600) / 10.f;
			d.push_back(num);
			cout << " [" << num << "] ";
		}
		cout << endl;

		// 把deque容器按升序排序
		sort(d.begin(), d.end());
		// 获取容器中第一个元素和最后一个元素,即为最低分和最高分
		cout << "----------去掉最低分:" << d.front() << endl;
		cout << "----------去掉最高分:" << d.back() << endl;
		// 删除容器中第一个元素和最后一个元素,就相当于去掉了最高分和最低分
		d.pop_back();
		d.pop_front();
		// 使用accumulate函数累加容器中的所有元素,  0.f表示没有初始值且返回值类型为浮点型;最后再除以容器中的元素个数即为选手的最终得分
		double score = accumulate(d.begin(), d.end(), 0.f) / d.size();
		cout << "----------最终成绩为:" << score << endl;

		// speaker是一个map容器,选手编号为key,具体的选手为value
		// 传给该函数的迭代器类型是vector容器,而vector容器里面存放的是选手编号,所以可以使用map容器提供的[]方法获取key(选手的编号)对应的value(具体的选手)
		// index表示当前是第几轮比赛,scores是一个数组,里面放的是选手第一轮和第二轮的比赛成绩
		this->speaker[*it].scores[index - 1] = score;

		// mm是上面创建的临时容器,容器按照比赛成绩降序排序
		mm.insert(make_pair(score, *it));
		cout << "===============================================" << endl;
	}
	cout << endl;

	// 当index等于1时表示当前是淘汰赛
	if (index == 1)
	{
		cout << "淘汰赛晋级的选手有:" << endl;
		// 记录循环次数,因为只取前三名晋级
		int count = 0;
		for (multimap<double, int>::iterator it = mm.begin(); it != mm.end(); it++)
		{
			if (count == 3)
			{
				// 前三名取出来后结束循环
				break;
			}
			cout << "姓名:<< "<< this->speaker[it->second].name << " >> " <<" 比赛编号: << "<< it->second <<" >>   淘汰赛成绩:[" << it->first << "] " << endl;

			// 并把这三名选手放入存放决赛选手编号的容器内
			this->v2.push_back(it->second);
			count++;
		}
	}
	// 当index等于2时表示当前时决赛
	else
	{
		string strs[3] = { "冠军", "亚军", "季军" };
		int count = 0;
		cout << "我们比赛的最终获胜者是:" << endl;
		for (multimap<double, int>::iterator it = mm.begin(); it != mm.end(); it++)
		{
			if (count == 3)
			{
				break;
			}
			cout<<"-<< " << strs[count] << " >>-  姓名:<< " << this->speaker[it->second].name << " >> " << " 比赛编号: << " << it->second << " >>   决赛成绩:[" << it->first << "] " << endl;
			this->v3.push_back(it->second);
			count++;
		}
	}
}

3、开始比赛

在管理员类中添加start_speacker函数

void Admin::start_speacker()
{
	// 当index等于1说明此时是淘汰赛
	if (this->index == 1)
	{
		// 获取淘汰赛第一组的起始迭代器和结束迭代器
		vector<int>::iterator it_begin = this->v1.begin();
		vector<int>::iterator it_end = this->v1.end() - (this->v1.size() / 2);
		mark(it_begin, it_end, this->index);
		cout << endl;
		cout << "第一组淘汰赛结束,按任意键开始下一组的淘汰赛" << endl;
		system("pause");
		cout << endl;

		// 获取淘汰赛第二组的起始迭代器和结束迭代器
		it_begin = this->v1.begin() + (this->v1.size() / 2);
		it_end = this->v1.end();
		mark(it_begin, it_end, this->index);
		cout << endl;
		cout << "第二组淘汰赛结束,按任意键开始决赛的抽签" << endl;
		system("pause");
		cout << endl;
	}
	else
	{
		// 获取决赛
		vector<int>::iterator it_begin = this->v2.begin();
		vector<int>::iterator it_end = this->v2.end();
		mark(it_begin, it_end, this->index);
		cout << endl;
		cout << "决赛完美结束,在此恭喜我们的三位参赛选手!!!" << endl;
		system("pause");
		cout << endl;
	}
}

4、保存比赛数据到文件中

在管理员类中添加函数save

// 保存文件
void Admin::save()
{
	// 使用追加的方式打开文件
	ofstream ofs;
	ofs.open("speacker.csv", ios::out | ios::app);

	// 全部参赛人员的信息作为一整行写入文件中,每个信息使用逗号隔开
	for (vector<int>::iterator it = this->v1.begin(); it != this->v1.end(); it++)
	{
		ofs << speaker[*it].name << ","
			<< *it << ","
			<< speaker[*it].scores[0] << ","
			<< speaker[*it].scores[1] << ",";
	}
	ofs << endl;

	// 淘汰赛胜出的人员信息作为一整行写入文件中,每个信息使用逗号隔开
	for (vector<int>::iterator it = this->v2.begin(); it != this->v2.end(); it++)
	{
		ofs << speaker[*it].name << ","
			<< *it << ","
			<< speaker[*it].scores[0] << ","
			<< speaker[*it].scores[1] << ",";
	}
	ofs << endl;

	// 把决赛胜出的人员信息作为一整行写入文件中,每个信息使用逗号隔开
	for (vector<int>::iterator it = this->v3.begin(); it != this->v3.end(); it++)
	{
		ofs << speaker[*it].name << ","
			<< *it << ","
			<< speaker[*it].scores[0] << ","
			<< speaker[*it].scores[1] << ",";
	}
	ofs << endl;

	// 关闭文件
	ofs.close();
}

5、完善比赛流程的函数

// 控制比赛流程
void Admin::flow_speacker()
{
	// 第一轮抽签
	draw_lots();
	
	// 第一轮比赛
	start_speacker();

	// 第一轮比赛结束
	this->index += 1;

	// 第二轮抽签
	draw_lots();

	// 第二轮比赛
	start_speacker();

	// 比赛结束,保存比赛数据到文件中
	save();
}

10、展示往届比赛记录

1、读取文件

把保存放文件中的比赛数据读出来

先在管理员类中添加两个成员属性

// int表示第几次比赛记录,大的vector容器放每一次比赛的全部记录,小的vector容器放文件中一整行行的数据
map<int, vector<vector<string>>> previous_game;

// true表示文件存在且有内容,false表示文件不存在或文件为空
bool if_file;

在管理员类中添加函数read_flie

// 读取文件
void Admin::read_file()
{
	// 读取文件中的比赛信息
	ifstream ifs;
	ifs.open("speacker.csv", ios::in);

	// 判断文件是否存在
	if (!ifs.is_open())
	{
		// 文件不存或文件为空时if_file属性为false
		this->if_file = false;
		return;
	}

	// 从文件中读取一个字符,判断是否是文件结束的标志,如果是说明文件为空
	char ch;
	ifs >> ch;
	if (ifs.eof())
	{
		// 文件不存或文件为空时if_file属性为false
		this->if_file = false;
		return;
	}

	// 文件中有内容
	this->if_file = true;
	// 把上面读走的一个字符读回来,否则下面读数据时少一个字符
	ifs.putback(ch);

	// 用来存放文件中一整行的数据
	string date;
	// 读取文件的行数,每读完一行数据进行自增
	int num = 0;
	// previous_game容器的key值,记录这是第几次比赛的记录
	int index = 1;
	// 作为previous_game容器的value值,存放当次比赛的所有数据
	vector<vector<string>> v_big;
	// 一次读文件中一整行的数据,并赋值给date
	while (ifs >> date)
	{
		// 记录逗号第一次出现的位置
		int pos = -1;
		// 记录开始截取字符的位置
		int start = 0;
		// 存放文件中一行所有的数据,通过逗号分隔截取的字符作为容器的元素
		vector<string> v_sm;
		while (true) 
		{
			// 从start位置开始,从左往右找第一个逗号出现的位置
			pos = date.find(",", start);
			// 当find函数没有找到想要的字符时则会返回-1,说明这一行的数据都读取完了,直接结束循环
			if (pos == -1)
			{
				break;
			}
			// substr从一个位置开始,截取指定长度的字符串
			// pos为当前逗号的位置,在减去开始查找逗号的位置就是要截取的字符串长度,start是开始截取字符串的位置
			string str = date.substr(start, pos - start);
			// 把截取的字符串作为一个元素放入存放一行数据的容器中
			v_sm.push_back(str);
			// 下一次循环要查找下一个被逗号分隔开的元素了,所以开始查找的位置要变成当前逗号所在位置再加一
			start = pos + 1;
		}
		// 把存放一行数据的容器放入存放当前比赛全部数据的容器中
		v_big.push_back(v_sm);
		// 读完一整行数据了,循环结束
		num++;
		// 当循环次数除以三没有余数时,说明刚好读完了三行数据,也就是一次比赛的全部记录已经读完了
		if (num % 3 == 0)
		{
			// 把比赛次数作为previous_game容器的key,当前比赛的全部记录作为value添加进previous_game容器
			this->previous_game.insert(make_pair(index, v_big));
			// 把记录当前比赛所有数据的容器清空,准备记录下一次比赛的全部数据
			v_big.clear();
			// 比赛次数也进行自增
			index++;
		}
	}
	// 关闭文件
	ifs.close();
}

2、展示比赛数据前期准备

在构造函数中添加读取文件函数

// 构造函数
Admin::Admin() 
{
	// 调用初始化属性函数
	init_stats();

	// 调用创建选手的功能
	create_speaker();

	// 读取文件中的数据
	read_file();
};

在初始化属性的函数中把previous_game容器置空

// 初始化属性功能
void Admin::init_stats()
{
	// 初始化容器为空
	this->v1.clear();
	this->v2.clear();
	this->v3.clear();
	this->speaker.clear();

	// 初始化比赛轮数
	this->index = 1;

	// 初始化存放比赛数据的容器
	this->previous_game.clear();
}

3、展示往届比赛记录

在管理员类中添加函数show_old_speack函数

void Admin::show_old_speack()
{
	// if_file属性为false时文件不存在或者文件为空
	if (!this->if_file)
	{
		cout << "当前没有比赛记录" << endl;
		system("pause");
		return;
	}

	// 获取用户输入
	int choick;
	cout << "当前共有:" << this->previous_game.size() << "届大赛的记录" << endl;
	cout << endl;
	cout << "输入0查看全部的大赛记录,输入序号可查看对应的大赛记录";
	cin >> choick;

	string std[4] = { "姓名:","参赛编号:","淘汰赛成绩:","决赛成绩:" };
	string sor[3] = { "冠军:","亚军:","季军:" };


	// for循环previous_game容器
	for (map<int, vector<vector<string>>>::iterator it = this->previous_game.begin(); it != this->previous_game.end(); it++)
	{
		// 大的vector容器的迭代器
		vector<vector<string>>::iterator it1;
		if (choick == 0)
		{
			// 当用户输入0时,迭代器指向大的vector容器下的第一个小的vector容器,因为要展示全部的大赛信息,大的vector容器会随着for循环变化,直到展示完所以信息
			it1 = it->second.begin();
			cout << ">>>>>>>>>>>>>>>第" << it->first << "届演讲比赛记录<<<<<<<<<<<<<<<" << endl;
		}
		else if (choick > this->previous_game.size() || choick < 0)
		{
			// 用户输入不合法时结束函数并清屏
			cout << "没有第" << choick << "届比赛的记录" << endl;
			system("pause");
			system("cls");
			return;
		}
		else
		{
			// 当用户想看具体的某一届比赛记录时,通过用户输入的choice作为key找到previous_game容器对应的value,迭代器指向这个大的vector容器下的第一个小的vector容器
			it1 = this->previous_game[choick].begin();
			cout << ">>>>>>>>>>>>>>>第" << choick << "届演讲比赛记录<<<<<<<<<<<<<<<" << endl;
		}
		cout << "=====全部参赛的人员信息=====" << endl;

		// 记录循环次数
		int count = 0;
		// for循环大vector容器中的小vector容器的元素,第一个小vector容器中放的是所有参赛的选手信息,每四个元素表示一个完整的选手信息
		for (vector<string>::iterator it2 = it1->begin(); it2 != it1->end(); it2++)
		{
			// 根据for循环的次数取出std数组中的元素,作为vector容器中元素的解释
			cout << std[count] << *it2 << "\t ";
			count++;
			// 每四个元素表示一个完整的选手信息,所以在count等于4后重新归零
			if (count == 4)
			{
				count = 0;
				cout << endl;
			}
		}

		// 第一个小vector容器中的数据展示完毕,迭代器自增,开始展示下一个小的vector容器中的数据
		it1++;
		// 初始化循环次数为0
		count = 0;
		cout << endl;

		cout << "=====淘汰赛晋级人员信息=====" << endl;
		for (vector<string>::iterator it2 = it1->begin(); it2 != it1->end(); it2++)
		{
			cout << std[count] << *it2 << "\t ";
			count++;
			if (count == 4)
			{
				count = 0;
				cout << endl;
			}
		}
		it1++;
		count = 0;
		cout << endl;

		cout << "=====最终获胜者=====" << endl;
		int num = 0;
		for (vector<string>::iterator it2 = it1->begin(); it2 != it1->end(); it2++)
		{
			if (count == 0)
			{
				cout << "--*--" << sor[num] << "--*--" << endl;
				num++;
			}
			cout << std[count] << " [" << *it2 << "] " << "\t \t";
			count++;
			if (count == 4)
			{
				count = 0;
				cout << endl;
			}
		}
		cout << endl;
		if (choick != 0)
		{
			break;
		}
	}
	system("pause");
	system("cls");
}

4、完善比赛流程函数

更新成员属性:this->if_file 的状态为true

// 控制比赛流程
void Admin::flow_speacker()
{
	// 第一轮抽签
	draw_lots();
	
	// 第一轮比赛
	start_speacker();

	// 第一轮比赛结束
	this->index += 1;

	// 第二轮抽签
	draw_lots();

	// 第二轮比赛
	start_speacker();

	// 比赛结束,保存比赛数据到文件中
	save();

	// 更改成员属性if_file,文件中已经有数据了,改为true
	this->if_file = true;
}

11、清空往届比赛记录

在管理员类中添加函数clear_speack

// 清空往届比赛记录
void Admin::clear_speack()
{
	cout << "确定要清空所有比赛记录吗" << endl;
	cout << "1、确定" << endl;
	cout << "0、取消" << endl;
	int choick;
	cin >> choick;

	if (choick == 0)
	{
		return;
	}
	else if (choick != 1 && choick != 0)
	{
		cout << "输入正确的序号" << endl;
		system("pause");
		system("cls");
		return;
	}

	ofstream ofs;
	// 打开模式,如果文件存在则删除并重新创建一个
	ofs.open("speacker.csv", ios::trunc);
	ofs.close();

	// 更新状态
	this->if_file = false;
	cout << "清空成功!!" << endl;
	system("pause");
	system("cls");
}

12、完善main函数

int main()
{
	// 随机数种子放到main文件的最上面,这样可以保证该程序的所有随机数每次随机的数据都是不一样的
	srand((unsigned int)time(NULL));

	// 接收用户输入
	int choice;
	while(true)
	{
		Admin admin;
		// 展示菜单
		admin.show_menu();

		cout << "输入想操作的功能的编码:";
		cin >> choice;
		switch (choice)
		{
		case 1:
			// 开始演讲比赛
			admin.flow_speacker();
			break;
		case 2:
			admin.show_old_speack();
			// 查看往期比赛记录
			break;
		case 3:
			// 清空比赛记录
			admin.clear_speack();
			break;
		case 4:
			// 退出当前程序
			admin.exit_system();
			break;
		default:
			// 输入其他数字时清屏
			system("cls");
			break;
		}
	}

	system("pause");
	return 0;
}

13、源码

共四个文件

  • 两个头文件:admin.hspeaker.h
  • 两个源文件:admin.cppmain.cpp

admin.h

#pragma once
#include<iostream>
#include<vector>
#include<map>
#include<string>
#include<ctime>
#include<algorithm>
#include<deque>
#include<functional>
#include<numeric>
#include<fstream>
#include"speaker.h"
using namespace std;


class Admin
{
public:
	// 构造函数
	Admin();

	// 菜单功能
	void show_menu();

	// 退出程序
	void exit_system();

	// 初始化属性
	void init_stats();

	// 创建参数选手功能
	void create_speaker();

	// 控制比赛流程
	void flow_speacker();

	// 抽签
	void draw_lots();

	// 开始比赛
	void start_speacker();

	// 评委打分
	void mark(vector<int>::iterator it_begin, vector<int>::iterator it_end, int index);

	// 保存文件
	void save();

	// 读取文件
	void read_file();

	// 展示往界比赛
	void show_old_speack();

	// 清空往届比赛记录
	void clear_speack();

	// 析构函数
	~Admin();

	// 成员属性
	// 该容器存放所有参赛选手的编号
	vector<int> v1;

	// 该容器存放第一轮晋级的选手编号
	vector<int> v2;

	// 该容器存放胜出的前三名选手
	vector<int> v3;

	// 存放编号以及对应的具体选手
	map<int, Speaker> speaker;

	// 该属性记录比赛进行的轮数
	int index;

	// int表示第几次比赛记录,大的vector容器放每一次比赛的全部记录,小的vector容器放文件中一整行行的数据
	map<int, vector<vector<string>>> previous_game;

	// true表示文件存在且有内容,false表示文件不存在或文件为空
	bool if_file;
};

speaker.h

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

class Speaker
{
public:
	// 姓名
	string name;

	// 分数,浮点型,避免出现分数一样的情况,数组类型,因为有两轮比赛
	double scores[2];
};

admin.cpp

#include "admin.h"

// 构造函数
Admin::Admin() 
{
	// 调用初始化属性函数
	init_stats();

	// 调用创建选手的功能
	create_speaker();

	// 读取文件中的数据
	read_file();
};

// 菜单功能
void Admin::show_menu()
{
	cout << "*****************************" << endl;
	cout << "*****欢迎来到演讲比赛现场****" << endl;
	cout << "-----------------------------" << endl;
	cout << "=====1、开始进行演讲比赛=====" << endl;
	cout << "=====2、查看往期比赛记录=====" << endl;
	cout << "=====3、清空往期比赛记录=====" << endl;
	cout << "=====4、退出演讲比赛程序=====" << endl;
	cout << "-----------------------------" << endl;
}

// 退出功能
void Admin::exit_system()
{
	cout << "欢迎下次使用!!!" << endl;
	exit(0);
}

// 初始化属性功能
void Admin::init_stats()
{
	// 初始化容器为空
	this->v1.clear();
	this->v2.clear();
	this->v3.clear();
	this->speaker.clear();

	// 初始化比赛轮数
	this->index = 1;

	// 初始化存放比赛数据的容器
	this->previous_game.clear();
}

// 创建参赛选手功能
void Admin::create_speaker()
{
	string names = "ABCDEFGHIJKL";
	for (int i = 0; i < names.size(); i++)
	{
		// 创建参赛选手对象
		Speaker sp;
		// 生成参赛选手姓名
		string name = "选手";
		name += names[i];
		sp.name = name;
		// 初始化参数选手两轮比赛的分数
		sp.scores[0] = 0;
		sp.scores[1] = 0;

		// 把选手编号放到存放所有参赛选手编号的容器中
		this->v1.push_back(i + 10001);

		// 在map容器中添加元素,把编号作为key,具体的选手作为value
		this->speaker.insert(make_pair(i + 10001, sp));
	}
}

// 控制比赛流程
void Admin::flow_speacker()
{
	// 第一轮抽签
	draw_lots();
	
	// 第一轮比赛
	start_speacker();

	// 第一轮比赛结束
	this->index += 1;

	// 第二轮抽签
	draw_lots();

	// 第二轮比赛
	start_speacker();

	// 比赛结束,保存比赛数据到文件中
	save();

	// 更改成员属性if_file,文件中已经有数据了,改为true
	this->if_file = true;
}

// 抽签
void Admin::draw_lots()
{
	// 随机数种子
	srand((unsigned int)time(NULL));

	// 判断这是第几轮比赛
	if (this->index == 1)
	{
		// 第一轮抽签
		cout << "==========第 << " << this->index << " >> 轮抽签开始==========" << endl;
		// 随机打乱容器中的元素排序
		random_shuffle(this->v1.begin(), this->v1.end());

		// distance函数可以返回两个迭代器之间隔了几个元素
		int num = distance(this->v1.begin(), this->v1.end());

		// 循环打印第一组的选手编号,因为平均分为两组,所以结束迭代器自减num/2个数
		cout << "-----第一组选手:-----" << endl;
		int count1 = 1;
		for (vector<int>::iterator it = this->v1.begin(); it != this->v1.end() - num / 2; it++)
		{
			cout << "第" << count1 << "个演讲的选手:" << *it << endl;
			count1++;
		}
		cout << endl;

		// 循环打印第二组的选手编号,因为平均分为两组,所以起始迭代器自增num/2个数
		cout << "-----第二组选手:-----" << endl;
		int count2 = 1;
		for (vector<int>::iterator it = this->v1.begin() + num / 2; it != this->v1.end(); it++)
		{
			cout << "第" << count2 << "个演讲的选手:" << *it << endl;
			count2++;
		}
		cout << endl;
		cout << "抽签结束,按下任意键开始淘汰赛!!!!" << endl;
		cout << endl;
	}
	else
	{
		// 第二轮抽签
		cout << "==========第 << " << this->index << " >> 轮抽签开始==========" << endl;
		// 随机打乱容器中的元素排序
		random_shuffle(this->v2.begin(), this->v2.end());

		// 循环打印容器中的元素
		int count = 1;
		for (vector<int>::iterator it = this->v2.begin(); it != this->v2.end(); it++)
		{
			cout << "第" << count << "个演讲的选手:" << *it << endl;
			count++;
		}
		cout << endl;
		cout << "抽签结束,按下任意键开始决赛!!!!" << endl;
		cout << endl;
	}
	system("pause");
}

// 开始比赛
void Admin::start_speacker()
{
	// 当index等于1说明此时是淘汰赛
	if (this->index == 1)
	{
		// 获取淘汰赛第一组的起始迭代器和结束迭代器
		vector<int>::iterator it_begin = this->v1.begin();
		vector<int>::iterator it_end = this->v1.end() - (this->v1.size() / 2);
		mark(it_begin, it_end, this->index);
		cout << endl;
		cout << "第一组淘汰赛结束,按任意键开始下一组的淘汰赛" << endl;
		system("pause");
		cout << endl;

		// 获取淘汰赛第二组的起始迭代器和结束迭代器
		it_begin = this->v1.begin() + (this->v1.size() / 2);
		it_end = this->v1.end();
		mark(it_begin, it_end, this->index);
		cout << endl;
		cout << "第二组淘汰赛结束,按任意键开始决赛的抽签" << endl;
		system("pause");
		cout << endl;
	}
	else
	{
		// 获取决赛
		vector<int>::iterator it_begin = this->v2.begin();
		vector<int>::iterator it_end = this->v2.end();
		mark(it_begin, it_end, this->index);
		cout << endl;
		cout << "决赛完美结束,在此恭喜我们的三位参赛选手!!!" << endl;
		system("pause");
		system("cls");
		cout << endl;
	}
}

// 评委打分
void Admin::mark(vector<int>::iterator it, vector<int>::iterator it_end, int index)
{
	// 函数参数:起始迭代器、结束迭代器、比赛论次

	// 记录这是第几个参赛选手
	int count = 1;
	// 临时容器:参赛选手的总成绩作为key,编号作为value,利用内建函数对象调整容器的排序规则为降序排序
	multimap<double, int, greater<double>> mm;

	// 形参接收起始迭代器和结束迭代器,循环范围内的元素
	for (; it != it_end; it++)
	{
		// 创建deque容器用了存放评委打的分数
		deque<double> d;

		cout << endl;
		cout << "第-" << count << "-位参赛选手 << " << *it << " >> 的打分如下:" << endl;
		count++;

		// 总共有十个评委打分,去掉最高分和最低分再求平均值
		for (int i = 0; i < 10; i++)
		{
			// 随机400~1000之间的数,再除以十,就可以得到40~100的随机小数,10后面加上.f表示这是小数不是整数,相除时就会保留小数部分
			double num = (rand() % 401 + 600) / 10.f;
			d.push_back(num);
			cout << " [" << num << "] ";
		}
		cout << endl;

		// 把deque容器按升序排序
		sort(d.begin(), d.end());
		// 获取容器中第一个元素和最后一个元素,即为最低分和最高分
		cout << "----------去掉最低分:" << d.front() << endl;
		cout << "----------去掉最高分:" << d.back() << endl;
		// 删除容器中第一个元素和最后一个元素,就相当于去掉了最高分和最低分
		d.pop_back();
		d.pop_front();
		// 使用accumulate函数累加容器中的所有元素,  0.f表示没有初始值且返回值类型为浮点型;最后再除以容器中的元素个数即为选手的最终得分
		double score = accumulate(d.begin(), d.end(), 0.f) / d.size();
		cout << "----------最终成绩为:" << score << endl;

		// speaker是一个map容器,选手编号为key,具体的选手为value
		// 传给该函数的迭代器类型是vector容器,而vector容器里面存放的是选手编号,所以可以使用map容器提供的[]方法获取key(选手的编号)对应的value(具体的选手)
		// index表示当前是第几轮比赛,scores是一个数组,里面放的是选手第一轮和第二轮的比赛成绩
		this->speaker[*it].scores[index - 1] = score;

		// mm是上面创建的临时容器,容器按照比赛成绩降序排序
		mm.insert(make_pair(score, *it));
		cout << "===============================================" << endl;
	}
	cout << endl;

	// 当index等于1时表示当前是淘汰赛
	if (index == 1)
	{
		cout << "淘汰赛晋级的选手有:" << endl;
		// 记录循环次数,因为只取前三名晋级
		int count = 0;
		for (multimap<double, int>::iterator it = mm.begin(); it != mm.end(); it++)
		{
			if (count == 3)
			{
				// 前三名取出来后结束循环
				break;
			}
			cout << "姓名:<< "<< this->speaker[it->second].name << " >> " <<" 比赛编号: << "<< it->second <<" >>   淘汰赛成绩:[" << it->first << "] " << endl;

			// 并把这三名选手放入存放决赛选手编号的容器内
			this->v2.push_back(it->second);
			count++;
		}
	}
	// 当index等于2时表示当前时决赛
	else
	{
		string strs[3] = { "冠军", "亚军", "季军" };
		int count = 0;
		cout << "我们比赛的最终获胜者是:" << endl;
		for (multimap<double, int>::iterator it = mm.begin(); it != mm.end(); it++)
		{
			if (count == 3)
			{
				break;
			}
			cout<<"-<< " << strs[count] << " >>-  姓名:<< " << this->speaker[it->second].name << " >> " << " 比赛编号: << " << it->second << " >>   决赛成绩:[" << it->first << "] " << endl;
			this->v3.push_back(it->second);
			count++;
		}
	}
}

// 保存文件
void Admin::save()
{
	// 使用追加的方式打开文件
	ofstream ofs;
	ofs.open("speacker.csv", ios::out | ios::app);

	// 全部参赛人员的信息作为一整行写入文件中,每个信息使用逗号隔开
	for (vector<int>::iterator it = this->v1.begin(); it != this->v1.end(); it++)
	{
		ofs << speaker[*it].name << ","
			<< *it << ","
			<< speaker[*it].scores[0] << ","
			<< speaker[*it].scores[1] << ",";
	}
	ofs << endl;

	// 淘汰赛胜出的人员信息作为一整行写入文件中,每个信息使用逗号隔开
	for (vector<int>::iterator it = this->v2.begin(); it != this->v2.end(); it++)
	{
		ofs << speaker[*it].name << ","
			<< *it << ","
			<< speaker[*it].scores[0] << ","
			<< speaker[*it].scores[1] << ",";
	}
	ofs << endl;

	// 把决赛胜出的人员信息作为一整行写入文件中,每个信息使用逗号隔开
	for (vector<int>::iterator it = this->v3.begin(); it != this->v3.end(); it++)
	{
		ofs << speaker[*it].name << ","
			<< *it << ","
			<< speaker[*it].scores[0] << ","
			<< speaker[*it].scores[1] << ",";
	}
	ofs << endl;

	// 关闭文件
	ofs.close();
}

// 读取文件
void Admin::read_file()
{
	// 读取文件中的比赛信息
	ifstream ifs;
	ifs.open("speacker.csv", ios::in);

	// 判断文件是否存在
	if (!ifs.is_open())
	{
		// 文件不存或文件为空时if_file属性为false
		this->if_file = false;
		return;
	}

	// 从文件中读取一个字符,判断是否是文件结束的标志,如果是说明文件为空
	char ch;
	ifs >> ch;
	if (ifs.eof())
	{
		// 文件不存或文件为空时if_file属性为false
		this->if_file = false;
		return;
	}

	// 文件中有内容
	this->if_file = true;
	// 把上面读走的一个字符读回来,否则下面读数据时少一个字符
	ifs.putback(ch);

	// 用来存放文件中一整行的数据
	string date;
	// 读取文件的行数,每读完一行数据进行自增
	int num = 0;
	// previous_game容器的key值,记录这是第几次比赛的记录
	int index = 1;
	// 作为previous_game容器的value值,存放当次比赛的所有数据
	vector<vector<string>> v_big;
	// 一次读文件中一整行的数据,并赋值给date
	while (ifs >> date)
	{
		// 记录逗号第一次出现的位置
		int pos = -1;
		// 记录开始截取字符的位置
		int start = 0;
		// 存放文件中一行所有的数据,通过逗号分隔截取的字符作为容器的元素
		vector<string> v_sm;
		while (true) 
		{
			// 从start位置开始,从左往右找第一个逗号出现的位置
			pos = date.find(",", start);
			// 当find函数没有找到想要的字符时则会返回-1,说明这一行的数据都读取完了,直接结束循环
			if (pos == -1)
			{
				break;
			}
			// substr从一个位置开始,截取指定长度的字符串
			// pos为当前逗号的位置,在减去开始查找逗号的位置就是要截取的字符串长度,start是开始截取字符串的位置
			string str = date.substr(start, pos - start);
			// 把截取的字符串作为一个元素放入存放一行数据的容器中
			v_sm.push_back(str);
			// 下一次循环要查找下一个被逗号分隔开的元素了,所以开始查找的位置要变成当前逗号所在位置再加一
			start = pos + 1;
		}
		// 把存放一行数据的容器放入存放当前比赛全部数据的容器中
		v_big.push_back(v_sm);
		// 读完一整行数据了,循环结束
		num++;
		// 当循环次数除以三没有余数时,说明刚好读完了三行数据,也就是一次比赛的全部记录已经读完了
		if (num % 3 == 0)
		{
			// 把比赛次数作为previous_game容器的key,当前比赛的全部记录作为value添加进previous_game容器
			this->previous_game.insert(make_pair(index, v_big));
			// 把记录当前比赛所有数据的容器清空,准备记录下一次比赛的全部数据
			v_big.clear();
			// 比赛次数也进行自增
			index++;
		}
	}
	// 关闭文件
	ifs.close();
}

// 展示往界比赛数据
void Admin::show_old_speack()
{
	// if_file属性为false时文件不存在或者文件为空
	if (!this->if_file)
	{
		cout << "当前没有比赛记录" << endl;
		system("pause");
		system("cls");
		return;
	}

	// 获取用户输入
	int choick;
	cout << "当前共有:" << this->previous_game.size() << "届大赛的记录" << endl;
	cout << endl;
	cout << "输入0查看全部的大赛记录,输入序号可查看对应的大赛记录";
	cin >> choick;

	string std[4] = { "姓名:","参赛编号:","淘汰赛成绩:","决赛成绩:" };
	string sor[3] = { "冠军:","亚军:","季军:" };


	// for循环previous_game容器
	for (map<int, vector<vector<string>>>::iterator it = this->previous_game.begin(); it != this->previous_game.end(); it++)
	{
		// 大的vector容器的迭代器
		vector<vector<string>>::iterator it1;
		if (choick == 0)
		{
			// 当用户输入0时,迭代器指向大的vector容器下的第一个小的vector容器,因为要展示全部的大赛信息,大的vector容器会随着for循环变化,直到展示完所以信息
			it1 = it->second.begin();
			cout << ">>>>>>>>>>>>>>>第" << it->first << "届演讲比赛记录<<<<<<<<<<<<<<<" << endl;
		}
		else if (choick > this->previous_game.size() || choick < 0)
		{
			// 用户输入不合法时结束函数并清屏
			cout << "没有第" << choick << "届比赛的记录" << endl;
			system("pause");
			system("cls");
			return;
		}
		else
		{
			// 当用户想看具体的某一届比赛记录时,通过用户输入的choice作为key找到previous_game容器对应的value,迭代器指向这个大的vector容器下的第一个小的vector容器
			it1 = this->previous_game[choick].begin();
			cout << ">>>>>>>>>>>>>>>第" << choick << "届演讲比赛记录<<<<<<<<<<<<<<<" << endl;
		}
		cout << "=====全部参赛的人员信息=====" << endl;

		// 记录循环次数
		int count = 0;
		// for循环大vector容器中的小vector容器的元素,第一个小vector容器中放的是所有参赛的选手信息,每四个元素表示一个完整的选手信息
		for (vector<string>::iterator it2 = it1->begin(); it2 != it1->end(); it2++)
		{
			// 根据for循环的次数取出std数组中的元素,作为vector容器中元素的解释
			cout << std[count] << *it2 << "\t ";
			count++;
			// 每四个元素表示一个完整的选手信息,所以在count等于4后重新归零
			if (count == 4)
			{
				count = 0;
				cout << endl;
			}
		}

		// 第一个小vector容器中的数据展示完毕,迭代器自增,开始展示下一个小的vector容器中的数据
		it1++;
		// 初始化循环次数为0
		count = 0;
		cout << endl;

		cout << "=====淘汰赛晋级人员信息=====" << endl;
		for (vector<string>::iterator it2 = it1->begin(); it2 != it1->end(); it2++)
		{
			cout << std[count] << *it2 << "\t ";
			count++;
			if (count == 4)
			{
				count = 0;
				cout << endl;
			}
		}
		it1++;
		count = 0;
		cout << endl;

		cout << "=====最终获胜者=====" << endl;
		int num = 0;
		for (vector<string>::iterator it2 = it1->begin(); it2 != it1->end(); it2++)
		{
			if (count == 0)
			{
				cout << "--*--" << sor[num] << "--*--" << endl;
				num++;
			}
			cout << std[count] << " [" << *it2 << "] " << "\t \t";
			count++;
			if (count == 4)
			{
				count = 0;
				cout << endl;
			}
		}
		cout << endl;
		if (choick != 0)
		{
			break;
		}
	}
	system("pause");
	system("cls");
	
}

// 清空往届比赛记录
void Admin::clear_speack()
{
	cout << "确定要清空所有比赛记录吗" << endl;
	cout << "1、确定" << endl;
	cout << "0、取消" << endl;
	int choick;
	cin >> choick;

	if (choick == 0)
	{
		return;
	}
	else if (choick != 1 && choick != 0)
	{
		cout << "输入正确的序号" << endl;
		system("pause");
		system("cls");
		return;
	}

	ofstream ofs;
	// 打开模式,如果文件存在则删除并重新创建一个
	ofs.open("speacker.csv", ios::trunc);
	ofs.close();

	// 更新状态
	this->if_file = false;
	cout << "清空成功!!" << endl;
	system("pause");
	system("cls");
}

// 析构函数
Admin::~Admin() 
{

}

main.cpp

#include<iostream>
using namespace std;
#include "admin.h"

int main()
{
	// 随机数种子放到main文件的最上面,这样可以保证该程序的所有随机数每次随机的数据都是不一样的
	srand((unsigned int)time(NULL));

	// 接收用户输入
	int choice;
	while(true)
	{
		Admin admin;
		// 展示菜单
		admin.show_menu();

		cout << "输入想操作的功能的编码:";
		cin >> choice;
		switch (choice)
		{
		case 1:
			// 开始演讲比赛
			admin.flow_speacker();
			break;
		case 2:
			admin.show_old_speack();
			// 查看往期比赛记录
			break;
		case 3:
			// 清空比赛记录
			admin.clear_speack();
			break;
		case 4:
			// 退出当前程序
			admin.exit_system();
			break;
		default:
			// 输入其他数字时清屏
			system("cls");
			break;
		}
	}

	system("pause");
	return 0;
}
posted @   7七柒  阅读(16)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
点击右上角即可分享
微信分享提示