C++构造和析构函数

目录

7.1构造函数的定义和使用:

7.2构造函数的特征:

7.3初始化列表:

7.4初始化列表和函数体内进行赋值的区别

7.5析构函数的定义

7.6析构函数的特点

7.7是否含有this指针

7.8构造函数的类型转换


数据成员多为私有的,要对它们进行初始化,必须用一个公有函数来进行。同时这个函数应该在且仅在定义对象时自动执行一次。称为构造函数。

构造函数用途: 1.创建对象、2. 初始化对象中的属性、3. 类型转换

构造函数无法为对象申请或者创建空间,只能在系统已经申请的空间中创建和初始化对象。

class CDate
{
	int year;
	int month;
	int day;
public:
	CDate(int y= 1, int m= 1, int d= 1):year(y),month(m),day(d)
    {}
	~CDate()
	{
		cout << "Destroy CDate" << endl;
	}
};
int main()
{
    CDate dt1;
    return 0;
}

先是系统进行空间的申请,然后再调用构造函数在申请的空间处去构建对象,即此时this指针指向dt1的地址,对dt1的属性进行初始化。

7.1构造函数的定义和使用:

构造函数是特殊的公用成员函数(在特殊用途中可以定义为私有或者保护)

class Int
{
private:
    int value;
};
int main()
{
    Int it={100};//error,在没有定义构造函数时,只有当成员数据为公有时,才能这样初始化,否则就需要构造函数
}

7.2构造函数的特征:

1.函数名与类名相同。

2.构造函数无函数返回类型说明。注意是没有而不是void,即什么也不写,也不可写void。实际上构造函数有返回值,返回的就是构造函数所创建的对象。

3.在程序运行时,当新的对象被建立,该对象所属的类构造函数自动被调用,在该对象生存期中也只调用这一次。但可以通过定位new对已创建的对象进行重新创建:new(对象的地址) 构造函数

4.构造函数可以重载。严格地讲,类中可以定义多个构造函数,它们由不同的参数表区分,系统在自动调用时按一般函数重载的规则选一个执行。

5.构造函数可以在类中定义,也可以在类中声明,在类外定义。

6.如果类说明中没有给出构造函数,则C++编译器自动给出一个缺省的构造函数:类名(void){ }

但只要我们定义了一个构造函数,系统就不会自动生成缺省的构造函数。只要构造函数是无参的或者只要各参数均有缺省值的,C++编译器都认为是缺省的构造函数,并且缺省的构造函数只能有一个。

class Complex
{
private:
    int real;
    int image;
public:
    Complex()//缺省构造函数
    {
        real=0;
        image=0;
    }
    Complex(int r)
    {
        real=r;
        image=0;
    }
    Complex(int r,int i)
    {
        real=r;
        image=i;
    }
    /*Complex(int r = 0, int i = 0)//缺省构造函数,与上面的缺省构造函数不能同时存在,否则会出现二义性
    {
        real = r;
        image = i;
    }*/
};
Complex fun()
{
    return Complex();//返回构造函数创建的无名对象
}
int main()
{
    Complex x1;//ok,调用无参的构造函数
    Complex x2(1);//ok
    Complex x3(1,2);//ok
    Complex x4();//error,因为编译器会将其看成函数的声明
    //所以为了防止这种情况发生,使用统一的初始化方式:
    Complex x5{};//ok,调用无参的构造函数
    //产生一个无名对象
    Complex{};
    Complex();
    //区别:第一个是定义一个无名对象,再调用无参构造函数进行初始化;第二个是直接调用无参构造函数返回一个无名对象
    return 0;
}


//当类中的成员数据全部为公有,自己也没有给出构造函数时,系统会给出带参数构造函数,以至于可以使用变量直接初始化对象。
class Complex
{
public:
    int real;
    int image;
};
int main()
{
    Complex c1(1,2);//ok
    Complex c2={2,3};//ok
    return 0;
}
//但若有一个数据成员为私有,则不会给出这种特殊的构造函数,即带参数的构造函数
class Complex
{
public:
    int real;
private:
    int image;
};
int main()
{
    Complex c1(1,2);//error
    Complex c2={2,3};//error
    return 0;
}

//对象不可以自己调用构造函数重新构建自己,但可以通过定位new再次调用构造函数
class Complex
{
private:
    int real;
    int image;
public:
    Complex(int r=0,int i=0)
    {
        real=r;
        image=i;
    }
};
int main()
{
    Complex c1;//ok
    Complex c2{10,20};//ok
    c1.Complex(12,23);//error,对象不能自己调用构造函数
    //如果想要完成重新构造可以使用定位new
    new(&c1) Complex(12,23);//ok
    return 0;
}

对象不可以调用构造函数的原因:构造函数没有返回值,实际是返回构建对象的地址;构造函数是由系统进行调用,任何一个对象只能被构建一次。

7.3初始化列表:

(只能出现在构造函数和拷贝构造函数中)

注意:

成员的初始化顺序与成员的声明顺序相同

成员的初始化顺序与初始化列表中的位置无关

初始化列表先于构造函数的函数体执行

class Complex
{
private:
    int real;
    int image;
public:
    Complex(int r=0,int i=0):real(r),image(i)
    {}
};

成员数据初始化的顺序与初始化列表顺序无关,与成员数据在类中声明的顺序有关。

//初始化列表的顺序不意味着是成员数据初始化的顺序
class Complex
{
private:
    int real;
    int image;
public:
    Complex(int x):image(x),real(image)
    {}
    void Print()
    {
        cout<<"real:"<<real<<" image:"<<image<<endl;
    }
};
int main()
{
    Complex c(10);
    c.Print();//real:-858993460 image:10
    return 0;
}

成员数据的使用函数体初始化初始化时,按照顺序进行初始化,且初始化列表先于函数体执行

class Complex
{
private:
    int real;
    int image;
public:
    Complex(int x):image(x)
    {
        real=image;
    }
    /*Complex(int x)
    {
        image=x;
        real=image;
    }*/
    void Print()
    {
        cout<<"real:"<<real<<" image:"<<image<<endl;
    }
};

以上两种初始化的方式结果均相同:

class Complex
{
private:
    int real;
    int image;
public:
    Complex(int r=0,int i=0):real(r),image(i)
    {
        cout<<"create Complex"<<this<<endl;
    }
};
int main()
{
    Complex ca;
    Complex cb(1,2);
    Complex cc=Complex{2,3};//ok,只构建一次对象,即直接构建cc对象
    return 0;
}

结果:

构建cc对象时,直接将cc所在空间的地址传给构造函数,及此时this指针指向cc的地址空间。

7.4初始化列表和函数体内进行赋值的区别

对于成员数据全为内置类型时,两种方式没有区别。但如果成员数据中有自己设计的类时,以初始化列表方式进行初始化时,只调用该对象的拷贝构造函数,而在函数体内时会先调用其缺省构造函数,再调用其赋值运算符重载函数。前者调用函数更少,空间和时间效率更高。

class Int
{
private:
	int value;
public:
	Int(int x=0):value(x)
	{
		cout << "Int create" << this << endl;
	}
	Int(const Int& it) :value(it.value) 
	{
		cout << "Int copy create " << this << endl;
	}
	Int& operator=(const Int& it)
	{
		if (this != &it)
		{
			value = it.value;
		}
		cout << this << "operator=" << &it << endl;
		return *this;
	}
	~Int()
	{
		cout << "Int destroy" << this << endl;
	}

};
class Obj
{
private:
	Int it;
public:
	Obj()
	{
		cout << "Obj create" << this << endl;
	}
	~Obj()
	{
		cout << "Obj destroy" << this << endl;
	}
};
int main()
{
	Obj ob1;
	return 0;
}

结果:

先去构建ob1对象中的成员属性it对象,再构建ob1对象自身,对应析构时是相反的,因为入栈和出栈相反。

7.5析构函数的定义

当定义一个对象时,C++自动调用构造函数建立该对象并进行初始化,那么当一个对象的生命周期结束时,C++也会自动调用一个函数注销该对象并进行善后工作,这个特殊的成员函数即析构函数:

1.构函数名与类名相同,但在前面加上字符~,如 : ~CGoods ()。

2.析构函数无函数返回类型,与构造函数在这方面是一样的。但析构函数不带任何参数。

3.一个类有一个也只有一个析构函数,这与构造函数不同。

4.对象注销时,系统自动调用析构函数。

5.如果类说明中没有给出析构函数,则C++编译器自动给出一个缺省的析构函数。如: ~类型名称()

7.6析构函数的特点

析构是将这个对象在外部申请的堆区,打开的文件等资源进行释放,并不是将对象本身所在空间进行释放。

实际上析构函数只是执行其函数体,而程序员需要做的就是在函数体中编写代码,将这个对象在外部所申请的资源等进行释放。

对象可以自己调用析构函数,但无法调用构造函数。

7.7是否含有this指针

构造函数和析构函数都有this指针,构建和析构的对象时一定要获取地址空间。

7.8构造函数的类型转换

class Int
{
 	int value;
public:
 	Int(int x=0) :value(x)
    { 
        cout << "Create Int Object " << this << endl; 
    }
 	Int(const Int& it) :value(it.value) 
 	{ 
     	cout << "Copy Create Int Object" << this << endl; 
     }
 	Int& operator=(const Int& it)
 	{
  		if (this != &it)
  		{
   			value = it.value;
  		}
  		return *this;
 	}
 	~Int() 
 	{ 
     	cout << "Destroy Int " << this << endl;
    }
 	void PrintInt() const 
 	{ 
     	cout << value << endl; 
 	}
    Int operator+(const Int& x)const
    {
        return Int(value+x.value);
    }
    Int operator+(const int x)const
    {
		return Int(value+x);
    }
};
int main()
{
    Int a(10);
    int x=100;
    a=x;//类型转换,即将x强转为Int类型,调用构造函数创建一个不具名对象(将亡值),调用赋值运算符重载函数将该将亡值对象赋值给a对象
    double dx=12.23;
    a=dx;//此时也可以进行类型转换,先将dx转成Int类型,在调用构造函数创建将亡值对象时,将dx要赋值给value,此时默认将bouble转成int,之后与上面相同。 
    return 0;
}

 上面的这种转换属于隐式的类型转换,当给构造函数的前面加上关键字explicit,此时就不能通过赋值构造对象,不能将int类型隐式的转换成Int类型。

explicit Int(int x=0) :value(x)
{ 
    cout << "Create Int Object " << this << endl; 
}
int main()
{
    Int a=10;//error
    int x=10;
    a=x;//error
    a=(Int)x;//ok
    return 0;
}

构造函数能进行隐式转换的条件是:该构造函数必须是单参的(即要么只有一个参数,要么只有一个参数不带默认值,其他参数均有默认值)

Int(int x,int y) :value(x+y)
{ 
    cout << "Create Int Object " << this << endl; 
}
int main()
{
    Int a(1,2);//ok
    int x=10,y=20;
    a=(x,y);//error
    a.PrintInt();
    return 0;
}
//当给y加上默认值就具有隐式转换的能力
Int(int x,int y=0) :value(x+y)
{ 
    cout << "Create Int Object " << this << endl; 
}
int main()
{
    Int a(1,2);//ok
    int x=10,y=20;
    a.PrintInt();//值为3
    a=(x,y);//ok
    //相当于下面的代码
    a=(Int)(x,y);//通过强转来调动构造函数
    a.PrintInt();//此时的值为20
    //和如下代码有区别
    a=Int(x,y);//通过构造匿名对象调动构造函数
    a.PrintInt();//此时的值为30
    return 0;
}

首先a=(x,y);能编译通过就说明此时构造函数的参数只接收一个值,因为只有单参才能进行隐式转换,所以构造函数在接收时,只有x接收值,也就是将(x,y)的值赋值给参数x,而(x,y)是一个逗号表达式,逗号表达式从左往右依次计算表达式1和表达式2,最后将最右边的表达式的值进行赋值,所以在赋值时是将y的值赋值给参数x,即20。最后构造函数中x的值为20,y的值为0,结果为20。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值