当前位置: 首页 > news >正文

【C++】构造函数初始化详解

0. 前篇

【C++】揭秘构造函数六大特性-CSDN博客

1. 再谈构造函数

1.1 构造函数体赋值 (Assignment) vs 初始化 (Initialization)

  • 核心区别:在构造函数体内使用=为成员变量赋值,这叫做赋值,而不是初始化。成员变量在进入函数体前其实已经被默认初始化了。

  • 关键点:初始化只能发生一次,而赋值可以进行多次。对于const成员、引用成员等必须初始化且不能再次赋值的类型,就无法在函数体内进行“初始化”操作。

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 初始化列表

为了解决上述问题,C++引入了初始化列表。它是真正的初始化。

  • 语法:在构造函数参数列表后以冒号:开始,用逗号,分隔,每个成员变量后跟用括号()包裹的初始值。

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. 类中包含以下成员,必须放在初始化列表位置进行初始化:

  • 引用成员变量

  • const成员变量

  • 没有默认构造函数的自定义类型成员 (编译器无法自己调用无参构造)

class A {
public:A(int a): _a(a) {} // A没有默认构造函数private:int _a;
};class B {
public:B(int aVal, int& refVal): _aObj(aVal) // 必须用初始化列表初始化A, _ref(refVal) // 必须用初始化列表初始化引用, _n(10) // 必须用初始化列表初始化const成员{}
private:A _aObj;int& _ref;const int _n;
};

3. 强烈建议:所有成员变量都应优先使用初始化列表进行初始化。对于内置类型(如int),初始化列表和函数体内赋值性能差别不大。但对于自定义类型,使用初始化列表通常效率更高,因为它直接调用拷贝构造函数初始化,省去了默认初始化再赋值的过程。

class Time
{
public:Time(int hour = 0):_hour(hour){cout << "Time()" << endl;}
private:int _hour;
};
class Date
{
public:Date(int day){}
private:int _day;Time _t;
};
int main()
{Date d(1);
}

1.3 成员变量的初始化顺序

  • 规则:成员变量的初始化顺序只取决于它们在类中声明的顺序,而与它们在初始化列表中的书写顺序无关。

  • 陷阱:如果初始化一个成员时用了另一个尚未初始化的成员,会导致未定义行为(通常是随机值)。

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 随机值

1.4 explicit 关键字

  • 作用:禁止编译器进行通过构造函数进行的隐式类型转换。

  • 使用场景:修饰单参构造函数或多参构造函数(但除第一个参数外都有默认值)。

class Date
{
public:// 1. 单参构造函数,没有使用explicit修饰,具有隐式类型转换作用Date(int year):_year(year){}private:int _year;int _month;int _day;
};void Test()
{Date d1(2022);// 用一个整形变量给日期类型对象赋值// 实际编译器背后会用2023构造一个无名对象,最后用无名对象给d1对象进行赋值// 这是一种隐式类型转换,构造出tmp(2022)再用tmp拷贝构造出d1(tmp)
}

如果使用 explicit 修饰构造函数,将会禁止构造函数的隐式转换:

class Date
{
public:// 1. 单参构造函数,没有使用explicit修饰,具有类型转换作用// explicit修饰构造函数,禁止类型转换---explicit去掉之后,代码可以通过编译explicit Date(int year):_year(year){}// 2. 虽然有多个参数,但是创建对象时后两个参数可以不传递,没有使用explicit修饰,具// 有类型转换作用// explicit修饰构造函数,禁止类型转换explicit Date(int year, int month = 1, int day = 1): _year(year), _month(month), _day(day){}Date& operator=(const Date& d){if (this != &d){_year = d._year;_month = d._month;_day = d._day;}return *this;}
private:int _year;int _month;int _day;
};
void Test()
{Date d1(2022);// 用一个整形变量给日期类型对象赋值// 实际编译器背后会用2023构造一个无名对象,最后用无名对象给d1对象进行赋值d1 = 2023;// 将1屏蔽掉,2放开时则编译失败,因为explicit修饰构造函数,禁止了单参构造函数类型转// 换的作用
}

补充:explicit关键字能提高代码的清晰度和安全性,避免意外的、难以察觉的类型转换。

2. static成员

2.1 概念与定义

  • 概念:被static修饰的类成员称为静态成员。它属于整个类,而不是某个具体的对象,所有对象共享同一份静态成员。

  • 定义规则:静态成员变量必须在类外进行定义和初始化(分配内存),定义时不再加static关键字。

比如:实现一个类,计算程序中创建出了多少个类对象

class A
{
public:A() { ++_scount; }A(const A& t) { ++_scount; }~A() { --_scount; }static int GetACount() { return _scount; }
private:static int _scount;    // 成员变量的声明
};
int A::_scount = 0;    // 成员变量的定义
void TestA()
{cout << A::GetACount() << endl;A a1, a2;A a3(a1);cout << A::GetACount() << endl;
}

2.2 特性与使用

1. 静态成员所有类对象所共享,不属于某个具体的对象,存放在静态区

2. 静态成员变量必须在类外定义,定义时不添加static关键字,类中只是声明

3. 访问方式:

  • 通过类名作用域访问(首选):A::GetACount()

  • 通过对象访问:a1.GetACount()

(ps:静态成员包括静态成员变量和静态成员函数。)

4. 静态成员函数:

  • 没有this指针,因此不能直接访问类的非静态成员(变量或函数)。

  • 可以访问静态成员变量和其他静态成员函数。

5. 静态成员也是类的成员,访问权限受public, protected, private访问限定符的限制。

所以:静态成员函数不能调用非静态成员变量和函数(因为没有this指针),而非静态成员函数可以调用静态成员变量

3. C++11的成员初始化新玩法

C++11 允许在类内声明非静态成员变量时直接为其指定一个缺省值。

  • 本质:这个缺省值的作用是提供给构造函数初始化列表的。如果构造函数的初始化列表没有显式初始化该成员,编译器就会使用这个类内缺省值来初始化它。

  • 注意:静态成员变量不享受此特性,它仍然必须在类外定义和初始化。

class Date
{
public:void Print() { /* ... */ }// 如果这样写构造函数:Date() {},则_year,_month,_day会使用下面的缺省值// 如果这样写:Date(int day) : _day(day) {},则_year和_month使用缺省值,_day使用参数值private:// C++11 才允许这样操作// 声明时给缺省值int _year = 0;int _month = 1;int _day = 1;// 静态成员变量不可以给缺省值,必须要在类外面定义static int _n;// static int _n = 10; // 错误!静态成员不能在类内初始化
};

C++11 类内初始化的好处:

  • 提高了代码的可读性,成员变量的默认值一目了然。

  • 减少了多个构造函数中重复的初始化代码。如果一个成员在大多数情况下初始值都一样,可以在类内指定缺省值,只在特殊的构造函数中覆盖它即可。

http://www.dtcms.com/a/359911.html

相关文章:

  • 漫谈《数字图像处理》之基函数与基图像
  • 分布式测试平台ITP:让自动化测试更高效、更稳定
  • IS-IS 与 OSPF 协议机制比较
  • 软考 系统架构设计师系列知识点之杂项集萃(138)
  • 【Proteus仿真】开关控制系列仿真——开关控制LED/拨码开关二进制计数/开关和继电器控制灯灭
  • Java试题-选择题(26)
  • zkML-JOLT——更快的ZK隐私机器学习:Sumcheck +Lookup
  • 【iOS】MVC架构
  • OpenCL C 内核(Kernel)
  • 在实践中学Java(中)面向对象
  • Elasticsearch vs Solr vs OpenSearch:搜索引擎方案对比与索引设计最佳实践
  • [光学原理与应用-353]:ZEMAX - 设置 - 可视化工具:2D视图、3D视图、实体模型三者的区别,以及如何设置光线的数量
  • 设计模式概述:为什么、是什么与如何应用
  • Ethers.js vs Wagmi 的差异
  • 如何利用AI IDE快速构建一个简易留言板系统
  • Playwright Python 教程:实战篇
  • 外贸服装跟单软件怎么选才高效?
  • C++ 迭代器的深度解析【C++每日一学】
  • 从零到一:使用anisble自动化搭建kubernetes集群
  • Openstack Eproxy 2025.1 安装指南
  • isat将标签转化为labelme格式后,labelme打不开的解决方案
  • IO_hw_8.29
  • TRELLIS:从多张图片生成3D模型
  • 【ACP】2025-最新-疑难题解析- 练习一汇总
  • Go学习1:常量、变量的命名
  • 一个投骰子赌大小的游戏
  • 内核等待队列以及用户态的类似机制
  • Chrome DevTools Performance 是优化前端性能的瑞士军刀
  • CD73.【C++ Dev】map和set练习题1(有效的括号、复杂链表的复制)
  • 嵌入式C学习笔记之编码规范