黑马C++学习笔记——C++编程提高

目录

1 模板

1.1 模板的概念

1.2 函数模板

1.2.1 函数模板语法

1.2.2 函数模板注意事项

1.2.3 函数模板的案例

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

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

1.2.6 模板的局限性

1.3 类模板

1.3.1 类模板语法

1.3.2 类模板与函数模板区别

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

1.3.4 类模板对象做函数参数

1.3.5 类模板与继承

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

1.3.7 类模板分文件编写

1.3.8 类模板与友元

1.3.9 类模板案例

2 STL初识

2.1 STL的诞生

2.2 STL基本概念

2.3 STL六大组件

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

2.5 容器算法迭代器初始

2.5.1 vector存放内置数据类型

2.5.2 vector存放自定义数据类型

2.5.3 vector容器嵌套容器

3 STL常用容器

3.1 string容器

3.1.1 string基本概念

3.1.2 string构造函数

3.1.3 string赋值操作

3.1.4 string字符串拼接

3.1.5 string查找和替换

3.1.6 string字符串比较

3.1.7 string字符存取

3.1.8 string插入和删除

3.1.9 string子串

3.2 vector 容器

3.2.1 vector基本概念

3.2.2  vector构造函数

3.2.3 vector赋值操作

3.2.4 vector容量和大小

3.2.5 vector插入和删除

3.2.6 vector数据存取

3.2.7 vector互换容器

3.2.8 vector预留空间

3.3 deque容器

3.3.1 deque容器基本概念

3.3.2 deque构造函数

3.3.2 deque赋值操作

3.3.4 deque大小操作

3.3.5 deque插入和删除

3.3.6 deque数据存取

3.3.7 deque排序

3.4 案例——评委打分

3.5 stack容器

3.5.1 stack基本概念

3.5.2 stack常用接口

3.6 queue容器

3.6.1 queue基本概念

3.6.2 queue常用接口

3.7 list容器

3.7.1 list基本概念

3.7.2 list构造函数

3.7.3 list赋值和交换

3.7.4 list大小操作 

3.7.5 list插入和删除

3.7.6 list数据存取

3.7.7 list反转和排序

3.7.8 排序案例

3.8 set容器

3.8.1 set基本概念

3.8.2 set构造和赋值

3.8.3 set大小和交换

3.8.4 set插入和删除

3.8.5 set查找和统计

3.8.6 set和multiset区别

3.8.7 pair对组创建

3.8.8 set容器排序

3.9 map/multimap容器

3.9.1 map基本概念

3.9.2 map构造和赋值

3.9.3 map容器大小和交换

3.9.4 map容器插入和删除

3.9.5 map容器查找和统计

3.9.6 map容器排序

3.10 案例——员工分组

3.10.1 案例描述

3.10.2 实现步骤

4 STL——函数对象

4.1 函数对象

4.1.1 函数对象概念

4.1.2 函数对象使用

4.2 谓词

4.2.1 谓词概念

4.2.2 一元谓词

4.2.3 二元谓词

4.3 内建函数对象

4.3.1 内建函数对象意义

4.3.2 算法仿函数

4.3.3 关系仿函数

4.3.4 逻辑仿函数

5 STL——常用算法

5.1 常用遍历算法

5.1.1 for_each

5.1.2 transform

5.2 常用查找算法

5.2.1 find

5.2.2 find_if

5.2.3 adjacent_find

5.2.4 binary_search

5.2.5 count

5.2.6 count_if

5.3 常用排序算法

5.3.1 sort

5.3.2 random_shuffle

5.3.3 merge

5.3.4 reverse 

5.4 常用拷贝和替换算法 

5.4.1 copy

5.4.2 replace

5.4.3 replace_if

5.4.4 swap

5.5 常用算术生成算法

5.5.1 accumulate

5.5.2 fill 

5.6 常用集合算法

5.6.1 set_intersection

5.6.2 set_union

5.6.3 set_difference


这个阶段的学习只要针对C++泛型编程和STL技术做详细讲解,探讨C++更深层的使用。

1 模板

1.1 模板的概念

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

特点:模板不能直接使用;

          模板不是万能的。

1.2 函数模板

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

1.2.1 函数模板语法

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

语法:

解释:

template——声明创建模板;

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

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

#include<iostream>
using namespace std;

//函数模板

//两个整形交换函数
void swapInt(int& a, int& b)
{
	int temp = a;
	a = b;
	b = temp;
}

//函数模板
template<typename T>  //声明一个模板,告诉编译器后面代码中紧跟着的T不要报错,T是一个通用数据类型
void mySwap(T& a, T& b)
{
	T temp = a;
	a = b;
	b = temp;
}

//交换两个浮点数型函数
void swapDouble(double& a, double& b)
{
	double temp = a;
	a = b;
	b = temp;
}

void test01()
{
	int a = 10;
	int b = 20;

	//swapInt(a, b);

	//利用函数模板交换
	//两种方式使用函数模板

	//1、自动类型推导
	//mySwap(a, b);

	//2、显示指定类型
	mySwap<int>(a, b);

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


	double c = 1.23;
	double d = 12.23;
	swapDouble(c, d);
	cout << "c= " << c << endl;
	cout << "d= " << d << endl;

}

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

总结:

  • 函数模板利用关键字template;
  • 使用函数模板有两种方式:自动类型推导、显示指定类型;
  • 模板的目的是为了提高复用性,将类型参数化。

1.2.2 函数模板注意事项

注意事项:

  • 自动类型推导,必须推导出一致的数据类型T,才可以使用;
  • 模板必须要确定出T的数据类型,才可以使用。
#include <iostream>
using namespace std;


//函数模板注意事项
template<typename T>//typename可以替换成class
void mySwap(T&a,T&b)
{
  T temp=a;
   a=b;
  b=temp;
}

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

void test01()

{

  int a=10;
 int b=20;
 char c='c';
 mySwap(a,b);//正确,可以推导出一致的T
 //mySwap(a,c);//错误,推导不出一只的T类型
}

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


void test02()
{
    //func();//错误,模板不能独立使用,必须确定出T的类型
    func<int>();//利用先指定类型的方式,给T一个类型,才可以使用该模板
}

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

}

总结:

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

1.2.3 函数模板的案例

案例描述:

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

//实现通用 对数组进行排序的函数

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


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

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

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

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



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


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

 普通函数与函数模板的区别:(是否发生自动类型转换)

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

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

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

void test01()
{
	int a = 10;
	int b = 20;
	char c = 'c';

	cout << myAdd01(a, c) << endl;
             //正确,将char类型的‘c’隐式转换为int类型‘c’对应的ASCII码99

	//myAdd02(a, c);//报错,使用自动类型推导时,不会发生隐式类型转换

	myAdd02<int>(a, c);//正确,如果用显示指定类型,可以发生隐式类型转换

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

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

调用规则如下:

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

//普通函数与函数模板调用规则
void myPrint(int a, int b)
{
	cout << "调用的普通函数" << endl;

}

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

}

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

}

void test01()
{
	//1、如果函数模板和普通都可以实现,优先调用普通函数
	//注意 如果告诉编译器 普通函数时有的,但是只是声明没有实现,或者不在当前文件内实现,就会报错找不到
	int a = 10;
	int b = 20;
	myPrint(a, b);//调用普通函数

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

	//3、函数模板也可以发生重载
	int c = 30;
	myPrint(a, b, c);//调用重载的函数模板

	//4、如果函数模板可以发生更好的匹配,优先调用函数模板
	char c1 = 'a';
	char c2 = 'b';
	myPrint(c1, c2);//调用函数模板
	//(也可以调用普通函数,会发生隐式类型转换 | 而调用模板的话则只用推导类型T) 

}

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

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

1.2.6 模板的局限性

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

​​

//1.2.6 模板的局限性
#include<string>
//模板并不是万能的,有些特定数据类型,需要用具体化方式做特殊实现

//对比两个数据是否相等
class Person
{
public:
	Person(string name, int age)
	{
		this->m_Name = name;
		this->m_Age = age;

	}
	//姓名
	string m_Name;
	//年龄
	int m_Age;
};

template<class T>
bool myCompare(T& a, T& b)
{
	if (a == b)
	{
		return true;
	}
	else
	{
		return false;
	}
}

//利用具体化person的版本实现代码,具体化优先调用
//具体化,显示具体化的原型,给定以template<>开头,并同过名称来指定类型
template<>bool myCompare(Person& p1, Person& p2)
{
	if (p1.m_Name == p2.m_Name && p1.m_Age == p2.m_Age)
	{
		return true;
	}
	else
	{
		return false;
	}
}


void test01()
{
	int a = 10;
	int b = 20;
	
	//内置数据类型可以直接使用通用的函数模板
	bool ret = myCompare(a, b);

	if (ret)
	{
		cout << "a==b" << endl;
	}

	else
	{
		cout << "a!=b" << endl;
	}
}

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

	//自定义数据类型,不会调用普通的函数模板
	//可以创建具体化的Person数据类型的模板,用于特殊处理这个类型
	bool ret = myCompare(p1, p2);//出错,不认识Person类型,(运算符重载可以解决)
	if (ret)
	{
		cout << "p1==p2" << endl;
	}
	else
	{
		cout << "p1!=p2" << endl;
	}
}

int main()
{

	test01();
	test02();

	system("pause");
	return 0;
}

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

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

1.3 类模板

1.3.1 类模板语法

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

语法:template<typename T>

          类

解释:template ——声明创建模板;

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

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

// 1.3 类模板
#include<string>
template<class NameType,class AgeType>
class Person
{
public:
	Person(NameType name,AgeType age)
	{
		this->m_Name = name;
		this->m_Age = age;
	}
	void showPerson()
	{
		cout << "name: " << this->m_Name << "age: " << this->m_Name << endl;
	}
public:
	NameType m_Name;
	AgeType m_Age;
};

void test01()
{
	//指定NameType为string类型,AgeType为int类型
	Person<string, int>P1("孙悟空",999); 
	P1.showPerson();
}
int main()
{
	test01();

	system("pause");
	return 0;
}

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

1.3.2 类模板与函数模板区别

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

  • 类模板没有自动类型推导的使用方式;
  • 类模板在模板参数列表中可以有默认参数。
//1.3.2 类模板与函数模板区别

#include<string>
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 << "age: " << this->m_Name << endl;
	}
public:
	NameType m_Name;
	AgeType m_Age;
};


//1、类模板没有自动类型推导使用方式
void test01()
{
	//Person p("孙悟空", 1000);//错误,无法使用自动类型推导

	Person<string, int>p("孙悟空", 1000);//正确,只能用显示指定类型
	p.showPerson();
 }

//2、类模板在参数列表中可以有默认参数
void test02()
{
	Person<string>p("猪八戒",999);//第二个参数为默认

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

总结:类模板使用只能用显示指定类型方式;

           类模板中的模板参数列表可以有默认参数。

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

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

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

// 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;

	//类模板中的成员函数——在调用时才会分配,obj无法确定数据类型
	void func1()
	{
		obj.showPerson1();
	}

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

void test01()
{
	MyClass<Person1>m;//替换为Person1的数据类型
	m.func1();
	//m.func2();//不能调用,说明函数调用才会创建成员函数
}

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

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

1.3.4 类模板对象做函数参数

类模板实例化出的对象,向函数传参的方式。

有一共有三种传入方式:

  1. 指定传入的类型——直接显示对象的数据类型。
  2. 参数模板化       ——将对象中的参数变为模板进行传参。
  3. 整个类型模板化——将这个对象类型模板化进行传递。
//1.3.4 类模板对象做函数参数
#include<string>
template<class T1,class T2>
class Person
{
public:
	Person(T1 name, T2 age)
	{
		this->m_Name = name;
		this->m_Age = age;
	}

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

//1、指定传入类型——最常用
void printPerson1(Person<string,int>&p)//类模板的对象做函数的参数
{
	p.showPerson();
}

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

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

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

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

void test03()
{
	Person<string, int>p("唐僧", 30);
	printPerson(p);
}
int main()
{
	test01();
	test02();
	test03();

	system("pause");
	return 0;
}

 总结:通过类模板创建的对象,可以有三种方式向函数中进行传参;

            使用比较广泛是第一种:指定传入的类型。

1.3.5 类模板与继承

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

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

//class Son:public Base//错误,必须要知道父类中的T类型,才能继承给子类(不知道计算内存时无法计算)
class Son:public Base<int>
{

};

void test01()
{
	Son s1;
}

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

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

int main()
{
	test01();
	test02();

	system("pause");
	return 0;
}

总结:如果父类时类模板,子类需要指定出父类中T的数据类型。

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

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

#include<iostream>
using namespace std;

//1.3.6 类模板中的成员函数类外实现
#include<string>
template<class T1,class T2>
class Person
{
public:
	//成员函数类内声明
	Person(T1 name, T2 age);
		/*{
			this->m_Namae = name;
			this->m_Age = age;
		}*/

	void showPerson();
	/*{
		cout << "姓名:" << this->m_Namae << " 年龄:" << endl;
	}*/
public:
	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; 
}

//成员函数类外实现
	//加作用域Person,
	//加模板参数列表<T1,T2>,
	//T1,T2也需要让编译器知道因此加template<class T1,class T2>
template<class T1,class T2>
void Person<T1, T2>::showPerson() 

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

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

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

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

1.3.7 类模板分文件编写

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

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

解决:

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

.hpp文件

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

#include<string>
template<class T1, class T2>
class Person
{
public:
	//构造函数类内声明
	Person(T1 name, T2 age);

	void showPerson();

	T1 m_Name;
	T2 m_Age;
};

//构造函数类外实现
template<class T1, class T2>
Person<T1, T2>::Person(T1 name, T2 age)  //模板一般加简尖括号,中为模板的参数模板
{
	this->m_Name = name;
	this->m_Age = age;
}

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

.cpp文件

//类模板分文件编写问题以及解决
//#include<string>

//第一种解决方式,直接包含源文件_不常用
//#include "person.cpp"

//第二种解决方式,将.h和.cpp中的内容写到一起,将后缀名改为.hpp文件
#include "person.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 << "姓名:" << this->m_Name << " 年龄:"<<this->m_Age  << endl;
//}

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

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

总结:主流的解决方式是第二种,将类模板成员函数(声明+实现)写到一起,并将后缀名改为.hpp。

1.3.8 类模板与友元

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

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

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

//1.3.8通过全局函数 打印Person信息

#include<string>

//提前让编译器知道Person类存在
//2、全局函数配合友元  类外实现——先做函数声明,下方在做函数模板定义,在做友元

//函数模板定义
template<class T1, class T2>
class Person;//函数

//类外实现
//如果声明了函数模板,可以将实现写到后面,否则需要将实现体写到类的前面让编译器提前看到
template<class T1, class T2>
void printPerson2(Person<T1, T2> p)
{
	cout << "类外实现——姓名:" << p.m_Name << " 年龄:" << p.m_Age << endl;
}



template<class T1,class T2>
class Person
{
	//全局函数 类内实现   让其作为本类的好朋友可以访问类中的私有属性
	friend void printPerson(Person<T1, T2> p)
	{
		cout << "姓名:" << p.m_Name << " 年龄:" << p.m_Age << endl;
	}

	//全局函数 类外实现
	
	//friend void printPerson2(Person<T1, T2> p);
	//上面为普通函数的声明、下面为函数模板的实现方式,因此导致test02链接错误
	//改为下面方式:加一个空模板的参数列表
	//如果全局函数 是类外实现,需要让编译器提前知道这个函数的存在
	friend void printPerson2<>(Person<T1, T2> p);

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

//1、全局函数在类内实现
void test01()
{
	Person<string, int>p("Tom", 20);

	printPerson(p);
}

//2、全局函数在类外实现
void test02()
{
	Person<string, int>p("Jerry",20);

	printPerson2(p);

}

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

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

1.3.9 类模板案例

这个案例就是写一个功能加强版的存储器。

 需求分析:因为不知道具体的数据类型,将其抽象为类型T

案例分两个文件编写,MyArray.hpp文件中主要写类声明和函数的实现,main.cpp中写主程序,对功能测试。

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++)
		{
			//如果T为对象,而且还包含指针,必须需要重载=操作符,因为这个等号不是 构造 而是赋值
			//普通类型可以直接= 但是指针类型需要深拷贝

			this->pAddress[i] = arr.pAddress[i];
		}
	}

	//opertor=防止浅拷贝问题 (连等号操作)
	MyArray& operator=(const MyArray& arr)
	{
		//cout << "MyArray等号调用" << 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;//在数组末尾插入数据:将val插到下标为m_Size的位置处
		this->m_Size++;//更新数组大小
	}

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

	//通过下标的方式访问数组中的元素
	 T& operator[](int index)
	{
		return this->pAddress[index];//函数调用作为左值需要返回引用
	}

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

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

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

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

	int m_Capacity;//数组容量

	int m_Size;//数组大小

};

mian.cpp

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

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

void test01()
{
	MyArray<int>arr1(5);

	for (int i = 0; i < 5; i++)
	{
		//利用尾插法向数组中插入数据
		arr1.Push_Back(i);
	}
	cout << "arr1的打印输出为:" << endl;

	printIntArray(arr1);  //0 1 2 3 4 5

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

	MyArray<int>arr2(arr1);

	cout << "arr2的打印输出:" << endl;

	printIntArray(arr2);

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

	测试代码
	//MyArray<int>arr1(5);//构造与析构

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

	//MyArray<int>arr3(100);//等号重载
	//arr3 = arr1;

}



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


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

	Person p1("孙悟空", 999);
	Person p2("韩信", 20);
	Person p3("妲己", 10);
	Person p4("赵云", 38);
	Person p5("安其拉", 22);

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

	//打印数组
	printPersonArray(arr);

	//输出容量
	cout << "arr容量为:" << arr.getCapacity() << endl;
	//输出大小
	cout << "arr大小为:" << arr.getSize() << endl;
}
int main()
{
	test01();
	//test02();
	system("pause");
	return 0;
}

测试:

测试主要分两部分,一个为内置类型int数组的测试,一个为自定义类型Person类的测试,其结果分别如下:

 

 总结:利用所学知识实现通用的数组。

2 STL初识

2.1 STL的诞生

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

面向对象三大特性:

    封装:将类似的属性和行为抽象,封装起来为一类,从而实现事或物。

    继承:子类继承父类中的属性和行为,不用重新声明,提高代码复用性。

    多态:一个函数名有多个接口,父类指针指向子类对象指向同一个接口会产生不同的形态。

泛型编程:主要是模板,模板将类型参数化,使代码更具有通用性。

在开发过程中,例如写一个数字相加的算法,有人写为Add()、pulas(),名称不同,但功能相同,为了避免重复,系统自己给出,从而诞生了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容器就是将运用最广泛的一些数据结构实现出来。

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

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

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

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

算法问题之解法也

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

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

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

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

迭代器:容器和算法之间粘合剂

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

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

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

迭代器种类:

2.5 容器算法迭代器初始

了解STL种容器、算法、迭代器概念之后,我们利用代码感受STL的魅力

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

2.5.1 vector存放内置数据类型

容器:vector

算法:for_each

迭代器:vector<int>::iterator

  •   每一个容器都有自己的迭代器,迭代器是用来遍历容器中的元素;
  • v.begin()返回迭代器,这个迭代器指向容器中的第一个数据;
  • v.end()返回迭代器,这个迭代器指向容器元素的最后一个元素的下一个位置;
  • vector<int>::iterator 拿到vector<int>这种容器的迭代器类型。

实例:

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

//vector容器存放内置数据类型
void myPrint(int val)
{
	cout << val << endl;
}


void test01()
{
	//创建了一个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();//起始迭代器 指向容器中的第一个元素
	//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提供遍历方式 头文件algorithm
	for_each(v.begin(), v.end(), myPrint);
	//利用回调技术,在for_each遍历期间调用函数

}  

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

2.5.2 vector存放自定义数据类型

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

//2.5.2 vector中存放自定义数据类型

#include<vector>
#include<string>

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

//存放对象
void test01()
{
	vector<Person> v;

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

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

	//遍历容器中的数据
	for (vector<Person>::iterator it = v.begin(); it != v.end(); it++)
	{
		//两种获取类中属性方式
		//cout << "姓名:" << (*it).m_Name << " 年龄:" << (*it).m_Age << endl;
		cout << "姓名:" << it->m_Name << " 年龄:" << it->m_Age << endl;
	}
	//it 为一个指针,(*it)中的类型为尖括号<>中的类型,.运算拿到具体属性。
}



//存放自定义数据类型 指针
//存放对象指针
void test02()
{
	vector<Person*> v;

	Person p1("aaa", 10);
	Person p2("bbb", 20);
	Person p3("ccc", 30);
	Person p4("ddd", 40);
	Person p5("eee", 50);

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

	//遍历容器中的数据
	for (vector<Person*>::iterator it = v.begin(); it != v.end(); it++)
	{
		//cout << "姓名:" << (*it)->m_Name << " 年龄:" << (*it)->m_Age << endl;//*it为Person*类型的数据(指针)
	
		Person* p = (*it);
		cout << "::姓名:" << p->m_Name << " 年龄:" << p->m_Age << endl;
	
	}
	//it 为一个指针,(*it)中的类型为尖括号<>中的类型,.运算拿到具体属性。
}

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

2.5.3 vector容器嵌套容器

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

//2.5.3 vector容器嵌套容器
#include<vector>

//容器嵌套容器
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;
	}
}

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

3 STL常用容器

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初始化(传一个c语言字符串构建一个C++字符串)
  • string(const string& str)  //使用一个string对象初始化另一个string 对象(拷贝构造)
  • string(int n,char c)          //使用n个字符c初始化

示例: 

//3.1.2 string的构造函数
#include<string>

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

	const char* str = "helllo world";
	string s2(str);
	cout << "s2= " << s2 << endl;

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

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


}

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

3.1.3 string赋值操作

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

赋值的函数原型:(大致分为两种:等号赋值,assign()赋值)

示例: 

//3.1.3 string 赋值操作
#include<string>
void test01()
{
	string str1;
	str1 = "hello world";
	cout << "str1= " << str1 << endl;

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

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

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

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

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

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

}

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

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

   但是要记住assign这种方式,别人用的时候要认识。

3.1.4 string字符串拼接

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

函数原型:

 示例:

//3.1.4 strng字符串拼接
#include<string>
void  test01()
{
	string str1 = "我";
	str1 += "爱玩游戏";

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

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

	string str2 = "LOL DNF";

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

	string str3 = "I";
	str3 += " LOVE ";
	cout << "str3= " << str3 << endl;//I LOVE

	str3.append("game abcde", 4);
	cout << "str3= " << str3 <<endl;//I LOVE game


	//str3.append(str2);//I LOVE gameLOL DNF
	str3.append(str2, 4, 3);//I LOVE gameDNF(从下标4开始截取三个字符)
	cout << "str3= " << str3 << endl;
}

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

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

3.1.5 string查找和替换

功能描述:查找:查找指定字符串是否存在;

     替换:再指定的位置替换字符串。

函数原型:

示例: 

//3.1.5 string字符串查找和替换
#include<string>
//1、查找
void test01()
{
	//find
	string str1 = "abcdefgde";

	int pos=str1.find("de");

	if (pos == -1)
	{
		cout << "未找到字符串" << endl;
	}
	else
	{
		cout << "找到字符串,pso= " << pos << endl;//3
	}

	//rfind和find的区别
	//rfind从右往左查找   find从左往右查找
	pos = str1.rfind("de");
	cout << "pso= " << pos << endl;//7
}

//2、替换
void test02()
{
	string str1 = "abcdefg";

	str1.replace(1, 3, "1111");//从1号位置起3个字符替换为“1111”

	cout << "str1= " << str1 << endl;//a1111efg

}

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

总结:

  • find查找是从左往右,rfind是从右往左;
  • find找到字符串后返回查找的第一个字符位置,找不到返回-1;
  • replace在替换时,要指定从那个位置起,多少个字符,替换成什么样的字符串。

3.1.6 string字符串比较

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

比较方式:字符串比较时按字符的ASCII码进行对比。= 返回 0;> 返回 1;

     < 返回 -1。

函数原型:

示例: 

//3.1.6 字符串比较
void test01()
{
	string str1 = "hello";
	string str2 = "hello";
	if (str1.compare(str2) == 0)
	{
		cout << "str1 等于 str2" << endl;
	}
	else
	{
		cout << "str1 不等于 str2" << endl;
	}
}

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

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

3.1.7 string字符存取

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

示例:


//3.1.7 string 字符存取
#include<string>
void test01()
{
	string str = "hello world";
	//通过[]访问单个字符
	for (int i = 0; i < str.size(); i++)
	{
		cout << str[i] << " ";//hello world
	}
	cout << endl;

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

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

	str[1] = 'x';
	cout << "str= " << str << endl;//xxllo world
}

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

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

3.1.8 string插入和删除

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

函数原型:

示例:

//3.1.8string字符串插入和删除
#include<string>
void test01()
{
	string str = "hello";

	//插入
	/*string str2 = "1112";
	str.insert(1, str2);*/

	str.insert(1, "111");
	cout << "str= " << str << endl;//h111ello

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

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

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

3.1.9 string子串

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

函数原型:

  示例:

//3.1.9 string子串

void test01()
{
	string str = "abcdf";

	string subStr = str.substr(1, 3);

	cout << "subStr= " << subStr << endl;//bcd

}

//使用操作
void test02()
{
	string email = "zhangsan@sina.com";
	//从邮件地址中 获取用户名信息

	int pos=email.find("@");//8
	//cout << pos << endl;

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


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

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

3.2 vector 容器

3.2.1 vector基本概念

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

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

动态扩展:

  • 并不是在原空间之后接续新空间,而是找更大的内存空间,然后将原数据拷贝到新空间,释放原空间。
  • vector容器的迭代器是支持随机访问的迭代器。

3.2.2  vector构造函数

功能描述:创建vector容器。

函数原型:

示例: 

//3.2.2 vector容器的构造
#include<vector>
void printVector(vector<int>& v)
{
	for (vector<int>::iterator it = v.begin(); it != v.end(); it++)
	{
		cout << *it << " ";
	}
	cout << endl;
}

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

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

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

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

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

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

3.2.3 vector赋值操作

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

函数原型:

 示例:

//3.2.3vector的赋值
#include<vector>

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

void test01()
{
	vector<int>v1;//无参构造

	for (int i = 0; i < 10; i++)
	{
		v1.push_back(i);
	}
	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
	vector<int>v3;
	v3.assign(v1.begin(), v1.end());//前闭后开
	printVector(v3);//0 1 2 3 4 5 6 7 8 9

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

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

总结:vector赋值方式比较简单,使用operator=,或者assign都可以。

3.2.4 vector容量和大小

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

函数原型:

 示例:

//3.2.4 vector容量和大小
#include<vector>

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

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


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

	//重新指定大小
	v1.resize(15);//扩充到15个
	//v1.resize(15,100);//利用重载的版本,可以指定默认填充值为参数2
	printVector(v1);//如果重新指定的比原来的长,默认用0填充新的位置

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

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

总结:判断为空:empty();             返回元素个数:size();

          返回容器容量:capacity();   重新指定大小:resize()。

3.2.5 vector插入和删除

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

函数原型:

示例:

//vector插入和删除
#include<vector>
void printVector(vector<int>& v)
{
	for (vector<int>::iterator it = v.begin(); it != v.end(); it++)
	{
		cout << *it << " ";
	}
	cout << endl;
}

void test01()
{
	vector<int>v1;
	//尾插
	v1.push_back(10);
	v1.push_back(20);
	v1.push_back(30);
	v1.push_back(40);
	v1.push_back(50);

	printVector(v1);//10 20 30 40 50 

	//尾删
	v1.pop_back();
	printVector(v1);//10 20 30 40

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

	v1.insert(v1.begin(), 2, 1000);
	printVector(v1);//1000 1000 10 20 30 40 

	//删除 参数也是迭代器
	v1.erase(v1.begin());//1000 10 20 30 40
	printVector(v1);

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

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

总结:尾插:push_back;  尾删:pop_back;   插入:insert(位置迭代器);

           删除:erase(位置迭代器);  清空:clear。

3.2.6 vector数据存取

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

函数原型:

 示例:

//3.2.6 vector容器数据存取
#include<vector>

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

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

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

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

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

总结:

  • 除了用迭代器获取vector容器中元素,[ ]和at也可以;
  • front返回容器第一个元素;
  • back返会容器最后一个元素。

3.2.7 vector互换容器

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

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

示例:

//3.2.7 vector互换容器
#include<vector>
void printVector(vector<int>& v)
{
	for (vector<int>::iterator it = v.begin(); it != v.end(); it++)
	{
		cout << *it << " ";
	}
	cout << endl;
}

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

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


	cout << "交换后:" << endl;
	v1.swap(v2);//互换

	printVector(v1);
	printVector(v2);
}

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

	v.resize(3);//重设大小为3
	cout << "v的容量为:" << v.capacity() << endl;// 138255(太浪费)
	cout << "v的大小为: " << v.size() << endl;//3

	//巧用swap收缩内存
	vector<int>(v).swap(v);//匿名对象

	cout << "v的容量为:" << v.capacity() << endl;// 3
	cout << "v的大小为: " << v.size() << endl;//3
}
int main()
{
	test01();
	//test02();
	system("pause");
	return 0;
}

test01:

test02:

 采用swap收缩空间示意:

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

3.2.8 vector预留空间

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

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

// 3.2.8 vector预留空间
#include<vector>
void test01()
{
	vector<int>v;

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

	int num = 0;//统计开辟次数
	int* p = NULL;
	for (int i = 0; i < 100000; i++)
	{
		v.push_back(i);

		if (p != &v[0])
		{
			p = &v[0];
			num++;
		}
	}
	cout << "num= " << num << endl;
	//不预留30(动态扩展30次) 预留为1
}

int main()
{
	test01();

	system("pause");
	return 0;
}

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

3.3 deque容器

3.3.1 deque容器基本概念

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

deque与vector区别:

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

deque内部工作原理:

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

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

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

3.3.2 deque构造函数

功能描述:deque容器构造。

函数原理:

 示例:

//deque容器构造函数
#include<deque>
void printDeque(const deque<int>& d)
{
	for (deque<int>::const_iterator it = d.begin(); it != d.end(); it++)
	{
		//*it = 100;//修改值,为了不能修改,加const
		cout << *it << " ";
	}
	cout << endl;
}

void test01()
{
	deque<int>d1;//无参构造
	for (int i = 0; i < 10; i++)
	{
		d1.push_back(i);
	}
	printDeque(d1);

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

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

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

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

总结:

限定容器数据为只读时,遍历数据的迭代器也要设置为只读;

deque容器和vector容器的构造方式几乎一样,灵活使用即可。

3.3.2 deque赋值操作

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

函数原型:

//3.3.2 deque赋值操作
#include<deque>
void printDeque(const deque<int>& d)
{
	for (deque<int>::const_iterator it = d.begin(); it != d.end(); it++)
	{
		cout << *it << " ";
	}
	cout << endl;
}
void test01()
{
		deque<int>d1;
	for (int i = 0; i < 10; i++)
	{
		d1.push_back(i);
	}
	printDeque(d1);

	//operator=赋值
	deque<int>d2;
	d2 = d1;
	printDeque(d2);

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

	deque<int>d4;
	d4.assign(10, 100);
	printDeque(d4);
}

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

3.3.4 deque大小操作

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

函数原型:

//3.3.4 deque大小操作
#include<deque>
void printDeque(const deque<int>& d)
{
	for (deque<int>::const_iterator it = d.begin(); it != d.end(); it++)
	{
		cout << *it << " ";
	}
	cout << endl;
}

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

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

	//重新指定大小
	//d1.resize(15);
	d1.resize(15, 1);//用1填充
	printDeque(d1);

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

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

总结:

  • deque没有容量的概念;
  • 判断是否为空——empty();
  • 返回元素个数——size();
  • 重新指定个数——resize();

3.3.5 deque插入和删除

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

函数原型:

 示例

//3.3.5 deque插入和删除
#include<deque>

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

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

	//尾插
	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();
	printDeque(d1);//200 100 10

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

void test02()
{
	deque<int>d1;
	d1.push_back(10);
	d1.push_back(20);
	d1.push_back(100);
	d1.push_back(200);

	printDeque(d1);//200 100 10 20

	//insert插入
	d1.insert(d1.begin(), 1000);
	printDeque(d1);//1000 200 100 10 20

	d1.insert(d1.begin(), 2, 10000);
	printDeque(d1);//10000 10000 1000 200 100 10 20

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

	d1.insert(d1.begin(), d2.begin(), d2.end());
	printDeque(d1);//1 2 3 10000 10000 1000 200 100 10 20

}

void test03()
{
	deque<int>d1;
	d1.push_back(10);
	d1.push_back(20);
	d1.push_front(100);
	d1.push_front(200);

	//删除
	deque<int>::iterator it = d1.begin();
	it++;
	d1.erase(it);
	printDeque(d1);// 200 10 20

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

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

总结 :

  • 插入和删除提供的位置时迭代器!
  • 尾插——push_back;
  • 尾删——opo_back;
  • 头插——push_front;
  • 头删——pop_front;

3.3.6 deque数据存取

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

函数原型:

//3.3.6 deque容器数据存取
#include<deque>
void test01()
{
	deque<int>d;
	d.push_back(20); 
	d.push_back(30);
	d.push_front(100);
	d.push_front(200);
	d.push_front(300);//300 200 100 20 30

	//通过[]凡是访问元素
	for (int i = 0; i < d.size(); i++)
	{
		cout << d[i] << " ";
	}
	cout << endl;

	//通过at的凡是访问元素
	for (int i = 0; i < d.size(); i++)
	{
		cout << d.at(i) << " ";
	}
	cout << endl;

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

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

总结:

  • 除了用迭代器获取deque容器中元素,[ ]和at也可以;
  • front返回容器第一个元素;
  • back返回容器最后一个元素。

3.3.7 deque排序

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

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

//3.3.7 deque排序
#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 test01()
{
	deque<int>d;
	d.push_back(10);
	d.push_back(20);
	d.push_front(100);
	d.push_front(200);
	d.push_front(300);//300 200 100 10 20

	printDeque(d);

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

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

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

3.4 案例——评委打分

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

实现步骤:

  1. 创建5名选手,放到vector中。
  2. 遍历vector容器,取出每一个选手,执行for循环,可以把10个评委打分分别存放到deque容器中。
  3. sort算法对deque容器中分数排序,去除最高和最低分。
  4. deque容器遍历一遍,累加总分。
  5. 获取平均分。
// 3.4 案例——评委打分
#include<vector>
#include<deque>
#include<string>
#include<algorithm>
#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 creatPerson(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 << "打分:" << endl;
		//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;//存放选手容器
	creatPerson(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;
}

3.5 stack容器

3.5.1 stack基本概念

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

栈不允许有遍历的行为,只能访问栈顶元素。

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

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

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

3.5.2 stack常用接口

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

#include<stack>

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

	//入栈
	s.push(10);
	s.push(20);
	s.push(30);
	s.push(40);

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

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

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

3.6 queue容器

3.6.1 queue基本概念

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

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

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

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

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

3.6.2 queue常用接口

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

 示例:

#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 test01()
{
	//创建队列
	queue<Person>q;

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

	//入队
	q.push(p1);
	q.push(p2);
	q.push(p3);
	q.push(p4);

	//判断只要队列不为空,查看对头,查看队尾,出队
	//队列不提供迭代器,更不支持随机访问
	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 << "队列大小为:"<<q.size() << endl;
}
int main()
{
	test01();
	system("pause");
	return 0;
}

 

3.7 list容器

3.7.1 list基本概念

 

 

3.7.2 list构造函数

//3.7.2 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);

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

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

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

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

3.7.3 list赋值和交换

//3.7.3 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);

	list<int>L2;
	L2 = L1;//operator=赋值
	printList(L2);

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

	list<int>L4;
	L4.assign(10, 1000);
	printList(L3);
}

//交换
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,100);

	cout << "交换前:" << endl;
	printList(L1);
	printList(L2);

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

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

3.7.4 list大小操作 

//3.7.4 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);

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

	//重新指定大小
	L1.resize(10);//用0来填充   10 20 30 40 0 0 0 0 0 0 
	printList(L1);

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

}

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

 

3.7.5 list插入和删除

//3.7.5 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>L;

	//尾插
	L.push_back(10);
	L.push_back(20);
	L.push_back(30);
	//头插
	L.push_front(100);
	L.push_front(200);
	L.push_front(300);

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

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

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

	//insert插入
	//L.insert(L.begin(), 1000);
	//printList(L);//1000 200 100 10 20

	//还可以这样插入
	list<int>::iterator it = L.begin();
	L.insert(++it, 1000);
	printList(L); //200 1000 100 10 20

	//删除
	it = L.begin();
	L.erase(++it);
	printList(L);//200 100 10 20

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

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


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

插入和删除需要提供迭代器。

3.7.6 list数据存取

/3.7.6 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(30);

	//L1[0];不可以用[]访问list容器中的元素

	//L1.at(0); 不可以用at访问list容器中的元素

	/st本质是一个链表,不是用连续线性空间存储数据,迭代器也是不支持随机访问的

	cout << "第一个元素为:" << L1.front() << endl;

	cout << "最后一个元素为:" << L1.back() << endl;

	//验证迭代器是不支持随机访问的
	list<int>::iterator it = L1.begin();
	//it = it + 1;//错误,不支持随机访问
	it++;//正确,支持双向
	it--;
}
int main()
{
	test01();
	system("pause");
	return 0;
}

 

 

3.7.7 list反转和排序

 //3.7.7 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);

	cout << "反转前:" << endl;
	printList(L1);//10 20 30 40 

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

}

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

void test02()
{

	list<int>L1;

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

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

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

	//sort是成员函数
	L1.sort();//默认排序规则 从小到大 升序
	cout << "排序后:" << endl;
	printList(L1);

	L1.sort(myCompare);//降序
	printList(L1);
}
int main()
{
	//test01();
	test02();
	system("pause");
	return 0;
}

 

3.7.8 排序案例

//3.7.8 排序案例
#include<list>
#include<string>
class Person
{
public:
	Person(string name, int age, int height)
	{
		m_Name = name;
		m_Age = age;
		m_Height = height;
	}
public:
	string m_Name;
	int m_Age;
	int m_Height;
};

//指定排序规则
bool comparePerson(Person &p1,Person &p2)
{
	//按照年龄 升序
	if (p1.m_Age == p2.m_Age)
	{
		//年龄相同,按照身高降序
		return p1.m_Height > p2.m_Height;
	}
	else
	{
		return p1.m_Age < p2.m_Age;
	}
	
}
void test01()
{
	list<Person>L;//创建容器

	//准备数据
	Person p1("刘备", 35, 175);
	Person p2("曹操", 45, 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>::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();//错误
	L.sort(comparePerson);
	for (list<Person>::iterator it = L.begin(); it != L.end(); it++)
	{
		cout << "姓名:" << (*it).m_Name << " 年龄:" << it->m_Age << " 身高:" << it->m_Height << endl;
	}
}
int main()
{
	test01();
	system("pause");
	return 0;
}

3.8 set容器

3.8.1 set基本概念

3.8.2 set构造和赋值

//3.8.2 set构造和赋值
#include<set>

/t容器遍历
void printSet(const set<int>& s)
{
	for (set<int>::const_iterator it = s.begin(); it != s.end(); it++)
	{
		cout << *it << " ";
	}
	cout << endl;
}
void test01()
{
	set<int>s1;

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

	//t容器特点:所有元素再插入时会自动排序
	//t容器不允许插入重复的值
	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

}

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

3.8.3 set大小和交换

//3.8.3 set大小和交换
#include<set>
void printSet(const set<int>& s)
{
	for (set<int>::const_iterator it = s.begin(); it != s.end(); it++)
	{
		cout << *it << " ";
	}
	cout << endl;
}

void test01()
{
	set<int>s1;

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

	printSet(s1);//10 20 30 40

	
	//判断是否为空
	if (s1.empty())
	{
		cout << "s1为空" << endl;
	}
	else
	{
		cout << "s1不为空" << endl;
		//t不支持resize,重新指定大小
		cout << "s1的大小为:" << s1.size() << endl;//4
	}

}

//交换
void test02()
{
	set<int>s1;

	//插入数据 
	s1.insert(10);
	s1.insert(40);
	s1.insert(30);
	s1.insert(20);
	
	set<int>s2;

	//插入数据 
	s2.insert(100);
	s2.insert(400);
	s2.insert(300);
	s2.insert(200);

	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
	
}
int main()
{
	//test01();
	test02();
	system("pause");
	return 0;
}

 

3.8.4 set插入和删除

/3.8.4 set容器插入和删除
#include<set>

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

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

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

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

3.8.5 set查找和统计

 

//3.8.5 set查找和统计
#include<set>
void test01()
{
	set<int>s1;

	//插入数据 
	s1.insert(10);
	s1.insert(40);
	s1.insert(30);
	s1.insert(20);
	s1.insert(30);
	s1.insert(30);//set中存储:10 20 30 40

	//查找
	set<int>::iterator pos = s1.find(30);//返回的是迭代器

	if (pos != s1.end())
	{
		cout << "找到了元素:" << *pos << endl;
	}
	else
	{
		cout << "未找到元素" << endl;
	}

	//统计
	int num = s1.count(30);
	cout << "num=" << num << endl;//1
}

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

 

 

3.8.6 set和multiset区别

//3.8.6 set和multiset区别
#include<set>
void test01()
{
	set<int>s;

	pair<set<int>::iterator, bool> ret = s.insert(10);
	//对于set来所,返回值有个bool类型的检测,插入成功或者失败有个返回
	if (ret.second)//为真插入成功
	{
		cout << "第一次插入成功" << endl;//成功
	}
	else
	{
		cout << "第一次插入失败" << endl;
	}

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

	multiset<int>ms;//允许插入重复的值
	//而multiset只是返回个迭代器
	ms.insert(10);
	ms.insert(10);
	ms.insert(10);
	for (multiset<int>::iterator it = ms.begin(); it!= ms.end(); it++)
	{
		cout << *it << " ";//10 10 10
	}
	cout << endl;
}

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

 3.8.7

3.8.7 pair对组创建

//3.8.7 pair队组创建
#include<set>
#include<string>
void test01()
{
	pair<string, int>p(string("Tom"), 20);
	cout << "姓名:" << p.first << " 年龄:" << p.second << endl;

	pair<string, int>p2 = make_pair("Jerry", 10);
	cout << "姓名:" << p2.first << " 年龄:" << p2.second << endl;
}

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

 

 

3.8.8 set容器排序

 示例1:存放内置数据类型

//3.8.8 set容器排序
//1、内置类型指定排序规则
#include<set>
class MyCompare
{
public:
	bool operator()(int v1, int v2)const
	{
		return v1 > v2;//降序
	}
};

//1、内置类型指定排序规则
void test01()
{
	set<int>s1;

	s1.insert(10);
	s1.insert(40);
	s1.insert(20);
	s1.insert(30);

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

	//指定排序规则为从大到小——利用仿函数
	set<int,MyCompare>s2;//按照仿函数规则排序

	s2.insert(10);
	s2.insert(40);
	s2.insert(20);
	s2.insert(30);
	s2.insert(20);
	s2.insert(40);

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

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

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

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

/3.8.8 set容器排序
//1、自定义类型指定排序规则
#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 comparePerson
{
public:
	bool operator()(const Person&p1,const Person&p2)const
	{
		//按照年龄 降序
		return p1.m_Age > p2.m_Age;
	}
};
void test01()
{
	//自定义数据类型 要指定排序规则
	set<Person, comparePerson>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, comparePerson>::iterator it = s.begin(); it != s.end(); it++)
	{
		cout << "姓名:" << it->m_Name << " 年龄:" << it->m_Age << endl;
	}
}
int main()
{
	test01();
	system("pause");
	return 0;
}

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

3.9 map/multimap容器

3.9.1 map基本概念

3.9.2 map构造和赋值

//3.9.1 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 test01()
{
	//创建map容器
	map<int, int>m;

	//插入数据
	//map中所有的元素都是成对出现的,插入数据时需要使对组
	m.insert(pair<int, int>(1, 10));//匿名队组放入m	
	m.insert(pair<int, int>(3, 30));
	m.insert(pair<int, int>(2, 20));
	m.insert(pair<int, int>(4, 40));

	printMap(m);

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

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

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

总结:map容器中所有元素都是成对出现,插入数据时候要用对组。

3.9.3 map容器大小和交换

//3.9.2 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 test01()
{
	//创建map容器
	map<int, int>m;

	//插入数据
	m.insert(pair<int, int>(1, 10));
	m.insert(pair<int, int>(3, 30));
	m.insert(pair<int, int>(2, 20));
	m.insert(pair<int, int>(4, 40));

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

}

void test02()
{
	//创建map容器
	map<int, int>m1;

	//插入数据
	m1.insert(pair<int, int>(1, 10));
	m1.insert(pair<int, int>(3, 30));
	m1.insert(pair<int, int>(2, 20));
	m1.insert(pair<int, int>(4, 40));

	//创建map容器
	map<int, int>m2;

	//插入数据
	m2.insert(pair<int, int>(4, 100));
	m2.insert(pair<int, int>(6, 300));
	m2.insert(pair<int, int>(7, 200));
	m2.insert(pair<int, int>(8, 400));

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

	m1.swap(m2);
	cout << "交换后:" << endl;
	printMap(m1);
	printMap(m2);
}
int main()
{
	//test01();
	test02();
	system("pause");
	return 0;
}

3.9.4 map容器插入和删除

//3.9.3 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 test01()
{
	map<int, int>m;

	//插入
	//第一种
	m.insert(pair<int, int>(1, 10));

	//第二种
	m.insert(make_pair(2, 20));

	//第三种
	m.insert(map<int, int>::value_type(3, 30));

	//第四种
	m[4] = 40;
	
	//不建议用[]插入,可以用key访问到value
	//cout << m[5] << endl;//这种写法,如果没有5,会创建处一个key为5,value=0的值
	printMap(m);

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

	m.erase(3);//按照key的值删除,不会按照value删除
	printMap(m);

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

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

 

3.9.5 map容器查找和统计

//3.9.5 map查找和统计
#include<map>
void test01()
{
	map<int, int>m;
	m.insert(pair<int, int>(1, 10));
	m.insert(pair<int, int>(2, 20));
	m.insert(pair<int, int>(3, 30));
	m.insert(pair<int, int>(3, 30));

	//查找
	map<int, int>::iterator pos = m.find(3);//查找返回的时迭代器

	if (pos != m.end())
	{
		cout << "查到了元素 key= " <<(*pos).first<<" value= "<<pos->second<< endl;
	}
	else
	{
		cout << "未找到元素" << endl;
	}

	//统计
	//map不允许插入重复key元素,count统计而言结果要么是 0 要么是 1
	//multimap的统计结果可能大于1
	int num = m.count(3);
	cout << "num= " << num << endl;// num=1 //map有key相同的两个元素仅村一个
}

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

 

3.9.6 map容器排序

//3.9.6 map容器排序
//利用仿函数改变排序规则

#include<map>

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

	m.insert(make_pair<int, int>(1, 10));
	m.insert(make_pair<int, int>(2, 20));
	m.insert(make_pair<int, int>(3, 30));
	m.insert(make_pair<int, int>(4, 40));

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

 

3.10 案例——员工分组

3.10.1 案例描述

3.10.2 实现步骤

//3.10 员工分组案例
#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 deptID = rand() % 3;//0 1 2
		//将员工插入到分组中
		//key部门编号,value具体员工
		m.insert(make_pair(deptID, *it));
	}
}

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

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

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

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

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

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

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

	//3、分组显示员工
	showWorkerByGourp(mWorker);
	system("pause");
	return 0;
}

 

4 STL——函数对象

4.1 函数对象

4.1.1 函数对象概念

4.1.2 函数对象使用

// 4.函数对象(仿函数)
//4.1.2 函数对象使用
#include<string>
class MyAdd
{
public:
	int operator()(int v1, int v2) 
	{
		return v1 + v2;
	}
};

//1、函数对象在使用时,可以像普通函数那样调用,可以有参数,可以有返回值
void test01()
{
	MyAdd myAdd;//实际上为一个类创建的对象
	cout << myAdd(10, 10) << endl;
}

//2、函数对象超出普通函数的概念,函数对象可以有自己的状态
class MyPrint
{
public:
	MyPrint()
	{
		this->count = 0;
		
	}
	void operator()(string test)
	{
		cout << test << endl;
		this->count++;
	}
	int count;//内部自己状态
};

void test02()
{
	MyPrint myPrint;
	myPrint("hello world");
	myPrint("hello world");
	myPrint("hello world");
	cout << "myPrint调用次数为" << myPrint.count << endl;//3
	//普通函数可能只能拿全局变量或者静态变量来记录
}

//3、函数对象可以作为参数传递
void doPrint(MyPrint& mp, string test)
{
	mp(test);
}
void test03()
{
	MyPrint myPrint;
	doPrint(myPrint, "Hello c++");//Hello c++
}
int main()
{
	//test01();
	//test02();
	test03();
	system("pause");
	return 0;
}

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

4.2 谓词

4.2.1 谓词概念

4.2.2 一元谓词

//4.2.2 一元谓词
#include<vector>
#include<algorithm>
class GreaterFive
{
public:
	//一元谓词
	bool operator()(int val)
	{
		return val > 5;
	}
};
void test01()
{
	vector<int>v;
	for (int i = 0; i < 10; i++)
	{
		v.push_back(i);
	}

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


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

4.2.3 二元谓词

//4.2.3 二元谓词
#include<vector>
#include<algorithm>
class MyCompare
{
public:
	bool operator()(int val1, int val2)
	{
		return val1 > val2;
	}
};
void test01()
{
	vector<int>v;
	
	v.push_back(10);
	v.push_back(40);
	v.push_back(20);
	v.push_back(30);
	v.push_back(30);
	sort(v.begin(), v.end());
	for (vector<int>::iterator it = v.begin(); it != v.end(); it++)
	{
		cout << *it << " ";
	}
	cout << endl;//10 20 30 30 40

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

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

int main()
{
	test01();

	system("pause");
	return 0;
}

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

4.3 内建函数对象

4.3.1 内建函数对象意义

4.3.2 算法仿函数

//4.2.3 算术仿函数
#include<functional>//内建函数对象头文件
//negate——一元仿函数,取反仿函数
void test01()
{
	negate<int>n;
	cout << n(50) << endl;//-50
}

//plus 二元仿函数 加法
void test02()
{
	plus<int>p;
	cout << p(10, 20) << endl;//30
}
int main()
{
	//test01();
	test02();
	system("pause");
	return 0;
}

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

4.3.3 关系仿函数

//4.3.3 关系仿函数
#include<vector>
#include<algorithm>
#include<functional>

class MyCompare
{
public:
	bool operator()(int val1, int val2)
	{
		return val1 > val2;
	}
};

void test01()
{
	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 << " ";
	}
	cout << endl;//10 20 30 40 50

	//降序
	//sort需要引用头文件#include<algorithm>
	//sort(v.begin(), v.end(), MyCompare());
	//使用内建函数  需要引用头文件 #include<functional> 其内部构造就类似于class MyCompare
	sort(v.begin(), v.end(), greater<int>());//greater大于
	for (vector<int>::iterator it = v.begin(); it != v.end(); it++)
	{
		cout << *it << " ";
	}
	cout << endl;//50 40 30 20 10
}
int main()
{
	test01();
	
	system("pause");
	return 0;
}

总结:关系仿函数中最常用的就是greater<>大于。

4.3.4 逻辑仿函数

//4.3.4 逻辑仿函数
//逻辑非 logical_not
#include<vector>
#include<algorithm>
#include<functional>

void test01()
{
	vector<bool>v;
	v.push_back(true);
	v.push_back(false);
	v.push_back(true);
	v.push_back(false);

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

	//利用逻辑非 将容器v 搬运到 容器2中,并执行取反操作
	vector<bool>v2;
	v2.resize(v.size());//必须提前开辟空间

	transform(v.begin(), v.end(), v2.begin(),logical_not<bool>());//transform中的参数:元容器的起始 终止,目标容器的起始,仿函数

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

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

5 STL——常用算法

5.1 常用遍历算法

5.1.1 for_each

//5.1 常用遍历算法
//5.1.1 for_each
#include<vector>
#include<algorithm>

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

//仿函数
class print02
{
public:
	void operator()(int val)
	{
		cout << val << " ";
	}
};
void test01()
{
	vector<int>v;
	for (int i = 0; i < 10; i++)
	{
		v.push_back(i);
	}

	//遍历
	//普通函数将函数名字放入第三个参数位置
	for_each(v.begin(), v.end(), print01);//0 1 2 3 4 5 6 7 8 9
	cout << endl;

	//仿函数将函数对象放入第三个参数位置 print02()为匿名函数对象
	for_each(v.begin(), v.end(), print02());
	cout << endl;
}
int main()
{
	test01();
	system("pause");
	return 0;
}

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

5.1.2 transform

//5.1.2 transform
#include<vector>
#include<algorithm>

class Transform
{
public:
	int operator()(int v)
	{
		return v+100;//搬运中还可以做逻辑上的运算
	}
};

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

	
	vector<int>vTarget;//目标容器

	//开辟目标容器空间_一定要注意提前开辟
	vTarget.resize(v.size());

	transform(v.begin(), v.end(), vTarget.begin(), Transform());//将v搬运到vTarget

	for_each(vTarget.begin(), vTarget.end(), MyPrint());
	cout << endl;// 100 101 102 103 104 105 106 107 108 109
}

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

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

5.2 常用查找算法

5.2.1 find

//5.2 常用查找算法
//5.2.1 find
#include<vector>
#include<algorithm>
#include<string>

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

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

//2、自定义数据类型查找
class Person
{
public:
	Person(string name, int age) 
	{
		this->m_Name = name;
		this->m_Age = age;
	}
	//重载==
	bool operator==(const Person& p)
	{
		if (this->m_Name == p.m_Name && this->m_Age == p.m_Age)
		{
			return true;
		}
		return false;
	}
	string m_Name;
	int m_Age;
};
void test02()
{
	vector<Person>v;

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

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

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

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


int main()
{
	//test01();
	test02();

	system("pause");
	return 0;
}

5.2.2 find_if

//5.2.2 find_if
#include<vector>
#include<algorithm>
#include<string>

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

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

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


//2、自定义数据类型
class Person
{
public:
	Person(string name, int age)
	{
		this->m_Name = name;
		this->m_Age = age;
	}
	//重载==
	bool operator==(const Person& p)
	{
		if (this->m_Name == p.m_Name && this->m_Age == p.m_Age)
		{
			return true;
		}
		return false;
	}
	string m_Name;
	int m_Age;
};

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

void test02()
{
	vector<Person>v;

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

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

	vector<Person>::iterator it = find_if(v.begin(), v.end(), Greater20());

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

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

5.2.3 adjacent_find

//5.2.3 adjacent_find
#include<vector>
#include<algorithm>


void test01()
{
	vector<int>v;

	v.push_back(0);
	v.push_back(2);
	v.push_back(0);
	v.push_back(4);
	v.push_back(1);
	v.push_back(3);
	v.push_back(3);

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

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

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

//5.2.4 binary_search
#include<vector>
#include<algorithm>


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

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

}

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

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

5.2.5 count

//5.2.5 count
#include<vector>
#include<algorithm>
#include<string>

//1、统计内置数据类型
void test01()
{
	vector<int>v;
	v.push_back(10);
	v.push_back(40);
	v.push_back(30);
	v.push_back(40);
	v.push_back(20);
	v.push_back(40);

	int num = count(v.begin(), v.end(),40);
	cout << "40的元素个数为:" << num << endl;//3
}

//2、统计自定义数据类型
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;
		}
		return false;
	}

	string m_Name;
	int m_Age;
};

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

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

	Person p("诸葛亮", 35);
	int num = count(v.begin(), v.end(),p);
	cout << "num=" << num << endl;//3
}

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

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

5.2.6 count_if

//5.2.6 count_if
#include<vector>
#include<algorithm>
#include<string>

//1、统计内置数据类型
//谓词
class Greater20
{
public:
	bool operator()(int val)
	{
		return val > 20;
	}
};

void test01()
{
	vector<int>v;

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

	//查找容器中是否有5 这个元素
	int num = count_if(v.begin(), v.end(), Greater20());
     
	cout << "大于20的个数为:" << num <<endl;//4
}


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

	string m_Name;
	int m_Age;
};

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

void test02()
{
	vector<Person>v;

	//创建数据
	Person p1("刘备", 35);
	Person p2("关羽", 35);
	Person p3("张飞", 35);
	Person p4("曹操", 45);
	Person p5("赵云", 20);

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

	//统计年龄大于20的个数
	int num= count_if(v.begin(), v.end(), AgeGreater20());

	cout << "大于20岁的人员个数为:" << num << endl;//4
}


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

5.3 常用排序算法

5.3.1 sort

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

	//sort默认从小到大排序
	sort(v.begin(), v.end());
	for_each(v.begin(), v.end(), myPrint);
	cout << endl;//10 20 30 40 50

	//从大到小
	sort(v.begin(), v.end(),greater<int>());
	for_each(v.begin(), v.end(), myPrint);
	cout << endl;//50 40 30 20 10
}

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

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

5.3.2 random_shuffle

//5.3.2 random_shuffle
#include<algorithm> //sort
#include<vector>
#include<functional>  //greater
#include<ctime>//time
void myPrint(int val)
{
	cout << val << " ";
}

void test01()
{
	srand((unsigned int)time(NULL));
	vector<int>v;
	for (int i = 0; i < 10; i++)
	{
		v.push_back(i);
	}
	//打乱顺序
	random_shuffle(v.begin(), v.end());

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

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

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

5.3.3 merge

//5.3.3 merge
#include<algorithm> //sort
#include<vector>
#include<functional>  //greater

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

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

	//目标容器
	vector<int>vTarget;

	//提前给目标容器分配空间
	vTarget.resize(v1.size() + v2.size());

	//有序序列  合并后仍为  有序序列
	merge(v1.begin(), v1.end(), v2.begin(), v2.end(), vTarget.begin());

	for_each(vTarget.begin(), vTarget.end(), myPrint);
	cout << endl;
}
int main()
{
	test01();
	system("pause");
	return 0;
}

总结:merge合并的两个容器必须有序序列。

5.3.4 reverse 

//5.3.4 reverse
#include<algorithm> //sort
#include<vector>
#include<functional>  //greater

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

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

	cout << "反转前:"<<endl;
	for_each(v.begin(), v.end(), myPrint);
	cout << endl;//10 30 50 20 40

	cout << "反转后:" << endl;
	reverse(v.begin(), v.end());
	for_each(v.begin(), v.end(),myPrint);
	cout << endl;//40 20 50 30 10
}
int main()
{
	test01();
	system("pause");
	return 0;
}

总结:reverse反转区间元素,面试题可能涉及到。

5.4 常用拷贝和替换算法 

5.4.1 copy

//5.4 常用拷贝和替换算法
//5.4.1 copy
#include<algorithm> 
#include<vector>
#include<functional>  

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

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

	vector<int>v2;
	v2.resize(v1.size());
	//不要过分依赖,实际开发中有=赋值拷贝直接用更方便
	copy(v1.begin(), v1.end(), v2.begin());
	for_each(v2.begin(), v2.end(), myPrint);
	cout << endl;//0~9
}

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

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

5.4.2 replace

//5.4.2 replace
#include<algorithm> //for_each
#include<vector>
#include<functional>  

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

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

	//将所有20替换为2000
	replace(v.begin(), v.end(), 20, 2000);
	cout << "替换后:" << endl;
	for_each(v.begin(), v.end(), MyPrint());
	cout << endl;
}
int main()
{
	test01();
	system("pause");
	return 0;
}

总结:replace会替换区间内满足条件的元素。

5.4.3 replace_if

//5.4.3 replace_if
#include<algorithm> //for_each
#include<vector>
#include<functional> 

//仿函数
class MyPrint
{
public:
	void operator()(int val)
	{
		cout << val << " ";
	}
};
//利用仿函数筛选出满足条件的数
class Greater30
{
public:
	bool operator()(int val)
	{
		return val >= 30;
	}
};
void test01()
{
	vector<int>v;
	v.push_back(20);
	v.push_back(30);
	v.push_back(50);
	v.push_back(20);
	v.push_back(20);
	v.push_back(30);
	v.push_back(40);

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

	//将大于等于30  替换为  3000
	replace_if(v.begin(), v.end(), Greater30(), 3000);
	cout << "替换后:" << endl;
	for_each(v.begin(), v.end(), MyPrint());
	cout << endl;//20 3000 3000 20 20 3000 3000
}
int main()
{
	test01();
	system("pause");
	return 0;
}

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

5.4.4 swap

//5.4.3 replace_if
#include<algorithm> //for_each
#include<vector>
#include<functional> 

//仿函数
class MyPrint
{
public:
	void operator()(int val)
	{
		cout << val << " ";
	}
};
//利用仿函数筛选出满足条件的数
class Greater30
{
public:
	bool operator()(int val)
	{
		return val >= 30;
	}
};
void test01()
{
	vector<int>v;
	v.push_back(20);
	v.push_back(30);
	v.push_back(50);
	v.push_back(20);
	v.push_back(20);
	v.push_back(30);
	v.push_back(40);

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

	//将大于等于30  替换为  3000
	replace_if(v.begin(), v.end(), Greater30(), 3000);
	cout << "替换后:" << endl;
	for_each(v.begin(), v.end(), MyPrint());
	cout << endl;//20 3000 3000 20 20 3000 3000
}
int main()
{
	test01();
	system("pause");
	return 0;
}

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

5.5 常用算术生成算法

5.5.1 accumulate

 总结:accumulate使用时头文件注意时numeric,这个算法很实用。

5.5.2 fill 

//5.5.2 fill
#include<vector>
#include<numeric>
#include<algorithm>
void myPrint(int val)
{
	cout << val << " ";
}
void test01()
{
	vector<int>v;
	v.resize(10);//10个0
	//后期重新填充
	fill(v.begin(), v.end(), 100);
	for_each(v.begin(), v.end(), myPrint);
	cout << endl;//100 100 100 100 100 100 100 100 100 100
}
int main()
{
	test01();
	system("pause");
	return 0;
}

总结:利用fill可以将容器区间内元素填充为指定的值。

5.6 常用集合算法

5.6.1 set_intersection

//5.6.1 set_intersection(交集)
#include<vector>
#include<algorithm>

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

void  test01()
{
	vector<int>v1;
	vector<int>v2;
	for (int i = 0; i < 10; i++)
	{
		v1.push_back(i);//0~9
		v1.push_back(i+5);//5~14
	}

	vector<int>vTarget;
	//目标容器提前开辟空间
	//最特殊的情况:大容器包含小容器,开辟空间  取小容器的size即可
	vTarget.resize(min(v1.size(), v2.size()));

	//获取交集  两个序列必须有序  返回目标容器的最后一个元素的迭代器地址
	vector<int>::iterator itEnd=set_intersection(v1.begin(), v1.end(), v2.begin(), v2.end(), vTarget.begin());

	for_each(vTarget.begin(), itEnd, myPrint);
	//结束迭代器若为vTarget.end();  则输出为 5 6 7 8 9 0 0 0 0 
	cout << endl;//5 6 7 8 9 
}

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

总结:求交集的两个集合必须的有序序列。

5.6.2 set_union

//5.6.2 set_union(并集)
#include<vector>
#include<algorithm>
void myPrint(int val)
{
	cout << val << " ";
}
void test01()
{
	vector<int>v1;
	vector<int>v2;
	for (int i = 0; i < 10; i++)
	{
		v1.push_back(i);//0~9
		v1.push_back(i + 5);//5~14
	}
	vector<int>vTarget;
	//目标容器提前开辟空间
	//最特殊的情况:两个容器没有交集   并集就是两个容器size相加
	vTarget.resize(v1.size()+v2.size());

	vector<int>::iterator itEnd = set_union(v1.begin(), v1.end(), v2.begin(), v2.end(), vTarget.begin());
	
	for_each(vTarget.begin(), itEnd, myPrint);
	//结束迭代器若为vTarget.end(); //0~14 0 0 0 0 0 
	cout << endl;//0~14
}

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

5.6.3 set_difference

//5.6.3 set_difference(差集)
#include<vector>
#include<algorithm>

void myPrint(int val)
{
	cout << val << " ";
}
void test01()
{
	vector<int>v1;
	vector<int>v2;
	for (int i = 0; i < 10; i++)
	{
		v1.push_back(i);//0~9
		v1.push_back(i + 5);//5~14
	}
	vector<int>vTarget;
	//给目标容器开辟空间
	//特殊情况:两个集合相互独立没有交集,就为大容器size大小为目标容器空间
	vTarget.resize(max(v1.size(), v2.size()));

	cout << "v1和v2的差集:" << endl;
	vector<int>::iterator itEnd = set_difference(v1.begin(), v1.end(), v2.begin(), v2.end(), vTarget.begin());
	
	for_each(vTarget.begin(), itEnd, myPrint);
	//结束迭代器若为vTarget.end(); //0 1 2 3 4 0 0 0 0 0
	cout << endl;//0  1 2 3 4 


	cout << "v2和v1的差集:" << endl;
	itEnd = set_difference(v2.begin(), v2.end(), v1.begin(), v1.end(), vTarget.begin());

	for_each(vTarget.begin(), itEnd, myPrint);
	//结束迭代器若为vTarget.end(); 
	cout << endl;// 10 11 12 13 14 

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

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值