一、模版
模版就是建立通用的工具,大大提高复用性。
1.1、函数模版
作用:建立通用函数,其函数返回值类型和形参类型可以不具体指定,用一个虚拟的类型来指定
// 语法:
template <typename T>
-
template ---- 模版的声明,声明创建的模版
-
typename ---- 表示后面的符号是一种数据类型,可以使用class代替
-
T ---- 通用的数据类型,名称可以替换,通常为大写字母
// 定义通用格式的两数交换
template <typename T> // 声明一个模版,告诉编辑器后面的代码紧跟着的T不要报错,T表示通用格式
void swap(T &a, T &b){
T temp = a;
a = b;
b = temp;
}
调用格式:
/*
* 使用函数模版
* 1、自动类型推导:自动类型推导需要推导出一至的数据类型才可以使用
* 2、显示指定类型
*/
int main(){
int a = 10;
int b = 30;
// 自动类型推导
swep(a ,b);
cout << "a = " << a << " , b = " << b << endl;
//显示指定类型
double aa = 23.3,bb = 55.4;
swep<double>(aa,bb);
cout << "aa = " << aa << " , bb = " << bb << endl;
return 0;
}
1.2、普通函数和模版的区别
普通函数和函数模版的区别:
-
普通函数调用时可以发现自动类型转换(隐式类型转换)
-
函数模版调用时,如果类型自动类型推导,就不会发生隐式类型转换
-
如果利用显示指定类型的方式,可以发生隐式类型转换
#include <iostream>
using namespace std;
// 定义普通函数
int my_add(int a, int b){
return a + b;
}
// 定义函数模版
template <typename T>
T myAdd(T a, T b){
return a + b;
}
int main(){
// 调用普通函数
cout << my_add('c',23) << endl; // 会发生隐式类型转换,将字符行转换为整数进行计算
// 自动类型推导调用函数模版
cout << myAdd(23,3) << endl;
//cout << myAdd('c',23) << endl; // 报错,不会将字符型自动转换为整形
// 显示指定类型调用函数模版
cout << myAdd<int>('c',23) << endl; // 会发生隐式类型转换,将字符行转换为整数进行计算
return 0;
}
1.3、调用规则
普通函数和函数模版的调用规则:
-
如果函数模版和普通函数都可以实现,优先调用普通函数
-
可以通过空模版参数列表来强制调用函数模版
-
函数模版也可以发生重载
-
如果函数模版可以产生更好的匹配,优先调用函数模版
#include <iostream>
using namespace std;
// 定义普通函数
void print_show(int a, int b){
cout << "普通函数的调用!" << endl;
}
// 定义函数模版
template<typename T>
void print_show(T a, T b){
cout << "函数模版的调用!" << endl;
}
// 定义重载的函数模版
template<typename T>
void print_show(T a, T b,T c){
cout << "重载函数模版的调用!" << endl;
}
int main(){
int a = 23, b = 24, c = 12;
print_show(a,b); // 优先调用普通函数
// 通过空模版参数列表,强制调用函数模版
print_show<>(a,b); // 模版不指定类型,为空
// 调用重载的函数模版
print_show(a,b,c);
return 0;
}
1.4、模版的局限性
模版并不是万能的,有些特定的数据类型,需要用具体的方式做特定的实现,c++提供函数模版的重载,如果模版中传入的是自定义数据类型,就需要模版的重载
#include <iostream>
#include <string>
using namespace std;
// 定义比较的模版
template <typename T>
void cmoputer(T &a,T &b){
if(a == b){
cout << "相等!" << endl;
}else{
cout << "不相等!" << endl;
}
}
// 定义类
class Person{
public:
string name;
int age;
Person(string name,int age){
this->name = name;
this->age = age;
}
};
//定义重载的比较模版---处理自定义类型的比较
template<> // 类型留空,因为后面指定了具体的类型
void cmoputer(Person& p1,Person &p2){
if(p1.name == p2.name && p1.age == p2.age){
cout << "相同!" << endl;
}else{
cout << "不相等!" << endl;
}
}
int main(){
// 调用模版
int a = 23, b = 2;
cmoputer(a,b);
Person p1 = Person("zhang",23);
Person p2 = Person("xianmi",12);
// 调用模版(传入自定义类型)---- 传入自定义类型时需要定义重载的函数模版
cmoputer(p1,p2);
return 0;
}
1.5、类模版
建立一个通用的类,类中的数据类型和成员可以不具体指定,用一个虚拟的类型来代替
#include <iostream>
#include <string>
using namespace std;
// 定义类模版
template <class NameType, class AgeType>
class Person{
public:
NameType name;
AgeType age;
Person(NameType name, AgeType age){
this->name = name;
this->age = age;
}
// 打印函数
void print_show(){
cout << "name = " << this->name << " , age = " << this->age << endl;
}
};
int main(){
// 创建类对象 --- 使用类模版
Person<string,int> p1("zhans",23);
p1.print_show();
return 0;
}
注意:类模版中的成员函数只有在调用的时候才会创建,定义的时候不会创建
1.6、类模版和函数模版的区别
主要区别:
-
类模版没有自动类型推导的使用方式---只能进行显示指定类型
-
类模版在模版参数列表中可以有默认的参数----可以在定义时指定类型
// 定义类模版
template <class NameType, class AgeType = int>
class Person{
public:
NameType name;
AgeType age;
Person(NameType name, AgeType age){
this->name = name;
this->age = age;
}
// 打印函数
void print_show(){
cout << "name = " << this->name << " , age = " << this->age << endl;
}
};
1.7、类模版对象做函数参数
// 定义模版类
template <class t1, class t2>
class Person{
public:
t1 m_name;
t1 m_age;
Person(t1 name, t2 age){
this->m_name = name;
this->m_age = age;
}
void print_show(){
cout << "name = " << this->m_name << " , age = " << this->m_age << endl;
}
};
类模版实例化出的对象,向函数参数传参的方式:
-
指定传入的类型 ---- 直接显示对象的数据类型
// 定义函数参数为类模版对象(指定类模版类型) void printPerson1(Person<string , int> &p1){ p1.print_show(); } int main(){ // 创建类对象 Person<string , int> p1("zhang",23); // 调用全局函数 printPerson1(p1); return 0; }
-
参数模版化 ---- 将对象中的参数变为模版进行传递
// 参数模版化 template <typename T1,typename T2> void printPerson2(Person<T1,T2> &p2){ p2.print_show(); // 查看模版推出的类型 ---- typeid(模版参数.name() cout << "T1的类型:" << typeid(T1).name() << endl; cout << "T2的类型:" << typeid(T2).name() << endl; } int main(){ // 定义类模版对象 Person<string , int> p2("zhang",25); // 调用参数模版化的全局函数 printPerson2(p2); return 0; }
-
整个类模版化 ---- 将这个对象类型模版化进行传递
// 整个类模版化 template <class T> void printPerson3(T &p){ p.print_show(); }
1.8、类模版与继承
当类模版碰到继承时:
-
当子类继承的父类是一个模版类时,子类在声明的时候需要指出父类中<T>的类型,不指定无法为子类分配内存
-
如果想灵活指定子类中的类型,就需要将子类也定义为模版类型
#include <iostream>
using namespace std;
// 定义父类为模版类
template <class T>
class Base{
public:
T m;
};
// 定义子类(继承父类--父类为模版类)------ 继承的同时需要指定父类中T的类型
class Son1: public Base<int>{
};
// 定义子类(继承父类--父类为模版类)------- 继承的同时不指定父类中T的类型,直接将子类定义为模版类
template <class T>
class Son2: public Base<T>{
};
int main(){
// 创建子类1的对象
Son1 s1;
// 创建子类2的对象----因为子类2是模版类,创建时需要指定模版类型
Son2<int> s2;
return 0;
}
1.9、类模版成员函数类外实现
// 定义类函数模版
template <class T1, class T2>
class Person{
public:
T1 m_name;
T2 m_age;
Person(T1 name, T2 age); // 构造函数类内声明
void show(T1 name, T2 age); // 成员函数的声明
};
// 类外实现类模版的构造函数 --- 必须声明为类模版
template <class T1,class T2>
Person<T1 , T2>::Person(T1 name, T2 age) {
cout << "Person类模版的构造函数的类外实现" << endl;
this->m_name = name;
this->m_age = age;
}
// 类外实现类模版的成员函数
template <class T1,class T2>
void Person<T1,T2>::show(T1 name, T2 age) {
cout << "name = " << name << " , age = " << age << endl;
cout << "Person类模版的成员函数的外部实现" << endl;
}
注意:类模版中的成员函数在运行的时候才会创建,如果进行分文件编写,在创建对象的时候会报错,解决方法直接导入.cpp原文件或者将.cpp文件中的内容和.h中的内容写到一起命名为.hpp文件----.hpp文件就表示类模版
1.10、类模版和友元
-
全局函数类内实现 ---- 直接在类内声明友元即可
-
全局函数类外实现 ---- 需要提前让编译器知道全局函数的存在