C++ 提高编程
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 普通函数和函数模板的调用规则
调用规则如下
如果函数模板和普通函数都可以实现,优先调用普通函数
可以通过空模板参数列表强制调用函数模板
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); // 空模板参数列表调用模板函数
}
函数模板也可以发生重载
如果函数模板可以产生更好的匹配模式,优先调用函数模板
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 类模板和函数模板的区别
类模板与函数模板区别主要有两点
类模板没有自动类型推导的使用方式
类模板在模板参数列表中可以有默认参数
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 类模板文件编写
掌握类模板成员函数分文件编写产生的问题以及解决方式
问题
- 类模板中成员函数创建时机是在调用阶段,导致文件编写是链接不到
解决
- 直接包含 .cpp 源文件
- 将声明和实现写到同一个文件中,并更改后缀名为 .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* 容器
特点
- string 类内部封装了很多成员方法
- 例如:find, copy, delete, replace, insert
- 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 个字符为字符串 str
string& 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 个字符 c
string& 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); // 尾部插入元素elem
pop_back(); // 删除最后一个元素
insert(const_iterator pos, elem); // 迭代器指向位置 pos 插入元素 elem
insert(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 实现步骤
- 创建五名选手,放到 vector 容器中
- 遍历 vector 容器,取出每名选手,执行 for 循环,可以把 10 个评分存到 deque 容器中
- sort 算法对 deque 容器中分数进行排序,去除最高分和最低分
- deque 容器遍历一遍,累加总分
- 获取平均分
// 选手类
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 实现步骤
- 创建10名员工,放到 vector 中
- 遍历 vector 容器,取出每个员工,进行随机分组
- 分组后,将员工部门编号作为 key,具体员工作为 value,放入到 multimap 容器中
- 分部门显示员工信息
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:结束迭代器
2.4 binary_search
功能描述:
- 查找指定元素是否存在
函数原型
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:目标容器开始迭代器
C++ 提高编程的更多相关文章
- 15个提高编程技巧的JavaScript工具
原文地址:http://www.imooc.com/wenda/detail/243523 JavaScript脚本库是一个预先用JavaScript语言写好的库,它方便了我们开发基于JavaScri ...
- 17款提高编程效率的css工具
参看下面链接:<17款提高编程效率的css工具>
- 提高编程能力的7条建议 分类: T_TALENT 2014-04-12 10:41 294人阅读 评论(0) 收藏
编程是非常酷的一件事情,但是在酷炫的背后它对很多人来说还是挺难的.很多人在学习编程之初就被困难击败了. 当你不熟悉编程的时候,你可能会觉得无从下手,并且不知道如何运用学到的知识.只要你通过了这一困难的 ...
- PHP提高编程效率的方法,你知道多少呢?
PHP语言是最WEB的计算机语言,而且也是应用最广泛的语言,那么PHP对编程的影响有多大呢?下面可以去了解一下. 用单引号代替双引号来包含字符串,这样做会更快一些.因为PHP会在双引号包围的字符串中搜 ...
- Visual Studio 如何使用代码片段Code Snippet提高编程速度!!!
使用Code Snippet简化Coding 在开发的项目的时候,你是否经常遇到需要重复编写一些类似的代码,比如是否经常会使用 for.foreach ? 在编写这两个循环语句的时候,你是一个字符 ...
- Qtcreator中常用快捷键总结(有必要牢记,提高编程效率)
F1 查看帮助F2 跳转到函数定义(和Ctrl+鼠标左键一样的效果)Shift+F2 声明和定义之间切换F4 头文件和源文件之间切换Ctrl+1 ...
- Eclipse:设置自动补全,提高编程效率
一.设置自动补全 1.进入eclipse的window里的perferences页面 2.找到java->Editor->Content Assist设置界面 3.在Auto activa ...
- Cats(3)- freeK-Free编程更轻松,Free programming with freeK
在上一节我们讨论了通过Coproduct来实现DSL组合:用一些功能简单的基础DSL组合成符合大型多复杂功能应用的DSL.但是我们发现:cats在处理多层递归Coproduct结构时会出现编译问题.再 ...
- 【转】高效Java编程工具集锦
原文地址:http://geek.csdn.net/news/detail/57469 Java 开发者常常都会想办法如何更快地编写 Java 代码,让编程变得更加轻松.目前,市面上涌现出越来越多的高 ...
随机推荐
- 痞子衡嵌入式:我入选了2021年度与非网(eefocus)星选创作者Top10
本周二「与非网」一个美女运营小姐姐加痞子衡微信,告知痞子衡评上了一个奖,让痞子衡把收件地址告诉她,她把证书寄过来. 昨天痞子衡收到了快递,拆开一看,原来是被评上了 与非网 2021 年度创作者,这个证 ...
- JavaWeb中Session会话管理,理解Http无状态处理机制
注:图片如果损坏,点击文章链接:https://www.toutiao.com/i6512955067434271246/ 1.<Servlet简单实现开发部署过程> 2.<Serv ...
- 使用Swing的GUI编程
Swing AWT概述 AWT:抽象窗口工具包,提供了一套与本地图形界面进行交互的接口,是Java提供的用来建立和设置Java的图形用户界面的基本工具 Swing以AWT为基础的,尽管Swing消除了 ...
- 痞子衡嵌入式:在IAR开发环境下将整个源文件代码重定向到任意RAM中的方法
大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家分享的是在IAR开发环境下将整个源文件代码重定向到任意RAM中的方法. 痞子衡旧文 <在IAR下将关键函数重定向到RAM中执行的方法> ...
- Nginx高级模块学习
Nginx的rewrite规则 实现url重写一级重定向 使用场景: 1.URL访问跳转,支持开发设计 页面跳转.兼容性支持.展示效果 2.SEO优化 3.维护 后台维护.流量转发等 4.安全 配置语 ...
- http://dl-ssl.google.com/android上不去解决方案
转:https://blog.csdn.net/j04110414/article/details/44149653/ 一. 更新sdk,遇到了更新下载失败问题: Fetching https://d ...
- day7 对字母数字的编排
1.函数fun()的功能:在s数组内寻找并且统计t数组在s数组中出现的次数 输入字符串:函数scanf()函数不好用,会出现很多bug,因此不使用scanf() 效果理想: 2.函数fun()功能:将 ...
- Three.js之绘制物体的边框及修改lineWidth
本博文主要记录如何使用three.js绘制物体的边框及修改其lineWidth.three.js是个技术点比较多,查询资料又比较少的框架,单单就这个修改lineWidth就是一个坑.先放一个动态的效果 ...
- Api自动生成
如果经常对接api, 可以自己写一个自动化生成代码,提高效率 只抛出一个思路,暂不提供源码 使用json+字符串处理+生成文件 发送一个请求,返回字符串转换为 Newtonsoft.Json.Linq ...
- NOIP2020 排水系统
几度欲写,却望高精而却步,今习得__int128,君子报仇,一年不晚. NOIP2020 排水系统 DAG图,拓扑就好,核心难点在于毒瘤的分数的操作,毕竟只是T!只有分数相加,就很简单了. a/b + ...