【C++】类与对象(二)特殊成员函数

在这里插入图片描述

前言
类与对象(二)


文章目录

  • 一、特殊成员函数
  • 二、构造函数
  • 三、析构函数
  • 四、拷贝构造函数
  • 五、拷贝赋值运算符

一、特殊成员函数

如果在类的声明中未显式提供某个成员函数的定义,编译器会自动生成一个默认实现。 这包括默认构造函数、默认析构函数、默认拷贝构造函数、默认拷贝赋值运算符以及默认移动构造函数和移动赋值运算符。

我们主要将讲解一下构造函数,析构函数,拷贝构造函数和默认拷贝赋值运算符。
在这里插入图片描述

二、构造函数

构造函数用来初始化对象的成员变量

主要特性:

  1. 与类同名

  2. 没有返回类型

  3. 在对象创建时自动调用

  4. 可以重载

    class Date{
    public:
        // 1.无参构造函数
        Date(){
            _year = 0;
            _month = 0;
            _day = 0;
        }
    
        // 2.带参构造函数
        Date(int year, int month, int day){
            _year = year;
            _month = month;
            _day = day;
        }
    private:
        int _year;
        int _month;
        int _day;
    };
    
    int main() {
        Date d1; // 调用无参构造函数,不需要跟括号
        Date d2(2015, 1, 1); // 调用带参的构造函数
    
        // 这不是在创建一个对象,而是声明一个函数 d3,该函数没有参数并返回一个
        //Date d3();  warning C4930 : “Date d3(void)” : 未调用原型函数(是否是有意用变量定义的 ? )
    
        return 0;
    }
    
  5. 类中没有显式定义构造函数,则C++编译器自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成。

    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”: 没有合适的默认构造函数可用
        Date d1;
        return 0;
    }
    
  6. 编译器在遇到内置类型时,自动生成的默认构造函数(没有显式定义构造函数的情况下,这符合第五点)不对其初始化;遇到自定义类型时调用该类型中的显式定义构造函数,如果没有,也会像内置类型那样,自动生成默认构造函数并调用,但不对内置类型初始化。

    对于内置类型,编译器生成的默认构造函数通常不包含任何实际的初始化代码,这意味着内置类型的成员变量将包含未定义的值,即取决于存储它们的内存的初始状态(内置类型的成员变量是在栈上或堆上分配内存的,而这块内存的初始值是未定义的,即它们可能包含任意的数值)。

    对于自定义类型:

    1. 如果类中没有任何构造函数,编译器会生成一个默认构造函数,对所有成员变量执行它们各自的默认构造函数。对于基本数据类型成员,执行与内置类型相同的处理,即保留未初始化的值。

    2. 如果类显式声明了其他构造函数(无论是默认构造函数还是带参数的构造函数),编译器将不再生成默认构造函数。此时,如果你确实需要一个默认构造函数,你需要显式提供它。

    示例:

    #include <iostream>
    
    class Example {
    public:
        // 默认构造函数
        Example() {
            std::cout << "默认构造函数被调用" << std::endl;
            // 对于基本数据类型,保留未初始化的值
        }
    
    private:
        int intValue;
        double doubleValue;
    };
    
    int main() {
        // 对于自定义类型 Example,会调用默认构造函数
        Example obj;
        
        return 0;
    }
    

    在上述例子中,Example 类包含两个基本数据类型成员变量。默认构造函数将被调用,并且对于 intdouble 类型的成员变量,它们将包含未初始化的值。

    C++11 允许在类的声明中直接进行成员变量的初始化,这被称为默认成员初始化。

    在没有显式提供构造函数的情况下,成员变量 intValue 将会被默认初始化为 42。

    class Example {
    public:
        int intValue = 42;  // 默认成员初始化
    };
    
  7. 自定义的无参构造函数和全缺省构造函数,以及编译器自动生成的默认构造函数都是默认构造函数。
    默认构造函数的意思是,在创建对象时不需要任何参数。而无参构造函数和全缺省构造函数,以及编译器自动生成的默认构造函数,它们都不需要任何参数就可以创建对象,因此它们都是默认构造函数。
    但要注意,因为它们都不需要参数,所以它们不会同时出现,否则编译器不知道要调用哪个函数。

    class Date{
    public:
    	Date(){
    		_year = 1900;
    		_month = 1;
    		_day = 1;
    	}
    	
    	Date(int year = 1900, int month = 1, int day = 1){
    		_year = year;
    		_month = month;
    		_day = day;
    	}
    
    private:
    	int _year;
    	int _month;
    	int _day;
    };
    // 以下测试函数能通过编译吗?
    int main() {
    
    	// “Date::Date” : 对重载函数的调用不明确
    	//Date d1;
    }
    

三、析构函数

析构函数是在对象生命周期结束时被调用的特殊成员函数。它的主要作用是进行对象的清理和资源释放工作。

在C++中,每个类都可以有一个析构函数,其名称与类名相同,前面加上波浪号(~)。

无参数无返回值类型。

一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构函数不能重载。

对象生命周期结束时自动调用析构函数。

class Date {
public:
	Date() {
		_year = 1900;
		_month = 1;
		_day = 1;
	}

	~Date() {

		cout <<"对象生命周期结束时自动调用"<< "\n";
	}

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

int main() {

	Date d1;
}

在这里插入图片描述


当一个类没有显式定义析构函数时,C++编译器会自动生成一个默认的析构函数。这个默认的析构函数会执行基本的清理工作,但对于动态分配的内存或其他资源的释放,它可能不会进行额外的操作。

#include <iostream>

class Example {
public:
    // 没有显式定义析构函数

    // 其他成员函数和变量
    void someFunction() {
        std::cout << "Executing some function\n";
    }
};

int main() {
    // 创建对象
    Example obj;

    // 调用成员函数
    obj.someFunction();

    // 对象超出作用域,析构函数被调用
    return 0;
}

默认的析构函数通常足够处理大多数情况,尤其是对于没有动态资源管理的简单类。然而,如果类涉及到动态分配的内存等复杂的操作,通常建议显式定义析构函数以确保这些资源能够被正确释放。


四、拷贝构造函数

拷贝构造函数是一种特殊的构造函数,用于创建一个对象,该对象是已有对象的精确副本。

拷贝构造函数通常在以下情况下调用:

  1. 通过一个对象初始化另一个对象。
  2. 将对象作为函数参数传递给函数。
  3. 从函数返回对象。

拷贝构造函数的基本语法如下:

class MyClass {
public:
    // 拷贝构造函数
    MyClass(const MyClass& other) {
        // 执行拷贝操作,创建一个对象的副本
    }

    // 其他成员函数和变量
};

可以将拷贝构造函数看作构造函数的重载。


拷贝构造函数的参数是一个对同类型对象的引用,并且通常是 const 引用,以确保不修改原始对象。在函数体内,你需要编写适当的代码来实现对象的拷贝。

同时拷贝构造函数不能通过传值的方式定义,因为这样会引发无限递归的拷贝构造函数调用。

class MyClass {
public:
    // 错误的拷贝构造函数,传值方式
    MyClass(MyClass another) {
        // 这里的传值方式将调用拷贝构造函数,导致无限循环
    }
    //正确方式 MyClass(const MyClass& another)
};

在这里插入图片描述

通过值传递方式定义拷贝构造函数时,传递的对象 another 会触发拷贝构造函数,而这个拷贝构造函数又传递了一个值,然后再次触发拷贝构造函数,导致无限递归调用。


如果你没有显式提供拷贝构造函数,C++ 编译器会为你生成一个默认的拷贝构造函数。 这个默认的拷贝构造函数执行的操作是按位拷贝(浅拷贝),即将一个对象的每个成员变量的值复制给另一个对象的对应成员变量。

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

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

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

int main(){
    Date d1;
    Date d2(d1);// 调用默认拷贝构造函数,创建对象d2

    d1.Print();
    d2.Print();
    return 0;
}

如果类中包含了动态分配的资源(比如使用 new 分配的内存),默认的拷贝构造函数执行的是浅拷贝,这可能导致两个对象共享相同的资源,而不是创建资源的副本。

而且当对象的生命周期结束时,析构函数清理动态分配的资源,但由于两个对象共享相同的资源,会将已经清理的空间再次清理,导致程序崩溃。


所以当中包含了动态分配的资源时,拷贝构造函数要由我们自己定义。这就是深拷贝。

class Date {
public:
    // 构造函数
    Date(const char* dateString) {
        // 假设 dateString 是通过 new 分配的内存
        data = new char[strlen(dateString) + 1];
        strcpy(data, dateString);
    }

    // 自定义的深拷贝构造函数
    Date(const Date& other) {
        // 分配新的内存
        data = new char[strlen(other.data) + 1];
        // 复制原始对象的数据到新分配的内存中
        strcpy(data, other.data);
    }

    // 析构函数
    ~Date() {
        // 释放动态分配的内存
        delete[] data;
    }

    // 打印日期
    void printDate() const {
        std::cout << "Date: " << data << std::endl;
    }

private:
    char* data;
};

int main() {
    // 创建日期对象
    Date date1("2022-01-01");

    // 使用深拷贝创建另一个日期对象
    Date date2 = date1;

    // 打印两个日期对象
    date1.printDate();
    date2.printDate();

    return 0;
}

在这里插入图片描述


调用拷贝构造函数的三种情况

  1. 对象的初始化: 当一个对象通过另一个对象进行初始化时,拷贝构造函数会被调用。

    MyClass obj1;          // 调用默认构造函数
    MyClass obj2 = obj1;   // 调用拷贝构造函数
    //或者 MyClass obj2(obj1);
    
  2. 传递对象给函数: 当对象作为参数传递给函数时,拷贝构造函数会被调用。

    void someFunction(MyClass param) {
        // 在函数体内使用 param
    }
    
    MyClass obj3;
    someFunction(obj3);    // 调用拷贝构造函数
    
  3. 从函数返回对象: 当一个函数返回一个对象时,拷贝构造函数会被调用,用于创建返回对象的副本。

    MyClass createObject() {
        MyClass obj;
        return obj; // 调用拷贝构造函数
    }
    
    MyClass obj4 = createObject(); // 调用拷贝构造函数
    

五、拷贝赋值运算符

拷贝赋值运算符用于将一个已经存在的对象的值赋给另一个已经存在的对象。这个运算符通常用于确保对象之间的深度拷贝,特别是在涉及到动态分配的资源时。

拷贝赋值运算符的一般形式如下:

class MyClass {
public:
    // 拷贝赋值运算符
    MyClass& operator=(const MyClass& other) {
        // 检查是否是自赋值
        if (this != &other) {
            // 执行深拷贝操作,复制资源
            // 注意:需要释放当前对象可能持有的资源
        }
        return *this; // 返回当前对象的引用
    }

    // 其他成员函数和变量
};

拷贝赋值运算符返回一个对当前对象的引用,这样可以支持链式赋值操作(例如 a = b = c。在实现拷贝赋值运算符时,需要注意避免自赋值,以免在释放资源时导致错误。


如果在类中没有显式定义拷贝赋值运算符,编译器会自动生成一个默认的拷贝赋值运算符。这个默认生成的版本会按字节拷贝对象的每个成员变量,即浅拷贝。
例子:

class MyClass {
public:
    // 构造函数,成员初始化列表
    MyClass(int val) : value(val) {}

    // 打印数据
    void printData() const {
        std::cout << "Value: " << value << std::endl;
    }

private:
    int value;
};

int main() {
    // 创建对象
    MyClass obj1(42);

    // 使用默认生成的拷贝赋值运算符进行赋值
    MyClass obj2(0);
    obj2 = obj1;

    // 打印两个对象的数据
    obj1.printData();
    obj2.printData();

    return 0;
}

在这里插入图片描述
同样的,如果类需要管理动态分配的资源,需要显式提供拷贝构造函数和拷贝赋值运算符以确保正确的资源复制。


拷贝构造函数和拷贝赋值运算符的一些区别:

  • 时机不同: 拷贝构造函数在对象的创建和复制时被调用,而拷贝赋值运算符在对象已经存在的情况下进行赋值时被调用。

  • 用途不同: 拷贝构造函数通常用于对象的初始化和创建副本(类类型传参和函数返回类类型时),而拷贝赋值运算符用于对象的赋值操作。

  • 返回类型不同: 拷贝构造函数没有返回类型,而拷贝赋值运算符返回当前对象的引用(链式赋值操作)。


在这里插入图片描述
如果你喜欢这篇文章,点赞👍+评论+关注⭐️哦!
欢迎大家提出疑问,以及不同的见解。

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

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

相关文章

怎么制作出圈的虚拟数字人城市宣传短片?

如今&#xff0c;中国城市面临一个从To B&#xff08;企业客户&#xff09;、To G&#xff08;政府客户&#xff09;到To C&#xff08;一般客户&#xff09;的转变。其中&#xff0c;城市宣传片作为与C端沟通的最佳途径&#xff0c;一个“吸睛”的城市短片&#xff0c;可以有效…

揭秘支付宝小程序开发:从零到一,轻松掌握开发流程!

目录 1、介绍支付宝小程序开发 1.1 什么是支付宝小程序 1.2 支付宝小程序与其他小程序的区别 1.3 支付宝小程序的优势 2、准备工作 2.1 注册支付宝小程序开发者账号 2.2 下载支付宝小程序开发工具 2.3 了解支付宝小程序的基本概念和架构 3、开发环境搭建 3.1 安装并配…

如何在 Ubuntu 中安装 Microsoft Edge 浏览器

微软终于聪明了一回&#xff0c;也学会了「打不过就加入」。Microsoft Edge 浏览器的 Linux 稳定版已经于 2020 年 10 月 23 日发布&#xff0c;并提供给 Linux 发行版使用。除了官方 Edge APT 源以外&#xff0c;还提供了.deb和.rpm格式的安装包。 Microsoft Edge 基于 Chrom…

深度学习快速入门--7天做项目

深度学习快速入门--7天做项目 0. 引言1. 本文内容2. 深度学习是什么3. 项目是一个很好的切入点4. 7天做项目4.1 第一天&#xff1a;数据整理4.2 第二天&#xff1a;数据处理4.3 第三天&#xff1a;简单神经网络设计4.4 第四天&#xff1a;分析效果与原因4.5 第五天&#xff1a;…

【MyBatis】快速入门MyBatis(保姆式教学),你值得一看

文章目录 &#x1f4c4;前言一. Mybatis简介✈️1. 什么是Mybatis&#x1f680;2. 为什么使用Mybatis 二. Mybatis快速入门&#x1f346;1. mybatis使用前准备1.1 创建springboot项目并引入相关依赖1.2 在 application.ym中进行数据源的配置1.3 创建数据表&#xff0c;准备表数…

【css】自定义列表项标记(图片、符号、表情)

1. 列表项标记是图片 ul{li {list-style: none;padding-left: 20px; /* 设置左边距&#xff0c;以容纳图标 */display: flex;align-items: center;/* 使小图标和文字高度对齐 */}li::before {content: ;display: inline-block;width: 20px; /* 设置容器宽度 */height: 20px; /*…

java学习02运算符

一 算术运算符 1.运算符和表达式 运算符 就是对常量或者变量进行操作的符号 表达式 用运算符把常量或者变量连接起来的&#xff0c;符合Java语法的式子就是表达式。 比如&#xff1a;a b 2.算术运算符 加减乘 package com.itheima.arithmeticoperator;public class Ar…

笔记本从零安装ubuntu系统+多种方式远程控制

文章目录 前言ubuntu启动盘Windows远程Ubuntu安装XrdpXrdp卡顿问题解决Xrdp 二次登录会死机的问题Xrdp 卡顿问题 MobaXtermRustDesk 外网远程VNC 远程SSH远程其它设置 总结 前言 我有台老笔记本&#xff0c;上大学第一年的时候买的&#xff0c;现在已经不怎么好用了。打算刷个…

GNSS定位技术总结与PPP定位技术

1.统一观测值方程 2.PPP方程构建 站间单差方程如下&#xff1a; 同样的&#xff0c;设计矩阵也更加庞大&#xff1a; 站间单差消除了卫星轨道、卫星钟、电离层、对流层以及卫星端的伪距和载波硬件延迟的影响。但在PPP中&#xff0c;我们无法通过站间单差消除这些影响&#xff…

虚拟机设置静态ip

有时候搭环境需要局域网&#xff0c;设置一下虚拟机静态ip&#xff0c;这里做个记录&#xff1a; 这里我用的是ubuntu18.04的虚拟机&#xff0c;安装完成之后&#xff0c;点击进入设置 这里设置一下桥接模式 这个时候输入ifconfig&#xff0c;就是和主机一个网段了&#xff…

Tomcat多实例配置与tomcat反向代理集群

目录 Tomcat多实例配置 1.首先配置Tomcat单实例 2.tomcat多实例配置 1.1复制单实例tomcat 1.2修改端口&#xff0c;以启动多实例。多实例之间端口不能一致 1.3对比文件不同之处 3.启动tomcat 4.检查端口查看是否启动: 5.测试浏览器访问 二、tomcat反向代理集群 1、负载…

【AI】Chinese-LLaMA-Alpaca-2 7B llama.cpp 量化方法选择及推理速度测试 x86_64 RTX 2060 6G 显存太小了

环境 操作系统 CPU 内存 生成量化版本模型 转换出q4_0 q4_k q6_k q8_0模型 cd ~/Downloads/ai/llama.cpp sourvce venv/bin/activate ~/Downloads/ai/llama.cpp/quantize /home/yeqiang/Downloads/ai/chinese-alpaca-2-7b/ggml-model-f16.gguf /home/yeqiang/Downloads/ai/ch…

elk之安装和简单配置

写在前面 本文看下elk的安装和简单配置&#xff0c;安装我们会尝试通过不同的方式来完成&#xff0c;也会介绍如何使用docker&#xff0c;docker-compose安装。 1&#xff1a;安装es 1.1&#xff1a;安装单实例 下载es安装包 在这里 下载&#xff0c;下载后解压到某个目录…

使用 Node.js 和 Cheerio 爬取网站图片

写一个关于图片爬取的小案例 爬取效果 使用插件如下&#xff1a; {"dependencies": {"axios": "^1.6.0","cheerio": "^1.0.0-rc.12","request": "^2.88.2"} }新建一个config.js配置文件 // 爬取图片…

企业内部知识库搭建原来这么轻松,靠这五步马上完成

在信息爆炸的时代&#xff0c;有效地管理企业内部的巨量信息&#xff0c;已经成为企业效率提升和竞争优势形成的关键。而一套完善的企业内部知识库&#xff0c;就是解决这个问题的第一步。那么如何建立起一个功能完备、使用便捷的知识库呢&#xff1f;只需要五步&#xff0c;你…

Leetcode—1828. 统计一个圆中点的数目【中等】

2024每日刷题&#xff08;一零五&#xff09; Leetcode—1828. 统计一个圆中点的数目 实现代码 class Solution { public:vector<int> countPoints(vector<vector<int>>& points, vector<vector<int>>& queries) {vector<int> a…

npm 被滥用 -- 有人上传了 700 多个武林外传切片视频

Sonatype 安全研究团队最近曝光了一起滥用 npm 的案例 —— 他们发现在 npm 上托管的 748 个软件包实际上是视频文件。 据介绍&#xff0c;这些软件包每个大小约为 54.5MB&#xff0c;包名以 “wlwz” 为前缀&#xff0c;并附带了代表日期的数字。根据时间戳显示&#xff0c;这…

❤搭建一个Springboot项目(ltbjava)

❤从0实现一个项目 搭建好我们的java环境和运行的IDEA软件以后&#xff0c;接下来我们就应该实现一个自己的项目了 0 项目描述 基于jdk17 的学习&#xff0c;因为据说最新的spring框架的最低要求是jdk17Maven 3.8.7PS&#xff1a;springboot3.0版本以上必须用jdk171、 项目创…

永磁同步电机位置闭环控制

文章目录 1、位置环分析与调节器2、电机参数3、模型总览4、位置给定与波形5、位置环前馈控制5.1 前馈模型5.2 位置前馈控制效果 模型下载地址&#xff1a; 链接: 位置闭环模型&#xff08;位置速度电流三闭环模型&#xff09; 1、位置环分析与调节器 2、电机参数 Vdc24; Rs0.…

ps缺少msvcp140.dll要怎么办?多种解决msvcp140.dll的方法分享

当您在尝试打开Adobe Photoshop时&#xff0c;如果遭遇一个典型的错误&#xff1a;“程序无法启动&#xff0c;因缺少MSVCP140.dll文件”&#xff0c;请放心&#xff0c;这并不少见&#xff0c;许多Photoshop用户都可能曾面临过这种情况。处理这个问题实际上是相当简单的。接下…