C++入门——模板

烂漫一生 提交于 2020-03-02 10:28:52

- 模板的介绍

模板是懒人的福音,是提高开发效率和降低维护成本的利器。

  • 相关概念
    模板分为函数模板和类模板,是泛型编程的基础。
    泛型编程就是编写与类无关的通用代码,使用时只需要传入实际的类型对模板进行实例化即可。

简单来说,模板就是一种万能的类型,我们使用这种类型编写出来的函数就是模板函数;使用这种类型作为类成员编写的类就是模板类。在使用模板的时候需要我们传入实际的类型对模板进行填充(这个步骤由编译器来完成),这样对于相同的逻辑我们就不必再去重载太多的函数了,若是代码有问题也只用改一份代码的逻辑,因此提高了代码的复用率的同时也减少了维护代码的耗费。

- 函数模板

  • 定义
    template<typename T1, typename T2,…,typename Tn>
    返回值类型 函数名(参数列表) {}

    其中,typename可以使用class代替,效果是一样的

  • 实例化
    函数模板的实例化分为:隐式实例化和显式实例化

    隐式实例化:让编译器根据实参推演模板参数的实际类型
    显式实例化:自己制定模板参数的类型

    下面举一个例子说明这两种实例化的区别
    写了一个交换函数

    template<typename T>
    void Swap(T& left, T& right) {
    	T tmp = left;
    	left = right;
    	right = tmp;
    }
    

    用这个函数做一些事情

    	int ai = 1, bi = 2;
    	float af = 1, bf = 2;
    	Swap(ai, bi);
    	Swap(af, bf);
    	Swap(ai, bf);
    

    前面两次交换都没有问题,但是交换ai和bf的时候出错了,因为编译器找不到符合这种情况的模板——有两个不同的模板参数的模板,为了解决这个问题,有三种解决方法

    1. 强制转换,使得两个参数类型相同
      Swap<int>(ai, (int&)bf);
    2. 显示实例化
      Swap<int>(ai, bf);
      虽然指定函数使用int类型实例化了,但这么写编译器依然会报错,因为编译器没法将float类型转换为int引用类型。但如果函数的参数不是引用类型是可以转换的。
    3. 再写一个模板函数
      template<typename T1, typename T2>
      void Swap(T1& left, T2& right) {
      	T1 tmp = left;
      	left = right;
      	right = tmp;
      }
      
  • 递归实例化
    参考:template 的递归调用问题
    若是这样定义一个函数模板

    template<int N>
    int fun() {
    	if (N == 1) {
    		return 1;
    	}
    	if (N == 2) {
    		return 1;
    	}
    	return fun<N-1>() + fun<N - 2>();
    }
    

    编译器会报错,原因是编译器不知道要实例化多少个这样的函数,因为if是运行时判断的,而实例化是编译期完成的
    于是我们只好自己写递归出口,也就是N的特例化函数

    注:特例化不是函数重载,而是我们自己实例化一个模板,它与显式实例化的区别在于显示实例化仍然是编译器写的,而特例化是我们自己手写的。

    template<int N>
    int fun() {
    	return fun<N-1>() + fun<N - 2>();
    }
    
    template<>
    int fun<1>() {
    	return 1;
    }
    
    template<>
    int fun<2>() {
    	return 1;
    }
    

    再写一个主函数试试

    int main() {
    	cout << fun<10>() << endl;
    	return 0;
    }
    

    好像没什么问题,编译执行后程序输出55
    再修改一下参数

    int main() {
    	cout << fun<4000>() << endl;
    	return 0;
    }
    

    编译报错了,错误输出和之前没写递归出口时一样
    原因是,编译期编译器需要对这4000个函数一个一个进行实例化,实例化到3900多个时感觉代码段不够用了,于是直接罢工。

- 类模板

  • 定义
    template<class T1, class T2, …, class Tn>
    class 类模板名{
     //类体
    };
  • 实例化
    类模板实例化与函数模板实例化不同,函数模板实例化时编译器可以根据输入参数的类型推导出实例化的模板,而类模板必须要显示实例化,因为编译器无法根据类的构造函数的传参推导出模板参数的类型

- typename 和 class的区别

参考:C++中typename和class在声明模板时的区别
前面说过,如果typename或class声明模板参数,效果是一样的,那为什么C++要引进typename呢?
假设有如下场景,我们的模板参数需要传一个类类型

class A {

};

class B :public A{

};

template<class T>
void Test() {
	T::A a1;
	typename T::A a2;
}

试着编译一下发现编译器报错了,因为在编译器眼里T::A是一个错误的语法,它不知道这个T是个啥,它通过作用域运算符访问到的A是个啥,所以编译器懵了。而在前面声明了typename关键字之后,编译器就会将T默认为一个类,这种嵌套依赖的问题也就解决了。

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!