泛型编程,主要是利用模板,其主要目的是提高代码复用性,将类型参数化;分为两类:函数模板和类模板
一,函数模板:
1,主要用法:
另外T也可以用于函数的返回值。
#include <iostream>
#incldue <string>
using namespace std;
template<typename T> //告诉编译器,下面这个函数是一个模板,T为模板类型
void mySwap(T& a, T& b) {
T temp = a;
a = b;
b = temp;
}
int main() {
int a = 1;
int b = 2;
mySwap(a, b); //使用方式一:直接用,编译器会自动识别参数类型
cout << "a = " << a << endl;
cout << "b = " << b << endl;
double c = 1.1;
double d = 2.2;
mySwap<double>(c, d); //使用方式二:指定好本次使用的类型T
cout << "c = " << c << endl;
cout << "d = " << d << endl;
}
2,模板使用注意事项
(1),自动类型推导,必须推导出一致的数据类型才可以
还是上面的例子,改成:
int a = 10;
char ch = 'c';
mySwap(a,c); //报错,一个是int,一个是char类型,自动类型推导失败
(2),模板必须要确定出T的数据类型
template<class T>
void func(){
cout << "func 函数调用" << endl;
}
int main(){
func(); //报错,这里func虽然被声明为一个模板,但函数体内并没有用到,也没有传参,编译器此时已经无法进行自动类型推导
func<int>();
func<double>(); //这两句都没问题,因为此时编译器无法自动类型推导,手动给他一个类型就可以继续跑下去
}
3,函数模板与普通函数的区别,主要在于普通函数可以进行隐式类型转换,而函数模板,在自动类型推导时(不显式地指定参数类型),不会进行隐式类型转换,在显示指定参数类型时(显示地给出参数类型),可以隐式类型转换
建议使用模板时显式地给出运算类型,以防编译器自动推导出的类型不是我们期望的类型
int addInt(int a, int b){
return a + b;
}
template<class T>
T addTemplate(T a, T b){
return a + b;
}
int main(){
int a = 10;
char b = 'b';
cout << addInt(a,b) << endl; //108,普通函数调用,自动将字符'b'转换成了98
cout << addTemplate(a,b) << endl; //报错,自动类型推导时无法隐式转换,参考注意事项2,必须推导出一致的数据类型才可以
cout << addTemplate<int>(a,b) << endl //98,显式地给出了参数类型,将会先隐式转换成int,再参与运算
return 0;
}
4,普通函数与函数模板的调用规则
(1),如果普通函数与函数模板都可以实现,优先调用普通函数;
(2),可以使用空模板参数列表调用函数模板;
(3),函数模板也可以发生重载;
(4),如果函数模板可以发生更好的匹配,那优先调用函数模板。
void myPrint(int a, int b){
cout << "普通函数调用" << endl;
}
template<class T>
void myPrint(T a, T b){
cout << "函数模板调用" << endl;
}
template<class T>
void myPrint(T a, T b, T c){
cout << "函数模板重载调用" << endl;
}
int main(){
int a = 10;
int b = 20;
int c = 30;
myPrint(a, b); //普通函数调用,规则1
myPrint<>(a, b); //函数模板调用,规则2
myPrint(a, b, c); //函数模板重载调用,规则3
myPrint('a', 'b'); //函数模板调用,规则4
return 0;
}
说明:
普通函数与函数模板名字相同,已经形成了重载
1)规则1,如果把普通函数只保留声明,而去掉实现,那么会报错找不到函数实现,而不是转而去调用函数模板,因为编译器已经认定了要去调普通函数;
2)规则3又是发生了一次重载,三个参数,编译器别无选择只能调这个三个参数的函数模板,此时三个函数是重载的(普通函数,两参数模板,三参数模板)
3)规则4中 “更好地匹配”,就是指不用发生隐式类型转换,如果调普通函数要发生转换,而模板只需要自动推导出类型,因此才会去调函数模板
既然提供了函数模板,就最好不要再提供同名的普通函数了,避免出现二义性!
5,模板的局限性
模板不是万能的,比如下面这段代码:对于一些类型当然没问题,比如 int double或者自定义的类型,但是如果传进来的是数组呢?数组中 int arr[] = … 中arr是const修饰的,不可指向其他空间,因此在运行时会报错
template<class T>
void func(T a, T b){
a = b;
}
另外下面代码,如果传参是自定义类型呢?普通类型可以比较,自定义类型要想比较,就要重写==运算符了,如果我们的模板很多,还想判定是否大于、小于…那么就得重新很多很多运算符,很麻烦
class Person{
public:
int mAge;
string mName;
}
template<class T>
bool myCompare(T& a, T& b){
if(a == b){
return true;
}
return false;
}
对于上面的不方便,可以使用特定类型的函数模板,template<>就表明这是一个针对特定类型的函数模板,参数列表里也不是写T了,而是直接写明了Person& 类型,当传参是Person类型时会优先走这个函数
template<> bool myCompare(Person& p1, Person& p2){
//自定义操作
}
二,类模板
与函数模板类似,其主要用于定义类模板
1,主要用法:
#include <iostream>
#include <string>
using namespace std;
template<class NameType, class AgeType>
class Person {
public:
Person(NameType name, AgeType age) {
this->mName = name;
this->mAge = age;
}
void showInfo() {
cout << "name:" << mName << " age:" << mAge << endl;
}
NameType mName;
AgeType mAge;
};
void test0110() {
Person<string, int> p1("zhangsan", 18);
p1.showInfo(); //zhangsan 18
}
int main() {
test0110();
return 0;
}
2,类模板与函数模板的区别
1)在实例化对象时,只能显式地给出类型,不能进行自动类型推导,以下语句将报错:
Person p2(“lisi”, 19);
2)类模板支持默认参数类型,一旦使用了默认参数,在实例化时此参数的类型可以省略,将使用默认的类型
template<class NameType, class AgeType = int> //第二个参数类型默认使用int
Person< string> p2(“lisi”, 19); //不会报错,因为默认使用了int
Person< string> p3(“wangwu”, ‘a’); //不会报错,将字符’a’转成了int类型(97)
Person<string, string> p4(“zhaoliu”, “abc”); //不会报错,第二个参数类型不使用默认类型int,而是手动指定string
3,类模板中成员函数的创建时机:在运行时才会创建,编译时不会
class Person1 {
public:
void showPerson1() {
cout << "show Person1" << endl;
}
};
class Person2 {
public:
void showPerson2() {
cout << "show Person2" << endl;
}
};
template <class T>
class Person0 {
public:
T obj;
void showPerson1() {
obj.showPerson1();
}
void showPerson2() {
obj.showPerson2();
}
};
int main(){
Person0<Person1> p1;
p1.showPerson1(); // flag1行
p1.showPerson2(); // flag2行
return 0;
}
以上代码中,如果注掉flag1、flag2这两行,将不会报错,为什么?我怎么知道obj是什么类型?既然不知道是什么类型,怎么会知道其有没有showPerson1()与showPerson2()函数呢?万一最后实例化的时候传进来的是个int类型,int类型哪有这两个函数啊?
这也从侧面验证了类模板的成员函数,是在运行时才会创建,编译时不会做这些检查,取消注释,那么flag2行将报错,因为这时候已经实例化了,编译器知道了p1对象使用的参数类型是Person1,有showPerson1()函数,没有showPerson2()函数,因此flag1行不会报错,flag2行报错。
那么为什么在运行时才会创建呢?我想应该是为了保证模板类的通用性吧。
4,类模板对象作为函数参数
既然我们的类模板对象,是在实例化时才确定类型,那么这个对象作为函数的参数时,又该如何写参数类型呢?有三种方式:
class Person {
public:
Person(NameType name, AgeType age) {
this->mName = name;
this->mAge = age;
}
void showInfo() {
cout << "name:" << mName << " age:" << mAge << endl;
}
NameType mName;
AgeType mAge;
};
//方式一:直接写清除什么类型
void printPerson1(Person<string, int>& p) {
cout << "方式一结果:" << endl;
p.showInfo();
}
//方式二:参数模板化,将对象中的参数作为模板传递
template<class T1, class T2>
void printPerson2(Person<T1, T2>& p) {
cout << "方式二结果:" << endl;
p.showInfo();
cout << "第二种方式,函数模板自动类型推导,推出的类型" << typeid(T1).name() << endl;
cout << "第二种方式,函数模板自动类型推导,推出的类型" << typeid(T2).name() << endl;
}
//方式三:整个类模板化,把函数也写成函数模板,然后实际传参时,会进行自动类型推导
template<class T>
void printPerson3(T& t) {
cout << "方式三结果:" << endl;
t.showInfo();
cout << "第三种方式,函数模板自动类型推导,推出的类型" << typeid(T).name() << endl;
}
int main(){
Person<string, int> p1("zhangsan", 18);
p1.showInfo();
printPerson1(p1);
printPerson2(p1);
printPerson3(p1);
return 0;
}
typeid关键字可以看类型信息,其返回值是个type_info类型,再调用name()函数可以看到其类型的名字
结果:这个长串就是string的原本真实名字,可以看出推导的没问题
建议使用第一种,简单直接,便于阅读维护,也不会产生什么歧义和非预期结果。
5,类模板与继承
如果父类是一个类模板
class Base {
public:
Base() {
cout << "父类中T的类型为:" << typeid(T).name() << endl;
}
T member;
};
template<class T1, class T2> //flag1
class Son :public Base<T2> { //flag2
public:
Son() {
cout << "子类中T1的类型为:" << typeid(T1).name() << endl;
cout << "子类中T2的类型为:" << typeid(T2).name() << endl;
}
T1 memberOfSon;
};
int main(){
Son<int,char> s;
return 0;
}
当父类是一个类模板时,其子类在继承时必须给出父类的类型,或者将子类也定义为类模板
1)上面代码中,在Son类型创建对象时两个类型分别是int、char,其中第二个类型char继承自父类,相当于又指定了父类的参数类型,因此跑起来的结果是:
2)如果将flag1行注掉,flag2行改为:
class Son :public Base { //报错
将会报错,因为父类是个类模板,在继承时必须要给出其父类的参数类型,比如这样:
class Son :public Base< int>
6,类模板成员函数的类外实现
class Dog {
public:
Dog(T1 name, T2 age);
void showInfo();
T1 mName;
T2 mAge;
};
template<class T1, class T2 >
Dog<T1, T2>::Dog(T1 name, T2 age) {
this->mName = name;
this->mAge = age;
}
template<class T1, class T2>
void Dog<T1, T2>::showInfo() {
cout << "name:" << this->mName << "age:" << this->mAge << endl;
}
注意:(以这块代码为例)
1),在类外实现时必须要加入template<class T1, class T2>的标注;
2),在写作用域时要写成Dog<T1, T2>
以告诉编译器 ,这是一个类模板的成员函数的类外实现。
7,类模板的分文件编写
由于类模板成员函数创建时机是在调用阶段,因此在.h中写声明,在.cpp中写实现这种方式,会导致编译器不知道.cpp里面的内容,因为调用阶段才回去访问cpp里面的内容,因此会报链接找不到的错误,解决办法:(主流使用第二种)
(1),直接包含.cpp,不包含.h了;
(2),不分文件编写,把声明和实现写在一个文件里,然后把后缀改为.hpp(这是一种约定俗成,一看到hpp大家就会明白这是个类模板)
8,类模板与友元
template<class T1, class T2> //类外实现,必须要先让编译器知道有这个函数printPerson2存在,而这个函数又用到了Person类
class Person; //所以要先声明class Person
template<class T1, class T2>
void printPerson2(Person<T1, T2>& p){
cout << "姓名:"<< p.mName << "年龄:" << p.mAge << endl;
}
template<class T1, class T2>
class Person{
friend void printPerson1(Person<T1, T2>& p){
cout << "姓名:"<< p.mName << "年龄:" << p.mAge << endl;
}
friend void printPerson2<>(Person<T1, T2>& p){
}
public:
Person(T1 name, T2 age){
this->mName = name;
this->mAge = age;
}
private:
T1 mName;
T2 mAge;
}
int main(){
Person<string, int> p("张三", 20);
printPerson1(p); //友元全局函数 类内实现
printPerson2(p); //友元全局函数 类外实现,
return 0;
}