目录
5.2.3 adjacent_find (adjacent 相邻)
C++提高编程
-
本阶段主要针对C++泛型编程和STL技术详细讲解, 探讨C++更深层的使用
1. 模板
1.1 模板的概念
模板就是建立通用的模具,大大提高复用性
例如生活中的模板, 一寸照片模板, PPT模板
模板的特点:
-
模板不可以直接使用, 他只是一个框架
-
模板的通用并不是万能的
1.2 函数模板
-
C++另一种编程思想称为泛型编程, 主要利用的技术就是模板
-
C++提供两种模板机制: 函数模板和类模板
1.2.1 函数模板语法
函数模板作用:
建立一个通用函数, 其函数的返回值类型和形参类型可以不具体制定, 用一个虚拟的类型来代表
语法:
template<typename T> 函数声明或定义
解释:
template----声明创建模板
typename----表明其后面的符号是一种数据类型, 也可以写成class
T ---- 通用的数据类型, 名称可以替换, 通常为大写字母
示例:
#include<iostream> using namespace std; //函数模板 //交换两个整型函数 void swapInt(int &a,int &b) { int temp = a; a = b; b = temp; } //交换两个浮点型函数 void swapDouble(double& a, double& b) { double temp = a; a = b; b = temp; } //函数模板 template<typename T>//声明一个模板,告诉编译器后面的代码中紧跟着的T不要报错, T是一个通用数据类型 void mySwap(T& a, T& b) { T temp = a; a = b; b = temp; } void test01() { int a = 10; int b = 20; //swapInt(a, b); //利用函数模板交换 //两种方式使用函数模板 //1. 自动类型推导 mySwap(a, b); //2. 显示指定类型 mySwap<int>(a, b); cout << "a=" << a << endl; cout << "b=" << b << endl; double c = 1.1; double d = 2.2; swapDouble(c, d); cout << "c=" << c << endl; cout << "d=" << d << endl; } int main() { test01(); return 0; }
总结:
-
函数模板利用关键字 template
-
使用函数模板有两种方式:自动类型推导和显式指定类型
-
模板的目的是为了提高复用性, 将类型参数化
1.2.2 函数模板注意事项
注意事项:
-
自动类型推导, 必须推导出一致的数据类型T, 才可以使用
-
模板必须要确定出T的数据类型, 才可以使用
示例:
#include<iostream> using namespace std; //注意事项: template<class T>//typename 可以替换为 class void mySwap(T& a, T& b) { T temp = a; a = b; b = temp; } //1. 自动类型推导, 必须推导出一致的数据类型T, 才可以使用 void test01() { int a = 10; int b = 20; char c = 'c'; mySwap(a, b);//正确 //mySwap(a, c);//错误,推导不出一致的T类型 cout << "a=" << a << endl; cout << "b=" << b << endl; } //2. 模板必须要确定出T的数据类型, 才可以使用 template<class T> void func() { cout << "func() 的调用" << endl; } void test02() { //func();//错误,模板必须要确定出T的数据类型, 才可以使用 func<int>(); } int main() { test01(); test02(); return 0; }
1.2.3 函数模板案例
案例描述:
-
利用函数模板封装一个排序的函数, 可以对不同数据数组进行排序
-
排序规则从大到小, 排序算法为选择排序
-
分别利用char数组和int数组进行测试
示例:
#include<iostream> using namespace std; //实现通用 对数组进行排序的函数 //规则 从大到小 //算法 选择排序 //测试 char 数组 int 数组 //交换函数模板 template<class T> void mySawp(T& a, T& b) { T temp = a; a = b; b = temp; } //排序算法 template<class T> void mySort(T arr[], int len) { for (int i = 0; i < len; i++) { int max = i;//认定最大值的下标是i for (int j = i + 1; j < len; j++) { //认定的最大值 比 遍历出的数值 要小, 说明j下标的元素才是真正的最大值 if (arr[max] < arr[j]) { max = j;//更新最大值下标 } } if(max!=i){ mySawp(arr[max], arr[i]); /*T temp = arr[max]; arr[max] = arr[i]; arr[i] = temp;*/ } } } //提供打印函数模板 template<class T> void printArray(T arr[], int len) { for (int i = 0; i < len; i++) { cout << arr[i] << " "; } cout << endl; } void test01() { //测试char数组 char charArr[] = "badcfe"; int num = sizeof(charArr) / sizeof(char); mySort(charArr, num); printArray(charArr, num); } void test02() { //测试int数组 int intArr[] = { 7,5,1,3,9,2,4,6,8 }; int num = sizeof(intArr) / sizeof(int); mySort(intArr, num); printArray(intArr, num); } int main() { test01(); test02(); return 0; }
1.2.3 普通函数与函数模板的区别
普通函数与函数模板的区别:
-
普通函数调用时可以发生自动类型转换(隐式类型转换)
-
函数模板调用时, 如果利用自动类型推导, 不会发生隐式类型转换
-
如果利用显示指定类型的方式, 可以发生隐式类型转换
示例:
#include<iostream> using namespace std; ///**普通函数与函数模板的区别: //1. 普通函数调用时可以发生自动类型转换(隐式类型转换) //2. 函数模板调用时, 如果利用自动类型推导, 不会发生隐式类型转换 //3. 如果利用显示指定类型的方式, 可以发生隐式类型转换 //普通函数 int myAdd01(int a, int b) { return a + b; } //函数模板 template<class T> T myAdd02(T a, T b) { return a + b; } void test01() { int a = 10; int b = 20; char c = 'c';//a--97, c-99 cout << myAdd01(a, c) << endl;//将字符'c'隐式转化为整型 //自动类型推导 //cout << myAdd02(a, b) << end;; //报错, 不会发生隐式类型转换 //显示指定类型 cout << myAdd02<int>(a, c) << endl;// 显示指定类型, 可以发生隐式类型转换 } int main() { test01(); return 0; }
总结:建议使用显示指定类型的方式, 调用h函数模板, 因为可以自己确定通用类型T
1.2.5 普通函数与函数模板的调用规则
调用规则:
-
如果函数模板和普通函数都可以实现, 优先调用普通函数
-
可以通过空模板参数列表来强制调用函数模板
-
函数模板也可以发生重载
-
如果函数模板可以产生更好的匹配, 优先调用函数模板
示例:
#include<iostream> using namespace std; //调用规则: //1. 如果函数模板和普通函数都可以实现, 优先调用普通函数 //2. 可以通过空模板参数列表来强制调用函数模板 //3. 函数模板也可以发生重载 //4. 如果函数模板可以产生更好的匹配, 优先调用函数模板 void myPrint(int a, int b){ cout << "调用的是普通函数" << endl; } template<class T> void myPrint(T a, T b) { cout << "调用的是函数模板" << endl; } template<class T> void myPrint(T a, T b,T c) {//函数模板也可以发生重载 cout << "调用的是重载的函数模板" << endl; } void test01() { int a = 10; int b = 20; //myPrint(a, b); //如果函数模板和普通函数都可以实现, 优先调用普通函数 //可以通过空模板参数列表来强制调用函数模板 // myPrint<>(a, b); //myPrint(a, b, 10); //如果函数模板可以产生更好的匹配, 优先调用函数模板 char c1 = 'a'; char c2 = 'b'; myPrint(c1, c2);//普通函数也可以调用, 但是需要进行一次类型转换, 不是更好的匹配 } int main() { test01(); return 0; }
总结:既然提供了函数模板, 最好就不要提供普通函数, 否则容易出现二义性
1.2.6 模板的局限性
局限性:
-
模板的通用不是万能的
例如:
template<class T> void f(T a,T b){ a = b; }
在上述代码中提供的赋值操作, 如果传入的a和b是数组, 就无法实现了
再例如:
template<class T> void f(T a,T b){ if(a>b){ ... } }
在上述代码中提供的赋值操作, 如果传入的a和都是数组,就无法实现了
因此C++为了解决这个问题, 提供函数的重载, 可以为这些特定的类型提供具体化的模板
示例:
#include<iostream> using namespace std; //模板局限性: //模板的通用不是万能的,有些特定的数据类型需要用具体化方式做特殊实现 class Person { public: Person(string name, int age) { this->m_Name = name; this->m_Age = age; } string m_Name; int m_Age; }; //对比两个数据是否相等函数 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 test01() { int a = 10; int b = 20; bool ret = myCompare(a, b); cout << ret << endl; if (ret) { cout << "a==b" << endl; } else { cout << "a!=b" << endl; } } void test02() { Person p1("Tom", 10); Person p2("Tom", 10); bool ret = myCompare(p1, p2); if (ret) { cout << "p1==p2" << endl; } else { cout << "p1!=p2" << endl; } } int main() { //test01(); test02(); return 0; }
总结:
-
利用 具体化的模板,可以解决自定义类型的通用化
-
学习模板并不是为了写模板, 而是在STL中能够运用系统提供的模板
1.3 类模板
1.3.1 类模板语法
类模板作用:
-
建立一个通用类, 类中的成员 数据类型可以不具体制定, 用一个虚拟的类型来代表
语法:
template<typename T> 类
解释:
template----声明创建模板
typename----表明其后面的符号是一种数据类型, 也可以写成class
T ---- 通用的数据类型, 名称可以替换, 通常为大写字母
示例:
#include<iostream> using namespace std; #include<string> //类模板 template<class NameType,class AgeType> class Person { public: Person(NameType name,AgeType age){ this->m_Name = name; this->m_Age = age; } void showPerson() { cout << "name:" << this->m_Name << " age:" << this->m_Age << endl; } NameType m_Name; AgeType m_Age; }; void test01() { Person<string, int>p1("孙悟空",999); p1.showPerson(); } int main() { test01(); return 0; }
1.3.2 类模板与函数模板的区别
类模板与函数模板的区别主要有两种:
-
类模板没有自动类型推导的使用方式
-
类模板在模板参数列表中可以有默认参数
示例:
#include<iostream> using namespace std; #include<string> //类模板与函数模板的区别 template<class NameType,class AgeType=int>//2. 类模板在模板参数列表中可以有默认参数 class Person { public: Person(NameType name, AgeType age) { this->m_Name = name; this->m_Age = age; } void showPerson() { cout << "name:" << this->m_Name << " age:" << this->m_Age << endl; } NameType m_Name; AgeType m_Age; }; //1. 类模板没有自动类型推导的使用方式 void test01() { //Person p("孙悟空", 1000);//报错, 无法用自动类型推导 Person<string, int>p("孙悟空", 1000); p.showPerson(); } //2. 类模板在模板参数列表中可以有默认参数 void test02() { Person<string>p("猪八戒", 900); p.showPerson(); } int main() { test01(); test02(); return 0; }
1.3.3 类模板中成员函数创建时机
类模板中成员函数和普通类中成员函数创建时机是有区别的:
-
普通类中成员函数一开始就可以创建
-
类模板中的成员函数在调用时才创建
示例:
#include<iostream> using namespace std; //类模板中成员函数和普通类中成员函数创建时机是有区别的: //1. 普通类中成员函数一开始就可以创建 //2. 类模板中的成员函数在调用时才创建 class Person1 { public: void showPerson1() { cout << "Person1 show" << endl; } }; class Person2 { public: void showPerson2() { cout << "Person2 show" << endl; } }; template<class T> class MyClass { public: T obj; //类模板中的成员函数 void func1() { obj.showPerson1(); } void func2() { obj.showPerson2(); }//没有报错, 类模板中的成员函数并不是一开始就创建的, 而是在模板调用时再生成 }; void test01() { MyClass<Person1>m; m.func1(); m.func2();//编译出错, 说明函数调用才会去创建成员函数 } int main() { test01(); return 0; }
总结:类模板中的成员函数并不是一开始就创建的, 而是在模板调用时再生成
1.3.4 类模板对象做函数参数
学习目标: 类模板实例化的对象, 向函数传参的方式
一共有三种传入方式:
-
指定传入的类型: 直接显示对象的数据类型
-
参数模板化: 将对象中的参数变为模板进行传递
-
整个类模板化: 将这个对象类型 模板化进行传递
示例:
#include<iostream> using namespace std; #include<string> //类模板实例化的对象做函数参数 template<class T1,class T2> class Person { public: Person(T1 name, T2 age) { this->m_Name = name; this->m_Age = age; } void showPerson() { cout << "姓名:" << this->m_Name << " 年龄:" << this->m_Age << endl; } T1 m_Name; T2 m_Age; }; //1. 指定传入的类型 : 直接显示对象的数据类型 void printPerson1(Person<string, int>&p) { p.showPerson(); } void test01() { Person<string, int>p("孙悟空", 100); printPerson1(p); } //2. 参数模板化 : 将对象中的参数变为模板进行传递 template<class T1,class T2> void printPerson2(Person<T1,T2>&p) { //cout << "T1的类型为:" << typeid(T1).name()<< endl; //cout << "T2的类型为:" << typeid(T2).name()<< endl; p.showPerson(); } void test02() { Person<string, int>p("猪八戒", 90); printPerson2(p); } //3. 整个类模板化 : 将这个对象类型 模板化进行传递 template<class T> void printPerson3(T &p) { p.showPerson(); cout << "T的类型为:" << typeid(T).name() << endl; } void test03() { Person<string, int>p("唐僧", 30); printPerson3(p); } int main() { test01(); test02(); test03(); return 0; }
总结:
-
查看编译器推导的T类型
cout << "T的类型为:" << typeid(T).name()<< endl;
-
通过类模板创建的对象,, 有三种方式向函数中进行传参
-
使用比较广泛的时第一种; 指定传入的类型
1.3.5 类模板与继承
当类模板碰到继承时, 需要注意以下几点:
-
当子类继承的父类是一个类模板时, 子类在声明的时候, 要指定出父类中的类型
-
如果不指定, 编译器无法给予子类内存分配
-
如果想灵活指定出父类中T的类型, 子类也需变为类模板
示例:
#include<iostream> using namespace std; //类模板与继承 //当子类继承的父类是一个类模板时, 子类在声明的时候, 要指定出父类中的类型 //如果不指定, 编译器无法给予子类内存分配 //如果想灵活指定出父类中T的类型, 子类也需变为类模板 template<class T> class Base { T m; }; //class Son :public Base {//错误, 必须要知道父类中的T类型, 才能继承给子类 class Son :public Base<int>{//正确 }; void test01() { Son s1; } //如果想灵活指定出父类中T的类型, 子类也需变为类模板 template<class T1, class T2> class Son2 :public Base<T2> { public: Son2() { cout << "T1的类型为:" << typeid(T1).name() << endl; cout << "T2的类型为:" << typeid(T2).name() << endl; } T1 obj; }; void test02() { Son2<int, char>S2; } int main() { //test01(); test02(); return 0; }
1.3.6 类模板成员函数类外实现
学习目标: 能够掌握模板中的成员函数类外实现
示例:
#include<iostream> using namespace std; #include<string> //类模板成员函数类外实现 template<class T1,class T2> class Person { public: Person(T1 name, T2 age); //类内只写函数声明 //{ // this->m_Name = name; // this->m_Age = age; //} void showPerson(); //{ // cout << "姓名:" << this->m_Name << " 年龄: " << this->m_Age << endl; //} T1 m_Name; T2 m_Age; }; //构造函数的类外实现 template<class T1,class T2> Person<T1, T2>::Person(T1 name, T2 age) { this->m_Name = name; this->m_Age = age; } //成员函数类外实现 template<class T1,class T2> void Person<T1, T2>::showPerson() { cout << "姓名:" << this->m_Name << " 年龄: " << this->m_Age << endl; } void test01() { Person<string, int>p("Tom", 20); p.showPerson(); } int main() { test01(); return 0; }
总结:类模板中成员函数类外实现时, 需要加上模板参数列表
1.3.7 类模板份文件编写
(类很多, 分文件编写)
学习目标: 掌握类模板成员函数分文件编写产生的问题以及解决方式
问题:
-
类模板中成员函数创建时期是在调用阶段, 导致分文件便携式链接不到
解决:
-
方式1: 直接包含.cpp源文件
-
方式2: 将声明和实现写到同一个文件中, 并更改后缀名为.hpp, .hpp是约定的名字, 并不是强制
示例:
person.hpp中 代码:
#pragma #include<iostream> using namespace std; #include<string> template<class T1, class T2> class Person { public: Person(T1 name, T2 age); void showPerson(); T1 m_Name; T2 m_Age; }; template<class T1, class T2> Person<T1, T2>::Person(T1 name, T2 age) { this->m_Name = name; this->m_Age = age; } template<class T1, class T2> void Person<T1, T2>::showPerson() { cout << "姓名:" << this->m_Name << " 年龄:" << this->m_Age << endl; }
类模板分文件编写.cpp中代码
#include<iostream> using namespace std; //第一种解决方式: 直接包含 源文件 //#include"person.cpp" //第二种解决方式: 将.h和.cpp中的内容写到一起,将后缀名改为.hpp #include"person.hpp" //#include<string> //类模板份文件编写问题以及解决 // //template<class T1,class T2> //class Person { //public: // Person(T1 name, T2 age); // // void showPerson(); // // T1 m_Name; // T2 m_Age; //}; //template<class T1, class T2> //Person<T1, T2>::Person(T1 name, T2 age) { // this->m_Name = name; // this->m_Age = age; //} // //template<class T1, class T2> //void Person<T1, T2>::showPerson() { // cout << "姓名:" << this->m_Name << " 年龄:" << this->m_Age << endl; //} void test01() { Person<string, int>p("Jerry", 18); p.showPerson(); } int main() { test01(); return 0; }
总结:主流解决方式是第二种, 将类模板成员函数写到一起, 并将后缀名改为.hpp
1.3.8 类模板与友元
学习目标: 掌握类模板配合友元函数的类内和类外实现
全局函数类内实现: 直接再类内声明友元即可
全局函数类外实现: 需要提前让编译器知道全局函数的存在
#include<iostream> using namespace std; #include<string> //通过全局函数 打印Person信息 直接在类内声明友元即可 //提前让编译器知道Person类的存在 template<class T1, class T2> class Person; //类外实现 template<class T1, class T2> void printPerson2(Person<T1, T2>p) { cout << "类外实现---姓名: " << p.m_Name << " 年龄:" << p.m_Age << endl; } template<class T1,class T2> class Person { //全局函数 类内实现 friend void printPerson(Person<T1, T2>p) { cout << "姓名: " << p.m_Name << " 年龄:" << p.m_Age << endl; } //全局函数 类外实现 //加空模板参数列表, 告诉编译器这是模板函数 //如果全局函数 是类外实现, 需要让编译器提前知道这个函数的存在 friend void printPerson2<>(Person<T1, T2>p); public: Person(T1 name,T2 age) { this->m_Name = name; this->m_Age = age; } private: T1 m_Name; T2 m_Age; }; //1. 全局函数 类内实现 void test01() { Person<string, int>p("Tom", 18); printPerson(p); } //2. 全局函数类外实现 需要提前让编译器知道全局函数的存在 void test02() { Person<string, int>p("Jerry", 20); printPerson2(p); } int main() { //test01(); test02(); return 0; }
总结:建议全局函数做类内实现, 用法简单, 而且编译器可以直接识别
1.3.9 类模板案例
案例描述: 实现一个通用的数组类, 要求如下:
-
可以对内置数据类型以及自定义数据类型的数据进行存储
-
将数组中的数据存储到堆区
-
构造函数中可以传入数组的容量
-
提供对应的拷贝构造函数以及operator=防止浅拷贝问题
-
提供尾插法和尾删法对数组中的数据进行增加和删除
-
可以通过下标的方式访问数组中的元素
-
可以获取数组中当前元素个数和数组的容量
示例:
myArray.h中代码
//自己的通用的数组类 #pragma once #include<iostream> using namespace std; template<class T> class MyArray { public: //有参构造, 参数是 容量 MyArray(int capacity) { this->m_Capacity = capacity; this->m_Size = 0; this->pAddress = new T[this->m_Capacity]; //cout << "MyArray的构造函数调用" << endl; } //拷贝构造 MyArray(const MyArray& arr) { this->m_Capacity = arr.m_Capacity; this->m_Size = arr.m_Size; //this->pAddress = arr.pAddress;//系统提供的拷贝构造--浅拷贝 this->pAddress = new T[arr.m_Capacity+4];//深拷贝 //将arr中的数据都拷贝过来 for (int i = 0; i < this->m_Size; i++) { this->pAddress[i] = arr.pAddress[i]; } //cout << "MyArray的拷贝函数调用" << endl; } //operator= 防止浅拷贝问题 MyArray& operator=(const MyArray& arr) { //先判断原来堆区是否有数据, 如果有,先释放 if (this->pAddress != NULL) { //cout << "MyArray的oprator=函数调用" << endl; 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[arr.m_Capacity]; for (int i = 0; i < this->m_Size; i++) { this->pAddress[i] = arr.pAddress[i]; } return *this; } //尾插法 void Push_Back(const T& val) { //判断容量是否等大小 if (this->m_Capacity == this->m_Size) { return; } this->pAddress[this->m_Size] = val;//在数组末尾插入数据 this->m_Size++;//更新数组大小 } //尾删法 void Pop_Back() { //让用户访问不到最后一个元素, 即为尾删---逻辑删除 if (this->m_Size == 0) { return; } this->m_Size--; } //通过下标的方式访问数组中的数据 //这是我们自己写的数组,编译器不认识[],要重载中括号[] T& operator[](int index) { return this->pAddress[index]; } //返回数组的容量 int getCapacity() { return this->m_Capacity; } //返回数组大小 int getSize() { return this->m_Size; } //析构函数 ~MyArray(){ if (this->pAddress != NULL) { delete[] this->pAddress; this->pAddress = NULL; } //cout << "MyArray的析构函数调用" << endl; } private: T* pAddress;//指针指向堆区开辟的真实数组 int m_Capacity;//容量 int m_Size;//大小 };
类模板案例-数组类封装.cpp中函数
#include<iostream> using namespace std; #include"MyArray.hpp" #include<string> 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 << endl; cout << "arr1的容量为: " << arr1.getCapacity() << endl; cout << "arr1的大小为: " << arr1.getSize() << endl; MyArray<int>arr2(arr1); cout << "arr2的打印输出为: " <<endl; printIntArray(arr2); cout << endl; //尾删 arr2.Pop_Back(); cout << "arr2尾删后的打印输出为: " <<endl; printIntArray(arr2); cout << endl; cout << "arr2的容量为: " << arr2.getCapacity() << endl; cout << "arr2的大小为: " << arr2.getSize() << 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("孙权", 22); Person p2("曹操", 22); Person p3("刘备", 21); Person p4("关羽", 20); Person p5("张飞", 19); //将数据插入到数组中 arr.Push_Back(p1); arr.Push_Back(p2); arr.Push_Back(p3); arr.Push_Back(p4); arr.Push_Back(p5); //打印数组 printPersonArray(arr); //输出容量 cout << "arr1的容量为: " << arr.getCapacity() << endl; //输出大小 cout << "arr1的大小为: " << arr.getSize() << endl; } int main() { //test01(); test02(); return 0; }
2. STL初识
2.1 STL的诞生
-
长久以来,软件界一直希望建立一种可重复利用的东西。
-
C++的面向对象和泛性编程思想,目的就是复用性的提升。
-
大多数情况下,数据结构和算法都未能有一套标准,导致被迫从事大量重复工作。
-
为了建立数据结构和算法的一套标准。诞生了STL。
2.2STL的基本概念
-
STL(standard template library,标准模板库)
-
STL从广义上分为:容器、算法、迭代器。
-
容器和算法之间通过迭代器进行无缝连接。
-
STL几乎所有的代码都采用了模板类或者模板函数。
2.3STL六大组件
STL分为6大组件分别是:容器、算法、迭代器、仿函数、适配器(配接器)、空间配置器。
-
容器:各种常用的数据结构:如Vector 、list、 deque、 set、 map等,用来存放数据
-
算法:各种常用的算法:如sort、find、copy、for_each等
-
迭代器:扮演了容器与算法之间的胶合剂。
-
仿函数:行为类似函数,可作为算法的某种策略。
-
适配器:一种用来修饰容器或者仿函数或者迭代器接口的东西。
-
空间配置器:负责空间的配置与管理。
2.4STL中容器、算法、迭代器
容器:置物之所也
STL容器就是将运用最广泛的一些数据结构实现出来
常用的数据结构:数组、链表、树、栈、队列、集合、映射表等
这些容器分为序列式容器和关联式容器:
序列式容器:强调值的排序序列,容器中的每个元素均有固定的位置。
关联式容器:二叉树结构,各元素之间没有严格的物理上的顺序关系。
(同样一组数据在序列式容器中按规定的顺序排列,而在关联式容器中可能会打乱顺序,按照某种特定的顺序排列)
算法:问题之解法也
有限的步骤,解决逻辑或数学上的问题。这一门学科我们叫做算法(Algorithms)
算法分为:质变算法和非质变算法
质变算法:是指运算过程中会更改区间内的元素的内容,例如拷贝、替换、删除等等。
非质变算法:是指运算过程中不会更改区间内的元素内容例如查找、计数、遍历、寻找极值等等
迭代器:容器与算法之间的胶合剂
(算法要通过迭代器才能访问数据中的元素)
提供一种方法,使之能够依序寻访某个容器所含的各个元素,而又无需暴露该容器的内部表示方式。
每个容器都有自己专属的迭代器。
迭代器使用非常类似于指针。初学者阶段,我们先理解迭代器为指针。
迭代器种类:
种类 | 功能 | 支持运算 |
---|---|---|
输入迭代器 | 对数据的只读访问 | 只读,支持++、==、!= |
输出迭代器 | 对数据的只写访问 | 只写,支持++ |
前向迭代器 | 读写操作、并能向前推进迭代器 | 读写,支持++、==、!= |
双向迭代器 | 读写操作、并能向前推进迭代器 | 读写,支持++、-- |
随机访问迭代器 | 读写操作、可以以跳跃的方式访问任意数据,是功能最强的迭代器 | 读写,支持++、--、[n]、-n、<、<=、>、>= |
常用的容器中迭代器种类为:双向迭代器和随机访问迭代器
2.5容器算法迭代器初识
STL中最常用的容器为vector,可以理解为数组。下面我们将学习如何向这个容器中插入数据并遍历这个容器。
2.5.1 vector存放内置数据类型
容器:vetor
算法:for_each
迭代器:vetor<int>::iterator
示例:
#include<iostream> using namespace std; #include <vector> #include<algorithm>//标准算法头文件 void myPrint(int val) { cout << val << endl; } void test01() { //创建vector容器对象,并且通过模板参数指定容器中存放的数据的类型 vector<int>v;//创建了一个vector容器,相当于数组,向容器中放数据 v.push_back(10); v.push_back(20); v.push_back(30); v.push_back(40);//向容器中插入数据 //通过迭代器访问容器中的数据 //vector<int>(相当于类型)作用域下的iterator(迭代器的名称) vector<int>::iterator itBegin = v.begin();//v.begin是起始迭代器(类似于指针),指向容器中第一个元素,v.rbegin()指向容器最后一个元素 vector<int>::iterator itEnd = v.end();//v.end是结束迭代器,指向容器中最后一个元素的下一个位置 //第一种遍历方式: while (itBegin != itEnd) { cout << *itBegin << endl; itBegin++; } //第二种遍历方式: for (vector<int>::iterator it = v.begin(); it != v.end(); it++) { cout << *it << endl; } //第三种遍历方式:利用STL提供的遍历三法 for_each(v.begin(), v.end(), myPrint); } int main() { test01(); return 0;
2.5.2 Vector中存放自定义数据类型
示例:
#include<iostream> using namespace std; #include<vector> #include<algorithm> //Vector中存放自定义数据类型 class Person { public: Person(string name, int age) { this->m_Name = name; this->m_Age = age; } string m_Name; int m_Age; }; void test01() { vector<Person>v ; //存放对象 Person p1 ("aaa", 10); Person p2 ("bbb", 20); Person p3 ("ccc", 30); Person p4 ("ddd", 40); Person p5 ("eee", 50); //向容器中添加数据 v.push_back(p1); v.push_back(p2); v.push_back(p3); v.push_back(p4); v.push_back(p5); //遍历容器中的数据 for (vector<Person>::iterator it = v.begin(); it != v.end(); it++) { cout << "姓名:" << (*it).m_Name << " 年龄:" << (*it).m_Age << endl; //it 是一个指针,解引用(*it)解出来的数据类型就是尖括号里的,即Person类型 需要拿他的属性出来,用“.” cout << "姓名:" << it->m_Name << " 年龄:" << it->m_Age << endl; //it 是一个指针,可以直接通过“->”得到其属性 } } void test02() { vector<Person*>v;//该容器中放的是数据的地址 //存放对象指针 Person p1("aaa", 10); Person p2("bbb", 20); Person p3("ccc", 30); Person p4("ddd", 40); Person p5("eee", 50); //向容器中添加数据 v.push_back(&p1); v.push_back(&p2); v.push_back(&p3); v.push_back(&p4); v.push_back(&p5); //遍历容器中的数据 for (vector<Person*>::iterator it = v.begin(); it != v.end(); it++) { cout<<"姓名:"<<(*it)->m_Name << " 年龄:" << (*it)->m_Age << endl; //(*it)解出来的是Person类型的指针,通过指针得其属性,用“->” } } int main(){ test01(); test02(); return 0; }
2.5.3Vector容器嵌套容器
示例:
#include<iostream> using namespace std; #include<vector> #include<algorithm> //容器嵌套容器 void test01() { vector<vector<int>>v; //创建几个小容器 vector<int>v1;//v1容器中存放的是1 2 3 4 vector<int>v2;//v1容器中存放的是2 3 4 5 vector<int>v3;//v1容器中存放的是3 4 5 6 vector<int>v4;//v1容器中存放的是4 5 6 7 //向小容器中添加数据 for (int i = 0; i < 4; i++) { v1.push_back(i + 1); v2.push_back(i + 2); v3.push_back(i + 3); v4.push_back(i + 4); } //将小容器插入到大的容器vector v中 v.push_back(v1); v.push_back(v2); v.push_back(v3); v.push_back(v4); //通过大容器,把所用数据遍历一遍 for ( vector<vector<int>>::iterator it = v.begin(); it != v.end(); it++) { //(*it)-----容器 vector<int> for (vector<int>::iterator vit = (*it).begin(); vit != (*it).end(); vit++) { cout << *vit << " "; } cout << endl; } } int main(){ test01(); return 0; }
3 STL常用容器
3.1 string容器
3.1.1string 基本概念
本质:
-
string是C++风格的字符串,而string本质上是一个类
string和char*的区别:
-
char*是一个指针
-
string是一个类,类内部封装了char,管理这个字符串,是一个char型的容器
特点:
string类内部封装了许多成员方法
例如:查找find、拷贝copy、删除delete、替换replace、插入insert
string管理char*所分配的内存,不用安心复制越界和取值越界等,由类内部进行负责
3.1.2string构造函数
构造函数原型:
-
string();
//创建一个空的字符串 例如:string str;string(const char* s);
//传C语言的字符串初始化C++的字符串 -
string(const string& str);
//使用一个string对象初始化另一个string,相当于copy -
string(int n,char c);
//使用n个字符c初始化
示例:
#include<iostream> using namespace std; #include<string> void test01(){ 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'); cout << "s4= " << s4 << endl; } int main(){ test01(); return 0; }
总结:string的多种构造方式没有可比性,灵活使用
3.1.3string赋值操作
功能描述:给string字符串进行赋值
赋值的函数类型;
-
string& operator=(const char* s);
//char*类型字符串赋值给当前字符串 operator---算子 -
string& operator=(const string &s);
//把字符串s赋给当前字符串 -
string& operator=(char c);
//字符赋值给当前字符串 -
string& assign(const char* s);
//把字符串s赋给当前字符串 assign--分配 -
string& assign(const char* s,int n);
//把字符串s的前n个字符赋给当前的字符串 -
string& assign(const string &s);
//把字符串s赋给当前字符串 -
string& assign(int n,char c);
//用n个字符串c赋给当前字符串示例:
#include<iostream> using namespace std; #include<string> void test01() { string str1; str1 = "Hello World!"; cout << "str1= " << str1 << endl; string str2; str2 = str1; cout << "str2= " << str2 << endl; string str3; str3 = 'c'; cout << "str3=" << str3 << endl; string str4; str4.assign("Hello C++!"); cout << "str4=" << str4 << endl; string str5; str5.assign("Hello C++", 5); cout << "str5=" << str5 << endl; string str6; str6.assign(str5); cout << "str6=" << str6 << endl; string str7; str7.assign(10,'w'); cout << "str7=" << str7 << endl; } int main() { test01(); return 0; }
总结:string的赋值方式很多,operator
这种方式比较实用
3.1.4string字符串拼接
函数原型:
-
string& operator+=(const char* str);
//重载+=操作符 -
string& operator+=(const char c);
//重载+=操作符 -
string& operator+=(const string& str);
//重载+=操作符 -
string& append(const char* s);
//把字符串s连接到当前字符串结尾 append---附加 -
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个字符连接到字符串结尾
示例:
void test01() { string str1; str1 = "我"; str1 += "爱学习。"; cout << "str1 = " << str1 << endl; string str2 ="LOL DNF"; str2 += str1; cout << "str2= " << str2 << endl; string str3="I "; str3.append("love "); cout << "str3=" << str3 << endl; str3.append("C++!", 3); cout << "str3=" << str3 << endl; str3.append(str1); cout << "str3=" << str3 << endl; str3.append(str2, 0, 3); cout << "str3=" << str3 << endl; }
3.1.5string查找和替换
功能描述:
-
查找(find or rfind):查找指定字符是否存在
-
find 从左向右查找,rfind 从右向左查找
-
-
替换(replace):在指定位置替换字符串
示例:
void(){ //查找 string str1="abcaecdesadeawag"; int pos=str1.find("de");//查到的是第一个字符第一次出现的位置 int pos1=str1.rfind("de");//查到的是第一个字符最后一次出现的位置 if(pos==-1){ //如果未找到字符串的话,则自动输出-1 cout<<"未找到字符串"<<endl; }else{ cout<<"找到字符串,pos= "<<pos<<endl;//输出6,d第一次出现的位置是6 } str1.find("adea",int n,int pos=x)//从x位置查找adea的前n个字符串出现的位置 //替换 string str2="abcdefg"; str2.replace(1,3,"111111");//替换 从1号位置起的3个字符为111111 cout<<"str2= "<<str2<<endl;//输出str2=a111111efg }
3.1.6string字符串比较
比较方式:
-
按字符的ASCLL码进行对比
相等 返回 0
大于 返回 1
小于 返回 -1
函数原型:
-
int compare(const string &s) const;
-
int compare(const char *s) const;
//与字符串s进行比较.
示例:
string str1="hello"; string str2="hello"; if(str1.cmpare(str2)==0){ cout<<"str1 等于 str2"<<endl; }
3.1.7 string 字符存取
string中单个字符存取方式有两种
-
char& operator[](int n);
//通过 [] -
char& at(int n);
//通过at
示例:
string str="hello world"; for(int i=0;i<str.size();i++){ cout<<str[i]<<" ";//方式一:通过 []访问单个字符 cout<<str.at(i)<<" ";//方式二:通过 at访问单个字符 } str[0]='w';//修改单个字符 str.at(1)='o';//修改单个字符 cout<<str<<endl;
3.1.8 string插入和删除
-
插入:insert()
-
删除:erase()
下标都是从0开始
示例:
string str="hello world"; str.insert(1,"111");//将字符111插入到第1个位置 输出 h111ello world str.insert(x,n,char c);//从第x个位置插入字符串c的n个字符 str.erase(1,3);//从第一个位置起,删除3个字符 输出为 ho world
3.1.9 string子串
从字符串中获取想要的子串
函数原型:
-
string substr(int pos=0,int n=npos) const;
//返回由pos开始的n个字符组成的字符串
string str="hello world"; string subStr=str.substr(1,3); cout<<"subStr = "<<subStr<<endl;//输出结果 ell //实用操作 void test(){ string email="zhangsan@sina.com"; //从邮箱地址中 获取 用户名信息 int pos=email.find('@'); string usrName=email.substr(0,pos); }
总结:灵活运用求字串功能,可以在实际开发中获取有效信息
3.2vector容器
3.2.1vector基本概念
功能:
-
vector数据结构和数组十分相似,也称单端数组
vector与普通数组的区别:
-
数组是静态空间,而vector可以动态扩展
动态扩展:
-
并不是在原空间之后续接新空间,而是找更大的内存空间,然后将原数据拷贝到新空间,释放原空间
-
vector 容器的迭代器是支持随机访问的迭代器
3.1.2 vector构造函数
四种构造方式
示例:
#include <vector> #include <iostream> using namespace std; 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); printVector(v3); //拷贝构造 vector<int>v4(v3); printVector(v4); } int main() { test01(); return 0; }
3.2.3 vector赋值操作
给vector容器进行赋值
-
v.assign()
示例:
#include <vector> #include <iostream> using namespace std; 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); //赋值 operator= vector<int>v2; v2 = v1; printVector(v2); //assign区间赋值,前闭后开 vector<int>v3; v3.assign(v1.begin(), v1.end()); printVector(v3); //saaign n个elem方式赋值 vector<int>v4; v4.assign(10,100); printVector(v4); } int main() { test01(); return 0; }
3.2.4 vector容量和大小
功能描述:
-
对vector容器的容量和大小操作
函数原型:
-
empty();
//判断容器是否为空 -
capacity();
//容器的容量(>=元素个数) -
size();
//返回容器中元素的个数 -
resize(int num);
//重新指定容器的长度为num,若容器指定值使其变长,则以默认值(0)填充新位置//若容器变短,则末尾超出容器长度的元素被删除
-
resize(int num,elem);
//重新指定容器的长度为num,若容器变长,则以elem值填充新位置//若容器变短,则末尾超出容器长度的元素被删除
3.2.5 vector 插入和删除
示例:
#include <vector> #include <iostream> using namespace std; void printVector(vector<int>& v) { for (vector<int>::iterator it = v.begin(); it != v.end(); it++) { cout << *it << " " ; } cout << endl; } //for (int i = 0; i < 10; i++) { // v1.push_back(i); //} //printVector(v1); void test01() { vector<int>v; //尾插 v.push_back(10); v.push_back(20); v.push_back(30); v.push_back(40); v.push_back(50); printVector(v);//遍历 //尾删 v.pop_back(); printVector(v); //插入---第一个参数是迭代器 v.insert(v.begin(), 100); printVector(v); v.insert(v.begin(), 2, 1000);//在第一个位置插入2个1000 printVector(v); //删除---第一个参数是迭代器 v.erase(v.begin()+i);//删除第i个元素 printVector(v); v.erase(v.begin(), v.end());//类似清空 printVector(v); //清空 v.clear(); printVector(v); } int main() { test01(); return 0; }
3.2.6 vector 数据存取
-
v[i]
-
v.at(i)
-
v.front()
-
v.back()
示例:
#include <vector> #include <iostream> using namespace std; void test01() { vector<int>v; for (int i = 0; i < 10; i++) { v.push_back(i); } //利用[]来访问数组中元素 for (int i = 0; i < v.size(); i++) { cout << v[i] << " "; } cout << endl; //利用at方式访问元素 for (int i = 0; i < v.size(); i++) { cout << v.at(i) << " "; } cout << endl; //获取第一个元素 cout << "第一个元素为:" << v.front() << endl; //获取最后一个元素 cout << "最后一个元素为:" << v.back() << endl; } int main() { test01(); return 0; }
3.2.7 vector 互换容器
实现两个容器内元素互换
函数原型:
-
swap(vec);
//将vec与本身元素互换
示例:
#include<iostream> using namespace std; #include<vector> void printVector(vector<int>& v) { for (vector<int>::iterator it = v.begin(); it != v.end(); it++) { cout << *it << " "; } cout << endl; } //基本使用 void test01() { vector<int>v1; for (int i = 0; i < 10; i++) { v1.push_back(i); } cout << "交换前的元素:" << endl; 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); } //实际用途 //巧用swap可以收缩内存空间 void test02() { vector<int>v; for (int i = 0; i < 100000; i++) { v.push_back(i); } cout << "v的容量是:" << v.capacity() << endl;//13w多 cout << "v的大小为:" << v.size() << endl;//10w v.resize(3); cout << "v的容量是:" << v.capacity() << endl;//容量还是13w多 cout << "v的大小为:" << v.size() << endl;//大小为3,造成了空间浪费 //巧用swap可以收缩内存空间 vector<int>(v).swap(v); // vector<int>(v) 是一个匿名对象(假设是容器x),并按照v容器对x进行初始化操作,所以x的大小、容量都为3 // .swap(v) 表示容器交换。匿名对象的特性:在上一语句执行完毕,系统自动回收匿名对象的空间 cout << "v的容量是:" << v.capacity() << endl;//容量变为3 cout << "v的大小为:" << v.size() << endl;//大小为3 } int main() { test01(); test02(); return 0; }
总结:swap可以使两个容器交换,可以达到实用的收缩内存效果
3.2.8 vector预留空间
功能描述:
-
减少vector在动态扩展时的扩展次数
函数原型:
-
reserve(int len);
//容器预留len个元素长度,预留位置不初始化,元素不可访问。
#include<iostream> using namespace std; #include<vector> void test01() { vector<int>v1; //v1.reserve(100000); int* p = NULL; int num=0;//统计开辟空间的次数 for (int i = 0; i < 100000; i++) { v1.push_back(i); if (p != &v1[0]) { p = &v1[0]; num++; } } cout << num << endl;//输出30,即将10w个数压入容器,共开辟了30次空间,较为麻烦。 //使用reserve预留空间,即可一次性压入数据,不会出现压满空间后,重新分配空间,复制数据到新的空间里的情况 } int main() { test01(); return 0; }
总结:如果数据量较大,可以一开始就利用reserve预留空间
3.3 deque 容器
3.3.1 deque 容器的基本概念
功能:
-
是双端数组,可以对头端进行插入删除操作
deque与vector的区别:
-
vector对于头部的插入删除效率低,数据越大,效率越低
-
deque相对而言,对于头部的插入删除速度比vector快
-
vector访问元素的速度回避deque快,这和两者内部实现有关
3.3.2 deque构造函数
#include<iostream> using namespace std; #include<deque> void printDeque(const deque<int>& d) {//对deque容器限制只读,不能改写其中数据,防止for循环内改写数据 for (deque<int>::const_iterator it = d.begin(); it != d.end(); it++) {//使用“不呢个修改的”迭代器 //*it=100;//容器中数据不能修改 cout << *it << " "; } cout << endl; } void test01() { //默认构造 deque<int>d1; for (int i = 0; i < 10; i++) { d1.push_back(i); } printDeque(d1); //区间构造 deque<int>d2(d1.begin(), d1.end()); printDeque(d2); //n个elem构造 deque<int>d3(10, 100); printDeque(d3); //拷贝构造 deque<int>d4(d3); printDeque; } int main() { test01(); return 0; }
3.3.3 deque赋值操作
类vector赋值
-
"="赋值
-
assign区间赋值 d.assign()
-
assign n个elem赋值 d.(10,100)
3.3.4 deque 大小操作
类vector,但deque无容量概念
-
d.empty();
//判断容器是否为空 -
d.size();
//返回容器中元素的个数 -
d.resize(int num);
//重新指定容器的长度为num,若容器指定值使其变长,则以默认值(0)填充新位置//若容器变短,则末尾超出容器长度的元素被删除
-
d.resize(int num,elem);
//重新指定容器的长度为num,若容器变长,则以elem值填充新位置//若容器变短,则末尾超出容器长度的元素被删除
3.3.5 deque 插入和删除
函数原型:
两端插入操作:
-
push_bank(elem);
//尾部插入一个元素 -
push_front(elem);
//头部 -
pop_back(elem);
//尾部删除一个元素 -
pop_front(elem);
//头部
指定位置操作:插入和删除提供的位置全都是迭代器
-
insert(pos,elem);
//在pos位置插入一个元素的拷贝,返回新数据的位置 -
insert(pos,n,elem);
//在pos位置插入n个elem,无返回值 -
insert(pos,beg,end);
//在pos位置插入区间内的数据,无返回值 -
clear();
//清空容器中所有数据 -
erase(beg,end);
//删除区间内的数据,返回下一个数据的位置 -
erase(pos);
//删除pos位置的数据,返回下一个数据位置
3.3.6 deque 数据存取
-
v.[i]
//利用[]来访问数组中元素 -
v.at(i)
//利用at方式访问元素 -
v.front()
//获取第一个元素 -
v.back()
//获取最后一个元素
3.3.7 deque 排序
利用算法实现对容器进行排序
算法:使用算法函数需包含标准算法头文件(algorithm)
-
sort(iterator beg,iterator end)
//对(beg,end)区间内的元素进行排序
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); } int main(){ sort(d1.begin(),d1.end());//默认从小到大排序 //对于支持随机访问的迭代器的容器,都可以利用sort算法直接对其排序 //vector、deque都支持使用sort算法 }
3.4案例-评委打分
3.4.1 案例描述
-
有5名选手ABCDE,10名评委分别对没一名选手进行打分,去除最高分,去除最低分,取平均值
3.4.2 实现步骤
-
创建五名选手(类),放到vector中
-
遍历vector容器,取出来每名选手,执行for循环,将10位评委的打分存到deque容器(可以对头端尾端进行操作)中
-
sort算法对deque容器中的分数进行排序,去除最低和最高分
-
遍历deque容器,累加得分
-
获取平均值
示例代码:
#include<iostream> using namespace std; #include<deque> #include<vector> #include<algorithm> #include<string> #include<ctime> //选手类 class Person { public: Person(string name, int score) { this->m_Name = name; this->m_Score = score; } string m_Name;//姓名 int m_Score;//平均分 }; void createPerson(vector<Person>& v) { string nameSeed = "ABCDE";//名称的种子 for (int i = 0; i < 5; i++) { string name = "选手"; name += nameSeed[i]; int score = 0;//平均分初始化 Person p(name, score); v.push_back(p);//将创建的person对象放到容器中 } } 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;//0~100的随机数 d.push_back(score); } //测试 cout << "选手:" << it->m_Name << " 打分:"; for (deque<int>::iterator dit = d.begin(); dit != d.end(); dit++) { cout << *dit << " "; } cout << endl; sort(d.begin(), d.end());//排序 d.pop_back(); d.pop_front();//删除最低和最高分 //取平均分 int sum = 0; for (deque<int>::iterator dit = d.begin(); dit != d.end(); dit++) { sum +=*dit;//* } int avg = sum / d.size(); //将平均分赋给到选手身上 it->m_Score = avg;//it就是人的指针 } } void showScore(vector<Person>&v) { for (vector<Person>::iterator it = v.begin(); it != v.end(); it++) { cout << "姓名:" << it->m_Name << " 平均分:" << it->m_Score << endl; } } int main() { //随机数种子 srand((unsigned int)time(NULL)); //1、创建五名选手 vector<Person>v;//存放选手的容器 createPerson(v); //测试 /*for (vector<Person>::iterator it = v.begin(); it != v.end(); it++) { cout << "姓名:"<<(*it).m_Name << "分数:" << (*it).m_Score << endl; }*/ //2、给五名选手打分 setScore(v); //3、显示最后得分 showScore(v); return 0; }
3.5 stack 容器
3.5.1 stack容器基本概念
概念:stack(栈)是一种先进后出(First In Last Out,FILO)的数据结构,他只有一个出口
栈中只有栈顶元素可以被访问,因此栈不允许被遍历
3.5.2 stack 常用接口
函数构造:
-
无参构造--
stack<int>s;
-
拷贝构造--stack<int>s(s1);
赋值操作:
-
”=“赋值--
s=s1;
数据存取:
-
入栈--
push()
-
出栈--
pop()
-
取栈顶元素--
top()
-
返回栈顶元素,不在堆栈中删除它--
peek()
大小操作:
-
判断栈是否为空--
empty()
-
栈的大小--
size()
示例:
void test01(){ stack<int>s; //入栈 s.push(10); s.push(20); s.push(30); s.push(40); cout<<"栈的大小:"<<s.size()<<endl; //栈不为空,查看栈顶,执行出栈操作 while(!empty()){ //查看栈顶元素 cout<<"栈顶元素是:"<<s.top<<endl; //出栈 s.pop(); } }
3.6 queue 容器
3.6.1 queue基本概念
概念:Queue(队列)是一种先进先出(First In First Out,FIFO)的数据结构,它有两个出口
队列容器允许从一端新增数据,从另一端移除数据
队列中只有队头和队尾才可以被外界使用,因此队列不要允许有遍历行为
3.6.2 queue 常用接口
函数构造:
-
无参构造--
queue<int>q;
-
拷贝构造--queue<int>q(q1);
赋值操作:
-
”=“赋值--```q=q1;
数据存取:
-
入队--
push()
-
出队--
pop()
-
返回最后一个元素--
back()
-
返回第一个元素--
front()
大小操作:
-
判断栈是否为空--
empty()
-
队列的大小--
size()
3.7 list 容器
3.7.1 list基本概念
功能:对数据进行链式存储
链表(list)是一种物理存储单元上非连续的存储结构,数据元素的逻辑顺序是通过链表中的指针连接实现的
链表的组成:由一系列结点组成
结点的组成:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域
STL中链表是一个双向循环链表
由于链表的存储方式并不是连续的内存空间,因此链表list中的迭代器只支持前移和后移,属于双向迭代器
list的优点:
-
采用动态存储分配,不会造成内存浪费和溢出(若vector中有十万个元素,则容器容量可能达到十三万)
-
链表执行插入和删除操作十分方便,修改指针即可,不需要要移动大量元素
list的缺点:
-
空间(指针域)和时间(遍历)额外耗费大
list的一个重要性质:插入和删除操作都不会造成原有list迭代器的失效,这在vector是不成立的(若vector容器已满,再插入元素时,则会重新开辟空间,迭代器失效)
总结:STL中List和Vector是最常被使用的容器,各有优缺点
3.7.2 list构造函数
-
默认构造
-
区间构造
-
n个elem构造
-
拷贝构造
#include<iostream> using namespace std; #include<list> void printList(const list<int>& l) { for (list<int>::const_iterator it = l.begin(); it != l.end(); it++) { cout << *it << " "; } cout << endl; } void test01() { //默认构造 list<int>L1; L1.push_back(10); L1.push_back(20); L1.push_back(30); L1.push_back(40); //遍历容器 printList(L1); //区间构造 list<int>L2(L1.begin(), L1.end()); printList(L2); //n个elem方式进行构造 list<int>L3(10, 100); printList(L3); //拷贝构造 list<int>L4(L3); printList(L4); } int main() { test01(); return 0; }
3.7.3 list赋值和交换
给list容器进行赋值
-
l.assign();
给list容器进行
-
swap(lst);
//将lst与本身元素互换
示例:
#include <list> #include <iostream> using namespace std; void printList(list<int>& l) { for (list<int>::iterator it = l.begin(); it != l.end(); it++) { cout << *it << " "; } cout << endl; } void test01() { list<int>l1; for (int i = 0; i < 10; i++) { l1.push_back(i); } printList(l1); //赋值 operator= list<int>l2; l2 = l1; printList(l2); //assign区间赋值,前闭后开 list<int>l3; l3.assign(l1.begin(), l1.end()); printList(l3); //saaign n个elem方式赋值 list<int>l4; l4.assign(10, 100); printList(l4); } void test02() { list<int>l1; for (int i = 0; i < 10; i++) { l1.push_back(i); } cout << "交换前的元素:" << endl; printList(l1); list<int>l2; for (int i = 10; i > 0; i--) { l2.push_back(i); } printList(l2); cout << "交换后:" << endl; l1.swap(l2); printList(l1); printList(l2); } int main() { //test01(); test02(); return 0; }
3.7.4 list 大小操作
-
empty();
//判断容器是否为空 -
capacity();
//容器的容量(>=元素个数) -
size();
//返回容器中元素的个数 -
resize(int num);
//重新指定容器的长度为num,若容器指定值使其变长,则以默认值(0)填充新位置//若容器变短,则末尾超出容器长度的元素被删除
-
resize(int num,elem);
//重新指定容器的长度为num,若容器变长,则以elem值填充新位置//若容器变短,则末尾超出容器长度的元素被删除
3.7.5 list插入和删除
函数原型:
两端插入操作:
-
push_bank(elem);
//尾部插入一个元素 -
push_front(elem);
//头部插入一个元素 -
pop_back(elem);
//尾部删除一个元素 -
pop_front(elem);
//头部删除一个元素
指定位置操作:插入和删除提供的位置全都是迭代器
-
insert(pos,elem);
//在pos位置插入一个元素的拷贝,返回新数据的位置 -
insert(pos,n,elem);
//在pos位置插入n个elem,无返回值 -
insert(pos,beg,end);
//在pos位置插入区间内的数据,无返回值 -
clear();
//清空容器中所有数据 -
erase(beg,end);
//删除区间内的数据,返回下一个数据的位置 -
erase(pos);
//删除pos位置的数据,返回下一个数据位置 -
remove();
//移除容器中所有与elem值匹配的元素
3.7.6 list数据存取
-
l.front()
//获取第一个元素 -
l.back()
//获取最后一个元素
不可以使用[]或at方式访问元素:list本质是链表,不是用联系线性空间存储数据,迭代器也是不支持随机访问的
list<int>::iterator it=l.begin(); it++;//正确,支持双向 it=it+1;//错误,不支持随机访问
3.7.7 list 反转和排序
函数原型:
-
reverse();
//反转链表 -
sort();
//链表排序
#include<iostream> using namespace std; #include<list> void printList(const list<int>& l) { for (list<int>::const_iterator it = l.begin(); it != l.end(); it++) { cout << *it << " "; } cout << endl; } bool myCompare(int v1,int v2) { //降序 就让第一个 > 第二个数 return v1 > v2; } void test01() { list<int>L1; L1.push_back(10); L1.push_back(30); L1.push_back(40); L1.push_back(20); L1.push_back(50); cout << "反转前链表元素;"; printList(L1); //反转链表 L1.reverse(); cout << "反转后链表元素;"; printList(L1); //排序 L1.sort();//默认从小到大升序排序 cout << "排序后链表元素;"; printList(L1); //从大到小降序排列 cout << "降序后链表元素;"; L1.sort(myCompare); printList(L1); } int main() { test01(); return 0; }
3.7.8排序案例
案例描述:将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 comparePerson(Person& p1, Person& p2) { //按年龄升序 if (p1.m_Age == p2.m_Age) { //年龄相同,按照身高降序 return p1.m_Height > p2.m_Height; } else { return p1.m_Age < p2.m_Age; } } void test01() { list<Person>L; Person p1("刘备", 35, 175); Person p2("曹操", 45, 176); Person p3("赵云", 33, 180); Person p4("关羽", 35, 177); Person p5("张飞", 30, 174); Person p6("孙权", 35, 176); //插入数据 L.push_back(p1); L.push_back(p2); L.push_back(p3); L.push_back(p4); L.push_back(p5); L.push_back(p6); for (list<Person>::iterator it = L.begin(); it != L.end(); it++) { cout << "姓名:"<<(*it).m_Name << " 年龄:"<<(*it).m_Age<<" 身高:"<<it->m_Height<<endl; } //排序 cout << "-----------------------------" << endl; cout << "排序后:" << endl; L.sort(comparePerson); for (list<Person>::iterator it = L.begin(); it != L.end(); it++) { cout << "姓名:" << (*it).m_Name << " 年龄:" << (*it).m_Age << " 身高:" << it->m_Height << endl; } } int main() { test01(); return 0; }
总结:
-
对于自定义数据类型,必须要指定排序规则否则编译器不知道如何排序
-
高级排序只是在排序规则上再进行一次逻辑规则制定,并不复杂
3.8 set/multiset 容器
3.8.1 set 基本概念
简介:
-
所有元素都会在插入时自动被排序
-
都使用<set>头文件
本质:
-
set/multiset属于关联式容器,底层结构是二叉树实现
3.8.2 set 构造和赋值
函数构造:
-
默认构造--
set<int>st;
-
拷贝构造--set<int>st(st1);
函数赋值:
-
“=”赋值---
s1=s2;
插入数据:只有 insert 方式
s1.insert(10);
3.8.3 set 大小和交换
函数原型:
-
empty();
//判断容器是否为空 -
size();
//返回容器中元素的个数
-
swap(st);
//将lst与本身元素互换
3.8.4 set插入和删除
函数原型:
-
insert(elem);
//在容器中插入一个元素 -
clear();
//清空容器中所有数据 -
erase(beg,end);
//删除区间内的数据,返回下一个元素的迭代器 -
erase(pos);
//删除pos位置的数据,返回下一个元素的迭代器 -
erase(elem);
//删除容器中值为elem的元素
3.8.5 set 查找和统计
函数原型:
-
find(key);
//查找key是否存在,若存在,返回该键的迭代器,若不存在,返回set.end(); -
count(key);
//统计key的元素个数
示例:
#include<iostream> #include<set> using namespace std; void test01() { set<int>s; s.insert(10); s.insert(20); s.insert(30); s.insert(40); set<int>::iterator pos=s.find(30); if (pos != s.end()) { cout << "找到元素:" << *pos << endl; } else { cout << "未找到元素" << endl; } } void test02() { set<int>s; s.insert(10); s.insert(30); s.insert(30); s.insert(40); //统计,对于set,个数只能是1或0 int num = s.count(30); cout << "num=" << num << endl; } int main() { test01(); test02(); return 0; }
3.8.6 set和multiset区别
-
set不允许容器中有重复元素
-
multiset允许容器中有重复元素
3.8.7 pair对组创建
功能描述:
-
成对出现的数据,利用对组可以返回两个数据
两种创建方式:
-
pair<type,type> p (value1,value2);
-
pair<type,type> p = make_pair(value1,value2);
示例:
#include<iostream> #include<algorithm> using namespace std; void test01() { //第一种方式: pair<string, int>p("Tom", 20); cout << "姓名:" << p.first << " 年龄:" << p.second << endl; //第一种方式: pair<string, int>p2=make_pair("Jerry", 18); cout << "姓名:" << p2.first << " 年龄:" << p2.second << endl; } int main() { test01(); return 0; }
3.8.8 set 容器排列
-
set 自动按从小到大排序,改变排序规则
主要技术点:
-
利用仿函数,可以改变排序规则
示例一:set存放内置数据类型
#include<iostream> #include<set> using namespace std; class MyCompare { public: bool operator()(int v1,int v2) {//重载小括号、参数列表 return v1>v2; } }; void test01() { set<int>s1; s1.insert(10); s1.insert(30); s1.insert(20); s1.insert(40); s1.insert(50); for (set<int>::iterator it = s1.begin(); it != s1.end(); it++) { cout << *it << " "; } cout << endl; set<int,MyCompare>s2; s2.insert(10); s2.insert(30); s2.insert(20); s2.insert(40); s2.insert(50); for (set<int,MyCompare>::iterator it = s2.begin(); it != s2.end(); it++) { cout << *it << " "; } cout << endl; } int main() { test01(); return 0; }
示例一:set存放自定义数据类型
3.9 map/multimap 容器
3.9.1 map 基本概念
简介:
-
map中所有元素都是pair
-
pair中第一个元素为key(键值),起到索引作用,第二个元素为value(实值)
-
所有元素都会根据元素的键值自动排序
本质:
-
map/multimap 属于关联式容器,底层结构是用二叉树实现
优点:
-
可以根据key值快速找到value值
map和multimap区别:
-
map不允许容器中有重复key值元素
-
multimap允许容器中有重复key值元素
3.9.2 map构造和赋值
函数构造:
-
默认构造--
map<int>mp;
-
拷贝构造--map<int>mp(mp1);
函数赋值:
-
“=”赋值---
mp1=mp2;
示例:
#include<iostream> using namespace std; #include<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; } } 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); map<int, int>m2(m);//拷贝构造 printMap(m2); //赋值 map<int, int>m3; m3 = m; } int main() { test01(); return 0; }
3.9.3 map 大小和交换
函数原型:
-
empty();
//判断容器是否为空 -
size();
//返回容器中元素的个数
-
swap(st);
//将lst与本身元素互换
3.9.4 map插入和删除
函数原型:
-
insert(elem);
//在容器中插入一个元素 -
clear();
//清空容器中所有数据 -
erase(beg,end);
//删除区间内的数据,返回下一个元素的迭代器 -
erase(pos);
//删除pos位置的数据,返回下一个元素的迭代器 -
erase(key);
//删除容器中键值为key的元素
示例:
#include<iostream> using namespace std; #include<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; } } 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()); printMap(m); m.erase(3);//按照key删除 printMap(m); // m.erase(m.begin()+1,m.end()); m.clear(); printMap(m); } int main() { test01(); return 0; }
3.9.5 map 查找和统计
函数原型:
-
find(key);
//查找key是否存在,若存在,返回该键的迭代器,若不存在,返回set.end(); -
count(key);
//统计key的元素个数,要么是0,要么是1
3.9.6 map 容器排序
-
map 自动按照key值从小到大排序,改变排序规则
主要技术点:
-
利用仿函数,可以改变排序规则
3.10 案例-员工分组
3.10.1 案例描述
-
公司今天招聘了10个员工(ABCDEFGHIJ)10名员工进入公司之后,需要指派员工在哪个部门工作
-
员工信息有:姓名、工资组成。部门分为策划、美术、研发。
-
随机给10名员工分配部门和工资。
-
通过multimap进行信息的插入。Key(部门编号)value(员工)。
-
分部门显示员工信息。
3.10.2 实现步骤
-
创建10名员工放到vector容器中。
-
遍历vector容器,取出每个员工进行随机分组。
-
分组后,将员工部门编号作为Key,具体员工作为value放入multimap容器中。
-
分部门显示员工信息。
示例:
#include<iostream> using namespace std; #include<map> #include<vector> #include<string> #include<ctime> #define CEHUA 0 #define MEISHU 1 #define YANFA 2 class Worker { public: Worker(string name, int salary) { this->m_Name = name; this->m_Salary = salary; } string m_Name; int m_Salary; }; void creatWorker(vector<Worker>&v) { string nameSeed = "ABCDEFGHIJ"; for (int i = 0; i < 10; i++) { string name = "员工"; name += nameSeed[i]; int salary =rand()%10001+10000; Worker p(name, salary); v.push_back(p); } } void setGroup(vector<Worker>&v, multimap<int, Worker>& m) { for (vector<Worker>::iterator it = v.begin(); it != v.end(); it++) { //产生随机部门编号 int deptId =rand()%3;//编号有:0 1 2 //将员工插入到分组中 //key是部门编号,value是具体的员工 m.insert(make_pair(deptId, *it)); } } void showWorkerByGroup(multimap<int, Worker>& m) { cout << "策划部门:" << endl; multimap<int, Worker>::iterator pos = m.find(CEHUA); int count = m.count(CEHUA);//统计策划部门人数 int index = 0; for (; pos !=m.end()&&index<count; pos++,index++) { cout << "姓名:" << (*pos).second.m_Name << " 工资:" << pos->second.m_Salary << endl; } cout << "---------------------------------------------" << endl;; cout << "美术部门:" << endl; pos = m.find(MEISHU); count = m.count(MEISHU);//统计策划部门人数 index = 0; for (; pos != m.end()&&index < count; pos++,index++) { cout << "姓名:" << (*pos).second.m_Name << " 工资:" << pos->second.m_Salary << endl; } cout << "---------------------------------------------"<<endl;; cout << "研发部门:" << endl; pos = m.find(YANFA); count = m.count(YANFA);//统计策划部门人数 index = 0; for (; pos != m.end()&&index < count; pos++,index++) { cout << "姓名:" << (*pos).second.m_Name << " 工资:" << pos->second.m_Salary << endl; } } int main() { srand((unsigned int)time(NULL)); //1、创建员工 vector<Worker>v; creatWorker(v); //测试 /*for (vector<Worker>::iterator it = v.begin(); it != v.end(); it++) { cout << "姓名:" << it->m_Name << " 工资:" << it->m_Salary << endl; }*/ //2、员工分组 multimap<int, Worker>m; setGroup(v,m); //3、分组显示员工 showWorkerByGroup(m); return 0; }
4 STL-函数对象(仿函数)
4.1 函数对象
4.1.1 函数对象概念
概念:
-
重载函数调用操作符的类, 其对象常称为函数对象
-
函数对象使用重载的()时, 行为类似函数调用, 也叫仿函数
本质:
函数对象(仿函数)是一个类, 不是一个函数
4.1.2 函数对象使用
特点:
-
函数对象在使用时, 可以像普通函数那样调用, 可以有参数, 可以有返回值
-
函数对象超出普通函数的概念, 函数对象可以有自己的状态
-
函数对象可以作为参数传递
示例:
#include<iostream> using namespace std; #include<string> //函数对象 (仿函数) //1. 函数对象在使用时, 可以像普通函数那样调用, 可以有参数, 可以有返回值 //2. 函数对象超出普通函数的概念, 函数对象可以有自己的状态 //3. 函数对象可以作为参数传递 class MyAdd { public: int operator()(int v1, int v2) { return v1 + v2; } }; //1. 函数对象在使用时, 可以像普通函数那样调用, 可以有参数, 可以有返回值 void test01() { MyAdd myAdd;//myAdd 就是一个函数对象 cout<<myAdd(10, 10) << endl; } //2. 函数对象可以有自己的状态 class MyPrint { public: MyPrint() { this->count = 0; } void operator()(string test) { cout << test << endl; this->count++; } int count;//内部自己状态 }; void test02() { MyPrint myPrint; myPrint("Hello World!"); myPrint("Hello World!"); myPrint("Hello World!"); cout << "myPrint调用的次数为:" << myPrint.count << endl; } //3. 函数对象可以作为参数传递 void doPrint(MyPrint& mp,string test) { mp(test); } void test03() { MyPrint myPrint; doPrint(myPrint, "Hello C++"); } int main() { //test01(); //test02(); test03(); return 0; }
4.2 谓词
4.2.1 谓词概念
概念:
-
返回bool类型的仿函数称为谓词
-
如果operator()接受一个参数, 那么叫做一元谓词
-
如果operator()接受两个参数, 那么叫做二元谓词
4.2.2 一元谓词
示例:
#include<iostream> using namespace std; #include<vector> #include<algorithm> //返回bool类型的仿函数称为 谓词 //如果operator()()接受一个参数, 那么叫做一元谓词 class GreaterFive { public: bool operator()(int val) {//一元谓词 return val > 5;//val>5时 返回true,否则返回false } }; void test01() { 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(), GreaterFive());//GreaterFive()是一个匿名对象 if (it == v.end()) { cout << "未找到" << endl; } else { cout << "找到了大于5的数字为:" << *it << endl; } } //如果operator()接受两个参数, 那么叫做二元谓词 int main() { test01(); return 0; }
算法:find_if();
4.2.3 二元谓词
示例:
#include<iostream> using namespace std; #include<vector> #include<algorithm> //二元谓词 class MyCompare { public: bool operator()(int val1, int val2) { return val1 > val2; } }; void test01() { vector<int>v; v.push_back(10); v.push_back(40); v.push_back(50); v.push_back(20); v.push_back(30); sort(v.begin(), v.end()); for (vector<int>::iterator it = v.begin(); it != v.end(); it++) { cout << *it << " "; } cout << endl; //使用函数对象 改变算法策略, 变为从大到小排序 sort(v.begin(), v.end(), MyCompare()); cout << "---------------------" << endl; for (vector<int>::iterator it = v.begin(); it != v.end(); it++) { cout << *it << " "; } cout << endl; } int main() { test01(); return 0; }
sort();算法
4.3 内建函数对象
4.3.1 内建函数对象意义
概念:
-
STL内建了一些函数对象
分类:
-
算数仿函数
-
关系仿函数
-
逻辑仿函数
用法:
-
这些仿函数所产生的对象, 用法和一般函数完全相同
4.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>
// 取反仿函数
示例:
#include<iostream> using namespace std; #include<functional>//内建函数对象头文件 //内建函数对象 算数仿函数 //negate 一元仿函数 取反仿函数(负数) void test01() { negate<int>n; cout << n(50) << endl; } //plus 二元仿函数 加法 void test02() { plus<int>p; cout << p(10, 20) << endl; } int main() { test01(); test02(); return 0; }
总结:使用内建函数对象时, 需要引入头文件#include<functional>
4.3.3 关系仿函数
功能描述:
-
实现关系对比
仿函数原型:
-
template<class T> bool equal_to<T>
//等于 -
template<class T> bool not_equal<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>
// 小于等于
示例:
#include<iostream> using namespace std; #include<vector> #include<functional> #include<algorithm> //内建函数对象_关系仿函数 //大于 greater class MyCompare { public: bool operator()(int v1,int v2) { return v1 > v2; } }; void test01() { vector<int>v; v.push_back(10); v.push_back(30); v.push_back(50); v.push_back(20); v.push_back(40); for (vector<int>::iterator it = v.begin(); it != v.end(); it++) { cout << *it << " "; } cout << endl; //降序 //sort(v.begin(), v.end(), MyCompare()); //greater<int>是内建函数对象 sort(v.begin(), v.end(), greater<int>()); for (vector<int>::iterator it = v.begin(); it != v.end(); it++) { cout << *it << " "; } cout << endl; } int main() { test01(); return 0; }
4.3.4 逻辑仿函数
功能描述:
-
实现逻辑运算
仿函数原型:
-
template<class T> bool logical_and<T>
//逻辑与 -
template<class T> bool logical_or<T>
// 逻辑或 -
template<class T> bool logical_not<T>
//逻辑非
示例:
#include<iostream> using namespace std; #include<vector> #include<functional> #include<algorithm> //内建函数对象_逻辑仿函数 //逻辑非 logical_not void test01() { vector<bool>v; v.push_back(true); 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 << " "; } 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 << " "; } cout << endl; } int main() { test01(); return 0; }
总结:逻辑仿函数实际应用较少,了解即可
5 STL-常用算法
概述:
-
算法主要是由头文件
<algorithm>``<functional>``<numeric>
组成 -
<algorithm>
是所有STL头文件中最大的一个, 范围设计到 比较, 交换, 查找, 遍历操作, 复制, 修改等 -
<numeric>
体积很小, 只包括几个在序列上面进行简单数学运算的模板函数 -
<functional>
定义了一些模板类, 用以声明函数对象
5.1 常用遍历算法
算法简介:
-
for_each
//遍历容器 -
transform
//搬运容器到另一个容器中
5.1.1 for_each
功能描述:
-
实现遍历容器
函数原型:
-
for_each(iterator beg,iterator end,_func);
//遍历算法 遍历容器元素
//beg 开始迭代器
//end 结束迭代器
//_func 函数或函数对象(使用普通函数时不带函数名后的括号)
示例:
#include<iostream> using namespace std; #include<vector> #include<algorithm> //常用遍历算法 for_each //普通函数 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; } int main() { test01(); return 0; }
总结: for_each在实际开发中是最常用的遍历算法, 需要熟练掌握
5.1.2 transform
功能描述:
-
搬运容器到另一个容器
函数原型:
-
transform(iterator beg1,itrator end1,itrator beg2,_func);
//beg1 原容器开始迭代器
//end1 原容器结束迭代器
//beg2 目标容器开始迭代器
//_func 函数或者函数对象
示例:
#include<iostream> using namespace std; #include<vector> #include<algorithm> //常用遍历算法 transform class Transform { public: int operator()(int v) { return v+10; } }; class MyPrint { public: void operator()(int val) { cout << val << " "; } }; void test01() { vector<int>v; for (int i = 0; i < 10; i++) { v.push_back(i); } vector<int>vTarget;//目标容器 vTarget.resize(v.size());//目标容器 需要提前开辟空间 transform(v.begin(), v.end(), vTarget.begin(), Transform()); for_each(vTarget.begin(), vTarget.end(), MyPrint()); cout << endl; } int main() { test01(); return 0; }
总结: 搬运的目标容器必须要提前开辟空间, 否则无法正常搬运
5.2 常用查找算法
算法简介:
-
find
//查找元素 -
find_if
//按条件查找 -
adjacent_find
//查找相邻重复元素 -
binary_search
//二分查找法 -
count
//统计元素个数 -
count_if
//按条件统计元素个数
5.2.1 find
功能描述:
-
查找指定元素, 找到返回指定元素的迭代器, 找不到返回结束迭代器end()
函数原型:
-
find(iterator beg, iterator end, value );
//按值查找元素,找到返回指定位置迭代器,找不到返回结束迭代器位置
//beg 开始迭代器
//end结束迭代器
//value 查找的元素
示例:
#include<iostream> using namespace std; #include<vector> #include<algorithm> #include<string> //常用查找算法 //find //查找 内置数据类型 void test01() { vector<int>v; for (int i = 0; i < 10; i++) { v.push_back(i); } //查找 容器中 是否有 5 这个元素 vector<int>::iterator it=find(v.begin(), v.end(), 5); if (it == v.end()) { cout << "没有找到!" << endl; } else { cout << "找到:" << *it << endl; } } class Person { public: Person(string name, int age) { this->m_Name = name; this->m_Age = age; } //重载 == 底层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("aaa", 10); Person p2("bbb", 20); Person p3("ccc", 30); Person p4("ddd", 40); Person p5("eee", 50); //放到容器中 v.push_back(p1); v.push_back(p2); v.push_back(p3); v.push_back(p4); v.push_back(p5); Person pp("bbb", 20); vector<Person>::iterator it=find(v.begin(), v.end(), pp); if (it == v.end()) { cout << "没有找到!" << endl; } else { cout << "找到元素 姓名:" << it->m_Name << " " << "年龄:" << it->m_Age << endl; } } int main() { //test01(); test02(); return 0; }
总结: 利用find可以在容器中找指定的元素, 返回值是迭代器
5.2.2 find_if
功能描述:
-
按条件查找元素
函数原型:
-
find_if(iterator beg, iterator end, _Pred );
//按值查找元素,找到返回指定位置迭代器,找不到返回结束迭代器位置
//beg 开始迭代器
//end结束迭代器
//_Pred 函数或者谓词(返回bool类型的仿函数)
示例:
#include<iostream> using namespace std; #include<vector> #include<algorithm> #include<string> //常用查找算法 find_if //1. 查找内置数据类型 class GreaterFive { public: bool operator()(int val){ return val > 5; } }; void test01() { vector<int>v; for (int i = 0; i < 10; i++) { v.push_back(i); } vector<int>::iterator it = find_if(v.begin(), v.end(), GreaterFive()); if (it == v.end()) { cout << "没有找到" << endl; } else { cout << "找到大于5的数:" <<*it<< endl; } } //2. 查找自定义数据类型 class Person { public: Person(string name,int age) { this->m_Name = name; this->m_Age = age; } string m_Name; int m_Age; }; class Greater20 { public: bool operator()(Person& p) { return p.m_Age > 20; } }; void test02() { vector<Person>v; //创建数据 Person p1("aaa", 10); Person p2("bbb", 20); Person p3("ccc", 30); Person p4("ddd", 40); v.push_back(p1); v.push_back(p2); v.push_back(p3); v.push_back(p4); //找年龄大于20的人 vector<Person>::iterator it=find_if(v.begin(), v.end(), Greater20()); if (it == v.end()) { cout << "没有找到" << endl; } else { cout << "找到大于20的人: 姓名: " <<it->m_Name<<" 年龄: "<<it->m_Age << endl; } } int main() { //test01(); test02(); return 0; }
5.2.3 adjacent_find (adjacent 相邻)
功能描述:
-
查找相邻重复元素
函数原型:
-
adjacent_find(iterator beg, iterator end);
//查找相邻重复元素, 返回相邻元素的第一个位置的迭代器位置
//beg 开始迭代器
//end结束迭代器
示例:
#include<iostream> using namespace std; #include<vector> #include<algorithm> //常用查找算法 adjacent_find void test01() { vector<int>v; v.push_back(0); v.push_back(2); v.push_back(0); v.push_back(3); v.push_back(1); v.push_back(0); v.push_back(3); v.push_back(3); vector<int>::iterator pos=adjacent_find(v.begin(), v.end()); if (pos == v.end()) { cout << "未找到相邻重复元素" << endl; } else { cout << "找到相邻重复元素,第一个相邻重复元素位置是: " << *pos << endl; } } int main() { test01(); return 0; }
5.2.4 binary_search (二分查找法)
功能描述:
-
查找指定元素是否存在
函数原型:
-
binary_search(iterator beg, iterator end,value);
//查找指定元素, 查到, 返回true, 否则false
//注意: 在无序序列中不可使用
//beg 开始迭代器
//end 结束迭代器
//value 查找的元素
示例:
#include<iostream> using namespace std; #include<vector> #include<algorithm> //常用查找算法 binary_search void test01() { vector<int>v; for (int i = 0; i < 10; i++) { v.push_back(i); } //查找容器中是否有元素 9 //注意: 容器必须是有序的序列. 如果是无序序列, 结果未知 bool ret = binary_search(v.begin(), v.end(), 9); if (ret == true) { cout << "找到了元素" << endl; } else { cout << "未找到" << endl; } } int main() { test01(); return 0; }
5.2.5 count
功能描述:
-
统计元素个数
函数原型:
-
count(iterator beg, iterator end,value);
//统计元素出现的次数
//beg 开始迭代器
//end 结束迭代器
//value 统计的元素
示例:
#include<iostream> using namespace std; #include<vector> #include<algorithm> #include<string> //常用查找算法 count //1. 统计内置数据类型 void test01() { vector<int>v; v.push_back(10); v.push_back(40); v.push_back(30); v.push_back(40); v.push_back(20); v.push_back(40); cout<<"40出现的次数为:"<< count(v.begin(), v.end(), 40)<<endl; } //1. 统计自定义数据类型 class Person { public: Person(string name, int age) { this->m_Name = name; this->m_Age = age; } bool operator==(const Person& p) {//防止修改P的年龄, 底层要求必须加const if (this->m_Age == p.m_Age) { return true; } else { return false; } } string m_Name; int m_Age; }; void test02() { vector<Person>v; Person p1("刘备", 35); Person p2("关羽", 35); Person p3("张飞", 35); Person p4("赵云", 30); Person p5("马超", 34); //将人员插入到容器中 v.push_back(p1); v.push_back(p2); v.push_back(p3); v.push_back(p4); v.push_back(p5); Person p("诸葛亮", 35); int num = count(v.begin(), v.end(), p); cout << "和诸葛亮同岁的人员个数为: " << num << endl; } int main() { //test01(); test02(); return 0; }
总结: 统计自定义数据类型时, 需要配合重载 operator==
5.2.6 count_if
功能描述:
-
按条件统计元素个数
函数原型:
-
count(iterator beg, iterator end,_Pred);
//按条件统计元素出现的次数
//beg 开始迭代器
//end 结束迭代器
//_Pred 谓词
示例:
#include<iostream> using namespace std; #include<vector> #include<algorithm> //常用查找算法_count_if //1. 统计内置数据类型 class Greater20 { public: bool operator()(int val) { return val > 20; } }; void test01() { vector<int>v; v.push_back(10); v.push_back(20); v.push_back(40); v.push_back(30); v.push_back(20); v.push_back(40); int num = count_if(v.begin(), v.end(), Greater20()); cout << "大于20的元素个数为: " << num << endl; } //2. 统计自定义数据类型 class Person { public: Person(string name, int age) { this->m_Name = name; this->m_Age = age; } string m_Name; int m_Age; }; class AgeGreater20 { public: bool operator()(const Person& p) { return p.m_Age > 20; } }; void test02() { vector<Person>v; Person p1("刘备", 35); Person p2("关羽", 35); Person p3("张飞", 35); Person p4("赵云", 40); Person p5("马超", 20); v.push_back(p1); v.push_back(p2); v.push_back(p3); v.push_back(p4); v.push_back(p5); //统计年龄大于20岁人员个数 int num = count_if(v.begin(), v.end(), AgeGreater20()); cout << "大于20岁的人员个数为: " << num << endl; } int main() { //test01(); test02(); return 0; }
5.3 常用排序算法
算法简介:
-
sort
//对容器内元素进行排序 -
random_shuffle
//shuffle(洗牌) 洗牌, 指定范围内的元素随机调整次序 -
merge
//merge(合并) 容器元素合并, 并存储到另一容器中 -
reverse
//反转指定范围的元素
5.3.1 sort
功能描述:
-
对容器内元素进行排序
函数原型:
-
sort(iterator beg, iterator end, _Pred );
//beg 开始迭代器
//end结束迭代器
//_Pred 谓词(可以填, 可以不填, 不填默认从小到大排序 )
示例:
#include<iostream> using namespace std; #include<algorithm> #include<vector> #include<functional>//内建函数对象头文件 //常用排序算法 sort void myPrint(int val) { cout << val << " "; } void test01() { vector<int>v; v.push_back(10); v.push_back(50); v.push_back(30); v.push_back(20); v.push_back(40); //利用sort进行升序 sort(v.begin(), v.end()); for_each(v.begin(), v.end(), myPrint); cout << endl; //改变为 降序 sort(v.begin(),v.end(),greater<int>());//greater<>()是内建函数对象 for_each(v.begin(), v.end(), myPrint); cout << endl; } int main() { test01(); return 0; }
总结:sort属于开发中最常用的算法之一, 需熟练掌握
5.3.2 random_shuffle
功能描述:
-
洗牌, 指定范围内的元素随机调整次序
函数原型:
-
random_shuffle(iterator beg, iterator end);
//指定范围内的元素随机调整次序
//beg 开始迭代器
//end结束迭代器
示例:
#include<iostream> using namespace std; #include<vector> #include<algorithm> #include<ctime> //常用排序算法 random_shuffle void myPrint(int val) { cout << val << " "; } void test01() { srand((unsigned int)time(NULL)); 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); cout << endl; } int main() { test01(); return 0; }
总结: random_shuffle 洗牌算法比较实用, 使用时记得加随机数种子
5.3.3 merge
功能描述:
-
两个容器元素合并, 并存储到另一个容器中
函数原型:
-
random_shuffle(iterator beg1, iterator end1, iterator beg2, iterator end2, iterator dest);
//注意:两个容器必须是有序的
//beg1 容器1开始迭代器
//end1 容器1结束迭代器
//beg2 容器2开始迭代器
//end2 容器2结束迭代器
//dest 目标容器开始迭代器
示例:
#include<iostream> using namespace std; #include<vector> #include<algorithm> //常用排序算法 merge 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 + 1); } //目标容器 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; } int main() { test01(); return 0; }
5.3.4 reverse
功能描述:
-
将容器内的元素进行反转
函数原型:
-
reverse(iterator beg, iterator end);
//反转指定范围的元素
//beg 容器开始迭代器
//end 容器结束迭代器
示例:
#include<iostream> using namespace std; #include<vector> #include<algorithm> //常用排序算法_ reverse void myPrint(int val) { cout << val<<" "; } void test01() { vector<int>v; v.push_back(10); v.push_back(20); v.push_back(30); v.push_back(40); v.push_back(50); cout << "反转前: " << endl; for_each(v.begin(), v.end(), myPrint); cout << endl; cout << "反转后: " << endl; reverse(v.begin(), v.end()); for_each(v.begin(), v.end(), myPrint); } int main() { test01(); return 0; }
5.4 常用拷贝和替换算法
算法简介:
-
copy
//容器内指定范围的元素拷贝到另一个容器中 -
replace
//将容器内指定范围就元素修改为新元素 -
replace_if
//容器内指定范围满足条件的元素替换为新元素 -
swap
//互换两个容器内的元素
5.4.1 copy
功能描述:
-
容器内指定范围的元素拷贝到另一容器中
函数原型:
-
copy(iterator beg, iterator end, interator dest );
//beg 开始迭代器
//end结束迭代器
//iterator dest 目标容器的起始迭代器
示例:
#include<iostream> using namespace std; #include<vector> #include<algorithm> //常用拷贝和替换算法 copy class myPrint { public: void operator()(int val) { cout << val << " " ; } }; void test01() { vector<int>v1; for (int i = 0; i < 10; i++) { v1.push_back(i); } vector<int>v2; v2.resize(v1.size()); copy(v1.begin(), v1.end(), v2.begin()); for_each(v2.begin(), v2.end(), myPrint()); cout << endl; } int main() { test01(); return 0; }
总结:利用copy算法在拷贝时, 目标容器记得提前开辟空间
5.4.2 replace
功能描述:
-
将容器内指定范围的旧元素修改为新元素
函数原型:
-
replace(iterator beg, iterator end, oldvalue, newvalue );
//将区间内 旧元素 替换成 新元素
//beg 开始迭代器
//end结束迭代器
//oldvalue 旧元素
//newvalue 新元素
示例:
#include<iostream> using namespace std; #include<vector> #include<algorithm> //常用拷贝和替换算法 repalce void myPrint(int val) { cout << val << " "; } void test01() { vector<int>v; v.push_back(20); v.push_back(30); v.push_back(50); v.push_back(30); v.push_back(40); v.push_back(20); v.push_back(10); v.push_back(20); cout << "替换前: " << endl; for_each(v.begin(), v.end(), myPrint); cout << endl; replace(v.begin(), v.end(), 20, 60); cout << "替换后: " << endl; for_each(v.begin(), v.end(), myPrint); cout << endl; } int main() { test01(); return 0; }
5.4.3 replace_if
功能描述:
-
将容器内满足条件的元素, 替换成指定元素
函数原型:
-
replace_if(iterator beg, iterator end, _Pred, newvalue );
//按条件替换元素, 满足条件的替换成指定元素
//beg 开始迭代器
//end结束迭代器
//_Pred 谓词
//newvalue 新元素
示例:
#include<iostream> using namespace std; #include<vector> #include<algorithm> //常用拷贝和替换算法 repalce_if class myPrint { public: void operator()(int val) { cout << val << " "; } }; class Greater30 { public: bool operator()(int val) { return val >= 30; } }; void test01() { vector<int>v; v.push_back(20); v.push_back(30); v.push_back(50); v.push_back(30); v.push_back(40); v.push_back(20); v.push_back(10); v.push_back(20); cout << "替换前: " << endl; for_each(v.begin(), v.end(), myPrint()); cout << endl; //将大于等于30的替换为90 replace_if(v.begin(), v.end(),Greater30() , 90); cout << "替换后: " << endl; for_each(v.begin(), v.end(), myPrint()); cout << endl; } int main() { test01(); return 0; }
总结: replace_if 按条件查找, 可以利用仿函数灵活筛选满足的条件
5.4.4 swap
功能描述:
-
互换两个容器的元素
函数原型:
-
swap(container c1, container c2);
//互换两个容器的元素
//c1 容器1
//c2 容器2
#include<iostream> using namespace std; #include<vector> #include<algorithm> //常用拷贝和替换算法 swap class myPrint { public: void operator()(int val) { cout << val << " "; } }; class Greater30 { public: bool operator()(int val) { return val >= 30; } }; void test01() { vector<int>v1; vector<int>v2; for (int i = 0; i < 10; i++) { v1.push_back(i); v2.push_back(i + 100); } cout << "交换前: " << endl; for_each(v1.begin(), v1.end(), myPrint()); cout << endl; for_each(v2.begin(), v2.end(), myPrint()); cout << endl; cout << "-------------------------" << endl; //将大于等于30的替换为90 swap(v1,v2); cout << "替换后: " << endl; for_each(v1.begin(), v1.end(), myPrint()); cout << endl; for_each(v2.begin(), v2.end(), myPrint()); cout << endl; } int main() { test01(); return 0; }
总结:swap 交换容器时, 注意交换的容器要同种类型
5.5 常用算术生成算法
注意:
-
算术生成算法属于小型算法, 使用时包含头文件为
#include<numeric>
算法简介:
-
accumulate
//计算容器元素累计总和 -
fill
//向容器内添加元素
5.5.1 accumulate
功能描述:
-
计算区间内 容器元素累计总和
函数原型:
-
accumulate(iterator beg, iterator end, value);
//计算容器元素累计总和
//beg 开始迭代器
//end 结束迭代器
//value 起始值
示例:
#include<iostream> using namespace std; #include<vector> #include<numeric> //常用的算术生成算法 void test01() { vector<int>v; for (int i = 0; i <= 100; i++) { v.push_back(i); } int total = accumulate(v.begin(), v.end(), 0); cout << "total = " << total << endl; } int main() { test01(); return 0; }
5.5.2 fill
功能描述:
-
向容器中填充指定的元素
函数原型:
-
fill(iterator beg, iterator end, value);
//向容器中填充指定的元素
//beg 开始迭代器
//end 结束迭代器
//value 填充的值
示例:
#include<iostream> using namespace std; #include<vector> #include<numeric> #include<algorithm> //常用算术生成算法 fill void myPrint(int val) { cout << val << " "; } void test01() { vector<int>v; v.resize(10);//默认填充10个0 //后期重新填充 fill(v.begin(), v.end(), 100); for_each(v.begin(), v.end(), myPrint); cout << endl; } int main() { test01(); return 0; }
5.6 常用集合算法
算法简介:
-
set_intersection
//求两个容器的交集 -
set_union
//求两个容器的并集 -
set_difference
//求两个容器的差集
5.6.1 set_intersection
功能描述:
-
求两个容器的交集
函数原型:
-
set_intersection(iterator beg1, iterator end1, iterator begin2, iterator end2,iterator dest);
//求两个集合的交集
//注意: 两个集合必须是有序序列
//beg1 容器1开始迭代器
//end1 容器1结束迭代器
//beg2 容器2开始迭代器
//end2 容器2结束迭代器
//dest 目标容器开始迭代器
示例:
#include<iostream> using namespace std; #include<vector> #include<algorithm> //常用的集合算法 set_intersection 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; //目标容器需要提前开辟空间 //最特殊情况 大容器包含小容器, 开辟空间, 取小容器的size即可 vTarget.resize(min(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); cout << endl; } int main() { test01(); return 0; }
总结:
-
求交集的两个集合必须是有序序列
-
目标容器开辟空间需要从两个容器中取小值
-
set_intersecton返回值即是交集中最后一个元素的位置
5.6.2 set_union
功能描述:
-
求两个容器的并集
函数原型:
-
set_union(iterator beg1, iterator end1, iterator begin2, iterator end2,iterator dest);
//求两个集合的并集
//注意: 两个集合必须是有序序列
//beg1 容器1开始迭代器
//end1 容器1结束迭代器
//beg2 容器2开始迭代器
//end2 容器2结束迭代器
//dest 目标容器开始迭代器
示例:
#include<iostream> using namespace std; #include<vector> #include<algorithm> //常用的集合算法 set_union 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_union(v1.begin(), v1.end(), v2.begin(), v2.end(), vTarget.begin()); for_each(vTarget.begin(), itEnd, myPrint); } int main() { test01(); return 0; }
5.6.2 set_difference
功能描述:
-
求两个容器的差集(两个容器中不属于交集的部分)
函数原型:
-
set_difference(iterator beg1, iterator end1, iterator begin2, iterator end2,iterator dest);
//求两个集合的并集
//注意: 两个集合必须是有序序列
//beg1 容器1开始迭代器
//end1 容器1结束迭代器
//beg2 容器2开始迭代器
//end2 容器2结束迭代器
//dest 目标容器开始迭代器
示例:
#include<iostream> using namespace std; #include<vector> #include<algorithm> //常用的集合算法 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; //最特殊情况 两个容器没有交集, 取两个容器中大的size作为目标容器开辟空间 vTarget.resize(max(v1.size(), v2.size())); cout << "v1和v2的差集为: " ; vector<int>::iterator itEnd = set_difference(v1.begin(), v1.end(), v2.begin(), v2.end(), vTarget.begin()); for_each(vTarget.begin(), itEnd, myPrint); cout << endl; cout << "v2和v1的差集为: "; itEnd = set_difference(v2.begin(), v2.end(), v1.begin(), v1.end(), vTarget.begin()); for_each(vTarget.begin(), itEnd, myPrint); cout << endl; } int main() { test01(); return 0; }
@#include<iostream> using namespace std;
void test01() {
}
int main() { test01(); return 0; }