当前课程知识点:基于Linux的C++ > 第九讲 类与对象 > 9.5 对象(二) > LinuxCPP0905
在构造一个对象的时候
我们可以在构造函数的开头
也就是构造函数的头部
定义它的初始化列表
基本的模式是这个样子
有一个classA类 还有一个classB类
A类有一个带单参数的构造函数版本
B类里面有一个双参数的构造函数版本
在A类的构造函数里面写它的初始化列表
在B类的构造函数里边写它的初始化列表
A类的格式是A::A( int a) : a(a)
这就是它的初始化列表
小括号对前边的这个a
就是这个A类的那个成员的名字
小括号对里边的那个a
就是构造这个对象的时候
对它进行初始化的时候
传给它的形式参数
这两个的名字是一模一样的
我们自己在写构造函数的时候
在内部实现的时候 当时不特别说了嘛
为了区分这两个名字
在函数体内部必须使用this指针
否则你区分不了它
但是在这个构造函数的初始化列表里面
不需要this指针
你写了 编译器就通不过了
实际上在初始化列表里边
它是能够区分出来的
它知道这个名字a是写在小括号对里边
还是在小括号对的前边
所以它就知道这个a就代表着
这个类A中的成员 还是它的形式参数
它能够区分开 所以同名没问题
如果你不想让它同名
简单 把这两个名字a、a
随便某一个改成一个其它的名字 就可以
如果有两个数据对象
那么你就按照a(a), b(b)这样的模式
一个接着一个去构造它
构造函数的初始化列表的最主要的一个目的
就是当我们在创建这个对象
我们要对它进行初始化
我们希望的就是能够同步地初始化
它的全部的数据对象
为什么要初始化列表呢
你把这些内部对象的那个初始化动作
写在那个构造函数函数体里边不行吗
大部分时候是行的 有些时候是不行的
因为两者的实现的策略是不一样的
你把那个代码写在构造函数的函数体里边
那就意味着这些代码将是在这个对象
被构造完成之后去调用的
这是最重要的一个地方
而你写在这个构造函数的初始化列表里面
它将是在构造那个对象的瞬间
去对它进行初始化的
还记得以前我们讲过
初始化和赋值之间的差异吗
这一点在构造函数初始化列表
和构造函数体内部代码之间
体现的情况是一样的
有些成员 你比如讲常量或引用
你是不能赋值的 只能对它进行初始化
所以你在函数体里边
是不可以用赋值语句去对它进行操作的
这在语法上是不允许的
它是常量 它是引用
这是不可以赋值的 只能对它进行初始化
写哪呢 就只能写在这个构造函数的
初始化列表里边 这是一个
第二个 有些成员 比如类的对象
你如果去赋值的话
就有可能导致我们两次构造这个对象
就是两次的初始化
因为你在构造这个对象的时候
如果这个对象的某一个数据成员
是另外一个类的对象
那么在这种情况下边
你构造这个对象的时候
它就分配整个这个对象的存储空间
然后对它进行初始化
当你没有把这个类的对象
在初始化列表里面写出来的时候
它就会缺省地构造它
也就是调用缺省的构造函数
去初始化这个对象
然后它执行你的构造函数的函数体
发现里边还要构造一次它
你这个函数体里面调用了
它的数据成员的那个类的构造函数
去构造那个数据成员
所以它就会再构造一次那个对象
又为它初始化了一遍
这个事情做下来
当然浪费了程序的执行时间
降低了程序效率
所以如果你的类里
它的某些数据成员是其它类的对象
那么这些数据对象都应该在
这个类的构造函数初始化列表里边
对它进行构造
而不要写在构造函数的函数体里
还有一点是需要特别注意的
如果你这个类里边没有缺省构造函数
你定义了一个带参数的构造函数版本
但是你又没有定义缺省的构造函数
系统又不会为你自动生成了
所以在这种情况下面
它会调用缺省构造函数
去构造你的那个对象的
但是你又不存在缺省构造函数
那么系统就会导致问题
所以这个地方使用的时候要特别特别小心
不过构造函数初始化列表
有一个特别需要注意的一个地方
第一个 成员初始化
它是按照那个成员的定义的顺序
而不是初始化列表里面的那个说明的顺序
你定义一个类 它的第一个成员是a
第二个成员是b 第三个成员是c
它构造的时候 它是先构造a
后构造b 再构造c
它按照这样一个顺序去构造的
它不会按照你的初始化列表里
写的那个顺序
如果你初始化列表里边
这个顺序是颠倒的 不是a、b、c
是b、a、c
那么它会按照那个a、b、c的模式去构造
它先构造b 接下来它看到后面有一个a
它就跳过去
因为a的构造时机已经被你错过了
前面就认为它是缺省构造
它就不管了
它读到你的初始化列表里面的
紧跟着b的那个构造后面的那个a
那个成员构造
它就会跳过去 它就会忽略掉
然后构造那个C类的对象
这种情况就会导致A类的那个对象
那个成员就没有被正确地构造
构造是决定我们这个数据对象的初始化
当我们这个程序运行到某一个时刻
或者程序结束的时候
这些对象生命期结束了
我们就要销毁它 销毁这个动作
不管是你主动地去销毁的
你调用free或者调用delete
去主动地销毁你动态分配的内存
还是由系统自动地去销毁的
都涉及到对这个数据对象
进行一系列的清除工作
我们要在对象的生命期结束的时候清除它
对象析构主要的技术手段
就是使用我们的析构函数
析构函数还是和类类型同名
但是前面有一个“~”
同样地 它没有返回值类型
即使是void类型也不行 没有参数
固定的格式
析构函数必须是公开的
这是一个明确的要求
因为当你想销毁这个对象的时候
它可能会被使用delete
这样的操作符来调用的
不管怎么样 它都需要使用
这个类的析构函数去销毁它
那肯定是在这个类的外部调用析构函数
所以如果析构函数不是公开的
那么就没有办法销毁这个对象
这程序代码就会有问题
就是你一定不能够销毁这个对象
回过头来 就像我们前面讲构造函数一样
我们前面说过
我们这个类一定要有一个公开的构造函数
这样的话我才能够保证
在外界构造这个对象 类似的呢
就是我们一定要有公开的析构函数
保证能够在外界析构这个对象
但是我们有例外的情况
例外的情况就是
如果我不想外界构造这个对象呢
那么就可以将所有的构造函数都设为私有的
或者保护的
外界不能够访问的 这是一个
第二个 如果我还不想外界析构我的对象
那我一样可以将析构函数设为私有或保护的
在这个类或派生类外部
你就完全没有权利
去构造和析构我们的对象
这样的程序代码一样是存在的
析构函数可以由系统自动为我们调用
也可以由我们程序员写程序的时候
主动地去调用
这一点和构造函数是不一样的
但是你要记住 系统自动调用析构函数
和我们主动地调用析构函数
两者的工作原理是不一样的
因为我们主动调用析构函数并不涉及到
销毁这个对象所分配的那个内存
仅仅是销毁它里面的数据
每一个类只能有一个析构函数
这一点和构造函数也不一样
构造函数可以有很多个
允许重载 但是析构只能有一个
因为我们的那个对象
不管你用什么方式来构造
构造完了以后 那个对象就是那个样子
我们就按照它的样子销毁它
所以析构函数只能有一个
如果你没有定义析构函数
系统就会自动地生成一个缺省的析构函数
它里面 代码还是没有
也就是析构函数内部什么也不做
但是一定有一个缺省的析构函数
保证存在这样的析构函数
能够被系统自动地调用
这是非常重要的地方
到此 同学们数一数也知道了
在类的声明里面有三个缺省的函数
如果你没有实现
它自动给你生成:缺省的构造函数
缺省的拷贝构造函数 缺省的析构函数
实际上还有第四个缺省的函数
我们下一讲才会说
我们继续看圆类库的接口
我们会按照这样一个方式“~Circle();”
来书写它的析构函数
因为我们这个对象里边
没有任何动态内存分配的数据
所以析构函数你不写
系统自动生成的这个析构函数就够用
你写了 其实代码里面
我也不知道要写什么
什么其实都不用写 空着就可以
但是你一定要实现它 对吧
你不能只声明不实现它 那个不成
只声明不实现就意味着不能调用了
对象的析构最主要的目的是释放
这个对象里边存在的动态分配的内存
就像拷贝构造函数一样
缺省的 它只能够完成浅拷贝
它缺省给你提供的那个析构函数
只能完成浅层的释放
它不能够完成深层的释放 注意这一点
所以如果你的对象里边有一个数据成员
它是一个指针 指向一个目标数据对象
而那个目标数据对象是你动态分配的
并且你这个对象负有销毁它的义务
那么你就应该写一个析构函数
在这个析构函数体内部
销毁那个目标数据对象
看我们这个例子
ClassA 构造函数 定义了析构函数
然后私有数据字段是一个
指向整数的一个指针
这个构造函数本身性质是清楚的
可以按照像这样的一个模式对它进行构造
析构的时候 你就必须释放这个指针
所指向的目标数据对象
我们就调用delete p
调用完以后把p赋值为NULL
如果你没写这个析构函数
编译器自动生成的那个缺省析构函数
就什么代码就没有
就意味着它没有销毁p所指向的
那个目标数据对象
而直接把这个对象本身销毁了
回忆一下我们讲指针的时候
特别谈到过的地方 这就叫内存泄露
p所指向的那个整数还存在
但是指向它的指针没了
那段内存空间就再也不能用了
所以如果你的类里面
有数据成员是指针形式的
就需要特别的小心
要明确要不要写自己的析构函数
大部分情况下面
这个时候我们都要写自己的析构函数
可以向普通的数组一样
定义我们的对象数组
也就是一个数组的每一个元素
都是一个类的一个对象
我们可以定义这个类的很多对象
然后构造成一个数组 这是没有问题的
当定义这个数组的时候
你可以对它进行初始化
如果这个构造函数
存在一个单参数的版本
那么当我们初始化这个数组的时候
就可以向普通的数组一样初始化
就是初始化符号 后面花括号对
后面跟着一个值、一个值 逗号分隔开的
系统就自动替你将这样的一个值
转换成那个类的那样一个对象
它其实就完成这样一个工作
完成一个类型转换
这就是单参数构造函数的意义
它看上去就像一个类型转换一样
它实际上 就是类型转换
如果是多参数的版本
你就不能这么写了呀
你不知道这个逗号分隔开的这些数据成员
到底是第一个元素 还是第二个元素
所以就必须是调用类的构造函数
来构造它:Circle(1.0, 0.0, 0.0)
因为是两个数据对象
所以逗号之后要构造第二个元素:
Circle(2.0, 1.0, 1.0)
按照这个方式来构造
-1.1 提纲
-1.2 程序设计的基本概念
-1.3 简单C/C++程序介绍
-1.4 程序设计的基本流程
-1.5 基本语法元素
-1.6 程序设计风格
-1.7 编程实践
-第一讲 C/C++基本语法元素--编程实践提交入口
-2.1 提纲
-2.2 结构化程序设计基础
-2.3 布尔数据
-2.4 分支结构
-2.5 break语句
-2.6 循环结构
-2.7 编程实践
-第二讲 程序控制结构--编程实践提交入口
-3.1 提纲
-3.2 函数声明、调用与定义
-3.3 函数调用栈框架
-3.4 编程实践
-第三讲 函数--编程实践提交入口
-4.1 提纲
-4.2 算法概念与特征
-4.3 算法描述
-4.4 算法设计与实现
-4.5 递归算法(一)
-4.6 递归算法(二)
-4.7 容错与计算复杂度
-4.8 编程实践
-第四讲 算法--编程实践提交入口
-5.1 提纲
-5.2 库与接口
-5.3 随机数库(一)
-5.4 随机数库(二)
-5.5 作用域与生存期
-5.6 典型软件开发流程(一)
-5.7 典型软件开发流程(二)
-5.8 编程实践
-第五讲 程序组织与开发方法--编程实践提交入口
-6.1 提纲
-6.2 字符
-6.3 数组(一)
-6.4 数组(二)
-6.5 结构体
-6.6 编程实践
-第六讲 复合数据类型--编程实践提交入口
-7.1 提纲
-7.2 指针基本概念
-7.3 指针与函数
-7.4 指针与复合数据类型(一)
-7.5 指针与复合数据类型(二)
-7.6 字符串
-7.7 动态存储管理(一)
-7.8 动态存储管理(二)
-7.9 引用
-7.10 编程实践
-第七讲 指针与引用--编程实践提交入口
-8.1 提纲
-8.2 数据抽象(一)
-8.3 数据抽象(二)
-8.4 链表(一)
-8.5 链表(二)
-8.6 链表(三)
-8.7 链表(四)
-8.8 函数指针(一)
-8.9 函数指针(二)
-8.10 抽象链表(一)
-8.11 抽象链表(二)
-8.12 编程实践
-第八讲 链表与程序抽象--编程实践提交入口
-9.1 提纲
-9.2 程序抽象与面向对象
-9.3 类类型
-9.4 对象(一)
-9.5 对象(二)
-9.6 类与对象的成员(一)
-9.7 类与对象的成员(二)
-9.8 类与对象的成员(三)
-9.9 继承(一)
-9.10 继承(二)
-9.11 继承(三)
-9.12 多态(一)
-9.13 多态(二)
-9.14 编程实践
-第九讲 类与对象--编程实践提交入口
-10.1 提纲
-10.2 四则运算符重载(一)
-10.3 四则运算符重载(二)
-10.4 关系与下标操作符重载
-10.5 赋值操作符重载(一)
-10.6 赋值操作符重载(二)
-10.7 赋值操作符重载(三)
-10.8 赋值操作符重载(四)
-10.9 赋值操作符重载(五)
-10.10 流操作符重载(一)
-10.11 流操作符重载(二)
-10.12 流操作符重载(三)
-10.13 操作符重载总结
-10.14 编程实践
-第十讲 操作符重载--编程实践提交入口
-11.1 提纲
-11.2 泛型编程概览
-11.3 异常处理机制(一)
-11.4 异常处理机制(二)
-11.5 运行期型式信息(一)
-11.6 运行期型式信息(二)
-11.7 模板与型式参数化
-11.8 题外话:术语翻译
-11.9 泛型编程实践(一)
-11.10 泛型编程实践(二)
-11.11 泛型编程实践(三)
-11.12 泛型编程实践(四)
-11.13 泛型编程实践(五)
-11.14 泛型编程实践(六)
-11.15 泛型编程实践(七)
-11.16 泛型编程实践(八)
-11.17 泛型编程实践(九)
-11.18 泛型编程实践(十)
-11.19 编程实践
-第十一讲 泛型编程--编程实践提交入口
-12.1 提纲
-12.2 程序执行环境(一)
-12.3 程序执行环境(二)
-12.4 程序执行环境(三)
-12.5 程序执行环境(四)
-12.6 输入输出(一)
-12.7 输入输出(二)
-12.8 文件系统
-12.9 设备
-12.10 库(一)
-12.11 库(二)
-12.12 makefile文件(一)
-12.13 makefile文件(二)
-12.14 makefile文件(三)
-12.15 编程实践
-第十二讲 Linux系统编程基础--编程实践提交入口
-13.01 提纲
-13.02 进程基本概念
-13.03 信号
-13.04 进程管理(一)
-13.05 进程管理(二)
-13.06 进程管理(三)
-13.07 进程间通信(一)
-13.08 进程间通信(二)
-13.09 进程间通信(三)
-13.10 进程间通信(四)
-13.11 进程池
-13.12 编程实践
-第十三讲 进程编程--编程实践提交入口
-14.1 提纲
-14.2 线程基本概念
-14.3 线程管理(一)
-14.4 线程管理(二)
-14.5 线程管理(三)
-14.6 线程管理(四)
-14.7 线程同步机制(一)
-14.8 线程同步机制(二)
-14.9 C++11线程库(一)
-14.10 C++11线程库(二)
-14.11 C++11线程库(三)
-14.12 C++11线程库(四)
-14.13 C++11线程库(五)
-14.14 编程实践
-第十四讲 线程编程--编程实践提交入口
-15.1 提纲
-15.2 Internet网络协议
-15.3 套接字(一)
-15.4 套接字(二)
-15.5 编程实践
-第十五讲 网络编程--编程实践提交入口