目录
1. 泛型编程
2. 函数模板
2.1 函数模板的概念及格式
2.2 函数模板的原理
2.3 模板的实例化
2.4 模板参数的匹配原则
3. 类模板
3.1 类模板格式
3.2 类模板的实例化
1. 泛型编程
什么是泛型编程?泛型编程是避免使用某种具体类型而去使用某种通用类型来进行程序编写的方式,依次来复用某段代码而避免大规模功能相似重复冗余的代码。下面的代码如果想用泛型编程该如何实现。
int add(int a,int b)
{
return a + b;
}
double add(double a, int b)
{
return a + b;
}
double add(int a, double b)
{
return a + b;
}
马云有次说了这么一句话:“世界是懒人创造的,懒不是傻懒,如果你想少干,就要想出懒的方法。 要懒出风格,懒出境界。”C++必然也是有风格有境界的,所以C++中设计了模板实现了泛型编程。
2. 函数模板
2.1 函数模板的概念及格式
模板就是一种模具,通过给这个模具中放不同的材料(类型),来获得不同材料的产品,以此来提高我们的工作效率。而函数模板就是某个函数的模具,与类型无关,在使用的时候参数化,在我们给出特定类型就会生成特定类型的版本。
函数模板的格式:
template<typename T1, typename T2 ...typename Tn>
返回值类型 函数名(形参列表)
{
//函数体
}
根据这个函数的模板我们就可以实现上述add函数的模板:
template<typename L, typename R,typename RET>
RET add(L l, R r)
{
return l + r;
}
注:typename是用来定义模板参数的关键字,也可以用class代替。
2.2 函数模板的原理
函数模板本身不是函数,而是一个模具,当我们传入实际类型的时候编译器会根据我们传入的实参类型来推演生成对应类型的函数进行调用。
2.3 模板的实例化
当我们将类型传入模板来生产函数的时候,称之为模板的实例化,模板的实例化分为隐式实例化和显式实例化。
1. 隐式实例化:隐式实例化就是我们不去指定类型,让编译根据我们传入的实参判断其类型传入模板参数列表进行实例化。如下:
template<typename L, typename R>
int add(L l, R r)
{
return l + r;
}
int main()
{
int a = 0, b = 1;
cout << add(a, b) << endl; //根据我传入的a和b自动判断类型进行模板实例化。
}
细心的同学发现,这里我将原来的代码中 typename RET 删掉了,原因就是模板不会对函数的返回值类型进行自动判断,而需要我们手动指定(也就是显式实例化)。
2. 显式实例化:显式实例化就是我们手动向模板传递类型,然后由编译器进行实例化。显式实例化的格式为:
函数名<类型1,类型2,...>(实参列表);
如:
template<typename L, typename R, typename RET>
RET add(L l, R r)
{
return l + r;
}
int main()
{
int a = 0, b = 1;
cout << add<int,int,int>(a, b) << endl;
}
上述代码我们传进去的 a和b的类型与模板参数类型是匹配的,如果不匹配的话编译器会进行隐式类型转化,如果转化失败就报错。如下:
类型转换成功:
template<typename L, typename R, typename RET>
RET add(L l, R r)
{
return l + r;
}
int main()
{
double a = 0.1;
int b = 1;
cout << add<int,int,int>(a, b) << endl; //a:double类型隐式转换成int类型
}
输出:
类型转化失败:
template<typename L, typename R, typename RET>
RET add(L l, R r)
{
return l + r;
}
class Date
{
//
};
int main()
{
int a = 0, b = 1;
cout << add<int,Date,int>(a, b) << endl; //a:类型转化失败
}
输出:
E0304 没有与参数列表匹配的 函数模板 "add" 实例
2.4 模板参数的匹配原则
一个函数的模板函数可以与非模板函数同时存在,并且在没有显式实例化且类型匹配的情况下会优先匹配非模板函数,如果是显式实例化才会调用模板,如下。
template<class T>
void swap(T& a, T& b)
{
std::swap(a, b);
std::cout << "我是模板 ";
std::cout << a << ' ' << b << std::endl;
}
void swap(int& a, int& b)
{
std::swap(a, b);
std::cout << "我是非模板 ";
std::cout << a << ' ' << b << std::endl;
}
int main()
{
int a = 1, b = 2;
std::cout << a << ' ' << b << std::endl;
swap(a, b); //类型匹配优先调用非模板函数
swap<int>(a, b); //调用模板实例化
}
输出:
当我们没有去写模板的时候,函数发生类型不匹配可能会进行隐式类型转换,但是有了模板之后就不会发生隐式类型转化,而是使用模板实例化出来一个更为合适的函数。如下:
没有模板时会发生隐式实例化:
void test(int a, int b)
{
std::cout << "我不是模板" << std::endl;
}
int main()
{
test(2, 2.0);//2.0发生隐式类型转化成int;
}
输出:
存在模板不会发生隐式类型转化,而是使用模板实例化出来一个更为合适的函数:
template<class T1,class T2>
void test(T1 a, T2 b)
{
std::cout << "我是模板" << std::endl;
}
void test(int a, int b)
{
std::cout << "我不是模板" << std::endl;
}
int main()
{
test(2, 2.0);//不发生隐式类型转化而是隐式实例化模板
}
输出:
3. 类模板
3.1 类模板格式
和函数模板一样,类模板是类的一个模具,类模板格式如下:
template<class T1,class T2,class T3,...class Tn>
class 类模板名
{
//类体
};
我们普通类是可以声明和定义分离的,如果类模板要实现声明和定义分离,那么在定义的时候也要加上模板声明。如:
template<class T1, class T2>
class A
{
~A(); //声明
};
template<class T1,class T2>
A<T1, T2>::~A() //定义
{
//...
}
3.2 类模板的实例化
类模板的实例化和函数模板相同,需要在类名后加上尖括号并且指定类型。注:模板名不是类,实例化后才是一个类。
template<class T>
class Date
{
//..
};
int main()
{
Date<int>;
}