首页 > 编程语言 >C++类与对象(二)

C++类与对象(二)

时间:2023-08-23 22:01:46浏览次数:37  
标签:初始化 变量 对象 函数 C++ 编译器 拷贝 构造函数

一、类的默认成员函数

类内的默认成员函数:用户不显示实现,编译器就会自动生成的成员函数,被称为类的默认成员函数。这些默认成员函数各有各存在的作用。但实际上,很多时候,需要自己写这些成员函数,而不是使用编译器生成的。

翻译一下就是,在类内有这样六个成员函数,如果你不写,编译器就会自动生成,因为是编译器生成的,使用被称为默认成员函数。但只要你写了,编译器就不会帮你生成。而这样的成员函数一共有六个。本文将会先介绍三个。

二、构造函数的初步认识

1.构造函数的特点

无返回值

函数名为类名

构造函数可以重载

对象实例化时编译器自动调用对应的构造函数

2.构造函数的作用

对象实例化时自动调用构造函数,完成对类对象的初始化。也就是对对象的成员变量初始化。

3.可以重载的构造函数

如下图,是一个用户显示实现的无参的构造函数

C++类与对象(二)_析构函数

当用户显示实现了一个无参构造函数时,即使函数内什么都没写,但是类对象s1依旧完成了初始化。这是因为,构造函数在类对象创建的时候会自动调用。虽然说是自动调用,但实际上还是需要一些辅助条件。

语句Stack s1;其实就相当于是在调用我们显示实现的构造函数。本来应该是这样的Stack s1();该语句更符合调用无参函数时的情景,但是但是Stack s1();的写法与函数声明相同,编译器无法区分,因此写做Stack s1其实就是调用无参构造函数的写法。而不能写作Stack s1();

如下图,是一个显示实现的含参的构造函数,同无参构造函数构成重载

C++类与对象(二)_类的默认成员函数_02

可以看到,即使显示实现的含参数的构造函数什么也不写,对象s2也依旧和s1一样被初始化。到这里大家可能以为s2自动调用的是含参数的构造函数,其实并不是。前文提到,构造函数的自动调用也是需要辅助条件的,这是因为构造函数支持重载,而Stack s1;的语句只是调用无参构造函数的写法,而要想自动调用含参数的构造函数,需要在实例化对象的时候传参数过去。如下图中的s2

C++类与对象(二)_构造函数_03

C++类与对象(二)_拷贝构造函数_04

可以看到,即使无参构造函数或者和含参数的构造函数内什么都没写,也会将对象s1,s2的成员变量初始化。但是如果我们在其中添加了赋值语句,则可以根据自己的需求任意初始化。如下图

C++类与对象(二)_类的默认成员函数_05

与此同时,构造函数的参数也支持缺省参数的写法,这样我们如果在对象实例化的时候,不传递参数,也会按照缺省值进行初始化。如果传递参数,则按照传递的参数进行初始化。如下图

C++类与对象(二)_类的默认成员函数_06

如果上图的无参构造函数没有被注释,那么对象实例化时不传参会造成调用歧义,编译器无法分辨是要调用无参的还是全缺省值,如下图

C++类与对象(二)_析构函数_07


4.编译器生成的构造函数

上文一直说的都是显示实现的无参构造函数。实际上,如果我们不写构造函数,编译器就会自动生成一个默认的无参的构造函数,来看看二者的区别。如下图

C++类与对象(二)_析构函数_08

可以看到,编译器生成的无参构造函数将对象s1的成员变量初始化,对象s1的成员变量任然是随机值。这是因为,编译器生成的无参构造函数在初始化对象的成员变量时,是分情况的,对于内置类型的成员变量,不做处理(实际上有的编译器会处理,但是我们当作不处理),而对于自定义类型则会调用该类型的默认构造函数。

补充:显示实现的无参构造函数,全缺省的构造函数,以及编译器生成的构造函数都被称为默认构造函数

补充:类内的成员变量的声明也可以有缺省值,这时候如果没有显示实现的构造函数,编译器生成的构造函数也会根据给出的缺省值进行初始化,但是如果显示传参或者显示实现的含参数构造函数有缺省值。(这其实就是对编译器生成的构造函数无法初始化内置类型打的一个补丁)。则以后者为优先如下图

C++类与对象(二)_拷贝构造函数_09

C++类与对象(二)_析构函数_10

C++类与对象(二)_类的默认成员函数_11

5.构造函数是否需要显示实现

如果不显示实现构造函数,编译器虽然也会生成一个无参构造函数,但却并不会对内置类型的成员变量进行初始化(会是随机值)。

什么情况下不需要写构造函数

1.类内的成员变量有缺省值。因为有缺省值,在不传参时,会按照缺省值进行初始化

2.类内的成员变量都是自定义类型。因为编译器生成的构造函数会对自定义类型调用该自定义类型(其实就是类)的默认构造函数,所以本身可以不写构造函数(但是调用的默认构造函数得有初始化功能)。

大多数情况下,构造函数是需要我们自己写的,它可以更好的完成对对象初始化的工作。

三、构造函数的再深入

在对象实例化的时候,会自动调用构造函数来完成对构造函数的初始化。但实际上,这是通过构造函数内的赋值语句来完成的赋初值,这其实并不能叫做初始化,因为赋值语句可以有多个,而初始化只能有一次。

如下图

C++类与对象(二)_析构函数_12

在构造函数中对成员变量_top的赋值语句有三句,最终结果以最后一次为准,但多次进行赋值很明显不符合初始化只能进行一次的含义。

1.初始化列表

赋值语句不符合构造函数初始化的定义,因此有了初始化列表的诞生。

1.1初始化列表的写法

在构造函数函数名和参数列表之后,函数内部{}之前。以冒号开始,紧接着是成员变量(),括号内是对成员变量的初始化值或者表达式,其余成员变量之间以逗号隔开。如下图


1.2初始化列表的要求

1.每个成员变量在初始化列表最多只能出现一次

在出现第二次时,会报错。如下图

C++类与对象(二)_拷贝构造函数_13

2.也可以不出现,如下图

C++类与对象(二)_构造函数_14

2.初始化列表的使用场景

2.1自定义类型的成员变量,且自定义类型没有默认构造函数

类 MyQueue中存在自定义类型的成员变量Stack,并且类Stack中没有默认构造函数,MyQueue又没有用初始化列表对Stack类型的成员变量push初始化,会存在报错。如下图

C++类与对象(二)_析构函数_15

即使在函数体内部通过赋值语句进行赋初值,报错依然存在。如下图

C++类与对象(二)_类的默认成员函数_16

使用初始化列表的方式初始化,报错消失

C++类与对象(二)_拷贝构造函数_17


2.2引用类型的成员变量

引用类型的成员变量必须采取初始化列表初始化,否则会报错。如下图

C++类与对象(二)_拷贝构造函数_18

初始化列表中初始化引用成员变量时,报错消失。如下图

C++类与对象(二)_类的默认成员函数_19

2.3const成员变量

存在const成员变量,但未用初始化列表初始化,出现报错,如下图

C++类与对象(二)_拷贝构造函数_20

函数体内部的赋值语句也会报错,如下图

C++类与对象(二)_类的默认成员函数_21

采用初始化列表,报错消失。如下图

C++类与对象(二)_构造函数_22

以上三种情况下,必须使用初始化列表进行初始化。但相比直接在构造函数体内部写赋值语句,还不如直接写初始化列表。

3.自定义类型成员变量的初始化

对于自定义类型的成员变量且该类型不存在默认构造函数时,必须利用初始化列表初始化。如果存在默认构造函数,就连构造函数也可以不写。

如下图,如果不写构造函数,编译器会调用自定义类型Stack的默认构造函数进行初始化

C++类与对象(二)_类的默认成员函数_23

如果写了,即使初始化列表内容为空(没有使用初始化列表),也可以进行初始化(实际上也是调用自定义类型Stack的默认构造)

C++类与对象(二)_拷贝构造函数_24

补充:无论是否使用初始化列表,对于自定义类型成员变量,一定会先使用初始化列表初始化。初始化类别是成员定义的地方,定义就是创建变量,因为是自定义类型,在定义的时候会调用该类型的构造函数

4.初始化列表的初始化顺序

按照类中成员变量的声明顺序进行初始化,和初始化列表的出现顺序无关。如下图

C++类与对象(二)_类的默认成员函数_25

成员变量_a的声明在前,所以_a是在_b前初始化的,可初始化列表中却用_b初始化_a,所以运行失败

5.赋初值和初始化列表

直接在构造函数内写下语句,只是对对象的成员变量赋初值,成员变量可出现多次,进行多次赋值。而初始化列表中,成员变量最多出现一次。并且,在成员变量有自定义类型或者引用或者const变量时,必须使用初始化列表。因此,推荐使用初始化列表的方式来完成构造函数,进行正确的初始化

四、析构函数

1.析构函数的作用

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

翻译一下就是:析构函数的存在是为了释放动态申请资源所占据的空间(堆区),其余的临时变量和静态变量和全局变量,在程序结束后,会由编译器自动释放。

2.析构函数的特点

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

1. 析构函数名是在类名前加上字符 ~。

2. 无参数无返回值类型。

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

4.对象生命周期结束时,C++编译系统系统自动调用析构函数

3.编译器生成的析构函数

规则:编译器生成的析构函数,不会对内置类型做处理,而对于自定义类型会调用该自定义类型的析构函数。

如下图。程序运行结束后,本应释放的是类MyQueue的对象q1,但因为没有显示实现MyQueue对象的析构函数,因为MyQueue的成员变量是Stack类型作用编译器生成的析构函数又会用自定义类型Stack的析构函数,因此显示的是类Stack的析构函数内容。

C++类与对象(二)_构造函数_26

4.析构函数是否需要显示实现

如果类中没有动态申请资源时,析构函数可以不写,直接使用编译器生成析构函数,因为只要不是动态申请的资源,其他都会在程序结束时被编译器释放。如下图

C++类与对象(二)_类的默认成员函数_27

C++类与对象(二)_析构函数_28

C++类与对象(二)_拷贝构造函数_29


五、拷贝构造函数

1.编译器默认生成的拷贝构造函数

规定:,编译器生成的默认拷贝构造函数对于内置类型只会进行浅拷贝,而对于自定义类型,则会调用该自定义类型的拷贝构造。

浅拷贝也叫值拷贝,拷贝的是变量本身。例如拷贝int类型变量,就是拷贝变量本身,int*类型的指针变量,也会拷贝指针变量本身,也就是地址,而这,就可能会引起问题。

2.拷贝构造函数的特点

1.是构造函数的重载函数。

2.拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,因为会引发无穷递归调用

这是因为,在调用拷贝构造函数的时候,也就是调用函数,如果是传值方式,会将实参拷贝一份,在传给形参,而这个过程也是在拷贝对象,也需要调用拷贝构造。也就是说,调用拷贝构造前的传参也是拷贝的过程,也需要调用拷贝构造,会引发无穷递归,或者报错。(注:对象是自定义类型)

对于存在动态内存申请的对象,正确写法如下图

C++类与对象(二)_拷贝构造函数_30

C++类与对象(二)_类的默认成员函数_31

3.拷贝构造函数的作用

作用:通过拷贝构造,用已经存在的对象初始化另一个对象(拷贝已存在的对象)。

如下图

C++类与对象(二)_类的默认成员函数_32

4.拷贝构造函数是否需要显示实现

对于需要动态申请资源的情况,必须自己写拷贝构造,否则就是浅拷贝。

例如栈。如下图

C++类与对象(二)_析构函数_33

图中注释掉的拷贝构造就是值拷贝。成员变量int* _a,也是内置类型,s2在拷贝s1时,因为是编译器生成的拷贝构造,所以s2._a拷贝的是s1._a的值,也就是说,两个对象的成员变量会指向同一块空间,这就会导致,一块空间会被析构两次,程序会崩溃。这种情况就需要自己动手写拷贝构造。

而如果没有动态申请资源的情况,拷贝构造写不写都可以

5.拷贝构造函数的调用场景

1.用已存在的对象创建(初始化)另一个对象

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

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

如下图

C++类与对象(二)_拷贝构造函数_30

C++类与对象(二)_拷贝构造函数_35




标签:初始化,变量,对象,函数,C++,编译器,拷贝,构造函数
From: https://blog.51cto.com/u_15466618/7207732

相关文章

  • C++笔记
    C++笔记将数字以十六进制输出:cout<<hex<<100<<endl;将数字以八进制输出:cout<<oct<<100<<endl;精度控制include保存a位小数:setprecision(a)将b保留a位:cout<<setprecision(a)<<b<<endl将b保留小数点后a位:cout<<setiosflags(ios::fixed)<<se......
  • javaScript之内置对象1
    一:概述JavaScript内置对象表示Web浏览器内部支持的对象,它们提供了很多有用的功能,让使用者可以快速实现许多功能。二:说明及其举例说明数据类型:包括Number、String、Boolean、Object、Array等,这些对象提供了用于操作基本数据类型的方法。例如,使用String对象的substring()方法可以截取......
  • C++面向对象笔记(转载自黑马程序员)
    C++核心编程本阶段主要针对C++面向对象编程技术做详细讲解,探讨C++中的核心和精髓。1内存分区模型C++程序在执行时,将内存大方向划分为4个区域代码区:存放函数体的二进制代码,由操作系统进行管理的全局区:存放全局变量和静态变量以及常量栈区:由编译器自动分配释放,存放函数的......
  • JS 给json数组新增对象
    varjsonstr="[{'name':'a','value':1},{'name':'b','value':2}]";varjsonarray=eval('('+jsonstr+')');vararr={"name":$('#names').val(),&qu......
  • 类和对象(c++对象模型和this指针)
    1.成员变量和成员函数分开储存只有非静态成员变量才属于类的对象上。空对象内存占用空间为1this指针的概念this指针指向被调用的成员函数所属的对象this指针式隐含每个非静态成员函数内的一种指针。this指针不需要定义,直接使用即可。this指针的用途:1.当形参和成员变量同名时,可用thi......
  • 标准C++ -- day03
    一、对象的创建和销毁过程分析对象的创建过程给对象划分内存空间执行初始化列表根据继承表的顺序调用父类的无参构造或者有参构造通过:父类名(val)调用父类的有参构造根据成员变量的定义顺序调用类类型成员的无参构造或者有参构造通过:类类型成员名(val)调用类......
  • python必坑知识点(面向对象)
    面向对象的三大特性:封装,继承,多态1三大特性1.1封装将数据或方法放到类里,以供外部调用或自己隐藏#封装原则1、高内聚:高内聚是指一个模块中各个部分之间关联应该是紧密的。2、低耦合:低耦合是指多个模块之间的关联应该是松散的。1.2继承将类中的公共的方法提取到基类中......
  • js 计算对象数组中某个字段sum之和
    1、一个字段之和要计算一个对象数组中某个字段的和,你可以使用JavaScript的Array.prototype.reduce()方法。reduce()方法对数组中的每个元素执行一个提供的函数,并将结果累积为单个值。以下是一个示例:假设你有一个对象数组 data,每个对象都有一个 value 字段,你想计算所有对......
  • Qt/C++开发经验小技巧281-285
    悬停窗体QDockWidget默认在标题栏右键会弹出悬停模块的显示隐藏菜单,如果需要去掉,会发现设置Qt::NoContextMenu或者事件过滤器拦截都是无效的,必须设置dockWidget->setContextMenuPolicy(Qt::PreventContextMenu);。Qt中的布局有个默认的margin边距值和spacing间距值,在没有设......
  • Pybind11:使用C++编写Python模块
    放假摆了一周了。看论文实在不是什么有意思的活。这两天研究了一下Pybind11的用法。使用C/C++和Python混合编程的想法很早就有了,在大一的一次比赛时曾经实践过(虽然不是我写的),当时获得了比较显著的性能提升。但是当时用的是Swig,据队友说Swig对于NumPy的支持极为阴间,当时调试花了好......