c++模板

一、模板的理解

模板就好比造房子的蓝图,实例化模板就是照着蓝图建房子。

所以程序员更愿意干的是画蓝图,造房子的重复工作交给编译器干就好了。

所以其实编译器是通过传过来的数据类型,用程序员写好的模板,实例化出一个个模板函数或模板类,来应对不同数据类型的函数或类操作。

二、函数模板

1、函数模板用法

​
​template<class T>
void swap(T& a, T& b)
{
    T tmp = a;
    a = b;
    b = tmp;
}

​

(1)模板的关键字是 template

(2)定义模板 template<class T> 或 template<typename T>

(3)其中T是模板参数(类型形参)名称随便取。

(4)模板参数用 , 分隔。

2、实现原理

编译器首先会记住模板,根据传入的数据类型创建具体的函数进行调用,这也是函数重载的原理。

3、问题探讨

上面的函数模板就是对同类型的数据进行交换。

那么问题来了,要是传入的是不同类型的数据呢?

解决办法

(1)强转类型。

(2)显示实例化,告诉编译器我要用哪个数据类型进行实例化模板。

​template<class T>
void swap(T& a, T& b)
{
    T tmp = a;
    a = b;
    b = tmp;
}

​int main()
{
    int a = 10;
    int b = 20;
    double c = 10.1;
    
    //方法一:强转
    swap(a, (int)c);
    
    //方法二:显示实例化
    swap<int>(a, c);
}

当然除了显示实例化,如果类型冲突,不采用上面方法解决,编译器就会采用隐式实例化来识别参数。此时返回的结果就是不可控的了。

4、函数模板匹配规则

如果有参数类型全部匹配的就用全部匹配的,如果没有就用自己实例化模板。

//模板一
template<class T1, classT2>
void add(T1& a, T2& b)
{
    cout << a + b << endl;
}

//模板二
template<classT2>
void add(int& a, T2& b)
{
    cout << a + b << endl;
}

//模板三
void add(int& a, int& b)
{
    cout << a + b << endl;
}


int main()
{
    add(1, 2);//匹配三
    add(1, 2.1);//匹配二
    add(1.1, 2);//匹配一
    return 0;
}


三、类模板

1、类模板使用方法

template<class T>
class Stack
{
public:
	Stack(int capacity = 4);
	~Stack();
	//……

private:
	T* _pData;
	int _top;
	int _capacity;
};

和函数模板基本一致,但是注意类模板一定只能显示实例化!

2、注意

(1)类模板如果不在原来类域中进行定义,就要通过类域访问 + 类模板方式定义。

(2)类模板十分不建议声明与定义分离,会造成链接错误。

四、重谈模板参数

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

1、类型形参

出现在模板参数列表中,跟在 class 或 typename 后面的参数类型名。

2、非类型形参

用常量作为类模板的参数(只支持整型),在类模板中可将该参数当常量使用。

3、举例

template<class T, size_t N>

T就是类型形参,N是非类型形参。

五、模板的实例化

模板的实例化实在编译时,对比函数调用是在运行时。

实例化过程:根据模板实例化成半成品模板 -> 根据数据类型实例化成具体类 -> 语法编译

模板是按需实例化,即调用哪个函数就实例化哪个函数。所以如果函数模板内部有错误,但是main函数不调用,编译器就不会报错。

六、模板特化

1、函数模板特化

//正常比较
template<class T>
bool less(T left, T right)
{
    return left < right;
}

//专门用于指针内容比较(特化)
template<>
bool less<int*>(int* left, int* right)
{
    return *left < *right;
}

若要比较两个指针类型指向的数据大小,用第一个模板显然是不可以的,所以就要针对特殊情况进行模板的特化。但其实个人认为比较别扭,因为函数重载就可以解决此问题。

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

2、类模板特化

相较于函数模板的特化,类模板特化就有一定意义。

​
//原模板
template<class T1, class T2>
class test
{
public:
    test()
    {...}

private:
	T1 t1;
	T2 t2;
};

​

原模版中 t1, t2类型由 T1, T2决定,这是最常见的写法。

​//全特化模板
template<>
class test<char, int>
{
public:
    test()
    {...}

private:
	char t1;
	int t2;
};

​

全特化之后的模板就没有模板特性了,只是用于特殊情况的处理。

​//半特化模板
template<class T1>
class test<T1, int>
{
public:
    test()
    {...}

private:
	T1 t1;
	int t2;
};

半特化模板不是一半模板参数固定,一半不固定,而是只要有固定的数据类型就是半特化。

​//半特化模板
template<class T1, class T2>
class test<T1*, T2*>
{
public:
    test()
    {...}

private:
	T1* t1;
	T2* t2;
};

半特化不一定是特化部分参数,只要是对参数进行限制就是半特化,如上图的指针类型,表示构造传入指针也走半特化模板。

偏特化(尤其是限制为某种类型)在泛型思想和特殊情况之间做了折中处理,使得限制范围式的偏特化也可以实现泛型。

七、模板分离编译

在上文中我们提到模板的声明与定义是不能分离的。会导致链接错误。

1、原因

当我们在编写模板声明与定义分离的代码时(此做法是错误的)会报两个错误。

(1)当我们没有指定命名空间时,就算包了 xxx.h 头文件依然找不到 xxx.cpp 中的定义,因为编译器默认不会到命名空间中找,所以加上命名空间,由于命名空间可以合并,编译器后就能找到。

(2)即使解决上面的问题,模板不建议分离编译的最大问题在于调用的地方(main函数,test.cpp文件)知道T会实例化成什么类型,由于 test.cpp文件包含 xxx.h 头文件,所以声明的地方(xxx.h头文件)也知道,但是定义的地方(xxx.cpp文件)不知道T是什么类型,所以就无法定义模板。

2、解决办法

(1)在定义的地方显式实例化,但是这就导致模板的特性消失,不推荐。

(2)最推荐的做法就是不要声明与定义分离!

八、总结

1、模板的优点

模板复用了代码,节省资源,更快的迭代开发。

增强了代码的灵活性。

2、模板的缺点

模板会导致代码膨胀问题,也会导致编译时间变长。

出现模板编译错误时,错误信息非常凌乱,不易定位错误。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值