C++:模板初阶篇

目录

一.泛型编程

二.函数模板

1.函数模板概念

2.函数模板格式

 swap(a, b); 和 swap(c, d); 调用的是同一个函数吗?

3.函数模板的原理  

4.函数模板的规则(包含类模板规则)

1.例1:可推演的例子

1.例2:不可推演的例子

3.例3

4.例4

函数模板:

类模板:

4.(1)例5

4.(2)例6

5.例7

6.例8

三.类模板  

1.类模板的定义格式

2.类模板规则 请看 函数模板规则第4条

3.类模板和模板类

三.模板类相关易错题目

1.下面有关C++中为什么用模板类的原因,描述错误的是? ( )

2.关于模板的编译说法错误的是( )

3.下列的模板声明中,其中几个是正确的( )


一.泛型编程

泛型编程: 不再是针对某种类型,能适应广泛的类型,跟具体的类型无关的代码

如何实现一个通用的交换函数呢?

void Swap(int& left, int& right)
{
	int temp = left;
	left = right;
	right = temp;
}

void Swap(double& left, double& right)
{
	double temp = left;
	left = right;
	right = temp;
}
使用函数重载虽然可以实现,但是有一下几个不好的地方:
1. 重载的函数仅仅是类型不同,代码复用率比较低,只要有新类型出现时,就需要用户自己增
加对应的函数
2. 代码的可维护性比较低,一个出错可能所有的重载均出错

因此我们需要告诉编译器一个模子,让编译器根据不同的类型利用该模子来生成代码

二.函数模板


模板分为:函数模板,类模板

1.函数模板概念

函数模板代表了一个函数家族,该函数模板与类型无关,在使用时被参数化,根据实参类型产生
函数的特定类型版本。

2.函数模板格式

template<typename T>   
返回值类型 函数名(参数列表){}
说明:
1.template<class T> 和 template<typename T>在此暂时认为一样,template<struct T>是 错误的 ,没有这种写法  
注意: typename 用来定义模板参数 关键字 也可以使用 class( 切记:不能使用 struct 代替
class)
2.T是type的缩写,T不一定写T,可以写任意字母,比如X,t ……但是习惯写为T
举例: 
template<typename T>    //或template<class T>
void Swap(T& left, T& right)
{
	T tmp = left;
	left = right;
	right = tmp;
}

int main()
{
	int a = 0, b = 1;
	double c = 2.2, d = 3.3;
	swap(a, b);
	swap(c, d);

	return 0;
}

 swap(a, b); 和 swap(c, d); 调用的是同一个函数吗?

答:不是同一个。底层汇编可以看出不是同一个,如果调试时发现走的是同一个函数,其实是编译器的优化导致。

实际上以后swap函数都不用自己写了,库中有模板,直接用就行

3.函数模板的原理  

在编译器编译阶段 ,对于模板函数的使用, 编译器需要根据传入的实参类型来推演生成对应类型
的函数 以供调用。比如: 当用 double 类型使用函数模板时,编译器通过对实参类型的推演,将 T
确定为 double 类型,然后产生一份专门处理 double 类型的代码 ,对于字符类型也是如此。
模板的实例化:

4.函数模板的规则(包含类模板规则)

1.函数模板的类型一般是编译器根据实参传递给形参,推演出来的如果不能自动推演,那么我们就需要显示实例化,指定模板参数(请看例1,2)

2.一个template<typename T>  下面只能定义一个函数

3.模板参数--很多用法和函数参数是很像的,模板也可以用缺省形式(请看例3)

        模板参数 -- 传递的是类型

        函数参数 -- 传递的对象值

例如 template<class K,class V=int>(缺省只能从右往左)  

4.模板可以声明定义分离(在同一个文件),但模板不支持声明和定义放到两个文件中的。会出现链接错误。(如果声明和定义分别放在.h和.cpp文件中,由于.h的声明中T没有确定,所以就找不到调用函数的地址)

声明定义写法解决方案:第一种更好,更常用

(1)声明定义合并写到.hpp或.h文件中:模板不支持声明和定义分别放到xxx.h和xxx.cpp中,一般是要放到一个文件中。有些地方就会命名成xxx.hpp,寓意就是头文件和定义实现内容合并一起,但是并不是必须是.hpp, .h也是可以的,只是.hpp寓意更好(实际上声明定义写在一个文件就直接实例化了)(请看例5)

(2)声明定义分别放在.h和.cpp中: 需要在template.cpp中针对于要使用的模板类型显示实例化(请看例6)

5.函数模板参数类型不可以矛盾。(请看例7)

6.有现成的函数,就用现成的函数;若显示实例化给出<int>就要用模板(请看例8)

1.例1:可推演的例子

template<typename T>    
void Swap(T& left, T& right)
{
	T tmp = left;
	left = right;
	right = tmp;
}
int main()
{
	int a = 0, b = 1;
	double c = 2.2, d = 3.3;
	swap(a, b);
	swap(c, d);

	return 0;
}

1.例2:不可推演的例子

需要在函数名后显示实例化,指定模板参数(即加尖括号)

template<class T>  
T* func(int n)
{
	return new T[n];
}
int main()
{
	// 函数模板显示实例化
	int* p1 = func<int>(10);
	double* p2 = func<double>(10);

	return 0;
}

3.例3

(1)模板也可以用缺省形式:template<class T = char>  不显示实例化时就是char类型

template<class T = char>  
T* func(int n)
{
	return new T[n];
}
int main()
{
	// 函数模板显示实例化
	int* p1 = func<int>(10);
	double* p2 = func<double>(10);     

    char* p3 = func(10);     //不显示实例化时就是char类型
	return 0;
}

(2)缺省只能从右往左

template<class K,class V=char>
void Func()
{
	cout << sizeof(K) << endl;
	cout << sizeof(V) << endl;
}
int main()
{
	Func<int,int>();
	Func<int>();

	return 0;
}

(3)全缺省

template<class K=char,class V=char>
void Func()
{
	cout << sizeof(K) << endl;
	cout << sizeof(V) << endl;
}
int main()
{
	Func<int,int>();
	Func<int>();
    Func();
	return 0;
}

4.例4

模板可以声明定义分离,但模板不支持声明和定义放到两个文件中的。会出现链接错误。

函数模板:

// 函数模板声明
template<typename T>
void Swap(T& left, T& right);
// 函数模板定义
template<typename T>
void Swap(T& left, T& right)
{
	T temp = left;
	left = right;
	right = temp;
}

类模板:

// 类模板声明
template<class T>
class Vector
{
public:
	Vector(size_t capacity = 10);
	void PushBack(const T& x);
private:
	T* _pData;
	size_t _size;
	size_t _capacity;
};
// 类模板定义
template<class T>
Vector<T>::Vector(size_t capacity)
	: _pData(new T[capacity])
	, _size(0)
	, _capacity(capacity)
{}

4.(1)例5

声明和定义放到同一个文件中,这个文件后缀写成.hpp,寓意就是头文件和定义实现内容合并一起,其实这个文件写成.h也是可以正常用的,只是.hpp寓意更明显。

​template.hpp中:

// 类模板声明
template<class T>
class Vector
{
public:
	Vector(size_t capacity = 10);
	void PushBack(const T& x);
private:
	T* _pData;
	size_t _size;
	size_t _capacity;
};
// 函数模板声明
template<typename T>    
void Swap(T& left, T& right);

// 类模板定义
template<class T>
Vector<T>::Vector(size_t capacity)
	: _pData(new T[capacity])
	, _size(0)
	, _capacity(capacity)
{}

template<class T>   //声明定义分开写,每个函数都要加 template<class T> 
void Vector<T>::PushBack(const T& x)
{
	// ...
}


// 函数模板定义
template<typename T>
void Swap(T& left, T& right)
{
	T temp = left;
	left = right;
	right = temp;
}

test.c中:  
#include "template.hpp"     //就可以正常使用了

int main()
{
	int a = 0, b = 1;
	Swap(a, b);

	Vector<int> v1;
	Vector<double> v2;
	return 0;
}

细节注意:类中每个函数定义时都要前面加上template<class T> ,且要指定类域,这个类域还要加上模板,比如里面的PushBack函数:

template<class T> 
void Vector<T>::PushBack(const T& x)
{
	// ...
}

4.(2)例6

声明定义分开在两个文件,只需要在.cpp文件中 显示实例化指定 要使用的类型

template.cpp中:
#include "template.h"
template<typename T>
void Swap(T& left, T& right)
{
	T temp = left;
	left = right;
	right = temp;
}

template<class T>
Vector<T>::Vector(size_t capacity)
	: _pData(new T[capacity])
	, _size(0)
	, _capacity(capacity)
{}
 //显示实例化指定
template
void Swap<int>(int& left, int& right);    //显示实例化指定

template
class Vector < int > ;         //显示实例化指定

template
class Vector < double > ;     //显示实例化指定

5.例7

如果参数类型一个传的是整形,另一个传的是浮点型就会报错,①需要把它们全部显示实例化成一个类型,②或改变其中一个参数类型,使整体类型相同

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

int main()
{
	int a1 = 10, a2 = 20;
	double d1 = 10.0, d2 = 20.0;
	Add(a1, a2);	//正常情况
	Add(d1, d2);

	//Add(a1, d2);	这样是推不出来的
	//解决方案:
	Add<int>(a1, d2);
	Add<double>(a1, d2);

	Add(a1, (int)d2);
	Add((double)a1, d2);
}

6.例8

6.有现成的函数,就用现成的函数;若显示实例化给出<int>就要用模板

// 专门处理int的加法函数
int Add(int left, int right)
{
	return left + right;
}

// 通用加法函数
template<class T>
T Add(T left, T right)
{
	return left + right;
}

void Test()
{
	Add(1, 2);		// 与非模板函数匹配,直接用现成的
	Add<int>(1, 2);	// 显示实例化,用模板
}

三.类模板  

1.类模板的定义格式

template < class T1 , class T2 , ..., class Tn 可以有多个:T1,T2......不是必须多个
class 类模板名
{
// 类内成员定义
};
类模板的类型显示实例化(类名后加<>),明确指定的
Vector<int> v1;
Vector <double> v2;

举例:调用 Stack<int> st1; 时,用int替换模板中的T。调用 Stack<double> st1; 时,用double替换模板中的T

// 类模板
template<class T>
class Stack
{
public:
	Stack(int capacity = 0)
	{
		_a = new T[capacity];
		_capacity = capacity;
		_top = 0;
	}

	~Stack()
	{
		cout << "~Stack()" << endl;
		delete[] _a;
		_capacity = 0;
		_top = 0;
	}

	void Push(const T& x)
	{}
private:
	T* _a;
	int _top;
	int _capacity;
};

int main()
{
	Stack<int> st1; // int
	st1.Push(1);

	Stack<double> st2; // double
	st2.Push(2.2);

	return 0;
}

2.类模板规则 请看 函数模板规则第4条

2.类模板中的成员函数全部是模板函数。(假设类中不用虚拟类型(模板类型),只用内置类型,然后我给一个打印函数只打印内置类型,这个打印函数虽然没有用模板类型,但它仍是模板函数)

3.类模板和模板类

类模板是一个类家族,模板类是通过类模板实例化的具体类

模板类是一个家族,编译器的处理会分别进行两次编译,其处理过程跟普通类不一样

三.模板类相关易错题目

1.下面有关C++中为什么用模板类的原因,描述错误的是? ( )

A.可用来创建动态增长和减小的数据结构

B.它是类型无关的,因此具有很高的可复用性

C.它运行时检查数据类型,保证了类型安全

D.它是平台无关的,可移植性

解:

A.模板可以具有非类型参数,用于指定大小,可以根据指定的大小创建动态结构

B.模板最重要的一点就是类型无关,提高了代码复用性

C.模板运行时不检查数据类型,也不保证类型安全,相当于类型的宏替换,故错误

D.只要支持模板语法,模板的代码就是可移植的

选C。

2.关于模板的编译说法错误的是( )

A.模板在.h文件中声明,在.cpp里面实现

B.模板程序一般直接在一个文件里面进行定义与实现

C.不久的将来,编译器有望支持export关键字,实现模板分离编译

D.模板不能分离编译,是因为模板程序在编译过程中需要经过两次编译

解:

A.模板不支持分离编译,所以不能在.h声明,在.cpp实现

B.由于不支持分离编译,模板程序一般只能放在一个文件里实现

C.不支持分离编译并不是语法错误,而是暂时的编译器不支持,不久将来,或许会被支持

D.模板程序被编译两次,这是不能分离编译的原因所在。选A。

3.下列的模板声明中,其中几个是正确的( )

1)template

2)template<T1,T2>

3)template<class T1,T2>

4)template<class T1,class T2>

5)template<typename T1,T2>

6)template<typename T1,typename T2>

7)template<class T1,typename T2>

8)<typename T1,class T2>

9)template<typename T1, typename T2, size_t N>

10)template<typename T, size_t N=100, class _A=alloc<T>>

11)template<size_t N>

A.3

B.4

C.5

D.6

答案:——>

分析:正确的定义为:4 6 7 9 10 11,一共6个,故答案为D。(第11个 template<size_t N> 也是正确的,模板可以是类型模板参数,也可以是非类型模板参数<单独的也行>,template<typename T1, typename T2, size_t N> 中 T1和T2就是类型模板参数,N是非类型模板参数)

  • 13
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 11
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值