Bootstrap

【面向对象】泛型编程

一、函数模板

1.泛型编程的概念

  1. 泛型编程就是不考虑具体数据类型的编程,再C++中通过模板来实现。其和普通编程方式的区别就是类型可以参数化,即其参数是不固定的。
  2. 语法:template<typename T>,其中 template关键字是告诉编译器要使用泛型编程,typename用来声明泛指类型,即模板中的数据类型可以用T来表示;
  3. 在模板中,泛指类型T可以代表任何的数据类型,而其代表的具体类型可以通过编译器自动推导,也可以在调用时来指定。
template<typename T>		//定义函数模板
void Sort(T a[], int len)	//定义排序函数,参数采用泛指类型
{
	for(int i = 0; i < len; i ++)
	{
		for(int j = i; j < len; j ++)
		{
			if(a[i] > a[j])
			{
				swap(a[i], a[j]);
			}
		}
	}
}
template<typename T>		//定义泛型编程模板
void print(T a[], int len)	//定义打印函数,参数采用泛指类型
{
	for(int i = 0; i < len; i ++)
	{
		cout << a[i] << " ";
	}
}
int main()
{
	int i[5]  = {3, 1, 2, 4, 5};		//定义int型数组
	print(i, 5);	//通过自动推导的泛指类型的函数打印
	Sort(i, 5);		//通过泛指类型的函数排序
	print<int>(i, 5);		//通过显示的泛指类型的函数打印
	string s[5] = {"aaa", "ccc", "bbb", "ddd", "eee"};		//定义string数组
	print(s, 5);
	Sort<string>(s, 5);		//通过显示的泛指类型的函数排序
	print(s, 5);
}

2.函数模板

  1. 函数模板是一种特殊的函数,编译器会对其编译两次,第一次对基本语法等进行常规编译,程序中泛指类型T的具体类型在第二次编译时才能确定,因此第二次是对数据的类型参数代替后进行编译。
  2. 若编译器自动推导时,类型必须严格的匹配,并不会自动进行隐式转换;而若进行显式指定则会进行隐式转换。
class cls{			//定义类
	cls(cls &s){};	//拷贝构造函数,private级别
};
template <typename T>		//函数模型
void Swap(T& a, T& b)		//交换函数
{
	T c = a;
	a = b;
	b = c;
}
typedef void(func1)(int&, int&);			//定义参数为int&的函数类型func1
typedef void(func2)(double&, double&);		//定义参数为doubled&的函数类型func2
typedef void(func3)(cls&, cls&);			//定义参数为cls&类的函数类型func3
int main()
{
	func1* p1 = Swap;		//将函数类型指针指向不同广泛类型的函数
	func2* p2 = Swap;		//函数自动推导严格匹配,因此func与swap类型必须一致
	func3* p3 = Swap;		//此处会报错,因为编译器第二次编译时,会发现此处的参数类型是cls类,而cls拷贝构造函数private,因此无法执行Swap()函数,会报错
	cout << "p1 = "<< p1 << endl;	//打印地址
	cout << "p2 = "<< p2 << endl;	//打印地址与p1不同,说明函数模板归根据不同参数来生成不同的函数
	cout << "p3 = "<< p3 << endl;
}
  1. 函数模板可以定义多个不同类型参数,对于多参数模板无法自动推导返回值类型,因此必须显示指定,可以通过从左到右指定类型参数,一般将返回值作为第一个类型参数;
template <typename T1, typename T2, typename T3>	//定义多个参数函数模板
T1 add(T2 a, T3 b)		//加函数
{
	return static_cast<T1>(a + b);		//将a+b的值转为T1类型返回
}
int main()
{
	int a = add<int>(3, 1.5);		//显示类型指定返回值为int型
	double b = add<double , int>(3, 5);		//显示指定返回值为double, 3为int,5的类型由函数模板自动推导
	double c = add<double, int, double>(3.14, 0.618);		//显示指定返回值为double, 参数类型为int 和 double; 实际上执行时对3.14进行了隐式转换
	cout << a << endl;		//输出4
	cout << b << endl;		//输出8
	cout << c << endl;		//输出3.618
}

  1. 函数模板也可以被重载,当函数模板与函数重载同时存在时,编译器会优先使用函数重载,但若函数模板效果更好,编译器会选择函数模板;可以通过空参的函数模板强制编译器使用函数模板。
template < typename T >		//二参函数模板
T Max(T a, T b)
{
    cout << "T Max(T a, T b)" << endl;
    return a > b ? a : b;
}
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, T c)
{
    cout << "T Max(T a, T b, T c)" << endl;
    return Max(Max(a, b), c);
}

int 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;               // 普通函数 因为函数模板自动推导不支持隐式类型转换
    return 0;
}

二、类模板

1.类模板的概念

  1. 类模板的意义
    (1)一些类的作用就是用于存储和组织数据元素,而并不关心类中成员的具体数据类型,因此可以通过类模板就可以使得类无需关心成员的具体类型;
    (2)类模板是以相同的方式处理不同的数据类型,只要关注类所实现的功能就可以。
  2. 语法
    类模板在定义对象时,无法通过自动推导的方式确定类中使用了泛指类型的成员的具体类型,必须通过class<int> obj显式指定来确定。
template <typename T>		//声明使用泛指类型T
class cls{					//定义类
public:
	T add(T a, T b)			//使用T
	{
		return (a + b);
	}
};
int main()
{
	cls<int> s1;			//定义对象,显式指定T的具体类型
}

2.类模板的使用

  1. 函数模板会根据参数类型的不同生成不同的函数,类模板也会根据定义对象时显式指定的类型不同生成不同的类。
  2. 类模板同函数模板一样,在编译时会进行二次编译,第一次是对基本语法等常规编译,第二次是对参数替换后的类进行编译。
  3. 在工程应用中,类模板的声明和其中的成员实现必须在同一个文件中,并且在外部实现成员函数时,必须加上模板<>声明。
/*********头文件Operator.h*****************/
#ifndef _OPT_H			//条件编译
#define _OPT_H

#include <iostream>
#include <string>

using namespace std;

template <typename T>		//泛指类型声明
class cls{					//定义类模板
public:
	T add(T a, T b);		//成员函数声明
	T sub(T a, T b);	
	T mul(T a, T b);	
	T div(T a, T b);	
};

template <typename T>		//成员函数实现,需要声明泛指类型
T cls<T> ::add(T a, T b)	//需要加上模板`<>`声明
{
	return (a + b);
}	
template <typename T>
T cls<T> ::sub(T a, T b)
{
	return (a - b);
}
template <typename T>
T cls<T> ::mul(T a, T b)
{
	return (a * b);
}
template <typename T>
T cls<T> ::div(T a, T b)
{
	return (a / b);
}

string operator- (string& a, string& b)		//重载-操作符
{
	return "overloaded successful!";
}
#endif

/**********实现文件Practice.cpp*************/
#include <iostream>
#include <string>
#include "operator.h"		//包含类模板头文件
using namespace std;

int main()
{
	cls<int> s1;			//定义类模板对象,显式指定类型
	cout << s1.add(1, 2) << endl;	//直接调用成员函数
	cout << s1.sub(1, 2) << endl;
	cout << s1.mul(1, 2) << endl;
	cout << s1.div(1, 2) << endl;
	cls<string> s2;		//显示指定string类型
	cout << s2.sub("abc", "bcd") << endl;  //在第二次编译时,会判断出-操作符无法进行字符串之间运算,因此需要重载-操作符	

3.类模板的特化

  1. 类模板同函数模板一样,可以定义任意多个泛指类型参数template <typename T1, typename T2>
  2. 类模板的特化是指,根据不同的类型参数,可以指定类模板的特殊实现,类似于重载。编译器话根据具体的类型来调用不同的特化类模板。
  3. 类模板的特化分为部分特化template<typename T> class cls<T, T>与完全特化template <> class cls<type, type>,完全特化中已经不含有泛指类型T了。
  4. 特化的类模板类似于函数的重载,本质是同一个类模板;使用方式都必须显式指定每一个具体类型;
template <typename T1, typename T2>		//泛指类型声明
class cls{		//类模板
public:
	void add(T1 a, T2 b)		//成员函数
	{
		cout << "类模板" <<" template <typename T1, typename T2>" << endl;
		cout << a + b << endl; 
	}
};
template <typename T>		//泛指类型声明
class cls<T*, T*>{		//类模板部分特化, 指针类型的类模板
public:
	void add(T* a, T* b)		//成员函数
	{
		cout << "部分特化" <<" template <typename T>" << endl;
		cout << *a + *b << endl; 
	}
	void other()		//不同的特化类模板可以添加自己的成员函数
	{
		cout << "more" << endl;
	}
};
template < >		//泛指类型声明
class cls<int , int >		//类模板完全特化
{
public:
	void add(int a, int b)
	{
		cout << "完全特化" <<" template < >" << endl;
		cout << a + b << endl; 
	}
};
int main()
{
	cls<int, double> s1;	//生成template <typename T1, typename T2>类模板的类对象
	s1.add(1, 2.5);			
	int a = 5, b = 10;		
	cls<int*, int*> s2;		//生成template <typename T>类模板的类对象;
	s2.add(&a, &b);			//注意:若此处显示声明类型若是int,则会默认调用完全特化的类模板
	cls<int, int> s3;		//生成template <>类模板的类对象	
	s3.add(a, b);
	return 0;
}
  1. 函数模板也支持特化,但只支持完全特化
template <typename T1, typename T2>		//声明泛指类型
void func(T1 a, T2 b)		//函数模板
{
	cout << "template <typename T1, typename T2>" << endl;
	cout << static_cast<int>(a + b) << endl;
}
template < >		//声明泛指类型
void func<int, int>(int a, int b)		//函数模板完全特化
{
	cout << "template < >" << endl;
	cout << static_cast<int>(a + b) << endl;
}
void func(int a, int b)			//普通函数
{
	cout << "func" << endl;
	cout << static_cast<int>(a + b) << endl;
}
int main()
{
	int a = 10;
	int b = 15;
	double c = 15;
	func(a, b);					//自动调用普通函数,因为默认先调用普通函数
	func<int, int>(a, b);		//自动调用完全特化函数模板
	func<int, double>(a, c);	//根据具体类型,会调用函数模板
}

三、类模板的应用

1.智能指针类模板

  1. STL库中的智能指针auto_ptr
    (1)智能指针本质是类的对象,因此声明周期结束时会自动调用析构函数来销毁指向的内存空间;
    (2)auto_ptr不能指向堆数组,只能指向堆对象/变量;
    (3)一片堆空间只属于一个智能指针;
class cls{		//定义类
public:
	cls()
	{
		cout << "cls" << endl; 
	}
	void print()	
	{
		cout << "function" << endl;
	}
	~cls()
	{
		cout << "~cls" << endl;
	}
};
int main()
{
	auto_ptr<cls> s1(new cls);		//智能指针的使用
	s1->print();					//通过智能指针调用成员函数
	cout << s1.get() << endl;		//打印智能指针指向的地址
	cout << endl;
	auto_ptr<cls> s2(s1);			//通过一个对象给另一个对象初始化
	cout << s1.get() << endl;		//0
	cout << s2.get() << endl;		//原s1指向的地址
	return 0;
}

2.自己制作智能指针类模板

template<typename T>
class T{			//定义一个类
	int a;
public:
	T(){
		a = 0;	
	}
	T(int i){
		a = i;	
	}
	int getA()
	{
		return a;
	}
};
class pointer{		//定义一个指针类
	T*ps;		//成员变量为cls类的指针
public:
	pointer(){		
		ps = NULL;	
	}
	pointer(const T* p){
		ps = const_cast<T*>(p);
	}
	pointer(const pointer &p)		//拷贝构造函数
	{
		ps = p.ps;					//当将指向的地址复制给另一个时
		const_cast<pointer&>(p).ps = NULL;		//取消其中一个的指向,避免多个指针指向同一个内存空间
	}
	T& operator *()			//重载指针操作符
	{
		return *ps;
	}
	T* operator ->()			//重载指针操作符
	{
		return ps;
	}
	T* getPs()
	{
		return ps;
	}
	~pointer(){					//析构函数,释放指向的空间
		cout << "delete" << endl;
		delete ps;	
	}
};

int main()
{
	pointer<cls> p1 = new cls(10);		//将new的地址给定义的pointer类对象p1
	cout << p1->getA() << endl;
	cout << p1.getPs() << endl;
	pointer<cls> p2 = p1;				//用p1来初始化p2
	cout << p1.getPs() << endl;		//此时为空,避免了指向同一块内存
	cout << p2.getPs() << endl;		//为p1原来指向的空间
	p2++;							//报错,避免了野指针
}									//会自动释放p1, p2

2.单例类模板

  1. 单例模式就是只能定义一个对象的类
class cls{
	static cls* mp;
	cls(const cls& s);
	cls& operator=(const cls* s);
	cls(){};
public:
	static cls* func();
	void getMp()
	{
		cout << "mp = " << mp << endl;
	}
};						
cls* cls::mp  = NULL;
cls* cls::func(){
	if(mp == NULL)
	{
		mp = new cls;
	}
	return mp;
}

int main()
{
	cls *s1 = cls::func();
	s1->getMp();
	cls *s2 = cls::func();
	s2->getMp();
	cls *s3 = cls::func();
	s3->getMp();
	return 0;
}

  1. 若多个类都需要单例模式,则可以创建单例类模板
/*****************operator.h****************/
template<typename T>		//声明泛指类型

class cls{					//定义类模板
	static T* mp;			//静态的泛指类型的指针成员变量
public:
	static T* func();		//静态泛指类型成员函数
};	
template<typename T>
T* cls<T>::mp  = NULL;		//初始化成员变量

template<typename T>
T* cls<T>::func(){			//初始化成员函数,用来构造T类型对象
	if(mp == NULL)
	{
		mp = new T;
	}
	return mp;
}
#endif
/**********practice****************/
#include <iostream>
#include <string>
#include <memory>
#include "operator.h"
using namespace std;

class cls1{
	friend class cls<cls1>;
public:
	void getMp(){
		cout << "this = "<< this;
	}
};

int main()
{
	cls1 *s1 = cls<cls1>::func();
	s1->getMp();
	return 0;
}

;