当前位置: 首页 > news >正文

C++类与对象(7)—友元、内部类、匿名对象、拷贝对象时编译器优化

目录

一、友元

1、定义 

2、友元函数

3、友元类

二、内部类

1、定义

2、特性:

三、匿名对象

四、拷贝对象时的一些编译器优化

1、传值&传引用返回优化对比

2、匿名对象作为函数返回对象

3、接收返回值方式对比

总结:


一、友元

1、定义 

友元提供了一种突破封装的方式,有时提供了便利。但是友元会增加耦合度,破坏了封装,所以友元不宜多用。
  • 友元分为:友元函数和友元类

2、友元函数

问题:现在尝试去重载operator<<,然后发现没办法将operator<<重载成成员函数,使用形式发生变化。
class Date
{
public:Date(int year, int month, int day): _year(year), _month(month), _day(day){}// d1 << cout; 或者 d1.operator<<(&d1, cout); 不符合常规调用// 因为成员函数第一个参数一定是隐藏的this,所以d1必须放在<<的左侧ostream & operator<<(ostream& _cout){_cout << _year << "-" << _month << "-" << _day << endl;return _cout;}
private:int _year;int _month;int _day;
};

因为cout的输出流对象和隐含的this指针在抢占第一个参数的位置。this指针默认是第一个参数也就是左操作数了。但是实际使用中cout需要是第一个形参对象,才能正常使用。所以要将operator<<重载成全局函数。但又会导致类外没办法访问成员,此时就需要友元来解决。operator>>同理。 

友元函数可以直接访问类的私有成员,它是定义在类外部的普通函数,不属于任何类,但需要在类的内部声明,声明时需要加friend关键字。

class Date
{friend ostream& operator<<(ostream& _cout, const Date& d);friend istream& operator>>(istream& _cin, Date& d);
public:Date(int year = 1900, int month = 1, int day = 1): _year(year), _month(month), _day(day){}
private:int _year;int _month;int _day;
};
ostream& operator<<(ostream& _cout, const Date& d)
{_cout << d._year << "-" << d._month << "-" << d._day;return _cout;
}
istream& operator>>(istream& _cin, Date& d)
{_cin >> d._year;_cin >> d._month;_cin >> d._day;return _cin;
}
int main()
{Date d;cin >> d;cout << d << endl;return 0;
}
  • 友元函数可访问类的私有和保护成员,但不是类的成员函数
  • 友元函数不能用const修饰
  • 友元函数可以在类定义的任何地方声明,不受类访问限定符限制
  • 一个函数可以是多个类的友元函数
  • 友元函数的调用与普通函数的调用原理相同

3、友元类

友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。
class Time
{friend class Date;   // 声明日期类为时间类的友元类,则在日期类中就直接访问Time类中的私有成员变量
public:Time(int hour = 0, int minute = 0, int second = 0): _hour(hour), _minute(minute), _second(second){}private:int _hour;int _minute;int _second;
};
class Date
{
public:Date(int year = 1900, int month = 1, int day = 1): _year(year), _month(month), _day(day){}void SetTimeOfDate(int hour, int minute, int second){// 直接访问时间类私有的成员变量_time._hour = hour;_time._minute = minute;_time._second = second;}private:int _year;int _month;int _day;Time _time;
}
  • 友元关系是单向的,不具有交换性。 比如上述Time类和Date类,在Time类中声明Date类为其友元类,那么可以在Date类中直接访问Time类的私有成员变量,但想在Time类中访问Date类中私有的成员变量则不行。
  • 友元关系不能传递。如果C是B的友元, B是A的友元,则不能说明C时A的友元。
  • 友元关系不能继承,在继承位置再给大家详细介绍。

二、内部类

1、定义

概念:如果一个类定义在另一个类的内部,这个内部类就叫做内部类
  • 内部类是一个独立的类, 它不属于外部类,更不能通过外部类的对象去访问内部类的成员。
  • 外部类对内部类没有任何优越的访问权限。
注意:内部类就是外类部的友元类,内部类可以通过外部类的对象参数来访问外部类中的所有成员。但是外部类不是内部类的友元。

2、特性:

  • 内部类可以定义在外部类的public、protected、private都是可以的。
  • 注意内部类可以直接访问外部类中的static成员,不需要外部类的对象/类名。
  • sizeof(外部类)=外部类,和内部类没有任何关系。

我们先来看一下 ”sizeof(外部类)=外部类,和内部类没有任何关系“ 在代码中怎么体现的。

class A{
private:int h;
public:class B{private:int b;};
};int main()
{A aa;cout << sizeof(aa) << endl;return 0;
}

输出结果显示,类A的对象对象只有一个int成员的大小。

在调试中也可以看到类对象aa只有一个成员变量h。

 

内部类B跟A是独立,只是受A的类域限制。

可以通过下面代码访问到B类

A::B bb;

如果B类的作用域变为私有,则不能访问到。

B天生就是A的友元。

class A{
private:int h = 0;static int k;
public:class B{public:void Print(const A& a){cout << k << endl;// >> OKcout << a.h << endl;// >> OK}};
};
int A::k = 1;int main()
{A aa;A::B bb;bb.Print(aa);return 0;
}

通过B类成功访问A类的静态成员变量k和整型成员变量h。

这时我们就可以对使用static成员的这道题使用内部类进行修改。 

求1+2+3+...+n_牛客题霸_牛客网 (nowcoder.com)

class Solution {class Sum {public:Sum() {_sum += _i;_i++;}};private:static int _sum;static int _i;public:int Sum_Solution(int n) {Sum a[n];return _sum;}
};
int Solution::_sum = 0;
int Solution::_i = 1;

三、匿名对象

class A
{
public:A(int a = 0):_a(a){cout << "A(int a)" << endl;}~A(){cout << "~A()" << endl;}
private:int _a;
};
class Solution {
public:int Sum_Solution(int n) {//...return n;}
};
int main()
{A aa1;// 不能这么定义对象,因为编译器无法识别下面是一个函数声明,还是对象定义//A aa1();// 但是我们可以这么定义匿名对象,匿名对象的特点不用取名字,// 但是他的生命周期只有这一行,我们可以看到下一行他就会自动调用析构函数A();A aa2(2);// 匿名对象在这样场景下就很好用。Solution().Sum_Solution(10);return 0;
}

这样定义类对象可以吗?

A aa1();
  • 不能这么定义对象,因为编译器无法识别下面是一个函数声明,还是对象定义。

但是我们可以这么定义匿名对象,匿名对象的特点不用取名字。

A();

但是他的生命周期只有这一行,我们可以看到下一行他就会自动调用析构函数

匿名对象在这样场景下就很好用。

Solution().Sum_Solution(10);

四、拷贝对象时的一些编译器优化

在传参和传返回值的过程中,一般编译器会做一些优化,减少对象的拷贝,这个在一些场景下还是非常有用的。

1、传值&传引用返回优化对比

class A
{
public:A(int a = 0):_a(a){cout << "A(int a)" << endl;}A(const A& aa):_a(aa._a){cout << "A(const A& aa)" << endl;}A& operator=(const A& aa){cout << "A& operator=(const A& aa)" << endl;if (this != &aa){_a = aa._a;}return *this;}~A(){cout << "~A()" << endl;}
private:int _a;
};void func1(A aa)
{}void func2(const A& aa)
{}int main()
{A aa1 = 1; // 构造+拷贝构造 -》 优化为直接构造func1(aa1); // 无优化,不能跨表达式优化func1(2); // 构造+拷贝构造 -》 优化为直接构造func1(A(3)); // 构造+拷贝构造 -》 优化为直接构造cout << "----------------------------------" << endl;func2(aa1);  // 无优化func2(2);    // 无优化func2(A(3)); // 无优化return 0;
}

我们看一下main函数中的代码:

  • A aa1 = 1; 这里首先调用构造函数创建一个临时对象,然后调用拷贝构造函数将临时对象的内容复制到aa1。但是,编译器通常会进行优化,直接调用构造函数创建aa1,避免了不必要的拷贝构造。
  • func1(aa1); 这里调用函数func1,参数是aa1的拷贝,所以会调用拷贝构造函数。这个过程没有优化。函数func1会调用析构函数清理临时变量aa。
  • func1(2); 和 func1(A(3)); 这两行代码都是先构造一个临时对象,然后调用拷贝构造函数将临时对象的内容复制到函数参数。但是,编译器会进行优化,直接将临时对象作为函数参数,避免了不必要的拷贝构造。

然后是func2的调用:

func2(aa1); func2(2); 和 func2(A(3)); 这三行代码都是将一个对象的引用作为函数参数,所以不需要调用拷贝构造函数,也就没有优化的空间。

  •  func2(aa1)引用传值,不需要构造和析构。

  • func2(2)构造一个临时对象,然后拷贝构造给aa。

  • func2(A(3))中 A(3) 创建了一个临时对象,调用了构造函数 A(int a = 0),并输出 "A(int a)"。

    • 这是因为在函数调用 func2(A(3)); 中,临时对象被创建,即 A(3)const A& aa 表示将这个临时对象通过常引用传递给 func2 函数。在这里,没有发生拷贝构造,因为是通过引用传递的。

    • 所以在 func2 函数内部,没有额外的构造或拷贝构造的调用。当 func2 函数执行完毕,临时对象开始析构。这时调用了析构函数 ~A(),并输出 "~A()"。这是因为在函数调用结束后,局部变量(包括通过临时对象构造的 aa)会被销毁。

    • 最后,整个程序执行结束,全局的 A(3) 对象也会被销毁,调用析构函数 ~A()。因此,总共有两次析构调用。一次是在 func2 函数内部的临时对象销毁,另一次是全局的 A(3) 对象销毁。

2、匿名对象作为函数返回对象

class A
{
public:A(int a = 0):_a(a){cout << "A(int a)" << endl;}A(const A& aa):_a(aa._a){cout << "A(const A& aa)" << endl;}A& operator=(const A& aa){cout << "A& operator=(const A& aa)" << endl;if (this != &aa){_a = aa._a;}return *this;}~A(){cout << "~A()" << endl;}
private:int _a;
};A func3()
{A aa;return aa;
}
A func4()
{return A();//匿名对象
}int main()
{func3();// 构造+拷贝构造A aa1 = func3();//构造+两个拷贝构造>>>优化为构造+一个拷贝构造func4(); // 构造+拷贝构造 -- 优化为构造A aa3 = func4(); // 构造+拷贝构造+拷贝构造  -- 优化为构造return 0;
}

通过对比,可以发现使用匿名对象在func4()中的好处。 

在函数 func4() 中,return A(); 创建了一个匿名对象,并且该匿名对象直接作为函数的返回值。这样,调用 func4() 将得到这个匿名对象的拷贝,而不需要额外的临时对象。因此,在 func4() 的调用中,可以直接构造并返回这个匿名对象,避免了多余的对象的创建和拷贝构造。

3、接收返回值方式对比

A func3()
{A aa;return aa;
}int main()
{A aa1 = func3(); // 拷贝构造+拷贝构造  -- 优化为一个拷贝构造cout << "****" << endl;A aa2;aa2 = func3();  // 声明和定义不在一行,不能优化return 0;
}

总结:

对象返回:

  • 接收返回值对象,尽量拷贝构造方式接收,不要赋值接收。
  • 函数中返回对象时,尽量返回匿名对象。

函数传参:

  • 尽量使用const &传参。

相关文章:

C++类与对象(7)—友元、内部类、匿名对象、拷贝对象时编译器优化

目录 一、友元 1、定义 2、友元函数 3、友元类 二、内部类 1、定义 2、特性&#xff1a; 三、匿名对象 四、拷贝对象时的一些编译器优化 1、传值&传引用返回优化对比 2、匿名对象作为函数返回对象 3、接收返回值方式对比 总结&#xff1a; 一、友元 1、定义…...

Django回顾2

目录 一.HTTP 1.URL介绍 2.格式&#xff1a; 3.补充&#xff1a; 二.web框架 1.什么是框架 2.什么是web框架 3.wsgi协议 基于wsgi协议的web服务器&#xff1a; 4.协议是怎么规定的 三.Django 1.MVC与MTV模型&#xff08;所有框架其实都遵循MVC架构&#xff09; 2.…...

<JavaDS> 二叉树遍历各种遍历方式的代码实现 -- 前序、中序、后序、层序遍历

目录 有以下二叉树&#xff1a; 一、递归 1.1 前序遍历-递归 1.2 中序遍历-递归 1.3 后序遍历-递归 二、递归--使用链表 2.1 前序遍历-递归-返回链表 2.2 中序遍历-递归-返回链表 2.3 后序遍历-递归-返回链表 三、迭代--使用栈 3.1 前序遍历-迭代-使用栈 3.2 中序遍…...

如何控制Spring工厂创建对象的次数?详解Spring对象的声明周期!

&#x1f609;&#x1f609; 学习交流群&#xff1a; ✅✅1&#xff1a;这是孙哥suns给大家的福利&#xff01; ✨✨2&#xff1a;我们免费分享Netty、Dubbo、k8s、Mybatis、Spring...应用和源码级别的视频资料 &#x1f96d;&#x1f96d;3&#xff1a;QQ群&#xff1a;583783…...

计算机杂谈系列精讲100篇-【计算机应用】PyTorch部署及分布式训练

目录 C平台PyTorch模型部署流程 1.模型转换 1. 不支持的操作 2. 指定数据类型 2.保存序列化模型 3.C load训练好的模型 4. 执行Script Module PyTorch分布式训练 分布式并行训练概述 Pytorch分布式数据并行 手把手渐进式实战 A. 单机单卡 B. 单机多卡DP C. 多机多卡DDP D. L…...

Opencv-C++笔记 (19) : 分水岭图像分割

文章目录 一、基于距离变换与分水岭的图像分割1、图像分割2、距离和变换与分水岭距离变换常见算法有两种分水岭变换常见的算法 3、距离变换API函数接口4、watershed 分水岭函数API接口步骤 5、代码 一、基于距离变换与分水岭的图像分割 1、图像分割 图像分割(Image Segmentat…...

Linux以nohup方式运行jar包

1、在需要运行的jar包同级目录下建立启动脚本文件&#xff1a; 文件内容&#xff1a; #! /bin/bash #注意&#xff1a;必须有&让其后台执行&#xff0c;否则没有pid生成 jar包路径为绝对路径 nohup java -jar /usr/local/testDemo/jdkDemo-0.0.1-SNAPSHOT.jar >/us…...

【c++|SDL】开始使用之---demo

every blog every motto: You can do more than you think. https://blog.csdn.net/weixin_39190382?typeblog 0. 前言 SDL 记录 1. hello word #include<SDL2/SDL.h>SDL_Window* g_pWindow 0; SDL_Renderer* g_pRenderer 0;int main(int argc, char* args[]) {//…...

leetcode:有效的括号

题目描述 题目链接&#xff1a;20. 有效的括号 - 力扣&#xff08;LeetCode&#xff09; 题目分析 题目给了我们三种括号&#xff1a;&#xff08;&#xff09;、{ }、[ ] 这里的匹配包括&#xff1a;顺序匹配和数量匹配 最优的思路就是用栈来解决&#xff1a; 括号依次入栈…...

使用STM32微控制器实现光电传感器的接口和数据处理

光电传感器在许多领域中被广泛应用&#xff0c;例如工业自动化、智能家居等。本文将介绍如何使用STM32微控制器实现光电传感器的接口和数据处理的方案&#xff0c;包括硬件设计、引脚配置、数据采集、滤波和阈值判断等关键步骤&#xff0c;并给出相应的代码示例。 一、引言 光…...

ELK企业级日志分析平台——kibana数据可视化

部署 新建虚拟机server5&#xff0c;部署kibana [rootelk5 ~]# rpm -ivh kibana-7.6.1-x86_64.rpm [rootelk5 ~]# cd /etc/kibana/[rootelk5 kibana]# vim kibana.ymlserver.host: "0.0.0.0"elasticsearch.hosts: ["http://192.168.56.11:9200"]i18n.local…...

Shell条件变量练习

1.算数运算命令有哪几种&#xff1f; (1) "(( ))"用于整数运算的常用运算符&#xff0c;效率很高 [rootshell scripts]# echo $((24*5**2/8)) #(( ))2452814 14 (2) "$[ ] "用于整数运算 [rootshell scripts]# echo $[24*5**2/8] #[ ]也可以运…...

【PHP】MySQL简介与MySQLi函数(含PHP与MySQL交互)

文章目录 一、MySQL简介二、MySQLi函数1. 开启mysqli扩展&#xff1a;2. PHP MySQLi扩展的常用函数 三、PHP与MySQL交互0. 准备1. 创建连接&#xff08;mysqli_connect() &#xff09;连接mysql语法 2. 选择数据库&#xff08;mysqli_select_db()&#xff09;3. 在php中操作数据…...

vscode在Windows上安装插件提示错误xhr failed

问题描述&#xff1a; 在Windows下&#xff0c;在vscode里搜索扩展时发现无法搜索&#xff0c;报如下错&#xff1a;”Error while fetching extensions. XHR failed“。 问题定位&#xff1a; 在vscode界面下键入ctrlshiftp&#xff0c; 然后输入&#xff1a;Developer: T…...

SHAP(一):具有 Shapley 值的可解释 AI 简介

SHAP&#xff08;一&#xff09;&#xff1a;具有 Shapley 值的可解释 AI 简介 这是用 Shapley 值解释机器学习模型的介绍。 沙普利值是合作博弈论中广泛使用的方法&#xff0c;具有理想的特性。 本教程旨在帮助您深入了解如何计算和解释基于 Shapley 的机器学习模型解释。 我…...

C++数据结构:图

目录 一. 图的基本概念 二. 图的存储结构 2.1 邻接矩阵 2.2 邻接表 三. 图的遍历 3.1 广度优先遍历 3.2 深度优先遍历 四. 最小生成树 4.1 最小生成树获取策略 4.2 Kruskal算法 4.3 Prim算法 五. 最短路径问题 5.1 Dijkstra算法 5.2 Bellman-Ford算法 5.3 Floyd-…...

「C++」红黑树的插入(手撕红黑树系列)

&#x1f4bb;文章目录 &#x1f4c4;前言红黑树概念红黑树的结构红黑树节点的定义红黑树的定义红黑树的调整 红黑树的迭代器迭代器的声明operator( )opeartor--( ) 完整代码 &#x1f4d3;总结 &#x1f4c4;前言 作为一名程序员相信你一定有所听闻红黑树的大名&#xff0c;像…...

2023年生肖在不同时间段的运势预测

随着信息技术的飞速发展&#xff0c;API已经成为了数据获取和交互的重要途径。很多网站和APP都在运用API来获取数据。今天我们来介绍一个十分有趣的API——《十二生肖运势预测API》&#xff0c;通过这个API&#xff0c;我们可以获取到每个生肖在不同时间段的运势预测&#xff0…...

ERRO报错

无法下载nginx 如下解决&#xff1a; 查看是否有epel 源 安装epel源 安装第三方 yum -y install epel-release.noarch NGINX端口被占用 解决&#xff1a; 编译安装的NGINX配置文件在/usr/local/ngin/conf 修改端口...

shiyan

import javax.xml.transform.Result; import java.util.Arrays; public class ParseText {//需要统计的字符串为private String text"Abstract-This paper presents an overview";private Result[] res;private int count;public ParseText(){resnew Result[100];cou…...

云原生核心技术 (7/12): K8s 核心概念白话解读(上):Pod 和 Deployment 究竟是什么?

大家好&#xff0c;欢迎来到《云原生核心技术》系列的第七篇&#xff01; 在上一篇&#xff0c;我们成功地使用 Minikube 或 kind 在自己的电脑上搭建起了一个迷你但功能完备的 Kubernetes 集群。现在&#xff0c;我们就像一个拥有了一块崭新数字土地的农场主&#xff0c;是时…...

Cesium1.95中高性能加载1500个点

一、基本方式&#xff1a; 图标使用.png比.svg性能要好 <template><div id"cesiumContainer"></div><div class"toolbar"><button id"resetButton">重新生成点</button><span id"countDisplay&qu…...

macOS多出来了:Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用

文章目录 问题现象问题原因解决办法 问题现象 macOS启动台&#xff08;Launchpad&#xff09;多出来了&#xff1a;Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用。 问题原因 很明显&#xff0c;都是Google家的办公全家桶。这些应用并不是通过独立安装的…...

【Go】3、Go语言进阶与依赖管理

前言 本系列文章参考自稀土掘金上的 【字节内部课】公开课&#xff0c;做自我学习总结整理。 Go语言并发编程 Go语言原生支持并发编程&#xff0c;它的核心机制是 Goroutine 协程、Channel 通道&#xff0c;并基于CSP&#xff08;Communicating Sequential Processes&#xff0…...

【C++从零实现Json-Rpc框架】第六弹 —— 服务端模块划分

一、项目背景回顾 前五弹完成了Json-Rpc协议解析、请求处理、客户端调用等基础模块搭建。 本弹重点聚焦于服务端的模块划分与架构设计&#xff0c;提升代码结构的可维护性与扩展性。 二、服务端模块设计目标 高内聚低耦合&#xff1a;各模块职责清晰&#xff0c;便于独立开发…...

Linux nano命令的基本使用

参考资料 GNU nanoを使いこなすnano基础 目录 一. 简介二. 文件打开2.1 普通方式打开文件2.2 只读方式打开文件 三. 文件查看3.1 打开文件时&#xff0c;显示行号3.2 翻页查看 四. 文件编辑4.1 Ctrl K 复制 和 Ctrl U 粘贴4.2 Alt/Esc U 撤回 五. 文件保存与退出5.1 Ctrl …...

Vue 模板语句的数据来源

&#x1f9e9; Vue 模板语句的数据来源&#xff1a;全方位解析 Vue 模板&#xff08;<template> 部分&#xff09;中的表达式、指令绑定&#xff08;如 v-bind, v-on&#xff09;和插值&#xff08;{{ }}&#xff09;都在一个特定的作用域内求值。这个作用域由当前 组件…...

五子棋测试用例

一.项目背景 1.1 项目简介 传统棋类文化的推广 五子棋是一种古老的棋类游戏&#xff0c;有着深厚的文化底蕴。通过将五子棋制作成网页游戏&#xff0c;可以让更多的人了解和接触到这一传统棋类文化。无论是国内还是国外的玩家&#xff0c;都可以通过网页五子棋感受到东方棋类…...

32单片机——基本定时器

STM32F103有众多的定时器&#xff0c;其中包括2个基本定时器&#xff08;TIM6和TIM7&#xff09;、4个通用定时器&#xff08;TIM2~TIM5&#xff09;、2个高级控制定时器&#xff08;TIM1和TIM8&#xff09;&#xff0c;这些定时器彼此完全独立&#xff0c;不共享任何资源 1、定…...

Python第七周作业

Python第七周作业 文章目录 Python第七周作业 1.使用open以只读模式打开文件data.txt&#xff0c;并逐行打印内容 2.使用pathlib模块获取当前脚本的绝对路径&#xff0c;并创建logs目录&#xff08;若不存在&#xff09; 3.递归遍历目录data&#xff0c;输出所有.csv文件的路径…...