C++提高——函数模板和类模板

前言

C++提供了函数模板(function template)。所谓函数模板,实际上是建立一个通用函数,其函数类型和形参类型不具体指定,用一个虚拟的类型来代表。这个通用函数就称为函数模板。凡是函数体相同的函数都可以用这个模板来代替,不必定义多个函数,只需在模板中定义一次即可。在调用函数时系统会根据实参的类型来取代模板中的虚拟类型,从而实现了不同函数的功能。
在这里插入图片描述
1)C++提供两种模板机制:函数模板、类模板
2)类属 —— 类型参数化,又称参数模板
使得程序(算法)可以从逻辑功能上抽象,把被处理的对象(数据)类型作为参数传递。

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

1、函数模板

1.1 为什么要有函数模板

需求:写n个函数,交换char类型、int类型、double类型变量的值。

#include "iostream"
using namespace std;

//函数业务逻辑相同, 类型不一样
void myswap01(int &a, int &b)
{

	int c = 0;
	c = a;
	a = b;
	b = c;
}
void myswap02(char &a, char &b)
{
	char c = 0;
	c = a;
	a = b;
	b = c;
}

//类型参数化   泛型编程
//template  告诉C++我要开始泛型编程
template <typename T>
void myswap03(T &a, T &b)
{
	T c=0;
	c = a;
	a = b;
	b = c;
	cout << "hello...我是模板函数"<<endl;
}

//函数模板的调用
//显示类型  调用
//自动类型推导
void main()
{
	int x = 10;
	int y = 20;
	myswap03<int>(x, y);  //函数模板  现KC实类型  调用
	myswap03(x, y);
	cout << x << "," << y << endl;

	char a = 'a';
	char b = 'b';
	myswap03<char>(a, b);
	cout << a << "," << b << endl;
	system("pause");
	return;
}



void main01()
{
	int x = 10;
	int y = 20;
	myswap01(x, y);
	cout << x << "," << y << endl;
	char a = 'a';
	char b = 'b';
	myswap02(a, b);
	cout << a << "," << b << endl;
	cout<<"hello..."<<endl;
	system("pause");
	return ;
}

1.2函数模板语法

函数模板定义形式
template < 类型形式参数表 >
类型形式参数的形式为:
typename T1 , typename T2 , …… , typename Tn
或 class T1 , class T2 , …… , class Tn
在这里插入图片描述
函数模板调用

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

1.3函数模板和模板函数

在这里插入图片描述

1.4函数模板做函数参数

#include "iostream"
using namespace std;


//让你对字符数组  int数组排序
template <typename T, typename T2>
int mySort(T *array, T2 size)
{
	int i, j;
	T tmp;
	if (array == NULL)
	{
		return -1;
	}
	for (i=0; i<size; i++)
	{
		for (j = i+1; j < size; j++)
		{
			if (array[i] < array[j])
			{
				tmp = array[i];
				array[i] = array[j];
				array[j] = tmp;
			}
		}
	}
	return 0;
}
template <typename T, typename T2>
void myPrint(T *array, T2 size)
{
	int i = 0;
	for (i=0; i<size; i++)
	{
		cout << array[i] << " ";
	}
	cout << endl;

}
void main()
{
	//int myarray[] = { 11,22,33,44,55,7,89,2 };
	char buf[] = "asdrgsdfdtgge";
	int len = strlen(buf);
	int size = sizeof(buf) / sizeof(*buf);
	mySort(buf, size);
	myPrint(buf, size);

	cout<<"hello..."<<endl;
	system("pause");
	return ;
}

1.5 函数模板遇上函数重载

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

  • 函数模板不允许自动类型转化
  • 普通函数能够进行自动类型转换
    函数模板和普通函数在一起,调用规则:
  • 1 函数模板可以像普通函数一样被重载
  • 2 C++编译器优先考虑普通函数
  • 3 如果函数模板可以产生一个更好的匹配,那么选择模板
  • 4 可以通过空模板实参列表的语法限定编译器只通过模板匹配
#include "iostream"
using namespace std;

template <typename T>
void myswap(T &a, T &b)
{
	T c = 0;
	c = a;
	a = b;
	b = c;
	cout << "hello...我是模板杉树" << endl;
}
void myswap(int a, char c)
{
	cout << "a:" << a << "c:" << c << endl;
	cout << "我是普通函数" << endl;
}
void main()
{
	int a = 10;
	char c = 'z';
	myswap(a, c);//普通调用函数:可以进行隐式的类型转换
	myswap(c, a);
	myswap(a, a);//调用函数模板(本质:类型参数化)严格按照类型进行匹配,不会进行类型转换
	
	cout<<"hello..."<<endl;
	system("pause");
	return ;
}
#include "iostream"
using namespace std;
/*
1. 函数模板可以像普通函数一样被重载
2. C++编译器优先考虑普通函数
3. 若函数模板可以产生一个更好的匹配,那么选择模板
4. 可通过空模板实参列表的语法限定编译器只通过模板匹配
*/

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; //调用普通函数 可以隐式类型转换

	cout<<"hello..."<<endl;
	system("pause");
	return ;
}

2. 类模板

2.1为什么需要类模板

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

在这里插入图片描述

  • 类模板用于实现类所需数据的类型参数化
  • 类模板在表示如数组、表、图等数据结构显得特别重要,
    这些数据结构的表示和算法不受所包含的元素类型的影响

2.2单个类模板语法

//类的类型参数化 抽象的类
//单个类模板
template<typename T>
class A 
{
public:
	A(T t)
	{
		this->t = t;
	}

	T &getT()
	{
		return t;
	}
protected:
public:
	T t;
};
void main()
{
   //模板了中如果使用了构造函数,则遵守以前的类的构造函数的调用规则
	A<int>  a(100); 
	a.getT();
	printAA(a);
	return ;
}

2.3继承中的类模板语法

在这里插入图片描述

  • 子模板类派生时,需要具体化模板类,C++编译器需要知道父类的具体数据类型,
  • 要知道父类占多大的内存 只有数据类型固定下来,才知道如何分配内存
#include "iostream"
using namespace std;

//A 编程模板  类
//模板类  类型参数化
template <typename T> 
class A
{
public:
	A(T a = 0)
	{
		this->a = a;
	}
public:
	void printA()
	{
		cout << "a: " << a << endl;
	}
protected:
public:
	T a;
};
//子模板类派生时,需要具体化模板类,C++编译器需要知道父类的具体数据类型,
//要知道父类占多大的内存  只有数据类型固定下来,才知道如何分配内存
class B : public A<int>
{
public:
	B(int a=10, int b=20) : A<int>(a)
	{
		this->b = b;
	}
	void printB()
	{
		cout << "a: " << a << " b: " << b << endl;
	}
protected:
private:
	int b;
};
template <typename T>
class C : public A<T>
{
public:
	C(T c, T a) : A<T>(a)
	{
		this->c = c;
	}
	void printC()
	{
		cout << "c: " << c << endl;
	}
protected:
private:
	T c;
};

void main()
{
	C<int> c1(1, 2);
	c1.printC();

	system("pause");
}
void main62()
{
	B b1(1, 2);
	b1.printB();

	cout << "hello..." << endl;
	system("pause");
	return;
}



void UsedA(A<int> &a)
{
	a.printA();
}
void main61()
{
	//类模板 本身就是类型化的====具体的类=====》定义具体的变量
	A<int> a1(11), a2(20), a3(30); //需要进行  类型具体化
	//a1.printA();

	UsedA(a1);
	UsedA(a2);
	UsedA(a3);

	cout<<"hello..."<<endl;
	system("pause");
	return ;
}

2.4类模板语法知识体系梳理

2.4.1 复数类------所有函数在类内部

#include "iostream"
using namespace std;

template <typename T>
class Complex
{
	friend Complex MySub(Complex &c1, Complex &c2)
	{
		Complex tmp(c1.a - c2.a, c1.b - c2.b);
		return tmp;
	}
	friend ostream &operator<<(ostream &out, Complex &c3)
	{
		out << c3.a << " + " << c3.b <<"i"<< endl;
		return out;
	}
public:
	Complex(T a = 0, T b = 0)
	{
		this->a = a;
		this->b = b;
	}

	Complex operator+ (Complex &c2)
	{
		Complex tmp(a + c2.a, b + c2.b);
		return tmp;
	}

	void printCom()
	{
		cout << "a:" << a << "  b:" << b << endl;
	}
protected:
private:
	T	a;
	T	b;
};
//运算符重载<< >>只能用友元函数,其它的运算符重载都要写成成员函数不要滥用友元函数
/*
ostream &operator<<(ostream &out, Complex &c3)
{
	out << "a:" << c3.a << " b:" << c3.b << endl;
	return out;
}*/

void main()
{
	//需要将模板类具体化之后才能定义对象  C++编译器要分配内存
	Complex<int> c1(1, 2);
	Complex<int> c2(3, 4);

	Complex<int> c3 = c1 + c2;
	//c3.printCom();
	cout << c3 << endl; 
	
	//滥用友元函数
	{
		Complex<int> c4 = MySub(c1, c2);

		cout << c4 << endl;

	}


	cout<<"hello..."<<endl;
	system("pause");
	return ;
}

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

构造函数

template <typename T>
Complex<T>::Complex(T a, T b)

普通函数

template <typename T>  
void Complex<T>::printCom()

友元函数:用友元函数重载 << >>

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;
#include "iostream"
using namespace std;

template <typename T>
class Complex;//类的前置声明

template <typename T>
Complex<T> MySub(Complex<T> &c1, Complex<T> &c2);


template <typename T>
class Complex
{
	friend ostream &operator<< <T> (ostream &out, Complex<T> &c3);
	friend Complex<T> MySub<T>(Complex<T> &c1, Complex<T> &c2);
public:
	Complex(T a = 0, T b = 0);
	void printCom();
	Complex operator+ (Complex &c2);
protected:
private:
	T	a;
	T	b;
};
//运算符重载<< >>只能用友元函数,其它的运算符重载都要写成成员函数不要滥用友元函数
template <typename T>
ostream &operator<<(ostream &out, Complex<T> &c3)
{
	out << c3.a << " + " << c3.b <<"i"<< endl;
	return out;
}
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;
}

//构造函数写在了类的外部
template <typename T>
Complex<T>::Complex(T a, T b)
{
	this->a = a;
	this->b = b;
}
template <typename T>  
void Complex<T>::printCom()
{
	cout << "a:" << a << "  b:" << b << endl;
}

//成员函数实现+运算符重载
template <typename T>
Complex<T> Complex<T>::operator+ (Complex<T> &c2)
{
	Complex tmp(a + c2.a, b + c2.b);
	return tmp;
}

void main()
{
	//需要将模板类具体化之后才能定义对象  C++编译器要分配内存
	Complex<int> c1(1, 2);
	Complex<int> c2(3, 4);

	Complex<int> c3 = c1 + c2;
	//c3.printCom();
	cout << c3 << endl;
	{
		Complex<int> c4 = MySub<int>(c1, c2);
		cout << c4 << endl;
	}

	cout << "hello..." << endl;
	system("pause");
	return;
}

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

dm_complex.h

#include <iostream>
using namespace std;


template <typename T>
class Complex
{
	friend ostream &operator<<<T>(ostream &out, Complex &c3);
	
public:
	Complex(T a = 0, T b = 0);
	void printCom();
	Complex operator+ (Complex &c2);
protected:
private:
	T	a;
	T	b;
};
dm_complex.cpp

#include <iostream>
using namespace std;
#include "dm_09_complex.h"

template <typename T>
Complex<T>::Complex(T a, T b)
{
	this->a = a;
	this->b = b;
}
template <typename T>
void Complex<T>::printCom()
{
	cout << "a:" << a << "  b:" << b << endl;
} 

//成员函数实现+运算符重载
template <typename T>
Complex<T> Complex<T>::operator+ (Complex<T> &c2)
{
	Complex tmp(a + c2.a, b + c2.b);
	return tmp;
}
//运算符重载<< >>只能用友元函数,其它的运算符重载都要写成成员函数不要滥用友元函数
template <typename T>
ostream &operator<<(ostream &out, Complex<T> &c3)
{
	out << c3.a << " + " << c3.b << "i" << endl;
	return out;
}
dm_main.cpp

#include <iostream>
using namespace std; 
#include "dm_09_complex.cpp"

void main()
{
	//需要将模板类具体化之后才能定义对象  C++编译器要分配内存
	Complex<int> c1(1, 2);
	Complex<int> c2(3, 4);

	Complex<int> c3 = c1 + c2;
	//c3.printCom();
	cout << c3 << endl;

	cout << "hello..." << endl;
	system("pause");
	return;
}

2.4.4总结

归纳以上的介绍,可以这样声明和使用类模板:

  • 1 先写出一个实际的类。由于其语义明确,含义清楚,一般不会出错。
  • 2 将此类中准备改变的类型名(如int要改变为float或char)改用一个自己指定的虚拟类型名(如上例中的numtype)。
  • 3在类声明前面加入一行,格式为:
template <class 虚拟类型参数>

如:

    template <class numtype> //注意本行末尾无分号
    class Compare
    {}; //类体

  • 4 用类模板定义对象时用以下形式:
    类模板名<实际类型名> 对象名;
    类模板名<实际类型名> 对象名(实参表列);

    如:
    Compare<int> cmp;
    Compare<int> cmp(3,7);
  • 5 如果在类模板外定义成员函数,应写成类模板形式:
   template <class 虚拟类型参数>

函数类型 类模板名<虚拟类型参数>::成员函数名(函数形参表列) {…}

关于类模板的几点说明:

  • 1 类模板的类型参数可以有一个或多个,每个类型前面都必须加class,如:
    template <class T1,class T2>
    class someclass
    {…};
    在定义对象时分别代入实际的类型名,如:
    someclass<int,double> obj;
  • 2 和使用类一样,使用类模板时要注意其作用域,只能在其有效作用域内用它定义对象。
  • 3 模板可以有层次,一个类模板可以作为基类,派生出派生模板类。

2.5类模板中的static关键字

  • 从类模板实例化的每个模板类有自己的类模板数据成员,该模板类的所有对象共享一个static数据成员
  • 和非模板类的static数据成员一样,模板类的static数据成员也应该在文件范围定义和初始化
  • 每个模板类有自己的类模板的static数据成员副本

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
原理图
在这里插入图片描述

#include <iostream>
using namespace std;

template <typename T>
class AA
{
public:
	static T m_a; 
protected:
private:
};
template <typename T>
T AA<T>::m_a = 0;

void main()
{
	AA<int> a1, a2, a3;
	a1.m_a = 10;
	a2.m_a++;
	a3.m_a++;
	cout << AA<int>::m_a << endl;

	AA<char> b1, b2, b3;
	b1.m_a = 'a';
	b2.m_a++;
	b3.m_a++;
	cout << AA<char>::m_a << endl;


	//m_a应该是  每一种类型的类 使用自己的m_a
	cout<<"Hello..."<<endl;
	system("pause");
	return;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值