c++核心技术07(函数模板和类模板)

目录

  • 模板概论
  • 为什么要有函数模板
  • 函数模板语法
  • 函数模板和函数重载
  • 函数模板调用机制
  • 类模板的定义和调用
  • 类模板和继承
  • 类模板的三种写法
    • 所有的类模板函数写在类的内部
    • 所有的类模板函数写在类的外部,在一个cpp里
    • 所有的类模板函数写在类的外部,在不同的.h和.cpp中
    • 特殊情况 友元函数
  • 类模和static数据成员
  • 类模板使用总结

模板概论

  c++提供了函数模板(function template.)所谓函数模板,实际上是建立一个通用函数,其函数类型和形参类型不具体制定,用一个虚拟的类型来代表。这个通用函数就成为函数模板。凡是函数体相同的函数都可以用这个模板代替,不必定义多个函数,只需在模板中定义一次即可。在调用函数时系统会根据实参的类型来取代模板中的虚拟类型,从而实现不同函数的功能。
  总结:

  1. 模板把函数或类要处理的数据类型参数化,表现为参数的多态性,成为类属。
  2. 模板用于表达逻辑结构相同,但具体数据元素类型不同的数据对象的通用行为。

为什么要有函数模板

  下面我们来看这个代码:
  实现多个函数用来返回两个数的最大值,要求能支持 char 类型、int 类型、double:

#include 
using namespace std;
int Max(int a, int b)
{
	return a>b ? a:b;
}
char Max(char a, char b)
{
	return a>b ? a:b;
}
float Max(float a, float b)
{
	return a>b ? a:b;
}
int main()
{
	//char a = 'c';
	int x = 1;
	int y = 2;
	cout<<"max(1, 2) = "<<Max(x, y)<<endl;
	float a = 2.0;
	float b = 3.0;
	cout<<"max(2.0, 3.0) = "<<Max(a, b)<<endl;
	return 0;
}

  由此代码我们可以看出,实际上我们不需要写那么多函数,只需要一个模板即可,那么接下来就是我们对这个代码进行的改良。
  代码如下:

#include 
using namespace std;
//int Max(int a, int b)
//{
//	return a > b ? a : b;
//}
//char Max(char a, char b)
//{
//	return a > b ? a : b;
//}
//float Max(float a, float b)
//{
//	return a > b ? a : b;
//}
template <typename T>//template:模板 <>:泛型编程  T:类型的名字,可以替代任何类型
T Max(T a, T b)
{
	return a > b ? a : b;
}

//如果T使用int 类型调用,就相当于调用这个函数
//int Max(int a, int b)
//{
//	return a > b ? a : b;
//}

int main()
{
	//char a = 'c';
	int x = 1;
	int y = 2;
	cout << "max(1, 2) = " << Max(x, y) << endl;
	cout << "max(1, 2) = " << Max<int>(x, y) << endl; //显示类型调用
	float a = 2.0;
	float b = 3.0;
	cout << "max(2.0, 3.0) = " << Max(a, b) << endl;
	return 0;
}

函数模板语法

  所谓函数模板,实际上是建立一个通用函数,其函数类型和形参类型不具体指
定,用一个虚拟的类型来代表。这个通用函数就称为函数模板。凡是函数体相同的函数都可以用这个模板来代替,不必定义多个函数,只需在模板中定义一次即可。在调用函数时系统会根据实参的类型来取代模板中的虚拟类型,从而实现了不同函数的功能。
  函数模板定义形式:
  由以下三部分组成: 模板说明 + 函数定义 + 函数模板调用
  template < 类型形式参数表 >
  类型 函数名 (形式参数表)
  {
    //语句序列
  }

  注意: 函数模板的参数可以有多个,但是如果定义多个一定要使用,不然会报错!typename写成class也是可以的,但是为了避免混淆,建议还是写成typename。
  例如下面修改之后的代码:

#include 
using namespace std;
//int Max(int a, int b)
//{
//	return a > b ? a : b;
//}
//char Max(char a, char b)
//{
//	return a > b ? a : b;
//}
//float Max(float a, float b)
//{
//	return a > b ? a : b;
//}
template <typename T, typename T2>//template:模板 <>:泛型编程  T:类型的名字,可以替代任何类型
T Max(T a, T2 b)
{
	return a > b ? a : b;
}

int main()
{
	char c = 'c';
	int x = 1;
	int y = 2;
	cout << "max(1, c) = " << Max(x, c) << endl;
	cout << "max(1, c) = " << Max<int, char>(x, c) << endl;//显示类型调用
	cout << "max(1, 2) = " << Max<int>(x, y) << endl; //写一个可以自动推导
	float a = 2.0;
	float b = 3.0;
	cout << "max(2.0, 3.0) = " << Max<float>(a, b) << endl;
	return 0;
}

  那么讲到了函数模板,我们可能以后会听到模板函数这个概念,那么什么是模板函数呢?那么请看下面这张图,以这张图为例,当我们调用这个函数Max(1,2)的时候,实际上是用int Max(int a,int b)来编译的,而这个通过模板生成的函数,就叫做模板函数。
c++核心技术07(函数模板和类模板)_第1张图片
  那么如果我们传一个类进去呢?实际上也会生成一个demo Max(demo a,demo b)的模板函数,但是要重载一下大于运算符,这样编译才不会报错。详见代码如下:

#include 
using namespace std;

class demo
{
public:
	demo(int _k = 0){k = _k;}
	~demo(){}
	int value(){return k;}
	bool operator>(demo& dest) 
	{
		if (this->k > dest.k) return true;
		else return false;
	}
private:
	int k;
};
//int Max(int a, int b)
//{
//	return a > b ? a : b;
//}
//char Max(char a, char b)
//{
//	return a > b ? a : b;
//}
//float Max(float a, float b)
//{
//	return a > b ? a : b;
//}
template <typename T>//template:模板 <>:泛型编程  T:类型的名字,可以替代任何类型
T Max(T a, T b)
{
	return a > b ? a : b;
}

//如果T使用int 类型调用,就相当于调用这个函数
//int Max(int a, int b)
//{
//	return a > b ? a : b;
//}

int main()
{
	//char a = 'c';
	int x = 1;
	int y = 2;
	cout << "max(1, 2) = " << Max(x, y) << endl; //实现参数类型的自动推导
	cout << "max(1, 2) = " << Max<int>(x, y) << endl; //显示类型调用

	float a = 2.0;
	float b = 3.0;
	cout << "max(2.0, 3.0) = " << Max(a, b) << endl;
	
	demo d1(10);
	demo d2(11);
	cout << "max(d1,d2) = " << Max(d1,d2).value() << endl;

	return 0;
}

函数模板和函数重载

  首先呢,我们先看下面的这个例子:

#include 

using namespace std;

template <typename T,typename T2>
T Swap(T2& a, T2& b)
{
	T t;
	t = a;
	a = b;
	b = t;
	cout << "Swap模板函数被调用了" << endl;
}

//void Swap(char& a, int& b)
//{
//	int t;
//	t = a;
//	a = b;
//	b = t;
//	cout << "普通函数被调用了" << endl;
//}

int main(void)
{
	char cNum = 'c';
	char iNum = 65;

	//第一种情况,模板函数和普通函数并存,参数类型和普通重载函数更匹配
	//调用普通函数
	//Swap(cNum,iNum);

	//第二种情况,不存在普通函数,函数模板会隐式数据类型转换嘛
	//结论:不提供隐式的数据类型转换,必须是严格的匹配
	//Swap(cNum, iNum);//如果一定要通过编译,一定要声明两种不同的数据类型
	

	return 0;
}

  根据代码可知,函数模板和普通函数区别结论:
  1.两者允许并存
  2.函数模板不允许自动类型转化
  3.普通函数能够进行自动类型转换
  接下来我们再举一个例子:

#include 

using namespace std;

int Max(int a, int b)
{
	cout << "调用int Max(int a,int b)" << endl;
	return a > b ? a : b;
}

template <typename T>
T Max(T a, T b)
{
	cout << "调用T Max(T a,T b)" << endl;
	return a > b ? a : b;
}

//函数模板可以嵌套调用
template <typename T6;........................................................................................... 8>
T Max(T a, T b,T c)
{
	cout << "调用T Max(T a,T b,T c)" << endl;
	return Max(Max(a, b), c);
}

template <typename T1,typename T2>
T1 Max1(T1 a, T2 b)
{
	cout << "调用T Max(T a,T b)" << endl;
	return a > b ? a : b;
}

int main()
{
	int a = 65;
	int b = 66;

	//当函数模板和普通函数都符合调用时,优先选择普通函数
	cout << "Max(a,b) = " << Max(a, b) << endl;
	
	//如果显示的使用函数模板,则使用<>类型列表,里面类型可加可不加
	Max<>(a,b);

	//如果函数模板会产生更好的匹配,会使用函数模板
	Max(1.0, 2.0);

	char c = 'a';
	Max(c, a);

	Max(3.0, 4.0, 5.0);
	return 0;
}

  所以由此我们得出结论,函数模板和普通函数在一起,调用规则:
  1.函数模板可以像普通函数一样被重载
  2.c++编译器优先考虑普通函数
  3.如果函数模板可以产生一个更好的匹配,那么选择模板
  4.可以通过空模板实参列表的语法限定编译器只通过模板匹配


函数模板调用机制

  下面看如下代码:

#include 

using namespace std;

template <typename T>
T Max(T a, T b)
{
	return a > b ? a : b;
}

int main()
{
	int x = 1;
	int y = 2;
	Max(x, y);

	return 0;
}

  该代码通过反汇编(g++ -S 1.cpp -o 1.S)观察之后,得出下图:
c++核心技术07(函数模板和类模板)_第2张图片

  我们再举一个例子,代码如下:

#include 

using namespace std;

template <typename T>
T Max(T a, T b)
{
	return a > b ? a : b;
}

int main()
{
	int x = 1;
	int y = 2;
	Max(x, y);
	
	float a = 2.0;
	float b = 3.0;
	Max(a,b);
	return 0;
}

  通过反汇编观察得到如下图:
c++核心技术07(函数模板和类模板)_第3张图片
c++核心技术07(函数模板和类模板)_第4张图片
  由此我们可以得出以下结论:
  1. 编译器并不是把函数模板处理成能够处理任意类型的函数
  2.编译器从函数模板通过具体类型产生不同的函数


类模板的定义和调用

  我们为什么需要使用类模板呢?类模板与函数模板的定义和使用类似,有时,有两个或多个类,其功能是相同的,仅仅是数据类型不同,我们可以通过如下语句声明了一个类模板:

template < typename T>
class A
{
public:
   A(T t)
   {
      this->t = t;
   }
   T &getT()
   {
      return t;
   }
private:
   T t;
};

  类模板定义
  类模板由模板说明和类说明构成:
  模板说明同函数模板,如下:
   template <类型形式参数>
   类声明
   例如如下的例子:

template < typename Type>
class ClassName
{
   //ClassName的成员函数
private:
   Type DataMember;
};

  下面就是类模板的使用了,如下代码所示:

#include 

using namespace std;

template <typename T>
class A
{
public:
	//函数的参数列表使用虚拟类型
	A(T t = 0) 
	{
		this->t = t;
	}

	//成员函数返回值使用虚拟类型
	T& getT()
	{
		return t;
	}
private:
	//成员变量使用虚拟类型
	T t;
};

//以上三个地方任意一个使用虚拟类型编译都能通过,当然三个都使用也是没问题的

void printA(A<int> &a)
{
	cout << a.getT() << endl;
}

int main()
{
	//1、类模板定义对象必须显示指定类型
	//2、模板中如果使用了构造函数,则遵守以前的类的构造函数的调用规则
	A<int> a(666);
	cout << a.getT() << endl;

	//模板类作为函数参数
	printA(a);
	return 0;
}

类模板和继承

#include 

using namespace std;

//继承中父子类和模板类的结合情况
//1.父类一般类,子类是模板类,和普通继承的玩法类似
//2.子类是一般类,父类是模板类,继承时必须在子类里实例化父类的类型参数
//3.父类和子类都是模板类时,子类的虚拟的类型可以传递到父类中

/*class B
{
public:
	B(int b)
	{
		this->b = b;
	}
private:
	int b;
};
*/
template <typename T>
class A
{
public:
	//函数的参数列表使用虚拟类型
	A(T t)
	{
		this->t = t;
	}
	//成员函数返回值使用虚拟类型
	T &getT()
	{
		return t;
	}
private:
	//成员变量使用虚拟类型
	T t;
};

template <typename Tb>
class B: public A<int>
{
public:
	B(Tb b):A<Tb>(b) //如果父类构造函数有默认参数,那么不显示指定也没问题,但是如果把父类构造函数的=0去掉,就一定要这么写了
	{
		this->b = b;
	}
private:
	Tb b;
};

void printA(A<int> &a)
{
	cout << a.getT() << endl;
}

int main(void)
{
	//1.模板类定义类对象,必须显示指定类型
	//2.模板中如果使用了构造函数,则遵守以前的类的构造函数的调用规则
	A<int> a(666);
	cout << a.getT() << endl;
	
	B<int> b(888);
	cout << "b(888): " << b.getT() << endl;

	//模板类作为函数参数
	printA(a);
	return 0;
}

  所以,通过上述代码可知,子类从模板类继承的时候,需要让编译器知道,父类的数据类型具体是什么


类模板的三种写法

所有的类模板函数写在类的内部

  上面已讲解

所有的类模板函数写在类的外部,在一个cpp里

#include 

using namespace std;

template <typename T>
class A
{
public:
	A(T t = 0);

	T& getT();

	A operator +(const A& other);

	void print();
private:
	T t;
};

template <typename T>
A<T>::A(T t)
{
	this->t = t;
}

template <typename T>
T& A<T>::getT()
{
	return t;
}

template <typename T>
A<T> A<T>::operator+(const A<T>& other)
{
	A<T> tmp;//类的内部类型可以显示声明也可以不显示
	tmp.t = this->t + other.t;
	return tmp;
}

template <typename T>
void A<T>::print()
{
	cout << this->t << endl;
}

int main(void)
{
	A<int> a(666),b(888);
	cout << a.getT() << endl;

	A<int> tmp = a + b;
	tmp.print();
	return 0;

	return 0;
}

  在同一个cpp文件中把模板类的成员函数放到类的外部,需要注意以下几点:

  1. 函数前声明template <类型形式参数表>
  2. 类的成员函数前的类限定域说明必须要带上虚拟参数列表
  3. 返回的变量是模板类的对象时必须带上虚拟参数列表
  4. 成员函数参数中出现模板类的对象必须带上虚拟参数列表
  5. 成员函数内部没有限定

所有的类模板函数写在类的外部,在不同的.h和.cpp中

demo.h:

#pragma once

template <typename T>
class A
{
public:
	A(T t=0);
	T &getT();
	A operator +(const A &other);
	void print();
private:
	T t;
};

demo.c:

#include "demo.h"
#include 

using namespace std;

template <typename T>
A<T>::A(T t)
{
	this->t = t;
}

template <typename T>
T& A<T>::getT()
{
	return t;
}

template <typename T>
A<T> A<T>::operator+(const A<T>& other)
{
	A<T> tmp;//类的内部类型可以显示声明也可以不显示
	tmp.t = this->t + other.t;
	return tmp;
}

template <typename T>
void A<T>::print()
{
	cout << this->t << endl;
}

int main(void)
{
	A<int> a(666),b(888);
	cout << a.getT() << endl;

	A<int> tmp = a + b;
	tmp.print();
	return 0;

	return 0;
}

  注意: 当类模板的声明(.h文件)和实现(.cpp或hpp文件)完全分离,因为类模板的特殊实现,我们应该在使用类模板时使用#include包含实现部分.cpp或hpp文件。代码如下:

demo.h:

#pragma once

template <typename T>
class A
{
public:
	A(T t=0);
	T &getT();
	A operator +(const A &other);
	void print();
private:
	T t;
};

demo.hpp:(一般在c++编译器中,对类模板的实现命名为.hpp)

#include "demo.h"
#include 

using namespace std;

template <typename T>
A<T>::A(T t)
{
	this->t = t;
}

template <typename T>
T& A<T>::getT()
{
	return t;
}

template <typename T>
A<T> A<T>::operator+(const A<T>& other)
{
	A<T> tmp;//类的内部类型可以显示声明也可以不显示
	tmp.t = this->t + other.t;
	return tmp;
}

template <typename T>
void A<T>::print()
{
	cout << this->t << endl;
}

main.cpp:

#include "demo.hpp" //一定要包含这个,而不是“demo.h”

int main(void)
{
	A<int> a(666),b(888);
	cout << a.getT() << endl;

	A<int> tmp = a + b;
	tmp.print();
	return 0;

	return 0;
}

特殊情况 友元函数

  在类里面写一个友元函数,代码如下:

#include 

using namespace std;

template <typename T>
class A
{
public:
	A(T t = 0);

	//声明一个友元函数,实现对两个A类对象进行加法操作
	template <typename T>
	friend A<T> addA(const A<T>& a, const A<T>& b);//在类里面要用友元函数一定要带声明,因为友元不属于类
	T& getT();

	A operator +(const A& other);

	void print();
private:
	T t;
};


template <typename T>
A<T>::A(T t)
{
	this->t = t;
}

template <typename T>
T& A<T>::getT()
{
	return t;
}

template <typename T>
A<T> A<T>::operator+(const A<T>& other)
{
	A<T> tmp;//类的内部类型可以显示声明也可以不显示
	tmp.t = this->t + other.t;
	return tmp;
}

template <typename T>
void A<T>::print()
{
	cout << this->t << endl;
}

//A 类的友元函数
template <typename T>
A<T> addA(const A<T>& a, const A<T>& b)
{
	A<T> tmp;
	cout << "call addA()..." << endl;
	tmp.t = a.t + b.t;
	return tmp;
}

int main(void)
{
	A<int> a(666), b(888);
	cout << a.getT() << endl;

	A<int> tmp = a + b;
	A<int> tmp1 = addA<int>(a, b);

	tmp.print();
	tmp1.print();

	return 0;
}

  所以结论如下:
  1.类内部声明友元函数,必须要写一下形式
  template < typename T >
  friend A< T > addA(A< T > &a,A< T > &b);
  2.友元函数实现,必须写成
  template < typename T >
  A< T > addA(A< T > &a,A< T > &b)
  {
    //…
  }
  3.友元函数调用必须写成
  A< int > c4 = addA< int >(c1,c2);


类模和static数据成员

  代码如下:

#include 

using namespace std;

template <typename T>
class A
{
public:
	A(T t = 0);

	//声明一个友元函数,实现对两个A类对象进行加法操作
	template <typename T>
	friend A<T> addA(const A<T>& a, const A<T>& b);//在类里面要用友元函数一定要带声明,因为友元不属于类
	T& getT();

	A operator +(const A& other);

	void print();
public:
	static int count;
private:
	T t;
};
template <typename T> int A<T>::count = 666;

template <typename T>
A<T>::A(T t)
{
	this->t = t;
}

template <typename T>
T& A<T>::getT()
{
	return t;
}

template <typename T>
A<T> A<T>::operator+(const A<T>& other)
{
	A<T> tmp;//类的内部类型可以显示声明也可以不显示
	tmp.t = this->t + other.t;
	return tmp;
}

template <typename T>
void A<T>::print()
{
	cout << this->t << endl;
}

//A 类的友元函数
template <typename T>
A<T> addA(const A<T>& a, const A<T>& b)
{
	A<T> tmp;
	cout << "call addA()..." << endl;
	tmp.t = a.t + b.t;
	return tmp;
}

int main(void)
{
	A<int> a(666), b(888);
	A<int> tmp = a + b;
	
	A<float> c(777), d(999);
	a.count = 888;

	cout << b.count << endl;//共享一个静态成员

	cout << "c.count:" << c.count << " " << "d.count:" << d.count << endl;//因为是两个不同的类
	return 0;
}

  总结:
  1.从类模板实例化的每个模板类都有自己的类模板数据成员,该模板类的所有对象共享一个satic数据成员
  2.和非模板类的static数据成员一样,模板类的static数据成员也应该在文件范围定义和初始化
  3.static数据成员也可以使用虚拟类型参数T


类模板使用总结

  1) 先写出一个实际的类
  2)将此类中准备改变的类型名(如int要改变成float或char)改成一个自己制定的虚拟类型名(如上例中的T)
  3)在类声明前面加入一行,格式为
  template < typename 虚拟类型参数 >
  如:template < typename 虚拟类型参数>
  class A
  {
  };//类体
  4)用类模板定义对象时用以下参数:
  类型板名<实际类形名>对象名;
  或者,类模板名<实际类型名>对象名(实参表列);
  如:
  A< int >cmp;
  A< int >cmp(3,7);
  5) 如果在类模板外定义成员函数,应写成类模板形式:
  template
  函数类型 类模板名<虚拟类型参数>::成员函数名(函数参数表列){…}

你可能感兴趣的:(c++,开发语言)