1.类的组合
类的组合描述的就是一个类内嵌其它类的对象作为成员的情况,它们之间的关系是一种包含与被包含的关系。
当创建类的对象时,如果这个类具有内嵌对象成员,那么各个内嵌对象将首先被自动创建。因为部件对象是复杂对象的一部分。因此,在创建对象时既要对本类的基本类型数据成员进行初始化,又要对内嵌对象成员进行初始化。
组合类构造函数定义的一般形式是
类名::类名(形参表):内嵌对象1(形参表),内嵌对象2(形参表)…
{
初始化
}
1.调用内嵌对象的构造函数,调用顺序按照内嵌对象在组合类的声明中出现的次序。
2.执行本类构造函数的函数体。
如果声明组合类的对象时没有指定对象的初始值,则默认形式(无形参)的构造函数被调用,这时内嵌对象的默认形式构造函数也被调用。析构函数的调用执行顺序与构造函数刚好相反。
当存在类的组合关系时,拷贝构造函数该如何编写呢?对一个类,如果没有编写拷贝构造函数,编译系统会在必要时自动生成一个默认的拷贝构造函数。若建立组合类的对象时调用默认拷贝构造函数,则编译器将自动调用内嵌对象的拷贝构造函数。
如果要为组合类编写拷贝构造函数,则需要为内嵌成员对象的拷贝构造函数传递参数。例如
,假设C类中包含B类的对象b作为成员,C类的拷贝构造函数形式如下:
C::C(C &c1):b(c1.b)
{}
2.拷贝构造函数
普通构造函数在对象创建时被调用,而拷贝构造函数在以下三种情况下会被调用:
1.当用类的一个对象去初始化该类的另一个对象时
int main(void)
{
CPoint P1(0,0);
CPoint P2(P1);//用对象P1去初始化对象P2,拷贝构造函数被调用
return 0;
}
2.如果函数的形参是类的对象,调用函数时,进行形参和实参结合时。例如
void f(CPoint p)
{
cout<<p.getx()<<endl;
}
void main()
{
CPoint p(1,2);
f(p);//函数的形参为类的对象,当调用函数时,拷贝构造函数被调用
}
3.如果函数的返回值是类的对象,函数执行完成返回调用者时。
CPoint g()
{
CPoint p(1,2);
return p;
}
void main()
{
CPoint A;
A=g();
}
函数g将p返回了主函数,但是A是g()的局部对象,离开建立它的函数g以后就消亡了,不可能在返回主函数后继续生存。所以在处理这种情况时编译系统会在主函数中创建一个临时的无名对象,该临时对象的生存周期只在函数调用所处的表达式中,也就是A=g()中。执行语句“return p;”时,实际上是调用拷贝构造函数将p的值拷贝到临时对象中。函数g运行结束时对象A消失,但临时对象会存在于表达式A=g()中。计算完这个表达式,临时对象的使命也就完成了,该临时对象便自动消失。
3.形参带默认参数
例子
#include <iostream>
using namespace std;
int sub(int x=8,int y=3){
return x+y;
}
int main(){
//freopen("D:\\input.in","r",stdin);
//freopen("D:\\output.out","w",stdout);
cout<<sub(20,15)<<endl;//35
cout<<sub(10)<<endl;//13
cout<<sub()<<endl;//11
return 0;
}
1.若函数具有多个形参,则默认形参值必须自右向左连续地定义,并且在一个默认形参值的右边不能有未指定默认值的参数。这是由于c++语言在函数调用时参数是自右向左入栈这一约定决定的。
eg:int f(int a, float b=5.0, char c=‘c’);
2.在调用一个函数时,如果省去了某个实参,则直到最右端的所有实参都得省去(当然,与其对应的形参要有默认值)。
eg:int f(int a, float b=5.0, char c=‘c’, int d=10); f(9,4.5) <=> f(9,4.5,‘c’,10).
3.默认形参值的说明必须出现在函数调用之前。而且,如果存在函数原型,则形参的默认值应在函数原型中指定;否则在函数定义中指定。另外,若函数原型中已给出了形参的默认值,则在函数定义中不得重复指定,即使所指定的默认值完全相同也不行。
#include <iostream>
using namespace std;
int sub(int x=8,int y=3);//函数原型中已经给出了形参的默认值
int main(){
//freopen("D:\\input.in","r",stdin);
/freopen("D:\\output.out","w",stdout);
cout<<sub(20,15)<<endl;//35
cout<<sub(10)<<endl;//13
cout<<sub()<<endl;//11
return 0;
}
int sub(int x,int y)//在函数的定义中不能再指定
{
return x+y;
}
4.在同一个作用域,一旦定义了默认形参值,就不能再定义它。
5.如果几个函数说明出现在不同的作用域内,则允许分别为它们提供不同的默认形参值。
#include <iostream>
using namespace std;
int sub(int x=8,int y=3);
int main(){
//freopen("D:\\input.in","r",stdin);
//freopen("D:\\output.out","w",stdout);
int sub(int x=0,int y=0);
cout<<sub()<<endl;
return 0;
}
6.对形参默认值的指定可以是初始化表达式,甚至可以包含函数调用。
eg:int f(int a, float b=5.0, char c=‘c’, int d=sub(20,15));
7.在函数原型给出了形参的默认值时,形参名可以省略。
eg:int f(int, float=5.0, char=‘c’, int=sub(20,15));
4.结合拷贝函数,函数形参为默认值的情况来讨论下类的组合下的对象初始化
先给出一个完整的例子:
定义一个Point类
#ifndef MYPOINT_H
#define MYPOINT_H
#include<iostream>
#include<cmath>
using namespace std;
class Point
{
public:
Point(int xx,int yy=1)
{
cout<<"这是点的带参构造函数"<<endl;
}
Point()
{
cout<<"这是点的无参构造函数"<<endl;
}
Point(Point &p)
{
cout<<"这是点的拷贝构造函数"<<endl;
}
private:
int X,Y;
};
#endif
定义一个line类
line的头文件
#include<iostream>
#include<cmath>
#include"Point.h"
using namespace std;
class Line
{
public:
Line(Point xp1,Point xp2);
Line(Line &);
Line();
private:
Point p1,p2;
};
line的CPP文件
#include"stdafx.h"
#include"Line.h"
using namespace std;
Line::Line(Point xp1,Point xp2)//:p1(xp1),p2(xp2)
{
cout<<"这是线的第一个构造函数"<<endl;
}
Line::Line()
{
cout<<"这是线的第三个构造函数"<<endl;
}
主函数
#include "stdafx.h"
#include "stdlib.h"
#include"Line.h"
#include"Point.h"
int _tmain(int argc, _TCHAR* argv[])
{
Point p1(0,0);
Point p2(1,1);
Line line(p1,p2);
system("pause");
return 0;
}
结果:
p1(0,0) ,p2(1,1)对象都是带参数的,
line(p1,p2)中p1 p2属于函数的形参是类的对象的情况,调用了拷贝构造函数,又line中的p1 p2没有初始化,故调用了默认的构造函数,在成员变量初始化完成后,再动用line自身的构造函数,很明显是带参的构造函数
line的CPP文件改为
#include"stdafx.h"
#include"Line.h"
using namespace std;
Line::Line(Point xp1,Point xp2):p1(xp1),p2(xp2)
{
cout<<"这是线的第一个构造函数"<<endl;
}
Line::Line()
{
cout<<"这是线的第三个构造函数"<<endl;
}
结果:
line中的p1 p2被显示的初始化,故调用的都是拷贝函数
5.容易出错的地方
带参数默认值的构造函数Point(int xx=1,int yy=1);因为两个形参都是带默认值的,在参数都省略的情况下,和Point()会引起定义上的重复。Line(Point xp1,Point xp2)就无法判断是哪个函数的初始化了。