数据挖掘工具
----构造与析构 数据挖掘实验室
作者:HolyFire 数据挖掘论坛
我们在平时的生活中一般会总结出一些规律,早上起床会刷牙洗脸,晚上会洗澡睡觉,这些都成了惯例。使用瓶装调味品时先将瓶盖打开,用完后将瓶盖盖上。这是一种好习惯。但是有些人不同,他们往往偷懒,一个常常不刷牙不洗脸不洗澡的人会有体味,东西放得乱七八糟的人生房间很不整洁。这些都是我们不希望看到的。当然编程中我们也不希望代码乱七八糟。 数据挖掘交友
使用一个未初始化的变量简直就是灾难,使用一个未初始化的指针将导致崩溃。这是我的忠告。在C++中初始化不会有附加的效果,不会降低效率,我们要做的是养成好习惯,产生一个对象的时候就将它初始化。 数据挖掘实验室
对于 数据挖掘实验室
Object.Init(); 数据挖掘工具
Object.Free(); 数据挖掘工具
这样的调用并不是很困难,要记住他也不是难事,但是谁都不能保证他永远不会忘记,更糟糕的是 数据挖掘交友
Object.Init();
Object.Free();
没有配对使用
Object.Init(); 数据挖掘论坛
Object.Free(); 数据挖掘交友
Object.Free();
或 数据挖掘工具
Object.Init(); 数据挖掘实验室
Object.Init(); 数据挖掘研究院
Object.Free(); 数据挖掘交友
会带来什么样的结果,谁也不知道,而且这样的错误,编译器不会报错。这是多么可怕的错误,一个程序员最怕遇上的就是这样的逻辑错误,它可能为了找这样的一个错误花上一整天时间。 数据挖掘工具
让我们看看有什么好的办法。
一个对象按时间来分析,一般有三个阶段,出生,活动,死亡。与我们要做的有什么相关之处呢,初始化,运行,释放。很好,对照一下,我们发现在对象出生的时候初始化,死亡的时候释放,如果这一切能用这样的机制来操作,我们就再也不用担心会由于忘记或错误的使用带来麻烦了。 数据挖掘工具
C++里就提供了这样的机制。使用他有个约定 数据挖掘论坛
class Object{ 数据挖掘交友
public: 数据挖掘工具
Object(); //与类同名的函数,该函数没有返回值,叫做构造函数 数据挖掘工具
~Object(); //类似的,在构造函数名前加一个取反符号,叫做析构函数 数据挖掘实验室
}; 数据挖掘研究院
构造函数将在对象产生的时候调用 数据挖掘实验室
析构函数将在对象销毁的时候调用 数据挖掘实验室
调用的过程和实现方法由编译器完成,我们只要记住他们调用的时间就行了,而且他们的调用是自动完成的,不需要我们控制。 数据挖掘工具
#include <iostream> 数据挖掘交友
using namespace std; 数据挖掘实验室
class Object{
public: 数据挖掘交友
Object(){ cout << "Object ON!" << endl; } 数据挖掘实验室
~Object(){ cout << "Object OFF!" << endl; } 数据挖掘研究院
}; 数据挖掘论坛
void main()
{
Object o; 数据挖掘论坛
}
运行结果
Object ON!
Object OFF! 数据挖掘论坛
构在函数和析构函数确实的执行了 数据挖掘交友
现在我们来一个应用的例子 数据挖掘交友
一个字符串类,它需要保存字符串的内容,但是它不知道字符串的大小,那么设计这个字符串类的时候,保存字符串的成员变量就不能用固定大小的数组,而是用可以间接操作数组的指针。
#include <iostream> 数据挖掘实验室
#include <string.h>
using namespace std; 数据挖掘论坛
class string{ 数据挖掘工具
private: 数据挖掘论坛
char * data;
public:
string(){ data = NULL; } 数据挖掘实验室
string( char * str ) 数据挖掘实验室
{ 数据挖掘工具
cout << "Copy string: " << str << endl; 数据挖掘论坛
data = new char[ strlen(str) + 1 ]; 数据挖掘实验室
memcpy( data , str , strlen(str) + 1 );
} 数据挖掘实验室
char * Data(){ return data; } 数据挖掘研究院
~string()
{ 数据挖掘研究院
if( data ) 数据挖掘工具
{ 数据挖掘交友
cout << "Free string: " << data << endl; 数据挖掘工具
delete data;
} 数据挖掘论坛
} 数据挖掘论坛
}; 数据挖掘研究院
void main()
{ 数据挖掘工具
{ 数据挖掘实验室
string s("abcd"); 数据挖掘工具
cout <<"Show String: " << s.Data() <<endl;
}
cin.get();
}
Copy string: abcd //执行了string::string( char * str ) 构造函数 数据挖掘交友
Show String: abcd 数据挖掘交友
Free string: abcd //由于在{}中产成的对象是临时对象,它的生命期在}后就结束了,所以string::~string() 析构函数被调用
申请内存和释放内存的操作自动完成了,构造函数和析构函数的目的在于一个类可以象普通类型一样初始化和释放,从而保证了封装。 数据挖掘研究院
上面的例子有两个构造函数,这么什么大不了的,我们看过《面面俱到----重载》得都知道,重载的把戏。 数据挖掘论坛
要注意的是构造函数可以有参数,在继承中如何处理呢。 数据挖掘工具
class mystring : public string{ 数据挖掘交友
public: 数据挖掘工具
mystring( char * str ):string( str ){ } 数据挖掘研究院
} 数据挖掘论坛
mystring( char * str ):string( str ) 数据挖掘工具
记住这样的形式,给自己的父类传递函数就用这样的书写格式,这是一个约定。 数据挖掘实验室
构造函数后面加上一个:表示后面是一个初始化序列,说它是一个序列是因为它可以初始化多个成员变量,在初始化序列里调用向父类传递参数是为了保证类的产生的顺序,先产生父类,然后是子类。使用初始化有个好处就是可以提高效率。 数据挖掘实验室
string(){ data = NULL; } 数据挖掘论坛
可以改写成 数据挖掘实验室
string():data(NULL){ }
他的作用是产生成员变量char * data时将他的值置为NULL。从而少了data = NULL;这步操作。 数据挖掘论坛
注意,这里构造和析构有一个顺序问题,就是构造时应该从基类开始按继承的层次顺序调用,析构的时候顺序正好相反。这样处理是因为,子类可能在构造函数里使用父类的成员变量,如果父类还没有创建,那就会有问题,而析构的时候,如果父类先析构,也会有这样的问题。 数据挖掘论坛
析构函数还有一个能否正确运行的问题。 数据挖掘论坛
#include <iostream> 数据挖掘实验室
using namespace std; 数据挖掘交友
class One{
public:
One(){ cout << "One ON!" << endl; } 数据挖掘交友
~One(){ cout << "One OFF!" << endl; }
}; 数据挖掘工具
class Two : public One{ 数据挖掘实验室
public: 数据挖掘交友
Two(){ cout << "Two ON!" << endl; } 数据挖掘论坛
~Two(){ cout << "Two OFF!" << endl; } 数据挖掘研究院
}; 数据挖掘实验室
class Three : public Two{ 数据挖掘实验室
public: 数据挖掘工具
Three(){ cout << "Three ON!" << endl; } 数据挖掘研究院
~Three(){ cout << "Three OFF!" << endl; }
}; 数据挖掘工具
void main() 数据挖掘研究院
{ 数据挖掘论坛
Three three; 数据挖掘论坛
}
运行结果 数据挖掘工具
One ON! 数据挖掘交友
Two ON! 数据挖掘论坛
Three ON! 数据挖掘研究院
Three OFF! 数据挖掘交友
Two OFF! 数据挖掘论坛
One OFF!
正确
void main()
{ 数据挖掘交友
Three * three = new Three; 数据挖掘交友
delete three; 数据挖掘论坛
} 数据挖掘实验室
运行结果 数据挖掘研究院
One ON! 数据挖掘论坛
Two ON! 数据挖掘论坛
Three ON!
Three OFF! 数据挖掘工具
Two OFF! 数据挖掘交友
One OFF! 数据挖掘实验室
正确
void main() 数据挖掘交友
{ 数据挖掘论坛
One * three = new Three; 数据挖掘工具
delete three; 数据挖掘交友
} 数据挖掘实验室
运行结果 数据挖掘研究院
One ON! 数据挖掘研究院
Two ON! 数据挖掘论坛
Three ON! 数据挖掘工具
One OFF!
不好了,Two和Three的析构都没有运行,怎么会这样,原来One * three指出了指针指向的是一个One类的对象。如何得到正确的结果呢,如果能让One类记住被继承后的变化就好了。 数据挖掘论坛
对了!虚函数,在《后入为主----虚函数》中可以知道,虚函数有这个特性,不信试试看。
class One{ 数据挖掘工具
public: 数据挖掘实验室
One(){ cout << "One ON!" << endl; } 数据挖掘工具
virtual ~One(){ cout << "One OFF!" << endl; } 数据挖掘交友
}; 数据挖掘研究院
void main() 数据挖掘论坛
{
One * three = new Three;
delete three; 数据挖掘论坛
} 数据挖掘交友
运行结果
One ON! 数据挖掘研究院
Two ON! 数据挖掘论坛
Three ON! 数据挖掘实验室
Three OFF! 数据挖掘论坛
Two OFF! 数据挖掘论坛
One OFF! 数据挖掘实验室
正确 数据挖掘实验室
这个特点很重要,我们要牢牢记住,我们称这种方法为“虚析构”,在多态里运用非常广泛,也是编写可复用代码的一个重要技巧。 数据挖掘工具
构造和析构的作用机制就是自动化,简化编程的复杂度。还有要记住的是,在一个类的构造函数里分配了的资源尽量要记得在该类的析构函数里释放,当然也允许提前释放,你可以在析构函数里判断它是否已经释放,如果没有就释放。这就是----由始至终,它间接的描述了一个对象的生和死(记住这一点很重要,因为我以后会讲到如何运用这个特性控制对象的生死)。 数据挖掘工具
2001/8/23 数据挖掘实验室
丁宁 数据挖掘交友 |