首页 > 编程语言 >【C++】类与对象理解和学习(中)

【C++】类与对象理解和学习(中)

时间:2023-03-25 16:04:12浏览次数:36  
标签:函数 对象 默认 编译器 理解 C++ 重载 拷贝 构造函数

【C++】类与对象理解和学习(中)_析构函数


六大默认成员函数

前言

每个类中都含有六大默认成员函数,也就是说,即使这个类是个空类,里面什么都没有写,但是编译器依然会自动生成六个默认成员函数,可以说它们六个是祖师爷钦点的“天选之子”。如下图所示:

【C++】类与对象理解和学习(中)_析构函数_02

这里我们来谈一谈何为默认?默认是有两种情况:

  1. 我们不写,编译器自动生成的
  2. 我们根据规定写的(后面会一一讲到),但是函数参数为全缺省或者不含有函数参数的。

构造函数

首先来谈谈它的作用,就是减少我们手动初始化,实现自动初始化。就比如下面的代码,我们要想对对象进行初始化,就要调用函数Init,会很麻烦,而构造函数就是为了解决这个麻烦的。接下来我们先谈一谈它的创建规则

构造函数的函数名与类名相同
无返回值
对象实例化时编译器自动调用对应的构造函数。
构造函数可以重载(可以存在多个构造函数,但默认构造函数只有一个)

【C++】类与对象理解和学习(中)_构造函数_03

(当然现阶段初始化的方式本质实际上是通过函数体赋值实现,并不是完全意义上的初始化,后面的章节会讲到初始化列表,通过初始化列表实现真正意义上的初始化)

这里还有一个注意点需要注意一但自己写了构造函数,不管是默认构造函数,还是普通构造函数,编译器都不会自动默认生成构造函数。

class Date
{
public:
    ////普通构造函数
    Date(char a)
    {
        _year = 2023;
        _month = 2;
        _day = 19;
    }
//一但我们自己写了构造函数,不管是属于默认类型的还是普通类型的,编译器都不会自动默认生成
private:
    int _year;
    int _month;
    int _day;
};
int main()
{
    //Date d1;//这里编译器会报错,没有合适的默认构造函数,这是因为我们自己写了普通类型的构造函数,编译器就不会再自动生成默认构造函数,而我们的类中又没有写默认构造函数,所以会报错。
    return 0;
}

默认构造函数

上面我们已经讲过默认构造函数的形式,一种是编译器自动生成,另一种是自己根据规则来写但是函数无参数或者参数为全缺省参数。后面的自己写的我们上面讲过了,这里我们来谈一谈编译器自动默认生成的。

【C++】类与对象理解和学习(中)_编译器_04

我们发现编译器自动生成的默认构造函数竟然没有给我们的数据做初始化处理,这是为啥呢?实际上是我们的祖师爷埋下的一个大坑:默认构造函数对内置类型(int double char...)不做处理,但是对自定义类型(struct class union)会自动调用它所对应的默认构造函数。我们下面来实验一下会发现确实如此:

【C++】类与对象理解和学习(中)_编译器_05

不过对于这个大坑,C++11中新增了一个补丁(一些老版本的编译器可能不支持)可以在声明时给内置类型一个缺省值:如下

【C++】类与对象理解和学习(中)_编译器_06

(当然对于构造函数还会有一些比较恶心的地方,我们把它放在后面一章来讲解关于初始化列表相关知识及注意点)

析构函数

析构函数的作用就是对类对象中的资源进行清理,它的创建方式与构造函数大致相同

析构函数名是在类名前加上字符 ~。
无参数无返回值类型。
一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构函数不能重载(只能存在一个,要么是编译器自动生成的,要么是自己写的)
对象生命周期结束时,C++编译系统系统自动调用析构函数

【C++】类与对象理解和学习(中)_构造函数_07

当然,在析构函数这里也存在着一个大坑,与默认构造函数相同,由于析构函数不支持重载,所以我们写的实际上就是默认析构函数,它在清理资源时对内置类型不做处理,对自定义类型会调用该自定义类型的默认析构函数。

【C++】类与对象理解和学习(中)_编译器_08

因此我们可以这样来说:

对于普通的内置类型,我们不需要写析构函数(写了后我们自己的就是默认析构函数),编译器自动生成的就够用了,但是对于向系统申请空间,涉及到动态内存开辟的,则必须由我们自己写析构函数进行释放资源,不然会造成内存泄漏。

【C++】类与对象理解和学习(中)_编译器_09

构造与析构的调用顺序

这里简单提一嘴,就是类对象默认构造与默认析构函数的调用顺序与我们学过的一种数据结构——栈类似,先定义的先调用构造,后调用析构(先进后出)

Date d1;
Date d2;
//构造函数调用顺序:d1 d2
//析构函数调用顺序:d2 d1

拷贝构造函数

拷贝构造函数看名字也可以看出来,是属于构造函数的一种重载。作用是完成拷贝工作。它的创造规则如下:

与构造函数相同
这里需要注意的是拷贝构造函数只有一个参数,并且这个参数必须是类类型对象的引用,使用传值方式编译器直接报错,因为会引发无穷递归调用。
如果自己没写,编译器会生成默认的拷贝构造,拷贝方式为浅拷贝(值拷贝),对内置类型实现浅拷贝,对自定义类型则调用该自定义类型的拷贝构造函数。

谈一谈无穷递归问题

对于自定义类型,在传值传参的时候会·产生拷贝构造,而调用拷贝构造函数又需要传参,传值传参又会产生拷贝构造...(无限套娃)

【C++】类与对象理解和学习(中)_析构函数_10

正确写法应为传引用传参

Date(const Date& d)
    {
        _year = d._year;
        _month = d._month;
        _day = d._day;
    }

谈一谈深浅拷贝

所谓浅拷贝,其实就是值拷贝一个字节一个字节的完成拷贝,我们的编译器默认生成的就是浅拷贝。所以对于两个上面这种日期类对象,假如实现将d1拷贝给d2,我们可以这么来写:

Date d2(d1);//或者:Date d2=d1;

【C++】类与对象理解和学习(中)_编译器_11

当然,由于编译器生成的默认拷贝构造既然已经可以实现一个字节一个字节拷贝,也就是浅拷贝(值拷贝),那么对于这种日期类对象,我们可以不写拷贝构造函数,因为编译器自动生成的已经够用了,我们把代码屏蔽,依然可以编译通过,实现拷贝。

【C++】类与对象理解和学习(中)_析构函数_12

那么可能有人会很好奇,既然编译器生成的已经够用了,那么还需要我们自己来写嘛?答案是肯定的,就比如以下这种情况:涉及到空间资源开辟

【C++】类与对象理解和学习(中)_编译器_13

【C++】类与对象理解和学习(中)_构造函数_14

因此,我们可以这么说:类中如果没有涉及资源申请时,拷贝构造函数是否写都可以;一旦涉及到资源申请时,则拷贝构造函数是一定要写的,否则浅拷贝会出大问题。

(另外给大家一个小建议:一般对象传参时,尽量使用引用类型,返回时根据实际场景,能用引用

尽量使用引用,这样做是为了减少拷贝,提高程序运行效率)。

赋值运算符重载

运算符重载

C++为了增强代码的可读性引入了运算符重载,所谓运算符重载的用处是什么呢?实际上就是是我们的自定义类型也可以直接使用操作符进行操作。就比如说,整型a大于整型b,我们可以直接用>进行判断,但是对于自定义类型来说,这里就不能直接使用>。

【C++】类与对象理解和学习(中)_构造函数_15

那么什么是运算符重载呢?实际上运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。

函数名为:关键字operator后面接需要重载的运算符符号

函数原型:返回值类型 operator操作符(参数列表)

这里还有以下几点需要注意:

  1. 不能通过连接其他符号来创建新的操作符:比如operator@
  2. 重载操作符必须有一个类类型参数
  3. 用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不 能改变其含义
  4. 作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this
  5. .* :: sizeof ?: . 注意以上5个运算符不能重载。

【C++】类与对象理解和学习(中)_编译器_16

我们再来谈一谈赋值运算符重载

赋值运算符重载是属于运算符重载中的一种,我们需要注意以下几点:

参数类型:const T&,传递引用可以提高传参效率

返回值类型:T&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值

另外,我们不要与拷贝构造记混了,拷贝构造是给实例化对象初始化,此时实例化对象还没有创建完成,但赋值是两个都已经实例化后的对象,a的值赋值给b,b=a,此时a与b都是以及实例化后的。

【C++】类与对象理解和学习(中)_析构函数_17

这里我们还需要注意的就是,它是祖师爷钦点的天选之子之一,所以即使我们不写,编译器也会生成一个默认赋值运算符重载,但是,我们写的话只能在类内写,不可以写在全局,这是因为如果类中没有赋值运算符,编译器会自动生成一个,假如此时我们在类外又写了一个,就会发生冲突!

【C++】类与对象理解和学习(中)_构造函数_18

关于默认运算符重载

编译器生成的默认运算符重载,对于内置类型会以值的方式逐字节拷贝(即浅拷贝),对于自定义类型,则会调用该自定义类型的默认运算符重载。(因此,我们上面的这个日期类,我们不写赋值运算符重载,用编译器生成的就够用了)

(当然,与我们上面讲的默认拷贝构造一样,如果不涉及到向内存申请空间资源,我们就不需要写,但是一旦涉及到,我们就需要使用深拷贝的方式来实现,原因与拷贝构造那里讲的一样

const成员函数

const成员函数其实就是在成员函数后面加个const,这里的const修饰的是隐含形参:this指针!const放在成员函数后面后,*this即调用这个函数的类对象里的数据便不可以发生修改!

【C++】类与对象理解和学习(中)_构造函数_19

取地址及const取地址操作符重载

这两个我们一般不需要进行重载,用编译器生成的默认的即可(自己返回一个假地址玩一玩的话,也可以自己写着玩)

【C++】类与对象理解和学习(中)_编译器_20


end

生活原本沉闷,但跑起来就会有风!

标签:函数,对象,默认,编译器,理解,C++,重载,拷贝,构造函数
From: https://blog.51cto.com/u_15954929/6149526

相关文章

  • feature map 的理解与处理
    特征理解不变性和等变性是图像特征表示的两个重要性质。分类需要不变特征表示,因为它的目标是学习高级语义信息。目标定位要求等变表示,因为它的目的是鉴别位置和尺度的变......
  • docker挂载volume的用户权限问题,理解docker容器的uid
    docker挂载volume的用户权限问题,理解docker容器的uid在刚开始使用dockervolume挂载数据卷的时候,经常出现没有权限的问题。这里通过遇到的问题来理解docker容器用户uid......
  • Winform/Csharp中使用StackExchange.Redis连接Redis存取数据并序列化对象/反序列化(支
    场景在winform程序中,需要连接Redis并根据Key进行模糊搜索,对value值进行反序列化为对象之后进行数据处理和显示。ServiceStack.redis这里不使用servicestack.redis,因为......
  • .net异步编程中async 和await深入理解
    1、await运算符应用于异步方法中的任务,在方法的执行中插入挂起点,直到所等待的任务完成。任务表示正在进行的工作。2、await仅可用于由async关键字修改的异步方法中。使......
  • C++餐厅点餐结算系统[2023-03-25]
    C++餐厅点餐结算系统[2023-03-25]题目某餐馆根据实际需要欲开发一套《餐厅点餐结算系统》,具体要求如下:1、系统用户包括消费者、收银员、厨师、服务员、餐厅老板、系统......
  • c++union用法
    参考文章:c++中union的使用  union使用方法union即为联合,它是一种特殊的类。通过关键字union进行定义,一个union可以有多个数据成员。在任意时刻,联合中只能有一个数据成......
  • 类和对象
    目录1.类和对象1.1类和对象的理解1.2类的定义1.3对象的使用1.4学生对象-练习2.对象内存图2.1单个对象内存图2.2多个对象内存图3.成员变量和局部变量3.1成员变量......
  • 程序员如何理解生死簿-宿命
    程序员如何理解生死簿-宿命假若:未知的世界有一台超级计算机,这台计算机的算力超级强大,拥有无限的算力。我们生活的世界就是这台计算机上的一个程序,算法逻辑已经固定,人生......
  • vue状态管理pinia之监听state里的对象
    如上图所示,我需要监听layerList的变化,代码如下:importuseLayersStorefrom"@/store/modules/layers";constlayersStore=useLayersStore();watch(()=>userStore.l......
  • 获得反射对象
    packageedu.wtbu;//反射publicclassDemo03extendsObject{publicstaticvoidmain(String[]args)throwsClassNotFoundException{Useruser=newUs......