1. Component(复合):has-a关系
queue (队列)容器:是一种先进先出的数据结构。
- 队列容器允许从一端新增元素,从另一端移除元素
- 队列中只有队头和队尾才可以被外界使用,因此队列不允许有遍历行为
deque (双端数组)容器:可以在头尾两端进行插入和删除操作。
- 擅长在序列尾部添加或删除元素(时间复杂度为O(1)),而不擅长在序列中间添加或删除元素。
- 可以根据需要修改自身的容量和大小。
- 容器中存储元素并不能保证所有元素都存储到连续的内存空间中。
queue 与deque 就是一种has-a关系,queue 中包含了一个deque ,通过deque 的函数功能来实现queue 的功能。这两个类的关系就叫做复合关系。
其中,这两个类的生命周期一致,如果queue 生命周期结束,deque 的生命周期也随之结束。
template<typename T>
class queue
{
protected:
deque<T> c;
public:
bool empty() const { return c.empty(); }
size_t size() const { return c.size(); }
T front() const { return c.front(); }
T back() const { return c.back(); }
void push(const T& x) { c.push_back(x); }
void pop() { c.pop_front(); }
};
1.1 复合关系下的构造和析构
构造由内而外: Container(queue )的构造函数首先调用Component(deque )的默认构造函数,然后才执行自己。 注:下行注释部分为编译器动作,无需自己处理。
Container::Container(...): {...};
析构由外而内: Container(queue )的析构函数首先执行自己,然后才调用Component(deque )的析构函数。 注:下行注释部分为编译器动作,无需自己处理。
Container::~Container(...) { ... };
2. Delegation(委托):Handle/Body
一个类中有一个指针指向另外一个类,把功能委托给另外一个类去做,这样的方式叫做委托。
String 类中有一个指针指向StringRep 类,将String 类需要做的功能委托给StringRep 类去做。
其中,这两个类的生命周期并不一致。等到需要用到StringRep 类的时候才去创建。
pimpl: String 类本该具有什么样的功能,并不直接在String 类中去设计出来,而只是将String 类当作对外的接口,至于真正的功能实现都在StringRep 类中,当String 需要动作的时候都调用StringRep 类来服务。这个叫做pimpl(pointer to Implementation)。
委托的好处:StringRep 类怎么变动都不会影响到String 类。这种方式也叫编译防火墙,String 永远都不需要再编译,如果更改需求,只需要编译StringRep 类即可。
class StringRep;
class String
{
public:
String();
String(const char* s);
String(const String& s);
String& operator=(const String& s);
~String();
private:
StringRep* rep;
};
class StringRep
{
friend class String;
StringRep(const StringRep& s);
~StringRep();
int count;
char* rep;
};
2.1 引用计数
通过这种委托关系,可以创建类似下图一样的引用计数的模型。创建3个String 类对象,他们共享StringRep 类数据,通过count 可以记录对象引用数据的个数。
当a需要更改数据时,把StringRep 数据单独拷贝一份给a修改,这样可以不影响StringRep 数据。达到节省空间的目的。他们遵循读时共享,写时复制原则。
3. (Inheritance)继承:is-a关系
继承:子类会完整继承下来父类的数据。子类的对象有父类的成分在里面。 继承最有价值的是和虚函数进行搭配。
struct _List_node_base
{
_List_node_base* _M_next;
_List_node_base* _M_prev;
};
template<typename _Tp>
struct _List_node : public _List_node_base
{
_Tp _M_data;
};
结构体_List_node 内不仅仅有自己本身的数据_M_data ,还有父类_List_node_base 的数据_M_next 以及_M_prev 。
3.1 继承关系下的构造与析构
构造由内而外:
Derived(_List_node )的构造函数首先调用Base(_List_node_base )的默认构造函数,然后才执行自己。 注:下行注释部分为编译器动作,无需自己处理。
Derived::Derived(...): {...};
析构由外而内:
Derived(_List_node )的析构函数首先执行自己,然后才调用Base(_List_node_base )的析构函数。 注:下行注释部分为编译器动作,无需自己处理。
Derived::~Derived(...) { ... };
4. 继承+复合关系下的构造和析构
如果组合关系中既有继承又有复合,那么构造和析构的顺序是什么样的? 如下图,Derived既继承Base,又包含了Component。
类在初始化的时候会先构造其父类,所以先调用父类(Base)构造函数,构造完成父类对象后;开始按照声明顺序给类内成员变量分配空间并进行初始化,Component属于子类成员变量,此时就会调用Component的构造函数构造Component类 ;最后调用子类(Derived)构造函数开始构造子类对象。
所以,当创建一个子类对象(Derived)时,
- 构造顺序:Base
→
\rightarrow
→ Component
→
\rightarrow
→ Derived。
- 析构顺序:Derived
→
\rightarrow
→ Component
→
\rightarrow
→ Base。
另外一种情形:Derived继承Base,Base又包含了Component类。
这种情形构造和析构顺序就比较清晰了,Base里包含了Component,所以构造时肯定是先构造Component然后构造Base,最后构造Derived。
所以,当创建一个子类对象(Derived)时,
- 构造顺序:Component
→
\rightarrow
→ Base
→
\rightarrow
→ Derived。
- 析构顺序为Derived
→
\rightarrow
→ Base
→
\rightarrow
→ Component。
|