C嘎嘎~~[构造函数提升篇]

news2024/11/25 14:36:01

构造函数提升篇

  • 1. 再谈构造函数
    • 1.1. 引入
      • 1.1.1问题引入
      • 1.1.2 const引入
    • 1.2 正篇
      • 1.2.1 构造函数体赋值
      • 1.2.2 初始化列表
        • 1.2.3.1 浅浅认识
        • 1.2.3.2 构造函数的 `行走顺序`
        • 1.2.3.3 引用修饰成员变量
        • 1.2.3.4 没有默认构造的自定义类型
      • 1.2.3初始化列表的 `'坑'`
      • 1.2.4 谈谈初始化列表 和 构造函数
      • 1.3 explicit关键字
        • 1.3.1 引入
        • 1.3.2 正篇
          • 1.3.2.1 赋值的含义
          • 1.3.2.2 探寻 `'隐式类型转换'` 的真相
          • 1.3.2.3 explicit关键字

1. 再谈构造函数

1.1. 引入

1.1.1问题引入

class Date
{
public:
private:
	int _year;
	int _month;
	int _day;
};

通过前面所学的知识, 我们知道了_year, _month, _day这三个变量都是一些声明, 并没有开辟空间, 不是定义.

Date d1;

这一个操作就是给 d1这个对象整体定义, 但是对象整体定义,并不代表着里面的三个成员变量定义了.

🗨️那么问题来了: 成员变量是在什么时候定义的??

1.1.2 const引入

class Date
{
public:

private:
	int _year;
	const int _month;
	int _day;
};

int main()
{
	Date d1;
	
	return 0;
}

当我们用const 来修饰一下其中的一个成员变量, 那么会怎么样了?

🗨️这样的报错是什么意思? — 具有未初始化的常量限定数据成员

  • 其实我们可以发现一些端倪: 就是const 修饰的变量有一个特点就是 定义的时候一定要初始化, 那么有些老铁就会说 在里面给 const修饰的变量一个缺省值试一试

    🗨️这些不是声明嘛, 给一个缺省值为什么就可以了? 原理是什么啊? 还有就是传一个缺省值是传给谁啊?
  • 其实, 初始化列表可以看成成员变量定义的地方. 而我们给的缺省值也是给初始化列表用的. 缺省, 缺省, 说实话就是一个备胎, 如果我们在初始化列表中给了这个变量的值⇒ 那么就不会用这个缺省值了, 而去用我们在初始化列表中的值.

1.2 正篇

在构造函数中,初始化成员变量有两种方式: 构造函数体赋值初始化列表
其实, 这两种方式是有所不同的, 通过下面的一些比较就能看出他们的不同

1.2.1 构造函数体赋值

class Date
{
public:
	Date(int year, int month, int day)
	{
		_year = year;
		_year = 2022;

		_month = month;
		_month = 5;

		_day = day;
		_day = 20;
	}

private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1(2023, 5, 16);

	return 0;
}

🗨️延续上面的问题, 这个是不是初始化?

  • 其实答案很明显, 这个是并不是初始化. 因为每一个变量只有一次初始化, 但是可以有多次赋值. 通过上面的代码, 我们可以发现: 在构造函数内部可以进行多次的赋值, 而初始化只有一次, => 构造函数不是成员变量初始化的地方

1.2.2 初始化列表

1.2.3.1 浅浅认识

初始化列表: 以一个冒号开始, 接着用一个逗号分隔数据成员列表, 每一个 成员变量后面跟一个放在括号中的初始值或者表达式

class Date
{
public:
    // 构造函数
	Date()
	     // 此部分是 初始化列表
		:_year(2023)
		// 此部分是 构造函数体赋值
	{
		_month = 5;
		_day = 16;
	}

private:
	const int _year; // const 修饰的成员变量
	int _month;
	int _day;
};

int main()
{
	Date d1;

	return 0;
}
  • 注意:
    1. 每个成员变量在初始化列表中 最多出现一次(因为只能初始化一次)
    2. 类中包含以下的成员时, 必须放在初始化列表中进行初始化:
    • const 修饰的成员变量
    • 引用修饰的成员变量
    • 没有默认构造函数的自定义类型

1.2.3.2 构造函数的 行走顺序

构造函数初始化成员变量有两种形式: 构造函数体赋值初始化列表
一个是 赋值, 一个是 初始化 ⇒ 由此不难看出, 编译器先走的是 初始化列表然后才是构造函数体赋值

🗨️如果没有初始化列表, 编译器会走初始化列表这一步吗?

  • 猛一看, 感觉这个问题是不是有问题; 仔细一想, 其实这个问题问的很有深度~~. 换一句话说, 其实这个问题是想问 ⇒ 初始化列表是构造函数必走的一步吗?
    通过下面的代码, 验证一番:
class Date
{
public:
	Date()
	{
		_month = 5;
		_day = 16;
	}

	void Print()
	{
		cout << _year << " " << _month << " " << _day << endl;
	}

private:
	const int _year = 5;
	int _month;
	int _day;
};

int main()
{
	Date d1;
	d1.Print(); // 5 5 16

	return 0;
}
class Date
{
public:
	Date()
		:_year(2023)
	{
		_month = 5;
		_day = 16;
	}

	void Print()
	{
		cout << _year << " " << _month << " " << _day << endl;
	}

private:
	const int _year = 5;
	int _month;
	int _day;
};

int main()
{
	Date d1;
	d1.Print(); //2023 5 16

	return 0;
}

通过上面的两个例子:

  1. 补丁 — — 成员变量给个缺省值, 这个缺省值其实传给初始化列表的. 如果初始化列表没有对此变量进行操作, 那么就会使用那个缺省值
  2. 初始化列表是构造函数必走的一步 — — 因为初始化列表是成员变量定义的地方在这里插入图片描述

1.2.3.3 引用修饰成员变量

class A
{
public:
	A(int a, int& b)
	{
		_a1 = a;
		_a2 = b;
	}
private:
	int _a1;
	int& _a2;
};

int main()
{
	int n = 10;
	A a(10, n);

	return 0;
}

*****
error C2530: “A::_a2”: 必须初始化引用
*****

🗨️有一些老铁, 就会有一些疑问: 为什么这里的传引用不能传递一个常量⇒ 就比如 A a(10, 10);

  • 首先这样写会有报错的:

    在这里, 我们只需要看一下红色的报错(蓝色的报错是这一类 的错误 — — 不能用构造函数体赋值来初始化 用引用修饰的成员变量):
    因为 _a2 是 b的引用, b 是 10的引用 ⇒ _a2 是 10的引用, 由于 10是一个常量, 所以引用都要用常量引用来接收, 否则就是引用的权限放大造成错误!!!
    如果我们这样修改的话, 就必须把成员变量里面也用 const来修饰此变量才可以. 不过这样多不方便⇒ 传一个常量, 一直用, 还不能改变⇒ 这不符合我们的需求啊~

通过上面的例子, 我们发现: 使用构造函数体赋值这种方式是不行的!!

那么, 我们就采用初始化列表:

  • _a2 = 10 — — _a2 是 b的引用, b 是 n的引用 ⇒ _a2 是 n的引用. 因为初始化列表是成员变量定义的地方, 所以可以在此处进行对 引用修饰的_a2 进行初始化
  • _a1 是一个随机值 — — 发现传参的 10没用⇒ 想告诉各位读者的是, 我们传参是有自己的目的性 和 选择性; 如果我们不用, 对于内置类型成员变量(当然也没有缺省值)就会被初始化为随机值.

1.2.3.4 没有默认构造的自定义类型

class B
{
public:
	// 无参调用 ==> 默认构造
	B()
	{

	}
private:
	int _b;
	int _tem;
};

class A
{
public:

private:
	int _a;
	B bb;
};

int main()
{
	A a1;

	return 0;
}

上面的代码是正确的, 因为A 类中有一个自定义类型的成语变量 bb. 构造函数是完成成员变量的初始化的, 对于 A类来说, 要完成 对 _a 和 bb( _b _tem) 的初始化. 而要完成对 bb的初始化, 就需要 B类的默认构造. 如果 B类存在默认构造, 那么就会对 bb 进行初始化, 如果 B类不存在默认构造, 那么就不会对 bb进行初始化.👇👇👇

class B
{
public:
	// 有参 ==> 就不存在默认构造
	B(int x, int y)
	{
		_b = x;
		_tem = y;
	}
private:
	int _b;
	int _tem;
};

class A
{
public:

private:
	int _a;
	B bb;
};

int main()
{
	A a1;

	return 0;
}

上面, 我们故意对 B类的构造函数写成了有参调用⇒ 那么 B类就会失去默认构造=> B(_b _ tem) 就会是随机值, 就不能完成对 B(_b _ tem) 的初始化了=> A类中就不能完成初始化

  • 那我们试一试初始化列表👇👇👇


  • 自定义类型成员变量的初始化总结:

1.2.3初始化列表的 '坑'

成员变量在类中声明次序就是在初始化列表中的初始化顺序, 与其在初始化列表中的先后次序无关

class A
{
public:
	A(int a)
		:_a1(a)
		,_a2(_a1)
	{}

private:
	int _a2;
	int _a1;
};

int main()
{
	A a(1);

}

🗨️上面代码的运行结果是什么?

  • _a2 是随机值, _a1 = 1
    因为声明中的顺序是 _a2 _a1, 那么编译器在初始化列表初始化的时候就会 先初始化 _a2, 再初始化_a1
    初始化 _a2 的时候: _a2( _a1), 这时候_a1还没有被初始化, 所以是一个随机值; 初始化 _a1的时候: _a1(1), 那么 _a1就是 1喽

建议— — 尽量按照声明的顺序来安排初始化列表中变量的初始化顺序

初始化列表中, 每一个成员变量最多只能出现一次

class A
{
public:
	A()
		:_a1(5)
		,_a2(10)
		,_a1(8)
	{}

private:
	int _a2;
	int _a1;
};

int main()
{
	A a;

	return 0;
}

  • 初始化列表是每个成员变量定义的地方, 也是初始化的地方

1.2.4 谈谈初始化列表 和 构造函数

其实, 每一个初学者在这个地方都会停留一段时间.
初始化列表 和 构造函数, 一不留神就会混淆了概念和作用

总的来说, 初始化列表是构造函数的一部分.构造函数的功能就是完成对成员变量的初始化工作, 对自定义类型会调用它的默认构造, 而对于内置类型, 就不会进行处理.
完成这一个初始化工作, 有两个方式: 初始化列表构造函数体赋值. 见名知义: 一个是初始化, 一个是赋值.
如果成员变量中 没有 const修饰, 引用修饰 或者 没有默认构造的自定义类型, 写不写初始化列表都是ok的, 只要有其中的一个存在, 就要写一下初始化列表. 其实const修饰的成员变量也可以不用写初始化列表(上面有例子, 不清楚的上去看看)
比较来说, 初始化列表可以完成的工作 > 构造函数体赋值 ⇒ 我们一般建议使用初始化列表. 当然有些工作时需要构造函数体赋值, 也是要写构造函数体赋值的.


1.3 explicit关键字

1.3.1 引入

前面C语言的学习, 我们知道存在一种 类型转换. 由于C++能够兼容C语言, 我们就大胆实验👇👇👇

class A
{
public:
	A(int a)
		:_a(a)
	{}

private:
	int _a;

};

int main()
{
	A a = 23;

	return 0;
}

上面的代码是可行的.
🗨️为什么可以直接从一个 int类型直接转换成 A类的对象a呢?? 中间出现了什么过程呢??

  • 构造函数不仅可以构造与初始化对象, 对于单个参数或者第一个参数无默认值其余有默认值的构造函数, 还具有类型转换的作用

1.3.2 正篇

1.3.2.1 赋值的含义

🗨️先有一个问题: 在C语言的学习中, 以int a = 10; double b = a; 为例子, 讲述一下 类型转换的过程

  • 类型转换不是直接就 把 a 的值 赋值给 b. 会生成一个 类型为 double的临时变量, 记作 tem , 然后把tem 赋值给 b . 这里多说一句, 后面就出现了引用 &, 就不会有临时对象的生成.

那么延续上面的思路, 我们就会知道上面代码的一个原理:
用 23去构造了一个 类型为A的临时对象, 记作tem, 然后把 tem 拷贝构造给 a
🗨️为啥换到这里就是 构造 和 拷贝构造了??

  • 生成一个类型是 A的临时对象 tem, 用 23去初始化tem — — 构造
    用一个已知的对象tem 去初始化另一个对象 a — — 拷贝构造
1.3.2.2 探寻 '隐式类型转换' 的真相

我们已经知道隐式类型转换的一个原理, 那么用代码来验证一番:

class A
{
public:
	A(int a)
		:_a(a)
	{
		cout << "调用了构造函数" << endl;
	}

	A(const A& x)
	{
		cout << "调用了拷贝构造函数" << endl;
	}

private:
	int _a;
};

int main()
{
	A a = 23;

	return 0;
}

*****
调用了构造函数
*****

🗨️嗯??, 跟我们想的不一样, 难道我们想错了??

  • 其实不然, 这里编译器把它给优化了.
    我们知道: 构造 和 拷贝构造 的功能都是 初始化, 如果在同一行, 我们同时调用 构造 + 拷贝构造 ⇒ 编译器就只会调用一个构造函数, 直接完成赋值~~

🗨️老陈, 你空口无凭, 给我们看一下证据??

  • ok, 这就安排
    由于,临时对象具有常性, 所以我们想到了用 引用 &来进行验证 <== 因为权限可以缩小 或 平移, 但是不能放大
    先看下面的代码👇👇👇
class A
{
public:
	A(int a)
		:_a(a)
	{
		cout << "调用了构造函数" << endl;
	}

	A(const A& x)
	{
		cout << "调用了拷贝构造函数" << endl;
	}

private:
	int _a;
};

int main()
{
	A& a = 23;

	return 0;
}

*****
error C2440: “初始化”: 无法从“int”转换为“A &*****
class A
{
public:
	A(int a)
		:_a(a)
	{
		cout << "调用了构造函数" << endl;
	}

	A(const A& x)
	{
		cout << "调用了拷贝构造函数" << endl;
	}

private:
	int _a;
};

int main()
{
	const A& a = 23;

	return 0;
}

*****
调用了构造函数
*****

这里就变相地证明了: 上面的隐式类型转换 是经历过了构造 + 拷贝构造, 不过是编译器有优化而已
注意: 不同的编译器有不同的优化, 所以看到不同的结果不必大惊小怪的~~

1.3.2.3 explicit关键字

在一些场景下, 隐式类型转换会很方便(后面会学到的 string, STL… …),
但在另一些场景下, 我们又不希望隐式类型转换的出现(后面会学到的 智能指针… …)
那我们如何不让 隐式类型转换 发生呢?? — — 答案就是explicit关键字
把explicit 放在 构造函数的前面就会不让 隐式类型转换发生

class A
{
public:
	explicit A(int a)
		:_a(a)
	{
		cout << "调用了构造函数" << endl;
	}

	A(const A& x)
	{
		cout << "调用了拷贝构造函数" << endl;
	}

private:
	int _a;
};

int main()
{
	A a = 23;

	return 0;
}

*****
error C2440: “初始化”: 无法从“int”转换为“A”
message : class“A”的构造函数声明为“explicit*****

大鹏一日同风起,扶摇直上九万里

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

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

相关文章

如何使用ChatGPT生成Excel公式?

Excel是日常工作和生活中经常使用的一款数据统计和分析软件&#xff0c;它可以帮助我们快速完成各种复杂的数据计算。但对于一些复杂的计算公式&#xff0c;不是每个人都能轻松掌握&#xff0c;或者说有时候并不想手动输入计算公式。如果能有一个智能工具&#xff0c;帮助我们快…

libiosa语音信号处理

img { margin: auto; display: block } librosa是一个非常强大的python语音信号处理的第三方库&#xff0c;本文参考的是librosa的官方文档&#xff0c;本文主要总结了一些重要&#xff0c;对我来说非常常用的功能。学会librosa后再也不用python去实现那些复杂的算法了&#xf…

人工智能与大模型主题师资培训落地,飞桨持续赋能AI人才培养

5月12日-5月14日&#xff0c;由百度飞桨联合中国自动化学会、复旦大学共同举办的“人工智能与大模型”主题师资培训班在百度飞桨&#xff08;张江&#xff09;人工智能产业赋能中心圆满举办。来自全国各地17所高校、26个人工智能及相关专业的50名教师线下参加此次培训。复旦大学…

Java --- redis的缓存淘汰策略

目录 一、redis内存查看与设置 二、redis的数据删除方式 三、redis缓存淘汰策略 一、redis内存查看与设置 查看redis最大占用内存&#xff1a; redis默认内存使用&#xff1a; 不设置最大内存大小或设置为0&#xff0c;在64位操作系统下不限制内存大小&#xff0c;32位操作系…

Linux系统编程(四)—— 进程基本知识

一、进程标识符pid 1、pid的类型&#xff1a;pid_t pid 是有符号的16位整型数&#xff0c;也就是说可以同时进行三万多进程。 2、命令&#xff1a;ps ps命令用于报告当前进程的信息&#xff1a; ps命令有着不同的组合&#xff0c;可以显示进程不同的内容&#xff1a; &…

夜天之书 #83 Web API 的开发工具和平台

上一篇文章《Web API 简介》的落脚点是 Web API 的体验。 Web API 作为许多软件的第一道门面&#xff0c;提升其体验的努力从来没有停止过。今天&#xff0c;围绕 Web API 的开发体验和使用体验&#xff0c;已经成长出一个庞大的软件生态。本文以常用的 Web API 开发工具和平台…

什么是 Java中的零拷贝

什么是零拷贝 WIKI中对其有如下定义&#xff1a; “Zero-copy” describes computer operations in which the CPU does not perform the task of copying data from one memory area to another. 从WIKI的定义中&#xff0c;我们看到“零拷贝”是指计算机操作的过程中&#x…

数据库迁移 | 拥抱国产化数据库openGauss

Squids DBMotion再添新库同步能力&#xff0c;本期增加了对openGauss数据库的同步支持。 openGauss数据库是一款开源关系型数据库管理系统&#xff0c;采用木兰宽松许可证v2发行。openGauss内核深度融合华为在数据库领域多年的经验&#xff0c;结合企业级场景需求&#xff0c;持…

PFTL101B 20KN 3BSE004203R1主要介绍ACS380 Modbus通讯功能

​ PFTL101B 20KN 3BSE004203R1ABB系统优化船舶性能、效率和可持续性 根据经合组织的一份报告&#xff0c;货物和人员的海上运输是全球经济活动的重要驱动力&#xff0c;到2050年将增加两倍。据国际海事组织&#xff08;IMO&#xff09;称&#xff0c;这一增长将导致该行业的温…

Hi3861 移植 LVGL

一、前言 给 Hi3861 适配了硬件 spi &#xff0c;master 模式下最高 spi 速率可以达到 40M&#xff0c;用来驱动 oled 屏幕。 适配过程遇到了一个芯片bug&#xff0c;困扰了很久&#xff0c;clk 管脚驱动能力差&#xff0c;需要外接一个上拉电阻才能正常运行。适配完成移植 lvg…

k8s系列(五)——资源控制器

k8s系列五——资源控制器 控制器的必要性 自主式Pod对象由调度器调度到目标工作节点后即由相应节点上的kubelet负责监控其容器的存活状态&#xff0c;容器主进程崩溃后&#xff0c;kubelet能够自动重启相应的容器。但对出现非主进程崩溃类的容器错误却无从感知&#xff0c;这…

阿里下放自动驾驶,汽车业务是个坑,或是时候探讨下一个乐视了

阿里发布公告指达摩院自动驾驶团队将全部并入菜鸟集团&#xff0c;虽然并没有说关闭自动驾驶业务&#xff0c;但是自动驾驶业务已不再是阿里看重的业务&#xff0c;导致如此结果在于当前汽车行业发生的重大变化。 一、传统汽车开始发力 今年4月份的新能源汽车企业销量排名数据显…

配置gitee ssh免密拉取代码-唯一客服系统文档中心

Gitee 我们的客服系统代码托管于Gitee私有仓库默认情况下只用于开发者自我代码管理&#xff0c;不对外公布。如果你也是放在私有仓库进行托管&#xff0c;可以如下配置免密操作。 部署公钥免密拉取代码 部署公钥允许以只读的方式访问仓库&#xff0c;主要用于仓库在生产服务器的…

即时通讯在线聊天APP开发解决方案

即时通讯是目前移动端最为流行的通讯方式&#xff0c;各种各样的即时通讯软件也层出不穷&#xff1b;服务提供商也提供了越来越丰富的通讯服务功能&#xff0c;打造一个实时通信系统&#xff0c;允许两人或多人使用网络实时的传递文字消息、文件、语音与视频交流。今天河北领行…

Excel中时间戳与标准日期格式的互相转换

背景 在excel中将13位毫秒级别的时间戳转换为标准的日期格式(yyyy-mm-dd hh:mm:ss.000)&#xff0c;使用如下模板 TEXT(<source_cell>/1000/8640070*36519,"yyyy-mm-dd hh:mm:ss.000") 在excel中将10位秒级别的时间戳转换为标准的日期格式(yyyy-mm-dd hh:mm:ss…

每日学习记录

GDB调试 首先是安装&#xff0c;以及普通的一些命令&#xff0c;重点是如何打断点 调试多进程和多线程 不同的gdb版本可能不是很支持&#xff0c;需要用比较新的版本>8.3 多进程 fork()函数创建一个一模一样的进程正常来说&#xff0c;同一个执行文件&#xff0c;gdb只…

使用 CNN 进行面部情绪识别

面部表情是人类之间交流的重要方式。 在人工智能研究中&#xff0c;深度学习技术已成为增强人机交互的强大工具。心理学中面部表情和情绪的分析和评估涉及评估预测个人或群体情绪的决定。 本研究旨在开发一种能够使用卷积神经网络&#xff08;CNN&#xff09;算法和特征提取技术…

【Java每日一练】总目录(2023.3.11~5.18)共69篇

2023.3.11~2023.5.18 连载两个多月共69篇&#xff0c;暂停更 Java 2023.05 2023.5.11-2023.5.18 20230518 1. 移除链表元素 &#x1f31f; 2. 跳跃游戏 II &#x1f31f;&#x1f31f; 3. 复原 IP 地址 &#x1f31f;&#x1f31f; 20230517 1. 存在重复元素 &#x…

chatgpt赋能Python-pycharm添加库

Pycharm添加库 – 提高Python编程效率的绝佳利器 Pycharm是一款强大的Python IDE&#xff0c;为广大Python开发人员提供了高度集成化的开发环境。通过Pycharm&#xff0c;我们可以充分利用各种强大的开发工具来简化开发流程&#xff0c;提高编程效率。其中一项重要的功能就是添…

SciPy库(一)常数与特殊函数与插值

一、概述 SciPy是一个开源的Python科学计算库&#xff0c;它建立在NumPy之上&#xff0c;提供了许多有用的科学计算功能。SciPy包括各种科学计算模块&#xff0c;如线性代数、优化、积分、插值、特殊函数、快速傅里叶变换、信号处理和图像处理等。SciPy库的主要特点是其高效性和…