【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…...

龙虎榜——20250610
上证指数放量收阴线,个股多数下跌,盘中受消息影响大幅波动。 深证指数放量收阴线形成顶分型,指数短线有调整的需求,大概需要一两天。 2025年6月10日龙虎榜行业方向分析 1. 金融科技 代表标的:御银股份、雄帝科技 驱动…...

多模态2025:技术路线“神仙打架”,视频生成冲上云霄
文|魏琳华 编|王一粟 一场大会,聚集了中国多模态大模型的“半壁江山”。 智源大会2025为期两天的论坛中,汇集了学界、创业公司和大厂等三方的热门选手,关于多模态的集中讨论达到了前所未有的热度。其中,…...
线程与协程
1. 线程与协程 1.1. “函数调用级别”的切换、上下文切换 1. 函数调用级别的切换 “函数调用级别的切换”是指:像函数调用/返回一样轻量地完成任务切换。 举例说明: 当你在程序中写一个函数调用: funcA() 然后 funcA 执行完后返回&…...

高频面试之3Zookeeper
高频面试之3Zookeeper 文章目录 高频面试之3Zookeeper3.1 常用命令3.2 选举机制3.3 Zookeeper符合法则中哪两个?3.4 Zookeeper脑裂3.5 Zookeeper用来干嘛了 3.1 常用命令 ls、get、create、delete、deleteall3.2 选举机制 半数机制(过半机制࿰…...

【OSG学习笔记】Day 16: 骨骼动画与蒙皮(osgAnimation)
骨骼动画基础 骨骼动画是 3D 计算机图形中常用的技术,它通过以下两个主要组件实现角色动画。 骨骼系统 (Skeleton):由层级结构的骨头组成,类似于人体骨骼蒙皮 (Mesh Skinning):将模型网格顶点绑定到骨骼上,使骨骼移动…...

dify打造数据可视化图表
一、概述 在日常工作和学习中,我们经常需要和数据打交道。无论是分析报告、项目展示,还是简单的数据洞察,一个清晰直观的图表,往往能胜过千言万语。 一款能让数据可视化变得超级简单的 MCP Server,由蚂蚁集团 AntV 团队…...

GC1808高性能24位立体声音频ADC芯片解析
1. 芯片概述 GC1808是一款24位立体声音频模数转换器(ADC),支持8kHz~96kHz采样率,集成Δ-Σ调制器、数字抗混叠滤波器和高通滤波器,适用于高保真音频采集场景。 2. 核心特性 高精度:24位分辨率,…...
稳定币的深度剖析与展望
一、引言 在当今数字化浪潮席卷全球的时代,加密货币作为一种新兴的金融现象,正以前所未有的速度改变着我们对传统货币和金融体系的认知。然而,加密货币市场的高度波动性却成为了其广泛应用和普及的一大障碍。在这样的背景下,稳定…...

浪潮交换机配置track检测实现高速公路收费网络主备切换NQA
浪潮交换机track配置 项目背景高速网络拓扑网络情况分析通信线路收费网络路由 收费汇聚交换机相应配置收费汇聚track配置 项目背景 在实施省内一条高速公路时遇到的需求,本次涉及的主要是收费汇聚交换机的配置,浪潮网络设备在高速项目很少,通…...

GruntJS-前端自动化任务运行器从入门到实战
Grunt 完全指南:从入门到实战 一、Grunt 是什么? Grunt是一个基于 Node.js 的前端自动化任务运行器,主要用于自动化执行项目开发中重复性高的任务,例如文件压缩、代码编译、语法检查、单元测试、文件合并等。通过配置简洁的任务…...