1. 模板
1.1 模板的概念
1.2 函数模板
1.C++另一种编程思想称为 泛型编程,主要利用的技术就是模板
2.C++提供两种模板机制: 函数模板 和 类模板
1.2.1 函数模板语法
函数模板作用:建立一个通用函数,其函数返回值类型和形参类型可以不具体确定,用一个虚拟的类型来代表。
//语法
//函数声明或定义
template<typename T>
解释:
template – 声明创建模板
typename — 表明其后面的符号是一种数据类型,可以用class代替
T ---- 通用的数据类型,,名称可以替换,通常为大写字母。
#include<iostream>
#include <bits/stdc++.h>
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.2.2 函数模板注意事项
注意事项:
1.自动类型推导,必须推导出一致的数据类型T,才可以使用
2.模板必须要确定出T的数据类型,才可以使用
#include<iostream>
#include <bits/stdc++.h>
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;
}
1.2.3 函数模板案例
#include<iostream>
#include <bits/stdc++.h>
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;
}
1.2.4 普通函数与函数模板的区别
#include<iostream>
#include <bits/stdc++.h>
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。
1.2.5 普通函数与函数模板调用规则
#include<iostream>
#include <bits/stdc++.h>
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;
}
1.2.6 模板的局限性
局限性:模板的通用性并不是万能的
#include<iostream>
#include <bits/stdc++.h>
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能够运用系统提供的模板
1.3 类模板
1.3.1 类模板语法
类模板作用:建立一个通用类,类中的成员 数据类型可以不具体制定,用一个虚拟的类型来代表。
//语法
template<typename T>
解释:
1.template – 声明创建模板
2.typename – 表明其后面的符号是一种数据类型,可以用class代替
#include<iostream>
#include <bits/stdc++.h>
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.3.2 类模板与函数模板区别
主要区别两点:
1.类模板没有自动类型推导的使用方式
2.类模板在模板参数列表中可以有默认参数
#include<iostream>
#include <bits/stdc++.h>
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<class NameType,class AgeType> 改为 template<class NameType,class AgeType = int> //下边的int不需要写,也可以 Person<string> p1("王",23); p1.showPer();
}
int main() {test01();test02();return 0;
}
总结:1.类模板使用只能用显示指定类型方式
2.类模板中的模板参数列表可以有默认参数
1.3.3 类模板中成员函数创建时机
类模板中成员函数和普通类中成员函数创建时机有区别:
1.普通类中的成员函数一开始就可以创建
2.类模板中的成员函数在调用时才创建
#include<iostream>
#include <bits/stdc++.h>
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.3.4 类模板对象做函数参数
学习目标:类模板实例化出的对象,向函数传参数的方式
一共有三种传入方式:
1.指定传入的类型 — 直接显示对象的数据类型
2.参数模板化 – 将对象中的参数变为模板进行传递
3.整个类模板化 – 将这个对象类型 模板化进行传递
#include<iostream>
#include <bits/stdc++.h>
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.3.5 类模板与继承
当类模板碰到继承,需要注意以下几点
1.当子类继承的父类是一个类模板时,子类声明时候,要指定出父类中T的类型
2.若不指定,编译器无法给子类分配内存
3.若想灵活指定出父类中T的类型,子类也需要变为类模板。
#include<iostream>
#include <bits/stdc++.h>
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的数据类型
1.3.6 类模板成员函数的类外实现
学习目标:掌握类模板中成员函数类外实现
#include<iostream>
#include <bits/stdc++.h>
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;
};
//构造函数类外实现
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.3.7 类模板分文件编写
学习目标:掌握类模板成员函数分文件编写产生的问题以及解决方式
问题:类模板中成员函数创建时机是在调用阶段,导致分文件编写时,连接不到
解决:
方式1:直接包含.cpp文件
方式2:将声明和实现写到同一个文件中,并更改后缀为.hpp,hpp是约定的名称,不是强制
方式1
//person.h
#pragma once
#include<iostream>
#include <bits/stdc++.h>
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<iostream>
#include <bits/stdc++.h>
//单独包含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;
}
将.h和.cpp内容写到一起,后缀改为.hpp
//person.hpp
#include<iostream>
#include <bits/stdc++.h>
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<iostream>
#include <bits/stdc++.h>
//包含hpp即可
#include "person.hpp"
using namespace std;
void test01(){Person<string,int> p("TOM",34);p.showPer();
}
int main() {test01();return 0;
}
总结:主流解决方法是第二种,将类模板成员函数写到一起,并将后缀名改为.hpp;
1.3.8 类模板与友元
学习目标:掌握类模板配合友元函数的类外和类内实现
1.全局函数类内实现:直接在类内声明友元即可
2.全局函数类外实现:需要提前让编译器知道全局函数的存在
#include<iostream>
#include <bits/stdc++.h>
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;
}
总结:建议全局函数做类内实现,用法简单,编译器可以直接识别
1.3.9 类模板案例
实现一个普通的数组类,要求如下:
//数组.hpp
#include<iostream>
#include <bits/stdc++.h>
using namespace std;
//实现数组类
template<class T>
class Myarray{
public:Myarray(int cap){//cout<<"Myarray有参构造"<<endl; 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拷贝构造"<<endl; //深拷贝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=cMyarray& operator=(const Myarray& arr){//cout<<"Myarray的operator调用"<<endl; //先判断原来堆区是否有数据,有则先释放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析构"<<endl; if(this->pAddress != NULL){delete[] pAddress;pAddress = NULL;}}
private://数组,指针指向堆区开辟的真实数组 T* pAddress;int m_cap; //数组容量int m_size; //数组大小 };
//main.cpp#include<iostream>
#include <bits/stdc++.h>
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;
}