【c++】类和对象(六)深入了解隐式类型转换
🔥个人主页:Quitecoder
🔥专栏:c++笔记仓
朋友们大家好,本篇文章我们来到初始化列表,隐式类型转换以及explicit的内容
目录
- 1.初始化列表
- 1.1构造函数体赋值
- 1.2初始化列表
- 1.2.1隐式类型转换与复制初始化
- 1.3explicit关键字
1.初始化列表
1.1构造函数体赋值
在创建对象时,编译器通过调用构造函数,给对象中各个成员变量一个合适的初始值
class Date
{
public:
Date(int year, int month, int day){_year = year;_month = month;_day = day;}
private:
int _year;
int _month;
int _day;
};
虽然上述构造函数调用之后,对象中已经有了一个初始值,但是不能将其称为对对象中成员变量的初始化,构造函数体中的语句只能将其称为赋初值,而不能称作初始化。因为初始化只能初始化一次,而构造函数体内可以多次赋值
1.2初始化列表
class Date {
public:Date(int year,int month,int day):_year(year),_month(month),_day(day){}
private:int _year;int _month;int _day;
};
初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个成员变量后面跟一个放在括号中的初始值或表达式
那么,为什么要使用初始化列表呢?它的优势在哪里呢?
我们来看构造函数对于下面类的初始化:
class Date2 {
public:Date2(int year, int month, int day) {_n=10;_year = year;_month = month;_day = day;}
private:int _year;int _month;int _day;const int _n;
};
我们发现const成员变量并不能用函数体进行初始化
int _year;
int _month;
int _day;
这三个成员既可以在函数体,又可以在初始化列表,但是类中包含以下成员,必须放在初始化列表位置进行初始化:
- 引用成员变量
- const成员变量
- 自定义类型成员(且该类没有默认构造函数时)
int _year;int _month;int _day;const int _n;
我们知道,这个只是一个声明,定义是对象实例化时候完成的,有些成员,必须在定义的时候进行初始化
初始化列表中的每个元素都直接对应一个成员变量或基类,允许在构造函数体执行之前对这些成员或基类进行初始化。这对于const成员变量、引用类型成员变量以及某些没有默认构造函数的类型尤其重要
Date2(int year, int month, int day) :_n(1)
{_year = year;_month = month;_day = day;
}
初始化列表是每个成员变量定义初始化的位置
class Date2 {
public:Date2(int year, int month, int day) :_n(1){_year = year;_month = month;_day = day;}
private:int _year;int _month;int _day;const int _n;
};
没有在初始化列表中显式初始化
_year
、_month
、和_day
这三个成员变量,它们仍然会在初始化列表阶段被默认初始化,然后在构造函数体内被赋新的值
对于基本类型(如
int
),如果它们未在类的初始化列表中显式初始化,则它们会进行默认初始化。对于类内的基本类型成员变量,默认初始化意味着不进行初始化(保留未定义值),除非它们是静态存储持续时间的对象(例如全局或静态变量,它们会被初始化为零)。然而,对于自动存储持续时间(如函数内的局部变量)的对象,如果未显式初始化,则其值是未定义的。在类构造函数中,成员变量的行为类似于局部变量,如果不在初始化列表中显式初始化,它们将不会被自动初始化
_n
是通过初始化列表初始化的,因为它是const
类型的,必须在那里初始化。而_year
、_month
、和_day
虽然没有在初始化列表中被显式赋值,但它们会在构造函数体开始执行前完成默认初始化(对于基本数据类型,这意味着它们的初始值是未定义的)。然后,在构造函数体内,它们被赋予新的值
因此,可以说成员变量_year
、_month
、和_day
先经历了默认初始化(在这个场景下,这意味着它们的值是未定义的),然后在构造函数体内被赋值
我们不妨提到前面讲的声明时给缺省值:
private:int _year=1;int _month;int _day;const int _n;
缺省值的本质就是给初始化列表用的
Date2(int year, int month, int day) : _n(1), _year(year), _month(month), _day(day)
{// 构造函数体可以留空,因为所有成员变量都已经在初始化列表中初始化
}
在这个版本中,所有成员变量都是通过初始化列表直接初始化的,这是推荐的做法,特别是对于复杂类型或类类型的成员变量
引用类型必须在定义的时候初始化,所以也得使用初始化列表
class A
{
public:A(int a=0):_a(a){}
private:int _a;
};
class Date2 {
public:Date2(int year, int month, int day,int x) :_n(1),_year(year),_month(month),_day(day),_ref(x){}
private:int _year=1;int _month;int _day;const int _n;int& _ref;A aa;
};
这里aa也会走初始化列表,来调用它的默认构造函数
我们可以在初始化列表来直接控制自定义类型的初始化
Date2(int year, int month, int day,int x) :_n(1),_year(year),_month(month),_day(day),_ref(x),aa(1)
{
}
初始化列表和构造函数共同定义了类对象的初始化行为。初始化列表提供了一种高效、直接初始化成员变量和基类的方式,而构造函数则完成剩余的初始化逻辑和设置,比如动态开辟一个数组进行赋值的时候,就用到函数体
成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关
我们来看下面的代码:
class A
{
public:A(int a):_a1(a),_a2(_a1){}void Print() {cout<<_a1<<" "<<_a2<<endl;}
private:int _a2;int _a1;
};
int main() {A aa(1);aa.Print();
}
这个结果是什么呢?
结果是1和一个随机值
在这个例子中,A
类有两个整型成员变量:_a1
和_a2
。在构造函数中,_a1
被初始化为传入的参数a
的值,而_a2
被初始化为_a1
的值。
然而,成员变量的初始化顺序是由它们在类中声明的顺序决定的,而不是它们在初始化列表中出现的顺序。在A
类中,_a2
在_a1
之前声明,因此_a2
会先于_a1
初始化。
这意味着当_a2(_a1)
执行时,_a1
还没有被初始化,所以_a2
的值是未定义的。然后,_a1
被初始化为1
因此,当调用aa.Print();
时,输出的第一个值(_a2
的值)是未定义的,而第二个值(_a1
的值)是1
。在实际执行时,未定义的值可能是内存中该位置的任何值,这取决于编译器和运行时环境。
要修正这个问题,应该按照成员变量在类中声明的顺序初始化它们,或者更改成员变量的声明顺序以反映期望的初始化顺序。例如:
class A {
public:A(int a):_a1(a) // 现在_a1首先初始化,_a2(_a1) // 然后是_a2{}void Print() {cout << _a1 << " " << _a2 << endl;}
private:int _a1; // 声明顺序改为先_a1int _a2; // 然后是_a2
};
在这个修改后的版本中,_a1
会先被初始化为1
,然后_a2
会被初始化为_a1
的值,即1
。所以Print
函数会输出1 1
。
1.2.1隐式类型转换与复制初始化
我们再来看下面的类:
class C
{
public:C(int x):_x(x){}
private:int _x;
};
int main()
{C cc1(1);C cc2 = 2;return 0;
}
C cc2 = 2;
为什么cc2能直接赋值呢?
在C++中,如果一个类的构造函数只需要一个参数(或所有参数除了第一个外都有默认值),那么这个构造函数允许从构造函数参数类型到类类型的隐式转换。这种转换使得单个值可以被视为是该类的一个实例,即使没有显式地调用构造函数
C cc1(1);
- 这行代码直接调用了
C
类的构造函数,使用1
作为参数创建了cc1
对象。
C cc2 = 2;
- 这行代码演示了隐式类型转换。虽然看起来像是将整数
2
赋值给cc2
,实际上C++编译器解释为使用2
作为参数调用C
类的构造函数来初始化cc2
。这是因为C(int x)
构造函数允许从int
到C
的隐式转换。
复制初始化是C++中一种对象初始化的方式,它与直接初始化有所不同,但在某些情况下可以产生类似的效果。理解复制初始化对于深入理解C++的对象构造和赋值语义非常重要。接下来,我们将通过详细说明来解释复制初始化的概念,以及为什么在某些情况下可以通过直接赋值的方式来初始化对象
复制初始化的基本概念
复制初始化通常发生在使用=
操作符进行对象初始化的场景中。不同于直接初始化(直接调用构造函数),复制初始化涉及到源对象到目标对象的潜在类型转换和赋值操作
C obj = value;
在上述代码中,value
可以是与C
类型兼容的任何值或对象。复制初始化的过程如下:
-
类型转换(如果必要):如果
value
不是C
类型的对象,则编译器会尝试使用value
调用C
的构造函数(或explicit
关键字修饰的构造函数除外),以创建一个临时的C
类型对象。这一步是隐式类型转换的一部分。 -
调用拷贝构造函数:编译器接下来会使用这个临时对象(如果第一步创建了临时对象的话)作为参数调用
C
的拷贝(或移动)构造函数,来初始化obj
。如果源对象就是C
类型,并且没有发生类型转换,那么这一步将直接用源对象来初始化obj
。 -
优化:在很多情况下,编译器可以应用(拷贝消除)优化来避免真正创建临时对象和执行拷贝(或移动)操作,直接在
obj
的存储位置构造对象
为什么可以直接赋值?
class C
{
public:C(int x):_x(x){}
private:int _x;
};
C cc2 = 2;
这里的2
是一个整型字面量,不是C
类型的对象。复制初始化的过程大致如下:
- 类型转换:编译器使用
2
调用C
的构造函数创建一个临时的C
类型对象。 - 拷贝构造函数:这个临时对象然后用于初始化
cc2
。但实际上,由于优化,这一步可能被省略,2
直接用于在cc2
的位置构造C
对象。
我们不妨来看看它是否调用了拷贝构造:
class C
{
public:C(int x):_x(x){}C(const C& cc){cout << "use copy" << endl;}
private:int _x;
};
这里就被编译器优化了,同一个表达式连续步骤的构造,一般会被合并为一个
因此,尽管代码看起来像是将
2
直接赋值给C
类型的对象cc2
,实际上则是通过编译器优化,直接在cc2
的存储位置用2
构造了一个C
对象。
来看下面的代码:
class C
{
public:C(int x):_x(x){}private:int _x;
};int main()
{C& cc3 = 2;return 0;
}
C& cc3 = 2;
试图将一个整型字面量2
赋给C
类型的引用cc3
。这行代码会导致编译错误,原因如下:
-
引用的基本要求:在C++中,引用必须绑定到一个已经存在的对象上。引用本质上是对象的别名,它不能像指针那样独立存在
-
引用与临时对象:尽管临时对象(如通过类型转换创建的临时
C
对象)可以被绑定到const
引用上(即const C&
),但它们不能直接绑定到非const
引用(C&
)上。这是为了防止通过非const
引用对临时对象进行修改,因为这种修改通常没有意义(临时对象在表达式结束后就销毁了)。 -
正确的用法:如果你的意图是创建一个
C
类型的临时对象,并将其绑定到引用上,正确的语法应该使用const
引用,如下:const C& cc3 = C(2); // 或者 const C& cc3 = 2; // 依赖于C(int)构造函数的隐式类型转换
这两种方式都是可行的,它们创建了一个
C
类型的临时对象,并将其绑定到const
引用cc3
上。由于引用是const
的,你不能通过cc3
修改对象的状态。
要解决原代码中的问题,需要确保使用const
引用来引用临时对象,或者创建一个非临时的C
对象并将其赋给一个非const
引用。例如:
C cc4(2);
C& cc3 = cc4; // cc3引用cc4
在这个修正后的示例中,cc4
是一个非临时的C
对象,cc3
是一个类型为C&
的引用,它直接引用(或绑定到)cc4
上
这个真正好处我们在后面会用到:
class Stack
{
public:void Push(const C& c){//}
};
比如我们想要在栈这个容器中压入c类型的对象有两种方式:
Stack st;
C cc3(3);
st.Push(cc3);st.Push(4);
直接用隐式类型转换就方便了很多
1.3explicit关键字
如果不想让隐式类型转换发生,我们就需要用 explicit
修饰构造函数,禁止类型转换
单参构造函数,没有使用explicit修饰,具有类型转换作用
C++11及以后版本版本支持多个参数隐式类型转换
class A
{
public://explicit A(int a1, int a2)A(int a1, int a2):_a1(a1),_a2(a2){}private:int _a1;int _a2;
};
int main()
{A aa={1,2};return 0;
}
不想让隐式类型转换发生,可以加上explicit关键字
相关文章:
【c++】类和对象(六)深入了解隐式类型转换
🔥个人主页:Quitecoder 🔥专栏:c笔记仓 朋友们大家好,本篇文章我们来到初始化列表,隐式类型转换以及explicit的内容 目录 1.初始化列表1.1构造函数体赋值1.2初始化列表1.2.1隐式类型转换与复制初始化 1.3e…...
什么是nginx正向代理和反向代理?
什么是代理? 代理(Proxy), 简单理解就是自己做不了的事情或实现不了的功能,委托别人去做。 什么是正向代理? 在nginx中,正向代理指委托者是客户端,即被代理的对象是客户端 在这幅图中,由于左边内网中…...
【Go】面向萌新的Gin框架知识梳理学习笔记
目录 Gin框架简介 路由&路由组 1. 定义基本路由 2. 参数传递 3. 查询字符串参数 4. 路由组 5. 路由中间件 模板渲染 1. 加载模板 2. 定义模板 3. 渲染模板 4. 自定义模板函数 返回json 1. 导入 Gin 包 2. 创建 Gin 引擎 3. 定义路由和处理器函数 4. 运行服…...
baseDao增删改查.
这里写目录标题 1、baseDao增删改查介绍2、basDao类3、BasDao类的作用 1、baseDao增删改查介绍 (1)、增加Create)操作: 通过BaseDao的insert方法可以向数据库中插入一条新的记录。 该方法接受一个实体对象作参数,将该对象的属性映射到表的字…...
什么是面向对象【大白话Java面试题】
什么是面向对象 同样是解决一个问题,面向对象的角度是将问题抽象成对象的形式。通过分类的思维方式,将问题分成几个解决方案的对象。给每个对象赋值属性和方法,对每个对象的细节进行面向过程的思维,执行自己的方法来解决问题。 …...
PyTorch 教程-快速上手指南
文章目录 PyTorch Quickstart1.处理数据2.创建模型3.优化模型参数4.保存模型5.加载模型 PyTorch 基础入门1.Tensors1.1初始化张量1.2张量的属性1.3张量运算1.3.1张量的索引和切片1.3.2张量的连接1.3.3算术运算1.3.4单元素张量转变为Python数值 1.4Tensor与NumPy的桥接1.4.1Tens…...
【有芯职说】数字芯片BES工程师
一、 数字芯片BES工程师简介 今天来聊聊数字芯片BES工程师,其中BES是Back End Support的缩写,就是后端支持的意思。其实这个岗位是数字IC前端设计和数字IC后端设计之间的一座桥,完成从寄存器传输级设计到具体工艺的mapping和实现。这个岗位在…...
暴力破解pdf文档密码
首先安装pdfcrack工具包 apt install pdfcrack 默认密码字典存储在/usr/share/wordlists里,是gz文件,将它解压并copy到pdf目录 然后使用pdfcrack破解 密码在最后一行user-password的单引号里...
蓝桥杯刷题第四天
思路: 这道题很容易即可发现就是简单的暴力即可完成题目,我们只需满足所有数的和为偶数即可保证有满足条件的分法,同时也不需要存下每个输入的数据,只需要知道他是偶数还是奇数即可,因为我们只需要偶数个奇数搭配在一块…...
03-数据库的用户管理
一、创建新用户 mysql> create user xjzw10.0.0.% identified by 1; Query OK, 0 rows affected (0.01 sec) 二、查看当前数据库正在登录的用户 mysql> select user(); ---------------- | user() | ---------------- | rootlocalhost | ---------------- 1 row …...
每日一题 --- 三数之和[力扣][Go]
三数之和 题目:15. 三数之和 给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i ! j、i ! k 且 j ! k ,同时还满足 nums[i] nums[j] nums[k] 0 。请 你返回所有和为 0 且不重复的三元组。 **注意&#x…...
vue render 函数详解 (配参数详解)
vue render 函数详解 (配参数详解) 在 Vue 3 中,render 函数被用来代替 Vue 2 中的模板语法。 它接收一个 h 函数(或者是 createElement 函数的别名),并且返回一个虚拟 DOM。 render 函数的语法结构如下: render(h) …...
ubuntu23.10配置RUST开发环境
系统版本: gcc版本 下载rustup安装脚本: curl --proto =https --tlsv1.2 https://sh.rustup.rs -sSf | sh下载完成后会自动执行 选择默认安装选项 添加cargo安装目录到环境变量 vim ~/.bashrc<...
Vue性能优化--gZip
一、gZip简单介绍 1.1 什么是gzip gzip是GNUzip的缩写,最早用于UNIX系统的文件压缩。HTTP协议上的gzip编码是一种用来改进web应用程序性能的技术,web服务器和客户端(浏览器)必须共同支持gzip。目前主流的浏览器,Chro…...
蓝桥杯第七届大学B组详解
目录 1.煤球数量; 2.生日蜡烛; 3.凑算式 4.方格填数 5.四平方和 6.交换瓶子 7.最大比例 1.煤球数量 题目解析:可以根据题目的意思,找到规律。 1 *- 1个 2 *** 3个 3 ****** 6个 4 ********** 10个 不难发现 第…...
荣誉 | 人大金仓连续三年入选“金融信创优秀解决方案”
3月28日,由中国人民银行领导,中国金融电子化集团有限公司牵头组建的金融信创生态实验室发布“第三期金融信创优秀解决方案”,人大金仓新一代手机银行系统解决方案成功入选,这也是人大金仓金融行业解决方案连续第三年获得用户认可。…...
【关于jupyter notebook】一打开就闪退的问题
在Anaconda Prompt中输入jupyter notebook发现是有个错误。 里面多了一个__init__.py的文件导致报错。删除之后,就可以使用了...
若依 3.8.7版本springboot前后端分离 整合mabatis plus
1.去掉mybatis 这一步我没有操作,看别人的博客有说不去掉可能冲突,也可能不冲突,我试下来就没去掉如需要去除,到总的pom.xml中properties标签下的<mybatis-spring-boot.version>x.x.x</mybatis-spring-boot.version>…...
vue做移动端自适应插件实现rem
1.实现方式 postcss-pxtorem:将px转换为rem amfe-flexible:为html、body提那家font-size,窗口调整的时候重新设置font-size 2.安装与使用 npm install amfe-flexible --save npm install postcss-pxtorem --save-dev 1.再main.js入口文件…...
android 快速实现 图片获取并裁剪(更换头像)
1.获取图片框架:https://github.com/LuckSiege/PictureSelector 2.图片裁剪框架:https://github.com/jdamcd/android-crop 3.Glide图片加载框架:https://github.com/bumptech/glide 2.build.gradle依赖: dependencies {// Pic…...
垃圾回收机制--GC 垃圾收集器--JVM调优-面试题
1.触发垃圾回收的条件 新生代 Eden区域满了,触发young gc (ygc)老年代区域满了,触发full gc (fgc)通过ygc后进入老年代的平均大小大于老年代的可用内存,触发full gc(fgc).程序中主动调用的System.gc()强制执行gc,是full gc,但是不必然执行。…...
Java基础知识总结(29)
Java虚拟机 运行时数据区 程序计数器 方法区:Java 8以后没有方法区,改为了元空间(MetaSpace) 虚拟机栈 堆 本地方法栈 程序计数器 它可以看作是当前线程所执行的字节码的行号指示器。在Java虚拟机的概念模型里,…...
vue js金额转中文
在Vue.js项目中,实现金额转中文的功能通常涉及编写一个JavaScript方法来处理数字转换逻辑,并在Vue组件中调用该方法。下面是一个基本的示例,展示如何在Vue组件中定义一个计算属性或方法来实现这一功能: /*** 思路: …...
《QT实用小工具·二》图片文字转base64编码
1、概述 源码放在文章末尾 base64编码转换类 图片转base64字符串。base64字符串转图片。字符转base64字符串。base64字符串转字符。后期增加数据压缩。Qt6对base64编码转换进行了重写效率提升至少200%。 下面是demo演示: 项目部分代码如下所示: #ifn…...
Django安装及第一个项目
1、安装python C:\Users\leell>py --version Python 3.10.6 可以看出我的环境python的版本3.10.6,比较新 2、 Python 虚拟环境创建 2.1 官网教程 目前,有两种常用工具可用于创建 Python 虚拟环境: venv 在 Python 3.3 及更高版本中默…...
专升本-物联网
物联网(IOT,Internet of things) 体系结构: 感知层(感知执行层) 网络层 应用层 基本特征: 全面感知 可靠传输 智能处理 作用: 信息采集、转换、收集 信息传递和处理 数据…...
二叉树的遍历C语言
二叉树作为FDS课程最核心的数据结构之一,要求每个人都掌握! 这是一道简单的二叉树问题! 我们将给出一颗二叉树,请你输出它的三种遍历,分别是先序遍历,中序遍历,后序遍历! 输入格式…...
PostgreSQL到Doris的迁移技巧:实时数据同步新选择!
PostgreSQL可以说是目前比较抢手的关系型数据库了,除了兼具多样功能和强大性能之外,还具备非常优秀的可扩展性,更重要的是它还开源,能火不是没有理由的。 虽然PostgreSQL很强大,但是它也有短板,相对于专业…...
【三维】关于万向节锁的直白解释
1. 分析理解 万向节长什么样子,请参考这篇文章中的图片:https://zhuanlan.zhihu.com/p/42519819。 看了很多篇解释性的文章,没怎么看懂。因为我个人最关注的问题点在于: 现实物体旋转为什么没有所谓的万向节锁的bug,…...
程序员的修养 - 变量
变量几乎代码程序程序 中最基础的组成单元,程序员几乎无时无刻都在接触变量。但你对变量的理解真的足够吗? 首先,什么是变量?! 第一层理解:一个可以改变的量,区别于常量,用户可以修…...
自己服务器建设网站/百度查询关键词排名工具
大家在利用镶嵌数据集管理影像的过程中,遭受过影像黑边的困扰吗?所谓的黑边就是在影像接边处有黑色的区域,严重影响视觉效果。 图 镶嵌数据集黑边示意 我们先来分析一下黑边产生原因:在使用镶嵌数据集管理影像数据的过程中&#…...
成都行业网站/怎么创建个人网站
**作者:张华 发表于:2016-12-07 版权声明:可以任意转载,转载时请务必以超链接形式标明文章原始出处和作者信息及本版权声明 ( http://blog.csdn.net/quqi99 )** python3中的默认编码unicode(UCS-2编码)采用2个字节表…...
营销型网站建设软件/免费网站推广群发软件
工作回顾前几天,我在公园里进行了一次冲刺回顾。 阳光普照,我们所有人都在努力完成积压工作,因此,这对每个人的努力都是一种不错的回报。 在户外举行回顾展也可以使它充满活力和热情,而这在小房间里很难找到。 我以前…...
懂的建设网站/企业网站排名优化方案
实验报告课程Java语言程序设计实验名称第八章 Swing图形用户界面程序设计实验任务(三)第页专业班级学号__ __ 姓名实验日期:2010 年11 月9 日报告退发(订正、重做)一、实验目的?布局管理器的使用二、实验环境1、微型计算机一台2、DOS或WINDOWS操作系统,…...
mip 网站/关键词整站优化
本文转自:https://www.imooc.com/article/17290 上节回顾 上篇记录了我对MySQL 事务 隔离级别read uncommitted的理解。 这篇记录我对 MySQL 事务隔离级别 read committed & MVCC 的理解。 前言 可以很负责人的跟大家说,MySQL 中的此隔离级别不单单…...
网站建设与管理可以专升本吗/chrome网页版入口
谈谈SQL Server高可用的常见问题每次谈到SQL Server的高可用,很多的DBA,特别是SQL Server DBA心里一痛:因为大家都认为SQL Server无法或者很难实现SQL Server。也有很多的DBA朋友脑袋一拍,给出答案“高可用不就是微软的那几个技术…...