类和对象(下)(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++标准并没有严格规定,各个编译器会根据情况⾃⾏处理。当前主流的相对新⼀点的编译器对于连续⼀个表达式步骤中的连续拷⻉会进⾏合并优化,有些更新更"激进"的编译器还会进行跨行跨表达式的合并优化。
- 隐式类型转换时的优化
#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)。
- 传参时的优化
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;
可以看到,这两种写法都触发了合并优化,而它们的共同的就在于⼀个表达式步骤中的连续拷⻉。
当然,有的更激进的编译器会进行跨行的合并优化。
- 传返回值时的优化
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()的栈帧里,否则出了作用域就销毁了。
也就是说只优化了传值返回时的拷贝构造。
我们知道,大部分场景传值传参我们都可以采用引用传参来避免拷贝造成的效率变低,但是对于传值返回来说,不是所有场景都能传引用返回的。所以编译器就会这样激进。
本文到此结束=_=
相关文章:
类和对象(下)(2)
类和对象(下)(2) static成员 • ⽤static修饰的成员变量,称之为静态成员变量,静态成员变量⼀定要在类外进⾏初始化。 • 静态成员变量为当前类的所有对象所共享,不属于某个具体的对象,不存在对象中&#…...
软件测试 - 自动化测试(概念)(Java)(自动化测试分类、web自动化测试、驱动、selenium自动化测试工具的安装)
一、自动化的概念 ⾃动洒⽔机,主要通上⽔就可以⾃动化洒⽔并且可以⾃动的旋转。 ⾃动洗⼿液,免去了⼿动挤压可以⾃动感应出洗⼿液 超市⾃动闸⻔,不需要⼿动的开⻔关⻔ ⽣活中的⾃动化案例有效的减少了⼈⼒的消耗,同时也提⾼了⽣…...
wpf datagrid 实现双向绑定
前台 <DataGridAutoGenerateColumns"False"Background"White"CanUserAddRows"True"Grid.Row"1"RowEditEnding"DataGrid_OnRowEditEnding"RowHeight"60"SelectionUnit"CellOrRowHeader"x:Name"…...
使用循环在el-select下拉框中循环出-3至50
问: 使用循环在el-select下拉框中循环出-3至50 回答: <el-form-itemprop"adPosition"label"广告位置":rules"{required: true, message: 广告位置不能为空, trigger: change}" ><el-select v-model"addDataForm.adPosition"…...
全球海事航行通告解析辅助决策系统
“全球海事航行通告解析辅助决策系统”是一个针对海事行业设计的智能系统,旨在帮助海上导航和航运操作人员解析和应对全球发布的海事航行通告。 要做这样的系统我们必须要了解海事签派员的日常工作。 海事签派员,也称为船舶操作员或船运调度员࿰…...
Spring 解决bean的循环依赖
Spring循环依赖-博客园 1. 什么是循环依赖 2. 循环依赖能引发什么问题 循环依赖可能引发以下问题: 初始化顺序不确定:循环依赖导致无法确定哪个对象应该先被创建和初始化,从而造成初始化顺序的混乱。这可能导致错误的结果或意外的行为。死…...
鸿蒙内核源码分析(ELF格式篇) | 应用程序入口并不是main
阅读之前的说明 先说明,本篇很长,也很枯燥,若不是绝对的技术偏执狂是看不下去的.将通过一段简单代码去跟踪编译成ELF格式后的内容.看看ELF究竟长了怎样的一副花花肠子,用readelf命令去窥视ELF的全貌,最后用objdump命令…...
seq2seq编码器encoder和解码器decoder详解
编码器 在序列到序列模型中,编码器将输入序列(如一个句子)转换为一个隐藏状态序列,供解码器生成输出。编码层通常由嵌入层和RNN(如GRU/LSTM)等组成 Token:是模型处理文本时的基本单元,可以是词,子词,字符…...
前端使用 Konva 实现可视化设计器(21)- 绘制图形(椭圆)
本章开始补充一些基础的图形绘制,比如绘制:直线、曲线、圆/椭形、矩形。这一章主要分享一下本示例是如何开始绘制一个图形的,并以绘制圆/椭形为实现目标。 请大家动动小手,给我一个免费的 Star 吧~ 大家如果发现了 Bug,…...
Python 将单词拆分为单个字母组成的列表对象
Python 将单词拆分为单个字母组成的列表对象 正文 正文 这里介绍一个简单算法,将英文单词拆分为其对应字母组成的列表。 str1 ACG lst1 [i for i in str1] lst2 list(str1)# Method 1 print(lst1) # Method 2 print(lst2) """ result: [A, C, G…...
欧洲 摩纳哥税务知识
摩纳哥是一个位于法国南部的城邦国家,以其豪华的生活环境和宽松的税收政策而闻名。自1869年以来,摩纳哥取消了个人所得税的征收,这使得它成为富裕人士和外籍人士的理想居住地。然而,这并不意味着摩纳哥的税收制度完全不存在。以下…...
域控制器的四大支柱分别是车载以太网、自适应Autosar
域控制器的四大支柱分别是车载以太网、自适应Autosar、高性能处理器和集中式E/E架构。 百度安全验证 。自适应Autosar采用Proxy/Skeleton的通信架构,同时采用中间件SOME/IP...
写给大数据开发:如何优化临时数据查询流程
你是否曾因为频繁的临时数据查询请求而感到烦恼?这些看似简单的任务是否正在蚕食你的宝贵时间,影响你的主要工作?如果是,那么这篇文章正是为你而写。 目录 引言:数据开发者的困境问题剖析:临时数据查询的…...
【MongoDB】Java连接MongoDB
连接URI 连接 URI提供驱动程序用于连接到 MongoDB 部署的指令集。该指令集指示驱动程序应如何连接到 MongoDB,以及在连接时应如何运行。下图解释了示例连接 URI 的各个部分: 连接的URI 主要分为 以下四个部分 第一部分 连接协议 示例中使用的 连接到具有…...
nginx支持的不同事件驱动模型
Nginx 支持的不同事件驱动模型 Nginx 是一款高性能的 Web 和反向代理服务器,它支持多种事件驱动模型来处理网络 I/O 操作。不同的操作系统及其版本支持不同的事件驱动模型,这些模型对于 Nginx 的并发处理能力和性能至关重要。下面详细介绍 Nginx 支持的…...
C++ TinyWebServer项目总结(7. Linux服务器程序规范)
进程 PID 进程的PID(Process ID)是操作系统中用于唯一标识一个进程的整数值。每个进程在创建时,操作系统都会分配一个唯一的PID,用来区分不同的进程。 PID的特点 唯一性: 在操作系统运行的某一时刻,每个…...
基于STM32单片机设计的秒表时钟计时器仿真系统——程序源码proteus仿真图设计文档演示视频等(文末工程资料下载)
基于STM32单片机设计的秒表时钟计时器仿真系统 演示视频 基于STM32单片机设计的秒表时钟计时器仿真系统 摘要 本设计基于STM32单片机,设计并实现了一个秒表时钟计时器仿真系统。系统通过显示器实时显示当前时间,并通过定时器实现秒表计时功能。显示小时…...
人才流失预测项目
在本项目中,通过数据科学和AI的方法,分析挖掘人力资源流失问题,并基于机器学习构建解决问题的方法,并且,我们通过对AI模型的反向解释,可以深入理解导致人员流失的主要因素,HR部门也可以根据分析…...
BUG——imx6u开发_结构体导致的死机问题(未解决)
简介: 最近在做imx6u的linux下裸机驱动开发,由于是学习的初级阶段,既没有现成的IDE可以使用,也没有GDB等在线调试工具,只能把代码烧写在SD卡上再反复插拔,仅靠卑微的亮灯来判断程序死在哪一步。 至于没有使…...
问答:什么是对称密钥、非对称密钥,http怎样变成https的?
文章目录 对称密钥 vs 非对称密钥HTTP 变成 HTTPS 的过程 对称密钥 vs 非对称密钥 1. 对称密钥加密 定义: 对称密钥加密是一种加密算法,其中加密和解密使用的是同一个密钥。特点: 速度快: 因为只使用一个密钥,所以加密和解密速度较快。密钥分发问题: 双…...
Linux应用开发之网络套接字编程(实例篇)
服务端与客户端单连接 服务端代码 #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <pthread.h> …...
web vue 项目 Docker化部署
Web 项目 Docker 化部署详细教程 目录 Web 项目 Docker 化部署概述Dockerfile 详解 构建阶段生产阶段 构建和运行 Docker 镜像 1. Web 项目 Docker 化部署概述 Docker 化部署的主要步骤分为以下几个阶段: 构建阶段(Build Stage):…...
Linux简单的操作
ls ls 查看当前目录 ll 查看详细内容 ls -a 查看所有的内容 ls --help 查看方法文档 pwd pwd 查看当前路径 cd cd 转路径 cd .. 转上一级路径 cd 名 转换路径 …...
ESP32读取DHT11温湿度数据
芯片:ESP32 环境:Arduino 一、安装DHT11传感器库 红框的库,别安装错了 二、代码 注意,DATA口要连接在D15上 #include "DHT.h" // 包含DHT库#define DHTPIN 15 // 定义DHT11数据引脚连接到ESP32的GPIO15 #define D…...
Auto-Coder使用GPT-4o完成:在用TabPFN这个模型构建一个预测未来3天涨跌的分类任务
通过akshare库,获取股票数据,并生成TabPFN这个模型 可以识别、处理的格式,写一个完整的预处理示例,并构建一个预测未来 3 天股价涨跌的分类任务 用TabPFN这个模型构建一个预测未来 3 天股价涨跌的分类任务,进行预测并输…...
TRS收益互换:跨境资本流动的金融创新工具与系统化解决方案
一、TRS收益互换的本质与业务逻辑 (一)概念解析 TRS(Total Return Swap)收益互换是一种金融衍生工具,指交易双方约定在未来一定期限内,基于特定资产或指数的表现进行现金流交换的协议。其核心特征包括&am…...
高防服务器能够抵御哪些网络攻击呢?
高防服务器作为一种有着高度防御能力的服务器,可以帮助网站应对分布式拒绝服务攻击,有效识别和清理一些恶意的网络流量,为用户提供安全且稳定的网络环境,那么,高防服务器一般都可以抵御哪些网络攻击呢?下面…...
以光量子为例,详解量子获取方式
光量子技术获取量子比特可在室温下进行。该方式有望通过与名为硅光子学(silicon photonics)的光波导(optical waveguide)芯片制造技术和光纤等光通信技术相结合来实现量子计算机。量子力学中,光既是波又是粒子。光子本…...
如何更改默认 Crontab 编辑器 ?
在 Linux 领域中,crontab 是您可能经常遇到的一个术语。这个实用程序在类 unix 操作系统上可用,用于调度在预定义时间和间隔自动执行的任务。这对管理员和高级用户非常有益,允许他们自动执行各种系统任务。 编辑 Crontab 文件通常使用文本编…...
【Linux】Linux安装并配置RabbitMQ
目录 1. 安装 Erlang 2. 安装 RabbitMQ 2.1.添加 RabbitMQ 仓库 2.2.安装 RabbitMQ 3.配置 3.1.启动和管理服务 4. 访问管理界面 5.安装问题 6.修改密码 7.修改端口 7.1.找到文件 7.2.修改文件 1. 安装 Erlang 由于 RabbitMQ 是用 Erlang 编写的,需要先安…...
