1. 模板
模板就是建立通用的模具,大大提高复用性
特点:
- 模板不可以直接使用,它只是一个框架
- 模板的通用并不是万能的
C++提供两种模板机制:函数模板和类模板
1.1 函数模板
函数模板作用:建立一个通用函数,其函数返回值类型和形参类型可以不具体制定,用一个虚拟的类型来代表
语法:
template <typename T> 或 template <class T>
函数声明或定义
解释:
template:声明创建模板(关键字)
typename:表明其后面的符号是一种数据类型,可以用class代替(关键字)
T:通用的数据类型,名称可以替换,通常为大写字母
如实现两个数交换的函数模板:
template<typename T>
void Swap(T& a, T& b) {
T temp = a;
a = b;
b = temp;
}
两种方式使用函数模板
1.自动类型推导 Swap(a, b);
2.显示指定类型 Swap<int>(a, b);
函数模板注意事项:
- 自动类型推导,必须推导出一致的数据类型T,才可以使用
- 模板必须要确定出T的数据类型,才可以使用
确定T的数据类型:
如定义一个函数模板 template <typename T> void func () { },func()中没有形参,无法确定T,直接调用func();会报错,可以确定函数的数据类型,正常调用为func<int>( );
普通函数和函数模板的区别:
- 普通函数调用时可以发生自动类型转换(隐式类型转换)
- 函数模板调用时,如果利用自动类型推导,不会发生隐式类型转换
- 如果利用显示指定类型的方式,可以发生隐式类型转换
普通函数与函数模板的调用规则:
- 如果函数模板和普通函数都可以实现,优先调用普通函数
- 可以通过空模板参数列表来强制调用函数模板 函数名称 < > (形参);
- 函数模板也可以发生重载
- 如果函数模板可以产生更好的匹配,优先调用函数模板
模板也拥有局限性,即模板的通用性并不是万能的,即无法直接实现数组的比对,如a=b;因此,为了解决这种问题,提供模板的重载,可以为这些特定的类型提供具体化的模板
利用具体化的模板,可以解决自定义类型的通用化
学习模板并不是为了写模板,而是在STL能够运用系统提供的模板
1.2 类模板
类模板作用:建立一个通用类,类中的成员数据类型可以不具体制定,用一个虚拟的类型来代表
语法:
template <typename T> 或 template <class T>
类
解释:
template:声明创建模板(关键字)
typename:表明其后面的符号是一种数据类型,可以用class代替(关键字)
T:通用的数据类型,名称可以替换,通常为大写字母
如定义一个人的类模板
template <class NameType, class AgeType> //成员中需要几种类型就定义几种类型
class Person{
public:
Person(){
this.m_Name=name;
this.m_Age=age;
}
NameType m_Name;
AgeType m_Age;
}
后续创建时 Person<string,int> p1("姓名",999);
类模板与函数模板的区别:
- 类模板没有自动类型推导的使用方式
- 类模板在模板参数列表中可以有默认参数
类模板在模板参数列表中可以有默认参数,如template <class NameType, class AgeType = int>,在后面定义时 Person<string> p1("姓名",999) 可以省去第二种数据类型
类模板中成员函数和普通类中成员函数创建时机:
- 普通类中的成员函数一开始就可以创建
- 类模板中的成员函数在调用时才创建
类模板对象做函数参数,一共有三种传入方式:
- 指定传入的类型 - 直接显示对象的数据类型(最常用)
- 参数模板化 - 将对象中的参数变为模板进行传递
- 整个类模板化 - 将这个对象类型模板化进行传递
查看类模板中的成员类型,使用 typeid(T).name() 可以查看
例子(传入方式):
#include<iostream>
using namespace std;
template<class T1,class T2>
class Person {
public:
Person(T1 name, T2 age) {
m_Name = name;
m_Age = age;
}
void showPerson() {
cout << "姓名:" << m_Name << " " << "年龄:" << m_Age << endl;
}
T1 m_Name;
T2 m_Age;
};
//1.指定传入类型
void printPerson1(Person<string, int>& p) {
p.showPerson();
}
void test01() {
Person<string, int> p("孙悟空", 999);
printPerson1(p);
}
//2.参数模板化
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;
}
void test02() {
Person<string, int> p("猪八戒", 989);
printPerson2(p);
}
//3.整个类模板化
template<class T>
void printPerson3(T& p) {
p.showPerson();
cout << "T的类型为: " << typeid(T).name() << endl;
}
void test03() {
Person<string, int> p("唐僧", 98);
printPerson3(p);
}
int main() {
test01();
test02();
test03();
system("pause");
return 0;
}
当类模板碰到继承时,需要注意:
- 当子类继承的父类是一个类模板时,子类在声明时,要指定出父类中T的类型
- 如果不指定,编译器无法给子类分配内存
- 如果想灵活指定出父类中T的类型,子类也需变为类模板
例如:
template<class T>
class Base {
T m;
};
class Son :public Base<int> { //指定出父类中T的类型
};
template<class T1,class T2>
class Son1 :public Base<T2> { // 子类也变为类模板
T1 obj;
};
.h文件和.cpp文件中的内容可以写在一起,后缀名为.hpp,约定俗称为类模板文件
类模板与友元:
- 全局函数类内实现 - 直接在类内声明友元即可
- 全局函数类外实现 - 需要提前让编译器知道全局函数的存在
类模板案例:实现一个通用数组类,要求为:
- 可以对内置数据类型以及自定义数据类型的数据进行存储
- 将数组中的数据存储到堆区
- 构造函数中可以传入数组的容量
- 提供对应的拷贝构造函数以及operator=防止浅拷贝问题
- 提供尾插法和尾删法对数组中的数据进行增加和删除
- 可以通过下标的方式访问数组中的元素
- 可以获取数组中当前元素个数和数组的容量
实现:
#include<iostream>
using namespace std;
template<class T>
class MyArray {
public:
MyArray(int capacity) {
//cout << "有参构造函数" << endl;
this->m_Capacity = capacity;
this->m_Size = 0;
this->Paddress = new T[this->m_Capacity];
}
MyArray(const MyArray& arr) {
//cout << "拷贝构造函数" << endl;
this->m_Capacity = arr.m_Capacity;
this->m_Size = arr.m_Size;
this->Paddress = new T[this->m_Capacity];
for (int i = 0; i < this->m_Size; i++) {
this->Paddress[i] = arr.Paddress[i];
}
}
MyArray& operator = (const MyArray & arr){
//cout << "=运算符重载" << endl;
if (this->Paddress != NULL) {
delete[] this->Paddress;
this->m_Capacity = 0;
this->m_Size = 0;
}
this->m_Capacity = arr.m_Capacity;
this->m_Size = arr.m_Size;
this->Paddress = new T[this->m_Capacity];
for (int i = 0; i < this->m_Size; i++) {
this->Paddress[i] = arr[i];
}
return *this;
}
T& operator[](int index) {
return this->Paddress[index];
}
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--;
}
int GetCapacity() {
return this->m_Capacity;
}
int GetSize() {
return this->m_Size;
}
~MyArray() {
//cout << "析构函数" << endl;
if (this->Paddress != NULL) {
delete[] this->Paddress;
this->Paddress = NULL;
this->m_Capacity = 0;
this->m_Size = 0;
}
}
private:
T* Paddress;
int m_Capacity;
int m_Size;
};
void printIntArray(MyArray<int>& arr) {
for (int i = 0; i < arr.GetSize(); i++) {
cout << arr[i] << " ";
}
cout << endl;
}
void test01() {
MyArray<int> arr1(5);
for (int i = 0; i < 5; i++) {
arr1.Push_Back(i);
}
cout << "arr1的打印输出为:" << endl;
printIntArray(arr1);
cout << "arr1的容量为:" << arr1.GetCapacity() << endl;
cout << "arr1的大小为:" << arr1.GetSize() << endl;
cout << "_____________________________" << endl;
MyArray<int> arr2(arr1);
arr2.Pop_Back();
cout << "arr2的打印输出为:" << endl;
printIntArray(arr2);
cout << "arr2的容量为:" << arr2.GetCapacity() << endl;
cout << "arr2的大小为:" << arr2.GetSize() << endl;
cout << "_____________________________" << endl;
/*MyArray<int> arr2(arr1);
MyArray<int> arr3(100);
arr3 = arr1;*/
}
class Person {
public:
Person() {}
Person(string name, int age) {
this->m_Name = name;
this->m_Age = age;
}
string m_Name;
int m_Age;
};
void printPersonArray(MyArray<Person> & arr) {
for (int i = 0; i < arr.GetSize(); i++) {
cout << "姓名:" << arr[i].m_Name << " " << "年龄:" << arr[i].m_Age << endl;
}
}
void test02() {
MyArray<Person> arr(10);
Person p1("Tom", 99);
Person p2("Coy", 93);
Person p3("Li", 85);
Person p4("VOU", 103);
Person p5("CCC", 166);
arr.Push_Back(p1);
arr.Push_Back(p2);
arr.Push_Back(p3);
arr.Push_Back(p4);
arr.Push_Back(p5);
printPersonArray(arr);
cout << "arr的容量为:" << arr.GetCapacity() << endl;
cout << "arr的大小为:" << arr.GetSize() << endl;
}
int main() {
test01();
test02();
system("pause");
return 0;
}
2. STL - 初识
C++的面向对象(封装,继承,多态)和泛型编程(模板)思想,目的就是复用性的提升
为了建立数据结构和算法的一套标准,诞生了STL
2.1 STL基本概念
STL:Standard Template Library,标准模板库
STL从广义上分为:容器(container),算法(algorithm),迭代器(iterator)
容器和算法之间通过迭代器进行无缝连接
STL几乎所有的代码都采用了模板类或者模板函数
2.2 STL六大组件
STL的六大组件为:容器,算法,迭代器,仿函数,适配器(配接器),空间配置器
- 容器:各种数据结构,如vector,list,deque,set,map等,用来存放数据
- 算法:各种常用算法,如sort,find,copy,for_each等
- 迭代器:扮演了容器与算法之间的胶合剂
- 仿函数:行为类似函数,可作为算法的某种策略
- 适配器:一种用来修饰容器或者仿函数或者迭代器接口的东西
- 空间配置器:负责空间的配置与管理
2.2.1 容器
STL容器就是将运用最广泛的一些数据结构实现出来
常用的数据结构:数组,链表,树,栈,队列,集合,映射表等
容器分为序列式容器和关联式容器:
- 序列式容器:强调值得排序,序列式容器中的每个元素均有固定的位置
- 关联式容器:二叉树结构,各元素之间没有严格的物理上的顺序关系
2.2.2 算法
有限的步骤,解决逻辑或数学上的问题,叫做算法
算法分为质变算法和非质变算法
- 质变算法:是指运算过程中会更改区间内的元素的内容,如拷贝,替换,删除等
- 非质变算法:是指运算过程中不会更改区间内的元素内容,如查找,计数,遍历,寻找极值等
2.2.3 迭代器
算法要通过迭代器才能访问容器中的元素
提供一种方法,使之能够依序寻访某个容器所含的各个元素,而又无需暴露该容器的内部表示方式
每个容器都有自己专属的迭代器
迭代器使用非常类似指针
常用的容器中迭代器种类为双向迭代器和随机访问迭代器
- 双向迭代器:功能为读写操作,并能向前和向后操作,支持运算为读写,支持++,--
- 随机访问迭代器:功能为读写操作,可以以跳跃的方式访问任意数据,功能最强的迭代器,支持运算为读写,支持++,--,[n],-n,<,<=,>,>=
3. STL- 常用容器
3.1 string容器
string本质上是一个类,类内部封装了char*,是一个char*型的容器。string管理char*所分配的内存,不用担心复制越界和取值越界等,由类内部进行负责
string内部封装了很多成员方法,例如查找find,拷贝copy,删除delete,替换replace,插入insert
3.1.1 string构造函数
举例四种初始化方法:
void test01() {
string s1; //默认构造
const char* str = "hello world";
string s2(str);
string s3(s2);
string s4(10, 'c'); //使用n个字符c初始化
}
3.1.2 string赋值操作
一种是等号的赋值方式,一种是assign的赋值方式,赋值例子如下:
有一种方法为assign()方法
void test01() {
string str1;
str1 = "hello world";
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); //获取字符串的前5个字符并赋给当前字符串,即str5为hello
cout << str5 << endl;
string str6;
str6.assign(str5);
string str7;
str7.assign(10, 'w'); //用n个字符c赋给当前字符串
cout << str7 << endl;
}
3.1.3 string字符串拼接
实现在字符串末尾拼接字符串,拼接例子如下:
有一种方法为append()方法
void test01() {
string str1="我";
str1 += "爱study";
cout << str1 << endl;
str1 += ':';
string str2 = "lol";
str1 += str2;
cout << str1 << endl;
string str3 = "I";
str3.append(" love "); //把字符串love连接到当前字符串结尾
cout << str3 << endl;
str3.append("youabcde", 4); //把字符串game abcde的前4个字符连接到当前字符串结尾
cout << str3 << endl;
str3.append(str2);
cout << str3 << endl;
str3.append(str2, 0, 2); // 参数2表示的是从哪个位置开始截取,参数3表示的是截取字符个数
cout << str3 << endl;
}
3.1.4 string查找和替换
查找:查找指定字符串是否存在
替换:在指定的位置替换字符串
查找的例子如下:
查找的方法为find()方法,rfind()方法
void test01() {
//find()方法
string str1="abcdefgjkloputde";
int pos = str1.find("de");
if (pos == -1) { // find方法,若返回-1,代表字符串中没有找到想要的字符串
cout << "未找到" << endl;
}
else {
//pos = 3;
cout << pos << endl; // find方法,索引位置从0开始,返回找到的第一个字符的位置
}
//rfind方法
pos = str1.rfind("de");
cout << pos << endl; // pos = 14;
}
find()方法和rfind()方法的区别为:find方法是从左往右查找,rfind方法是从右往左查找,也可以理解为find是查找字符串第一次出现的位置,rfind是查找字符串最后一次出现的位置
替换的例子如下:
替换的方法为replace()方法
void test02() {
string str1 = "abcdefg";
str1.replace(1, 3,"1111"); // 从str1的1号字符起,3个字符替换为"1111"
cout << str1 << endl; // str1 = a1111efg
}
3.1.5 string字符串比较
字符串比较是按字符的ASCII码进行对比,相等返回0,大于返回1,小于返回-1
字符串比较的例子如下:
比较的方法为compare()方法
void test01() {
string str1 = "xello";
string str2 = "hello";
if (str1.compare(str2) == 0) {
cout << "相等" << endl;
}
else if (str1.compare(str2) > 0) {
cout << "str1 大于 str2 " << endl;
}
else {
cout << "str1 小于 str2 " << endl;
}
}
3.1.6 string字符存取
存取列子如下:
存取的方法为at()方法
void test01() {
string str = "hello";
//通过 [] 访问单个字符
for (int i = 0; i < str.size(); i++) {
cout << str[i] << " ";
}
cout << endl;
//通过 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;
}
3.1.7 string插入和删除
对字符串进行插入和删除字符操作,例子如下:
插入字符串使用insert()方法,删除使用erase()方法,插入和删除的起始下标都是从0开始
void test01() {
string str = "hello";
str.insert(1, "111"); // 从哪个位置插入什么字符串
cout << str << endl; //str=h111ello
str.erase(1, 3); // 从哪个位置开始,删多少个字符
cout << str << endl;
}
3.1.8 string子串
功能为:从字符串中获取想要的子串
获取子串的例子如下:
获取子串的方法为substr()方法
void test01() {
string str = "abcdef";
string subStr = str.substr(1, 3); //从1号位置开始截取3个字符
cout << subStr << endl; // bcd
}
//实用操作
void test02() {
string email = "zhangsan@sina.com";
//从邮件地址中获取用户名信息
int pos = email.find("@");
string userName = email.substr(0, pos);
cout << userName << endl;
}
3.2 vector容器
使用vector容器要包含头文件 #include<vector>
vector数据结构和数组非常相似,也称为单端数组
vector与普通数组的区别在于:数组是静态空间,而vector可以动态扩展
动态扩展指的是:并不是在原空间之后续接新空间,而是找更大的内存空间,然后将原数据拷贝新空间,释放原空间
vector容器中所用到的方法:
vector容器的迭代器是支持随机访问的迭代器
3.2.1 vector构造函数
void printVector(vector<int> &v) {
for (vector<int>::iterator it = v.begin(); it != v.end(); it++) {
cout << *it << " ";
}
cout << endl;
}
void test01() {
vector<int> v1; // 默认构造 无参构造
for (int i = 0; i < 10; i++) {
v1.push_back(i);
}
printVector(v1);
//通过区间方式进行构造
vector<int> v2(v1.begin(), v1.end());
printVector(v2);
//n个elem方式构造
vector<int> v3(10, 100); //添加10个100
printVector(v3);
//拷贝构造
vector<int> v4(v3);
printVector(v4);
}
3.2.2 vector赋值操作
赋值操作可以使用等号赋值,可以使用assign()方法赋值
void test01() {
vector<int> v1; // 默认构造 无参构造
for (int i = 0; i < 10; i++) {
v1.push_back(i);
}
printVector(v1);
//赋值 opoerator=
vector<int> v2;
v2 = v1;
printVector(v2);
//assign
vector<int> v3;
v3.assign(v1.begin(), v1.end());
printVector(v3);
//n个elem 方式赋值
vector<int> v4;
v4.assign(10, 100);
printVector(v4);
}
3.2.3 vector容量和大小
利用empty()方法判断容器是否为空
利用capacity()方法判断容器的容量
利用size()方法返回容器中元素的个数
利用resize(int num)方法重新指定容器的长度为num,若容器变长,则以默认值0填充新位置;如果容器变短,则末尾超出容器长度的元素被删除
利用resize(int num,elem)方法重新指定容器的长度为num,若容器变长,则以elem值填充新位置;如果容器变短,则末尾超出容器长度的元素被删除
void test01() {
vector<int> 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.capacity() << endl;
cout << v1.size() << endl;
}
//重新指定大小 resize
v1.resize(15);
printVector(v1); //0,1,2,3,4,5,6,7,8,9,0,0,0,0,0
v1.resize(5);
printVector(v1); //0,1,2,3,4
}
3.2.4 vector插入和删除
利用push_back(ele)方法实现在尾部插入元素ele
利用pop_back()方法实现删除最后一个元素
利用insert(const_iterator pos,ele)方法实现向迭代器指向位置pos插入元素ele
利用insert(const_iterator pos,int count,ele)方法实现向迭代器指向位置pos插入count个元素ele
利用erase(const_iterator pos)方法实现删除迭代器指向的元素
利用erase(const_iterator start,const_iterator end)方法实现删除迭代器从start到end之间的元素
利用clear()方法删除容器中所有元素
const_iterator代表的是迭代器
void test01() {
vector<int> v1;
//尾插法
v1.push_back(10);
v1.push_back(20);
v1.push_back(30);
v1.push_back(40);
v1.push_back(50);
printVector(v1);
//尾删法
v1.pop_back();
printVector(v1);
//插入 insert的第一个参数是迭代器
v1.insert(v1.begin(), 100); //v1.begin()指向的是容器中的第一个元素的位置,在第一个位置前插入100
printVector(v1);
v1.insert(v1.begin(), 2, 1000); //增加两个1000
printVector(v1);
//删除
v1.erase(v1.begin());
printVector(v1); // 删除了一个1000
//清空
v1.erase(v1.begin(), v1.end());
// v1.clear();
printVector(v1);
}
3.2.5 vector数据存取
利用at(int idx)方法返回索引idx所指的数据
利用operator[ ] 返回索引idx所指的数据
利用front()方法返回容器中第一个数据元素
利用back()方法返回容器中最后一个数据元素
void test01() {
vector<int> v1;
for (int i = 0; i < 10; i++) {
v1.push_back(i);
}
//利用[]方式访问数组中的元素
for (int i = 0; i < v1.size(); i++) {
cout << v1[i] << " ";
}
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;
}
3.2.6 vector互换容器
实现两个容器内元素进行互换,利用swap(vec)方法将vec与本身的元素互换
void test01() {
vector<int> 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;
v1.swap(v2);
printVector(v1);
printVector(v2);
}
//实际用途:巧用vector可以收缩内存空间
void test02() {
vector<int> v;
for (int i = 0; i < 100000; i++) {
v.push_back(i);
}
cout << v.capacity() << endl;
cout << v.size() << endl;
v.resize(3);
cout << v.capacity() << endl;
cout << v.size() << endl;
//巧用swap收缩内存
vector<int>(v).swap(v);
cout << v.capacity() << endl;
cout << v.size() << endl;
}
3.2.7 vector预留空间
目的是减少vector在动态扩展容量时的扩展次数,利用reserve(int len)方法实现,即容器预留len个元素长度,预留位置不初始化,元素不可访问
void test01() {
vector<int> v;
//利用reserve预留空间
v.reserve(100000);
int num = 0;//统计开辟次数
int* p = NULL;
for (int i = 0; i < 100000; i++) {
v.push_back(i);
if (p != &v[0]) {
p = &v[0];
num++;
}
}
cout << num << endl; //num=1;若不使用reserve方法,则num=30
}
3.3 deque容器
使用时需要加头文件 #include <deque>
deque容器也叫做双端数组,可以对头端进行插入删除操作
deque与vector区别:
- vector对于头部的插入删除效率低,数据量越大,效率越低
- deque相对而言,对头部的插入删除速度会比vector快
- vector访问元素时的速度会比deque快,这和两者内部实现有关
- deque中没有capacity,即容量这个概念
deque容器中所用到的方法:
deque内部工作原理:
- deque内部有个中控器,维护每段缓冲区中的内容,缓冲区中存放真实数据
- 中控器维护的是每个缓冲区的地址,使得使用deque时像一片连续的内存空间
deque容器的迭代器也是支持随机访问的
3.3.1 deque构造函数
void printDeque(const deque<int> &d) {
for (deque<int>::const_iterator it = d.begin(); it != d.end(); it++) {
cout << *it << " ";
}
cout << endl;
}
void test01() {
deque<int> d1;
for (int i = 0; i < 10; i++) {
d1.push_back(i);
}
printDeque(d1);
deque<int> d2(d1.begin(), d1.end());
printDeque(d2);
deque<int> d3(10, 100);
printDeque(d3);
deque<int> d4(d3);
printDeque(d4);
}
3.3.2 deque赋值操作
利用等号赋值或者使用assign()方法进行赋值
void test01() {
deque<int> d1;
for (int i = 0; i < 10; i++) {
d1.push_back(i);
}
printDeque(d1);
//等号赋值
deque<int> d2;
d2 = d1;
printDeque(d2);
//assign赋值
deque<int> d3;
d3.assign(d1.begin(), d1.end());
d3.assign(10, 100);
printDeque(d3);
}
3.3.3 deque大小操作
利用empty()方法判断容器是否为空
利用size()方法返回容器中元素的个数
利用resize(num)方法重新指定容器的长度为num,若容器变长,则以默认值0填充新位置;如果容器变短,则末尾超出容器长度的元素被删除
利用resize(int num,elem)方法重新指定容器的长度为num,若容器变长,则以elem值填充新位置;如果容器变短,则末尾超出容器长度的元素被删除
写法和vector类似
3.3.4 deque插入和删除
两端插入操作:
利用push_back(elem)方法实现在尾部插入元素elem
利用push_front(elem)方法实现在头部插入元素elem
利用pop_back()方法实现删除最后一个元素
利用pop_front()方法实现删除第一个元素
指定位置操作:
利用insert(const_iterator pos,ele)方法实现向迭代器指向位置pos插入元素ele,返回新数据的位置
利用insert(const_iterator pos,int count,ele)方法实现向迭代器指向位置pos插入count个元素ele,无返回值
利用insert(const_iterator start pos,const_iterator start beg,const_iterator start end)方法实现在pos位置插入[beg,end)区间的数据,无返回值
利用erase(const_iterator pos)方法实现删除迭代器指向的元素,返回下一个数据的位置
利用erase(const_iterator start,const_iterator end)方法实现删除迭代器从start到end之间的元素,返回下一个数据的位置
利用clear()方法删除容器中所有元素
void test01() {
deque<int> d1;
//尾插
d1.push_back(10);
d1.push_back(20);
//头插
d1.push_front(100);
d1.push_front(200);
printDeque(d1);
//尾删
d1.pop_back();
printDeque(d1);
//头删
d1.pop_front();
printDeque(d1);
}
void test02() {
deque<int> d2;
d2.push_back(10);
d2.push_back(20);
d2.push_front(100);
d2.push_front(200);
printDeque(d2);
//insert方式
d2.insert(d2.begin(), 1000);
printDeque(d2);
d2.insert(d2.begin(), 2,10000);
printDeque(d2);
d2.insert(d2.begin(), d2.begin(), d2.end());
printDeque(d2);
//删除
deque<int>::iterator it = d2.begin();
it++;
d2.erase(it);
printDeque(d2);
d2.clear();
printDeque(d2);
}
3.3.5 deque数据存取
利用at(int idx)方法返回索引idx所指的数据
利用operator[ ] 返回索引idx所指的数据
利用front()方法返回容器中第一个数据元素
利用back()方法返回容器中最后一个数据元素
写法和vector容器类似
3.3.6 deque排序
利用sort(iterator beg,iterator end)方法实现对beg和end区间内元素进行排序,需要利用sort算法,要引入头文件#include<algorithm>
void test01() {
deque<int> d1;
//尾插
d1.push_back(10);
d1.push_back(20);
d1.push_back(30);
//头插
d1.push_front(100);
d1.push_front(200);
d1.push_front(300);
printDeque(d1);
sort(d1.begin(),d1.end()); // 默认排序算法为从小到大排序
printDeque(d1);
}
对于支持随机访问的迭代器的容器,都可以利用sort算法直接对其进行排序
3.4 stack容器
使用时需要加头文件 #include <stack>
stack是一种先进后出的数据结构,它只有一个出口
栈中只有顶端的元素才可以被外界使用,因此栈不允许有遍历行为
栈中进入数据称为入栈,push
栈中弹出数据称为出栈,pop
3.4.1 stack常用接口
利用push(elem)方法实现向栈顶添加元素
利用pop()方法实现从栈顶移除第一个元素
利用top()方法实现返回栈顶元素
利用empty()方法判断堆栈是否为空
利用size()方法返回栈的大小
void test01() {
stack<int> s;
//入栈
s.push(10);
s.push(20);
s.push(30);
s.push(40);
//只要栈不为空,就查看栈顶,并执行出栈操作
while (!s.empty()) {
//查看栈顶元素
cout << s.top() << endl;
//出栈
s.pop();
}
cout << s.size() << endl;
}
3.5 queue容器
使用时需要加头文件 #include <queue>
queue是一种先进先出的数据结构,它有两个出口
队列容器允许从一端新增元素,从另一端移除元素
队列中只有对头和队尾才可以被外界使用,因此队列不允许有遍历行为
队列中进数据称为入队,push
队列中出数据称为出队,pop
3.5.1 queue常用接口
利用push(elem)方法实现向队尾添加元素
利用pop()方法实现从对头移除第一个元素
利用back()方法返回最后一个元素
利用front()方法返回第一个元素
利用empty()方法判断队列是否为空
利用size()方法返回队列的大小
class Person {
public:
Person(string name,int age) {
this->m_age = age;
this->m_name = name;
}
int m_age;
string m_name;
};
void test01() {
Person p1("aa", 30);
Person p2("bb", 40);
Person p3("cc", 35);
Person p4("dd", 38);
queue<Person> q;
//入对
q.push(p1);
q.push(p2);
q.push(p3);
q.push(p4);
cout << q.size() << endl;
while (!q.empty()) {
//查看对头
cout << q.front().m_age << " " << q.front().m_name << endl;
//查看队尾
cout << q.back().m_age << " " << q.back().m_name << endl;
//出队
q.pop();
}
cout << q.size() << endl;
}
3.6 list容器
使用时需要加头文件 #include <list>
list的功能是将数据进行链式存储
链表(list)是一种物理存储单元上非连续的存储结构,数据元素的逻辑顺序是通过链表中的指针链接实现的
链表由一系列结点组成,结点由两部分组成,一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域
STL中的链表是一个双向循环链表
链表中所包含的方法:
由于链表的存储方式并不是连续的内存空间,因此链表list中的迭代器只支持前移和后移,属于双向迭代器
list的优点:
- 采用动态存储分配,不会造成内存浪费和溢出
- 链表执行插入和删除操作十分方便,修改指针即可,不需要移动大量元素
list的缺点:
- 链表灵活,但是空间(指针域)和时间(遍历)额外耗费较大
list有一个重要的性质,插入操作和删除操作都不会造成原有list迭代器的失效,这在vector是不成立的
3.6.1 list构造函数
void test01() {
list<int> L1;//默认构造
//添加数据
L1.push_back(10);
L1.push_back(20);
L1.push_back(30);
L1.push_back(40);
//区间方式构造
list<int> L2(L1.begin(),L1.end());
//拷贝构造
list<int> L3(L2);
//n个elem
list<int> L4(10, 1000);
}
3.6.2 list赋值和交换
利用assign()方法或等号赋值的方式进行赋值,利用swap()方法实现list与本身元素互换
void test01() {
list<int> L1;
L1.push_back(10);
L1.push_back(20);
L1.push_back(30);
L1.push_back(40);
list<int> L2;
L2.assign(L1.begin(), L1.end());
list<int> L3;
L3 = L1; //等号赋值
list<int> L4;
L4.assign(10, 100);
//交换
L1.swap(L4);
printList(L1);
}
3.6.3 list大小操作
利用empty()方法判断容器是否为空
利用size()方法返回容器中元素的个数
利用resize(num)方法重新指定容器的长度为num,若容器变长,则以默认值0填充新位置;如果容器变短,则末尾超出容器长度的元素被删除
利用resize(int num,elem)方法重新指定容器的长度为num,若容器变长,则以elem值填充新位置;如果容器变短,则末尾超出容器长度的元素被删除
写法和vector,deque的类似
3.6.4 list插入和删除
利用push_back(elem)方法实现在尾部插入元素elem
利用push_front(elem)方法实现在头部插入元素elem
利用pop_back()方法实现删除最后一个元素
利用pop_front()方法实现删除第一个元素
利用insert(const_iterator pos,ele)方法实现向迭代器指向位置pos插入元素ele,返回新数据的位置
利用insert(const_iterator pos,int count,ele)方法实现向迭代器指向位置pos插入count个元素ele,无返回值
利用insert(const_iterator start pos,const_iterator start beg,const_iterator start end)方法实现在pos位置插入[beg,end)区间的数据,无返回值
利用erase(const_iterator pos)方法实现删除迭代器指向的元素,返回下一个数据的位置
利用erase(const_iterator start,const_iterator end)方法实现删除迭代器从start到end之间的元素,返回下一个数据的位置
利用clear()方法删除容器中所有元素
利用remove(elem)方法删除容器中所有与elem值匹配的元素
写法和deque类似
3.6.5 list数据存取
利用front()方法返回容器中第一个数据元素
利用back()方法返回容器中最后一个数据元素
不可以用 [ ] 和 at 方式访问list容器中的元素,原因是list本质是链表,不是用连续线性空间存储数据,迭代器也是不支持随机访问的
void test01() {
list<int> L1;
L1.push_back(10);
L1.push_back(20);
L1.push_front(100);
L1.push_front(200);
cout << L1.front() << endl;
cout << L1.back() << endl;
//验证迭代器是否支持随机访问
list<int>::iterator it = L1.begin();
it++; //支持双向
it--;
//it = it + 1; //报错,不支持随机访问
}
3.6.6 list反转和排序
利用reverse()方法实现反转链表,将容器中的元素反转
利用sort()方法实现对链表中的数据的排序,使用时包含头文件#include<algorithm>
//实现降序
bool myCompare(int v1, int v2) {
return v1 > v2;
}
void test01() {
list<int> L1;
L1.push_back(10);
L1.push_back(20);
L1.push_front(100);
L1.push_front(200);
L1.push_front(50);
printList(L1);
L1.reverse(); //反转
printList(L1);
L1.sort(); //升序 sort是成员函数
printList(L1);
L1.sort(myCompare); //降序
printList(L1);
}
所有不支持随机访问迭代器的容器,不可以用标准算法,内部会提供对应一些算法
案列:将Person自定义数据类型进行排序,Person中属性有姓名,年龄,身高,排序规则为按年龄进行升序,如果年龄相同按照身高进行降序
#include<iostream>
using namespace std;
#include<list>
class Person {
public:
Person(string name, int age, int height) {
this->m_name = name;
this->m_age = age;
this->m_height = height;
}
string m_name;
int m_age;
int m_height;
};
bool compare_age_height(Person &p1,Person &p2){
if (p1.m_age == p2.m_age) {
return p1.m_height > p2.m_height;
}
return p1.m_age < p2.m_age;
}
void printList(list<Person>& L) {
for (list<Person>::iterator it = L.begin(); it != L.end(); it++) {
cout << "姓名:" << it->m_name << " 年龄:" << it->m_age << " 身高:" << it->m_height <<endl;
}
cout << endl;
}
void test01() {
list<Person> L;
Person p1("aa", 20, 181);
Person p2("bb", 20, 184);
Person p3("cc", 25, 175);
Person p4("dd", 26, 173);
Person p5("ee", 18, 172);
Person p6("ff", 18, 190);
L.push_back(p1);
L.push_back(p2);
L.push_back(p3);
L.push_back(p4);
L.push_back(p5);
L.push_back(p6);
cout << "排序前:" << endl;
printList(L);
cout << "排序后:" << endl;
L.sort(compare_age_height);
printList(L);
}
int main() {
test01();
system("pause");
return 0;
}
3.7 set/multiset容器
使用时包含头文件 #include<set>,既可以使用set也可以使用multiset
set容器特点:所有元素都会在插入时自动被排序
set/multiset属于关联式容器,底层结构是用二叉树实现
set和multiset区别:
- set不允许容器中有重复的元素
- multiset允许容器中有重复的元素
3.7.1 set构造和赋值
只能利用insert()方法进行数据的插入
void test01() {
set<int> s1;
s1.insert(10);
s1.insert(40);
s1.insert(30);
s1.insert(20);
s1.insert(30);
printSet(s1); // 结果为 10,20,30,40
//拷贝构造
set<int> s2(s1);
printSet(s2);
//赋值
set<int>s3;
s3 = s2;
printSet(s3);
}
3.7.2 set大小和交换
利用size()方法返回容器中元素的数目
利用empty()方法判断容器是否为空
利用swap(st)方法交换两个集合容器
void test01() {
set<int> s1;
s1.insert(10);
s1.insert(40);
s1.insert(30);
s1.insert(20);
s1.insert(30);
printSet(s1); // 结果为 10,20,30,40
//大小
cout << s1.size() << endl;
set<int> s2;
s2.insert(100);
s2.insert(200);
//交换两个容器
s1.swap(s2);
cout << s1.size() << endl; // 2
printSet(s1); // 100,200
}
3.7.3 set插入和删除
利用insert(elem)方法在容器中插入元素elem
利用erase(const_iterator pos)方法实现删除迭代器指向的元素,返回下一个元素的迭代器
利用erase(const_iterator start,const_iterator end)方法实现删除迭代器从start到end之间的元素,返回下一个元素的迭代器
利用erase(elem)方法删除容器中值为elem的元素
利用clear()方法删除容器中所有元素
方法和前面容器的类似
3.7.4 set查找和统计
利用find(key)方法查找key是否存在,若存在,返回该键的元素的迭代器;若不存在,返回set.end(),即返回元素结束的位置
利用count(key)方法统计key的元素个数
void test01() {
set<int> s1;
s1.insert(10);
s1.insert(40);
s1.insert(30);
s1.insert(20);
s1.insert(30);
printSet(s1); // 结果为 10,20,30,40
//统计个数
cout << s1.count(30) << endl; // 1
//查找
set<int>::iterator pos = s1.find(40);
if (pos != s1.end()) {
cout << "找到元素为:" << *pos << endl;
}
else {
cout << "未找到" << endl;
}
}
3.7.5 set和multiset区别
区别:
- set不可以插入重复数据,而multiset可以
- set插入数据的同时会返回插入结果,表示插入是否成功
- multiset不会检测数据,因此可以插入重复数据
void test01() {
set<int> s1;
pair<set<int>::iterator, bool> ret = s1.insert(10);
if (ret.second) { // ret的第二个元素
cout << "第一次插入成功" << endl;
}
else {
cout << "第一次插入失败" << endl;
}
pair<set<int>::iterator, bool> ret1 = s1.insert(10);
if (ret1.second) {
cout << "第二次插入成功" << endl;
}
else {
cout << "第二次插入失败" << endl;
}
multiset<int> ms;
ms.insert(10);
ms.insert(10);
printSet(ms); // 10,10
}
3.7.6 pair对组创建
成对出现的数据,利用对组可以返回两个数据
两种创建方式:
- pair<type, type> p (value1, value2);
- pair<type, type> p = make_pair (value1, value2);
访问对组中第一个元素调用first,访问第二个元素调用second
void test01() {
//第一种方式
pair<string, int> p("Tom", 20);
cout << "姓名:" << p.first << " 年龄:" << p.second << endl;
//第二种方式
pair<string, int> p2 = make_pair("Jerry", 30);
cout << "姓名:" << p2.first << " 年龄:" << p2.second << endl;
}
3.7.7 set容器排序
set容器默认排序规则为从小打大,可以利用仿函数,改变排序规则
set存放内置数据类型,修改排序规则:
class MyCompare { // 实现降序
public:
bool operator()(int v1, int v2) const{
return v1 > v2;
}
};
void test01() {
set<int> s1;
s1.insert(10);
s1.insert(50);
s1.insert(20);
s1.insert(40);
s1.insert(30);
printSet(s1); //10 20 30 40 50
set<int, MyCompare> s2;
s2.insert(10);
s2.insert(50);
s2.insert(20);
s2.insert(40);
s2.insert(30);
for (set<int, MyCompare>::iterator it = s2.begin(); it != s2.end(); it++) {
cout << *it << " "; //50 40 30 20 10
}
cout << endl;
}
set存放自定义数据类型,修改排序规则(自定义数据类型,都会指定排序规则):
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 &p1, const Person &p2) const{
return p1.m_age>p2.m_age;
}
};
void test01() {
set<Person,MyCompare> s;
Person p1("aa", 24);
Person p2("bb", 28);
Person p3("cc", 25);
Person p4("dd", 21);
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_name << " 年龄:" << it->m_age << endl;
}
cout << endl;
}
3.8 map/multimap容器
使用时包含头文件 #include<map>
map中所有元素都是pair(对组)
pair中第一个元素为key(键值),起到索引作用,第二个元素为value(实值)
所有元素都会根据元素的键值自动排序,可以根据键值快速找到value值
map/multimap属于关联式容器,底层结构是用二叉树实现
map和multimap区别:
- map不允许容器中有重复的key值元素
- multimap允许容器中有重复key值元素
3.8.1 map构造和赋值
void printMap(map<int, int>& m) {
for (map<int, int>::iterator it = m.begin(); it != m.end(); it++) {
cout << "key: " << it->first << " value: " << it->second <<endl;
}
cout << endl;
}
访问对组中第一个元素调用first,访问第二个元素调用second
void test01() {
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);
//赋值
map<int, int>m3;
m3 = m2;
}
3.8.2 map大小和交换
利用size()方法返回容器中元素的数目
利用empty()方法判断容器是否为空
利用swap(st)方法交换两个集合容器
和set容器写法类似
3.8.3 map插入和删除
利用insert(elem)方法在容器中插入元素elem
利用erase(const_iterator pos)方法实现删除迭代器指向的元素,返回下一个元素的迭代器
利用erase(const_iterator start,const_iterator end)方法实现删除迭代器从start到end之间的元素,返回下一个元素的迭代器
利用erase(key)方法按照key值删除对组,如m.erase(3),若存在(3,30),则删除该对组
利用clear()方法删除容器中所有元素
void test01() {
map<int, int> m;
//插入 第一种
m.insert(pair<int,int>(1, 10));
//插入 第二种
m.insert(make_pair(2, 20));
//插入 第三种
m.insert(map<int, int>::value_type(3, 30));
//插入 第四种
m[4] = 40; //[]不建议插入,用途可以利用key访问到value
printMap(m);
//删除
m.erase(m.begin());
m.erase(3);
printMap(m);
}
3.8.4 map查找和统计
利用find(key)方法查找key是否存在,若存在,返回该键的元素的迭代器;若不存在,返回set.end(),即返回元素结束的位置
利用count(key)方法统计key的元素个数
均通过键值key进行操作
使用方法和set容器类似
3.8.5 map排序
map容器默认排序规则为 按照key值进行从小到大排序,可以利用仿函数,改变排序规则
class myCompare {
public:
bool operator()(int v1, int v2) const {
//降序
return v1 > v2;
}
};
void printMap(map<int, int, myCompare>& m) {
for (map<int, int, myCompare>::iterator it = m.begin(); it != m.end(); it++) {
cout << "key: " << it->first << " value: " << it->second <<endl;
}
cout << endl;
}
void test01() {
map<int, int, myCompare> m;
m.insert(make_pair(2, 20));
m.insert(make_pair(1, 40));
m.insert(make_pair(3, 80));
printMap(m);
}
4. STL - 函数对象
4.1 函数对象
重载函数调用操作符的类,其对象常称为函数对象
函数对象使用重载的()时,行为类似函数调用,也叫仿函数
函数对象(仿函数)是一个类,不是一个函数
函数对象的使用特点:
- 函数对象在使用时,可以像普通函数那样调用,可以有参数,可以有返回值
- 函数对象超出普通函数的概念,函数对象可以有自己的状态
- 函数对象可以作为参数传递
4.2 谓词
返回bool类型的仿函数称为谓词
如果operator()接受一个参数,叫一元谓词
如果operator()接受两个参数,叫二元谓词
class GreaterFive {
public:
bool operator()(int val) { //bool类型 一个参数,叫做一元谓词
return val > 5;
}
};
void test01() {
vector<int> v;
for (int i = 0; i < 10; i++) {
v.push_back(i);
}
//查找容器中,有无大于5的数字 GreaterFive()为匿名函数对象
vector<int>::iterator it = find_if(v.begin(), v.end(), GreaterFive());
if (it == v.end()) {
cout << "未找到" << endl;
}
else {
cout << "大于5的数字为:" << *it << endl;
}
}
4.3 内建函数对象
STL内建了一些函数对象,分为算数仿函数,关系仿函数,逻辑仿函数
这些仿函数所产生的对象,用法和一般函数完全相同
使用内建函数对象,需要引入头文件 #include<functional>
4.3.1 算数仿函数
功能为实现四则运算
其中negate(取反)是一元运算,其他都是二元运算
加法 plus 减法 minus 乘法multiplies 除法divides 取模modulus 取反negate
void test01() {
negate<int> n;
cout << n(50) << endl; // -50
plus<int> p;
cout << p(10, 20) << endl; // 30
}
4.3.2 关系仿函数
功能为实现关系对比
等于equal_to 不等于not_equal_to 大于greater 大于等于greater_equal
小于less 小于等于less_equal
void test01() {
vector<int> v;
for (int i = 0; i < 5; i++) {
v.push_back(i);
}
sort(v.begin(), v.end(), greater<int>()); // 实现了降序,提供内建函数对象greater<int>()
for (vector<int>::iterator it = v.begin(); it != v.end(); it++) {
cout << *it << " ";
}
cout << endl;
}
4.3.3 逻辑仿函数
功能为实现逻辑运算
逻辑与logical_and 逻辑或logical_or 逻辑非logical_not
void test01() {
vector<bool> v;
v.push_back(true);
v.push_back(false);
v.push_back(true);
v.push_back(false);
for (vector<bool>::iterator it = v.begin(); it != v.end(); it++) {
cout << *it << " "; // 1 0 1 0
}
cout << endl;
//利用逻辑非,将容器v搬运到容器v2中,并执行取反操作
vector<bool> v2;
v2.resize(v.size());
transform(v.begin(), v.end(), v2.begin(), logical_not<bool>());
for (vector<bool>::iterator it = v2.begin(); it != v2.end(); it++) {
cout << *it << " "; // 0 1 0 1
}
cout << endl;
}
5. STL - 常用算法
算法主要是由头文件<algorithm> <functional> <numeric> 组成
- <algorithm>是所有STL头文件中最大的一个,范围涉及到比较,交换,查找,遍历操作,复制,修改等
- <numeric>体积很小,只包括几个在序列上面进行简单数学运算的模板函数
- <functional>定义了一些模板类,用以声明函数对象
5.1 常用遍历算法
使用时包含头文件 #include<algorithm>
for_each:遍历容器
transform:搬运容器到另一个容器中
5.1.1 for_each
for_each ( iterator beg, iterator end, _func) // 遍历算法,遍历容器元素
参数解释:
- beg为开始迭代器
- end为结束迭代器
- _func为函数名或者函数对象
//普通函数
void print01(int val) {
cout << val << " ";
}
//仿函数
class print02 {
public:
void operator()(int val) {
cout << val << " ";
}
};
void test01() {
vector<int> v;
for (int i = 0; i < 10; i++) {
v.push_back(i);
}
for_each (v.begin(), v.end(), print01); //普通函数名
cout << endl;
for_each(v.begin(), v.end(), print02()); //函数对象(类名+小括号)
cout << endl;
}
5.1.2 transform
transform ( iterator beg1, iterator end1, iterator beg2, _func); //搬运容器到另一个容器中
参数解释:
- beg1为源容器开始迭代器
- end1为源容器结束迭代器
- beg2为目标容器开始迭代器
- _func为普通函数名或者函数对象
class Transform {
public:
int operator()(int val) {
return val+100; //可以进行一些操作
}
};
void print01(int val) {
cout << val << " ";
}
void test01() {
vector<int> v;
for (int i = 0; i < 10; i++) {
v.push_back(i);
}
vector<int> vTarget; // 目标容器
vTarget.resize(v.size()); //目标容器需要提前开辟空间
transform(v.begin(), v.end(), vTarget.begin(), Transform());
for_each(vTarget.begin(), vTarget.end(), print01);
cout << endl;
}
5.2 常用查找算法
使用时包含头文件 #include<algorithm>
find 查找元素
find_if 按条件查找元素
adjacent_find 查找相邻重复元素
binary_search 二分查找法
count 统计元素个数
count_if 按条件统计元素个数
5.2.1 find
功能为查找指定元素,找到返回指定元素的迭代器,找不到返回结束迭代器end()
find ( iterator beg, iterator end, value);
其中beg为开始迭代器,end为结束迭代器,value为查找的元素
//查找内置数据类型
void test01() {
vector<int> v;
for (int i = 0; i < 10; i++) {
v.push_back(i);
}
vector<int>::iterator it = find(v.begin(), v.end(), 6);
if (it == v.end()) {
cout << " 未找到 " << endl;
}
else {
cout << *it << " ";
}
cout << endl;
}
class Person {
public:
Person(string name, int age) {
this->m_age = age;
this->m_name = name;
}
//重载==,让find知道如何对比Person数据类型
bool operator==(const Person& p) {
if (this->m_name == p.m_name && this->m_age == p.m_age) {
return true;
}
else {
return false;
}
}
string m_name;
int m_age;
};
//查找自定义数据类型
void test02() {
vector<Person> v;
Person p1("aa", 34);
Person p2("bb", 39);
Person p3("cc", 30);
Person p4("dd", 27);
v.push_back(p1);
v.push_back(p2);
v.push_back(p3);
v.push_back(p4);
vector<Person>::iterator it = find(v.begin(), v.end(), p2);
if (it == v.end()) {
cout << " 未找到 " << endl;
}
else {
cout << it->m_name << " + "<<it->m_age<<endl;
}
cout << endl;
}
5.2.2 find_if
功能为按值返回查找元素,找到返回指定元素的迭代器,找不到返回结束迭代器end()
find_if ( iterator beg, iterator end, _Pred);
其中beg为开始迭代器,end为结束迭代器,_Pred为函数名或者谓词(返回bool类型的仿函数)
5.2.3 adjacent_find
功能为查找相邻重复元素,返回相邻元素的第一个位置的迭代器
adjacent_find ( iterator beg, iterator end);
其中beg为开始迭代器,end为结束迭代器
加入vector容器中存放的元素为 0,2,0,3,1,4,3,3,则使用该算法返回的是3,3中的第一个3的位置,因为0元素之间不相邻,第一个3元素和后面的也不相邻
5.2.4 binary_search
功能为查找指定的元素,查到返回true,否则返回false,在无序序列中不可用
bool binary_search ( iterator beg, iterator end, value);
其中beg为开始迭代器,end为结束迭代器,value为查找的元素
5.2.5 count
功能为统计元素出现次数
count ( iterator beg, iterator end, value);
其中beg为开始迭代器,end为结束迭代器,value为统计的元素
class Person {
public:
Person(string name, int age) {
this->m_age = age;
this->m_name = name;
}
//重载==
bool operator==(const Person& p) {
if (this->m_age == p.m_age) {
return true;
}
else {
return false;
}
}
string m_name;
int m_age;
};
//查找自定义数据类型
void test01() {
vector<Person> v;
Person p1("aa", 34);
Person p2("bb", 34);
Person p3("cc", 30);
Person p4("dd", 27);
Person p5("cc", 34);
v.push_back(p1);
v.push_back(p2);
v.push_back(p3);
v.push_back(p4);
int it = count(v.begin(), v.end(), p5);
cout << it << endl;
}
5.2.6 count_if
功能为按条件统计元素出现次数
count_if ( iterator beg, iterator end, _Pred);
其中beg为开始迭代器,end为结束迭代器,_Pred为谓词(返回bool类型的仿函数)
5.3 常用排序算法
使用时包含头文件 #include<algorithm>
sort 对容器内元素进行排序
random_shuffle 洗牌,指定范围内的元素随机调整次序
merge 容器元素合并,并存储到另一容器中
reverse 反转指定范围元素
5.3.1 sort
sort ( iterator beg, iterator end, _Pred);
其中beg为开始迭代器,end为结束迭代器,_Pred为谓词。
_Pred默认不写,则是升序排序,添加后可以改变顺序变为降序排序
5.3.2 random_shuffle
random_shuffle ( iterator beg, iterator end);
其中beg为开始迭代器,end为结束迭代器
void myPrint(int val) {
cout << val << " ";
}
void test01() {
vector<int> v;
for (int i = 0; i < 10; i++) {
v.push_back(i);
}
random_shuffle(v.begin(), v.end());
for_each(v.begin(), v.end(), myPrint);
}
5.3.3 merge
功能为容器元素合并,并存储到另一容器中,注意两个容器必须是有序的
merge ( iterator beg1, iterator end1, iterator beg2, iterator end2, iterator dest);
其中beg1为容器1开始迭代器,end1为容器1结束迭代器,beg2为容器2开始迭代器,end2为容器2结束迭代器,dest为目标容器开始迭代器
void myPrint(int val) {
cout << val << " ";
}
void test01() {
vector<int> v1;
vector<int> v2;
for (int i = 0; i < 10; i++) {
v1.push_back(i);
v2.push_back(i + 2);
}
vector<int> vTarget;
vTarget.resize(v1.size()+v2.size()); //开辟空间
merge(v1.begin(), v1.end(), v2.begin(), v2.end(), vTarget.begin());
for_each(vTarget.begin(),vTarget.end(),myPrint);
cout << endl;
}
5.3.4 reverse
reverse ( iterator beg, iterator end);
其中beg为开始迭代器,end为结束迭代器
5.4 常用拷贝和替换算法
使用时包含头文件 #include<algorithm>
copy 容器内指定范围的元素拷贝到另一容器中
replace 将容器内指定范围的旧元素改为新元素
replace_if 容器内指定范围满足条件的元素替换为新元素
swap 互换两个容器的元素
5.4.1 copy
copy ( iterator beg, iterator end, iterator dest);
其中beg为容器开始迭代器,end为容器结束迭代器,dest为目标容器开始迭代器
拷贝时要注意开辟空间 v2.resize(v1.size());
5.4.2 replace
replace ( iterator beg, iterator end, oldvalue, newvalue);
其中beg为容器开始迭代器,end为容器结束迭代器,oldvalue为旧元素,newvalue为新元素
会替换区间内满足条件的所有元素,如容器中的数据为20,3,4,5,20,6。若替换元素20为2,则容器中的数据变为2,3,4,5,2,6
5.4.3 replace_if
replace_if ( iterator beg, iterator end, _pred, newvalue);
其中beg为容器开始迭代器,end为容器结束迭代器,_pred为谓词,newvalue为替换的新元素
5.4.4 swap
swap ( container c1, container c2);
其中c1为容器1,c2为容器2
5.5 常用算术生成算法
使用时包含头文件 #include<numeric>
accumulate 计算容器元素累计总和
fill 向容器中添加元素,利用fill可以将容器区间内元素填充为指定的值
5.5.1 accumulate
accumulate ( iterator beg, iterator end, value);
其中beg为容器开始迭代器,end为容器结束迭代器,value为起始值
void test01() {
vector<int> v;
for (int i = 0; i <= 100; i++) {
v.push_back(i);
}
int total = accumulate(v.begin(), v.end(), 1000); // 1000为起始累加值
cout << total << endl; //6050 = 1000 + 1 +2 +...+100
}
5.5.2 fill
fill ( iterator beg, iterator end, value);
其中beg为容器开始迭代器,end为容器结束迭代器,value为起始值
void test01() {
vector<int> v;
v.resize(10);
fill(v.begin(), v.end(), 100); //10个0全部替换为了10个100
for_each(v.begin(), v.end(), myPrint);
}
5.6 常用集合算法
set_intersection 求两个容器的交集
set_union 求两个容器的并集
set_difference 求两个容器的差集
5.6.1 set_intersection
set_intersection ( iterator beg1, iterator end1, iterator beg2, iterator end2, iterator dest );
其中beg1为容器1开始迭代器,end1为容器1结束迭代器,beg2为容器2开始迭代器,end2为容器2结束迭代器,dest为目标容器开始迭代器
注意事项:
- 求交集的两个集合必须是有序序列
- 目标容器开辟空间需要从两个容器中取小值 min (v1.size(), v2.size());
- set_intersection返回值即是交集中最后一个元素的位置
5.6.2 set_union
set_union ( iterator beg1, iterator end1, iterator beg2, iterator end2, iterator dest );
其中beg1为容器1开始迭代器,end1为容器1结束迭代器,beg2为容器2开始迭代器,end2为容器2结束迭代器,dest为目标容器开始迭代器
注意事项:
- 求并集的两个集合必须是有序序列
- 目标容器开辟空间需要两个容器相加
- set_union返回值即是并集中最后一个元素的位置
5.6.3 set_difference
set_difference ( iterator beg1, iterator end1, iterator beg2, iterator end2, iterator dest );
其中beg1为容器1开始迭代器,end1为容器1结束迭代器,beg2为容器2开始迭代器,end2为容器2结束迭代器,dest为目标容器开始迭代器
注意事项:
- 求差集的两个集合必须是有序序列
- 目标容器开辟空间需从两个容器取较大值
- set_difference返回值即是差集中最后一个元素的位置
集合算法的例子:
void myPrint(int val) {
cout << val << " ";
}
void test01() {
vector<int> v1;
vector<int> v2;
for (int i = 0; i < 10; i++) {
v1.push_back(i);
v2.push_back(i+5);
}
vector<int> vTarget;
vTarget.resize(v1.size() + v2.size());
vector<int>::iterator itEnd = set_intersection(v1.begin(), v1.end(), v2.begin(), v2.end(), vTarget.begin());
for_each(vTarget.begin(), itEnd, myPrint); //使用itEnd避免了出现填充0的情况
cout << endl;
vector<int>::iterator itEnd1 = set_union(v1.begin(), v1.end(), v2.begin(), v2.end(), vTarget.begin());
for_each(vTarget.begin(), itEnd1, myPrint);
cout << endl;
vector<int>::iterator itEnd2 = set_difference(v1.begin(), v1.end(), v2.begin(), v2.end(), vTarget.begin());
for_each(vTarget.begin(), itEnd2, myPrint);
cout << endl;
}