构造函数与析构函数

来源:互联网 发布:免费网络宣传 编辑:程序博客网 时间:2024/05/26 05:51

       构造函数和析构函数是面向对象程序设计中的一个非常重要的概念,但其实它们也并不是很复杂,要想学习它们,我们就需要了解它们是什么。

 

【对象的生命周期】

       在这里我们今天先暂时不讨论有关于构造函数和析构函数的相关概念,我们先来认识一下对象的生命周期。众所周知程序中所有的数据,无论是变量、常量都是有生命周期限制的,通常我们使用的最多的就是在主函数中定义某一个变量,从你定义这个变量开始的时候,这个变量也就相当于有了生命,当程序运行完成之后,变量完成了它们的使命,自然就消亡了。这很类似于人类从出生到死亡的过程,但是与人类的生命周期不同的是,数据的生命周期都是可以控制的,我们可以通过在程序中使用一对花括号的方式限定某一个变量的生命周期。既然变量有生命周期,那么与变量相似的对象也同样具有生命周期。那么,对象的生命周期由什么来决定呢?这就是我们今天所要学习的课题。

 

【构造函数初识】

        构造函数,顾名思义就是用于构造对象的函数。通常,它是和我们的类名重名的,并且由于它的特殊性,它是不需要任何的返回值类型的,也就是我们在定义一个构造函数时,并不需要写明它的返回类型,请记住是不需要写,而不是写成void!

        在我们使用某一个类,例如Student类定义一个对象std时,实际上Student类调用了它的构造函数完成了这个让人觉得很不起眼的功能。也就是说构造函数是在我们新建类的对象的时候自动执行的。默认的情况下你并不需要去考虑构造函数,如果你不希望它在执行的时候额外的为你做些什么的话,你完全可以把它忽略掉。因此我们在上例中并没有使用构造函数。

       在刚刚的描述中我们已经知道了构造函数是用于在我们定义对象的时候为我们在内存中生成这个对象的,但其实,构造函数对于我们来说还有另外一种妙用。那就是对私有的成员变量进行赋值!通常的情况下在我们定义类时,C++的编译系统会在不经过你同意的情况下给你建立一个默认的隐藏的构造函数,由于它并不需要完成什么功能,所以这一类的构造函数是不需要任何参数的,没有任何参数的构造函数我们就称之为无参数的构造函数,简称“无参构造函数”。

       那么有无参构造函数自然就会有“有参构造函数”,它的作用其实不用我说明了,这就是构造函数的妙用所在,它可以在我们建立对象的时候为我们赋值!

       说了那么多貌似我们还没有真正见过构造函数的样子,那么我们将继续以Student类为案例说明这个构造函数:

class Student{private://使用private关键字定义数据成员(属性)string ID;//学号string Name;//姓名int Age;//年龄public://使用public关键字定义方法成员(方法)Student();//无参构造函数Student(string ID,string Name,int Age);//有参构造函数};


       大家一定很奇怪吧!为什么我在这里只是对构造函数进行了函数声明,但是却没有实现构造函数中的具体功能呢?这就是我的目的,接下来我要教大家如何在类外进行函数的定义,实现函数的内部功能。在类外实现某个类的函数,无论它是不是构造函数还是析构函数,只要它是个函数,它就要遵守以下的规范,前提是你必须是在类外定义它。它的规范如下:

 

函数的返回类型 函数所在的类名::函数名(函数参数1,函数参数2,……,函数参数n)

{

       函数体中的具体代码

}

 

       在这里我需要说明这样的前提,函数的返回类型就是你在类中定义的那个函数的返回值类型,如果是构造函数或者是析构函数的话,你必须将这个选项给忽略。例如我们在类外实现Student类的构造函数时,就需要按照如下的写法:


//实现无参构造函数Student::Student(){this->ID = "";this->Name = "";this->Age = 0;}//实现有参构造函数Student::Student(string ID, string Name, int Age){this->ID = ID;this->Name = Name;this->Age = Age;}


       其实大家可以看到,这是完全遵照格式的写法,因为构造函数和类的名称本就相同,所以才会出现两个名称都是Student的情况,细细想来这样的安排也并没有什么不妥。和上面类中代码一比较我们也可以清楚的发现,类名和“::”(我们称之为域运算符)后面的内容就是构造函数的函数声明而已。但是我们也可以发现一些不同,是的!构造函数并没有任何返回值,它不需要,也不能要!所以它并不需要像常规的函数一样,在前面加上void作为无返回值的说明,直接而了当!也许这个例子并不能够带给大家多么深刻的体会,那么现在我们可以改造一下这个案例,在Student类中添加一个用于实现打印功能的print函数,那么这个时候Student类就会变成下面的样子:

 

class Student{private://使用private关键字定义数据成员(属性)string ID;//学号string Name;//姓名int Age;//年龄public://使用public关键字定义方法成员(方法)Student();//无参构造函数Student(string ID,string Name,int Age);//有参构造函数void Print();//用于打印成员变量};

        现在大家可以清楚的看到,在Student类中多了一行print函数的声明。接下来,我们就要开始在类外对这个函数进行实现了,它的代码如下:


//对Print函数进行实现void Student::Print(){cout << "学号:" << this->ID << endl;cout << "姓名:" << this->Name << endl;cout << "年龄:" << this->Age << endl;}


       这个时候我们再和前面的构造函数的实现相比,我们可以看到前面多了一个void声明,那是因为我们在声明print函数的时候已经把这个函数声明成了void类型的。里面具体实现它功能的代码我想大家就不用再看了。

       那么这样我们可以对有参构造函数和无参构造函数进行功能上的总结:

 

       当我们需要为类中的数据成员设定默认值时,我们可以定义无参构造函数,当我们需要对类中的数据成员进行赋值时,那么我们可以使用有参构造函数。

 

        那么如何在定义对象的时候使用有参构造函数或者是无参构造函数呢?接下来我们可以看看主函数中的代码:


void main(){Student std1;//定义一个Student类型的对象std1,使用无参构造函数Student std2("211301013", "蒋涵鑫", 22);//定义一个Student类的对象std2,使用有参构造函数std1.Print();//打印std1的信息std2.Print();//打印std2的信息}


       现在大家应该可以清楚的看到,当我们需要使用无参构造函数时,我们并不需要刻意做一些什么,我们只需要直接使用类去定义对象就可以了。简而言之,就是遵循如下的格式:

 

类名    对象名

 

        但是当我们需要调用有参构造函数为对象进行赋值时,那么我们需要遵循以下的格式:

 

类名   对象名(函数实参列表)

        下面我们可以看一下刚刚运行的代码的结果:



【复制构造函数】

       其实所谓的复制构造函数,是使用同类的对象获取数据的一种手段,它和其他的构造函数没有什么太大的不同,只是它在有参构造函数的基础之上,把原先的形参换成了同类的对象罢了。按照这样的概念我们可以很快的定义一个复制构造函数出来。如下:

 

public://使用public关键字定义方法成员(方法)Student();//无参构造函数Student(string ID,string Name,int Age);//有参构造函数Student(Student std);//复制构造函数,本行有错void Print();//用于打印成员变量 

       但是我们竟然发现报错了,报错的原因如下:


 


        这就奇怪了,按照我们的概念为什么我们不能够使用同类型的参数呢?说实话这个问题我也一直很迷茫,而且限于篇幅,可能需要大家自己课后去找资料解决这个问题了。出错的原因我们姑且不论,但是总归函数知道它的解决方案的,只要我们使用引用传递,就可以解决这个问题了。代码如下:

 

class Student{private://使用private关键字定义数据成员(属性)string ID;//学号string Name;//姓名int Age;//年龄public://使用public关键字定义方法成员(方法)Student();//无参构造函数Student(string ID,string Name,int Age);//有参构造函数Student(Student &std);//复制构造函数void Print();//用于打印成员变量};

 

        大家也可以看到,只是在形参前加上一个“&”,就可以完美的解决这个问题。但是这并不是最佳的解决方案,为什么呢?让我们来看一下函数的实现代码吧:

//复制构造函的实现Student::Student(Student &std){this->ID = std.ID;this->Name = std.Name;this->Age = std.Age; std.Age = 10;//重点在这儿}

         我的天!我们竟然修改了形参的数据!大家都知道,使用引用传递时,这个名为std的别名可就相当于我们实际在调用过程中代入的参数,万一要是有谁脑残的把这个参数中的一点东西给改变了,后果简直是不堪设想!我们做了毁灭数据的事情!这简直是太危险了!

         既然遇到了危险,那么怎么办呢?当然是要防微杜渐,防患于未然啦!在这个时候,已经被我们遗忘了很久一个关键字派上了用场。在我们遗弃了它很久之后不得不靠它维持数据的原样,确保数据不会被改变。它就是用于定义常变量的const。代码如下:


//复制构造函的实现Student::Student(const Student &std){this->ID = std.ID;this->Name = std.Name;this->Age = std.Age; std.Age = 10;//重点在这儿}

        这个时候我们可以看到,在加入了conts关键字之后,最后一行出现了报错,而其他的语句却没有出现报错的情况。让我们来看一下,它具体报错的内容是什么:


        “表达式必须是可修改的左值”,这是什么意思呢?其实这句话是说赋值号左边的std.age是不可修改的,为什么不可修改,相信我不说大家也能够猜得出来,因为参数是常量!常变量的特性是什么?只读不改!这就能够保证形参中的值不会随意的出现更改,可以减少我们犯低级错误的可能!好了,接下来我们要说说拷贝构造函数如何使用呢?,我们还是先来看一下代码:


void main(){Student std1;//定义一个Student类型的对象std1,使用无参构造函数Student std2("211301013", "蒋涵鑫", 22);//定义一个Student类的对象std2,使用有参构造函数Student std3(std2);//定义一个Student类的对象std3,使用复制构造函数std1.Print();//打印std1的信息std2.Print();//打印std2的信息std3.Print();//打印std3的信息}

       大家可以看到,使用复制构造函数和使用有参构造函数也并没有什么不同,只是把函数的参数代入了进去而已。

       写到这里了,大家一定会想问我一个问题,为什么这三种构造函数可以同时存在于一个类中?其实这个问题很好回答,它取决于C++中的一种特殊的机制——“函数重载”!

 

【析构函数】

        好了,说完了构造函数我们就来聊聊这个析构函数吧!虽然它没有构造函数显得重要,毕竟它什么都做不了,唯一能够完成的就是删除在内存中建立的对象,将这些对象占用的内存空间释放出来而已。那么为什么我还要提它呢!原因就在于它的功能是非常重要的,试想一下,我们运行的程序如果在执行完成之后不把数据所占用的空间释放出来,那么其他需要运行的软件用什么地方来保存自己的数据呢?强占了别人的土地不还,这是一件很无耻的事情! 

析构函数的作用已经交代完了,但是它究竟长什么样子呢?其实析构函数通常具有两个特征:

1.     使用“~”开头;

2.     它和构造函数一样,不需要交代返回值类型

 

        例如Student类的析构函数就可以写成如下的形式:

 

class Student{private://使用private关键字定义数据成员(属性)string ID;//学号string Name;//姓名int Age;//年龄public://使用public关键字定义方法成员(方法)Student();//无参构造函数Student(string ID,string Name,int Age);//有参构造函数Student(const Student &std);//复制构造函数~Student();//析构函数void Print();//用于打印成员变量};

        那么析构函数在什么时候执行呢?当然是在程序执行完成时执行呀!那么析构函数能不能够带有参数呢?这个答案是不能!当然这在情理上也可以想得通,本来嘛!析构函数就只是负责垃圾回收,既然都要回收了自然就没有利用价值了,那又何必还要让析构函数具有参数,多此一举呢?

       当然,析构函数和构造函数一样,倘若你不声明,系统会默认的帮你建一个析构函数,只是你不知道,看不见而已!

       析构函数需要进行实现吗?这个答案我可以这样回答你。你在类中声明了多少函数,你就需要实现多少函数,否则,程序就是报错,你也没辙!析构函数的实现如下:

//析构函数的实现Student::~Student(){}

        当然,其实你并不需要析构函数为你做什么,事实上,它也的确是无能为力。但是如果你写了它,你就依旧要实现它!这的确是个不小的麻烦,要耽误一些功夫!所以最好的方式就是不写!甄嬛的经典台词叫“无用的人不必留着!”在程序设计的世界中也是如此,无用的代码也不必留着,更不必写出来!


【程序完整代码】

#include <iostream>#include <string>using namespace std;class Student{private://使用private关键字定义数据成员(属性)string ID;//学号string Name;//姓名int Age;//年龄public://使用public关键字定义方法成员(方法)Student();//无参构造函数Student(string ID,string Name,int Age);//有参构造函数Student(const Student &std);//复制构造函数~Student();//析构函数void Print();//用于打印成员变量};//实现无参构造函数Student::Student(){this->ID = "";this->Name = "";this->Age = 0;}//实现有参构造函数Student::Student(string ID, string Name, int Age){this->ID = ID;this->Name = Name;this->Age = Age;}//复制构造函的实现Student::Student(const Student &std){this->ID = std.ID;this->Name = std.Name;this->Age = std.Age; }//对Print函数进行实现void Student::Print(){cout << "学号:" << this->ID << endl;cout << "姓名:" << this->Name << endl;cout << "年龄:" << this->Age << endl;}//析构函数的实现Student::~Student(){}void main(){Student std1;//定义一个Student类型的对象std1,使用无参构造函数Student std2("211301013", "蒋涵鑫", 22);//定义一个Student类的对象std2,使用有参构造函数Student std3(std2);//定义一个Student类的对象std3,使用复制构造函数std1.Print();//打印std1的信息std2.Print();//打印std2的信息std3.Print();//打印std3的信息}



1 0
原创粉丝点击