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

news2024/9/23 21:30:37

目录

一、友元

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;// >> OK
			cout << 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.coloradmin.cn/o/1263875.html

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

相关文章

0005Java程序设计-ssm基于微信小程序的校园求职系统

文章目录 摘 要目 录系统设计开发环境 编程技术交流、源码分享、模板分享、网课分享 企鹅&#x1f427;裙&#xff1a;776871563 摘 要 随着我国经济迅速发展&#xff0c;人们对手机的需求越来越大&#xff0c;各种手机软件也都在被广泛应用&#xff0c;但是对于手机进行数据…

因为计算机中丢失MSVCP140.dll,无法启动此程序运行软件的解决方法

msvcp140.dll重新安装五个解决方法与msvcp140.dll文件的作用和丢失对电脑的影响介绍 正文&#xff1a; 在计算机使用过程中&#xff0c;我们经常会遇到一些错误提示&#xff0c;其中最常见的就是“缺少xxx.dll文件”。而msvcp140.dll就是其中之一。那么&#xff0c;msvcp140.…

哈希表——闭散列表

该哈希表实现是闭散列实现法。 闭散列表&#xff1a; 闭散列&#xff1a;也叫开放定址法&#xff0c;当发生哈希冲突时&#xff0c;如果哈希表未被装满&#xff0c;说明在哈希表中必然还有空位置&#xff0c;那么可以把key存放到冲突位置中的“下一个” 空位置中去。 那如何寻…

sprintboot快速初始化【Springboot】

1.首先选择创建项目 2.填写对应的项目信息 一定要勾选maven&#xff0c;否则没有pom文件&#xff0c;选择next 3.选择应用场景 点击 create&#xff0c;DIEA就会根据你的选择自动创建项目骨架&#xff1b; 4.创建一个控制层 随便创建一个控制层&#xff0c;测试一下项目是否…

阿里云语雀频繁崩溃,有什么文档管理工具是比较稳定的?

10月23 日14:00左右&#xff0c;蚂蚁集团旗下的在线文档编辑与协同工具语雀发生服务器故障&#xff0c;在线文档和官网都无法打开。直到当天晚上22:24&#xff0c;语雀服务才全部恢复正常。从故障发生到完全恢复正常&#xff0c;语雀整个宕机时间将近 8 小时&#xff0c;如此长…

麒麟V10桌面搭建FTP服务

1.1介绍 FTP&#xff1a;File transfer protocol &#xff08;文件传输协议&#xff09;是 TCP/IP 协议组中的协议之一。FTP协议包括两个组成部分&#xff0c;其一为FTP服务器&#xff0c;其二为FTP客户端。其中FTP服务器用来存储文件&#xff0c;用户可以使用FTP客户端通过FT…

Java变量理解

成员变量VS局部变量的区别 语法形式&#xff1a;从语法形式上看&#xff0c;成员变量是属于类的&#xff0c;而局部变量是在代码块或方法中定义的变量或是方法的参数&#xff1b;成员变量可以被 public,private,static 等修饰符所修饰&#xff0c;而局部变量不能被访问控制修饰…

深度盘点:100 个 Python 数据分析函数总结

经过一段时间的整理&#xff0c;本期将分享我认为比较常用的100个实用函数&#xff0c;这些函数大致可以分为六类&#xff0c;分别是统计汇总函数、数据清洗函数、数据筛选、绘图与元素级运算函数、时间序列函数和其他函数。 技术交流 技术要学会交流、分享&#xff0c;不建议…

DMX512协议及对接口电路的分析

1、DMX512协议简介 DMX 是Digital MultipleX 的缩写&#xff0c;意为多路数字传输(具有512条信息的数字多路复用”)。DMX512控制协议是美国舞台灯光协会(usITT)于1990年发布的灯光控制器与灯具设备进行数据传输的工业标准&#xff0c;全称是USITTDMX512(1990); DMX512 在其物理…

达索系统3DEXPERIENCE WORKS 2024 结构仿真功能增强

simulia结构仿真是什么&#xff1f; 不仅能对结构进行力学、热学、声学等多学科计算&#xff0c;辅助于设计方案的优化&#xff1b;还能采用数字化技术模拟产品性能&#xff0c;大幅节约试验和样机迭代成本。达索系统3DEXPERIENCE WORKS 2024 结构仿真为企业提供随需应变、精准…

Android开源框架--Dagger2详解

功名只向马上取&#xff0c;真是英雄一丈夫 一&#xff0c;定义 我们知道在一个类中&#xff0c;通常会定义其他类型的变量&#xff0c;这个变量就是我们所说的“依赖“。 对一个类的变量进行初始化&#xff0c;有两种方式。第一种&#xff0c;这个类自己进行初始化&#xff…

Linux环境安装Java,Tomcat,Mysql,

1、Java的安装 载 jdk1.8 注&#xff1a;此处 CentOS7 是64位&#xff0c;所以下载的是&#xff1a;Linux x64&#xff0c; 文件类型为 tar.gz 的文件 JDK 官网地址&#xff1a;https://www.oracle.com/java/ cd /usr/local/ mkdir jdk cd jdk/tar -xvf jdk-8u202-linux-x64.…

pandas教程:US Baby Names 1880–2010 1880年至2010年美国婴儿姓名

文章目录 14.3 US Baby Names 1880–2010&#xff08;1880年至2010年美国婴儿姓名&#xff09;1 Analyzing Naming Trends&#xff08;分析命名趋势&#xff09;评价命名多样性的增长“最后一个字母”的变革变成女孩名字的男孩名字&#xff08;以及相反的情况&#xff09; 14.3…

【Docker项目实战】使用Docker部署Plik临时文件上传系统

【Docker实战项目】使用Docker部署Plik 临时文件上传系统 一、Plik介绍1.1 Plik简介1.2 Plik特点 二、本地环境介绍2.1 本地环境规划2.2 本次实践介绍 三、本地环境检查3.1 检查Docker服务状态3.2 检查Docker版本3.3 检查docker compose 版本 四、下载Plik镜像五、部署Plik临时…

学习知识随笔(Django)

文章目录 MVC与MTV模型MVCMTV Django目录结构Django请求生命周期流程图路由控制路由是什么路由匹配反向解析路由分发 视图层视图函数语法reqeust对象属性reqeust对象方法 MVC与MTV模型 MVC Web服务器开发领域里著名的MVC模式&#xff0c;所谓MVC就是把Web应用分为模型(M&#…

案例-某乎参数x-zse-96逆向补环境

文章目录 前言一、流程分析二、导出代码三、补环境总结 前言 本文章中所有内容仅供学习交流使用&#xff0c;不用于其他任何目的&#xff0c;不提供完整代码&#xff0c;抓包内容、敏感网址、数据接口等均已做脱敏处理&#xff0c;严禁用于商业用途和非法用途&#xff0c;否则…

Java代码的编译与执行过程

一、编译过程 1、javac 编译 Java源代码通过编译器&#xff08;javac&#xff09;编译为字节码文件(.class)。 idea中的 build 和 maven package等指令都可以编译为 .class 2、类加载器(Class Loader) 类加载器负责将类的字节码文件加载到内存中&#xff0c;以便在运行时创…

【RTP】5:从network收到rtp包到组帧之间的数据传递

m79 代码。从网络中收到rtp、rtcp 后交给call 进行处理这是因为call 具有PacketReceiver 的能力。收到的包是一个 :CopyOnWriteBuffer 类型:rtc::CopyOnWriteBuffer packetclass Call PacketReceiver 准备delivery包:返回delivery结果:}成功、包错误、ssrc未知 D:\zhb-dev\…

96.STL-遍历算法 transform

目录 transform 语法&#xff1a; 功能描述&#xff1a; 函数原型&#xff1a; 代码示例&#xff1a; transform 是 C 标准模板库&#xff08;STL&#xff09;中的一个算法&#xff0c;用于对一个范围内的元素进行转换并将结果存储到另一个范围。以下是简要解释和一个示例…

Ansys Lumerical|带 1D-2D 光栅的出瞳扩展器

附件下载 联系工作人员获取附件 此示例显示了设置和模拟出瞳扩展器 &#xff08;EPE&#xff09; 的工作流程&#xff0c;EPE 是波导型增强现实 &#xff08;AR&#xff09; 设备的重要组成部分。该工作流程将利用 Lumerical 和 Zemax OpticStudio 之间的动态链接功能 。为了…