IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> [12] 初识C++模板 -> 正文阅读

[C++知识库][12] 初识C++模板

C++模板


写C/C++代码的时候总是会存在重复造轮子的情况,C++就比较好一点,函数有重载的特性,可以减少一些代码量,而C语言就真的要自己一个个写出来。C++还有一个强大的工具,就是模板(template)。 模板可以减少大量的代码量。C++模板有分有函数模板和类模板。

函数模板

没有模板之前,写同一个实现的功能,需要写重载函数,有了函数模板之后,就可以把类型当作未知量去使用,从而减少重载带来的代码量。

函数模板的写法:

template <typename 未知类型的名字> 函数返回值 函数名(形参列表)
{
    ···
    函数体
    ···
}

注意的是template <typename 未知类型的名字>和函数返回值 函数名是写在同一行的,但是一般为了更清楚的看函数就把这两个分成两行来写。

在template的尖括号中的typename可以使用class代替,表示的是一个意思,即以下两行代码表示是同一个意思

template<typename T>
template<class T>

简单使用一下函数模板:

  • 不用函数模板的情况
#include<iostream>
using namespace std;

int Max(int a, int b)
{
	return a > b ? a : b;
}

string Max(string str1, string str2)
{
	return str1 > str2 ? str1 : str2;
}

double Max(double d1, double d2)
{
	return d1 > d2 ? d1 : d2;
}

int main()
{
	cout << Max(2, 5) << endl;
	cout << Max(3.14159, 5.57) << endl;
	cout << Max("ILoveyou", "IMissyou") << endl;
	return 0;
}
5
5.57
IMissyou

代码比较多,而且这三个Max的重载函数实现的功能都是一样的,只是换了参数而已。

  • 使用函数模板
#include<iostream>
using namespace std;

template <typename T>
T Max(T a, T b)
{
	return a > b ? a : b;
}
//这里的T就表示一个未知的类型,根据传进来的参数不同就变成对应的类型

int main()
{
	cout << Max(2, 5) << endl;
	cout << Max(3.14159, 5.57) << endl;
	cout << Max("ILoveyou", "IMissyou") << endl;

	return 0;
}
5
5.57
ILoveyou

细心的同学就可以发现前面的比较数字是没什么问题,但是比较字符串的时候就出了点分歧。函数重载的方式说IMissyou比较大,而函数模板的方式说ILoveyou比较大。这个是因为下面函数模板比较字符串的时候把ILoveyou和IMissyou理解常const char*类型了。我们的字符串比较的认知中,无论是按单个字符的ASCII比还是整体的ASCII比都是IMissyou的比较大。const char*是不能用 ‘>’ 等运算符直接比较的,所以在上面得出结果是ILoveyou比较大是因为ILoveyou和IMissyou比较的是它们自己的地址,不是字符串本身。如果把

cout<<Max("ILoveyou","IMissyou")<<endl;

改成

cout<<Max(string("ILoveyou"),string("IMissyou"))<<endl;

这样就是正常的比较字符串本身了,string的字符串比较和const char*的字符串比较是一样的都是按字符的ASCII比,只是说string可以直接用 '>'等运算符直接比,而const char*不能。

类与类之间使用函数模板的比较

重点就是类里面的运算符重载的问题,因为类在没有写运算符重载的时候是不能之间使用 '>'等运算符之间比较的。没有写运算符重载类和类之间的比较也不知道比什么。所以当函数模板存在类类型作为参数并且要比较它们的时候,就必须写类的运算符重载。

  • 看代码,没有写重载的时候比较两个类
#include<iostream>
using namespace std;

class MM
{
public:
	MM(string name,int faceScore):name(name),faceScore(faceScore){}
	void print()
	{
		cout << name << '\t' << faceScore << endl;
	}
protected:
	string name;
	int faceScore;
};

template <typename T>
T Max(T a, T b)
{
	return a > b ? a : b;
}

int main()
{
	MM mm1("Alice", 100);
	MM mm2("Coco", 80);
	MM result = Max(mm1, mm2);
	result.print();
	return 0;
}

运行肯定是不会通过的,并且会报下面的错误:
C2676 二进制“>”:“T”不定义该运算符或到预定义运算符可接收的类型的转换

原因就是没有写运算符重载,类和类之间没有比较的依据

写了运算符重载后:

#include<iostream>
using namespace std;

class MM
{
	friend bool operator>(const MM& mm1, const MM& mm2);
public:
	MM(string name,int faceScore):name(name),faceScore(faceScore){}
	void print()
	{
		cout << name << '\t' << faceScore << endl;
	}
protected:
	string name;
	int faceScore;
};

template <typename T>
T Max(T a, T b)
{
	return a > b ? a : b;
}

int main()
{
	MM mm1("Alice", 100);
	MM mm2("Coco", 80);
	MM result = Max(mm1, mm2);
	result.print();
	return 0;
}

bool operator>(const MM& mm1, const MM& mm2)
{
	return mm1.faceScore > mm2.faceScore ? mm1.faceScore : mm2.faceScore;
}

程序可以正常运行。

输出结果:

Alice	100

显式调用与隐式调用

函数模板的显示调用很简单,就是在调用的时候加一对尖括号,尖括号里面写显式调用的类型;隐式调用则什么都不用写,程序运行调用函数的时候会自己推断类型,然后生成对应类型的函数。

直接看代码:

#include<iostream>
using namespace std;

class MM
{
	friend bool operator>(const MM& mm1, const MM& mm2);
public:
	MM(string name,int faceScore):name(name),faceScore(faceScore){}
	void print()
	{
		cout << name << '\t' << faceScore << endl;
	}
protected:
	string name;
	int faceScore;
};

template <typename T>
T Max(T a, T b)
{
	return a > b ? a : b;
}

int main()
{
	MM mm1("Alice", 100);
	MM mm2("Coco", 80);
	MM result = Max(mm1, mm2);		//这个是隐式调用
	result.print();
    MM result2=Max<MM>(mm1,mm2);	//这个是显式调用
    result2.print();
	return 0;
}

bool operator>(const MM& mm1, const MM& mm2)
{
	return mm1.faceScore > mm2.faceScore ? mm1.faceScore : mm2.faceScore;
}

同时存在函数模板和普通函数时,隐式调用则优先调用已有的普通函数,然后才考虑调用模板函数。如果使用了显示调用的话就只会调用函数模板

#include<iostream>
using namespace std;

template<typename T>
T Max(T a, T b)
{
	cout << "函数模板" << endl;
	return a > b ? a : b;
}

int Max(int a, int b)
{
	cout << "普通函数" << endl;
	return a > b ? a : b;
}

int main()
{
	cout << Max(55, 46) << endl;
	cout << Max<int>(3, 78) << endl;
	return 0;
}
普通函数
55
函数模板
78

我们把普通函数注释掉的时候再运行程序会得到下面结果:

函数模板
55
函数模板
78

我们再把函数模板注释掉,然后留下普通函数,再运行程序会得到下面结果:

然后就会报下面错误:
E0299 无法确定需要哪个 函数模板 “std::endl” 实例

综上所述,隐式调用的时候,优先调用普通函数,如果没有对应类型的普通函数才会调用函数模板。而显式调用则是必须存在函数模板才可以正确调用

存在多个未知类型的函数模板调用情况

函数模板是可以存在多个未知类型的

template <class T1,class T2>
.....

调用情况:

  • 如果实参是同一个类型的时候优先调用同类型的未知类型
  • 如果实参不是同一个类型的时候,就对应调用合适的函数模板

直接看代码:

#include<iostream>
using namespace std;

template <class T1>
void print(T1 a, T1 b)
{
	cout << "T1+T1" << endl;
	cout << a << '\t' << b << endl;
}

template <class T1,class T2>
void print(T1 a, T2 b)
{
	cout << "T1+T2" << endl;
	cout << a << '\t' << b << endl;
}

int main()
{
	print(5, 6);
	print("Hello", 99);
	print(5.9, 5);
	print('a', "Yes");
	print('h', 't');
	return 0;
}
T1+T1
5       6
T1+T2
Hello   99
T1+T2
5.9     5
T1+T2
a       Yes
T1+T1
h       t

函数模板的注意点:

  • 函数模板本身在编译时不会生成任何目标代码,只有由模板生成的实例会生成目标代码。即用到模板的时候才会生成对应的函数
  • 被多个源文件引用的函数模板,应当连同函数体一同放在头文件中,而不能向普通函数一样只将声明放在头文件中。即模板的实现和头文件写在一起;.hpp文件而不是.h文件
  • 函数指针也只能指向模板的实例,而不能指向模板本身。即模板只是模型,本身不存在。在使用模板的时候才会生成对应的函数

类模板

使用类模板使用户可以为类定义一种模式,使得类中的某些数据成员、某些成员函数的参数、返回值或局部变量能取不同类型(包括系统预定义的和用户自定义的)。这个是《清华大学出版社C++语言程序设计》的原话。简单的理解类模板就是,类中存在未知类型的数据那就是类模板

类模板的基本概念

类模板:

  • 类中用到未知类型
  • 必须显式调用
  • 所有用到类名的地方都要用:类名<未知类型> 的方式使用
  • 类模板只有实例化的时候才生成对应的类,本身不是类,而是一个模板
#include<iostream>
using namespace std;

//下面这个类就是类模板
template <class T>
class MM
{
public:
	void print();
	void printData()
	{
		cout << "类中实现没有用到类名" << endl;
	}
};
template <class T>
void MM<T>::print()
{
	cout << "类外实现" << endl;
}

int main()
{
	MM<int> mm;	//这里要显式使用
	mm.print();
	mm.printData();
	return 0;
}

从代码中可以看到的是,除了一开始写MM类的时候,MM不用加尖括号,而后面的每出现一次就使用尖括号的方式写代码,然后创建对象的时候,也是使用了尖括号(显式的使用)。

再看下面的例子,可以看到结构体类型作为实参传入的话要特殊写一下,然后在构建对象的时候结构体类型也是按结构体初始化的方式做。

#include<iostream>
using namespace std;

template <class T1,class T2>
class MM
{
public:
    //构造函数的MM是函数名不是类名,只不过长得和类名一样,所以不用加<>
	MM(T1 data1,T2 data2):data1(data1),data2(data2){}
	void print();
	void printData();
protected:
	T1 data1;
	T2 data2;
};

struct Score
{
	int faceScore;
	int math;
};

int main()
{
    //使用到了类名,所以需要加<>,并且这里要显式的书写
	MM<string, int> mm("Alice", 18);
	mm.print();
	MM<string, Score> mm2("Coco", { 99,78 });
	mm2.printData();
	return 0;
}
//声明了两个未知的数据类型分别为T1和T2
template<class T1, class T2>
void MM<T1, T2>::print()
{
	cout << data1 << '\t' << data2 << endl;
}

template<class T1, class T2>
void MM<T1, T2>::printData()
{
	cout << data1 << '\t' << data2.math << '\t' << data2.faceScore << endl;
}

类模板的特化

原来声明的模板类中有多个未知类型,然后特化的作用就是,把这几个类型给它特殊化了,特殊成少一点的未知类型。

举个简单的例子,原来一个模板类中有三个未知数据类型,然后特殊化后可以,特殊化成这三个未知的数据类型变成同一个类型的未知数据类型。

直接看代码:

#include<iostream>
using namespace std;

//本来有三个未知的数据类型,相互之间可能不相同,也可以相同
template <class T1,class T2,class T3>
class MM	//声明类的时候,因为这样说明是模板,所以也不需要尖括号
{
public:
	//构造函数不是类名 故不用尖括号
	MM(T1 Data1,T2 Data2,T3 Data3):Data1(Data1),Data2(Data2),Data3(Data3)
	{
		cout << "类模板" << endl;
	}
	void printData();
protected:
	T1 Data1;
	T2 Data2;
	T3 Data3;
};

//模板类的成员函数在类外实现的时候需要用到类名,这个时候类名就需要加尖括号
template<class T1, class T2, class T3>
void MM<T1, T2, T3>::printData()	
{
	cout << Data1 << '\t' << Data2 << '\t' << Data3 << endl;
}

//类的特化,使三个未知的数据类型变成三个都相同的数据类型
template <class T>
class MM<T, T, T>
{
public:
	MM(T Data1,T Data2,T Data3):Data1(Data1),Data2(Data2),Data3(Data3)
	{
		cout << "类模板的特化,3->1" << endl;
	}
protected:
	T Data1;
	T Data2;
	T Data3;
};


int main()
{
	MM<string, int, int> mm("Alice", 18, 1001);
	MM<int, int, int> mm2(5, 2, 0);
	return 0;
}
类模板
类模板的特化,3->1

就是很简单,本来是有三个互不相同的数据类型,经过特化后变成三个相同 的类型,如果显式传参的是int那三个未知数据类型都变成int如果是显式传参是string那三个未知数据类型都变成string

当然也是可以是两个未知数据类型相同,然后第三个未知数据类型与前面两个不同,也可以是后面两个相同,前面那个不同,当然也可以有4个未知数据类型,然后再在这4个未知数据类型里面进行特化操作。

#include<iostream>
using namespace std;

//本来有三个未知的数据类型,相互之间可能不相同,也可以相同
template <class T1,class T2,class T3>
class MM	//声明类的时候,因为这样说明是模板,所以也不需要尖括号
{
public:
	//构造函数不是类名 故不用尖括号
	MM(T1 Data1,T2 Data2,T3 Data3):Data1(Data1),Data2(Data2),Data3(Data3)
	{
		cout << "类模板" << endl;
	}
	void printData();
protected:
	T1 Data1;
	T2 Data2;
	T3 Data3;
};

//模板类的成员函数在类外实现的时候需要用到类名,这个时候类名就需要加尖括号
template<class T1, class T2, class T3>
void MM<T1, T2, T3>::printData()	
{
	cout << Data1 << '\t' << Data2 << '\t' << Data3 << endl;
}

//类的特化,使三个未知的数据类型变成三个都相同的数据类型
template <class T>
class MM<T, T, T>
{
public:
	MM(T Data1,T Data2,T Data3):Data1(Data1),Data2(Data2),Data3(Data3)
	{
		cout << "类模板的特化,3->1" << endl;
	}
protected:
	T Data1;
	T Data2;
	T Data3;
};

template <class T1,class T2>
class MM<T1, T2, T2>
{
public:
	MM(T1 Data1,T2 Data2,T2 Data3):Data1(Data1),Data2(Data2),Data3(Data3)
	{
		cout << "类模板的特化,3->2+1" << endl;
	}
protected:
	T1 Data1;
	T2 Data2;
	T2 Data3;
};
int main()
{
	MM<string, int, int> mm("Alice", 18, 1001);
	MM<int, int, int> mm2(5, 2, 0);
	MM<string, string, string> mm3("于文文", "张靓颖", "宋茜");
	MM<int, double, const char*> mm4(8, 32.2, "Hello");
	return 0;
}
类模板的特化,3->2+1
类模板的特化,3->1
类模板的特化,3->1
类模板

注意点:类模板不是真正的类,所以不可以使用多文件写法写类模板,只能把声明和实现写在同一个文件中。创建的时候写.hpp中。

//头文件
#pragma once
template<class T>
class Test
{
public:
	Test(T Data):Data(Data){}
	void Fun();
	void Fun2();
protected:
	T Data;
};
//源文件
#include"test.h"
#include<iostream>
using namespace std;

template<class T>
inline void Test<T>::Fun()
{
	cout << "Fun" << endl;
}

template<class T>
inline void Test<T>::Fun2()
{
	cout << "Fun2" << endl;
}

在主函数的源文件中进行测试,然后就会报下面的错误:

在这里插入图片描述

原因就是把类模板的头文件和源文件分开写了。只要把头文件和源文件都写在一起就可以了。

#pragma once
#include<iostream>
using namespace std;
template<class T>
class Test
{
public:
	Test(T Data):Data(Data){}
	void Fun();
	void Fun2();
protected:
	T Data;
};
template<class T>
inline void Test<T>::Fun()
{
	cout << "Fun" << endl;
}

template<class T>
inline void Test<T>::Fun2()
{
	cout << "Fun2" << endl;
}

使用以上的代码(头文件和源文件写在一起就不会错误)。

模板类的嵌套

嵌套,意思就是,一个东西里面又有另外一个东西。而这里的模板类的嵌套就是在一个模板类中又嵌套了另一个模板类。

直接看代码观察效果:

#include<iostream>
using namespace std;

template <class T>
class MM
{
    //友元函数这里要写在类中,因为实现和声明不写在一起,实例化可能会不统一类型
	friend ostream& operator<<(ostream& out, MM<T> mm)
	{
		out << mm.Data;
		return out;
	}
public:
	MM(T Data) :Data(Data) {}
	void print()
	{
		cout << Data << endl;
	}
protected:
	T Data;
};

template <class T1, class T2>
class GG
{
public:
	GG(MM<T1> Data1, T2 Data2) :Data1(Data1), Data2(Data2) {}
	void print()
	{
		cout << Data1 << '\t' << Data2 << endl;
	}
protected:
    //嵌套了MM模板类
	MM<T1> Data1;
	T2 Data2;
};

int main()
{
    //这里也必须要做一个强转才可以成功的创建对象
	GG<MM<string>, int> gg(MM <string>("Cukor"), 20);
	gg.print();
	return 0;
}
Cukor   20

上面的代码中的一些问题:

  • 友元函数的实现和声明不写在一块
friend ostream& operator<<(ostream& out, MM<T> mm);
template <class T>
ostream& operator<<(ostream& out, MM<T> mm)
{
	out << mm.Data;
	return out;
}

会报下面的错误:
在这里插入图片描述

  • 创建对象的时候不使用强转
GG<MM<string>, int> gg("Cukor", 20);

会出现下面的错误:
在这里插入图片描述

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2021-09-08 10:29:29  更:2021-09-08 10:30:17 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 19:45:13-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码