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 &传参。

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

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

相关文章

RPC之grpc重试策略

1、grpc重试策略 RPC 调用失败可以分为三种情况&#xff1a; 1、RPC 请求还没有离开客户端&#xff1b; 2、RPC 请求到达服务器&#xff0c;但是服务器的应用逻辑还没有处理该请求&#xff1b; 3、服务器应用逻辑开始处理请求&#xff0c;并且处理失败&#xff1b; 最后一种…

2020年3月2日 Go生态洞察:Go协议缓冲区的新API发布

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…

如何轻松将 4K 转换为 1080p 高清视频

由于某些原因&#xff0c;你可能有一些 4K 视频&#xff0c;与1080p、1080i、720p、720i等高清视频相比&#xff0c;4K 视频具有更高的分辨率&#xff0c;可以给您带来更多的视觉和听觉享受。但是&#xff0c;播放4k 视频是不太容易的&#xff0c;因为超高清电视没有高清电视那…

C#面向对象

过程类似函数只能执行没有返回值 函数不仅能执行&#xff0c;还可以返回结果 1、面向过程 a 把完成某一需求的所有步骤 从头到尾 逐步实现 b 根据开发需求&#xff0c;将某些 功能独立 的代码 封装 成一个又一个 函数 c 最后完成的代码就是顺序的调用不同的函数 特点 1、…

【问题系列】消费者与MQ连接断开问题解决方案(二)

1. 问题描述 当使用RabbitMQ作为中间件&#xff0c;而消费者为服务时&#xff0c;可能会出现以下情况&#xff1a;在长时间没有消息传递后&#xff0c;消费者与RabbitMQ之间出现连接断开&#xff0c;导致无法处理新消息。解决这一问题的方法是重启Python消费者服务&#xff0c;…

大数据平台/大数据技术与原理-实验报告--部署ZooKeeper集群和实战ZooKeeper

实验名称 部署ZooKeeper集群和实战ZooKeeper 实验性质 &#xff08;必修、选修&#xff09; 必修 实验类型&#xff08;验证、设计、创新、综合&#xff09; 综合 实验课时 2 实验日期 2023.11.04-2023.11.05 实验仪器设备以及实验软硬件要求 专业实验室&#xff08…

Spring Boot 3.2.0 Tomcat虚拟线程初体验 (部分装配解析)

写在前面 spring boot 3 已经提供了对虚拟线程的支持。 虚拟线程和平台线程主要区别在于&#xff0c;虚拟线程在运行周期内不依赖操作系统线程&#xff1a;它们与硬件脱钩&#xff0c;因此被称为 “虚拟”。这种解耦是由 JVM 提供的抽象层赋予的。 虚拟线程的运行成本远低于平…

如何使用APP UI自动化测试提高测试效率与质量?

pythonappium自动化测试系列就要告一段落了&#xff0c;本篇博客咱们做个小结。 首先想要说明一下&#xff0c;APP自动化测试可能很多公司不用&#xff0c;但也是大部分自动化测试工程师、高级测试工程师岗位招聘信息上要求的&#xff0c;所以为了更好的待遇&#xff0c;我们还…

C++11『右值引用 ‖ 完美转发 ‖ 新增类功能 ‖ 可变参数模板』

✨个人主页&#xff1a; 北 海 &#x1f389;所属专栏&#xff1a; C修行之路 &#x1f383;操作环境&#xff1a; Visual Studio 2022 版本 17.6.5 文章目录 &#x1f307;前言&#x1f3d9;️正文1.右值引用1.1.什么是右值引用&#xff1f;1.2.move 转移资源1.3.左值引用 vs …

CSS问题:如何实现瀑布流布局?

前端功能问题系列文章&#xff0c;点击上方合集↑ 序言 大家好&#xff0c;我是大澈&#xff01; 本文约2500字&#xff0c;整篇阅读大约需要4分钟。 本文主要内容分三部分&#xff0c;如果您只需要解决问题&#xff0c;请阅读第一、二部分即可。如果您有更多时间&#xff…

JavaEE进阶学习:Bean 作用域和生命周期

1.Bean 作用域 .通过一个案例来看 Bean 作用域的问题 假设现在有一个公共的 Bean&#xff0c;提供给 A 用户和 B 用户使用&#xff0c;然而在使用的途中 A 用户却“悄悄”地修改了公共 Bean 的数据&#xff0c;导致 B 用户在使用时发生了预期之外的逻辑错误。 我们预期的结果…

colab notebook导出为PDF

目录 方法一&#xff1a;使用浏览器打印功能 方法二&#xff1a;使用nbconvert转换 方法三&#xff1a;在线转换 方法一&#xff1a;使用浏览器打印功能 一般快捷键是CTRLP 然后改变目标打印机为另存为PDF 这样就可以将notebook保存为PDF了 方法二&#xff1a;使用nbconver…

芯片技术前沿:了解构现代集成电路的设计与制造

芯片技术前沿&#xff1a;解构现代集成电路的设计与制造 摘要&#xff1a;本文将深入探讨芯片技术的最新进展&#xff0c;重点关注集成电路的设计与制造。我们将带领读者了解芯片设计的基本流程&#xff0c;包括电路分析、版图设计和验证等步骤&#xff0c;并介绍当前主流的制…

强化学习中的深度Q网络

深度 Q 网络&#xff08;Deep Q-Network&#xff0c;DQN&#xff09;是一种结合了深度学习和强化学习的方法&#xff0c;用于解决离散状态和离散动作空间的强化学习问题。DQN 的核心思想是使用深度神经网络来近似 Q 函数&#xff0c;从而学习复杂环境中的最优策略。 以下是 DQN…

从苹果到蔚来,「车手互联」网罗顶级玩家

作者 |Amy 编辑 |德新 汽车作为家之外的第二大移动空间&#xff0c;正与手机这一移动智能终端进行「车手互联」。 车手互联始于十年前的苹果CarPlay&#xff0c;一度成为时代弄潮儿&#xff0c;不断有后继者模仿并超越。时至今日&#xff0c;CarPlay2.0依旧停留在概念阶段&am…

RK3568笔记六:基于Yolov8的训练及部署

若该文为原创文章&#xff0c;转载请注明原文出处。 基于Yolov8的训练及部署&#xff0c;参考鲁班猫的手册训练自己的数据集部署到RK3568,用的是正点的板子。 1、 使用 conda 创建虚拟环境 conda create -n yolov8 python3.8 ​ conda activate yolov8 2、 安装 pytorch 等…

osgFX扩展库-异性光照、贴图、卡通特效(1)

本章将简单介绍 osgFX扩展库及osgSim 扩展库。osgFX库用得比较多,osgSim库不常用&#xff0c;因此&#xff0c;这里只对这个库作简单的说明。 osgFX扩展库 osgFX是一个OpenSceneGraph 的附加库&#xff0c;是一个用于实现一致、完备、可重用的特殊效果的构架工具&#xff0c;其…

UE 事件分发机制 day9

观察者模式原理 观察者模式通常有观察者与被观察者&#xff0c;当被观察者状态发生改变时&#xff0c;它会通知所有的被观察者对象&#xff0c;使他们能够及时做出响应&#xff0c;所以也被称作“发布-订阅模式”。总得来说就是你关注了一个主播&#xff0c;主播的状态改变会通…

Fabric:搭建自定义网络

Hyperledger Fabric: V2.5.4 写在最前 从本篇博客开始&#xff0c;将陆续介绍使用Fabric搭建自定义网络及部署执行链码的过程。本篇主要介绍如何搭建网络。   由于前文在安装Fabric的时候&#xff0c;已经将目录fabric-samples/bin加入到了环境变量PATH中&#xff0c;所以正文…