【C++】类的默认成员函数:深入剖析与应用(上)

😀在上一篇文章中我们初步了解了C++的基础概念,现在我们进行对C++类的默认成员函数进行更加深入的理解!

👉【C++新手入门指南:从基础概念到实践之路】 


目录

💯前言 

💯构造函数

一、构造函数的定义与特性

1.定义

2.特性

二、构造函数的种类

1.默认构造函数

2.带参数的构造函数

💯拷贝构造函数

⭐概念

⭐特征

💯析构函数

⭐概念 

⭐特性

💯总结


💯前言 

在 C++ 的面向对象编程世界中,类的默认成员函数犹如构建程序大厦的基石,发挥着至关重要的作用。它们分别是构造函数析构函数拷贝构造函数赋值运算符重载const成员函数以及取地址及const取地址操作符重载。 

深入理解和掌握这些默认成员函数,对于每一位 C++ 开发者来说都至关重要。


💯构造函数

 在解析这个概念之前,我们引用一段代码👇

#include <iostream>

class Date
{
public:
    void Init(int year, int month, int day)
    {
        _year = year;
        _month = month;
        _day = day;
    }

    void Print()
    {
        std::cout << _year << "-" << _month << "-" << _day << std::endl;
    }

private:
    int _year;
    int _month;
    int _day;
};

int main()
{
    Date d1;
    d1.Init(2022, 7, 5);
    d1.Print();

    Date d2;
    d2.Init(2022, 7, 6);
    d2.Print();

    return 0;
}

“对于Date类,可以通过 Init 公有方法给对象设置日期,但如果每次创建对象时都调用该方法设置信息,未免有点麻烦,那能否在对象创建时,就将信息设置进去呢?

一、构造函数的定义与特性

1.定义

构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,以保证每个数据成员都有 一个合适的初始值,并且在对象整个生命周期内只调用一次。  

2.特性


构造函数是特殊的成员函数,需要注意的是,构造函数虽然名称叫构造,但是构造函数的主要任
并不是开空间创建对象,而是初始化对象 通过构造函数,可以为对象的数据成员赋予初始值,确保对象在诞生之时就处于一个合理的状态

 😕例如:

当我们定义一个表示学生信息的类时,可以在构造函数中为学生的姓名年龄等属性赋予初始值,这样在创建学生对象时,就不必再单独为每个属性进行赋值操作,提高了代码的简洁性和可读性。

构造函数的特性:

  1. 函数名与类名相同。
  2. 无返回值。
  3. 对象实例化时编译器自动调用对应的构造函数。
  4. 构造函数可以重载。
    #include <iostream>
    
    class MyClass {
    public:
        int value;
    
        // 默认构造函数
        MyClass() {
            value = 0;
            std::cout << "默认构造函数被调用。" << std::endl;
        }
    
        // 带参数的构造函数,实现了构造函数的重载
        MyClass(int val) : value(val) {
            std::cout << "带参数的构造函数被调用,值为:" << value << std::endl;
        }
    };
    
    int main() {
        // 对象实例化时编译器自动调用对应的构造函数
        MyClass obj1; // 调用默认构造函数
        MyClass obj2(42); // 调用带参数的构造函数
    
        return 0;
    }
  5. 如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成。
    #include <iostream>
    
    class Person {
    public:
        std::string name;
        int age;
    
        // 如果没有显式定义构造函数,编译器自动生成默认构造函数
        // Person() {
        //     std::cout << "编译器自动生成的默认构造函数被调用。" << std::endl;
        //     name = "Unknown";
        //     age = 0;
        // }
    };
    
    class AnotherPerson {
    public:
        std::string name;
        int age;
    
        AnotherPerson() {
            std::cout << "显式定义的默认构造函数被调用。" << std::endl;
            name = "Another Unknown";
            age = 0;
        }
    };
    
    int main() {
        // 对于没有显式定义构造函数的类 Person,编译器自动生成默认构造函数
        Person p1;
        std::cout << "Person 的名字:" << p1.name << ", 年龄:" << p1.age << std::endl;
    
        // 对于显式定义了构造函数的类 AnotherPerson
        AnotherPerson ap1;
        std::cout << "AnotherPerson 的名字:" << ap1.name << ", 年龄:" << ap1.age << std::endl;
    
        return 0;
    }

  6. 无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认构造函数。
    #include <iostream>
    
    class MyClass {
    public:
        int value;
    
        // 无参构造函数
        MyClass() {
            value = 0;
            std::cout << "无参构造函数被调用。" << std::endl;
        }
    
        // 全缺省构造函数(也被认为是默认构造函数,但不能与无参构造函数同时存在)
        // MyClass(int v = 0) {
        //     value = v;
        //     std::cout << "全缺省构造函数被调用。" << std::endl;
        // }
    };
    
    int main() {
        // 调用无参构造函数创建对象
        MyClass obj1;
        std::cout << "obj1 的值:" << obj1.value << std::endl;
    
        // 如果存在全缺省构造函数,可以这样创建对象
        // MyClass obj2;
        // std::cout << "obj2 的值:" << obj2.value << std::endl;
    
        return 0;
    }


二、构造函数的种类

1.默认构造函数

默认构造函数是在没有显式定义任何构造函数时,由编译器自动生成的构造函数。它没有参数,或者所有参数都有默认值。默认构造函数的作用是在创建对象时,为对象的数据成员赋予默认值。

class Student {
public:
    string name;
    int age;
    Student() {
        name = "Unknown";
        age = 0;
    }
};

在这个例子中,Student()就是默认构造函数,它将学生的姓名初始化为 “Unknown”,年龄初始化为 0。

2.带参数的构造函数

带参数的构造函数可以接受一个或多个参数,用于在创建对象时为数据成员赋予特定的值。这种构造函数可以提高代码的灵活性,允许程序员根据不同的情况创建对象。

class Student {
public:
    string name;
    int age;
    Student(string n, int a) {
        name = n;
        age = a;
    }
};

在这个例子中,Student(string n, int a)是带参数的构造函数,可以通过传入学生的姓名和年龄来创建学生对象。

 😕例如:

#include <iostream>
#include <string>

class Student {
public:
    std::string name;
    int age;

    Student() {
        name = "Unknown";
        age = 0;
    }

    Student(std::string n, int a) : name(n), age(a) {}
};

int main() {
    Student s1;
    std::cout << "Default constructor: " << s1.name << ", " << s1.age << std::endl;

    Student s2("Tom", 18);
    std::cout << "Parameterized constructor: " << s2.name << ", " << s2.age << std::endl;

    return 0;
}

 代码结果如下:


💯拷贝构造函数

⭐概念


在现实生活中,可能存在一个与你一样的自己,我们称其为双胞胎。

🌚"那在创建对象时,可否创建一个与已存在对象一某一样的新对象呢?"

拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存
在的类类型对象创建新对象时由编译器自动调用
。 

⭐特征

拷贝构造函数也是特殊的成员函数,其特征如下:

  1.  拷贝构造函数是构造函数的一个重载形式
  2. 拷贝构造函数的参数只有一个必须是类类型对象的引用,使用传值方式编译器直接报错
    因为会引发无穷递归调用。
    #include <iostream>
    
    class Date
    {
    public:
        // 构造函数,可以接受三个参数,也可以不接受参数,此时使用默认值
        Date(int year = 1900, int month = 1, int day = 1)
        {
            _year = year;  // 初始化成员变量_year为传入的年份或默认值1900
            _month = month;  // 初始化成员变量_month为传入的月份或默认值1
            _day = day;  // 初始化成员变量_day为传入的日期或默认值1
        }
    
        // 拷贝构造函数,正确写法
        Date(const Date& d) 
        {
            _year = d._year;  // 将传入对象的年份赋值给当前对象的年份
            _month = d._month;  // 将传入对象的月份赋值给当前对象的月份
            _day = d._day;  // 将传入对象的日期赋值给当前对象的日期
        }
        // 错误写法:会引发无穷递归
        /*Date(const Date& d)
        {
            // 这里如果再次调用拷贝构造函数,就会形成无穷递归
            Date temp(d);
            _year = temp._year;
            _month = temp._month;
            _day = temp._day;
        }*/
    
    private:
        int _year;  // 存储年份
        int _month;  // 存储月份
        int _day;  // 存储日期
    };
    
    int main()
    {
        // 创建一个使用默认值初始化的 Date 对象 d1
        Date d1;
        // 使用拷贝构造函数,以 d1 为蓝本创建一个新的 Date 对象 d2
        Date d2(d1);
        return 0;
    }

  3. 若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按
    字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。
    #include <iostream>
    
    class MyClass {
    public:
        int* data;
    
        MyClass() {
            data = new int(10);
        }
    
        // 默认拷贝构造函数,由编译器生成(浅拷贝)
        // MyClass(const MyClass& other) : data(other.data) {}
    };
    
    int main() {
        MyClass obj1;
        MyClass obj2 = obj1;
    
        // 修改 obj2 的数据,会影响 obj1 的数据,因为它们指向同一块内存
        *(obj2.data) = 20;
    
        std::cout << "obj1 的数据:" << *(obj1.data) << std::endl;
        std::cout << "obj2 的数据:" << *(obj2.data) << std::endl;
    
        return 0;
    }

    在这个例子中,没有显式定义拷贝构造函数,编译器会生成默认的拷贝构造函数进行浅拷贝。对象obj2通过默认拷贝构造函数从obj1复制而来,但是它们的成员指针data指向了同一块内存地址。当修改obj2的数据时,obj1的数据也会被改变,因为它们实际上共享同一块内存空间,这就是浅拷贝的行为。如果要避免这种情况:需要显式定义一个深拷贝的拷贝构造函数,在其中为新对象分配独立的内存空间并复制数据。


💯析构函数

概念 

通过前面构造函数的学习,我们知道一个对象是怎么来的,😮“那一个对象又是怎么没呢的?”

析构函数:与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。 

特性

析构函数是特殊的成员函数,其特征如下:

  1.  析构函数名是在类名前加上字符 ~。
  2.  无参数无返回值类型。
  3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构函数不能重载。
  4.  对象生命周期结束时,C++编译系统系统自动调用析构函数。
    #include <iostream>
    
    class MyClass {
    public:
        MyClass() {
            std::cout << "构造函数被调用。" << std::endl;
        }
    
        ~MyClass() {
            std::cout << "析构函数被调用。" << std::endl;
        }
    };
    
    int main() {
        {
            MyClass obj;
        }
        std::cout << "离开局部作用域。" << std::endl;
        return 0;
    }

  5. “😦关于编译器自动生成的析构函数,是否会完成一些事情呢?”下面的程序我们会看到,编译器生成的默认析构函数,对自定类型成员调用它的析构函数。
    #include <iostream>
    
    class SomeResource {
    public:
        SomeResource() {
            std::cout << "SomeResource 构造函数被调用。" << std::endl;
        }
    
        ~SomeResource() {
            std::cout << "SomeResource 析构函数被调用。" << std::endl;
        }
    };
    
    class MyClass {
    public:
        MyClass() {
            std::cout << "MyClass 构造函数被调用。" << std::endl;
        }
    
        ~MyClass() {
            std::cout << "MyClass 析构函数被调用。" << std::endl;
        }
    
    private:
        SomeResource resource;
    };
    
    int main() {
        MyClass obj;
        std::cout << "程序继续执行..." << std::endl;
        return 0;
    }

    😎析构函数会对自定义类型的成员调用它们的析构函数,以正确地清理资源。

  6. 如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数,比如:Date类;有资源申请时,一定要写,否则会造成资源泄漏,比如Stack类。
    #include <iostream>
    
    // 没有申请资源的类 Date
    class Date {
    public:
        int year;
        int month;
        int day;
    
        Date(int y, int m, int d) : year(y), month(m), day(d) {}
    };
    
    // 申请资源的类 Stack(这里只是简单模拟,实际的栈通常更复杂)
    class Stack {
    public:
        int* data;
        int top;
        int capacity;
    
        Stack(int size) {
            data = new int[size];
            top = -1;
            capacity = size;
        }
    
        ~Stack() {
            // 释放申请的资源
            delete[] data;
            std::cout << "Stack 析构函数被调用,资源已释放。" << std::endl;
        }
    };
    
    int main() {
        // Date 类可以直接使用默认析构函数,无需显式编写
        Date date(2023, 10, 19);
    
        // Stack 类必须显式编写析构函数以避免资源泄漏
        Stack stack(10);
    
        return 0;
    }


💯总结

以上就是本文的全部内容了,本文讲解了C++ 类的默认成员函数的前半部分内容,如果你渴望深入了解 C++ 类的内部机制,如果你追求更高水平的编程技艺,那么千万不要错过 C++ 类的默认成员函数(下)。😜持续关注,一同开启这场充满挑战与惊喜的编程之旅吧!👉【A charmer】

😎让我们在 C++ 的海洋中继续乘风破浪,探索无限可能!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

常见TCP/IP协议基础——计算机网络

目录 前言常见协议基础常见协议-基于TCP的应用层协议常见协议-基于UDP的应用层协议常见协议-网络层协议习题自测1.邮件发送协议2.接收邮件协议端口3.建立连接4.层次对应关系5.FTP服务器端口 前言 本笔记为备考软件设计师时的重点知识点笔记&#xff0c;关于常见TCP/IP协议基础…

【飞腾加固服务器】全国产化解决方案:飞腾FT2000+/64核,赋能关键任务保驾护航

在信息安全和自主可控的时代背景下&#xff0c;国产化设备的需求与日俱增&#xff0c;尤其是在国防、航空航天、能源和其他关键行业。高可靠性和极端环境设计的国产加固服务器&#xff0c;搭载强大的飞腾FT2000/64核处理器&#xff0c;全面满足国产自主可控的严苛要求。 性能强…

Python案例小练习——小计算器

文章目录 前言一、代码展示二、运行展示 前言 这是用python实现一个简单的计器。 一、代码展示 def calculate(num1, op, num2):if op "":return float(num1) float(num2)elif op "-":return float(num1) - float(num2)elif op "*":return…

stable diffusion安装ai绘画真人动漫win中文版软件

前言 所有的AI设计工具&#xff0c;安装包、模型和插件&#xff0c;都已经整理好了&#xff0c;&#x1f447;获取~ Stable Diffusion&#xff08;简称SD&#xff09;&#xff0c;是通过数学算法实现文本输入&#xff0c;图像输出的开源软件&#xff01; 引用维基百科&#x…

expect工具

一.expect工具介绍 在写脚本的过程当中不可避免的需要去写交互式命令 那么如何让交互式命令在脚本中自动执行&#xff1f; 使用expect工具 作用&#xff1a;捕获交互式的输出&#xff0c;自动执行交互式命令 如上图所示&#xff0c;可以使用expect工具去捕获交互式命令的提…

什么是大数据分析:定义、优缺点、应用、机遇和风险

大数据分析的概念已经成为我们社会不可或缺的一部分。众多公司和机构已经开发了大数据应用程序&#xff0c;取得了不同程度的成功。社交媒体平台和传感器等技术正在以前所未有的速度生成数据&#xff0c;就像一条装配线。如今&#xff0c;几乎所有东西都是物联网的一部分&#…

[Xshell] Xshell的下载安装使用及连接linux过程 详解(附下载链接)

前言 Xshell.zip 链接&#xff1a;https://pan.quark.cn/s/5d9d1836fafc 提取码&#xff1a;SPn7 安装 下载后解压得到文件 安装路径不要有中文 打开文件 注意&#xff01;360等软件会拦截创建注册表的行为&#xff0c;需要全部允许、同意。或者退出360以后再安装。 在“绿化…

vscode pylance怎么识别通过sys.path.append引入的库

问题 假如我有一个Python项目 - root_path -- moduleA ---- fileA.py -- moduleB ---- fileB.py# fileAimport sys sys.path.append(moduleB)import fileB # vscode pylance找不到&#xff0c;因为sys.path.append(moduleB)是动态添加的print(fileB)结果 代码正常运行但是vs…

【北京迅为】《STM32MP157开发板嵌入式开发指南》- 第五十四章 Pinctrl 子系统和 GPIO 子系统

iTOP-STM32MP157开发板采用ST推出的双核cortex-A7单核cortex-M4异构处理器&#xff0c;既可用Linux、又可以用于STM32单片机开发。开发板采用核心板底板结构&#xff0c;主频650M、1G内存、8G存储&#xff0c;核心板采用工业级板对板连接器&#xff0c;高可靠&#xff0c;牢固耐…

基于百度智能体开发爱情三十六计

基于百度智能体开发爱情三十六计 文章目录 基于百度智能体开发爱情三十六计1. 爱情三十六计智能体2. 三十六计开发创意3. 智能体开发实践3.1 基础配置3.2 进阶配置3.3 调优心得3.4可能会遇到的问题 4. 为什么选择文心智能体平台 1. 爱情三十六计智能体 爱情三十六计 是一款基于…

Kaggle竞赛——森林覆盖类型分类

目录 1. 竞赛简要2. 数据分析2.1 特征类型统计2.2 四个荒野区域数据分析2.3 连续特征分析2.4 离散特征分析2.5 特征相关性热图2.6 特征间的散点关系图 3. 特征工程3.1 特征组合3.2 连续特征标准化 4. 模型搭建4.1 模型定义4.2 绘制混淆矩阵和ROC曲线4.3 模型对比与选择 5. 测试…

从0-1实战演练后台管理系统 (3)还在寻找优秀的后台管理系统?Pure Admin 源码及目录结构带你一探究竟!

一、获取源码: 从-gitee-上拉取从 Gitee 上拉取 1、完整版前端代码 git clone https://gitee.com/yiming_chang/vue-pure-admin.git2、国际化精简版前端代码 git clone -b i18n https://gitee.com/yiming_chang/pure-admin-thin.git3、非国际化精简版前端代码 git clone ht…

【Vue】Vue扫盲(七)如何使用Vue脚手架进行模块化开发及遇到的问题(cmd中无法识别vue命令、vue init webpack 命令执行失败)

上篇文章&#xff1a; Vue】Vue扫盲&#xff08;六&#xff09;关于 Vue 项目运行以及文件关系和关联的详细介绍 文章目录 一、安装 相关工具二、处理相关问题问题一&#xff1a;vue -v 提示 vue不是内部或外部命令&#xff0c;也不是可运行的程序或批处理文件。问题二&#xf…

wifi、热点密码破解 - python

乐子脚本&#xff0c;有点小慢&#xff0c;试过多线程&#xff0c;系统 wifi 连接太慢了&#xff0c;需要时间确认&#xff0c;多线程的话系统根本反应不过来。 也就可以试试破解别人的热点&#xff0c;一般都是 123456 这样的傻鸟口令 # coding:utf-8 import pywifi from pyw…

el-table修改指定列字体颜色 ,覆盖划过行的高亮显示文字颜色

修改指定列字体颜色 ,覆盖划过行的高亮显示文字颜色 代码如下&#xff1a; <div class"c1"><el-table:data"tableData"striperow-class-name"custom-table-row"style"width:100%"cell-mouse-enter"lightFn"cell-…

Android开发 Camera2(最全代码Camera2开发)

介绍 google已经在Android5.1之后取消了对Camera1的更新,转而提供了功能更加强大的Camera2.虽然新版本依然可以使用Camera1但是,不管是各种机型适配还是拍照参数自定义都是很鸡肋的.跟上最新的技术了解Camera2是必要的.关于Camera2的兼容一般是支持API22之后包括API22的Androi…

Flink时间语义和时间窗口

前言 在实际的流计算业务场景中&#xff0c;我们会发现&#xff0c;数据和数据的计算往往都和时间具有相关性。 举几个例子&#xff1a; 直播间右上角通常会显示观看直播的人数&#xff0c;并且这个数字每隔一段时间就会更新一次&#xff0c;比如10秒。电商平台的商品列表&a…

【大数据技术基础 | 实验一】配置SSH免密登录

文章目录 一、实验目的二、实验要求三、实验原理&#xff08;一&#xff09;大数据实验一体机&#xff08;二&#xff09;SSH免密认证 四、实验环境五、实验内容和步骤&#xff08;一&#xff09;搭建集群服务器&#xff08;二&#xff09;添加域名映射&#xff08;三&#xff…

基于SpringBoot+Vue+MySQL的智慧博物馆管理系统

系统展示 用户前台界面 管理员后台界面 系统背景 随着信息技术的飞速发展&#xff0c;智慧化已成为博物馆发展的新趋势。然而&#xff0c;当前许多博物馆仍面临着预约困难、参观体验不佳等问题&#xff0c;严重影响了博物馆的服务质量和公众形象。传统的预约和票务管理方式已难…

mac安装brew时踩坑解决方案

安装包 mac上如果按照git等工具可能会使用brew&#xff0c;例如使用&#xff1a;$ brew install git命令&#xff0c;如果电脑没有按照brew&#xff0c;则会提示&#xff1a;zsh: command not found: brew 解决方案 需要我们打开brew的官网https://brew.sh/&#xff0c;复制…