c++模板 --- 类模板、自定义类型当做模板参数

2023-11-19

生成一个类模板

  • 类中用到了未知类型叫做类模板

  • 用 template 修饰的类,这个类就是一个模板类 

  • 多用在数据结构中,忽略类型的问题

  • 只要被 template 修饰,就是一个模板类,有没有用未知类型都是模板类

  • 把模板当做一种特殊的数据类型即可

template<class _Ty>    
class MM
{
	public:
    protected:
}
  • 类模板调用

    • 模板类必须采用显式调用(必须要传参) 不存在隐式调用

    • 类模板不是一个实际的类,所有用到 类型 & 类名的地方 都需要使用:类名<未知类型> 的方式使用(在类型没有实例化之前,不是真实存在的类)

  • 多文件中,类模板中的声明 .h 和实现 .cpp 一定是在一起的,不能分开写 --->因为类模板不是一个真实存在的类,如果分开,编译器找不到,写在同一个 .h 文件中即可

//template <>       //MM类中没有用到未知类型,不能直接写未知类型的模板类
//class Data 
//{
//};
template <class _Ty> 
class MM 
{
public:
	MM() {}
	MM(string name):name(name) {}
	void print();    //类中声明在类外实现
protected:
	string name;
};

//void MM::print()    //报错
//{
//   cout << "类模板" << endl;
//}

//类模板中的函数在类外实现,都需要类名限定
template <class _Ty>
void MM<_Ty>::print() //所有用到类名的地方:都需要用类名<类型>的方式使用   
{
	cout << "类模板" << endl;
}

int main() 
{
/*用类型+对象名创建对象的方式报错
    MM mm; 报错:不能直接创建一个对象,必须要传一个类型,就算没有用到 也要传类型*/
//只要用template修饰就是一个模板类,必须采用显式调用
	MM<int> mm1;
    MM<string> mm2;
    MM<double> mm3;
}

类的继承中 - - - 模板类被继承

用到类型的地方:

  • 在类外实现函数

  • 在子类继承父类中用到类名

  • 在继承中的构造函数

template <class _Ty> 
class MM 
{
public:
	MM() {}
	MM(string name):name(name) {}
	void print();   

protected:
	string name;
};

template <class _Ty>
void MM<_Ty>::print() 
{
	cout << "类模板" << endl;
}

//class Girl :public MM
//{
//};
template<class _Ty>
class Girl :public MM<_Ty>
{   
public:
	Girl(string name) :MM<_Ty>(name) /*注意:这里用的是类型!!!虽然是调用父类的构造函数
                                       但是解析为类型也需要加<>的方式使用*/
	{
	}
/*报错: [非法的成员初始化:MM类不是基类或成员] 
  解决方案:因为用到了类名 需要采用<>的方式使用*/
protected:
};

int main()
{	
	Girl<int> girl("Loveyou"); //显示调用
	girl.print();        	
	return 0;
}
/*输出*/

类模板

综合案例 - - - 两个未知类型的情况

template <class _Ty1, class _Ty2>
class Data 
{
public:
	Data(_Ty1 one, _Ty2 two) :one(one), two(two) {} //使用初始化参数列表,this指针
	void print();    //在类外实现
protected:
	_Ty1 one;
	_Ty2 two;
};
template <class _Ty1, class _Ty2>    //类名限定 用到类名加<>使用
void Data<_Ty1, _Ty2>::print() 
{
	cout << one <<"\t"<< two << endl;//成员函数在类外实现,访问自己的成员
}
int main()
{
//这个类可以构造出不同的类型 只能显式调用
    Data<string, int> mmInfo("小芳", 19); //MM的信息 
	mmInfo.print();                       
	Data<int, int> data(12, 11);          //日期
	data.print();
}
/*输出*/
小芳    19
12      11

自定义类型当做模板参数 

  • 基本自定义类型(没有用到模板)

  • 自定义类型也是一个模板(用到模板)

  • 模板传入自定义类型,关键点就在于运算符重载

函数模板传入基本数据类型 & 自定义类型

class MM
{
public:
	MM(string name, int age) :name(name), age(age) {}
protected:
	string name;
	int age;
};
template <class _Ty>
void print(_Ty one) 
{
	cout << one << endl;
}
int main()
{

//函数模板传入基本数据类型
	print(12);               //隐式调用
	print<string>("string"); //显示调用

//函数模板传入自定义数据类型
	print(MM("mm", 19));     //构建一个无名对象(自定义类型数据)传入报错!!!
}                            //传入一个对象进来,对象是不能直接打印的,需要运算符重载
/*输出*/

12
string
//函数模板传入自定义数据类型报错
//error C2679: 二进制“<<”: 没有找到接受“_Ty”类型的右操作数的运算符(或没有可接受的转换)

 函数模板传自定义类型 

  • 看模板中的报错,看 error 部分即可

  • 需要重载运算符,重载后,模板就可以操作自定义类型的数据

错误:

error C2679:二进制 "<<" :没有找到接受 "_Ty" 类型的右操作数的运算符(或没有可接受的转换)

原因:

传入一个对象进来,one 是一个MM类对象,对象不能直接打印,因为我们传的是自定义类型,要为这个数据类型写一个重载 ---> 运算符重载

打印MM的数据

class MM
{
public:
	MM(string name, int age) :name(name), age(age) {}
    friend ostream& operator<<(ostream& out, MM& mm) //为这个数据类型写一个重载
    {
		out << mm.name << " " << mm.age;
		return out;
    }
protected:
	string name;
	int age;
};
template <class _Ty>
void print(_Ty one) 
{
	cout << one << endl;
}
int main()
{
//函数模板传入自定义数据类型
	print(MM("mm", 19));     
}

/*输出*/

mm 19

 返回两人中年龄最大的 

class MM
{
public:
	MM(string name, int age) :name(name), age(age) {}
	friend ostream& operator<<(ostream& out, MM& mm)
	{
		out << mm.name << " " << mm.age;
		return out;
	}
	bool operator>(MM& mm) const     //用类的成员函数|友元函数的方式重载都可以
	{
		return this->age > mm.age;   //返回年龄最大的
	}
protected:
	string name;
	int age;
};
template <class _Ty>
void print(_Ty one) 
{
//error C2679: 二进制“<<”: 没有找到接受“_Ty”类型的右操作数的运算符(或没有可接受的转换)
	cout << one << endl;   
}
template <class _Ty>
_Ty Max(_Ty a, _Ty b) 
{
//error C2676: 二进制“>”: “_Ty”不定义该运算符或到预定义运算符可接收的类型的转换
	return a > b ? a : b;             //因为是自定义类型,没办法直接比较
}

int main() 
{

//函数模板传入自定义类型
	MM xiaoF("小芳", 18);
	MM xiaoL("小丽", 28);
//显式调用 传入MM类型
	MM result = Max<MM>(xiaoF, xiaoL);
	//MM result = Max(xiaoF, xiaoL);  //函数模板可以隐式调用,传入自定义类型即可
	cout << result << endl;           //直接输出 或 print(result);
}
/*输出*/

小丽 28

类模板传自定义类型 - - - 写一个链表管理模板类型的数据

class MM
{
public:
	MM(string name, int age) :name(name), age(age) {}
	friend ostream& operator<<(ostream& out, const MM& mm)    //改为const类型的引用
	{
		out << mm.name << " " << mm.age;
		return out;
	}
	bool operator>(MM& mm) const     
	{
		return this->age > mm.age;  
	}
protected:
	string name;
	int age;
};
template <class _Ty>    //节点
class Node
{
public:
	Node(_Ty data, Node<_Ty>* next) :data(data), next(next) {}
/*pmove = pmove->data error C2248:“Node<_Ty>::data”无法访问protected成员(在“Node<_Ty>”
  类中声明) 需要提供公有接口*/
	_Ty getData() 
	{
		return data;
	}
	Node<_Ty>* getNext()//访问指针 返回值类型是指针类型
	{
		return next;
	}
protected:
	_Ty data;
	Node<_Ty>* next;    //类模板中用到了类名,要用<>的方式,Node是一个类型,必须要用<>的方式
	//普通写法:Node* next;
};
template <class _Ty>    //需要template <class _Ty>,因为用到了Node类型,Node里面是未知量
class List 
{
public:
	List() 
	{
		headNode = nullptr;       //把指针指为空即可
	}
	void insertList(_Ty data)     //数据插入 头插法
	{
		headNode = new Node<_Ty>(data, headNode);  //类模板必须要用显式写法
	}
	void printList()              //打印
	{
		Node<_Ty>* pmove = headNode;
		while (pmove != nullptr) 
		{
//error C2679: 二进制“<<”: 没有找到接受“_Ty”类型的右操作数的运算符(或没有可接受的转换)
			cout << pmove->getData() << endl;      //getData()是MM类型
			pmove = pmove->getNext();
		}
		cout << endl;
	}
protected:
	Node<_Ty>* headNode;
};
void testList() 
{
	List<int> list;
//链表存整数
	list.insertList(1);
	list.insertList(2);
	list.insertList(3);
	list.printList();
//链表存自定义类型---MM类是一个普通类
	List<MM> mmList;
	mmList.insertList(MM("小芳", 18));
	mmList.insertList(MM("小丽", 28));
	mmList.insertList(MM("小美", 38));
	mmList.printList();
}

int main() 
{
    testList();
}
/*输出*/
3
2
1

小美 38
小丽 28
小芳 18

注意:提供的接口是返回值,是一个const类型的对象(常属性) 需要把MM的重载函数改为const MM& mm

  • 返回值是值,不可以成为左值 ---> 返回值充当函数参数,需要加const修饰 

  • 加const是为了 传入const 对象的时候这个重载函数也能用

  • 函数调用时函数参数类型必须要一致 ---> 用右值引用只能使用传入右值不能传入变量

  • 由于const 类型的参数,既可以传入变量也可以传入常量,所以用const MM& mm 

friend ostream& operator<<(ostream& out, const MM& mm)
{
	out << mm.name << " " << mm.age;
	return out;
}

while (pmove != nullptr) 
{		
	cout << pmove->getData() << endl;
	pmove = pmove->getNext();
}

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

c++模板 --- 类模板、自定义类型当做模板参数 的相关文章

随机推荐

  • 22黑马QT笔记之事件全总结

    22黑马QT笔记之事件全总结 1 每个控件重写过滤器 event函数 各个事件处理函数都一样 都是先类中声明 类外定义 2 每个控件都可以重写事件过滤器 但是他一般写在窗口 安装时参数要求继承QObject嘛 event函数和各个事件处理函
  • Flutter状态管理Provider,简单上手

    学习Flutter一段时间了 偶然看到大家都说状态管理 多数人都是用redux 对于一个Android开发人员来说之前根本没接触过 于是开始了解redux 之后又了解闲鱼推出的fish redux 然后又看到Vadaski发表的一系列关于F
  • ChatBox安装--ChatGPT的桌面客户端

    ChatBox 是什么 是开源的 ChatGPT API OpenAI API 桌面客户端 Prompt 的调试与管理工具 支持 Windows Mac 和 Linux gt github地址 下载链接 支持的平台 Windows 请下载
  • 设置QFrame的背景图片并不影响其子控件的效果

    项目建立完成后 右键点你的项目 Add New gt QT Resource file 生成一个qrc文件 然后双击它 点add 然后Add Prefix 再Add file 完事之后build一下 在你的ui上点右键 gt Change
  • 选择软件外包公司需要注意哪些方面

    每个行业中不同公司的实力都是良莠不齐 特别是IT软件外包公司更是如此 当我们一旦将整个项目交付对方之后 项目的成败就全看软件外包公司的表现 风险极大 那么 我们该如何选择一家靠谱的深圳软件外包公司 选择软件外包公司需要注意哪些方面 北京木奇
  • 刷脸支付让城市真正迈入智能化数字化新阶段

    众所周知 每一次通信时代的变革都会催生一系列新兴事物的发展 比如3G时代的到来让越来越多国人开始了解互联网 4G时代的普及 让互联网产业得到了前所未有的发展空间 而5G时代的来临 将进一步推动数字化工作的进程刷脸支付正是如此 让城市真正迈入
  • 二分查找--中间值取值原则

    在数组总长度为奇数时 二分查找的中间值就是数组中间的那个元素 例如 对于长度为5的数组 中间元素的下标为2 在数组总长度为偶数时 二分查找的中间值有两个 可以取任意一个作为中间值 一种常用的方法是取靠左的那个中间值 例如 对于长度为6的数组
  • Image Processing图像处理(对比俩张图像的差异并且在图上标注出来)

    图像处理是构建所有计算机视觉的基础 按照我的图像处理指南使用OpenCV库学习计算机视觉的基础知识 SSIM进阶 利用python openCV将图片的差异性画框展示出来 诀窍是学习如何准确地确定在 x y 坐标位置上 图像的差异在哪里 使
  • shader学习网站

    https www shadertoy com https shaderfrog com http glslb in http glslsandbox com
  • Linux网络网卡配置相关知识记录

    Linux系统网络相关配置 1 网卡配置文件说明 2 使用 ifconfig 配置网卡 3 配置 DNS 地址 3 1 编辑 etc hosts 文件 在空白处输入 3 2 编辑 DNS 配置文件 4 确定 linux 服务器哪个网卡对应哪
  • MyTinyStl源码刨析

    文章目录 源码信息及目录 启动项目 迭代器 分配器 算法 容器实现 测试框架Test 源码信息及目录 MyTinySTL 项目地址 该项目实现了C 的stl库 阅读该项目可以使我们对stl的底层有更深入的认识 同时可以增强我们的c 的内功
  • 配置使用Eslint的时候 版本错误 "eslint": "5.6.0" a different version of eslint was detected higher up in the tr

    1 如果你也遇到下面的问题 你可以 按照命令行提示的那样 下面这四步完成的一般就可以了 但是不排除你在运行的时候忘记下载某些依赖 1 删除 package lock json 不是package json 你可以选择 yarn lock 或
  • W800开发板

    目录 概况 下载工具链编译工具 查看wsl版本 wsl所采用的工具链 添加工具链bin到环境变量中 编译 接口说明 概况 海凌科W800开发板 HLK W800 KIT 是海凌科电子面向开发者针对联盛德W800 芯片推出的一款多功能开发板
  • 服务器修改动态磁盘,服务器数据库使用动态磁盘

    服务器数据库使用动态磁盘 内容精选 换一换 云服务器备份 云服务器备份可以对普通服务器进行整机备份或部分磁盘备份 不适用于部署了数据库等应用的服务器 支持备份弹性云服务器ECS和裸金属服务器BMS 成本相对于VBS较高 适合对需要备份整个服
  • for循环执行顺序

    for循环的表达式一般如下 for 表达式1 表达式2 表达式3 表达式4 执行的顺序为 1 第一次循环 即初始化循环 首先执行表达式1 一般为初始化语句 再执行表达式2 一般为条件判断语句 判断表达式1是否符合表达式2的条件 如果符合 则
  • JDK1.6官方下载_JDK6官方下载

    JDK1 6官方下载 JDK6官方下载 JDK1 6官方下载 JDK6官方下载地址 http www java net download jdk6 6u10 promoted b32 binaries jdk 6u10 rc2 bin b3
  • 【Java基础】泛型记录概要

    泛型记录概要 泛型出现重要原因之一 创造容器类 泛型主要目的之一 指定容器持有什么类型的对象 保证编译的正确性 泛型简单实例 class GenericsClass
  • CoLab设置使用GPU和TPU

    tf2 4 0 from tensorflow python keras callbacks import EarlyStopping from tensorflow python keras layers import Embedding
  • mysql学习系列(2)--忘记mysql登录密码怎么办?

    系列文章目录 文章目录 系列文章目录 前言 一 登录mysql 二 操作步骤 1 找到mysql exe所在的文件夹 2 Win R打开cmd 进入bin文件夹 3 跳过mysql用户验证 3 net start mysql启动服务 总结
  • c++模板 --- 类模板、自定义类型当做模板参数

    生成一个类模板 类中用到了未知类型叫做类模板 用 template 修饰的类 这个类就是一个模板类 多用在数据结构中 忽略类型的问题 只要被 template 修饰 就是一个模板类 有没有用未知类型都是模板类 把模板当做一种特殊的数据类型即