C++底层学习预备:模板初阶

06-01 1212阅读

文章目录

  • 1.编程范式
  • 2.函数模板
    • 2.1 函数模板概念
    • 2.2 函数模板原理
    • 2.3 函数模板实例化
      • 2.3.1 隐式实例化
      • 2.3.2 显式实例化
      • 2.4 模板参数的匹配原则
      • 3.类模板
      • 希望读者们多多三连支持
      • 小编会继续更新
      • 你们的鼓励就是我前进的动力!

        进入STL库学习之前我们要先了解有关模板的学习,以便在学习完STL库使用之后,能更深入的了解其底层工作原理

        1.编程范式

        编程范式指的是我们使用编程的基本风格和方法

        常见的方式有以下几种:

        面向对象编程(OOP)

        将数据和操作数据的方法封装在类中,通过类的实例(对象)来进行交互,强调数据的封装、继承和多态性

        定义一个Shape基类,包含计算面积的纯虚函数,再派生出Circle和Rectangle等类,重写计算面积的函数,体现了面向对象的继承和多态特性

        函数式编程

        将计算视为函数的组合和应用,强调不可变数据和纯函数,避免副作用,注重函数的输入输出关系

        使用std::function和lambda表达式可以方便地进行函数式编程,如用lambda表达式定义一个简单的加法函数,不修改外部状态,只返回计算结果

        过程式编程

        以过程(函数)为中心,将程序分解为一系列的步骤和函数调用,数据和操作数据的函数相对独立

        传统的C语言风格的编程方式,如编写一个计算阶乘的函数,通过循环和递归来实现计算过程,就是典型的过程式编程

        泛型编程

        定义函数、类或其他程序结构时,不指定具体的数据类型,而是使用类型参数来代表未知的数据类型

        在algorithm头文件中的swap函数就是一种常见的泛式编程,他不指定任何类型就能实现交换,依靠的就是泛式编程,也是我们接下来要学习的模板

        2.函数模板

        在还不知道头文件前实现swap函数通常是这样的:

        void Swap(int& left, int& right)
        {
        	 int temp = left;
        	 left = right;
        	 right = temp;
        }
        void Swap(double& left, double& right)
        {
        	 double temp = left;
        	 left = right;
        	 right = temp;
        }
        void Swap(char& left, char& right)
        {
        	 char temp = left;
        	 left = right;
        	 right = temp;
        }
        ......
        

        为了符合各个场景下实现参数互换,要对同一个函数实现不同类型的函数重载,这种方式固然可行,但是每个类型都写一遍太过于冗余了

        1. 重载的函数仅仅是类型不同,代码复用率比较低,只要有新类型出现时,就需要用户自己增加对应的函数
        2. 代码的可维护性比较低,一个出错可能所有的重载均出错

        2.1 函数模板概念

        我们知道文字的印刷是依靠活字印刷术的模板实现的,那能否告诉编译器一个模子,让编译器根据不同的类型利用该模子来生成代码呢?

        这里用到的模板就是函数模板,其语法形式为:

        template
        

        template就是模板的意思,是用来定义模板参数关键字,也可以使用class,切记:不能使用struct代替class,因为struct和class的默认权限不同,会导致一些混淆和潜在的问题

        2.2 函数模板原理

        函数模板是一个蓝图,它本身并不是函数,是编译器用使用方式产生特定具体类型函数的模具。所以其实模板就是将本来应该我们做的重复的事情交给了编译器

        举个例子:

        template
        void Swap(T& a, T& b)
        {
        	T temp = a;
        	a = b;
        	b = temp;
        }
        

        实现一个Swap交换函数

        C++底层学习预备:模板初阶

        对两个不同类型的函数进行同一个函数的调用,调试模式下转到反汇编可以发现,两个函数式模板示例化后被调用的

        这直接说明了调用的不是同一个函数

        C++底层学习预备:模板初阶

        在编译器编译阶段,对于模板函数的使用,编译器需要根据传入的实参类型来推演生成对应类型的函数以供调用。

        比如:当用double类型使用函数模板时,编译器通过对实参类型的推演,将T确定为double类型,然后产生一份专门处理double类型的代码,这个类型无论是内置类型还是自定义类型都可以

        2.3 函数模板实例化

        用不同类型的参数使用函数模板时,称为函数模板的实例化

        2.3.1 隐式实例化

        让编译器根据实参推演模板参数的实际类型叫作隐式实例化

        template
        T Add(const T& left, const T& right)
        {
        	return left + right;
        }
        int main()
        {
        	int a1 = 10, a2 = 20;
        	double d1 = 10.0, d2 = 20.0;
        	Add(a1, a2);
        	Add(d1, d2);
        	return 0;
        }
        

        正常情况下的调用就是隐式实例化

        🔥值得注意的是: Add函数前加const是因为这里如果像下面例子一样进行强制转化会生成临时变量,具有常性

        该知识点在前面有提到过:

        传送门:C++命运石之门代码抉择:C++入门(中)

        2.3.2 显式实例化

        在函数名后的中指定模板参数的实际类型叫作显式实例化

        Add(a1, d1);
        

        还是上面的例子,如果既调用int,又调用double,到底是用哪种类型编译器无法决定,就需要显式实例化

        🚩用户自己来强制转化

        Add(a1, (int)d1);
        

        🚩使用显式实例化

        Add(a1, d1);
        

        指定T的类型为int

        这通常不是显式实例化的常用场景,举个例子:

        template
        T* Alloc(int n)
        {
        	return new T[n];
        }
        int main()
        {
        	Alloc(5);
        	return 0;
        }
        

        如果写成Alloc(5),编译器不知道你要分配的是int数组、double数组还是其他类型的数组,所以无法自动推导T的类型,这时候就需要显式指定模板参数,像Alloc(5) 这样明确告诉编译器T是int类型

        2.4 模板参数的匹配原则

        🚩一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数

        // 专门处理int的加法函数
        int Add(int left, int right)
        {
        	return left + right;
        }
        // 通用加法函数
        template
        T Add(T left, T right)
        {
        	return left + right;
        }
        void Test()
        {
        	Add(1, 2); // 与非模板函数匹配,编译器不需要特化
        	Add(1, 2); // 调用编译器特化的Add版本
        }
        

        🚩对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调用非模板函数而不会从该模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数, 那么将选择模板

        // 专门处理int的加法函数
        int Add(int left, int right)
        {
        	return left + right;
        }
        // 通用加法函数
        template
        T1 Add(T1 left, T2 right)
        {
        	return left + right;
        }
        void Test()
        {
        	Add(1, 2); // 与非函数模板类型完全匹配,不需要函数模板实例化
        	Add(1, 2.0); // 模板函数可以生成更加匹配的版本,编译器根据实参生成更加匹配的Add函
        	数
        }
        

        🚩模板函数不允许自动类型转换,但普通函数可以进行自动类型转换

        这里的自动转化就是上面的实例化中的转化,也要和auto自动推导区分开,不是同一个东西

        3.类模板

        类模板其实和函数模板是类似的

        其语法形式为:

        template
        

        因为类不像函数那样语法上支持自动类型转化,所以类模板调用必须显式实例化

        // 动态顺序表
        // 注意:Vector不是具体的类,是编译器根据被实例化的类型生成具体类的模具
        template
        class Vector
        {
        public:
        	Vector(size_t capacity = 10)
        		: _pData(new T[capacity])
        		, _size(0)
        		, _capacity(capacity)
        	{}
        	// 使用析构函数演示:在类中声明,在类外定义。
        	~Vector();
        	void PushBack(const T& data);
        		void PopBack();
        		// ...
        		size_t Size() { return _size; }
        	T& operator[](size_t pos)
        	{
        		assert(pos  
        

        我们在写模板类时尽量不要声明定义分离,原因有些复杂放在模板进阶的时候讲,如果一定分离的话要注意:

        1. 对于普通类,类名和类型一样
        2. 对于模板类,Vector类名,Vector才是类型

        希望读者们多多三连支持

        小编会继续更新

        你们的鼓励就是我前进的动力!

        C++底层学习预备:模板初阶

免责声明:我们致力于保护作者版权,注重分享,被刊用文章因无法核实真实出处,未能及时与作者取得联系,或有版权异议的,请联系管理员,我们会立即处理! 部分文章是来自自研大数据AI进行生成,内容摘自(百度百科,百度知道,头条百科,中国民法典,刑法,牛津词典,新华词典,汉语词典,国家院校,科普平台)等数据,内容仅供学习参考,不准确地方联系删除处理! 图片声明:本站部分配图来自人工智能系统AI生成,觅知网授权图片,PxHere摄影无版权图库和百度,360,搜狗等多加搜索引擎自动关键词搜索配图,如有侵权的图片,请第一时间联系我们。

相关阅读

目录[+]

取消
微信二维码
微信二维码
支付宝二维码