类的构造函数与析构函数
- 类的构造函数
-
引例
/ * * 定义一个类型:Cirlce,圆 * 成员:圆心坐标(x, y),半径radius * / class Cirlce { public: int x, y; int radius; }; int main() { // 定义一个对象 Circle a; // 问题:此时这个对象a算是一个有效的对象么? // 看看它的x,y和radius,是乱的 // 那么,再给它赋值不就好了么? a.x = 0; a.y = 0; a. radius = 1; // 但是从“对象”的观点来看,对象被创建之时,就应该是有效的,不应该存在“半成品”对象。 // 比如说,一辆车Car在造出来的时候,必须带着轮子。如果没有轮子,此时他还不能称为Car。 return 0; }
-
存在问题:定义了一个对象,新建的的对象在内存中的值是无效的(杂乱的负值)。如何让一个对象在被创建的时候,就赋予初始值。
-
解决方法:构造函数
-
构造函数是类的一种特殊的成员函数,构造函数不是普通的函数
- 函数名与类名必须相同
- 没有返回值
class Circle { public: Circle() { x = y = 0; radius = 1; } };
-
构造函数可以带参数,也可以重载
class Cirlce { ... public: Cirlce () { printf("111\n"); x = y =0; radius = 1; } Circle(int x, int y, int r) { printf("222\n"); this->x = x; this->y = y; this->radius = r; } public: int x, y; int radius; };
-
构造函数如何调用?
-
构造函数和普通成员函数不一样,一般不显示调用。
-
在创建一个对象时,构造函数被自动调用(由编译器完成)
Circle a; Circle b(1,1,4)l;
-
它们在内部实质上是分别调用了不同的构造函数,但是表面上没有这个函数调用过程。
int main() { Circle a; Circle b(1, 1, 4); return 0; }
-
-
构造函数的作用:对象一“出生”就是有效的。不存在“半成品”对象。
它可以理解为“初始化”动作。
基本类型的初始化;
int a(10); // 将a初始化为10, 也可以写成int a = 10; Student s = {1, "name"}; // struct的初始化
现在,类class的初始化使用构造函数的方式。
-
小结:
- 介绍构造函数的语法:名字与类名相同,没有返回值
- 构造函数的作用:用于初始化对象
- 构造函数的调用:创建对象的同时,被编译器自动调用。
- 构造函数也可以重载。
- 析构函数
- 析构和构造是一对相反的过程。
- 构造函数:对象被创建时被调用
- 析构函数:对象被销毁时被调用
析:分崩离析
英文:构造函数 constructor
析构函数 destructor
-
析构函数也不是普通的函数
- 名称固定:类名前加上波浪线~
- 没有返回值
- 不能带参数
class Object { public: ~Object() { } };
注意:析构函数只能有一个,不能重载
-
析构函数如何调用?
析构函数从不显示地调用,而是被编译器自动地调用。
什么时候被调用?对象被销毁之时
对于局部变量(对象),在超出变量作用域后,该对象失效
-
析构函数的作用?
析构函数:对象在销毁之前,做一个清理和善后的工作。
比如,申请来的内存要释放,打开的文件FIle*要关闭。
class StringBuffer { public: StringBuffer() { m_buffer = malloc(1024*512); // 申请内存 } ~StringBuffer() { free(m_buffer); // 释放内存 } }; class DataStore { public: DataStore() { m_head.next = NULL; } ~DataStore() { // 删除链表中所有节点,把所有,malloc的内存都free掉 } private: Student m_head; };
-
分开写在.h和.cpp里时,规则和普通函数一样
class DataStore { public: // 函数声明 DataStore(); ~DataStore(); }; // 函数定义,要加类名前缀 DataStore::DataStore() {} DataStore::~DataStore() {}
-
完整DataStore实例(Add和Find函数)
///DataStore.h struct Student { int id; char name[16]; Student* next; } class DataStore { public: DataStore(); ~DataStore(); public: void Add(const Student* data); Student* Find(int id); void Print(); private: Student m_head; }; ///DataStore.cpp/// #include <stdio.h> #include "DataStore.h" DataStore::DataStore() // 创建资源 { m_head.next = NULL; } DataStore::~DataStore() // 释放资源 { Student* p = m_head.next; while(p) { Student* next = p->next; free(p); p = next; } } void DataStore::Add(const Student* data) { // 创建对象、复制数据 Student* copy = (Student*)malloc(sizeof(Student)); *copy = *data; // 插入一个对象到链表中 Student* cur = m_head.next; // 省略了this指针,直接调用成员变量m_head Student* pre = &m_head; // 省略了this指针,直接调用成员变量m_head while(cur) { if(copy->id < cur->id) // 找到这个位置 break; pre = cur; cur = cur->next; // 找到最后一个对象 } // 插入到pre节点的后面 copy->next = pre->next; pre->next = copy; } Student& DataStore::Find(int id) { Student* p = m_head.next; while(p) { if(p->id == id) return p; p = p->next; // 下一个对象 } return NULL; } void DataStore::Print() { Student* p = this->m_head.next; // this指针可加可不加 while(p) { printf("ID: %d, name: %s\n", p->id, p->name); p = p->next; // 下一个对象 } } main.cpp #include <stdio.h> #include"DataStore.h" int main() { DataStore ds; Student nodeA = {13,"JWB",NULL}; Student *p = &nodeA; ds.Add(p); ds.Find(13); ds.print(); return 0; }
- 构造与析构
- 默认构造函数
把那种不需要传参的构造函数,称为默认构造函数// 没有缺省值 Object(); // 所有参数都有缺省值 Object(int a = 10, int b = 11);
- 有了默认构造函数之后,对象在构造时就可以不传递参数,Object obj;
如果一个类没有默认构造函数,则无法构造数组 - 如果一个类没有写任何构造函数,则编译器隐含地生成一个构造函数,相当于添加了 Object::Object() {},写了就不会添加
如果没有写析构函数,则编译器隐含地生成一个析构函数,相当于添加了 Object::~Object() {} - 成员的初始化与析构
(考虑成员变量本身也是class类型的情况)- 当对象被构造时,成员变量也被构造(成员变量的构造函数被调用)
- 当对象被析构时,成员变量也被析构(成员变量的析构函数被调用)
- 结论:
- 构造的时候:
成员被依次构造:从前到后
先执行成员的构造,再执行自己的构造函数 - 成员被一次析构:从后到前
成员被依次析构
先执行自己的析构函数,再执行成员的析构
- 构造的时候:
- 初始化列表
可以在构造函数后面直接初始化- 以冒号引导
- 使用小括号来初始化
// Object有两个成员变量x,y
Object:x(1),y(2)
{
}
/// ///
Object: m_child(1, 2)
{
}
- 小结:
- 默认构造函数:不要传参的构造函数
- 构造函数与析构函数的顺序问题
- 初始化列表:在构造函数的位置指定成员的初始化列表