【C++初阶】类和对象(下)构造函数(初始化列表) + explicit关键字 +static成员

news2024/11/24 14:42:07

在这里插入图片描述

👦个人主页:@Weraphael
✍🏻作者简介:目前学习C++和算法
✈️专栏:C++航路
🐋 希望大家多多支持,咱一起进步!😁
如果文章对你有帮助的话
欢迎 评论💬 点赞👍🏻 收藏 📂 加关注✨


目录

  • 一、再谈构造函数
    • 1.1 构造函数体赋值
    • 1.2 初始化列表
    • 1.3 为什么C++要设计初始化列表
      • 1. 若类中的成员变量包含`const`类型,必须在初始化列表位置进行初始化
      • 2. 若类中的成员变量包含`引用`类型,必须在初始化列表位置进行初始化
      • 3. 当成员变量是自定义类型,且该类没有默认构造函数时,必须在初始化列表位置进行初始化
    • 1.4 扫尾补充
  • 二、explicit关键字
  • 三、static成员
    • 3.1 概念
    • 3.2 特性
    • 3.3 面试题
    • 3.4 使用静态成员变量的好处

一、再谈构造函数

1.1 构造函数体赋值

以下代码是在创建对象时,编译器通过构造函数,给对象中各个成员变量一个合适的初始值。

using namespace std;

class Date
{
public:
	Date(int year, int month, int day)
	{
		Year = year;
		Month = month;
		Day = day;
	}
	void Print()
	{
		cout << Year << '-' << Month << '-' << Day << endl;
	}
private:
	int Year;
	int Month;
	int Day;
};

int main()
{
	Date d1(2023, 1, 1);
	d1.Print();

	return 0;
}

【程序结果】

在这里插入图片描述

若对象调用了以上的构造函数,则对象就有了一个初始值。但这不能称其为对象成员中成员变量的初始值。因为初始化只能初始化一次,而构造函数体内可以多次赋值。因此,构造函数体中的语句只能叫做赋值,而不是初始化。

1.2 初始化列表

那成员变量该如何初始化呢?我们一起来看看下面的代码:

#include <iostream>
using namespace std;

class Date
{
public:
	Date(int year = 2023, int month = 5, int day = 23)
		:Year(year)
		,Month(month)
		,Day(day)
	{

	}
	void Print()
	{
		cout << Year << '-' << Month << '-' << Day << endl;
	}
private:
	int Year;
	int Month;
	int Day;
};

int main()
{
	Date d1;
	d1.Print();

	return 0;
}

【程序结果】

在这里插入图片描述

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

1.3 为什么C++要设计初始化列表

有的人想,构造函数赋值好像就可以满足平时的代码需求。但对于某些数据类型,只能在初始化时进行赋值。

1. 若类中的成员变量包含const类型,必须在初始化列表位置进行初始化

【在构造函数体内给值的情况】

#include <iostream>
using namespace std;

class A
{
public:
	A(int x = 1)
	{
		i = x;
	}

private:
	const int i;
};

int main()
{
	A aa1(2);

	return 0;
}

【错误报告】

在这里插入图片描述

【正确做法:在初始化列表初始化】

#include <iostream>
using namespace std;

class A
{
public:
	A(int x = 1)
		:i(x)
	{

	}

	void Print()
	{
		cout << i << endl;
	}
private:
	const int i;
};

int main()
{
	A aa1(3);
	aa1.Print();

	return 0;
}

【程序结果】

在这里插入图片描述

2. 若类中的成员变量包含引用类型,必须在初始化列表位置进行初始化

【在构造函数体内给值的情况】

#include <iostream>
using namespace std;

class A
{
public:
	A(int x = 1)
	{
		i = x;
	}
	void Print()
	{
		cout << i << endl;
	}
private:
	int& i;
};

int main()
{
	A aa1(3);
	aa1.Print();

	return 0;
}

【错误报告】

在这里插入图片描述

【正确做法:在初始化列表初始化】

#include <iostream>
using namespace std;

class A
{
public:
	A(int &x)
		:i(x)
	{

	}
	void Print()
	{
		cout << i << endl;
	}
private:
	int& i;
};

int main()
{
	int a = 3;
	A aa1(a);
	aa1.Print();

	return 0;
}

【程序结果】

在这里插入图片描述

【补充】

  1. 为什么const引用类型需要在初始化列表给值?
    原因如下:
    引用和const的特征:必须在定义的时候初始化。 因为const修饰的变量在定义后不能被修改;同样的,引用在定义时,必须初始化,并且一旦引用一个变量,就再也不能引用其他变量。又因为构造函数体内可以多次赋值,因此导致报错。
  2. 每个成员变量在初始化列表中最多只能出现一次(初始化只能初始化一次),当然也可以不初始化

【错误展示】

在这里插入图片描述

3. 当成员变量是自定义类型,且该类没有默认构造函数时,必须在初始化列表位置进行初始化

默认构造函数:无参构造、全缺省构造、编译器自动生成的构造函数(自身不定义的情况)

【错误展示】

#include <iostream>
using namespace std;

class A
{
public:
	A(int c) // 不是默认构造函数
	{
		x = c;
	}
private:
	int x;
};

class B
{
public:
	B(int i = 3)
		:x(i)
	{

	}
private:
	A a;
	int x;
};

【错误报告】

在这里插入图片描述

【正确做法:初始化列表给值】

#include <iostream>
using namespace std;

class A
{
public:
	A(int c)
		:d(c)
	{
		
	}
private:
	int d;
};

class B
{
public:
	B(int i = 3)
		:x(i)
		,a(22)
	{}
private:
	A a;
	int x;
};

int main()
{
	B b1;

	return 0;
}

【结果】

在这里插入图片描述

1.4 扫尾补充

  • 尽量使用初始化列表初始化,因为不管你是否使用初始化列表,对于自定义类型成员变量,一定会先使用初始化列表初始化。但需要注意的是:初始化列表并不能百分之百完成所有初始化工作。例如:
#include <iostream>
using namespace std;

class Stack
{
public:
	Stack(int default_capacity = 4)
		:a((int*)malloc(sizeof(int) * default_capacity))
		,top(0)
		,capacity(default_capacity)
	{
		// 断言
		if (a == nullptr)
		{
			return;
		}
		// 初始化
		memset(a, 0, sizeof(int) * capacity);
	}

private:
	int* a;
	int top;
	int capacity;
};

int main()
{
	Stack s1;

	return 0;
}

【结果展示】

在这里插入图片描述

  • 注意:初始化列表的初始化顺序一定是根据成员变量在类中声明顺序而定的

看看一下程序,就是因为没有根据成员变量在类中声明顺序,导致出现随机值

using namespace std;

class A
{
public:
	A(int a)
		:a1(a)
		, a2(a1)
	{}
	void Print() 
	{
		cout << a1 << " " << a2 << endl;
	}
private:
	int a2;
	int a1;
};
int main() 
{
	A a(1);
	a.Print();

	return 0;
}

【解释 + 结果】

在这里插入图片描述

二、explicit关键字

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

#include <iostream>
using namespace std;

class A
{
public:
	A(int x)
		:a(x)
	{

	}
	void Print()
	{
		cout << a << endl;
	}
private:
	int a;
};

int main()
{
	A a1(1); // 调用构造
	A a2 = 2; // 隐式类型转化 

	a1.Print();
	a2.Print();

	return 0;
}

【结果展示】

在这里插入图片描述

A a1(1)毋庸置疑调用的是构造函数,而对于A a2 = 2单个参数是具有 类型转换 的作用。其隐式转化过程:用2去调用构造函数生成一个A类型的临时变量,临时变量再通过拷贝构造给a2
在这里插入图片描述

但需要注意的是:对于这种连续的构造,编译器会直接优化用直接构造。代码说话:

#include <iostream>
using namespace std;

class A
{
public:
	// 构造函数
	A(int x)
		:a(x)
	{
		cout << "调用了构造函数" << endl;
	}
	// 拷贝构造函数
	A(const A& d)
		:a(d.a)
	{
		cout << "调用了拷贝构造函数" << endl;
	}
	void Print()
	{
		cout << a << endl;
	}
private:
	int a;
};

int main()
{
	A a2 = 2; // 隐式类型转化 

	a2.Print();

	return 0;
}

【程序结果】

在这里插入图片描述

若不想有这样的隐式转化,可以在构造函数前加上explicit,这样编译器就不支持隐式转化了

在这里插入图片描述

三、static成员

3.1 概念

声明为static类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量;用static修饰的成员函数,称之为静态成员函数

3.2 特性

  1. 要注意区分成员变量和静态成员变量。成员变量属于每一个类对象,存储在对象里;而静态成员变量属于类,属于类的每个对象共享,不属于某个具体的对象,存储在静态区。
  2. 静态成员变量必须在类外定义,(是不受publicprotectedprivate 访问限定符的限制)。定义时不添加static关键字,类中只是声明
  • 类外定义的原因:
    静态成员变量属于类,而不属于每一个类对象。因此,它们在内存中只有一份副本,不会随着类的对象的创建和销毁而变化。当我们在类定义中声明一个静态成员变量时,它只是一个声明,它并没有在内存中分配存储空间。因此,我们必须在类外部的某个地方为其分配存储空间,这样才能让它真正存在于内存中。因此,静态成员变量必须在类外定义,这样编译器才知道要为它分配存储空间。同时,我们也可以在类外初始化这个静态成员变量。
  1. 静态成员也是类的成员,受publicprotectedprivate 访问限定符的限制。因此,类静态成员即可用 类名::静态成员 或者 对象.静态成员 来访问
    4. 静态成员函数没有隐藏的this指针,指定类域和访问限定符就可以访问静态成员变量和静态成员函数
  • 为什么静态成员函数没有隐藏的this指针?
    静态成员函数是属于类的,而不是属于类的某个对象,因此在静态成员函数中没有隐含的 this 指针。this 指针是指向当前对象的指针,因此只能在非静态成员函数中使用。而静态成员函数不依赖于具体的对象,只依赖于类本身,所以无需this 指针。在静态成员函数中,只能访问静态成员变量和静态成员函数,不能访问非静态成员变量和非静态成员函数。
  • 静态成员函数可以调用非静态成员函数吗?
    不可以。调用非静态的成员函数需要this指针,而静态成员函数没有隐藏的this指针
  • 非静态成员函数可以调用类的静态成员函数吗?
    可以。因为调用静态成员函数不需要this指针

3.3 面试题

根据以上static成员的概念和特征,我们来做一个经典面试题

  • 实现一个类,计算程序中创建出了多少个类对象
#include <iostream>
using namespace std;

class A
{
public:
	// 构造函数
	// 每次创建对象时自增 count
	A() 
	{ 
		count++; 
	}
	// 拷贝构造函数
	// 每次创建对象时自增 count
	A(const A& x) 
	{ 
		count++; 
	}
	// 析构函数
	// 每次销毁对象时自减 count
	~A() 
	{ 
		count--;
	}
	//指定类域和访问限定符
	//就可以访问静态成员变量和静态成员函数
	static int GetACount() 
	{ 
		return count; 
	}
private:
	// 静态成员变量
	static int count;
};

// 静态成员变量必须在类外定义,
// 定义时不添加static关键字
int A::count = 0;

int main()
{
	// 如果直接想访问类中的count是不行的
	// 因此我们首先想到成员函数
	cout << A::GetACount() << endl;
	A a1, a2;
	A a3(a1);
	cout << A::GetACount() << endl;

	return 0;
}

  • 在类中,定义了一个静态成员变量count,用于统计当前类对象的数量。每次创建对象时,构造函数和拷贝构造函数会自增count,每次销毁对象时,析构函数会自减count
  • 在主函数中,我们创建了三个对象 a1a2a3,并输出当前对象数量。第一次输出并没有创建对象,因此输出0;第二次分别创建了a1a2,这两个都调用了构造函数,此时count = 2,接着又通过a1拷贝构造a3count再自增1,所以第二次count3

【结果展示】

在这里插入图片描述

3.4 使用静态成员变量的好处

  1. 全局性:静态成员变量是属于类的,而不是属于类的某个对象。因此,它可以被所有类的对象共享,具有全局性。

  2. 生命周期长:静态成员变量在程序运行期间只会被创建一次,它的生命周期长,可以一直存在于内存中,直到程序结束。

  3. 方便访问:由于静态成员变量是属于类的,因此可以通过类名直接访问,不需要先创建类的对象。

  4. 数据共享:静态成员变量可以用于实现数据共享,多个对象可以共享同一个静态成员变量,达到节省内存空间的目的。

  5. 保护数据:静态成员变量可以被用于保护数据,将数据设为私有的静态成员变量,只能通过类的公共接口来访问,从而保护数据的安全性。

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

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

相关文章

如何下载旧版的Chrome

&#xff08;下面网址需要科学上网&#xff09; 1.获得浏览器版本号 访问 https://www.google.com 然后在搜索框中输入要搜索的浏览器版本号&#xff0c;例如 : site:chromereleases.googleblog.com 96.0。其中96.0是大版本号。 2.查询浏览器版本号的具体信息 访问 https://…

CANopenNode Master RPDO 配置

文章目录 CANopenNode 简介CANopenNode 主栈SDO ClientRPDORPDO 通讯参数RPDO 通信参数设置实例PDO 映射参数RPDO 映射参数设置实例 CANopenNode 简介 CANopenNode 是一个开源的免费的开源 CANopen 协议栈。 对象字典为任何变量提供清晰灵活的组织。可以直接或通过读/写函数…

电力系统的虚假数据注入攻击和MTD系统研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

代码随想录算法训练营第十五天|树的层序遍历 、226.翻转二叉树 、101.对称二叉树

层序遍历&#xff08;广度优先遍历&#xff09;&#xff1a; 遍历思路&#xff1a; 借用队列来实现。 若根节点不为空&#xff0c;则先将根节点放入队列&#xff0c; 随后&#xff0c;在while循环中&#xff0c;判断队列当前的size&#xff0c;队列的size就是树在该层中的节点…

ubuntu换镜像源(ubuntu换源)

ubuntu换镜像源&#xff08;ubuntu换源&#xff09; 文章目录 ubuntu换镜像源&#xff08;ubuntu换源&#xff09;1. 备份镜像源文件2. 根据不同 ubuntu 版本设置不同的镜像源2.1 focal 版本镜像源2.2 bionic 版本镜像源2.3 ubuntu 自带源&#xff1a; 参考文献 1. 备份镜像源文…

数据库连接 ---MySQL的总结(八)

数据库连接 —MySQL的总结&#xff08;八&#xff09; mysql使用在c编程之中使用&#xff0c;需要调用官方c库进行使用。 库的安装 库文件&#xff0c;安装mysql的c链接库 yum install mysql-server接口介绍 初始化 MYSQL *mysql mysql_init(nullptr);连接 mysql_real_conn…

“智汇新算力,众启Z力量”惠普发布全新一代Z系列工作站,共赢算力黄金时代

5月23日&#xff0c; “智汇新算力&#xff0c;众启Z力量”惠普Z系列新品发布会在上海盛大举行。本次发布会上&#xff0c;惠普Z 系列工作站焕新升级&#xff0c;以高算力、强稳定、强拓展的产品及解决方案&#xff0c;帮助各行业用户专注自身领域、驾驭复杂工作&#xff0c;从…

HNU-电路与电子学-小班2

第二次讨论 讨论题目&#xff1a; 1、电子秤的电桥电路可以分别用 1 个压控电阻、 2 个压控电阻、 3 个压控电阻、 4 个压控电阻实现吗&#xff1f;试写出每种实现的 U AB 输出表达式&#xff0c;并分析哪种实现电桥 电压的灵敏度&#xff08;SV/ △ R &#xff09;高。 …

【Python Tableau】零基础也能轻松掌握的学习路线与参考资料

一、学习路线 Python和Tableau都是当前市场上非常热门的数据分析和可视化工具。下面是Python Tableau的学习路线&#xff1a; 学习Python基础知识&#xff1a;Python是一门非常易学易用的编程语言&#xff0c;可以选择相应书籍进行学习&#xff0c;如《笨办法学Python》、《Py…

Node.js中Buffer的一些实现原理

1.前言 在ES6之前&#xff0c;JavaScript无法直接处理二进制数据&#xff0c;Node.js为了弥补这个不足引入了 Buffer&#xff0c;其是Node.js的核心模块之一&#xff0c;底层实现基于C。本文将从 Node.js v14.20.0 的源码分析 Buffer 的一些实现原理。 2.ArrayBuffer 在介绍…

Visual Studio插件DevExpress CodeRush v22.1- 支持C# 10

DevExpress CodeRush是一个强大的Visual Studio .NET 插件&#xff0c;它利用整合技术&#xff0c;通过促进开发者和团队效率来提升开发者体验。为Visual Studio IDE增压、消除重复的代码并提高代码质量&#xff0c;可以快速思考、自动化测试、可视化调试和重构。 CodeRush v2…

资深程序员深度体验ChatGPT一周发现竟然....

周一打卡上班&#xff0c;老板凑到我跟前&#xff1a;“小李啊&#xff0c;这周有个新需求交给你做一下&#xff0c;给我们的API管理平台新增一个智能Mock的功能...”。我条件反射般的差点脱口而出&#xff1a;“这个需求做不了..”。不过在千钧一发之间&#xff0c;我想起了最…

【文章学习系列之技巧】Network Slimming

本章内容 文章概况问题来源方法实验结果总结 文章概况 这是一篇2017年发表于ICCV的一篇论文。该论文指出深度卷积神经网络的应用受到了高计算成本的阻碍&#xff0c;并提出一种修剪模型结构的方式用于降低这种成本&#xff0c;使得模型大小减小、运行内存减小且不降低精度的情…

http\https协议

前言 小亭子正在努力的学习编程&#xff0c;接下来将开启javaEE的学习~~ 分享的文章都是学习的笔记和感悟&#xff0c;如有不妥之处希望大佬们批评指正~~ 同时如果本文对你有帮助的话&#xff0c;烦请点赞关注支持一波, 感激不尽~~ 目录 前言 一、 认识http协议 1.概念 1.1…

ChatGPT APP Plus升级全记录:购买礼品卡、兑换和处理失败

大家好&#xff0c;我是可夫小子&#xff0c;《小白玩转ChatGPT》专栏作者&#xff0c;关注AIGC、读书和自媒体。 在上一篇《ChatGPT APP来了&#xff0c;支持语音输入&#xff0c;还可以直接订阅Plus账号》文章中&#xff0c;我介绍了ChatGPT App下载安装教程。本文主要介绍怎…

YOLO中的值得借鉴的思想

关键理论的理解&#xff0c;后面会补充结构等。 1.YOLO1中将图像划分为7*7个网格&#xff0c;每个网格都预测网格中的的类别&#xff08;是什么物体&#xff09;&#xff0c;以及预测到的物体所对应的框&#xff08;四个位置量&#xff0c;一个置信度&#xff09;&#xff0c;所…

一、尚医通预约下单

文章目录 一、预约下单1、需求分析1.1订单表结构1.2下单分析 2、搭建service-order模块2.1 搭建service-order模块2.2 修改配置2.3 启动类2.4配置网关 3、添加订单基础类3.1 添加model3.2 添加Mapper3.3 添加service接口及实现类3.4 添加controller 4、封装Feign调用获取就诊人…

【Redis】聊一下Redis事务以及watch机制

我们知道熟悉MySQL的同学&#xff0c;一定了解ACID属性。ACID分别对应四种属性&#xff0c;但是Redis的事务和ACID属性有什么不一样的地方嘛&#xff0c;我们来深入探讨下。 Redis事务和MySQL事务的区别 ACID的本质是保证了事务执行前后对结果的保证&#xff0c;以及数据状态…

二、数据结构2:双链表 模板题+算法模板(双链表)

文章目录 算法模板双链表题目模板 模板题双链表原题链接题目思路题解 算法模板 双链表题目模板 // e[]表示节点的值&#xff0c;l[]表示节点的左指针&#xff0c;r[]表示节点的右指针&#xff0c;idx表示当前用到了哪个节点 int e[N], l[N], r[N], idx;// 初始化 void init()…

Android进阶 View事件体系(一):概要介绍和实现View的滑动

Android进阶 View事件体系&#xff08;一&#xff09;&#xff1a;概要介绍和实现View的滑动 内容概要 本篇文章为总结View事件体系的第一篇文章&#xff0c;将介绍的内容主要有&#xff1a; 什么是View和ViewGroupAndroid中View的坐标轴手势检测和速度检测如何实现View的滑动…