【C++】模板及模板的特化

news2025/7/19 4:45:26

               

目录

一,模板

  1,函数模板

什么是函数模板

函数模板原理

函数模板的实例化

 推演(隐式)实例化

 显示实例化 

模板的参数的匹配原则

2,类模板

什么是类模板

类模板的实例化

二,模板的特化

1,类模板的特化

全特化

 偏特化

2,函数模板的特化——全特化

三,非类型模板参数


一,模板

  1,函数模板

        什么是函数模板

所谓函数模板,实际上是建立一个通用的函数,该函数类型和形参类型不具体指定,而是用一个表示任意类型的虚拟类型来代表(这里的任意类型可以任意选择,如 T)。

定义函数模板的一般形式:

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

返回值类型 函数名(参数列表) 

{

        // .....

}

       其中templateclass是关键字,typename 可以用class关键字代替,在这里typename 和class没区别,<>括号中的参数叫模板形参,模板形参和函数形参很相像,模板形参不能为空。一但声明了模板函数就可以用模板函数的形参名声明类中的成员变量和成员函数,即可以在该函数中使用内置类型的地方都可以使用模板形参名。模板形参需要调用该模板函数时提供的模板实参来初始化模板形参,一旦编译器确定了实际的模板实参类型就称他实例化了函数模板的一个实例。比如swap的模板函数形式为

下面来看一下,完成多个不同类型的两个数据的交换

针对具体类型(常规函数)

// 完成两个整形变量的交换
void Swap(int& left, int& right)
{
	int temp = left;
	left = right;
	right = temp;
}

// 完成两个double型变量的交换
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;
}


int main()
{
	int a = 1, b = 2;
	double c = 1.1, d = 2.2;
	char ch1 = 'a', ch2 = 'b';

	Swap(a, b);
	Swap(c, d);
	Swap(ch1, ch2);
    
    return 0;
}

通过上面可以看出,若想要完成以上三种不同类型的交换须要写三个交换函数,这是针对每种类型分别写出具体的交换函数;但是以上的三种交换函数除了参数的类型不一样,其他都是一样的,显得代码既冗余又不够简练。来看下面使用函数模板

跟具体类型无关(函数模板)

//函数模板
//template<class T>
template<typename T>           // 声明一个类型模板参数 T>
void Swap(T& left, T& right)   // 使用模板参数T来声明函数参数left和right
{
	T temp = left;
	left = right;
	right = temp;
}

int main()
{
	int a = 1, b = 2;
	double c = 1.1, d = 2.2;
	char ch1 = 'a', ch2 = 'b';

	Swap(a, b);
	Swap(c, d);
	Swap(ch1, ch2);
    
    return 0;
}

     在这个例子中,T 是一个类型模板参数,它告诉编译器我们希望这个函数能够处理多种类型。在函数模板的声明中,使用 typename 关键字(也可以使用 class 关键字,两者在函数模板中都是等价的)来声明类型模板参数。

    然后,使用类型模板参数 T 来声明函数的参数 left和 right,它们都是类型为 T 的引用。这意味着可以传递任何类型的变量给 swap 函数,只要这两个变量的类型相同。

函数模板原理

        当编译器遇到一个函数调用时,编译器会尝试根据传递给函数模板的实参类型来推导出模板参数的类型,一旦类型推导成功,编译器就会生成一个或多个具体的函数实例,这些实例的类型与推导出的模板参数类型相匹配。

注意:函数模板本身并不产生代码,它只是一个蓝图。只有在模板被实例化时,编译器才会生成具体的函数代码。

当实参a、b 是 int 时,编译器会把模板参数 T 推演成 int 类型,会实例化出一份具体类型的Swap 函数来调用;当实参a、b 是 double 时, 编译器会把模板参数 T 推演成 int 类型; char类型也一样。

注意:以上三个函数在实例化时虽然走的都是同一个函数模板,但是调用的不是同一个函数;只是用同一个函数模板实例化出了三份针对具体类型的函数

如下:

函数模板的实例化

         推演(隐式)实例化

隐式实例化:编译器在调用一个模板函数时,根据提供的参数类型自动推断出模板参数的类型,并生成相应的函数实例。这个过程是编译器自动完成的,不需要程序员显式指定模板参数的类型。

// 声明一个函数模板  
template <typename T>
T Add(T a, T b) 
{
    return a + b;
}

int main()
{
    int a1 = 3, a2 = 5;
    double d1 = 3.5, d2 = 6.5;
    // 隐式实例化:编译器根据参数类型(int)推断出模板参数T为int
    Add(a1, a2);    // 生成了 add<int>(int, int) 的实例 
    // 编译器根据参数类型(double)推断出模板参数T为double
    Add(d1, d2);    // 生成了 add<double>(double, double) 的实例 


    Add(a1, d1);   // a1和d1是不同的类型,此时编译报错,编译器无法推演出具体的类型

    return 0;
}

上述第三个 Add(a1, d1); 该语句不能通过编译,因为在编译期间,当编译器看到该实例化时,需要推演其实参类型 通过实参a1将T推演为int通过实参d1将T推演为double类型,但模板参数列表中只有一个T, 编译器无法确定此处到底该将T确定为int 或者 double类型而报错

解决方法:1. 可以改成多参数的函数模板   如:template<typename T1, typename T2>

                  2. 手动强制类型转换    如:上面的  Add(a1, (int)d1); 或  Add((double)a1, d1);

                  3. 就是下面要说的 显式实例化   

 显示实例化 

    函数模板允许编写通用的函数,这些函数可以处理不同类型的数据。但是,在某些情况下,可能想要为特定的类型显式地实例化函数模板,以便在编译时生成具体的函数版本。

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

// 声明一个函数模板  
template <typename T>
T Add(T a, T b)
{
    return a + b;
}

int main()
{
    int a1 = 3, a2 = 5;
    double d1 = 3.5, d2 = 6.5;

    // Add(a1, d1);   // a1和d1是不同的类型,此时编译报错,编译器无法推演出具体的类型
    Add<int>(a1, d1);   // 显式实例化 可强制为 int 类型实例化,并将参数 d1 强制转换为 int 类型
    // 或
    Add<double>(a1, d1); // 显式实例化 可强制为 double 类型实例化,并将参数 a1 强制转换为 double 类型

    return 0;
}

上面的模板与函数调用 Add(a1, d1) 不匹配,因为该模板要求两个函数参数的类型相同。但通过使用 Add<int>(a1, d1),  可强制为 int 类型实例化,并将参数 d1 强制转换为 int 类型,这样就可以与函数 Add<int>(int, int) 的第二个参数匹配。

模板的参数的匹配原则

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

2. 优先调用非模板函数非模板函数和同名函数模板在参数上相匹配时,编译器会优先调用非模板函数,而不是从模板产生出一个实例(即有现成的就吃现成的

3. 模板的更好匹配如果模板可以产生一个具有更好匹配的函数,那么编译器会选择模板而不是非模板函数(即有更合适的就吃更合适的,没有就将就吃)。

// 函数模板,可以处理任何类型的加法 
template <typename T>
T Add(T a, T b)
{
    return a + b;
}

// 非模板函数,专门处理int类型的加法  
int Add(int a, int b) 
{
    return a + b;
}

int main()
{
    int a1 = 3, a2 = 5;
    double d1 = 3.5, d2 = 6.5;

    // 调用非模板函数,因为参数是int类型,与非模板函数匹配 
    Add<int>(a1, a2); 

    Add<double>(d1, d2);  // 调用模板函数,因为参数是double类型,与非模板函数不匹配
    Add<double>(a1, d1);  // 调用模板函数,模板函数会生成更匹配的,因为参数是double类型,与非模板函数不匹配 (a1会被强转成 double)

    return 0;
}

2,类模板

什么是类模板

        类模板是对一批成员数据类型不同的类的抽象。只需为这一批类所组成的整个类家族创建一个类模板,并给出一套程序代码,就可以用来生成多种具体的类(这类可以看作是类模板的实例),从而大大提高编程的效率。

类模板的基本结构如下:

//template <typename 参数名, typename ...> // 可以有多个类型参数,使用逗号分隔

template < class T> // 或使用typename代替class  
class 类名

{  
    // 类的定义,可以使用类型T作为成员变量、函数参数或返回类型  
};

其中,template 关键字用于声明一个模板,<class 参数名, ...> 部分定义了模板参数,这些参数在模板内部可以用作类型。class关键字是用来指示随后的标识符是一个类型名称的,但也可以使用 typename 关键字代替 class

栈模板类可以定义如下:

// 类模板
template<class T>
class Stack
{
public:
	Stack(int capacity = 4)
	{
		cout << "Stack(int capacity = 4)" << endl;

		_a = new T[capacity];
		_top = 0;
		_capacity = capacity;
	}

	~Stack()
	{
		cout << "~Stack()" << endl;

		delete[] _a;
		_a = nullptr;
		_top = 0;
		_capacity = 0;
	}
private:
	T* _a;
	int  _top;
	int  _capacity;
};


在上面的例子中,定义了一个名为 Stack的类模板,它接受一个类型参数 T。这个 T 类型被用作栈中元素的类型。

类模板的实例化

int main()
{
	// 显示实例化
	Stack<int> st1;    // 实例化一份存储int 类型的栈
	Stack<double> st2;  // 实例化一份存储double 类型的栈

	return 0;
}

      在 main 函数中,分别实例化了两个 Stack 对象:一个用于存储整数(Stack<int>),另一个用于存储浮点数(Stack<double>)。这两个对象都使用了相同的 Stack 类模板,但是它们内部处理的数据类型是不同的。这就是类模板的强大之处:通过编写一次代码,就可-以创建出多种类型安全的栈类。

注意:1. 对于类模板,模板形参的类型必须在类名后的尖括号中明确指定。比如A<2> m;这种方法把模板形参设置为int是错误的,类模板形参不存在实参推演的问题。也就是说不能把整型值2推演为int 型传递给模板形参。要把类模板形参调置为int 型必须这样指定A<int> m

            2. 模板的声明或定义只能在全局,命名空间或类范围内进行。即不能在局部范围,函数内进行,比如不能在main函数中声明或定义一个模板

二,模板的特化

1,类模板的特化

类模板特化是针对特定类型的模板参数提供定制的类模板实现。它允许在某些情况下,使用与通用模板不同的实现方式。类模板特化分为全特化和偏特化(局部特化)两种

全特化

对模板参数列表中的所有模板类型都进行具体化。例如,如果有一个模板类Test<T1, T2>,我们可以为T1T2都是int的情况提供一个全特化的版本

template<class T1, class T2>
class Data
{
public:
	Data() { cout << "Data<T1, T2>" << endl; }
};

// 全特化为 int和char
template<>
class Data<int, char>
{
public:
	Data() { cout << "Data<int, char>" << endl; }
private:
	int _d1;
	char _d2;
};

// 全特化为 int int
template <>
class Data<int, int>
{
public:
	Data() { cout << "Data<int, int>" << endl; }
private:
};

int main()
{
	Data<int, int> d1;
	Data<int, char> d2;

	return 0;
}

  写一个类的全特化,就相当于写一个新的类一样,你可以自己定义任何东西,不管是函数、数据成员、静态数据成员等等;根据自己的需求

 偏特化

偏特化就是如果这个模板有多个类型,那么只限定其中的一部分,即只对模板的部分类型进行明确指定

特化部分参数:将模板参数类表中的一部分参数特化

一个 Data的类模板,它接受两个类型参数 T1T2T2为 int 的情况提供了偏特化

//原模版
template<class T1, class T2>
class Data
{
public:
	Data() { cout << "Data<T1, T2>" << endl; }
};

// 特化部分参数
template <class T1>
class Data<T1, int>
{
public:
	Data() { cout << "Data<T1, int>" << endl; }
};

int main()
{
	Data<char, int> d1;   // 走特化版本 Data<T1, int>
	Data<int, int> d2;

    Data<int, char> d3;   // 走原模板,第二个参数为char与第二个模板参数int不匹配

	return 0;
}

T2int时,编译器将使用偏特化的Data类模板,而不是原始的模板。如果T2不是int,则编译器将使用原始的模板

对参数类型进行一定限制。比如:限制是指针或者引用等

//对参数类型限制为指针
template <class T1, class T2>
class Data<T1*, T2*>
{
public:
	Data() { cout << "Data<T1*, T2*>" << endl; }
};

//对参数类型限制为引用
template <class T1, class T2>
class Data<T1&, T2&>
{
public:
	Data() { cout << "Data<T1&, T2&>" << endl; }
};

//对参数类型T1限制为指针,T2限制为引用
template <class T1, class T2>
class Data<T1*, T2&>
{
public:
	Data() { cout << "Data<T1*, T2&>" << endl; }
};

int main()
{
	Data<char*, int*> d4;   // 走特化版本 Data<T1*, T2*>
	Data<int*, int*> d5;
	                        
	Data<int&, int*> d6;    // 走原模板 
    Data<int&, int&> d6;    // 走特化版本 Data<T1&, T2&>
	Data<int*, int&> d7;    // 走特化版本 Data<T1*, T2&>

	return 0;
}

注意:偏特化有一些限制。不能为一个非类型模板参数提供偏特化,也不能为一个函数模板提供偏特化同时,偏特化的结果仍然是一个模板,而不是一个具体的类

2,函数模板的特化——全特化

//函数模板
template<typename T1, typename T2>
void fun(T1 a, T2 b) 
{
	cout << "函数模板" << endl;
}

//全特化
template<>
void fun<int, char >(int a, char b) 
{
	cout << "全特化" << endl;
}

//函数不存在偏特化:下面的代码是错误的
/*
template<typename T2>
void fun<char, T2>(char a, T2 b) 
{
	cout << "偏特化" << endl;
}
*/

注意:对于函数模板,只有全特化,不能偏特化

三,非类型模板参数

模板参数分为类型形参非类型形参

类型形参即:出现在模板参数列表中,跟在class或者typename之类的参数类型名称(上面已经介绍过)

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

// 定义一个模板类型的静态数组
template<class T, size_t N>  
class Array 
{  
public:    
  
    // ... 其他成员函数 ...
  
private:
    T data[N];
};  

  
int main() 
{  
    Array<int, 5> arr; // 创建一个大小为5的整型数组  
    // ...  
}

注意:

1. 非类型模板参数必须是常量表达式,也就是说它们必须在编译时就能确定其值

2. 非类型模板参数的类型通常是整数类型(如 intsize_t 等)、指针(如 指向函数的指针)或引用,但不能是类类型或其他复杂类型。

3. 非类型模板参数在模板实例化时会被替换为具体的值,因此它们会影响生成的代码的类型和布局


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1822001.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于uni-app与图鸟UI打造的各领域移动端模板大赏

随着移动互联网的迅猛发展&#xff0c;各类移动端应用层出不穷&#xff0c;为了帮助企业快速搭建高效、美观的移动平台&#xff0c;我们基于强大的uni-app与图鸟UI&#xff0c;精心打造了不下于40套覆盖多个领域的移动端模板。今天&#xff0c;就让我们一起领略这些模板的风采吧…

django-vue-admin 本地部署

一、项目地址 主分支&#xff1a;master&#xff08;稳定版本&#xff09; 开发分支&#xff1a;develop django-vue3-admin-masterhttps://gitee.com/huge-dream/django-vue3-admin 注意&#xff1a;下载master分支zip代码包&#xff0c;解压后删掉web\src\views\syst…

185.二叉树:二叉搜索树的最近公共祖先(力扣)

代码解决 /*** Definition for a binary tree node.* struct TreeNode {* int val;* TreeNode *left;* TreeNode *right;* TreeNode(int x) : val(x), left(NULL), right(NULL) {}* };*/class Solution { public:// 函数用于寻找二叉搜索树中节点 p 和 q 的最低…

小程序外卖开发中的关键技术与实现方法

小程序外卖服务凭借其便捷性和灵活性&#xff0c;正成为现代餐饮行业的重要组成部分。开发一个功能完善的小程序外卖系统&#xff0c;需要掌握一系列关键技术和实现方法。本文将介绍小程序外卖开发中的核心技术&#xff0c;并提供具体的代码示例&#xff0c;帮助开发者理解和实…

慎投!4区SCISSCI有停刊风险!网传的水刊之王解析大全,真的好投吗?

本周投稿推荐 SSCI • 中科院2区&#xff0c;6.0-7.0&#xff08;录用友好&#xff09; EI • 各领域沾边均可&#xff08;2天录用&#xff09; CNKI • 7天录用-检索&#xff08;急录友好&#xff09; SCI&EI • 4区生物医学类&#xff0c;0.5-1.0&#xff08;录用…

JDBC操作数据库的方法

目录 一、JDBC介绍 二、使用方法&#xff08;以MySQL为例&#xff09; &#xff08;1&#xff09;MySQL的jar包&#xff0c;导入到IDEA &#xff08;2&#xff09;使用代码&#xff0c;操作数据库 1&#xff09;设置数据源 1.创建MysqlDataSource对象&#xff0c;使用set…

【Unity每日一记】FairyGUI为什么能自动生成代码,它的好处是什么

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 秩沅 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a;uni…

GPT办公与科研应用、论文撰写、数据分析、机器学习、深度学习及AI绘图高级应用

原文链接&#xff1a;GPT办公与科研应用、论文撰写、数据分析、机器学习、深度学习及AI绘图高级应用https://mp.weixin.qq.com/s?__bizMzUzNTczMDMxMg&mid2247606667&idx3&sn2c5be84dfcd62d748f77b10a731d809d&chksmfa82606ccdf5e97ad1a2a86662c75794033d8e2e…

mmap引起的内存泄漏分析

最近遇到一个内存泄漏问题&#xff0c;由于问题出现在客户端&#xff0c;只能通过客户提供的Log来分析。 根据客户提供的/proc/meminfo数据发现&#xff0c;MemAvailable 由294072kB减小至18128kB&#xff0c;减小约269MB&#xff0c;引起该变化的最直接原因是PageTables由614…

Python云实例初始化和配置的工具库之cloud-init使用详解

概要 在云计算环境中,自动化配置和管理实例是非常重要的任务。cloud-init 是一个用于云实例初始化和配置的工具,广泛应用于各种云服务提供商(如 AWS、Azure、GCP 等)的实例启动过程。通过 cloud-init,用户可以在实例启动时自动执行脚本、安装软件包、配置网络等。本文将详…

20240613解决飞凌的OK3588-C的核心板的适配以太网RTL8211F-CG

20240613解决飞凌的OK3588-C的核心板的适配以太网RTL8211F-CG 2024/6/13 16:58 缘起&#xff1a;对于飞凌的OK3588-C的核心板&#xff0c;参照飞凌的底板/开发板。 ETH0空接&#xff0c;ETH1由RTL8211FSI-CG【20&#xffe5;】更换为RTL8211F-CG【4&#xffe5;】。 都是千兆网…

快速掌握 Python requests 库发送 JSON 数据的 POST 请求技巧

在现代 Web 开发中&#xff0c;客户端与服务器之间进行数据交换的需求越来越普遍。而在 Python 这个强大的编程语言中&#xff0c;requests 库是一个广泛使用且功能强大的 HTTP 请求库。特别是在进行 API 调用时&#xff0c;发送 POST 请求并附带 JSON 数据是一个非常常见的需求…

【React】useMemo

什么是 useMemo&#xff1f; useMemo 是 React 中的一个 Hook&#xff0c;它可以用来缓存计算结果&#xff0c;并在后续的渲染中重复利用这些计算结果。useMemo 接收两个参数&#xff1a;一个函数和一个依赖数组。当依赖数组中的任何一个值发生变化时&#xff0c;useMemo 会重新…

内网环境实现maven项目打包部署(包括踩坑!!)

内网环境实现maven项目打包部署&#xff08;包括踩坑&#xff01;&#xff01;&#xff09; 由于内网保密项目原因 拿我本地测试 过程&#xff1a; jdk1.8 java环境 maven setting配置加上 <!-- 仓库地址(本地上传到内网环境仓库) --> <localRepository>D:\ma…

flutter开发实战-创建一个微光加载效果

flutter开发实战-创建一个微光加载效果 当加载数据的时候&#xff0c;loading是必不可少的。从用户体验&#xff08;UX&#xff09;的角度来看&#xff0c;最重要的是向用户展示加载正在进行。向用户传达数据正在加载的一种流行方法是在与正在加载的内容类型近似的形状上显示带…

Python业务规则引擎库之rules使用详解

概要 在软件开发中,业务规则引擎是一种重要的工具,可以帮助开发者将复杂的业务逻辑从代码中解耦出来,并以更直观的方式进行管理和维护。rules 是一个轻量级的 Python 库,专门用于定义和执行业务规则。它提供了一种简洁且强大的方式来管理应用程序中的规则逻辑,使代码更加…

【Redis】安装和命令行客户端

https://www.bilibili.com/video/BV1cr4y1671t https://www.oz6.cn/articles/58 redis 非结构化有&#xff1a; 键值类型(Redis)文档类型(MongoDB)列类型(HBase)Graph:类型(Neo4j) 扩展性&#xff1a;水平即为分布式扩展 redis特征 键值&#xff08;key-value&#xff09;型…

代码签名证书的申请途径和应用场景

代码签名证书是一种安全技术&#xff0c;主要用于确保软件的完整性和来源的真实性&#xff0c;防止软件在分发过程中被篡改或植入恶意代码。它通过数字签名的方式为软件或代码提供验证&#xff0c;使得终端用户可以确信他们下载的软件确实来自声称的开发者&#xff0c;并且未被…

VMware Ubuntu 虚拟机网卡消失及解决办法

VMware Ubuntu 虚拟机网卡消失 描述原因查找解决方法 描述 在正常使用过程中重启后发现 VMware Ubuntu 虚拟机中的网卡消失了&#xff0c;使用 ifconfig 查看只能看到本地回环&#xff1a; 原因查找 使用如下命令查看是否和我这边遇到的问题一致的原因。 sudo lshw -c netwo…

mathematical-expression-cpp | C++ 数学表达式解析库

数学表达式-cpp Switch to English Document 介绍 本框架是一种针对数学公式解析的有效工具&#xff0c;能够通过C的API解析包含嵌套函数&#xff0c;包含函数&#xff0c;数列步长累加等数学公式&#xff0c;返回值是一个数值的结果对象&#xff0c;同时也可以进行比较运算…