参考视频:C++类模板_哔哩哔哩_bilibili
遗留问题:编译器怎么处理函数模板和类模板
目录
一、为什么会有函数模版?函数模板是为了解决什么问题?
函数模板是为了减少重复代码,是整合多个函数的函数体相同而区别只在参数类型不同的代码冗余的情况。不用函数模板前,多个函数的参数类型不同,但函数体相同,导致重复代码较多。有了函数模板,只需要一份函数体一份代码就可以实现原来需要几个函数体才实现的功能,例如不使用函数模板时,要分别对int、char等类型的两个数据做是否相等判断,需要定义多个函数:
cpp
bool isEqual( int nA, int nB)
{
return nA == nB;
}
bool isEqual(char cA, char cB)
{
return cA == cB;
}
int main()
{
int nA = 10;
int nB = 20;
char cA = 'A';
char cB = 'B';
std::cout << isEqual(nA, nB)<< std::endl;
std::cout << isEqual(cA, cB) << std::endl;
return 0;
}
可见重载的两个函数的函数体实现一样,函数体代码重复,下面是使用函数模板的代码:
cpp
#include <iostream>
//使用模板代替原来的多个函数
template<typename T>
bool isEqual(T tA, T tB)
{
return tA == tB;
}
int main()
{
//变量以匈牙利命名法命名
int nA = 10;
int nB = 20;
char cA = 'A';
char cB = 'B';
//编译期间,编译器根据实参类型和模板函数生成函数bool isEqual(int, int);
std::cout << isEqual(nA, nB)<< std::endl;
//编译期间,编译器根据实参类型和模板函数生成函数bool isEqual(char, char);
std::cout << isEqual(cA, cB) << std::endl;
return 0;
}
二、函数模板的概念
函数模板是用于生成函数的模板。在编译阶段,编译器会根据函数模板的使用情况(例如上面的isEqual(nA, nB)、isEqual(cA, cB)创建出函数名相同,参数类型由编译器判断的若干函数。通过函数模板创建的函数拥有相同的函数体,只是函数的参数类型不同。
三、函数模版的使用
每当在一个编译单元(经过预处理的.cpp文件)中使用了函数模版,则必须在该编译单元中给出函数模版的定义(要是把函数模板声明写在.h文件,函数模板声明写在.cpp文件,然后在使用函数模板时只使用include 包含函数模板的头文件,不给出函数模板的定义,那么编译会报无法解析的外部符号错误)。因此,为了避免在每个编译单元都定义函数模板,建议在头文件中对函数模板进行声明定义(即将函数模板的声明和定义都写在一个头文件中,然后在需要使用函数模板的地方加上该头文件)。
函数模版的声明:
cpp
template<typename T>
返回类型 函数名(参数列表);
其中T表示任意类型,参数的类型和返回类型都可以指定为T
函数模版的定义
cpp
template<typename T,..> //可指定多个泛型
返回类型 函数名(参数列表)
{
//函数体
}
将一、为什么会有函数模版?函数模板是为了解决什么问题?这一节中的函数模板的声明和定义写在isEqualFunc.h头文件中再使用:
cpp
#ifndef ISEQUALFUNC_H
#define ISEQUALFUNC_H
//函数模板声明
template<typename T>
bool isEqual(T tA, T tB);
//函数模板定义
template<typename T>
bool isEqual(T tA, T tB)
{
return tA == tB;
}
#endif // ISEQUALFUNC_H
cpp
//这是main.cpp
#include "isEqualFunc.h"
#include <iostream>
int main()
{
//变量以匈牙利命名法命名
int nA = 100111111;
int nB = 111111;
char cA = 'A';
char cB = 'A';
//编译期间,编译器根据实参类型和模板函数生成函数bool isEqual(int, int);
std::cout << isEqual(nA, nB)<< std::endl;
//编译期间,编译器根据实参类型和模板函数生成函数bool isEqual(char, char);
std::cout << isEqual(cA, cB) << std::endl;
return 0;
}
四、函数模板的特化
函数模板特化是指在实例化函数模板时,对特定类型的实参进行特殊的处理,即当实参为特定类型时,通过函数模板生成的函数会有不同的函数体。
特化时需要为函数模板添加新的定义,方式如下:
cpp
template<>
返回类型 函数名<特定的参数类型>(参数列表)
{
//函数体
}
在上面的例子中,对于char*类型的字符串,使用上面的函数模板无法对字符串正确判断是否相等,因为字符串比较不是直接比较指针是否相等,而要调用strcmp函数判断,对上面的例子就需要对函数模板进行特化
cpp
#ifndef ISEQUALFUNC_H
#define ISEQUALFUNC_H
#include <string.h>
//函数模板声明
template<typename T>
bool isEqual(T tA, T tB);
//函数模板定义
template<typename T>
bool isEqual(T tA, T tB)
{
return tA == tB;
}
//函数模板的特化
template<>
bool isEqual< char* >( char* szA, char* szB)
{
return strcmp(szA, szB) == 0;
}
#endif // ISEQUALFUNC_H
cpp
//这是main.cpp
#include "isEqualFunc.h"
#include <iostream>
int main()
{
//变量以匈牙利命名法命名
int nA = 100111111;
int nB = 111111;
char cA = 'A';
char cB = 'A';
char szA[4] = "AAA";
char szB[4] = "AAA";
// 编译器会怎么处理函数模板和特化?待参考https://blog.csdn.net/zgaoq/article/details/85232968
//编译期间,编译器根据实参类型和模板函数生成函数bool isEqual(int, int);
std::cout << isEqual(nA, nB)<< std::endl;
//编译期间,编译器根据实参类型和模板函数生成函数bool isEqual(char, char);
std::cout << isEqual(cA, cB) << std::endl;
std::cout << isEqual(szA, szB) << std::endl; //判断字符串是否相等
return 0;
}
五、类模板的概念
类模板是用于生成类的模板。在编译阶段,编译器会根据类模板的使用情况创建出仅部分成员数据类型和部分成员函数的参数类型不同,其他完全相同的若干类。
通过类模板的这些特性我们可以尝试写出用于存放不同类型数据的容器。
六、类模板的使用
类模板的声明格式:
cpp
template<typename T,...>
class 类名
{
//成员
};
类模板的成员函数定义如下,一般类模板的定义也应该写在头文件中:
cpp
template<typename T,...>
返回类型 类名<T,...>::成员函数名(形参列表)
{
//函数体
}
下面是一个例子,这个例子定义了一个MyArray的类模板,其中数据成员是数组,通过泛型T达到可定义各种类型数组的目的,而所有对这个数组的操作函数都相同,这样就不用针对不同类型的数组,定义不同的类,达到了复用代码即减少重复代码的目的:
cpp
//这是myArray.h
#ifndef MYARRAY_H
#define MYARRAY_H
#include <iostream>
constexpr int m_nMaxLen = 20;
template <typename T>
class MyArray
{
public:
MyArray():size(0){}
//添加数据
bool addData(T value);
//删除数据
bool deleteData(T value);
//获取数据
T getData(int nIndex);
void print()
{
for(int i = 0; i < size; i++)
{
std::cout << data[i] << std::endl;
}
}
private:
T data[m_nMaxLen];
int size;
};
template<typename T>
bool MyArray<T>::addData(T value)
{
if(size == m_nMaxLen)
{
return false;
}
data[size] = value;
size++;
return true;
}
template<typename T>
bool MyArray<T>::deleteData(T value)
{
if(size == 0)
{
return false;
}
int nDataIndex = -1;
for(int i = 0; i < size; i++)
{
if(data[i] == value)
{
nDataIndex = i;
break;
}
}
if(nDataIndex == -1)
{
std::cout<< "找不到该数据!" << std::endl;
return false;
}
else
{
for(int i = nDataIndex; i < size -1; i++)
{
data[i] = data[i+1]; //往前移动后面的数据,即可以删除该数据
}
size --;
}
return true;
}
template<typename T>
T MyArray<T>::getData(int nIndex)
{
if(nIndex >= 0 && nIndex < size)
{
return data[nIndex];
}
else
{
return (T)0;
}
}
#endif // MYARRAY_H
cpp
//这是main.cpp
#include "myarray.h"
#include <iostream>
int main()
{
std::cout <<"----int begin----" << std::endl;
MyArray<int> intData;
for(int i = 1; i <= 10; i++)
{
intData.addData(i);
}
intData.print();
std::cout <<"--------" << std::endl;
intData.deleteData(11);
std::cout <<"--------" << std::endl;
intData.print();
std::cout << intData.getData(0) << std::endl;
std::cout <<"----int end----" << std::endl;
std::cout <<"----char begin----" << std::endl;
MyArray<char> charData;
for(int i = 1; i <= 10; i++)
{
charData.addData('A'+ i);
}
charData.print();
std::cout <<"--------" << std::endl;
charData.deleteData('B');
std::cout <<"--------" << std::endl;
charData.print();
std::cout << charData.getData(1) << std::endl;
std::cout <<"----char end----" << std::endl;
return 0;
}
七、类模板的特化
类模板的特化是在实例化类模板时,对特定类型的泛型进行特殊的处理,即用户指定所有特定类型的类模板时,通过特化类模板生成的类可能与其他类有完全不同的结构。
特化类模板是需要对整个类模板进行声明定义:
cpp
template<>
class 类名<指定类型,指定类型,...>
{
//类成员
};
这里对第六节中的例子添加特化:
cpp
#ifndef MYARRAY_H
#define MYARRAY_H
//....其他代码不变
//对特定泛型的类的成员函数进行特化
template<>
MyArray<char>::MyArray()
{
std::cout<< "MyArray<char>::MyArray()" << std::endl;
}
//对特定泛型的类进行特化
//这里例子可能不太恰当,只是展示如何对特定类型的类进行特化
template<>
class MyArray<float>
{
public:
MyArray();
private:
float data;
};
MyArray<float>::MyArray()
{
std::cout<< "MyArray<float>::MyArray()" << std::endl;
}
#endif // MYARRAY_H
八、类模板的偏特化
偏特化和特化类似,只是特化会指定所有的泛型,而偏特化只指定部分泛型。
偏特化类模板需要对整个类模板进行声明定义:
cpp
template <typename T,...不需特化的泛型...>
class 类名<指定类型,...不需特化的泛型名,...>
{
//类成员;
}
例子:
cpp
#ifndef PAIR_H
#define PAIR_H
#include <iostream>
template<typename T1, typename T2>
class Pair
{
public:
Pair();
private:
T1 m_first;
T2 m_second;
};
template<typename T1, typename T2>
Pair<T1, T2>::Pair()
{
std::cout << "Pair<T1, T2>::Pair()" << std::endl;
}
//偏特化
template<typename T2>
class Pair<char, T2>
{
public:
Pair();
private:
char first;
T2 second;
};
template<typename T2>
Pair<char, T2>::Pair()
{
std::cout << "Pair<char, T2>::Pair()" << std::endl;
}
#endif // PAIR_H
cpp
//这是main.cpp
#include <iostream>
#include "Pair.h"
int main()
{
Pair<int, int> pNn;
Pair<char, int> pCn;
return 0;
}
运行结果: