C++:类的设计————构造与析构函数及其动态内存管理

来源:互联网 发布:天池大数据竞赛官网 编辑:程序博客网 时间:2024/04/30 02:11

构造与析构函数

1.构造与析构函数的意义

创建对象是往往需要初始化,但是对象不可以直接访问私有成员,因此C语言中直接赋值的初始化方法行不通,于是C++使用构造函数进行对象的初始化操作。
构造函数的原型声明在public中,名称与类名一致,即:

class class_name{    private:        ..    public:        class_name();};

C++规定,如果程序员不自定义构造函数,会给出一个默认的构造函数(需要注明的是构造函数没有声明类型),默认的构造函数没有任何功能,还需要程序员添加相应的操作。
例如下面这个类:

class Bank{    private:        int client;        double money;    public:        Bank();//default        Bank(int c,double m);        ~Bank();};Bank::Bank(){    client = 0;    money = 0.0;}

注意到上述例子中,有Bank()的一个重载版本Bank(int c,double m),这也是一个构造函数,是程序员自定义的,带有参数列表,这样,我们在创建新的对象的时候,就可以这样写:

Bank bank1;//不初始化调用默认构造函数Bank bank2(500,1000.0);//隐式的使用Bank(int c,double m)Bank bank3 = Bank(200,50.0);//显式的使用Bank(int c,double m)

有意思的是,如果类的声明仅仅给出了Bank(int c,double m)而没有Bank()时,编译器会对bank1报错。原因在于,只有当程序员不定义任何构造函数时C++才会给出默认的构造函数,当自定义了构造函数时必须定义默认的构造函数,这样意在禁止创建未初始化的变量。

复制构造函数

还有一种特殊的构造函数,被称为复制构造函数,一般来说,这也是C++自己默认给出的。
所谓的复制构造函数 实际上是对赋值号(=)的一种规则约定,它仅用于初始化过程中,它的原型如下:

class_name (const class_name &)

例如属于Time类的复制构造函数

Time (const Time & time)

不同于普通的自定义构造函数,参数只有一个且类型和类的名称统一
实际上,复制构造函数仅用于初始化过程中包含两层含义。
一个是常规的初始化对象,还有一个是指函数按值传递对象。
前一个很好理解,那么后一个是怎么回事?
实际上,同前一个一样,函数按值传递对象是,会先生成一个临时变量,将原始对象的值赋值给临时变量,此时会调用复制构造函数
有时候我们回想更改赋值规则,那么可以自己定义复制构造函数,显式的使用。
例如下面这个例子:

class String{    private:        int length;        char * str;     public:        String();        String(const String & s);//复制构造函数        String(const char * s);//自定义构造函数        ~String();}String::String(const String & s)//构造函数这里的参数是给私有成员赋值的,而复制构造函数的类型是Class_name的引用{    length = s.length;    str = new char [length+1];    strcpy(str,s.str);}

这个例子其实能很好的说明为什么自定义复制构造函数。
默认的复制构造函数,成员之间的复制是“值”赋值。
比如:

String s1("xxxxx");String s2("sssss");s1 = s2;

假如没有自定义复制构造函数,使用C++默认的复制构造函数的话。上述代码中的s1 = s2相当于:

s1.length = s2.length;s1.str = s2.str;

乍一看感觉十分正确,但是仔细想想其实不妥。str是一个char类型的指针,存储的是一个地址,默认的复制构造函数会直接将地址赋值,此时如果s1生命周期结束,调用了析构函数delete掉了s1.str,那么s2.str也被delete了(因为s1.str和s2.str指向的是同一块内存),如果s2在s1 delete掉后还想被程序使用,那么就会出现大麻烦,使用delete掉的内存属于未定义的行为。
其实这个地方我们只是想把s2的字面值赋值给s1罢了,因此这种情况下自定义复制构造函数很有必要了

类型转换与转换函数

下面来看一个例子:

class Stone{    private:        int m_stone;        double m_pounds;    public:        Stone();        Stone(double pounds);//construct for double pounds        Stone(int stone,double pounds = 0);//construct for int stone and double pounds        ~Stone();};Stone::Stone(double pounds){    m_pounds = pounds;    m_stone = (int)pounds / 14;}

假如有如上定义。
这样,Stone(double pounds)这个构造函数就为将double类型转换为Stone类型提供了一个蓝图。
也就是说,可以这样写代码:

Stone my_stone;my_stone = 19.6;

程序将使用构造函数Stone(double pounds)来创建一个临时的Stone对象然后将19.6作为初始值,随后采用成员逐个赋值的方式将该临时对象的内容复制到my_stone中,这一过程称为隐式转换
(注:只有接受一个参数的构造函数才能做转换函数,或者其他参数都提供了默认值)
这种自动特性看起来很好,但是可能会导致意外的类型转换,因此,C++新增关键字explicit来显式的定义这种转换方式,即:

explicit Stone(double pounds);
Stone my_stone;my_stone = 19.6;//not valid if Stone(double) is declared as explicitmy_stone = Stone(19.6);//ok,and recommendedmy_stone = (Stone)19.6;//ok

那么,是否能进行如下转换呢?

Stone my_stone(19.6);double host = my_stone;

答案是可以的,但是不能用构造函数。
构造函数只用与将某种类型转换为类类型,要进行相反的操作,需要借助c++特殊运算符函数——转换函数。
如果定义来从Stone到double的转换函数,就可以使用下面的转换。

Stone my_stone(19.6);double host = (double)my_stone;double host = double (my_stone);

转换函数的定义如下:

operator type_name();

要注意的是,转换函数必须是类方法,转换函数没有返回类型也没有参数。
假如说要添加从Stone类型到int类型和double类型的转换,只需要:

class Stone{    private:        int m_stone;        double m_pounds;    public:        Stone();        Stone(double pounds);//construct for double pounds        Stone(int stone,double pounds = 0);//construct for int stone and double pounds        ~Stone();        //conversion function         operator double()const;        operator int() const ;};

2.动态内存管理

0 0