c++提高编程

文章目录

c++ 提高编程

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

1. 模板

1.1 模板的概念

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

例如生活中的模板:一寸照片模板、ppt 模板

特点:

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

1.2 函数模板

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

1.2.1 函数模板语法

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

语法:

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

解释:

  • template —— 声明创建模板
  • typename —— 表面其后面的字符是一种数据类型,可以用 class 代替
  • T —— 通用的数据类型,名称可以替换,通常为大写字母

总结:

  • 函数模板利用关键字 template
  • 使用函数模板有两种方式:自动类型推导、显示指定类型
  • 模板的目的是为了提高复用性,将类型参数化(指定为通用的 T,使用时传入)
template<typename T>  //声明一个模板,告诉编译器后面代码中紧跟着的 T 不要报错,T 是一个通用数据类型
void mySwap(T & a, T & b)
{
	T temp = a;
	a = b;
	b = temp;
}

void test()
{
	int a = 10;
	int b = 20;
	double c = 1.1;
	double d = 2.2;
	
	//利用函数模板交换,两种方式使用
	//1. 自动类型推导
	mySwap(a, b);
	cout << "a = " << a << endl;
	cout << "b = " << b << endl;

	//2. 显示指定类型
	mySwap<double>(c, d);  // <double> —— < T >
	cout << "c = " << c << endl;
	cout << "d = " << d << endl;
}

1.2.2 函数模板主要事项

注意事项:

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

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

//函数模板注意事项:
template<class T>
void mySwap(T & a, T & b)
{
	T temp = a;
	a = b;
	b = temp;
}
//1. 自动类型推导,必须推导出一致的数据类型 T ,才可以使用
void test01()
{
	int a = 10;
	int b = 20;
	char c = 'c';

	mySwap(a, b);
	//mySwap(a, c);  错误:推到不出一致的 T 类型

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

void test02()
{
	func<int>();  //随便指定 T 的类型为 int ,才可以使用
	//func();  错误,编译器无法推导出 T 的数据类型,必须要确定出 T 的数据类型
}

1.2.3 函数模板案例

描述:

  • 利用函数模板封装一个排序的函数,可以对 不同数据类型数组 进行排序
  • 排序规则从小到大,排序算法为 选择排序
  • 分别利用 char 数组int 数组 进行测试
//交换函数模板
template<class T>
void mySwap(T & a, T & b)
{
	int temp = a;
	a = b;
	b = temp;
}

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

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

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

	//测试 int 数组
	int intArr[ ] = { 7, 5, 1, 3, 9, 2, 4, 6, 8 };
	int num2 = sizeof(intArr) / sizeof(int);
	mySort(intArr, num2);
	printArray(intArr, num2);
}

f e d c b a
9 8 7 6 5 4 3 2 1

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

区别:

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

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

int myAdd01(int a, int b)
{
	return a + b;
}

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

//普通函数调用时可以发生隐式类型转换
void test01()
{
	int a = 10, b = 20;
	char c = 'c';
	cout << myAdd01(a, c) << endl;  //109

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

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

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

调用规则如下:

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

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

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

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

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

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

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

	//3. 函数模板也可以发生重载
	myPrint(a, b, 30);

	//4. 如果函数模板产生更好的匹配,优先调用函数模板
	char c1 = 'a', c2 = 'b';
	myPrint(c1, c2);
}

调用的普通函数
调用的函数模板
调用重载的函数模板
调用的函数模板

1.2.6 模板的局限性

局限性: 模板的通用性并不是万能的

例如1:

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

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

例如2:

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

在上述代码中,如果 T 的数据类型传入的是像 Person 这样的自定义类型数据,也无法正常运行

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

总结:

  • 利用具体化的模板,可以解决自定义类型的通用化
  • 学习模板并不是为了写模板,而是在 STL 能够运用系统提供的模板
class Person
{
public:
	Person(string name, int age)  //构造函数赋初值
	{
		this->m_Name = name;
		this->m_Age = age;
	}
	string m_Name;
	int m_Age;
};

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

//1. 运算符重载
//2. 利用具体化的 Person 的版本实现代码,具体化优先调用
template<> bool myCompare(Person & p1, Person & p2)
{
	if( p1.m_Name == p2.m_Name && p1.m_Age == p2.m_Age )
	{
		return true;
	}
	else
	{
		return false;
	}
}

void test01()
{
	int a = 10, b = 20;
	bool ret = myCompare(a, b);
	if( ret )
	{
		cout << "a == b" << endl;
	}
	else
	{
		cout << "a != b" << endl;
	}
}

void test02()
{
	Person p1("Tom", 10);
	Person p2("Tom", 20);
	bool ret = myCompare(p1, p2);
	if( ret )
	{
		cout << "p1 == p2" << endl;
	}
	else
	{
		cout << "p1 != p2" << endl;
	}
}

a != b
p1 != p2

1.3 类模板

1.3.1 类模板语法

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

语法:

template<typename T>
类

解释:

template —— 声明创建模板

typename —— 表面其后面的符号是一种数据类型,可以用 class 代替

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

总结: 类模板和函数模板语法相似,在声明模板 template 后面加类,此类称为类模板

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

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

void test()
{
	Person<string, int> p1("张三", 20);
	p1.showPerson();
}

1.3.2 类模板与函数模板区别

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

  1. 类模板没有自动类型推导的使用方式
  2. 类模板在模板参数列表中可以有默认参数
template<class NameType,class AgeType = int>  //默认参数
class Person
{
public:
	Person(NameType name, AgeType age)
	{
		this->m_Name = name;
		this->m_Age = age;
	}

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


void test()
{
	//1. 类模板没有自动类型推导的使用方式
	Person<string, int> p1("张三", 20);  //正确,只能用显式指定类型
	//Person p1("张三", 20);  错误,无法用自动类型推导

	p1.showPerson();

	//2. 类模板在模板参数列表中可以有默认参数
	Person<string> p2("李四", 30);
	p2.showPerson();
}

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

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

  • 普通类中的成员函数一开始就可以创建
  • 类模板中的成员函数在 调用时 才可以创建
class Person1
{
public:
	void showPerson1()
	{
		cout << "Person1 show" << endl;
	}
};

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

template<class T>
class MyClass
{
public:
	T obj;  //无法确定是 Person1 还是 Person2 数据类型

	//类模板中的成员函数
	void func1()
	{
		obj.showPerson1();  //没有报错——没调用
	}

	void func2()
	{
		obj.showPerson2();
	}
};

void test()
{
	MyClass<Person1> m;  //确定 T 的数据类型
	m.func1();
	//m.func2();  Person1 中没有 showPerson2() 成员函数
}

Person1 show

1.3.4 类模板对象做函数参数

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

共有三种传入方式:

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

总结: 使用笔记广泛的是第一种:指定传入的类型。( 2、3 属于函数模板配合类模板 )

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

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

	T1 m_Name;
	T2 m_Age;
};

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

void test01()
{
	Person<string, int> p1("张三", 20);
	printPerson1(p1);
}
//2. 参数模板化
template<class T1, class T2>  //函数模板
void printPerson2(Person<T1, T2> & p2)
{
	p2.showPerson();
	cout << "T1 的类型:" << typeid( T1 ).name() << endl;
	cout << "T2 的类型:" << typeid( T2 ).name() << endl;
}

void test02()
{
	Person<string, int> p2("李四", 30);
	printPerson2(p2);
}
//3. 整个类模板化
template<class T>  //函数模板
void printPerson3(T & p3)
{
	p3.showPerson();
	cout << "T 的数据类型:" << typeid( T ).name() << endl;
}

void test03()
{
	Person<string, int> p3("王五", 40);
	printPerson3(p3);
}

name:张三
age:20
name:李四
age:30
T1 的类型:class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >
T2 的类型:int
name:王五
age:40
T 的数据类型:class Person<class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >,int>

1.3.5 类模板与继承

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

  1. 当子类继承的父亲是一个类模板时,子类在声明的时候,要指定出父类中 T 的类型
  2. 如果不指定,编译器无法给子类分配内存
  3. 如果想灵活指定出父类中 T 的类型,子类也需要变为类模板
template<class T>
class Base
{
	T m;
};

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

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

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

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

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

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

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

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

	T1 m_Name;
	T2 m_Age;
};

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

//成员函数类外实现
template<class T1, class T2>  // showPerson 是类模板中成员函数
void Person<T1,T2>::showPerson()
{
	cout << "name:" << this->m_Name << endl;
	cout << "age:" << this->m_Age << endl;
}

void test()
{
	Person<string, int> p("张三", 20);
	p.showPerson();
}

1.3.7 类模板分文件编写

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

问题:类模板中成员函数创建时机是在调用阶段 ,导致分文件编写时链接不到

解决:

  • 方式1 :直接包含 .cpp 源文件
  • 方式2:将声明和实现写在同一个文件中,并更改后缀名为 .hpp ,.hpp 是约定的名称,并不是强制

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

< 解决方式1 >

person.cpp

#include "person.h"

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

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

person.h

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

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

	T1 m_Name;
	T2 m_Age;
};

类模板分文件编写.cpp

#include <iostream>
using namespace std;

//类模板中的成员函数在 调用时 才可以创建
//#include "person.h"  错误,编译器不会生成 Person(T1 name, T2 age); void showPerson(); 两个函数,不会看见 person.cpp 中的内容,会导致链接不到

//解决方式1:直接包含 person.cpp
#include "person.cpp"

void test()
{
	//没调用前不会报错
	Person<string, int> p("张三", 20);
	p.showPerson();
}

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

< 解决方式2 >

person.hpp

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

//解决方式2:将 .h 和 .cpp 中的内容写在一起,将后缀名改为 .hpp 文件
template<class T1, class T2>
class Person
{
public:
	Person(T1 name, T2 age);
	void showPerson();

	T1 m_Name;
	T2 m_Age;
};

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

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

类模板分文件编写.cpp

#include "person.hpp"

void test()
{
	//没调用前不会报错
	Person<string, int> p("张三", 20);
	p.showPerson();
}

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

1.3.8 类模板与友元

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

  • 全局函数类内实现 —— 直接在类内声明友元即可

  • 全局函数类外实现 —— 需要提前让编译器指定全局函数的存在

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

//类外实现
//(2)提前让编译器知道 Person类 存在
template<class T1,class T2>
class Person;

//全局函数 类外实现——全局函数,不需要加作用域
//(1)printPerson2存在
template<class T1, class T2>  //函数模板的函数实现,两者不是一个关系
void printPerson2(Person<T1, T2> p)
{
	cout << "类外实现:" << endl;
	cout << "name:" << p.m_Name << endl;
	cout << "age:" << p.m_Age << endl;
}

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

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

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


void test()
{
	//1. 全局函数在类内实现
	Person<string, int> p1("张三", 20);
	printPerson1(p1);

	//2. 全局函数在类外实现
	Person<string, int> p2("李四", 30);
	printPerson2(p2);
}

1.3.9 类模板案例

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

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

MyArray.hpp

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

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

	//拷贝构造——防止浅拷贝的问题
	MyArray(const MyArray& arr)
	{
		//cout << "MyArray 拷贝构造调用" << endl;
		this->m_Capacity = arr.m_Capacity;
		this->m_Size = arr.m_Size;
		//this->pAddress = arr.pAddress;  指针不能直接赋值,浅拷贝的问题,会导致堆区的数据重复释放
		
		//深拷贝
		this->pAddress = new T[ arr.m_Capacity ];

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

	// operator= ——防止浅拷贝的问题 a = b = c
	MyArray & operator=(const MyArray& arr)  //返回自身
	{
		//cout << "MyArray 的 operator= 调用" << endl;
		//先判断原来堆区是否有数据,如果有先释放
		if( this->pAddress != NULL )
		{
			delete[ ] this->pAddress;
			this->pAddress = NULL;
			this->m_Capacity = 0;
			this->m_Size = 0;
		}

		//深拷贝
		this->m_Capacity = arr.m_Capacity;
		this->m_Size = arr.m_Size;
		this->pAddress = new T[ arr.m_Capacity ];
		for( int i = 0; i < this->m_Size; i++ )
		{
			this->pAddress[ i ] = arr.pAddress[ i ];
		}
		return *this;
	}

	//尾插法
	void Push_Back(const T & val)
	{
		//判断容量是否等于大小
		if( this->m_Capacity == this->m_Size )
		{
			return;
		}
		this->pAddress[ this->m_Size ] = val;  //在数组末尾插入数据
		this->m_Size++;  //更新数组大小
	}

	//尾删法
	void Pop_Back()
	{
		//让用户访问不到最后一个元素,即为尾删,逻辑删除
		if( this->m_Size == 0 )
		{
			return;
		}
		this->m_Size--;
	}

	//通过下标的方式访问数组中的元素
	T & operator[](int index)  //arr[0] = 100 想让arr[0]作为一个左值,需返回T的引用,数据本身做一个返回,再做一个赋值操作
	{
		return this->pAddress[ index ];
	}

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

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

	//析构函数
	~MyArray()
	{
		if( this->pAddress != NULL )
		{
			//cout << "MyArray 析构函数调用" << endl;
			delete[] this->pAddress;
			this->pAddress = NULL;  //防止野指针
		}
	}
private:
	T * pAddress;  //指针指向堆区开辟的真实数组
	int m_Capacity;  //数组容量
	int m_Size;  //数组大小
};

类模板案例—数组类封装.cpp

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

void printIntArray(MyArray<int> & arr)
{
	for( int i = 0; i < arr.getSize(); i++ )
	{
		cout << arr[ i ] << " ";
	}
	cout << endl;
}

void test01()
{
	MyArray<int> arr1(5);
	for( int i = 0; i < 5; i++ )
	{
		//利用尾插法向数组中插入数据
		arr1.Push_Back(i);
	}
	cout << ">>arr1 :" << endl;
	printIntArray(arr1);

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

	MyArray<int> arr2(arr1);
	cout << ">>arr2 尾删后:" << endl;
	arr2.Pop_Back();  //尾删
	printIntArray(arr2);
	cout << "arr2 的容量为:" << arr2.getCapacity() << endl;
	cout << "arr2 的大小为:" << arr2.getSize() << endl;

	/*测试
	MyArray<int> arr1(5);  //有参构造

	MyArray<int> arr2(arr1);  //拷贝构造

	MyArray<int> arr3(100);  //有参构造
	arr3 = arr1;  //等号赋值

	MyArray 有参构造调用
	MyArray 拷贝构造调用
	MyArray 有参构造调用
	MyArray 的 operator= 调用
	MyArray 析构函数调用
	MyArray 析构函数调用
	MyArray 析构函数调用
	*/
}

//测试自定义数据类型
class Person
{
public:
	Person(){ }  //默认构造
	Person(string name, int age)  //有参构造
	{
		this->m_Name = name;
		this->m_Age = age;
	}

	string m_Name;
	int m_Age;
};

void ptintPersonArr(MyArray<Person> & arr)
{
	for( int i = 0; i < arr.getSize(); i++ )
	{
		cout << "姓名:" << arr[ i ].m_Name << "\t" 
			<< "年龄:" << arr[ i ].m_Age << endl;
	}
}

void test02()
{
	MyArray<Person> arr3(10);
	Person p1("韩信", 28);
	Person p2("赵云", 30);
	Person p3("妲己", 23);
	Person p4("貂蝉", 22);
	Person p5("吕布", 26);

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

	//打印数组
	ptintPersonArr(arr3);

	//输出容量
	cout << "arr3 的容量为:" << arr3.getCapacity() << endl;

	//输出大小
	cout << "arr3 的大小为:" << arr3.getSize() << endl;
}

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

>>arr1 :
0 1 2 3 4
arr1 的容量为:5
arr1 的大小为:5
>>arr2 尾删后:
0 1 2 3
arr2 的容量为:5
arr2 的大小为:4
姓名:韩信      年龄:28
姓名:赵云      年龄:30
姓名:妲己      年龄:23
姓名:貂蝉      年龄:22
姓名:吕布      年龄:26
arr3 的容量为:10
arr3 的大小为:5

2. STL 初识

2.1 STL 的诞生

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

2.2 STL 基本概念

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

2.3 STL 六大组件

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

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

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

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

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

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

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

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

  • 容器: 置物之所也

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

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

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

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

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

  • 算法: 问题之解法也

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

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

    1. 质变算法:是指运算过程中会更改区间内的元素的内容,例如拷贝、替换、删除 等等
    2. 非质变算法:是指运算过程中不会更改区间内的元素的内容,例如查找、计数、遍历、寻找极值 等等
  • 迭代器: 容器和算法之间的结合剂

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

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

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

    迭代器种类:

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

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

2.5 容器算法迭代器初识

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

2.5.1 vector 存放内置数据类型

  • 容器:vector

  • 算法:for_each

  • 迭代器:vector<int>::iterator

#include <vector>
#include <algorithm>  //标准算法头文件

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

void test()
{
	//创建了一个 vector 容器,数组
	vector<int> v;

	//向容器中插入数据
	v.push_back(10);
	v.push_back(20);
	v.push_back(30);
	v.push_back(40);

	//第一种遍历方式
	//通过迭代器访问容器中的数据
	//vector<int>::iterator itBegin = v.begin();  //v.begin() 起始迭代器,指向容器中第一个元素
	//vector<int>::iterator itEnd = v.end();  //结束迭代器,指向容器中最后一个元素的下一个位置

	//while( itBegin != itEnd )
	//{
	//	cout << *itBegin << endl;
	//	itBegin++;
	//}

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

	//第三种遍历方式,利用 STL 提供遍历算法
	for_each(v.begin(), v.end(), myPrint);  //回调函数
}

2.5.2 vector 存放自定义数据类型

学习目标: vector 中存放自定义数据类型,并打印输出

#include <vector>
#include <algorithm>

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

void test01()
{
	vector<Person> v;
	Person p1("aaa", 10);
	Person p2("bbb", 20);
	Person p3("ccc", 30);
	Person p4("ddd", 40);
	Person p5("eee", 50);

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

	//遍历容器中的数据
	for( vector<Person>::iterator it = v.begin(); it != v.end(); it++ )
	{
		cout << "姓名:" << ( *it ).m_Name << "\t" 
			<< "年龄:" << ( *it ).m_Age << endl;
		/*cout << "姓名:" << it->m_Name << "\t"
			<< "年龄:" << it->m_Age << endl;*/
	}
}

//存放自定义数据类型 指针
void test02()
{
	vector<Person *> v;
	Person p1("aaa", 10);
	Person p2("bbb", 20);
	Person p3("ccc", 30);
	Person p4("ddd", 40);
	Person p5("eee", 50);

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

	//遍历
	for( vector<Person *>::iterator it = v.begin(); it != v.end(); it++ )
	{
		//解引用(*it)后的数据类型,看<>的类型——<Person *>,指针
		cout << "姓名:" << ( *it )->m_Name << "\t"
			<<"年龄:" << ( *it )->m_Age << endl;
	}
}

2.5.3 vector 容器嵌套容器

学习目标: 容器中嵌套容器,将所有数据进行遍历输出

#include <vector>
#include <algorithm>

void test01()
{
	vector<vector<int>> v;

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

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

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

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


1 2 3 4
2 3 4 5
3 4 5 6
4 5 6 7

3. 常用容器

3.1 string 容器

3.1.1 string 基本概念

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

string 和 char * 的区别:

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

特点: string 类内部封装了很多成员方法

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

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

3.1.2 string 构造函数

构造函数原型:

  • string(); //默认构造——创建一个空的字符串,例如:string str;

    string(const char * s); //使用字符串 s 初始化

  • string(const string & str); //拷贝构造——使用一个已知的 string 对象初始化另一个 string 对象

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

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

void test()
{
	string s1;  //默认构造

	const char * str = "hello world";
	string s2(str);
	cout << "s2 = " << s2 << endl;  //s2 = hello world
	
	string s3(s2);
	cout << "s3 = " << s3 << endl;  //s3 = hello world

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

3.1.3 string 赋值操作

功能描述: 给 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 赋给当前字符串

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

void test()
{
	string str1;
	str1 = "hello world";
	cout << "str1 = " << str1 << endl;  //str1 = hello world

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

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

	string str4;
	str4.assign("hello c++");
	cout << "str4 = " << str4 << endl;  //str4 = hello c++

	string str5;
	str5.assign("hello c++", 5);
	cout << "str5 = " << str5 << endl;  //str5 = hello

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

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

3.1.4 string 字符串拼接

功能描述: 实现在字符串末尾拼接字符串

函数原型:

  • string & operator+=(const char * str); //重载 += 操作符
  • string & operator+=(const char c); //重载 += 操作符
  • string & operator+=(const string & str); //重载 += 操作符
  • string & append(const char * s); //把字符串 s 连接到当前字符串结尾
  • string & append(const char * s,int n); //把字符串 s 的前 n 个字符连接到当前字符串结尾
  • string & append(const string & s); //同 operator+=(const string & str)
  • string & append(const string & s,int pos,int n); //字符串 s 中从 pos 开始的 n 个字符连接到字符串结尾

总结: 字符串拼接的重载版本很多,初学阶段记住几种即可

void test()
{
	string str1 = "我";
	str1 += "爱玩游戏";
	cout << "str1 = " << str1 << endl;  //str1 = 我爱玩游戏

	str1 += ':';
	cout << "str1 = " << str1 << endl;  //str1 = 我爱玩游戏:

	string str2 = " LOL, DNF !";
	str1 += str2;
	cout << "str1 = " << str1 << endl;  //str1 = 我爱玩游戏: LOL, DNF !

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

	str3.append("game : abcde", 6);
	cout << "str3 = " << str3 << endl;  //str3 = I love game :

	str3.append(str2);
	cout << "str3 = " << str3 << endl;  //str3 = I love game : LOL, DNF !

	string str4 = " 哈哈哈哈";
	str3.append(str4, 0, 7);  //空格 + 哈哈哈
	cout << "str3 = " << str3 << endl;  //str3 = I love game : LOL, DNF ! 哈哈哈
}

3.1.5 string 查找和替换

功能描述:

  • 查找:查找指定字符串是否存在
  • 替换:在指定的位置替换字符串

函数原型:

  • int find(const string & str,int pos = 0) const; //查找 str 第一次出现位置,从 pos 开始查找
  • int find(const char * s,int pos = 0) const; //查找 s 第一次出现位置,从 pos 开始查找
  • int find(const char * s,int pos,int n) const; //从 pos 位置查找 s 的前 n 个字符第一个位置
  • int find(const char c,int pos = 0) const; //查找字符 c 第一次出现位置
  • int rfind(const string & str,int pos = npos) const; //查找 str 最后一次位置,从 pos 开始查找
  • int rfind(const char * s,int pos = npos) const; //查找 s 最后一次出现位置,从 pos 开始查找
  • int rfind(const char * s,int pos,int n) const; //从 pos 查找 s 的前 n 个字符最后一次位置
  • int rfind(const char c,int pos = 0) coonst; //查找字符 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 和 rfind 区别: find 是从左往右查找,rfind 是从右往左查找

总结:

  • find 是从左往右查找,rfind 是从右往左查找
  • find 找到字符串后返回查找的第一个字符位置,找不到返回 -1
  • replace 在替换时,要指定从哪个位置起,多少个字符,替换成什么样的字符串
//查找
void test01()
{
	string str1 = "abcdefgde";
	int pos1 = str1.find("de");
	int pos2 = str1.rfind("de");
	if( pos1 == -1 )
	{
		cout << "未找到字符串!" << endl;
	}
	else
	{
		cout << "pos1(find) = " << pos1 << endl;  //pos1(find) = 3
		cout << "pos2(rfind) = " << pos2 << endl;  //pos2(rfind) = 7
	}
}

//替换
void test02()
{
	string str2 = "abcdefg";
	str2.replace(3, 4, "xyz");
	cout << "str2 = " << str2 << endl;  //str2 = abcxyz
}

3.1.6 string 字符串比较

功能描述: 字符串之间的比较

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

  • ​ = 返回 0

  • ​ > 返回 1

  • ​ < 返回 -1

函数原型:

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

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

void test()
{
	string str1 = "hello";
	string str2 = "xel";

	if( str1.compare(str2) == 0 )
	{
		cout << "str1 = str2" << endl;
	}
	else if( str1.compare(str2) > 0 )  //1
	{
		cout << "str1 > str2" << endl;
	}
	else  //-1
	{
		cout << "str1 < str2" << endl;
	}
}

3.1.7 string 字符存取

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

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

总结: string 字符串中单个字符串存取有两种方式,利用 [ ] 或 at

void test()
{
	string str = "hello";
	cout << "str = " << str << endl;  //str = hello

	//1. 通过 [] 访问单个字符
	for( int i = 0; i < str.size(); i++ )
	{
		cout << str[ i ] << " ";  //h e l l o
	}
	cout << endl;

	//2. 通过 at 方式访问字符
	for( int i = 0; i < str.size(); i++ )
	{
		cout << str.at(i) << " ";  //h e l l o
	}
	cout << endl;

	//修改单个字符
	str[ 0 ] = 'x';
	cout << "str = " << str << endl;  //str = xello

	str.at(1) = 'y';
	cout << "str = " << str << endl;  //str = xyllo
}

3.1.8 string 插入和删除

功能描述: 对 string 字符串进行插入和删除字符操作

函数原型:

  • string & insert(int pos,const char * s); //插入字符串
  • string & insert(int pos,const string & str); //插入字符串
  • string & insert(int pos,int n,char c); //在指定位置插入 n 个字符 c
  • string & erase(int pos,int n = npos); //删除从 pos 开始的 n 个字符

总结: 插入和删除的起始下标都是从 0 开始的

void test()
{
	string str = "hello";

	//插入
	str.insert(1, "333");
	cout << "str = " << str << endl;  //str = h333ello

	//删除
	str.erase(1, 3);
	cout << "str = " << str << endl;  //str = hello
}

3.1.9 string 子串

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

函数原型:

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

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

void test01()
{
	string str1 = "abcdef";
	cout << "str1 = " << str1 << endl;  //str1 = abcdef

	string str2 = str1.substr(1, 3);
	cout << "str2 = " << str2 << endl;  //str2 = bcd
}

//实用操作
void test02()
{
	string email = "zhangsan@sina.com";

	//从邮件地址中,获取用户名信息
	int pos = email.find("@");
	cout << "pos = " << pos << endl;  //pos = 8

	string userName = email.substr(0, pos);
	cout << "userName = " << userName << endl;  //userName = zhangsan
}

3.2 vector 容器

3.2.1 vector 基本概念

功能:

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

vector 与普通数组区别:

  • 不同之处在于数组是静态空间,而 vector 可以动态扩展

动态扩展:

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

  • vector 容器的迭代器是支持随机访问的迭代器
    在这里插入图片描述

3.2.2 vector 构造函数

功能描述: 创建 vector 容器

函数原型:

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

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

#include <vector>

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

void test()
{
   //默认构造——无参构造
	vector<int> v1;
	for( int i = 0; i < 10; i++ )
	{
		v1.push_back(i);
	}
	printVector(v1);  //0 1 2 3 4 5 6 7 8 9

	//通过区间方式进行构造
	vector<int> v2(v1.begin(), v1.end());
	printVector(v2);  //0 1 2 3 4 5 6 7 8 9

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

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

3.2.3 vector 赋值操作

功能描述: 给 vector 容器进行赋值

函数原型:

  • vector & operator=(const vector & vec); //重载等号操作符
  • assign(beg,end); //将 [ beg , end ) 区间的数据拷贝赋值给本身
  • assign(n,elem); //将 n 个 elem 拷贝赋值给本身
void printVector(vector<int> & v)
{
	for( vector<int>::iterator it = v.begin(); it != v.end(); it++ )
	{
		cout << *it << " ";
	}
	cout << endl;
}

void test()
{
	vector<int> v1;
	for( int i = 0; i < 10; i++ )
	{
		v1.push_back(i);
	}
	printVector(v1);  //0 1 2 3 4 5 6 7 8 9

	//赋值——operator=
	vector<int> v2;
	v2 = v1;
	printVector(v2);  //0 1 2 3 4 5 6 7 8 9

	//赋值——assign(beg,end)
	vector<int> v3;
	v3.assign(v1.begin(), v1.end());
	printVector(v3);  //0 1 2 3 4 5 6 7 8 9

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

3.2.4 vector 容量和大小

功能描述: 对 vector 容器的容量和大小操作

函数原型:

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

  • capacity(); //容器的容量

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

  • resize(int num); //重新指定容器的长度为 num

    ​ //若容器长度变长,则以默认值填充新位置;

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

  • resize(int num,elem); //重新指定容器的长度为 num

    ​ //若容器长度变长,则以 elem 值填充新位置;

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

总结:

  • 判断是否为空 —— empty
  • 返回元素个数 —— size
  • 返回容器容量 —— capacity
  • 重新指定大小 —— resize
void printVector(vector<int> & v)
{
	for( vector<int>::iterator it = v.begin(); it != v.end(); it++ )
	{
		cout << *it << " ";
	}
	cout << endl;
}

void test()
{
	vector<int> v1;
	for( int i = 0; i < 10; i++ )
	{
		v1.push_back(i);
	}
	printVector(v1);  //0 1 2 3 4 5 6 7 8 9

	if( v1.empty() )  //为真,代表容器为空
	{
		cout << "v1 为空" << endl;
	}
	else
	{
		cout << "v1 不为空" << endl;
		cout << "v1 的容量为:" << v1.capacity() << endl;  //v1 的容量为:13
		cout << "v1 的大小为:" << v1.size() << endl;  //v1 的大小为:10
	}

	//重新指定大小——如果重新指定的比原来长,默认用 0 来填充新的位置
	v1.resize(15, 100);  //利用重载版本,可以指定默认填充值,参数2
	printVector(v1);  //0 1 2 3 4 5 6 7 8 9 100 100 100 100 100

	v1.resize(5);  //如果重新指定的比原来短了,超出部分会删除掉
	printVector(v1);  //0 1 2 3 4
}

3.2.5 vector 插入和删除

功能描述: 对 vector 容器进行插入、删除操作

函数原型:

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

总结:

  • 尾插 —— push_back
  • 尾删 —— pop_back
  • 插入 —— insert(位置迭代器)
  • 删除 —— erase(位置迭代器)
  • 清空 —— clear
void printVector(vector<int> & v)
{
	for( vector<int>::iterator it = v.begin(); it != v.end(); it++ )
	{
		cout << *it << " ";
	}
	cout << endl;
}

void test()
{
	vector<int> v1;
	//尾插
	for( int i = 10; i <= 100; i += 10 )
	{
		v1.push_back(i);
	}
	printVector(v1);  //10 20 30 40 50 60 70 80 90 100

	//尾删
	for( int i = 0; i < 5; i++ )
	{
		v1.pop_back();
	}
	printVector(v1);  //10 20 30 40 50

	//插入
	v1.insert(v1.begin(), 60);  //第一个参数是迭代器
	printVector(v1);  //60 10 20 30 40 50

	v1.insert(v1.begin(), 3, 70);
	printVector(v1);  //70 70 70 60 10 20 30 40 50

	//删除——参数也是迭代器
	for( int i = 0; i < 4; i++ )
	{
		v1.erase(v1.begin());
	}
	printVector(v1);  //10 20 30 40 50

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

3.2.6 vector 数据存取

功能描述: 对 vector 中的数据存取操作

函数原型:

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

总结:

  • 除了用迭代器获取 vector 容器中元素, [ ] 和 at 也可以
  • front 返回容器第一个元素, back 返回容器最后一个元素
void test()
{
	vector<int> v1;
	for( int i = 0; i < 10; i++ )
	{
		v1.push_back(i);
	}

	//利用 [ ] 方式访问数组元素
	for( int i = 0; i < v1.size(); i++ )
	{
		cout << v1[ i ] << " ";  //0 1 2 3 4 5 6 7 8 9
	}
	cout << endl;

	//利用 at 方式访问元素
	for( int i = 0; i < v1.size(); i++ )
	{
		cout << v1.at(i) << " ";  //0 1 2 3 4 5 6 7 8 9
	}
	cout << endl;

	cout << "第一个元素为:" << v1.front() << endl;  //第一个元素为:0
	cout << "最后一个元素为:" << v1.back() << endl;  //最后一个元素为:9
}

3.2.7 vector 互换容器

功能描述: 实现两个容器内元素进行互换

函数原型: swap(vec); //将 vec 与本身的元素互换

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

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

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

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

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

	cout << "v2: ";
	printVector(v2);
}

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

	v.resize(3);
	cout << "v 的容量:" << v.capacity() << endl;
	cout << "v 的大小:" << v.size() << endl;

	//巧用 swap 收缩内存
	vector<int>(v).swap(v);
	cout << "v 的容量:" << v.capacity() << endl;
	cout << "v 的大小:" << v.size() << endl;
}


>>交换前:
v1: 0 1 2 3 4 5 6 7 8 9
v2: 10 9 8 7 6 5 4 3 2 1
>>交换后:
v1: 10 9 8 7 6 5 4 3 2 1
v2: 0 1 2 3 4 5 6 7 8 9
v 的容量:12138
v 的大小:10000
v 的容量:12138
v 的大小:3
v 的容量:3
v 的大小:3

在这里插入图片描述

3.2.8 vector 预留空间

功能描述: 减少vector 在动态扩展容量时的扩展次数

函数原型: reserve(int len); //容器预留 len 个元素长度,预留位置不初始化,元素不可访问

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

void test()
{
	vector<int> v;

	//利用 reserve 预留空间
	v.reserve(100000);  //num=1

	int num = 0;  //统计开辟次数
	int * p = NULL;
	for( int i = 0; i < 100000; i++ )
	{
		v.push_back(i);
		if( p != &v[ 0 ] )
		{
			p = &v[ 0 ];
			num++;
		}
	}
	cout << "num = " << num << endl;  //30
}

3.3 deuqe 容器

3.3.1 deque 基本概念

功能: 双端数组,可以对头端进行插入删除操作

deque 和 vector 区别:

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

在这里插入图片描述
deque 内部工作原理:

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

中控器维护的是每个缓冲区的地址,使得使用 deque 时像一片连续的内存空间
在这里插入图片描述

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

3.3.2 deque 构造函数

功能描述: deque 容器构造

函数原型:

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

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

#include <deque>

void printDeque(const deque<int>&d)  //只读
{
	for (deque<int>::const_iterator it = d.begin(); it != d.end(); it++)  //遍历的迭代器也要发生改变:const_iterator
	{
		//*it = 100;  容器中的数据不可修改
		cout << *it << " ";
	}
	cout << endl;
}

void test()
{
	//默认构造
	deque<int>d1;
	for (int i = 0; i < 10; i++)
	{
		d1.push_back(i);
	}
	printDeque(d1);  //0 1 2 3 4 5 6 7 8 9

	//通过区间方式进行构造
	deque<int>d2(d1.begin(), d1.end());
	printDeque(d2);  //0 1 2 3 4 5 6 7 8 9

	//n 个elem 方式构造
	deque<int>d3(10, 100);
	printDeque(d3);  //100 100 100 100 100 100 100 100 100 100

	//拷贝构造
	deque<int>d4(d3);
	printDeque(d4);  //100 100 100 100 100 100 100 100 100 100
}

3.3.3 deque 赋值操作

功能描述: 给 deque 容器进行复制

函数原理:

  • deque & operator = (const deque & deq); //重载等号操作符
  • assign(beg,end); //将 [ beg , end ) 区间的数据拷贝赋值给本身
  • assign(n,elem); //将 n 个 elem 拷贝赋值给本身

总结: deque 赋值操作也与 vector 相同,需熟练掌握

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

void test()
{
	deque<int>d1;
	for (int i = 0; i < 10; i++)
	{
		d1.push_back(i);
	}
	printDeque(d1);  //0 1 2 3 4 5 6 7 8 9

	//operator= 赋值
	deque<int>d2;
	d2 = d1;
	printDeque(d2);  //0 1 2 3 4 5 6 7 8 9

	//assign(beg,end) 赋值
	deque<int>d3;
	d3.assign(d1.begin(), d1.end());
	printDeque(d3);  //0 1 2 3 4 5 6 7 8 9

	//assign(n,elem) 赋值
	deque<int>d4;
	d4.assign(10, 100);
	printDeque(d4);  //100 100 100 100 100 100 100 100 100 100
}

3.3.4 deque 大小操作

功能描述: 对 deque 容器的大小进行操作

函数原型:

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

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

  • deque.resize(num); //重新指定容器的长度为 num

    //若容器变长,则以默认值填充新位置

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

  • deque.resize(num,elem); //重新指定容器的长度为 num

    //若容器变长,则以 elem 值填充新位置

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

总结:

  • deque 没有容量的概念
  • 判断是否为空 —— empty
  • 返回元素个数 —— size
  • 重新指定个数 —— resize
void printDeque(const deque<int>&d)
{
	for (deque<int>::const_iterator it = d.begin(); it != d.end(); it++)
	{
		cout << *it << " ";
	}
	cout << endl;
}

void test()
{
	deque<int>d1;
	for (int i = 0; i < 10; i++)
	{
		d1.push_back(i);
	}
	printDeque(d1);  //0 1 2 3 4 5 6 7 8 9

	if (d1.empty())
	{
		cout << "d1 为空" << endl;
	}
	else
	{
		cout << "d1 不为空" << endl;
		cout << "d1 的大小为:" << d1.size() << endl;  //d1 的大小为:10
		//deque 容器没有容量的概念
	}

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

	d1.resize(5);
	printDeque(d1);  //0 1 2 3 4
}

3.3.5 deque 插入和删除

功能描述: 向 deque 容器中插入和删除数据

函数原型:

两端插入操作:

  • push_back(elem); //在容器尾部添加一个数据
  • push_front(elem); //在容器头部插入一个数据
  • pop_back(); //删除容器最后一个数据
  • pop_front(); //删除容器第一个数据

指定位置操作:

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

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

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

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

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

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

总结:

  • 插入和删除提供的位置是迭代器——索引值不好使,一定要使用迭代器
  • 尾插 —— push_back
  • 尾删 —— pop_back
  • 头插 —— push_front
  • 头删 —— pop_front
void printDeque(const deque<int>&d)
{
	for (deque<int>::const_iterator it = d.begin(); it != d.end(); it++)
	{
		cout << *it << " ";
	}
	cout << endl;
}

//两端操作
void test01()
{
	deque<int>d1;

	//尾插
	d1.push_back(10);
	d1.push_back(20);

	//头插
	d1.push_front(100);
	d1.push_front(200);
	printDeque(d1);  //200 100 10 20

	//尾删、头删
	d1.pop_back();
	d1.pop_front();
	printDeque(d1);  //100 10
}

//指定位置操作——插入
void test02()
{
	deque<int>d2;
	d2.push_back(10);
	d2.push_back(20);
	d2.push_front(100);
	d2.push_front(200);
	printDeque(d2);  //200 100 10 20

	//insert 插入——提供迭代器
	d2.insert(d2.begin(), 1000);
	printDeque(d2);  //1000 200 100 10 20

	d2.insert(d2.end(), 2, 2000);
	printDeque(d2);  //1000 200 100 10 20 2000 2000

	//按照区间进行插入
	deque<int>d3;
	d3.push_back(1);
	d3.push_back(2);
	d3.push_back(3);

	d2.insert(d2.begin(), d3.begin(), d3.end());
	printDeque(d2);  //1 2 3 1000 200 100 10 20 2000 2000
}

//指定位置操作——删除
void test03()
{
	deque<int>d4;
	d4.push_back(10);
	d4.push_back(20);
	d4.push_front(100);
	d4.push_front(200);  //200 100 10 20

	//d4.erase(d4.begin());
	//printDeque(d4);  //100 10 20
	deque<int>::iterator it = d4.begin();  //先找一个迭代器,指向第一个元素
	it++;  //迭代器向后偏移,即指向第二个元素
	d4.erase(it);
	printDeque(d4);  //200 10 20

	//按区间方式删除:清空
	d4.erase(d4.begin(), d4.end());
	d4.clear();
	printDeque(d4);
}

3.3.6 deque 数据存取

功能描述: 对 deque 中的数据的存取操作

函数原型:

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

总结:

  • 除了用迭代器获取 deque 容器中的元素,[ ] 和 at 也可以
  • front 返回容器的第一个元素
  • back 返回容器最后一个元素
void test()
{
	deque<int>d;
	d.push_back(10);
	d.push_back(20);
	d.push_back(30);
	d.push_front(100);
	d.push_front(200);
	d.push_front(300);

	//通过 [] 的方式访问元素
	for (int i = 0; i < d.size(); i++)
	{
		cout << d[i] << " ";
	}
	cout << endl;  //300 200 100 10 20 30

	//通过 at 的方式访问元素
	for (int i = 0; i < d.size(); i++)
	{
		cout << d.at(i) << " ";
	}
	cout << endl;  //300 200 100 10 20 30

	cout << "第一个元素:" << d.front() << endl;  //第一个元素:300
	cout << "最后一个元素:" << d.back() << endl;  //最后一个元素:30
}

3.3.7 deque 排序

功能描述: 利用算法实现对 deque 容器进行排序

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

总结: sort 算法非常实用,使用时包含头文件 algorithm 即可

#include <deque>
#include <algorithm>  //标准算法头文件

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

void test()
{
	deque<int>d;
	d.push_back(10);
	d.push_back(20);
	d.push_back(30);
	d.push_front(100);
	d.push_front(200);
	d.push_front(300);
	cout << "排序前:";
	printDeque(d);  //300 200 100 10 20 30

	//排序  默认排序规则:从小到大 升序
	//对于支持随机访问的迭代器的容器,都可以利用 sort 算法直接对其进行排序
	//vector 容器也可以利用 sort 进行排序
	sort(d.begin(),d.end());
	cout << "排序后:";
	printDeque(d);  //排序后:10 20 30 100 200 300
}

3.4 案例:评委打分

案例描述: 有5名选手:ABCDE ,10个评委分别对每一位选手打分,去除最高分和最低分,取平均分

实现步骤:

  1. 创建5名选手,放到 vector 中;
  2. 遍历 vector 容器,取出每一个选手,执行 for 循环,可以把10个评分打分存在 deque 容器中;
  3. sort 算法对 deque 容器中的分数排序,去除最高分和最低分;
  4. deque 容器遍历一遍,累加总分;
  5. 获取平均分
#include <iostream>
using namespace std;
#include <vector>
#include <deque>
#include <algorithm>  //标准算法头文件  sort算法
#include <ctime>

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

void createPerson(vector<Person>&v)  //形参和实参要对应修改的话,需用 引用 的方式传递
{
	string nameSeed = "ABCDE";
	for (int i = 0; i < 5; i++)
	{
		string name = "选手";
		name += nameSeed[i];
		
		int score = 0;
		Person p(name, score);

		//将创建的Person 放入到容器中
		v.push_back(p);
	}
}

//打分
void setScore(vector<Person>&v)
{
	for (vector<Person>::iterator it = v.begin(); it != v.end(); it++)
	{
		//将评委的分数 放入到 deque 容器中
		deque<int>d;
		for (int i = 0; i < 10; i++)
		{
			int score = rand() % 41 + 60;  //60~100
			d.push_back(score);
		}
		//cout << it->m_Name << "  得分:";
		//for (deque<int>::iterator dit = d.begin(); dit != d.end(); dit++)
		//{
		//	cout << *dit << " ";
		//}
		//cout << endl;

		//排序
		sort(d.begin(), d.end());

		//去除最高分和最低分
		d.pop_back();
		d.pop_front();

		//取平均分
		int sum = 0;
		for (deque<int>::iterator dit = d.begin(); dit != d.end(); dit++)
		{
			sum += *dit;  //累加每个评委的分数
		}

		int avg = sum / d.size();

		//将平均分 赋值给选手
		it->m_Score = avg;
	}
}

void showScore(vector<Person>&v)
{
	for (vector<Person>::iterator it = v.begin(); it != v.end(); it++)
	{
		cout << it->m_Name << " 平均分:" << it->m_Score << endl;
	}
}

int main()
{
	//随机数种子
	srand((unsigned int)time(NULL));

	//1. 创建5名选手
	vector<Person>v;  //存放选手容器
	createPerson(v);

	//测试
	//for (vector<Person>::iterator it = v.begin(); it != v.end(); it++)
	//{
	//	cout << "姓名:" << (*it).m_Name << "  分数:" << (*it).m_Score << endl;
	//}

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

	system("pause");
	return 0;
}


选手A 平均分:84
选手B 平均分:81
选手C 平均分:81
选手D 平均分:81
选手E 平均分:79

3.5 stack 容器

3.5.1 stack 基本概念

概念: stack 是一种 先进后出 (First In Last Out,FILO)的数据结构,它只有一个出口
在这里插入图片描述
栈中只有顶端的元素才可以被外界使用,因此栈不允许有遍历行为

可以判断容器为空 —— empty

可以返回元素个数 —— size (入栈时记录)

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

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

3.5.2 stack 常用接口

功能描述: 栈容器常用的对外接口

  1. 构造函数:

    • stack<T> stk; //stack 采用模块实现,stack 对象的默认构造形式
    • stack(const stack & stk); //拷贝构造函数
  2. 赋值操作:

    • stack & operator=(const stack & stk); //重载等号操作符
  3. 数据存取:

    • push(elem); //向栈顶添加元素
    • pop(); //从栈顶移除第一个元素
    • top(); //返回栈顶元素
  4. 大小操作:

    • empty(); //判断堆栈是否为空

    • size(); //返回栈的大小

总结:

  • 入栈 —— push
  • 出栈 —— pop
  • 返回栈顶 —— top
  • 判断栈是否为空 —— empty
  • 返回栈大小 —— size
#include <stack>

void test()
{
	//特点:符合 先进后出 的数据结构
	stack<int>s;

	//入栈
	s.push(10);
	s.push(20);
	s.push(30);
	s.push(40);
	cout << "栈的大小:" << s.size() << endl;

	//只要栈不为空,查看栈顶,并且执行出栈操作
	while (!s.empty())
	{
		//查看栈顶元素
		cout << "栈顶元素为:" << s.top() << endl;

		//出栈
		s.pop();
	}
	cout << "栈的大小:" << s.size() << endl;
}


栈的大小:4
栈顶元素为:40
栈顶元素为:30
栈顶元素为:20
栈顶元素为:10
栈的大小:0

3.6 queue 容器

3.6.1 queue 基本概念

概念: queue 是一种 先进先出 (First In First Out,FIFO)的数据结构,它有两个出口
在这里插入图片描述

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

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

  • 可以判断队列为空 —— empty

  • 可以返回队列大小—— size

  • 队列中进数据称为 —— 入队 push

  • 队列中出数据称为 —— 出队 pop

3.6.2 queue 常用接口

功能描述: 栈容器常用的对外接口

  1. 构造函数:
    • queue<T> que; //queue 采用模块类实现,queue 对象的默认构造形式
    • queue(const queue & que); //拷贝构造函数
  2. 赋值操作:
    • queue operator=(const queue & que); //重载等号操作符
  3. 数据存取:
    • push(elem); //往队尾添加元素
    • pop(); //从队头移除第一个元素
    • back(); //返回最后一个元素
    • front(); //返回第一个元素
  4. 大小操作:
    • empty(); //判断堆栈是否为空
    • size(); //返回栈的大小

总结:

  • 入队 —— push
  • 出队 —— pop
  • 返回队头元素 —— front
  • 返回队尾元素 —— back
  • 判断队是否为空 —— empty
  • 返回队列大小 —— size
#include <queue>

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

	string m_Name;
	int m_Age;
};

void test()
{
	//创建队列
	queue<Person>q;  //其他数据类型

	//准备数据
	Person p1("唐僧", 30);
	Person p2("悟空", 1000);
	Person p3("八戒", 500);
	Person p4("沙僧", 400);

	//入队
	q.push(p1);
	q.push(p2);
	q.push(p3);
	q.push(p4);
	cout << "队列大小:" << q.size() << endl;

	//判断只要队列不为空,查看队头,查看队尾,出队
	while (!q.empty())
	{
		//查看队头
		cout << "队头元素 —— 姓名:" << q.front().m_Name << " 年龄:" << q.front().m_Age << endl;

		//查看队尾
		cout << "队尾元素 —— 姓名:" << q.back().m_Name << " 年龄:" << q.back().m_Age << endl;

		//出队
		q.pop();
		cout << "-------------------------------------" << endl;
	}
	cout << "队列大小:" << q.size() << endl;
}


队列大小:4
队头元素 —— 姓名:唐僧 年龄:30
队尾元素 —— 姓名:沙僧 年龄:400
-------------------------------------
队头元素 —— 姓名:悟空 年龄:1000
队尾元素 —— 姓名:沙僧 年龄:400
-------------------------------------
队头元素 —— 姓名:八戒 年龄:500
队尾元素 —— 姓名:沙僧 年龄:400
-------------------------------------
队头元素 —— 姓名:沙僧 年龄:400
队尾元素 —— 姓名:沙僧 年龄:400
-------------------------------------
队列大小:0

3.7 list 链表

3.7.1 list 基本概念

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

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

  • 链表的组成:链表有一系列 结点 组成

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

STL 中的链表是一个 双向循环链表
在这里插入图片描述
由于链表的存储方式并不是连续的内存空间,因此链表 list 中的迭代器只支持前移和后移,属于 双向迭代器

  • 优点:

    • 采用动态存储分配,不会造成内存的浪费和溢出
    • 链表执行插入和删除操作十分简单,修改指针即可,不需要移动大量元素,可以对任意位置进行快速插入或删除元素
  • 缺点:

    • 容器的遍历速度,没有数组快

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

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

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

3.7.2 list 构造函数

功能描述: 创建 list 容器

函数原型:

  • list<T> lst; //list 采用模块类实现对象的默认构造形式
  • list(beg, end); //构造函数(beg,end)区间中的元素拷贝给本身
  • list(n, elem); //构造函数将 n 个 elem 拷贝给本身
  • list(const list &lst); //拷贝构造函数

总结: list 构造方式同其他几个 STL 常用容器,熟练掌握即可

#include <list>

void printList(const list<int>& L)
{
	for (list<int>::const_iterator it = L.begin(); it != L.end(); it++)
	{
		cout << *it << " ";
	}
	cout << endl;
}

void test()
{
	//创建容器
	list<int>L1;

	//添加数据
	L1.push_back(10);
	L1.push_back(20);
	L1.push_back(30);
	L1.push_back(40);

	//遍历容器
	printList(L1);  //10 20 30 40

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

	//拷贝构造
	list<int>L3(L2);
	printList(L1);  //10 20 30 40

	//n 个 elem 
	list<int>L4(5, 200);
	printList(L4);  //200 200 200 200 200
}

3.7.3 list 赋值和交换

功能描述: 给 list 容器进行赋值,以及交换 list 容器

函数原型:

  • assign(beg,end); //将 [beg,end) 区间中的数据拷贝赋值给本身

  • assign(n,elem); //将 n 个 elem 拷贝

  • list & operator=(const list &lst); //重载等号运算符

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

总结: list 赋值和交换操作能够灵活运用即可

#include <list>

void printList(const list<int>&L)
{
	for (list<int>::const_iterator it = L.begin(); it != L.end(); it++)
	{
		cout << *it << " ";
	}
	cout << endl;
}

//赋值
void test01()
{
	list<int>L1;

	L1.push_back(10);
	L1.push_back(20);
	L1.push_back(30);
	L1.push_back(40);

	printList(L1);  //10 20 30 40

	list<int>L2;
	L2 = L1;  // operator=  赋值
	printList(L2);  //10 20 30 40

	list<int>L3;
	L3.assign(L2.begin(), L2.end());
	printList(L3);  //10 20 30 40

	list<int>L4;
	L4.assign(10, 500);
	printList(L4);  //500 500 500 500 500 500 500 500 500 500
}

void test02()
{
	list<int>L1;

	L1.push_back(10);
	L1.push_back(20);
	L1.push_back(30);
	L1.push_back(40);

	list<int>L2;
	L2.assign(10, 200);
	cout << "交换前:" << endl;
	printList(L1);
	printList(L2);

	L1.swap(L2);
	cout << "交换后:" << endl;
	printList(L1);
	printList(L2);
}


交换前:
10 20 30 40
200 200 200 200 200 200 200 200 200 200
交换后:
200 200 200 200 200 200 200 200 200 200
10 20 30 40

3.7.4 list 大小操作

功能描述: 对 list 容器的大小进行操作

函数原型:

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

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

  • resize(); //重新指定容器的长度为 num ,

​ //若容器变长,则以默认值填充新位置

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

  • resize(num,elem); //重新指定容器的长度为 num ,

​ //若容器变长,则以 elem 值填充新位置

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

总结:

  • 判断是否为空 —— empty
  • 发回元素个数 —— size
  • 重新制定个数 —— resize
#include <list>

void printList(const list<int>& L)
{
	for (list<int>::const_iterator it = L.begin(); it != L.end(); it++)
	{
		cout << *it << " ";
	}
	cout << endl;
}

void test()
{
	list<int>L1;
	L1.push_back(10);
	L1.push_back(20);
	L1.push_back(30);
	L1.push_back(40);
	printList(L1);  //10 20 30 40

	//判断容器是否为空
	if (L1.empty())
	{
		cout << "L1 为空!" << endl;
	}
	else
	{
		cout << "L1 不为空!" << endl;  //L1 不为空!
		cout << "L1 的元素个数为:" << L1.size() << endl;  //L1 的元素个数为:4
	}

	//重新指定大小
	//L1.resize(10);  10 20 30 40 0 0 0 0 0 0  默认值:0
	L1.resize(10,90);
	printList(L1);  //10 20 30 40 90 90 90 90 90 90

	L1.resize(2);
	printList(L1);  //10 20
}

3.7.5 list 插入和删除

功能: 对 list 容器进行数据的插入和删除

函数原型:

  • push_back(elem); //在容器尾部加入一个元素
  • push_back(); //删除容器中最后一个元素
  • push_front(elem); //在容器开头插入一个元素
  • push_front(); //从容器开头移除第一个元素
  • insert(pos,elem); //在 pos 位置插 elem 元素的拷贝,返回新数据的位置
  • insert(pos,n,elem); //在 pos 位置插入 n 个 elem 数据,无返回值
  • insert(pos,beg,end); //在 pos 位置插入 [beg,end) 区间的数据,无返回值
  • clear(); //移除容器的所有数据
  • erase(beg,end); //删除 [beg,end) 区间的数据,返回下一个数据的位置
  • erase(pos); //删除 pos 位置的数据,返回下一个数据的位置
  • remove(elem); //删除容器中所有与 elem 值匹配的元素

总结:

  • 尾插 —— push_back
  • 尾删 —— pop_back
  • 头插 —— push_front
  • 头删 —— pop_front
  • 插入 —— insert ,提供迭代器
  • 删除 —— erase ,提供迭代器
  • 移除 —— remove
  • 清空 —— clear
#include <list>

void printList(const list<int>& L)
{
	for (list<int>::const_iterator it = L.begin(); it != L.end(); it++)
	{
		cout << *it << " ";
	}
	cout << endl;
}

void test()
{
	list<int>L1;

	//尾插
	L1.push_back(10);
	L1.push_back(20);
	L1.push_back(30);

	//头插
	L1.push_front(100);
	L1.push_front(200);
	L1.push_front(300);

	printList(L1);  //300 200 100 10 20 30

	//尾删
	L1.pop_back();
	printList(L1);  //300 200 100 10 20

	//头删
	L1.pop_front();
	printList(L1);  //200 100 10 20

	//insert 插入——迭代器
	  //L1.insert(L1.begin(), 1000);
	  //printList(L1);  //1000 200 100 10 20
	list<int>::iterator it = L1.begin();
	L1.insert(++it, 1000);
	printList(L1);  //200 1000 100 10 20

	//erase 删除——迭代器
	it = L1.begin();
	L1.erase(++it);
	printList(L1);  //200 100 10 20

	//remove 移除
	L1.push_back(10000);
	L1.push_back(10000);
	L1.push_back(10000);
	L1.push_back(10000);
	printList(L1);  //200 100 10 20 10000 10000 10000 10000
	L1.remove(10000);
	printList(L1);  //200 100 10 20

	//清空
	L1.clear();
	printList(L1);
}

3.7.6 list 数据存取

功能描述: 对 list 容器中的数据进行存取

函数原型:

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

总结:

  • list 容器中不可以通过 [ ] 或者 at 方式访问容器
  • 返回第一个元素 —— front();
  • 返回最后一个元素 —— back();
#include <list>

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

	//L1[0]  //不可以使用 [] 和 at 的方式访问 list 容器中的元素
	//L1.at(0)  //原因:list 本质是链表,不是用连续性空间存储数据,迭代器也是不支持随机访问的
	cout << "第一个元素为:" << L1.front() << endl;  //第一个元素为:10
	cout << "第二个元素为:" << L1.back() << endl;  //第一个元素为:40

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

3.7.7 list 反转和排序

功能描述: 将容器中的元素反转,以及将容器中的数据进行排序

函数原型:

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

总结:

  • 反转 —— reverse
  • 排序 —— sort (成员函数)
#include <list>

void printList(const list<int>&L)
{
	for (list<int>::const_iterator it = L.begin(); it != L.end(); it++)
	{
		cout << *it << " ";
	}
	cout << endl;
}

bool myCompare(int v1, int v2)  //回调函数
{
	//降序 —— 让第一个数 > 第二个数
	return v1 > v2;
}

void test()
{
	list<int>L1;
	L1.push_back(20);
	L1.push_back(10);
	L1.push_back(50);
	L1.push_back(40);
	L1.push_back(30);
	cout << "反转前:";
	printList(L1);  //反转前:20 10 50 40 30

	//反转
	L1.reverse();
	cout << "反转后:";
	printList(L1);  //反转后:30 40 50 10 20

	//所有不支持随机访问迭代器的容器,不可以使用标准算法
	//不支持随机访问迭代器的容器,内部会提供对应一些算法
	//#include <algorithm>
	//sort(L1.begin(), L1.end());  全局函数

	//排序 —— 默认排序规则:从小到大 升序
	L1.sort();  //成员函数
	cout << "排序后:";
	printList(L1);  //排序后:10 20 30 40 50

	L1.sort(myCompare);  //sort底层
	cout << "降序后:";
	printList(L1);  //降序后:50 40 30 20 10
}

3.7.8 排序案例

案例描述: 将 Person 自定义数据类型进行排序,Person 中属性有姓名、年龄、身高

排序规则: 按照年龄进行升序,如果年龄相同按照身高进行降序

总结:

  • 对于自定义数据类型,必须要指定排序规则,否则编译器不知道如何进行排序
  • 高级排序只是在排序规则上再进行一次逻辑规则制定(二次判断),并不复杂
#include <iostream>
using namespace std;
#include <list>

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

	string m_Name;
	int m_Age;
	int m_Height;
};

//指定排序规则——高级排序
bool comparePerson(Person& p1, Person& p2)
{
	//按照年龄 升序
	if (p1.m_Age == p2.m_Age)
	{
		//年龄相同 按照身高降序
		return p1.m_Height > p2.m_Height;
	}
	else
	{
		return p1.m_Age < p2.m_Age;
	}
}

void test()
{
	list<Person>L;  //创建容器

	//准备数据
	Person p1("刘备", 35, 175);
	Person p2("曹操", 45, 180);
	Person p3("孙权", 40, 170);
	Person p4("赵云", 25, 190);
	Person p5("张飞", 35, 160);
	Person p6("关羽", 35, 200);

	//插入数据
	L.push_back(p1);
	L.push_back(p2);
	L.push_back(p3);
	L.push_back(p4);
	L.push_back(p5);
	L.push_back(p6);

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

	//排序
	cout << "--------------------------------" << endl;
	cout << "排序后:" << endl;

	L.sort(comparePerson);  //自定义数据类型,排序必须指定规则
	for (list<Person>::const_iterator it = L.begin(); it != L.end(); it++)
	{
		cout << "姓名:" << (*it).m_Name << "  年龄:" << it->m_Age << "  身高:" << it->m_Height << endl;
	}
}

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


姓名:刘备  年龄:35  身高:175
姓名:曹操  年龄:45  身高:180
姓名:孙权  年龄:40  身高:170
姓名:赵云  年龄:25  身高:190
姓名:张飞  年龄:35  身高:160
姓名:关羽  年龄:35  身高:200
----------------------------------
排序后:
姓名:赵云  年龄:25  身高:190
姓名:关羽  年龄:35  身高:200
姓名:刘备  年龄:35  身高:175
姓名:张飞  年龄:35  身高:160
姓名:孙权  年龄:40  身高:170
姓名:曹操  年龄:45  身高:180

3.8 set/multiset 容器

3.8.1 set 基本概念

简介: 所有元素都会在插入时自动被排序

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

set 和 multiset 区别:

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

3.8.2 set 构造和赋值

功能描述: 创建set 容器以及赋值

构造:

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

赋值:

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

总结:

  • set 容器插入数据时用 insert
  • set 容器插入数据时会自动排序
#include <set>

void printSet(set<int>& s)
{
	for (set<int>::iterator it = s.begin(); it != s.end(); it++)
	{
		cout << *it << " ";
	}
	cout << endl;
}

void test()
{
	set<int>s1;

	//插入数据 只有 insert 方式
	s1.insert(10);
	s1.insert(40);
	s1.insert(30);
	s1.insert(20);
	s1.insert(30);

	//遍历容器
	//set容器特点:
	//1. 所有元素插入时自动被排序
	//2. set容器不允许插入重复值
	printSet(s1);  //10 20 30 40

	//拷贝构造
	set<int>s2(s1);
	printSet(s2);  //10 20 30 40

	//赋值
	set<int>s3;
	s3 = s2;
	printSet(s3);  //10 20 30 40
}

3.8.3 set 大小和交换

功能描述: 统计 set 容器大小以及交换 set 容器

函数原型:

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

总结:

  • 统计大小 —— size
  • 判断是否为空 —— empty
  • 交换容器 —— swap
#include <set>

void printSet(set<int>& s)
{
	for (set<int>::iterator it = s.begin(); it != s.end(); it++)
	{
		cout << *it << " ";
	}
	cout << endl;
}

void test()
{
	set<int>s1;

	//插入数据
	s1.insert(10);
	s1.insert(20);
	s1.insert(30);
	s1.insert(40);

	//打印容器
	printSet(s1);  //10 20 30 40

	//判断是否为空
	if (s1.empty())
	{
		cout << "s1 为空!" << endl;
	}
	else
	{
		cout << "s1 不为空!" << endl;
		cout << "s1 的大小为:" << s1.size() << endl;  //s1 的大小为:4
	}

	set<int>s2;
	s2.insert(100);
	s2.insert(200);
	s2.insert(300);
	s2.insert(400);

	cout << "交换前:" << endl;
	printSet(s1);  //10 20 30 40
	printSet(s2);  //100 200 300 400

	cout << "交换后:" << endl;
	s1.swap(s2);
	printSet(s1);  //100 200 300 400
	printSet(s2);  //10 20 30 40
}

3.8.4 set 插入和删除

功能描述: set 容器进行插入和删除数据

函数原型:

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

  • clear(); //清除所有元素

  • erase(pos); //删除 pos 迭代器所指的元素,返回下一个元素的迭代器

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

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

总结:

  • 插入 —— insert
  • 删除 —— erase
  • 清空 —— clear
#include <set>

void printSet(set<int>& s)
{
	for (set<int>::iterator it = s.begin(); it != s.end(); it++)
	{
		cout << *it << " ";
	}
	cout << endl;
}

void test()
{
	set<int>s1;
	s1.insert(30);
	s1.insert(10);
	s1.insert(20);
	s1.insert(40);

	//遍历
	printSet(s1);  //10 20 30 40

	//删除
	s1.erase(s1.begin());  //迭代器
	printSet(s1);  //20 30 40  会排序

	//删除重载版本
	s1.erase(30);  //删除指定元素
	printSet(s1);  //20 40

	//清空
	//s1.erase(s1.begin(),s1.end());
	s1.clear();
	printSet(s1);
}

3.8.5 set 查找和统计

功能描述: 对 set 容器进行查找数据以及统计数据

函数原型:

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

总结:

  • 查找 —— find(返回的是迭代器)
  • 统计 —— count(对于 set ,结果为 0 或 1)
#include <set>

void test()
{
	set<int>s1;
	s1.insert(10);
	s1.insert(20);
	s1.insert(30);
	s1.insert(40);
	s1.insert(30);
	s1.insert(30);

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

	//统计
	//对于 set 而言,统计结果 要么是0,要么是1
	int num = s1.count(30);
	cout << "num = " << num << endl;  //num = 1
}

3.8.6 set 和multiset 区别

区别:

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

总结:

  • 如果不允许插入重复数据可以利用 set
  • 如果需要插入重复数据利用 multiset
#include <set>

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

	ret = s.insert(10);
	if(ret.second)
	{
		cout<<"第二次插入成功"<<endl;
	}
	else
	{
		cout<<"第二次插入失败"<<endl;
	}

	multiset<int>ms;
	//允许插入重复的值
	ms.insert(10);
	ms.insert(10);
	ms.insert(10);

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

第一次插入成功
第二次插入失败
10 10 10

3.8.7 pair 对组创建

功能描述: 成对出现的数据,利用对组可以返回两个数据

两种创建方式:

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

总结: 两种方式都可以创建对组,记住一种即可

//对组创建
void test()
{
	//第一种方式
	pair<string,int>p1("Tom",20);
	cout<<"姓名:"<<p1.first<<" 年龄:"<<p1.second<<endl;

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


姓名:Tom 年龄:20
姓名:Jerry 年龄:30

3.8.8 set 容器排序

学习目标: set 容器默认排序规则为从小到大,掌握如何改变排序规则

主要技术点: 利用仿函数,可以改变排序规则

总结: 利用仿函数可以指定 set 容器的排序规则

//示例一: set 存放内置数据类型
#include <set>

//仿函数
class MyCompare
{
public:
	bool operator()(int v1,int v2)const  //返回值是 布尔 类型
	{
		return v1>v2;
	}
};

void test()
{
	set<int>s1;
	s1.insert(10);
	s1.insert(40);
	s1.insert(20);
	s1.insert(50);
	s1.insert(30);

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

	//指定排序规则——从大到小
	//set 容器插入数据之后,就改不了了,需在插入数据之前,在参数列表中,加入仿函数,指定排序规则
	set<int,MyCompare>s2;
	s2.insert(10);
	s2.insert(40);
	s2.insert(20);
	s2.insert(50);
	s2.insert(30);

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

总结: 对于自定义数据类型,set 必须指定排序规则才可以插入数据

//示例二: set 存放自定义数据类型
#include <set>

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

class cmparePerson
{
public:
	bool operator()(const Person&p1,const Person&p2)
	{
		//按照年龄 降序
		return p1.m_Age>p2.m_Age;
	}
};

void test()
{
	//自定义数据类型,都会指定排序规则——利用仿函数:本质是一个类
	set<Person,cmparePerson>s;
	
	//创建Person 对象
	Person p1("刘备",24);
	Person p2("关羽",28);
	Person p3("张飞",25);
	Person p4("赵云",21);
	
	s.insert(p1);
	s.insert(p2);
	s.insert(p3);
	s.insert(p4);

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


姓名:关羽 年龄:28
姓名:张飞 年龄:25
姓名:刘备 年龄:24
姓名:赵云 年龄:21

3.9 map/multimap 容器

3.9.1 map 基本概念

简介:

map 中所有元素都是pair

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

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

本质:

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

优点:

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

map 和 multimap 区别:

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

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

3.9.2 map 构造和赋值

功能描述: 对 map 容器进行构造和赋值操作

函数原型:

  • 构造:

    • map<T1,T2>mp; //map 默认构造函数

    • map(const map &mp); //拷贝构造函数

  • 赋值:

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

总结: map 中所有元素都是成对出现,加入数据时要使用对组

#include <map>

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

void test()
{
	//默认构造
	map<int,int>m1;

	//会按照 key 值自动排序,成对出现——对组
	m1.insert(pair<int,int>(1,10));  //key值为1,value值为10
	m1.insert(pair<int,int>(3,30));
	m1.insert(pair<int,int>(2,20));
	m1.insert(pair<int,int>(4,40));

	printMap(m1);

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

	//赋值
	map<int,int>m3;
	m3=m2;
	printMap(m3);
}


key = 1 value = 10
key = 2 value = 20
key = 3 value = 30
key = 4 value = 40

key = 1 value = 10
key = 2 value = 20
key = 3 value = 30
key = 4 value = 40

key = 1 value = 10
key = 2 value = 20
key = 3 value = 30
key = 4 value = 40

3.9.3 map 大小和交换

功能描述: 统计map 容器大小以及交换map 容器

函数原型:

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

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

  • swap(st); //交换两个集合容器

总结:

  • 统计大小 —— size
  • 判断是否为空 —— empty
  • 交换容器 —— swap
#include <map>

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

void test()
{
	map<int,int>m1;
	m1.insert(pair<int,int>(1,10));
	m1.insert(pair<int,int>(2,20));
	m1.insert(pair<int,int>(3,30));

	if(m1.empty())
	{
		cout<<"m1 为空"<<endl;
	}
	else
	{
		cout<<"m1 不为空"<<endl;
		cout<<"m1 的大小为:"<<m1.size()<<endl;
	}

	map<int,int>m2;
	m2.insert(pair<int,int>(4,100));
	m2.insert(pair<int,int>(5,200));
	m2.insert(pair<int,int>(6,300));

	cout<<"交换前:"<<endl;
	printMap(m1);
	printMap(m2);

	m1.swap(m2);
	cout<<"交换后:"<<endl;
	printMap(m1);
	printMap(m2);
}


m1 不为空
m1 的大小为:3
交换前:
key = 1 value = 10
key = 2 value = 20
key = 3 value = 30

key = 4 value = 100
key = 5 value = 200
key = 6 value = 300

交换后:
key = 4 value = 100
key = 5 value = 200
key = 6 value = 300

key = 1 value = 10
key = 2 value = 20
key = 3 value = 30

3.9.4 map 插入和删除

功能描述: map 容器进行插入和删除数据

函数原型:

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

  • clear(); //清除所有元素

  • erase(pos); //删除 pos 迭代器所指的元素,返回下一个元素的迭代器

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

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

总结: map 插入方式很多,记住一种即

  • 插入 —— insert

  • 删除 —— erase

  • 清空 —— clear

#include <map>

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

void test()
{
	map<int,int>m;
	
	//插入
	//方法一
	m.insert(pair<int,int>(1,10));

	//方法二
	m.insert(make_pair(2,20));

	//方法三
	m.insert(map<int,int>::value_type(3,30));  //map 容器作用域下:: 值类型
	
	//方法四——不建议插入
	m[4]=40;

	//cout<<m[5]<<endl;  //若key不存在,咋会自动创建数据
	//[ ]不建议插入,用途:可以利用key 访问到value
	cout<<m[4]<<endl;

	printMap(m);

	//删除
	m.erase(m.begin());  //迭代器
	printMap(m);

	m.erase(3);  //按照key删除
	printMap(m);

	//清空
	//m.erase(m.begin(),m.end());  //区间
	m.clear();
	printMap(m);
}


40  //自动创建的数据
key = 1 value = 10
key = 2 value = 20
key = 3 value = 30
key = 4 value = 40

key = 2 value = 20
key = 3 value = 30
key = 4 value = 40

key = 2 value = 20
key = 4 value = 40

3.9.5 map查找和统计

功能描述: 对map 容器进行查找数据以及统计数据

函数原型:

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

总结:

  • 查找 —— find(返回的是迭代器)
  • 统计 —— count(对于map ,结果为0或1)
#include <map>

void test()
{
    //查找
    map<int,int>m;
    m.insert(pair<int,int>(1,10));
    m.insert(pair<int,int>(2,20));
    m.insert(pair<int,int>(3,30));
    //m.insert(pair<int,int>(3,40));  //key值相同,无法插入

    map<int,int>::iterator pos=m.find(3);
    if(pos!=m.end())
    {
        cout<<"查到了元素 key = "<<(*pos).first<<" value = "<<(*pos).second<<endl;
    }
    else
    {
        cout<<"未找到元素"<<endl;
    }

    //统计——结果要么是 0 ,要么是1
    //multimap 的count 统计可能大于1
    //map 不允许插入重复的key 元素
    int num=m.count(3);
    cout<<"num = "<<num<<endl;
}


查到了元素 key = 3 value = 30
num = 1

3.9.6 map 容器排序

学习目标: map 容器默认排序规则为 按照key 值进行 从小到大 排序,掌握如何改变排序规则

主要技术点: 利用仿函数,可以改变排序规则

总结:

  • 利用仿函数可以指定 map 容器的排序规则
  • 对于自定义数据类型,map 容器必须要指定排序规则,同 set 容器
#include <map>

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

void test()
{
    //默认 从小到大 排序
    map<int,int,MyCompare>m;
    m.insert(make_pair(1,10));
    m.insert(make_pair(2,20));
    m.insert(make_pair(5,50));
    m.insert(make_pair(3,30));
    m.insert(make_pair(4,40));

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


key = 5 value = 50
key = 4 value = 40
key = 3 value = 30
key = 2 value = 20
key = 1 value = 10

3.10 案例:员工分组

案例描述:

  • 公司招聘10个员工(ABCDEFGHIJ),10名员工需被指派在哪个部门
  • 员工信息:姓名、工资组成;部门分为:策划、美术、研发
  • 随机给10名员工分配部门和工资
  • 通过multimap 进行信息的插入,key(部门编号)value(员工)(有可能都在一个部门)
  • 分部门显示员工信息

实现步骤:

  1. 创建10名员工,放在vector 中
  2. 遍历vector 容器,取出每个员工,进行随机分组
  3. 分组后,将员工信息编号作为key ,具体员工作为value ,放入multimap 容器中
  4. 分部门显示员工信息
#include <iostream>
using namespace std;
#include <vector>
#include <map>
#include <ctime>

#define CEHUA 0
#define MEISHU 1
#define YANFA 2

class Worker
{
public:
    string m_Name;
    int m_Salary;
};

void createWorker(vector<Worker>&v)
{
    string nameSeed = "ABCDEFGHIJ";
    for(int i=0;i<10;i++)
    {
        Worker worker;
        worker.m_Name="员工";
        worker.m_Name+=nameSeed[i];
        worker.m_Salary=rand()%10000 + 10000;  //10000——19999

        //将员工放入到容器中
        v.push_back(worker);
    }
}

//员工分组
void setGroup(vector<Worker>&v,multimap<int,Worker>&m)
{
    for(vector<Worker>::iterator it=v.begin();it!=v.end();it++)
    {
        //产生随机部门编号
        int depId = rand()%3;  //0  1  2

        //将员工插入到分组中
        //key:部门编号  value:具体员工
        m.insert(make_pair(depId,*it));
    }
}

void showWorkerByGroup(multimap<int,Worker>&m)
{
    cout<<"策划部门:"<<endl;
    multimap<int,Worker>::iterator pos = m.find(0);
    int count = m.count(0);  //统计具体人数
    int index = 0;
    for(; pos!=m.end() && index < count; pos++, index++)
    {
        cout<<"姓名:"<<pos->second.m_Name<<" 工资:"<<pos->second.m_Salary<<endl;
    }

    cout<<"-----------------------------"<<endl;
    cout<<"美术部门:"<<endl;
    pos = m.find(1);
    count = m.count(1);  //统计具体人数
    index = 0;
    for(; pos!=m.end() && index < count; pos++, index++)
    {
        cout<<"姓名:"<<pos->second.m_Name<<" 工资:"<<pos->second.m_Salary<<endl;
    }

    cout<<"-----------------------------"<<endl;
    cout<<"研发部门:"<<endl;
    pos = m.find(2);
    count = m.count(2);  //统计具体人数
    index = 0;
    for(; pos!=m.end() && index < count; pos++, index++)
    {
        cout<<"姓名:"<<pos->second.m_Name<<" 工资:"<<pos->second.m_Salary<<endl;
    }
}

int main()
{
    srand((unsigned int)time(NULL));

    //1、创建员工
    vector<Worker>vWorker;
    createWorker(vWorker);

    //2、员工分组
    multimap<int,Worker>mWorker;
    setGroup(vWorker,mWorker);

    //3、分组显示员工
    showWorkerByGroup(mWorker);

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

    system("pause");
    return 0;
}


策划部门:
姓名:员工A 工资:11679
姓名:员工D 工资:12405
姓名:员工E 工资:10068
姓名:员工I 工资:17404
-----------------------------
美术部门:
姓名:员工C 工资:15313
姓名:员工F 工资:11992
-----------------------------
研发部门:
姓名:员工B 工资:17882
姓名:员工G 工资:10535
姓名:员工H 工资:16263
姓名:员工J 工资:12212

4. STL - 函数对象

4.1 函数对象

4.1.1 函数对象的概念

概念:

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

本质:

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

4.1.2 函数对象使用

特点:

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

总结: 仿函数写法非常灵活,可以作为参数进行传递

class MyAdd
{
public:
    int operator()(int v1,int v2)  //可以有参数、返回值
    {
        return v1 + v2;
    }
};

class MyPrint
{
public:
    MyPrint()
    {
        this->count=0;
    }

    void operator()(string test)
    {
        cout<<test<<endl;
        count++;  //统计使用次数
    }

    int count;  //内部自己的状态
};

void doPrint(MyPrint & mp,string test)
{
    mp(test);
}

void test()
{
    //1、函数对象在使用时,可以像普通函数那样调用,可以有参数,可以有返回值
    MyAdd myAdd;
    cout<<myAdd(10,10)<<endl;  //20

    //2、函数对象超出普通函数的概念,函数对象可以有自己的状态
    MyPrint myPrint;
    myPrint("Hello World!");
    myPrint("Hello World!");
    myPrint("Hello World!");
    myPrint("Hello World!");

    cout<<"myPrint 调用的次数:"<<myPrint.count<<endl;  //myPrint 调用的次数:4

    //3、函数对象可以作为参数传值
    doPrint(myPrint,"Hello C++ !");  //Hello C++ !
}

4.2 谓词

4.2.1 谓词概念

概念:

  • 返回 bool 类型的仿函数称为 谓词
  • 如果 operator() 接收一个参数,那么叫做一元谓词
  • 如果 operator() 接收两个参数,那么叫做二元谓词

4.2.2 一元谓词

总结: 参数只有一个的谓词,称为一元谓词

#include <vector>
#include <algorithm>

class GreaterFive
{
public:
    //谓词
    bool operator()(int val)  //一元谓词
    {
        return val > 5;
    }
};

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

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

4.2.3 二元谓词

总结: 参数只有两个的谓词,称为二元谓词

#include <vector>
#include <algorithm>

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

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

    sort(v.begin(),v.end());  //需包含头文件
    for(vector<int>::iterator it=v.begin();it!=v.end();it++)
    {
        cout<<*it<<" ";  //10 20 30 40 50
    }
    cout<<endl;

    //使用函数对象,改变算法策略,变为排序规则为 从大到小
    sort(v.begin(),v.end(),MyCompare());  //匿名函数对象
    cout<<"-----------------"<<endl;
    for(vector<int>::iterator it=v.begin();it!=v.end();it++)
    {
        cout<<*it<<" ";  //50 40 30 20 10
    }
    cout<<endl;
}

4.3 内建函数对象

4.3.1 内建函数对象意义

概念: STL内建了一些函数对象

  • 算术仿函数
  • 关系仿函数
  • 逻辑仿函数

用法:

  • 这些仿函数所产生的对象,用法和一般函数完全相同
  • 使用内建函数对象,需要引入头文件 #include <functional>

4.3.2 算术仿函数

功能描述:

  • 实现四则运算
  • 其中 negate 是一元运算,其他都是二元运算

仿函数原型:

  • template<class T> T plus<T> //加法仿函数
  • template<class T> T minus<T> //减法仿函数
  • template<class T> T multiplies<T> //乘法仿函数
  • template<class T> T divides<T> //除法仿函数
  • template<class T> T modulus<T> //取模仿函数
  • template<class T> T negate<T> //取反仿函数

总结:

  • 使用内建函数对象时,需要引入头文件 #include <functional>
  • 使用二元仿函数,默认都是同种数据类型,即模板参数列表只需一个参数
#include <functional>  //内建函数头文件

void test()
{
    //negate 一元仿函数 取反
    negate<int>n;
    cout<<n(50)<<endl;  //-50

    //plus 二元仿函数 加法
    plus<int>p;  //默认是同种数据类型
    cout<<p(10,10)<<endl;  //20
}

4.3.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<> 大于

#include <vector>
#include <algorithm>
#include <functional>  //内建函数头文件

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

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

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

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

4.3.4 逻辑仿函数

功能描述: 实现逻辑运算

函数原型:

  • template<class T> bool logical_and<T> //逻辑与
  • template<class T> bool logical_or<T> //逻辑或
  • template<class T> bool logical_not<T> //逻辑非

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

#include <vector>
#include <algorithm>
#include <functional>  //内建函数头文件

void test()
{
    vector<bool>v1;
    v1.push_back(true);
    v1.push_back(false);
    v1.push_back(true);
    v1.push_back(false);

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

    //利用逻辑非 将容器v1,放在容器v2中,并执行取反操作
    vector<bool>v2;

    //目标容器
    v2.resize(v1.size());

    //transform 算法:搬运,目标容器必须开辟一段空间
    transform(v1.begin(),v1.end(),v2.begin(),logical_not<bool>());  //内建函数
    for(vector<bool>::iterator it=v2.begin();it!=v2.end();it++)
    {
        cout<<*it<<" ";  //0 1 0 1
    }
    cout<<endl;
}

5. STL - 常用算法

概述: 算法主要是由头文件 algorithmfunctionalnumeric 组成

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

5.1 常用遍历算法

学习目标: 掌握常用的遍历算法

算法简介:

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

5.1.1 for_each

功能描述: 实现遍历容器

函数原型: for_each(iterator beg, iterator end, _func);

  • beg —— 起始迭代器
  • end —— 结束迭代器
  • _func —— 函数或函数对象

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

#include <vector>
#include <algorithm>

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

//仿函数
class print02
{
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(),print01);  //0 1 2 3 4 5 6 7 8 9
    cout<<endl;

    //print02() —— 匿名对象
    for_each(v.begin(),v.end(),print02());  //0 1 2 3 4 5 6 7 8 9
    cout<<endl;
}

5.1.2 transform

功能描述: 搬运容器到另一个容器中

函数原型: transform(iterator beg1,iterator end1,iterator beg2,_func);

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

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

#include <vector>
#include <algorithm>

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

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

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

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

    for_each(vTarget.begin(),vTarget.end(),MyPrint());
    cout<<endl;
}


100 101 102 103 104 105 106 107 108 109

5.2 常用查找算法

学习目标: 掌握常用的查找算法

算法简介:

  • find //查找元素
  • find_if //按条件查找元素
  • adjacent_find //c哈找相邻重复元素
  • binary_search //二分查找法
  • count //统计元素个数
  • count_if //按条件统计元素个数

5.2.1 find

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

函数原型: find(iterator beg,iterator end,value);

  • 按值查找元素,找到返回指定位置迭代器,找不到返回结束迭代器位置
  • beg —— 开始迭代器
  • end —— 结束迭代器
  • value —— 查找的元素

总结:

  • 利用 find 可以在容器中找到指定的元素,返回值是 迭代器
  • 查找自定义数据类型时,必须重载 == ,否则不知道该如何对比底层数据
#include <vector>
#include <algorithm>

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

    string m_Name;
    int m_Age;
};

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

    //查找 容器中 是否有 5 这个元素
    vector<int>::iterator it1 = find(v1.begin(),v1.end(),5);
    if(it1 == v1.end())
    {
        cout<<"未找到该元素"<<endl;
    }
    else
    {
        cout<<"找到:"<<*it1<<endl;
    }
    //查找 自定义数据类型
    vector<Person>v2;
    //创建数据
    Person p1("张三",20);
    Person p2("李四",24);
    Person p3("王五",18);
    Person p4("赵六",35);

    //放入到容器中
    v2.push_back(p1);
    v2.push_back(p2);
    v2.push_back(p3);
    v2.push_back(p4);

    Person pp("李四",24);

    vector<Person>::iterator it2 = find(v2.begin(),v2.end(),pp);
    if(it2 == v2.end())
    {
        cout<<"没有找到"<<endl;
    }
    else
    {
        cout<<"找到元素  姓名:"<<it2->m_Name<<" 年龄:"<<it2->m_Age<<endl;
    }
}


找到:5
找到元素  姓名:李四 年龄:24

5.2.2 find_if

功能描述: 按条件查找元素

函数原型: find_if(iterator beg,iterator end,_Pred);

  • 按值查找元素,找到返回指定位置迭代器,找不到返回结束迭代器的位置
  • beg —— 开始迭代器
  • end —— 结束迭代器
  • _Pred —— 函数或者谓词(返回 bool 类型的仿函数)
#include <vector>
#include <algorithm>

class GreaterFive
{
public:
    bool operator()(int val)
    {
        return val>5;
    }
};

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

    string m_Name;
    int m_Age;
};

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

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

    vector<int>::iterator it1 = find_if(v1.begin(),v1.end(),GreaterFive());
    if(it1 == v1.end())
    {
        cout<<"未找到"<<endl;
    }
    else
    {
        cout<<"找到大于 5 的数:"<<*it1<<endl;  //找到大于 5 的数:6
    }

    //查找 自定义数据类型
     vector<Person>v2;

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

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

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


找到大于 5 的数:6
找到姓名:ccc 年龄:30

5.2.3 adjacent_find

功能描述: 查找 相邻 、重复 元素

函数原型: adjacent_find(iterator beg,iterator end);

  • 查找相邻重复元素,返回相邻元素的第一个位置的迭代器
  • beg —— 开始迭代器
  • end —— 结束迭代器

总结: 面试题中如果出现查找重复相邻元素,记得使用 STL 中的 adjacent_find 算法

#include <vector>
#include <algorithm>

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

    vector<int>::iterator pos = adjacent_find(v.begin(),v.end());
    if(pos == v.end())
    {
        cout<<"未找到相邻、重复元素"<<endl;
    }
    else
    {
        cout<<"找到相邻重复元素:"<<*pos<<endl;  //找到相邻重复元素:3
    }
}

5.2.4 binary_search

功能描述: 查找指定元素是否存在。

函数原型: bool binary_search(iterator beg,iterator end,value);

  • 查找指定的元素,查到 返回 true,否则 返回false
  • beg —— 开始迭代器
  • end —— 结束迭代器
  • value —— 查找的元素

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

总结: 二分查找法查找效率很高,值得注意的是查找的容器中元素必须是有序序列

#include <vector>
#include <algorithm>

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

    //v.push_back(2);  //如果是无序序列,结果未知

    //查找容器中是否有 9 元素
    //注意:容器必须是有序序列
    bool ret = binary_search(v.begin(),v.end(),9);
    if(ret)
    {
        cout<<"找到了"<<endl;  //找到了
    }
    else
    {
        cout<<"未找到"<<endl;
    }
}

5.2.5 count

功能描述: 统计元素个数

函数原型: count(iterator beg,iterator end,value);

  • 统计元素出现的次数
  • beg —— 开始迭代器
  • end —— 结束迭代器
  • value —— 统计的元素

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

#include <vector>
#include <algorithm>

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

    bool operator==(const Person &p)
    {
        if(this->m_Age==p.m_Age)
        {
            return true;
        }
        else
        {
            return false;
        }
    }

    string m_Name;
    int m_Age;
};

void test()
{
    //统计 内置数据类型
    vector<int>v1;
    v1.push_back(10);
    v1.push_back(40);
    v1.push_back(30);
    v1.push_back(40);
    v1.push_back(20);
    v1.push_back(40);

    int num1 = count(v1.begin(),v1.end(),40);
    cout<<"40 的个数为:"<<num1<<endl;  //40 的个数为:3

    //统计 自定义数据类型
    vector<Person>v2;
    Person p1("刘备",35);
    Person p2("关羽",35);
    Person p3("张飞",35);
    Person p4("赵云",30);
    Person p5("曹操",40);

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

    Person p("诸葛亮",35);
    
    int num2 = count(v2.begin(),v2.end(),p);
    cout<<"和诸葛亮同岁数的人员个数为:"<<num2<<endl;  //和诸葛亮同岁的人员个数为:3
}

5.2.6 count_if

功能描述: 按条件统计元素个数

函数原型: count_if(iterator beg,iterator end,_Pred);

  • 按条件统计元素出现的次数
  • beg —— 开始迭代器
  • end —— 结束迭代器
  • _Pred —— 谓词
#include <vector>
#include <algorithm>

class Greater20
{
public:
    bool operator()(int val)
    {
        return val>20;
    }
};

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

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

void test()
{
    //统计 内置数据类型
    vector<int>v1;
    v1.push_back(10);
    v1.push_back(40);
    v1.push_back(30);
    v1.push_back(20);
    v1.push_back(40);
    v1.push_back(20);

    int num1 = count_if(v1.begin(),v1.end(),Greater20());
    cout<<"大于20 的元素个数为:"<<num1<<endl;  //大于20 的元素个数为:3

    //统计 自定义数据类型
    vector<Person>v2;

    Person p1("刘备",35);
    Person p2("关羽",35);
    Person p3("张飞",35);
    Person p4("赵云",40);
    Person p5("曹操",20);

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

    //统计 大于20岁的人员个数
    int num2 = count_if(v2.begin(),v2.end(),AgeGreater20());
    cout<<"年龄大于20岁 的人员个数为:"<<num2<<endl;  //年龄大于20岁 的人员个数为:4
}

5.3 常用排序算法

学习目标: 掌握常用的排序算法

算法简介:

  • sort //对容器内元素进行排序
  • random_shuffle //洗牌 指定范围内的元素随机调整次序
  • merge //容器元素合并,并存储到另一容器中
  • reverse //反转指定范围的元素

5.3.1 sort

功能描述: 对容器内元素进行排序

函数原型: sort(iterator beg,iterator end,_Pred);

  • 按值查找元素,找到 返回指定位置迭代器,未找到 返回结束迭代器位置
  • beg —— 开始迭代器
  • end —— 结束迭代器
  • _Pred —— 谓词

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

#include <vector>
#include <algorithm>
#include <functional>

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

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

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

    //改为 降序
    //greater<int>() 内建函数对象,包含头文件 #include <functional>
    sort(v.begin(),v.end(),greater<int>());
    for_each(v.begin(),v.end(),myPrint);  //50 40 30 20 10
    cout<<endl;
}

5.3.2 random_shuffle

功能描述: 洗牌,指定范围内的元素随即调整次序

函数原型: random_shuffle(iterator beg,iterator end);

  • 指定范围内的元素随即调整次序
  • beg —— 开始迭代器
  • end —— 结束迭代器

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

#include <vector>
#include <algorithm>
#include <ctime>

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

void test()
{
    srand((unsigned int)time(NULL));
    vector<int>v;
    for(int i=0;i<10;i++)
    {
        v.push_back(i);
    }

    //利用洗牌 算法 打乱顺序
    random_shuffle(v.begin(),v.end());
    for_each(v.begin(),v.end(),myPrint);  //5 3 0 2 8 6 4 1 9 7
    cout<<endl;
}

5.3.3 merge

功能描述: 两个容器元素合并,并存储在另一个容器中

函数原型: merge(iterator beg1,iterator end1,iterator beg2,iterator end2,iterator dest);

  • beg1 —— 容器1开始迭代器
  • end1 —— 容器1结束迭代器
  • beg2 —— 容器2开始迭代器
  • end2 —— 容器2结束迭代器
  • dest —— 目标容器开始迭代器

总结: merge 合并的两个容器必须是 有序序列

#include <vector>
#include <algorithm>

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

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

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

    //目标容器
    vector<int>vTarget;

    //提前给目标容器分配空间
    vTarget.resize(v1.size()+v2.size());
    merge(v1.begin(),v1.end(),v2.begin(),v2.end(),vTarget.begin());

    for_each(vTarget.begin(),vTarget.end(),MyPrint);
    cout<<endl;
    //0 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 9 9 10 
}

5.3.4 reverse

功能描述: 将容器内元素进行反转

函数原型: reverse(iterator beg,iterator end);

  • beg —— 开始迭代器
  • end —— 结束迭代器

总结: reverse 反转区间内元素,面试题可能会涉及到

#include <vector>
#include <algorithm>

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

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

    cout<<"反转前:";
    for_each(v.begin(),v.end(),MyPrint);
    cout<<endl;

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


反转前:10 30 50 20 40 
反转后:40 20 50 30 10 

5.4 常用拷贝和替换算法

学习目标: 掌握常用的拷贝和替换算法

算法简介:

  • copy //容器内指定范围的元素拷贝到另一容器中
  • replace //将容器内指定范围的旧元素修改为新元素
  • replace_if //容器内指定范围满足条件的元素替换为新元素
  • swap //互换两个容器的元素

5.4.1 copy

功能描述: 容器内指定范围的元素拷贝到另一容器中

函数原型: copy(iterator beg,iterator end,iterator dest);

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

  • beg —— 开始迭代器

  • end —— 结束迭代器

  • dest —— 目标起始迭代器

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

#include <vector>
#include <algorithm>

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

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

    vector<int>v2;

    //开辟空间
    v2.resize(v1.size());
    copy(v1.begin(),v1.end(),v2.begin());
    for_each(v2.begin(),v2.end(),MyPrint);
    cout<<endl;
}


0 1 2 3 4 5 6 7 8 9

5.4.2 replace

功能描述: 将容器指定范围内的旧元素修改为新元素

函数原型:

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

  • beg —— 开始迭代器
  • end —— 结束迭代器
  • oldvalue —— 旧元素
  • newvalue —— 新元素

总结: replace 会替换区间内满足条件的元素

#include <vector>
#include <algorithm>

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

// void MyPrint(int val)
// {
//     cout<<val<<" ";
// }

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

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

    replace(v.begin(),v.end(),20,2000);
    cout<<"替换后:";
    for_each(v.begin(),v.end(),MyPrint());
    cout<<endl;
}


替换前:20 30 50 30 40 20 10 20
替换后:2000 30 50 30 40 2000 10 2000

5.4.3 replace_if

功能描述: 将区间内满足条件的元素,替换成指定元素

函数原型: replace_if(iterator beg,iterator end,_pred,newvalue);

  • 按条件替换元素,满足条件的替换成指定元素
  • beg —— 开始迭代器
  • end —— 结束迭代器
  • _pred —— 谓词
  • newvalue —— 替换的新元素

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

#include <vector>
#include <algorithm>

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

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

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

    //大于等于30  替换为 3000
    cout<<"替换前:";
    for_each(v.begin(),v.end(),MyPrint());
    cout<<endl;

    replace_if(v.begin(),v.end(),Greater30(),3000);
    cout<<"替换后:";
    for_each(v.begin(),v.end(),MyPrint());
    cout<<endl;
}


替换前:10 40 20 40 30 50 20 30
替换后:10 3000 20 3000 3000 3000 20 3000

5.4.4 swap

功能描述: 互换两个容器的元素

函数原型: swap(iterator c1,iterator c2);

  • c1 —— 容器1
  • c2 —— 容器2

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

#include <vector>
#include <algorithm>

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

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

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

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

    swap(v1,v2);
    cout<<"交换后:"<<endl;
    cout<<"v1:";
    for_each(v1.begin(),v1.end(),MyPrint());
    cout<<endl;
    cout<<"v2:";
    for_each(v2.begin(),v2.end(),MyPrint());
    cout<<endl;
}


交换前:
v1:0 1 2 3 4 5 6 7 8 9
v2:100 101 102 103 104 105 106 107 108 109
-------------------------------------------------
交换后:
v1:100 101 102 103 104 105 106 107 108 109
v2:0 1 2 3 4 5 6 7 8 9

5.5 常用算术生成算法

学习目标: 掌握常用的算术生成算法

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

算法简介:

  • accumulate //计算容器元素累计总和
  • fill //向容器中添加元素

5.5.1 accumulate

功能描述: 计算区间内 容器元素累计总和

函数原型: accumulate(iterator beg,iterator end,value);

  • beg —— 开始迭代器
  • end —— 结束迭代器
  • value —— 起始累加值

总结: accumulate 使用时头文件是 include <numeric> ,这个算法很实用

#include <vector>
#include <numeric>

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

    int total = accumulate(v.begin(),v.end(),0);  //0:起始累加值
    cout<<total<<endl;  //5050
}

5.5.2 fill

功能描述: 向容器中填充指定的元素

函数原型: fill(iterator beg,iterator end,value);

  • beg —— 开始迭代器
  • end —— 结束迭代器
  • value —— 填充的值

总结: 利用 fill 可以将容器区间内元素填充为 指定的值

#include <vector>
#include <numeric>
#include <algorithm>

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

void test()
{
    vector<int>v;
    v.resize(10);  //0 0 0 0 0 0 0 0 0 0

    //后期重新填充
    fill(v.begin(),v.end(),100);
    for_each(v.begin(),v.end(),MyPrint());
    cout<<endl;
}


100 100 100 100 100 100 100 100 100 100

5.6 常用集合算法

学习目标: 掌握常用的集合算法

算法简介:

  • set_intersection //求两个容器的交集
  • set_union //求两个容器的并集
  • set_difference //求两个容器的差集

5.6.1 set_intersection

功能描述: 求两个容器的交集

函数原型: set_intersection(itreratorbeg1,iterator end1,iterator beg2,iterator end2,iterator dest);

  • beg1 —— 容器1开始迭代器
  • end1 —— 容器2结束迭代器
  • beg2 —— 容器2开始迭代器
  • end2 —— 容器2结束迭代器
  • dest —— 目标容器开始迭代器

总结:

  • 求交集的两个集合必须是 有序序列
  • 目标容器开辟空间要从两个容器中取小值
  • set_intersection 返回值就是 交集中最后一个元素的位置
#include <vector>
#include <algorithm>

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

void test()
{
    vector<int>v1;
    vector<int>v2;
    for(int i=0;i<10;i++)
    {
        v1.push_back(i);  //0 - 9
        v2.push_back(i+5);  //5 - 14
    }

    vector<int>vTarget;
    //目标容器需提前开辟空间——最特殊情况,大容器包含小容器,开辟小容器空间即可
    vTarget.resize(min(v1.size(),v2.size()));

    //获取交集
    vector<int>::iterator itEnd = set_intersection(v1.begin(),v1.end(),v2.begin(),v2.end(),vTarget.begin());
    for_each(vTarget.begin(),itEnd,MyPrint);  //itEnd:用返回的迭代器
    cout<<endl;
}


5 6 7 8 9

5.6.2 set_union

功能描述: 求两个集合的并集

函数原型: set_union(iterator beg1,iterator end1,iterator beg2,iterator end2,iterator dest);

  • beg1 —— 容器1开始迭代器
  • end1 —— 容器2结束迭代器
  • beg2 —— 容器2开始迭代器
  • end2 —— 容器2结束迭代器
  • dest —— 目标容器开始迭代器

总结:

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

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

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

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

    vector<int>vTarget;
    vTarget.resize(v1.size()+v2.size());

    vector<int>::iterator itEnd = set_union(v1.begin(),v1.end(),v2.begin(),v2.end(),vTarget.begin());
    //for_each(vTarget.begin(),vTarget.end(),MyPrint());
    for_each(vTarget.begin(),itEnd,MyPrint());
    cout<<endl;
}


0 1 2 3 4 5 6 7 8 9 10 11 12 13 14

5.6.3 set_difference

功能描述: 求两个集合的差集

函数原型: set_union(iterator beg1,iterator end1,iterator beg2,iterator end2,iterator dest);

  • beg1 —— 容器1开始迭代器
  • end1 —— 容器2结束迭代器
  • beg2 —— 容器2开始迭代器
  • end2 —— 容器2结束迭代器
  • dest —— 目标容器开始迭代器

总结:

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

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

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

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

    vector<int>vTarget;

    vTarget.resize(max(v1.size(),v2.size()));
    cout<<"v1 和 v2 的差集:";
    vector<int>::iterator itEnd = set_difference(v1.begin(),v1.end(),v2.begin(),v2.end(),vTarget.begin());
    for_each(vTarget.begin(),itEnd,MyPrint());
    cout<<endl;

    cout<<"v2 和 v1 的差集:";
    itEnd = set_difference(v2.begin(),v2.end(),v1.begin(),v1.end(),vTarget.begin());
    for_each(vTarget.begin(),itEnd,MyPrint());
    cout<<endl;
}


v1 和 v2 的差集:0 1 2 3 4
v2 和 v1 的差集:10 11 12 13 14
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值