构造函数、复制构造函数、类型转换构造函数、析构函数

构造函数

成员函数的一种,名字与类名相同,可以有参数,不能有返回值(void也不行)。
一个类可以有多个构造函数。
如果定义类时没写构造函数,则编译器生成一个默认的无参构造函数,这个构造函数不做任何操作。如果定义了构造函数,则编译器不生成默认的无参构造函数。
对象生成时构造函数自动被调用,对象一旦生成,就再也不能在其上执行构造函数。对象占用的存储空间不是构造函数分配的,构造函数是在对象已经占用的存储空间上做一些初始化操作。只要对象生成,构造函数就会自动调用,不管这个对象是如何生成的。
例如:

class Complex{
    private:
        double real,imag;
    public:
        Complex(double r,double i=0);
};
Complex::Complex(double r,double i){
    real=r;imag=i;
}
Complex c1;//error,缺少构造函数的参数
Complex *pc=new Complex;//error,没有参数
Complex c1(2);
Complex c1(2,4),c2(3,5);
Complex *pc=new Complex(3,4);

构造函数最好是public的,private构造函数不能直接用来初始化对象。例如,

class CSample{
    private:
        CSample(){}
};
int main(){
    CSample Obj;//err.唯一的构造函数是private
    return 0;
}

构造函数在数组中的使用:

class CSample{
    int x;
public:
    CSample(){cout<<"Constructor 1 called"<<endl;}
    CSample(int n){
        x=n;
        cout<<"Constructor 2 called"<<endl;
    }
};
int main(){
    CSample array1[2];//数组的两个元素都调用无参构造函数
    cout<<"step1"<<endl;
    CSample array2[2]={4,5};//数组的两个元素都调用有参构造函数
    cout<<"step2"<<endl;
    CSample array3[2]={3};//第一个元素调用2构造函数,第二个调用1构造函数
    cout<<"step3"<<endl;
    CSample * array4=new CSample[2];//数组的两个元素都调用无参构造函数
    delete []array4;
    return 0;
}

函数的输出为:
Constructor 1 called
Constructor 1 called
step1
Constructor 2 called
Constructor 2 called
step2
Constructor 2 called
Constructor 1 called
step3
Constructor 1 called
Constructor 1 called
class Test{
    public:
        Test(int n){}      //(1)
        Test(int n,int m){}//(2)
        Test(){}           //(3)
};
Test array1[3]={1,Test(1,2)};
//数组有三个元素但是只给出了两个初始值,因此三个元素分别用(1)(2)(3)初始化
Test array2[3]={Test(2,3),Test(1,2),1};
//三个元素分别用(2)(2)(1)初始化
Test *pArray[3]={new Test(4),new Test(1,2)};
//pArray是指针数组,不是对象数组,它的三个元素都是指针分别指向某块区域。如果只写Test *pArray[3]而不写等号后面的部分,则没有对象生成,也不会引发Test构造函数的调用。这里只初始化了数组的前两个元素,因此只有两个对象生成,new Test(4)的返回类型是Test*,赋值给数组的第一个元素,也就是把新构造的对象的地址赋值给指针数组的第一个元素。这里两个元素分别调用(1)(2)初始化。

复制构造函数(拷贝构造函数)

只有一个参数,即同类对象的引用,形如X::X(X&)或X::X(const X &),只能是对象的引用,不能是对象,不允许有形如X::X(X)的复制构造函数。
如果没有定义复制构造函数,那么编译器生成默认复制构造函数,默认的复制构造函数完成复制功能。如果自己定义了复制构造函数,则默认的复制构造函数不存在。
复制构造函数起作用的三种情况:
1)当用一个对象去初始化同类的另一个对象时

Complex c2(c1);
Complex c2 = c1;//初始化语句,非赋值语句,调用复制构造函数
Complex c1,c2;
c2 = c1;//赋值语句,不会调用复制构造函数,复制构造函数只有在初始化时才会调用

2)如果某函数的一个参数是类A的对象,那么该函数被调用时,类A的复制构造函数将被调用来初始化形参

class A{
public:
    A(){};
    A(A &a){cout<<"Copy constructor called"<<endl;}
};
void Func(A a1){}
int main(){
    A a2;
    Func(a2);//形参a1被复制构造函数初始化,调用复制构造函数的参数是实参a2,这里a1和a2的值是否相等取决于复制构造函数怎么写,上面定义的复制构造函数只是输出了一个语句,并没有实现复制功能,因此这里的a1和a2不一定相等
    return 0;
}
程序输出结果:Copy constructor called

3)如果函数的返回值是类A的对象时,则函数返回时,A的复制构造函数被调用

class A{
public:
    int v;
    A(int n){v = n;}
    A(const A &a){
        v = a.v;
        cout<<"Copy constructor called"<<endl;
    }
};
A Func(){
    A b(4);
    return b;
}//返回值对象被初始化时调用复制构造函数,其参数是b,这里返回值对象A的值是否等于b取决于复制构造函数怎么写
int main(){
    cout<<Func().v<<endl;
    return 0;
}
输出结果:
Copy constructor called
4

类型转换构造函数

实现类型的自动转换,不是复制构造函数,只有一个参数。
编译系统会自动调用类型转换构造函数,建立一个临时对象/临时变量。

class Complex{
public:
    double real,imag;
    Complex(int i){//类型转换构造函数
        cout<<"IntConstructor called"<<endl;
        real = i;imag = 0;
    }
    Complex(double r,double i){
        real = r;imag = i;
    }
};
int main(){
    Complex c1(7,8);
    Complex c2 = 12;//初始化语句,会调用类型转换构造函数以12为参数进行初始化,但是不会生成临时对象
    c1 = 9;//9被自动转换成一个临时Complex对象
    cout<<c1.real<<","<<c1.imag<<endl;
    return 0;
}
输出:
IntConstructor called
IntConstructor called
9,0

析构函数

名字与类名相同,在前面加‘~’,没有参数和返回值,一个类最多只有一个析构函数。
对象消亡时,析构函数自动被调用。
定义类时没写析构函数,则编译器生成缺省析构函数;定义了析构函数,则编译器不生成缺省析构函数。
析构函数不涉及释放用户申请的内存释放等工作,如果用户自己申请了内存空间,那么用户必须自己释放,不然程序不会自动释放用户自己申请的内存空间。也就是说,程序里面写了new就必须有对应的delete,否则程序结束时不会自动析构用户new的这块空间。
析构函数和数组:对象数组生命期结束时,对象数组的每个元素的析构函数都会被调用。
析构函数和运算符delete:delete运算导致析构函数调用。

Ctest *pTest;
pTest = new Ctest[3];//构造函数调用3次
delete []pTest;//析构函数调用3次

构造函数和析构函数的调用时机

class Demo{
    int id;
public:
    Demo(int i)
    {
        id = i;
        cout<<"id="<<id<<"Constructed"<<endl;
    }
    ~Demo()
    {
        cout<<"id="<<id<<"Destructed"<<endl;
    }
};
Demo d1(1);
void Func(){
    static Demo d2(2);
    Demo d3(3);
    cout<<"Func"<<endl;
}
int main(){
    Demo d4(4);
    d4 = 6;
    cout<<"main"<<endl;
    {Demo d5(5);}
    Func();
    cout<<"main ends"<<endl;
    return 0;
}

输出:
id=1 Constructed
id=4 Constructed
id=6 Constructed
id=6 Destructed
main
id=5 Constructed
id=5 Destructed
id=2 Constructed
id=3 Constructed
Func
id=3 Destructed
main ends
id=6 Destructed
id=2 Destructed
id=1 Destructed
注:
1.d4=6是类型转换,会生成临时对象,因此有对临时对象生成的构造函数和析构函数的调用。
2.{Demo d5(5);} 离对象最近的花括号是对象的作用域,作用域标志着对象的生命周期,在对象的生命周期结束时要被析构。
3.Func()函数内部定义了静态变量d2和普通变量d3,它们的作用域是这个函数内部,但是静态变量是在整个程序结束时析构,因此当Func()结束时只析构变量d3。
4.d4是在main函数内部的局部变量,所以main函数结束时d4析构,d1是全局变量要在整个程序结束时析构,d2是静态变量也要在整个程序结束时析构,先创建的后析构。

  • 1
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值