C\C++_模板_函数模板

本文详细介绍了C++中的函数模板,涵盖非类型参数、模板类型参数、显式初始化、编译器匹配规则、模板具体化和后置返回类型等概念,以及在类中使用模板的示例。读者将理解如何利用模板处理不同类型,以及如何解决类型声明问题和优化函数版本选择。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

0. 参数类型

  • 概念
    分为模板类型参数和非类型模板参数

0.1 非类型参数

非类型参数可以使一个整形,或者是一个指向对象或函数类型的指针或(左值)引用。绑定到非类型整型参数的实参必须是一个常量表达式。绑定到指针或引用非类型参数的实参必须具有静态的生存期(参见c++ primer 第5版——第12章,第400页)

namespace jj02
{
	template <unsigned N, unsigned M>
	int Compare(const char (&p1)[N], const char (&p2)[M])
	{
		std::cout << " N = " << N << std::endl;
		std::cout << " M = " << M << std::endl;
		return strcmp(p1, p2);
	}

	void Test01()
	{
		//! 编译器会使用字面常量的大小来代替N和M的值
		std::cout << Compare("hi", "mom") << std::endl;
	}

}

0.2 模板类型参数

#include <iostream>
namespace jj01
{
	template <typename T>
	T Foo(T* p)
	{
		T tmp = *p;	//<	tmp的类型将是指针p指向的类型
		//...
		return tmp;
	}

	void Test01()
	{
		int a = 20;
		int b = Foo(&a);
		std::cout << "b = " << b << std::endl;
	}
}

03 基础类型的明确初始化

C++标准库(第2版)——3.2.1基础类型的明确初始化

//! 模板函数内基础类型的初始化
	template <typename T>
	void F()
	{
		T x2;		//< 随机值
		T x = T();	//< 基础类型double会被初始化为0.0000
		std::cout << x << " " << x2 << std::endl;
	}

	void Test01()
	{
		F<double>();  
	}

1. 使用方式

一般将模板放在头文件中,并在需要使用模板的文件中包含头文件

2. 编译器匹配规则

  • 如果有多个原型,则编译器在选择原型时的优先级如下:

非模板函数 > 具体化模板函数 > 常规模板函数

3. 模板显示具体化的举例说明

题目:只希望交换两个Job结构中salary的数据,不希望交换name的数据

struct Job
{
	char name[40];
	double salary;
};

//! 常规模板
template <typename T>
void Swap(T &j1, T &j2)
{
	T temp = j1;
	j1 = j2;
	j2 = temp;
}

//! 显示具体化模板函数
template <> void Swap<Job>(Job &j1, Job &j2)
{
	Job temp;
	temp.salary = j1.salary;
	j1.salary = j2.salary;
	j2.salary = temp.salary;
}
int main()
{
	double u, v;
	Swap(u, v);		//<	调用常规模板
	Job a, b;
	Swap(a, b);		//<	优先匹配具体化模板函数
}

结论:可以看出原先的模板只是针对常规交换,但如果需要对一些类型特殊处理,这时候就不适用了,需要使用模板具体化功能

4. 函数模板的本质

  • 实例化:在代码中包含模板本身不会生成函数定义,它只是一个用于生成函数定义的方案,编译器使用模板为特定类型生成函数定义时,得到的是模板示例。
//! 1. 隐式实例化:
//! 这个例子则是隐式实例化,程序调用Swap()导致编译器生成Swap的示例,编译器之所以知道需要进行定义,
//! 是因为程序调用Swap()函数的是偶提供了int参数
template <typename T>
void Swap(T &j1, T &j2)
//! 2. 显示实例化:
//! C++还允许显示实例化,这意味着可以直接命令编译器创建特定的实例,语法为 template void Swap<int> (int &j1, int &j2), 该声明的意思是“使用Swap()模板生成int类型的函数定义”
template void Swap<int> (int &j1, int &j2)
//! 也可以在程序中使用函数来创建显示实例化,例如:
int a =20;int b = 30;
Swap<int>(a, b);

//! 3. 显示具体化:
//!与显示实例化不同的是,显示具体化使用下面两个等价的声明之一:
template <> void Swap<int> (int &, int &);
template <> void Swap(int &, int &);
//! 区别在于,这些声明的意思是“不要使用Swap()模板来生成函数定义,而应使用专门为int类型显示地定义的函数定义”。
//! 这些原型必须有自己的函数定义。显示具体化声明在关键字template后包含<>,而显示实例化没有。

警告:试图在同一个文件(或转换单元)中使用同一种类型的显示实例和显示具体化将出错。

5. 编译器选择使用哪个函数版本

C++ Primer plus(第六版)——8.5.5 编译器选择使用哪个函数版本

6. 关键字decltype(c++11)

6.1 问题:遇到无法声明的类型

template<typename T1, typename T2>
void ft(T1 x, T2 y)
{
	?type> xpy = x + y; //< 这里的xpy是什么类型无法声明
}

decltype语法:decltype(expression) var

6.2 解决方案

  1. 情形一:
    如果expression 是一个没有用括号括起的标识符,则var的类型与该标识符的类型相同,包括const等限定符
double x = 5.5;
double y = 7.9;
double &rx = x;
const double *pd;
decltype(x) w;		//< w is type double
decltype(rx) u = y; //< u is type double &
decltype(pd) v; 	//<	v is type const double *
  1. 情形二:
    如果express是一个函数调用,则var的类型与函数的返回值类型相同
long indeed(int);
decltype (indeed(3)) m; 	//< m is type int

注意:并不会实际调用函数。编译器通过查看函数的原型来获悉返回类型,而无需实际调用函数。

  1. 情形三:
    expression是一个左值,并且用括号括起的标识符:
double xx = 4.4;
decltype ((xx)) r2 = xx;	//<	r2 is double &
decltype (xx) w = xx;		//<	w is double (Stage 1 match)
  1. 情形四:
    如果前面的条件都不满足,则var的类型与expression的类型相同:
    int j = 3;
    int &k = j;
    int &n = j;
    decltype(j+6) i1; //< i1 type int
    decltype(100L) i2; //< i2 type long
    decltype(k+n) i3; //< i3 type int

注意:虽然k和n都是引用,但表达式k+n不是引用;它是两个int的和,因此类型为int

总结:如果需要多次声明,可以结合使用typedef和decltype
template<typename T1, typename T2>
void ft (T1 x, T2 y)
{
typedef decltype(x + y) xytype;
xytype xpy = x + y;
xytype arr[10];
xytype & rxy = arr[2]; //< rxy a reference
}

7. 另一种函数声明语法(c++11 后置返回类型)

  1. 问题:无法预先知道a*b得到的类型,好像可以将返回类型设置为decltype(a*b),但现在还没声明参数a和b,不在作用域范围(编译器看不到它们,也无法使用它们),必须声明参数后使用decltype
  2. 解决措施:c++新增了一种声明和定义函数的语法。这个时候可以结合auto关键字,使用后置类型来声明,auto只是个占位符,代表函数Multiple -> 后面的声明
  • 代码示例:
template <typename T1, typename T2>
auto Multiple(T1 a, T2 b) -> decltype(a*b)	//< ->后面被称为后置返回类型,现在a和b在参数声明后面,因此a和b位于作用域内,可以使用它们
{
	return a*b;
}

int main(void)
{
	int a = 10;
	double b = 20.3;
	cout << Multiple(a, b) << endl;

	return 0;
}

8. 参考书籍

C++ Primer plus(第六版)——8.5函数模板

9. 函数模板在类中的使用方式

  • 头文件——Person.h
#pragma once
class CPerson
{
public:
	CPerson(void);
	~CPerson(void);

	template <typename T>
	void Swap(T &a, T& b);
};

//! 类中定义函数模板方式:只能和声音在同一个文件中,不能将模板声明和实现分离文件编译
template <typename T>
void CPerson::Swap(T& a, T& b)
{
	T tem = a;
	a = b;
	b = tem;
}

源文件——test.cpp
#include <iostream>
#include "Person.h"
using namespace std;

int main(void)
{
	int a = 3;
	int b = 4;

	CPerson per;
	per.Swap<int>(a, b);

	cin.get();
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值