目录
2.将.h和.cpp文件中的内容写到一起,后将后缀名改为.hpp文件
模板
前言:
- C++中相对于面向对象的另一种编程思想就是泛型编程,主要利用的技术就是模板
- C++提供了两种模板机制(函数模板和类模板)
概念:模板就是建立通用的模具,大大提高复用性
模板的特点
- 模板不可以直接使用,它只是一个框架
- 模板通用性很强,但是并不是万能的
使用模板的目的:提高复用性,将类型参数化
函数模板
前言
作用:建立一个通用函数,其函数返回值类型和形参类型可以不具体制定,用一个虚拟的类型来代表
语法:
//声明一个模板,告诉编译器后面代码中紧跟着的T不要报错,T是一个通用的数据类型
template<typename T>
函数声明或函数定义
解释:
- template:声明创建模板
- typename:表明其后面的符号为一种数据类型(该关键字也可以用class代替)
- T:通用的数据类型;名称可以替换,通常为大写字母。
函数模板的使用方式
- 自动类型推导:向交换模板函数中传递的参数的类型会被自动解析
- 显示指定类型:指定模板的具体数据类型
函数模板具体案例
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
//函数模板
template <typename T>
//通用交换函数模板
void mySwap(T& a, T& b) {
T temp = a;
a = b;
b = temp;
}
void main() {
int a = 10;
int b = 20.0;
//自动类型推导(a和b的数据类型会被自动解析)
mySwap(a, b);
cout << "a:" << a << "\tb:" << b << endl;
//显式指定类型(<int>为模板参数)
mySwap<int>(a,b);
cout << "a:" << a << "\tb:" << b << endl;
}
使用模板的注意事项
- 在函数中,若多个参数使用模板,那么自动类型推导必须推导出一致的数据类型才可以使用
- 模板必须确定出T的数据类型才可以使用
普通函数与函数模板间的区别
普通函数调用时可以发生自动类型转换(隐式类型转换);函数模板调用时,若利用自动类型推导,则不会发生隐式类型转换,但若利用显式指定类型的方式则可以发生隐式类型转换
具体案例
#include <iostream>
using namespace std;
template <typename T>
T myAdd(T a, T b) {
return a + b;
}
void main() {
int a = 10;
char c = 'c';
//自动类型推导
//cout << myAdd(a, c) << endl;//报错,参数类型的指定必须一致,不能自动类型转换
//隐式指定类型
cout << myAdd<int>(a, c) << endl;
//明确说明T的类型为int,传入的参数是int类型直接传,不是int类型则使用隐式类型转换转为int类型
}
普通函数与函数模板调用规则
1.若函数模板与普通函数都可以调用,则优先调用普通函数
void myPrint(int a, int b) {
cout << "调用普通函数" << endl;
}
template <typename T>
void myPrint(T a, T b) {
cout << "调用函数模板" << endl;
}
void main() {
int a = 10;
int b = 20;
myPrint(a, b);//调用普通函数
}
2.可以通过空模板参数列表的方式来强制调用函数模板
//只写了函数声明,没写函数实现
void myPrint(int a, int b);
template <typename T>
void myPrint(T a, T b) {
cout << "调用函数模板" << endl;
}
void main() {
int a = 10;
int b = 20;
//通过空模板的参数列表强制调用函数模板
myPrint<>(a, b);
}
3.模板也可以实现函数重载
template <typename T>
void myPrint(T a, T b) {
cout << "调用函数模板1" << endl;
}
//重载函数模板
template <typename T>
void myPrint(T a, T b, T c) {
cout << "调用函数模板2" << endl;
}
void main() {
int a = 10;
int b = 20;
int c = 30;
//调用函数模板1
myPrint(a, b);
//调用函数模板2
myPrint(a, b, c);
}
4.如果函数模板可以产生更好的匹配,则优先使用函数模板
void myPrint(int a, int b) {
cout << "调用普通函数" << endl;
}
template <typename T>
void myPrint(T a, T b) {
cout << "调用函数模板" << endl;
}
void main() {
char a = 'a';
char b = 'b';
//调用函数模板
myPrint(a, b);
}
注意:若提供了函数模板,那么最好就不要提供普通函数,否则容易出现二义性
模板的局限性
template <class T>
void func(T a, T b) {
a = b;
}
注意:模板并不是万能的,该模板函数有一个局限性,若传入的a和b是一个数组,那么就无法实现了
具体化模板
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;
}
}
//利用具体化(template<>)的Person版本来实现代码,具体化优先调用
template<> bool myCompare(Person & a, Person & b) {
if (a.m_Age == b.m_Age && a.m_Name == b.m_Name) {
return true;
}
else {
return false;
}
}
void main() {
Person p1("Tom", 10);
Person p2("Tom", 10);
bool ret = myCompare(p1, p2);
if (ret) {
cout << "p1==p2" << endl;
}
else {
cout << "p1!=p2" << endl;
}
}
解释:模板的类型若为Person类型,那么自动调用具体化了的方法
类模板
前言
作用:建立一个通用的类,类中的成员,数据类型可以不具体指定,用一个虚拟的类型来代表
语法:
template<typename T>
类
解释:
- template:声明创建模板
- typename:表明其后面带符号的是一种数据类型(也可以用class替换)
- T:通用的数据类型,名称可以替换,通常为大写字母
类模板与函数模板的区别
1.类模板没有自动类型推导的使用方式
template<class NameType,class AgeType>
class Person {
public:
Person(NameType name, AgeType age) {
this->m_Name = name;
this->m_Age = age;
}
NameType m_Name;
AgeType m_Age;
};
void main() {
//类模板的使用(显式指定类型)
Person<string, int> p("叶秋", 25);
cout << "p的name:" << p.m_Name << endl << "p的age:" << p.m_Age << endl;
}
2.类模板在模板参数列表中可以有默认参数
//int为类模板的默认参数
template<class NameType,class AgeType=int>
class Person {
public:
Person(NameType name, AgeType age) {
this->m_Name = name;
this->m_Age = age;
}
NameType m_Name;
AgeType m_Age;
};
void main() {
//类模板默认参数的使用
Person<string> p("叶秋", 25);
cout << "p的name:" << p.m_Name << endl << "p的age:" << p.m_Age << endl;
}
类模板中成员函数创建时机
- 普通类中的成员函数一开始就可以创建
- 类模板中的成员函数在调用时才可以创建
类模板对象做函数的参数
传入方式
- 指定传入类型——直接显示对象的数据类型
- 参数模板化——将对象中的参数变为模板进行传递
- 整个类模板化——将这个对象类型模板化进行传递
具体案例
template<class NameType,class AgeType>
class Person {
public:
Person(NameType name, AgeType age) {
this->m_Name = name;
this->m_Age = age;
}
void showPerson() {
cout << "姓名:" << this->m_Name << "\t年龄:" << this->m_Age << endl;
}
NameType m_Name;
AgeType m_Age;
};
//指定传入类型
void printPerson1(Person<string,int> &p) {
p.showPerson();
}
//参数模板化
template<class T1 ,class T2>
void printPerson2(Person<T1,T2> &p) {
p.showPerson();
}
//整个类模板化
template<class P>
void printPerson3(P &p) {
p.showPerson();
}
void main() {
Person<string, int> p("孙悟空", 100);
printPerson1(p);
printPerson2(p);
printPerson3(p);
}
类模板与继承
前言
- 当子类继承的父类是一个类模板时,子类在声明的时候需要指定父类的类型
- 若不指定具体的类型,那么编译器无法给予子类分配内存
- 若想灵活指定父类中T的类型,子类也需要变成类模板
子类指定具体类型案例
template<class T>
class Base {
T m;
};
class Son:public Base<int> {};
子类不指定具体类型案例
template<class T>
class Base {
T m;
};
template<class T>
class Son:public Base<T> {};
类模板成员函数的类外实现
template<class NameType,class AgeType>
class Person {
public:
Person(NameType name, AgeType age);
void showPerson();
NameType m_Name;
AgeType m_Age;
};
//构造函数的类外实现
template<class NameType, class AgeType>
Person<NameType,AgeType>::Person(NameType name,AgeType age){
this->m_Name = name;
this->m_Age = age;
}
//成员函数的类外实现
template<class NameType, class AgeType>
void Person<NameType, AgeType>::showPerson() {
cout << "姓名:" << this->m_Name << "\t年龄:" << this->m_Age << endl;
}
注意:类模板中的成员函数类外实现时需要加上模板的参数列表
类模板分文件编写
1.直接包含源文件
person.h文件内
#pragma once
#include <iostream>
using namespace std;
template<class NameType, class AgeType>
class Person {
public:
Person(NameType name, AgeType age);
void showPerson();
NameType m_Name;
AgeType m_Age;
};
person.cpp文件内
#include "person.h"
//构造函数的类外实现
template<class NameType, class AgeType>
Person<NameType, AgeType>::Person(NameType name, AgeType age) {
this->m_Name = name;
this->m_Age = age;
}
//成员函数的类外实现
template<class NameType, class AgeType>
void Person<NameType, AgeType>::showPerson() {
cout << "姓名:" << this->m_Name << "\t年龄:" << this->m_Age << endl;
}
执行文件内
//这里必须包含.cpp文件方可
#include "person.cpp"
void main() {
Person<string, int> p("lili", 18);
p.showPerson();
}
执行文件不可以包含.h文件必须包含.cpp问件原因:类模板中的成员函数在调用时才可以创建
2.将.h和.cpp文件中的内容写到一起,后将后缀名改为.hpp文件
person.hpp文件内
#pragma once
#include <iostream>
using namespace std;
template<class NameType, class AgeType>
class Person {
public:
Person(NameType name, AgeType age);
void showPerson();
NameType m_Name;
AgeType m_Age;
};
//构造函数的类外实现
template<class NameType, class AgeType>
Person<NameType, AgeType>::Person(NameType name, AgeType age) {
this->m_Name = name;
this->m_Age = age;
}
//成员函数的类外实现
template<class NameType, class AgeType>
void Person<NameType, AgeType>::showPerson() {
cout << "姓名:" << this->m_Name << "\t年龄:" << this->m_Age << endl;
}
执行文件内
//这里必须包含.hpp文件
#include "person.hpp"
void main() {
Person<string, int> p("lili", 18);
p.showPerson();
}
类模板与友元
1.全局函数类内实现,直接在类内声明友元即可
template<class T1,class T2>
class Person {
//全局函数类内实现
friend void printPerson(Person<T1,T2> p) {
cout << "姓名:" << p.m_Name << "\t年龄:" << p.m_Age << endl;
}
public:
Person(T1 name, T2 age) {
this->m_Name = name;
this->m_Age = age;
}
private:
T1 m_Name;
T2 m_Age;
};
void main() {
Person<string, int> p("lili", 18);
printPerson(p);
}
2.全局函数类外实现,需要让编译器知道该全局函数的存在
//编译器需要先知道printPerson函数的存在以及Person类的存在
template< class T1, class T2 >
class Person;
template<class T1, class T2>
void printPerson(Person< T1, T2 > p) {
cout << "姓名:" << p.m_Name << "\t年龄:" << p.m_Age << endl;
}
template<class T1,class T2>
class Person {
//全局函数类外实现
//加空模板参数列表证明他是个模板函数
//若全局函数是类外实现,需要让编译器提前知道这个函数的存在
friend void printPerson<>(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;
};
void main() {
Person<string, int> p("lili", 18);
printPerson(p);
}