- 一、模板
- 1.1 函数模板
- 1.1.1 函数模板基础知识
- 案例一: 数组排序
- 1.2.1 普通函数与函数模板
- 1.2.2 函数模板的局限性
- 1.2 类模板
- 1.2.1 类模板的基础知识
- 1.2.2 类模板与函数模板
- 1.2.3 类模板中的成员函数创建时机
- 1.2.4 类模板成员函数类外实现
- 1.2.5 类模板的对象做函数参数
- 1.2.6 类模板与继承
- 1.2.7 类模板分文件编写
- 1.2.8 类模板与友元
- 案例二: 通用的数组类
- 二、STL
- 2.1 STL的基础知识
- 2.2 STL中的容器,算法,迭代器
- 2.2.1 用vector对容器算法迭代器再认识
- 1. vector存放内置数据类型
- 2. vector存放自定义数据类型
- 3. vector容器嵌套容器
一、模板提高阶段主要针对泛型编程和STL技术
模板就是建立通用的模具,大大提高复用性,也是泛型编程的思想。C++提供两种模板机制:①函数模板 ②类模板
注意:
① 模板不是万能的。
② 模板不能直接使用。
语法:
template函数声明或定义
解释:template— 声明创建模板;typename — 可以用class代替;T — 通用的数据类型
使用:①自动类型推导 ②显示指定类型
意义:提高复用性,将类型参数化。
//函数模板的使用 templatevoid MySwap(T& a, T& b) { T temp = a; a = b; b = temp; } int main() { int a = 10; int b = 20; MySwap(a, b);//自动类型推导 MySwap (a, b);//显示类型推导 cout << "a=" << a << "b=" << b << endl; return 0; }
注意:
① 自动类型推导,必须推导出一致的数据类型T。
② 模板必须要确定出T的数据类型,才可以使用,因为自动类型推导推不出来。
//自动类型推导必须推出一致的类型 templatevoid MySwap(T& a, T& b) { T temp = a; a = b; b = temp; } int main() { int a = 10; double b = 20; MySwap(a, b);//err cout << "a=" << a << "b=" << b << endl; return 0; }
//模板必须要确定出T的数据类型,才可以使用 template案例一: 数组排序void MySwap( ) { cout << "MySwap的调用"; } int main() { MySwap( );//err 自动类型推导推导不出数据类型 MySwap ();//显示指定类型强制推导数据类型 return 0; }
问题描述:
利用函数模板封装一个排序的函数,可以对不同数据类型数组进行排序; 排序规则从大到小,排序算法为选择排序;分别利用char数组和int数组进行测试
//交换模板 template1.2.1 普通函数与函数模板void MySwap(T& a, T& b) { T temp = a; a = b; b = temp; } //打印模板 template void MyPrint(T arr[], int sz) { for (int i = 0; i < sz; i++) { cout << arr[i]; } } //排序模板 template void MySort(T arr[], int sz) { for (int i = 0; i < sz; i++) { T max = arr[i]; for (int j = i + 1; j < sz; j++) { if (arr[j] > arr[i]) { MySwap(arr[j], arr[i]); } } } MyPrint(arr,sz); } int main() { //字符数组测试 char arr1[] = "badcfeg"; MySort(arr1, sizeof(arr1) / sizeof(char)); //整形数组测试 int arr2[] = { 1,3,4,5,2,6,7,9 }; MySort(arr2, sizeof(arr2) / sizeof(int)); }
普通函数与函数模板的区别:
- 普通函数调用时可以发生自动类型转换(隐式类型转换)
- 函数模板调用时,如果利用自动类型推导,不会发生隐式类型转换
- 如果利用显示指定类型的方式,可以发生隐式类型转换
//普通函数
int add(int a, int b)
{
return a + b;
}
//函数模板
template
T MyAdd(T a, T b)
{
return a + b;
}
int main()
{
int a = 10;
int b = 20;
char c = a;
cout<< add(a, b) <(a, c) << endl;//显示显示指定类型可以进行隐式类型转换
}
普通函数与函数模板调用规则:
- 同名普通函数与函数模板,优先调用普通函数。
- 可以通过空模板参数列表来强制调用函数模板。
- 如果函数模板可以产生更好的匹配,优先调用函数模板
- 函数模板可以函数重载
void print(int a)
{
cout << "普通函数调用" << endl;
}
template
void print(T a)
{
cout << "函数模板调用" << endl;
}
template
void print(T a, T b)
{
cout << "重载调用" << endl;
}
int main()
{
int a = 10;
//1.同名普通函数与函数模板,优先调用普通函数。
print(a);//输出:普通函数调用
//2.可以通过空模板参数列表来强制调用函数模板。
print<>(a);//输出:函数模板调用
//3.如果函数模板可以产生更好的匹配,优先调用函数模板
char c = a;
print(c);//输出:函数模板调用
//4.函数模板可以函数重载
print(a, 1);//输出:重载调用
}
1.2.2 函数模板的局限性
❗ 模板的通用性并不是万能的
templatevoid f(T a, T b) { a = b; }
在上述代码中提供的赋值操作,如果传入的a和b是一个数组或是一个类,就无法实现了
✅ 可以为这些特定的类型提供具体化的模板
class person
{
public:
person(string name,int age)
{
this->age = age;
this->name = name;
}
string name;
int age;
};
template
bool test(T a, T b)
{
if (a == b)
return true;
else
return false;
}
//特定的类型提供具体化的模板
//具体化优先于常规模板
template<>bool test(person p1, person p2)
{
if (p1.age == p2.age && p1.name == p2.name)
return true;
else
return false;
}
int main()
{
person p1("张三", 10);
person p2("张三", 10);
if (test(p1, p2))
cout << "a==b" << endl;
else
cout << "a!=b" << endl;
}
1.2 类模板
1.2.1 类模板的基础知识
语法:
template函数声明或定义
解释:template— 声明创建模板;typename — 可以用class代替;T — 通用的数据类型
作用:建立一个通用类,类中的成员 数据类型可以不具体制定,用一个虚拟的类型来代表
//类模板的使用 template1.2.2 类模板与函数模板class person { public: person(NameType name,AgeType age) { this->name = name; this->age = age; } NameType name; AgeType age; }; int main() { person p("xiyang", 18); }
两者使用的区别主要有两个:
- 类模板无法进行自动类型推导。
- 类模板在模板的参数列表中可以有默认参数。
template1.2.3 类模板中的成员函数创建时机class person { public: person(NameType name,AgeType age) { this->name = name; this->age = age; } NameType name; AgeType age; }; int main() { //1.类模板没有自动类型推导 person p1("xiyang", 18);//err //2.类模板在模板参数列表中可以有默认参数 person p2("xiyang", 18); person p3("xiyang",18.1); cout << p3.age;//输出:18 }
类模板中成员函数和普通类中成员函数创建时机是有区别的:
- 普通类中成员函数一开始就创建。
- 类模板中的成员函数只有调用时才创建。
class person1
{
public:
void test1()
{
cout << "test1()" << endl;
}
};
class person2
{
public:
void test2()
{
cout << "test2()" << endl;
}
};
template
class MyClass
{
public:
T t;
void test()
{
t.test1();
t.test2();
}
};
int main()
{
MyClass mc;
//没有编译之前不会报错,编译之后报错
//说明类模板的成员函数是在调用后创建的
}
1.2.4 类模板成员函数类外实现
template1.2.5 类模板的对象做函数参数class person { public: person(T name); void print(); T name; }; //构造函数的类外实现 template person ::person(T name) { this->name = name; } //成员函数的类外实现 template void person ::print() { cout << this->name << endl; }
有三种方式:
- 指定传入的类型 — 直接显示对象的数据类型
- 参数模板化 — 将对象中的参数变为模板进行传递
- 整个类模板化 — 将这个对象类型 模板化进行传递
template1.2.6 类模板与继承class person { public: person(NameType name,AgeType age) { this->name = name; this->age = age; } void print() { cout << this->name << this->age; } NameType name; AgeType age; }; void print1(person & p) { p.print(); } template void print2(person &p) { p.print(); cout << "T1的类型:" << typeid(T1).name() << endl;//模板推出的数据类型 cout << "T2的类型:" << typeid(T2).name() << endl; } template void print3(T& p) { p.print(); cout << "T的数据类型:" << typeid(T).name() << endl; } int main() { person p("xiyang", 18); // 1.指定传入的类型 print1(p); // 2.参数模板化 print2(p); // 3.整个类模板化 print3(p); }
注意:
① 当子类继承的父类是一个类模板时,子类在声明的时候,要指定出父类中T的类型
template1.2.7 类模板分文件编写class base { T m; }; class Son :public base{}; //err T没有指定类型 class Son1 :public base {}; //灵活指定父类中的T类型,子类也需要变类模板 template class Son2 :public base {};
❗ 类模板中成员函数创建时机是在调用阶段,导致分文件编写时链接不到
//person.h #pragma once #include#include using namespace std; template class person { public: person(NameType name,AgeType age); void print(); NameType name; AgeType age; }; //person.cpp #include "person.h" template person ::person(NameType name, AgeType age) { this->name = name; this->age = age; } template void person ::print() { cout << this->age << this->name << endl; } //test.cpp #include "person.h" int main() { person p("xiyang", 18); p.print(); return 0; }
✅ 解决方案:
① 在test.cpp中,将 person.h 改为 person.cpp
② 直接将 person.h 和 person.cpp 合并,后缀为.hpp
//person.hpp #pragma once #include#include using namespace std; template class person { public: person(NameType name,AgeType age); void print(); NameType name; AgeType age; }; template person ::person(NameType name, AgeType age) { this->name = name; this->age = age; } template void person ::print() { cout << this->age << this->name << endl; }
//test.cpp
#include "person.hpp"
int main()
{
personp("xiyang", 18);
p.print();
return 0;
}
1.2.8 类模板与友元
- 全局函数类内实现 - 直接在类内声明友元即可
- 全局函数类外实现 - 需要提前让编译器知道全局函数的存在
//类模板与友元 template案例二: 通用的数组类class person; template void print2(person & p) { cout << "类外" << p.age << p.name; } template class person { //1.全局函数在类内实现 friend void print1(person &p) { cout << "类内" << p.age << p.name; } //2.全局函数类外实现 //类外实现需要让编译器提前看到,所以全局函数放在前面 //全局函数放在前面的同时,需要再提前看到person类,所以还需要声明person类 friend void print2<>(person & p); public: person(T1 name, T2 age) { this->name = name; this->age = age; } private: T1 name; T2 age; }; int main() { //全局函数类内实现测试 person p1 ("xiyang", 18); print1(p1); //全局函数类外实现测试 person p2("xiyang", 19); print2(p2); }
问题描述:实现一个通用的数组类,要求如下:
- 可以对内置数据类型以及自定义数据类型的数据进行存储
- 将数组中的数据存储到堆区
- 构造函数中可以传入数组的容量
- 提供对应的拷贝构造函数以及operator=防止浅拷贝问题
- 提供尾插法和尾删法对数组中的数据进行增加和删除
- 可以通过下标的方式访问数组中的元素
- 可以获取数组中当前元素个数和数组的容量
//MyArray.hpp #pragma once #include#include using namespace std; template class MyArray { public: //构造函数 MyArray(int capacity) { this->capacity = capacity; this->size = 0; this->arr = new T[this->capacity]; } // 拷贝构造——为了解决浅拷贝带来的问题 MyArray(MyArray& ma) { this->capacity = ma.capacity; this->size = ma.size; this->arr = new T[ma.capacity]; for (int i = 0; i < ma.size; i++) { this->arr[i] = ma.arr[i]; } } // operator= ——解决浅拷贝带来的问题 MyArray& operator=(MyArray& ma) { //赋值前有内容则需要清空 if (this->capacity != 0) { this->size = 0; this->capacity = 0; delete[] this->arr; this->arr = NULL; } this->size = ma.size; this->capacity = capacity; this->arr = new T[ma.capacity]; for (int i = 0; i < ma.size; i++) { this->arr[i] = ma.arr[i]; } return *this; } //通过下标访问数据 T& operator[ ](int idex) { //越界 if (idex<0 || idex>size) exit(0);//强制退出 return this->arr[idex]; } //尾插 void PushBack(T val) { //判断是否满了 if (this->capacity == this->size) { return; } this->arr[this->size] = val; this->size++; } //尾删 void PopBack() { //判断是否是空的 if (this->size == 0) { return; } this->size--; } //获取数组容量 int GetCapacity() { return this->capacity; } //获取数组大小 int GetSize() { return this->size; } //析构函数 ~MyArray() { if (this->arr != NULL) { this->capacity = 0; this->size = 0; delete[] this->arr; this->arr = NULL; } } private: T* arr; //指向数组 int capacity; //容量 int size; //数组大小 };
//test.cpp #include "MyArray.hpp" //测试内置数据类型 void printIntArray(MyArray二、STL 2.1 STL的基础知识& arr) { for (int i = 0; i < arr.GetSize(); i++) { cout << arr[i] << " "; } cout << endl; } void test01() { MyArray array1(10); for (int i = 0; i < 10; i++) { array1.PushBack(i); } cout << "array1打印输出:" << endl; printIntArray(array1); cout << "array1的大小:" << array1.GetSize() << endl; cout << "array1的容量:" << array1.GetCapacity() << endl; cout << "--------------------------" << endl; } //测试自定义数据类型 class Person { public: Person() {} Person(string name, int age) { this->name = name; this->age = age; } public: string name; int age; }; void printPersonArray(MyArray & personArr) { for (int i = 0; i < personArr.GetSize(); i++) { cout << "姓名:" << personArr[i].name << " 年龄: " << personArr[i].age << endl; } } void test02() { //创建数组 MyArray pArray(10); Person p1("孙悟空", 30); Person p2("韩信", 20); Person p3("妲己", 18); Person p4("王昭君", 15); Person p5("赵云", 24); //插入数据 pArray.PushBack(p1); pArray.PushBack(p2); pArray.PushBack(p3); pArray.PushBack(p4); pArray.PushBack(p5); printPersonArray(pArray); cout << "pArray的大小:" << pArray.GetSize() << endl; cout << "pArray的容量:" << pArray.GetCapacity() << endl; } int main() { test01(); test02(); }
定义:C++ STL(标准模板库)是一套功能强大的 C++ 模板类,提供了通用的模板类和函数,这些模板类和函数可以实现多种流行和常用的算法和数据结构,如向量、链表、队列、栈。
分类:广义上分为 :① 容器(container) ② 算法(algorithm) ③ 迭代器(iterator)。其中容器和算法之间通过迭代器进行无缝连接。
六大组件:容器、算法、迭代器、仿函数、适配器(配接器)、空间配置器
| 组件名字 | 作用 |
|---|---|
| 容器 | 各种数据结构,如vector、list、deque、set、map等,用来存放数据 |
| 算法 | 各种常用的算法,如sort、find、copy、for_each等 |
| 迭代器 | 扮演了容器与算法之间的胶合剂 |
| 仿函数 | 行为类似函数,可作为算法的某种策略 |
| 适配器 | 一种用来修饰容器或者仿函数或迭代器接口的东西 |
| 空间配置器 | 负责空间的配置与管理 |
-
容器:置物之所也。
STL容器:将运用最广泛的一些数据结构实现出来,常用的数据结构:数组, 链表,树, 栈, 队列, 集合, 映射表 等。
分类:
① 序列式容器: 强调值的排序,序列式容器中的每个元素均有固定的位置。
② 关联式容器: 二叉树结构,各元素之间没有严格的物理上的顺序关系。 -
算法:问题之解法也。
算法(Algorithms):有限的步骤,解决逻辑或数学上的问题。
分类:
① 质变算法:是指运算过程中会更改区间内的元素的内容。例如拷贝,替换,删除等等
② 非质变算法:是指运算过程中不会更改区间内的元素内容,例如查找、计数、遍历、寻找极值等等 -
迭代器:容器和算法之间粘合剂。
提供一种方法,使之能够依序寻访某个容器所含的各个元素,而又无需暴露该容器的内部表示方式。
每个容器都有自己专属的迭代器。迭代器使用非常类似于指针,初学阶段我们可以先理解迭代器为指针。
分类如下表:
| 种类 | 功能 | 支持运算 |
|---|---|---|
| 输入迭代器 | 对数据的只读访问 | 只读,支持++、==、!= |
| 输出迭代器 | 对数据的只写访问 | 只写,支持++ |
| 前向迭代器 | 读写操作,并能向前推进迭代器 | 读写,支持++、==、!= |
| 双向迭代器 | 读写操作,并能向前和向后操作 | 读写,支持++、–, |
| 随机访问迭代器 | 读写操作,可以以跳跃的方式访问任意数据,功能最强的迭代器 | 读写,支持++、–、[n]、-n、<、<=、>、>= |
STL中最常用的容器为vector,可以理解为数组。
1. vector存放内置数据类型 容器: vector
算法: for_each
迭代器:vector
//eg #include2. vector存放自定义数据类型//vector容器的头文件 #include//标准算法头文件 using namespace std; void print(int val) { cout << val; } int main() { //创建一个int类型vector容器,相当于int数组 vector v; //向容器插入数据 v.push_back(0); v.push_back(1); v.push_back(2); v.push_back(3); //通过迭代器访问容器中的数据 vector ::iterator ItBegin = v.begin();//起始迭代器,指向容器中的第一个元素 vector ::iterator ItEnd = v.end();//结束迭代器,指向容器中最后一个元素的下一个位置 //vector 中:0 1 2 3 // | | // begin end // //第一种遍历方式 while (ItBegin != ItEnd) { cout << *ItBegin; ItBegin++; } //第二种遍历方式(相当于整合第一种方式) for (vector ::iterator begin = v.begin(); begin != v.end(); begin++) { cout << *begin; } //第三种遍历方式 for_each(v.begin(), v.end(), print); }
// vector存放自定义数据类型
using namespace std;
class person
{
public:
person(string name, int age)
{
this->age = age;
this->name = name;
}
int age;
string name;
};
int main()
{
//person类
vector v;
//创建
person p1("a", 18);
person p2("b", 18);
person p3("c", 18);
person p4("d", 18);
//向容器中添加数据
v.push_back(p1);
v.push_back(p2);
v.push_back(p3);
v.push_back(p4);
//遍历
for (vector::iterator it = v.begin(); it != v.end(); it++)
{
cout << "姓名:" << (*it).age << endl;//it->age
cout << "年龄:" << (*it).name << endl;//it->name
}
//指针类
vector v1;
//向容器中添加数据
v1.push_back(&p1);
v1.push_back(&p2);
v1.push_back(&p3);
v1.push_back(&p4);
//遍历
for (vector::iterator it = v1.begin(); it != v1.end(); it++)
{
cout << "姓名:" << (*it)->age << endl;
cout << "年龄:" << (*it)->name << endl;
}
}
3. vector容器嵌套容器
//vector容器嵌套容器
int main()
{
//大容器
vector< vector > v;
//小容器
vector v1;
vector v2;
vector v3;
vector v4;
//小容器中添加数据
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);
}
//将小容器的元素插入到大容器中
v.push_back(v1);
v.push_back(v2);
v.push_back(v3);
v.push_back(v4);
//遍历
for (vector>::iterator it = v.begin(); it != v.end(); it++)
{
for (vector::iterator vit = (*it).begin(); vit != (*it).end(); vit++)
{
cout << *vit << " ";
}
cout << endl;
}
}
注意:
意义:
特点:
❗
✅
定义:
①



