1.C++另一种编程思想称为 泛型编程,主要利用的技术就是模板
2.C++提供两种模板机制: 函数模板 和 类模板
函数模板作用:建立一个通用函数,其函数返回值类型和形参类型可以不具体确定,用一个虚拟的类型来代表。
//语法
//函数声明或定义
template<typename T>
解释:
template – 声明创建模板
typename — 表明其后面的符号是一种数据类型,可以用class代替
T ---- 通用的数据类型,,名称可以替换,通常为大写字母。
#include
#include
using namespace std;
//函数模板
//两个整形交互
void swapInt(int &a,int &b){
int t = a;
a = b;
b = t;
}
//两个浮点型交互
void swapDou(double &a,double &b){
double t = a;
a = b;
b = t;
}
//函数模板
template<typename T> //声明一个模板,告诉编译器后面代码中紧跟的T不要报错,T是一个通用数据类型
void mySwap(T &a,T &b){
T tmp = a;
a = b;
b = tmp;
}
int main() {
int a=1,b=2;
//1.自动推导类型
mySwap(a,b);
//2.显示指定类型
mySwap<int>(a,b);
return 0;
}

注意事项:
1.自动类型推导,必须推导出一致的数据类型T,才可以使用
2.模板必须要确定出T的数据类型,才可以使用
#include
#include
using namespace std;
//函数模板注意事项
template<typename T> //typename可以替换为class没有区别
//1.自动类型推导,必须推导出一致的数据类型T,才可以使用
void mySwap(T &a,T &b){
T tmp = a;
a = b;
b = tmp;
}
void test01(){
int a = 10;
int b = 20;
mySwap(a,b);
/*char c = 'c';
mySwap(a,c);//错误 推导不出一致的T类型 */
cout<<a<<"-"<<b<<endl;
}
//2.模板必须要确定出T的数据类型,才可以使用
template<class T>
void func(){
cout<<"调用func"<<endl;
}
void test02(){
//无法调用
//func();
//确定T的类型,可以调用
func<int>();
}
int main() {
int a=1,b=2;
//1.自动推导类型
mySwap(a,b);
//2.显示指定类型
mySwap<int>(a,b);
return 0;
}

#include
#include
using namespace std;
//实现通用 对数组进行排序的函数
//规则 从大到小
//算法 选择排序
//测试 char 数组,int 数组
template<class T>
void mySwap(T &a,T &b){
T tmp = a;
a = b;
b =tmp;
}
//排序算法--选择排序
template<class T>
void mySort(T arr[],int len){
for(int i=0;i<len;i++){
int max = i; //认定最大值下标
for(int j=i+1;j<len;j++){
//认定的最大值比遍历的最大值小,说明j下标元素才是真正的最大值
if(arr[max] < arr[j]){
max = j;
}
}
if(max != i){
//交换max和i元素
mySwap(arr[max],arr[i]);
}
}
}
//打印数组模板
template<class T>
void printArr(T arr[],int len){
for(int i=0;i<len;i++){
cout<<arr[i]<<" ";
}
cout<<endl;
}
void test01(){
//测试char数组
char chaArr[] = "badcfe";
int num = sizeof(chaArr) / sizeof(char);
mySort(chaArr,num);
printArr(chaArr,num);
}
void test02(){
int intArr[] = {7,5,1,5,8,9};
int num = sizeof(intArr) / sizeof(int);
mySort(intArr,num);
printArr(intArr,num);
}
int main() {
test01();
test02();
return 0;
}

#include
#include
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=2,b=3;
char c = 'c';
cout<<myAdd01(a,b)<<endl; //正常
//发生隐式转换
cout<<myAdd01(a,c)<<endl; //正常-隐式转换
//模板函数
cout<<myAdd01(a,b)<<endl; //正常
cout<<myAdd01(a,c)<<endl; //出错
//显示指定类型
cout<<myAdd01<int>(a,c)<<endl; //正常-可以隐式转换
}
int main() {
test01();
test02();
return 0;
}
总结:建议使用显式指定方式,调用函数模板,因为自己可以确定通用类型T。

#include
#include
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=2,b=4;
//调用的是 普通函数
myPrint(a,b);
//通过空参数列表,强制调用函数模板
myPrint<>(a,b);
//函数模板重载
myPrint<>(a,b,100);
//若函数模板可以产生更好的匹配,优先调用函数模板
char c1='a',c2='b';
myPrint(c1,c2); //调用模板,不用隐式转换
}
int main() {
test01();
test02();
return 0;
}
局限性:模板的通用性并不是万能的
#include
#include
using namespace std;
//模板局限性
//模板不是万能的,有些特定数据类型,需要具体化方式做特殊实现
class Person{
public:
Person(string n,int a){
this->name = n;
this->age = a;
}
string name;
int age;
};
//对比两个数据是否相等
template<class T>
bool myCompare(T &a,T &b){
if(a==b){
return true;
}
return false;
}
void test01(){
int a=10,b=20;
bool ret = myCompare(a,b);
if(ret){
cout<<"a==b"<<endl;
}else{
cout<<"a!=b"<<endl;
}
}
//利用具体化Person的版本实现代码,具体化优先调用
template<> bool myCompare(Person &a,Person &b){
if(a.name==b.name && a.age == b.age){
return true;
}
return false;
}
void test02(){
Person p1("Tom",12);
Person p2("Tom",12);
bool ret = myCompare(p1,p2);
if(ret){
cout<<"p1==p2"<<endl;
}else{
cout<<"p1!=p2"<<endl;
}
}
int main() {
test01();
test02();
return 0;
}
总结:
1.利用具体化的模板,可以解决自定义类型的通用化
2.学习模板并不是为了写模板,而是在STL能够运用系统提供的模板
类模板作用:建立一个通用类,类中的成员 数据类型可以不具体制定,用一个虚拟的类型来代表。
//语法
template<typename T>
解释:
1.template – 声明创建模板
2.typename – 表明其后面的符号是一种数据类型,可以用class代替
#include
#include
using namespace std;
//类模板
template<class NameType,class AgeType>
class Person{
public:
Person(NameType n,AgeType a){
this->name = n;
this->age = a;
}
void showPer(){
cout<<this->name<<"-"<<this->age<<endl;
}
NameType name;
AgeType age;
};
void test01(){
//指定数据类型
Person<string,int> p1("王",23);
p1.showPer();
}
int main() {
test01();
return 0;
}
总结:类模板和函数模板类似,在声明模板template后面加类,此类称为类模板
主要区别两点:
1.类模板没有自动类型推导的使用方式
2.类模板在模板参数列表中可以有默认参数
#include
#include
using namespace std;
//类模板 与 函数模板区别
template<class NameType,class AgeType = int>
class Person{
public:
Person(NameType n,AgeType a){
this->name = n;
this->age = a;
}
void showPer(){
cout<<this->name<<"-"<<this->age<<endl;
}
NameType name;
AgeType age;
};
//1.类模板没有自动类型推导使用方式
void test01(){
//指定数据类型
//Person p1("王",23); //错误,编译器不会自动推导
Person<string,int> p1("王",23); //正确,只能用显示指定类型
p1.showPer();
}
//2.类模板 在模板参数列表中可以有默认参数
void test02(){
// template 改为 template
//下边的int不需要写,也可以
Person<string> p1("王",23);
p1.showPer();
}
int main() {
test01();
test02();
return 0;
}
总结:1.类模板使用只能用显示指定类型方式
2.类模板中的模板参数列表可以有默认参数
类模板中成员函数和普通类中成员函数创建时机有区别:
1.普通类中的成员函数一开始就可以创建
2.类模板中的成员函数在调用时才创建
#include
#include
using namespace std;
//类模板中成员函数创建时机
//类模板中成员函数在调用时才创建
class Person1{
public:
void showPer1(){
cout<<"showPer1 show"<<endl;
}
};
class Person2{
public:
void showPer2(){
cout<<"showPer2 show"<<endl;
}
};
template<class T>
class MyClass{
public:
//因为该成员函数,没有调用时候不会创建,因此可以编译通过
T obj;
void func1(){
obj.showPer1();
}
void func2(){
obj.showPer2();
}
};
void test01(){
MyClass<Person1> m1;
//调用func1的时候,才会创建MyClass的成员,此时有 Person1 obj,
m1.func1(); //成功
//m1.func2(); //失败
MyClass<Person2> m2;
m2.func2(); //成功
}
int main() {
test01();
return 0;
}
学习目标:类模板实例化出的对象,向函数传参数的方式
一共有三种传入方式:
1.指定传入的类型 — 直接显示对象的数据类型
2.参数模板化 – 将对象中的参数变为模板进行传递
3.整个类模板化 – 将这个对象类型 模板化进行传递
#include
#include
using namespace std;
//类模板对象做函数参数
template<class T1,class T2>
class Person{
public:
Person(T1 n,T2 a){
this->name = n;
this->age = a;
}
void showPer(){
cout<<this->name<<" "<<this->age<<endl;
}
T1 name;
T2 age;
};
//1.指定传入类型
void printPerson1(Person<string,int> &p){
p.showPer();
}
void test01(){
Person<string,int> p("孙悟空",100);
printPerson1(p);
}
//2.参数模板化
template<class T1,class T2>
void printPerson2(Person<T1,T2> &p){
p.showPer();
cout<<"T1的数据类型"<<typeid(T1).name()<<endl;
cout<<"T2的数据类型"<<typeid(T2).name()<<endl;
}
void test02(){
Person<string,int> p("猪八戒",100);
printPerson2(p);
}
//3.整个类模板化
template<class T>
void printPerson3(T &p){
p.showPer();
cout<<"T的数据类型"<<typeid(T).name()<<endl;
}
void test03(){
Person<string,int> p("唐僧",24);
printPerson3(p);
}
int main() {
test01();test02();test03();
return 0;
}
总结:
1.通过类模板创建的对象,可以有三种方式向函数中进行传参
2.使用比较广泛是第一种:指定传入的类型
当类模板碰到继承,需要注意以下几点
1.当子类继承的父类是一个类模板时,子类声明时候,要指定出父类中T的类型
2.若不指定,编译器无法给子类分配内存
3.若想灵活指定出父类中T的类型,子类也需要变为类模板。
#include
#include
using namespace std;
//继承中的类模板
template<class T>
class Base{
public:
T m;
};
//class Son : public Base{}; //错误,必须要知道父类中的类型,才能继承给子类
class Son : public Base<int>{
};
void test01(){
Son s;
}
//若想零或指定父类中T类型,子类也需要变类模板
template<class T1,class T2>
class Son2:public Base<T1>{
public:
Son2(){
cout<<"T1类型:"<<typeid(T1).name()<<endl;
cout<<"T2类型:"<<typeid(T2).name()<<endl;
}
T1 obj;
};
void test02(){
//父类的T是char,子类额外的属性是int
Son2<int,char> s2;
}
int main() {
test01();test02();
return 0;
}
总结:若父类是类模板,子类需要指定出父类中T的数据类型
学习目标:掌握类模板中成员函数类外实现
#include
#include
using namespace std;
//类模板成员函数类外实现
template<class T1,class T2>
class Person{
public:
Person(T1 n,T2 a);
/*{
this->name = n;
this->age = a;
}*/
void showPer();
/*{
cout<name<<"-"<age<
T1 name;
T2 age;
};
//构造函数类外实现
template<class T1,class T2>
Person<T1,T2>::Person(T1 n,T2 a){
this->name = n;
this->age = a;
}
//成员函数类外实现
template<class T1,class T2>
void Person<T1,T2>::showPer(){
cout<<this->name<<"-"<<this->age<<endl;
}
void test01(){
Person<string,int> p("TOM",34);
p.showPer();
}
int main() {
test01();
return 0;
}
学习目标:掌握类模板成员函数分文件编写产生的问题以及解决方式
问题:类模板中成员函数创建时机是在调用阶段,导致分文件编写时,连接不到
解决:
方式1:直接包含.cpp文件
方式2:将声明和实现写到同一个文件中,并更改后缀为.hpp,hpp是约定的名称,不是强制
//person.h
#pragma once
#include
#include
using namespace std;
template<class T1,class T2>
class Person{
public:
Person(T1 n,T2 a);
void showPer();
T1 name;
T2 age;
};
//person.cpp
#include "person.h"
template<class T1,class T2>
Person<T1,T2>::Person(T1 n,T2 a){
this->name = n;
this->age = a;
}
template<class T1,class T2>
void Person<T1,T2>::showPer(){
cout<<this->name<<"-"<<this->age<<endl;
}
//主函数
#include
#include
//单独包含person.h则无法运行
//#include "person.h"
//单独包含person.h则可以运行,此时直接从cpp文件查找Person构造函数和成员函数
#include "person.cpp"
using namespace std;
void test01(){
Person<string,int> p("TOM",34);
p.showPer();
}
int main() {
test01();
return 0;
}
//person.hpp
#include
#include
using namespace std;
//类模板分文件编写问题及解决
template<class T1,class T2>
class Person{
public:
Person(T1 n,T2 a);
void showPer();
T1 name;
T2 age;
};
template<class T1,class T2>
Person<T1,T2>::Person(T1 n,T2 a){
this->name = n;
this->age = a;
}
template<class T1,class T2>
void Person<T1,T2>::showPer(){
cout<<this->name<<"-"<<this->age<<endl;
}
//主函数
#include
#include
//包含hpp即可
#include "person.hpp"
using namespace std;
void test01(){
Person<string,int> p("TOM",34);
p.showPer();
}
int main() {
test01();
return 0;
}
总结:主流解决方法是第二种,将类模板成员函数写到一起,并将后缀名改为.hpp;
学习目标:掌握类模板配合友元函数的类外和类内实现
1.全局函数类内实现:直接在类内声明友元即可
2.全局函数类外实现:需要提前让编译器知道全局函数的存在
#include
#include
using namespace std;
//通过全局函数打印person信息
//2.全局函数 类外实现
//2.1 先声明模板类的存在
template<class T1,class T2>
class Person;
//2.2 再声明 printPer2 的存在
template<class T1,class T2>
void printPer2(Person<T1,T2> p){
cout<<p.name<<"-"<<p.age<<endl;
}
template<class T1,class T2>
class Person{
//1.全局函数 类内实现
//printPer相当于全局函数,然后做了Person类的友元
friend void printPer(Person<T1,T2> p){
cout<<p.name<<"-"<<p.age<<endl;
}
//2.全局函数 类外实现
// 加空模板参数列表
// 若全局函数 是类外实现,需要让编译器提前知道这个函数的存在
friend void printPer2<>(Person<T1,T2> p);
public:
Person(T1 n,T2 a){
this->name = n;
this->age = a;
}
private:
T1 name;
T2 age;
};
//1.全局函数类内实现 -测试
void test01(){
Person<string,int> p("TOM",34);
printPer(p);
}
//2. 全局函数类外实现 -测试
void test02(){
Person<string,int> p("Jom",24);
printPer2(p);
}
int main() {
test01(); test02();
return 0;
}
总结:建议全局函数做类内实现,用法简单,编译器可以直接识别
实现一个普通的数组类,要求如下:

//数组.hpp
#include
#include
using namespace std;
//实现数组类
template<class T>
class Myarray{
public:
Myarray(int cap){
//cout<<"Myarray有参构造"<
this->m_cap = cap;
this->m_size = 0;
this->pAddress = new T[this->m_cap];
}
//因为有堆区数据,因此需要深拷贝
Myarray(const Myarray& arr){
this->m_cap = arr.m_cap;
this->m_size = arr.m_size;
//cout<<"Myarray拷贝构造"<
//深拷贝
this->pAddress = new T[arr.m_cap];
//将arr中的数据都拷贝过来
for(int i=0;i<this->m_size;i++){
this->pAddress[i] = arr.pAddress[i];
}
}
//opeartor = 防止浅拷贝 a=b=c
Myarray& operator=(const Myarray& arr){
//cout<<"Myarray的operator调用"<
//先判断原来堆区是否有数据,有则先释放
if(this->pAddress != NULL){
delete[] this->pAddress;
this->m_cap = 0;
this->m_size = 0;
}
//深拷贝
this->m_cap = arr.m_cap;
this->m_size = arr.m_size;
this->pAddress = new T[arr.m_cap];
//将arr中的数据都拷贝过来
for(int i=0;i<this->m_size;i++){
this->pAddress[i] = arr.pAddress[i];
}
return *this;
}
//尾插法
void PushBack(const T& val){
//判断容量是否 等于 大小
if(this->m_cap == this->m_size){
return;
}
this->pAddress[this->m_size] = val; //数组末尾插入数据
this->m_size++; //更新数组大小
}
//尾删法
void PopBack(){
//用户访问不到最后一个元素,即尾删
if(this->m_size==0) return;
this->m_size--;
}
//通过下标访问数组元素 arr[0]
T& operator[](int index){
return this->pAddress[index];
}
//返回数组容量
int getCap(){
return this->m_cap;
}
//返回数组大小
int getSize(){
return this->m_size;
}
~Myarray(){
//cout<<"Myarray析构"<
if(this->pAddress != NULL){
delete[] pAddress;
pAddress = NULL;
}
}
private:
//数组,指针指向堆区开辟的真实数组
T* pAddress;
int m_cap; //数组容量
int m_size; //数组大小
};
//main.cpp
#include
#include
using namespace std;
#include "数组类1.hpp"
void test01(){
Myarray<int> arr1(5); //有参构造测试
Myarray<int> arr2(arr1); //拷贝构造测试
Myarray<int> arr3(100);
arr3 = arr1;
}
void printArr(Myarray<int>& arr1){
for(int i=0;i<arr1.getSize();i++){
cout<<arr1[i]<<" ";
}
cout<<endl;
}
void test02(){
Myarray<int> arr1(5);
//尾插法向数组插入数据
for(int i=0;i<5;i++){
arr1.PushBack(i);
}
cout<<"arr1打印输出为:"<<endl;
printArr(arr1);
cout<<"arr1容量为:"<<arr1.getCap()<<endl;
cout<<"arr1大小为:"<<arr1.getSize()<<endl;
Myarray<int> arr2(arr1);
cout<<"arr2尾删后打印输出为:"<<endl;
arr2.PopBack();
printArr(arr2);
}
//测试自定义数据类型
class Person{
public:
Person(){
};
Person(string n,int a){
this->age = a;
this->name = n;
}
int age;
string name;
};
void printPersonArr(Myarray<Person>& arr1){
for(int i=0;i<arr1.getSize();i++){
cout<<"姓名:"<<arr1[i].name<<" 年龄:"<<arr1[i].age<<endl;
}
}
void test03(){
Myarray<Person> arr1(10);
Person p1("孙悟空",353);
Person p2("韩信",153);
Person p3("赵云",33);
Person p4("安琪",233);
//数组插入数组中
arr1.PushBack(p1);arr1.PushBack(p2);arr1.PushBack(p3);arr1.PushBack(p4);
printPersonArr(arr1);
cout<<"personArr容量为:"<<arr1.getCap()<<endl;
cout<<"personArr大小为:"<<arr1.getSize()<<endl;
}
int main() {
test03();
return 0;
}