c++使用类的一些注意事项
前言:
本篇内容为前面的补充,介绍了我们使用类时需要注意些什么以及一些编译器的优化,可能有些理解不到位或者错误,请斧正。
目录
前言:
1.再谈构造函数
2.(c++98)隐式类型转换中的编译器的优化
3.explicit关键字
4.static成员
5.匿名对象
6.友元函数
7.内部类
8.编译器的一些场上的优化
总结:
若有歧义,请指出,感谢阅读!
1.再谈构造函数
我们在构造函数体中,给成员变量赋值能叫做成员变量的初始化吗?并不可以,这种行为只是给成员变量赋初值,在函数体中,我们可以多次赋值,而初始化只能初始化一次。
那该如何初始化呢?
使用初始化列表。以一个冒号开始,接着是一个以逗号分割的数据成员列表,每个成员变量的后面跟一个放在括号中的初始值表达式。
class Date
{
public:Date(int year, int month, int day):_year(year),_month(month),_day(day){}private:int _year;int _month;int _day;
};
那像const这样的必须在定义位置的初始化的,放在成员变量里面该怎么初始化呢?
可以给缺省值,但不是初始化:
class A
{
public:A():_x(1){_a1++;}private:int _a1 = 1;int _a2 = 2;const int _x;//可以给缺省值,但不是初始化};
另外,缺省值也会在初始化列表进行初始化:
如上图我们可以看到, _a1的结果是2,虽然没有在初始化列表中显示的初始化,但是还是会走初始化列表初始化;其次_a2的结果是0,虽然_a2的缺省值是2,但是在初始化列表中显示的初始化为了1,所以再--就是0。而对于即不给缺省值也不给初始化的普通成员变量,经过测试,那就是随机值。
对于成员变量是引用的与成员变量是自定义类型的:
class B
{
public:B(int b):_b(0){cout << "B()" << endl;}private:int _b;
};class A
{
public:A():_x(1),_ref(_a1),_bb(0){_a1++;}private:int _a1 = 1;int _a2 = 2;int& _ref;B _bb;const int _x;//可以给缺省值,但不是初始化};
成员变量是引用的,跟const一样,本身引用就是要在定义的位置初始化,所以我们可以给缺省值,或者要在初始化列表初始化。
对于自定义类型的成员变量,_bb会去调用它的构造函数初始化吗?经过我的测试,_bb这个自定义类型的成员变量,如果不在A中的初始化列表初始化,就要去调用它的构造函数,但是一定要确保B中的构造函数一定是默认的,也就是说必须是全缺省的或者是不写编译器自动生成的。而上面的代码中B中的构造函数不是默认的构造函数,所以我们如果在A的类中不对_bb进行初始化列表的初始化,就会报错。
再看一个例子:
注释部分的构造函数可以,对两个自定义类型的成员进行了初始化列表的初始化,而内置类型由于没有显示的写就使用了缺省值,所以可以;如果Stack这个自定义类型中的构造函数是默认的,什么也没写的构造函数也是可以的,对于自定义类型初始化去调用它的默认构造函数,内置类型的初始化由于没有显示的写就使用它的缺省值。
其次还需要注意一个点:
class A1
{
public:A1(int a):_a1(a),_a2(_a1){}void Print(){cout << _a1 << " " << _a2 << endl;}private:int _a2;//声明的次序就是在初始化列表中的初始化的顺序int _a1;
};int main(){//A aa;A1 aa(1);aa.Print();return 0;
}
上面的结果应该是什么?结果是输出1和随机值。
这是因为成员变量在类中的声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关。
总结:
首先不管在初始化列表中显示的写没写初始化,都会在初始化的列表中走一遍。
其次记住一个原则:给初始化就在初始化列表中给初始化。
2.(c++98)隐式类型转换中的编译器的优化
class A
{
public:A(int a): _a1(a){cout << "A(int a)" << endl;}A(const A& aa):_a1(aa._a1){cout << "A(const A& aa)" << endl;}private:int _a1;
};int main(){A aa1(1);//构造函数A aa2 = 1;//隐式类型转换return 0;
}
根据我们之前的隐式类型转换的知识,我们可以知道这里对aa2这个对象赋值就是在进行隐式类型转换:首先1先构造一个临时对象,这个临时对象再拷贝给aa2,所以会去调用拷贝构造(注意拷贝构造也是构造,所以也有初始化列表)。
但是我们看到结果不是这样的:
为什么呢?这就是编译器所做的优化,因为编译器觉得自定义类型的这种初始化写起来还要调用拷贝构造,所以直接就优化了,直接就优化为了一步构造,1直接构造aa2。(注意这里的优化只限定于c++98的单参数的构造)
而对于这一种,编译器还能这样优化吗?答案是不能的,因为我们知道,10先构造出一个A类型的临时变量,而这个临时变量又具有常性且ref是这个临时变量的别名,所以需要加上const,而就是由于这个临时变量具有常性,编译器在这里就不会优化掉这个临时变量,所以10就没法直接构造ref了,而是先构造这个临时变量(这里使用的vs2022的编译器发现也没有调用拷贝构造,可能是编译器做的更极端了,也优化了,但是我们知道其后的原来即可)。
3.explicit关键字
explicit的引入就是为了防止隐式类型转换,这里加上了explicit, A aa2=1和const A& ref=10的隐式类型转换就没有了,就编不过了。
但是上面的隐式类型转换都是对于单参数的构造,对于多参数的构造,c++11可以使用多参数的构造来进行隐式类型转换:
其实也都是先调用构造,然后再进行隐式类型转换,再经过编译器的优化, 会节省一次隐式类型转换产生的拷贝。
同样的,如果不想使用隐式类型转换,就在构造函数上加上explicit,防止构造函数的隐式类型转换,这时A aa(1,1)这样需要隐式类型转换的就编不过了。
如果想使用隐式类型转换,让编译器进行优化,节省一次拷贝,就可以不加explicit。
4.static成员
我们将声明为static的类成员称为类的静态成员,用static修饰的成员变量,称为静态成员变量;用static修饰的成员函数,称之为静态成员函数。注意的是,静态成员函数没有this指针,静态成员变量在类中声明,在类外进行初始化。
统计程序中创建出了多少个类对象:
class A
{
public:A(int a = 0){cout << "构造" << endl;}A(const A& aa){cout << "拷贝构造" << endl;++count;}static int GetCount()//没有this指针{return count;//static函数没有this指针,访问不到成员count,只能读不能写}private:static int count;//声明,属于所有对象,属于整个类};int A::count = 0;//定义初始化
void func(A a)
{}int main()
{A aa1;//调用构造A aa2(aa1);//调用拷贝构造func(aa1);//函数传参,且参数是A类型的,所以调用拷贝构造A aa3 = 1;//隐式类型转换,经过编译器的优化,优化掉了拷贝构造,只有构造A aa4[10];//调用10次构造cout << aa3.GetCount() << endl;//如果GetCount是个static修饰的,只能接受返回的count,不能改//类中的静态成员也受访问限定符的限制,如果我们不让静态成员count设为私有,如何访问count?//A::count;//aa2.count或者aa3.count count属于所有对象//A* ptr=nullptr;ptr->count 这里不会去解引用,会直接去静态区中找return 0;
}
首先上面代码需要注意的是,GetCount是一个静态成员函数,所以访问它可以通过对象访问即aa3.GetCount(),或者指定内域访问,A::GetCount(),但是由于静态成员函数没有this指针,所以函数内不能访问非静态成员变量(这里返回count就是因为count是个静态成员变量,换成是普通的变量就不行了):
其次类中的静态成员也是受类的访问限定符的限制的,像这里count为私有,虽然是静态的全局的变量,但是在类外面还是访问不到的。
如果我们不将count设为私有,那怎么访问这个静态成员变量呢?
1.A::count 直接指定内域访问
2.aa2.count或者aa3.count 因为static成员是属于所有对象的,所以可以
3.A* ptr=nullptr;ptr->count 这里不会解引用,直接去静态区找
同时我们上方的代码也复习之前的知识:
分析结果:
前两个分别调用构造与拷贝构造,没什么说的;第三个是函数传参,由于参数也是A,也就是类类型的,所以会调用拷贝构造;然后是下面的隐式类型转换,编译器优化掉了拷贝,直接就是构造;然后可以看到如果自定义对象是数组,可以看到调用了10次。
总结:
5.匿名对象
当我们需要调用某个类的成员函数时需要先创建一个对象,所以我们引入了一个匿名对象,可以直接不创建对象直接去调用,写法为:类名()
通过析构函数可知,匿名对象的周期只在它出现的这一行,到下一行就会销毁。
返回值也可以使用匿名对象,更加简洁。
6.友元函数
友元函数在我们之前提到过,现在再来细看一下。
7.内部类
内部类c++很少用,隔壁Java常用。
首先,如果B这个类在A中是公有的,可以直接在外面指明内域访问例如A::B bb;
其次,如果B是私有的,那就不能通过A来访问了,所以B这个内部类既受A的类域的
限制(因为B为公有在外面需要指定在A的内域),又受到A的访问限定符的限制
然后,B这个内部类天生就是A的友元,所以可以通过内部类访问外部类的成员(静态的也可以)
补充:一个类里面,公有可以访问私有
总结:
8.编译器的一些场上的优化
首先先来分析3个优化:
class A
{
public:A(int a = 0){cout << "构造" << endl;}A(const A& a){cout << "拷贝构造" << endl;}~A(){cout << "~A" << endl;}
};void func1(A aa)
{}void func2(A& aa)
{}A func3()
{return A();
}int main()
{A aa1 = 1;func1(aa1);func1(2);func1(A(3));return 0;
}
第一个就是隐式类型转换编译器会将隐式类型转换产生的拷贝优化掉,所以就只有一个构造。
第二个是传参调用拷贝构造,但是这个拷贝构造不会被优化(大部分情况下只有c++98中的那个单参数构造和c++11的多参数构造会优化),但是可以使用传引用传参来减少拷贝。由于拷贝构造产生了临时变量,所以在func1函数结束时,会调用析构销毁这个临时变量。
第三个与第四个也都是传参调用拷贝构造,但是结果发现,拷贝构造被优化为了构造?!理想的结果不应该是拷贝构造,然后跟析构吗???当时我也疑惑了好久,其实这是因为编译器的处理,在我的vs2022的编译器下,可能由于编译器太新,优化的比较极端,编译器看到你func1函数什么也没写,干嘛要在拷贝构造一次,干脆直接就优化为了拿形参构造实参,可以看到构造后紧跟的就是析构,这也表明还是存在拷贝产生的临时变量需要销毁。
最后主函数结束aa1销毁调用析构。
另外,如果我们使用func2的传引用传参,后两个传参会直接报错,因为它们是临时变量,使用引用后,传过去后函数作用域销毁,这个变量就找不到了;而aa1的作用域在main函数中,尽管fun2的函数结束,但是还是能找到aa1。
再看一种优化:
在这个场景下, A aa先构造,返回aa再调用拷贝构造(因为没有创造对象,所以这个拷贝构造有可能被优化掉),拷贝的临时变量销毁调用一次析构,aa这个局部对象销毁再调用一次析构,那这两个析构谁先调用的呢?拷贝构造后面经跟的就是拷贝时创建的临时变量销毁调用的析构
此时还没有优化,但是当我们来接受它的返回值时:
aa拷贝给一个临时变量,临时变量再拷贝给给aa1,这里就会被优化为一个拷贝构造。
如果我们在将赋值重载再写出来(下面的称为赋值接受):
就会得到下面的结果:
如果先定义一个对象,再接收返回值,就是aa2先构造,func3里aa构造,返回时一个拷贝构造(这个拷贝构造有可能会被优化,因为没有创造新的对象来接收返回值,看编译器,这里就是被优化了)然后没有被优化,那会多出来一对拷贝构造+析构。结果中的两个析构分别是func3函数结束aa销毁调用的析构和main函数结束aa2销毁的析构。
如果是下面的场景(下面的称为拷贝构造接受):
如果直接调用fun2,返回的匿名对象调用一次构造,匿名对象出了fun2析构一次。
如果接收fun2的返回值,匿名对象A()先构造一次,返回时拷贝构造一次,返回给aa2再拷贝一次编译器会优化用 匿名对象直接构造aa2,直接就是构造一次,所以最后一次析构是aa2销毁的析构
总结:
优化的场景很多,我们只要记住:
总结:
若有歧义,请指出,感谢阅读!
相关文章:
c++使用类的一些注意事项
前言: 本篇内容为前面的补充,介绍了我们使用类时需要注意些什么以及一些编译器的优化,可能有些理解不到位或者错误,请斧正。 目录 前言: 1.再谈构造函数 2.(c98)隐式类型转换中的编译器的优…...
C++蓝桥考级一级到十八级的考点内容整理
以下是C蓝桥考级一级到十八级的考点内容整理: C一级考点内容 C程序基本结构 初步了解C编程了解C程序基本结构:头文件、命名空间、主函数、基本输入输出 cin、cout C二级考点内容 数据类型与变量 掌握编程中数学表达式的计算方式基础数据类型、变量的…...
C++智能指针简单剖析
导读 最近在补看《C Primer Plus》第六版,这的确是本好书,其中关于智能指针的章节解析的非常清晰,一解我以前的多处困惑。C面试过程中,很多面试官都喜欢问智能指针相关的问题,比如你知道哪些智能指针?shar…...
mysql笔记:25. docker环境中mysql主从复制、主主复制实操
文章目录 一、准备工作1. 安装配置Docker2. 准备MySQL相关的配置和数据目录 二、基于日志点的主从复制1. 配置Master服务器1.1 修改配置文件1.2. 在docker中启动Master节点1.3. 创建用户并授权 2. 配置Slave1服务器2.1. 修改配置2.2. 启动服务2.3. 指定Master2.4. 开始复制 3. …...
大数据面试专题 -- kafka
1、什么是消息队列? 是一个用于存放数据的组件,用于系统之间或者是模块之间的消息传递。 2、消息队列的应用场景? 主要是用于模块之间的解耦合、异步处理、日志处理、流量削峰 3、什么是kafka? kafka是一种基于订阅发布模式的…...
深度学习入门简单实现一个神经网络
实现一个三层神经网络 引言测试数据 代码 引言 今天我们实现一个简单的神经网络 俩个输入神经元 隐藏层两个神经元 一个输出神经元 激活函数我们使用sigmoid 优化方法使用梯度下降 我们前期准备是需要把这些神经元的关系理清楚 x1:第一个输入 x2:第二个…...
win11 环境配置 之 Jmeter(JDK17版本)
一、安装 JDK 1. 安装 jdk 截至当前最新时间: 2024.3.27 jdk最新的版本 是 官网下载地址: https://www.oracle.com/java/technologies/downloads/ 建议下载 jdk17 另存为到该电脑的 D 盘下,新建jdk文件夹 开始安装到 jdk 文件夹下 2. 配…...
Windows下载使用nc(netcat)命令
‘nc’ 不是内部或外部命令,也不是可运行的程序? 点击链接地址,下载压缩包。 完成后解压 使用方式(三种): 1、直接双击exe使用 2、把这个exe放到cmd启动的默认路径下 放到默认路径下,使用nc&a…...
istio 设置 istio-proxy sidecar 的 resource 的 limit 和 request
方式一 修改 configmap 查看当前 sidecar 的 cpu 和 memory 的配额 在 istio-sidecar-injector 中查找,修改后重启 pod 可以生效(下面那个 proxy_init 配置不管,不知道是干嘛的) 方式二 如果是通过 iop 安装的 istio…...
flutter弹框
alertDialog:弹框 simpleDialog:选择弹框 showModalBottomSheet:底部弹出弹框 showtoast:三方插件弹框 Navigator.of(context).pop(点击取消) 关闭弹框,传递参数 import package:flutter/material.dart; // import package:flutter/cupertino.dart; import package:flut…...
2013年认证杯SPSSPRO杯数学建模B题(第一阶段)流行音乐发展简史全过程文档及程序
2013年认证杯SPSSPRO杯数学建模 B题 流行音乐发展简史 原题再现: 随着互联网的发展,流行音乐的主要传播媒介从传统的电台和唱片逐渐过渡到网络下载和网络电台等。网络电台需要根据收听者的已知喜好,自动推荐并播放其它音乐。由于每个人喜好…...
代码随想录算法训练营第39天 | 62.不同路径, 63不同路径II
Leetcode - 62:不同路径 题目: 一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” &#…...
Redis 的慢日志
Redis 的慢日志 Redis 的慢日志(Slow Log)是用于记录执行时间超过预设阈值的命令请求的系统。慢日志可以帮助运维人员和开发人员识别潜在的性能瓶颈,定位那些可能导致 Redis 性能下降或响应延迟的慢查询。以下是 Redis 慢日志的相关细节&…...
第十四届蓝桥杯第十题:蜗牛分享
问题描述 输入格式 输出格式 输出共一行,一个浮点数表示答案(四舍五入保留两位小数)。 样例输入 3 1 10 11 1 1 2 1样例输出 4.20样例说明 蜗牛路线:(0,0)→(1,0)→(1,1)→(10,1)→(10,0)→(11,0)(0,0)→(1,0)→(1,1)→(10,1…...
不懂技术的老板,如何避免过度依赖核心技术人员
在这个日新月异、技术驱动的时代,即使作为非技术背景的老板,也深知核心技术人员的价值。然而,过度依赖某几位核心技术人员,不仅可能带来经营风险,还可能限制企业的创新与发展。那么,不懂技术的老板…...
Vue系列-el挂载
<!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>el:挂载点</title> </head> <body&g…...
python--os和os.path模块
>>> import os >>> #curdir #获取当前脚本的绝对路径 >>> os.curdir . >>> import os.path >>> #获取绝对路径 >>> os.path.abspath(os.curdir) C:\\Users\\GUOGUO>>> #chdir #修改当前目录 >&g…...
前端通用命名规范和Vue项目命名规范
通用命名规范 变量和常量命名:变量和常量的命名应具有描述性,清晰明了,使用驼峰命名法或下划线命名法,例如:firstName、MAX_VALUE。 函数和方法命名:函数和方法的命名应该能够准确描述其功能&…...
NTP服务搭建
一、ntpd和ntpdate区别 1.ntpd是自动执行的远程更新本地系统时钟的服务,是平滑同步; 2.ntpdate是手工执行的服务,也就是一般用它执行一次本地时间更新,如果做成半自动,可以写入到crontab自动任务,从而变成…...
Linux离线安装mysql,node,forever
PS:本文是基于centos7实现的,要求系统能够查看ifconfig和unzip解压命令, 实现无网络可安装运行 首先现在百度网盘的离线文件包****安装Xftp 和 Xshell 把机房压缩包传到 home目录下****解压unzip 包名.zip 获取IP先获取到 linux 主机的ip ifconfig Xftp 连接输入IP,然后按照…...
WPF中获取TreeView以及ListView获取其本身滚动条进行滚动
实现自行调节scoll滚动的位置(可相应获取任何控件中的内部滚动条) TreeView:TreeViewAutomationPeer lvap new TreeViewAutomationPeer(treeView); var svap lvap.GetPattern(PatternInterface.Scroll) as ScrollViewerAutomationPeer; var scroll svap.Owner as ScrollVie…...
C语言: 指针讲解
为什么需要指针? (1)指针的使用使得不同区域的代码可以轻易的共享内存数据。当然你也可以通过数据的复制达到相同的效果,但是这样往往效率不太好,因为诸如结构体等大型数据,占用的字节数多,复制很消耗性能…...
C#使用Stopwatch类来实现计时功能
前言 在 C# 中,Stopwatch 类是用于测量经过的时间的工具类,提供了高精度的计时功能。Stopwatch 类位于 System.Diagnostics 命名空间中。通常情况下,使用 Stopwatch 的流程是创建一个 Stopwatch 对象,然后调用 Start 方法开始计时…...
ubuntu18.04安装qt
ubuntu18.04安装qt 1、下载文件 比如我下载的是5.13.0版本 下载链接 2、安装 wget https://download.qt.io/archive/qt/5.13/5.13.0/qt-opensource-linux-x64-5.13.0.runsudo chmod x qt-opensource-linux-x64-5.13.0.runsudo ./qt-opensource-linux-x64-5.13.0.run参考文…...
ElasticSearch、java的四大内置函数式接口、Stream流、parallelStream背后的技术、Optional类
第四周笔记 一、ElasticSearch 1.安装 apt-get install lrzsz adduser -m es 创建用户组: useradd *-m* xiaoming(用户名) *PS:追加参数-m* passwd xiaoming(用户名) passwd xiaoming 输入新的 UNIX 密码: 重新输入新的 UNIX 密码&…...
深入MNN:开源深度学习框架的介绍、安装与编译指南
引言 在人工智能的世界里,深度学习框架的选择对于研究和应用的进展至关重要。MNN,作为一个轻量级、高效率的深度学习框架,近年来受到了众多开发者和研究人员的青睐。它由阿里巴巴集团开源,专为移动端设备设计,支持跨平…...
[LeetCode][400]第 N 位数字
题目 400. 第 N 位数字 给你一个整数 n ,请你在无限的整数序列 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, …] 中找出并返回第 n 位上的数字。 示例 1: 输入:n 3 输出:3 示例 2: 输入:n 11 输出:…...
clickhouse 查询group 分组最大值的一行数据。
按照 sql_finger_md5 分组取query_time_ms 最大的一行数据。 使用any函数可以去匹配到的第一行数据,所以可以先让数据按照query_time_ms 排序,然后再使用group by 和any结合取第一行数据,就是最大值的那一行数据。 selectany (time) as time…...
Python装饰器与生成器:从原理到实践
一、引言 Python 是一种功能强大且易于学习的编程语言,其丰富的特性使得开发者能够高效地完成各种任务。在 Python 中,装饰器和生成器是两个非常重要的概念,它们能够极大地增强代码的可读性和可维护性。本文将详细介绍如何学习 Python 装饰器…...
python-函数引入模块面向对象编程创建类继承
远离复读机行为 def calculate_BMI(weight,height):BMI weight / height**2if BMI < 18.5:category "偏瘦"elif BMI < 25:category "正常"elif BMI < 30:category "偏胖"else:category "肥胖"print(f"您的BMI分类…...
免费建站的手机app/广东seo网站优化公司
转载于:https://blog.51cto.com/241998/43668...
wordpress侧栏菜单/淄博网站推广
垃圾回收机制 # 不能被程序访问到的数据,就称之为垃圾 引用计数 # 引用计数是用来记录值的内存地址被记录的次数的# 每一次对值地址的引用都可以使该值的引用计数 1# 每一次对值地址的释放都可以使该值得引用计数 -1# 当一个值的引用计数为0时,该值就…...
宝安网站建设/爱网
数组 要注意的是数组名里面存放了数组的首地址,但是数组名等价于指针,因为用sizeof运算符队数组名可以直接计算出整个数组的大小,而sizeof对指针则是返回指针的大小。 c语言对数组不做越界检查,使用的时候要注意。 数组不初始化&a…...
自己做网站需要买什么/游戏推广论坛
一、Android Studio使用夜神模拟器进行调试开发: 1、首先打开Android Studio工具,然后再运行夜神模拟器 2、打开夜神模拟器bin运行目录(如cd D:\Nox\bin) 3、cmd执行命令:nox_adb.exe connect 127.0.0.1:62001&…...
旅游网站建设目的/by网站域名
一、go的搭建 二、初识go 三、混个脸熟--go 四、go的语言结构 五、go的常量与变量 六、go基础数据类型 七、go 条件语句 八、go 运算符 九、go条件语句switch 十、go循环语句 十一、go之数组 十二、go初级函数 十三、go函数高级运用 十四、go之变量、指针、内存地址 十五、go之…...
server2012 wordpress/百度一下首页百度一下
Android 技术专题系列之三 -- 编译(build) 流程 Android使用的build系统与普通Linux软件使用的build工具有很大不同,因而,可能使熟悉传统Linux开发工具的程序员稍感陌生。普 通的软件项目一般使用autotools工具&#x…...