关于c++模板参数的详细介绍

​模板基础概念的介绍​


一.关于typename

·用于在创建变量时放在模板参数前,说明是类型而不是对象

问题

在介绍模板参数时,提到过这样一句话“在​​template<class T>​​使用class效果与typename一样”,为了方便,一般会直接写成class,那typename有什么用?

有这样一种情形:我们需要写一个函数,参数是vector或list等某一种容器,例如Print()函数:

有了模板参数之后,能不能考虑往泛型去写?

如果用class的形式定义模板参数,在运行时会发现编不过:

什么叫必须以typename为前缀?换成template<typename Container>呢?发现在VS2022下仍然编译不过去(实际这种情况下使用typename意思更明确)。

解决

或者直接使用auto:​​auto it = v.begin();​

原因:

使用 const_iterator是通过::被访问的,而通过::被访问的成员有两种情况:①typedef出的类型 ②静态成员变量。如果这里的const_iterator是对象(或者说是静态成员--毕竟const_iterator只是一个名字,不一定非得是迭代器)的话,这样创建it完全是错误的。

也就是说在没有被实例化之前,无法确定通过模板参数访问的const_iterator是对象还是类型,我们要用typename告诉编译器,这里一种类型,不需要报错,并在之后调用Print时实例化出具体类型。

而使用auto,auto代表的一定是类型,不存在类型对象不明的问题,auto直接在实例化时被具体确定。

二.非类型模板参数

用常量作为类(函数)模板的一个参数,在其中可以直接将参数当成常数使用。

1.介绍
//类似静态栈这样:需要固定大小的数组,但具体大小是多少又不确定
template<class T , size_t N>
class Stack
{
private:
	T _a[N];
	int _top;
};

我们将N这个整型变量当成模板参数去使用,方便的实例化出大小不同的数组。

这样,当我们还需要类中大小为100的数组时,可以通过模板参数确定​​Stack<int,100> st2​​即可。

2.优势

非类型模板参数意义如下:

- 增强定制性:以常量作参数,灵活调整模板行为,如定义不同大小的固定数组模板。

- 支持编译期计算:实现编译期常量求值,像编译期算阶乘,减少运行时开销。

- 提升效率:编译期确定信息,便于优化,避免运行时动态开销。

- 促进代码复用:针对不同常量需求,复用相似逻辑代码,减少重复编写。

但一般只用整型作为非类型模版参数(float,double等一些类型不支持,因为其在不同平台上的表示可能有差异),且它必须是常量,在编译期间就被确定,同时在不能被修改。

三.模板分离编译导致的问题

拥有模板参数的成员函数以声明和定义分离的方式被我们写出,会引发问题。

问题

就像这样:

当我们尝试运行时会发现编译器报错:

这是什么意思?

先明确一点:声明和定义分离的函数在链接时,连接器会通过符号表来查找函数的具体地址。

四个概念:

①符号表:是编译器和链接器在编译、链接过程中用于存储各类符号相关信息的数据结构 ,这些符号包括变量、函数、结构体、类等。

②程序生成过程: 预处理(头文件展开,宏替换等)、编译(检查语法,生成汇编代码等)、汇编(生成二进制代码等)、链接(链接若干源文件,生成可执行程序)。

③编译阶段:C++ 支持函数重载,为区分同名但参数列表不同的函数,编译器会对函数进行“名字改编”(Name Mangling)。

④链接阶段:将一个项目中若干个源文件形成的目标文件链接起来,形成可执行程序。

由这些,我们可以看到编译过程发生在链接之前,而当我们调用push_back(10)这个函数时,模板参数被确定为整型,符号表中储存的是被“重命名”之后的函数名:

假如说被重命名为XXXpushi,然而,在链接时,编译器试图找到参数为整型的push_back()函数,却根本找不到函数的具体地址(Aclass.cpp的符号表中无xxxpushi函数。

原因:

编译Aclass.cpp时,编译器看到模板的定义,但没有具体的类型实例化,不会生成任何实际的代码(符号表中无具体地址)。

编译Test.cpp时,编译器看到A<int> 但只有声明没有定义——头文件引入了,A类被实例化出来在Test.cpp中。

所以若分离编译在链接阶段找不到具体函数

解决:
方案I
//声明定义分离
template<class T,size_t N>
void A<T,N>::push_back(const T& x)
{
	assert(_top < N);
	_a[_top++] = x;
}


//解决①:显示实例化
template
class A<int>;//对这个类模板实例化

template
class A<double>;//但每次创建不同类型的对象,都需要显式实例化,不够方便
方案II

将定义也写在Aclass.h中

template<class T,size_t N = 10>
class A
{
public:
	void push_back(const T& x);
	T top()
	{
		return _a[0];
	}
private:
	T _a[N];
	size_t _top = 0;
};
//在当前文件声明定义分离--
//有的地方会把这种文件后缀写为.hpp,代表头文件+定义(类似源文件)
template<class T, size_t N>
void A<T, N>::push_back(const T& x)
{
	assert(_top < N);
	_a[_top++] = x;
}
//因为用声明和定义分离的形式主要因为大量函数定义在类中会导致类很长,不方便我们去查找一些内容
//所以将定义写在.h文件中,但不写在类里,可以满足我们的需求

在一个文件下 Aclass.hpp:

编译器看到a<int>的实例化请求,同时看到完整的模板定义,(可以根据类型)当场生成具体函数 ,(在编译时都处于在一个文件中)能直接找到定义。

四.模板的特化

一个建立在原有类上的新类,对一些特殊数据类型进行处理,但新类不影响原有类。

1.作用:

①当模板函数在特定类型下需要特殊处理时(如性能优化、类型兼容性问题),可对该类型特化。

// 通用模板  
template <typename T>  
T max(T a, T b) 
{
    return a > b ? a : b; 
}
// 针对const char*类型的特化(避免字符串指针比较,改为字符串内容比较)  
template <>  
const char* max<const char*>(const char* a, const char* b)
{  
    return strcmp(a, b) > 0 ? a : b;  
}

②为类模板的特定参数类型定制成员变量或方法,解决通用实现无法处理的场景。

类模板的特化分成两种:全特化和偏特化,借助代码具体介绍:

//函数可以直接走重载形式,但是类只能走特化
//对某一种情况特殊处理
template<class T1,class T2>
class A
{
public:
	A(T1 t1,T2 t2)
	{
		cout << "T t1,T t2" << endl;
	}
};
//比如需要对int和double情况特殊处理
//1.全特化
template<>
class A<int,double>//特化,不影响原本的A类
{
public:
	A(int t1, double t2)
	{
		cout << "int t1, double t2" << endl;
	}
};
//2.偏特化 -- 第一个无要求-只有第二个double时特殊处理
//①特化部分参数
template<class T1>
class A<T1, double>
{
public:
	A(int t1, double t2)
	{
		cout << "int t1, double t2" << endl;
	}
};
//②对类型的进一步限制
template<class T1,class T2>
class A<T1*, T2*>
{
public:
	A(int t1, double t2)
	{
		cout << "int t1, double t2" << endl;
	}
};

//对不同类的使用更加灵活方便,更加智能
//调用的不同类--但看上去是一个类
int main()
{
	A<int,int> aa1(10, 20);
	A<int, double> aa2(10, 10.1);
	return 0;
}

对于类模板的特化,新类中写什么是根据需求决定的,不必和基础类保持一致(甚至可以完全与原有类不同),但必须有原有类才能有特化类,特化类不能独立存在。

五.模板总结

1.优点

①模板复用代码,STL产生,大大方便了使用。

②增强了代码的灵活性,适配器,仿函数产生

2.缺点

①模板会导致代码膨胀,编译时间增多(实际一般不影响)

②在报错时不易找到产生错误的具体位置

3.知识点总结

- 函数模板:通过模板参数表示不确定类型,编译器依调用参数实例化具体函数,实现代码复用,如通用比较函数 template <typename T> int compare(T a, T b) 。

- 类模板:以模板参数定制类行为,可创建不同类型成员的类,如 template <typename T> class Stack 实现通用栈。

- 模板参数有类型参数(如 typename T )和非类型参数(如 int N )。非类型参数多为整型、指针、引用等,支持编译期计算与定制,如固定大小数组 template <typename T, size_t N> class FixedArray 。

- 实例化:分隐式(编译器依实参推断)和显式(指定模板参数)实例化,如 add(1, 2) (隐式)和 add<int>(1.5, 2.5) (显式,强制类型)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值