类和对象(下)(2)

类和对象(下)(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的友元函数声明中编译器不认识Bclass 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.rhkb.cn/news/405863.html

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

相关文章

HiveSQL实战——大厂面试真题

一、字节跳动 最高峰同时直播人数 https://blog.csdn.net/SHWAITME/article/details/135918264 0 问题描述 有如下数据记录直播平台主播上播及下播时间&#xff0c;根据该数据计算出平台最高峰同时直播人数。 ------------------------------------------------------ | us…

CTFHUB | web进阶 | JSON Web Token | 无签名

一些JWT库也支持none算法&#xff0c;即不使用签名算法。当alg字段为空时&#xff0c;后端将不执行签名验证 开启题目 账号密码随便输&#xff0c;登录之后显示只有 admin 可以获得 flag 在此页面抓包发到 repeater&#xff0c;这里我们需要用到一个 Burp 插件&#xff0c;按图…

Linux信号机制探析--信号的产生

&#x1f351;个人主页&#xff1a;Jupiter. &#x1f680; 所属专栏&#xff1a;Linux从入门到进阶 欢迎大家点赞收藏评论&#x1f60a; 目录 &#x1f4da;信号什么是信号&#xff1f;为什么要有信号&#xff1f;查看Linux系统中信号 &#x1f388;信号产生&#x1f4d5;kill…

【流媒体】RTMPDump—RTMP_ConnectStream(创建流连接)

目录 1. RTMP_ConnectStream函数1.1 读取packet&#xff08;RTMP_ReadPacket&#xff09;1.2 解析packet&#xff08;RTMP_ClientPacket&#xff09;1.2.1 设置Chunk Size&#xff08;HandleChangeChunkSize&#xff09;1.2.2 用户控制信息&#xff08;HandleCtrl&#xff09;1…

JAVA面试汇总

JAVA面试 JAVA面试精华 面试精华 互联网面试真题

keepalived详解

概念 keepalived 是一款基于 VRRP&#xff08;Virtual Router Redundancy Protocol&#xff0c;虚拟路由冗余协议&#xff09;协议来实现高可用&#xff08;High Availability, HA&#xff09;的轻量级软件。它主要用于防止单点故障&#xff0c;特别是在 Linux 环境下&#xff…

使用maven快速生成打包文件

最近在部署基于SpringBoot开发的项目时&#xff0c;由于微服务较多&#xff0c;本地工程编译后只得出一个JAR包&#xff0c;部署起来实在不方便&#xff0c;因此总想着怎么偷偷懒&#xff0c;执行一次命令编译出整个部署的文件。先说结果&#xff0c;最后期望打包的目录如下&am…

C++ | 继承

前言 本篇博客讲解c中的继承 &#x1f493; 个人主页&#xff1a;普通young man-CSDN博客 ⏩ 文章专栏&#xff1a;C_普通young man的博客-CSDN博客 ⏩ 本人giee: 普通小青年 (pu-tong-young-man) - Gitee.com 若有问题 评论区见&#x1f4dd; &#x1f389;欢迎大家点赞&…

Kubernetes 如何给pod的 /etc/hosts文件里面添加条目

创建pod的时候&#xff0c;pod会在其/etc/hosts里面添加一个条目。 [rootmaster ~]# kubectl get pod -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES dns-test 1/1 R…

LLM概念梳理(二):检索增强RAG

非常感谢RAG&#xff08;检索增强生成&#xff09;技术详解&#xff1a;基于垂直领域专有数据的 Chatbots 是如何实现的&#xff0c;这篇文章对 RAG 技术进行了详细的描述。我根据自己的理解&#xff0c;并且按照代码思路重新进行整理。 RAG 技术看似神奇&#xff0c;其本质是…

图片怎么压缩得小一点?这八种免费图片压缩方法赶紧试试

在数字化时代&#xff0c;无论是工作还是日常生活中&#xff0c;图片的使用已变得不可或缺。然而&#xff0c;随着高分辨率图片的广泛应用&#xff0c;文件体积也随之增加&#xff0c;这不仅占用了大量存储空间&#xff0c;还可能导致传输和加载速度变慢。因此&#xff0c;如何…

干货:2024必备的四大PDF编辑器推荐!

面对PDF文件的编辑需求&#xff0c;你是否感到无从下手&#xff1f;那么&#xff0c;今天就为大家推荐几款实用的PDF编辑工具&#xff0c;让你轻松应对各种PDF编辑难题。 福昕PDF编辑器 链接&#xff1a;editor.foxitsoftware.cn 福昕PDF编辑器多功能专业级是我PDF编辑器。它…

python-docx 实现 Word 办公自动化

前言&#xff1a;当我们需要批量生成一些合同文件或者简历等。如果手工处理对于我们来说不仅工作量巨大&#xff0c;而且难免会出现一些问题。这个时候运用python处理word实现自动生成文件可极大的提高工作效率。 python-docx是python的第三方插件&#xff0c;用来处理word文件…

HTML 列表和容器元素——WEB开发系列10

HTML 提供了多种方式来组织和展示内容&#xff0c;其中包括无序列表、有序列表、分区元素 ​​<div>​​ 和内联元素 ​​<span>​​、以及如何使用 ​​<div>​​​ 进行布局和表格布局。 一、HTML 列表 1. 无序列表 (​​<ul>​​) 无序列表用于展…

JavaScript_11_练习:小米搜索框案例(焦点事件)

效果图 代码 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>练习&#xff1a;小米搜索框案例&#…

C语言基础(八)

1、标准库函数&#xff1a; 测试代码1&#xff1a; #include <stdio.h> // 标准库函数头文件导入 // 自定义函数 int add(int a, int b) { return a b; } // 声明回调函数类型 typedef void (*Callback)(int); // 调用回调函数的函数 void process(Callb…

MySQL中的EXPLAIN的详解

一、介绍 官网介绍&#xff1a; https://dev.mysql.com/doc/refman/5.7/en/explain-output.htmlhttps://dev.mysql.com/doc/refman/8.0/en/explain-output.htmlexplain&#xff08;执行计划&#xff09;&#xff0c;使用explain关键字可以模拟优化器执行sql查询语句&#xff…

QT 与 C++实现基于[ TCP ]的聊天室界面

TCP客户端 Widget.h #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QTcpSocket> //客户端类 #include <QMessageBox> #include <QListWidgetItem> #include <QDebug>QT_BEGIN_NAMESPACE namespace Ui { class Widget; } …

SpingBoot集成kafka发送读取消息

SpingBoot集成kafka开发 kafka的几个常见概念 1、springboot和kafka对应版本&#xff08;重要&#xff09;2、创建springboot项目&#xff0c;引入kafka依赖2.1、生产者EventProducer2.2、消费者EventConsumer2.3、启动生产者的方法SpringBoot01KafkaBaseApplication2.4、appli…

map与set容器初识:初步运用map与set

前言&#xff1a; 本文主要讲解的时对于map与set容器的初步使用&#xff0c;希望大家对map与set容器不熟悉的看了之后可以快速运用set与map到日常中来。&#xff08;本文适合对vector等基础容器有一定基础的同学&#xff09; 一、set与map容器常见接口 迭代器接口与以往的所…