初识模板详解--C++

目录

前言

函数模板

定义

格式

实例

函数模板的原理

函数模板的实例化

隐式实例化

显式实例化

模板参数的匹配原则

类模板

定义

格式

实例

 类模板的实例化

模板的分离编译

定义

模板声明与定义分离

链接错误解析

解决方案

总结 


前言

我们以前在写代码的时候会遇到功能相似,代码内容基本一样的函数,唯一的不同点可能算是参数和返回值的不同。

最简单的例子就是不同类型数据的求和(Add)等数学函数。学习C语言的时候我们遇到这种情况无非是把函数名改一改,参数改一改,多写几个函数,顶多再整一个函数指针数组来方便使用。这种做法显得非常笨,但显然这是语言限制了我们。

学习C++时我们接触到了重载函数,使函数名不用再变来变去,增强了可读性,但仍然要写多个函数,依然不够方便。

事实上,这些函数既然功能相似,代码内容相差不大,能不能写一个模板,把相同的代码保留下来,不同的部分用一个可变的符号来代替?答案是可以,C++后来为了解决这个问题,创造了模板这个东西,浅显来看,就是一个模子,你需要什么类型的,就给你创造什么类型的,非常人性化。

函数模板

定义

函数模板就是一类函数的集合,根据使用者所传的参数类型,来生成相应类型的代码,构成相应类型的函数供使用者使用。

格式

template<typename T1, typename T2, ... , typenameTn>

返回值类型 函数名(参数列表){函数内容}

实例

1.

#include<iostream>
using namespace std;
template<typename T>
T Add(const T& a, const T& b)
{
	return a + b;
}
int main()
{
	cout << Add(1, 2) << endl;
	cout << Add(1.1, 2.2) << endl;

	return 0;
}

 

2. 如果是两种不同的类型数据呢?简单,就用两个typename呗!

#include<iostream>
using namespace std;
template<typename T1, typename T2>
T1 Add(const T1& a, const T2& b)
{
	return a + b;
}
int main()
{
	cout << Add(1.1, 2) << endl;
	cout << Add(1, 2.2) << endl;

	return 0;
}

 

 当然了,如果涉及到不同类型的之间的转换,可能返回值会精度丢失,这个就得使用者自己去限制参数的传参顺序了。

 

3.事实上,模板也同样支持缺省功能。

#include<iostream>
using namespace std;
template<typename T1 = int , typename T2 = int>//全缺省
T1 Add(const T1& a, const T2& b)
{
	return a + b;
}
template<typename T1 = int, typename T2 = double>
void Func()
{
	cout << sizeof(T1) << endl;
	cout << sizeof(T2) << endl<< endl;
}
int main()
{
	Func();
	Func<double, int>();//<double , int>为显式实例化,下面的也是,后面会有讲到
	Func<double>();
	return 0;
}

 

函数模板的原理

在程序编译的过程中,会将函数模板作为蓝图,根据后面的代码所用到的数据类型来推演生成对应的函数。用到多少种类型的数据,就会生成多少个函数。

函数模板的实例化

用不同类型的参数使用函数模板时,称为函数模板的实例化。

模板参数实例化分为:隐式实例化 和显式实例化。

隐式实例化

让编译器根据实参推演模板参数的实际类型

#include<iostream>
using namespace std;
template<typename T>
T Add(const T& a, const T& b)
{
	return a + b;
}
int main()
{
	cout << Add(1, 2) << endl;
	cout << Add(1.1, 2.2) << endl;

	return 0;
}

Add(1,2)就是隐式实例化,参数传的是1和2,是const int型数据,因此编译器会自动推演生成int型的Add函数。

显式实例化

在函数名后的<>指定模板参数的实际类型

#include<iostream>
using namespace std;
template<typename T>
T Add(const T& a, const T& b)
{
	return a + b;
}
int main()
{
	int a = 10;
	double b = 2.5;
	cout << Add<int>(a, b) << endl;//显式指定为int型
	cout << Add<double>(a, b) << endl;//显式指定为double型
	return 0;
}

 不同的显式指定造成了不同的结果。

模板参数的匹配原则

一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数,对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调用非模板函数而 不会从该模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数, 那么将选择模板。

#include<iostream>
using namespace std;
int Add(int a,int b)
{
    return a + b;
}
template<typename T1 = double , typename T2 = double>
T2 Add(const T1& a, const T2& b)
{
	return a + b;
}
int main()
{
    cout << Add(1, 2) << endl;
    cout << Add(2.3, 2.2) << endl;
	return 0;
}

类模板

定义

类模板和函数模板的定义相似,在类的前面加可变类型参数T。是一系列不同类型数据的类的集合。

格式

template<class T1, class T2, ... , calss Tn>

类模板名

{

// 类内成员定义

};    

实例

1.

#include<iostream>
using namespace std;
template<class T = int>
class A
{
public:
	A(T a = 10)
		:_a(a)
	{
		_p = new T[10];
	}
	~A()
	{
		delete[] _p;
	}
private:
	T _a;
	T* _p;
};
int main()
{
	A<double>* pa=new A<double>[10];//类模板的实例化,后面会讲
	delete[] pa;
	return 0;
}

 

2.类模板中函数放在类外进行定义时,需要加模板参数列表

#include<iostream>
using namespace std;
template<class T = int>
class A
{
public:
	A(T a = 10)
		:_a(a)
	{
		_p = new T[10];
	}
	~A()
	{
		delete[] _p;
	}
	void Print();
private:
	T _a;
	T* _p;
};

template<class T>//这里注意不能写成缺省,直接写成T就行
void A<T>::Print()  //加模板参数列表T
{
	cout << _a << endl;
}

int main()
{
	A<> a(5);
	a.Print();
	return 0;
}

 类模板的实例化

类模板实例化与函数模板实例化不同,类模板实例化需要在类模板名字后跟<>,然后将实例化的 类型放在<>中即可,类模板名字不是真正的类,而实例化的结果才是真正的类。

#include<iostream>
using namespace std;
template<class T = int>
class A
{
public:
	A(T a = 10)
		:_a(a)
	{
		_p = new T[10];
	}
	~A()
	{
		delete[] _p;
	}
	void Print();
private:
	T _a;
	T* _p;
};
template<class T>
void A<T>::Print()
{
	cout << _a << endl;
}
int main()
{
	A<int> a(5);
	a.Print();

	A<double> b(5.5);
	b.Print();
	return 0;
}

注:其中A是类名,A<int>A<double> 才是类型 

模板的分离编译

定义

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

模板声明与定义分离

如果我们把一个模板的声明放在头文件中,把定义放在源文件中,结果会怎么样呢?

//Add.h
#pragma once
using namespace std;
template<typename T1 = double, typename T2 = double>
T2 Add(const T1& a, const T2& b);


//Add.cpp
#include"Add.h"
template<typename T1 = double, typename T2 = double>
T2 Add(const T1& a, const T2& b)
{
    return a + b;
}


//test.cpp
#include<iostream>
#include"Add.h"
int main()
{
    cout << Add(1, 2) << endl;
    cout << Add(2.3, 2.2) << endl;

    return 0;
}

链接错误解析

 可以看出此时发生了链接错误,原因何在?

实际上理解起来也并不麻烦,在头文件中声明不定义,代码预编译时展开头文件只有函数的声明在 test,cpp 中展开,而 Add.cpp 中的模板函数没有实例化,编译器也不知道要生成哪个数据类型的函数,也就是实例化不明确。这时在 test,cpp 中调用Add函数时就会发生这样一件事:找到了模板的地址,但找不到对应实例化过的相应类型的函数地址。因此链接错误也就不可避免了。

解决方案

1.将声明和定义放到一个文件 "xxx.hpp" 里面或者xxx.h中,这样在预编译时就会在对应的原文件中展开,从而使模板实例化与定义在同一个文件中,编译器才会推演生成对应的函数。

//Add.h
#pragma once
using namespace std;
template<typename T1 = double, typename T2 = double>
T2 Add(const T1& a, const T2& b)
{
    return a + b;
}



//test.cpp
#include<iostream>
#include"Add.h"
int main()
{
    cout << Add(1, 2) << endl;
    cout << Add(2.3, 2.2) << endl;

    return 0;
}

 

 

2.模板定义的位置显式实例化。相当于手动告诉编译器要生成怎样类型的函数了。这样做虽然也可以解决问腿,但是一旦数据类型很多或是比较复杂使,实际操作起来并不方便。

//Add.h
#pragma once
using namespace std;
template<typename T1 = double, typename T2 = double>
T2 Add(const T1& a, const T2& b);


//Add.cpp
#include"Add.h"
template<typename T1 = double, typename T2 = double>
T2 Add(const T1& a, const T2& b)
{
    return a + b;
}
//显示实例化
template
int Add<int , int>(const int& a,const int& b);

template
double Add<double ,double>(const double& a, const double& b);


//test.cpp
#include<iostream>
#include"Add.h"
int main()
{
    cout << Add(1, 2) << endl;
    cout << Add(2.3, 2.2) << endl;

    return 0;
}

总结 

 模板的优点:

1.模板复用了代码,节省资源。

2.增强了代码的灵活性

模板的缺点:

1.模板会导致代码膨胀问题,也会导致编译时间变长。这与模板的底层原理相关,缺点无法逆转,算是在优点之下的一种妥协了。

2.出现模板编译错误时,错误信息非常凌乱,不易定位错误。因为模板的实例化是在使用到时才会进行,编译错误使编译器报错可能没有那么人性化,因此常常会出现一些奇奇怪怪的错误提示。

模板的内容很多,一下子也不好把握全部内容,这篇博客仅仅对模板初学的一些情况做了阐述,内容多有偏失的地方还请给位读者老爷指正。我们下篇博客见~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值