C++学习笔记:(八)模板

版权声明:转载标明出处 https://blog.csdn.net/qq_38289815/article/details/82054947

8.1模板的概念

函数重载可以实现具有相同功能的函数的函数名相同,使程序更加易于理解。系统可以根据不同的参数类型来区分函数。这样虽然很方便,但是书写函数的个数并没有减少,重载函数的代码量几乎完全相同。那么如何解决这个问题?

C++提供了模板。模板是C++的一个重要特征。模板是类型参数化的工具。所谓类型参数化,是指把类型定义为参数,当参数实例化时,可指定不同的数据类型,从而真正实现代码的可重用。模板分函数模板和类模板,它们分别允许用户构造模板函数和模板类。

例如,求两个数的最大值:

int max(int x, int y)
{
    return (x>y)?x:y;
}
char max(char x, char y)
{
    return (x>y)?x:y;
}
float max(float x, float y)
{
    return (x>y)?x:y;
}

这些函数所执行的功能相同,只是参数类型和函数返回值类型不同。这样不仅程序代码的重用性差,而且存在大量冗余信息,是程序维护起来相当困难。解决这个问题的最好方法是使用模板。

 

8.2函数模板和模板函数

当对每种数据类型执行相同的操作,用函数模板来完成将非常简单。程序员只需定义一次函数模板,根据调用函数时提供的参数类型,编译器会产生相应的目标代码函数,以正确处理每种类型的调用。

声明函数模板的格式:

template <class 类型参数>
返回类型 函数名(模板形参表)
{
    函数体
}

例如,输出不同类型数组的元素值可定义成函数模板:

template <class T>
void Printarray(T *array, int count)
{
    for(int i = 0; i < count; i++)
    {
        cout << array[i] << ” ”;
    }
    cout <<endl;
}

也可以定义成:

扫描二维码关注公众号,回复: 2959053 查看本文章
template <typename T>
void Printarray(T *arrya, int count)
{
    for(int i = 0; i < count; i++)
    {
    cout << array[i] << ” ”;
    }
    cout <<endl;
}

说明:

(1)T是类型参数,它既可以是系统预定义的数据类型,也可以是用户自定义的类型。

(2)类型参数前需要加关键字class(或typename),这个class并不是类的意思,而是表示任何类型的意思。

(3)在使用模板函数时,关键字class(或typename)后面的类型参数,必须实例化,即用实际的数据类型替代它。

(4)< >里面的类型参数可以有一个或多个类型参数,但多个类型参数之间要用逗号分隔。

 

函数模板应用举例:输出不同类型数组的元素值。

#include <iostream>
using namespace std;
	template <class T>
void Printarray(T *array, int count)
{
	for(int i = 0; i < count; i++)
	{
		cout << array[i] << " ";
	}
	cout <<endl;
}

int main()
{
	int a[5] = {1,2,3,4,5};
	double b[7] = {1.1,2.2,3.3,4.4,5.5,6.6,7.7};
	char c[6] = "HELLO";
	Printarray(a,5);
	Printarray(b,7);
	Printarray(c,6);
	return 0;
}

程序在调用Printarray()打印各个数组时,当参数为a,则类型参数T为int;当参数是b,则类型参数T为double;当参数是c,类型参数T为char。从本程序可以看出,使用函数模板不用单独定义3个函数,从而解决了当采用函数重载技术时所产生的代码冗余问题。

注意:(1)在执行Printarray函数调用时,根据参数的类型,系统自动在内存中生成一个模板函数(即实例化函数),并执行该函数。

(2)函数模板中可以使用多个类型参数。但每个模板形参前必须有关键字class或typename。

多个类型参数应用举例:求两个数的最大值。

#include <iostream>
using namespace std;

	template <class T1, class T2>
T1 Max(T1 x, T2 y)
{
	return (x>y)?x:y;
}

int main()
{
	int i = 10;
	float f = 12.5;
	double d = 50.344;
	char c = 'A';
	cout << "the max of i,c is: " << Max(i,c) <<endl;
	cout << "the max of i,f is: " << Max(f,i) <<endl;
	cout << "the max of d,f is: " << Max(d,f) <<endl;
	return 0;
}

说明:由于函数模板中的参数类型不一样,因此每个模板形参前都有class。在执行语句Max(i,c)时,令编译器实例化Max模板函数,类型参数T1为int,T2为char,然后调用Max计算最大值。其他语句与此类似。

(3)在template语句和函数模板定义语句之间不允许有其他的语句,例如:

template <class T1, class T2>
int n;
T1 Max(T1 x, T2 y)
{
    return (x>y)?x:y;
}

(4)模板函数类似于函数重载,但与函数重载不同。在进行函数重载时,每个函数体的动作可以相同也可以不同;但模板函数中的动作必须相同。例如,下面的函数只能用函数重载,而不能用模板函数。

void print(char *name)
{
    cout << name <<endl;
}
void print(char *name, int no)
{
    cout << name << no <<endl;
}

(5)函数模板中的模板形参T可以实例化为各种类型,但实例化T的各模板实参之间必须保证类型一致,否则将发生错误,例如:

#include <iostream>
using namespace std;
template <class T>
T Max(T x, T y)
{
    return (x>y)?x:y;
}
int main()
{
    int i = 10, j = 20;
    float f = 12.5;
    cout << ”the max of i, j is: ” << Max(i , j) <<endl;
    cout << ”the max of i, j is: ” << Max(i , j) <<endl;
    return 0;
}

解决这个问题有两种方法:

(1)采用强制类型转换,将Max(i , j)修改为Max(i , int(f))。

(2)定义一个完整的非模板函数重载函数模板,例如:

#include <iostream>
using namespace std;
template <class T>
T Max(T x, T y)
{
    return (x>y)?x:y;
}
int Max(int , float);
int main()
{
    int i = 10, j = 20;
    float f = 12.5;
    cout << "the max of i, j is: " << Max(i , j) <<endl;
    cout << "the max of i, j is: " << Max(i , j) <<endl;
    return 0;
}

int Max(int x, float y)
{
    return 0;
}

处理所有类型值的冒泡排序:

#include <iostream>
using namespace std;

template <class T>
void bubble_sort(T *array, int n);
template <class T>
void show_array(T *array, int n);

int main()
{
	int a[7] = {7,6,5,4,3,2,1};
	double b[5] = {2.4,2.3,2.1,2.9,2.6};
	bubble_sort(a,7);
	show_array(a,7);
	bubble_sort(b,5);
	show_array(b,5);
	return 0;
}

	template <class T>
void bubble_sort(T *array, int n)
{
	int i,j,flag;
	T temp;
	for(i = 0; i < n-1; i++)
	{
		flag = 0;
		for(j = 1; j < n-i; j++)
		{
			if(array[j-1] > array[j])
			{
				temp = array[j-1];
				array[j-1] = array[j];
				array[j] = temp;
				flag = 1;
			}
		}
		if(flag == 0)
			break;
	}
}

	template <class T>
void show_array(T *array, int n)
{
	int i;
	for(i = 0; i < n; i++)
	{
		cout << array[i] <<" ";
	}
	cout <<endl;
}

从以上的例子可以看出,函数模板提供了一类函数的抽象,它可以是任意类型T为参数及函数返回值。函数模板经实例化而生成的具体函数模板函数。函数模板代表了一类函数,模板函数表示某一具体的函数。

 

8.3类模板与模板类

类是对一组对象的公共性质的抽象,而类模板是更高层次的抽象。类模板允许用户为类定义一种模式,使类中的某些数据成员、成员函数的参数或返回值可以根据需要取任意类型。

类模板的定义格式:

template <class T>
class 类名
{
    //...
};

说明:

(1)在每个类模板定义之前,都需要在前面加上模板声明,如template <class T>。在使用类模板时首先应将它实例化为一个具体的类(即模板类),类模板实例化为模板类的格式为:类名<具体类型名>。

(2)模板类可以有多个模板参数。

(3)在类定义体外定义成员函数时,如果该成员函数中有类型参数,则需要在函数体外进行模板声明,即在类名和函数名之间缀上<T>。具体格式:

返回类型 类名<T>::成员函数名(参数表)

(4)类模板不代表一个具体的、实际的类,而代表着一类类。因此在使用时,必须将类模板实例化为一个具体的类,格式如下:

类名<实际的类型> 对象名;

实例化的类称为模板类。模板类是类模板对某一特定类型的实例。类模板代表了一类类,模板类表示某一具体的类。

用类模板实现栈的基本运算:

#include <iostream>
using namespace std;

const int Size = 100;
template <class T>
class Stack
{
	private:
		T s[Size];
		int top;
	public:
		Stack();
		void Push(T x);
		T Pop();
};

	template <class T>
Stack<T>::Stack()
{
	top = -1;
}

	template <class T>
void Stack<T>::Push(T x)
{
	if(top == Size-1)
	{
		cout << "Stack is full." <<endl;
		return;
	}
	s[++top] = x;
}

	template <class T>
T Stack<T>::Pop()
{
	if(top == -1)
	{
		cout << "Stack underflow." <<endl;
		return 0;
	}
	return s[top--];
}

int main()
{
	Stack<int> a;
	Stack<double> b;
	Stack<char> c;
	int i;
	char ch;
	for(i = 0; i < 10; i++)
	{
		a.Push(i);
	}
	for(ch = 'a'; ch <= 'j'; ch++)
	{
		c.Push(ch);
	}
	for(i = 0; i < 10; i++)
	{
		b.Push(1.1+i);
	}

	for(i = 0; i < 10; i++)
	{
		cout << a.Pop() << " ";
	}
	cout <<endl;
	for(i = 0; i < 10; i++)
	{  
		cout << b.Pop() << " ";
	}
	cout <<endl;
	for(i = 0; i < 10; i++)
	{  
		cout << c.Pop() << " ";
	}

	cout <<endl;
	return 0;
}

类模板中有多个类型参数实例:

#include <iostream>
using namespace std;

template <class T1, class T2>
class A
{
	private:
		T1 x;
		T2 y;
	public:
		A(T1 a, T2 b);
		void print();
};

	template <class T1, class T2>
A<T1, T2>::A(T1 a, T2 b)
{
	x = a;
	y = b;
}

	template <class T1, class T2>
void A<T1, T2>::print()
{
	cout << x << " " << y <<endl;
}

int main()
{
	A<int, double> ob1(10, 12.3);
	A<char*, char*> ob2((char*)"Diligence", (char*)"makes your dreams come true");
	ob1.print();
	ob2.print();
	return 0;
}

用类模板和重载[]运算符实现不同类型的数组的输出:

#include <iostream>
using namespace std;

const int Size = 10;
template <class T>
class Array
{
	private:
		T a[Size];
	public:
		Array();
		T &operator[](int i);
};

	template <class T>
Array<T>::Array()
{
	int i;
	for(i = 0; i < Size; i++)
	{
		a[i] = 0;
	}
}

	template <class T>
T & Array<T>::operator[](int i)
{
	if(i < 0 || i > Size-1)
	{
		cout << "Index value of" << i << "is not of bounds." <<endl;
	}
	return a[i];
}

int main()
{
	Array<int> int_array;
	Array<double> double_array;
	int i;

	cout << "Integer array:";
	for(i = 0; i < Size; i++)
		int_array[i] = i;
	for(i = 0; i < Size; i++)
		cout << int_array[i] <<" ";
	cout <<endl;
	cout << "Double array:";
	cout.precision(2);
	for(i = 0; i < Size; i++)
		double_array[i] = double(i)/3;
	for(i = 0; i < Size; i++)
		cout << double_array[i] <<" ";
	cout <<endl;
	return 0;
}

由于在类中重载[]运算符,因此执行cout << int_array[i] << “ ”;时,编译系统将int_array[i]解释为int_array.operator[](i),从而调用运算符重载函数operator[](int i)。定义重载[]函数时,由于返回时一个T的引用,因此可以使用重载的[]用在赋值语句的左边,所以语句int_array[i] = i;是合法的。对于对象double_array的操作与int_array相同。

 

8.4程序实例

用类模板实现学生成绩管理系统:

分析:本例将要操作的所有对象构成一个链表,链表中的每个结点元素就是一个对象。定义一个类模板Linklist,数据成员*head表示指向链表的头指针,链表中每个结点元素包含数据域data和指针域next,数据域data是T类型,指针next指向链表中下一个结点元素。成员函数Inser_Linklist表示插入一个结点元素;成员函数Get_Linklist表示返回第i个结点元素的地址;成员函数Del_Linklist表示删除第i个结点元素;成员函数Print_Linklist表示输出链表中结点元素的值。

Linklist
Node<T> *head;
Linklist();
int Insert_Linklist();
Node<T> *Get_Linklist(int i);
int Del_Linklist(int i);
void Print_Linklist();
~Linklist();
Stu
long no;
char name[10];
float score;
Stu();
void Print();
#include <iostream>
using namespace std;

template <class T>
struct Node
{
	T data;
	Node *next;
};

template <class T>
class Linklist
{
	private:
		Node<T> *head;
		Node<T> *r;
	public:
		Linklist();
		int Insert_linklist();
		Node<T>* Get_linklist(int i);
		int Del_linklist(int i);
		void Print_linklist();
		~Linklist();
};

class Stu
{
	private:
		long no;
		char name[10];
		float score;
	public:
		Stu();
		void Print();
};

	template <class T>
Linklist<T>::Linklist()
{
	head = NULL;
	r = head;
}

	template <class T>
int Linklist<T>::Insert_linklist()
{
	Node<T> *s = new Node<T>;
	if(s)
	{
		if(head)
		{
			r->next = s;
			r = s;
		}
		else
		{
			head = s;
			r = s;
		}
	}
	if(r)
		r->next = NULL;
	return 1;
}

	template <class T>
void Linklist<T>::Print_linklist()
{
	Node<T> *p;
	p = head;
	while(p)
	{
		p->data.Print();
		p = p->next;
	}
	cout <<endl;
}

	template <class T>
Node<T> * Linklist<T>::Get_linklist(int i)
{
	Node<T> *p = head;
	int j = 1;
	while(p != NULL && j < i)
	{
		p = p->next;
		j++;
	}
	if(j == i)
		return p;
	else
		return NULL;
}

	template <class T>
int Linklist<T>::Del_linklist(int i)
{
	Node<T> *p, *s;
	if(i == 1)
	{
		p = head;
		head = head->next;
		delete p;
		return 1;
	}
	else
	{
		p = Get_linklist(i-1);
		if(p == NULL)
		{
			cout << "Error!" <<endl;
			return -1;
		}
		else if(p->next == NULL)
		{
			cout << "NULL!" <<endl;
			return 1;
		}
		else
		{
			s = p->next;
			p->next = s->next;
			if(!p->next)
				r = p;
			delete s;
			return 1;
		}
	}
}

	template <class T>
Linklist<T>::~Linklist()
{
	delete head;
}

void menu()
{
	cout <<endl;
	cout << "1......Insert:" <<endl;
	cout << "2......Delete:" <<endl;
	cout << "3......Display:" <<endl;
	cout << "4......Exit:" <<endl;
	cout << "Choice:" <<endl;
}

Stu::Stu()
{
	cout << "Input number,name,score:" <<endl;
	cin >>no>>name>>score; 
}

void Stu::Print()
{
	cout << no << " " << name << " " << score <<endl;
}

int main()
{
	Linklist<Stu> L;
	int n,m = 1;
	while(m)
	{
		menu();
		cin >> n;
		switch(n)
		{
			case 1:
				{
					int success;
					success = L.Insert_linklist();
					if(success == 1)
						cout << "Insert successfully" <<endl;
					else
						cout << "Insert failure" <<endl;
					break;
				}

			case 2:
				{
					int i,success;
					cout << "Input sitution" <<endl;
					cin >> i;
					success = L.Del_linklist(i);
					if(success == 1)
						cout << "Delete successfully" <<endl;
					else 
						cout << "Delete failure" <<endl;
					break;
				}

			case 3:
				{
					cout << "Infomation:" <<endl;
					L.Print_linklist();
					break;
				}
			case 0: m = 0;
		} 
	}
	return 0;
}

类Stu是类模板Linklist所要实例化的一个具体类。数据成员包括学生的学号、姓名和成绩;成员函数Print表示输出学生信息。

 

用类模板实现银行办公系统:

本例将要操作的所有对象构成一个队列,队列中的每个结点元素就是一个对象。定义一个类模板Lqueue,数据成员*front表示指向队头的指针,*rear表示指向队尾的指针,链表中每个结点元素包含数据域data和指针域next,数据域data是T类型,指针next指向链表中下一个结点元素。成员函数In_Lqueue表示入队操作;成员函数Empty_Lqueue表示判断队列是否为空;成员函数Out_Lqueue表示出队操作;成员函数Print_Lqueue表示输出队列中结点元素的值。

Lqueue
QNode<T> *front,*rear;
Lqueue();
void In_Lqueue();
int Empty_Lqueue();
void Print_Lqueue();
~Lqueue();
Customer
int account, amount;
Customer();
void Print();
#include <iostream>
using namespace std;

template <class T>
struct Qnode
{
	T data;
	Qnode *next;
};

template <class T>
class Lqueue
{
	private:
		Qnode<T> *front,*rear;
	public:
		Lqueue();
		void In_lqueue();
		int Empty_lqueue();
		int Out_lqueue();
		void Print_lqueue();
		~Lqueue();
};

class Customer
{
	private:
		int account;
		int amount;
	public:
		Customer();
		void Print();
};

	template <class T>
Lqueue<T>::Lqueue()
{
	rear = NULL;
	front = NULL ;
}

	template <class T>
void Lqueue<T>::Print_lqueue()
{
	Qnode<T> *p;
	p = front;
	while(p != NULL)
	{
		p->data.Print();
		p = p->next;
	}
	cout << endl <<endl;
}

	template <class T>
void Lqueue<T>::In_lqueue()
{
	Qnode<T> *p;
	p = new Qnode<T>;
	p->next = NULL;
	if(front == 0)
	{
		front = p;
		rear = p;
	}
	else 
	{
		rear->next = p;
		rear = p;
	}
}

	template <class T>
int Lqueue<T>::Empty_lqueue()
{
	if(front == NULL && rear == NULL)
		return 1;
	else
		return 0;
}

	template <class T>
int Lqueue<T>::Out_lqueue()
{
	Qnode<T> *p;
	if(Empty_lqueue() == 1)
		return 0;
	else 
	{
		p = front;
		front = p->next;
		delete p;
		if(front == NULL)
			rear = front;
		return 1;
	}
}

	template <class T>
Lqueue<T>::~Lqueue()
{
	delete rear;
}

void menu()
{
	cout <<endl;
	cout << "1......Push" <<endl;
	cout << "2......Show" <<endl;
	cout << "3......Pop" <<endl;
	cout << "0......Exit" <<endl;
	cout << "Choice:" <<endl;
}

Customer::Customer()
{
	cout << "Input account and amount:" <<endl;
	cin >>account>>amount;
}

void Customer::Print()
{
	cout << account << "  " << amount <<endl;
}

int main()
{
	Lqueue<Customer> L;
	int n,m = 1;
	while(m)
	{
		menu();
		cin >> n;
		switch(n)
		{
			case 1:
				{
					L.In_lqueue();
					cout << endl << "Element of Queue:" <<endl;
					L.Print_lqueue();
					break;
				}

			case 2:
				{
					int flag;
					flag = L.Empty_lqueue();
					if(flag != 1)
					{
						cout << "Element of Queue:" <<endl;
						L.Print_lqueue();
					}
					else 
						cout << "NULL" <<endl;
					break;
				}

			case 3:
				{
					int flag;
					flag = L.Out_lqueue();
					if(flag == 1)
					{
						cout << endl << "Element of Queue:" <<endl;
						L.Print_lqueue();
					}
					else 
						cout << "Empty!" <<endl;
					break;
				}
			case 0: m = 0;
		} 
	}
	return 0;
}

类Customer是类模板Lqueue所实例化的一个具体类。数据成员包括顾客的账号和金额;成员函数Print表示输出顾客的信息。

猜你喜欢

转载自blog.csdn.net/qq_38289815/article/details/82054947