购买了网站如何使用,免费注册个人电子邮箱,做美直播网站,企业网站怎么做中英文切换目录 一.什么是泛型编程#xff1f;
编辑
编辑 二.函数模板 函数模板的实例化
当不同类型形参传参时的处理
使用多个模板参数
三.模板参数的匹配原则
四.类模板
1.定义对象时要显式实例化
2.类模板不支持声明与定义分离 3.非类型模板参数
4.模板的特化
函数模板…目录 一.什么是泛型编程
编辑
编辑 二.函数模板 函数模板的实例化
当不同类型形参传参时的处理
使用多个模板参数
三.模板参数的匹配原则
四.类模板
1.定义对象时要显式实例化
2.类模板不支持声明与定义分离 3.非类型模板参数
4.模板的特化
函数模板特化
类模板特化
类模板特化应用示例
模板的优缺点 一.什么是泛型编程
当我们写一个交换两个数的函数
void swap(int x, int y)
{int temp x;x y;y temp;
}
我们知道这个函数只能交换两个整形变量的值在C中虽然实现了函数重载,但是如果我们想要实现交换两个浮点数的值仍要重新写一个函数但是函数的内容几乎和交换整形的内容几乎一样这就显得代码有些重复同时又增加了程序员的工作量且不方便维护代码。
那能否告诉编译器一个模子让编译器根据不同的类型利用该模子来生成代码呢 如果在 C 中也能够存在这样一个 模具 通过给这个模具中 填充不同材料 ( 类型 ) 来 获得不同材料的铸件 ( 即生成具体类型的代码。 这里大佬们早已想到在C使用泛型编程来做。 泛型编程编写与类型无关的通用代码是代码复用的一种手段。 模板是泛型编程的基础。 模板分为函数模板与类模板 二.函数模板
概念 函数模板代表了一个函数家族该函数模板与类型无关在使用时被参数化根据实参类型产生函数的特定 类型版本。 函数模板格式
templatetypename T1, typename T2,......,typename Tn
返回值类型 函数名(参数列表){}
注typename是用来定义模板参数关键字也可以使用class(切记不能使用struct代替class)
例子
#includeiostream
using namespace std;templatetypename T
void Swap(T x, T y)
{int temp x;x y;y temp;
}int main()
{int a 1, b 2;double e 1.1, f 2.2;Swap(a, b);Swap(e, f);cout a b endl;cout e f endl;return 0;
} 注main()函数里的两个Swap函数调用的不是同一个函数也不是模板而是编译器用模板生成的实例化Swap函数在上述代码中模板会生成int和double两种实例化函数。 函数模板是一个蓝图它本身并不是函数是编译器用使用方式产生特定具体类型函数的模具。所以其实模 板就是将本来应该我们做的重复的事情交给了编译器 在编译器编译阶段 对于模板函数的使用 编译器需要根据传入的实参类型来推演生成对应类型的函数 以供 调用。比如当用 double 类型使用函数模板时编译器通过对实参类型的推演将 T 确定为 double 类型然 后产生一份专门处理 double 类型的代码 对于字符类型也是如此 函数模板的实例化 用不同类型的参数使用函数模板时 称为函数模板的 实例化 。模板参数实例化分为 隐式实例化和显式实例 化 。 1. 隐式实例化让编译器根据实参推演模板参数的实际类型
templateclass T
T Add(const T left, const T right)
{return left right;
}
int main()
{int a1 10, a2 20;double d1 10.0, d2 20.0;Add(a1, a2);Add(d1, d2);return 0;
}
2. 显式实例化在函数名后的中指定模板参数的实际类型
int main()
{int a 10;double b 20.0;// 显式实例化Addint(a, b);return 0;
}
当不同类型形参传参时的处理
int main()
{int a1,b2;double c1.1,d2.2;/*Add(a,c);该语句不能通过编译因为在编译期间当编译器看到该实例化时需要推演其实参类型通过实参a1将T推演为int通过实参d1将T推演为double类型但模板参数列表中只有一个T编译器无法确定此处到底该将T确定为int 或者 double类型而报错注意在模板中编译器一般不会进行类型转换操作因为一旦转化出问题编译器就需要背黑锅*/// 此时有两种处理方式1. 用户自己来强制转化 2. 使用显式实例化Add(a,(int)c);//用户自己来强制转化Addint(a,c);//使用显式实例化return 0;
}
方式一在编译器推演前就将c强转成int
方式二明确告诉编译器类型是int经此编译器直接将c作为int处理 使用多个模板参数
T1 Add(const T1 a, const T1 b)
{return a b;}int main()
{int a 1, b2;double c 1.1, d 2.2;Add(a, c); //多个模板参数编译器推演为Addint,double(a,c)return 0;
} 三.模板参数的匹配原则 1. 一个非模板函数可以和一个同名的函数模板同时存在而且该函数模板还可以被实例化为这个非模板函数 // 专门处理int的加法函数
int Add(int left, int right)
{return left right;
}
// 通用加法函数
templateclass T
T Add(T left, T right)
{return left right;
}
void Test()
{Add(1, 2); // 与非模板函数匹配编译器不需要特化Addint(1, 2); // 调用编译器特化的Add版本
} 2. 对于非模板函数和同名函数模板如果其他条件都相同在调动时会优先调用非模板函数而不会从该模 板产生出一个实例。如果模板可以产生一个具有更好匹配的函数 那么将选择模板 // 专门处理int的加法函数
int Add(int left, int right)
{return left right;
}// 通用加法函数
templateclass T1, class T2
T1 Add(T1 left, T2 right)
{return left right;
}void Test()
{Add(1, 2); // 与非函数模板类型完全匹配不需要函数模板实例化Add(1, 2.0); // 模板函数可以生成更加匹配的版本编译器根据实参生成更加匹配的Add函
数
} 四.类模板
定义格式
templateclass T1, class T2, ..., class Tn
class 类模板名
{// 类内成员定义
};
例子
templateclass T
class Stack
{
public:Stack():_data(nullptr),_size(0),_capacity(0){}~Stack(){cout ~Stack() endl;delete[]_data;_data nullptr;_size _capacity 0;}void push(T x){//入栈}private:T* _data;int _size;int _capacity;};
1.定义对象时要显式实例化
int main()
{Stackint s;s.push(1);Stackdouble x;x.push(1.1);return 0;
}
我们知道使用模板定义时需要通过参数确定类型进行显示实例化显而易见的时类模板无法向函数模板一样通过传参来确定类型。因此必须要在定义对象时显示实例化类型。 类模板显式实例化的类型不同他们就是不同的类。如上面的s与x就属于不同的类定义出的两个对象。所以不能又sx这样的操作除非你在s类中对进行运算符重载。
类模板名字不是真正的类而实例化的结果才是真正的类
// Vector类名Vectorint才是类型
Vectorint s1;
Vectordouble s2;
2.类模板不支持声明与定义分离
templateclass T
class Stack
{
public:Stack();~Stack();void push(const T ch);private:T* _data;int _size;int _capacity;
};templateclass T
StackT::Stack()
{//构造函数初始化
}templateclass T
StackT::~Stack()
{//析构
}templateclass T
void StackT::push(const T ch)
{//入栈
}类模板的成员函数不支持声明与定义分离。因为类模板和函数模板都不是真正的定义真正的定义是在实例化之后由编译器完成。如果声明与定义分离编译器会由于没有推演出参数使定义部分并不会编译而main函数里由于有声明会编译最后导致在汇编时找不到函数的定义报错。 解决放法
1.在模板定义的位置显式实例化
template
class Stackint;//显式实例化
2.声明与定义不分离全部放在一个.hpp文件相当于.h.cpp。一般里面大概率会有模板或.h文件里。 3.非类型模板参数 模板参数分类类型形参与非类型形参 。 类型形参即出现在模板参数列表中跟在 class 或者 typename 之类的参数类型名称 。 非类型形参就是用一个常量作为类 ( 函数 ) 模板的一个参数在类 ( 函数 ) 模板中可将该参数当成常量来使用 。 如当你想在一个类中定义一个静态数组的成员变量在C语言中只能使用宏定义的方式确定数组的大小但是这样数组的大小是固定的。
C中可以在模板使用非类型模板参数在类里生成一个可以指定大小的成员变量。
templateclass T,size_t n
class arr
{
private:T a[n];
};int main()
{arrint,10 a1;arrint,20 a2;return 0;
}
注非类型模板参数只支持 整形常量。浮点型变量类对象都不行。 4.模板的特化
概念 通常情况下 使用模板可以实现一些与类型无关的代码但对于一些特殊类型的可能会得到一些错误的结 果 需要特殊处理比如实现了一个专门用来进行小于比较的函数模板 举例
// 函数模板 -- 参数匹配
templateclass T
bool Less(T left, T right)
{return left right;
}
int main()
{cout Less(1, 2) endl; // 可以比较结果正确Date d1(2022, 7, 7);Date d2(2022, 7, 8);cout Less(d1, d2) endl; // 可以比较结果正确Date* p1 d1;Date* p2 d2;cout Less(p1, p2) endl; // 可以比较结果错误return 0;
} 可以看到 Less 绝对多数情况下都可以正常比较但是在特殊场景下就得到错误的结果。上述示例中 p1 指向的d1 显然小于 p2 指向的 d2 对象但是 Less 内部并没有比较 p1 和 p2 指向的对象内容而比较的是 p1 和 p2 指 针的地址这就无法达到预期而错误。 此时就 需要对模板进行特化。即 在原模板类的基础上 针对特殊类型所进行特殊化的实现方式。 模板特化中分为函数模板特化 与 类模板特化 函数模板特化 函数模板的特化步骤 1. 必须要先有一个基础的函数模板 2. 关键字 template 后面接一对空的尖括号 3. 函数名后跟一对尖括号尖括号中指定需要特化的类型 4. 函数形参表 : 必须要和模板函数的基础参数类型完全相同如果不同编译器可能会报一些奇怪的错误。 // 函数模板 -- 参数匹配
templateclass T
bool Less(T left, T right)
{return left right;
}
// 对Less函数模板进行特化
template
bool LessDate*(Date* left, Date* right)
{return *left *right;
}
int main()
{cout Less(1, 2) endl;Date d1(2022, 7, 7);Date d2(2022, 7, 8);cout Less(d1, d2) endl;Date* p1 d1;Date* p2 d2;cout Less(p1, p2) endl; // 调用特化之后的版本而不走模板生成了return 0;
} 注一般情况下如果函数模板遇到不能处理或者处理有误的类型为了实现简单通常都是将该函数直接给 出。 bool Less(Date* left, Date* right)
{return *left *right;
} 该种实现简单明了代码的可读性高容易书写因为对于一些参数类型复杂的函数模板特化时特别给 出因此函数模板不建议特化。 类模板特化
类模板特化与函数模板特化一样必须存在模板.(即在原模板的基础上进行特化) 类模板特化分为全特化偏特化 全特化
全特化即是将模板参数列表中所有的参数都确定化
templateclass T1, class T2
class Data
{
public:Data() {coutDataT1, T2 endl;}
private:T1 _d1;T2 _d2;
};template
class Dataint, char //特化为int char
{
public:Data() {coutDataint, char endl;}
private:int _d1;char _d2;
};void TestVector()
{Dataint, int d1;Dataint, char d2;
}
偏特化
偏特化任何针对模版参数进一步进行条件限制设计的特化版本。比如对于以下模板类
templateclass T1, class T2
class Data
{
public:Data() {coutDataT1, T2 endl;}
private:T1 _d1;T2 _d2;
};
偏特化有以下两种表现方式 部分特化将模板参数类表中的一部分参数特化 // 将第二个参数特化为int
template class T1
class DataT1, int
{
public:Data() {coutDataT1, int endl;}
private:T1 _d1;int _d2;
}; 参数更进一步的限制 偏特化并不仅仅是指特化部分参数而是针对模板参数更进一步的条件限制所设计出来的一个特化版 本。 //两个参数偏特化为指针类型
template typename T1, typename T2
class Data T1*, T2*
{
public:Data() {coutDataT1*, T2* endl;}private:T1 _d1;T2 _d2;
};//两个参数偏特化为指针类型
template typename T1, typename T2
class Data T1*, T2*
{
public:Data():_d1(d1),_d2(d2){coutDataT1*, T2* endl;}private:const T1 _d1;const T2 _d2; };void test2 ()
{Datadouble , int d1; // 调用特化的int版本Dataint , double d2; // 调用基础的模板 Dataint *, int* d3; // 调用特化的指针版本Dataint, int d4(1, 2); // 调用特化的指针版本
} 注当模板模板的特护全都存在是那个类型最匹配就用那个。 类模板特化应用示例
专门用来按照小于比较的类模板Less
#includevector
#include algorithm
templateclass T
struct Less
{bool operator()(const T x, const T y) const{return x y;}
};int main()
{Date d1(2022, 7, 7);Date d2(2022, 7, 6);Date d3(2022, 7, 8);vectorDate v1;v1.push_back(d1);v1.push_back(d2);v1.push_back(d3);// 可以直接排序结果是日期升序sort(v1.begin(), v1.end(), LessDate());vectorDate* v2;v2.push_back(d1);v2.push_back(d2);v2.push_back(d3);// 可以直接排序结果错误日期还不是升序而v2中放的地址是升序// 此处需要在排序过程中让sort比较v2中存放地址指向的日期对象// 但是走Less模板sort在排序时实际比较的是v2中指针的地址因此无法达到预期sort(v2.begin(), v2.end(), LessDate*());return 0;
} 通过观察上述程序的结果发现对于日期对象可以直接排序并且结果是正确的。但是如果待排序元素是指 针结果就不一定正确。因为sort 最终按照 Less 模板中方式比较所以只会比较指针而不是比较指针指 向空间中内容此时可以使用类版本特化来处理上述问题 // 对Less类模板按照指针方式特化
template
struct LessDate*
{bool operator()(Date* x, Date* y) const{return *x *y;}
};
特化之后在运行上述代码就可以得到正确的结果
模板的优缺点 【优点】 1. 模板复用了代码节省资源更快的迭代开发 C 的标准模板库 (STL) 因此而产生 2. 增强了代码的灵活性 【缺陷】 1. 模板会导致代码膨胀问题也会导致编译时间变长 2. 出现模板编译错误时错误信息非常凌乱不易定位错误