初始化列表 / 隐式转换 / 静态

目录

  • 初始化列表
  • 隐式转换
    • 单参数的隐式类型转换
    • 多参数的隐式类型转换
    • explicit关键字
  • static

初始化列表

大部分时候成员变量在对象实例化的时候调用构造函数就整体定义了,注意此时只有定义,不算初始化。而定义后的值的值是在构造函数里面给的。我们知道构造函数可以执行很多语句,可以给成员变量多次赋值。此时就不能说在构造函数内部初始化了。
但是有些成员必须在定义的时候初始化。如果定义一个const成员变量,但是const成员变量规定不能在构造函数里面给他初始化而且const就只有一次初始化的机会。此时就可以在初始化列表给他初始化。
初始化列表的写法:

class Date
{
public:Date(int y):_year(1),_month(2),_day(3)//初始化列表初始化的值{_year = y;//_day = 100;const无法在构造函数中改变cout << "Date(int y)" << endl;}Date(const Date& d)//拷贝构造也可以有初始化列表:_year(1), _month(2), _day(3){cout << "Date(Date& d)" << endl;}
private:int _year;int _month;const int _day;
};

在构造函数下面写初始化列表,来拷贝构造和构造函数都可以有初始化列表

祖师爷为了区分初始化和定义就设计了初始化列表,在初始化列表中初始化我们的成员变量。我们每一次定义对象执行构造函数时,会先走初始化列表,即使我们没写也会走初始化列表。 初始化列表给了初始值,如果没有初始值,在C++11中的打了补丁,给了缺省值,即在类声明的时候给的值,其实就是初始化时候要给的值,如果没有缺省值,那初始化就给默认值。

private:int _year = 10;int _month = 20;const int _day = 30;

如上,如果没有给初始化列表,初始值就是这些缺省值

建议能用初始化列表就用初始化列表,因为初始化列表是必走的。

注意:

  1. const必须走初始化列表,因为const属性必须给予初始值, 必须使用初始化列表,这里说使用初始化列表是,必须给一个缺省值或者在初始化列表给一个值
  2. 引用必须走初始化列表,因为引用只能是一个对象的引用,一个对象可以有多个引用,但一个引用只能对应一个引用对象。从这个角度看,引用就像是一个常量一样。
    引用既可以给缺省值引用也可以在初始化列表进行引用
private:int _year;int _month;const int _day;int& yy = _month;

自定义类型也会走初始化列表,然后去调用自己的默认构造,如果他没有默认构造就会报错,这一点在之前写构造函数时提到过。但是有了初始化列表,我们在初始化列表里面给自定义类型参数,此时就不会报错。此时相当于直接定义然后调用了构造。

如下,如果我们不写初始化列表里面的a(1,2),编译器就会报错,因为类A没有默认构造函数。但是如果在初始化列表里面写了的话,就相当于定义的对象,并且给了参数。然后走他的构造函数。

class A
{
public:A(int a, int b):x(a+10){cout << "A(int a, int b)" << endl;cout << x << endl;}
private:int x;
};class Date
{
public:Date(int y):_year(1),_month(2),_day(3), yy(_month),a(1,2){_year = y;cout << "Date(int y)" << endl;cout << _day << endl;}Date(const Date& d)//拷贝构造也可以有初始化列表:_year(1), _month(2), _day(3), yy(_month), a(1, 2){cout << "Date(Date& d)" << endl;}
private:int _year;int _month;const int _day;int& yy = _month;A a;
};int main()
{Date d1(1);return 0;
}

上述代码执行结果如下:
在这里插入图片描述
初始化列表主要解决的就是这三类问题:
引用成员变量,const成员变量,自定义类型成员(且没有默认构造函数)。就是那些不能在函数体内定义的成员变量。
其他的成员,可以在初始化列表,也可以在构造函数内初始化处理,但是建议在初始化列表里面处理。

另一个注意的点是:
初始化列表按照声明顺序初始化

class Date
{
public:Date(int y):_month(2) ,_year(_month){cout << _year << endl;cout << _month << endl;}
private:int _year;int _month;
};int main()
{Date d1(1);return 0;
}

执行结果是
在这里插入图片描述
可以发现初始化列表先写了month,再写的year,但是year并没有使用month的值初始化自己。调试的时候也是执行的year行再执行的month行。

隐式转换

单参数的隐式类型转换

看下面代码理解隐式转换:

class Date
{
public:Date(int y){cout << y << endl;}
private:int _year;int _month;
};int main()
{Date d1(11);Date d2 = 12;return 0;
}

执行结果为
在这里插入图片描述
可以发现代码中Date d2 = 12;,是将一个整形赋值给了一个类类型。这样的写法就是隐式类型转换。但是前提是单参数构造函数,只有单参数构造函数才支持隐式类型转换

转换原理: 整型12作为参数构造了一个临时对象,然后d2再通过临时对象进行拷贝构造,但是编译器会进行优化,同一个表达式的连续步骤的构造,一般会合二为一,此时不会去执行拷贝构造,如果上述代码写了拷贝构造也不会执行的。
但是当我真正去使用的时候会发现,编译器直接调用了构造函数,可以直接理解为将等号右边直接进行了传参。
比如改为下面代码

class Date
{
public:Date(int y){cout << y << endl;cout << this << endl;}Date(Date& d){d._month = 100;}void operator=(Date d){cout << "void operator=(Date d)" << endl;}Date* Print(){cout << "Date* Print()" << endl;cout << this << endl;return this;}
private:int _year;int _month;
};int main()
{Date d1(11);Date d2 = 12;cout << d2.Print() << endl;const Date& d3 = 13;int i = 10;double j = i;return 0;
}

执行结果就是
在这里插入图片描述

可以发现全部调用的都是构造函数

需要注意的地方有两点:

  1. 并不是只有刚创建的是才会进行隐式类型转换。如果创建过后在进行赋值,此时执行的就是赋值运算符重载,如果没有写就会进行浅拷贝,若此时成员变量有const限制的变量时,编译器就会报错。在VS2022中会报1>D:\Desktop\cpp\Project1\Project1\main.cpp(432,5): error C2280: “Date &Date::operator =(const Date &)”: 尝试引用已删除的函数
    这是因为如果没有定义自定义的复制构造函数或复制赋值运算符,编译器会默认生成这些函数。但当这些函数尝试访问 const 成员时,由于它们不能被改变,因此编译器会将复制赋值运算符标记为“已删除”。 此时就没有默认生成拷贝构造,如果我们写了赋值运算符的重载,并且不牵扯const限制的变量,就不会在报错。
    比如下面代码,如果把注释去掉就不会报错了
class Date
{
public:Date(int y){cout << "Date(int y)" << endl;}//void operator=(Date d)//{//}
private:int _year;int _month;const int _day = 1;
};int main()
{Date d1(1);d1 = 3;return 0;
}

但是如果没有const限制的成员变量,就不会出现上述报错,此时还是像刚创建一样,进行隐式类型转换,但是接着执行的就是赋值运算符重载,并且不会像刚赋值一样被优化不去执行,是真的执行了赋值运算符重载。

  1. 类型转换或者传值返回会产生临时变量,临时变量具有常性,所以引用不加const会报错
int main()
{Date d1(11);Date d2 = 12;const Date& d3 = 13;int i = 10;//这种赋值不产生临时变量const double j = i;return 0;
}

上述代码中d3是13临时对象的引用,13产生的临时对象具有常性,不能被改变,所以要加const,但是此时临时的,所以可能会产生野引用。但是const会延长临时对象的生命周期,临时对象在其引用的作用域结束后才会被销毁。 这里要注意,此外这句话不像Date d2 = 12; Date d2 = 12;会直接调用构造函数,传参然后创建d2对象。但是const Date& d3 = 13;是会产生临时变量的。这条语句不是创建对象,只要创建对象才会被优化。

i 赋值给 j ,i 会进行类型转换产生一个临时的具有常性的变量,然后赋值给 j ,此时 j 应该被const限制。不然是不正确的,但是有的编译器会优化,不会报错,VS2022就没有报错。
但是Date d2 = 12;按理来说也有一个具有临时对象,此时的拷贝构造的参数应该是const限制的,但是上面说了会优化为直接执行构造函数传参,所以这里也不存在这个错误了。

这种隐式类型转换使用的情况一般是下面这种:

void StackPush(Date d)
{//
}
StackPush(1);

这样写可以很方便的进行Push,而不需要创建一个对象再Push。

多参数的隐式类型转换

上面写的多参数的构造函数不能进行隐式类型转换,其实并不,只是要加限制,不能直接加个等号就进行赋值了。

class Date
{
public:Date(int y, int j){cout << y << endl;cout << j << endl;}Date(Date& d){d._month = 100;}void operator=(Date d){cout << "void operator=(Date d)" << endl;}
private:int _year;int _month;
};int main()
{Date d1(11, 111);Date d2 = { 12, 122 };const Date& d3 = { 13, 133 };int i = 10;double j = i;return 0;
}

要像上述代码,写一个大括号才能进行隐式类型转换,此时执行的流程和上面单参数的的执行过程是一样的。
如果类里含有类类型的成员变量,我们也可以使用上述方法给缺省值。

explicit关键字

explicit是C++中的一个关键字,它用来修饰只有一个参数的类构造函数,以表明该构造函数是显式的,而非隐式的。当使用explicit修饰构造函数时,它将禁止类对象之间的隐式转换,以及禁止隐式调用拷贝构造函数。
例如:

class Date
{
public:explicit Date(int y){cout << y << endl;cout << this << endl;}
private:int _year;int _month;
};

此时就不能进行隐式类型转换了,多参数的也一样。

static

通过一个场景引出static
怎样统计我们调用了几次构造函数?
可以定义一个全局变量,在构造和拷贝构造里面进行加 1 操作。
如下:

int n = 0;
class Date
{
public:Date(){++n;}Date(const Date& d){++n;}
private:
};Date Func()
{Date a;return a;
}
int main()
{Date d1;Date d2;Func();cout << n << endl;return 0;
}

定义一个全局的n,每次进入构造就执行一次++n操作。但是执行后输出结果却是3次,其实应该是4次,在Func()函数的return时是传值返回,会执行一次拷贝构造,但是编译器认为没有人使用,返回了也没用,就不执行拷贝构造了,其实试的时候发现就算有对象承接,也不会执行,应该是优化的原因。并且定义为全局的可能会在不经意间被修改。总之,这种方式不靠谱。

于是可以使用static关键字,封装成静态的。static修饰的仍然是全局的,封装在类中,可以在全局使用,而且不会出现上述的被优化或者被修改的情况。
写法如下:

class Date
{
public:Date(){++n;}Date(const Date& d){++n;}
//private:static int n;
};
int Date::n = 0;
Date Func()
{Date a;return a;
}
int main()
{Date d1;Date d2;Func();cout << Date::n << endl;return 0;
}

需要注意的是:
static修饰的变量不能给缺省值,因为它此时不是属于某一个对象,而是属于所有对象,属于整个类。 所以也不能在初始化列表初始化,初始化列表是初始化某一个对象的。但是static不是属于某一个对象的。而且要在类外面定义。类里面是声明。
可以理解为此时还是全局的一个静态变量,但是受到了类域和访问限定符的限制。
此时若要访问这个变量,就要加访问限定符,如下几种情况都是可以的:

int main()
{Date d1;Date d2;Date* ptr = nullptr;Func();cout << d1.n << endl;cout << d2.n << endl;cout << ptr->n << endl;cout << Date::n << endl;return 0;
}

此时访问的都是n,只要确定是哪个类就好,指定类确定或者通过对象确定都可以。像指向空的Date类型指针都可以访问的,因为n并不在ptr指向的地址,而是在静态区,这样访问知识为了让他突破类域,找到n在哪。
但是上述的访问也要在公有的情况下才可以,其实和全局的区别不大。所以没关系如果要是私有,就要用公有的函数才可以访问了。
如下:

class Date
{
public:Date(){++n;}Date(const Date& d){++n;}int Getn(){return n;}
private:static int n;
};int Date::n = 0;Date Func()
{Date a;return a;
}
int main()
{Date d1;Date d2;Date* ptr = nullptr;Func();cout << d1.Getn() << endl;return 0;
}

同样可以把函数封装为静态的,如下:

class Date
{
public:Date(){++n;}Date(const Date& d){++n;}static int Getn(){return n;}
private:static int n;
};
int Date::n = 0;
Date Func()
{Date a;return a;
}
int main()
{Date d1;Date d2;Date* ptr = nullptr;Func();cout << d1.Getn() << endl;cout << Date::Getn() << endl;return 0;
}

此时可以通过类进行访问这个函数,但是要注意,静态的成员函数没有this指针,此时要做的事情如果牵扯到this指针就无法执行。

其实由于VS2022编译器优化的过于厉害,用上述的static其实也无法正确统计调用的构造的次数,也可能是真的就调用了编译器返回的那么多次,那么多次可能对于编译器来说够了。编译器会有各种优化,并且会修改我们的代码,所以被优化导致不符合预期也是正常的。

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

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

相关文章

<数据集>无人机航拍不同高度牧羊识别数据集<目标检测>

数据集格式&#xff1a;VOCYOLO格式 图片数量&#xff1a;6065张 标注数量(xml文件个数)&#xff1a;6065 标注数量(txt文件个数)&#xff1a;6065 标注类别数&#xff1a;1 标注类别名称&#xff1a;[sheep] 序号类别名称图片数框数1sheep6065149785 使用标注工具&…

Macos M1 IDEA本地调试 HBase 2.2.2

# 1. 前提 执行 mvn clean package assembly:single -DskipTests没问题&#xff0c;并在hbase-assembly/target目录下生成hbase-2.2.2-bin.tar.gz 文件夹 证明Maven 下载依赖没问题 1.1 报错 1 这里应该是报错找不到 com.google.protobuf:protoc:exe:osx-aarch_64:3.5.1 …

入门STM32—外部中断

外部中断的存在使得微控制器能够及时响应外部事件&#xff0c;避免频繁的轮询操作&#xff0c;从而提高系统的实时性、效率和低功耗性能。 1.什么是外部中断&#xff1f; 外部中断是指微控制器接收到外部引脚的信号变化时触发的中断。STM32F103系列微控制器支持多个外部中断线…

鸿蒙(API 12 Beta3版)【DRM会话管理(C/C++)】数字版权保护开发

DRM会话管理&#xff08;MediaKeySession&#xff09;支持媒体密钥管理及媒体解密等&#xff0c;MediaKeySession实例由系统管理里的MediaKeySystem实例创建和销毁。 开发步骤 导入NDK接口&#xff0c;接口中提供了DRM相关的属性和方法&#xff0c;导入方法如下。 #include &…

学习嵌入式第二十九天

ipc进程间通信方式 PC&#xff0c;即进程间通信&#xff08;Inter-Process Communication&#xff09;&#xff0c;是操作系统中不同进程之间交换数据的一种机制。以下是一些常见的IPC方式&#xff1a; 管道&#xff1a;用于父子进程或兄弟进程之间的通信。消息队列&#xff…

selenium-java实现自动登录跳转页面

如果要一直刷新一个网页&#xff0c;总不能人工一直去点&#xff0c;所以想到大学时候学过selenium技术&#xff0c;写个脚本来一直刷新&#xff0c;因为经常写java语言&#xff0c;所以选用java语言来写 实验环境 注意&#xff0c;需要先准备好Google浏览器和Chrome-Driver驱…

ffmpeg6.1集成Plus-OpenGL-Patch滤镜

可参考上一篇文章。ffmpeg6.1集成ffmpeg-gl-transition滤镜-CSDN博客 安装思路大致相同&#xff0c; 因为 Plus-OpenGL-Patch也是基于 ffmpeg 4.x 进行开发的&#xff0c;所以在高版本上安装会有很多报错。 这是我安装后的示例&#xff0c;需要安装教程或者改代码可私信我。 …

记录一次 Redis 优化发送数据(使用管道批量传送)

一 项目背景 此前的项目中&#xff0c;鉴于客户方服务器的安全配置对 MQ 中间件有所限制&#xff0c;我们只得采用 Redis 的 list 作为简易的 MQ 来传送报文数据。然而&#xff0c;近段时间客户关闭了相关端口&#xff0c;导致大量数据积压&#xff0c;需要进行补发。在补发过程…

大数据背景下基于Python的牛油果销售数据可视化分析

注&#xff1a;源码在最后&#xff0c;只是一次实验记录&#xff0c;不合理的地方自行修改。 一 研究背景及意义 21世纪以来&#xff0c;随着科学技术的进步&#xff0c;人们的生活水平也随之大幅提升提高。在科技和经济快速发展下&#xff0c;全球已经进入了大数据时代。大数…

8.21-部署eleme项目

1.设置主从从mysql57服务器 &#xff08;1&#xff09;配置主数据库 [rootmsater_5 ~]# systemctl stop firewalld[rootmsater_5 ~]# setenforce 0[rootmsater_5 ~]# systemctl disable firewalldRemoved symlink /etc/systemd/system/multi-user.target.wants/firewalld.serv…

使用 Fyne 构建 GUI 应用:设置标签文本和自增计数器

引言 Fyne 是一个用 Go 语言编写的跨平台 GUI 框架&#xff0c;它提供了一套丰富的组件来帮助开发者快速构建出漂亮的用户界面。在本文中&#xff0c;我们将通过一个简单的案例来演示如何使用 Fyne 创建 GUI 应用程序&#xff0c;该程序包含设置标签文本和自增计数器的功能。 …

「字符串」前缀函数|KMP匹配:规范化next数组 / LeetCode 28(C++)

目录 概述 思路 核心概念&#xff1a;前缀函数 1.前缀函数 2.next数组 1.考研版本 2.竞赛版本 算法过程 构建next数组 匹配过程 复杂度 Code 概述 为什么大家总觉得KMP难&#xff1f;难的根本就不是这个算法本身。 在互联网上你可以见到八十种KMP算法的next数组…

项目1 物流仓库管理系统

一、项目概述 本项目旨在开发一个功能全面的物流仓库管理系统&#xff0c;以数字化手段优化仓库作业流程&#xff0c;提高管理效率。系统集成了前端用户交互界面与后端数据处理逻辑&#xff0c;涵盖了从用户注册登录、订单管理、货单跟踪到用户信息维护等多个核心业务模块。通…

基于django的学生作业提交与管理系统,有管理后台,可作为课设使用

在本项目中&#xff0c;我们设计并实现了一个基于Django框架的学生作业提交与管理系统&#xff0c;旨在为教师和学生提供一个高效、便捷的作业管理平台。Django作为一个高效的Web框架&#xff0c;因其强大的功能和灵活的架构&#xff0c;使得本系统能够快速开发并扩展。 系统功…

Maven的简单使用

Maven使用 Maven的作用1. 自动构建标准化的java项目结构(1) 项目结构① 约定目录结构的意义② 约定大于配置 (2)项目创建坐标坐标的命名方法&#xff08;约定&#xff09; 2. 帮助管理java中jar包的依赖(1) 配置使用依赖引入属性配置 (2) maven指令(3) 依赖的范围(4) 依赖传递(…

【密码学】密钥管理:②密钥分配

一、密钥分配的定义 密钥分配是密钥管理生命周期中最重要的部分&#xff0c;密钥分配方案研究的是密码系统中密钥的分发和传送问题。从本质上讲&#xff0c;密钥分配为通信双方建立用于信息加密、解密签名等操作的密钥&#xff0c;以实现保密通信或认证签名等。 &#xff08;1…

win10蓝牙只能发送,无法接收

给win10升了级&#xff0c;到22H2&#xff0c;蓝牙出了问题 以前接收&#xff0c;就是默认直接就可以接收。现在只能发送&#xff0c;无法接收。 在网上找了很多办法都没奏效&#xff0c;目前的方法是&#xff0c; 每次接收&#xff0c;都要操作一次&#xff0c;而不是自动接…

leetcode-538. 把二叉搜索树转换为累加树

题目描述 给出二叉 搜索 树的根节点&#xff0c;该树的节点值各不相同&#xff0c;请你将其转换为累加树&#xff08;Greater Sum Tree&#xff09;&#xff0c;使每个节点 node 的新值等于原树中大于或等于 node.val 的值之和。 提醒一下&#xff0c;二叉搜索树满足下列约束…

计量自动化终端上行通信规约

物理层 TCP 和 UDP 的传输接口 该类接口的登录链接和心跳检测采用链路测试服务&#xff0c;链路测试周期可设定。 参见 TCP/IP 协议规范。 串行通信传输接口 字节传输按异步方式进行&#xff0c;它包含 8 个数据位、1 个起始位“0”、1 个偶校验位 P 和 1 个停止位“1”。 …

Android Studio 动态表格显示效果

最终效果 一、先定义明细的样式 table_row.xml <?xml version"1.0" encoding"utf-8"?> <RelativeLayout xmlns:android"http://schemas.android.com/apk/res/android"android:layout_width"match_parent"android:layout_h…