本文首发于个人博客
封装
- 成员变量私有化,提供公共的getter和setter给外界去访问成员变量
1 | class Person { |
堆空间
在程序运行过程,为了能够自由控制内存的生命周期、大小,会经常使用堆空间的内存
堆空间的申请\释放
malloc
\free
new
\delete
new
[] \delete []
注意
- 申请堆空间成功后,会返回那一段内存空间的地址
- 申请和释放必须是1对1的关系,不然可能会存在内存泄露
现在的很多高级编程语言不需要开发人员去管理内存(比如Java),屏蔽了很多内存细节,利弊同时存在
- 利:提高开发效率,避免内存使用不当或泄露
- 弊:不利于开发人员了解本质,永远停留在API调用和表层语法糖,对性能优化无从下手
例如开盘int类型的空间,使用完之后销毁
1 | int *p = (int *)malloc(sizeof(int)); |
堆空间的初始化
memset
memset
函数是将较大的数据结构(比如对象、数组等)内存清零的比较快的方法
如下所示
1 | Person person; |
初始化
1 | int *p1 = (int *)malloc(sizeof(int)); //*p1 未初始化 |
如下几种方式
1 | int *p1 = new int; //未初始化 |
构造函数(Constructor)
- 构造函数(也叫构造器),在对象创建的时候自动调用,一般用于完成对象的初始化工作
特点
- 函数名与类同名,无返回值(void都不能写),可以有参数,可以重载,可以有多个构造函数
- 一旦自定义了构造函数,必须用其中一个自定义的构造函数来初始化对象
注意
- 通过malloc分配的对象不会调用构造函数
- 一个广为流传的、很多教程\书籍都推崇的错误结论:
- 默认情况下,编译器会为每一个类生成空的无参的构造函数
- 正确理解:在某些特定的情况下,编译器才会为类生成空的无参的构造函数
比如我们自己写2个构造函数
1 | class Person{ |
在不同的空间调用的时候,如下区别
1 | // 全局区 |
构造函数的互相调用
下面代码中有2个构造函数,Person()
是可以调用Person(int age, int height):m_age(age),m_height(height)
的。
1 | class Person{ |
父类的构造函数
- 子类的构造函数默认会调用父类的无参构造函数
- 如果子类的构造函数显式地调用了父类的有参构造函数,就不会再去默认调用父类的无参构造函数
- 如果父类缺少无参构造函数,子类的构造函数必须显式调用父类的有参构造函数
析构函数
- 析构函数(也叫析构器),在对象销毁的时候自动调用,一般用于完成对象的清理工作
特点
- 函数名以~开头,与类同名,无返回值(void都不能写),无参,不可以重载,有且只有一个析构函数
注意
- 通过malloc分配的对象free的时候不会调用析构函数
- 构造函数、析构函数要声明为public,才能被外界正常使用
例如下面的代码
1 | class Cat{ |
输出
1 | Cat() |
当person销毁的时候,其持有的cat并没有销毁。
原因
- 当person销毁的时候,其指向cat对象的指针销毁了,但是堆空间的cat对象依然存在,就会有内存泄露。所以需要在析构函数里面来释放掉。类似的析构函数在许多其他语言底层也是应用广泛,例如Objective-C的源码中,大量使用析构函数。
代码改成如下所示:
1 | ~Person(){ |
输出
1 | Cat() |
可知,cat对象才真正销毁。