C++ 模板、函数模板、类模板

函数模板、类模板


文章目录

  • 函数模板、类模板
  • 前言
  • 1.模板
    • 1.1 模板的概念
    • 1.2 模板的特点
  • 2. 函数模板
    • 2.1 函数模板语法
    • 2.2 函数模板注意事项
    • 2.3 普通函数与函数模板的区别
    • 2.4 普通函数与函数模板的调用规则
    • 2.5 模板的局限性
    • 2.6 函数模板案例
  • 3. 类模板
    • 3.1 类模板语法
    • 3.2 类模板与函数模板区别
    • 3.3 类模板中成员函数创建时机
    • 3.4 类模板对象做函数参数
    • 3.5 类模板与继承
    • 3.6 类模板成员函数类外实现
    • 3.7 类模板分文件编写
    • 3.8 类模板与友元
  • 总结


前言

本文包含模板的概念、模板的特点、函数模板语法、函数模板注意事项、普通函数与函数模板的区别、普通函数与函数模板的调用规则、模板的局限性、类模板语法、类模板与函数模板区别、类模板中成员函数创建时机、类模板对象做函数参数、类模板与继承、类模板成员函数类外实现、类模板分文件编写、类模板与友元。


1.模板

1.1 模板的概念

(1)、模板就是建立通用的模具,大大提高复用性

(2)、生活中的模板

一寸照片模板:
在这里插入图片描述
PPT模板:
在这里插入图片描述

1.2 模板的特点

(1)、模板不可以直接使用,它只是一个框架;

(2)、模板的通用并不是万能的。

2. 函数模板

(1)、C++另一种编程思想称为泛型编程 ,主要利用的技术就是模板

(2)、C++提供两种模板机制:函数模板类模板

2.1 函数模板语法

函数模板作用:

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

语法:

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

解释:

template — 声明创建模板

typename — 表面其后面的符号是一种数据类型,可以用class代替

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

// 函数模板语法

#include <iostream>  // 包含标准输入输出流头文件
using namespace std;  // 使用标准命名空间

// 交换整型函数
void Int_Swap(int& a, int& b) {  // 使用引用的方式传递,将本体交换,修改实参;不使用引用,则为赋值,形参不修改实参,会创建一个临时变量
	int temp = a;  // 创建一个临时变量存放元素
	a = b;
	b = temp;
}

// 交换双精度浮点型函数
void Double_Swap(double& a, double& b) {
	double temp = a;
	a = b;
	b = temp;
}

// 利用模板提供通用的交换函数
template<typename T>  // 声明一个模板,告诉编译器后面代码中紧跟着的T不要报错,T是一个通用数据类型
void Fun_Swap(T& a, T& b) {  // 使用模板时再指定类型
	T temp = a;
	a = b;
	b = temp;
}

void test() {
	int a = 10;
	int b = 20;
	double c = 5.55;
	double d = 10.11;

	cout << "交换前:" << endl;
	cout << "a = " << a << endl;
	cout << "b = " << b << endl;
	cout << "c = " << c << endl;
	cout << "d = " << d << endl;

	Int_Swap(a, b);
	cout << "使用Int_Swap()函数交换后:" << endl;
	cout << "a = " << a << endl;
	cout << "b = " << b << endl;

	Double_Swap(c, d);
	cout << "使用Double_Swap()函数交换后:" << endl;
	cout << "c = " << c << endl;
	cout << "d = " << d << endl;

	// 1、自动类型推导
	Fun_Swap(a, b);  // 编译器运行时,将变量传入函数形参中,根据变量类型int自动推导出T类型为int
	cout << "使用Fun_Swap()函数模板交换后:" << endl;
	cout << "a = " << a << endl;
	cout << "b = " << b << endl;

	// 2、显示指定类型
	Fun_Swap<double>(c, d);  // <double>指定模板参数类型
	cout << "使用Fun_Swap()函数模板交换后:" << endl;
	cout << "c = " << c << endl;
	cout << "d = " << d << endl;
}

int main() {

	test();

	system("pause");  // 相当于在本地 Windows 调试器中的:请按任意键继续...;暂停,方便看清楚输出结果

	return 0;  // 程序正常退出
}

在这里插入图片描述

2.2 函数模板注意事项

(1)、自动类型推导,必须推导出一致的数据类型T,才可以使用;

// 函数模板注意事项

#include <iostream>  // 包含标准输入输出流头文件
using namespace std;  // 使用标准命名空间

// 利用模板提供通用的交换函数
template<typename T>  // 声明一个模板,告诉编译器后面代码中紧跟着的T不要报错,T是一个通用数据类型
void Fun_Swap(T& a, T& b) {  // 使用模板时再指定类型
	T temp = a;
	a = b;
	b = temp;
}

void test() {

	int a = 10;
	int b = 20;
	double c = 5.55;
	
	// 1、自动类型推导,必须推导出一致的数据类型T,才可以使用
	Fun_Swap(a, b);  // 正确,可以推导出一致的T
	//Fun_Swap(a, c);  // 错误,推导不出一致的T类型
}

int main() {

	test();

	system("pause");  // 相当于在本地 Windows 调试器中的:请按任意键继续...;暂停,方便看清楚输出结果

	return 0;  // 程序正常退出
}

(2)、模板必须要确定出T的数据类型,才可以使用。

// 函数模板注意事项

#include <iostream>  // 包含标准输入输出流头文件
using namespace std;  // 使用标准命名空间

template<typename T>  // 声明一个模板,告诉编译器后面代码中紧跟着的T不要报错,T是一个通用数据类型
void Fun() {
	cout << "调用Fun()函数" << endl;
}

void test() {

	// 2、模板必须要确定出T的数据类型,才可以使用
	//Fun();  // 错误,模板不能独立使用,必须确定出T的类型

	Fun<int>();  // 利用显示指定类型的方式,给T一个类型,才可以使用该模板
}

int main() {

	test();

	system("pause");  // 相当于在本地 Windows 调试器中的:请按任意键继续...;暂停,方便看清楚输出结果

	return 0;  // 程序正常退出
}

在这里插入图片描述

2.3 普通函数与函数模板的区别

(1)、普通函数调用时可以发生自动类型转换(隐式类型转换);

// 普通函数与函数模板区别

#include <iostream>  // 包含标准输入输出流头文件
using namespace std;  // 使用标准命名空间

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

void test() {

	int a = 10;
	char c = 'A';  // A - 65

	// 1、普通函数调用时可以发生自动类型转换(隐式类型转换)
	cout << "a + c = " << Fun_Add01(a, c) << endl;  // 正确,将char类型的'A'隐式转换为int类型  'A' 对应 ASCII码 65
}

int main() {

	test();

	system("pause");  // 相当于在本地 Windows 调试器中的:请按任意键继续...;暂停,方便看清楚输出结果

	return 0;  // 程序正常退出
}

在这里插入图片描述
(2)、函数模板调用时,如果利用自动类型推导,不会发生隐式类型转换;

// 普通函数与函数模板区别

#include <iostream>  // 包含标准输入输出流头文件
using namespace std;  // 使用标准命名空间

// 利用模板提供通用的相加函数
template<typename T>  // 声明一个模板,告诉编译器后面代码中紧跟着的T不要报错,T是一个通用数据类型
int Fun_Add02(T a, T b) {
	return a + b;
}

void test() {

	int a = 10;
	char c = 'A';  // A - 65

	// 2、函数模板调用时,如果利用自动类型推导,不会发生隐式类型转换
	//cout << "a + c = " << Fun_Add02(a, c) << endl; // 报错,使用自动类型推导时,不会发生隐式类型转换
}

int main() {

	test();

	system("pause");  // 相当于在本地 Windows 调试器中的:请按任意键继续...;暂停,方便看清楚输出结果

	return 0;  // 程序正常退出
}

(3)、如果利用显示指定类型的方式,可以发生隐式类型转换。

// 普通函数与函数模板区别

#include <iostream>  // 包含标准输入输出流头文件
using namespace std;  // 使用标准命名空间

// 利用模板提供通用的相加函数
template<typename T>  // 声明一个模板,告诉编译器后面代码中紧跟着的T不要报错,T是一个通用数据类型
// 此处不可使用&,引用的本质在c++内部实现是一个指针常量;指针常量是指针指向不可改,指向a地址后,不可再指向b地址
// 使用赋值,会创建一个临时变量,不改变实参
int Fun_Add02(T a, T b) {  
	return a + b;
}

void test() {

	int a = 10;
	char c = 'A';  // A - 65

	// 3、如果利用显示指定类型的方式,可以发生隐式类型转换
	cout << "a + c = " << Fun_Add02<int>(a, c) << endl;  // 正确,如果用显示指定类型,可以发生隐式类型转换
}

int main() {

	test();

	system("pause");  // 相当于在本地 Windows 调试器中的:请按任意键继续...;暂停,方便看清楚输出结果

	return 0;  // 程序正常退出
}

在这里插入图片描述

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

(1)、如果函数模板和普通函数都可以实现,优先调用普通函数;

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

#include <iostream>  // 包含标准输入输出流头文件
using namespace std;  // 使用标准命名空间

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

// 函数模板
template<typename T>  // 声明一个模板,告诉编译器后面代码中紧跟着的T不要报错,T是一个通用数据类型
void Fun_Print(T a, T b) {
	cout << "调用函数模板!" << endl;
}

void test() {

	int a = 10;
	int b = 20;

	// 1、如果函数模板和普通函数都可以实现,优先调用普通函数
	// 注意 如果告诉编译器  普通函数是有的,但只是声明没有实现,或者不在当前文件内实现,就会报错找不到
	Fun_Print(a, b);
}

int main() {

	test();

	system("pause");  // 相当于在本地 Windows 调试器中的:请按任意键继续...;暂停,方便看清楚输出结果

	return 0;  // 程序正常退出
}

在这里插入图片描述
(2)、可以通过空模板参数列表来强制调用函数模板;

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

#include <iostream>  // 包含标准输入输出流头文件
using namespace std;  // 使用标准命名空间

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

// 函数模板
template<typename T>  // 声明一个模板,告诉编译器后面代码中紧跟着的T不要报错,T是一个通用数据类型
void Fun_Print(T a, T b) {
	cout << "调用函数模板!" << endl;
}

void test() {

	int a = 10;
	int b = 20;

	// 2、可以通过空模板参数列表来强制调用函数模板
	Fun_Print<>(a, b);
}

int main() {

	test();

	system("pause");  // 相当于在本地 Windows 调试器中的:请按任意键继续...;暂停,方便看清楚输出结果

	return 0;  // 程序正常退出
}

在这里插入图片描述
(3)、函数模板也可以发生重载;

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

#include <iostream>  // 包含标准输入输出流头文件
using namespace std;  // 使用标准命名空间

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

// 函数模板
template<typename T>  // 声明一个模板,告诉编译器后面代码中紧跟着的T不要报错,T是一个通用数据类型
void Fun_Print(T a, T b) {
	cout << "调用函数模板!" << endl;
}

// 重载函数模板
template<typename T>  // 声明一个模板,告诉编译器后面代码中紧跟着的T不要报错,T是一个通用数据类型
void Fun_Print(T a, T b, T c) {
	cout << "调用重载函数模板!" << endl;
}

void test() {

	int a = 10;
	int b = 20;

	// 3、函数模板也可以发生重载
	int c = 30;
	Fun_Print<>(a, b, c);
}

int main() {

	test();

	system("pause");  // 相当于在本地 Windows 调试器中的:请按任意键继续...;暂停,方便看清楚输出结果

	return 0;  // 程序正常退出
}

在这里插入图片描述
(4)、如果函数模板可以产生更好的匹配,优先调用函数模板。

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

#include <iostream>  // 包含标准输入输出流头文件
using namespace std;  // 使用标准命名空间

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

// 函数模板
template<typename T>  // 声明一个模板,告诉编译器后面代码中紧跟着的T不要报错,T是一个通用数据类型
void Fun_Print(T a, T b) {
	cout << "调用函数模板!" << endl;
}

void test() {

	int a = 10;
	int b = 20;

	// 4、如果函数模板可以产生更好的匹配,优先调用函数模板
	char d = 'a';
	char e = 'b';
	Fun_Print(d, e);  // 调用函数模板;如果调用普通函数,需要将char转为int,程序为了避免麻烦,调函数模板,可以产生更好的匹配
}

int main() {

	test();

	system("pause");  // 相当于在本地 Windows 调试器中的:请按任意键继续...;暂停,方便看清楚输出结果

	return 0;  // 程序正常退出
}

在这里插入图片描述

2.5 模板的局限性

(1)、模板的通用性并不是万能的

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这样的自定义数据类型,也无法正常运行

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

// 函数模板的局限性

#include <iostream>  // 包含标准输入输出流头文件
using namespace std;  // 使用标准命名空间

// 创建一个Person类
class Person {

public:
	Person(string name, int age) {  // 有参构造,赋值
		this->m_Name = name;
		this->m_Age = age;
	}

	string m_Name;
	int m_Age;
};

// 普通的通用函数模板;判断两个数是否相等
template<typename T>  // 声明一个模板,告诉编译器后面代码中紧跟着的T不要报错,T是一个通用数据类型
bool Fun_Compare(T& a, T& b) {
	// 相等返回true,否则返回false
	if (a == b) {
		return true;
	}
	else {
		return false;
	}
}

// 解决方法:1、运算符重载(麻烦,如果是<,>等都需要重载一遍)

// 具体化,显示具体化的原型和定义以template<>开头,并通过名称来指出类型
// 解决方法:2、具体化优先于常规模板
template<> bool Fun_Compare(Person& p1, Person& p2) {  // template<>告诉编译器为模板的重载
	if (p1.m_Name == p2.m_Name && p1.m_Age == p2.m_Age) {  // 判断实例p1和p2的姓名、年龄是否都相等
		return true;
	}
	else {
		return false;
	}
}

void test01() {

	int a = 10;
	int b = 10;

	// 内置数据类型可以直接使用通用的函数模板
	bool con = Fun_Compare(a, b);

	if (con) {
		cout << "a == b" << endl;
	}
	else {
		cout << "a != b" << endl;
	}
}

void test02() {

	Person p1("张三", 18);
	Person p2("张三", 18);

	// 自定义数据类型,不会调用普通的通用函数模板
	// 可以创建具体化的Person数据类型的模板,用于特殊处理这个类型
	bool con = Fun_Compare(p1, p2);

	if (con) {
		cout << "p1 == p2" << endl;
	}
	else {
		cout << "p1 != p2" << endl;
	}
}

int main() {

	test01();
	test02();

	system("pause");  // 相当于在本地 Windows 调试器中的:请按任意键继续...;暂停,方便看清楚输出结果

	return 0;  // 程序正常退出
}

在这里插入图片描述

2.6 函数模板案例

(1)、利用函数模板封装一个排序的函数,可以对不同数据类型数组进行排序

(2)、排序规则从大到小,排序算法为选择排序

(3)、分别利用char数组int数组进行测试

// 函数模板案例

#include <iostream>  // 包含标准输入输出流头文件
using namespace std;  // 使用标准命名空间

// 利用模板提供通用的交换函数
template<typename T>  // 声明一个模板,告诉编译器后面代码中紧跟着的T不要报错,T是一个通用数据类型
void Fun_Swap(T& a, T& b) {
	T temp = a;  // 创建一个临时变量存放元素
	a = b;
	b = temp;
}

// 利用选择排序,进行对数组从大到小的排序,提供通用排序模板
template<typename T>
void Fun_Sort(T arr[], int len) {
	for (int i = 0; i < len; i++) {  // 获取每一个元素,与它之后的元素进行对比
		int max = i;  // 最大数的下标;假设认定第一个下标为最大值
		for (int j = i + 1; j < len; j++) {  // 获取第i个元素之后的所有元素
			if (arr[max] < arr[j]) {  // 认定的最大值比遍历出的数组要小,说明j下标的元素才是真正的最大值
				max = j;  // 将j赋值给max
			}
		}
		if (max != i) {  // 如果最大数的下标不是i,交换两者
			Fun_Swap(arr[max], arr[i]);  // 将最大下标的元素,与第i个元素进行交换
		}
	}
}

// 打印数组通用模板
template<typename T>
void Fun_Print(T arr[], int len) {
	for (int i = 0; i < len; i++) {
		cout << arr[i] << " ";  // 每个数组元素之间使用空格分开
	}
	cout << endl;  // 换行
}

// 测试int数组
void test01() {

	int int_Arr[] = { 7,2,6,9,1,0,5,3 };
	int len = sizeof(int_Arr) / sizeof(int);  // sizeof():求对象或者类型的大小

	cout << "int型数组排序前:" << endl;
	Fun_Print(int_Arr, len);

	Fun_Sort(int_Arr, len);

	cout << "int型数组排序后:" << endl;
	Fun_Print(int_Arr, len);
}

// 测试char数组
void test02() {

	char char_Arr[] = "ksiudxtenz";
	int len = sizeof(char_Arr) / sizeof(char);

	cout << "char型数组排序前:" << endl;
	Fun_Print(char_Arr, len);

	Fun_Sort(char_Arr, len);

	cout << "char型数组排序后:" << endl;
	Fun_Print(char_Arr, len);
}

int main() {

	test01();
	test02();

	system("pause");  // 相当于在本地 Windows 调试器中的:请按任意键继续...;暂停,方便看清楚输出结果

	return 0;  // 程序正常退出
}

在这里插入图片描述

3. 类模板

3.1 类模板语法

类模板作用:

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

语法:

template<class T>
类

解释:

template — 声明创建模板

class — 表面其后面的符号是一种数据类型,可以用typename代替

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

// 类模板语法

#include <iostream>  // 包含标准输入输出流头文件
using namespace std;  // 使用标准命名空间

// 创建一个Person类模板
template<class nameType, class ageType>  // nameType、ageType为虚拟类型
class Person {

public:
	Person(nameType name, ageType age) {  // 有参构造,赋值
		this->m_Name = name;
		this->m_Age = age;
	}

	void Fun_Print() {  // 打印成员属性
		cout << "姓名:" << this->m_Name << "   年龄:" << this->m_Age << endl;
	}

	nameType m_Name;  // 姓名
	ageType m_Age;  // 年龄
};

void test() {

	// 指定nameType为string类型,ageType为int类型
	Person<string, int> p("张三", 18);  // <string, int>类型参数化;<>模板的参数列表

	p.Fun_Print();  // 调用成员函数Fun_Print()
}

int main() {

	test();

	system("pause");  // 相当于在本地 Windows 调试器中的:请按任意键继续...;暂停,方便看清楚输出结果

	return 0;  // 程序正常退出
}

在这里插入图片描述

3.2 类模板与函数模板区别

(1)、类模板没有自动类型推导的使用方式;

// 类模板与函数模板区别

#include <iostream>  // 包含标准输入输出流头文件
using namespace std;  // 使用标准命名空间

// 创建一个Person类模板
template<class nameType, class ageType>  // nameType、ageType为虚拟类型
class Person {

public:
	Person(nameType name, ageType age) {  // 有参构造,赋值
		this->m_Name = name;
		this->m_Age = age;
	}

	void Fun_Print() {  // 打印成员属性
		cout << "姓名:" << this->m_Name << "   年龄:" << this->m_Age << endl;
	}

	nameType m_Name;  // 姓名
	ageType m_Age;  // 年龄
};

void test() {

	// 1、类模板没有自动类型推导的使用方式
	//Person p("张三", 18);  // 错误 类模板使用时候,不可以用自动类型推导

	// 必须使用显示指定类型的方式,使用类模板
	Person<string, int> p("张三", 18);

	p.Fun_Print();  // 调用成员函数Fun_Print()
}

int main() {

	test();

	system("pause");  // 相当于在本地 Windows 调试器中的:请按任意键继续...;暂停,方便看清楚输出结果

	return 0;  // 程序正常退出
}

在这里插入图片描述
(2)、类模板在模板参数列表中可以有默认参数

// 类模板与函数模板区别

#include <iostream>  // 包含标准输入输出流头文件
using namespace std;  // 使用标准命名空间

// 创建一个Person类模板
template<class nameType, class ageType = int>  // 类模板在模板参数列表中可以有默认参数
class Person {

public:
	Person(nameType name, ageType age) {  // 有参构造,赋值
		this->m_Name = name;
		this->m_Age = age;
	}

	void Fun_Print() {  // 打印成员属性
		cout << "姓名:" << this->m_Name << "   年龄:" << this->m_Age << endl;
	}

	nameType m_Name;  // 姓名
	ageType m_Age;  // 年龄
};

void test() {

	// 2、类模板在模板参数列表中可以有默认参数
	Person<string> p("李四", 40);  // 类模板中的模板参数列表,可以指定默认参数

	p.Fun_Print();  // 调用成员函数Fun_Print()
}

int main() {

	test();

	system("pause");  // 相当于在本地 Windows 调试器中的:请按任意键继续...;暂停,方便看清楚输出结果

	return 0;  // 程序正常退出
}

在这里插入图片描述

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

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

(1)、普通类中的成员函数一开始就可以创建;

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

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

#include <iostream>  // 包含标准输入输出流头文件
using namespace std;  // 使用标准命名空间

// 创建Aoo类
class Aoo {

public:
	void Fun_Print1() {
		cout << "调用Fun_Print1()函数!" << endl;
	}
};

// 创建Boo类
class Boo {

public:
	void Fun_Print2() {
		cout << "调用Fun_Print2()函数!" << endl;
	}
};

// 创建Coo类模板
template<class T>  // 类模板在模板参数列表中可以有默认参数
class Coo {

public:

	T t;

	// 类模板中的成员函数,并不是一开始就创建的,而是在模板调用时再生成

	void Fun1() {    // 没有调用时,不会创建;无法确定obj是何类型
		t.Fun_Print1();
	}

	void Fun2() {
		t.Fun_Print2();
	}
};

void test() {

	Coo<Aoo> c;  // T为Aoo的数据类型

	c.Fun1();  // 相当于Aoo.Fun_Print1()

	// 编译会出错,说明函数调用才会去创建成员函数
	//c.Fun2();  // 编译会出错:Fun_Print2不是Aoo的成员;Aoo.Fun_Print2(),Aoo类中并没有Fun_Print2()函数
}

int main() {

	test();

	system("pause");  // 相当于在本地 Windows 调试器中的:请按任意键继续...;暂停,方便看清楚输出结果

	return 0;  // 程序正常退出
}

在这里插入图片描述

3.4 类模板对象做函数参数

(1)、类模板实例化出的对象,向函数传参的方式;

(2)、一共有三种传入方式:

1)、指定传入的类型 — 直接显示对象的数据类型;

// 类模板对象做函数参数:指定传入的类型

#include <iostream>  // 包含标准输入输出流头文件
using namespace std;  // 使用标准命名空间

// 创建一个Person类模板
template<class nameType, class ageType = int>  // 类模板在模板参数列表中可以有默认参数
class Person {

public:
	Person(nameType name, ageType age) {  // 有参构造,赋值
		this->m_Name = name;
		this->m_Age = age;
	}

	void Fun_Print() {  // 打印成员属性
		cout << "姓名:" << this->m_Name << "   年龄:" << this->m_Age << endl;
	}

public:
	nameType m_Name;  // 姓名
	ageType m_Age;  // 年龄
};

// 1、指定传入的类型
void Print_Person(Person<string>& p) {  // Person<string>类模板的对象p做函数Print_Person中的一个参数
	p.Fun_Print();
}

void test() {

	Person<string> p("张三", 18);  // <string>:类模板参数类型;p("张三", 18):有参构造
	Print_Person(p);  // 调用Print_Person()函数,传入p
}

int main() {

	test();

	system("pause");  // 相当于在本地 Windows 调试器中的:请按任意键继续...;暂停,方便看清楚输出结果

	return 0;  // 程序正常退出
}

在这里插入图片描述
2)参数模板化 — 将对象中的参数变为模板进行传递;

// 类模板对象做函数参数:参数模板化

#include <iostream>  // 包含标准输入输出流头文件
using namespace std;  // 使用标准命名空间

// 创建一个Person类模板
template<class nameType, class ageType = int>  // 类模板在模板参数列表中可以有默认参数
class Person {

public:
	Person(nameType name, ageType age) {  // 有参构造,赋值
		this->m_Name = name;
		this->m_Age = age;
	}

	void Fun_Print() {  // 打印成员属性
		cout << "姓名:" << this->m_Name << "   年龄:" << this->m_Age << endl;
	}

public:
	nameType m_Name;  // 姓名
	ageType m_Age;  // 年龄
};

// 2、参数模板化
template<class T1, class T2>  // 告诉函数T1, T2是模板中的两个参数类型
void Print_Person(Person<T1, T2>& p) {  // 将string, int模板化,变成T1, T2
	p.Fun_Print();

	// typeid()查看类型,name()将类型以字符串形式显示出来
	cout << "T1的类型为: " << typeid(T1).name() << endl;  
	cout << "T2的类型为: " << typeid(T2).name() << endl;
}

void test() {

	Person<string, int> p("李四", 40);  // <string, int>:类模板参数类型;p("李四", 40):有参构造
	Print_Person(p);  // 调用Print_Person()函数,传入p
}

int main() {

	test();

	system("pause");  // 相当于在本地 Windows 调试器中的:请按任意键继续...;暂停,方便看清楚输出结果

	return 0;  // 程序正常退出
}

在这里插入图片描述
3)、整个类模板化 — 将这个对象类型模板化进行传递。

// 类模板对象做函数参数:整个类模板化

#include <iostream>  // 包含标准输入输出流头文件
using namespace std;  // 使用标准命名空间

// 创建一个Person类模板
template<class nameType, class ageType = int>  // 类模板在模板参数列表中可以有默认参数
class Person {

public:
	Person(nameType name, ageType age) {  // 有参构造,赋值
		this->m_Name = name;
		this->m_Age = age;
	}

	void Fun_Print() {  // 打印成员属性
		cout << "姓名:" << this->m_Name << "   年龄:" << this->m_Age << endl;
	}

public:
	nameType m_Name;  // 姓名
	ageType m_Age;  // 年龄
};

// 3、整个类模板化
template<class T>
void Print_Person(T& p) {  // 编译器自动推导出T为Person类型
	p.Fun_Print();

	// typeid()查看类型,name()将类型以字符串形式显示出来
	cout << "T的类型为: " << typeid(T).name() << endl;  
}

void test() {

	Person<string, int> p("王五", 51);
	Print_Person(p);  // 调用Print_Person()函数,传入p
}

int main() {

	test();

	system("pause");  // 相当于在本地 Windows 调试器中的:请按任意键继续...;暂停,方便看清楚输出结果

	return 0;  // 程序正常退出
}

在这里插入图片描述

3.5 类模板与继承

当类模板碰到继承时,需要注意一下几点:

(1)、当子类继承的父类是一个类模板时,子类在声明的时候,要指定出父类中T的类型;

(2)、如果不指定,编译器无法给子类分配内存;

// 类模板继承

#include <iostream>  // 包含标准输入输出流头文件
using namespace std;  // 使用标准命名空间

// 创建一个父类模板
template<class T>
class Aoo {
	T m;
};

//class Boo :public Aoo {};  // 错误,c++编译需要给子类分配内存,必须知道父类中T的类型才可以向下继承
class Boo :public Aoo<int> {  // 必须指定一个类型;此时类型写死,必须是int

};

void test() {

	Boo b;
}

int main() {

	test();

	system("pause");  // 相当于在本地 Windows 调试器中的:请按任意键继续...;暂停,方便看清楚输出结果

	return 0;  // 程序正常退出
}

(3)、如果想灵活指定出父类中T的类型,子类也需变为类模板。

// 类模板继承

#include <iostream>  // 包含标准输入输出流头文件
using namespace std;  // 使用标准命名空间

// 创建一个父类模板
template<class T>
class Aoo {
	T m;
};

// 如果想灵活指定父类中T类型,子类也需要变类模板
template<class T1, class T2>
class Boo :public Aoo<T2> {  // 类模板继承类模板 ,可以用T2指定父类中的T类型

public:
	Boo() {
		// typeid()查看类型,name()将类型以字符串形式显示出来
		cout << "T1的类型为: " << typeid(T1).name() << endl;
		cout << "T2的类型为: " << typeid(T2).name() << endl;
	}

	T1 t;
};

void test() {

	Boo<int, char> b;  // int传给T1,T1为子类Boo中t的类型;char传给T2,T2为父类Aoo中m的类型
}

int main() {

	test();

	system("pause");  // 相当于在本地 Windows 调试器中的:请按任意键继续...;暂停,方便看清楚输出结果

	return 0;  // 程序正常退出
}

在这里插入图片描述

3.6 类模板成员函数类外实现

// 类模板中成员函数类外实现

#include <iostream>  // 包含标准输入输出流头文件
using namespace std;  // 使用标准命名空间

// 创建一个类模板,只声明
template<class T1, class T2>
class Person {

public:

	// 成员函数类内声明
	Person(T1 name, T2 age);
	void Fun_Print();

public:

	T1 m_Name;  // 姓名
	T2 m_Age;  // 年龄
};

// 构造函数 类外实现
template<class T1, class T2>
Person<T1, T2>::Person(T1 name, T2 age) {  // <T1, T2>类模板的类外实现;Person::Person(T1 name, T2 age)普通函数的类外实现;Person::作用域
	this->m_Name = name;
	this->m_Age = age;
}

// 成员函数 类外实现
template<class T1, class T2>
void Person<T1, T2>::Fun_Print() {  // Person类::作用域下的Fun_Print()函数,<T1, T2>体现为模板参数列表,template<class T1, class T2>告诉服务器<T1, T2>为类模板中的成员函数参数类型
	cout << "姓名:" << this->m_Name << "   年龄:" << this->m_Age << endl;
}

void test() {

	Person<string, int> p("张三", 18);
	p.Fun_Print();
}

int main() {

	test();

	system("pause");  // 相当于在本地 Windows 调试器中的:请按任意键继续...;暂停,方便看清楚输出结果

	return 0;  // 程序正常退出
}

在这里插入图片描述

3.7 类模板分文件编写

类模板成员函数分文件编写产生的问题:

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

解决方式:

(1)、解决方式1:直接包含.cpp源文件(将包含头文件person.h改为person.cpp);

Person.h中代码:

#pragma once  // 防止头文件重复包含

#include <iostream>  // 包含标准输入输出流头文件
using namespace std;  // 使用标准命名空间

// 创建一个类模板,只声明
template<class T1, class T2>
class Person {

public:

	// 成员函数类内声明
	Person(T1 name, T2 age);
	void Fun_Print();

public:

	T1 m_Name;  // 姓名
	T2 m_Age;  // 年龄
};

Person.cpp中代码:

# include "Person.h"  // 包含Person.h头文件

// 构造函数 类外实现
template<class T1, class T2>
Person<T1, T2>::Person(T1 name, T2 age) {  // <T1, T2>类模板的类外实现;Person::Person(T1 name, T2 age)普通函数的类外实现;Person::作用域
	this->m_Name = name;
	this->m_Age = age;
}

// 成员函数 类外实现
template<class T1, class T2>
void Person<T1, T2>::Fun_Print() {  // Person类::作用域下的Fun_Print()函数,<T1, T2>体现为模板参数列表,template<class T1, class T2>告诉服务器<T1, T2>为类模板中的成员函数参数类型
	cout << "姓名:" << this->m_Name << "   年龄:" << this->m_Age << endl;
}

main.cpp中代码:

//#include "person.h"  // 模板函数,调用时生成,包含person.h头文件,头文件中的模板函数Person、showPerson并不会生成产生这两个函数,看不到也不会去找cpp中的函数

#include "Person.cpp"  // 解决方式1:直接包含Person.cpp源文件

void test() {

	Person<string, int> p("LZQ", 25);
	p.Fun_Print();  // 调用p对象的Fun_Print()函数
}

int main() {

	test();

	system("pause");  // 相当于在本地 Windows 调试器中的:请按任意键继续...;暂停,方便看清楚输出结果

	return 0;  // 程序正常退出
}

在这里插入图片描述
(2)、解决方式2:将声明和实现写到同一个文件中,并更改后缀名为.hpp,hpp是约定的名称,并不是强制。

Person.hpp中代码:

#pragma once  // 防止头文件重复包含

#include <iostream>  // 包含标准输入输出流头文件
using namespace std;  // 使用标准命名空间

 // 创建一个类模板,只声明
template<class T1, class T2>
class Person {

public:

	// 成员函数类内声明
	Person(T1 name, T2 age);
	void Fun_Print();

public:

	T1 m_Name;  // 姓名
	T2 m_Age;  // 年龄
};

// 构造函数 类外实现
template<class T1, class T2>
Person<T1, T2>::Person(T1 name, T2 age) {  // <T1, T2>类模板的类外实现;Person::Person(T1 name, T2 age)普通函数的类外实现;Person::作用域
	this->m_Name = name;
	this->m_Age = age;
}

// 成员函数 类外实现
template<class T1, class T2>
void Person<T1, T2>::Fun_Print() {  // Person类::作用域下的Fun_Print()函数,<T1, T2>体现为模板参数列表,template<class T1, class T2>告诉服务器<T1, T2>为类模板中的成员函数参数类型
	cout << "姓名:" << this->m_Name << "   年龄:" << this->m_Age << endl;
}

main.cpp中代码:

// 解决方式2:将声明和实现写到一起,文件后缀名改为.hpp(hpp一般看见,就知道是类模板,约定俗成)
#include "person.hpp"

void test() {

	Person<string, int> p("LZQ", 25);
	p.Fun_Print();  // 调用p对象的Fun_Print()函数
}

int main() {

	test();

	system("pause");  // 相当于在本地 Windows 调试器中的:请按任意键继续...;暂停,方便看清楚输出结果

	return 0;  // 程序正常退出
}

在这里插入图片描述

3.8 类模板与友元

类模板配合友元函数的类内和类外实现

(1)、全局函数类内实现 ----- 直接在类内声明友元即可;

// 类模板中成员函数类内实现

#include <iostream>  // 包含标准输入输出流头文件
using namespace std;  // 使用标准命名空间

 // 创建一个类模板Person
template<class T1, class T2>
class Person {

	// 1、全局函数配合友元   类内实现  (直接在类内写全局有元函数的函数定义和函数实现)
	friend void Fun_Print(Person<T1, T2>& p) {  // Person类型,有两个属性<T1, T2>,参数模板化
		cout << "姓名:" << p.m_Name << "   年龄:" << p.m_Age << endl;
	}

public:

	Person(T1 name, T2 age) {  // 构造函数需写在public公共下,否则无法初始化对象
		this->m_Name = name;
		this->m_Age = age;
	}

private:  // 私人属性

	T1 m_Name;  // 姓名
	T2 m_Age;  // 年龄
};

void test() {

	// 1、全局函数在类内实现

	Person <string, int> p("张三", 18);  // 创建Person对象p("张三", 18);
	Fun_Print(p);  // 调用全局函数
}

int main() {

	test();

	system("pause");  // 相当于在本地 Windows 调试器中的:请按任意键继续...;暂停,方便看清楚输出结果

	return 0;  // 程序正常退出
}

在这里插入图片描述
(2)、全局函数类外实现 ----- 需要提前让编译器知道全局函数的存在。

// 类模板中成员函数类外实现

#include <iostream>  // 包含标准输入输出流头文件
using namespace std;  // 使用标准命名空间

// 2、全局函数配合友元  类外实现 - 先做函数模板声明,下方在做函数模板定义,在做友元
// 提前让编译器知道Person类的存在
template<class T1, class T2> class Person;

// 如果声明了函数模板,可以将实现写到后面,否则需要将实现体写到类的前面让编译器提前看到
//template<class T1, class T2> void Fun_Print(Person<T1, T2> & p); 

template<class T1, class T2>
// 函数模板的实现;先让编译器看到这有一个类外实现的模板函数定义;函数中有个Person类型,需让编译器知道有个Person存在,并告诉编译器这是一个模板,加上模板template<class T1, class T2>
void Fun_Print(Person<T1, T2>& p) {  // 全局函数,不需要加作用域
	cout << "类外实现 ----    姓名:" << p.m_Name << "   年龄:" << p.m_Age << endl;
}

 // 创建一个类模板Person
template<class T1, class T2>
class Person {

	// 2、全局函数配合友元  类外实现
	// 加一个空模板参数列表<>;代表函数模板的函数声明
	// 如果全局函数是类外实现,需要让编译器提前知道这个函数的存在;本质发生变化,这是一个函数模板
	// 如果不加<>,实际是一个普通函数的声明;和类外的函数模板实现不是一套东西
	friend void Fun_Print<>(Person<T1, T2>& p);

public:

	Person(T1 name, T2 age) {  // 构造函数需写在public公共下,否则无法初始化对象
		this->m_Name = name;
		this->m_Age = age;
	}

private:  // 私人属性

	T1 m_Name;  // 姓名
	T2 m_Age;  // 年龄
};

void test() {

	// 2、全局函数在类外实现

	Person <string, int> p("张三丰", 999);  // 创建Person对象p("张三丰", 999);
	Fun_Print(p);  // 调用全局函数
}

int main() {

	test();

	system("pause");  // 相当于在本地 Windows 调试器中的:请按任意键继续...;暂停,方便看清楚输出结果

	return 0;  // 程序正常退出
}

在这里插入图片描述
经典案例:类模板案例


总结

(1)、函数模板利用关键字 template;

(2)、使用函数模板有两种方式:自动类型推导、显示指定类型;

(3)、模板的目的是为了提高复用性,将类型参数化;

(4)、使用模板时必须确定出通用数据类型T,并且能够推导出一致的类型;

(5)、建议使用显示指定类型的方式,调用函数模板,因为可以自己确定通用类型T;

(6)、既然提供了函数模板,最好就不要提供普通函数,否则容易出现二义性;

(7)、利用具体化的模板,可以解决自定义类型的通用化;

(8)、学习模板并不是为了写模板,而是在STL能够运用系统提供的模板;

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

(10)、类模板使用只能用显示指定类型方式;

(11)、类模板中的模板参数列表可以有默认参数;

(12)、类模板中的成员函数并不是一开始就创建的,在调用时才去创建;

(13)、通过类模板创建的对象,可以有三种方式向函数中进行传参;

(14)、使用比较广泛是第一种:指定传入的类型;

(15)、如果父类是类模板,子类需要指定出父类中T的数据类型;

(16)、类模板中成员函数类外实现时,需要加上模板参数列表;

(17)、类模板分文件编写主流的解决方式是第二种,将类模板成员函数写到一起,并将后缀名改为.hpp

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

(19)、模板可以提高代码复用,需要熟练掌握。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/5667.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

2023年Java程序员生存指南,8个中肯建议度过寒潮!

最近小源看到一个2023Java程序员生存指南&#xff0c;觉得挺有意思的&#xff0c;今天分享给大家&#xff01; 第一&#xff0c;不要在互联网公司基于低代码做开发。 第二&#xff0c;保证一定强度的刷题&#xff0c;原因嘛你懂的~ 第三&#xff0c;凡是要求名校的岗位&#xf…

C#,码海拾贝(08)——埃特金(Aitken)逐步曲线插值算法,《C#数值计算算法编程》源代码升级改进版

埃特金逐步线性插值法&#xff08;Aitken successive linear interpolation method&#xff09;一种能逐步升阶的插值方法.用拉格朗日插值多项式计算函数近似值时&#xff0c;如果精度不满足要求&#xff0c;需增加插值节点以提高插值多项式次数时&#xff0c;原来算出的结果均…

字节二面,原来我对自动化测试的理解太浅了

如果你入职一家新的公司&#xff0c;领导让你开展自动化测试&#xff0c;作为一个新人&#xff0c;你肯定会手忙脚乱&#xff0c;你会如何落地自动化测试呢&#xff1f; 01 什么是自动化 有很多人做了很长时间的自动化但却连自动化的概念都不清楚&#xff0c;这样的人也是很悲…

七、网络安全

&#xff08;一&#xff09;网络安全设计 1、网络安全体系设计 &#xff08;1&#xff09;物理安全 通信线路的可靠性、硬软件设备的安全性、设备的备份、防灾害防干扰能力、设备的运行环境、不间断电源 eg机房安全 &#xff08;2&#xff09;系统安全 操作系统本身的缺陷…

vue尚品汇商城项目-day01【4.完成非路由组件Header与Footer业务】

文章目录4.完成非路由组件Header与Footer业务4.1使用组件的步骤&#xff08;非路由组件&#xff09;本人其他相关文章链接4.完成非路由组件Header与Footer业务 在咱们项目开发中&#xff0c;不在以HTML CSS 为主&#xff0c;主要搞业务、逻辑 开发项目的流程&#xff1a; (1)…

模式识别 —— 第六章 支持向量机(SVM)与核(Kernel)

模式识别 —— 第六章 支持向量机&#xff08;SVM&#xff09;与核&#xff08;Kernel&#xff09; 文章目录模式识别 —— 第六章 支持向量机&#xff08;SVM&#xff09;与核&#xff08;Kernel&#xff09;硬间隔&#xff08;Hard-Margin&#xff09;软间隔&#xff08;Soft…

52LeetCode刷题_LeetCode刷题手册

虽然刷题一直饱受诟病&#xff0c;不过不可否认刷题确实能锻炼我们的编程能力&#xff0c;相信每个认真刷题的人都会有体会。现在提供在线编程评测的平台有很多&#xff0c;比较有名的有 hihocoder&#xff0c;LintCode&#xff0c;以及这里我们关注的 LeetCode。 LeetCode收录…

Spring 注解和 XML 配置文件重复定义 Bean,会怎样?

作者&#xff1a;明明如月学长&#xff0c; CSDN 博客专家&#xff0c;蚂蚁集团高级 Java 工程师&#xff0c;《性能优化方法论》作者、《解锁大厂思维&#xff1a;剖析《阿里巴巴Java开发手册》》、《再学经典&#xff1a;《EffectiveJava》独家解析》专栏作者。 热门文章推荐…

iPhone屏幕适配(之屏幕尺寸)

Device screen size 各设备屏幕尺寸 DeviceDimensions (portrait)iPhone 14 Pro Max430x932 pt (1290x2796 px 3x)iPhone 14 Pro393x852 pt (1179x2556 px 3x)iPhone 14 Plus428x926 pt (1284x2778 px 3x)iPhone 14390x844 pt (1170x2532 px 3x)iPhone 13 Pro Max428x926 pt (…

Element Plus 实例详解(七)___Typography 排版

Element Plus 实例详解&#xff08;七&#xff09;___Typography 排版 目录 一、前言 二、搭建Element Plus试用环境 1、搭建Vue3项目&#xff08;基于Vite Vue&#xff09; 2、安装Element Plus 三、Element Plus Typography 排版功能试用 1、字号 2、行高 3、Font-fam…

C语言:位运算符----与(),或(|),非(~),异或(^),左移(<<)和右移(>>)

C语言 基础开发----目录 一、位运算符----简介 位运算符 就是按二进制位进行运算。 C语言中位运算符主要包括六种&#xff0c;具体如下&#xff1a; 与(&)&#xff0c;或(|)&#xff0c;非(~)&#xff0c;异或(^)&#xff0c;左移(<<)和右移(>>) 位运算符含…

【C++】类和对象(三)

类和对象&#xff08;三&#xff09; 拷贝构造函数&#xff1a; 当我们想要将一个已确定的类变量的值拷贝给另外一个相同类型的类变量&#xff0c;有什么快捷的方法吗&#xff1f; 就相当于定义了一个int类型的i10&#xff0c;想将i复制给一个刚初始化的遍历j&#xff0c;in…

2022国赛E题完整成品文章数据代码模型--小批量物料的生产安排

基于LSTM循环神经网络的小批量物料生产安排分析 摘要 某电子产品制造企业面临以下问题&#xff1a;在多品种小批量的物料生产中&#xff0c;事先无法知道物料的 实际需求量。企业希望运用数学方法&#xff0c;分析已有的历史数据&#xff0c;建立数学模型&#xff0c;帮助企业…

优化测试生命周期行之有效的三种方法

确保软件质量和按时交付产品的最有效方法是什么&#xff1f;对于公司来说&#xff0c;无缺陷地为客户带来价值是一件重要的事情。随着软件开发生命周期变得越来越复杂&#xff0c;测试可能成为拖慢整个过程的瓶颈。为了加速它&#xff0c;创建了组织可以采用的多种策略和方法。…

python面向对象编程

&#x1f42c;在本次的博客当中我们要学习的是在python语言当中的面向对象的编程。我们之前学过的C语言是面向对象的编程。面向过程&#xff0c;其实就是面向着具体的每一个步骤和过程&#xff0c;把每一个步骤和过程完成&#xff0c;然后由这些功能方法相互调用&#xff0c;完…

Go语言精修(尚硅谷笔记)第十七和十八章

十七、反射 17.1 基本介绍 1 ) 反射可以在运行时动态获取变量的各种信息, 比如变量的类型(type)&#xff0c;类别(kind) 2 ) 如果是结构体变量&#xff0c;还可以获取到结构体本身的信息(包括结构体的字段、方法) 3 ) 通过反射&#xff0c;可以修改变量的值&#xff0c;可以…

react脚手架

一、首先了解一下react脚手架 .xxx脚手架: 用来帮助程序员快速创建一个基于xxx库的模板项目 a.包含了所有需要的配置&#xff08;语法检查、jsx编译devServer…&#xff09; b.下载好了所有相关的依赖 c.可以直接运行一个简单效果react提供了一个用于创建react项目的脚手架库:…

LLaMA:Open and Efficient Foundation Language Models

LLaMA&#xff1a;Open and Efficient Foundation Language ModelsIntroductionApproachPre-training DataArchitectureIntroduction 在大规模数据下训练的大模型&#xff0c;已经展示了很好的表现&#xff0c;当模型足够大的时&#xff0c;模型会出现一个涌现的能力&#xff…

Chapter8.3:控制系统校正的根轨迹法

该系列博客主要讲述Matlab软件在自动控制方面的应用&#xff0c;如无自动控制理论基础&#xff0c;请先学习自动控制系列博文&#xff0c;该系列博客不再详细讲解自动控制理论知识。 自动控制理论基础相关链接&#xff1a;https://blog.csdn.net/qq_39032096/category_10287468…

区块链技术之密码学

密码学是研究编制密码和破译密码的技术科学&#xff0c;研究密码变化的客观规律&#xff0c;应用于编制密码以保守通信秘密的&#xff0c;成为编码学&#xff1b;应用于破译密码以获取通信情报的&#xff0c;称为破译学&#xff0c;总称密码学。在区块链中重要问题之一就是区块…