(一) 模板与泛型
⑴ 泛型
它是一种泛化的编程方式,其实现原理为程序员编写一个函数/类的代码示例,让编译器去填补出不同的函数实现。允许您延迟编写类或方法中的编程元素的数据类型的规范,直到实际在程序中使用它的时候。换言之,泛型允许您编写一个可以与任何数据类型一起工作的类或方法。
⑵ 模板
模板是泛型(泛型generic type——通用类型之意)编程的基础,是创建泛型类或函数的蓝图或公式。库容器,比如迭代器和算法,都是泛型编程的例子,它们都使用了模板的概念。每个容器都有一个单一的定义,比如 向量,我们可以定义许多不同类型的向量,比如 vector 或 vector 。
模板是一种对类型进行参数化的工具,可以是如下形式:
-
函数模板:
-
类模板:
-
别名模板(C++11):
-
变量模板(C++14):
-
约束与概念(C++20):
模板是一种忽略数据一种泛型编程。把数据当做未知量,当使用的传入类型一种编程方式。
⑶ 为什么要有模板的原因?
现实中,我们可能先用设计一个包含两个int参数的函数,用来求两个对象的和,在实践中我们发现我们可以还需要其他类型的求两个对象之和,则需要定义n多个函数
int Max(int a, int b)
{
return a > b ? a : b;
}
string Max(string a, string b)
{
return a > b ? a : b;
}
double Max(double a, double b)
{
return a > b ? a : b;
}
写这样相似代码,有点麻烦,冗余,故用一个模板来总结,该比较怎样的数据,就传对应的参数给模板。
这个例子就是实现了多种不同类型的两个数之间的交换,我们这里只是实现了三种,如果要实现很多种,那代码量就非常大。如果可以只用一个函数和类来描述,那将会大大 减少代码量,并能实现程序代码的复用性,所以这里就要用到模板了。
对于函数功能相同,唯一的区别就是参数类型不同,在这种情况下,不必定义多个函数,只需要在模板中定义一次即可。在调用函数时编译器会根据实参的类型生成对应类的函数实体(调用的不是模板,而是模板实例化出来的实体),从而实现不同的函数功能。
PS:编译器根据实参生成特定的函数的过程叫做模板实例化
语法格式:
template <class T> //告诉编译器,接下来要用到一个未知类型是T类型
//template <typename T> typename等效用class
template <class T1,class T2,class T3> //三个未知类型
具体代码:
#include <iostream>
#include <string>
using namespace std;
template <class Type> Type Max(Type a, Type b)
{
return a > b ? a : b;
}
//两种写法没有区别
template<class Type>
void print(Type a)
{
cout << a << endl;
}
int main()
{
print(Max(2, 6));
print(Max(3.2, 0.6));
print(Max("3.2", "0.6"));
return 0;
}
(二)C++函数模板
-
函数模板,就是定义一个通用函数,其函数类型和形参类型不具体指定,用一个虚拟的类型来代表。这个通用函数就称为函数模板。实际上是定义一个可以生成任意类型函数的模板,它所用到的数据的类型均被作为参数:不指定具体类型,而是用一个虚拟 的类型来代替(实际上是用一个标识符来占位)。凡是函数体相同的函数都可以用这个模板来代替,在函数调用时根据传入的实参来逆推出真正的类型,从而产生一个针对该类型的实体函数。这个通用函数就称为函数模板。
-
所有函数体相同的函数都可以用这个模板来代替,不必定义多个函数,只需在模板中定义一次即可。在调用函数时系统会根据实参的类型来取代模板中的虚拟类型,从而实现了不同函数的功能。
函数模板声明语法:
template <模板参数表>
类型名 函数名 (参数表){
函数体的定义}
template<typename Type,...> //注意后面不能加分号 Type funName(Type val){ //Code }
template: 是声明模板的关键字,告诉编译器开始泛型编程。
<typename type>:尖括号中的typename是定义模板形参的关键字,用来说明后面的模板形参是类型。typename还可以用class关键字来代替,但是推荐用typename 。
-
函数模板调用
-
函数模板隐式调用
-
显式调用 : 函数名<未知类型>(函数参数)
-
自动类型推导:根据参数的类型进行推导,但是两个参数的类型必须一致,否则会报错。
-
-
函数模板本质就是函数传参
-
函数模板也是可以缺省
-
-
函数模板种存在变量
-
这种函数模板必须显示调用
-
变量传参只能传入常量
-
-
当函数模板和普通函数相遇
-
优先调用类型一致的普通函数
-
显式调用一定是调用模板
-
-
函数模板重载
-
优先调用传参数目少的函数模板
-
-
具体代码:
#include <iostream>
#include <string>
using namespace std;
template <class Type>
Type Max(Type a, Type b)
{
return a > b ? a : b;
}
template <class _Ty1, class _Ty2, class _Ty3>
void print(_Ty1 one, _Ty2 two, _Ty3 three)
{
cout << "1:" << one << endl;
cout << "2:" << two << endl;
cout << "3:" << three << endl;
}
//函数模板的缺省
template <class _Ty1, class _Ty2 = string, class _Ty3 = int>
void printData(_Ty1 one, _Ty2 two, _Ty3 three)
{
cout << "1:" << one << endl;
cout << "2:" << two << endl;
cout << "3:" << three << endl;
}
template <class _Ty, int size>
_Ty* createArray()
{
_Ty* parray = new _Ty[size];
return parray;
}
template <class _Ty, int size>
void printArray(_Ty array[])
{
for (int i = 0; i < size; i++)
{
cout << array[i] << "\t";
}
cout << endl;
}
template <class _Ty, int size = 3>
void printArray2(_Ty array[])
{
for (int i = 0; i < size; i++)
{
cout << array[i] << "\t";
}
cout << endl;
}
void test()
{
//No.4 函数模板存在变量
int* pInt = createArray<int, 5>(); //_Ty =int size=5
string* pStr = createArray<string, 5>();
double* pDouble = createArray<double, 5>();
//必须要显式调用
int array[3] = { 1,2,3 };
printArray<int, 3>(array);
double dNum[3] = { 1.11,2.33,4.44 };
printArray<double, 3>(dNum);
//变量做了缺省,可以隐式调用
printArray2(array);
printArray2(dNum);
}
void Func1(int a, string b, double c)
{
cout << "普通函数" << endl;
}
//函数模板允许重载
template <class _Ty1, class _Ty2, class _Ty3>
void Func1(_Ty1 a, _Ty2 b, _Ty3 c)
{
cout << "三个类型模板" << endl;
}
template <class _Ty1, class _Ty2>
void Func1(_Ty1 a, _Ty2 b, _Ty2 c)
{
cout << "两个类型模板" << endl;
}
template <class _Ty1>
void Func1(_Ty1 a, _Ty1 b, _Ty1 c)
{
cout << "一个类型模板" << endl;
}
void test2()
{
Func1<int, string, double>(1, "sd", 1.11);
Func1(1, string("sd"), 1.11);
Func1(1, 1, 1); //一个参数的模板
Func1("string", 1, 1);
Func1("string", 1, "sfsd");
}
int main()
{
//No.1 隐式调用法
cout << Max(string("abc"), string("dbc")) << endl; //Type=string
cout << Max(1, 2) << endl; //Type=int
cout << Max(1.11, 2.33) << endl; //Type=double
//No.2 显示调用
cout << Max<string>(string("abc"), string("dbc")) << endl; //Type=string
cout << Max<int>(1, 2) << endl; //Type=int
cout << Max<double>(1.11, 2.33) << endl; //Type=double
print<int, string, double>(1, "ILoveyou", 1.11);
print<string, string, string>("abc", "ILoveyou", "模板");
//No.3 缺省
printData<string>("str1", "str2", 1); //_Ty1=string _Ty2=string _Ty3=int
printData<string, double>("str1", 1.11, 1);
printData<string, double, string>("str1", 1.11, "str3");
test();
test2();
return 0;
}
(三)类模板
使用类模板使用户可以为类定义一种模式,使得类中的某些数据成员、某些成员函数的参数、返回值或局部变量能取不同类型(如系统预定义和用户自定义的)。
类模板则是对不同类的公共性质的抽象,而类是对一组对象的公共性质的抽象,因此类依是属于更高层次的抽象。由于类模板需要一种或多种类型参数,所以类模板也常常称参数化类。当然,模板参数的实参也不总是可以用任何类型的。
类模板声明的语法形式是:
template<模板参数表>
class 类名{
类成员声明
};
其中类成员声明的方法与普通类的定义几乎相同,只是在它的各个成员(数据成员和函敬成员)中通常要用到模板的类型参数T。其中“模板参数表”的形式与函数模板中的“模板数表”完全一样。
如果需要在类模板以外定义其成员函数,则要采用以下的形式:
template<模板参数表>
类型名 类名<模板参数标识符列表>::函数名(参数表)
一个类模板声明自身并不是一个类,只有当被其他代码引用时,模板才根据引用的需要
生成具体的类。
使用一个模板类来建立对象时,应按如下形式声明:
模板名<模板参数表>对象名1,…,对象名 n;
具体代码:
#include <iostream>
#include <string>
using namespace std;
template <class _Ty1, class _Ty2>
class Data
{
public:
void print();
protected:
public:
static int count;
};
template <class _Ty1, class _Ty2>
int Data<_Ty1, _Ty2>::count = 0;
template <class _Ty1, class _Ty2>
void Data<_Ty1, _Ty2>::print()
{
cout << "类模板中函数" << endl;
}
template <class _Ty1, class _Ty2>
class Son :public Data<_Ty1, _Ty2>
{
public:
protected:
};
struct MMInfo
{
string name;
int age;
int num;
};
ostream& operator<<(ostream& out, const MMInfo& object)
{
out << object.name << "\t" << object.age << "\t" << object.num;
return out;
}
struct Score
{
int math;
int english;
int py;
};
ostream& operator<<(ostream& out, const Score& object)
{
out << object.math << "\t" << object.english << "\t" << object.py;
return out;
}
template <class _Ty1, class _Ty2>
class MM
{
public:
MM(_Ty1 one, _Ty2 two) :one(one), two(two)
{
}
void print()
{
cout << one << "\t" << two << endl;
}
protected:
_Ty1 one;
_Ty2 two;
};
void testMM()
{
MM<string, int> mm("月亮", 18);
mm.print();
MM<int, int> complex(1, 2);
complex.print();
MMInfo info = { "美女",18,1001 };
Score score = { 88, 99, 100 };
MM<MMInfo, Score> mmObject(info, score);
mmObject.print();
}
//类模板特化
template <class _Ty1, class _Ty2, class _Ty3>
class A
{
public:
A(_Ty1 one, _Ty2 two, _Ty3 three) :one(one), two(two), three()
{
cout << "三个类型" << endl;
}
protected:
_Ty1 one;
_Ty2 two;
_Ty3 three;
};
//局部特化
template <class _Ty1>
class A<_Ty1, _Ty1, _Ty1>
{
public:
A(_Ty1 one, _Ty1 two, _Ty1 three) :one(one), two(two), three()
{
cout << "局部特化" << endl;
}
protected:
_Ty1 one;
_Ty1 two;
_Ty1 three;
};
void testA()
{
A<int, int, int> object1(1, 1, 1);
A<double, double, double> object2(1.1, 1.1, 1.1);
A<double, string, double> object3(1.1, string("模板"), 1.1);
}
int main()
{
Data<int, string> object;
Data<int, string>* pObject = new Data<int, string>;
object.print();
pObject->print();
cout << Data<int, int>::count << endl;
cout << Data<string, int>::count << endl;
testMM();
testA();
return 0;
}