构造函数与析构函数

构造函数与析构函数

当声明一个类对象时, 编译程序需要为对象分配存储空间, 进行必要的初始化, 这部分工作随着类的不同而不同。在 C + + 中, 由构造函数来完成这些工作。构造函数是属于某一个类的,它可以由用户提供, 也可以由系统自动生成。与构造函数对应的是析构函数, 当撤消类对象时, 析构函数就回收存储空间,并做一些善后工作。析构函数也属于某一个类, 它可以由用户提供, 也可以由系统自动生成。

构造函数

构造函数是一种特殊的成员函数,它主要用于为对象分配空间, 进行初始化。构造函数具有一些特殊的性质:

  1. 构造函数的名字必须与类名相同。
  2. 构造函数可以有任意类型的参数, 但不能具有返回类型
  3. 定义对象时, 编译系统会自动地调用构造函数
    下面我们为类 complex 建立一个构造函数。
class complex
{
private :
    double real;
    // 表示复数的实部 double imag;
    // 表示复数的虚部
public:
    complex ( double r, double i) // 定义构造函数, 其名与类名相同
    {
        real = r;    // 在构造函数中, 对私有数据 real 和 imag 赋值
        imag = i;
    }
    double abscomplex()
    {
        double t;
        t = real * real + imag * imag;
        return sqrt(t);
    } 
} ;

上面声明的类名为 complex, 其构造函数名也是 complex。构造函数的主要功能是对对象进行初始化,即对数据成员赋初值。这些数据成员通常为私有成员。构造函数很少做赋初值以外的事情。

构造函数不能像其它成员函数那样被显式地调用,它是在定义对象的同时调用的, 其一般格式为:

类名 对象名(实参表) ;
说明:
  1. 构造函数的名字必须与类名相同, 否则编译程序将把它当作一般的成员函数来 处理。
  2. 构造函数没有返回值, 在声明和定义构造函数时, 是不能说明它的类型的,甚至 说明为 void 类型也不行。
  3. 在实际应用中, 通常需要给每个类定义构造函数。如果没有给类定义构造函数, 则编译系统自动地生成一个缺省的构造函数。例如,编译系统为类 complex 生成下述形 式的构造函数:
complex∷complex( )
{ }
  1. 构造函数可以是不带参数的
  2. 构造函数也可采用构造初始化表对数据成员进行初始化, 这是某些程序员喜欢使用的方法。
class A
{
    int i;
    char j;
    float f;
public:
    A(int I, char J, float F)i(I),j(J), (F) {}
} ;

但是如果需要将数据成员存放在堆中或数组中,则应在构造函数中使用赋值语句, 即使构造函数有初始化表也应如此,例如:

class x
{
    int i;
    char c;
    float f;
    char name[ 25 ] ;
public:
    x(int I, char C, float F, char N[ ] )i( I), c(C), f( F)
    {
        strcpy ( name, N) ;
    } ;

注意:在这个类的构造函数中, 构造初始化表初始化了三个非数组成员, 而字符数组必须在函数体内被赋值。

  1. 对没有定义构造函数的类, 其公有数据成员可以用初始值表进行初始化。
#include <iostream>
class myclass
{
public:
    char name[10] ;
    int no;
} ;
void main()
{
    myclass a = {"chen",25} ;
    cout << a.name <<" "<< a.no << endl;
}

缺省参数的构造函数

对于带参数的构造函数,在定义对象时必须给构造函数传递参数, 否则构造函数将不被执行。但在实际使用中,有些构造函数的参数值通常是不变的, 只有在特殊情况下才需要改变它的参数值,这时可以将其定义成带缺省参数的构造函数, 例如:

class complex
{
private :
    double real;
    double imag;
public:
    complex ( double r = 0.0, double i = 0.0 ) ;
    // 含有缺省参数的构造函数
    double abscomplex();
} ;
complex∷complex( double r, double i)
{
    real = r;
    imag = i;
}
double complex∷abscomplex()
{
    double t ;
    t = real * real + imag * imag;
    return sqrt(t) ;
}

在类 complex 中, 构造函数 complex( )的两个参数均含有缺省参数值 0 .0。因此, 在定义对象时可根据需要使用其缺省值。
下面我们用 main( )函数来使用它:

main ()
{
    complex S1;
    // 不传递参数, 全部用缺省值 complex S2( 1 .1) ;
    // 只传递一个参数
    complex S3( 1.1, 2.2) ;
    // 传递两个参数
    // …
}

在上面定义了三个对象 S1、S2 和 S3, 它们都是合法的对象。由于传递参数的个数不同,使它们的私有数据成员 real 和 imag 取得不同的值。由于定义对象 S1 时,没有传递数,所以 real 和 imag 均取构造函数的缺省值为其赋值, 因此 real 和 imag 均为 0.0。在定义对象 S2 时, 只传递了一个参数,这个参数传递给构造函数的第一个参量, 而第二个参量取缺省值, 所以对象 S2 的 real 取值为 1 .1, 而 imag 取值为 0 .0。在定义对象 S3 时, 传递了两个参数,这两个参数分别传给了 real 和 imag,因此 real 取值为 1 .1, imag 取值为 2 .2。

析构函数

析构函数也是一种特殊的成员函数。它执行与构造函数相反的操作, 通常用于执行一些清理任务,如释放分配给对象的内存空间等。析构函数有以下一些特点:

  1. 析构函数与构造函数名字相同, 但它前面必须加一个波浪号(~)
  2. 析构函数没有参数, 也没有返回值, 而且不能重载, 因此在一个类中只能有一个析构函数。
  3. 当撤消对象时, 编译系统会自动地调用析构函数

下面我们重新说明 complex 类, 使它既含有构造函数,又含有析构函数。

#include<iostream>
#include<math.h>
using namespace std;
class test
{
private:
    double real;
    double imag;
public:
    test(double r = 0.0 , double i = 0.0);  //声明构造函数
    ~test();                                //声明析构函数
    double abstest();
};

test::test(double r , double i )            //定义构造函数
{
    cout << "constructing..."<<endl;
    real = r;
    imag = i;
}
test::~test()                               //定义析构函数
{
    cout << "destructing..."<<endl;
}
double test::abstest()
{
    double t;
    t = real* real +imag * imag;
    return sqrt(t);
}

int main()
{
    test A(1.1,2.2);
    cout << "abs of test A ="<< A.abstest() <<endl;
    return 0;
}


在类 complex 中定义了构造函数和析构函数。由于类 complex 较为简单, 对象撤消时不需要什么特殊的清理工作,因此我们让析构函数只输出一个串“destructing…”。程序
运行结果如下:
constructing…
abs of complex A = 2 .459675
destructing…
**说明:**每个类必须有一个析构函数。若没有显式地为一个类定义析构函数, 编译系统会自动地生成一个缺省的析构函数。例如, 编译系统为类 complex 生成缺省的构造函数

complex∷
~complex ( )
{ }

对于大多数类而言,缺省的析构函数就能满足要求。但是, 如果在一个对象完成其操作之前需要做一些内部处理,则应该显式地定义析构函数, 例如:

class string - data
{
    char * str;
public:
    string - data ( char * s)
    {
        str = new char[strlen(s) + 1] ;
        strcpy(str,s) ;
    }
    ~string - data( )
    {
        delete str;
    }
    void get - info( char * ) ;
    void sent - info( char * ) ;
} ;

这是构造函数和析构函数常见的用法,即在构造函数中用运算符 new 为字符串分配存储空间,最后在析构函数中用运算符 delete 释放已分配的存储空间。

重载构造函数

在重载没有参数和带缺省参数的构造函数时, 有可能产生二义性,例如:

class x
{
public:
    x() ;
    // 没有参数的构造函数
    x(int i = 0) ;
    // 带缺省参数的构造函数
} ;
int main()
{
    x one( 10 ) ;
    // 正确,调用 x(int i = 0 )
    x two;
    // 存在二义性
    // …
    return 0;
}

该例定义了两个重载构造函数 x, 其中一个没有参数, 另一个带有一个缺省参数。创建对象 two 时, 由于没有给出参数,它既可以调用第一个构造函数, 也可以调用第二个构造函数。这时,编译系统无法确定应该调用哪一个构造函数, 因此产生了二义性。在实际应用时,一定要注意避免这种情况。

拷贝构造函数

拷贝构造函数是一种特殊的构造函数。它用于依据已存在的对象建立一个新对象。典型的情况是,将参数代表的对象逐域拷贝到新创建的对象中。
用户可以根据自己的需要定义拷贝构造函数,系统也可以为类产生一个缺省的拷贝构造函数。

1 . 自定义拷贝构造函数

自定义拷贝构造函数的一般形式如下:

classname ( const classname &ob)
{
// 拷贝构造函数的函数体
}

其中,ob 是用来初始化另一个对象的对象的引用。
下面是一个用户自定义的拷贝构造函数:

#include <iostream >
using namespace std;
class point
{
    int x, y;
public:
    point(int a, int b) // 构造函数
    {
        x = a;
        y = b;
    }
    point( const point &p) // 拷贝构造函数
    {
        x = 2 * p .x;
        y = 2 * p .y;
    }
    void print( )
    {
        cout << x << " "<< y << endl;
    }
} ;
int main ()
{
    point p1 (30, 40 ) ;
    // 定义类 point 的对象 p1
    point p2 ( p1) ;
    // 显式调用拷贝构造函数,创建对象 p2
    p1 .print( ) ;
    p2 .print( ) ;
    return 0 ;
}

假如 p1、p2 为类 point 的两个对象,且 p1 已经存在, 则下述语句可以调用拷贝构造函数初始化 p2:

point p2( p1 ) ;

本例除了显式调用拷贝构造函数外, 还可以采用赋值形式调用拷贝构造函数。例如

main ()
{
point p1 (10, 20 ) ;
point p2 = p1; // 赋值形式调用 point( const point &p )
// …
}

在定义对象 p2 时, 虽然从形式上看是将对象 p1 赋值给了对象 p2, 但实际上调用的是拷贝构造函数, 在对象 p2 被创建 时, 将对象 p1 的值逐域 拷贝给对 象 p2, 运行结 果同上。

2 . 缺省的拷贝构造函数

如果没有编写自定义的拷贝构造函数, C + + 会自动地将一个已存在的对象复制给新对象,这种按成员逐一复制的过程是由缺省拷贝构造函数自动完成的。
说明:

  1. 与一般的构造函数一样, 拷贝构造函数没有返回值
  2. 通常缺省的拷贝构造函数是能够胜任工作的, 但若类中有指针类型时, 按成员复 制的方法有时会产生错误。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值