C++高级编程
1 模板(泛型编程)
1.1 模版的概念
模板就是建立通用的模具,大大提高复用性
1.2 函数模板
- C++的另一种编程思想称为泛型编程,主要利用的技术就是模板
- C++的两种模板机制:函数模板和类模板
1.2.1 函数模板语法
函数模板作用:
建立一个通用函数,其函数返回值类型和形参类型可以不具体制定,用一个虚拟的类型来代表。
template<typename T>
函数声明或定义
解释:
template — 声明创建模板
typename — 表明其后面的符号是一种数据类型,可以用class代替
**T ** — 通用的数据类型,名称可以替换,通常都是大写字母
//函数模板
template<typename T> //声明一个模板,T是一个通用数据类型
void mySwap(T& a, T& b) {
T temp = a;
a = b;
b = temp;
}
void test01() {
int a = 10;
int b = 20;
//两种方式使用函数模板
//1.自动类型推导
//mySwap(a, b);
//2.显示指定类型
mySwap<int>(a, b);
cout << "a=" << a << endl;
cout << "b=" << b << endl;
}
int main() {
test01();
system("pause");
return 0;
}
总结:
- 函数模板利用关键字template
- 使用函数模板有两种方式,自动类型推导、显示指定类型
- 模板的目的是提高复用性,将类型参数化
1.2.2 函数模板注意事项
- 自动类型推导,必须是推导一致的数据类型T,才可以使用。
- 模板必须确定出T的数据类型,才可以使用
template<typename T> //声明一个模板,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 = 'd';
//1.自动类型推导
mySwap(a, b);
//mySwap(a,c); //错误,推导不出一致的数据类型
cout << "a=" << a << endl;
}
//2.模板必须确定出T的数据类型,才可以使用
template<class T>
void func(){
cout<<"cout调用"<<endl;
}
int main() {
test01();
//func();//报错, 该函数模板没指定出T的数据类型
func<int>();//显示指定类型,随便给T指定一个数据类型, 就能运行起来了
system("pause");
return 0;
}
1.2.3 函数模板案例
- 利用函数模板封装一个排序的函数,可以对不同数据类型数组进行排序
- 排序规则从大到小,排序算法为选择排序
- 分别利用char数组和int数组进行测试
//交换模板
template<typename T>
void mySwap(T &a,T &b) {
T temp = a;
a = b;
b = temp;
}
//选择排序模板
template<typename 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++)
{
if (arr[max] < arr[j]) {
max = j; //因为是从大到小排序,每次都找最大值交换
}
}
if(max!=i){
mySwap(arr[max], arr[i]);
}
}
}
//打印的模板
template<typename T>
void printArray(T arr[],int len){
for (int i = 0; i < len; i++) {
cout << arr[i] << " ";
}
cout << endl;
}
void test01() {
char charArr[] = "badcfe";
int len = sizeof(charArr) / sizeof(char);
mySort(charArr, len);
printArray(charArr, len);
}
void test02() {
int intArr[] = { 7,5,1,3,9,2,4,6,8 };
int len = sizeof(intArr) / sizeof(intArr[0]);
mySort(intArr, len);
printArray(intArr, len);
}
int main() {
//test01();
test02();
system("pause");
return 0;
}
1.2.4 普通函数和函数模板区别
- 普通函数调用时可以发生自动类型转换(隐式类型转换)
- 函数模板调用时,如果利用自动类型推导,不会发送隐式类型转换
- 如果利用显示指定类型的方式,可以发生隐式类型转换
//普通函数
int myAdd01(int a,int b) {
return a + b;
}
//函数模板
template<typename T>
T myAdd02(T a, T b) {
return a + b;
}
void test01() {
int a = 10;
int b = 20;
char c = 'c';
cout << myAdd01(a, c) << endl;//普通函数调用时可以发生隐式类型转换
//自动类型推导
myAdd02(a, b);
//myAdd02(a, c); //报错,如果利用自动类型推导,不会发送隐式类型转换
//显示指定类型
myAdd02<int>(a, b);
//如果利用显示指定类型的方式,可以发生隐式类型转换
myAdd02<int>(a, c);//明确告诉编译器如果不是int类型的转为int类型
}
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() {
int a = 10;
int b = 20;
//如果函数模板和普通函数都可以实现,则优先调用普通函数
myPrint(a, b);//此时会调用普通函数
//通过空模板参数列表来强制调用函数模板
myPrint<>(a, b);
//调用重载的函数模板
myPrint(a, b, 100);
//如果函数模板可以产生更好的匹配,优先调用函数模板
char c = 'c';
char d = 'd';
myPrint(c, d);//编译器会觉得强转为int型比较麻烦,直接使用可以匹配的模板
}
总结:既然提供了函数模板,最好不要提供相同功能的普通函数,否则容易出现二义性
1.2.6 模板的局限性
- 模板的通用性并不是万能的
为了解决T的数据类型传入的是像Person这样的自定义数据类型的问题,C++提供模板的重载,可以为这些特定的类型提供具体化的模板
class Person {
public:
Person(string name,int age) {
this->m_Name = name;
this->m_Age = age;
}
string m_Name;
int m_Age;
};
//对比两个数据是否相等,常规函数模板
template<typename T>
bool myCompare(T &a, T &b) {
if (a == b) {
return true;
}
else {
return false;
}
}
//利用具体化Person的版本实现代码,具体化优先调用
//template<> 声明这是一个具体化数据类型的函数模板
template<> bool myCompare(Person& p1, Person& p2) { //myCompare函数模板重载
if (p1.m_Age == p2.m_Age && p1.m_Name == p2.m_Name) {
return true;
}
else {
return false;
}
}
void test02() {
Person p1("Tom", 11);//看到是Person,优先走具体化Person的函数模板
Person p2("Tom", 10);
bool ret = myCompare(p1, p2);
if (ret) {
cout << "p1==p2" << endl;
}
else {
cout << "p1!=p2" << endl;
}
}
总结:
- 利用具体化的模板,可以解决自定义类型的通用化
- 学习模板并不是为了写模板,而是在STL能够运用系统提供的模板
1.3 类模板
1.3.1 类模板语法
类模板作用:
- 建立一个通用类,类中的成员 数据类型可以不具体指定,用一个虚拟的类型来代表
语法:
template<typename T>
类
示例:
//类模板
template<typename NameType,typename AgeType>
class Person {
public:
Person(NameType name,AgeType age) {
this->m_Name = name;
this->m_Age = age;
}
void showPerson() {
cout << "name: " << this->m_Name << " age:" << this->m_Age << endl;
}
NameType m_Name;
AgeType m_Age;
};
void test01() {
Person<string, int> p1("Tom", 18);//<string,int>是模板参数列表
p1.showPerson();
}
1.3.2 类模板与函数模板区别
- 类模板没有自动类型推导的使用方式,只能用显示指定类型方式!
- 类模板在模板参数列表中可以有默认参数
//类模板
template<typename NameType,typename AgeType>
//template<typename NameType,typename 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_Age << endl;
}
NameType m_Name;
AgeType m_Age;
};
void test01() {
//Person p1("Tom",18);//报错,类模板没有自动类型推导的使用方式
Person<string, int> p1("Tom", 18);//<string,int>是模板参数列表
//在模板参数列表中可以有默认参数
Person<string> p2("猪八戒", 999);//因为AgeType有默认参数int,所以不用写
p1.showPerson();
}
1.3.3 类模板成员函数创建时机
- 普通类中的成员函数一开始就可以创建
- 类模板中的成员函数在调用时才创建
class Person1 {
public:
void showPerson1() {
cout << "Person1 show" << endl;
}
};
class Person2 {
public:
void showPerson2() {
cout << "Person2 show" << endl;
}
};
//创建一个类模板
template<typename T>
class Myclass {
public:
T obj;//当T是Person数据类型时,看作是实例化一个对象
//类模板中的成员函数在调用时才创建
void func1() {
obj.showPerson1();
}
void func2() {
obj.showPerson2();
}
};
void test01() {
Myclass<Person1> m;//实例化对象m
m.func1();
//m.func2();//报错,Person1的对象无法访问Person2的成员函数
}
1.3.4 类模板对象做函数参数
类模板实例化出的对象,向函数传参的方式:
- 指定传入的类型 — 直接显示对象的数据类型
- 参数模板化 — 将对象的参数变为模板进行传递
- 整个类模板化 — 将这个对象类型 模板化进行传递
//类模板
template<typename T1, typename T2>
class Person {
public:
Person(T1 name, T2 age) {
this->m_Name = name;
this->m_Age = age;
}
void showPerson() {
cout << "name: " << this->m_Name << " age:" << this->m_Age << endl;
}
T1 m_Name;
T2 m_Age;
};
//1.指定传入的类型(最常用)
void printPerson1(Person<string, int>& p) {
p.showPerson();
}
void test01() {
Person<string, int> p("孙悟空", 100);
printPerson1(p);
}
//2.参数模板化
template<typename T1,typename T2>
void printPerson2(Person<T1,T2> &p) {
p.showPerson();
//如何看模板中参数列表推导出的具体参数类型
cout << typeid(T1).name() << endl;//输出string
}
void test02() {
Person<string, int> p("猪八戒", 100);
printPerson2(p);
}
//3.整个类模板化
template<typename T>
void printPerson3(T & p) {//将整个Person类看做一个数据类型,整个用T表示
p.showPerson();
}
void test03() {
Person<string, int> p("唐僧", 30);
printPerson3(p);
}
1.3.5 类模板与继承
注意以下几点:
- 当子类继承的父类是一个类模板时,子类在声明的时候,要指定父类中T的类型
- 如果不指定,编译器无法给予子类分配内存
- 如果想灵活指定出父类中T的类型,子类也需变为类模板
template<class T>
class Base{
T m;
};
//class Son:public Base;//报错,必须要知道父类中的T类型,才能继承给子类
class Son:public Base<int>{
};
void test01(){
Son s1;
}
//如果想灵活指定父类中T类型,子类也需要变类模板
template<class T1,class T2>
class Son2:public Base<T2>{
public:
T1 obj;
};
void test02(){
Son2<int ,char> s2;//实例化子类
}
1.3.6 类模板成员函数类外实现
//类模板成员函数类外实现
template<typename T1, typename T2>
class Person {
public:
Person(T1 name, T2 age);//函数声明
/*{
this->m_Name = name;
this->m_Age = age;
}*/
void showPerson();//函数声明
/*{
cout << "name: " << this->m_Name << " 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>
void Person<T1, T2>::showPerson() //记得加上模板参数列表<>
{
cout << "name: " << this->m_Name << " age:" << this->m_Age << endl;
}
1.3.7 类模板分文件编写
类模板中成员函数创建时机是在调用阶段,导致分文件编写时链接不到,解决方案:
- 直接包含.cpp源文件
- 将声明和实现写到同一个文件中,并更改后缀名为.hpp, hpp是约定的名称,并不是强制
.h文件
#pragma once
#include<string>
template<typename T1, typename 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"
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 << " age:" << this->m_Age << endl;
}
main函数文件
#include "Person.cpp"
void test01() {
Person<string,int> p("Tom", 18);
p.showPerson();
}
int main() {
test01();
system("pause");
return 0;
}
或者将声明和实现写到同一个文件中,并更改后缀名为.hpp(主流解决方法)
.hpp文件,声明实现都在一个文件中,一看就知道是类模板
#pragma once
#include<string>
template<typename T1, typename 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 << " age:" << this->m_Age << endl;
}
1.3.8 类模板与友元
全局函数类内实现-- 直接在类内声明友元即可
全局函数类外实现-需要提前让编译器知道全局函数的存在
//全局函数,类外实现
template<class T1,class T2>
class Person;
template<class T1, class T2>
void printPerson2(Person<T1, T2> p) {
cout << "类外实现---姓名:" << p.m_Name << "年龄:" << p.m_Age << endl;
}
template<class T1,class T2>
class Person {
//全局函数,类内实现
friend void printPerson(Person<T1,T2> p) {
cout << "姓名:" << p.m_Name << "年龄:" << p.m_Age << endl;
}
//全局函数,类外实现
friend void printPerson2<>(Person<T1, T2> p);
public:
Person(T1 name, T2 age) {
this->m_Name = name;
this->m_Age = age;
}
private:
T1 m_Name;
T2 m_Age;
};
void test01() {
Person<string, int> p("Tom", 18);
printPerson(p);
}
void test02() {
Person<string, int> p("jack", 18);
printPerson2(p);
}
1.3.9 类模板案例
案例描述:实现一个通用的数组类,要求如下:
- 可以对内置数据类型以及自定义数据类型的数据进行存储
- 将数组中的数据存储在堆区
- 构造函数中可以传入数组的容量
- 提供对应的拷贝构造函数以及operator=防止浅拷贝问题
- 提供尾插法和尾删法对数组中的数据进行增加和删除
- 可以通过下标的方式访问数组中的元素
- 可以获取数组中当前元素个数和数组的容量
MyArray.hpp文件
#pragma once
#include<iostream>
using namespace std;
#include<string>
//通用数组类
template<class T>
class myArray {
public:
//有参构造 参数为容量
myArray(int capacity) {
this->m_Capacity = capacity;
this->m_Size = 0;//初始化元素个数
this->pAddress = new T[capacity];
}
//拷贝构造
myArray(const myArray& arr) {
this->m_Capacity = arr.m_Capacity;
this->m_Size = arr.m_Size;
//this->pAddress =arr.pAddress //这是个指针,浅拷贝会导致堆区数据重复释放问题,需用深拷贝
//深拷贝
this->pAddress = new T[arr.m_Capacity];
//将数组arr中的数据都拷贝过来
for (int i = 0; i < this->m_Size; i++) {
this->pAddress[i] = arr.pAddress[i];
}
}
//重载operator= 防止浅拷贝问题
myArray& operator=(const myArray & arr) {
//先判断原来堆区是否有数据,如果有先释放
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];
//将数组arr中的数据都拷贝过来
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--;
}
//通过下标的方式访问数组中的数据 arr[0]=100,当重载运算符做左值时应该使用引用类型返回
T& operator[](int index) {
return this->pAddress[index];
}
//返回数组容量
int getCapacity() {
return this->m_Capacity;
}
//返回数组大小
int getSize() {
return this->m_Size;
}
//析构函数
~myArray() {
if (this->pAddress != NULL) {
delete[] this->pAddress;
this->pAddress = NULL;
}
}
private:
T* pAddress;
int m_Capacity;
int m_Size;
};
main.cpp文件
#include "MyArray.hpp"
void printArray(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);
}
printArray(arr1);
//拷贝复制
myArray<int> arr2(arr1);
//对arr2进行尾删
arr2.Pop_Back();
cout << "尾删后:" << endl;
cout << "arr2容量为:"<<arr2.getCapacity() << endl;
cout << "arr2大小为" << arr2.getSize() << endl;
}
//测试自定义数据类型
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;//arr[i]返回的是一个Person对象
}
}
void test02() {
myArray<Person> arr(10);//会调用无参构造
Person p1("刘一", 18);
Person p2("刘2", 19);
Person p3("刘3", 13);
Person p4("刘4", 14);
Person p5("刘5", 15);
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;
}
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容器就是将运用最广泛地一些数据结构实现出来。
常用的数据结构:数组,链表,树,栈,队列,集合,映射表等
这些容器分为序列式容器和关联式容器两种:
- 序列式容器:强调值的排序,序列式容器中的每个元素均有固定的位置。
- 关联式容器:二叉树结构,各元素之间没有严格的物理上的顺序关系。
算法:
算法分为:质变算法和非质变算法。
质变算法:是指运算过程中会更改区间内的元素的内容。例如拷贝,替换,删除等
非质变算法:是指运算过程中不会更改区间内的元素内容,例如查找,计数,遍历,寻找极值等
迭代器:
容器和算法之间的粘合剂。每个容器都有自己专属的迭代器,迭代器使用非常类似于指针,初学阶段先理解迭代器为指针。
迭代器种类:
常用的容器中迭代器种类为双向迭代器,和随机访问迭代器。
2.5 容器算法迭代器初识
STL中最常用的容器为Vector,可以理解为数组。
2.5.1 vector存放内置数据类型
容器:vector
算法:for_each
迭代器:vector: : iterator
#include<iostream>
using namespace std;
#include<string>
#include<vector>
#include<algorithm>
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();//起始迭代器,指向容器中第一个元素,即用itBegin指针指向第一个元素的地址
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存放自定义数据类型
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("a",10);
Person p2("b", 20);
Person p3("c", 30);
Person p4("d", 40);
//向容器添加数据
v.push_back(p1);
v.push_back(p2);
v.push_back(p3);
v.push_back(p4);
//遍历容器中数据
for (vector<Person>::iterator it = v.begin(); it != v.end(); it++) {//*it解引用后表示一个具体的对象
cout << "姓名"<<(*it).m_Name <<"年龄" <<(*it).m_Age << endl;
//it看做是一个结构体指针,所以也可以用->访问属性
cout << "姓名" << it->m_Name << "年龄" << it->m_Age << endl;
}
}
//存放自定义数据类型,指针
void test02() {
vector<Person*> v;
Person p1("a", 10);
Person p2("b", 20);
Person p3("c", 30);
Person p4("d", 40);
//向容器添加数据
v.push_back(&p1);
v.push_back(&p2);
v.push_back(&p3);
v.push_back(&p4);
//遍历容器中数据
//一个技巧:*it解引用出来的数据类型跟<Person>尖括号里的类型保持一致。
for (vector<Person*>::iterator it = v.begin(); it != v.end(); it++) {//*it迭代得到的是Person类型指针
cout << "姓名" << (*it)->m_Name << "年龄" << (*it)->m_Age << endl;
}
}
2.5.3 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);
v2.push_back(i+1);
v3.push_back(i+2);
v4.push_back(i+3);
}
//将小容器放进大容器
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实际上代表一个容器数据类型,所有还得进一步遍历
for (vector<int>::iterator vit = (*it).begin(); vit != (*it).end(); vit++) {
cout << *vit << " ";
}
cout << endl;
}
}
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(const char* s);
//使用字符串s初始化 -
string(const string& str); //使用一个string对象初始化另一个string对象,拷贝构造函数
-
string(int n,char c); //使用n个字符c初始化
string s1;//创建空字符串,调用无参构造函数
const char * str = "hello world";
string s2(str);
string s3(s2);//调用拷贝构造
string s4(10,'a');
3.1.3 string赋值操作
赋值的函数原型:
- string& operator=(const char* s); //char*类型字符串,赋值给当前的字符串
- string& operator=(const string &s); //把字符串s赋给当前的字符串
- string& operator=(char c); //字符赋值给当前字符串
- string& assign(const char *s); //把字符串s赋给当前的字符串
- string& assign(const char *s,int n); //把字符串s的前n个字符赋给当前的字符串
- string& assign(const string &s); //把字符串s赋给当前字符串
- string& assign(int n,char c); //用n个字符c赋给当前字符串
string str1;
str1 = "hello world";
str2 = str1;
string str3;
str3 = 'a';
string str4;
str4.assign("hello C++");
string str5;
str5.assign("hello C++",5);//输出hello
string str6;
str6.assign(str5);//类似拷贝构造函数
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) ;
- string& append(const string &s, int pos, int n); //字符串s中从pos开始的n个字符连接到字符串结尾
string str1="我";
str1+="爱玩儿游戏";//追加一个字符串
string str3="I";
str3.append(" love ");//输出 I love
str3.append("game abcde",4);//把字符串s的前4个字符连接到当前字符串结尾
3.1.5 string查找和替换
- 查找:查找指定字符串是否存在
- 替换:在指定的位置替换字符串
函数原型:
string str1="abcdefgde";
int pos = str1.find("de");//pos=3,没找到则返回pos=-1;
//rfind和find区别:rfind是从右往左查找,find是从左往右
pos =str1.rfind("de");//pos=7
string str2= "abcde";
str2.replace(1,3,"1111");//替换从位置1开始的3个字符为字符串1111,输出a1111e
3.1.6 string字符串比较
比较方式:
- 字符串比较是按字符的ASCII码进行对比
= 返回 0;> 返回 1;< 返回 -1;
函数原型:
- int compare(const string &s) const;
- int compare(const char * s) const;
string str1 = "hello";
string str2 = "hello";
if(str1.compare(str2)==0){
cout<<"str1等于str2"<<endl;
}
3.1.7 string 字符存取
string中单个字符存取方式有两种:
- char& operator[] (int n);
- char& at(int n);
string str="hello";
//1.通过[]访问单个字符
for(int i=0;i<str.size();i++){
cout<<str[i]<<endl;
}
//2.通过at方式访问单个字符
for(int i=0;i<str.size();i++){
cout<<str.at(i)<<endl;
}
//修改单个字符
str[0]='x';
cout<<str<<endl;//xello
str.at(1)='x';
cout<str<<endl;//xxllo
3.1.8 string插入和删除
功能描述:对string字符串进行插入和删除操作。
函数原型:
string str="hello";
//插入
str.insert(1,"111");//输出h111ello
//删除
str.erase(1,3);//输出hello
3.1.9 string 子串
函数原型;
- string substr(int pos=0,int n=npos) const; //返回由pos开始的n个字符组成的字符串
string str = "abcdef";
string str2 = str.substr(1,3);//输出子串bcd
string email ="zhangsan@sina.com";
//截取用户名
int pos =email.find('@');
string usrName = email.substr(0,pos);//输出zhangsan
3.2 vector容器
3.2.1 vector基本概念
功能:vector数据结构和数组非常相似,也称为单端数组
vector与普通数组区别:
- 不同之处在于数组是静态空间,而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;//1.默认构造
for (int i = 0; i < 10; i++) {
v1.push_back(i);
}
printVector(v1);
//2.通过区间方式构造
vector<int> v2(v1.begin(), v1.end());
printVector(v2);
//3.n个elem方式构造,构造函数将10个100拷贝给本身
vector<int> v3(10, 100);
printVector(v3);
}
3.2.3 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;//1.默认构造
for (int i = 0; i < 10; i++) {
v1.push_back(i);
}
printVector(v1);
//赋值 operator=
vector<int> v2;
v2 = v1;
printVector(v2);
//assign
vector<int> v3;
v3.assign(v1.begin(), v1.end());//左闭右开
printVector(v3);
//n个elem方式
vector<int> v4;
v4.assign(10, 100);
printVector(v4);
}
3.2.4 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;//1.默认构造
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;
cout << "大小为:" << v1.size() << endl;
}
//v1.resize(15);//如果重新指定的比原来size长,默认用0填充
v1.resize(15,100);//利用重载版本,可以指定默认填充值为100
printVector(v1);
v1.resize(5);
printVector(v1);//如果重新指定size的比原来短,超出部分会删除
}
3.2.5 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;//1.默认构造
v1.push_back(10);
v1.push_back(20);
v1.push_back(30);
v1.push_back(40);
printVector(v1);
//尾删
v1.pop_back();
printVector(v1);
//在迭代器指向位置插入100
v1.insert(v1.begin(), 100);
printVector(v1);
//在迭代器指向位置插入2个1000
v1.insert(v1.begin(),2,1000);
printVector(v1);//1000,1000,100,10,20,30,40
//删除迭代器指向的起始元素
v1.erase(v1.begin());
//删除迭代器从start到end之间的元素,类似清空操作
v1.erase(v1.begin(),v1.end());
//清空
v1.clear();
}
总结:
- 尾插 — push_back
- 尾删 — pop_back
- 插入 — insert(位置迭代器)
- 删除 — erase(位置迭代器)
3.2.6 vector数据存取
函数原型:
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]<<endl;
}
//利用at方式访问
for( int i=0;i<v1.size();i++){
cout<<v1.at(i)<<endl;
}
//获取第一个元素
cout<<v1.front()<<endl;
//获取最后一个元素
cout<<v1.back()<<endl;
}
3.2.7 vector互换空间
功能描述:
- 实现两个容器内元素进行互换
函数原型:
- vector1.swap(vector2) ; //将vec与本身元素互换
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;//1.默认构造
for (int i = 0; i < 10; i++) {
v1.push_back(i);
}
printVector(v1);
vector<int> v2;//1.默认构造
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;//1.默认构造
for (int i = 0; i < 10000; i++) {
v.push_back(i);
}
cout << "v的容量为:" << v.capacity()<<endl;//13825
cout << "v的大小为:" << v.size() << endl;//10000
v.resize(3);//重新指定大小,产生浪费空间的问题
cout << "v的容量为:" << v.capacity() << endl;//13825
cout << "v的大小为:" << v.size() << endl;//3
//巧用swap收缩内存,解决问题
//vector<int>(v)是一个匿名对象,当前行结束便被回收
//按照v的大小来初始化当前匿名对象,它的大小和容量都为3
vector<int>(v).swap(v); //容器交换,类似指针交换
cout << "v的容量为:" << v.capacity() << endl;//3
cout << "v的大小为:" << v.size() << endl;//3
}
3.2.8 vector 预留空间
功能描述:
- 减少vector在动态扩展容量时的扩展次数
函数原型:
- reserve(int len) ; //容器预留len个元素长度,预留位置不初始化,元素不可访问
void test02() {
vector<int> v;//1.默认构造
//利用reserve预留空间
v.reserve(10000);
int num = 0;//用于统计内存开辟次数
int* p = NULL;
for (int i = 0; i < 10000; i++) {
v.push_back(i);
if (p != &v[0]) {//如果指针没有指向容器第一个元素地址
p = &v[0];
num++;
}
}
cout << "num=" << num << endl;//如果不预留空间需要开辟24次,预留后就一次
}
3.3 deque容器(双向队列)
3.3.1 deque容器基本概念
功能:
- 双端数组,可以对两端进行插入删除操作
deque和vector区别:
- vector对于头部的插入删除效率低,数据量越大,效率越低
- deque相对而言,对头部的插入删除速度会比deque快
- vector访问元素时的速度会比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++) {
//*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);
//构造函数将n个elem拷贝给本身
deque<int> d3(10, 100);
printDeque(d3);
//拷贝构造
deque<int> d4(d3);
printDeque(d4);
}
3.3.3 deque赋值操作
函数原型:(与vector操作相同)
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);
//利用operator=赋值
deque<int> d2;
d2 = d1;
//assign赋值
deque<int> d3;
d3.assign(d1.begin(), d1.end());
deque<int> d4;
d4.assign(10, 100);
}
3.3.4 deque大小操作
函数原型:
deque因为本身数据结构的原因,不存在capacity()这个接口。
3.3.5 deque插入和删除
函数原型:
3.3.6 deque数据存取
函数原型:(跟vector一样)
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;
d1.push_back(10);
d1.push_back(20);
d1.push_front(100);
d1.push_front(200);
for(int i=0;i<d1.size();i++){
cout<<d1[i]<<endl;
}
for(int i=0;i<d1.size();i++){
cout<<d1.at(i)<<endl;
}
cout<<d1.front()<<endl;
cout<<d1.back()<<endl;
}
3.3.7 deque排序
利用算法实现对deque容器进行排序。
对于支持随机访问的迭代器的容器,都可以利用sort算法来直接排序。比如vector容器也支持使用sort排序。
算法:(sort默认升序)
sort(iterator begin, iterator end);
//对begin到end区间元素进行排序
#include<deque>
#include<algorithm>
void printDeque(const deque<int> &q){
for(deque<int>::const_iterator it=q.begin();it!=q.end();it++){
cout<<*it<<" ";
}
cout<<endl;
}
void test01(){
deque<int> d1;
d1.push_back(10);
d1.push_back(20);
d1.push_back(30);
d1.push_front(100);
d1.push_front(200);
d1.push_front(300);
printDeque(d1);
//排序,默认排序规则为从小到大,升序
sort(d1.begin(),d1.end());//使用算法时记得引入头文件algorithm
}
3.4 案例-评委打分
案例描述:
有5名选手:选手ABCDE,10个评委分别对每一位选手打分,去除最高分和最低分,取平均分。
实现步骤:
#include<deque>
#include<vector>
#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 createPerson(vector<Person> &v) {
string nameSeed = "ABCDE";
for (int i = 0; i < 5; i++) {
string name = "选手";
name += nameSeed[i];
int score = 0;
Person p(name, score);
//将创建的对象放入到容器
v.push_back(p);
}
}
//打分
void setScore(vector<Person> &v) {
for (vector<Person>::iterator it = v.begin(); it != v.end(); it++) {
deque<int> d;
for (int i = 0; i < 10; i++) {
int score = rand() % 41 + 60;//rand()%41可以生成0-40的随机数
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_front();
d.pop_back();
//求平均
int sum = 0;
for (deque<int>::iterator dit = d.begin(); dit != d.end(); dit++) {
sum += (*dit);
}
int average = sum / d.size();
it->m_Score = average;
}
}
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));
//创建5名选手
vector<Person> v;
createPerson(v);
//给5名选手打分
setScore(v);
//显示最后得分
showScore(v);
system("pause");
return 0;
}
3.5 stack容器(栈)
3.5.1 stack基本概念
概念:stack是一种先进后出的数据结构,栈中只有顶端的元素才可以被外界使用,因此栈不允许有遍历行为。
3.5.2 stack常用接口
void test(){
stack<int> s;
//入栈
s.push(10);
s.push(20);
while(!s.empty()){
cout<<"栈顶元素为:"<<s.top()<<endl;
}
//出栈
s.pop();
cout<<"栈的大小为:"<<s.size()<<endl;
}
3.6 queue容器(队列)
3.6.1 queue基本概念
概念:queue是一种先进先出的数据结构。
队列中进数据称为-----入队 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("刘1", 18);
Person p2("刘2", 19);
//入队
q.push(p1);
q.push(p2);
while (!q.empty()) {
cout << "姓名:" << q.front().m_Name << "年龄:" << q.front().m_Age << endl;
q.pop();
}
cout << "队列大小:" << q.size() << endl;
}
3.7 list容器
3.7.1 list基本概念
功能:将数据进行链式存储。
链表list是一种物理存储单元上非连续的存储结构,数据元素的逻辑顺序是通过链表中的指针链实现的。
链表的组成:由一系列结点组成,结点由数据域(存储数据元素)和指针域(存储下一个结点的地址)组成。
由于链表的存储方式并不是连续的内存空间,因此不支持随机存取,list中的迭代器只支持前移和后移,属于双向迭代器。
优点:
- 可以快速对任意位置进行插入删除元素,不需要移动大量元素。
- 采样动态存储分配,不会造成内存浪费和溢出。
缺点:链表遍历速度没有数组快,占用空间比数组大。
List有一个重要的性质,插入操作和删除操作都不会造成原有list迭代器失效,而在vector是不成立的。
总结:STL中list和vector是两个最常用的容器,各有优缺点。
3.7.2 list构造函数
函数原型:(跟vector,deque一致)
//创建list容器
list<int> L1;//默认构造
//区间方式构造
list<int> L2(L1.begin(),L1.end());
//拷贝构造
list<int> L3(L2);
3.7.3 list赋值和交换
函数原型:(与vector一致)
list<int> L1;
L1.push_back(10);
L1.push_back(20);
list<int> L2;
//赋值
L2 = L1;
//交换
L2.swap(L1);
3.7.4 list大小操作
函数原型:
3.7.5 list插入和删除
3.7.6 list数据存取
函数原型:
front();
//返回第一个元素back();
//返回最后一个元素
因为不是连续的空间,所以不支持随机存取,即不能用[ ]和at( )存取数据。
list<int> L1;
//验证迭代器是不支持随机访问的
list<int>::iterator it=L1.begin;
it++;
it--;//加减都可以,则支持双向访问
//it=it+1;//报错,不支持随机访问
3.7.7 list反转和排序
函数原型:
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 compare(int v1,int v2){
return v1>v2;
}
void test01() {
list<int> L1;
L1.push_back(10);
L1.push_back(20);
L1.push_back(50);
L1.push_back(30);
printList(L1);
cout << "反转后:" << endl;
L1.reverse();
printList(L1);
cout << "排序后:" << endl;
//不支持随机访问迭代器的容器,内部会提供对应一些算法
//sort(L1.begin(),L1.end());//这个全局算法不能在不能随机访问的容器使用
L1.sort();//默认为升序
printList(L1);
//利用重载,提供一个函数变为降序
L1.sort(compare);
}
3.7.8 排序案例
案例描述:将Person自定义数据类型进行排序
排序规则:按照年龄进行升序,如果年龄相同则按身高降序
class Person {
public:
Person(string name,int age,int height) {
this->m_Name = name;
this->m_Age = age;
this->m_Height = height;
}
string m_Name;
int m_Age;
int m_Height;
};
//指定排序规则
bool comparePerson(Person &p1,Person &p2) {
if (p1.m_Age == p2.m_Age) {
return p1.m_Height > p2.m_Height;
}
else {
return p1.m_Age < p2.m_Age;
}
}
void test01() {
list<Person> L;
Person p1("刘备", 35, 175);
Person p2("曹操", 45, 180);
Person p3("孙权", 40, 170);
Person p4("赵云", 35, 190);
Person p5("关羽", 35, 185);
//放入容器
L.push_back(p1);
L.push_back(p2);
L.push_back(p3);
L.push_back(p4);
L.push_back(p5);
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;
}
}
3.8 set/multiset 容器
3.8.1 set基本概念
简介:
- 所有元素都会在插入时自动被排序
本质:
- set/multiset属于关联式容器,底层结构是由二叉树实现的。
set和multiset区别:
- set不允许容器中有重复的元素
- multiset允许容器中有重复的元素
3.8.2 set构造和赋值
构造:
set<T> st;
//默认构造set(const set &st);
//拷贝构造函数
赋值:
set& operator=(const set &st);
//重载等号操作符
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;
//插入数据,只有insert方式
s1.insert(10);
s1.insert(40);
s1.insert(30);
s1.insert(20);
s1.insert(30);
printSet(s1);//输出10,20,30,40,会默认排好序,并去除重复值
//拷贝构造
set<int> s2(s1);
//赋值
set<int> s3;
s3 = s2;
}
3.8.3 set大小和交换
函数原型;
size();
//返回容器中元素的数目,记住它不支持resize()empty();
//判断容器是否为空swap(st);
//交换两个集合容器 // s1.swap(s2);
3.8.4 set插入和删除
3.8.5 set查找和统计
函数原型:
find(key)
; //查找key是否存在,若存在,返回该键的元素的迭代器,若不存在,返回set.end();count(key);
//统计key的元素个数
void test01(){
set<int> s;
s.insert(10);
s.insert(30);
s.insert(20);
//查找,并返回位置的迭代器(地址)
set<int>::iterator pos = s.find(30);
if(pos!=s.end())P{
cout<<"找到元素:"<<*pos<<endl;
}
else{
cout<<"未找到元素"<<endl;
}
//统计
int sum = s.count(30);
}
3.8.6 set和multiset区别
- set不可以插入重复数据,而multiset可以
- set插入数据的同时会返回插入结果,表示插入是否成功
- multiset不会检测数据,因此可以插入重复数据
void test01() {
set<int> s;
pair<set<int>::iterator, bool> ret = s.insert(10);//pair<a,b>称为对组数据类型
if (ret.second) {
cout << "第一次插入成功!" << endl;
}
else {
cout << "第一次插入失败" << endl;
}
ret = s.insert(10);
if (ret.second) {//第二次为false,所以插入失败
cout << "第二次插入成功!" << endl;
}
else {
cout << "第二次插入失败" << endl;//
}
multiset<int> ms;
ms.insert(10);
ms.insert(10);
for (multiset<int>::iterator it = ms.begin(); it != ms.end(); it++) {
cout << *it << " ";
}
cout << endl;
}
3.8.7 pair对组创建
成对出现的数据,可以利用对组来返回两个数据。
两种创建方式:
pair<type,type> p (value1,value2);
pair<type,type> p=make_pair(value1,value2);
void test01(){
//第一种方式
pair<string,int> p ("Tom",20);
cout<<"姓名:"<<p.first<<"年龄"<<p.second<<endl;
//第二种方式
pair<string,int> p2=make_pair("Jerry",30);
cout<<"姓名:"<<p2.first<<"年龄"<<p2.second<<endl;
}
3.8.8 set容器排序
学习目标:set容器默认排序规则为从大到小,掌握如何改变排序规则。
主要技术点:利用仿函数,可以改变排序规则
//利用仿函数
class Mycompare {
public:
//加const后为常函数内,不可修改成员属性
bool operator()(int v1,int v2) const {// 非常对象可以调用常函数,但是常对象只能调用常函数,不能调用其他任何函数。
return v1 > v2;
}
};
void test01() {
//指定排序规则从大到小 按降序排列
set<int,Mycompare> s1;//把数据类型Mycompare放入<>中,随后重载()符号
s1.insert(20);
s1.insert(10);
s1.insert(50);
s1.insert(40);
s1.insert(30);
for (set<int, Mycompare>::iterator it = s1.begin(); it != s1.end(); it++) {
cout << *it << " ";
}
cout << endl;
}
/**************************************/
//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 test02() {
//指定排序类型
set<Person,comparePerson> s;
Person p1("刘",24);
Person p2("关", 27);
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;
}
}
3.9 map/multimap 容器
3.9.1 map基本概念
简介:
- map中所有元素都是pair。
- pair中第一个元素为key(键值),起到索引作用,第二个元素为value(实值)。
- 所有元素都会根据元素的键值自动排序
本质:
- map/multimap属于关联式容器,底层结构是用二叉树实现的
优点:
- 可以根据key值快速找到value值
map与multimap区别:
- map不允许容器有重复key值元素
- multimap允许容器中有重复key值元素
3.9.2 map构造和赋值
构造:
map<T1,T2> mp;
//map默认构造map<const map &mp>; //拷贝构造
赋值:
map& operator=(const map &mp)
//重 载=符号
void printMap(map<int,int> &m){
for(map<int,int>::iterator it=m.begin();it!=m.end();it++){
cout<<"key="<<(*it).first<<"value="<<(*it).second<<endl;
}
}
void test01(){
//创建map容器
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> (4,40));
printMap(m);//会根据key值自动排序 升序
//拷贝构造
map<int,int> m2(m);
//=赋值
map<int,int> m3;
m3 = m2;
}
3.9.3 map大小和交换
函数原型:
size();
//返回容器中元素的数目empty();
//判断容器是否为空swap(st);//交换两个容器集合
3.9.4 map插入和删除
函数原型:
insert(elem);
//在容器中插入元素clear();
//清除所有元素erase(pos);
//删除pos迭代器所指的元素,返回下一个元素的迭代器erase(beg,end);
//删除区间[begin,end)的所有元素,返回下一个元素的迭代器erase(key);
//删除容器中值为key的元素
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.erase(m.begin());
//按照key删除
m.erase(3);//删除key=3的对组
m.erase(m.begin(),m.end());
m.clear();
}
3.9.5 map查找和统计
函数原型:
find(key);
//查找key是否存在,若存在,返回该键元素的迭代器,若不存在,返回map.end()count(key);
//计算key的元素个数
map<int,int> m;
map<int,int>::iterator pos = m.find(3);
int num = m.count(3);//返回0或1,因为map不允许重复插入
3.9.6 map容器排序
//利用仿函数,改变排序规则
class myCompare {
public:
bool operator()(int v1,int v2) const{
//降序
return v1 > v2;
}
};
void test02() {
map<int, int,myCompare> m;
m.insert(make_pair(1, 40));
m.insert(make_pair(3, 30));
m.insert(make_pair(2, 20));
m.insert(make_pair(4, 20));
m.insert(make_pair(5, 50));
for (map<int, int,myCompare>::iterator it = m.begin(); it != m.end(); it++) {
cout << "key值为:" << it->first << " value: " << it->second << endl;
}
}
3.10 案例 员工分组
-
创建10名员工,放到vector中。
-
遍历vector容器,取出每个员工,进行随机分组
-
分组后,将员工部门编号作为key,具体员工工作为value,放入到multimap容器中
-
分部门显示员工信息。
#include<ctime>
#include<vector>
#include<map>
#define CEHUA 0
#define MEISHU 1
#define YANFA 2
class Worker {
public:
string m_Name;
int m_Salary;
};
void createWorker(vector<Worker> &w) {
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;
w.push_back(worker);
}
}
//员工分组
void setGroup(vector<Worker> &v,multimap<int,Worker> &w) {
for (vector<Worker>::iterator it = v.begin(); it != v.end(); it++) {
//产生随机部门
int deptId = rand() % 3;
//key为部门编号,value为具体员工
w.insert(make_pair(deptId, *it));
}
}
void showWorkerByGroup(multimap<int,Worker> &m) {
cout << "策划部门:" << endl;
multimap<int, Worker>::iterator pos = m.find(CEHUA);
int count = m.count(CEHUA);
int index = 0;
for (; pos != m.end() && index < count; index++, pos++) {
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; index++, pos++) {
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; index++, pos++) {
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);
system("pause");
return 0;
}
4 STL-函数对象
4.1 函数对象
4.1.1 函数对象概念
概念:
- 重载函数调用操作符的类,其对象常称为函数对象
- 函数对象使用重载的()时,行为类似函数调用,也叫仿函数
本质:
函数对象(仿函数)是一个类,不是一个函数。
4.1.2 函数对象使用
特点:
- 函数对象在使用时,可以像普通函数那样调用,可以有参数,可以有返回值。
- 函数对象超出普通函数的概念,函数对象可以有自己的状态。
- 函数对象可以作为参数传递。
//1.函数对象在使用时,可以像普通函数那样调用,可以有参数,可以有返回值
class myAdd {
public:
int operator()(int v1,int v2) {
return v1 + v2;
}
};
//2.函数对象超出普通函数的概念,函数对象可以有自己的状态。
class myPrint {
public:
myPrint() {
this->count = 0;
}
void operator()(string test) {
cout << test << endl;
count++;
}
int count;//内部自己的状态
};
void test01() {
myAdd myadd;
cout << myadd(10, 20) << endl;
}
void test02() {
myPrint myprint;
myprint("hello");
myprint("hello");
myprint("hello");
myprint("hello");
cout << myprint.count << endl;
}
//3.函数对象可以作为参数传递。
void doPrint(myPrint& mp,string test) {
mp(test);
}
void test03() {
myPrint myprint;
doPrint(myprint, "hello");
}
4.2 谓词
4.2.1 谓词概念
概念:
- 返回bool类型的仿函数称为谓词
- 如果operator()接受一个参数,那么叫一元谓词
- 如果operator()接受两个参数,那么叫二元谓词
4.2.2 一元谓词
#include<iostream>
using namespace std;
#include<string>
#include<vector>
#include<algorithm>
//一元谓词
class GreaterFive {
public:
bool operator()(int val) {
return val > 5;
}
};
void test01() {
vector<int> v;
for (int i; 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 << *it << endl;//输出6
}
}
4.2.3 二元谓词
//二元谓词
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(20);
v.push_back(40);
sort(v.begin(), v.end());
for (vector<int>::iterator it = v.begin(); it != v.end(); it++) {
cout << *it << " ";
}
cout << endl;
//使用函数对象,改变算法策略,变为降序
cout << "变为降序后:" << endl;
sort(v.begin(), v.end(), MyCompare());
for (vector<int>::iterator it = v.begin(); it != v.end(); it++) {
cout << *it << " ";
}
cout << endl;
}
4.3 内建函数对象
4.3.1 内建函数对象意义
概念:
- STL内建了一些函数对象
分类:
- 算术仿函数
- 关系仿函数
- 逻辑仿函数
用法:
- 这些仿函数所产生的对象,用法和一般函数完全相同
- 使用内建函数对象,需要引入头文件
#include<functional>
4.3.2 算术仿函数
功能描述:
- 实现四则运算
- 其中negate是一元运算,其他都是二元
#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
}
4.3.3 关系仿函数
函数原型:
#include<functioinal>
#include<vector>
void test(){
vector<int> v;
v.push_back(10);
v.push_back(20);
v.push_back(30);
v.push_back(40);
//greater<int>() 内建函数对象
sort(v.begin(),v.end(),greater<int>());//降序排序
for(vector<int>::iterator it=v.begin();it!=end();it++){
cout<<*it<<" ";
}
cout<<endl;
}
4.3.4 逻辑仿函数
实现逻辑运算:
#include<algorithm>
#include<vector>
#include<functional>
void test01(){
vector<bool> v;
v.push_back(false);
v.push_back(true);
v.push_back(true);
v.push_back(false);
//利用逻辑非 将容器v搬运到容器v2,并执行取反操作
vector<bool> v2;
v2.resize(v.size());//指定v2空间大小
transform(v.begin(),v.end(),v2.begin(),logical_not<bool>());
}
5 STL-常用算法
概述:
5.1 常用遍历算法
5.1.1 for_each
函数原型:
for_each(iterator beg,iterator end,_func);
//for_each
//1.利用普通函数实现遍历操作
void print01(int val) {
cout << val <<" ";
}
//2.利用仿函数实现遍历操作
class print02 {
public:
void operator()(int val) {
cout << val << " ";
}
};
void test01() {
vector<int> v;
for (int i = 0; i < 10; i++) {
v.push_back(i);
}
//遍历,最后一个参数中放入函数名
for_each(v.begin(), v.end(), print01);
cout << endl;
//仿函数遍历最后一个参数必须传函数对象才行
for_each(v.begin(), v.end(), print02());
}
5.1.2 transform
功能:搬运容器到另一个容器
函数原型:
transform(iterator beg1,iterator end1,iterator begin2,_func)
class Transform {//转换时并存的仿函数,这里做的操作是元素搬过来后都+100
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());
for_each(vTarget.begin(), vTarget.end(), MyPrint());
}
5.2 常用查找算法
算法简介:
find
//查找元素find_if
//按条件查找元素adjacent_find
//查找相邻重复元素binary_search
//二分查找法count
//统计元素个数count_if
//按条件统计元素个数
5.2.1 find
功能描述:
- 查找指定元素,找到返回指定元素的迭代器,找不到返回结束迭代器。
函数原型:
find(ietrator beg,iterator end,value);
//内置数据类型的查找
void test01() {
vector<int> v1;
for (int i = 0; i < 10; i++) {
v1.push_back(i);
}
vector<int>::iterator it = find(v1.begin(), v1.end(), 5);
if (it == v1.end()) {
cout << "没有这个数" << endl;
}
else {
cout << *it << endl;
}
}
//自定义数据类型的查找
class Person {
public:
Person(string name,int age) {
this->m_Name = name;
this->m_Age = age;
}
//重载=符号 让底层fand知道Person数据类型
bool operator==(const Person&p) {
if (this->m_Name==p.m_Name&&this->m_Age==p.m_Age) {
return true;
}
else {
return false;
}
}
string m_Name;
int m_Age;
};
void test02() {
vector<Person> v;
Person p1("a", 13);
Person p2("b", 14);
Person p3("c", 15);
v.push_back(p1);
v.push_back(p2);
v.push_back(p3);
Person pp("b", 14);
//查找有没有跟pp一样的人
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;
}
}
5.2.2 find_if
功能:
- 按条件查找元素
函数原型:
find_if(iterator beg, iterator end, _pred)
//最后一个参数为函数或谓词
//1.内置数据类型
class GreaterFive {
public:
bool operator()(int val) {
return val > 5;
}
};
void test01() {
vector<int> v;
for (int i; 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 << *it << endl;//输出6
}
}
//2.查找自定义数据类型
class Person {
public:
Person(string name,int age) {
this->m_Name = name;
this->m_Age = age;
}
string m_Name;
int m_Age;
};
class greater13 {
public:
bool operator()(Person &p) {
return p.m_Age > 13;
}
};
void test02() {
vector<Person> v;
Person p1("a", 13);
Person p2("b", 14);
Person p3("c", 15);
v.push_back(p1);
v.push_back(p2);
v.push_back(p3);
//使用find_if按条件查,年龄大于13的
vector<Person>::iterator it=find_if(v.begin(),v.end(),greater13());
if (it == v.end()) {//这里不需要重载==,因为谓词指定好了明确比较属性m_Age
cout << "查找失败" << endl;
}
else {
cout << "姓名" << it->m_Name << "年龄" << it->m_Age << endl;
}
}
5.2.3 adjacent_find
功能:
- 查找相邻且重复的元素,并返回第一个的迭代器
函数原型:
adjacent_find(iterator beg,iterator end);
void test(){
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(1);
v.push_back(3);
v.push_back(3);
v.push_back(2);
vector<int>::iterator pos=adjacent_find(v.begin(),v.end());
if(pos==v.end()){
cout<<"未找到重复元素"<<endl;
}
else{
cout<<*pos<<endl;//相邻且重复的元素为3,所有输出为3
}
}
5.2.4 binary_search
功能:
- 二分查找,查找指定元素是否存在。返回真或假。(注:无序序列中不可用)
函数原型:
bool binary_search(iterator beg,iterator end,value)
void test01(){
vector<int> v;
for(int i=0;i<10;i++){
v.push_back(i);
}
//v.push_back(2); 再插入2进去后,变为无序序列,结果未知
bool ret = binary_search(v.begin(),v.end(),9);
if(ret){
cout<<"找到了"endl;
}
}
5.2.5 count
功能:统计元素个数
函数原型:
count(iterator beg,iterator end,value);
总结:统计自定义数据类型时候,需要配合重载 operator==
//1.统计内置数据类型
void test01() {
vector<int> v;
v.push_back(10);
v.push_back(10);
v.push_back(20);
v.push_back(30);
v.push_back(10);
int num = count(v.begin(), v.end(), 10);
cout << num << 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_Age==p.m_Age) {
return true;
}
else {
return false;
}
}
string m_Name;
int m_Age;
};
void test02() {
vector<Person> v;
Person p1("a", 30);
Person p2("b", 40);
Person p3("c", 40);
Person p4("d", 35);
v.push_back(p1);
v.push_back(p2);
v.push_back(p3);
v.push_back(p4);
//统计vector容器中与e相同年龄的人
Person p("e", 40);
int num = count(v.begin(), v.end(), p);
cout << num << endl;
}
5.2.6 count_if
功能:按条件统计元素个数
函数原型:
count_if(iterator beg, iterator end, _pred)
//_pred谓词
案例参照find_if的案例。
5.3 常用排序算法
常用算法:
- sort //对容器内元素进行排序
- random_shuffle //洗牌,指定范围内的元素随机调整次序
- merge //容器元素合并,并存储到另一容器中
- reverse //反转指定范围的元素
5.3.1 sort
功能:
- 对容器内元素进行排序
函数原型:
sort(iterator beg, iterator end, _pred)
// _pred为谓词(函数对象)或一个函数,不填的话默认升序
#include<functioinal>
#include<vector>
#include<algorithm>
void test(){
vector<int> v;
v.push_back(10);
v.push_back(20);
v.push_back(30);
v.push_back(40);
//greater<int>() 内建函数对象
sort(v.begin(),v.end(),greater<int>());//降序排序
for(vector<int>::iterator it=v.begin();it!=end();it++){
cout<<*it<<" ";
}
cout<<endl;
}
5.3.2 random_shuffle
功能:洗牌,指定范围内的元素随机调整次序
函数原型:
random_shuffle(iterator beg,ietrator end);
可以加上随机数种子,让每次随机打乱得不一样。
#include<ctime>
#include<algorithm>
srand((unsigned int)time(NULL));
5.3.3 merge
功能:容器元素合并,并存储到另一容器中,
注:原容器都必须是有序序列,且都是升序或降序。将两个有序序列融合到一起,最后依旧是有序序列。
函数原型:
merge(ieterator beg1,iterator end1,ieterator beg2,iterator end2,iterator dest);
//dest是目标容器开始迭代器
#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);
v2.push_back(i+1);
}
//目标容器
vector<int> v3;
//提前给v3分配空间
v3.resize(v1.size()+v2.size());
merge(v1.begin(),v1.end(),v2.begin(),v2.end(),v3.begin());
for_each(v3.begin(),v3.end(),Myprint);
cout<<endl;
}
5.3.4 reverse
功能:将容器中的元素进行反转
函数原型:
reverse(iterator beg,iterator end);
5.4 常用拷贝和替换算法
算法简介:
- copy //容器内指定范围的元素拷贝到另一个容器。
- replace //将容器内指定范围的旧元素修改为新元素
- replace_if //容器内指定范围满足条件的元素替换为新元素
- swap //互换两个容器的元素
5.4.1 copy
功能:容器内指定范围的元素拷贝到另一个容器。
函数原型:
copy(iterator beg,iterator end,iterator dest)
vector<int> v1;
vector<int> v2;
v2.resize(v1.size());
copy(v1.begin(),v1.end(),v2.begin());
5.4.2 replace
功能:将容器内指定范围的旧元素修改为新元素
函数原型:
replace(iterator beg,iterator end,oldvalue,newvalue)
;
5.4.3 replace_if
功能:容器内指定范围满足条件的元素替换为新元素
函数原型:
replace_if(iterator beg,iterator end,_pred,newvalue);
//_pred谓词告诉判断条件
class greater30{
public:
bool operator()(int val){
return val>30;
}
};
class Myprint{
public:
void operator()(int val){
cout<<val<<" ";
}
};
void test01(){
vector<int> v;
v.push_back(20);
v.push_back(50);
v.push_back(30);
v.push_back(40);
//将大于30的替换成100
replace(v.begin(),v.end(),greater30(),100);
//打印
for_each(v.begin(),v.end(),Myprint());
}
5.4.4 swap
功能:互换同种类型的两个容器的元素
函数原型:
- swap(container c1,container c2);
#include<algorithm>
vector<int> v1;
vector<int> v2;
swap(v1,v2);
5.5 常用算术生成算法(#include < numeric >)
5.5.1 accumulate
功能:计算区间内,容器元素累计总和
- accumulate(iterator beg, iterator end, value); //参数value为起始累加值
#include<numeric>
void test(){
vextor<int> v;
for(int i=0;i<100;i++){
v.push_back(i);
}
int total = accumulate(v.begin(),v.end(),0)//这里的0为起始累加值
}
5.5.2 fill
功能:向容器中填充指定的元素
函数原型:
fill(iterator beg,iterator end,value);
vector<int> v;
v.resize(10);//全部为0填充
//后期重新填充
fill(v.begin(),v.end(),100);//输出10个100
5.6 常用集合算法
5.6.1 set_intersection
功能:求两个容器的交集。两个容器都必须为有序序列。
函数原型:
set_intersection(v1.begin(),v1.end(),v2.begin(),v2.end(),v3.begin())
//v3为目标容器
#include<algorithm>
class Myprint{
public:
void operator()(int val){
cout<<val<<" ";
}
};
void test01(){
vector<int> v1;
vector<int> v2;
for(int i=0;i<10;i++){
v1.push_back(i);
v2.push_back(i+5);
}
vector<int> v3;
//目标容器需要提前开辟空间,特殊情况为大容器包含小容器,所以取小容器size即可
v3.resize(min(v1.size(),v2.size()));
//获取交集,并返回交集中最后一个元素的迭代器
vector<int>::iterator itEnd = set_intersection(v1.begin(),v1.end(),v2.begin(),v2.end(),v3.begin());
//遍历,使用最后一个交集元素的迭代器作为结束迭代器。
for_each(v3.begin(),itEnd,Myprint());
}
5.6.2 set_union
功能:求两个容器的并集,两个容器都必须为有序序列。
函数原型:
set_union(v1.begin(),v1.end(),v2.begin(),v2.end(),v3.begin())
//v3为目标容器
//目标容器需要提前开辟空间,特殊情况为二者无交集
v3.resize(v1.size()+v2.size());
//获取交集,并返回并集中最后一个元素的迭代器
vector<int>::iterator itEnd = set_intersection(v1.begin(),v1.end(),v2.begin(),v2.end(),v3.begin());
//遍历,使用最后一个交集元素的迭代器作为结束迭代器。
for_each(v3.begin(),itEnd,Myprint());
5.6.3 set_difference
功能:求两个容器的差集(不是交集的部分),两个容器都必须为有序序列。
函数原型:
- ``set_difference(v1.begin(),v1.end(),v2.begin(),v2.end(),v3.begin())` //v3为目标容器
//目标容器需要提前开辟空间,特殊情况为二者无交集,差集为大的size()
v3.resize(max(v1.size(),v2.size());
//获取交集,并返回并集中最后一个元素的迭代器
//v1和v2的差集为:
vector<int>::iterator itEnd = set_intersection(v1.begin(),v1.end(),v2.begin(),v2.end(),v3.begin());
//遍历,使用最后一个交集元素的迭代器作为结束迭代器。
for_each(v3.begin(),itEnd,Myprint());
//v2和v1的差集为:
vector<int>::iterator itEnd = set_intersection(v2.begin(),v2.end(),v1.begin(),v1.end(),v3.begin());
//遍历,使用最后一个交集元素的迭代器作为结束迭代器。
for_each(v3.begin(),itEnd,Myprint());