1.模板的概念

  模板就是建立通用的模具,大大提高复用性。

模板的特性:

  • 模板不可以直接使用,它只是一个框架。
  • 模板的通用并不是万能的。

C++提供两种模板机制:函数模板和类模板。

2.函数模板

2.1 函数模板语法

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

目的:提高复用性,将类型参数化。

语法: template<typename T> 

            函数声明或定义

template ----- 声明创建模板

typename ----- 表明其后面的符号是一种数据结构,可以用class代替

T ----- 通用的数据类型,名称可以替换,通常为大写字母

#include <iostream>
using namespace std;

// 交换整型数据
void swapINT(int& a, int& b)
{
	int temp = a;
	a = b;
	b = temp;
}

// 交换浮点型数据
void swapDOUBLE(double& a, double& b)
{
	double temp = a;
	a = b;
	b = temp;
}

// 函数模板
template<typename T>
void mySWAP(T& a, T& b)
{
	T temp = a;
	a = b;
	b = temp;
}

void test()
{
	int a = 1;
	int b = 2;
	//swapINT(a, b);
	// 两种方式使用函数模板
	// 1.自动类型推导
	//mySWAP(a, b);
	
	// 2.显示指定类型
	mySWAP<int>(a, b);

	cout << "a= " << a << endl;
	cout << "b= " << b << endl;

	/*double c = 1.1;
	double d = 2.2;
	swapDOUBLE(c, d);
	cout << "c= " << c << endl;
	cout << "d= " << d << endl;*/
}

int main()
{
	test();
	return 0;
}
2.2 函数模板注意事项
  • 自动类型推导,必须推导出一致的数据类型T,才可以使用
  • 模板必须要确定T的数据类型才可以使用
#include <iostream>
using namespace std;

// 函数模板
template<class T>
void mySWAP(T& a, T& b)
{
	T temp = a;
	a = b;
	b = temp;
}

void test()
{
	int a = 1;
	int b = 2;
	char c = 'c';

	//mySWAP(a, c); //错误,推导不出一致的T类型

	cout << "a= " << a << endl;
	cout << "b= " << b << endl;
}

// 模板必须要确定出T的数据类型
template<class T>
void func()
{
	cout << "func的调用" << endl;
}

void test2()
{
	func<int>();
}

int main()
{
	test();
	test2();
	return 0;
}

例1:利用函数模板封装一个排序的函数,排序规则为从大到小,排序算法为选择排序。

#include <iostream>
using namespace std;

template<class T>
void mySwap(T& a, T& b)
{
	T temp = a;
	a = b;
	b = temp;
}


template<class T>
void sort(T a[],int len)
{
	for (int i = 0; i < len; i++)
	{
		int max = i;
		for (int j = i + 1; j < len; j++)
		{
			if (a[max] < a[j])
				max = j;
		}

		if (max != i)
		{
			mySwap(a[max], a[i]);
		}
	}
}

template<class T>
void print(T arr[], int len)
{
	for (int i = 0; i < len; i++)
	{
		cout << arr[i] << ' ';
	}
}

void test()
{
	char arr[] = "ahkgs";
	int len = sizeof(arr) / sizeof(char);
	sort(arr, len);
	print(arr, len);
}

void test2()
{
	int arr[] = { 2,4,1,6,3,7 };
	int len = sizeof(arr) / sizeof(int);

	sort(arr, len);
	print(arr, len);
}

int main()
{
	//test();
	test2();
	return 0;
}
2.3 普通函数和函数模板的区别
  • 普通函数调用的时候可以发生自动类型转换(隐式类型转换)
  • 函数模板调用时,如果利用自动类型推导,不会发生隐式类型转换
  • 如果利用显示指定类型的方式,可以发生隐式类型转换
#include <iostream>
using namespace std;

// 普通函数
int add(int a, int b)
{
	return a + b;
}

// 模板函数
template<class T>
T myAdd(T a, T b)
{
	return a + b;
}

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

	char c = 'c';

	//cout << add(a, b) << endl;
	//cout << add(a, c) << endl; //c - 99 此时为隐式类型转换

	cout << myAdd(a, b) << endl;
	// 自动类型推导  不会发生隐式转换
	//cout << myAdd(a, c) << endl; //此时报错,不会发生隐式转换

	// 显示指定类型  会发生隐式转换
	cout << myAdd<int>(a, c) << endl;
}

int main()
{
	test();
	return 0;
}
2.4 普通函数与函数模板的调用规则
  1. 如果函数模板和普通函数都可以实现,优先调用普通函数。
  2. 可以通过空模板参数列表来强制调用函数模板。
  3. 函数模板也可以发生重载。
  4. 如果函数模板可以产生更好的匹配,优先调用函数模板。
#include <iostream>
using namespace std;


void print(int a, int b)
{
	cout << "调用普通函数" << endl;
}


template<class T>
void print(T a, T b)
{
	cout << "调用函数模板" << endl;
}

template<class T>
void print(T a, T b,T c)
{
	cout << "调用重载的函数模板" << endl;
}

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

	//print(a, b);

	//通过空模板参数列表强制调用函数模板
	//print<>(a, b);

	//print(a, b, 10);

	// 当函数模板可以更好的匹配,会调用函数模板
	char c1 = 'a';
	char c2 = 'b';

	print(c1, c2);
}

int main()
{
	test();
	return 0;
}

当我们提供了函数模板时,最好不要再提供普通函数了,容易出现二义性。

2.5 模板的局限性

局限性:模板的通用性不是万能的。

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

#include <iostream>
using namespace std;

class Person
{
public:

	Person(string name, int age)
	{
		this->m_Name = name;
		this->m_Age = age;
	}

	string m_Name;
	int m_Age;
};

template<class T>
bool compare(T& a, T& b)
{
	if (a == b)
		return true;
	else
		return false;
}

// 利用具体化Person的版本实现代码,具体化优先调用
template<>bool compare(Person& p1, Person& p2)
{
	if (p1.m_Name == p2.m_Name && p1.m_Age == p2.m_Age)
		return true;
	else
		return false;
}

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

	bool ret = compare(a, b);
	if (ret)
	{
		cout << "a==b" << endl;
	}
	else
		cout << "a!=b" << endl;


}

void test2()
{
	Person p1("张三", 18);
	Person p2("张三", 19);

	bool ret = compare(p1, p2);
	if (ret)
		cout << "p1==p2" << endl;
	else
		cout << "p1!=p2" << endl;
}

int main()
{
	//test();
	test2();
	return 0;
}

3. 类模板

3.1 类模板基本语法

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

语法: template<typename T> 

            类

#include <iostream>
using namespace std;

template<class NameType,class AgeType>
class Person
{
public:

	Person(NameType name, AgeType age)
	{
		this->m_Age = age;
		this->m_Name = name;
	}

	void show()
	{
		cout << "姓名:" << this->m_Name << " 年龄:" << this->m_Age << endl;
	}

	NameType m_Name;
	AgeType m_Age;
};

void test()
{
	Person<string, int>p1("张三", 18);

	p1.show();
}

int main()
{
	test();
	return 0;
}
3.2 类模板和函数模板的区别
  • 类模板没有自动类型推导的使用方法
  • 类模板在函数模板列表中可以有默认参数
#include <iostream>
using namespace std;

template<class NameType,class AgeType=int>  // 只有类模板可以有默认参数
class Person
{
public:

	Person(NameType name, AgeType age)
	{
		this->m_Age = age;
		this->m_Name = name;
	}

	void show()
	{
		cout << "姓名:" << this->m_Name << " 年龄:" << this->m_Age << endl;
	}

	NameType m_Name;
	AgeType m_Age;
};

void test()
{
	//Person p("张三", 18); //有些编译器可以自动类型推导,有些编译器不行
	Person<string,int> p("张三", 18); //最好使用显示指定类型
	p.show();
	
}

void test2()
{
	Person<string>p1("李四", 19);
	p1.show();

}

int main()
{
	test();
	test2();
	return 0;
}
3.3 类模板中成员函数创建时机

  类模板中的成员函数在调用时才创建。

#include <iostream>
using namespace std;


class Person1
{
public:
	void show1()
	{
		cout << "Person1 show" << endl;
	}
};

class Person2
{
public:
	void show2()
	{
		cout << "Person2 show" << endl;
	}
};

template<class T>
class Class
{
public:
	T obj;

	void func1()
	{
		obj.show1();
	}

	void func2()
	{
		obj.show2();
	}
};

void test1()
{
	Class<Person1>m;
	m.func1();
	//m.func2(); 编译错误,说明函数调用才会去创建成员函数
}

int main()
{
	test1();
	return 0;
}
3.4 类模板对象做函数参数

三种传入方式:

  1. 指定传入的类型 --- 直接显示对象的数据类型 (使用比较广泛)
  2. 参数模板化 --- 将对象中的参数变为模板进行传递
  3. 整个类模板化 --- 将这个对象类型模板化进行传递
#include <iostream>
using namespace std;

template<class T1,class T2>
class Person
{
public:

	Person(T1 name, T2 age)
	{
		this->m_Age = age;
		this->m_Name = name;
	}

	void show()
	{
		cout << "name = " << m_Name << " age = " << m_Age << endl;
	}

	T1 m_Name;
	T2 m_Age;
};

//1. 指定传入类型
void print1(Person<string, int>& p)
{
	p.show();
 }

void test1()
{
	Person<string, int>p("张三", 18);
	print1(p);
}


//2. 参数模板化
template<class T1,class T2>
void print2(Person<T1, T2>& p)
{
	p.show();

	cout << "T1的类型:" << typeid(T1).name() << endl;
	cout << "T2的类型:" << typeid(T2).name() << endl;

}

void test2()
{
	Person<string, int>p("李四", 19);
	print2(p);
}
//3. 整个类模板化
template<class T>
void print3(T& p)
{
	p.show();

	cout << "T的类型:" << typeid(T).name() << endl;
}

void test3()
{
	Person<string, int>p("王五", 20);
	print3(p);
}

int main()
{
	//test1();
	//test2();
	test3();
	return 0;
}
3.5 类模板与继承

注意:

  • 当子类继承的父类是一个类模板时,子类在声明的时候,要指定出父类中T的类型。
  • 如果不指定,编译器无法给子类分配内存。
  • 如果想灵活指定出父类中T的类型,子类也需变成类模板。
#include <iostream>
using namespace std;

template<class T>
class Base
{
public:
	T m;
};

//class Son :public Base // 错误,必须要知道父类中T的类型
class Son:public Base<int>
{

};

void test()
{
	Son s1;
}
 
// 想要灵活指定父类中T的类型,子类也需要变为类模板
template<class T,class T2>
class Son2 :public Base<T>
{
	T2 obj;
};

void test2()
{
	Son2<int, char>s;
}


int main()
{
	return 0;
}
3.6 类模板成员函数类外实现
#include <iostream>
using namespace std;

template<class T1,class T2>
class Person
{
public:

	Person(T1 name, T2 age);
		/*{
			this->m_Age = age;
			this->m_Name = name;
		}*/

		void show();
	/*{
		cout << "name = " << m_Name << " age = " << m_Age << endl;
	}*/

	T1 m_Name;
	T2 m_Age;
};

// 构造函数类外实现
template<class T1,class T2>
Person<T1, T2>::Person(T1 name, T2 age)
{
	this->m_Age = age;
	this->m_Name = name;
}

// 成员函数类外实现
template<class T1, class T2>
void Person<T1, T2>::show()
{
	cout << "name = " << m_Name << " age = " << m_Age << endl;
}

void test()
{
	Person<string, int>p("Tom", 18);
	p.show();
}

int main()
{
	test();
	return 0;
}
3.7 类模板分文件编写

类模板中成员函数创建时机是在调试阶段,导致分文件编写时链接不到。

此时有两种解决方法:

1. 直接包含.cpp源文件。

2. 将声明和实现写在同一个文件中,并更改后缀名为.hpp,hpp是约定的名称,可以更改。

方法一:

// 头文件Person.h

#pragma once
#include <iostream>
using namespace std;

template<class T1, class T2>
class Person
{
public:

	Person(T1 name, T2 age);


	void show();


	T1 m_Name;
	T2 m_Age;
};
// 源文件 Person.cpp

#include "Person.h"

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

template<class T1, class T2>
void Person<T1, T2>::show()
{
	cout << "name = " << m_Name << " age = " << m_Age << endl;
}
#include <iostream>
using namespace std;
// 解决方法一 : 将 .h 改为 .cpp
#include "Person.cpp"


void test()
{
	Person<string, int> p("Tom", 18); //此时编译错误
	p.show();
}

int main()
{
	test();
	return 0;
}

方法二:

// 类模板Person.hpp

#pragma once
#include <iostream>
using namespace std;

template<class T1, class T2>
class Person
{
public:

	Person(T1 name, T2 age);


	void show();


	T1 m_Name;
	T2 m_Age;
};


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

template<class T1, class T2>
void Person<T1, T2>::show()
{
	cout << "name = " << m_Name << " age = " << m_Age << endl;
}
#include <iostream>
using namespace std;
#include "Person.hpp"


void test()
{
	Person<string, int> p("Tom", 18); //此时编译错误
	p.show();
}

int main()
{
	test();
	return 0;
}
3.8 类模板与友元

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

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

#include <iostream>
using namespace std;


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


// 类外实现
template<class T1, class T2>
void print2(Person<T1, T2> p)
{
	cout << "类外 name = " << p.m_Name << " age = " << p.m_Age << endl;
}



template<class T1,class T2>
class Person
{
	// 全局函数类内实现
	friend  void print(Person<T1,T2> p)
	{
		cout << "name = " << p.m_Name << " age = " << p.m_Age << endl;
	}

	// 全局函数类外实现
	// 如果全局函数是类外实现的,需要让编译器提前知道这个函数的存在
	friend  void print2<>(Person<T1, T2> p);

public:

	Person(T1 name, T2 age)
	{
		this->m_Age = age;
		this->m_Name = name;
	}
	
private:
	T1 m_Name;
	T2 m_Age;
};





void test()
{
	Person<string, int> p("Tom", 18); //此时编译错误

	print(p);
}

void test2()
{
	Person<string, int> p("Amy", 18);

	print2(p);
}

int main()
{
	//test();
	test2();
	return 0;
}
3.9 案例

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

  • 可以对内置数据类型以及自定义类型的数据进行存储
  • 将数组中的数据存储到堆区
  • 构造函数中可以传入数组的容量
  • 提供对应的拷贝构造函数以及operator=防止浅拷贝问题
  • 提供尾插法和尾删法对数组中的数据进行增加和删除
  • 可以通过下标的方法访问数组中的元素
  • 可以获取数组中当前元素个数和数组的容量
// mwArray.hpp

#pragma once
#include <iostream>
using namespace std;

template<class T>
class myArray
{
public:


	// 有参函数
	myArray(int capacity)
	{
		this->m_capacity = capacity;
		this->m_size = 0;
		pAddress = new T[this->m_capacity];

		//cout << "myArray有参函数调用" << endl;
	}


	// 拷贝函数
	myArray(const myArray& arr)
	{
		this->m_capacity = arr.m_capacity;
		this->m_size = arr.m_size;

		// 深拷贝
		this->pAddress = new T[arr.m_capacity];

		for (int i = 0; i < this->m_size; i++)
		{
			this->pAddress[i] = arr.pAddress[i];
		}

		//cout << "myArray的拷贝函数调用" << endl;
	}

	// operator=
	myArray& operator=(const myArray& arr)
	{
		//cout << "myArray的 operator= 函数调用" << endl;

		if (this->pAddress != NULL)
		{
			delete[] this->pAddress;
			this->pAddress = NULL;
			this->m_capacity = 0;
			this->m_size = 0;
		}

		this->m_capacity = arr.m_capacity;
		this->m_size = arr.m_size;
		this->pAddress = new T[this->m_capacity];
		for (int i = 0; i < this->m_size; i++)
		{
			this->pAddress[i] = arr.pAddress[i];
		}

		return *this;
	}

	// 尾插法
	void push_back(const T& val)
	{
		// 判断容量的大小
		if (this->m_capacity == this->m_size)
		{
			return;
		}

		this->pAddress[this->m_size] = val;
		this->m_size++;
	}

	// 尾删法
	void pop_back()
	{
		if (this->m_size == 0)
		{
			return;
		}

		this->m_size--;
	}

	// 通过下标的方式来访问
	T& operator[](int index)
	{
		return this->pAddress[index];
	}

	// 返回数组容量
	int getcapacity()
	{
		return this->m_capacity;
	}

	// 返回数组大小
	int getsize()
	{
		return this->m_size;
	}

	// 析构函数
	~myArray()
	{
		//cout << "myArray的析构函数调用" << endl;

		if (this->pAddress != NULL)
		{
			delete[] this->pAddress;
			this->pAddress = NULL;
		}
	}

private:

	int m_capacity;
	int m_size;
	T* pAddress;
};
#include <iostream>
using namespace std;
#include "myArray.hpp"


void print(myArray<int>& arr)
{
	for (int i = 0; i < arr.getsize(); i++)
	{
		cout << arr[i] << endl;
	}
}

void test()
{
	myArray<int>arr1(5);

	for (int i = 0; i < 5; i++)
	{
		arr1.push_back(i);
	}

	print(arr1);

	cout << "arr1的容量:" << arr1.getcapacity() << endl;
	cout << "arr1的大小:" << arr1.getsize() << endl;

	myArray<int>arr2(arr1);

	//尾删
	arr2.pop_back();

	cout << "arr2的容量:" << arr2.getcapacity() << endl;
	cout << "arr2的大小:" << arr2.getsize() << endl;


	/*myArray<int>arr2(arr1);

	myArray<int>arr3(100);
	arr3 = arr1;*/
}

// 自定义类型
class Person
{
public:

	Person() {};

	Person(string name, int age)
	{
		this->m_Age = age;
		this->m_Name = name;
	}

	string m_Name;
	int m_Age;
};

void printPerson(myArray<Person>& arr)
{
	for (int i = 0; i < arr.getsize(); i++)
	{
		cout << "name = " << arr[i].m_Name << " age = " << arr[i].m_Age << endl;
	}
}


void test2()
{
	myArray<Person> arr(10);

	Person p1("张三", 18);
	Person p2("李四", 19);
	Person p3("王五", 20);
	Person p4("Tom", 21);
	Person p5("Amy", 22);

	//尾插
	arr.push_back(p1);
	arr.push_back(p2);
	arr.push_back(p3);
	arr.push_back(p4);
	arr.push_back(p5);

	printPerson(arr);

	cout << "arr的容量:" << arr.getcapacity() << endl;
	cout << "arr的大小:" << arr.getsize() << endl;

}

int main()
{
	//test();

	test2();

	return 0;
}

更多推荐