OSX + Xcode + C++ (7)

类的组合

考虑这样一个问题:工人要组装一个台灯,现在有图纸,说明了台灯的零件有灯泡和底座,以及他俩的组合方式。也就是说,工人不需要会制造灯泡和底座,甚至不需要知道这两个零件的工作原理,只需要提供这两个零件,就能制造出一个灯泡。
在c++编程中,也有类似的概念,即类的组合,一个类的对象可以作为另一个类的成员。那么组合类的成员函数可以访问部件对象的私有成员吗?显然是不行,就像工人组装台灯,灯泡供应厂家并不会告诉他灯泡的内部构造,只提供一个螺旋头供他使用,工人也不会把灯泡敲碎去看。
组合类的难点在于构造函数的实现。在对组合类的对象进行初始化时,必然要对部件类的对象进行初始化,因此构造函数的参数列表不仅要包括组合类的成员,还要包括部件类构造函数的参数。那么需要注意以下两点:
1. 如果一个部件类没有默认构造函数,那么组合类的参数列表就必须给出相应的形参。
2. 如果一个部件类只有默认构造函数,那么组合类的参数列表就不能给出相应的形参。
这些限制反过来也要求我们,在定义类时,一定要加上默认构造函数。

1. 组合类的构造函数示例

下面我们通过一个例子,来看一下组合类的构造函数的实现过程。

//
//  main.cpp
//  CombinedClass
//
//  Created by Evelyn on 2018/8/4.
//  Copyright © 2018年 Evelyn. All rights reserved.
//

#include <iostream>
using namespace std;

class Point {
public:
    //default constructor
    Point();
    //normal constructor
    Point(int x, int y);
    //copy constructor
    Point(const Point& p);
private:
    int x, y;

};
class Line {
public:
    //default constructor
    Line();
    //normal constructor
    Line(Point p1, Point p2);
    //copy constructor
    Line(const Line& l);
private:
    Point p1, p2;
};

Point::Point(int newX, int newY) {
    x = newX;
    y = newY;
};
Point::Point():Point(0, 0) {};
Point::Point(const Point& p) {
    x = p.x;
    y = p.y;
    cout << "calling copy constructor of point...\n";
};

Line::Line(Point newP1, Point newP2): p1(newP1), p2(newP2) {
    //CAUTION1
    cout << "calling constructor of line...\n";
};
Line::Line(): Line(Point(0, 0), Point(0, 0)) {};
Line::Line(const Line& l): p1(l.p1), p2(l.p2) {
    //CAUTION2
    cout << "calling copy constructor of line...\n";
};

int main(int argc, const char * argv[]) {
    Point myp1(1, 2), myp2(4, 5);
    Line line(myp1, myp2);
    Line line2(line);

    return 0;
}

首先,如果在xcode中调试,选中project navigator中的项目,右侧会出现设置选项,在build settings的other c++ flags中,debug那一行后添加编译选项-fno-elide-constructors。然后将断点设置在main函数第二行,启动调试。
1. 两个Point对象的声明和初始化都是调用正常的构造函数来实现的。
2. 进入第三行,line对象的构造函数的参数是point对象,我们知道,这时候会默认调用copy constructor把实参传递给形参,通过单步调试,传递的过程是myp1 -> newP1,myp2 -> newP2;在CAUTION1处,要特别注意,这里又是两个copy constructor来定义p1和p2,调试时的传递顺序是newP1 -> p1,newP2 -> p2。这样完成了对象line的定义。
3. 进入第四行,在CAUTION2处,又是两个copy constructor来定义p1和p2。
最终的输出如下:
calling copy constructor of point...
calling copy constructor of point...
calling copy constructor of point...
calling copy constructor of point...
calling constructor of line...
calling copy constructor of point...
calling copy constructor of point...
calling copy constructor of line...

2. 示例修改之CAUTION1

我们将CAUTION1处修改为如下代码:

Line::Line(Point newP1, Point newP2)/*: p1(newP1), p2(newP2)*/ {
    //CAUTION1
    p1 = newP1;
    p2 = newP2;
    cout << "calling constructor of line...\n";
};

调试输出为:
calling copy constructor of point...
calling copy constructor of point...
calling constructor of line...
calling copy constructor of point...
calling copy constructor of point...
calling copy constructor of line...

分析可知,copy constructor和赋值符号“=”来对对象进行定义时,是完全不同的两个构造函数。

3. 示例修改之CAUTION2

如果保持CAUTION1不变,将CAUTION改成委托构造函数:

Line::Line(const Line& l): /*p1(l.p1), p2(l.p2) {*/
Line(l.p1, l.p2) {
    //CAUTION2
    cout << "calling copy constructor of line...\n";
};

这时候我们看到,输出变成了:
calling copy constructor of point...
calling copy constructor of point...
calling copy constructor of point...
calling copy constructor of point...
calling constructor of line...
calling copy constructor of point...
calling copy constructor of point...
calling copy constructor of point...
calling copy constructor of point...
calling constructor of line...
calling copy constructor of line...

可以看到,函数调用的次数明显增加,这在大型程序中也许就是非常大的开销。也许有的时候,编程的便利也会带来性能的损失。

4. 类的相互引用

考虑这样一种情形,在A类中有一个成员函数的参数有B类的对象引用,而在B类中也有一个成员函数的参数有A类的对象引用,那么在定义这两个类时,无论谁在前,谁在后,编译器都会报错,因为他不知道这个对象引用的声明是什么。为了解决这个问题,c++提供了前向引用声明:先声明类的名字,类体在其他地方声明。
但是前向引用声明不是万能的,在一个类的细节定义没有实现之前,是不能使用这个类的细节的,比如说类的对象的声明和定义、类的成员的访问等。

猜你喜欢

转载自blog.csdn.net/qq_42857385/article/details/81417261