C++——详解类模板与友元函数

news2024/10/6 16:27:32

在这里插入图片描述
纵有疾风起,人生不言弃。本文篇幅较长,如有错误请不吝赐教,感谢支持。

💬文章目录

    • 类模板与友元函数
      • 1️⃣非模板友元函数
      • 2️⃣约束模板友元函数
      • 3️⃣非约束模板友元函数

类模板与友元函数

模板类的友元函数有三类:

  • 1)非模板友元函数:友元函数不是模板函数,而是利用模板类参数生成的函数。
  • 2)约束模板友元函数:模板类实例化时,每个实例化的类对应一个友元函数。
  • 3)非约束模板友元函数:模板类实例化时,如果实例化了n个类,也会实例化n个友元函数,每个实例化的类都拥有n个友元函数。

1️⃣非模板友元函数

非模板友元函数就是将一个普通函数声明为友元函数。我们先来看一段代码:

#include <iostream> 
#include<string> 
using namespace std;
template<class T1,class T2>
class Maker
{
private:
    T1 m_x;
    T2 m_y;
public:
    Maker(const T1 x, const T2 y) : m_x(x), m_y(y) { }
};
Maker<string,int> m1("感谢支持强风吹拂king的博客",666);
void show()
{
    cout << "x = " << m1.m_x << ", y = " << m1.m_y << endl;
}
int main()
{
    show();
}

📝解释:

Maker是模板类,有两个模板参数T1,T2,在模板Maker类中用T1和T2分别定义了m_x,和m_y,构造函数使用初始化列表给m_x,和m_y赋值。
用模板类Maker定义全局对象m1,再定义全局函数show(),在全局函数show()中访问模板类Maker的私有成员,看下面运行结果(报错):
在这里插入图片描述
如果我们把全局函数show(),给声明成模板Maker的友元函数可以访问Maker的私有成员吗?

friend void show();

🖲执行结果如下:
在这里插入图片描述

在类模板Maker中,将普通函数show()函数声明为友元函数,则show()函数是类模板Maker所有实例的友元函数。例如,它是Maker<string,int>,Maker<string,int>,Maker<string,double>等的友元函数。

这种为类模板设置友元函数的方法在语法上没有任何错误,但是要用模板类去创建全局对象,代码这么写实在是太笨了,在学习友元的时候,如果Maker是普通类,我们可以把Maker普通类的引用传给友元函数而不必创建全局对象
那么Maker是类模板,可以这么写吗?答案是不可以。Maker只是类的通用描述,根本不存在类名叫Maker的类,但是你具体化函数模板,也就是后面加上参数列表<具体的数据类型>,这就可以当做一个具体的类,就可以像普通类一样使用。

#include <iostream> 
#include<string> 
using namespace std;
template<class T1,class T2>
class Maker
{
private:
    T1 m_x;
    T2 m_y;
public:
    Maker(const T1 x, const T2 y) : m_x(x), m_y(y) {}
    friend void show(const Maker<string, int>& a);
};
void show(const Maker<string, int>& a)
{
   cout << "x = " << a.m_x << ", y = " << a.m_y << endl;
}
int main()
{
    Maker<string,int> m1("感谢支持强风吹拂king的博客",777);
    show(m1);
    return 0;
}

🖲执行结果如下:
在这里插入图片描述

如果main函数内部我们这样写,编译器会报错吗?

int main()
{
    Maker<char,int> m1('k',666);
    show(m1);
    return 0;
}

错误如下:
在这里插入图片描述那么我们重载show函数,为Maker<char,int> m1(“k”,666);单独写一个show函数,并声明为Maker的友元函数。

void show(const Maker<char, int>& a)
{
   cout << "x = " << a.m_x << ", y = " << a.m_y << endl;
}

🖲运行结果如下:
在这里插入图片描述

编译通过了,但是问题来了,现在这种方法解决友元函数的问题,但是很麻烦,要为模板类的每一个实例化版本都要写一个友元函数,所以,C++提供了一个解决方案,利用模板参数,自动生成友元函数,这个友元函数也叫作非模板友元。
非模板友元:友元函数不是模板函数,而是利用模板类参数生成的函数,只能在类内实现。

#include <iostream> 
#include<string> 
using namespace std;
template<class T1, class T2>
class Maker
{
private:
    T1 m_x;
    T2 m_y;
public:
    Maker(const T1 x, const T2 y) : m_x(x), m_y(y) {}
    //友元函数的形参用模板的通用类型
    friend void show(const Maker<T1, T2>& a)
    {
        cout << "x = " << a.m_x << ", y = " << a.m_y << endl;
    }
};
int main()
{
    Maker<string, int> m1("感谢支持强风吹拂king的博客", 666);
    show(m1);
    Maker<char, int> m2('k', 666);
    show(m2);
    return 0;
}

🖲运行结果如下:
在这里插入图片描述

这种方法的本质是编译器利用模板参数为我们生成友元函数,但有一点我们要注意⚠️:
编译器利用模板参数生成友元函数,但是这个友元函数不是函数模板。

📝我举个例子证明“友元函数不是函数模板”一下:

#include <iostream> 
#include<string> 
using namespace std;
template<class T1, class T2>
class Maker
{
private:
    T1 m_x;
    T2 m_y;
public:
    Maker(const T1 x, const T2 y) : m_x(x), m_y(y) {}
    //友元函数的形参用模板的通用类型
    friend void show(const Maker<T1, T2>& a)
    {
        cout << "x = " << a.m_x << ", y = " << a.m_y << endl;
    }
    friend void show(const Maker<string, int>& a);
    friend void show(const Maker<char, int>& a);
};
void show(const Maker<char, int>& a)
{
    cout << "x = " << a.m_x << ", y = " << a.m_y << endl;
}
void show(const Maker<string, int>& a)
{
    cout << "x = " << a.m_x << ", y = " << a.m_y << endl;
}
int main()
{
    Maker<string, int> m1("感谢支持强风吹拂king的博客", 666);
    show(m1);
    Maker<char, int> m2('k', 666);
    show(m2);
    return 0;
}

这两个有具体类型的函数和模板参数的函数,它们之间是什么关系?
如果上面带模板参数的是函数模板,那两个带具体类型的就是函数模板的具体化版本。在模板技术中,普通函数,具体化版本和函数模板是可以共存的,编译器会优先使用普通函数和具体化版本,现在这三个函数之间的关系也是这样的吗?
在这里插入图片描述
在这里插入图片描述

函数出现了重载,为什么编译器不是优先选择一个函数调用,而是报重定义的错误呢?原因很简单,void show(const Maker<T1, T2>& a)不是函数模板,只是借用模板参数,编译器创建模板类实例的时候,会用具体的数据类型去替代模板参数,生成友元函数的实体,与下面定义的具体类型的函数发生代码冲突。报重定义的错误。

💬小结一下:
从上面的程序中我们可以看出,非模板友元的使用特别得呆板。

  • 如果我们想为某种具体化的类模板单独写友元函数,这是做不到的,会报重定义的错误。(例如给Maker<string,int>单独写个友元函数,是不行🙅🏻‍♂️的)
  • 非模板友元函数,在哪个模板类定义,就只能用于这个类,不能用于其他类,因为非模板友元借助的是本类模板参数,并且还只能在本类的类内定义。

2️⃣约束模板友元函数

约束模板友元函数是将一个函数模板声明为类的友元函数。函数模板的实例化类型取决于类模板被实例化时的类型,类模板实例化时会产生与之匹配的具体化友元函数。

📝使用约束模板友元的步骤:

  • 第一步,在模板类的定义前,声明友元函数模板,为的是让模板类知道友元函数模板的存在。
  • 第二步再次声明友元函数模板,在类模板中将函数模板声明为友元函数。在声明友元函数时,函数模板要实现具体化,即函数模板的模板参数要与类模板的模板参数保持一致,目的是让编译器知道需要实例化的友元函数模板,类模板和函数模板本来没什么关系的,正是第二步让它们产生了关系,编译器在实例化某种数据类型的类模板时,也会实例化这种数据类型的模板函数。
  • 第三步,友元函数模板的定义,放在类模板下,因为友元函数是函数模板,所以可以和具体化的函数模板共存,不会发生代码冲突,报重定义的错误。
#include <iostream>
#include<string>
using namespace std;
// 约束模板友元:模板类实例化时,每个实例化的类对应一个友元函数。
template <typename T>
void show(T& a); // 第一步:在模板类的定义前面,声明友元函数模板。
template<class T1, class T2>
class AA // 模板类AA。
{
    friend void show<>(AA<T1, T2>& a); // 第二步:在模板类中,再次声明友元函数模板。
    T1 m_x;
    T2 m_y;
public:
    AA(const T1 x, const T2 y) : m_x(x), m_y(y) { }
};
template <typename T> // 第三步:友元函数模板的定义。
void show(T& a)
{
	cout << "通用:x = " << a.m_x << ", y = " << a.m_y << endl;
}
template <> // 第三步:具体化版本。
void show(AA<int, string>& a)
{
	cout << "具体AA<int, string>:x = " << a.m_x << ", y = " << a.m_y << endl;
}
int main()
{
    AA<int, string> a1(66, "感谢支持强风吹拂king的博客");
    show(a1); // 将使用具体化的版本。
    AA<char, string> a2(66, "感谢支持强风吹拂king的博客");
    show(a2);// 将使用通用的版本。
}

🖲运行结果如下:
在这里插入图片描述
解释:
第一次在声明时,show()函数与AA类模板没有关系,只是让编译器知道函数模板的存在,第二步,函数模板再次声明,并完成显示实例化,模板参数变为了AA类,当生成AA< char,string>模板类时,会生成与之匹配的show< char,string>()函数和作为友元函数。
这种友元函数模板可以应用于多个模板类,只是第二步,函数模板在类内具体化不一样。
把类模板AA的代码复制一遍,将类名改为BB。
📝代码举例:

#include <iostream> // 包含头文件
#include<string>
using namespace std;
// 约束模板友元:模板类实例化时,每个实例化的类对应一个友元函数。
template <typename T>
void show(T& a); // 第一步:在模板类的定义前面,声明友元函数模板。
template<class T1, class T2>
class AA // 模板类AA。
{
	friend void show<>(AA<T1, T2>& a); // 第二步:在模板类中,再次声明友元函数模板。
	T1 m_x;
	T2 m_y;
public:
	AA(const T1 x, const T2 y) : m_x(x), m_y(y) { }
};
template<class T1, class T2>
class BB // 模板类BB。
{
	friend void show<>(BB<T1, T2>& a); // 第二步:在模板类中,再次声明友元函数模板。
	T1 m_x;
	T2 m_y;
public:
	BB(const T1 x, const T2 y) : m_x(x), m_y(y) { }
};
template <typename T> // 第三步:友元函数模板的定义。
void show(T& a)
{
	cout << "通用:x = " << a.m_x << ", y = " << a.m_y << endl;
}
template <> // 第三步:具体化版本。
void show(AA<int, string>& a)
{
	cout << "具体AA<int, string>:x = " << a.m_x << ", y = " << a.m_y << endl;
}
template <> // 第三步:具体化版本。
void show(BB<int, string>& a)
{
	cout << "具体BB<int, string>:x = " << a.m_x << ", y = " << a.m_y << endl;
}
int main()
{
	AA<int, string> a1(66, "感谢支持强风吹拂king的博客");
	show(a1); // 将使用具体化的版本。
	AA<char, string> a2(66, "感谢支持强风吹拂king的博客");
	show(a2); // 将使用通用的版本。
	BB<int, string> b1(66, "感谢支持强风吹拂king的博客");
	show(b1); // 将使用具体化的版本。
	BB<char, string> b2(66, "感谢支持强风吹拂king的博客");
	show(b2); // 将使用通用的版本。
}

🖲运行结果如下:
在这里插入图片描述
关于友元函数的声明,多说一点:

template<typename U>//类模板的定义 
class A 
{
  ……//其他成员
  friend void func<U>();//声明无参友元函数func() 
  friend void show<>(A<U>& a);//声明有参友元函数show()
  ……//其他成员
  };

在上述代码中,将函数模板func()与show()声明为类的友元函数
func()是无参的函数模板,我们在学习函数模板的时候,必须显示或隐式的让编译器知道模板参数将要被哪个具体的数据类型替换,以便生成具体的函数定义,但这是无参的函数模板,隐式实例化不行(没参数),所以只能借助参数列表显示实例化。
show()函数模板有一个模板类参数,编译器可以根据函数参数隐式推导出模板参数,生成函数定义,因此show()函数模板具体化中<>可以为空。

💬小结一下:
两个要点:

  • 可以某种具体化的类模板单独写友元函数,例如上面代码中为AA<int, string> a1(66, “感谢支持强风吹拂king的博客”);提供单独具体化的友元函数
  • 可以支持多个类模板。

这种友元方案更有价值,就是语法稍微麻烦一点。

3️⃣非约束模板友元函数

模板类实例化时,如果实例化了n个类,也会实例化n个友元函数,每个实例化的类都拥有n个友元函数。
这其实不科学,按理说每个实例化的类只需要一个友元函数即可,不需要n个,其他数据类型的友元函数和自己没关系,其实原因就是没有约束模板友元函数的第二步,没有将函数模板具体化,函数模板和类模板没有绑定起来。
声明非约束模板友元函数示例代码如下所示:
在这里插入图片描述
📝案例:

#include <iostream> // 包含头文件。
using namespace std; 
// 非类模板约束的友元函数,实例化后,每个函数都是每个每个类的友元。
template<class T1, class T2>
class AA
{
	template <typename T> friend void show(T& a); // 把函数模板设置为友元。
	T1 m_x;
	T2 m_y;
public:
	AA(const T1 x, const T2 y) : m_x(x), m_y(y) { }
};
template <typename T> void show(T& a) // 通用的函数模板。
{
	cout << "通用:x = " << a.m_x << ", y = " << a.m_y << endl;
}
template <>void show(AA<int, string>& a) // 函数模板的具体版本。
{
	cout << "具体<int, string>:x = " << a.m_x << ", y = " << a.m_y << endl;
}
int main()
{
	AA<int, string> a1(66, "感谢支持强风吹拂king的博客");
	show(a1); // 将使用具体化的版本。
	AA<char, string> a2(66, "感谢支持强风吹拂king的博客");
	show(a2);// 将使用通用的版本。
}

🖲运行结果如下:
在这里插入图片描述
虽然说运行结果和约束模板友元函数一致,但还是推荐使用约束模板友元函数。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Qt/C++编写手机版本视频播放器和Onvif工具(可云台和录像)

一、前言 用Qtffmpeg写播放器很多人有疑问&#xff0c;为何不用Qt自己的多媒体框架来写&#xff0c;最重要的原因是Qt自带的目前都依赖具体的本地解码器&#xff0c;如果解码器不支持&#xff0c;那就是歇菜的&#xff0c;最多支持个MP4格式&#xff0c;而且在手机上也都是支持…

C国演义 [第七章]

第七章 最长重复子数组题目理解步骤dp含义递推公式初始化为啥dp数组如此奇怪 遍历顺序 代码 最长公共子序列题目理解步骤dp含义递推公式初始化遍历顺序 代码 总结 最长重复子数组 力扣链接 给两个整数数组 nums1 和 nums2 &#xff0c;返回 两个数组中 公共的 、长度最长的子…

设计模式之中介者模式笔记

设计模式之中介者模式笔记 说明Mediator(中介者)目录中介者模式示例类图抽象中介者类抽象同事类租房者类房主类具体的中介者角色类测试类 说明 记录下学习设计模式-中介者模式的写法。JDK使用版本为1.8版本。 Mediator(中介者) 意图:用一个中介对象来封装一系列的对象交互。…

剑指 Offer 68 - II. 二叉树的最近公共祖先 / LeetCode 236. 二叉树的最近公共祖先(搜索与回溯)

题目&#xff1a; 链接&#xff1a;剑指 Offer 68 - II. 二叉树的最近公共祖先&#xff1b;LeetCode 236. 二叉树的最近公共祖先 难度&#xff1a;中等 上一题博客&#xff1a;剑指 Offer 68 - I. 二叉搜索树的最近公共祖先 / LeetCode 235. 二叉搜索树的最近公共祖先&#xf…

python:并发编程(二十四)

前言 本文将和大家一起探讨python并发编程的实际项目&#xff1a;win图形界面应用&#xff08;篇六&#xff0c;共八篇&#xff09;&#xff0c;系列文章将会从零开始构建项目&#xff0c;并逐渐完善项目&#xff0c;最终将项目打造成适用于高并发场景的应用。 本文为python并…

Pandas进阶修炼120题-第二期(Pandas数据处理,21-50题)

文章目录 Pandas进阶修炼120题第二期 Pandas数据处理21.读取本地EXCEL数据22.查看df数据前5行23.将salary列数据转换为最大值与最小值的平均值方法一&#xff1a;正则表达式&#xff08;分别使用apply()&#xff0c;applymap(),map()来实现&#xff09;方法二&#xff1a;apply…

wifi芯片原理

一、在系统中的位置 基带&#xff08;baseband&#xff09; 基带的作用有三个&#xff1a; 1、队列包的管理 2、调试解调 3、CSMA/CA机制 CSMA/CA的全称是Carrier Sense Multiple Access with Collision Avoidance&#xff0c;即载波侦听多路访问&#xff0f;冲突避免。 各…

java——集合框架

文章目录 接口实现&#xff08;类&#xff09;算法1. 排序算法2. 查找算法3. 拷贝算法4. 填充算法5. 比较算法6. 随机算法7. 迭代器算法8. 交集、并集、差集9. 分割集合10. 数组和集合的互转 集合框架是一个用来代表和操纵集合的统一架构。所有的集合框架都包含如下内容&#x…

C++手撕红黑树

目录&#xff1a; 红黑树的概念红黑树的性质红黑树节点的定义 红黑树结构红黑树的插入操作红黑树的验证红黑树的代码实现 红黑树的删除红黑树与AVL树的比较红黑树的应用 总结 红黑树的概念 红黑树&#xff0c;是一种二叉搜索树&#xff0c;但在每个结点上增加一个存储位表示结…

DevOps系列文章之 Spring Boot Docker打包

应用准备容器化&#xff0c;因为几十个应用从测试到发布太麻烦了&#xff0c;而且还会因为环境的因素导致部署中出现各种问题。为了在开发、测试、生产都能保持一致的环境&#xff0c;就引进了容器技术&#xff0c;而目前常用的应用使用基于spring boot的。 在Spring Boot应用…

AI数字人之语音驱动面部模型及超分辨率重建Wav2Lip-HD

1 Wav2Lip-HD项目介绍 数字人打造中语音驱动人脸和超分辨率重建两种必备的模型&#xff0c;它们被用于实现数字人的语音和图像方面的功能。通过Wav2Lip-HD项目可以快速使用这两种模型&#xff0c;完成高清数字人形象的打造。 项目代码地址&#xff1a;github地址 1.1…

Canal监听MySQL

Canal监听MySQL 1、Mysql数据库开启binlog模式 注意&#xff1a;Mysql容器&#xff0c;此处Mysql版本为5.7 #进入容器 docker exec -it mysql /bin/bash #进入配置目录 cd /etc/mysql/mysql.conf.d #修改配置文件 vi mysqld.cnf(1) 修改mysqld.cnf配置文件&#xff0c;添加如…

Android:安卓开发使用okHttp进行网络请求和MySQL数据库完成图书馆管理系统APP

1、总体目标 1.1 项目概述 项目名称&#xff1a;基于安卓平台的图书管理系统。 本项目旨在研发一个图书管理系统&#xff0c;实现图书馆的信息化管理。在方便用户在线浏览&#xff0c;借阅&#xff0c;归还图书&#xff0c;方便图书馆管理员对图书进行管理。能很好的为用户提…

从零开始理解Linux中断架构(7)--- Linux执行上下文之中断上下文

1 中断处理程序的基本要求 当前运行的loop是一条执行流,中断程序运行开启了另外一条执行流,从上一节得知这是三种跳转的第三类,这个是一个大跳转。对中断程序的基本要求就是中断执行完毕后要恢复到原来执行的程序,除了时间流逝外,原来运行的程序应该毫无感知。 具体到Armv…

如何设计一个短信发送功能

本文主要分享了如何设计一个发送短信功能。 一、总结简述 1.梳理多个平台短信API的发送参数&#xff0c;集成封装提供统一的API&#xff0c;支持多个短信平台&#xff08;阿里云、腾讯云、百度云、京东云、七牛云&#xff09;灵活切换 2.提供存储方案&#xff0c;表结构设计…

Redis数据库操作

Redis 命令参考 — Redis 命令参考http://doc.redisfans.com/ 1、Redis&#xff0c;远程词典服务器&#xff0c;是一个基于内存的键值型NoSQL数据库 特征&#xff1a; 键值型&#xff0c;支持多种不同数据结构&#xff0c;功能丰富 单线程&#xff0c;每个命令具备原子性 …

C语言督学营(中级阶段)

文章目录 中级阶段9.数据结构概述逻辑结构 与 存储结构时间复杂度、空间复杂度 10.11.12.线性表 (代码实战)线性表的定义、特点1.线性表的顺序存储(顺序表示)&#xff1a;顺序表静态分配动态分配顺序表的定义、初始化、插入、删除、按值查找、按位查找 操作 (代码)&#xff1a;…

go开发多云资产管理平台

go开发多云资产管理平台cmdb 代码仓库github.com/yunixiangfeng/gocmdb 云主机管理 主机资源监控 开发流程 Welcome to Beego | Beego bee new gocmdb/servercd gocmdb/servergo mod tidygo get -u github.com/beego/beego/v2 go get -u "github.com/astaxie/beego/o…

津津乐道设计模式 - 模版模式详解(以女友化妆流程带你彻底明白)

&#x1f337; 古之立大事者&#xff0c;不惟有超世之才&#xff0c;亦必有坚忍不拔之志 &#x1f390; 个人CSND主页——Micro麦可乐的博客 &#x1f425;《Docker实操教程》专栏以最新的Centos版本为基础进行Docker实操教程&#xff0c;入门到实战 &#x1f33a;《RabbitMQ》…

JDBC-->java如何连接数据库(详细版小白必备)

个人名片&#xff1a; &#x1f405;作者简介&#xff1a;一名大二在校生&#xff0c;热爱生活&#xff0c;爱好敲码&#xff01; \ &#x1f485;个人主页 &#x1f947;&#xff1a;holy-wangle ➡系列内容&#xff1a; &#x1f5bc;️ tkinter前端窗口界面创建与优化 &…