首页 > 代码库 > 关于类和对象的进一步讨论之析构函数 C++
关于类和对象的进一步讨论之析构函数 C++
析构函数也是一个特殊的成员函数。它的作用与构造函数相反。它的名字是在类名的前面加一个“~”符号。在C++中“~”是位取反运算符。当对象的生命结束时,会自动执行解析函数。以下几种情况会执行析构函数:
1.如果在一个函数中定义了一个对象,当这个函数被调用结束时,对象应该释放,在对象释放前自动执行析构函数。
2.static局部对象在函数调用结束时对象并不释放,因此也不调用析构函数,只在main函数结束或调用exit函数时,才调用staitic局部对象的析构函数。
3.如果定义了一个全局对象,则在程序的流程离开其作用域时(如main函数结束或exit函数),调用该全局对象的析构函数。
4.如果用new运算符动态的建立一个对象,当用delete运算符释放该对象时,先调用该对象的析构函数。
析构函数的作用并不是删除对象,而是在撤销对象占用的内存之前完成一些清理工作,使这部分内存可以被程序分配给新的对象使用。程序设计者事先设计好析构函数,以完成所需的功能,只要对象的生命结束,程序就自动执行析构函数来完成这些工作。
析构函数不返回任何值,没有函数类型,也没有函数参数。由于没有函数参数,它不能被重载。并且一个类中只能有一个析构函数。
析构函数还可以被用来执行“用户希望在最后一次使用对象之后所执行的任何操作”。例如输出有关的信息。这里说用户是指类的设计者。析构函数可以完成设计者多指定的任何操作。
想让析构函数完成任何工作,都必须在定义的析构函数中指定。
调用构造函数和析构函数的顺序
在一般情况下,调用析构函数的次序正好与调用调用构造函数的次序相反,最先被调用的构造函数,其对应的析构函数最后被调用,而而最后调用的构造函数,其对应的析构函数最先被调用。先构造的后析构,后构造的先析构。(并非任何时候都是按照这一原则进行的。会在作用域和存储类别的概念方面的不同)。对象可以在不同的作用域中定义,但是这是有区别的。
下面归纳一下是什么时候调用构造函数和析构函数:
1.在全局范围内定义的对象(即在所有函数之外定义的对象),它的构造函数在文件中所有函数(包括main函数)执行之前调用。但是如果一个程序中有多 个文件,而不同的文件都定义了全局对象,则这些对象的构造函数的执行顺序是不确定的。当main函数执行完毕或调用exit函数(此时程序终止)时,调 用析构函数。
2.如果定义的是局部自动对象(例如在函数中定义对象),则在建立对象时调用其构造函数。如果函数被调用多次,则在每次建立对象时都要调用构造函 数。在函数调用结束、对象释放时先调用析构函数。
3.如果在函数中定义静态static局部对象,则只在程序第一次调用此函数建立对象时调用构造函数一次,在调用结束时对像并不释放,因此也不调用析构 函数,只在main函数结束或调用exit函数结束程序时,才调用析构函数。
对象数组
在建立数组时,同样要调用构造函数,如果有50个元素,需要调用50次构造函数。在需要时可以在定义数组时提供实参以实现初始化。如果构造函数只有一 个参数,在定义数组时可以直接在等号后面的花括号里提供实参。如
Student stud[3]={60,70,80};
如果构造函数有多个参数,则不能在定义数组时直接提供所有实参的方法,因为一个数组有多个元素,对每个元素提供多个实参,如果在考虑到构造函数有默 认参数的情况,很容易造成实参和形参的对应关系不清晰,出现歧义。
那么,如果构造函数有多个参数,在定义对象数组时应该怎么实现初始化呢?答案:在花括号中分别写出构造函数并指定实参,如果构造函数有3个参数,分 别代表学号、年龄、成绩。则可以这样定义数组:
Student stud[3]={ //定义对象数组
Student(1001,19,87); // 调用第一个元素的构造函数,为他提供三个参数
Student(1002,19,78); //调用第二个元素的构造函数,为他提供3个参数
Student(1003,18,79); // .......................
};
例子:
#include <iostream>
using namespace std;
class Box
{
public:
Box(int h,int w,int len):height(h),width(w),length(len){}
int volume();
private:
int height;
int width;
int length;
};
int Box::volume()
{
return (height*width*length);
}
int main()
{
Box a[3]={Box(10,12,15),Box(15,18,20),Box(16,20,26)};
cout<<"volume of a[0] is "<<a[0].volume()<<endl;
cout<<"volume of a[1] is "<<a[1].volume()<<endl;
cout<<"volume of a[2] is "<<a[2].volume()<<endl;
return 0;
}
对象指针
指向对象的指针,对象空间的起始地址就是对象的指针。
类名 *对象指针名;
指向对象成员的指针,对象有指针,存放对象初始地址的指针变量就是指向对象的指针变量。对象中的成员也有地址,存放对象成员地址的指针变量就是指 向对象成员的指针变量。
定义指向对象数据成员的指针变量的方法和定义指向普通变量的指针变量方法相同,例如:int *p;
定义指向对象数据成员和指针变量的一般形式:
数据类型名 *指针变量;
如果Time类的数据成员hour为公用的整数数据,则可以在类外通过指向对象数据成员的指针变量访问对象数据成员hour。
p1=&t1.hour;
cout<<*p1<<endl;
指向对象成员函数的指针,定义指向对象成员函数的指针变量的方法和定义指向普通函数的指针变量方法有所不同。下面是普通函数的指针变量的定义方 法: 数据类型名 (*指针变量名)(参数列表);
如:void (*p)(); //p是指向void型函数的指针变量
可以使它指向一个函数,并通过指针变量调用该函数。
p=fun; //将fun函数的入口地址赋给指针变量p,p就指向了函数fun
(*p)(); //调用fun函数
成员函数与普通函数一个最根本区别是:它是类中的一个成员。编译系统要求在上面的赋值语句中,指针变量的类型必须与赋值号右侧函数的类型相匹配,要 求以下3方面都要匹配:1.函数参数的类型和参数个数;2.函数返回值的类型;3.所属的类。
应该按照如下形式定义指向成员函数的指针变量:
void(Time::*p2)(); //定义p2为指向Time类中公用成员函数的指针变量。
注意:(Time::*p2)两侧的括号不能省略,因为()的优先级高于*。
定义指向公用成员函数的指针变量的一般形式是:
数据类型名 (类名::*指针变量名)(参数列表);
可以让它指向一个公用成员函数,只需要把公用成员函数的入口地址赋给一个指向公用成员函数的指针变量即可。例如:
p2=&Time::get_time;
使指针变量指向一个公用成员函数的一般形式:
指针变量名=&类名::成员函数名;
例子:
#include <iostream>
using namespace std;
class Time
{
public:
Time(int,int,int);
int hour;
int minute;
int sec;
void get_time();
};
Time::Time(int h,int m,int s)
{
hour=h;
minute=m;
sec=s;
}
void Time::get_time()
{
cout<<hour<<":"<<minute<<":"<<sec<<endl;
}
int main()
{
Time t1(15,12,55);
int *p1=&t1.hour;
cout<<*p1<<endl;
t1.get_time();
Time *p2=&t1;
p2->get_time();
void(Time::*p3)();
p3=&Time::get_time;
(t1.*p3)();
}
关于类和对象的进一步讨论之析构函数 C++