析构函数
C++中,有时候需要new为对象申请堆内存空间,当我们不想使用对象时,需要销毁对象时在public用free()函数delete释放堆空间。但是有时我们都会忘记这一手动操作而造成内存泄漏,那么有没有一种可以让C++编译器自动调动销毁对象的函数呢?于是引入析构函数。
C++中的类可以定义一个自动清理的函数叫析构函数,它的功能与构造函数相反。
语法格式:
~ClassName()
{
析构函数体;
delete p;
//delete[] p;
}
注意:
(1)析构函数没有返回值,也没有参数:析构函数不能重载。类中只能有一个,且是独一无二的函数;
(2)当类中自定义了构造函数使用系统资源时,就需要定义析构函数来释放系统资源。
(3)析构函数在对象销毁(return 0结束程序)时,会被自动调用清理对象占用的系统资源。
临时对象
- 在工程中,应避免手动直接调用构造函数产生临时对象。因为临时对象是性能的瓶颈,也是bug的主要来源之一。同时现在的C++编译器会在不影响最终执行结果的前提下,会优化直接调用的临时对象来减少临时对象的产生。
#include <stdio.h>
class Test
{
int mi;
public:
Test(int i)
{
printf("Test(int i) : %d\n", i);
mi = i;
}
Test(const Test& t)
{
printf("Test(const Test& t) : %d\n", t.mi);
mi = t.mi;
}
Test()
{
printf("Test()\n");
mi = 0;
}
int print()
{
printf("mi = %d\n", mi);
}
~Test()
{
printf("~Test()\n");
}
};
Test func()
{
return Test(20);
}
int main()
{
Test t = Test(10); // ==> Test t = 10;
Test tt = func(); // ==> Test tt = Test(20); ==> Test tt = 20;
t.print();
tt.print();
return 0;
}
析构函数的顺序
单(多)个对象创建时,构造函数的构造顺序是:
(1)先调用父类的构造过程
(2)再调用成员变量的初始化列表构造。(成员变量的初始化列表顺序和成员变量的声明顺序相关)
(3)最后调用类自身的构造函数
注意:单(多)个对象的析构函数调用顺序和构造函数的顺序相反:
先调用类自身的析构函数,然后成员变量的析构函数,最后调用父类的析构函数。
#include <stdio.h>
class Member
{
const char* ms;
public:
Member(const char* s)
{
printf("Member(const char* s): %s\n", s);
ms = s;
}
~Member()
{
printf("~Member(): %s\n", ms);
}
};
class Test
{
Member mA;
Member mB;
public:
Test() : mB("mB"), mA("mA")
{
printf("Test()\n");
}
~Test()
{
printf("~Test()\n");
}
};
Member gA("gA");
int main()
{
Test t;
return 0;
}
构造顺序:因为没有父类,所以不考虑父类构造。
1.ga:先执行全局变量的构造(注意:多个全局变量构造顺序是不确定的)
2.ma:成员变量根据初始化列表构造和据声明时顺序有关
3.mb:成员变量根据初始化列表构造和据声明时顺序有关
4.test():调用类自身的构造函数
析构顺序:与构造顺序相反
总结:
(1)对于栈对象(局部变量)和全局对象(全局变量),类似于数据结构里面入栈和出栈顺序,最后构造的对象会被先最析构。
(2)堆对象的析构出现在delete堆内存空间的时候,析构顺序和使用delete顺序相关。
const 对象
const可以修饰变量,从语言的角度对象也是变量,那么const是否可以修饰对象呢?
如果可以,const 对象又有什么特性呢?
答:const对象为只读对象,且只读对象的成员变量是不允许被改变的。只读对象只是程序编译阶段有效,程序运行时无效。只读对象其实还是可以通过指针取改变的。
const成员函数
const 对象(只读对象)只能调用const 成员函数,也可以调用成员变量;
const 成员函数内部也只能调用const 成员函数;
const 成员函数中不能直接改写成员变量的值;
- const 成员函数的定义语法格式:
const成员函数:
type ClassName::func(参数列表) const
{
成员函数体;
}
注意:类中成员函数声明和定义中必须得有const关键字
(1)是不是每一个对象都有自己的成员变量和函数呢?
答:
- 每一个类的对象都有自己独立的成员变量,但是属于同类的对象间共用一套成员函数。成员函数可以直接访问成员变量。
- 从面向对象角度来看,对象是由属性(成员变量)和功能(成员函数)组成的;
从程序运行的角度看,对象是由数据(变量)和函数构成的,数据(变量)可以位于栈、堆、全局区,可以被删除销毁空间;但是函数位于代码段且是只读的,不可被改,代码段不能动态添加和删除,对同一成员函数只能有一个。因此属于同类的对象间共用一套成员函数。
(2)属于同类的所有对象共用同一成员函数,那么调用成员函数时,成员函数又是怎么分辨到底是哪个对象调用的呢?
答:
- 在类的普通成员函数当中,有一个看不到的隐藏了的参数,这个参数是一个有固定通用名字的指针this关键字;this指针的值:就是当前调用此成员函数的对象的地址。即this指针指向当前对象
static 成员变量
统计程序在运行期间,随时可以获取某类的对象的个数,为保证程序的安全性不能使用全局变量.
因为全局变量在程序的任何位置都可以访问修改,没有访问限制不安全。于是引出静态成员变量。
(1)C++中,
- 静态成员变量是属于定义它的这一个类所共有,即静态全局变量也就是某一类的所有实体所共有,不为没有关系的别的类所有;
- 静态成员变量的生命期不依赖于任何对象,其生命周期(程序的运行周期)和全局变量一样;
- 可以通过类名直接访问公有的public静态成员变量,但是安全性不好;
#include <stdio.h>
class demo
{
public:
static int var;
}
int demo::var = 0;
int main()
{
printf("demo::var = %d\n", demo::var);//::为作用于访问符, demo::var为类作用域访问
demo::var = 100;//安全性不好,var可被改变;
printf("demo::var = %d\n", demo::var);
}
- 所有对象都共享类的静态成员变量;
- 也可以通过对象名直接访问公有的public静态成员变量;
(2)静态成员变量的特性
- 在定义时,直接通过static关键字修饰:
class demo
{
private:
static int var;
}
- 静态成员变量需要在类的外部进行单独定义,目的是为了让编译器知道需要为静态全局变量在全局区分配内存空间,否则链接器找不到静态成员变量对应的存储空间,因为静态成员变量不属于任何的对象特所有,而是属于定义它的这一个类所共有:
class demo
{
private:
static int var;
}
int demo::var = 0;//静态成员变量初始化
- 静态成员变量的内存空间分配位于全局区,和全局变量、static静态局部变量一致存储在全局区;
static 成员函数
怎么可以不依赖类的对象就可以安全地访问静态成员变量,方便快捷地获取静态成员变量的值?
于是C++引入静态成员函数
(1)静态成员函数的访问:
- 静态成员函数是类中特殊的成员函数,属于此整个类所有,在内存空间代码段且只有一份只读;
- 静态成员函数可以通过类名(::)直接访问 / 对象名(obj.var)间接访问 public静态成员函数;
注意:不能通过类名直接访问普通成员函数,必须通过类的对象访问
(2)静态成员函数的语法格式:直接在函数声明/定义时,函数前面加static
class demo
{
private:
static int i;
public:
static int func(int arg);//静态成员函数的声明
static void func1()//静态成员函数的声明和定义
{
}
int getval()
{
return i;
}
}
int demo::i = 0;
int demo::func(int arg)//静态成员函数的定义
{
静态成员函数体;
return 0;
}
int main()
{
printf("demo::getval = %d\n", demo::getval());//错误,无法通过类名访问普通成员函数,必须有对象
return 0;
}
(3)代码案例
#include <stdio.h>
class Demo
{
private:
int i;
public:
int getI();
static void StaticFunc(const char* s);//声明静态成员函数
static void StaticSetI(Demo& d, int v);//声明静态成员函数
};
int Demo::getI()//获得私有成员变量i的值
{
return i;
}
void Demo::StaticFunc(const char* s)//打印传进来的字符串
{
printf("StaticFunc: %s\n", s);
}
void Demo::StaticSetI(Demo& d, int v)//把参数v的值赋值给对象d的私有成员变量i
{
d.i = v;
}
int main()
{
Demo::StaticFunc("main Begin...");//使用类域直接访问静态成员函数
Demo d;//定义一个demo类对象
Demo::StaticSetI(d, 10);
printf("d.i = %d\n", d.getI());
Demo::StaticFunc("main End...");
return 0;
}
static静态成员函数和普通(const)成员函数的区别
注意:
- 静态成员函数没有隐藏的this指针
- 静态成员函数可以通过类名直接(::作用域访问符)访问
- 静态成员函数内部只能访问静态成员变量或者静态成员函数,不能直接访问非static的成员变量。