C++提高编程 泛型编程和STL技术

本阶段主要针对C++泛型编程和STL技术,探讨C++更深层的使用

这里写目录标题

一 模板

##1.1模板的概念

  • 模板就是建立通用的模具,大大提高复用性。
  • 模板不可以直接使用,它只是一个框架
  • 模板的通用并不是万能的

1.2 函数模板

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

1.2.1 函数模板语法

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

语法
template
函数声明或定义

解释:
template —— 声明创建模板
typename —— 表面其后面的符号是一种数据类型,可以用class代题
T —— 通用的数据类型,名称可以替换,通常都使用 T 而已。

总结:

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

//声明一个模板,告诉编译器后面代码中紧跟着的T不要报错,T是一个数据类型
template
void swap(T &a, T &b)
{
T temp = a;
a = b;
b = temp;
}
//---------------模板的两种使用方法
// 1.自动类型推导
swap(a,b);
// 2.显示指定类型
swap(a,b);

1.2.2 函数模板注意事项

注意事项:

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

//1.自动类型推导,必须推导出一致的数据类型T,才可以使用
template
void mySwap(T &a, T &b)
{
T temp = a;
a = b;
b = temp;
}

//2.模板必须要确定出T的数据类型,才可以使用
template
void func() //形参无类型的时候,就没办法自动类型推导。
{
cout << “func 调用” << endl;
}

int main()
{
return 0;
}

1.2.3 函数模板案例

案例描述:

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

//实现通用 对数组进行排序的函数
//规则 从大到小
//算法 选择
//测试 char 数组,int数组

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

//排序算法
template
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)
{
//交换 max 和 i 元素
mySwap(arr[max],arr[i]);
}
}
}
//提供打印数组模板
template
void printArray(T arr[],int len)
{
for(int i = 0;i < len;i++)
{
cout << arr[i] <<endl;
}
}
void test01()
{
//测试char数组
char charArr[] = “adassar”;
int len = sizeof(charArr) / sizeof(char);
mySort(charArr,len);
printArray(charArr,len)
}
void test01()
{
//测试char数组
int intArr[] = {1,2,0,4,7,4,6,8};
int len = sizeof(intArr) / sizeof(int);
mySort(intArr,len);
printArray(intArr,len)
}
//main 函数省略

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

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

所以建议还是选择显示指定类型的方式去使用 函数模板

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

同名函数 调用规则如下:
1.如果函数模板和普通函数都可以实现,优先调用普通函数
2.可以通过空模板参数列表来强制调用函数模板
3.函数模板也可以发生重载
4.如果函数模板可以产生更好的匹配,优先调用函数模板 //类型转换 比 类型推导麻烦

所以既然提供了函数模板,最好就不要提供普通函数,否则容易出现二义性。
//空模板参数列表,强制调用函数模板
template
void myPrint(T a,T b)
{
cout << “调用的是模板” << endl;
}
void myPrint(int a,int b)
{
cout << “调用的是模板” << endl;
}
void test01()
{
int a = 0,b = 0;
//myPrint(a,b);//直接调用,则优先普通函数
myPrint<>(a,b);//不写类型,就是空模板参数列表,普通函数无法如此调用
}

1.2.6 模板的局限性

  • 模板的通用性并不是万能的。

总结:

  • 利用具体化的模板,可以解决自定义类型的通用化。
  • 学习模板并不是为了写模板,而是在STL能够运用系统提供的模板。
    //利用具体化 某类型的 版本 实现代码,具体化优先调用
    template //函数模板 原始状态
    void myprint(T &a, T &b){}

template<> //函数模板 具体化重载 优先调用Person类型(Person是类)
void myprint(Person &a, Person &b){}

1.3 函数模板

1.3.1 类模板语法

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

语法:template

1.3.2 类模板与函数模板区别

主要有两点:
1.类模板没有自动类型推导的使用方式,只能显示类型。
2.类模板在模板参数列表中可以有默认参数。

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

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

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

1.3.4 类模板对象做函数参数 (运行时类型信息)

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

(运行时类型信息,可以查看数据类型 但是面对 & 引用 不是很完整能输出,可以用boost库解决)

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

总结:

  • 最常用是第一种方式
    #include
    #include
    using namespace std;

//类模板对象做函数参数
template <typename T1,typename T2>
class Person
{
Person(T1 name,T2 name)
{
this->m_name = name;
this->m_age = m_age;
}
void showPerson()
{
cout << this->m_name << this->m_gae << endl;
}
T1 m_name;
T2 m_age;
};
//1.指定传入的类型
void printPerson1(Person<string,int> &p)
{
p.showPerson();
}
void test01()
{
Person<string,int> p1(“孙悟空”,100);
printPerson1(p1);
}
//2.参数模板化
template <typename T1,typename T2>
void printPerson2(Person<T1,T2> &p)
{
p.showPerson();
//如何看类型
cout << “T1的类型为:” << typeid(T1).name() << endl;
cout << “T2的类型为:” << typeid(T2).name() << endl;
}
void test02()
{
Person<string,int> p2(“猪八戒”,90);
printPerson2(p2);
}
//3.整个类模板化
template
void printPerson3(T &p)
{
p.showPerson();
cout << “T的类型为:” << typeid(T).name() << endl;
//输出 class Person
}
void test03()
{
Person<string,int> p3(“唐僧”,30);
printPerson3(p3);
}

1.3.5 类模板与继承

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

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

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

template
class Base
{
public:
T m;
};
/*
//1.此时会报错,未指定父类中T的类型
class Son:public Base
{

};
*/
class Son:public Base//解决1的问题,指明类型
{};
//2.如果想灵活指定父类中T类型,子类也许变为类模板
template<class T1,class T2>
class Son2:public Base
{
T1 m;
};
void test02()
{
Son2<int,char> S2;//int 给 T1,char 给 T2,而子类T2传给父类 T
}

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

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

总结:
类模板中成员函数类外实现时,需要加上模板参数列表
#include
#include
using namespace std;

//类模板成员函数类外实现
template <typename T1,typename T2>
class Person
{
public:
T1 name;
T2 age;
Person(T1 name,T2 age);
void showPerson();

};
//类模板 的 构造函数类外实现
//告诉作用域 Person, 说明类型 <T1,T2>
template <typename T1,typename T2>
Person<T1,T2>::Person(T1 name,T2 age)
{
this->m_name = name;
this->m_age = age;
}
// 普通成员函数 与 构造函数 语法一致 即使没使用也要加类型
template <typename T1,typename T2>
void Person<T1,T2>::showPerson()
{
cout << this->m_name << this->m_age << endl;
}
//测试函数
void test01()
{
Person<string,int> P(“Tom”,20);
P.showPerson();
}

1.3.7 类模板分文件编写

学习目标:

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

问题:

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

解决:

  • 解决方式1:直接包含.cpp源文件
  • 解决方式2:将声明和实现写到同一个文件中,并更改后缀名为.hpp,hpp是约定的名称,并不是强制。
    (看到.hpp 一般就知道是类模板了)

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

1.3.8 类模板与友元

学习目标:

  • 掌握类模板配合 友元函数 的 类内 和 类外 实现

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

总结:
建议全局函数做类内实现,用法简单,而且编译器可以直接识别
#include
using namespace std;
/* //这个注释在后面会解释
template<class T1,class T2>
class Person
*/ //目的:提前让编译器知道

//2.类外实现 全局函数(不用加作用域,但是要加T1,T2)
template <class T1,class T2>
void printPerson(Person<T1,T2> p)
{
cout << p.m_name << p.age << endl;
}

// 通过全局函数 打印Person信息
template<class T1,class T2>
class Person
{
//全局函数 类内实现
friend void printPerson(Person<T1,T2> p)
{
cout << p.m_name << p.age << endl;
}
friend void printPerson2(Person<T1,T2> p);
//按如下增加空参数列表修改后,才可以正常运行
//friend void printPerson2<>(Person<T1,T2> p);
public:
Person(T1 name,T2 age)
{
this->m_name = name;//姓名
this->m_age = age;//年龄
}
};
//1.全局函数在类内实现 测试
void test01()
{
Person<string,int> p(“Tom”,20);
printPerson§;
}
/*
//2.类外实现 全局函数(不用加作用域,但是要加T1,T2)
template <class T1,class T2>
void printPerson(Person<T1,T2> p)
{
cout << p.m_name << p.age << endl;
}
*/
void test02()
{
Person<string,int> p(“Jerry”,10);
printPerson2§;
}
//此时在链接时出错, 无法执行该代码 如何解决呢?
//上方友元函数声明下方已经注释过了, 就是增加一个空模板参数列表
//还是报错 因为:
//如果全局函数 类外实现,需要让编译器提前知道这个函数的存在
//而存在于类中, 所以只需要预先声明类模板 就行了
//类外实现这个真的有点复杂,做的事情很多了

1.3.9 类模板案例

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

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

需求分析图:(双击可放大)

myArray.hpp (因为是类模板,成员函数和声明放一个文件比较好)
//自己的通用的数组类
#pragma once
#include
using namespace std;

template
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= 防止浅拷贝问题
MyArray& operator=(const MyArray& arr)
{
	cout << "MyArray运算符重载operator=调用" << endl;
	//先判断原来堆区是否有数据,如果有,先释放
	if (this->pAddress != nullptr)
	{
		delete[] this->pAddress;
		this->pAddress = nullptr;
		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--;
}
//通过下标方式访问数组中的元素 arr[0]
T& operator[](int index)
{
	return this->pAddress[index];
}
//返回数组容量
int getCapacity()
{
	return this->m_Capacity;
}
//返回数组大小
int getSize()
{
	return this->m_Size;
}
~MyArray()//析构函数
{
	cout << "~MyArray析构函数调用" << endl;
	if (this->pAddress != nullptr)
	{
		delete [] this->pAddress;
		this->pAddress = nullptr;//指针置空,防止野指针
	}
}

private:
T* pAddress; //指针指向堆区开辟的真实的数组
int m_Capacity; //数组容量
int m_Size; //数组大小
};

test.cpp (已经测试 完美)
#include
#include
#include “MyArray.hpp”
using namespace std;
void printIntArray(MyArray & arr)
{
for (int i = 0; i < arr.getSize(); i++)
{
cout << arr[i] << endl;
}
}
void test01()
{
MyArray arr1(5);

for (int i = 0; i < 5; i++)
{
	//利用尾插法向数组中插入数据
	arr1.Push_Back(i);
}
cout << "输出以下数组中元素" << endl;
printIntArray(arr1);
cout << "输出数组的容量:" << arr1.getCapacity() << endl;
cout << "输出数组的大小:" << arr1.getSize() << endl;
MyArray <int>arr2(arr1);
printIntArray(arr2);
//尾删
arr2.Pop_Back();
cout << "arr2 尾删后 输出" << endl;
printIntArray(arr2);
cout << "输出数组的容量:" << arr2.getCapacity() << endl;
cout << "输出数组的大小:" << arr2.getSize() << endl;	

}
//测试自定义数据类型
class Person
{
public:
string m_name;
int m_age;
Person() {}
Person(string name, int age)
{
this->m_name = name;
this->m_age = age;
}
};
void printPerson(MyArray& arr)
{
for (int i = 0; i < arr.getSize(); i++)
{
cout << arr[i].m_name << " " << arr[i].m_age << endl;
}
}
void test02()
{
MyArray arr(10);
Person p1(“孙悟空”,999);
Person p2(“孙”, 99);
Person p3(“孙悟”, 9);
Person p4(“孙空”, 0);
Person p5(“悟空”, 98);
//将数据插入到数组中
arr.Push_Back(p1);
arr.Push_Back(p2);
arr.Push_Back(p3);
arr.Push_Back(p4);
arr.Push_Back(p5);
//打印
printPerson(arr);
//输出容量
cout << “输出数组的容量:” << arr.getCapacity() << endl;
//输出大小
cout << “输出数组的大小:” << arr.getSize() << endl;
}
int main()
{
//test01();
test02();
return 0;
}

二 STL

2.1 STL的诞生

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

2.2 STL基本概念

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

2.3 STL六大组件

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

①.容器: 各种数据结构,如vecotr,list,deque,set,map等,用来存放数据。
②.算法: 各种常用的算法,如sort,find,copy,for_each等
③.迭代器: 扮演了容器与算法之间的胶合剂。
④.仿函数: 行为类似函数,可作为算法的某种策略。
⑤.适配器: 一种用来修饰容器或者仿函数或迭代器接口的东西。
⑥.配置器: 负责空间的配置与管理。

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

容器:置物之所也
STL容器就是将运用最广泛的一些数据结构实现出来
常用的数据结构:数组,链表,树,栈,队列,集合,映射表 等。
这些容器分为序列式容器和关联式容器两种:
序列式容器:强调值得排序,序列式容器中的每个元素均有固定的位置。
关联式容器:二叉树结构,各元素之间没有严格的物理上的顺序关系。

算法:问题之解法也
有限的步骤,解决逻辑或数学上的问题,这一门学科我们叫做算法(Algorithms)
算法分为:质变算法和非质变算法。
质变算法:是指运算过程中会更改区间内的元素的内容。例如拷贝,替换,删除等等
非质变算法:是指运算过程中不会更改区间内的元素内容,例如查找,计数,遍历,寻找极值等等。

迭代器:容器和算法之间粘合剂
提供一种方法,使之能够依序寻访某个容器所含的各个元素,而又无需暴露该容器的内部表示方式。
每个容器都有自己专属的迭代器。
迭代器使用非常类似于指针,初学阶段我们可以先理解迭代器为指针。

2.5 容器算法迭代器初始

STL 中最常用的容器为vector,可以理解为数组,下面举例插入,遍历整个容器

2.5.1 vector存放内置数据类型

容器: vector 算法: for_each 迭代器 vector::iterator

#include
#include
#include
using namespace std;

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

void test01()
{
vector v1; //创建一个vector容器,数组
v1.push_back(10); //向容器中插入数据
v1.push_back(20);
v1.push_back(30);
v1.push_back(40);

//通过迭代器访问容器中的数据
//起使迭代器,指向容器的第一个元素
vector<int>::iterator itBegin = v1.begin();
//结束迭代器,指向容器中最后一个元素的下一个位置
vector<int>::iterator itEnd = v1.end();	   
//通常我们会用 auto省略长的类型声明

//第一种遍历方式 逐个遍历 
while (itBegin != itEnd)
{
	cout << *itBegin << endl;
	itBegin++;
}

//第二种遍历方式  这里用auto 省略了大部分声明代码
for (auto it = v1.begin(); it != v1.end(); ++it)
{
	cout << *it << endl;
}

//第三种方式 利用STL提供的遍历算法 但要包含头文件
for_each(v1.begin(), v1.end(), myPrint);
//三个参数 第三个是 可调用对象 这里写的是函数地址

}

int main()
{
return 0;
}

2.5.2 Vector存放自定义数据类型 (自己写的类)

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

示例 1.自定义数据 2.自定义数据指针
#include
#include
#include
using namespace std;

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 v1;
Person p1(“aaa”, 10);
Person p2(“bbb”, 20);
Person p3(“ccc”, 30);
Person p4(“ddd”, 40);
Person p5(“eee”, 50);
v1.push_back(p1);
v1.push_back(p2);
v1.push_back(p3);
v1.push_back(p4);
v1.push_back(p5);
for (auto it = v1.begin(); it != v1.end(); ++it)
{
cout << (*it).m_name << (it).m_age << endl;
}
}
//存放自定义数据类型 指针
void test02()
{
vector<Person
> v1;
Person p1(“aaa”, 10);
Person p2(“bbb”, 20);
Person p3(“ccc”, 30);
Person p4(“ddd”, 40);
Person p5(“eee”, 50);
v1.push_back(&p1);
v1.push_back(&p2);
v1.push_back(&p3);
v1.push_back(&p4);
v1.push_back(&p5);
//遍历容器
for (auto it = v1.begin(); it != v1.end(); ++it)
{
cout << (*it)->m_age << (*it)->m_name << endl;
}
}
int main()
{
//test01();
test02();
return 0;
}

2.5.3 Vector容器嵌套容器

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

总结:
主要是理清楚逻辑,知道 *it 是<>类型声明括号中的内容,
#include
#include
using namespace std;

//容器嵌套容器
void test01()
{
vector< vector> v;

//创建小容器
vector<int> v1;
vector<int> v2;
vector<int> v3;
vector<int> v4;
vector<int> v5;
//向小容器中添加数据
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);
	v5.push_back(i + 5);
}

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

//通过大容器把所有数据遍历一遍
for (vector<vector<int>>::iterator it = v.begin(); it != v.end(); ++it)
{
	// (*it)是个容器,只能再次遍历
	for (auto it1 = (*it).begin(); it1 != (*it).end(); it1++)
	{
		cout << (*it1) << " ";
	}
	cout << endl;
}

}

int main()
{
test01();

return 0;

}

三 string 类

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(); //创建一个空的字符串,例如 string str;
string(const char* s); //使用字符串S初始化
string(const string& str); //使用一个string对象初始化另一个string对象
string(int n,char c); //使用n个字符C初始化

示例:
#include
#include
using namespace std;
/*
string(); //创建一个空的字符串,例如 string str;
string(const char* s); //使用字符串S初始化
string(const string& str); //使用一个string对象初始化另一个string对象
string(int n,char c); //使用n个字符C初始化
*/
void tese01()
{
string s1;//默认构造

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

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

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

}
int main()
{
return 0;
}

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= 这种方式是比较使用的.
#include
#include
using namespace std;

/*
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赋给当前字符串
*/

void test01()
{
string str1;
str1 = “hello world”;
cout << str1 << endl;

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

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

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

string str5;
str5.assign("hello C++", 5);//字符串前五个赋值
cout << str5 << endl;

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

string str7;
str7.assign(10, 'W');
cout << str7 << endl;

}

int main()
{
test01();
return 0;
}

3.1.4 string字符串拼接

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

函数原型:

总结:
字符串拼接的重载保本很多,记住几种即可。(直接+最舒服)
示例:都实现一遍:
#include
#include
using namespace std;

void test01()
{
string str1 = “hello”;
str1 += “world”;
str1 += “;”;
cout << str1 << endl;

string str2 = "LOL DNF";
str1 += str2;
cout << str1 << endl;

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

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

str3.append(str2);
//I LOVE LOL DNF
cout << str3 << endl;

str3.append(str2, 0, 3);
//只截取到LOL 参数2,从哪个位置开始截取
//参数3 截取的个数
cout << str3 << endl;

}

int main()
{
test01();
return 0;
}

3.1.5 string查找和替换

功能描述:

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

函数原型:

总结:
find查找是从左往右,rfind从右往左
find找到字符串后返回查找的第一个字符位置,找不到返回-1
replace在替换时,要指定从哪个位置起,多少个字符,替换成什么样的字符串
#include
#include
using namespace std;

//1.查找
void test01()
{
string str1 = “abcdefg”;

//返回值为数组下标, 没找到返回-1
int pos = str1.find("de");
if (pos == -1)
{
	cout << "未找到字符串" << endl;
}
else
{
	//输出3 从第0个位置, 第三个位置出现de
	cout << pos << endl;
}
//find 和rfiend 区别:
//find 从左往右  rfind 从右往左
pos = str1.rfind("de"); 
//输出3

}

//2.替换
void test02()
{
string str1 = “abcdefg”;
str1.replace(1, 3, “1111”);
//1参数:起点位置 2参数:往后替换3个字符替换为 3参数:替换的字符
cout << str1 << endl;
}

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

3.1.6 string字符串比较

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

比较方式:字符串比较是按 字符串的ASCII码进行对比
= 返回0 > 返回1 < 返回-1

函数原型:

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

void test01()
{
string str1 = “hello”;
string str2 = “hello”;

if (str1.compare(str2) == 0)
{
	cout << "str1 等于 str2" << endl;
}
else if (str1.compare(str2) > 0)
{
	cout << "str1 大于 str2" << endl;
}
else if (str1.compare(str2) < 0)
{
	cout << "str1 小于 str2" << endl;
}

}

int main()
{
test01();
return 0;
}

3.1.7 string字符存取

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

函数原型:

总结:
string字符串单个字符存取有两种方法, [] 或者 at
#include
#include
using namespace std;

void test01()
{
string str = “hello”;
//1.通过[]访问单个字符
for (int i = 0; i < str.size(); i++)
{
cout << str[i] << " ";
}
cout << endl;
//2.通过at方式访问单个字符
for (int i = 0; i < str.size(); i++)
{
cout << str.at(i) << " ";
}
cout << endl;

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

str.at(1) = 'x';
cout << str << endl;

}

int main()
{
test01();
return 0;
}

3.1.8 string插入和删除

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

函数原型:

总结:
插入和删除的起始下标都是从0开始。
#include
#include
using namespace std;

void test01()
{
string str1 = “hello”;
//插入
str1.insert(1, “111”);
cout << str1 << endl;
//h111ello

//删除
//参数1 位置  参数2 删除几个
str1.erase(1, 3);
cout << str1 << endl;

}

int main()
{
test01();
}

3.1.9 string子串

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

函数原型:

参数1:第几个位置, 参数2:到第几个位置为止。

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

//string 求子串
void test01()
{
string str = “abcdef”;
string subStr = str.substr(1, 3);
cout << subStr << endl;

}

//使用操作
void test02()
{
string email = “[email protected]”;
//从邮件地址中 获取 用户名信息

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

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

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

四 vector 容器

4.1.1 vector基本概念

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

vector与普通数组区别:

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

动态扩展:

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

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

4.1.2 vector构造函数

示例:
#include
#include
using namespace std;

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

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

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

//n个elem方式构造
//参数1 个数  参数2 内容
vector<int> v3(10, 100);
printVector(v3);

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

}

int main()
{
test01();
return 0;
}

4.1.2 vector赋值操作

功能:给vector容器进行赋值

#include
#include
using namespace std;

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

void test01()
{
vector v1;
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);

}

int main()
{
test01();
return 0;
}

4.1.3 vector容量和大小

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

函数原型

总结:

  • 判断是否为空 --empty
  • 返回元素个数 --size
  • 返回容器容量 --capacity
  • 重新指定大小 --resize
    #include
    #include
    using namespace std;

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

void test01()
{
vector v1;
for (int i = 0; i < 10; i++)
{
v1.push_back(i);
}
printVector(v1);
if (v1.empty())//为真,容器为空
{
cout << “v1为空” << endl;
}
else
{
cout << “v1不为空” << endl;
cout << “v1的容量为:” << v1.capacity() << endl;
cout << "v1.大小为: " << v1.size() << endl;
}

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

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

}

int main()
{
test01();
return 0;
}

4.1.4 vector插入和删除

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

函数原型:

总结:
尾插 ——push_back
尾删 ——pop_back
插入 ——insert (位置迭代器)
删除 ——erase (位置迭代器)
清空 ——clear
#include
#include
using namespace std;

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

void test01()
{
vector v1;
//尾插法
v1.push_back(10);
v1.push_back(20);
v1.push_back(30);
//遍历
printVector(v1);
//尾删 一次删除一个
v1.pop_back();
printVector(v1);

//插入 前插  参数1:迭代器 参数2 内容
v1.insert(v1.begin(), 100);
printVector(v1);
//重载版本   迭代器  2 个 1000
v1.insert(v1.begin(), 2, 1000);
printVector(v1);

//删除 参数 迭代器
v1.erase(v1.begin());
printVector(v1);
//重载 提供区间
//以下两种都清空了 输出无
//v1.erase(v1.begin(), v1.end());
v1.clear();
printVector(v1);

}

int main()
{
test01();
return 0;
}

4.1.5 vector数据存取

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

函数原型:

总结:
除了用迭代器获取vector容器中的元素, [ ] 和 at 也可以
front 返回容器第一个元素
back 发挥容器最后一个元素
#include
#include
using namespace std;

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

//vector容器 数据存取
void test01()
{
vector v1;
for (int i = 0; i < 10; i++)
{
v1.push_back(i);
}
//利用 [] 方式访问元素
for (int i = 0; i < v1.size(); i++)
{
cout << v1[i] << " ";
}
cout << endl;
//利用 at 方式访问元素
for (int i = 0; i < v1.size(); i++)
{
cout << v1.at(i) << " ";
}
cout << endl;

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

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

}

int main()
{
test01();
return 0;
}

4.1.6 vector互换容器

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

函数原型:

  • swap(vec) :将vec与本身的元素互换

总结:
swap可以使两个容器互换,可以达到使用的收缩内存效果
#include
#include
using namespace std;

void printVector(vector& v)
{
for (auto it = v.begin(); it != v.end(); ++it)
{
cout << *it << " ";
}
cout << endl;
}
//vector容器互换
//1.基本使用
void test01()
{
vector v1;
for (int i = 0; i < 10; i++)
{
v1.push_back(i);
}
printVector(v1);

vector<int> v2;
for (int i = 10; i > 0; i--)
{
	v2.push_back(i);
}
printVector(v2);
cout << "交换前的打印 如上" << endl;
cout << "-----------------------" << endl;
cout << "交换后的打印 如下" << endl;
v1.swap(v2);
printVector(v1);
printVector(v2);

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

//重新指定大小
v2.resize(3); 
//容量不变 大小改变
cout << "v2的容量:" << v2.capacity() << endl;
cout << "v2的大小:" << v2.size() << endl;

//巧用swap收缩内存
//使用了匿名对象 容器交换 执行完后匿名对象内存被系统回收
vector<int>(v2).swap(v2);
cout << "--------------------" << endl;
cout << "v2的容量:" << v2.capacity() << endl;
cout << "v2的大小:" << v2.size() << endl;
//v2.resize(3); vector<int>(v2).swap(v2); 
//两步 解决了  重置后 大小和容量相等

}

int main()
{
test02();
return 0;
}

4.1.7 vector预留空间

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

函数原型:

只是分配内存,但是未初始化

总结:
如果数据量较大,可以一开始利用reserve预留空间
#include
#include
using namespace std;

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

//vector容器 预留空间
void test01()
{
vector v1;
//利用 reserve 预留空间
v1.reserve(100000);

int num = 0; //统计开辟次数
int* p = nullptr;
for (int i = 0; i < 100000; i++)
{
	v1.push_back(i);
	//每指向一次首地址,就是开辟一次内存
	if (p != &v1[0])
	{
		p = &v1[0];
		num++;
	}
}
cout << "开辟空间" << num << "次" << endl;

}

int main()
{
test01();
return 0;
}

五 deque 容器

5.1.1 deque容器基本概念

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

deque与vector 区别:

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

deque内部工作原理:

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

5.1.2 deque构造函数

功能描述:deque容器构造

函数原型:

基本的构造函数 同 vector,没有什么区别
#include
#include
using namespace std;

void printDeque(const deque& d)
{
for (auto it = d.begin(); it != d.end(); ++it)
{
//容器中的数据不可修改
cout << *it << " ";
}
cout << endl;
}

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

int main()
{
test01();
return 0;
}

5.1.3 deque赋值操作

  • 给deque容器进行赋值

函数原型:

总结 : 仍然是与vector类似。完全一样,掌握一个就行

5.1.4 deque大小操作

  • 对deque容器的大小进行操作

函数原型

总结:
无容量的概念,可以无限去扩充。只有元素个数。

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

5.1.5 deque插入和删除

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

总结:

  • 插入和删除 提供的位置 是迭代器
  • 尾插 ——push_back
  • 尾删 ——pop_back
  • 头插 ——push_front
  • 头删 ——pop_front
    #include
    #include
    using namespace std;

void printDeque(const deque& d)
{
for (auto it = d.begin(); it != d.end(); ++it)
{
//容器中的数据不可修改
cout << *it << " ";
}
cout << endl;
}
//两端操作
void test01()
{
deque d1;
//尾插
d1.push_back(10);
d1.push_back(20);

//头插
d1.push_front(30);
d1.push_front(40);

//遍历
printDeque(d1);

//尾删
d1.pop_back();
printDeque(d1);

//头删
d1.pop_front();
printDeque(d1);

}
int main()
{
test01();
return 0;
}

5.1.6 deque数据存取

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

函数原型:

总结:与vector容器相同;

5.1.7 deque排序

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

算法 :

总结:
algorithm 头文件不要忘。
#include
#include
#include //标准算法头文件
using namespace std;

void printDeque(const deque& d)
{
for (auto it = d.begin(); it != d.end(); ++it)
{
//容器中的数据不可修改
cout << *it << " ";
}
cout << endl;
}
//deque 容器数据存取
void test01()
{
deque 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);
printDeque(d);
// 300 200 100 10 20 30
cout << “----------------” << endl;
//排序 默认排序规则 从小到大 升序
//对于支持随机访问的迭代器的容器,都可以利用sort算法直接对其进行排序。
//vector容器也可以利用sort进行排序
sort(d.begin(), d.end());
cout << “排序后:” << endl;
printDeque(d);
}

int main()
{
test01();
return 0;
}

六 stack 容器

6.1 基本概念

stack 是一种先进后出 的数据结构,它只有一个出口

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

栈 符合先进后出
栈 不允许有遍历行为;
栈 可以判断容器是否为空;
栈 可以返回元素个数;

栈中进入数据称为——入栈 push
栈中弹出数据称为——出栈 pop

6.2 stack 常用接口

栈容器常用的对外接口。

#include
#include
using namespace std;

//栈stack容器
void test01()
{
//特点:符合先进后出数据结构
stack s;
//入栈
s.push(10);
s.push(20);
s.push(30);
s.push(40);
// 40 30 20 10 输出顺序
cout << “栈的大小:” << s.size() << endl;
//只要栈不为空,查看栈顶,并且执行栈操作
while (!s.empty())
{
//查看栈顶元素
cout << “栈顶元素为:” << s.top() << endl;

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

}

int main()
{
return 0;
}

七 queue 容器

7.1 queue 基本概念

概念:queue 是一种先进先出的数据结构,它有两个出口。

7.2 queue 常用接口

#include
using namespace std;
#include

class Person
{
public:
Person(string name, int age):m_name(name),m_age(age){}
string m_name;
int m_age;
};

void test01()
{
//创建队列
queue q;
//准备数据
Person p1(“孙悟空”, 30);
Person p2(“唐僧”, 20);
Person p3(“猪八戒”, 90);
Person p4(“沙僧”, 80);
//入队
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 << “队列大小” << q.size() << endl;
}

int main()
{
test01();
return 0;
}

八 list 容器

8.1 list 基本概念

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

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

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

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

8.2 list 构造函数

功能描述:创建list容器

总结:
list构造方式同其他几个STL常用容器,熟练掌握即可。
#include
using namespace std;
#include

//list 容器构造函数
void printList(const list& L)
{
for (auto it = L.begin(); it != L.end(); ++it)
{
cout << *it << " ";
}
cout << endl;
}

void test01()
{
//创建list 容器
list L1; //默认构造

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

//遍历容器
printList(L1);

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

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

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

}

int main()
{
test01();
return 0;
}

8.3 list 赋值和交换

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

总结: 结果就不放了,确实发生了交换, swap 函数 功能很好。
#include
using namespace std;
#include

void printList(const list& L)
{
for (auto it = L.begin(); it != L.end(); ++it)
{
cout << *it << " ";
}
cout << endl;
}
//list 容器赋值和交换
void test01()
{
list L1;
L1.push_back(10);
L1.push_back(20);
L1.push_back(30);
L1.push_back(40);
//遍历
printList(L1);

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

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

list<int> L4;
L4.assign(10, 100);
printList(L4);

}
//交换
void test02()
{
list L1;
L1.push_back(10);
L1.push_back(20);
L1.push_back(30);
L1.push_back(40);

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

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

}

int main()
{
//test01();
test01();
return 0;
}

8.4 list 大小操作

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

这些函数其实与 其他容器没太大区别, 函数的使用也一致。

总结: 判断是否为空 ——empty(); 返回元素个数 —size() 重新指定个数 ——resize()
#include
using namespace std;
#include

//防止误操作修改内容才加const
void printList(const list& L)
{
for (auto it = L.begin(); it != L.end(); ++it)
{
cout << *it << " ";
}
cout << endl;
}
//list 容器大小操作
void test01()
{
list L1;
L1.push_back(10);
L1.push_back(20);
L1.push_back(30);
L1.push_back(40);
//遍历
printList(L1);

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

//重新指定大小
L1.resize(10, 1000);
printList(L1);

L1.resize(2);
printList(L1);

}

int main()
{
test01();

return 0;

}

8.5 list 插入和删除

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

总结:

#include
using namespace std;
#include

//防止误操作修改内容才加const
void printList(const list& L)
{
for (auto it = L.begin(); it != L.end(); ++it)
{
cout << *it << " ";
}
cout << endl;
}
//list 容器插入和删除
void test01()
{
list L1;
//尾插
L1.push_back(10);
L1.push_back(20);

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

//遍历一下,检验 应该是200 100 10 20
printList(L1);

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

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

//insert 插入 第一个参数放迭代器 可以移动
//auto it = L1.begin();
//L1.insert(++it,10000);
L1.insert(L1.begin(),10000);
printList(L1);

//删除 10000 10
auto it = L1.begin();
L1.erase(++it);
printList(L1);

//移除 匹配的所有都删除
L1.push_back(100);
L1.push_back(100);
printList(L1); //10000 10 100 100
L1.remove(100);
printList(L1);//10000 10 

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

}

int main()
{
test01();
return 0;
}

8.6 list 数据存取

功能描述:对 list 容器中数据进行存取
(迭代器不能支持随机访问)

#include
using namespace std;
#include

//防止误操作修改内容才加const
void printList(const list& L)
{
for (auto it = L.begin(); it != L.end(); ++it)
{
cout << *it << " ";
}
cout << endl;
}
//list 容器数据存取
void test01()
{
list L1;
L1.push_back(10);
L1.push_back(20);

//L1[0],at 都不能使用
//原因是list本质是链表,不是用连续线性空间存储数据
//所以迭代器是不支持随机访问的。所以连续才支持随机访问

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

//验证迭代器是不支持随机访问的
list<int>::iterator it = L1.begin();
//it 迭代器此时只能 ++ -- 不能跳跃式的如 +3 -2;
it++;
it--;
//it = it + 1; //不支持随机访问,这代码也可以验证其他容器
cout << *it << endl;

}

int main()
{
test01();

return 0;

}

8.7 list 反转和排序

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

总结:降序和升序 可以利用一个可调用对象来更改 (举例用的函数)

#include
using namespace std;
#include

//防止误操作修改内容才加const
void printList(const list& L)
{
for (auto it = L.begin(); it != L.end(); ++it)
{
cout << *it << " ";
}
cout << endl;
}

bool myCompare(int v1, int v2)
{
return v1 > v2;
}
//list 容器反转和排序
void test01()
{
list L1;
L1.push_back(10);
L1.push_back(50);
L1.push_back(40);
L1.push_back(30);
printList(L1);// 10 50 40 30

//反转
cout << "-------反转后,排序前:-------" << endl;
L1.reverse();
printList(L1);

//所有不支持随机访问迭代器的容器,不可以使用标准算法
//排序 默认升序 从小到大
//sort(L1.begin(),L1.end()); 
cout << "-------排序后-------" << endl;
L1.sort();
printList(L1);

//那如何做 降序 排序呢
L1.sort(myCompare);
printList(L1);

}

int main()
{
test01();
return 0;
}

8.7 list 排序案例
因为生活中使用排序很多,所以有必要举例说明,也讲一下自定义类型

代码如下,这里没有重载运算符,如果想简写代码,还是用记起如何重载运算符。
#include
#include
using namespace std;

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

};
//排序1.0
bool comparePerson1(Person& p1, Person& p2)
{
//按照年龄 升序
return p1.m_age < p2.m_age;
}
//排序2.0
bool comparePerson2(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 l1;//创建容器

//准备数据
Person p1("1", 35, 190);
Person p2("2", 99, 153);
Person p3("3", 22, 123);
Person p4("4", 33, 142);
Person p5("5", 33, 190);
Person p6("6", 33, 178);

//插入数据
l1.push_back(p1);
l1.push_back(p2);
l1.push_back(p3);
l1.push_back(p4);
l1.push_back(p5);
l1.push_back(p6);
for (auto it = l1.begin(); it != l1.end(); ++it)
{
	cout << (*it).m_name << " " << (*it).m_age << " " << (*it).m_height << endl;
}

//排序
cout << "------------- 排序后 ---------------" << endl;
//对于list容器,操作一个自定义容器,排序要指定规则所以要写 可调用对象
l1.sort(comparePerson2);
for (auto it = l1.begin(); it != l1.end(); ++it)
{
	cout << (*it).m_name << " " << (*it).m_age << " " << (*it).m_height << endl;
}

}

int main(void)
{
test01();
return 0;
}

九 set / multiset 容器 (集合容器)

9.1 set基本概念

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

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

set和multiset区别:

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

9.2 set 构造和赋值
功能:创建 set 容器以及赋值

构造:

赋值

总结:

  • set 容器插入数据时用 insert
  • set 容器插入数据的数据会自动排序
    #include
    #include
    using namespace std;

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

//set容器构造和赋值
void test01()
{
set s1;

//插入数据 只有insert方式
s1.insert(10);
s1.insert(20);
s1.insert(30);
s1.insert(40);
//插入重复的会失败 插了白插
s1.insert(40);
s1.insert(40);
//遍历容器
//set容器特点:所有元素插入时候自动被排序
//set容器不允许插入重复值
printSet(s1);

//拷贝构造
set<int> s2(s1);
printSet(s2);

//赋值
set<int> s3;
s3 = s2;
printSet(s3);

}

int main()
{
test01();
return 0;
}

9.3 set 大小和交换

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

总结:看到的属性就说明只有这几个, 不支持 resize

#include
#include
using namespace std;

void printSet(set& s)
{
for (auto it = s.begin(); it != s.end(); ++it)
{
cout << *it << " ";
}
cout << endl;
}
//set容器 大小和交换
void test01()
{
set s1;
//插入数据 只有insert方式
s1.insert(40);
s1.insert(30);
s1.insert(20);
s1.insert(10);
printSet(s1);//检验是否自动排序

//判断是否为空
if (s1.empty())
{
	cout << "s1为空" << endl;
}
else
{
	cout << "s1不为空" << endl;
	cout << "元素个数为:" << s1.size() << endl;
}
set<int> s2;
s2.insert(400);
s2.insert(300);
s2.insert(200);
s2.insert(100);
cout << "------交换前---------" << endl;
printSet(s1);
printSet(s2);
cout << "------交换前---------" << endl;
s1.swap(s2);
printSet(s1);
printSet(s2);

}

int main()
{
test01();
return 0;
}

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

这里说明一下,删除有一个重载版本就是最后这个函数,类似 list 容器 中的 remove 函数
功能 实现指定数据删除。

总结:

#include
#include
using namespace std;

void printSet(set& s)
{
for (auto it = s.begin(); it != s.end(); ++it)
{
cout << *it << " ";
}
cout << endl;
}
//set容器 插入和删除
void test01()
{
set s1;
//插入数据 只有insert方式
s1.insert(40);
s1.insert(30);
s1.insert(20);
s1.insert(10);
printSet(s1);//检验是否自动排序
//插入便排序好
//删除 按排序后删除
s1.erase(s1.begin());
printSet(s1);//检验删除的是按排序的第一个删除

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

//清空
s1.clear();
printSet(s1);

}

int main()
{
test01();
return 0;
}

9.5 set 查找 和 统计

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

总结:

#include
#include
using namespace std;

void printSet(set& s)
{
for (auto it = s.begin(); it != s.end(); ++it)
{
cout << *it << " ";
}
cout << endl;
}
//set容器 查找和统计
void test01()
{
set s1;
//插入数据 只有insert方式
s1.insert(40);
s1.insert(30);
s1.insert(20);
s1.insert(10);
//返回值为迭代器
//成功返回迭代器 失败返回 s.end();
set::iterator it = s1.find(30);
if (it != s1.end())
{
cout << “找到元素:” << *it << endl;
}
else
{
cout << “未找到元素” << endl;
}
//统计30的个数
int num = s1.count(30);
cout << “num=” << num << endl;

//对于set而言,count()统计结果要么是0要么是1
//对于muiltset而言就很有用了

}

int main()
{
test01();
return 0;
}

9.6 set 和 multiset 区别
学习目标:掌握区别

总结:

#include
#include
using namespace std;

void printMultiet(multiset& s)
{
for (auto it = s.begin(); it != s.end(); ++it)
{
cout << *it << " ";
}
cout << endl;
}
//set容器 和 multiset 容器的区别
void test01()
{
set s1;
//插入数据 只有insert方式 但不允许有重复数据
//返回值是 pair,包括两个参数
pair<set::iterator,bool> ret = s1.insert(40);
if (ret.second)
{
cout << “第一次插入成功” << endl;
}
else
{
cout << “第一次插入失败” << endl;
}
ret = s1.insert(40);
if (ret.second)
{
cout << “第一次插入成功” << endl;
}
else
{
cout << “第一次插入失败” << endl;
}

multiset<int> m1;//使用和set是一样的,只是允许重复数据
m1.insert(10);//返回值是迭代器,不进行检测
m1.insert(10);
printMultiet(m1);

}

int main()
{
test01();
return 0;
}

9.8 set 容器 排序

学习目标:set容器默认排序规则 升序,掌握如何改变排序规则

主要技术点:利用仿函数,可以改变排序规则
注意:自定义数据类型的时候,需要自己设定排序规则

示例一 set 存放内置数据类型
示例代码如下; 但是在vs2019下会报错,但是 g++ 能通过

#include
using namespace std;
#include

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

class MyConmpare
{
public:
bool operator()(int a, int b)
{
return a > b;
}
};

//示例一 set 存放内置数据类型
//set容器排序
void test01()
{
set s1;//默认升序
s1.insert(10);
s1.insert(30);
s1.insert(20);
s1.insert(40);
printSet(s1);//遍历检测

//指定排序规则 降序 从大到小
set<int, MyConmpare> s2;
s2.insert(10);
s2.insert(30);
s2.insert(20);
s2.insert(40);
//遍历
for (set<int, MyConmpare>::iterator it = s2.begin(); it != s2.end(); ++it)
{
	cout << *it << " ";
}
cout << endl;

}

int main()
{
test01();
return 0;
}

示例二 set 存放自定义数据类型
示例代码如下; 但是在vs2019下会报错,在linux中 g++能通过

#include
using namespace std;
#include
#include

class Person
{
public:
Person(string name, int age)
{
this->m_name = name;
this->m_age = age;
}
string m_name;
int m_age;
};
class MyCompare
{
public:
bool operator()(const Person& a, const Person& b)
{
//按照年龄 降序
return a.m_age > b.m_age;
}
};
//示例二 set 存放自定义数据类型
void test01()
{
//自定义数据类型 都会指定排序规则
set<Person, MyCompare> s;
//创建Person对象
Person p1(“1”,25);
Person p2(“2”,12);
Person p3(“3”,32);
Person p4(“4”,99);
s.insert(p1);
s.insert(p2);
s.insert(p3);
s.insert(p4);
for (set<Person,MyCompare>::iterator it = s.begin(); it != s.end(); ++it)
{
cout << (*it).m_age << " " << (*it).m_name << endl;
}
}

int main()
{
test01();
return 0;
}

十 pair 对组创建

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

两种创建方式

个人喜欢第二种。
#include
using namespace std;
#include
//pair 对组的创建
void test01()
{
//第一种方式
pair<string,int>p(“tom”, 20);
cout << p.first << " " << p.second << endl;

//第二种方式 喜欢第二种直白的
pair<string, int>p2 = make_pair("jack", 30);
cout << p.first << " " << p.second << endl;

}
int main()
{
test01();
return 0;
}

十一 map / multimap 容器 (高性能 高效率)

11.1 map 基本概念

说对key值,便能快速找到,而且能按照key值自动排序
只区分key值,value不管

11.2 map 构造和赋值

成对出现 需要注意如何插入。

总结:map中所有元素都是成对出现,插入数据时要使用对组。
#include
#include
using namespace std;

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

//map容器 构造和赋值
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>(4, 40));
m.insert(pair<int, int>(3, 30));
printMap(m);

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

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

}

int main()
{
test01();
return 0;
}

11.3 map 大小和交换

之间的容器大量使用这几个函数,就不继续写代码了。

11.4 map 插入和删除

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

#include
#include
using namespace std;

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

//map容器 插入和删除
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>(4, 40));
m.insert(pair<int, int>(3, 30));
printMap(m);

//第二种 make_pair 比较通用了挺好的,只需要记住不同容器参数
m.insert(make_pair(2, 20));

//第三种 太长了,只需要知道 不然别人写起来看不懂也尴尬
m.insert(map<int, int>::value_type(3, 30));

//第四种 不建议使用
//m.[4] = 40; g++能使用,vs2019不能
//作用:可以利用key访问到value
printMap(m);

//删除
m.erase(m.begin());
printMap(m);

m.erase(2);//按照key值删除,value值不起作用
printMap(m);

//按区间删除 相当于清空了
m.erase(m.begin(), m.end());
printMap(m);

//m.clear();//清空函数也是有的

}

int main()
{
test01();
return 0;
}

11.5 map 查找 和 统计

功能描述:对map容器进行查找数据以及统计数据
函数原型是根据key值查找的,但key不允许重复的,插入重复无效对count而言 结果要么0要么1

11.6 map 容器排序

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

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

还是vs2019编译不通过, g++允许此规则通过
规则:利用仿函数修改排序规则。

#include
#include
using namespace std;

class MyCompare
{
public:
bool operator()(int a, int b)
{
//降序
return a > b;
}
};

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

//map容器 排序
void test01()
{
//创建map容器
map<int, int, MyCompare> m;
//乱入乱序 插入后 还是会按照 默认升序排序
m.insert(make_pair<int, int>(1, 10));
m.insert(make_pair<int, int>(2, 20));
m.insert(make_pair<int, int>(4, 40));
m.insert(make_pair<int, int>(3, 30));
printMap(m);

//改变排序规则,通过仿函数

}

int main()
{
test01();
return 0;
}

11.7 map 案例 员工分组(复习map)

案例描述:

实现步骤:

代码验证过,是成功的, 环境VS2019
#include
#include
#include
#include
#include
using namespace std;

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

class Worker
{
public:
string m_name;
int m_salary;
};
void createWorker(vector& 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& v, multimap<int, Worker>& m)
{
for (vector::iterator it = v.begin(); it != v.end(); ++it)
{
//产生随机部门编号
int deptID = rand() % 3;//0 1 2

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

}
//分部门显示
void showWorkerByGourp(multimap<int, Worker>& m)
{
//0 A B C 1 D E 2 F G
cout << “策划部门:” << endl;
multimap<int, Worker>::iterator pos = m.find(CEHUA);
int count = m.count(CEHUA);//统计具体人数
int index = 0;
for (; pos != m.end() && index < count; ++pos,++index)
{
cout << pos->second.m_name << " " << pos->second.m_salary << endl;
}
cout << “-------------------------------------” << endl;
cout << “美术部门:” << endl;
pos = m.find(MEISHU);
count = m.count(MEISHU);
index = 0;
for (; pos != m.end() && index < count; ++pos, ++index)
{
cout << pos->second.m_name << " " << pos->second.m_salary << endl;
}
cout << “-------------------------------------” << endl;
cout << “研发部门:” << endl;
pos = m.find(YANFA);
count = m.count(YANFA);
index = 0;
for (; pos != m.end() && index < count; ++pos, ++index)
{
cout << pos->second.m_name << " " << pos->second.m_salary << endl;
}
}
int main()
{
srand((unsigned int)time(NULL));
//1.创建员工
vector vWorker;
createWorker(vWorker);

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

//3.分组显示员工
showWorkerByGourp(mWorker);

/*1.测试
for (vector<Worker>::iterator it = vWorker.begin(); it != vWorker.end(); ++it)
{
	cout << (*it).m_name << " " << (*it).m_salary << endl;
}*/

return 0;

}

猜你喜欢

转载自blog.csdn.net/GameStrategist/article/details/107995369
今日推荐