C++提高编程

博客 分享
0 343
优雅殿下
优雅殿下 2022-02-18 18:55:31
悬赏:0 积分 收藏

C++ 提高编程

目录
  • C++ 提高编程
    • 一、 模板
      • 1、 概念
      • 2、 函数模板
        • 2.1 函数模板语法
        • 2.2 注意事项
        • 2.3 普通函数和函数模板的区别
        • 2.4 普通函数和函数模板的调用规则
        • 2.5 模板的局限性
      • 3、 类模板
        • 3.1 类模板语法
        • 3.2 类模板和函数模板的区别
        • 3.3 使用时机
        • 3.4 类模板对象函数做参数
        • 3.5 类模板与继承
        • 3.6 类模板成员函数类外实现
        • 3.7 类模板文件编写
        • 3.8 类模板和友元
        • 3.9 数组类封装
    • 二、 STL 初识
      • 1、 基本概念
      • 2、 STL 六大组件
        • 2.1 容器、算法、迭代器
      • 3、 迭代器初始
        • 3.1 vector 存放内置数据类型
        • 3.2 vector 存放自定义数据类型
        • 3.3 vector 中嵌套容器
    • 三、 STL 常用容器
      • 1、 string 容器
        • 1.1 string 基本概念
        • 1.2 string 构造函数
        • 1.3 string 赋值操作
        • 1.4 string 字符串拼接
        • 1.5 string 查找和替换
        • 1.6 string 字符串比较
        • 1.7 string 字符存取
        • 1.8 string 插入和删除
        • 1.9 string 中的子串
      • 2、 vector 容器
        • 2.1 vector 基本概念
        • 2.2 vector 构造函数
        • 2.3 vector 赋值操作
        • 2.4 vector 大小操作
        • 2.5 vector 插入和删除
        • 2.6 vector 数据存取
        • 2.7 vector 互换容器
        • 2.8 vector 预留空间
      • 3、 deque 容器
        • 3.1 deque 基本概念
        • 3.2 deque 构造函数
        • 3.3 deque 赋值操作
        • 3.4 deque 大小操作
        • 3.5 deque 插入和删除
        • 3.6 deque 数据存取
        • 3.7 deque 排序
      • 4、 案例-评委打分
        • 4.1 案例描述
        • 4.2 实现步骤
      • 5、 stack 容器
        • 5.1 stack 基本概念
        • 5.2 stack 常用接口
          • 5.2.1 构造函数
          • 5.2.2 赋值操作
          • 5.2.3 大小操作
          • 5.2.4 数据存取
      • 6、 queue 容器
        • 6.1 queue 基本概念
        • 6.2 queue 常用接口
          • 6.2.1 构造函数
          • 6.2.2 赋值操作
          • 6.2.3 大小操作
          • 6.2.4 数据存取
      • 7、 list 容器
        • 7.1 list 基本概念
        • 7.2 list 构造函数
        • 7.3 list 赋值和交换
        • 7.4 list 大小操作
        • 7.5 list 插入和删除
        • 7.6 list 数据存取
        • 7.7 list 反转和排序
      • 8、 set / multiset 容器
        • 8.1 set 基本概念
        • 8.2 set 构造和赋值
        • 8.3 set 大小和交换
        • 8.4 set 插入合删除
        • 8.5 set 查找和统计
        • 8.6 set 和 multiset 区别
        • 8.7 pair 对组创建
        • 8.8 set 排序
      • 9、 map / multimap 容器
        • 9.1 map 基本概念
        • 9.2 map 构造和赋值
        • 9.3 map 大小和交换
        • 9.4 map 插入和删除
        • 9.5 map 查找和统计
        • 9.6 map 排序
      • 10、 案例-员工分组
        • 10.1 案例描述
        • 10.2 实现步骤
        • 10.3 代码演示
    • 四、 STL 函数对象
      • 1、 函数对象
        • 1.1 基本概念
        • 1.2 使用方法
      • 2、 谓词
        • 2.1 谓词概念
        • 2.2 一元谓词
        • 2.3 二元谓词
      • 3、 内建函数对象
        • 3.1 意义
        • 3.2 算术仿函数
        • 3.3 关系仿函数
        • 3.4 逻辑仿函数
    • 五、 STL 常用算法
      • 1、 常用遍历算法
        • 1.1 for_each
        • 1.2 transform
      • 2、 常用查找算法
        • 2.1 find
        • 2.2 find_if
        • 2.3 adjacent_find
        • 2.4 binary_search
        • 2.5 count
        • 2.6 count_if
      • 3、 常用排序算法
        • 3.1 sort
        • 3.2 random_shuffle
        • 3.3 merge
        • 3.4 reverse
      • 4、 常用拷贝和替换算法
        • 4.1 copy
        • 4.2 replace
        • 4.3 replace_if
        • 4.4 swap
      • 5、 常用算术生成算法
        • 5.1 accumulate
        • 5.2 fill
      • 6、 常用集合算法
        • 6.1 set_insersection
        • 6.2 set_union
        • 6.3 set_difference

C++ 提高编程

主要针对C++泛型编程STL技术

一、 模板

1、 概念

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

模板特点

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

2、 函数模板

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

2.1 函数模板语法

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

语法

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

参数

  • template:声明创建模板
  • typename:表明其后面的符号是一种数据类型,可以用class来代替
  • T:通用的数据类型,名称可以替换,通常为大写字母
// 两个整型交换函数void swap(int& a, int& b){	int temp = a;	a = b; 	b = temp;}// 交换浮点型的函数void swap(double& a, double& b){	double temp = a;	a = b;	b = temp;}// 函数模板template <typename T>  // 声明模板,告诉编译器后面代码紧跟着T,不要报错,T是一个通用的数据类型void m_swap(T& a, T& b){	T temp = a;	a = b;	b = temp;}void test(){	int a = 1;	int b = 3;	double a1 = 4;	double b1 = 5;	/* swap(a, b);	cout << a << b << endl;	swap(a1, b1);	cout << a1 << b1 << endl; */	// 使用函数模板	// 1、 自动推导	m_swap(a, b);	cout << a << b << endl;	// 2、 显示指定类型	m_swap<int>(a, b);	cout << a << b << endl;}

模板可以将数据类型参数化

模板的使用方法

  • 自动推导
  • 显示指定类型

2.2 注意事项

注意事项

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

2.3 普通函数和函数模板的区别

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

2.4 普通函数和函数模板的调用规则

调用规则如下

  1. 如果函数模板和普通函数都可以实现,优先调用普通函数

  2. 可以通过空模板参数列表强制调用函数模板

    void myPrint(int a, int b){	cout << a << b << endl;	cout << "普通函数" << endl;}template<typename T>void myPrint(T a, T b){	cout << a << b << endl;	cout << "模板函数" << endl;}void test(){	int a = 10;	int b = 20;	myPrint<>(a, b);  // 空模板参数列表调用模板函数}
  3. 函数模板也可以发生重载

  4. 如果函数模板可以产生更好的匹配模式,优先调用函数模板

    void myPrint(int a, int b){	cout << a << b << endl;	cout << "普通函数" << endl;}template<typename T>void myPrint(T a, T b){	cout << a << b << endl;	cout << "模板函数" << endl;}void test(){	char a = 'a';	char b = 'b';	myPrint(a, b);  // 函数模板可以产生更好的匹配 }

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

2.5 模板的局限性

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

如果传入的是一个元组以及自定义数据类型,就无法实现了

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

// 模板重载// 对比两个数据是否相等class Person{public:	Person(string name, int age)	{		m_Age = age;		m_Name = name;	}	string m_Name;	int m_Age;};template<class T>bool myCompare(T& a, T& b)  // 如果传入的是一个自定义数据类型呢{	if (a == b)	{		return true;	}	else	{		return false;	}}// 利用具体化Person的版本实现代码,具体化优先调用// 也可以使用运算符重载template<>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 test(){	Person p1("Tom", 10);	Person p2("Tom", 10);	cout << myCompare(p1, p2) << endl;}

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

3、 类模板

3.1 类模板语法

类模板作用

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

语法

template<typename T>类

参数

  • template:声明创建模板
  • typename:表明其后面的符号是一种数据类型,可以用class来代替
  • T:通用的数据类型,名称可以替换,通常为大写字母
template<typename NameT, typename AgeT>class Person{public:	Person(NameT name, AgeT age)	{		m_Age = age;		m_Name = name;	}	NameT m_Name;	AgeT m_Age;};void test(){	Person<string, int>("Tom", 30);  // 调用-只有一种调用方式}

3.2 类模板和函数模板的区别

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

  1. 类模板没有自动类型推导的使用方式

  2. 类模板在模板参数列表中可以有默认参数

    template<typename NameT, typename AgeT = int>  // 默认参数class Person{public:	Person(NameT name, AgeT age)	{		m_Age = age;		m_Name = name;	}	NameT m_Name;	AgeT m_Age;};void test(){	Person<string>("Tom", 30);}

3.3 使用时机

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

  • 普通类中的成员函数一开始就可以创建
  • 类模板中的成员函数在调用时才创建
class Person1{public:	void show()	{		cout << "Person1" << endl;	}};template<typename T>class Person{public:    // 没调用,其不会编译,因为无法确定T的数据类型	T p1;	void func1()	{		p1.show();	}	};void test(){	Person<Person1> p;	p.func1();}

3.4 类模板对象函数做参数

类模板实例出的对象,向函数传参

一共有三种传入方式

  • 指定传入的数据类型:直接显示对象的数据类型

    • // 指定传入类型void printPerson1(Person<string, int> &p); 
  • 参数模板化:将对象中的参数变为模板进行传递

    • // 参数模板化template<class T1, class T2>void printPerson2(Person<T1, T2>& p);
  • 整个类模板化:将这个对象类型模板化进行传递

    • // 整个类模板化template<class T>void printPerson3(T &p);
// 类模板做函数的参数template<class T1, class T2>class Person{public:	Person(T1 name, T2 age)	{		m_Name = name;		m_Age = age;	}	T1 m_Name;	T2 m_Age;	void showPerson()	{		cout << "name:" << m_Name << " age:" << m_Age << endl;	}};// 指定传入类型void printPerson1(Person<string, int> &p)  {	p.showPerson();}// 参数模板化template<class T1, class T2>void printPerson2(Person<T1, T2>& p){	p.showPerson();	cout << "T1的类型为:" << typeid(T1).name() << endl;	cout << "T2的类型为:" << typeid(T2).name() << endl;}// 整个类模板化template<class T>void printPerson3(T &p){	p.showPerson();}void test(){	Person<string, int> p("Tom", 12);	printPerson1(p);	printPerson2(p);	printPerson3(p);}

查看数据类型的方式

typeid(T2).name()

3.5 类模板与继承

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

  • 当子类继承的父类是一个类模板时,子类在声明的时候,要指定出父类中 T 的数据类型
  • 如果不指定,编译器无法给子类分配内存
  • 如果想灵活指定出父类的 T 的类型,子类也需变为模板
// 类模板与继承template<class T>class Base{public:	T m_M;};// 当子类继承的父类是一个类模板时,子类在声明的时候,要指定出父类中 T 的数据类型class Son : public Base<string> {};// 如果想灵活使用父类中的 T 类型,子类也需要变为类模板template<class T1, class T2>class Son1 : public Base<T2> {};

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

能够掌握类模板中的成员函数类外实现

// 类外实现template<class T1, class T2>class Person{public:	Person(T1 age, T2 name);	void shouPerson();	T1 m_Age;	T2 m_Name;};template <class T1, class T2>Person<T1, T2>::Person(T1 age, T2 name){	this->m_Name = name;	m_Age = age;}// 要体现其为类模板的类函数,没有参数也要添加template <class T1, class T2>void Person<T1, T2>::shouPerson(){	cout << "name:" << m_Name << " age:" << m_Age << endl;}

3.7 类模板文件编写

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

问题

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

解决

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

person.hpp 中代码

#pragma once#include <iostream>using namespace std;template<class T1, class T2>class Person{public:	Person(T1 age, T2 name);	void shouPerson();	T1 m_Age;	T2 m_Name;};template <class T1, class T2>Person<T1, T2>::Person(T1 age, T2 name){	this->m_Name = name;	m_Age = age;}template <class T1, class T2>void Person<T1, T2>::shouPerson(){	cout << "name:" << m_Name << " age:" << m_Age << endl;}

程序入口代码

#include <iostream>using namespace std;#include <fstream>// 第一中解决方式,包含 .cpp源文件// #include"Person.cpp"// 第二种解决方式,将 .h 和 .cpp 中的内容写到 .hpp 文件中#include "Person.hpp"void test(){	Person<string, int> p("Tom", 132);	p.shouPerson();}int main() {	test();	system("pause");	return 0;}

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

3.8 类模板和友元

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

  • 全局函数类内实现:直接在类内声明友元即可
  • 全局函数类外实现:需要提前让编译器知道全局函数的存在
// 通过全局函数打印全局信息 // 提前让编译器知道Person类的存在template<class T1, class T2>class Person;// 如果是类外实现的话需要让编译器提前知道该函数存在template<class T1, class T2>void printPerson1(Person<T1, T2>& p);template<class T1, class T2>class Person{	// 全局函数,类内实现	friend void printPerson(Person<T1, T2>& p)	{		cout << "姓名:" << p.m_Name << " 年龄:" << p.m_Age << endl; 	}	// 全局函数,类外实现	friend void printPerson1<>(Person<T1, T2>& p);  // <> 其为函数模板声明public:	Person(T1 name, T2 age)	{		m_Name = name;		m_Age = age;	}private:	T1 m_Name;	T2 m_Age;};template<class T1, class T2>void printPerson1(Person<T1, T2>& p){	cout << "姓名:" << p.m_Name << " 年龄:" << p.m_Age << endl;	cout << "类外实现" << endl;}

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

3.9 数组类封装

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

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

myArray.hpp 中代码

#pragma once#include<iostream>using namespace std;template<class T1>  // 输出函数void printArray();template<class T1>class MyArray{public:	MyArray(int capacity);  // 有参构造	MyArray(const MyArray& arr);  // 拷贝构造	MyArray& operator=(const MyArray& arr);  // 赋值运算符重载,防止浅拷贝问题	void pushBack(const T1& val);  //  尾插法插入数据	void delBack();  // 尾删法删除数据	T1& operator[](int index);  // 重载[],使得可以使用索引访问数组,同时可以赋值	int getCapacity();// 返回数组的容量	int getSize();// 返回数组的大小	~MyArray();  // 清空堆区数据private:	T1* pAddress;  // 指针指向开辟到堆区的真实数组	int m_Capacity; // 数组容量	int m_Size;  // 数组大小};template<class T1>MyArray<T1>::MyArray(int capacity){	this->m_Capacity = capacity;	this->m_Size = 0;	this->pAddress = new T1[this->m_Capacity];  // 开辟数组空间}template<class T1>MyArray<T1>::~MyArray(){	if (this->pAddress)	{		delete[] pAddress;		pAddress = NULL;	}}template<class T1>MyArray<T1>::MyArray(const MyArray& arr){	this->m_Capacity = arr.m_Capacity;	this->m_Size = arr.m_Size;	// this->pAddress = arr.pAddress  // 浅拷贝	this->pAddress = new T1[arr.m_Capacity];  // 深拷贝	// arr中的数据都拷贝过去	for (int i = 0; i < this->m_Size; i++)	{		this->pAddress[i] = arr.pAddress[i];	}}template<class T1>MyArray<T1>& MyArray<T1>::operator=(const MyArray& arr){	// 先判断原来堆区是否有数据,如果有先释放	if (this->pAddress)	{		delete[] pAddress;		this->pAddress = NULL;		this->m_Size = 0;		this->m_Capacity = 0;	}	// 深拷贝	this->m_Capacity = arr.m_Capacity;	this->m_Size = arr.m_Size;	this->pAddress = new T1[this->m_Capacity];	for (int i = 0; i < this->m_Size; i++)	{		this->pAddress[i] = arr.pAddress[i];	}	return *this;}template<class T1>void MyArray<T1>::pushBack(const T1& val){	// 判断容量是否等于大小	if (this->m_Capacity == this->m_Size)	{		cout << "达到数组容量,无法插入" << endl;		return;	}	this->pAddress[this->m_Size] = val;	this->m_Size++;  // 更新数组大小}template<class T1>void MyArray<T1>::delBack(){	// 让用户访问不到最后一个元素,即为尾删,逻辑删除	if (!this->m_Size)	{		cout << "数组中没有元素" << endl;		return;	}	this->m_Size--;  // 访问不到那个元素}template<class T1>T1& MyArray<T1>::operator[](int index){	return this->pAddress[index];}template<class T1>int MyArray<T1>::getCapacity(){	return this->m_Capacity;}template<class T1>int MyArray<T1>::getSize(){	return this->m_Size;}template<class T1>void printArray(MyArray<T1>& arr){	for (int i = 0; i < arr.getSize(); i++)	{		cout << arr[i] << endl;	}}

主函数调用

#include <iostream>using namespace std;#include <fstream>#include "Person.hpp"void test(){	MyArray<int> arr(5);	for (int i = 0; i < 5; i++)	{		arr.pushBack(i);	}	cout << "开始输出数组" << endl;	printArray(arr);}int main() {	test();	system("pause");	return 0;}

该数组也可以存储自定义数据类型

二、 STL 初识

1、 基本概念

  • STL 基本模板库
  • STL 从广义上分为容器、算法和迭代器
  • 容器和算法事件通过迭代器无缝连接
  • STL 几乎所有的代码都采用了模板类或模板函数

2、 STL 六大组件

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

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

2.1 容器、算法、迭代器

容器:置物之所也

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

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

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

  • 序列式容器:强调值的排序,序列式容器中的每个元素均有固定的位置
  • 关联式容器:二叉树结构,各元素之间没有严格的物理上的顺序关系

算法:问题之解也

有限的步骤,解决逻辑或数学上的问题,这叫做算法

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

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

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

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

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

迭代器使用非常类似于指针

迭代器种类

种类权限支持运算
Input iterator(输入迭代器)只读++、==、!=
Output iterator(输出迭代器)只写++
Forward iterator(前向迭代器)读和写,并且推进迭代器++、==、!=
Bidirectional iterator(双向迭代器)读和写,可以向前或向后操作++、--
Random access iterator(随机访问迭代器)读和写。可以跳跃式访问任意数据++、--、[n]、-n、<、>

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

3、 迭代器初始

3.1 vector 存放内置数据类型

容器:vector

算法:for_each

迭代器:vector<int>::iterator

#include <vector>  // vector 头文件#include <algorithm>  // 标准算法头文件void printVector(int value){	cout << value << endl;}// vector 存放内置数据类型void test(){	// 创建一个 vector 容器——数组	vector<int> v;	// 向容器中插入数据	v.push_back(10);  // 尾插数据	v.push_back(11);	v.push_back(12);	// 通过迭代器访问容器中的数据	vector<int>::iterator itBegin = v.begin(); // 起始迭代器,指向容器中第一个元素,当做指针使用	vector<int>::iterator itEnd = v.end();  // 结束迭代器,指向容器最后一个元素的下一个位置	// 第一种遍历方式	while (itBegin != itEnd)	{		cout << *itBegin << endl;		itBegin++;	}	// 第二种遍历方式	for (vector<int>::iterator it = v.begin(); it != v.end(); it++)	{		cout << *it << endl;	}	// 第三种遍历方式	for_each(v.begin(), v.end(), printVector);  // 回调函数}

3.2 vector 存放自定义数据类型

vector 中存放自定义数据类型,并打印输出

// 存放自定义数据类型class Person{public:	Person(string name, int age)	{		m_Name = name;		m_Age = age;	}	int m_Age;	string m_Name;};void test(){	vector<Person> v;	Person p1("a", 20);	Person p2("b", 34);	Person p3("c", 20);	v.push_back(p1);	v.push_back(p2);	v.push_back(p3);	// 遍历数据	for (vector<Person>::iterator it = v.begin(); it != v.end(); it++)	{		cout << "name:" << it->m_Name  			<< " age:" << it->m_Age << endl;  // it是一个指针	}	// 存放自定义数据类型的指针	vector<Person*> v1;	v1.push_back(&p1);	v1.push_back(&p2);	v1.push_back(&p3);	// 遍历数据	for (vector<Person*>::iterator its = v1.begin(); its != v1.end(); its++)	{		cout << "name:" << (*its)->m_Name			<< " age:" << (*its)->m_Age << endl;  // it是一个指针	}}

3.3 vector 中嵌套容器

容器中嵌套容器,我们将所有数据遍历输出

// 容器嵌套容器void test(){	vector<vector<int>> V;	// 创建小容器	vector<int> v1;	vector<int> v2;	vector<int> v3;	vector<int> v4;	// 向小容器中添加数据	for (int i = 0; i < 10; i++)	{		v1.push_back(i);		v2.push_back(10 - i);		v3.push_back(20 - i);		v4.push_back(i + 10);	}	// 将小容器插入到大容器中	V.push_back(v1);	V.push_back(v2);	V.push_back(v3);	V.push_back(v4);	// 遍历大容器	for (vector<vector<int>>::iterator i = V.begin(); i != V.end(); i++)	{		// *i 是一个容器		for (vector<int>::iterator j = (*i).begin(); j != (*i).end(); j++)		{			cout << *j << "\t";		}		cout << endl;	}}

三、 STL 常用容器

每个容器都要添加头文件

1、 string 容器

1.1 string 基本概念

本质

  • string 是 C++ 风格的字符串,而 string 本质是一个类

string 和 char* 的区别

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

特点

  1. string 类内部封装了很多成员方法
    • 例如:find, copy, delete, replace, insert
  2. string 管理 char* 所分配的内存,不用担心复制和取值越界等,由类内进行负责

1.2 string 构造函数

构造函数原型

  • string(); 创建一个空字符串

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

  • string(const string& str); 使用一个 string 对象初始化另一个 string 对象

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

/*- string();							创建一个空字符串  string(const char* s);			使用字符串 s 初始化- string(const string& str);		使用一个 string 对象初始化另一个 string 对象- string(int, char c);              使用 n 个字符 c 初始化*/void test(){	string s1;  // 默认构造	const char* str = "hello world";	string s2(str);  // 有参构造	cout << "s2:" << s2 << endl;	string s3(s2);  // 拷贝构造	cout << "s3:" << s3 << endl;	string s4(10, 'a');  // 10 个 a 构造	cout << "s4:" << s4 << endl;}

string 的多种构造方式没有可比性,灵活性较高

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

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 个字符连接到字符串结尾

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) const;  // 查找字符 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找到字符串后返回查找的第一个字符位置,找不到返回-1
  • replace在替换时,要指定从哪个位置起,多少个字符,替换成什么样的字符串

1.6 string 字符串比较

功能描述

  • 字符串之间的比较

比较方式

  • 字符串比较时按字符的ASCII码进行对比
  • = 返回 0
  • < 返回 1
  • > 返回 -1

函数原型

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

主要比较两个字符串是否相等

1.7 string 字符存取

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

char& operator[](int n);  // 通过 [] 方法获取字符char& at(int n);  // 通过 at 方法获取字符str.size();  // 返回字符串的长度 

可以修改字符,str[int n] = 'c'

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 个字符

1.9 string 中的子串

功能描述

  • 从字符串中获取想要的子串

函数原理

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

2、 vector 容器

2.1 vector 基本概念

功能

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

vector 与普通数组的区别

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

    动态扩展

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

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

2.2 vector 构造函数

功能描述

  • 创建 vector 容器

函数原型

vector<T> v;  // 采用模板实现类实现,默认构造函数vector v2(v.begin(), v.end());  // 将 v[begin(), end()] 区间中的元素拷贝到自身,左闭右开vector v3(n, elem);  // 构造函数将 n 个 elem 拷贝给本身vector v4(const vector& vec);  // 拷贝构造函数

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

2.3 vector 赋值操作

功能描述

  • 给 vector 容器进行赋值

函数原理

vector& operator=(const vector &vec);  // 重载赋值操作符assign(v.begin(), v.end());  // 将v[begin, end]区间中的数据拷贝赋值给本身assign(n, elem);  // 将 n 个 elem 拷贝赋值给本身

2.4 vector 大小操作

功能描述

  • 对 vector 容器的容量和大小操作

函数原型

empty();  // 判断容器是否为空capacity();  // 容器的容量size();  // 返回容器中元素的个数resize(int num);  // 重新指定容器的长度为 num,若容器变长,则以默认值填充新位置;如果容器变短,则末尾超出容器长度的元素被删除(默认为0)resize(int num, elem);  // 重新指定容器的长度为 num,若容器边长,则以 elem 值填充新位置;如果容器变短,则末尾超出容器长度的元素被删除
void printVector(vector<int>& v){	for (vector<int>::iterator i = v.begin(); i != v.end(); i++)	{		cout << *i << " ";	}	cout << endl;}void test(){	vector<int> v;	for (int i = 0; i < 10; i++)	{		v.push_back(i);	}	cout << v.capacity() << endl;	cout << v.size() << endl;	v.resize(20);  // 默认使用0填充	cout << v.size() << endl;	cout << v.capacity() << endl;	printVector(v);}

容量大于等于大小

2.5 vector 插入和删除

功能描述

  • 对 vector 容器进行插入、删除操作

函数原型

push_back(elem);  // 尾部插入元素elempop_back();  // 删除最后一个元素insert(const_iterator pos, elem);  // 迭代器指向位置 pos 插入元素 eleminsert(const_iterator pos, int n, elem);  // 迭代器指向位置 pos 插入 n 个元素erase(const_iterator pos);  // 删除迭代器指向的长度erase(const_iterator start, const_iterator end);  // 删除迭代器从 start 到 end 之间的元素,左闭右开clear();  // 删除容器中所有元素

? v1.insert(v1.begin(), 100); // 第一个参数是迭代器

2.6 vector 数据存取

功能描述

  • 对 vector 中的数据的存取操作

数据原型

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

2.7 vector 互换容器

功能描述

  • 实现两个容器内元素进行互换

函数原型

swap(v);  // 将 vec 与 本身的元素互换
void test(){	vector<int> v;	vector<int> v1;	for (int i = 0; i < 10; i++)	{		v.insert(v.begin(), i);		v1.push_back(i + 100);	}	cout << "交换前" << endl;	printVector(v);	printVector(v1);	v.swap(v1);	cout << "交换后" << endl;	printVector(v);	printVector(v1);}

作用:巧用 swap 可以收缩内存空间vector<int> (v).swap(v); // 使用匿名对象

2.8 vector 预留空间

功能描述

  • 减少 vector 在动态扩展容量时的扩展次数

函数原理

reserve(int len);  // 容器预留 len 个长度,预留位置不初始化,元素不可访问
void test(){	vector<int> v;	int num = 0;  // 统计开辟次数	v.reserve(1000000);  // 当没有添加此代码时,开辟了 35 次内存空间	int* p = NULL;  	for (int i = 0; i < 1000000; i++)	{		v.push_back(i);		if (p != &v[0])  // 开辟一次内存,其首地址会发生改变		{			p = &v[0];			num++;		}	}	cout << num << endl;}

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

3、 deque 容器

3.1 deque 基本概念

功能

  • 双端数组:可以对头部进行插入删除操作

deque 和 vector 区别

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

deque 内部工作原理

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

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

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

3.2 deque 构造函数

功能描述

  • deque 容器构造

函数原理

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

3.3 deque 赋值操作

功能描述

  • 给 deque 容器进行赋值

函数原理

deque& operator=(const deque& d);  // 重载赋值运算符assign(beg, end);  // 将 [beg, end] 区间中的数据拷贝赋值给本身assign(n, elem);  // 将 n 个 elem 拷贝赋值给本身

3.4 deque 大小操作

功能描述

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

函数原理

empty();  // 判断容器是否为空size();  // 返回容器中元素的个数resize(int num);  // 重新指定容器的长度为 num,若容器变长,则以默认值填充新位置;如果容器变短,则末尾超出容器长度的元素被删除(默认为0)resize(int num, elem);  // 重新指定容器的长度为 num,若容器边长,则以 elem 值填充新位置;如果容器变短,则末尾超出容器长度的元素被删除

deque 没有容量的概念

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 位置插入 (d1.begin(), d1.end()) 数据,无返回值clear();  // 清空容器的所有数据erase(beg, end);  // 删除 [beg, end] 区间的数据,返回下一个数据的位置erase(pos);  // 删除 pos 位置的数据,返回下一个数据的位置

里面的 pos 是迭代器指针的位置

3.6 deque 数据存取

功能描述

  • 对 deque 中的数据的存取操作

函数原型

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

3.7 deque 排序

功能描述

  • 利用算法对 deque 中的数据进行排序

函数原型

sort(iterator beg, iterator end);  // 对 [beg, end] 区间内元素进行排序

注意使用时,要包含头文件#include <algorithm>

对于支持随机访问的迭代器的容器,都可以利用 sort 算法直接对其进行排序

vector 也可以利用 sort 进行排序

4、 案例-评委打分

4.1 案例描述

有五名选手:选手 ABCDE ,10个评委分别对每一名选手打分,去除最高分,去除评委中最低分,取平均分

4.2 实现步骤

  1. 创建五名选手,放到 vector 容器中
  2. 遍历 vector 容器,取出每名选手,执行 for 循环,可以把 10 个评分存到 deque 容器中
  3. sort 算法对 deque 容器中分数进行排序,去除最高分和最低分
  4. deque 容器遍历一遍,累加总分
  5. 获取平均分
// 选手类class Person{public:	Person(string name, int score)	{		m_Name = name;		m_Score = score;	}	string m_Name;	int m_Score;  // 平均分};void createPerson(vector<Person>& v){	// 创建五名选手	for (int i = 0; i < 5; i++)	{		char nameSeed[] = { 'A', 'B', 'C', 'D', 'E' };		string name = "选手";		name += nameSeed[i];		int score = 0;  // 默认为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容器中		deque<int> d;		for (int i = 0; i < 10; i++)		{			int score = rand() % 41 + 60;  // 分数在 60 到 100之间,随机分			d.push_back(score);  // 将分数放入容器中		}		// 排序		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 avr = sum / d.size();		it->m_Score = avr;	}}void showScore(vector<Person> v){	for (vector<Person>::iterator it = v.begin(); it != v.end(); it++)	{		cout << "名字为:" << it->m_Name << "  平均分为:" << it->m_Score << endl;	}}void test(){	// 随机数种子	srand((unsigned int)time(NULL));	vector<Person> v;  // 存放选手类	v.reserve(5);	createPerson(v);	setScore(v);	showScore(v);}

5、 stack 容器

5.1 stack 基本概念

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

栈中进入元素称为入栈:push();

栈中弹出元素称为出栈:pop();

5.2 stack 常用接口

功能描述:

  • 栈容器常用的对外接口
5.2.1 构造函数
stack<T> stk;  // stack 采用模板实现,stack对象的默认构造形式stack stk1(const stack& stk);  // 拷贝构造函数
5.2.2 赋值操作
stack& operator=(const stack& stk);  // 重载赋值运算符
5.2.3 大小操作
empty();  // 判断堆栈是否为空size();  // 返回栈的大小
5.2.4 数据存取
push(elem);  // 向栈顶添加元素pop();  // 从栈顶移除第一个元素top();  // 返回栈顶元素

6、 queue 容器

6.1 queue 基本概念

概念:

  • queue 是一种先进先出的数据结构,它有两个出口

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

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

队列中进数据称为入队:push();

队列中出数据称为出队:pop();

6.2 queue 常用接口

功能描述

  • 栈容器常用的对外接口
6.2.1 构造函数
queue<T> q;  // queue 采用模板类实现,queue 对象的默认构造函数queue(const queue& que);  // 拷贝构造函数
6.2.2 赋值操作
queue& operator=(const queue& q);  // 重载赋值操作符
6.2.3 大小操作
empty();  // 判断堆栈是否为空size();  // 返回栈大小
6.2.4 数据存取
push(elem);  // 往队尾添加元素pop();  // 从队头移除第一个元素back();  // 返回最后一个元素front();  // 返回队头第一个元素

7、 list 容器

7.1 list 基本概念

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

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

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

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

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

由于链表的存储方式并不是连续的内存空间,因此链表 list 中的迭代器只支持前移或后移,属于双向迭代器

list 优点

  • 采用动态分配内存,不会造成内存浪费和溢出
  • 链表执行插入和删除操作十分方便,修改指针即可,不需要移动大量数据

list 缺点

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

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

总结:STL 中 list 和 vector 是最常被使用的容器,各有优缺点

7.2 list 构造函数

功能描述

  • 创建 list 容器

函数原型

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

7.3 list 赋值和交换

功能描述

  • 给 list 容器进行赋值,以及交换 list 容器

函数原型

assign(beg, end);  // 将 [beg, end] 区间中的数据拷贝赋值给本身assign(n, elem);  // 将 n 个 elem 拷贝赋值给本身list& operator=(const list& l);  // 重载赋值运算符swap(l);  // 将 list 与本身的元素互换

7.4 list 大小操作

功能描述

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

函数原型

size();  // 返回容器中元素的个数empty();  // 判断容器是否为空resize(int num);  // 重新指定容器的长度为 num,若容器变长,则以默认值填充新位置;如果容器变短,则末尾超出容器长度的元素被删除(默认为0)resize(int num, elem);  // 重新指定容器的长度为 num,若容器边长,则以 elem 值填充新位置;如果容器变短,则末尾超出容器长度的元素被删除

7.5 list 插入和删除

功能描述

  • 对 list 容器进行数据的插入和删除

函数原型

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 位置插入 (l.begin(), l.end()) 数据,无返回值clear();  // 清空容器的所有数据erase(beg, end);  // 删除 [beg, end] 区间的数据,返回下一个数据的位置erase(pos);  // 删除 pos 位置的数据,返回下一个数据的位置remove(elem);  // 删除容器中所有与 elem 值匹配的元素

7.6 list 数据存取

功能描述

  • 对 list 容器中数据进行存取

函数原型

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

注意不能使用 at 和 [] 的方式访问容器中的元素

原因是 list 本质是链表,而不是使用连续线性空间存储数据,迭代器也是不支持随机访问的

迭代器不支持随机访问,支持双向访问

7.7 list 反转和排序

功能描述

  • 将容器中的元素反转,以及将容器中的数据进行排序

函数原型

reverse();  // 反转链表sort();  // 链表排序,其为成员函数

所有不支持随机访问迭代器容器,不可以使用标准算法

不支持随机访问迭代器的容器,内部会提供对应一些算法

对于自定义数据类型,sort() 括号可以添加一个排序规则

高级排序只是在排序规则上再进行一次逻辑规则的制定,并不复杂

bool comparePerson(Person& p1, Person& p2){    // 按照年龄升序    if (p1.m_Age == p2.m_Name)    {        // 年龄相同,身高升序        return p1.Height > p2.Height;    }    else    {        return p1.m_Age < p2.m_Age;    }}l.sort(comparePerson);  // 排序算法

8、 set / multiset 容器

8.1 set 基本概念

简介:

  • 所有元素都会在插入时自动被排序

本质

  • set 属于关联式容器,底层结构使用二叉树实现

set 和 multiset 区别

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

8.2 set 构造和赋值

功能描述

  • 创建 set 容器以及赋值

函数原型

set<T> s;  // 默认构造函数set(const set& s);  // 拷贝构造函数set& operator=(const set& s);  // 重载赋值运算符inset(elem);  // 插入数据

8.3 set 大小和交换

功能描述

  • 统计 set 容器大小及交换 set 容器

函数原型

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

8.4 set 插入合删除

功能描述

  • set 容器进行插入和删除操作

函数原型

insert(elem);  // 在容器中插入元素clear();  // 清空所有元素erase(pos);  // 删除 pos 迭代器所指的元素,返回下一个元素的迭代器erase(beg, end);  // 删除区间 [beg, end] 的所有元素,返回下一个元素的迭代器erase(elem);  // 删除容器中值为 elem 的元素

8.5 set 查找和统计

功能描述

  • 对 set 容器进行查找数据以及进行数据统计

函数原型

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

8.6 set 和 multiset 区别

掌握 set 和 multiset 的区别

区别

  • set 不可以插入重复数据,而 multiset 可以

  • set插入数据的同时会返回插入结果,表示插入是否成功

    ret.second用来查看是否插入成功

  • multiset 不会检测数据,因此可以重复插入数据

8.7 pair 对组创建

功能描述

  • 成功出现的数据,利用对组可以返回两个数据

两种创建方式

pair<type1, type2> p (value1, value2);  // 两个 type 分别对应 value 的数据类型pair<type1, type2> p = make_pair(value1, value2);

两种创建方式,记住一种就可以了

使用方式

pair<string, int> p ("Tom", 20);cout << "name:" << p.first << "  age:" << p.second;

8.8 set 排序

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

  • 利用仿函数,可以改变排序规则
#include <set>class MyCompare{public:	bool operator()(int v1, int v2) const  	{		return v1 > v2;  // 降序排序	}};void test(){	// 存放内置数据类型,改变排序规则	set<int, MyCompare> s1;  // 仿函数的本质是一个类	s1.insert(10);	s1.insert(30);	s1.insert(20);	s1.insert(40);	for (set<int, MyCompare>::iterator it = s1.begin(); it != s1.end(); it++)	{		cout << *it << endl;	}}

对于自定义的数据类型,要创建排序规则,必须要指定排序规则

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

9、 map / multimap 容器

9.1 map 基本概念

简介:

  • map 中所有元素都是 pair
  • pair 中第一个元素为 key(键值),起到索引作用,第二个元素为 value(实值)
  • 所有元素都会根据元素的键值自动排序

本质:

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

优点:

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

map/ multimap 区别

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

9.2 map 构造和赋值

功能描述:

  • 对 map 容器进行构造和赋值操作

函数原型

map<T1, T2> mp;  // map 默认构造函数map (const map& mp);  // 拷贝构造map& operator=(const map& mp);  // 重载赋值运算符

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

9.3 map 大小和交换

功能描述:

  • 对 map 容器大小以及交换 map 值

函数原型

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

9.4 map 插入和删除

功能描述:

  • map 容器进行插入和删除数据

函数原型

insert(elem);  // 在容器中插入元素clear();  // 清空所有元素erase(pos);  // 删除 pos 迭代器所指的元素,返回下一个元素的迭代器erase(beg, end);  // 删除区间 [beg, end] 的所有元素,返回下一个元素的迭代器erase(key);  // 删除容器键为 key 的元素

注意插入的是对组

// 第一种m.insert(pair<int, int>(1, 10));// 第二种m.insert(make_pair(2, 20));// 第三种m.insert(map<int, int>::value_type(3, 30));// 第四种m[4] = 40;  // 不建议使用,可以利用 [] 访问值

9.5 map 查找和统计

功能描述:

  • 对 map 容器进行查找数据和统计数据

函数原型

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

9.6 map 排序

map 容器默认排序规则为按照键升序排序

  • 利用仿函数可以改变排序规则

其和 [set 排序](#8.8 set 排序)类似

10、 案例-员工分组

10.1 案例描述

  • 公司每天招聘10个员工,10名员工进入公司后,需要指派员工在哪个部门工作
  • 员工信息:姓名、工资组成;部门分为:策划、美术、研发
  • 随机给10名员工分配部门和工资
  • 通过 multimap 进行信息的插入 key:部门编号、value:员工
  • 分部门显示员工

10.2 实现步骤

  1. 创建10名员工,放到 vector 中
  2. 遍历 vector 容器,取出每个员工,进行随机分组
  3. 分组后,将员工部门编号作为 key,具体员工作为 value,放入到 multimap 容器中
  4. 分部门显示员工信息

10.3 代码演示

class Worker{	// 创建员工public:	string m_Name;	int m_Salary;};void createWorker(vector<Worker>& v){	// 创建10名员工	for (int i = 0; i < 10; i++)	{		Worker worker;		string nameSeed = "ABCDEFGHIJ";		worker.m_Name = "员工";		worker.m_Name += nameSeed[i];		worker.m_Salary = rand() % 10000 + 10000;  // 10000~19999		v.push_back(worker);  // 将员工放入分组中	}}void printWorker(const vector<Worker>& v){	for (vector<Worker>::const_iterator it = v.begin(); it != v.end(); it++)	{		cout << "name: " << it->m_Name << "   salary: " << it->m_Salary << endl;	}}void printWorker(multimap<string, Worker>& mp, string* arr){	string s0(20, '-');	for (int i = 0; i < 3; i++)	{		string s = arr[i];		s += "部门信息";		cout << s << endl;		multimap<string, Worker>::iterator pos = mp.find(arr[i]);  // 返回迭代器对象		int count = mp.count(arr[i]);		for (int index = 0; pos != mp.end() && index < count; pos++, index++)		{			cout << "姓名:" << pos->second.m_Name << "   工资:" << pos->second.m_Salary << endl;		}		cout << s0 << endl;	}}void setGroup(vector<Worker>& v, multimap<string, Worker>& mp, string* arr){	for (vector<Worker>::iterator it = v.begin(); it != v.end(); it++)	{		// 产生随机部门编号		int depId = rand() % 3;  // 0 1 2 随机数		// 将员工插入到分组中,key编号,value员工		mp.insert(pair<string, Worker>(arr[depId], *it));	}}void test(){	// 添加随机种子	srand((unsigned int)time(NULL));	string dep[] = { "策划", "美术", "研发" };	vector<Worker> v;	createWorker(v);	printWorker(v);	// 员工分组	multimap<string, Worker> mp;	setGroup(v, mp, dep);	printWorker(mp, dep);}

四、 STL 函数对象

1、 函数对象

1.1 基本概念

概念:

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

本质:

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

1.2 使用方法

特点:

  • 函数对象在使用时,可以像普通函数那样调用,可以有参数,可以有返回值
  • 函数对象超出普通函数的概念,函数对象可以有自己的状态
  • 函数对象可以作为参数传递
// 函数对象class MyAdd{public:	MyAdd()	{		this->count = 0;	}	int operator()(int v1, int v2)	{		this->count++;		return v1 + v2;	}	int count;  // 函数对象可以有自己的内部状态};void test(){	MyAdd ma;	cout << ma(1, 2) << endl;	cout << ma(1, 2) << endl;	cout << ma(1, 2) << endl;	cout << ma(1, 2) << endl;	cout << "调用次数为:" << ma.count << endl;}

2、 谓词

2.1 谓词概念

概念

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

2.2 一元谓词

class CreateFive{public:	bool operator()(int val)	{		return val > 5 ? true : false;	}  // 一元谓词};void test(){	vector<int> v;	for (int i = 0; i < 10; i++)	{		v.push_back(i);	}	// 查找容器中有么有大于5的数字	vector<int>::iterator it = find_if(v.begin(), v.end(), CreateFive());  // 其为匿名函数对象,find_if 的返回值为一个迭代对象	if (it == v.end())	{		cout << "没有找到" << endl;	}	else	{		cout << "大于五的数字为:" << *it << endl;	}}

2.3 二元谓词

// 二元谓词class MySort{public:	bool operator()(int a, int b)	{		return a > b ? true : false;	}};void test(){	vector<int> v;	v.push_back(10);	v.push_back(20);	v.push_back(30);	v.push_back(40);	v.push_back(50);	// sort(v.begin(), v.end());  // 升序排列	// 使用函数对象,改变算法策略,变为排序规则降序排列	sort(v.begin(), v.end(), MySort());	for (vector<int>::iterator it = v.begin(); it != v.end(); it++)	{		cout << *it << " ";	}	cout << endl;}

3、 内建函数对象

3.1 意义

概念:

  • STL 内建了一些函数对象

分类:

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

用法:

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

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>;  // 取反运算,10 取反为 -10
plus<int> p;cout << p(10, 20) << endl;negate<int> n;cout << n(20) << endl; 

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

3.4 逻辑仿函数

功能描述

  • 实现逻辑运算

仿函数原理

template<typename T> bool logical_and<T>;  // 与template<typename T> bool logical_or<T>;  // 或template<typename T> bool logical_not<T>;  // 非

五、 STL 常用算法

描述:

  • 算法主要是由头文件<algorithm><functional><numeric>组成
  • <algorithm>是所有 STL 头文件中最大的一个,范围涉及到比较、交换、查找、遍历、赋值等等
  • <numeric>体积很小,只包括几个在序列上面进行简单数学运算的模块函数
  • <functional>定义了一些模快类,用以声明函数对象

1、 常用遍历算法

学习目标:

  • 掌握常用遍历算法

算法简介:

for_each();  // 遍历容器transform();  // 搬运容器到另一个容器中

1.1 for_each

功能描述:

  • 实现遍历容器

函数原型

for_each(iterator beg, iterator end, _func);

遍历算法:遍历容器元素

参数:

  • beg:开始迭代器
  • end:结束迭代器
  • _func:函数或者函数对象,一般为输出内容的函数,回调函数

1.2 transform

功能描述:

  • 搬运容器到另一个容器中

函数原型

transform(iterator beg1, iterator end1, iterator beg2, _func);

目标容器要提前开辟空间,否则会报错

参数:

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

2、 常用查找算法

学习目标:

  • 掌握常用的查找算法

算法简介:

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

2.1 find

功能描述:

  • 查找指定元素,找到返回指定元素的迭代器,找不到返回结束迭代器

函数原型

find(iterator beg, iterator end, value);

参数:

  • beg:开始迭代器
  • end:结束迭代器
  • value:查找的元素

如果是自定义数据类型,查找时要重载等号运算符

2.2 find_if

功能描述:

  • 按条件查找元素

函数原型

find_if(iterator beg, iterator end, _Pred);

参数:

  • beg:起始迭代器
  • end:结束迭代器
  • _Pred:函数或者谓词(返回 bool 类型的仿函数)

2.3 adjacent_find

功能描述:

  • 查找相邻重复元素

函数原型

adjacent_find(iterator beg, iterator end);

参数:

  • beg:开始迭代器
  • end:结束迭代器

功能描述:

  • 查找指定元素是否存在

函数原型

bool binary_search(iterator beg, iterator end, value);

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

参数:

  • beg:开始迭代器
  • end:结束迭代器
  • value:查找的元素

2.5 count

功能描述:

  • 统计元素个数

函数原型

count(iterator beg, iterator end, value);

beg:开始迭代器

end:结束迭代器

value:统计的元素

统计自定义数据类型时,要使用仿函数

class Person{public:    Person(string name, int age)      {        this->m_Name = name;        this->m_Age = age;    }    bool operator==(const Person& p)  // 需要重载等号运算符,才可以统计    {        return this->m_Age == p.m_Age && this->m_Name = p.m_Name? true : flase;    }    int m_Age;    string m_Name;};

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

2.6 count_if

功能描述:

  • 按条件统计元素个数

函数原型

count_if(iterator beg, iterator end, _Pred);

参数:

  • beg:开始迭代器
  • end:结束迭代器
  • _Pred:谓词

3、 常用排序算法

学习目标

  • 掌握常用的排序算法

算法简介:

sort();  // 对容器内元素进行排序random_shuffle();  // 洗牌,指定范围内的元素随机调整次序merge();  // 容器元素合并,并存储到另一个容器中reverse();  // 反转指定范围内的元素

3.1 sort

功能描述

  • 对容器内元素进行排序

函数原型

sort(iterator beg, iterator end, _Pred);

参数:

  • beg:开始迭代器
  • end:结束迭代器
  • _Pred:谓词

3.2 random_shuffle

功能描述

  • 洗牌,指定范围内的元素随机调整次序

函数原型

random_shuffle(iterator beg, iterator end);

使用时记得添加随机种子

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

参数:

  • beg:起始迭代器
  • end:结束迭代器

3.3 merge

功能描述:

  • 两个容器元素合并,并存储到另一个容器中

函数原型

merge(iterator beg1, iterator end1, iterator beg2, iterator end2, iterator dest);

注意:

  • 两个容器必须是有序的
  • 要提前给目标容器分配空间

参数:

  • beg1:容器1开始迭代器
  • end1:容器1结束迭代器
  • beg2:容器2开始迭代器
  • end2:容器2结束迭代器
  • dest:目标容器开始迭代器

3.4 reverse

功能描述

  • 将容器内元素进行反转

函数原型

reverse(iterator beg, iterator end);

参数:

  • beg:开始迭代器
  • end:结束迭代器

4、 常用拷贝和替换算法

学习目标

  • 掌握常用的拷贝和替换算法

算法简介

copy();  // 容器内指定范围内的元素拷贝到另一个容器中replace();  // 将容器内指定范围的旧元素修改为新元素replace_if();  // 容器内指定范围满足条件的元素替换为新元素swap();  // 互换两个容器的元素

4.1 copy

功能描述:

  • 容器内指定范围的元素拷贝到另一个容器中

函数原型

copy(iterator beg, iterator end, iterator dest);

按值查找元素,找到返回指定位置迭代器,找不到返回结束迭代器位置

需要先预定目标容器的空间

参数:

  • beg:开始迭代器
  • end:结束迭代器
  • dest:目标容器起始迭代器

4.2 replace

功能描述

  • 将容器内指定范围内的旧元素修改为新元素

函数原型

replace(iterator beg, iterator end, oldvalue, newvalue);

它会替换区间内所有满足条件的元素

参数:

  • beg:起始迭代器
  • end:结束迭代器
  • oldvalie:旧元素
  • newvalue:新元素

4.3 replace_if

功能用法

  • 将区间内满足条件的元素,替换成特定元素

函数原理

replace_if(iterator beg, iterator end, _Pred, newvalue);

它会替换区间内所有满足条件的元素

参数:

  • beg:起始迭代器
  • end:结束迭代器
  • _Pred:谓词
  • newvalue:替换的新元素

4.4 swap

功能描述:

  • 互换两个容器的元素

函数原型

swap(container c1, container c2);

同种数据类型的容器才能互换

参数:

  • c1:容器1
  • c2:容器2

5、 常用算术生成算法

学习目标

  • 掌握常用的算术生成算法

注意:

  • 算术生成算法属于小型算法,使用时需要包含的头文件为#include <numeric>

算法简介

accumulate();  // 计算容器元素累计总和fill();  // 向容器中添加元素

5.1 accumulate

功能描述:

  • 计算区间内元素的总和

函数原型

accumulate(iterator beg, iterator end, firstValue);

参数:

  • beg:起始迭代器
  • end:结束迭代器
  • firstValue:起始累加值

5.2 fill

功能描述:

  • 向容器中填充指定的元素

函数原型

fill(iterator beg, iterator end, value);

参数:

  • beg:开始迭代器
  • end:结束迭代器
  • value:填充的值

6、 常用集合算法

学习目标:

  • 掌握常用的集合算法

算法简介

set_insersection();  // 求两个容器的交集set_union();  // 求两个容器的并集set_different();  // 求两个容器的差集

6.1 set_insersection

功能描述:

  • 求两个容器的交集,重复的元素

函数原型

iterator itEnd = set_insersection(iterator beg1, iterator end1, iterator beg2, iterator end2, iterator dest);  // 交集,返回最后一个值的迭代器

需要提前开辟空间,最特殊的情况:大容器包含小容器,开辟空间,取小容器的 size 即可

dest.resize(c1.size() > c2.size() ? c2.size() : c1.size());

参数:

  • beg1:容器1开始迭代器
  • end1:容器1结束迭代器
  • beg2:容器2开始迭代器
  • end2:容器2结束迭代器
  • dest:目标容器开始迭代器

6.2 set_union

功能描述:

  • 求两个容器的并集

函数原型

iterator itEnd = set_insersection(iterator beg1, iterator end1, iterator beg2, iterator end2, iterator dest);  // 并集,返回最后一个值的迭代器

目标容器要提前开辟空间,最特殊的情况是两个容器没有交集

dest.resize(c1.size() + c2.size());

参数:

  • beg1:容器1开始迭代器
  • end1:容器1结束迭代器
  • beg2:容器2开始迭代器
  • end2:容器2结束迭代器
  • dest:目标容器开始迭代器

6.3 set_difference

功能描述:

  • 求两个容器的差集

函数原型

iterator itEnd = set_insersection(iterator beg1, iterator end1, iterator beg2, iterator end2, iterator dest);  // c1 和 c2 的差集,返回最后一个值的迭代器,c1 - c2

目标容器要提前开辟空间,最特殊的情况是两个容器没有交集,取 size 大的作为容器的空间

dest.resize(c1.size() > c2.size() ? c1.size() : c2.size());  // 也可以使用 max(c1.size(), c2.size());

参数:

  • beg1:容器1开始迭代器
  • end1:容器1结束迭代器
  • beg2:容器2开始迭代器
  • end2:容器2结束迭代器
  • dest:目标容器开始迭代器

本文来自博客园,作者:A-L-Kun,转载请注明原文链接:https://www.cnblogs.com/liuzhongkun/p/15910727.html

posted @ 2022-02-18 17:47 A-L-Kun 阅读(1) 评论(0) 编辑 收藏 举报
回帖
    优雅殿下

    优雅殿下 (王者 段位)

    2018 积分 (2)粉丝 (47)源码

    小小码农,大大世界

     

    温馨提示

    亦奇源码

    最新会员