C++梁哥笔记day7
类与对象
一、构造函数的调用规则
默认情况下,c++编译器至少为我们写的类增加三个函数:
1、默认构造函数(无参、函数体为空)
2、默认析构函数(无参、函数体为空)
3、默认拷贝构造函数(对类中非静态成员属性进行简单的值拷贝<浅拷贝>)
1.如果用户提供了有参构造,将屏蔽默认构造函数
2.如果用户定义了析构函数,将会屏蔽默认析构函数,不会屏蔽默认构造函数和拷贝构造函数
3.如果用户定义了拷贝构造函数,将屏蔽默认构造函数和默认拷贝构造函数
总结:为了避免以上的情况,用户定义的类一般要实现,无参构造函数、有参构造函数、拷贝构造函数和析构函数
二、深拷贝和浅拷贝
浅拷贝
同一个类型的对象之间可以赋值,使得两个对象的成员变量的值相同,两个对象仍然是独立的两个对象,这种情况被称为浅拷贝,一般情况下,浅拷贝是没有任何副作用的,但是当类中有指针,并且指针指向动态分配的内存空间,而浅拷贝的赋值只是将引用进行了赋值,两个指针指向的是同一片空间,当析构函数做动态内存释放的时候会导致内存重复释放。仅仅是指向被复制的内存地址,如果原地址发生改变,那么浅复制出来的对象也会相应的改变。
深拷贝
在计算机中开辟一块新的内存地址用于存放复制的对象。
在拷贝构造函数中实现深拷贝
三、初始化列表
构造函数和其他函数不同,除了有名字,参数列表,函数体之外还有初始化列表。
初始化列表的简单使用:
1 | class Data{ |
运行结果:
注意:初始化成员列表(参数列表)只能在构造函数中使用。
四、类的对象作为另一个类的成员(初始化列表的用途)
在类中定义的数据成员一般都是基本的数据类型。但是类中的成员也可以是对象,叫做对象成员。C++中对对象的初始化是非常重要的操作,当创建一个对象的时候,C++编译器必须确保调用了所有子对象的构造函数。如果所有子对象有默认构造函数,编译器可以自动调用他们。但是如果自对象没有默认的构造函数,或者想指定调用某个构造函数怎么办?那么是否可以在类的构造函数直接调用子类的属性完成初始化呢?但是如果子类的成员属性是私有的,我们没有办法访问并完成初始化。解决办法非常简单:对于子类调用构造函数,C++为此提供了专门的语法,即构造函数初始化列表。当调用构造函数时,首先按各对象成员在类定义中的顺序(和参数列表的顺序无关)依次调用它们的构造函数,对这些对象初始化,最后在调用本身的函数体。也就是说,先调用对象成员的构造函数,再调用本身的构造函数。析构函数和构造函数的调用相反,先构造,后析构。
1 | class A{ |
运行结果:
1 | class A{ |
运行结果:
五、explicit关键字
C++提供了关键字explicit
,禁止通过构造函数进行的隐式转换。声明为explicit
的构造函数不能在隐式转换中使用。
注意:explicit
用于修饰构造函数,防止隐式转化。是针对单参数的构造函数(或者除了第一个参数外其余参数都有默认值的多参数构造函数而言)
1 | class Data{ |
运行结果:
1 | class Data{ |
运行结果:
六、new delete 与 malloc 的区别
对象的动态创建
对象的创建
当创建一个C++对象时会发生两件事- 为对象分配内存
- 调用构造函数来初始化那块内存,第一步我们能保证实现,需要我们确保第二步一定能发生。C++强迫我们这么做事因为使用未初始化对象是程序出错的一个重要的原因。
C动态内存分配的方法
为了在运行时动态分配内存,C在它的标准库中提供了一些函数,malloc以及他的变种calloc和realloc,释放内存的free,这些函数是有效的,但是它们是原始的,需要程序员理解和小心使用。为了使用C动态内存分配函数在堆上创建一个类的实例我们必须要申请空间啊、判断啊,这就比较麻烦。问题:
- 程序员必须确定对象的长度
- malloc返回一个void指针,c++不允许将void赋值给其他任何指针,必须进行强制类型转换。
- malloc可能会申请失败,所以必须判断返回值来确保内存分配成功。
- 用户在使用对象之前必须记住对他进行初始化化,构造函数不能显示调用初始化(构造函数是由编译器调用),用户有可能忘记调用初始化函数。
- !!! malloc不会调用构造函数 free不会调用析构函数(重点)
1 | class Data{ |
运行结果:
不能调用构造函数 也不能调用析构函数