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

👦个人主页:@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;
}
【程序结果】

【补充】
- 为什么
const和引用类型需要在初始化列表给值?
原因如下:
引用和const的特征:必须在定义的时候初始化。 因为const修饰的变量在定义后不能被修改;同样的,引用在定义时,必须初始化,并且一旦引用一个变量,就再也不能引用其他变量。又因为构造函数体内可以多次赋值,因此导致报错。- 每个成员变量在初始化列表中最多只能出现一次(初始化只能初始化一次),当然也可以不初始化
【错误展示】
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 特性
- 要注意区分成员变量和静态成员变量。成员变量属于每一个类对象,存储在对象里;而静态成员变量属于类,属于类的每个对象共享,不属于某个具体的对象,存储在静态区。
- 静态成员变量必须在类外定义,(是不受
public、protected、private访问限定符的限制)。定义时不添加static关键字,类中只是声明
- 类外定义的原因:
静态成员变量属于类,而不属于每一个类对象。因此,它们在内存中只有一份副本,不会随着类的对象的创建和销毁而变化。当我们在类定义中声明一个静态成员变量时,它只是一个声明,它并没有在内存中分配存储空间。因此,我们必须在类外部的某个地方为其分配存储空间,这样才能让它真正存在于内存中。因此,静态成员变量必须在类外定义,这样编译器才知道要为它分配存储空间。同时,我们也可以在类外初始化这个静态成员变量。
- 静态成员也是类的成员,受
public、protected、private访问限定符的限制。因此,类静态成员即可用 类名::静态成员 或者 对象.静态成员 来访问
4. 静态成员函数没有隐藏的this指针,指定类域和访问限定符就可以访问静态成员变量和静态成员函数
- 为什么静态成员函数没有隐藏的this指针?
静态成员函数是属于类的,而不是属于类的某个对象,因此在静态成员函数中没有隐含的this指针。this指针是指向当前对象的指针,因此只能在非静态成员函数中使用。而静态成员函数不依赖于具体的对象,只依赖于类本身,所以无需this指针。在静态成员函数中,只能访问静态成员变量和静态成员函数,不能访问非静态成员变量和非静态成员函数。- 静态成员函数可以调用非静态成员函数吗?
不可以。调用非静态的成员函数需要this指针,而静态成员函数没有隐藏的this指针- 非静态成员函数可以调用类的静态成员函数吗?
可以。因为调用静态成员函数不需要this指针
3.3 面试题
根据以上static成员的概念和特征,我们来做一个经典面试题
- 实现一个类,计算程序中创建出了多少个类对象
#include <iostream>
using namespace std;class A
{
public:// 构造函数// 每次创建对象时自增 countA() { count++; }// 拷贝构造函数// 每次创建对象时自增 countA(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。- 在主函数中,我们创建了三个对象
a1、a2、a3,并输出当前对象数量。第一次输出并没有创建对象,因此输出0;第二次分别创建了a1和a2,这两个都调用了构造函数,此时count = 2,接着又通过a1拷贝构造a3,count再自增1,所以第二次count为3
【结果展示】

3.4 使用静态成员变量的好处
-
全局性:静态成员变量是属于类的,而不是属于类的某个对象。因此,它可以被所有类的对象共享,具有全局性。
-
生命周期长:静态成员变量在程序运行期间只会被创建一次,它的生命周期长,可以一直存在于内存中,直到程序结束。
-
方便访问:由于静态成员变量是属于类的,因此可以通过类名直接访问,不需要先创建类的对象。
-
数据共享:静态成员变量可以用于实现数据共享,多个对象可以共享同一个静态成员变量,达到节省内存空间的目的。
-
保护数据:静态成员变量可以被用于保护数据,将数据设为私有的静态成员变量,只能通过类的公共接口来访问,从而保护数据的安全性。
相关文章:
【C++初阶】类和对象(下)构造函数(初始化列表) + explicit关键字 +static成员
👦个人主页:Weraphael ✍🏻作者简介:目前学习C和算法 ✈️专栏:C航路 🐋 希望大家多多支持,咱一起进步!😁 如果文章对你有帮助的话 欢迎 评论💬 点赞…...
chatgpt赋能python:Python代码怎么用?一个10年编程经验工程师的实践总结
Python代码怎么用?一个10年编程经验工程师的实践总结 如果你正在学习Python或已经是一名Python开发者,你需要知道如何正确地使用Python代码以实现项目需求。在本文中,我将分享我的10年Python编程经验,并介绍一些关于如何使用Pyth…...
【Android定制】修改BUILD_AGO_GMS = no 和 BUILD_GMS=no属性
文章目录 概要名词解释细节小结 概要 在安卓底层源码中,有这样的两个属性,这两个第一眼看上去都像是带不带谷歌,BUILD_AGO_GMS no和BUILD_GMSno有什么区别?? 如果带了谷歌,那么这个设备就差不多是国外定…...
第十章:C语言的调试
很多小伙伴刚开始听到C语言的调试,这是个啥,表示很怀疑,敲代码不就是直接就是干嘛,结果很多小白们,一运行错误多的数都数不过来。就开始这改改,那删删,莫名奇妙就运行成功了。到最后都不知道到底…...
【20】SCI易中期刊推荐——计算机信息系统工程电子与电气(中科院3区)
💖💖>>>加勒比海带,QQ2479200884<<<💖💖 🍀🍀>>>【YOLO魔法搭配&论文投稿咨询】<<<🍀🍀 ✨✨>>>学习交流 | 温澜潮生 | 合作共赢 | 共同进步<<<✨✨ 📚📚>>>人工智能 | 计算机视觉…...
初识网络之UDP网络套接字
目录 一、UDP中的socket编程常用接口 1. socket的含义 2. sockaddr结构 3. socket编程中UDP协议常用接口介绍 3.1 创建socket文件描述符(TCP/UDP、客户端 服务器) 3.2 绑定端口号(TCP/UDP,服务器) 3.3 接收数据…...
数据中心末端配电的数字化方案及设备选型
普通PDU和智能PDU有什么区别? 机架安装配电盘或机架配电单元 (PDU) 是一种配备许多插座的设备,可将电力分配给位于数据中心机架或机柜内的服务器、存储设备和网络设备。领先的分析公司 IHS 将它们分为两大类: 1) 基本 PDU 提供可靠的配电。 2…...
k8s入门实战-Service
k8s入门实战-Service Service 和 Label Service 通过一组 Pod 路由通信。Service 是一种抽象,它允许 Pod 死亡并在 Kubernetes 中复制,而不会影响应用程序。在依赖的 Pod (如应用程序中的前端和后端组件)之间进行发现和路由是由Kubernetes Service 处理…...
Python量化交易:策略创建运行流程
学习目标 目标 知道策略的创建和运行知道策略的相关设置知道RQ的策略运行流程应用 无 1、体验创建策略、运行策略流程 1.1 创建策略 1.2 策略界面 2、 策略界面功能、运行介绍 2.1 一个完整的策略需要做的事情 选择策略的运行信息: 选择运行区间和初始资金选择回…...
企业该如何自主构建信息化管理系统?
实践证明,企业自己搭建的信息化系统灵活性更高,更能契合企业的需求。 我们可以借助零代码平台自主搭建,既提供了各个应用系统的标准化功能,又支持自定义搭建,可根据企业自身需求修改。 全球领先的信息技术研究和顾问公…...
linuxOPS基础_操作系统概述
计算机发展史 第一台计算机是1946 年2 月14 日诞生日,第一台名称ENIAC。体积一间屋子的大小,重量高达28t。 第一代:1946 – 1958 > 12 年 (电子管) 第二代:1958 – 1964 > 6 年 (晶体管…...
常用adb命令记录下
adb root 获取root 权限(以root权限运行ADB守护程序)adb remount 以读写方式重新挂载设备的文件系统adb reboot 重启设备adb devices 查看当前连接设备adb get-serialno 获取设备的序列号adb backup 备份设备的应用程序和数据adb bugreport 收集设备的当前状态信息以进行故障排…...
Etcdctl 命令v3
一、v3必须导出环境变量 export ETCDCTL_API3 二、查看版本 etcdctl version 三、写入键 1.基本 etcdctl put foo bar 2.绑定租约 etcdctl put foo bar --leasexxxx 四、获取键 1.基本 etcdctl get foo 2.按十六进制获取 etcdctl get foo --hex 3.只读取键值 et…...
第二十一章 开发Productions - ObjectScript Productions - 延迟发送
文章目录 第二十一章 开发Productions - ObjectScript Productions - 延迟发送延迟发送 生成事件日志条目在 ObjectScript 中生成事件日志条目 第二十一章 开发Productions - ObjectScript Productions - 延迟发送 延迟发送 除了同步(等待)和异步&…...
用vue-full-calendar实现酒店预定管理展示
文章目录 前言一、关于vue-full-calendar二、使用步骤1. 引入库2. 使用库3. 开始编码4. 实际效果图展示5. 点击弹窗展示6. 弹窗展示效果图 总结 前言 近些天有位做酒店业务朋友问到我,有没有前端比较好用的预定日历查看插件,实际上我也没有研究过&#…...
DirectX12环境配置(1)
开发环境:visual studio 2022 第一种配置DirectX12环境得方式。首先创建一个c得空项目,然后创建一个main.cpp文件。把下面这串代码放进去,先不用管这串代码什么意思,后面会逐行逐句得讲解,因为我们创建得是空项目&am…...
Go-异常处理(defer recover panic)
系列文章目录 提示:goi语言基础文章 GO-异常处理 文章目录 系列文章目录前言一、关键字含义defer /recover 实现异常捕获和处理应用场景deferrecoverpanic 二、实例实例讲解上述几种情况 总结 前言 提示:这里可以添加本文要记录的大概内容: …...
【完美解决】mysql启动不了:本地计算机上的MySQL服务启动后停止
本文基于mysql8.0,5.7也可以参考 navicat 突然莫名其妙连不上mysql 查看服务,也启动不了,手动启动出现错误: 本地计算机上的MySQL服务启动后停止。某些服务在未由其他服务或程序使用时将自动停止 20230525更新! 先…...
C++ Qt 项目设计:基于C++与Qt的跨平台定时关机/关屏应用开发
基于C与Qt框架的定时关机与关屏应用开发全攻略 (一) 项目概述 (Project Overview)1.1 项目需求解析 (Analyzing the Project Requirements)1.2 选择技术栈 (Choosing the Tech Stack)1.3 功能概览 (Function Overview) (二) 设计思路 (Design Thinking)2.1 系统架构设计 (Syste…...
Python新技术和趋势:如何应对Python生态的变化和发展趋势
第一章:引言 Python作为一门简洁、优雅且易于学习的编程语言,一直以来都备受开发者的喜爱。它拥有强大的生态系统和活跃的社区,使得Python在各个领域都有广泛的应用。然而,随着时间的推移,Python生态系统也在不断演变…...
【网络】每天掌握一个Linux命令 - iftop
在Linux系统中,iftop是网络管理的得力助手,能实时监控网络流量、连接情况等,帮助排查网络异常。接下来从多方面详细介绍它。 目录 【网络】每天掌握一个Linux命令 - iftop工具概述安装方式核心功能基础用法进阶操作实战案例面试题场景生产场景…...
Vue记事本应用实现教程
文章目录 1. 项目介绍2. 开发环境准备3. 设计应用界面4. 创建Vue实例和数据模型5. 实现记事本功能5.1 添加新记事项5.2 删除记事项5.3 清空所有记事 6. 添加样式7. 功能扩展:显示创建时间8. 功能扩展:记事项搜索9. 完整代码10. Vue知识点解析10.1 数据绑…...
树莓派超全系列教程文档--(62)使用rpicam-app通过网络流式传输视频
使用rpicam-app通过网络流式传输视频 使用 rpicam-app 通过网络流式传输视频UDPTCPRTSPlibavGStreamerRTPlibcamerasrc GStreamer 元素 文章来源: http://raspberry.dns8844.cn/documentation 原文网址 使用 rpicam-app 通过网络流式传输视频 本节介绍来自 rpica…...
【人工智能】神经网络的优化器optimizer(二):Adagrad自适应学习率优化器
一.自适应梯度算法Adagrad概述 Adagrad(Adaptive Gradient Algorithm)是一种自适应学习率的优化算法,由Duchi等人在2011年提出。其核心思想是针对不同参数自动调整学习率,适合处理稀疏数据和不同参数梯度差异较大的场景。Adagrad通…...
.Net框架,除了EF还有很多很多......
文章目录 1. 引言2. Dapper2.1 概述与设计原理2.2 核心功能与代码示例基本查询多映射查询存储过程调用 2.3 性能优化原理2.4 适用场景 3. NHibernate3.1 概述与架构设计3.2 映射配置示例Fluent映射XML映射 3.3 查询示例HQL查询Criteria APILINQ提供程序 3.4 高级特性3.5 适用场…...
cf2117E
原题链接:https://codeforces.com/contest/2117/problem/E 题目背景: 给定两个数组a,b,可以执行多次以下操作:选择 i (1 < i < n - 1),并设置 或,也可以在执行上述操作前执行一次删除任意 和 。求…...
DBAPI如何优雅的获取单条数据
API如何优雅的获取单条数据 案例一 对于查询类API,查询的是单条数据,比如根据主键ID查询用户信息,sql如下: select id, name, age from user where id #{id}API默认返回的数据格式是多条的,如下: {&qu…...
Docker 本地安装 mysql 数据库
Docker: Accelerated Container Application Development 下载对应操作系统版本的 docker ;并安装。 基础操作不再赘述。 打开 macOS 终端,开始 docker 安装mysql之旅 第一步 docker search mysql 》〉docker search mysql NAME DE…...
【JavaSE】多线程基础学习笔记
多线程基础 -线程相关概念 程序(Program) 是为完成特定任务、用某种语言编写的一组指令的集合简单的说:就是我们写的代码 进程 进程是指运行中的程序,比如我们使用QQ,就启动了一个进程,操作系统就会为该进程分配内存…...
Xela矩阵三轴触觉传感器的工作原理解析与应用场景
Xela矩阵三轴触觉传感器通过先进技术模拟人类触觉感知,帮助设备实现精确的力测量与位移监测。其核心功能基于磁性三维力测量与空间位移测量,能够捕捉多维触觉信息。该传感器的设计不仅提升了触觉感知的精度,还为机器人、医疗设备和制造业的智…...


