C++ 面向对象编程知识体系 系列一: 构造函数与析构函数
这篇文章总结了 C++ 中面向对象编程的知识体系:构造函数和析构函数,包括其中的底层实现等等。
1 面向对象特性
1. 类与对象
- 对象是现实中的对象在程序中的模拟
- 类是同一类对象的抽象,对象是类的某一特定实体
- 类是一种用户自定义的类型,包含函数与数据的特殊结构体
举一个简单的例子,
|
|
这里所举的例子,Orange
是类,但橙子有分不同个体,它们都有对应的酸度、甜度、水分和生长高度,所以不同的橙子个体代表着这一类橙子的不同对象。
类成员的访问控制
- 访问权限分为公有类型
public
,保护类型protected
,私有类型private
- 成员默认访问权限为
private
- 友元函数或者友元类可访问类的保护成员或私有成员
来看一个例子:
|
|
我们定义了 class Teacher
, Teacher
是Student
的友元类,PrintStudentId
是Student
的友元函数,该函数可用于访问Student
类的保护成员或者私有成员。
例如:就像上面所写的,Teacher
类里面定义的PrintStudentId
函数可以访问Student
类的私有成员stud.m_id
。
- 友元函数是单向的: 打个比方,
Student
把Teacher
当成朋友,而Teacher
不把Student
当朋友。就如上面的例子,Teacher
是Student
的友元,但是Student
不是Teacher
的友元,所以Teacher
类的成员函数在没有friend
的关键字作用下,不能访问Teacher
类的保护成员或者私有成员。 - 友元函数不是类的成员。
2. 类的构造与析构
构造函数
- 构造函数是用于构造函数的特殊函数,在对象被创建时被调用以初始化对象
- 未定义构造函数时,编译器自动生成不带参数的默认版本
- 执行构造函数时先执行其初始化列表,再执行函数体
- 构造函数和其他函数一样,允许被重载和被委托
- 构造函数的名称与类的名称是完全相同的,并且不会返回任何类型,也不会返回
void
。构造函数可用于为某些成员变量设置初始值。
构造函数主要有以下三个方面的作用:
- 给创建的对象建立一个标识符;
- 为对象数据成员开辟内存空间;
- 完成对象数据成员的初始化。
|
|
关于以上的Student
类,我们声明了三个Student
的构造函数。第一个构造函数Student()
不带参数。三个构造函数的具体定义如下:
|
|
按照上面的写法,我们可以使用初始化列表进行初始化字段。假设有一个类C
,具有多个字段X、Y、Z
等需要进行初始化,同理地,您可以使用上面的语法,只需要在不同的字段使用逗号进行分隔,如下所示:
|
|
需要注意的是, 在进行构造函数的重载时要注意重载和参数默认的关系要处理好, 避免产生代码的二义性导致编译出错, 例如以下具有二义性的重载:
|
|
在上面的重载中, 当尝试用Point
类重载一个无参数传入的对象M
时, Point M
; 这时编译器就报一条error: call of overloaded 'Point()' is ambiguous
的错误信息来告诉我们说 Point
函数具有二义性。
这是因为Point(int x = 0, int y = 0)
全部使用了默认参数, 即使我们不传入参数也不会出现错误, 但是在重载时又重载了一个不需要传入参数了构造函数Point()
, 这样就造成了当创建对象都不传入参数时编译器就不知道到底该使用哪个构造函数了, 就造成了二义性。
析构函数
与构造函数相反, 析构函数是在对象被撤销时被自动调用, 用于对成员撤销时的一些清理工作, 例如在前面提到的手动释放使用new
或malloc
进行申请的内存空间。析构函数具有以下特点:
- 析构函数函数名与类名相同, 紧贴在名称前面用波浪号 ~ 与构造函数进行区分, 例如:
~Point()
; - 构造函数没有返回类型, 也不能指定参数, 因此析构函数只能有一个, 不能被重载;
- 当对象被撤销时析构函数被自动调用, 与构造函数不同的是, 析构函数可以被显式的调用, 以释放对象中动态申请的内存。
某种意义上理解析构函数是回收间接资源的函数。
|
|
代码中创建了一个Book
类, 类的数据成员只有一个字符指针型的bookName
, 在创建对象时系统会为该指针变量分配它所需内存, 但是此时该指针并没有被初始化所以不会再为其分配其他多余的内存单元。在构造函数中, 我们使用new
申请了一块strlen(name)+1
大小的空间, 也就是比传入进来的字符串长度多1的空间, 目的是让字符指针bookName
指向它, 这样才能正常保存传入的字符串。
在main
函数中使用Book
类创建了一个对象CPP
, 初始化bookName
属性为"C++ Primer"
。从运行结果可以看到, 析构函数被调用了, 这时使用new
所申请的空间就会被正常释放。
自然状态下对象何时将被销毁取决于对象的生存周期, 例如全局对象是在程序运行结束时被销毁, 自动对象是在离开其作用域时被销毁。
如果需要显式调用析构函数来释放对象中动态申请的空间只需要使用 对象名.析构函数名(); 即可, 例如上例中要显式调用析构函数来释放bookName
所指向的空间只要:
|
|