C++模板全方位深入解读

目录
  • 1.泛型编程
  • 2.函数模板
    • 概念
    • 函数模板的格式
    • 函数模板的原理
    • 函数模板的实例化
      • 隐式实例化
      • 显式实例化
    • 模板参数的匹配原则
  • 3.类模板
    • (1) 类模板的定义格式
    • (2) 类模板的实例化
  • 4.非类型模板参数
  • 5.模板特化
    • (1)函数模板的特化
    • (2)类模板的特化
      • 全特化
      • 偏特化
  • 6.模板的分离编译
    • 问题分析

1.泛型编程

如何实现一个通用的交换函数?

这点函数重载可以做到,比如一下Swap函数的重载,分别重载了俩种不同参数类型的Swap

void Swap(int& x, int& y)
{
	int tmp = x;
	x = y;
	y = tmp;
}
void Swap(char& x, char& y)
{
	char tmp = x;
	x = y;
	y = tmp;
}

但是这也带来了几点不好的地方:

1.重载的函数仅仅是类型不同,代码的复用率比较低,只要有新类型出现,就需要增加对应的函数

2.代码的可维护性比较低,一个出错可能所有的重载均出错

那么有什么好的解决方法吗?我们能否告诉编译器一个模子,让编译器根据不同类型利用该模子自己去生成相应的代码呢?

当然能,这就是泛型编程,即编写与类型无关的通用代码,这是代码复用的一种手段。而模板是泛型编程的基础。模板分为两种,函数模板和类模板。

2.函数模板

概念

函数模板代表了一个函数家族,该函数模板与类型无关,在使用时被参数化,根据实参类型产生函数的特定类型版本。

函数模板的格式

template<typename T1, typename T2,......,typename Tn>

即:返回值类型 函数名(参数列表){}

其中typename可以改成class(不能用struct)

例:

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

函数模板的原理

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

在编译器编译阶段,对于模板函数的使用,编译器需要根据传入的实参类型来推演生成对应类型的函数以供调用。比如:当用double类型使用函数模板时,编译器通过对实参类型的推演,将T确定为double类型,然后产生一份专门处理double类型的代码,对于字符类型也是如此。

函数模板的实例化

用不同类型的参数使用函数模板时,称为函数模板的实例化。模板参数的实例化分为两种:隐式实例化和显式实例化。

隐式实例化

让编译器自己推演函数参数的类型。

需要注意的是隐式实例化的参数一定要匹配,否则可能产生分歧导致编译器无法识别。比如:

template<typename T>
T Add(const T& left, const T& right)
{
	return left + right;
}
int main()
{
	int a1 = 10, b1 = 20;
	double a2 = 10.0, b2 = 20.0;
	Add(a1, b2);
	return 0;
}

编译器报错,该语句不能通过编译,因为无法确定T是int还是double。

如何处理?有两种方式:

1.强制类型转换!但值得注意的是,强转会产生临时变量,临时变量是具有常性的,需要const修饰一下!

2.使用显式实例化

显式实例化

在函数名后的< >中指定模板参数的实际类型。

template<typename T>
T Add(const T& left, const T& right)
{
	return left + right;
}
int main()
{
	int a1 = 10, b1 = 20;
	double a2 = 10.0, b2 = 20.0;
	cout<<Add<int>(a1, b2)<<endl;
	return 0;
}

模板参数的匹配原则

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

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

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

  • 如果有定义出来的函数,且类型完全匹配调用时实参类型,则执行定义出来的函数.如果定义出来的函数,不符合,则执行模板推演.
  • 这部分没啥难度,不再举例说明,总的来说,对于函数调用的优先级就是:完全匹配 >模板匹配 >转换匹配。

3.类模板

(1) 类模板的定义格式

template<class T1, class T2, ..., class Tn>
class 类模板名
{
 // 类内成员定义
}; 

注意:

类模板中函数放在类外进行定义时,需要加模板参数列表;

template <typename T>
class Stack
{
public:
	Stack(int capacity = 4)
		:_a(new T[capacity])
		,_top(0)
		,_capacity(capacity)
	{}
	~Stack()
	{
		delete[] _a;
		_top = _capacity = 0;
	}
	void Push(T x);
private:
	T* _a;
	int _top;
	int _capacity;
};
// 注意:类模板中函数放在类外进行定义时,需要加模板参数列表
template <typename T>
void Stack<T>::Push(T x)
{
	if (_top == _capacity)
	{
		_capacity *= 2;
		T* tmp = (T*)realloc(_a, sizeof(int) * _capacity);
		if (tmp == nullptr)
		{
			cout << "realloc fail" << endl;
			exit(-1);
		}
		_a = tmp;
	}
	_a[_top++] = x;
}

对于普通类,类名就是类型;对于类模板,类名不是类型,类型是Class < T >

(2) 类模板的实例化

类模板实例化与函数模板实例化不同,类模板实例化需要在类模板名字后跟 < >,然后将实例化的类型放在<>中即可,类模板名字不是真正的类,而实例化的结果才是真正的类。

int main()
{
	// Stack只是类名,不是类型,Stack<int>才是类型
	Stack<int> s1;
	Stack<char> s2;
	return 0;
}

4.非类型模板参数

类型参数:就是在模板的参数列表中在class后面加上参数的类型名称。

非类型参数:就是用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常量来使用。

注意两点:

1.浮点数、类对象以及字符串是不允许作为非类型模板参数的。

2.非类型的模板参数必须在编译期就能确认结果。

template<class T =int, size_t N = 10>
class array
	{
		 private:
	 	 T _array[N];
		 size_t _size;
	 }

5.模板特化

(1)函数模板的特化

当针对某一情景或者某一类型,函数模板无法满足要求,模板需要有特殊的处理,这个时候就需要用到模板的特化。

比如咱们要比较两个字符串是否相同:

template<class T>
bool IsEqual(T str1, T str2)
{
	return str1 == str2;
}
int main()
{
	char str1[] = "hello";
	char str2[] = "hello";
	if (IsEqual(str1, str2))
		cout << "true";
	else
		cout << "false";
}

上述代码输出false, 不满足咱们的要求, 因为调用函数IsEqual()时传递过去的是两个char*类型,他们两个比较的不是字符串的内容,而是指针的地址,所以返回false。

此时模板特化派上用场了:如果要比较char*, 可以用strcmp来对这个情况进行特殊处理

template<>
bool IsEqual<char*>(char* str1, char* str2)
{
	return strcmp(str1, str2) == 0;
}

此时就返回true, 符合预期了。

函数模板的特化步骤:

  • 必须要先有一个基础的函数模板
  • 关键字template后面接一对空的尖括号<>
  • 函数名后跟一对尖括号,尖括号中指定需要特化的类型
  • 函数形参表: 必须要和模板函数的基础参数类型完全相同

(2)类模板的特化

类也是同理,如果需要有特殊情景也需要特化处理

以如下类举例(后边全特化、偏特化都针对它):

template<class T1, class T2>
class test
{
public:
	test()
	{
		cout << "test<T1, T2>" << endl;
	}
private:
	T1 _x;
	T2 _y;
};

全特化

全特化即是将模板参数列表中所有的参数都确定化。

例如:

这里对test<int,double>版本特化

template<>
class test<int, double>
{
public:
	test()
	{
		cout << "test<int, double>" << endl;
	}
private:
	int _x;
	double _y;
};
int main()
{
	test<double, double> t1;
	test<int, double> t2;
}

偏特化

偏特化即是任何针对模版参数进一步进行条件限制设计的特化版本

偏特化有两种表现方式,一种是部分参数特化,一种是参数修饰特化

部分参数特化

这里对第二个参数特化,只要第二个参数是double就会调用对应特化版本

template<class T1>
class test<T1, double>
{
public:
	test()
	{
		cout << "test<T1, double>" << endl;
	}
private:
	T1 _x;
	double _y;
};
int main()
{
	test<double, double> t1;
	test<int, double> t2;
}

参数修饰特化

比如用指针或者引用来修饰类型,也可以进行特化

template<class T1, class T2>
class test<T1*, T2*>
{
public:
	test()
	{
		cout << "test<T1*, T2*>" << endl;
	}
private:
	T1* _x;
	T2* _y;
};
int main()
{
	test<int*, double*> t;
}

6.模板的分离编译

对于一个代码量比较多的项目,通常都会采用声明与定义分离的方法,比如在头文件进行声明,在源文件完成代码的实现,最后通过链接的方法链接成单一的可执行文件。但是C++的编译器却不支持模板的分离编译,一旦进行分离编译,就会出现链接错误。

问题分析

//头文件a.h
template<class T>
bool IsEqual(const T& str1, const T& str2);
-------------
//源文件a.cpp
template<class T>
bool IsEqual(const T& str1, const T& str2)
{
	return str1 == str2;
}
--------------
//test.c
#include<iostream>
#include"a.h"
using namespace std;
int main()
{
	cout << IsEqual(3, 5);
	cout << IsEqual('a', 'b');
}

这里看上去是没有问题的,但是涉及到了模板的实例化规则。

当主函数调用这个函数的时候他就会去头文件中找到函数的声明,再通过声明找到a.h中的实现。

但是对于模板却并不会这样,因为上一章说过,模板的实例化只会在其第一次使用的时候才会进行,例如这里IsEqual(3, 5),他就会去头文件中寻找,但是头文件中只有声明,没有定义,无法将其实例化。他又想通过找到a.cpp中的函数定义来进行实例化,但是遗憾的是,a.cpp中只有IsEqual(const T& str1, const T& str2)的定义,没有IsEqual(const int & str1, const int T& str2),因为在a.cpp中并没有使用到该类型的实例,所以自然也不会为其实例化出来,这时test.cpp中就根本无法找到这个函数的实现,就导致了链接失败。

解决方法:

这个问题其实没有什么完美的解决方法

  • 将声明和定义放到同一个头文件中。
  • 类模板显式实例化。

到此这篇关于C++模板全方位深入解读的文章就介绍到这了,更多相关C++模板内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

时间: 2022-06-06

c++模板自定义数组

目录 1.自定义数组.hpp--文件 2.测试文件 前言: 制造通用模板,创建自定义的数组, 一个数组,里面有这么几个属性,数组容量,数组元素个数,数组本身内存地址,这几个数据都是定义私有类型,提供有参构造,让用户可以构造出这个数组对象.下面是有参构造和拷贝构造和析构函数还有operator=重载的代码 在前面类模板中成员函数创建有这个主意问题,最好的办法就是把类模板写在一个hpp的文件中,不要拆开写成多个文件 1.自定义数组.hpp--文件 #pragma once #include<iost

C++超详细讲解模板的使用

目录 一.函数模板 1.1函数模板概念 1.2 函数模板格式 1.3 函数模板的原理 1.4 函数模板的实例化 二.类模板 2.1 类模板的定义格式 2.2类模板的实例化 总结 一.函数模板 1.1函数模板概念 函数模板代表了一个函数家族,该函数模板与类型无关,在使用时被参数化,根据实参类型产生函数的特定类型版本. 1.2 函数模板格式 template<typename T1, typename T2,…,typename Tn> 返回值类型 函数名(参数列表){} template<

c++分离讲解模板的概念与使用

目录 泛类编程 函数模板 函数模板的概念 函数模板的使用 函数模板的实例化 函数模板的匹配原则 类模板 类模板的定义格式 类模板的实例化 泛类编程 学习模板,首先我们需要了解一下什么是泛类编程 #include<iostream> using namespace std; int add(int a, int b) { return a + b; } double add(double a, double b) //这两个add构成了函数重载 { return a + b; } int mai

C++模板Template详解及其作用介绍

目录 1. 模板 2. 函数模板 2.1 函数模板概念 2.2 函数模板格式 2.3 函数模板原理 2.4 函数模板的实例化 2.5 模板参数的匹配原则 2.6声明定义分离 3. 类模板 3.1 类模板格式 3.2 类模板的实例化 3.3 类模板中函数放在类外进行定义时 4. 模板分离编译 4.1 什么是分离编译 4.2 模板的分离编译 5. 缺省值与返回值 6. 总结 1. 模板 首先模板分为函数模板和类模板 想到模板,就会联想到泛型编程 泛型编程:编写与类型无关的通用代码,是代码复用的一种手

C++11中模板隐式实例化与显式实例化的定义详解分析

目录 1. 隐式实例化 2. 显式实例化声明与定义 3. 显式实例化的用途 1. 隐式实例化 在代码中实际使用模板类构造对象或者调用模板函数时,编译器会根据调用者传给模板的实参进行模板类型推导然后对模板进行实例化,此过程中的实例化即是隐式实例化. template<typename T> T add(T t1, T2) { return t1 + t2; } template<typename T> class Dylan { public: T m_data; }; int ma

C++可变参数模板的展开方式

文章目录 前言可变参数模板的定义参数包的展开递归函数方式展开逗号表达式展开enable_if方式展开折叠表达式展开(c++17) 总结 前言 可变参数模板(variadic templates)是C++11新增的强大的特性之一,它对模板参数进行了高度泛化,能表示0到任意个数.任意类型的参数.相比C++98/03这些类模版和函数模版中只能含固定数量模版参数的“老古董”,可变模版参数无疑是一个巨大的进步. 如果是刚接触可变参数模板可能会觉得比较抽象,使用起来会不太顺手,使用可变参数模板时通常离不开模

C++将模板实现放入头文件原理解析

目录 写在前面 例子 原因 分析 解决方案 方案一 方案二 参考 写在后面 写在前面 本文通过实例分析与讲解,解释了为什么C++一般将模板实现放在头文件中.这主要与C/C++的编译机制以及C++模板的实现原理相关,详情见正文.同时,本文给出了不将模板实现放在头文件中的解决方案. 例子 现有如下3个文件: // add.h template <typename T> T Add(const T &a, const T &b); // add.cpp #include "

C++&nbsp;深入浅出探索模板

目录 非类型模板参数 模板特化 函数模板特化 类模板特化 全特化 偏特化 模板分离编译 模板的分离编译 解决方法 总结 非类型模板参数 模板参数分类类型形参与非类型形参. 类型形参:出现在模板参数列表中,跟在class或者typename之类的参数类型名称. 非类型形参,就是用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常量来使用. 注意: 浮点数,类对象以及字符串是不允许作为非类型模板的. 非类型的模板参数必须在编译期就能确认结果. 模板特化 有时候,编译默认函数模板

如何C++使用模板特化功能

目录 前言: 1.函数模板的特化 2.类模板的特化 前言: 通过定义模板,使得函数或者类不依赖于特定的类型,这样大幅提升了代码的复用性. 然而,不管是类模板还是函数模板,对所有的类型都是采用相同的处理方式(同一份代码).为此,C++提出了“模板特化的概念”,对特定类型提供模板的改造. 比如,对于函数模板来说,可以通过特化指定特定的类型,针对此特定类型对函数体内容进行重写,使得函数对特定类型实现特定功能. 注意: 按照语法,对函数模板和类模板进行特化后,就变为普通函数和普通的类,而不再是模板. 1

深入分析C++模板特化与偏特化

1.模板特化 1.1概述 模板特化(template specialization)不同于模板的实例化,模板参数在某种特定类型下的具体实现称为模板的特化.模板特化有时也称之为模板的具体化,分别有函数模板特化和类模板特化. 1.2函数模板特化 函数模板特化是在一个统一的函数模板不能在所有类型实例下正常工作时,需要定义类型参数在实例化为特定类型时函数模板的特定实现版本.查看如下例子. #include <iostream> using namespace std; template<type

C#开发之微信小程序发送模板消息功能

步骤一:获取模板ID 有两个方法可以获取模版ID 通过模版消息管理接口获取模版ID 在微信公众平台手动配置获取模版ID 步骤二:页面的 <form/> 组件,属性report-submit为true时,可以声明为需发模板消息,此时点击按钮提交表单可以获取formId,用于发送模板消息.或者当用户完成支付行为,可以获取prepay_id用于发送模板消息. 步骤三:调用接口下发模板消息 今天重要的说第三步怎么实现,前面的步骤比较简单就略过. ----------------------------

laravel 5 实现模板主题功能

众所周知,laravel渲染模板是通过View::make()实现的,需要显式指定模板文件路径: 复制代码 代码如下: function index() {     return View::make('index.index'); } 既然这样,我们就可以自己实现模板主题功能,我们只需要将模板文件放到一个主题名称对应的目录里就行,比如默认主题为 default 的话,我们就这样写: 复制代码 代码如下: function index() {     return View::make('def

PHP实现简单的模板引擎功能示例

本文实例讲述了PHP实现简单的模板引擎功能.分享给大家供大家参考,具体如下: php web开发中广泛采取mvc的设计模式,controller传递给view层的数据,必须通过模板引擎才能解析出来.实现一个简单的仅仅包含if,foreach标签,解析$foo变量的模板引擎. 编写template模板类和compiler编译类.代码如下: <?php namespace foo\base; use foo\base\Object; use foo\base\Compiler; /** * */ c

laravel 5 实现模板主题功能(续)

在之前一篇文章中我介绍了通过定义Response宏的方式来实现动态改变模板文件路径以实现主题功能: laravel实现模板主题功能,但后来我发现这种方法有个弊端,在模板中使用@extends必须显式指定模板路径,这可能造成混乱,我决定还是改变思想,主题和主题之间应该是完全隔离的,不存在就是不存在,不要自动去另外的主题中寻找替代的模板. 而原来定义response宏的方式可以实现,但我决定使用更加规范的方法. laravel的View类里有一个方法 View::addNamespace ,这个方法

Python实现的简单模板引擎功能示例

本文实例讲述了Python实现的简单模板引擎功能.分享给大家供大家参考,具体如下: #coding:utf- 8 __author__="sdm" __author_email='sdmzhu3@gmail.com' __date__ ="$2009-8-25 21:04:13$" '' ' pytpl 类似 php的模板类 '' ' import sys import StringIO import os.path import os #模 板的缓存 _tpl_c

Python编程之微信推送模板消息功能示例

本文实例讲述了Python微信推送模板消息功能.分享给大家供大家参考,具体如下: 官方文档:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1445241432 具体代码如下: #!/usr/bin/env python #-*- coding: utf-8 -*- import httplib import json import MySQLdb #从数据库中获取access_token access_token="&quo

C#微信接口之推送模板消息功能示例

本文实例讲述了C#微信接口之推送模板消息功能.分享给大家供大家参考,具体如下: public string SendTempletMessge() { string strReturn = string.Empty; try { #region 获取access_token string apiurl = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=你的appid&secr

php实现QQ小程序发送模板消息功能

QQ小程序群里有伙伴要发送模板消息的代码,所以今天给大家分享QQ小程序模板消息发布,绝对一步一步带着大家走,每个细节都讲到. 今天先用php简单写一下,有空了再写java的. 首先创建一个空项目: 因为QQ小程序没有编译器,先用微信小程序创建. 然后新建一个页面,直接上html代码: <form bindsubmit="form_submit" report-submit="true"> <button formType="submit&

微信小程序实现发送模板消息功能示例【通过openid推送消息给用户】

本文实例讲述了微信小程序实现发送模板消息功能.分享给大家供大家参考,具体如下: 一.获取access_token access_token是接口调用的凭证,目前有效期为两个小时,需要定时刷新,重复获取将导致上次获取的access_token失效.(注:不建议每次调用需要access_token的接口,都去重新获取access_token,会导致失败) 获取access_token的接口地址: https://api.weixin.qq.com/cgi-bin/token?grant_type=c