C++:面向对象编程(续)
2.5构造函数
当我们⽤声明的类创建对象时,如果需要给对象初始化怎么办哪?
1)什么是构造函数?
c++中,有⼀种特殊的成员函数,它的名字和类名相同,没有返回值,⽽在创建对象时会⾃动执⾏,这种特殊的成员函数就是构造函数。类中的数据成员的初始化往往通过构造函数来实现。
构造函数分类:
构造函数按照是否有参数⼜分为:⽆参构造函数和有参构造函数
如果⽤户希望对不同的对象赋予不同的初值。可以采⽤带参数的构造函数。
2)如何给构造函数传参哪?
有参构造,在定义对象时给出实参:类名 实例名(实参1,实参2,...)
构造函数⽆参与有参举例:
-
构造函数不需⽤户调⽤,也不能被⽤户调⽤,在创建对象时会被⾃动调⽤,但是在声明⼀个类的指针对象时,构造函数不会被调⽤,当new⼀个空间的时候,构造函数才会被调⽤。
-
如果⽤户⾃⼰没有定义构造函数,则C++系统会⾃动⽣成⼀个构造函数,只是这个构造函数的函数体是空的,也没有参数,不执⾏初始化操作。
-
在构造函数的函数体中不仅可以对数据成员赋初值,⽽且可以包含其他语句。但是⼀般不提倡在构造函数中加⼊与初始化⽆关的内容,以保持程序的清晰。
-
对于⽆参构造函数,在创建对象是,不可以写成:类名 对象名();
3)参数初始化列表
构造函数的⼀项重要功能是对成员变量进⾏初始化,除了在构造函数的函数体中对成员变量⼀⼀赋值,还可以采⽤初始化列表(初始化列表是声明在构造函数中来实现的)。参数初始化列表的效率相对⽐较⾼,参数多的时候推荐。
成员初始化表的⼀般形式为:
构造函数名([参数表]):数据成员名1(初始值1),数据成员名2(初始值2),……
{
// 构造函数体
}
-
执⾏带参数初始化列表的构造函数时,先执⾏初始化列表 后执⾏构造的函数体代码。
-
初始化的顺序和其在类中声明时的顺序是⼀致的,与列表的先后顺序⽆关。
-
初始化列表可以⽤于全部成员变量,也可以只⽤于部分成员变量。
-
成员初始化列表只能⽤于构造函数
什么时候必须选择参数初始化列表⽅式初始化数据成员?
-
需要初始化const修饰的类成员;
-
需要初始化的数据成员是对象(复合类型)的情况(并且这个对象所属的类没有提供默认的构造函数或者继承时⼦类对象创建时,通过⼦类构造给⽗类构造传参);
-
需要初始化引⽤类型的成员数据。
4)默认构造函数
默认构造函数定义:
调⽤构造函数时不必给出实参的构造函数,称为默认构造函数(defaultconstructor),显然,⽆参的构造函数属于默认构造函数。
⼀个类只能有⼀个默认构造函数。构造函数中的参数还可以提供默认参数,⽐如Point(int x=1,int y=3){x=2,y=3},这种也称为缺省默认构造函数,如果此时在类中再定义⼀个Point(),此时编译器就会报错。因为编译器⽆法匹配。
因此在⼀个类中定义了全部是默认参数的构造函数后,不能再定义重载构造函数。
5)构造函数的重载
在⼀个类中可以定义多个构造函数,以便对类对象提供不同的初始化的⽅法,供⽤户选⽤。这些构造函数具有相同的名字,⽽参数的个数或参数的类型不相同。这称为构造函数的重载。
尽管在⼀个类中可以包含多个构造函数,但是对于每⼀个对象来说,建⽴对象时只执⾏其中⼀个构造函数,并⾮每个构造函数都被执⾏。
对于任意⼀个类A,如果不编写上述函数,C++编译器将⾃动为A 产⽣四个缺省的函数,例如:
A(void);//缺省的⽆参数构造函数
A(const A&a);//缺省的拷⻉构造函数 A a1(a2)或 A a1=a2是触发
~A();//缺省的析构函数
A&operator=(const A &a);//缺省的赋值构造函数,a1=a2是触发
还会增加 取地址运算符重载和取地址运算符const
2.6析构函数
析构函数是⼀个特殊的由⽤户定义的成员函数 ,当该类的对象离开了它的域(对象的⽣⽣命期结束时) 或者 delete表达式应⽤到⼀个该类的对象的指针上时 析构函数会⾃动被调⽤。
析构函数的名字是在类名前加上波浪线 ~ ,它不返回任何值也没有任何参数。尽管我们可以为⼀个类定义多个构造函数 但是我们只能提供⼀个析构函数, 它将被应⽤在类的所有对象上。
例如:~Stu_info(); //在上类定义中增加该函数
析构函数和构造函数⼀样,也是每个类必须的,不定义编译器会⾃动⽣成⼀个缺省的析构函数。
**需要注意的是:**static局部对象在函数调⽤结束时对象并不释放,因此也不调⽤析构函数,只在main函数结束或调⽤exitexit函数结束程序时,才调⽤static局部对象的析构函数。另外就是析构函数⼀般需要设置为公有的,设置私有没什么意义。
匿名对象只存在于构造该对象的那⾏代码,离开构造匿名对象的哪⾏代码后⽴即调⽤析构函数。
匿名对象:cout << stu(“cd” , 33, 44).math << endl;
什么是对象的生存期:
⼀个对象从创建到释放为⽌的时间段称为对象的⽣存期,⼀个对象的⽣存期主要
包括:
1.给对象分配内存空间
2.由构造函数初始化
3.通过调⽤对象的成员函数实现对象的功能
4.由析构函数清理现场(⽐如new申请内容,delete释放)
5.销毁内部对象(释放对象内部创建的⼦对象或数据成员)
6.从内存中清除该对象(系统回收其分配的内存)
2.7对象指针this
This 是 C++ 中的⼀个关键字,也是⼀个 const 指针,它指向当前对象,通过它可以访问当前对象的所有成员。在C++⾥⾯,每⼀个对象都能通过this指针来访问⾃⼰的地址。
this 实际上是成员函数的⼀个形参,在调⽤成员函数时将对象的地址作为实参传递给 this。不过 this 这个形参是隐式的,并不出现在代码中,是在编译阶段由编译器默默地添加到参数列表。this 作为隐式形参,本质上是成员函数的局部变量,所以只能⽤在成员函数的内部,并且只有在通过对象调⽤成员函数时才给 this 赋值。
注意:this关键字只能⽤于成员函数,不能⽤于被static修饰的函数(静态函数)。
this指针的常⻅显式使⽤场景:
this是所有成员函数的隐藏参数,在C++中,当成员函数中某个变量与成员变量名字相同,则使⽤this关键字来表示成员变量。
返回调⽤对象的⾃引⽤。(return this)
class Stu_info
{
public:
int a;
void test(int a)
{
this->a = a;// 区分不同的变量
cout << "类内" << this << endl;
}
};
int main()
{
Stu_info a;
a.test();
cout << "类外" << &a << endl;
return 0;
}
2.8拷贝构造函数
对象之间的赋值可以通过赋值运算符“=”进⾏的。但是只能⽤来对单个的变量赋值,现在被扩展为两个同类对象之间的赋值,这是通过对赋值运算符的重载实现的。实际这个过程是通过成员复制来完成的,即将⼀个对象的成员值⼀⼀复制给另⼀对象的对应成员。
1)对象赋值
C++还提供另⼀种⽅便⽤户的复制形式,⽤赋值号代替括号,
其⼀般形式为:
类名 对象名1 = 对象名2;
如:Box box2=box1; //⽤box1初始化box2
可以在⼀个语句中进⾏多个对象的复制。如:
Box box2=box1,box3=box2;
按box1来复制box2和box3 。
注意:对象名1和对象名2必须属于同⼀个类
2)拷贝构造函数
有时需要⽤到多个完全相同的对象,要将对象在某⼀瞬时的状态保留下来。这就是对象的复制机制。⽤⼀个已有的对象快速地复制出多个完全相同的对象。如 Boxbox2(box1);
其作⽤是⽤已有的对象box1去克隆出⼀个新对象box2。
拷⻉构造函数的⼀般格式: A(const A& a);
例如:Box b1(11,22,33);
Box b2(b1); //当创建实例时传递的是类对象时会被触发拷⻉构造函数
-
这⾥的A为类名,复制构造函数的作⽤就是将实参对象的各成员值⼀⼀赋给新的对象中对应的成员 。
-
拷⻉构造函数也是构造函数,⻓相和构造函数⼀样的,只是参数是固定,拷⻉构造函数唯⼀的参数是对对象引⽤
-
它只有⼀个参数,这个参数是本类的对象(不能是其他类的对象),⽽且采⽤对象的引⽤的形式(⼀般约定加const声明,使参数值不能改变,以免在调⽤此函数时因不慎⽽使对象值被修改) 。
-
如果⽤户⾃⼰未定义复制构造函数,则编译系统会⾃动提供⼀个默认的复制构造函数,其作⽤只是简单地复制类中每个数据成员。(要实现更复杂操作需要⾃⼰定义拷⻉构造函数)
拷⻉构造定义举例:
// The copy constructor definition. box是类名
Box::Box(const Box&b)
{
height = b.height;
width = b.width;
length = b.length;
}
普通构造函数与复制构造函数的区别:
1)形式上不⼀样:
类名(形参表列); //普通构造函数的声明,如Box(int h,int w,intlen);
类名(类名& 对象名); //复制构造函数的声明,如Box(Box &b);
2)在建⽴对象时,实参类型不同。系统会根据实参的类型决定调⽤普通构造函数或复制构造函数 。
3)什么情况下被调⽤
普通构造函数在程序中建⽴对象时被调⽤。复制构造函数在⽤已有对象复制⼀个新对象时被调⽤ 。
以下3种情况下需要克隆对象:
-
程序中需要新建⽴⼀个对象,并⽤另⼀个同类的对象对它初始化。
-
函数的参数为类的对象时。在调⽤函数时需要将实参对象完整地传递给形参,也就是需要建⽴⼀个实参的拷⻉,这就是按实参复制⼀个形参,系统是通过调⽤复制构造函数来实现的,这样能保证形参具有和实参完全相同的 。
-
函数的返回值是类的对象。在函数调⽤完毕将返回值带回函数调⽤处时。此时需要将函数中的对象复制⼀个临时对象并传给该函数的调⽤处 。