跟我学C++中级篇——模板友元的应用
一、友元
友元在以前分析过,而且一般编程是不推荐使用友元的,原因是友元破坏了类的封装性。但凡事总有例外,在某些情况下,用友元还是比较方便的,那么该用还得用,不能因噎废食。普通的友元,各种书籍资料和网上多得很。一般来说,可以把友元分为两大类:
1、普通的友元
这种友元的使用,不管是类还是函数,开发者都相对比较熟悉,此处就不再赘述,如果有不明白的可以 翻一下书籍。一般来说,即使在模板里,使用特化的或者实例化的模板类或者函数,也看做是普通友元。模板只要特化或者说实例化就和普通类或者函数没有什么区别了。
2、模板的友元
这里重点分析一下模板中使用模板函数或者模板类做为友元时的处理情况,这里面涉及到一些具体的编译器的不同处理方式,需要引起重视。
二、模板函数做为友元
老规矩行先看代码:
#include <iostream>//第一种情况
template <typename T> class FriendF {
public:T t_;public:FriendF() { t_ = T(1.9); }// VS OK// template <typename T> friend void Display(const FriendF<T> &f);template <typename U> friend void Display(const FriendF<U> &f);
};template <typename U> void Display(const FriendF<U> &f) {std::cout << "cur value is:" << f.t_ << std::endl;
}int friendtest() {FriendF<int> f;Display<int>(f);return 0;
}
//
//第二种情况
template <typename T> class FriendF1;
template <typename T> void Display(const FriendF1<T> &a);template <typename T> class FriendF1 {T t_;public:FriendF1() { t_ = T(1.6); }friend void Display<T>(const FriendF1<T> &f);
};template <typename T> void Display(const FriendF1<T> &f) {std::cout << "cur value is:" << f.t_ << std::endl;
}int friendtest1() {FriendF1<int> f1;Display<int>(f1);return 0;
}
///
//第三种情况template <typename T> class FriendF2 {T t_;public:FriendF2() { t_ = T(2.0f); }friend void Display(const FriendF2<T> &f2) {std::cout << f2.t_ << std::endl;}
};int friendtest2() {FriendF2<int> f2;Display(f2);return 0;
}///
//第四种情况
template <typename T> class NData;
template <typename T> void swap1(NData<T> &n1, NData<T> &n2);
template <typename T> void Display1(NData<T> &n1, NData<T> &n2);
template <typename T> void swap(NData<T> &n1, NData<T> &n2);template <typename T> class NData {
public:NData() {}NData(T t) : t_(t) {}public:NData<T> *left_;NData<T> *right_;T t_;public:friend void swap1<>(NData<T> &, NData<T> &);friend void swap<>(NData<T> &, NData<T> &);friend void Display1<>(NData<T> &, NData<T> &);
};
template <typename T> void Display1(NData<T> &n1, NData<T> &n2) {std::cout << n1.t_ << std::endl;
}
template <typename T> void swap1(NData<T> &n1, NData<T> &n2) {std::swap(n1.left_, n2.left_);std::swap(n1.right_, n2.right_);std::swap(n1.t_, n2.t_);std::cout << "vlaue is:" << n1.t_ << std::endl;
}
template <typename T> void swap(NData<T> &n1, NData<T> &n2) {// // std::swap(n1.left_, n2.left_);// // std::swap(n1.right_, n2.right_);// std::swap(n1.t_, n2.t_);std::cout << "n1,b" << n1.t_ << std::endl;
}int friendtest3() {NData<int> nd1;NData<int> nd2;swap(nd1, nd2);swap1(nd1, nd2);Display1(nd1, nd2);return 0;
}
int main()
{friendtest();friendtest1();friendtest2();friendtest3();
}
在模板函数做为友元的情况下,分成了三种情况两大类:
1、模板类内声明,模板外实现
第一种情况就是这种,在类声明了模板函数为友元,但是在类外实现函数体。
2、模板类内声明,类内实现
第三种就是在类内直接实现模板函数友元的函数体。
3、模板类外声明
第二种情况就是把友元模板函数在类外进行声明,有点类似于头文件的作用,然后在类中声明友元,类外进行函数体的实现。第四种情况中,其实和第二种情况基本没有区别。只是用<>来表明这是一种模板函数的具现化。
*这里需要说明一下"friend void swap1<>(NData &, NData &)"这种<>尖括号声明,用侯捷老师的书中的原话是“使用空的template argument list,这种形式告诉编译器,只从template具现体中挑选合适的呼叫对象,所有template parameters 都自 call parameters 推导而得”。所以才在下面说第四种情况视情况而定的原因。
其实它的意思就是说,这个是要从具体的调用参数推导出来。也就是说,常规的函数是允许参数自动类型转换,但模板参数推断时不允许,这就等于在实际推导时要根据已经生成的挑选(推导)最匹配的。
两大类是从另外一种角度看:
1、可以直接在类内声明限定约束(bound)模板友元的参数类型
也就是说,友元模板参数要和应用 其类的模板参数保持一致。第一种情况的注释版第二种及第三种情况都是约束的情况。这种也容易为开发者理解和应用。
2、模板未对友元模板进行约束(unbound)
这种情况就是第一种情况,第一种情况注释的部分在vs上可以 编译应用 ,但在Gcc和Clang上编译会出现“Declaration of ‘T’ shadows template parameter”,这个在前面也分析过(“模板的模板参数的匹配”)。
第四种情况视情况而定吧。
三、模板类做为友元
而模板类做为友元可以 分为两种情况:
//第一种情况
template <typename T> class FriendB;template <typename T> class FriendA {
public:T t_;public:FriendA() { t_ = T(3.6); }friend class FriendB<T>;
};
//第二种情况
template <typename T> class FriendAA {
public:T t_;public:FriendAA() { t_ = T(100); }template <class U> friend class FriendBB;
};
template <typename U> class FriendBB {
public:static void Display(const FriendAA<U> &faa) {std::cout << "cur value is:" << faa.t_ << std::endl;}
};
template <typename T> class FriendB {
public:static void Display(const FriendA<T> &fa) {std::cout << "cur value is::" << fa.t_ << std::endl;}
};int friendclass() {FriendA<int> aa1;FriendAA<double> aa2;FriendB<int>::Display(aa1);FriendBB<double>::Display(aa2);return 0;
}
int main()
{
friendclass();
}
它们两种情况都是在类声明为友元,但是第一种情况中在前声明友元前进行了友元模板类的前向声明;第二种情况则直接在类内声明。前者更类似于一种实例化的类,在前面的学习中知道,在模板类中,声明的模板形参可以在内部认为已经实例化,直接使用。而第二种则和前面函数模板类似,属于不受约束的方式,是一种声明形式。
也就是说,第一种是码元模板参数受约束的形式,而第二种则为不受约束的形式。
四、问题
在这次的例程编写中,遇到了一个基础问题,觉得对初学者甚至中等水平开发者还是有一定意义的,这里展示出来。
......template <typename T> class NData {
public:NData() {}NData(T t) : t_(t) {}public:NData<T> *left_;NData<T> *right_;T t_;public:friend void swap1<>(NData<T> &, NData<T> &);friend void swap<>(NData<T> &, NData<T> &);
......
};template <typename T> void swap1(NData<T> &n1, NData<T> &n2) {std::swap(n1.left_, n2.left_);std::swap(n1.right_, n2.right_);std::swap(n1.t_, n2.t_);std::cout << "vlaue is:" << n1.t_ << std::endl;
}
template <typename T> void swap(NData<T> &n1, NData<T> &n2) {// // std::swap(n1.left_, n2.left_);// // std::swap(n1.right_, n2.right_);// std::swap(n1.t_, n2.t_);std::cout << "n1,b" << n1.t_ << std::endl;
}
也就是函数模板做为友元的第四种情况中,使用swap做函数名时,总是报"assertion failed: template argument must be a complete class or an unbounded array"。有点摸不着头脑,类NData是一个完全定义好的啊。后来突然想到会不会是名空间污染的问题(std::swap())。于是又写了一个swap1,结果没问题。验证与想的完全一致,回头查看一下代码,在工程中被包含的cpp文件中声明了using namespae std,所以在实际工程中,尽量增加自定义的名字空间,并且不要使用类似“using namespae std”这种偷懒的方式来编程。
五、总结
C++语言更需要多实践,多干活自然就会有各种情况遇到,和所学的知识不断的验证、巩固。查遗补缺,技术的增长就会越来越快,光死记理论,时间长了要么忘记了,要么记混了,写起代码来还是有无法驾驭的情况。
相关文章:
跟我学C++中级篇——模板友元的应用
一、友元 友元在以前分析过,而且一般编程是不推荐使用友元的,原因是友元破坏了类的封装性。但凡事总有例外,在某些情况下,用友元还是比较方便的,那么该用还得用,不能因噎废食。普通的友元,各种…...
软件测试基础篇——MySQL
MySQL 1、数据库技术概述 数据库database:存放和管理各种数据的仓库,操作的对象主要是【数据data】,科学的组织和存储数据,高效的获取和处理数据SQL:结构化查询语言,专为**关系型数据库而建立的操作语言&…...
FreeRTOS(二值信号量)
资料来源于硬件家园:资料汇总 - FreeRTOS实时操作系统课程(多任务管理) 目录 一、信号量的概念 1、信号量的基本概念 2、信号量的分类 二、二值信号量的定义与应用 1、二值信号量的定义 2、二值信号量的应用 三、二值信号量的运作机制 1、FreeRTOS任务间二值…...
leetcode面试题:动物收容所(考查对队列的理解和运用)
题目: 有家动物收容所只收容狗与猫,且严格遵守“先进先出”的原则。在收养该收容所的动物时,收养人只能收养所有动物中“最老”(由其进入收容所的时间长短而定)的动物,或者可以挑选猫或狗(同时…...
【Linux命令行与Shell脚本编程】第十八章 文本处理与编辑器基础
Linux命令行与Shell脚本编程 第十八章 文本处理与编辑器基础 文章目录 Linux命令行与Shell脚本编程第十八章 文本处理与编辑器基础 文本处理与编辑器基础8.1.文本处理8.1.1.sed编辑器8.1.1.1.在命令行中定义编辑器命令8.1.1.2.在命令行中使用多个编辑器命令8.1.1.3.从文件中读…...
2023牛客暑期多校训练营7
Beautiful Sequence 贪心,二进制,构造 Cyperation 模拟 ,数学 We Love Strings 分块,二进制枚举,二进制容斥dp Writing Books 签到 根据相邻两个异或值B,因为前小于等于后,故从高到低遍历B的每一…...
centos7升级glibc2.28
1 概述 centos7自带的glibc对于某些软件是太旧的,决定将glibc升级至2.28。 2 安装过程 2.1 下载glibc源码 mkdir -p /opt/third-party && cd /opt/third-party wget http://ftp.gnu.org/gnu/glibc/glibc-2.28.tar.gz tar -xf glibc-2.28.tar.gz cd glibc…...
腾讯云香港服务器租用_2核2G20M_2核4G30M
腾讯云香港服务器租用费用表,目前中国香港地域轻量应用服务器可选配置2核2G20M、2核2G30M、2核4G30M,操作系统可选Windows和Linux,不只是香港云服务器,新加坡、硅谷、法兰克福和东京服务器均有活动,腾讯云服务器网分享…...
十三、ESP32PS2摇杆(ADC)
1. 运行效果 在上下左右操作PS2摇杆的时候,会检测到数据 2. 滑动电阻...
网络安全的相关知识点
网络安全威胁类型: 1.窃听:广播式网络系统。 2.假冒 3.重放:重复一份报文或者报文的一部分,以便产生一个被授权的效果。 4.流量分析 5.数据完整性破坏 6.拒绝服务 7.资源的非授权使用 8.陷门和特洛伊木马:木马病毒有客…...
算法练习(6):牛客在线编程06 递归/回溯
package jz.bm;import java.io.PushbackInputStream; import java.lang.reflect.Array; import java.util.ArrayList; import java.util.Arrays;public class bm6 {/*** BM55 没有重复项数字的全排列*/ArrayList<ArrayList<Integer>> res new ArrayList<>()…...
C#使用OpenCv(OpenCVSharp)图像局部二值化处理实例
本文实例演示C#语言中如何使用OpenCv(OpenCVSharp)对图像进行局部二值化处理。 目录 图像二值化原理 局部二值化 自适应阈值 实例 效果...
MySQL多表关联查询
目录 1. inner join: 2. left join: 3. right join: 4.自连接 5.交叉连接: 6、联合查询 7、子查询 1. inner join: 代表选择的是两个表的交差部分。 内连接就是表间的主键与外键相连,只取得键值一致…...
flutter开发实战-CustomClipper裁剪长图帧动画效果
flutter开发实战-CustomClipper裁剪长图帧动画效果 在开发过程中,经常遇到帧动画的每一帧图显示在超长图上,需要处理这种帧动画效果。我这里使用的是CustomClipper 一、CustomClipper CustomClipper继承于Listenable abstract class CustomClipper e…...
CSS 中的优先级规则是怎样的?
聚沙成塔每天进步一点点 ⭐ 专栏简介⭐内联样式(Inline Styles)⭐ID 选择器(ID Selectors)⭐类选择器、属性选择器和伪类选择器(Class, Attribute, and Pseudo-class Selectors)⭐元素选择器和伪元素选择器…...
概率图模型(Probabilistic Graphical Model,PGM)
概率图模型(Probabilistic Graphical Model,PGM),是一种用图结构来描述多元随机变量之间条件独立性的概率模型。它可以用来表示复杂的概率分布,进行有效的推理和学习,以及解决各种实际问题,如图…...
Oracle 知识篇+会话级全局临时表在不同连接模式中的表现
标签:会话级临时表、全局临时表、幻读释义:Oracle 全局临时表又叫GTT ★ 结论 ✔ 专用服务器模式:不同应用会话只能访问自己的数据 ✔ 共享服务器模式:不同应用会话只能访问自己的数据 ✔ 数据库驻留连接池模式:不同应…...
MySQL 数据库文件的导入导出
目录 数据库的导出 导出整个数据库 导出数据库中的数据表 导出数据库结构 导出数据库中表的表结构 导出多个数据库 导出所有数据库 数据库的导入 数据库的导出 mysqldump -h IP地址 -P 端口 -u 用户名 -p 数据库名 > 导出的文件名 用管理员权限打开cmd进入MySQL的bi…...
找不到资产文件project.assets.json
NuGet 在“obj”文件夹中写入名为 project.assets.json 的文件,.NET SDK 使用该文件来获取有关要传递到编译器的包的信息 。 如果在生成过程中找不到资产文件 project.assets.json,则会发生此错误。 1.执行命令的方式解决 点击工具,分别展开命…...
【python】python将json字符串导出excel | pandas处理json字符串保存为csv
如何将json转为csv 1、通过json直接转为csv 在Python中,你可以使用pandas库来处理DataFrame(数据帧)和将JSON数据转换为CSV格式。下面是一个简单的示例代码,展示了如何使用pandas库将JSON数据转换为CSV文件: import p…...
STM32标准库-DMA直接存储器存取
文章目录 一、DMA1.1简介1.2存储器映像1.3DMA框图1.4DMA基本结构1.5DMA请求1.6数据宽度与对齐1.7数据转运DMA1.8ADC扫描模式DMA 二、数据转运DMA2.1接线图2.2代码2.3相关API 一、DMA 1.1简介 DMA(Direct Memory Access)直接存储器存取 DMA可以提供外设…...
VTK如何让部分单位不可见
最近遇到一个需求,需要让一个vtkDataSet中的部分单元不可见,查阅了一些资料大概有以下几种方式 1.通过颜色映射表来进行,是最正规的做法 vtkNew<vtkLookupTable> lut; //值为0不显示,主要是最后一个参数,透明度…...
(一)单例模式
一、前言 单例模式属于六大创建型模式,即在软件设计过程中,主要关注创建对象的结果,并不关心创建对象的过程及细节。创建型设计模式将类对象的实例化过程进行抽象化接口设计,从而隐藏了类对象的实例是如何被创建的,封装了软件系统使用的具体对象类型。 六大创建型模式包括…...
pikachu靶场通关笔记19 SQL注入02-字符型注入(GET)
目录 一、SQL注入 二、字符型SQL注入 三、字符型注入与数字型注入 四、源码分析 五、渗透实战 1、渗透准备 2、SQL注入探测 (1)输入单引号 (2)万能注入语句 3、获取回显列orderby 4、获取数据库名database 5、获取表名…...
Linux部署私有文件管理系统MinIO
最近需要用到一个文件管理服务,但是又不想花钱,所以就想着自己搭建一个,刚好我们用的一个开源框架已经集成了MinIO,所以就选了这个 我这边对文件服务性能要求不是太高,单机版就可以 安装非常简单,几个命令就…...
Vue 3 + WebSocket 实战:公司通知实时推送功能详解
📢 Vue 3 WebSocket 实战:公司通知实时推送功能详解 📌 收藏 点赞 关注,项目中要用到推送功能时就不怕找不到了! 实时通知是企业系统中常见的功能,比如:管理员发布通知后,所有用户…...
高效的后台管理系统——可进行二次开发
随着互联网技术的迅猛发展,企业的数字化管理变得愈加重要。后台管理系统作为数据存储与业务管理的核心,成为了现代企业不可或缺的一部分。今天我们要介绍的是一款名为 若依后台管理框架 的系统,它不仅支持跨平台应用,还能提供丰富…...
未授权访问事件频发,我们应当如何应对?
在当下,数据已成为企业和组织的核心资产,是推动业务发展、决策制定以及创新的关键驱动力。然而,未授权访问这一隐匿的安全威胁,正如同高悬的达摩克利斯之剑,时刻威胁着数据的安全,一旦触发,便可…...
本地部署drawDB结合内网穿透技术实现数据库远程管控方案
文章目录 前言1. Windows本地部署DrawDB2. 安装Cpolar内网穿透3. 实现公网访问DrawDB4. 固定DrawDB公网地址 前言 在数字化浪潮席卷全球的背景下,数据治理能力正日益成为构建现代企业核心竞争力的关键因素。无论是全球500强企业的数据中枢系统,还是初创…...
第6章:Neo4j数据导入与导出
在实际应用中,数据的导入与导出是使用Neo4j的重要环节。无论是初始数据加载、系统迁移还是数据备份,都需要高效可靠的数据传输机制。本章将详细介绍Neo4j中的各种数据导入与导出方法,帮助读者掌握不同场景下的最佳实践。 6.1 数据导入策略 …...
