深入了解模板知识(c++)

前言

        在c++中模板是很重的,泛型编程就是模板最好的体现,模板的出现就是为了更好的复用代码,有了它,我们不必写各种逻辑相同只是逻辑中的数据的类型的不同的代码,使得我们编写代码变得更加高效,下面让我们一起来深入的理解和模板有关的知识。

目录

1.非类型模板参数

2.模板的特化 

        2.1函数模板的特化

                2.1.1什么叫函数模板的特化 

                2.1.2函数模板特化的步骤 

        2.2类模板的特化 

                2.2.1什么叫类模板的特化

                2.2.2全特化

                2.2.3偏特化

                2.2.4类模板特化的应用示例

3.模板在分离编译时的问题

        3.1什么是分离编译

        3.2模板的分离编译

        3.3解决法 

 4.总结

        模板的优点:

        缺点:


1.非类型模板参数

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

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

        非类型形参是指在模板形参列表中的一个作为形参的常量,在函数模板或者类模板中可以将该参数当做常量使用。

        如果有这样的需求:需要定义同类型的对象,一个对象中成员变量中的数组的大小为10,另一个对象的成员变量中的数组的大小为20,这时候你就可能需要在模板参数中加入非类型形参:

#include<iostream>
using namespace std;
template <class T,size_t N = 7>//模板中含有非类型的参数
class A
{
public:
	cout << a;
private:
	T _arr[N];
	size_t _size;
};
int main()
{
	return 0;
}

        注意:非类型模板参数是一个常量,该常量可以是整形,字符型,短整型等整形家族的内置类型不可以是其他类型(可以这样理解这种非类型模板参数一般是作为改变静态数组(固定大小的数组)的大小来用的,让其他类来做非类型模板参数没有太大的意义),语法不允许。

        例如:

#include<iostream>
using namespace std;
template <class T,size_t N = 7>
class A
{
public:
	cout << a;
private:
	T _arr[N];
	size_t _size;
};
int main()
{
	return 0;
}

        此时就会有语法错误。

2.模板的特化 

        2.1函数模板的特化

                2.1.1什么叫函数模板的特化 

        一般情况下使用模板可以实现一些与类型无关的代码,但是对于一些特殊的类型可能会出现错误的情况,此时就需要我们做一些特殊的处理,比如实现一个专门用来进行小于比较的函数模板。如下:

template <class T1, class T2>
bool Greater(const T1 & x1,const T2 & x2)
{
	return x1 > x2;
}
int main()
{
	int a1 = 3;
	int b1 = 5;
	cout << (a1 > b1)<<endl;//如果比较内置类型可以得到正确的结果
	const char* p1 = "bcdefg";
	//但是比较其他的可能就会出现错误
	const char* p2 = "bcdef";
	cout << (p1 > p2)<<endl;
	return 0;
}

         可以发现Greater在大多数情况下是正常的,但是在一些特殊的情况下会出现问题,比如在比较两个字符串的大小时,这时候就需要我们对这种特殊的情况进行特殊的处理,也就是对函数模板进行特化,对模板进行特化就是在原来模板的基础上,针对特殊的类型所进行的特殊处理,进行特殊化的实现方式。模板特化分为函数特化和类特化。

        例如: 

template<>//特化针对某些类型进行特殊化处理
bool Greater<char *>(char * &p1, char * &p2)
{
	if (strcmp(p1, p2) > 0)
		return true;
	else
		return false;
}

                2.1.2函数模板特化的步骤 

        1.必须要有一个基础的模板(没有特化的模板)

        2.关键字template后面接一对尖括号

        3.函数名后面跟一对尖括号,尖括号中指定特化的类型。

        4.函数形参表必须和模板函数的基础参数类型完全相同,如果不同,编译器会报出一些奇奇怪怪的错误。

例如:

template <class T>
bool Greater( T&  x1, T&  x2)
{
	return x1 > x2;
}
template<>//特化针对某些类型进行特殊化处理
bool Greater<char *>(char * &p1, char * &p2)
{
	if (strcmp(p1, p2) > 0)
		return true;
	else
		return false;
}
int main()
{
	int a = 5;
	int b = 7;
	cout << Greater(a, b);//调非特化的模板函数
	const char* p1 = "abcde";
	const char* p2 = "accde";
	cout << Greater(p1, p2);//调用特化的模板函数

	return 0;
}

        2.2类模板的特化 

                2.2.1什么叫类模板的特化

                当模板参数为某种特殊类型时模板会实例化出特殊的对象。

        例如:

template<class T1, class T2>
class A
{
public:
	A()
	{
		cout << "非特化模板类  " << _a << endl;
	}
private:
	T1 _a = 0;
	T2 _b;
};
template<>
class A<int,char>
{
public:
	A()
	{
		cout << "特化模板类  " << _a << endl;
	}
private:
	int _a = 0;
	char _b;
};
int main()
{
	A <int, int>a1;//调用非特化的模板类实例化对象
	A<int, char> a2;//调用特化的模板类实例化对象
	return 0;
}

                2.2.2全特化

        全部的模板参数都进行特化。

        例如:

template<class T1, class T2>
class A
{
public:
	A()
	{
		cout << "非特化模板类  " << _a << endl;
	}
private:
	T1 _a = 0;
	T2 _b;
};
template<>
class A<int,char>//全特化
{
public:
	A()
	{
		cout << "特化模板类  " << _a << endl;
	}
private:
	int _a = 0;
	char _b;
};

                2.2.3偏特化

        任何对模板参数进一步进行条件限制的特化版本。偏特化有两种表现形式:1.部分特化,例如:

template<class T2>
class A<int, T2>//全特化
{
public:
	A()
	{
		cout << "特化模板类  " << _a << endl;
	}
private:
	int _a = 0;
	T2 _b;
};
int main()
{
	A<int, double> a3;//调用部分特化的模板类实例化对象
	return 0;
}

2. 对参数更进一步的限制,例如:

template<>//对参数做出限制,如果是两个指针参数就会实例化这个模板
class A<class T1*, class T2*>
{
public:
	A()
	{
		cout << "参数进行限制的特化模板类  " << endl;
	}
private:
	T1* _a = nullptr;
	T2* _b = nullptr;
};
int main()
{
	A<char*, char*> a4;//调用对参数进行限制的模板类实例化出对象
	return 0;
}

偏特化不仅仅是对模板参数部分特化,也可以是对模板参数的进一步限制。 

                2.2.4类模板特化的应用示例

 例如有一下专门用来比较小于的类模板Less:

#include<algorithm>
template<class T>
struct Less
{
	bool operator()(const T& x1, const T& x2)const
	{
		return x1 < x2;
	}
};
int main()
{
	int arr[] = { 2,3,4,1,6,5,8,7,9 };
	sort(arr, arr + 9, Less<int>());//对数组进行排序
	for (auto& e : arr)
		cout << e << " ";
	return 0;
}

 但是如果用上面的Less类对

指针数组的内容 进行排序就会出现排序的结果错误,例如:

    int main()
    {
        const char* p1 = "abcde";
	    const char* p2 = "abbde";
	    const char* p3= "accde";

	    const char* parr[]= {p1,p2,p3};
	    sort(parr, parr + 3, Less<const char*>()); 
    }

这时候就需要对Less类模板进行特化处理。 如下:

#include<algorithm>
#include<string.h>
template<class T>
struct Less
{
	bool operator()(const T& x1, const T& x2)const
	{
		return x1 < x2;
	}
};
template<>
struct Less< char*>
{
	bool operator()(char* x1,  char* x2)const
	{
		if (strcmp(x1, x2) < 0)
			return true;
		else
			return false;
	}
};
int main()
{
	int arr[] = { 2,3,4,1,6,5,8,7,9 };
	sort(arr, arr + 9, Less<int>());//对数组进行排序
	for (auto& e : arr)
		cout << e << " ";
	 char p1[] = "abcde";
	 char p2[] = "abbde";
	 char p3[]= "accde";

	char* parr[]= {p1,p2,p3};
 	sort(parr, parr + 3, Less<char*>());//调用特化的less进行排序
	for(auto&e:parr)
		cout << e << " ";
	return 0;
}

3.模板在分离编译时的问题

        3.1什么是分离编译

        在实际工程中 一个程序是由若干个源文件共同实现的,而每个源文件单独编译生成目标文件,最后将所有的目标文件连接起来形成单一的可执行文件的过程称为分离编译模式。

        3.2模板的分离编译

        如果将模板分离编译会产生什么结果呢?我们一起试试,

//Func.h
#pragma once
#include<iostream>
using namespace std;

void F1();
template<class T>
T Add(const T& x1, const T& x2);//模板函数的申明
//Func.cpp

#include"Func.h"
void F1()
{
	cout << "普通函数" << endl;
}
template<class T>
T Add(const T& x1, const T& x2)
{
	return x1 + x2;
}
//Test.cpp
#include"Func.h"
int main()
{
	F1();
	Add(1, 2);//调用函数模板
	return 0;
}

         运行程序,程序会报出以下错误:

        这是为什么呢?我们来一起探究一下:

        在c/c++程序要运行,一般要经历一下几个步骤:

        1.预处理,2.编译,3.汇编,4.链接。这四个步骤都做了什么事情呢?详见:编译和链接详解 

        预处理阶段:会进行头文件的展开,注释的删除,条件编译,define定义的符号和函数的替换。

        编译阶段:对程序按照语言特性,进行词法,语法,语义的分析,错误检查无误后生成汇编代码,编译器对工程中的多个源文件是分离开单独编译的。

       链接阶段:将多个obj文件合并成一个,并处理没有解决的地址问题。

        如图:

 

        3.3解决法 

        解决方法有两种,

        第一种是,将定义和声明放在同一个文件"xxx.hpp"里面或者"xxx.h"其实也可以。推荐使用这种。

        第二种是模板定义的位置的显示实例化。例如:

#include"Func.h"
void F1()
{
	cout << "普通函数" << endl;
}
template<class T>
T Add(const T& x1, const T& x2)
{
	return x1 + x2;
}
//显示实例化
template
int  Add<int>(const int& x1, const int& x2);

        第二种方法的缺点在于,如果想要用模板实例化出其他类型的函数时,又要在模板定义的位置显示实例化其他的类型,不够方便。 例如:

#include"Func.h"
int main()
{
	Add(2.3, 4.5);//调用两个double类型的值进行相加
	return 0;
}
#include"Func.h"
void F1()
{
	cout << "普通函数" << endl;
}
template<class T>
T Add(const T& x1, const T& x2)
{
	return x1 + x2;
}
//显示实例化
template
int  Add<int>(const int& x1, const int& x2);

//显示实例化double类型的Add函数
template
double Add<double>(const double& x1, const double& x2);

 

 4.总结

        模板的优点:

        1.增强了代码的复用性,节省资源,易于更快的开发,c++标准库(STL)因此而产生。

        2.增加了代码的灵活性。

        缺点:

        1.模板会导致代码的膨胀问题,也会导致编译时间增加(因为要通过模板实例化出具体的函数或者类)

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值