(1)从程序设计的角度来看,对象只是变量,定义对象就是定义变量,所以:
在栈上创建对象时,成员变量初始值为随机值
在堆上创建对象时,成员变量初始值为随机值
在静态数据区上创建对象时,成员变量初始值为0
(2)全局变量和static修饰的局部变量存储在静态数据区,没有显式初始化其值为0(bss/ZI段)
(1)生活中的对象都是初始化之后上市的,如手机,电脑等,我们希望程序中的对象也可以初始化为固定值
(1)在类中提供一个public的initialize函数
(2)函数中手工对类的成员进行显式初始化
(3)对象创建后需要立即调用initialize函数进行初始化
(4)initialize函数只是一个普通函数,如果未及时调用,运行结果是不确定的
(1)C++中可以定义与类同名的构造函数
(2)构造函数:与类同名、没有任何的返回值类型、在对象定义时会被自动调用
(1)构造函数可以根据需要定义参数
(2)一个类中可以存在多个重载的构造函数构造函数的重载遵循C++重载的规则
(3)构造函数在对象定义时会被自动调用,此外我们可以手工调用构造函数
注意:
对象的声明和定义不同,对象定义:申请对象的空间并调用构造函数
对象声明:告诉编译器有这样一个对象
(1)就是没有参数的构造函数
(2)当类中没有定义构造函数时(拷贝构造函数也是构造函数),编译器会默认提供一个无参构造函数,其函数体为空
(1)参数为const class_name&的构造函数
(2)当类中没有定义拷贝构造函数时,编译器会默认提供一个拷贝构造函数,简单的进行成员变量的复制(浅拷贝)
(3)拷贝构造函数的意义:兼容C语言的初始化方式(使用变量为其他变量赋值),使用已创建的对象为其他对象赋值
(1)浅拷贝:拷贝后的物理状态相同
(2)深拷贝:拷贝后的逻辑状态相同
(3)编译器默认提供的拷贝构造函数只进行浅拷贝
什么时候需要深拷贝?
对象中有成员使用了系统资源(成员指向了动态内存空间、成员打开了外存中的文件、成员使用了系统中的网络端口)
(1)调用浅拷贝构造函数进行初始化,初始化的两个变量不但参数相同,而且共用同一块内存,在两次释放内存时就会出错
(2)工程中自定义拷贝构造函数时,必然要实现深拷贝(为新的对象重新分配资源)
(1)在类中可以定义const成员,const成员会被分配空间,存储位置取决于其对象定义在哪里
(2)类中的const是只读变量
(3)在类中不能直接对const成员进行初始化,只能在初始化列表中指定初始值
(1)C++中提供了初始化列表对成员进行初始化
(2)语法规则
ClassName::ClassName() //构造函数
: m1(v1),m2(v2, v3),m3(v1) //初始化列表
{
//构造函数函数体
}
(3)注意事项:
成员的初始化顺序与成员的声明顺序相同,与初始化列表中的顺序无关
初始化列表先于构造函数的函数体执行
(1)局部变量的构造顺序依赖于程序的执行流,所以开发中要避免使用goto语句)(破坏程序的执行流)
(2)堆对象的构造顺序依赖于new的使用顺序
(3)全局对象的构造顺序是不确定的,不同的编译器使用不同的规则确定构造顺序,所以要尽量避免全局对象
(1)生活中对象都是初始化后才上市的,对象被销毁前会做一些清理工作
方案1:
提供一个public的free函数,
(1)当对象不再需要时立即调用free函数进行清理
(2)free只是一个普通的函数,必须显示的调用
(3)对象销毁之前没有做清理,很可能造成资源泄漏
方案2:
析构函数
(1)C++中可以定义一个特殊的清理函数,析构函数,功能和构造函数相反
(2)析构函数在对象销毁时被自动调用
(3)析构函数没有返回值也没有参数(表明析构函数在一个类中是唯一的,不可能重载)
(4)语法:
~ClassName()
(5)一般当类中自定义了构造函数,并且函数中使用了系统资源,则需要定义析构函数,释放系统资源,防止内存泄漏
(1)直接调用构造函数将产生一个临时对象,临时对象的生命周期只有一条语句的时间,临时对象的作用域只在一条语句中
(2)临时对象是C++中值得警惕的灰色地带,是性能的瓶颈,也是bug的来源之一
(3)实际工程开发中需要人为的避开临时对象
(4)现代C++编译器会尽力避开临时对象
思考:如何解决构造函数的代码复用问题?
方案是提供一个private的init函数,然后在构造函数中去调用它.
回顾构造函数的特点:
与类同名,没有返回值,在对象创建时被动调用,用于对象的初始化
1、如何判断构造函数的执行结果?
一般来说无法判断。但是我们可以人为的类中定义一个用于表明构造函数执行结果的变量,并在构造函数结束的地方给该变量赋值,最后通过读取该变量的值来得知构造函数的执行结果
2、在构造函数中执行return语句会发生什么?
首先在构造函数中指向他return是合法的,执行return语句后构造函数立即结束
3、构造函数执行结束是否意味着对象构造成功?
构造函数只提供自动初始化成员变量的机会,不能保证初始化逻辑一定成功。构造函数决定的是对象的初始化状态,而不是对象的诞生。
也就是说构造函数初始化操作的失败不影响对象的诞生
初始化操作不能按照预期完成而得到的对象
是C++中的合法对象,也是bug的来源
工程开发中的构造过程可分为:
第一阶段构造:(真正的构造函数)
资源无关的初始化操作,不可能出现异常的操作
第二阶段构造:(返回值表示初始化状态的普通函数)
需要时用系统资源的操做,可能出现异常情况(内存申请,访问文件)
如图所示:(27-2)
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiI9s2RkBnVHFmb1clWvB3MaVnRtp1XlBXe0xCM581dvRWYoNHLwEzX5xCMx8FesU2cfdGLwATMfRHLGZkRGZkRfJ3bs92YskmNhVTYykVNQJVMRhXVEF1X0hXZ0xiNx8VZ6l2cssmch1mclRXY39CXldWYtlWPzNXZj9mcw1ycz9WL49zZuBnL2MWOmFTZmJjM0QjY5IDOyAzY3EjY2MDM5YTNmRzYkVzLcljMvwlMxgTMwIzLcd2bsJ2LcNXZnFWbp9CXt92Yu8GdjFTNuITavw1LcpDc0RHaiojIsJye.png)
总结:
(1)二阶构造认为的将初始化分为两部分,能够确保创建的对象都是完整的
(2)二阶构造的构造函数都是私有的,并提供了一个用于创建对象的静态函数指针???(通过类名直接访问,然后创建对象),所以最终的对象分配在堆区
(3)实际工程中需要初始化的数据都是比较多的,所以对象创建在堆区是合理的
使用二阶构造完善之前的数组类
析构函数的调用顺序
析构函数与对应的构造函数的调用顺序相反,所以我们之只要知道构造函数的调用顺序就可以知道析构的顺序
(1)单个函数创建时构造函数的调用顺序(先父母,后他人,再自己)
1、调用父类的构造过程
2、调用成员变量的构造函数
3、调用类自身的构造函数
(2)对于栈对象和全局对象,类似于入栈和出栈的顺序,最先构造的对象最后被析构
(3)堆对象的析构发生在使用delete的时候,与delete的使用顺序相关