类和对象(下)(2)

news2024/11/28 0:29:17

类和对象(下)(2)

在这里插入图片描述

static成员

• ⽤static修饰的成员变量,称之为静态成员变量,静态成员变量⼀定要在类外进⾏初始化。

• 静态成员变量为当前类的所有对象所共享,不属于某个具体的对象,不存在对象中,存放在静态区

#include<iostream>
using namespace std;
class A
{
public:
	A()
	{
		++_scount;
	}
	A(const A& t)
	{
		++_scount;
	}
	~A()
	{
		--_scount;
	}

private:
	// 类⾥⾯声明 
	static int _scount;
	//不能在这里给缺省值,因为这个缺省值是给初始化列表用的。但是这个值不会走初始化列表。
};

它不存在对象里面。

从sizeof计算的结果我们可以看出,它果然不是存在对象里面的。

它这样初始化:

// 类外⾯初始化 
int A::_scount = 0;

• ⽤static修饰的成员函数,称之为静态成员函数,静态成员函数没有this指针。

// 实现⼀个类,计算程序中创建出了多少个类对象?
class A
{
public:
	A()
	{
		++_scount;
	}
	A(const A& t)
	{
		++_scount;
	}
	~A()
	{
		--_scount;
	}
	static int GetACount()//静态成员函数
	{
		return _scount;
	}

private:
	// 类⾥⾯声明 
	static int _scount;
	//不能在这里给缺省值,因为这个缺省值是给初始化列表用的。但是这个值不会走初始化列表。
};

// 类外⾯初始化 
int A::_scount = 0;

int main()
{
	//cout << sizeof(A) << endl;
	//
	cout << A::GetACount() << endl;
	A a1, a2;
	
	//代码块,出去后析构时--
	{
		A a3(a1);
		cout << A::GetACount() << endl;
	}
    
    cout << A::GetACount() << endl;

	return 0;
}

• 静态成员函数中可以访问其他的静态成员,但是不能访问⾮静态的,因为没有this指针。

可以看到我们的静态成员函数无法访问⾮静态的成员变量_a,因为没有this指针。

• ⾮静态的成员函数,可以访问任意的静态成员变量和静态成员函数。

//非静态的访问静态的,可以随便访问	
void func()
	{
		cout << _scount << endl;
		cout << GetACount()<< endl;
	}

突破类域就可以访问静态成员,可以通过类名::静态成员或者对象.静态成员来访问静态成员变量和静态成员函数。

	cout << A::GetACount() << endl;
	cout << a1.GetACount() << endl;

• 静态成员也是类的成员,受public、protected、private访问限定符的限制。

• 静态成员变量不能在声明位置给缺省值初始化,因为缺省值是个构造函数初始化列表的,静态成员变量不属于某个对象,不⾛构造函数初始化列表。

一道题目,可以帮助感受:

当然一些编译器如VS是不支持变长数组的。

我们看一下这个问题:

构造:

局部的静态变量,无论是自定义类型还是内置类型,都是在第一次走到运行位置时才会初始化,而不是main函数之前就初始化。只有全局的静态变量才会在main函数之前就初始化。

析构:

注意静态变量d 的生命周期是全局的;后定义的先析构,所以b比a先析构,d比c先析构。

友元

• 友元提供了⼀种突破类访问限定符封装的⽅式,友元分为:友元函数和友元类,在函数声明或者类声明的前面加friend,并且把友元声明放到⼀个类的⾥⾯

• 外部友元函数可访问类的私有和保护成员,友元函数仅仅是⼀种声明,他不是类的成员函数。

class B
{
     // 友元声明 
     friend void func(const A& aa, const B& bb);

private:
     int _b1 = 3;
     int _b2 = 4;
};

void func(const A& aa, const B& bb)
{
     cout << aa._a1 << endl;
     cout << bb._b1 << endl;
}

• 友元函数可以在类定义的任何地⽅声明,不受类访问限定符限制

• ⼀个函数可以是多个类的友元函数。

前置声明

看下面这个代码:

class B;//如果没有这个前置声明,A的友元函数声明中编译器不认识B

class A
{
    //友元声明
    friend void func(const A& aa,const B& bb);
 private:
    int _a1 = 1;
    int _a2 = 2;
};

友元类中的成员函数都可以是另⼀个类的友元函数,都可以访问另⼀个类中的私有和保护成员。

class A
{
    //友元声明
private:
    int _a1 = 1;
    int _a2 = 2;
};

class B
{
public:
    void fun1(const A& aa)
    {
        cout<<aa._a1<<endl;
        cout<<_b1<<endl;
    }
    
    void func2(const A& aa)
    {
        cout<<aa._a2<<endl;
    }
}

• 友元类的关系是单向的,不具有交换性,⽐如A类是B类的友元,但是B类不是A类的友元。

• 友元类关系不能传递,如果A是B的友元,B是C的友元,但是A不是C的友元。

• 有时提供了便利。但是友元会增加耦合度,破坏了封装,所以友元不宜多⽤

内部类

• 如果⼀个类定义在另⼀个类的内部,这个内部类就叫做内部类。内部类是⼀个独⽴的类,跟定义在全局相⽐,他只是受外部类类域限制和访问限定符限制,所以外部类定义的对象中不包含内部类

class A
{
private:
	static int _k;
	int _h = 1;
public:
	class B//B默认就是A的友元
	{
	public:
		void foo(const A& a)
		{
			cout << _k << endl;//B是A的友元,可以访问A的私有
			cout << a._h << endl;
		}
	private:
		int _b = 1;
	};
};

int main()
{
    cout<<sizeof(A)<<endl;
    A::B b;
    
    return 0;
}

A的大小为4而不是8。

• 内部类默认是外部类的友元类

• 内部类本质也是⼀种封装,当A类跟B类紧密关联,A类实现出来主要就是给B类使⽤,那么可以考虑把A类设计为B的内部类,如果放到private/protected位置,那么A类就是B类的专属内部类,其他地⽅都⽤不了。

所以刚才那道题可以这样写:

也就是把Sum变为Solution的专属内部类,然后把两个静态成员变量变为在Solution中而不是Sum内部。

匿名对象

我们之前说过:

int main()
{
    A aa1;
    A aa1();//编译器无法识别是函数声明还是对象定义
    
    A();//但可以这样定义对象
    A(1);//也可以传参初始化匿名对象
    
}

最后一种写法,就是匿名对象。匿名对象和之前总提的临时对象都是编译器自己生成的没有名字的对象。与之对应的就是有名对象。

• ⽤类型(实参)定义出来的对象叫做匿名对象,相⽐之前我们定义的类型对象名(实参)定义出来的叫有名对象

那么这个匿名对象有什么用呢?

class Solution 
{
public:
     int Sum_Solution(int n) 
     {
         //...
         return n;
     }
    
};

int main()
{
    Solution st;
    cout<<st.Sum_Solution(10)<<endl;
    
    cout<<Solution().Sum_Solution(10)<<endl;
}

可以看到我们缩成了一句。无需定义有名对象再调用。

• 匿名对象⽣命周期只在当前⼀⾏,⼀般临时定义⼀个对象当前⽤⼀下即可,就可以定义匿名对象。

拓展知识:

对象拷贝时的编译器优化

• 现代编译器会为了尽可能提高程序的效率,在不影响正确性的情况下会尽可能减少⼀些传参和传返回值的过程中可以省略的拷⻉。

• 如何优化C++标准并没有严格规定,各个编译器会根据情况⾃⾏处理。当前主流的相对新⼀点的编译器对于连续⼀个表达式步骤中的连续拷⻉会进⾏合并优化,有些更新更"激进"的编译器还会进行跨行跨表达式的合并优化。

  1. 隐式类型转换时的优化
#include<iostream>
using namespace std;

class A
{
public:
	A(int a = 0)
		:_a1(a)
	{
		cout << "A(int a)" << endl;
	}

	A(const A& aa)
		:_a1(aa._a1)
	{
		cout << "A(const A& aa)" << endl;
	}

	A& operator=(const A& aa)
	{
		cout << "A& operator=(const A& aa)" << endl;
		if (this != &aa)
		{
			_a1 = aa._a1;
		}
		return *this;
	}

	~A()
	{
		cout << "~A()" << endl;
	}

private:
	int _a1 = 1;
};

int main()
{
	A aa = 1;//按理说是一个构造加拷贝构造

	return 0;
}

但是看结果我们发现合并为了一个构造:

//但是这样就无法省略
int main()
{
	A aa1 = 1;//省略为直接去构造
	const A& aa2 = 1;//这一句无法省略

	return 0;
}

因为前一句是用1去构造一个A类型的临时对象,再用临时对象去拷贝构造aa1,⼀个表达式步骤中的连续拷⻉**会进⾏合并优化。但是,下一句代码中没有拷贝构造这个过程,是用1去构造一个A类型的临时对象后,aa2直接变成这个临时对象的别名。(临时对象具有常性,所以要用const)。

  1. 传参时的优化
void f1(A aa)
{}

int main()
{
	A aa1;
	f1(aa1);

	return 0;
}

结果:

可以看到,没有进行优化。默认构造和拷贝构造(传值传参会调用拷贝构造)都进行了。

我们想要减少这个拷贝构造的办法是:

将函数的形参改为引用,而不是用传值传参。

形参是实参的别名。现在就没有拷贝构造了。

void f1(A aa)
{}

int main()
{
	f1(A(1));//匿名对象

	return 0;
}

在这里,A(1)是构造,f1()再去拷贝构造。

可以看到,结果是合并为了只有一次构造。

如果写成这样,A有单参数的构造函数,可以隐式类型转换:用1构造一个A类型的临时对象。因为是f1(1),传值传参,所以再去**拷贝构造。**所以这里又是一个连续的构造+拷贝构造,编译器进行了合并优化。

	f1(A(1));//匿名对象
	cout << endl;

	f1(1);
	cout << endl;

可以看到,这两种写法都触发了合并优化,而它们的共同的就在于⼀个表达式步骤中的连续拷⻉。

当然,有的更激进的编译器会进行跨行的合并优化。

  1. 传返回值时的优化
class A
{
    //……
    void Print()
    {
        cout << "A::Print->" << _a1 << endl;
    }
    
    private:
	int _a1 = 1;
};

A f2()
{
	A aa(1);//构造
	return aa;//传值返回会生成临时对象,会拷贝构造
}

int main()
{
	f2().Print();
	cout << endl;

	return 0;
}

打印结果:

可以看到这是比较激进的优化。

这时候有一个问题,编译器是没有生成临时对象,还是没有生成aa?

我们再将代码该得更直观一些:

int main()
{
	f2().Print();
	cout <<"*****************"<<endl;

	return 0;
}

我们看到,这个析构发生在Print之后说明构造的是临时对象而不是aa(如果是aa,应该在调用Print之前就析构了);这个析构发生在星号之前,说明生命周期只有一行,也符合临时对象的特性。

编译器在看到f2().Print();这样的代码后决定不生成aa了,直接用1构造临时对象作为函数返回值。(原本是需要用1构造aa,然后再用aa拷贝构造临时对象,现在直接用1构造临时对象,打印的也是临时对象的_a1)

这算是非常激进的。

如果不是在这么激进的编译器下,应该是这样的打印结果:

A(int a)//构造aa
A(const A& aa)//用aa去拷贝构造临时对象进行返回
~A()//aa生命周期结束,调用析构
A::Print->1//临时对象调用打印
~A()//临时对象生命周期结束,调用析构
*****************

那么现在如果我们改为这样:

class A
{
    //……
    void Print()
    {
        cout << "A::Print->" << _a1 << endl;
    }
    
    A& operator++()//重载一个前置++
	{
		++_a1;
		return *this;
	}
    
    private:
	int _a1 = 1;
};

A f2()
{
	A aa(1);
    ++aa;
    
	return aa;
}

int main()
{
	f2().Print();
	cout << endl;

	return 0;
}

可以看到,编译器还是合并优化了,而且很聪明地知道根据语法,临时对象的值应该为2。可以说它敢大胆地优化的同时也有能力保证结果的正确性不会因优化而出错。

我们再看这个不使用匿名对象,而是接收返回值的场景:

A f2()
{
	A aa(1);

	return aa;
}

int main()
{
	A ret = f2();
    ret.Print();
	cout <<"*****************"<<endl;

	return 0;
}

按照语法逻辑,应该是先构造aa,再用aa拷贝构造临时对象,再用临时对象拷贝构造ret。构造+拷贝构造+拷贝构造,会如何优化呢?

稍微老一点的编译器(如VS2019):

先看第一个构造的是aa,然后第一个析构析构的就是aa。

然后这只有一次的拷贝构造可能是aa去拷贝构造临时对象,或者是aa直接去拷贝构造ret。怎么判断? 这个在星号之后才析构的,析构的只能是ret,因为临时对象得在Print之前析构。

这个拷贝构造发生在aa的析构之前,由此可知在aa析构之前就先用aa拷贝构造了ret。

所以结论就是省掉的是临时对象。

原本我们要先用aa拷贝构造临时对象,再用临时对象拷贝构造ret,这个临时对象就相当于“中间商”,编译器把这个中间商优化掉了。

新一点的编译器(VS2022):

可以看到被编译器三步合一了,A ret = f2();合为一个构造。

从析构在星号之后可以看出构造的是ret,用1提前算好结果,直接一步到位去构造我们最后要的这个ret。

连aa都省掉了。

我们再试试++

可以看到,不是用1直接去构造ret,而是用计算好的2去构造ret。

再看这个场景:

可以看到我们现在只优化了一次,也就是传参返回时临时对象的拷贝构造被省去了。再看aa是在赋值之后析构的,且在打印之前,也就是赋值时是直接用aa去赋值给ret,而不是用临时对象赋值。赋值完后aa析构,然后调用Print,然后打印星号,最后再把ret析构。

可以说aa充当了临时对象。因为赋值后才析构的应该是临时对象。也就是说构造aa时不在f2()的栈帧里,否则出了作用域就销毁了。

也就是说只优化了传值返回时的拷贝构造。

我们知道,大部分场景传值传参我们都可以采用引用传参来避免拷贝造成的效率变低,但是对于传值返回来说,不是所有场景都能传引用返回的。所以编译器就会这样激进。

本文到此结束=_=

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

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

相关文章

m4a转wav,使用FFmpeg和Python将M4A文件转换为WAV

你可以使用Python程序或Linux命令来将M4A文件转换为WAV文件。下面分别介绍这两种方法。 方法一:使用FFmpeg命令行工具(Linux命令) FFmpeg 是一个强大的多媒体处理工具,支持音视频转换、处理等操作。你可以在终端中使用以下命令将M4A文件转换为WAV文件: ffmpeg -i input…

网络通信(1)

网络&#xff1a; 不同主机间的通信问题 实现网络通信 物理层面 有一个 信息通路 &#xff08;有线 &#xff1b;无线 &#xff1b;5G&#xff1b;4G&#xff1b;星链 &#xff09;软件层面(逻辑层面) 也需要 一个通路 网络编程 Open System Interconnect (OSI网…

[OC]萝卜圈玩行车记录仪

图1-1&#xff0c;你的手动小车 代码是 #机器人驱动主程序 #请在main中编写您自己的机器人驱动代码 import tkinter as tk import turtle v0 # 速度 accFalse;slowFalse;leftFalse;rightFalse # 按键状态 step0.5 # 一次速度变化量 def keyup_press(event):global acc;accTru…

归并排序(Java实现)

目录 归并排序的思想 代码实现 思路 代码 归并排序的特点 归并排序的思想 归并排序的核心思想是分治&#xff0c;分而治之&#xff0c;就是把数组先分成若干个子数组&#xff0c;先将这些子数组排序后&#xff0c;再合并到一起去。 我们常说的归并排序是二路归并排序&…

uview-plus upload组件在上传视频在小程序环境下点击无反应

你们好&#xff0c;我是金金金。 场景 我正在使用uniapp开发微信小程序&#xff0c;使用的vue3&#xff0c;所以集成的uview-plus这个ui库 代码非常的简单&#xff0c;就是一个上传组件 在h5环境下点击是可以上传视频的&#xff0c;在微信小程序开发者工具里面点击完全没反应…

初阶数据结构之计数排序

非比较排序 计数排序 计数排序⼜称为鸽巢原理&#xff0c;是对哈希直接定址法的变形应⽤。 操作步骤&#xff1a; 1&#xff09;统计相同元素出现次数 2&#xff09;根据统计的结果将序列回收到原来的序列中 #include "CountSort.h" void Count(int* arr, int n)…

利用GBDT进行对表格类数据的机器学习的实战项目

一&#xff1a;题目简介 在数据集中&#xff0c;每个样本都对应一个葡萄牙大学的学生。原始数据集中共有4424名学生&#xff0c;。对于每个学生&#xff0c;我们获得了人口统计数据、宏观经济数据以及课程前两个学期的表现。目标是预测学生在三年或四年学习后的状态&#xff1…

虚拟机桥接模式下设置静态IP

虚拟机桥接模式下设置静态ip 1. 设置虚拟机网络适配器为桥接模式2. 配置 CentOS 虚拟机的静态 IP3. 重启网络服务4. 验证配置5. 网络测试 要将 CentOS 虚拟机的网络模式从 NAT 模式更改为桥接模式&#xff0c;并设置静态 IP 地址以与 Windows 内网保持在相同网段&#xff0c;你…

PPP简介

介绍PPP特性的定义和目的。 定义 PPP&#xff08;Point-to-Point Protocol&#xff09;协议是一种点到点链路层协议&#xff0c;主要用于在全双工的同异步链路上进行点到点的数据传输。 目的 PPP协议是在串行线IP协议SLIP&#xff08;Serial Line Internet Protocol&#x…

vue---echarts环形图

1、完整代码直接可以cv <template><div id"main1"></div> </template><script> import * as echarts from echarts; // import { mapState } from vuex; // import { Alarm_Device } from ../utils/api.js; export default {name: P…

视频美颜SDK与直播美颜工具的架构设计与性能优化

本篇文章&#xff0c;小编将深入讲解视频美颜SDK与直播美颜工具的架构设计&#xff0c;并分享一些性能优化的实践经验。 一、视频美颜SDK的架构设计 视频美颜SDK的核心在于其模块化的设计思路。通常&#xff0c;视频美颜SDK由以下几个主要模块组成&#xff1a; 1.图像预处理…

【Qt】常用控件QCalendarWidget的使用

常用控件QCalendarWidget的使用 QCalendarWidget表示一个日历 核心属性 属性说明 selectDate 当前选中的⽇期 minimumDate 最⼩⽇期 maximumDate 最⼤⽇期 firstDayOfWeek 每周的第⼀天(也就是⽇历的第⼀列) 是周⼏. gridVisible 是否显⽰表格的边框 selectionMode…

AWS不同类型的EC2实例分别适合哪些场景?

Amazon Web Services&#xff08;AWS&#xff09;的弹性计算云&#xff08;EC2&#xff09;提供了多种实例类型&#xff0c;以满足不同的应用需求和工作负载。了解不同类型的 EC2 实例及其适用场景&#xff0c;可以帮助用户更好地优化性能和控制成本。九河云和大家一起了解一下…

Selenium + Python 自动化测试21(PO+HTML+Mail)

我们的目标是&#xff1a;按照这一套资料学习下来&#xff0c;大家可以独立完成自动化测试的任务。 上一篇我们讨论了PO模式并举例说明了基本的思路&#xff0c;今天我们继续学习。 本篇文章我们综合一下之前学习的内容&#xff0c;如先将PO模式和我们生成HTML报告融合起来&am…

gewe微信聊天机器人搭建教程

由于自身在机器人方面滚爬多年&#xff0c;现在收藏几个宝藏机器人 推荐一下自己常用的机器人&#xff1a; 适合有技术开发的公司&#xff0c;可以自主开发所需要的功能&#xff01;十分齐全 测试问文档&#xff1a;开发前必读 - GeWe开放平台 有需要的兄弟可以看一下&…

笔记 6 : 彭老师课本第 5 章 ,举例分析 IIC 编程,以及开启虚拟机

&#xff08;60&#xff09; 首先看 IIC 的陀螺仪的底板图&#xff0c;board 图&#xff1a; 以 GYRO_INT 为例去查找其对应的控制器&#xff1a; 继续查找 I2C_SCL5 以及 I2C_SDA5 : MPU6050 以及比较复杂&#xff0c;需要查看其手册&#xff0c;全英文版&#xff1a; 再…

iOS开发进阶(二十二):Xcode* 离线安装 iOS Simulator

文章目录 一、前言二、模拟器安装 一、前言 Xcode 15 安装包的大小相比之前更小&#xff0c;因为除了 macOS 的 Components&#xff0c;其他都需要动态下载安装&#xff0c;否则提示 iOS 17 Simulator Not Installed。 如果不安装对应的运行模拟库&#xff0c;真机和模拟器无法…

深入理解Java虚拟机(线程安全)

文章收录在网站&#xff1a;http://hardyfish.top/ 文章收录在网站&#xff1a;http://hardyfish.top/ 文章收录在网站&#xff1a;http://hardyfish.top/ 文章收录在网站&#xff1a;http://hardyfish.top/ 线程安全 当多个线程同时访问一个对象时&#xff0c;如果不用考虑…

图片清晰修复有什么方法?归纳了三种

图片清晰修复有什么方法&#xff1f;图片清晰度的修复是一项常见的后期处理任务&#xff0c;尤其当原始照片由于各种原因如手抖、光线不足等出现模糊不清的情况时。下文将介绍三种不同的软件修复方法来帮助提高图片的清晰度和细节&#xff0c;让你的照片看起来更加生动和专业。…

Kubectl命令、初识pod、namespace

文章目录 一、Kubectl简介基础命令1.基本信息命令2.创建和更新资源命令3.删除资源命令4. 查看日志和调试命令5. 端口转发和复制文件命令6. 部署管理命令7. 伸缩命令8. 配置和上下文管理命令9.常用命令 二、Pod简介核心概念pod常见状态调度和初始化阶段容器创建和运行阶段异常状…