函数模板和类模板
前言
C++提供了函数模板,所谓函数模板,实际上是建立一个通用的函数,其函数类型和形参类型不具体指定,而是用一个虚拟类型来代替。这个通用函数就成为函数模板。使用函数模板就不用定义多个函数,只需要在模板中定义一次即可。在调用函数时,系统会根据实参的类型来取代模板中的虚拟类型,从而实现函数的功能。
1、函数模板
问题:为什么要有函数模板?
先看一个需求:交换char类型、int类型、double类型变量的值。
普通函数实现:
// int类型互换 void myswap_int(int &num1,int &num2) { int num = 0; num = num1; num1 = num2; num2 = num; }
// char类型互换 void myswap_char(char &char_a,char &char_b) { char char_c = '0'; char_c = char_a; char_a = char_b; char_b = char_c; }
函数调用:
int main() { { int num1 = 10; int num2 = 20; myswap_int(num1,num2); printf("num1:%d num2:%d",num1,num2); } { char char_a = 'a'; char char_b = 'b'; myswap_char(char_a,char_b); printf("char_a:%c char_b:%c",char_a,char_b); } return 0; }
解决该问题的普通函数有以下特点:
1)函数的业务逻辑是一样的;
2)只是函数的类型参数不一样。
根据以上特点,可以提出一个简化代码的想法,让类型参数化,从而简化代码,方便程序员进行编码。由此便有了编程的一个思想——泛型编程。将以上交换函数的数据类型参数化,就可以得到一个通用的函数,并将其称为函数模板(模板函数)。
函数模板定义形式:
template <typename T1,typename T2,...> 类型 函数名(形式参数表) { ... // 函数内容 }
在看代码的时候,会发现有些代码用的是class关键字来定义泛型类型T,而不是使用typename,其实在最初定义模板的方法为:template<class T>,后来为了避免定义类模板的时候两个class混淆,才引入了typename这个关键字。
针对上面数据交换的问题,可以用一个模板函数来完成:
// 使用模板函数来实现 template <typename T> void myswap(T &a,T &b) { T c; c = a; a = b; b = c; cout << "这是模板函数!" << endl; }
函数模板调用方式:
1)显式类型调用;
2)自动类型推导。
int num1 = 10; int num2 = 20; myswap<int>(num1,num2); // 显示类型调用
char char_a = 'a'; char char_b = 'b'; myswap(char_a,char_b); // 自动类型推导调用
当模板函数遇上普通函数:
若定义如下两个重载函数,一个函数为模板函数,另一个为普通函数:
// 模板函数 template <typename T> void myswap(T &a,T &b) { T c; c = a; a = b; b = c; cout << "这是模板函数!" << endl; } // 普通函数 void myswap(int num,char char_a) { cout << num << " " << char_a << endl; cout << "这是普通函数!"; }
函数调用:
int main() { int num = 99; char char_a = 'a'; char char_b = 'b'; // 普通函数调用 myswap(num,char_a); // 模板函数要求两个数据类型一样才能调用,这里还是调用普通函数,并进行数据类型转换 myswap(char_a,num); // 模板函数调用(本质:类型参数化):将严格地按照类型进行匹配,不会进行类型转换 myswap(char_a,char_b); return 0; }
普通函数和模板函数的区别:
函数模板不允许自动类型转化;而普通函数能够进行自动类型转换。
当模板函数遇上函数重载:
#include <iostream> using namespace std; int max_value(int num1,int num2) { cout << "int max_value(int num1,int num2)" << endl; return num1 > num2 ? num1 : num2; } template<typename T> T max_value(T a,T b) { cout << "T max_value(T a,T b)" << endl; return a > b ? a : b; } template<typename T> T max_value(T a,T b,T c) { cout << "T max_value(T a,T b,T c)" << endl; return max_value(max_value(a,b),c); } int main() { int num1 = 1; int num2 = 2; // 当模板函数和普通函数都符合调用要求时,优先选择普通函数 cout << max_value(num1,num2) << endl; // 如果想在都满足调用条件时选择模板函数,可以使用<>类型列表来显示调用 cout << max_value<>(num1,num2) << endl; // 如果模板函数能更好地匹配,使用模板函数 cout << max_value(3.1,4.1) << endl; // 模板函数的重载 cout << max_value(3.1,4.1,5.1) << endl; // 调用普通函数,可以隐式类型转化 cout << max_value('a',1) << endl; return 0; }
当模板函数和普通函数重名时,即发生重载时,调用函数遵循一下规则:
1)C++编译器优先考虑普通函数;
2)如果模板函数可以产生一个更好的匹配,那么选择模板函数;
3)可以通过空模板实参列表的语法限定编译器只通过模板匹配;
4)模板函数可以像普通函数一样被重载。
函数模板处理机制:
- 编译器并不是吧函数模板处理成能够处理任意类的函数
- 编译器从函数模板通过具体类型产生不同的函数
- 编译器会对函数模板进行两次编译:在声明的地方对模板代码本身进行编译; 在调用的地方对参数类型替换后的代码进行编译。
2、类模板
类模板与函数模板类似,将具有相同功能,仅仅是数据类型不同的多个类用一个模板类来代替,就不用定义多个类。
类模板的定义如下:
template <typename T1, typename T2, ...>
class TClass
{
//成员变量 和 成员函数
}
下面给出一个实例说明模板类的定义和使用:
#include <iostream> using namespace std; template<typename T> class Tclass { public: Tclass(T a) { this->a = a; } public: void printA() { cout << a << endl; } private: T a; }; // 模板类作为函数参数
// 函数参数需要指定具体的类,所以需要明确模板类中的数据类型 void useTclass(Tclass<int> tclass) { tclass.printA(); } int main() { // Tclass tclass1(12); 编译错误 Tclass<int> tclass1(12); // 要指定具体的数据类型,不然编译器无法分配内存 tclass1.printA(); // 12 useTclass(tclass1); // 12 return 0; }
模板类的继承:
#include <iostream> using namespace std; template<typename T> class Tclass { public: Tclass(T a) { this->a = a; } public: void printA() { cout << a << endl; } protected: T a; }; // 从模板类派生时,需要具体化模板类,C++编译器需要知道数据类型, //=>编译器才知道如何分配内存 class Tclass_son : public Tclass<int> { public: // 基类显示定义了构造函数,派生类也要显示定义构造函数 Tclass_son(int a,int b):Tclass<int>(a) { this->b = b; } void printB() { cout << "a:" << a << endl; cout << "b:" << b << endl; } private: int b; }; // 模板类派生模板类 template<typename T> class Tclass_tson : Tclass<T> { public: Tclass_tson(T a,T c) : Tclass<T>(a) { this->c = c; } void printC() { cout << "c:" << c << endl; } protected: T c; }; int main() { Tclass_son tclassSon(11,12); tclassSon.printB(); Tclass_tson<int> tclassTson(13,14); tclassTson.printC(); return 0; }
输出:
a:11 b:12 c:14
类模板中的static关键字:
static关键字:
C++中使用 static 关键字可以把类成员定义为静态的。当我们声明类的成员为静态时,这意味着无论创建多少个类的对象,静态成员都只有一个副本。静态成员在类的所有对象中是共享的。
在C++中,不能把静态成员放置在类的定义中,但是可以在类的外部通过使用范围解析运算符 :: 来重新声明静态变量从而对它进行初始化,如下面的实例所示:
class StaticDeclare{ public: static int num; //声明了但未定义,未分配内存 }
以上StaticDeclare类中声明了一个静态成员变量,但是还未定义,不能使用,还需要在类外部进行定义和初始化:
int StaticDeclare::num = 10; //定义了成员静态变量,并同时初始化,分配内存
这样编译器才能为num分配内存,实例化的对象才能访问静态成员变量num。
那么,如果是在类模板中声明定义一个静态成员变量,这个成员变量都为所有实例化的对象所使用吗?比如:
template<typename T> class StaticDeclare{ public: static T variableTest; }; template<typename T> T StaticDeclare<T>::variableTest = 0;
如果用这个模板类创建了int类型的对象和double类型的对象,它们都能共享variableTest静态变量吗?
那让编译器来告诉我们答案:
//============================================================================ // Name : static_in_tempalteClass.cpp // Author : Yun Hong // Version : // Copyright : Your copyright notice // Description : Hello World in C++, Ansi-style //============================================================================ #include <iostream> using namespace std; template<typename T> class StaticTest{ public: static T variableTest; private: }; template<typename T> T StaticTest<T>::variableTest = 0; int main() { StaticTest<int> staticTest1_int1,staticTest1_int2,staticTest1_int3; staticTest1_int1.variableTest = 10; staticTest1_int2.variableTest++; staticTest1_int3.variableTest++; cout<< StaticTest<int>::variableTest <<endl; StaticTest<char> staticTest1_char1,staticTest1_char2,staticTest1_char3; staticTest1_char1.variableTest = 'a'; staticTest1_char2.variableTest ++; staticTest1_char3.variableTest ++; cout<< StaticTest<char>::variableTest <<endl; return 0; }
这里分别实例化了int和char两种类型的对象,输出结果如下:
12 c
很明显,答案已经出来了,它们并没有共享variableTest静态变量,而是int类型和char类型各自有自己的静态变量。这是因为,对类模板进行实例化的时候,会先自动创建一个类,再进行实例化。比如:上面例子中要对类模板进行int类型实例化,则泛型T确定为int,创建一个int类型的StaticTest类,这时,静态成员变量变量variableTest属于int类型的StaticTest类。即每一种数据类型的类,有自己的静态成员。


浙公网安备 33010602011771号