文章目录
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 普通函数与函数模板调用规则
调用规则如下:
- 如果函数模板和普通函数都可以实现,优先调用普通函数
- 可以通过空模板参数列表来强制调用函数模板
- 函数模板也可以发生 重载
- 如果函数模板可以产生更好的匹配,优先调用函数模板
总结: 既然提供了函数模板,最好就不要提供普通函数,否则容易出现二义性
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 类模板与函数模板区别
类模板与函数模板区别主要有两点:
- 类模板没有自动类型推导的使用方式
- 类模板在模板参数列表中可以有默认参数
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 类模板对象做函数参数
学习目标: 类模板实例化出的对象,向函数传参的方式
共有三种传入方式:
- 指定传入的类型 —— 直接显示对象的数据类型
- 参数模板化 —— 将对象中的参数变为模板进行传递
- 整个类模板化 —— 将这个对象类型模板化进行传递
总结: 使用笔记广泛的是第一种:指定传入的类型。( 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 类模板与继承
当类模板碰到继承时,需要注意以下几点:
- 当子类继承的父亲是一个类模板时,子类在声明的时候,要指定出父类中 T 的类型
- 如果不指定,编译器无法给子类分配内存
- 如果想灵活指定出父类中 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 大体分为六大组件,分别是:容器 、算法 、迭代器 、仿函数 、适配器(配接器) 、空间配置器
-
容器: 各种数据结构,如 vector、list、deque、set、map 等,用来存放数据
-
算法: 各种常用的算法,如 sort、find、copy、for_each 等
-
迭代器: 扮演了容器与算法之间的胶合剂
-
仿函数: 行为类似函数,可作为算法的某种策略
-
适配器: 一种用来修饰容器或者仿函数或者迭代器接口的东西
-
空间配置器: 负责空间的配置与管理
2.4 STL 中容器、算法、迭代器
-
容器: 置物之所也
STL 容器就是将运用 最广泛的一些数据结构 实现出来
常用的数据结构:数组、链表、树、栈、队列、集合、映射表 等
这些容器分为 序列式容器 和 关联式容器 两种:
-
序列式容器:强调值的排序,序列式容器中的每个元素均有固定的位置
-
关联式容器:二叉树结构,各元素之间没有严格的物理上的顺序关系
-
-
算法: 问题之解法也
有限的步骤,解决逻辑或数学上的问题,这一门学科我们叫做算法( Algorithms )
算法分为: 质变算法 和 非质变算法
- 质变算法:是指运算过程中会更改区间内的元素的内容,例如拷贝、替换、删除 等等
- 非质变算法:是指运算过程中不会更改区间内的元素的内容,例如查找、计数、遍历、寻找极值 等等
-
迭代器: 容器和算法之间的结合剂
提供一种方法,使之能够依序寻访某个容器所含的各个元素,而又无需暴露该容器的内部表示方式
每个容器都有自己专属的迭代器
迭代器使用非常类似于指针,初学阶段我们可以先理解迭代器为指针
迭代器种类:
种类 功能 支持运算 输入迭代器 对数据的只读访问 只读,支持 ++、==、!= 输出迭代器 对数据的只写访问 只写,支持 ++ 前向迭代器 读写操作,并能向前推进迭代器 读写,支持 ++、==、!= 双向迭代器 读写操作,并能向前和向后操作 读写,支持 ++、– – 随机访问迭代器 读写操作,可以以跳跃的方式访问任意数据,功能最强的迭代器 读写,支持 ++、– – 、[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 个字符为字符串 strstring & 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 个字符 cstring & 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);
//尾部插入元素 elepop_back();
//删除最后一个元素insert(const_iterator pos,ele);
//迭代器指向位置 pos 插入元素 eleinsert(const_iterator pos,int count,ele);
//迭代器指向位置 pos 插入 count 个元素 eleerase(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个评委分别对每一位选手打分,去除最高分和最低分,取平均分
实现步骤:
- 创建5名选手,放到 vector 中;
- 遍历 vector 容器,取出每一个选手,执行 for 循环,可以把10个评分打分存在 deque 容器中;
- sort 算法对 deque 容器中的分数排序,去除最高分和最低分;
- deque 容器遍历一遍,累加总分;
- 获取平均分
#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 常用接口
功能描述: 栈容器常用的对外接口
-
构造函数:
stack<T> stk;
//stack 采用模块实现,stack 对象的默认构造形式stack(const stack & stk);
//拷贝构造函数
-
赋值操作:
stack & operator=(const stack & stk);
//重载等号操作符
-
数据存取:
push(elem);
//向栈顶添加元素pop();
//从栈顶移除第一个元素top();
//返回栈顶元素
-
大小操作:
-
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 常用接口
功能描述: 栈容器常用的对外接口
- 构造函数:
queue<T> que;
//queue 采用模块类实现,queue 对象的默认构造形式queue(const queue & que);
//拷贝构造函数
- 赋值操作:
queue operator=(const queue & que);
//重载等号操作符
- 数据存取:
push(elem);
//往队尾添加元素pop();
//从队头移除第一个元素back();
//返回最后一个元素front();
//返回第一个元素
- 大小操作:
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 中 list 和 vector 是最常使用的容器,各有优缺点
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(员工)(有可能都在一个部门)
- 分部门显示员工信息
实现步骤:
- 创建10名员工,放在vector 中
- 遍历vector 容器,取出每个员工,进行随机分组
- 分组后,将员工信息编号作为key ,具体员工作为value ,放入multimap 容器中
- 分部门显示员工信息
#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 - 常用算法
概述: 算法主要是由头文件 algorithm
、 functional
、 numeric
组成
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