5、泛型编程

模板

建立通用模具,提高通用性

函数模板

建立一个通用函数,其函数返回值类型和形参类型可以不具体制定,用一个虚拟的类型来代表。

函数模板语法

//函数声明或定义
template<typename T>

template --- 声明创建模板
typename --- 表面其后面的符号是一种数据类型,可以用class代替
T --- 通用的数据类型,名称可以替换,通常为大写字母

例子:

#include<iostream>

using namespace std;

//两个整形交换
void changeInt(int& a, int& b) {
	int temp = a;
	a = b;
	b = temp;
}
//两个浮点型交换
void changeDouble(double& a, double& b) {
	double temp = a;
	a = b;
	b = temp;	
}
//声明一个模板,告诉编译器,后面代码如果使用T,不要报错,T是一个泛型
template <typename T> 
//使用模板,可以提高交换函数的通用性
void change(T& a, T& b) {
	T temp = a;
	a = b;
	b = temp;
}

int main() {
	int a = 10;
	int b = 20;
	//此处为自动类型推断,编译器自动推断类型
	change(a, b);
	cout << "a = " << a
		<< ",b = " << b << endl; //a = 20,b = 10
	double c = 5.2;
	double d = 20.5;
	//此处指定类型,告诉编译器此处的T是double类型
	change<double>(c, d);
	cout << "c = " << c
		<< ",d = " << d << endl; //c = 20.5,d = 5.2
	return 0;
}

模板使用案例

#include<iostream>

using namespace std;

//使用选择排序,对多种类型的数组进行选择排序,从小到大

//声明模板
template<class T>
//选择排序
void chooseSort(T & arr,int len) {
	for (int i = 0; i < len; i++) {
		int maxIndex = i;
		for (int j = i + 1; j < len; j++) {
			if (arr[maxIndex] < arr[j]) {
				maxIndex = j;
			}
		}
		if (maxIndex != i) {
			int temp = arr[i];
			arr[i] = arr[maxIndex];
			arr[maxIndex] = temp;
		}
	}
}
//声明模板
template<class T>
//遍历数组
void showArr(T & arr, int len) {
	for (int i = 0; i < len; i++) {
		cout << "第" << i << "个元素:" << arr[i] << endl;
	}
}
int main() {
	//int数组
	int intArr[] = { 9,66,50,2,35,46,12,57 };
	//int数组长度
	int intArrLen = sizeof(intArr) / sizeof(intArr[0]);
	//进行排序
	chooseSort(intArr, intArrLen);
	//遍历数组
	showArr(intArr, intArrLen);
	

	return 0;
}

普通函数和函数模板的区别

例子:

#include<iostream>

using namespace std;

//普通函数
int add(int a, int b) {
	return a + b;
}
//自动类型推导的函数模板
template<class T>
T add1(T a, T b) {
	return a + b;
}
int main() {
	int a = 10;
	char b = 'a';

	//普通函数,可以发生隐式类型转换,在可以满足自动类型提升的情况下
	//例如char->int
	cout << add(a, b) << endl; //107

	//自动类型推导的函数模板,不可以发生隐式类型转换,必须两个参数为同一类型
	//cout << add1(a, b) << endl; //报错

	//显式指定类型的函数模板,可以发生隐式类型转换
	cout << add1<int>(a, b) << endl;//107


	return 0;
}

普通函数与函数模板的调用规则

实际开发中,既然提供了函数模板,最好就不要提供普通函数,否则容易出现二义性

例子:

#include<iostream>

using namespace std;
//普通函数
void func(int a,int b) {
	cout << "普通函数" << endl;
}
//声明定义模板函数
template<class T>
void func(T a, T b) {
	cout << "模板函数" << endl;
}

//3、函数模板也可以发生重载
template<class T>
void func(T a) {
	cout << "重载的模板函数" << endl;
}

int main() {
	int a = 10;
	int b = 20;
	//1、如果函数模板和普通函数都可以实现,优先调用普通函数
	func(a, b);
	//2、可以通过空模板参数列表来强制调用函数模板
	func<>(a, b);

	char c = 'a';
	char d = 'b';
	//3、如果函数模板可以产生更好的匹配,优先调用函数模板
	func(c, d);


	return 0;
}

模板的局限性

例如:

template<class T>
void f(T a, T b){
	a = b;
}

在上述代码中提供的赋值操作,如果传入的a和b是一个数组,就无法实现了

再例如:

template<class T>
void f(T a, T b){
	if(a > b) { ... }
}

在上述代码中,如果T的数据类型传入的是像Person这样的自定义数据类型,也无法正常运行

因此C++为了解决这种问题,提供模板的重载,可以为这些特定的类型提供具体化的模板

例子:

#include<iostream>
#include<string>

using namespace std;

class Person {
public:
	string name;
	int age;
	Person(string name, int age) {
		this->name = name;
		this->age = age;
	}
};

template<typename T>
bool equal(T& a, T& b) {
	if (a == b) {
		return true;
	}
	else {
		return false;
	}
}
//解决方法:2、利用具体化的Person来实现
template<> bool equal(Person& a, Person& b) {
	if (a.name == b.name && a.age == b.age) {
		return true;
	}
	else {
		return false;
	}
}


int main() {
	int a = 10;
	int b = 20;
	//对于内置数据类型,可以进行比较
	cout << equal(a, b) << endl; //0

	Person p1("lucy", 18);
	Person p2("tom", 19);
	//此时,运行时会报错,解决方法:1、运算符重载,但是太繁琐
	cout << equal(p1, p2) << endl;
	
	return 0;
}

类模板

建立一个通用类,类中的成员数据类型可以不具体制定,用一个虚拟的类型来代表。

类模板语法

类模板和函数模板语法相似,在声明模板template后面加类,此类称为类模板

template<class T>
class MyClass{
    
}

例子:

#include<iostream>
#include<string>

using namespace std;

//声明一个姓名的类型,和一个年龄的类型
template<class NameType,class AgeType>
class Person {
public:
	Person(NameType name,AgeType age) {
		this->name = name;
		this->age = age;
	}
	//声明Person属性
	NameType name;
	AgeType age;
};

int main() {
	//使用类模板
	Person<string, int> p1("lucy", 18);

	cout << "姓名:" << p1.name << ",年龄:" << p1.age << endl;

	return 0;
}

类模板与函数模板区别

类模板与函数模板区别主要有两点:

  1. 类模板没有自动类型推导的使用方式
  2. 类模板在模板参数列表中可以有默认参数

例子:

#include<iostream>
#include<string>

using namespace std;

//类模板的参数列表,可以有默认参数
template<class NameType = string,class AgeType = int>
class Person {
public:
	Person(NameType name,AgeType age) {
		this->name = name;
		this->age = age;
	}
	NameType name;
	AgeType age;
};

int main() {
	//使用类模板,如果有默认的参数,可以不指定类型
	Person<> p1("lucy", 18);

	cout << "姓名:" << p1.name << ",年龄:" << p1.age << endl;

	return 0;
}

类模板中成员函数创建时机

类模板中成员函数和普通类中成员函数创建时机是有区别的:

类模板对象做函数参数

一共有三种传入方式:

  1. 指定传入的类型 --- 直接显示对象的数据类型
  2. 参数模板化 --- 将对象中的参数变为模板进行传递
  3. 整个类模板化 --- 将这个对象类型 模板化进行传递

例子:

#include<iostream>
#include<string>

using namespace std;

template<class NameType,class AgeType>
class Person {
public:
	Person(NameType name,AgeType age) {
		this->name = name;
		this->age = age;
	}
	NameType name;
	AgeType age;
	void showPerson() {
		cout << "姓名:" << this->name << ",年龄:" << this->age << endl;
	}
};
//1、指定传入类型
void func1(Person<string,int>& p) {
	p.showPerson();
}
//2、参数模板化
template<class T1,class T2>
void func2(Person<T1,T2>& p) {
	p.showPerson();
	//查看传入的T1和T2类型
	cout << "T1:" << typeid(T1).name() << endl;
	cout << "T2:" << typeid(T2).name() << endl;
}
//3、将整个类模板化
template<class T>
void func3(T& p) {
	p.showPerson();
}

int main() {
	Person<string, int> p("lucy", 18);

	func1(p);

	//指定传入类型
	func2<string, int>(p);
	//自动类型推导
	func2(p);

	//自动类型推导
	func3(p);
	//指定传入类型
	func3<Person<string, int>>(p);

	return 0;
}

类模板与继承

例子:

#include<iostream>

using namespace std;

template<class T>
class Base {
	T m;
};
//class son :public Base {}; //错误,必须要指定父类中T的数据类型
//1、指定父类中T的数据类型
class Son1 :public Base<int> {

};
//2、灵活的指定父类中T的类型,子类也需要是一个类模板
template<class T>
class son2 :public Base<T> {

};

类模板成员函数类外实现

#include<iostream>
#include<string>

using namespace std;

template<class T1,class T2>
class Person {
public:
	T1 name;
	T2 age;
	//构造函数
	Person(T1 name, T2 age);
	//成员函数
	void showPerson(); 

};
//类外实现有参构造函数
template<class T1,class T2>
Person<T1, T2>::Person(T1 name, T2 age) {
	this->name = name;
	this->age = age;
}
//类外实现成员函数,即使成员函数没有使用模板,也要声明
template<class T1, class T2>
void Person<T1, T2>::showPerson() {
	cout << "name:" << this->name << ",age:" << this->age << endl;
}

int main() {
	Person<string, int> p("lucy", 18);
	p.showPerson();

	return 0;
}

类模板的分文件编写

解决方法1,不推荐

person.h文件:

#pragma once
#include<iostream>
#include<string>

using namespace std;

template<class T1,class T2>
class Person {
public:
	T1 name;
	T2 age;
	Person(T1 name, T2 age);
	void showPerson();
};

person.cpp文件:

#include"person.h"

template<class T1,class T2>
Person<T1,T2>::Person(T1 name,T2 age) {
	this->name = name;
	this->age = age;
}

template<class T1,class T2>
void Person<T1, T2>::showPerson() {
	cout << "name:" << this->name << ",age:" << this->age << endl;
}

main.cpp文件:

#include<iostream>
#include<string>

//如果这样引入头文件,可以正常编译,但是,执行报错
//原因是:类模板中成员的创建时机是在运行时,编译器无法连接到cpp文件
//#include"person.h"

//解决方法1,不推荐
//编译器可以通过cpp文件中的include连接.h文件
#include"person.cpp"

using namespace std;

int main() {
	Person<string, int> p("lucy", 18);
	p.showPerson();

	return 0;
}
解决方法2

将.h和.cpp的内容写在一起,文件后缀为.hpp

person.hpp文件:

#pragma once
#include<iostream>
#include<string>

using namespace std;
//类模板的声明
template<class T1,class T2>
class Person {
public:
	T1 name;
	T2 age;
	Person(T1 name, T2 age);
	void showPerson();
};

//类模板成员函数的实现
template<class T1, class T2>
Person<T1, T2>::Person(T1 name, T2 age) {
	this->name = name;
	this->age = age;
}

template<class T1, class T2>
void Person<T1, T2>::showPerson() {
	cout << "name:" << this->name << ",age:" << this->age << endl;
}

main.cpp文件:

#include<iostream>
#include<string>

#include"person.hpp"


using namespace std;

int main() {
	Person<string, int> p("lucy", 18);
	p.showPerson();

	return 0;
}

类模板和友元

全局函数类内实现 - 直接在类内声明友元即可

全局函数类外实现 - 需要提前让编译器知道全局函数的存在

建议全局函数做类内实现,用法简单,而且编译器可以直接识别

全局函数类内实现:

#include<iostream>
#include<string>

using namespace std;

template<class T1,class T2>
class Person {
	//全局函数类内实现
	friend void printPerson(Person<T1,T2> p) {
		cout << p.name << p.age << endl;
	}
public:
	Person(T1 name, T2 age) {
		this->name = name;
		this->age = age;
	}
private:
	T1 name;
	T2 age;
};


int main() {
	Person<string, int> p("lucy", 17);
	printPerson(p);
	return 0;
}

全局函数类外实现:

#include<iostream>
#include<string>

using namespace std;

//需要让编译器提前知道有Person类的存在
template<class T1, class T2>
class Person;

//实现
template<class T1, class T2>
void printPerson(Person<T1, T2> p) {
	cout << p.name << p.age << endl;
}

template<class T1,class T2>
class Person {
	//全局函数类外实现
	//1、需要添加一个空模板的参数列表,如果不加,会报错:1 个无法解析的外部命令
	friend void printPerson<>(Person<T1, T2> p);
public:
	Person(T1 name, T2 age) {
		this->name = name;
		this->age = age;
	}
private:
	T1 name;
	T2 age;
};

int main() {
	Person<string, int> p("lucy", 17);
	printPerson(p);
	return 0;
}

模板使用案例

实现一个通用的数组类,要求如下:

MyArray.hpp文件:

#pragma once
#include<iostream>

using namespace std;

//编写通用的数组类
template<typename T>
class MyArray{
private:
    //数组
    T * arr;
    //数组的容量
    int capacity;
    //数组的大小
    int size;
public:
    //有参构造,指定初始容量
    MyArray(int capacity){
        cout << "有参构造" << endl;
        this->capacity = capacity;
        this->arr = new T[capacity];
    }
    //析构函数,手动释放
    ~MyArray(){
        cout << "析构" << endl;
        if(this->arr != NULL){
            delete[] this->arr;
            this->arr = NULL; //置空数组指针,防止野指针
        }
    }
    //拷贝构造,深拷贝
    MyArray(const MyArray& arr){
        //获取容量和大小
        this->size = arr.size;
        this->capacity = arr.capacity;
        //堆区new一个新的数组,容量和拷贝的数组一样
        this->arr = new T[arr.capacity];
        //将所有的数据都拷贝过来
        for(int i = 0;i < this->size;i++){
            this->arr[i] = arr[i];
        }
    }
    //重载=,防止直接=赋值出现浅拷贝问题
    MyArray& operator=(const MyArray& arr){
        //判断当前对象数组是否在堆区已经有数据,如果有,先释放
        if(this->arr != NULL){
            delete[] this->arr;
            this->arr = NULL;
            this->capacity = 0;
            this->size = 0;
        }
        //进行深拷贝
        this->size = arr.size;
        this->capacity = arr.capacity;
        this->arr = new T[arr.capacity];
        for(int i = 0;i < this->size;i++){
            this->arr[i] = arr[i];
        }
    }
    //尾插法,新增元素
    void push(const T& value){
        //判断容量是否等于大小
        if(this->size == this->capacity){
            return;
        }
        this->arr[this->size] = value;
        this->size++;
    }
    //尾删法,删除元素
    void remove(){
        delete arr[this->size - 1];
        arr[this->size - 1] = NULL;
        this->size--;
    }
    //支持下标访问元素,重载[]
    T& operator[](int index){
        return this->arr[index];
    }
    //获取容量
    int getCapacity(){
        return this->capacity;
    }
    //获取大小
    int getSize(){
        return this->size;
    }
};

main.cpp文件:

```cp
#include<iostream>
#include"MyArray.hpp"

using namespace std;

int main(){

    MyArray<int> array(10);
    array.push(521);
    array.push(13);
    cout << array[0] << endl;
    cout << array[1] << endl;
    cout << "数组的容量:" << array.getCapacity() << endl;
    cout << "数组的大小:" << array.getSize() << endl;
    system("pause");
    return 0;
}