C++类和对象进阶:初始化列表和static成员深度详解

C++类和对象:初始化列表和static成员深度详解

  • 1. 前言
  • 2. 构造函数初始化成员变量的方式
    • 2.1 构造函数体内赋值
    • 2.2 初始化列表
      • 2.2.1 初始化列表的注意事项
    • 2.3 初始化列表的初始化顺序
  • 3. 类的静态成员
    • 3.1 引入
    • 3.2 静态成员变量
    • 3.3 静态成员函数
    • 3.4 静态成员的注意事项
    • 3.5 静态成员的另一个实践场景
  • 4. 常见问题解答
  • 4. 总结对比

1. 前言

在C++面向对象编程中,构造函数初始化列表和静态成员是提升代码质量与安全性的重要特性。本文深度详解关于C++构造函数的初始化列表C++类中的static成员,其中static成员包含static成员变量static成员函数,以及介绍static的相关实践场景

2. 构造函数初始化成员变量的方式

2.1 构造函数体内赋值

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

class Date{
public://赋值方式初始化Date(int year, int month, int day){_year = year;_month = month;_day = day;}
private:int _year;int _month;int _day;
};

我们可以利用以上构造函数,在创建一个对象的时候对该对象进行初始化。

虽然上述构造函数调用之后,对象中已经有了一个初始值,但是不能将其称为对对象中成员变量的初始化构造函数体中的语句只能将其称为赋初值,,而不能称作初始化。
因为初始化只能初始化一次,而构造函数体内可以多次赋值。

2.2 初始化列表

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

例如:

class Date{
public://初始化列表初始化Date(int year, int month, int day): _year(year), _month(month), _day(day){}
private:int _year;int _month;int _day;
};
int main(){Date d1(2025, 2, 18);//传入的参数会通过初始化列表的方式进行初始化。return 0;
}

2.2.1 初始化列表的注意事项

首先回顾一下往期文章提出的对默认构造函数的理解:
C++类和对象进阶:构造函数和析构函数详解

默认构造函数,以下三种函数都可以被称作是默认构造函数。

  1. 无参构造函数。
  2. 全缺省构造函数。
  3. 我们没写编译器默认生成的构造函数
  • 总结来说就是,不需要传参的构造函数,都属于是默认构造函数
  1. 无参构造函数。没有参数,因此无需传参。
  2. 全缺省构造函数。参数全缺省,不需要传参。
  3. 我们没写编译器默认生成的构造函数。编译器生成的,我们无法显示调用。自动调用时无需传参。

注意

  1. 要把初始化列表理解成非静态成员变量创建(占内存)的地方。类中只是成员变量的声明
  2. 无论是否显式指定初始化列表,都会走一遍初始化列表来对成员变量进行初始化。
    (静态成员变量在类外进行初始化)
  1. 每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)
  2. 类中包含以下成员,必须放在初始化列表位置进行初始化:
    • 引用成员变量
    • const成员变量
    • 自定义类型成员(且该类没有默认构造函数时)

分析这三类变量的特征:

  • 引用成员变量:引用成员变量的初始化是“绑定”过程,而非赋值操作。构造函数体内无法完成初始化(此时成员已定义,只能赋值),因此必须在初始化列表(引用变量创建时)初始化
  • const成员变量const成员变量的初始化是“定义时赋值”,构造函数体内无法修改其值。const变量在定义后其值不可修改,因此必须在创建时初始化,也就是必须在初始化列表初始化
  • 自定义类型成员(该类没有默认构造函数时):编译器只会调用默认构造函数,若类没有默认构造函数(即无参构造函数或所有参数均有默认值的构造函数或编译器自己生成的构造函数),也就说程序员自己写了构造函数且调用时需要显式传参,则必须显式调用其某个构造函数,该工作在初始化列表中进行。

对比以上三类特殊情形:

变量类型核心特征初始化方式未正确初始化的后果
引用成员变量必须绑定对象,不可重新绑定初始化列表中绑定编译错误(未初始化引用)
const成员变量不可修改,需定义时赋值初始化列表中赋值编译错误(未初始化常量)
自定义类型成员(无默认构造函数)必须显式调用构造函数初始化列表中调用带参构造函数编译错误(找不到默认构造函数)

2.3 初始化列表的初始化顺序

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

我们来看如下代码:

class MyClass {
public:MyClass(int init_value): _value1(init_value)   // 类中的声明顺序决定初始化顺序, _value2(_value1)  // _value2先声明,会先用 _value1 初始化 _value2// 此时 _value1 还未完成初始化,是随机值{}void Print() const {cout << "_value1: " << _value1<< "  _value2: " << _value2 << endl;}
private:int _value2;     int _value1;
};
int main() {MyClass obj(5);obj.Print();  // 输出:_value1: 5  _value2: 随机值return 0;
}

在这里插入图片描述

结论:

  • 初始化列表中,变量初始化的顺序应该和变量在类中声明的次序保持一致
  • 尽量使用初始化列表对成员变量进行初始化,因为不管程序员是否使用初始化列表,对于自定义类型成员变量,一定会先试用初始化列表初始化。
  • 不能在初始化列表中完成的,在函数体内用语句来完成(如开辟空间后对指针的检查等)

初始化列表总结:

  • ⽆论是否显式写初始化列表,每个构造函数都有初始化列表
  • ⽆论是否在初始化列表显式初始化成员变量,每个成员变量都要⾛初始化列表初始化
    在这里插入图片描述

3. 类的静态成员

3.1 引入

设计一个类,计算程序中创建了多少个该类的类对象

#include <iostream>
//设计一个程序,统计当前正在使用的某个对象有多少个
int _scount = 0;	//我们可以利用全局变量class A {
public:A() { ++_scount; }	//构造函数A(const A& t) { ++_scount; }	//拷贝构造函数~A() { --_scount; }		//析构函数
};
int main() {cout << __LINE__ << ": " << _scount << endl;	// 是 1 ,此处还没进入Func函数,static 对象还没创建A aa1;Func();	//3Func(); //3return 0;
}

在这里插入图片描述
以上程序确实可以实现统计,但全局变量有极大的缺陷:

void Func() {static A aa2;	//局部静态对象,只会创建一次,不在函数栈帧内,在静态区cout << __LINE__ << ": " << _scount << endl;	//3//全局变量的劣势:任何地方都可以随意改变,不安全//_scount++;
}

因此,我们想到了利用类来对计数器进行封装,并将计数器设置成静态成员变量。

什么是静态成员?

  • 声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量
  • static修饰的成员函数,称之为静态成员函数静态成员变量一定要在类外进行初始化。

利用静态成员实现的类:

class A {
public:A(){ ++_scount;}A(const A& t) { ++_scount; }~A() { --_scount; }
private:static int _scount;		//此处只是该成员变量的声明//类内的静态成员,相当于用类去封装全局变量
};
//全局位置,类外定义   类的 static 成员,类内声明,类外初始化  static 成员定义时不受访问限定符的限制
int A::_scount = 0;
  • 需要尤其注意,静态成员变量,在类内是声明,需要在类外进行初始化:int A::_scount = 0;这是规定的写法,通过类作用域限定符来访问

3.2 静态成员变量

private:// 非静态成员变量 ----- 属于每一个类对象, 存储在对象里面int _a1 = 1;	//成员变量给缺省值,会自动进入初始化列表int _a2 = 2;// 静态成员变量 ----- 属于类,类的每个对象共享,存储在静态区, 生命周期是全局的,不能用初始化列表初始化static int _scount;
};

需要注意:静态成员变量和非静态成员变量存储的位置不同。

  • 静态成员变量:属于类内,类的每个对象共享,存储在静态区, 生命周期是全局的,程序运行期间持续存在,不能用初始化列表初始化。
  • 非静态成员变量: 属于每一个类对象, 存储在对象里面

3.3 静态成员函数

静态成员函数一般是和静态成员变量成对出现的。
我们在类中添加以下函数方便我们获取_scount的值

public:
static int GetACount() {return _scount;
}

静态成员函数的特点:

  • 没有this指针
  • 指定类域和访问限定符就可以访问
  • 可以直接访问类内的静态成员变量

通过指定类域和访问限定符访问静态成员函数。

int main(){//由于静态成员变量是私有的//可以通过静态成员函数来访问静态成员变量cout << A::GetACount() << endl;return 0;
}

因此我们可以得出:

  1. 静态成员函数,不能访问类内的非静态成员变量,因为没有this指针(没有传入调用对象的地址)
  2. 静态成员函数不能调用非静态成员函数,非静态成员函数的调用需要传递this指针,但static成员函数没有this指针

3.4 静态成员的注意事项

  1. 静态成员为所有类对象所共享,不属于某个具体的对象,存放在静态区
  2. 静态成员变量必须在类外定义,定义时不添加static关键字,类中只是声明。类内声明,类外初始化
  3. 类静态成员(成员变量和成员函数)可用 类名::静态成员 或者 对象.静态成员 来访问
  4. 静态成员函数没有隐藏的this指针,不能用const修饰不能访问任何非静态成员
  5. 静态成员也是类的成员,受public、protected、private 访问限定符的限制
  6. 静态成员变量:不能是auto推导类型。

在这里插入图片描述

  • 核心特点
    • 静态成员函数无this指针,无法访问非静态成员
    • 静态成员函数可直接调用无需实例化

3.5 静态成员的另一个实践场景

静态成员函数的妙用
设计一个类,在类外只能在 栈 上创建对象
设计一个类,在类外只能在 堆 上创建对象

我们可以这样做

class Obj {
public://通过类作用域限定符来调用函数获取对象static Obj GetStackObj() {Obj obj;return obj;}static Obj* GetHeapObj() {Obj* obj = new Obj;return obj;}//构造函数私有化,防止直接调用构造函数在栈或堆上创建对象。
private:Obj(){}
private:int _a1 = 1;int _a2 = 2;
};
int main() {//这三种方式都会调用构造函数,我们将构造函数私有化后,就无法再类外创建 堆/栈 上的对象了/*static OBj o1;OBj o2;Obj* o3 = new Obj;*///提供对外的接口//无需创建对象,通过类作用域限定符来调用静态成员函数。Obj obj_1 = Obj::GetStackObj();Obj* p_obj = Obj::GetHeapObj();return 0;
}
  • 实现原理
    1. 私有化构造函数
    2. 通过静态工厂方法控制对象创建

4. 常见问题解答

Q1:为什么静态成员变量必须类外初始化?

  • 静态成员不属于单个对象,类内声明仅表示存在性,需在程序全局空间进行内存分配

Q2:静态成员函数能否调用非静态成员函数?

  • 不能。非静态成员函数隐含this指针参数,而静态函数无this指针

Q3:如何选择初始化列表与构造函数体?

  • 优先使用初始化列表,特别是对于const/引用成员/无默认构造函数的自定义类型等必须初始化的场景

4. 总结对比

特性初始化列表静态成员
作用对象对象成员初始化类级别共享数据/操作
关键优势处理特殊类型成员初始化减少全局变量使用
典型应用场景const/引用成员初始化计数器、工具类函数
内存管理对象内存空间静态存储区

以上就是本文的所有内容了,码字整理不易,欢迎各位大佬在评论区留言交流

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

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

相关文章

ubuntu ffmpeg 安装踩坑

ffmpeg 安装踩坑 安装命令: sudo apt update sudo apt install ffmpeg如果以上命令没有报错&#xff0c;那么恭喜你很幸运&#xff0c;可以关闭这篇文章了&#xff01; 如果跟我一样&#xff0c;遇到如下报错&#xff0c;可以接着往下看&#xff1a; 报错信息&#xff1a; …

第13章 int指令

目录 13.1 int 指令13.2 编写供应用程序调用的中断例程13.3 对int、iret和栈的深入理解13.4 BIOS和DOS所提供的中断例程13.5 BIOS和DOS中断例程的安装过程13.6 BIOS中断例程应用13.7 DOS中断例程应用实验13 编写、应用中断例程 中断信息可以来自CPU的内部和外部&#xff0c;当C…

最新扣子(Coze)案例教程:全自动DeepSeek 写影评+批量生成 + 发布飞书,提效10 倍!手把手教学,完全免费教程

&#x1f468;‍&#x1f4bb;群里有同学是做影视赛道的博主&#xff0c;听说最近DeepSeek这么火&#xff0c;咨询能不能用DeepSeek写影评&#xff0c;并整理电影数据资料&#xff0c;自动发布到飞书文档&#xff0c;把每天的工作做成一个自动化的流程。 那今天斜杠君就为大家…

DeepSeek 提示词:定义、作用、分类与设计原则

&#x1f9d1; 博主简介&#xff1a;CSDN博客专家&#xff0c;历代文学网&#xff08;PC端可以访问&#xff1a;https://literature.sinhy.com/#/?__c1000&#xff0c;移动端可微信小程序搜索“历代文学”&#xff09;总架构师&#xff0c;15年工作经验&#xff0c;精通Java编…

鸟语林-论坛系统自动化测试

文章目录 一、自动化实施步骤1.1编写Web测试用例1.2 编写自动化代码1.2.1 LoginPageTest1) 能否正确打开登录页面2) 点击去注册能否跳转注册页面3) 模拟用户登录&#xff0c;输入多组登录测试用例 1.2.2 RegisterPageTest1) 能否成功打开注册页面2) 注册测试用例3) 点击去登录按…

DeepSeek模型量化

技术背景 大语言模型&#xff08;Large Language Model&#xff0c;LLM&#xff09;&#xff0c;可以通过量化&#xff08;Quantization&#xff09;操作来节约内存/显存的使用&#xff0c;并且降低了通讯开销&#xff0c;进而达到加速模型推理的效果。常见的就是把Float16的浮…

本周行情——250222

本周A股行情展望与策略 结合近期盘面特征及市场主线演化&#xff0c;本周A股预计延续结构性分化行情&#xff0c;科技成长与政策催化板块仍是资金主战场&#xff0c;但需警惕高标股分歧带来的波动。以下是具体分析与策略建议&#xff1a; 1. 行情核心驱动因素 主线延续性&…

【JT/T 808协议】808 协议开发笔记 ② ( 终端注册 | 终端注册应答 | 字符编码转换网站 )

文章目录 一、消息头 数据1、消息头拼接2、消息 ID 字段3、消息体属性 字段4、终端手机号 字段5、终端流水号 字段 二、消息体 数据三、校验码计算四、最终计算结果五、终端注册应答1、分解终端应答数据2、终端应答 消息体 数据 六、字符编码转换网站 一、消息头 数据 1、消息头…

使用ezuikit-js封装一个对接摄像头的组件

ezuikit-js 是一个基于 JavaScript 的视频播放库&#xff0c;主要用于在网页中嵌入实时视频流播放功能。它通常用于与支持 RTSP、RTMP、HLS 等协议的摄像头或视频流服务器进行交互&#xff0c;提供流畅的视频播放体验。 主要功能 多协议支持&#xff1a;支持 RTSP、RTMP、HLS …

一周学会Flask3 Python Web开发-flask3模块化blueprint配置

锋哥原创的Flask3 Python Web开发 Flask3视频教程&#xff1a; 2025版 Flask3 Python web开发 视频教程(无废话版) 玩命更新中~_哔哩哔哩_bilibili 我们在项目开发的时候&#xff0c;多多少少会划分几个或者几十个业务模块&#xff0c;如果把这些模块的视图方法都写在app.py…

DSC数字选择性呼叫

GMDSS Digital Selective Calling WAVECOM Decoder Online Help 12.0.0 VHF Marine GMDSS/DSC Decode & Scicos Simulation Black Cat Systems &#xff08;一&#xff09;DSC调制方式 DSC&#xff08;Digital Selective Calling&#xff0c;数字选择性呼叫&#xff0…

科普:你的笔记本电脑中有三个IP:127.0.0.1、无线网 IP 和局域网 IP;两个域名:localhost和host.docker.internal

三个IP 你的笔记本电脑中有三个IP&#xff1a;127.0.0.1、无线网 IP 和局域网 IP。 在不同的场景下&#xff0c;需要选用不同的 IP 地址&#xff0c;如下为各自的特点及适用场景&#xff1a; 127.0.0.1&#xff08;回环地址&#xff09; 特点 127.0.0.1 是一个特殊的 IP 地…

《AI与NLP:开启元宇宙社交互动新纪元》

在科技飞速发展的当下&#xff0c;元宇宙正从概念逐步走向现实&#xff0c;成为人们关注的焦点。而在元宇宙诸多令人瞩目的特性中&#xff0c;社交互动体验是其核心魅力之一。人工智能&#xff08;AI&#xff09;与自然语言处理&#xff08;NLP&#xff09;技术的迅猛发展&…

量化方法bitsandbytes hqq eetq区别

量化方法bitsandbytes、HQQ&#xff08;Half-Quadratic Quantization&#xff09;和EETQ&#xff08;Efficient and Effective Ternary Quantization&#xff09;在深度学习模型压缩和加速中各有特点&#xff0c;以下是它们的区别&#xff1a; 1. bitsandbytes 概述: bitsand…

Hutool - Log:自动识别日志实现的日志门面

一、简介 在 Java 开发中&#xff0c;日志记录是一项非常重要的功能&#xff0c;它可以帮助开发者在开发和生产环境中监控程序的运行状态、排查问题。然而&#xff0c;Java 生态系统中有多种日志实现框架&#xff0c;如 Log4j、Logback、JDK 自带的日志框架等。为了在不同的项…

伪404兼容huawei生效显示404

根据上述思考&#xff0c;以下是详细的中文分步说明&#xff1a; --- **步骤 1&#xff1a;获取目标设备的User-Agent信息** 首先&#xff0c;我们需要收集目标设备的User-Agent字符串&#xff0c;包括&#xff1a; 1. **iPhone设备的User-Agent**&#xff1a; Mozi…

github配置sshkey

使用命令生成sshkey ssh-keygen -t rsa -b 4096 -C "your_emailexample.com" 依此会要求输入以下信息&#xff0c;可以使用默认值 设置保存密钥的路径 设置SSH密钥密码&#xff08;备注&#xff1a;空内容表示不设置SSH密钥密码&#xff09; 再次确认SSH密钥密…

深入理解WebSocket接口:如何使用C++实现行情接口

在现代网络应用中&#xff0c;实时数据传输变得越来越重要。通过WebSocket&#xff0c;我们可以建立一个持久连接&#xff0c;让服务器和客户端之间进行双向通信。这种技术不仅可以提供更快的响应速度&#xff0c;还可以减少不必要的网络流量。本文将详细介绍如何使用C来实现We…

FFMPEG编码容错处理解决办法之途径----升级库文件

在qt开发环境下接收网络数据&#xff0c;调用ffmpeg解码播放视频&#xff0c;出现闪屏现象&#xff0c;具体现象可以使用操作系统自带的ffplay播放器播放原始视频流可复现&#xff1b;而使用操作系统自带的mpv播放器播放视频则不会出现闪屏&#xff1b;闪屏时会报Could not fin…

什么是超越编程(逾编程)(元编程?)

超越编程(逾编程)(元编程&#xff1f;)(meta-programming) 目录 1. meta- 的词源 2. 逾编程(meta-programming) 的直实含义 2.1 定义 2.2 说明 3. 翻译成“元编程”应该是一种错误 1. meta- 的词源 这是一个源自希腊语的构词元素&#xff0c;其有三种含义&#xff…