【C++指南】类和对象(二):类的默认成员函数——全面剖析 :构造函数

news2025/1/23 0:49:59

           💓 博客主页:倔强的石头的CSDN主页 

           📝Gitee主页:倔强的石头的gitee主页

            ⏩ 文章专栏:《C++指南》

                                  期待您的关注

 

47f09392526c71b5885ec838a3ea7ffe.gif

 

阅读本篇文章之前,你需要具备的前置知识:类和对象的基础

点击下方链接

【C++指南】类和对象(一):类和对象的定义和使用 基础讲解-CSDN博客

目录

引言

默认成员函数的介绍

深入解析C++类的构造函数

构造函数的概念

构造函数的特性

默认构造函数

编译器默认生成的构造函数的行为

 

需要自己实现构造函数的情况

  初始化列表

构造函数与析构函数的关系

结尾


 

引言

在C++编程中,类的设计是实现面向对象编程(OOP)理念的核心。类不仅封装了数据(即属性)和操作这些数据的方法(即成员函数),还通过特定的成员函数——默认成员函数,管理着对象的生命周期和状态变化。

这些默认成员函数,包括构造函数、析构函数、拷贝构造函数以及赋值运算符重载函数,是C++类设计中不可或缺的部分,它们定义了对象如何被创建、销毁、复制以及赋值。

 

  • 构造函数是对象生命周期的起点,负责初始化对象的内部状态。
  • 析构函数则标志着对象生命周期的结束,用于执行必要的清理工作,确保资源得到妥善管理。
  • 拷贝构造函数赋值运算符重载函数则与对象的复制行为紧密相关,它们定义了如何创建一个对象的副本以及如何将一个对象的状态复制到另一个对象上。 

 

理解并正确实现这些默认成员函数对于编写健壮、可维护的C++代码至关重要。它们不仅影响着对象的性能,还直接关系到程序的安全性和正确性。然而,这些函数的自动生成和默认行为往往无法满足所有情况的需求,特别是在涉及资源管理、动态内存分配或复杂数据结构时。因此,作为C++开发者,我们有必要深入了解这些默认成员函数的工作原理,学会在适当的时候自定义它们以满足特定的需求。

 

本文旨在详细讲解C++中类的默认成员函数,包括构造函数、析构函数、拷贝构造函数、赋值运算符重载函数以及取地址运算符重载函数的基本概念、使用场景、实现细节和注意事项。

通过本文的学习,读者将能够更加熟练地运用这些默认成员函数,编写出更加高效、安全、易于维护的C++代码。

 

注:

因默认构造函数内容较多,限于篇幅分成系列文章发布:

 

可点击下方链接阅读:

【C++指南】类和对象(三):类的默认成员函数——全面剖析析构函数-CSDN博客

【C++指南】类和对象(四):类的默认成员函数——全面剖析 拷贝构造函数-CSDN博客

【C++指南】类和对象(五):类的默认成员函数——全面剖析 赋值运算符重载函数-CSDN博客

 

 

默认成员函数的介绍

在C++中,当定义一个类时,编译器会自动为该类生成几个特殊的成员函数,如果开发者没有显式定义它们的话。这些函数被称为默认成员函数特殊成员函数。它们对于类的对象管理至关重要,包括对象的创建、销毁、复制以及赋值操作。

以下是几个关键的默认成员函数:

  1. 构造函数(Constructor):用于在创建对象时初始化对象。
  2. 析构函数(Destructor):用于在对象生命周期结束时执行清理工作。
  3. 拷贝构造函数(Copy Constructor):用于创建一个新对象,作为已存在对象的副本。
  4. 赋值运算符重载函数(Assignment Operator Overloading):用于实现对象之间的赋值操作。
  5. 取地址运算符重载函数:实际上,C++标准中并没有直接为取地址运算符(&)提供默认的重载机制,因为对象的地址总是由编译器自动处理。但理解何时需要重载其他运算符(如*,对于指针类)对于完整理解运算符重载是有帮助的。这里我们将重点放在前四个默认成员函数上。

 

类的默认成员函数虽然看起来复杂,但其实一点也不简单。 掌握默认成员函数对于后期的学习是非常重要的基础,想要理解透彻,除了默认成员函数本身的特性,还应该从两个方面入手:

  • 第⼀:我们不写时,编译器默认生成的函数行为是什么,是否满足我们的需求。
  • 第⼆:编译器默认⽣成的函数不满足我们的需求,我们需要自己实现,那么如何自己实现

 

深入解析C++类的构造函数

构造函数的概念

C++中的构造函数是一种特殊的成员函数,用于在创建对象时初始化对象的数据成员。构造函数的主要任务是在对象实例化时,根据提供的参数(如果有的话)来设置对象的初始状态。要注意构造函数的主要任务并 不是开空间创建对象(我们常使用的局部对象是栈帧创建时,空间就开好了),而是对象实例化时初始化。 对象构造函数的名字与类名完全相同,并且没有返回类型(连void也不允许)。

 

构造函数的特性

  1. 名字与类名相同:构造函数的名字必须与类名完全一致,包括大小写。
  2. 无返回类型:构造函数不能有任何返回类型,包括void
  3. 自动调用:在创建对象时,编译器会自动调用相应的构造函数。
  4. 可以重载:一个类可以有多个构造函数,只要它们的参数列表不同,就可以实现重载。
  5. 默认构造函数:如果程序员没有显式定义任何构造函数,编译器会自动生成一个默认的无参构造函数。但一旦定义了任何构造函数,编译器就不会再自动生成默认构造函数。
  6. 构造初始化列表:构造函数可以使用初始化列表来给成员变量赋值,这种方式比在构造函数体内赋值更高效。

默认构造函数

默认构造函数是没有参数或者所有参数都有默认值的构造函数。如果类中没有显式定义任何构造函数,编译器会自动生成一个默认的无参构造函数。

无参构造函数、全缺省构造函数、我们不写构造时编译器默认⽣成的构造函数,都叫做默认构造函 数。但是这三个函数有且只有⼀个存在,不能同时存在。

无参构造函数和全缺省构造函数虽然构成 函数重载,但是调⽤时会存在歧义。要注意很多人会认为默认构造函数是编译器默认⽣成那个叫 默认构造,实际上⽆参构造函数、全缺省构造函数也是默认构造,总结⼀下就是不传实参就可以调 ⽤的构造就叫默认构造

示例: 

class Date
{
public:
    // 1.无参构造函数

    /*Date()
    {
        _year = 1;
        _month = 1;
        _day = 1;
    }*/
     //2.全缺省构造函数

   Date(int year = 1, int month = 1, int day = 1)
   {
   _year = year;
   _month = month;
   _day = day;
   }
    // 3.带参构造函数

   // 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;
};

比如上述类的定义中:我们定义了三个构造函数,其中第一个、第二个以及不写构造函数时编译器默认生成的构造函数,都属于默认构造函数

一般情况下,建议写一个全缺省的构造函数,这样就可以应对各种传参的情况

第三个构造函数——带参的构造函数,就属于正常的构造函数 

 

再次强调:

无参构造函数、全缺省构造函数、我们不写构造时编译器默认⽣成的构造函数,这三个函数有且只有⼀个存在,不能同时存在。

当自己实现了构造函数之后,编译器就不会生成默认构造函数,而且无参构造函数和全缺省构造函数虽然构成 函数重载,但是调用时会存在歧义,两个构造函数只能存在一个,否则就会报错。

 

编译器默认生成的构造函数的行为

我们不写,编译器默认⽣成的构造

  • 对内置类型成员变量的初始化没有要求,也就是说是否初始 化是不确定的,看编译器。
  • 对于⾃定义类型成员变量,要求调⽤这个成员变量的默认构造函数初始化。如果这个成员变量,没有默认构造函数,那么就会报错,我们要初始化这个成员变量,需要⽤ 初始化列表才能解决

 

需要自己实现构造函数的情况

结论: 大多数情况下,构造函数都需要自己实现

比如:

  1. 类中存在内置类型,需要在构造时初始化赋值(这种情况多数存在)
  2. 类初始化需要申请资源,比如通过指针指向一块动态申请的空间

少数情况下不需要写构造函数:

比如:当类中的成员变量全部为自定义类型(类类型),会自动调用成员变量的默认构造函数,这时不需要自己实现构造函数

 

  初始化列表

构造函数初始化列表是构造函数体执行之前执行的一部分,用于初始化成员变量。它使用冒号:分隔参数列表和成员初始化列表。使用初始化列表比在构造函数体内赋值通常更高效,因为它直接调用成员的构造函数(如果有的话)。

MyClass(int x) : member(x) {} // 使用初始化列表初始化member
class ClassName {  
public:  
    ClassName(parameters) : member1(initializer1), member2(initializer2), ... {  
        // 构造函数体  
    }  
  
private:  
    MemberType1 member1;  
    MemberType2 member2;  
    // 其他成员变量  
};

 构造函数初始化列表的规则

  • 语法理解上初始化列表可以认为是每个成员变量定义 初始化的地方
  • 引⽤成员变量,const成员变量,没有默认构造的类类型变量,必须放在初始化列表位置进⾏初始 化,否则会编译报错
  1. 对于常量成员变量,它们必须在构造函数的初始化列表中初始化,因为常量一旦被定义就不能被修改。
  2. 对于引用成员变量,它们也必须在初始化列表中初始化,因为引用一旦被定义就必须指向一个有效的对象。
  3. 对于某些类型的对象(如没有默认构造函数的类类型对象),它们可能需要在初始化列表中通过特定的值或另一个对象的拷贝来初始化。
  • C++11⽀持在成员变量声明的位置给缺省值,这个缺省值主要是给没有显⽰在初始化列表初始化的 成员使⽤的
  • 初始化列表中按照成员变量在类中声明顺序进⾏初始化,跟成员在初始化列表出现的的先后顺序⽆ 关。建议声明顺序和初始化列表顺序保持⼀致

 

初始化列表的使用示例

#include <iostream>  
#include <string>  
  
// 定义一个简单的类Point,表示二维空间中的一个点  
class Point {  
private:  
    int x; // x坐标  
    int y; // y坐标  
  
public:  
    // 构造函数使用初始化列表来初始化成员变量x和y  
    Point(int xVal, int yVal) : x(xVal), y(yVal) {  
        // 构造函数体可以为空,因为成员变量已经在初始化列表中初始化了  
        std::cout << "Point constructed at (" << x << ", " << y << ")" << std::endl;  
    }  
  
    // 一个方法来打印点的坐标  
    void print() const {  
        std::cout << "Point is at (" << x << ", " << y << ")" << std::endl;  
    }  
};  
  
int main() {  
    // 创建一个Point对象,并使用初始化列表来设置x和y的值  
    Point p1(5, 10);  
  
    // 调用print方法来打印点的坐标  
    p1.print();  
  
    return 0;  
}

 

 

构造函数的执行顺序参考图

a3d80ef6151a459ba196beda1feb8936.png

 

构造函数与析构函数的关系

  • 构造函数和析构函数是对象生命周期的两端。
  • 构造函数负责初始化对象,而析构函数负责清理对象所占用的资源。
  • 一旦对象被创建,其构造函数就会被调用一次,并且在对象的整个生命周期内不会被再次调用。
  • 而析构函数则在对象的生命周期结束时被调用,确保所有资源得到释放,避免内存泄漏等问题。

 

结尾

构造函数是C++面向对象编程中的核心概念之一,它决定了对象如何被初始化和配置。了解构造函数的工作原理、类型、初始化列表以及其与析构函数的关系,对于编写高效、安全、易于维护的C++代码至关重要。通过合理利用构造函数,我们可以更加灵活地控制对象的创建和初始化过程,为程序的稳定性和性能打下坚实的基础

 

 

 

 

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

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

相关文章

Stable Diffusion的核心插件—ControlNet!万字长文解读!

前言 一、介绍 论文地址&#xff1a;[https://arxiv.org/abs/2302.05543](https://arxiv.org/abs/2302.05543 "https://arxiv.org/abs/2302.05543")代码地址&#xff1a;[GitHub - lllyasviel/ControlNet: Let us control diffusion models!](https://github.com/ll…

Python和C++胶体粒子三维残差算法模型和细化亚像素算法

&#x1f3af;要点 使用信噪比、对比度噪声比和点扩展函数量化实验数据&#xff0c;增强共聚焦显微镜成像。参考粒子跟踪算法&#xff1a;使用二维和三维径向模型细化亚像素。胶体粒子三维图形分割学习模型模拟检测球形胶体。使用网格搜索优化模型和归一化处理以避免光漂白。 …

idear 控制台乱码、查询结果、参数、控制台乱码

一、 -Dfile.encodingUTF-8 二、 三、 四、 五、

【区间选点】

题目 代码 左端点排序区间合并 #include <bits/stdc.h> using namespace std; #define null -0x3f3f3f3f #define x first #define y second typedef pair<int, int> PII; const int N 1e510; vector<PII> v; int main() {int n;cin >> n;for(int i…

LLM大模型企业应用实战-“消灭”LLM幻觉的利器

大模型一定程度改变了我们生活工作的思考方式&#xff0c;越来越多的个人和企业在思考如何将大模型应用到更加实际的生产生活。 1 LLM的问题 1.1 幻觉 LLM因为是一个预训练模型&#xff0c;它已有一些知识储备&#xff0c;我们提的问题跟他的知识储备不相符时&#xff0c;会…

当管理遇上AI,工作效率翻了3倍!

最近这段时间&#xff0c;很多企业都开始降薪、裁员。 在降本增效的大背景下&#xff0c;企业但凡有什么大动作&#xff0c;压力往往都会转嫁到管理者的身上。 一方面&#xff0c;要调大家的状态&#xff0c;处理团队中的各种琐事&#xff1b;另一方面&#xff0c;要及时响应…

C++ : STL容器之string剖析

STL容器之string剖析 一、string 的迭代器&#xff08;一&#xff09;起始迭代器&#xff08;二&#xff09;末尾迭代器&#xff08;三&#xff09;反向迭代器 二、容量相关的函数&#xff08;一&#xff09;size&#xff08;二&#xff09;capacity&#xff08;三&#xff09;…

Unity3d使用JsonUtility.FromJson读取json文件

使用JsonUtility.FromJson方法不需要额外引用第三方库。该方法只能读取json对象&#xff0c;而不能读取json数组。 假如我们有如下的json数组&#xff1a; [ {"id":1, "name":"first2021", "level":5, "score":100, "…

cefsharp129.0.110(CEF 129.0.11,Chromium 129.0.6668.90)版本体验

一、依赖包 1.1 CefSharp.WinForms CefSharp.WinForms .NET Framework:net462 net462 is compatible. net463 net463 was computed. net47 net47 was computed. net471 net471 was computed. net472 net472 was computed. net48 net48 was computed. net481 1.2 CefSharp.…

JAVA思维提升

利用java做一个双色球彩票系统 要求 package ZY; import java.util.Random; import java.util.Scanner; public class Test9双色球 { //目标&#xff1a;模拟双色球//规则投注号码由6个红色球号码和1个蓝色球号码组成。红色球号码从1-33中选择;蓝色球号码从1-16中选择。publi…

springboot网站开发-解决web前端请求的一个错误

springboot网站开发-解决web前端请求的一个错误&#xff01;近期在开发网站时&#xff0c;web页面&#xff0c;点击金币结算按钮时&#xff0c;一直报错&#xff0c;说金币余额不足。经过使用浏览器自带的js调试插件&#xff0c;一步一步跟踪调试&#xff0c;发现了原因。是漏写…

程计软题3-编译程序基本原理

(一)词法分析的工具-正规式 题1-语句找表达式 1. 排除法 对于A&#xff1a;a*取0个a&#xff0c;&#xff08;a&#xff09;*取1个a,b*取b,A例可取ab,不满足题目 对于C:取ba*取b,a*取1个a&#xff0c;&#xff08;b&#xff09;*,取ab&#xff0c;不满足题目 对于D:&#xff…

360桌面助手意见反馈

360桌面助手收纳盒和映射文件夹互转需求 现有方式 收纳盒 桌面新建收纳盒后, 可以在收纳盒内, 增加文件 映射文件夹 首先磁盘新建文件夹, 然后复制文件夹路径, 然后桌面右键, 新建映射文件夹, 把路径放进去, 实现闭合 存在的问题 收纳盒问题 桌面收纳盒过多后, 直接解散…

ArcGIS实战——一文教会你调整适合中国宝宝体质的标准地图投影参数

各位要出图的小伙伴有没有遇到一个问题&#xff0c;当我们的研究区域是中国时&#xff0c;使用常见的WGS1984投影&#xff0c;会让我们的中国地图看起来不够挺拔&#xff0c;投影使得左右的拉伸比较长&#xff0c;进而让我们的中国地图变得不够美观。就像下面这样子&#xff0c…

stable diffusion各种插件及模型、教程

stable diffusion各种插件及模型、教程 下载地址&#xff1a;https://s.tb.cn/c.0Efdko

31 基于51单片机的水位监测系统仿真

目录 一、主要功能 二、硬件资源 三、程序编程 四、实现现象 一、主要功能 基于51单片机&#xff0c;DHT11温湿度检测&#xff0c;水位检测&#xff0c;通过LCD1602显示&#xff0c;超过阈值报警&#xff0c;继电器驱动电机转动。通过矩阵按键切换选择设置各项参数阈值。 …

手机地址IP显示不对?别急,这里有解决方案

在当今的数字化生活中&#xff0c;手机已成为我们连接世界的重要工具。而手机的IP地址&#xff0c;作为我们在网络上的“身份证”&#xff0c;其准确性对于网络体验至关重要。然而&#xff0c;有时我们可能会遇到手机IP地址显示不正确的问题&#xff0c;这不仅会影响网络连接质…

《数据结构》学习系列——树

系列文章目录 目录 树的基本概念树的定义树的特点树的相关术语度层数高度路径二叉树定义特点定理满二叉树定义特点完全二叉树定义特点二叉树的存储结构顺序存储结点结构优点缺点 链式存储 结点结构三叉链表表示法算法搜索结点的父结点搜索符合数据域条件的结点删除给定结点及其…

【Python】物流行业数据分析与可视化案例

一&#xff0c;前言 在本文中&#xff0c;我将使用python语言基于Jupyter notebook对这一份物流行业数据集进行多维度数据分析&#xff0c;文章内容参考自b站马士兵《数据分析五大经典实战项目》教学视频&#xff0c;并对其中一些操作做出优化。 数据集下载地址&#xff1a;物流…

数组与集合的应用-数组演练

1、获取一维数组最小值 1.1 实例说明 一维数组常用于保存线性数据&#xff0c;例如数据库中的单行数据就可以使用一维数组保存。本实例接收用户在文本框中输入的单行数据&#xff0c;其中数据都是整数数字&#xff0c;以不同数量的空格分割数字&#xff0c;如图1所示。这个先行…