C++:类的默认成员函数

news2024/9/20 20:39:20

默认成员函数就是⽤⼾没有显式实现,编译器会⾃动⽣成的成员函数称为默认成员函数。⼀个类,我们不写的情况下编译器会默认⽣成以下6个默认成员函数,需要注意的是这6个中最重要的是前4个。

 定义一个空类:

class A
{
};

经过编译器处理之后,类A不在为空,它会自动的生成六个默认的成员函数,即使这六个成员函数什么也不做。处理之后相当于:

class A
{
    A();//1、构造函数

    A(const A& x);//2、拷贝构造函数

    ~A();//3、析构函数

    A& operator= (const A& x);4、赋值操作符重载

    A* operator &();//5、取地址运算符重载

    const A* operator& () const;//6、const修饰的取地址操作符重载
};

1、构造函数

构造函数是特殊的成员函数,需要注意的是,构造函数虽然名称叫构造,但是构造函数的主要任务并不是开空间创建对象(我们常使⽤的局部对象是栈帧创建时,空间就开好了),而是对象实例化时初始化对象。构造函数的本质是要替代我们以前Stack和Date类中写的Init函数的功能,构造函数自动调用的特点就完美的替代的了Init。

它的特点:

  • 函数名与类名相同。
  • 无返回值。
  • 对象实例化时系统会⾃动调用对应的构造函数。
  • 构造函数可以重载。
  • 如果类中没有显式定义构造函数,则C++编译器会自动动⽣成⼀个无参的默认构造函数,⼀旦用户显式定义编译器将不再生成。

 构造函数的使用

若我们要初始化一个时间类,我们还需要写一个初始化函数Init()来初始化时间类的成员变量,但其实我们可以写一个构造函数来初始化成员变量。

//用Init() 初始化成员变量
class Date
{
public:
    void Init(int day)
    {
        _day = day;
    }
private:

    int _day;
};

int main()
{
    Date t;
    t.Init(100);
    return 0;
}

 在这里我们其实在创建一个类对象t的时候就调用了编译器默认生成的构造函数。

//用Date() 初始化成员变量
class Date
{
public:
    Date(int day)
    {
        _day = day;
    }
private:

    int _day;
};

int main()
{
    Date t(100);

    return 0;
}

但我们将上面的构造函数Date()和初始化函数Init()放到一起呢?

class Date
{
public:
    Date(int day)
    {
        _day = day;
    }

    void Init(int day)
    {
        _day = day;
    }
private:
    int _day;
};

int main()
{
    Date t;
    t.Init(100);
    return 0;
}

解决方法:

   a、将默认构造函数再加上

编译将会报错,因为我们自己写了一个构造函数,所以编译器不会生成它的默认构造函数了,在创建类对象的时候,没有可以匹配的构造函数,从而导致了编译报错。我们只需要将编译器生成的默认构造函数再加上即可。

class Date
{
public:
    Date() {}

    Date(int day)
    {
        _day = day;
    }

    void Init(int day)
    {
        _day = day;
    }
private:
    int _day;
};

int main()
{
    Date t;
    t.Init(100);
    return 0;
}
   b、写一个全缺省构造函数

写一个全缺省构造函数,它既匹配了类对象的创建,又完成了类成员变量的初始化。

class Date
{
public:
    Date(int day = 100)
    {
        _day = day;
    }
private:
    int _day;
};

int main()
{
    Date t;
    return 0;
}

这样我们可以完成创建一个类对象,也不需要写初始化函数Init()也可以初始化成员变量。

初始化列表

之前我们实现构造函数时,初始化成员变量主要使用函数体内赋值,构造函数初始化还有⼀种方
式,就是初始化列表,初始化列表的使用方式是以⼀个冒号开始,接着是⼀个以逗号分隔的数据成
员列表,每个"成员变量"后⾯跟⼀个放在括号中的初始值或表达式。

class A
{
public:
    A(int a,int b,int c)
    :_a(a)
    ,_b(b)
    ,_c(c)
    {}
private:   
    int _a;
    int& _b;
    const int _c;
};

易错点:

  • 无参构造函数、全缺省构造函数、我们不写构造时编译器默认生成的构造函数,都叫做默认构造函数。但是这三个函数有且只有⼀个存在,不能同时存在。无参构造函数和全缺省构造函数虽然构成函数重载,但是调用时会存在歧义。
class Date
{
public:
    Date()
    {
        _day = 1;
    }
    
    Date(int day = 100)
    {
        _day = day;
    }
private:
    int _day;
};

int main()
{
    Date t;
    return 0;
}

但创建类对象t的时候,编译器会不知道匹配哪个构造函数,从而导致编译报错。

  • 不能写 Date t(); 这样代码,因为编译器可能会认为它是一个函数。

因为我们可以将Date看成函数的返回值,t看成函数名,()表示无参传递。


2、拷贝构造函数

如果⼀个构造函数的第⼀个参数是⾃⾝类类型的引用,且任何额外的参数都有默认值,则此构造函数也叫做拷贝构造函数,也就是说拷贝构造是⼀个特殊的构造函数。

拷构造的特点:

  • 拷贝构造函数是构造函数的⼀个重载。
  • C++规定自定义类型对象进行拷贝行为必须调用拷贝构造,所以这里自定义类型传值传参和传值返回都会调用拷贝构造完成。

 拷贝构造函数的使用

  • 若我们创建了两个Date类,一个类对象a,一个类对象b,我们想将a的成员变量的值传给b,这时候我们就要利用拷贝构造函数。

以上面的代码为例,编译器会默认生成一个拷贝构造函数。编译器会根据类的成员变量来生成一个拷贝构造函数。(注:下面的拷贝构造函数是为了理解才写出来的,编译器默认生成的拷贝构造函数不会显示。)

//拷贝构造函数
class Date
{    
public:
    //编译器默认生成的。
    Date(const Date& d)
    {    
        _day = d._day;
    }

    Date(int day = 100)
    {
        _day = day;
    }
private:
    int _day;
};

int main()
{
    Date a(10);
    Date b(a);

    return 0;
}
  • 像Date这样的类成员变量全是内置类型且没有指向什么资源,编译器自动生成的拷贝构造就可以完成需要的拷贝,所以不需要我们显示实现拷贝构造。但想Stack这样的类,编译器自动生成的拷贝构造完成的值拷贝/浅拷贝不符合我们的需求,所以需要我们自己实现深拷贝(对指向的资源也进行拷贝)。

若我们要想上面一样创建两个Stack对象,然后将一个对象的成员变量的值传给另一个对象,将会发生什么。 

class Stack
{ 
public:
    Stack(int n = 4)
    {
        _a = new int[n];
        _capacity = n;
        _top = 0;
    }
private:
    int* _a;
    int _capacity;
    int _top;
};

int main()
{
    Stack s1;
    Stack s2(s1);
    return 0;
}

运行成功了

但其实有很大的问题的,它们的_a都指向了同一块空间,s1中_a值的改变会导致s2_a值的改变。所以我们要自己写一个拷贝构造函数。

解决方法:

用深拷贝的方式来拷贝构造函数,将它们成员变量里的值按字节的方式来拷贝即可。

class Stack
{ 
public:
    Stack(int n = 4)
    {
        _a = new int[n];
        _capacity = n;
        _top = 0;
    }
    Stack(const Stack& st)
    { 
        _a = new int[st._capacity];
        memcpy(_a, st._a, int * st._top)
        _top = st._top;
        _capacity = st._capacity;
    }
private:
    int* _a;
    int _capacity;
    int _top;
};

int main()
{
    Stack s1;
    Stack s2(s1);
    return 0;
}

这时候它们两成员变量_a指向的就是不同的空间了


易错点:

  • 拷贝构造函数的参数只有⼀个且必须是类类型对象的引用,使用传值⽅式编译器直接报错,因为语法逻辑上会引发⽆穷递归调用。
//拷贝构造函数
class Date
{    
public:
    Date(const Date d)
    {    
        _day = d._day;
    }

    Date(int day = 100)
    {
        _day = day;
    }
private:
    int _day;
};

int main()
{
    Date a(10);
    Date b(a);

    return 0;
}

 如上面将  Date(const Date& d)   改为  Date(const Date d)  就会触发无限递归。

原理是当我们要传一个自定义类型的时候,且没有用引用传参,编译器会在实参传递给形参的时候会调用拷贝构造函数,但要调用拷贝构造函数的时候又要传参,传参的时候又要调用拷贝构造函数……所以会触发无限递归。


3、析构函数

 析构函数与构造函数功能相反,析构函数不是完成对对象本⾝的销毁,比如局部对象是存在栈帧的,函数结束栈帧销毁,他就释放了,不需要我们管,C++规定对象在销毁时会自动调⽤析构函数,完成对象中资源的清理释放工作。析构函数的功能类⽐我们之前Stack实现的Destroy功能,而像Date没有Destroy,其实就是没有资源需要释放,所以严格说Date是不需要析构函数的。

 析构函数的特点:

  • 析构函数名是在类名前加上字符~。
  • 无参数无返回值。(这里跟构造类似,也不需要加void)
  • 一个类只能有⼀个析构函数。若未显式定义,系统会自动生成默认的析构函数。
  • 对象生命周期结束时,系统会自动调用析构函数。
  • 跟构造函数类似,我们不写编译器自动生成的析构函数对内置类型成员不做处理,自定类型成员会调用他的析构函数。

析构函数的使用

在一个类里面如果没有申请资源时,析构函数可以不写,直接使⽤编译器⽣成的默认析构函数。若用了像malloc、cealloc、realloc或new……之类资源申请,我们一定要自己写析构函数,否则会造成资源泄漏。

以Stack为例:

class Stack
{ 
public:
    Stack(int n = 4)
    {
        _a = new int[n];
        _capacity = n;
        _top = 0;
    }
private:
    int* _a;
    int _capacity;
    int _top;
};

int main()
{
    Stack s1;
    
    return 0;
}

单单依靠编译器生成的析构函数会导致内存泄漏的,我们要自己”手搓“一个析构函数。

~Stack()
{
    free(_a);
    _a = nullptr;
    _top = _capacity = 0;
}

向上面的代码一样,我们有资源的申请,就要有资源的释放。


4、赋值运算符重载

赋值运算符重载是⼀个默认成员函数,⽤于完成两个已经存在的对象直接的拷贝赋值,这⾥要注意跟拷贝构造区分,拷贝构造⽤于⼀个对象拷贝初始化给另⼀个要创建的对象。

赋值运算符重载的特点:

  • 赋值运算符重载是⼀个运算符重载,规定必须重载为成员函数。赋值运算重载的参数建议写成const当前类类型引用,否则会传值传参会有拷贝。
  • 有返回值,且建议写成当前类类型引用,引用返回可以提高效率,有返回值目的是为了支持连续赋值场景。
  • 没有显式实现时,编译器会自动⽣成⼀个默认赋值运算符重载,默认赋值运算符重载行为跟默认构造函数类似,对内置类型成员变量会完成值拷贝/浅拷贝(⼀个字节⼀个字节的拷贝),对⾃定义类型成员变量会调用他的拷贝构造。

 赋值运算符重载的使用

 以Date类为例子。我们先创建三个Date类对象,我们用对象a来赋值运算符重载将值传给对象b。

//拷贝构造函数
class Date
{    
public:
    Date(int day = 100)
    {
        _day = day;
    }
private:
    int _day;
};

int main()
{
    Date a(10);
    Date b;
    b = a;
    return 0;
}

类里没有资源的申请,单单用编译器生成的默认赋值运算符重载就可以了,但具体代码是如何实现的呢?

//赋值运算符重载
Date operator= (const Date& d)
{
    _day = d._day;
    return *this
}

 上面的代码放入到Date类里也编译器也可以运行,但如我们要再创建一个Date对象c,a赋值运算符重载对象b的同时也赋值运算符重载对象c。我们可以优化一下,将返回值改为Date&即可。



//拷贝构造函数
class Date
{    
public:
    Date(int day = 100)
    {
        _day = day;
    }
    //赋值运算符重载
    Date& operator= (const Date& d)
    {
        _day = d._day;
        return *this
    }
private:
    int _day;
};

int main()
{
    Date a(10);
    Date b;
    Date c;
    c = b = a;
    return 0;
}

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

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

相关文章

Etsy开店指南:分步指南与防封技巧

您的Etsy帐户在注册后不久就被封了吗?如果是这样,您在设置Etsy帐户时就已经错误了,其实这其中还是有很多细节需要注意;本文全面讲解了如何逐步创建帐户,如果你也正在准备,那就继续看吧! 一、在开…

基于Java的汽车租赁管理系统设计(含文档、源码)

本篇文章论述的是基于Java的汽车租赁管理系统设计的详情介绍,如果对您有帮助的话,还请关注一下哦,如果有资源方面的需要可以联系我。 目录 摘 要 系统运行截图 系统总体设计 系统论文 资源下载 摘 要 近年来,随着改革开放…

元宇宙:科技巨头的下一个战场

热门标题: “元宇宙:科技巨头的下一个战场” 相关文章问题: 问题: 在科技巨头纷纷布局元宇宙的背景下,元宇宙将如何影响未来的科技产业和日常生活? 文章概要: 随着Facebook更名为Meta&…

Unity XR Interaction Toolkit的安装(二)

提示:文章有错误的地方,还望诸位大神不吝指教! 文章目录 前言一、安装1.打开unity项目2.打开包管理器(PackageManage)3.导入Input System依赖包4.Interaction Layers unity设置总结 前言 安装前请注意:需要…

吴恩达深度学习笔记:机器学习策略(2)(ML Strategy (2)) 2.9-2.10

目录 第三门课 结构化机器学习项目(Structuring Machine Learning Projects)第二周:机器学习策略(2)(ML Strategy (2))2.9 什么是端到端的深度学习?(What is end-to-end deep learning?&#x…

前端转base64格式的字体图标方法

1.将需要的字体图标包下载到本地 2.访问 transfonter.org 将字体图标转成base64格式 如 这样就可以和正常的字体图标一样使用了

【深度学习驱动智能超材料设计与应用】

在深度学习与超材料融合的背景下,不仅提高了设计的效率和质量,还为实现定制化和精准化的治疗提供了可能,展现了在材料科学领域的巨大潜力。深度学习可以帮助实现超材料结构参数的优化、电磁响应的预测、拓扑结构的自动设计、相位的预测及结构…

【HTML入门】第十五课 - form表单(下)表单控件们(二)

上一小节我们说了文本输入框,密码输入框,数值型输入框,还有大的文本域。这一小节,我们继续说form表单中的一些常用的控件们。 目录 1 单选按钮 2 复选框 3 下拉列表选择 1 单选按钮 单选按钮,就是说一组按钮中&am…

栈(用C语言实现)

1. 栈 1.1 概念与结构 栈:⼀种特殊的线性表,其只允许在固定的⼀端进行插入和删除元素操作。进行数据插入和删除操作的⼀端称为栈顶,另⼀端称为栈底。栈中的数据元素遵守后进先出 LIFO(Last In First Out)的原则。 压…

多源字段聚合重塑算法

要求如下 [[{"oone": "评估是否聘请第三方机构","otwo": null,"othree": "test",},{"oone": "评估是否聘请第三方机构","otwo": null,"othree": "test",}],[{"oon…

使用 Flask 3 搭建问答平台(三):注册页面模板渲染

前言 前端文件下载 链接https://pan.baidu.com/s/1Ju5hhhhy5pcUMM7VS3S5YA?pwd6666%C2%A0 知识点 1. 在路由中渲染前端页面 2. 使用 JinJa 2 模板实现前端代码复用 一、auth.py from flask import render_templatebp.route(/register, methods[GET]) def register():re…

Elasticsearch:评估搜索相关性 - 第 1 部分

作者:来自 Elastic Thanos Papaoikonomou, Thomas Veasey 这是一系列博客文章中的第一篇,讨论如何在更好地理解 BEIR 基准的背景下考虑评估你自己的搜索系统。我们将介绍具体的技巧和技术,以便在更好地理解 BEIR 的背景下改进你的搜索评估流程…

java用freemarker导出word

freemarker导出word 第一步、将word转换为xml格式第二步、将转换后的xml文件修改后缀为ftl后复制到项目 resources 目录下(可以自己新建一个文件夹放在文件夹中)第三步、格式化xml代码(如果问价太大可能会无法格式化)这时候需要在…

鸿蒙语言基础类库:【@system.router (页面路由)】

页面路由 说明: 从API Version 8 开始,该接口不再维护,推荐使用新接口[ohos.router])。本模块首批接口从API version 3开始支持。后续版本的新增接口,采用上角标单独标记接口的起始版本。 导入模块 import router from system.ro…

Unity扩展SVN命令

可以直接在unity里右键文件提交和查看提交记录 顶部菜单栏上回退和更新整个unity工程 SvnForUnity.CS 记得要放在Editor文件夹下 using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; using UnityEditor; using Unity…

前端pc和小程序接入快递100(跳转方式和api方式)====实时查询接口

文章目录 跳转方式微信小程序(我以uniapp为例)pc api接入说明关于签名计算成功示例 跳转方式 没有任何开发成本,直接一键接入 可以直接看官方文档 https://www.kuaidi100.com/openapi/api_wxmp.shtml 微信小程序(我以uniapp为例…

算法日记day 11(KMP算法)

一、KMP算法 基本原理: KMP算法(Knuth-Morris-Pratt算法)是一种用于在一个文本串(字符串)中查找一个模式串(子串)的高效算法。它的主要优点是在匹配过程中避免了回溯(backtracking…

【QT】label中添加QImage图片并旋转(水平翻转、垂直翻转、顺时针旋转、逆时针旋转)

目录 0.简介 1.详细代码及解释 1)原label显示在界面上 2)水平翻转 3)垂直翻转 4)顺时针旋转45度 5)逆时针旋转 0.简介 环境:windows11 QtCreator 背景:demo,父类为QWidget&a…

盒须图boxplot 展示第6条线

正常情况下,盒须图是有5条线的,但是实际产品场景是需要6条线,看了下echarts官网,没看到可配置的地方,只能自己骚操作了,效果图如下: 重点:用两条x轴,第6条线挂在第二条x…

【flink】之如何快速搭建一个flink项目

1.通过命令快速生成一个flink项目 curl https://flink.apache.org/q/quickstart.sh | bash -s 1.19.1 生成文件目录: 其中pom文件包好我们所需要的基础flink相关依赖 2.测试 public class DataStreamJob {public static void main(String[] args) throws Except…