目录
数据成员多为私有的,要对它们进行初始化,必须用一个公有函数来进行。同时这个函数应该在且仅在定义对象时自动执行一次。称为构造函数。
构造函数用途: 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。