C++ 函数模板和类模板

1 函数模板

1.1 为什么要有函数模板

前言:
C++提供了函数模板(function template)。所谓函数模板,实际上是建立一个通用函数,其函数类型和形参类型不具体指定,用一个虚拟的类型来代表。这个通用函数就称为函数模板。凡是函数体相同的函数都可以用这个模板来代替,不必定义多个函数,只需在模板中定义一次即可。在调用函数时系统会根据实参的类型来取代模板中的虚拟类型,从而实现了不同函数的功能。
我们举一个例子来说明这种情况,比如你要写一个交换变量整数变量值的函数。函数代码如下

void myswap(int &a,int &b) 
{
	int box;
	box = a;
	a = b;
	b = box;
	return;
}

但是如果需求发生了改变,要求交换的变量是浮点型,或者字符类型,那么你就需要利用函数的重载机制多写两套换汤不换药的代码,这从工作角度来说是低效率。而函数模板机制可以使用在这种情况下,大大的开发程序员的开发效率。

1.2 函数模板语法

函数模板定义形式
template < 类型形式参数表 >
类型形式参数的形式为:typename T1 , typename T2 , …… , typename Tnclass T1 , class T2 , …… , class Tn

  • 函数的模板定义有模板和函数定义组成
  • 模板说明的类属参数必须在函数中至少出现一次
  • 函数参数表中可以使用类属参数,也可以使用一般类型参数
1.3 函数模板调用

函数模板的调用分为显示调用和隐式调用
myswap<float>(a, b); //显示类型调用
myswap(a, b); //自动数据类型推导

在了解函数模板的概念之后,上边的交换需求就可以写成下边的形式:

#include <iostream>
using namespace std;

template <typename T>
void myswap(T& a, T& b)
{
	T t;
	t = a;
	a = b;
	b = t;
}

int main()
{
	int  x = 1;
	int	 y = 2;
	myswap(x, y);		 // 自动数据类型 推导的方式 

	float a = 2.0;
	float b = 3.0;

	myswap(a, b);		 // 自动数据类型 推导的方式 
	myswap<float>(a, b); // 显示类型调用 

	char c1 = 'a';
	char c2 = 'b';
	myswap<char>(c1, c2);
	return 0;
}

这样就大大减少代码量,提高了成员的工作效率。

1.4 函数模板和模板函数

当我们编写好函数模板后,当我调用模板的时候,编译器会自动生成模板函数,过程如下图所示。
在这里插入图片描述

1.5 函数模板遇上函数重载

函数模板和普通函数区别结论:

  • 函数模板不允许自动类型转化
  • 普通函数能够进行自动类型转换

函数模板和普通函数在一起,调用规则:

  1. 函数模板可以像普通函数一样被重载
  2. C++编译器优先考虑普通函数
  3. 如果函数模板可以产生一个更好的匹配,那么选择模板
  4. 可以通过空模板实参列表的语法限定编译器只通过模板匹配
#include "iostream"
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 T>
T Max(T a, T b, T c)
{
	cout << "T Max(T a, T b, T c)" << endl;
	return Max(Max(a, b), c);
}


void main()
{
	int a = 1;
	int b = 2;

	cout << Max(a, b) << endl;		//当函数模板和普通函数都符合调用时,优先选择普通函数
	cout << Max<>(a, b) << endl;	//若显示使用函数模板,则使用<> 类型列表

	cout << Max(3.0, 4.0) << endl; //如果 函数模板产生更好的匹配 使用函数模板

	cout << Max(5.0, 6.0, 7.0) << endl; //重载

	cout << Max('a', 100) << endl;  //调用普通函数 可以隐式类型转换 
	system("pause");
	return;
}
1.6 C++编译器模板机制剖析
  1. 编译器并不是把函数模板处理成能够处理任意类的函数
  2. 编译器从函数模板通过具体类型产生不同的函数
  3. 编译器会对函数模板进行两次编译
  4. 在声明的地方对模板代码本身进行编译;在调用的地方对参数替后的代码进行编译。

2 类模板

2.1 为什么需要类模板

类模板与函数模板的定义和使用类似,我们已经进行了介绍, 有时,有两个或多个类,其功能是相同的,仅仅是数据类型不同,如下面语句声明了一个类:
在这里插入图片描述

  • 类模板用于实现类所需数据的类型参数化

  • 类模板在表示如数组、表、图等数据结构显得特别重要,

    这些数据结构的表示和算法不受所包含的元素类型的影响

2.2 继承中的类模板语法
class Father
{
public:
	Father(T x)
	{
		a = x;
	}
private:
	T a;
};

class Children : public Father<int>
{
public:
	Children(int a, double y) :Father<int>(a) 
	{
		b = y;
	}
private:
	double b;
};

子类从模板类继承的时候,需要让编译器知道 父类的数据类型具体是什么(数据类型的本质:固定大小内存块的别名)。

3 模板在编程中的使用

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

如果把所有的模板函数函数写在类的内部,只需要在内的开头使用template<typename T>进行标识

3.2 所有的类模板函数写在类的外部,在一个cpp中
    构造函数 没有问题
	普通函数 没有问题
	友元函数:用友元函数重载 << >>
	friend ostream& operator<< <T> (ostream &out, Complex<T> &c3) ;
	友元函数:友元函数不是实现函数重载(非 << >>)
		1)需要在类前增加 类的前置声明 函数的前置声明 
			template<typename T>
			class Complex;  
			template<typename T>
			Complex<T> mySub(Complex<T> &c1, Complex<T> &c2);

		2)类的内部声明 必须写成: 
			friend Complex<T> mySub <T> (Complex<T> &c1, Complex<T> &c2);
		3)友元函数实现 必须写成:
			template<typename T>
			Complex<T> mySub(Complex<T> &c1, Complex<T> &c2)
			Complex<T> tmp(c1.a - c2.a, c1.b-c2.b);
			return tmp;

	    4)友元函数调用 必须写成
			Complex<int> c4 = mySub<int>(c1, c2);
			cout<<c4;
	这种调用方法太麻烦,不建议使用,容易出现问题。结论:友元函数只用来进行 左移 友移操作符重载。		
3.4 所有的类模板函数写在类的外部,在不同的.h和.cpp中,
	也就是类模板函数说明和类模板实现分开
	类模板函数
	构造函数
	普通成员函数
	友元函数
	用友元函数重载<<>>; 
	用友元函数重载非<< >>
	要包含.cpp

这一种是我们工程中建议使用的方法,我们举一个例子来进行说明:
这个是一个利用模板实现的Vector方法,由于使用了模板,可以有更多使用范围。

#pragma once
#include <iostream>
#include "MyVector.cpp"  // 注意要包含实现函数的cpp,有的时候也写为.hpp后缀
#include "Teacher.h"
using namespace std;

// 测试代码
int main()
{
	MyVector<int> vector1(10);
	for (int i = 0; i < vector1.getVectorLength(); i++)
	{
		vector1[i] = i + 1;
		cout << vector1[i] << " ";
	}
    MyVector<int> vector2 = vector1;
	Teacher t1("1", 1);
	Teacher t2("2", 2);
	Teacher t3("3", 3);
	Teacher t4("4", 4);
	MyVector<Teacher> tarry(4);
	tarry[0] = t1;
	tarry[1] = t2;
	tarry[2] = t3;
	tarry[3] = t4;

	for (int i = 0; i < tarry.getVectorLength(); i++)
	{
		Teacher temp = tarry[i];
		temp.printf();
	}

	cout << vector2;
	return 0;
}
#pragma once //函数声明
#include <iostream>
using namespace std;

template <typename T>
class MyVector
{
public:
   friend ostream& operator << <T>(ostream& out, const MyVector<T>& obj);
public: 
	MyVector(int size = 0);			// 构造函数
	MyVector(const MyVector& obj);	// 拷贝构造函数
	~MyVector();

public:
    T& operator[](int index);
	MyVector operator = (MyVector& obj);
	int getVectorLength();
protected:
	T* point;
	int vectorLength;
private:
};
#pragma warning( disable : 4996)
#include <iostream>
#include "Teacher.h"
using namespace std;


Teacher::Teacher(const char* name, int age)
{
	strcpy(this->name, name);
	this->age = age;
}
Teacher::Teacher() 
{
	strcpy(this->name, "");
	age = 0;
}
void Teacher::printf()
{
	cout << "Teacher name is " << name << "  age is " << age << endl;
}
#pragma warning( disable : 4996)
#include <iostream>
#include "Teacher.h"
using namespace std;


Teacher::Teacher(const char* name, int age)
{
	strcpy(this->name, name);
	this->age = age;
}
Teacher::Teacher() 
{
	strcpy(this->name, "");
	age = 0;
}
void Teacher::printf()
{
	cout << "Teacher name is " << name << "  age is " << age << endl;
}
#pragma once
#include <iostream>
using namespace std;

class Teacher
{
public:
	Teacher(const char *name,int age = 0);
	Teacher();
	void printf();
private:
	int age;
	char name[100];
};

这个自己写的MyVector利用模块编程实现MyVetor 这个类的功能。设计到了类的封装的一些工程应用。

4 类模板中的static关键字
  • 从类模板实例化的每个模板类有自己的类模板数据成员,该模板类的所有对象共享一个static数据成员
  • 和非模板类的static数据成员一样,模板类的static数据成员也应该在文件范围定义和初始化
  • 每个模板类有自己的类模板的static数据成员副本
    在这里插入图片描述
    这个是类模板中静态变量的处理过程,可以看出类模板的静态变量并不是和共享的,对于不同的类型。编译器会创建一个不同的静态变量。
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值