类和对象:构造函数,析构函数与拷贝构造函数

1.类的6个默认成员函数

如果一个类中什么成员都没有,简称为空类。

空类中真的什么都没有吗?并不是,任何类在什么都不写时,编译器会自动生成以下6个默认成员函数。

默认成员函数:用户没有显式实现,编译器会生成的成员函数称为默认成员函数。

class Date
{};

类一共有6个默认成员函数,本篇文章先来介绍前三个函数,构造函数,析构函数,与拷贝构造函数。 

2.构造函数

2.1 概念

对于以下Date类

class Date
{
public:void Init(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 d1;d1.Init(2022, 7, 5);d1.Print();Date d2;d2.Init(2022, 7, 6);d2.Print();return 0;
}

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

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

2.2 特性

构造函数是特殊的成员函数,需要注意的是,构造函数虽然名称叫构造,但是构造函数的主要任务并不是开空间创建对象,而是初始化对象

其特征如下:

  1. 函数名与类名相同。
  2. 无返回值。
  3. 对象实例化时编译器自动调用对应的构造函数。
  4. 构造函数可以重载。
    class Date
    {
    public:// 1.无参构造函数Date(){}// 2.带参构造函数Date(int year, int month, int day){_year = year;_month = month;_day = day;}
    private:int _year;int _month;int _day;
    };void TestDate()
    {Date d1; // 调用无参构造函数Date d2(2015, 1, 1); // 调用带参的构造函数// 注意:如果通过无参构造函数创建对象时,对象后面不用跟括号,否则就成了函数声明// 以下代码的函数:声明了d3函数,该函数无参,返回一个日期类型的对象// warning C4930: “Date d3(void)”: 未调用原型函数(是否是有意用变量定义的?)Date d3();
    }

    注意:如果通过无参构造函数创建对象时,对象后面不用跟括号,否则就成了函数声明

  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类中构造函数放开,代码编译失败,因为一旦显式定义任何构造函数,编译器将不再生成//无参构造函数,放开后报错:error C2512: “Date”: 没有合适的默认构造函数可用Date d1;return 0;
    }
  6. 关于编译器生成的默认成员函数,大家可能会有疑惑:不实现构造函数的情况下,编译器会生成默认的构造函数。但是看起来默认构造函数又没什么用?d对象调用了编译器生成的默认构造函数,但是d对象_year/_month/_day,依旧是随机值。也就说在这里编译器生成的默认构造函数并没有什么用??

     

    解答:C++把类型分成内置类型(基本类型)和自定义类型。内置类型就是语言提供的数据类型,如:int/char...,自定义类型就是我们使用class/struct/union等自己定义的类型,看看下面的程序,就会发现编译器生成默认的构造函数会对自定类型成员_t调用的它的默认成员函数,内置类型不做处理

    class Time
    {
    public:Time(){cout << "Time()" << endl;_hour = 0;_minute = 0;_second = 0;}
    private:int _hour;int _minute;int _second;
    };
    class Date
    {
    private:// 基本类型(内置类型)int _year;int _month;int _day;// 自定义类型Time _t;
    };
    int main()
    {Date d;return 0;
    }

    注意:C++11 中针对内置类型成员不初始化的缺陷,又打了补丁,即:内置类型成员变量在类中声明时可以给默认值。

    class Time
    {
    public:Time(){cout << "Time()" << endl;_hour = 0;_minute = 0;_second = 0;}
    private:int _hour;int _minute;int _second;
    };
    class Date
    {private:// 基本类型(内置类型)int _year = 1970;int _month = 1;int _day = 1;// 自定义类型Time _t;
    };
    int main()
    {Date d;return 0;
    }
  7. 无参的构造函数全缺省的构造函数都称为默认构造函数并且默认构造函数只能有一个
    注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为
    是默认构造函数

 

3.析构函数

3.1 概念

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

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

3.2 特性

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

  1. 析构函数名是在类名前加上字符 ~。
  2. 无参数无返回值类型。
  3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构函数不能重载
  4. 对象生命周期结束时,C++编译系统系统自动调用析构函数。
    typedef int DataType;
    class Stack
    {
    public:Stack(int capacity = 4){_data = (DataType*)malloc(sizeof(DataType) * capacity);if (_data == nullptr){perror("malloc fail");return;}_top = 0;_capacity = capacity;}void Push(DataType data ){//CheckCapacity();_data[_top] = data;_top++;}//析构函数~Stack(){free(_data);_data = nullptr;_top = _capacity = 0;}private:DataType* _data;int _top;int _capacity;
    };int main()
    {Stack s;s.Push(1);s.Push(1);s.Push(1);return 0;
    }
  5. 关于编译器自动生成的析构函数,是否会完成一些事情呢?下面的程序我们会看到,编译器生成的默认析构函数,对自定类型成员调用它的析构函数,内置类型不做处理

    class Time
    {
    public:~Time(){cout << "~Time()" << endl;}
    private:int _hour;int _minute;int _second;
    };
    class Date
    {
    private:// 基本类型(内置类型)int _year = 1970;int _month = 1;int _day = 1;// 自定义类型Time _t;
    };
    int main()
    {Date d;return 0;
    }
  6. 如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数,比如
    Date类;有资源申请时,一定要写,否则会造成资源泄漏,比如Stack类。

4.拷贝构造函数

4.1 概念

在现实生活中,可能存在一个与你一样的自己,我们称其为双胞胎,那在创建对象时,可否创建一个与已存在对象一某一样的新对象呢?

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

4.2 特性

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

  1. 拷贝构造函数是构造函数的一个重载形式。
  2. 拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,因为会引发无穷递归调用
    class Date
    {
    public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}// Date(const Date d) // 错误写法:编译报错,会引发无穷递归Date(const Date& d) // 正确写法{_year = d._year;_month = d._month;_day = d._day;}
    private:int _year;int _month;int _day;
    };
    int main()
    {Date d1;Date d2(d1);return 0;
    }

    拷贝构造函数的参数必须是引用,因为使用传值方式调用拷贝构造函数,传值过程中也是拷贝,也会调用拷贝构造函数,然后调用还需要传参,还会调用拷贝构造函数,会发生死循环。

  3. 若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。
     

    class Time
    {
    public:Time(){_hour = 1;_minute = 1;_second = 1;}Time(const Time& t){_hour = t._hour;_minute = t._minute;_second = t._second;cout << "Time::Time(const Time&)" << endl;}
    private:int _hour;int _minute;int _second;
    };
    class Date
    {
    private:// 基本类型(内置类型)int _year = 1970;int _month = 1;int _day = 1;// 自定义类型Time _t;
    };
    int main()
    {Date d1;// 用已经存在的d1拷贝构造d2,此处会调用Date类的拷贝构造函数// 但Date类并没有显式定义拷贝构造函数,则编译器会给Date类生成一个默认的拷贝构造函数Date d2(d1);return 0;
    }

    注意:在编译器生成的默认拷贝构造函数中,内置类型是按照字节方式直接拷贝的,而自定
    义类型是调用其拷贝构造函数完成拷贝的

  4. 编译器生成的默认拷贝构造函数已经可以完成字节序的值拷贝了,还需要自己显式实现吗?当然像日期类这样的类是没必要的。那么下面的类呢?验证一下试试?
    typedef int DataType;
    class Stack
    {
    public:Stack(int capacity = 4){cout << "Stack(int capacity = 4)" << endl;_data = (DataType*)malloc(sizeof(DataType) * capacity);if (_data == nullptr){perror("malloc fail");return;}_top = 0;_capacity = capacity;}void Push(DataType data){//CheckCapacity();_data[_top] = data;_top++;}~Stack(){free(_data);_data = nullptr;_top = _capacity = 0;}private:DataType* _data;int _top;int _capacity;
    };
    int main()
    {Stack s1;s1.Push(1);s1.Push(2);s1.Push(3);Stack s2(s1);return 0;
    }

    这里Stack会在堆区开辟了空间,如果还是使用编译器提供的默认拷贝构造函数,在定义s2时,Stack中的_data也会只是简单的拷贝,s1与s2的_data指向的是同一块空间,当程序运行完会依次销毁s2,s1,调用析构函数,在s2调用析构函数时,s2的_data已经被释放,s1的_data也会被释放,s1调用析构函数时,会再次释放_data,程序就会崩溃。

    所以我们要自己实现拷贝构造函数,进行深拷贝

    	Stack(const Stack& stack)//权限缩小 引用做参数如果不是做输出型参数,一般都加const{cout << "Stack(Stack & stack)" << endl;_top = stack._top;_capacity = stack._capacity;_data = (DataType*)malloc(sizeof(DataType) * _capacity);memcpy(_data, stack._data, sizeof(DataType) * stack._top);}

    注意:类中如果没有涉及资源申请时,拷贝构造函数是否写都可以;一旦涉及到资源申请时,则拷贝构造函数是一定要写的,否则就是浅拷贝。

  5. 拷贝构造函数典型调用场景:

    1. 使用已存在对象创建新对象

    2. 函数参数类型为类类型对象

    3. 函数返回值类型为类类型对象

    为了提高程序效率,一般对象传参时,尽量使用引用类型,返回时根据实际场景,能用引用
    尽量使用引用

本篇结束!

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

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

相关文章

C++位图

一、前提引入 思考如何实现下面的题目 给40亿个不重复的无符号整数&#xff0c;没排过序。给一个无符号整数&#xff0c;如何快速判断一个数是否在 这40亿个数中。【腾讯】 一个整型数据int为4个字节&#xff0c;计算得 &#xff0c;大约需要16G的存储空间。如果采用遍历判断…

01-从JDK源码级别剖析JVM类加载机制

上一篇&#xff1a;JVM虚拟机调优大全 1. 类加载运行全过程 当我们用java命令运行某个类的main函数启动程序时&#xff0c;首先需要通过类加载器把主类加载到JVM。 public class Math {public static final int initData 666;public static User user new User();public i…

论文阅读 - Outlier detection in social networks leveraging community structure

目录 摘要 1. Introduction 2. Related works 3. Preliminaries 3.1. 模块化度量 3.2. Classes of outliers 3.2.1. 点异常 3.2.2. Contextual anomalies 3.2.3. Collective anomalies 3.3. Problem definition 3.4. Outliers score 4. Methodology 4.1. Proposed appr…

初识FUSE(Filesystem in userspace)

初识FUSE&#xff08;Filesystem in userspace&#xff09; 什么是FUSE&#xff1f;FUSE原理FUSE协议 如何去使用&#xff1f;参考文章 之前因为一次作业有幸接触过FUSE,觉得它是一个很不错的框架&#xff0c;没来得及仔细了解。现在有点时间了&#xff0c;想要利用它做一个文件…

Linux部署kettle并设置定时任务

一.安装Kettle linux中使用kettle时首先需要jdk环境&#xff0c;这里就不概述linux中jdk的安装与配置了。 1.首先将kettle压缩包放入linux并解压 unzip data-integration.zip kettle安装路径为:/root/Kettle9.3/data-integration 设置权限 chmod -R 755 /root/Kettle9.3/d…

LVS负载均衡群集NAT模式

LVS负载均衡群集NAT模式 一、集群与分布式1.1、集群的含义1.2、lvs模型1.3、系统性能扩展方式1.4、群集的三种类型1.4.1、负载均衡群集1.4.2、高可用群集1.4.3、高性能运算群集 1.5、LVS的负载调度算法1.5.1、轮询1.5.2、加权轮询1.5.3、最少连接1.5.4、加权最少连接1.5.5、ip_…

pc端测试手机浏览器运行情况,主要是测试硬件功能

测试h5震动摇晃等功能时不方便测试&#xff0c;需要连电脑显示调试数据 方法&#xff1a; 1.需要手机下载谷歌浏览器&#xff0c;pc端用edge或这谷歌浏览器 2.手机打开USB调试&#xff0c;打开要测试的网页 3.pc端地址栏输入edge://inspect/#devices&#xff08;这里用的edge浏…

K8S:kubeadm搭建K8S+Harbor 私有仓库

文章目录 一.部署规划1.主机规划2.部署流程 二.kubeadm搭建K8S1.环境准备2.安装docker3. 安装kubeadm&#xff0c;kubelet和kubectl4.部署K8S集群&#xff08;1&#xff09;初始化&#xff08;2&#xff09;部署网络插件flannel&#xff08;3&#xff09;创建 pod 资源 5.部署 …

Linux HTTP协议

目录 1.浏览器与服务器通信过程2.HTTP请求报头&#xff08;1&#xff09;HTTP的请求报头结构&#xff08;2&#xff09;HTTP的请求方法 3.HTTP应答报头&#xff08;1&#xff09;HTTP的应答报头结构&#xff08;2&#xff09; HTTP的应答状态 1.浏览器与服务器通信过程 浏览器…

回归预测 | MATLAB实现PSO-SDAE粒子群优化堆叠去噪自编码器多输入单输出回归预测(多指标,多图)

回归预测 | MATLAB实现PSO-SDAE粒子群优化堆叠去噪自编码器多输入单输出回归预测&#xff08;多指标&#xff0c;多图&#xff09; 目录 回归预测 | MATLAB实现PSO-SDAE粒子群优化堆叠去噪自编码器多输入单输出回归预测&#xff08;多指标&#xff0c;多图&#xff09;效果一览…

清理Maven仓库中下载失败的文件

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…

Nginx详解 五:反向代理

文章目录 1. 正向代理和反向代理1.1 正向代理概述1.1.1 什么是正向代理1.1.2 正向代理的作用1.1.3 正向代理的基本格式 1.2 反向代理概述1.2.1 什么是反向代理1.2.2 反向代理可实现的功能1.2.3 反向代理的可用模块 2. 配置反向代理2.1 反向代理配置参数2.1.1 proxy_pass2.1.2 其…

当你的公司突然开始大量的裁员,被留下的你,真的准备好面对以后了吗?

留下来的&#xff0c;也是迷茫的 最近公司突然开始大量裁员&#xff0c;裁了一多半&#xff0c;作为唯一留下的APP 端开发人员&#xff0c;也开始陷入了焦虑&#xff0c;开始了思考&#xff0c;未来究竟何去何从&#xff0c;是否再去转到原生&#xff0c;从事原生的开发工作&a…

C语言入门Day_19 初识函数

目录 1.函数的定义 2.函数的调用 3.易错点 4.思维导图 前言&#xff1a; printf()我们已经很熟悉了&#xff0c;它有一个特定的功能&#xff0c;就是在屏幕上输出一行文字。之前的课程我们都称呼printf()为一个功能&#xff0c;实际上ta在编程中有个特定的名字——函数。 …

测试-----selenuim webDriver

文章目录 1.页面导航2.元素定位3. 浏览器操作4.获取元素信息5. 鼠标的操作6. 键盘操作7. 元素等待8.下拉框9.弹出框10.滚动条11.frame处理12.验证码处理&#xff08;cookie&#xff09; 1.页面导航 首先是导入对应的包 :from selenium import webdriver然后实例化:driver web…

网络安全宣传周|探索AI数字人的魅力和价值所在

9月11日至9月17日是国家网络安全宣传周&#xff0c;在福州举办的安全博览会上有着多种人工智能模型产品亮相现场&#xff0c;吸引着众多参观者的目光&#xff0c;尤其是AI数字人面对不同的问题、不同的场景都可以进行实时响应&#xff0c;不同于冷冰冰的传统智能客服的对话场景…

前端面试合集(二)

前端面试题合集 1.懒加载的原理及实现了解吗2.如何理解JS异步3.阐述一下 JS 的事件循环4.JS 中的计时器能做到精确计时吗&#xff1f;为什么&#xff1f; 1.懒加载的原理及实现了解吗 原理&#xff1a;当图片没有到达可视范围内时&#xff0c;图片不加载&#xff0c;当图片一旦…

Mobileye CEO来华:只有能控制住成本的公司,才能活下来

‍作者|德新 编辑|王博 上午9点近一刻&#xff0c;Mobileye CEO Amnon Shuashua步入酒店的会议室。由于Amnon本人是以色列希伯来大学的计算机科学教授&#xff0c;大部分人更习惯称他为「教授」。 时近以色列的新年&#xff0c;这趟教授的中国之行安排十分紧凑。 他率领了一…

遥遥领先的内存函数

目录 ​编辑 函数介绍 1.1 strlen 1.2 strcpy 1.3 strcmp 1.4 strcat 1.5 strstr 2.1 memcpy 2.2 memmove 2.3 memcmp 函数实现 1.1 strlen 1.2 strcpy 1.3 strcmp 1.4 strcat 1.5 strstr 2.1 memcpy 2.3 memcmp 函数介绍 1.1 strlen size_t strlen ( const char *…

SpringBoot整合Redis,基于Jedis实现redis各种操作

前言&#xff08;三步教你学会redis&#xff0c;主打一个实用&#xff09; springboot整合redis步骤&#xff0c;并基于jedis对redis数据库进行相关操作&#xff0c;最后分享非常好用、功能非常全的redis工具类。 第一步&#xff1a;导入maven依赖 <!-- springboot整合re…