C++学习记录——십유 模板进阶


1、非类型模板参数

模板参数分为类型参数和非类型参数。

如果要定义一个静态数组

#define N 10
template<class T>
class Array
{
private:
	T _a[N];
};

	Array<int> a1;
	Array<double> a2;

这就是类型模板参数,实例化那里定义了对象参数,模板那里定义类的类型参数。但是这样只能固定大小,不能用同一个类定义不同大小的数组。非类型模板参数定义的是常量。

//#define N 10
template<class T, size_t N>
class Array
{
private:
	T _a[N];
};

int main()
{
	Array<int, 10> a1;
	Array<double, 100> a2;
	return 0;
}

非类型模板参数也可以用缺省值,但只能是整型常量。布尔类型也可以。

2、类模板特化

1、全特化

先弄一个简单的日期类

class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
		: _year(year)
		, _month(month)
		, _day(day)
	{}
	bool operator<(const Date& d)const
	{
		return (_year < d._year) ||
			(_year == d._year && _month < d._month) ||
			(_year == d._year && _month == d._month && _day < d._day);
	}
	bool operator>(const Date& d)const
	{
		return (_year > d._year) ||
			(_year == d._year && _month > d._month) ||
			(_year == d._year && _month == d._month && _day > d._day);
	}
	friend ostream& operator<<(ostream& _cout, const Date& d)
	{
		_cout << d._year << "-" << d._month << "-" << d._day;
		return _cout;
	}
private:
	int _year;
	int _month;
	int _day;
};
template<class T>
bool Less(T left, T right)
{
	return left < right;
}

int main()
{
	//Array<int, 10> a1;
	//Array<double, 100> a2;
	//func1(a1);
	//func2(a2);
	cout << Less(1, 2) << endl;
	
	Date d1(2022, 7, 7);
	Date d2(2022, 7, 8);
	cout << Less(d1, d2) << endl;

	Date* p1 = &d1;
	Date* p2 = &d2;
	cout << Less(p1, p2) << endl;
	return 0;
}

结果确实是1 1 0,但是现在我不想这样比较。虽然指针可以调用Less比较,但是还想要单独写出来

template<class T>
bool Less(T left, T right)
{
	return left < right;
}

template<class T>
bool Less<Date*>(Date* left, Date* right)
{
	return *left < *right;
}

这就是特化,对于某些类型特殊化处理。模板可以泛型编程,每个类型都可以使用成员函数,但是也可以对某个类型单独拿出来。

特化必须要先有一个基础的函数模板,函数形参要和模板函数的基础参数类型相同。

模板特化是在原生类型不符合需求时单独写一个函数。类模板是在类名后加<类型>。特化对于类模板特化更有意义。

2、偏特化

template<class T>
struct Less
{
	bool operator()(const T& l, const T& r)
	{
		return l < r;
	}
};


	Date d1(2022, 7, 7);
	Date d2(2022, 7, 8);
	cout << Less<Date>()(d1, d2) << endl;

	Date* p1 = &d1;
	Date* p2 = &d2;
	cout << Less<Date*>()(p1, p2) << endl;

    int* p3 = new int(1);
	int* p4 = new int(2);
	cout << Less<int*>()(p3, p4) << endl;

刚才写到是全特化,针对某一类型做特化,除此之外,我们还可以做偏特化。

template<class T>
struct Less
{
	bool operator()(const T& l, const T& r)
	{
		return l < r;
	}
};

template<class T>
struct Less<T*>
{
	bool operator()(const T* l, const T* r)
	{
		return *l < *r;
	}
};

偏特化并不是在使用一个具体的类型,而是用模板,来代表某些相同特征的类型,比如都是指针,有些泛型编程的意思。如果传自定义类型的指针呢?那就要用这个类型对应的方法了,这就需要程序员自定义。

偏特化可以将模板参数表中的一部分参数特化,比如限定为<T, int>,让模板参数限制为某一类型。两种特化可以共存,哪个合适走哪个,由编译器决定。不过如果限制为引用类型,引用必须初始化,就需要在类里面的函数参数中给缺省值。

3、模板分离编译

在Func.h文件里这样写

template<class T>
T Add(const T& Left, const T& right);

void func();

Func.cpp文件里这样写

template<class T>
T Add(const T& Left, const T& right)
{
	return left + right;
}

void func()
{
	cout << "func" << endl;
}

Test.cpp里调用Add和Func函数

#include "Func.h"

int main()
{
	Add(1, 2);
	func();
}

调用Add会出现链接错误,而func则没事。常见的声明和定义分离。类模板不能这样,普通函数可以。

现在有三个文件func.h func.cpp test.cpp,编译器先对它们进行预处理,展开头文件,变成func.i,以及test.i;编译阶段,要开始检查语法,生成汇编代码,func.s test.s;汇编阶段,将汇编代码转换成二进制机器码, func.o test.o;链接阶段,合并生成可执行文件a.out。

从编译阶段到汇编阶段,编译器只会生成func的汇编代码,而没有Add。

在这里插入图片描述

jmp指令前面的就是函数地址,push前面的就是函数第一句的地址,编译阶段完成后,编译器把func转换成一堆指令后,就会在func.o也就是汇编阶段完成了函数就有地址了,指令也就是上面这部分。而Add则不能这样,因为Add没有实例化,编译器无法确定T。

在函数调用处,都会有call的指令。Add也可以过编译阶段,因为它有声明,一般有声明就有定义,所以编译器也把它看作合乎规则的,但是最多生成.s文件。到链接之前,只有call指令,两个函数地址也还没有完全生成,到了链接的时候,编译器就会按照修饰后的函数名去找对应的地址,.o文件里面会有符号表去映射对应的地址。符号表里有func函数的地址,就把这个函数链接好了,但没有Add。之前两个cpp文件各自经过处理,互不影响,所以也不知道对方什么情况,test.cpp里有对Add的调用,给了具体的变量类型,但是func.cpp中的Add它不知道自己要变成什么样,所以最后就无法链接,找不到Add的地址。

为了解决这个问题,我们可以显式实例化。

template
int Add<int>(const int& left, const int& right);

但是显式实例化一次只能写一个类型。常用的办法就是不分离,都放在.h文件里,因为头文件会展开。声明和定义放在一起,直接就实例化了,编译时就有地址,链接时不需要去找。

4、模板总结

模板复用了代码,节省资源,更快地迭代开发,C++标准模板库(STL)因此而诞生
增强了代码的灵活性
模板会导致代码膨胀问题,也会导致编译时间变长
出现模板编译错误时,错误信息非常凌乱,不易定位错误

结束。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值