c++构造函数与析构函数

注:部分来源于网络相关资料,用于个人总结学习记录,如有侵权,请联删。

1、构造函数的定义

建立一个对象时,通常最需要立即做的工作是初始化对象,如对数据成员赋初值

构造函数就是用来在创建对象时初始化对象,为对象数据成员赋初始值。

类的数据成员是不能在类定义时初始化的,例如:

原因是类定义不没有产生实体,而只是给出了一个数据类型,不占用存储空间,因而业务处容纳数据。

如果一个类中所有的数据成员是共有的,则可以在定义对象时对数据成员进行初始化。例如:

如果类中的数据成员是私有的,如private或者protected的,就不能用这种方法进行初始化,因为外部不能直接访问私有的数据成员。

c++提供了构造函数来处理对象的初始化问题。构造函数是类的一种特殊成员函数,不需要人为调用,而是建立对象时自动被执行。

定义构造函数:

c++规定构造函数的名字与类的名字相同,而且不能指定返回类型。

定义形式为:

构造函数可以没有形参,有如下两种形式:

与其他任何函数一样,构造函数可以被声明为内联函数。

只要创建了类类型的新对象,都要执行构造函数。因此,构造函数主要用途是初始化类的数据成员。

构造函数举例:

关于构造函数的声明:

(1)构造函数是在创建对象时自动执行的,而且只执行一次,并先于其他成员函数执行。构造函数不需要人为调用,也不能被人为调用

(2)构造函数一般声明为公有的,因为创建对象通常是在类的外部进行的。

(3)构造函数的函数体中不仅可以对数据成员初始化,而且可以包含任意其他功能的语句,例如分配动态内存,但一般不会在构造函数中加入与初始化无关的内容

(4)每个构造函数应该为每个数据成员提供初始化。否则将使那些数据成员处于未定义的状态。而使用一个未定义的成员是错误的。

2、构造函数初始化列表

与普通函数一样,构造函数是具有函数名、形参列表和函数体。与其他函数不同的是,构造函数可以包含一个构造函数初始化列表,一般形式为:

与其他的成员函数一样,构造函数可以定义在类的内部或外部,但构造函数初始化列表只在构造函数的定义中而不是函数原型声明中指定

从初始化角度来看,可以认为构造函数分为两个阶段执行:

1、初始化阶段;

2、普通的计算阶段。

初始化阶段由构造函数初始化列表组成,计算阶段由构造函数函数体的所有语句组成,初始化阶段先于普通的计算阶段。即:

举例:

关于构造函数初始化列表的说明:

有时必须用构造函数初始化列表

如果没有为类类型的数据成员提供初始化列表,编译器会隐式地使用该成员地默认构造函数(A类中包含B类地对象)。如果那个类没有默认构造函数(不带参数的构造函数),则编译器会报告错误。在这种情况下,为了初始化类类型的数据成员,必须提供初始化列表。

一般地,没有默认构造函数的成员,以及const或引用类型的成员,都必须在构造函数初始化列表中进行初始化。

举例:

3、构造函数的重载

在一个类中可以定义多个构造函数的版本,即构造函数允许被重载,只要每个构造函数的形参列表是唯一的。一个类的构造函数数量是没有限制的。一般地,不同的构造函数允许建立对象时用不同的方式来初始化数据成员。

尽管在一个类中可以包含多个构造函数,但是对于每一个对象来说,建立对象只执行其中一个,并非每个构造函数都被执行。

关于构造函数默认参数的说明:

(1)必须在类的内部指定构造函数的默认参数,不能在类外部指定默认参数

(2)如果构造函数的全部实参都指定了默认值,则在定义对象时可以给一个或几个实参,也可以不给出实参。这时,就与无参数的构造函数有歧义了,所以编写构造函数一定不要出现这种歧义。

(3)在一个类中定义了带默认参数的构造函数后,不能再定义与之有冲突的重载构造函数。

一般地,不应同时使用构造函数的重载和带默认参数的构造函数。

4、定义默认构造函数

默认构造函数就是在没有显示提供初始化式时调用的构造函数,它是一个不带参数的构造函数。如果定义某个类的对象时没有提供初始化式就会使用默认构造函数。

定义默认构造函数的一般形式为:

类名()
{
    函数体
}

它由不带参数的默认构造函数,或者所有形参均是默认参数的构造函数定义。与默认构造函数相对应的对象定义形式为:

类名 对象名;

(1)任何一个类有且只有一个默认构造函数。如果定义的类中没有显式定义任何构造函数,编译器会自动为该类生成默认构造函数,称为合成默认构造函数

(2)一个类哪怕是只定义了一个构造函数,编译器也不会再生成默认构造函数。即,如果为类定义了一个带参数的构造函数,还想要无参数的构造函数,就必须自己定义它。

(3)一般地,任何一个类都应该定义一个默认构造函数。因为,在很多情况下,默认构造函数是由编译器隐式调用的

5、隐式类类型转换

为了实现其他类型到类类型的隐式转换,需要定义合适的构造函数。可以用到单个实参调用的构造函数(称为转换构造函数)定义从形参类型到该类类型的隐式转换。

举例:

Data(i); //将i这个string类型的对象转换成了Data类型的对象,生成了一个临时对象,然后再将这个临时对象赋值给Data对象。

使用单个参数的构造函数来进行类类型转换的方法可以总结如下:

(1)声明一个类

(2)在这个类中定义一个只有一个参数的构造函数,参数的类型是需要转换的数据类型,即转换构造函数的一般形式为:

类名(const 指定数据类型& obj)

(3)采用转换构造函数定义对象时即进行类型转换,一般形式为:

类名(指定数据类型的数据对象)

可以禁止由构造函数定义的隐式类型转换,方法是通过将构造函数声明为explicit,来防止在需要隐式转换的上下文使用构造函数。

c++关键字explicit用来修饰类的构造函数,指明该构造函数是显式的。expllicit关键字只能用于类内部的构造函数声明上,在类定义外不能重复它。

一般地,除非有明显的理由想要定义隐式转换,否则,单形参构造函数应该为explicit。将构造函数设置为explicit可以避免错误,如果真需要转换,可以显式地构造对象。

6、拷贝构造函数

对象赋值给对象,拷贝构造函数的作用是用一个已经生成的对象来初始化另一个同类的对象。

变量的初始化:

int a = 10; int b = a;

对象的初始化:

Point pt1(10,20); Point pt2 = pt1;

拷贝构造函数定义的一般形式:

例如:

拷贝构造函数有且只有一个本类类型的引用形参,通常使用const限定。

拷贝构造函数的功能是利用一个已知的对象来初始化一个被创建的同类的对象

与拷贝构造函数对应的对象的定义形式为:

类名 对象名1(类对象1),对象名2(类对象2), ... ...;

对象的拷贝是从无到有地建立一个新对象,并使它与一个已有的对象完全相同(包括对象的结构和对象的成员)。这点要与赋值区分开来。

7、合成拷贝构造函数

每个类必须有一个合成拷贝构造函数。如果类没有定义复制构造函数,编译器就会自动合成一个,称为合成拷贝构造函数

与合成默认构造函数不同,即使定义了其他构造函数,编译器也会合成拷贝构造函数

合成拷贝构造函数的操作是:执行逐个成员初始化,将新对象初始化为原对象的副本。所谓“逐个成员”,指的是编译器将现对象的每个非静态数据成员,依次拷贝到正创建的对象中。

三种情况会用到拷贝构造函数:

(1)用一个对象显式或者隐式初始化另一个对象。

复制初始化和直接初始化是有区别的:直接初始化会调用与实参匹配的构造函数;而复制初始化总是调用拷贝构造函数。

(2)函数参数按值传递对象时或函数返回对象时。

当函数形参为对象类型,而非指针和引用类型时,函数调用按值传递对象,即编译器调用拷贝构造函数产生一个实参对象副本传递到函数中。类似地,以对象类型作为返回值时,编译器调用拷贝构造函数产生一个return语句中的值的副本返回到调用函数。

(3)根据元素初始化式列表初始化数组元素时。

如果没有为类类型数组提供元素初始化式,则将用默认构造函数初始化每个元素。然而,如果使用常规的大括号的数组初值列表形式来初始化数组时,则使用拷贝初始化来初始化每一个元素。

总结:正是有拷贝构造函数,函数才可以传递对象和返回对象,对象数组才能用初值列表的形式初始化

8、深拷贝与浅拷贝

若拷贝对象但未拷贝资源内容成为浅拷贝。如下:

带来的问题:撤销a的p存储空间,b如何处理呢?

深拷贝:如果一个拥有资源的类对象发生拷贝时,若对象数据与组员内容一起复制,称之为深拷贝,如下图:

系统合成拷贝构造函数都是浅拷贝构造函数,自己定义拷贝构造函数主要就是为了定义深拷贝构造函数。

深拷贝与浅拷贝举例:

Str = cstr; //为浅拷贝

9、析构函数定义

析构函数:当对象脱离其作用域时(例如对象所在的函数已经调用完毕),系统会自动执行析构函数。析构函数往往用来做清理的工作(例如在建立对象时用new开辟了一段内存空间,则在该对象消亡前应在析构函数中用delete释放这段存储空间)。

~类名()
{
    函数体
}

析构函数不返回任何值,没有返回类型,也没有函数参数。由于没有函数参数,因此它不能被重载。换言之,一个类可以有多个构造函数,但是只能有一个析构函数。

何时调用析构函数:

(1)对象在程序运行超出其作用域时自动撤销,撤销时自动调用该对象的析构函数。如函数中的非静态局部对象。

(2)如果用new运算动态地建立一个对象,那么用delete运算释放该对象时,调用该对象的析构函数。

举例:

10、合成析构函数

与拷贝构造函数不同,编译器总是会为类生成一个析构函数,称为合成析构函数。

合成析构函数并不删除指针成员所指向的对象,所以需要编写程序者显式地编写析构函数去处理。

11、何时需要编写析构函数

如果类需要析构函数,则该类几乎必然需要定义自己的拷贝构造函数和赋值运算符重载,这个规则称为析构函数三法则。

往往有一个指针成员,在析构函数里面进行释放。

构造函数和析构函数的调用很像一个栈的先进先出,调用析构函数的次序正好与调用构造函数的次序相反。

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值