C++面向对象编程Part I

C++面向对象编程 (C++Object-Oriented Programming) Part I

注:此篇文章是根据侯捷老师的课程所做的笔记,侯捷老师的课程真的让人受益匪浅。一些文字说明仅是个人观点,能力不足难免有误,诚恳大家指正,你的指正是对我莫大的帮助。

目录索引

 Bibliography(书目)

推荐书籍:
 《C++ Primer》
 《The C++ Programming Language》
 《Effective C++》
 《The C++ Standard Library》
 《STL 源码剖析》

 Data and Functions, from C to C++(C和C++中的数据与函数)

呃呃呃
 在C中,创建变量为全局变量,在任何函数中均可用,这样会对之后的程序引起不必要的麻烦。
 在C++中,通过类创建对象,这样再使用变量时可实现局部隐藏,在需要的时候用。
两个概念:
基于对象(Object Based):面对的是单一C++ class的设计。
面向对象(Object Oriented):面对的是多重classes的设计,注重classes和classes之间的关系

 Basic forms of C++ programs(C++代码基本形式)

在这里插入图片描述
 main函数在单独的.cpp文件中,它包含标准库头文件(用<>括起来)和自己定义的头文件(用“”括起来)。
 延伸文件名不一定是.h或.cpp,也可能是.hpp或其他或无延伸名。

 About output(输出)

在这里插入图片描述
 关于C和C++的输出,C++需要包含头文件#include<iostream.h>(或#include),使用输出流cout对对象进行输出;C需要包含头文件#include<stdio.h>(或#include,在C++中包含C标准库文件时,可以直接包含.h文件,或者去掉后缀.h在前面加c),使用printf进行输出。

 Guard declarations of header files(头文件防卫式声明)

在这里插入图片描述
 防卫式声明的意思是#ifndef COMPLEX(如果没有定义__COMPLEX__),#define COMPLEX(那就定义__COMPLEX__),#endif(定义结束);如果定义了__COMPLEX__,那么头文件接下来的内容将略过。
 建议写任何一个头文件加上防卫式声明,其目的是防止头文件重复包含。

 Layout of headers(头文件的布局)

在这里插入图片描述
 头文件的布局包含0前置声明、1类声明和2类定义,这些需要按顺序在头文件中编写。

 Object Based(基于对象)

基于对象(Object Based):面对的是单一C++ class的设计。

 Class without pointer member(不带有指针成员的类)

 以复数类complex为例,不带有指针成员变量的类一般不需要重写析构函数。

  Class declarations(类声明)

在这里插入图片描述
 类的声明包含成员函数、成员变量和友元函数等,有些函数可以在类内定义,有些函数也可以在类外定义。

  Class template, introductions and overviews(类模板)

在这里插入图片描述
 类模板的将类的数据类型不先写死,即编写时什么类型先不决定,使用时指定数据类型。在定义对象时指定数据类型,如complex<double> c1(2.5, 1.5);complex<int> c2(2, 16);…

  What is ‘this’(“this”指针)

 所有非静态成员函数都有隐藏的参数this,this取决于调用者,谁调用函数谁是this。

  Inline functions(内联函数)

在这里插入图片描述
 在类内部直接定义的函数,或者在类外部定义,但在函数名称前面加inline关键字的函数被称为内联函数。
 编译器会将内联函数直接解析成一行语句,不需要额外开辟栈空间,这样会使得执行效率高,内联函数只是对编译器建议,具体的执行是要编译器根据函数的复杂程度。

  Access levels(访问级别)

在这里插入图片描述
 C++类的访问级别有三种:public、protect、private,类内默认的访问、继承的访问级别是private(struct为public)。
 public允许类外访问,private和protect只允许类内访问。对象c1可以访问real()、imag()函数,但不可以访问re、im数据成员。
 建议:数据成员设为私有属性(private),成员函数根据是否需要外界调用,设为公有属性(public)或私有属性(private)。

  Constructor (ctor)(构造函数)

在这里插入图片描述
构造函数的特点:
 构造函数在创建对象时自动调用,在类内其他成员函数中不可以调用构造函数;
 构造函数与类名相同;
 构造函数可有默认参数,默认参数可以有默认值;
 构造函数无返回类型,不需要有,因为构造函数只用来创建对象,对象的类型就是该类的类型。

构造函数的初始化列表:
 构造函数的参数可以在构造函数定义后面直接进行初始化,也可以在函数体内进行赋值,两者的区别在于变量的初始化和赋值,即变量在哪个阶段被赋值。变量在定义的时候可以直接初始化赋值,也可以在定义之后进行赋值操作,初始化一步完成,赋值两步完成,显然初始化效率较高。
 初始化列表时构造函数特有的语法,应予以充分利用。
 初始化列表只能对本类定义的成员变量初始化,不可以对其继承的父类或其他类内的成员变量初始化。
在这里插入图片描述
 构造函数可以发生重载,编译器会根据函数名、返回值类型、参数列表等进行函数重命名,在我们看来名字一样的函数,在编译器内其实并不一样。
 图中黄色背景的函数重载并不允许,因为当定义对象时complex c1,编译器并不知道要调用哪个构造函数。
在这里插入图片描述
 构造函数放在private区,外部不能直接调用.。在这里插入图片描述
 在单例模式中,构造函数被放在private下,类中包含静态函数,在静态函数中创建类对象,由于静态函数不属于类对象,只从属于该类,这样就只允许该类外界只有一份。

  Const member functions(常量成员函数)

在这里插入图片描述
 函数可以分为两种类型:一种是会改变数据内容,另一种是不会改变数据内容。对于不会改变数据内容的函数,在函数后面一定要加const,这样会确保安全。
 上图中,若函数后面没有加const,表明函数内部可能会改变数据内容,在定义对象时,调用者不想改变数据内容,在调用对象时加const关键字,即**const complex c1(2, 1);**这样会出错。

  Parameters : pass by value vs. pass by reference(值传递与引用传递)

在这里插入图片描述
值传递是将整个值打包复制到函数内部,函数内部对值的修改是对复制后的值进行修改,与原来的值无关,所以原来的值不会发生改变。由于值传递是将整个值打包传递,当值所占内存大的时候,值传递效率会变慢。
引用传递与指针传递类似,传递大小为4字节,传递速度相对较快,引用传递在函数内部修改值会直接修改原值。
参数传递尽量传引用,若参数不希望被修改,可在引用前面加const。

  Return values : return by value vs. return by reference(返回值与返回引用)

在这里插入图片描述
 视情况而定,尽量返回引用。

  Friend(友元)

在这里插入图片描述
 类外的函数或类想要访问类内部的私有成员,可以在类内部声明一个友元函数,在声明前加friend关键字,友元函数即能访问类的私有成员。
在这里插入图片描述
 相同class的各个对象互为友元。如上图func函数内,类对象param可以调用私有成员变量。

  Definitions outside class body(类外的各种定义)

在这里插入图片描述
什么时候可以传引用:传递引用的范围较广,可以参考上面引用传递的描述。
什么时候可以返回引用:能否返回引用在于返回的对象在函数体之前是否存在,若存在可返回引用,若不存在,即是函数体内临时定义的,那么函数体执行结束,对象就会被销毁,返回的引用是个被销毁的地址,所以该情况下不可返回引用。

  Operator overloading, as member function(成员函数的操作符重载)

在这里插入图片描述
 操作符重载目的是完成两个类之间按照某种约定进行数学运算,这种约定就是操作符重载的内容。
 在非静态成员函数中,都带有隐藏的参数this,图片下面指出的就是隐藏的this,但在实际编程中,我们不需要在参数列表中写上this。

  Return by reference, again(返回引用)

在这里插入图片描述
 当函数的返回类型为返回引用时,函数体内返回值正常返回值即可,即传递者无须知道接收者是以reference形式接收。
 返回引用可以连续使用操作符,即c3 += c2 += c1;因为c3 += c2会返回c3,c3可继续进行+=操作,如何是返回值类型,值类型没有+=的操作,就不能连续的进行操作符操作。

  Operator overloading, as non-member function(非成员函数的操作符重载)

在这里插入图片描述
 为了应对调用者的三种可能用法,这里对应开发三个函数。
在这里插入图片描述
 若有新对象返回,不可以引用形式作为函数返回类型。
在这里插入图片描述
 输出操作符重载函数要global范围,第一个参数类型为ostream,不可加const,因为输出要加载到它身上。返回引用类型可完成连续的输出操作,若返回void,则一次只能输出一个对象。

  Temporary objects(临时对象)

在这里插入图片描述
 临时对象也为匿名对象,该对象没有名字,生存周期仅在定义的那一行,生命在下一行就结束了。
 上图中函数的返回类型不能为引用,原因是返回值对象是在函数体内定义的,当函数体结束后,返回值对象将销毁,所以只能是以值作为返回类型,因为函数在执行完也会销毁对象,可以设置临时对象,这样都可以完成销毁对象的目的。

  Expertise(专业写法)

当你完成一个类时,你应该检查你定义的的:
成员变量是否在private内,允许外界调用的函数应是public访问权限
是否采用引用传递(在传递引用时要关注参数是否需要改动,是否应该加const);
返回值是否返回引用类型(关注返回对象是在何时创建,在函数之前创建应返回引用类型;是否需要连续的输出操作,若需要,则应返回它本身,即引用类型);
某些函数是否该加const(关注在函数体内是否修改了参数);
构造函数初始化列表

  Code demonstration(编程示例)

#ifndef __MYCOMPLEX__
#define __MYCOMPLEX__
#include <iostream>

class complex; 
complex&  __doapl(complex* ths, const complex& r);

class complex
{
public:
	complex (double r = 0, double i = 0) : re(r), im(i) 
	{ }
	
	complex& operator += (const complex &);
	
	double real() const 
	{
		return re;
	}
	
	double imag() const
	{	
		return im;
	}
	
private:
	double re, im;

	friend complex& __doapl(complex*, const complex&);
};

inline complex& __dopal(complex* ths, const complex& r)
{
	ths->re += r.re;
	ths->im += r.im;
	return ths;
}

inline double imag(const complex& x)
{
  return x.imag();
}

inline double real(const complex& x)
{
  return x.real();
}

inline complex& complex::operator += (const complex& r)
{
	return __dopal(this, r);
}

//若将operator+写作成员函数,那么它只能应对复数加复数,不能应对复数加实数。
inline complex operator + (const complex& x, const complex& y)
{
	return complex( real(x) + real(y), imag(x) + imag(y));
}

inline complex operator + (const complex& x, double y)
{
	return complex( real(x) +y, imag(x) );
}

inline complex operator + (double x, const complex& y)
{
	return complex( x + real(y), imag(y));
}

inline std::ostream& operator << (std::ostream& os, const complex& x)
{
	return os << "( " << real(x) << " , " << imag(x) << " )";
}

#endif //__MYCOMPLEX__

 此代码并不完整,只是列出了典型的一种运算。

 class with pointer members(带有指针成员的类)

 以string类为例。

  The “Big Three”(三个特殊函数)

在这里插入图片描述
 拷贝构造函数,拷贝赋值函数,析构函数。
拷贝构造函数:构造函数的一种,与构造函数语法一直,参数为该类类型。
拷贝赋值函数:赋值操作运算符的重载函数,返回类的引用,参数为该类类型。
析构函数:无返回类型和参数,函数名为“~” + “类名”。

  Ctor and Dtor, in our String class(构造函数和析构函数)

在这里插入图片描述
 在类中含有指针的情况下,需要重写拷贝构造函数和析构函数,拷贝构造函数用于对象的深拷贝,析构函数用于释放在堆区开辟的空间。

  “MUST HAVE” in our String class(String类中必须有的函数)

在这里插入图片描述
 函数指针类型的类中一定要有拷贝构造函数拷贝赋值函数
 若使用默认的拷贝构造函数,则在有指针的情况下会发生浅拷贝,即两个指针指向同一块内存区域,那就导致另一块内存区域没有指针指向它,造成内存泄漏,而两个指针同时指向的内存,通过一个指针改变数据,另一个指针的数据也将发生变化。我们需要的是一个指针指向一块内存区域。

   Copy Constructor(拷贝构造函数)

在这里插入图片描述
 重写拷贝构造函数完成深拷贝,首先开辟一块合适大小的内存(需要拷贝的数据的大小+1,1指的是字符串结束的标记:‘\0’),然后将需要拷贝的数据复制到新开辟的内存中。String s2(s1);将调用拷贝构造函数。

   Copy assignment operator(拷贝赋值函数)

在这里插入图片描述
拷贝赋值函数的过程:
 例如a = b;即将b的值赋值给a,首先,删除a所指向的内存空间,然后新开辟一块与b大小一致的内存空间,将b的值赋值到a开辟的内存中,最后返回该类的引用类型。

  Deal with “self assignment”(检查自我赋值)

在这里插入图片描述
 注意到在拷贝赋值函数体中,一开始检查是否是自我赋值,若是自我赋值直接返回本身,若是没有这一判断会怎样?
 如果不是自我赋值,函数将正常运行。
 如果是自我赋值,则先将this指针指向的内存区域删掉,然后开辟一块与rhs内存大小一致的新内存,这是我们发现,this与rhs指向的是同一块内存,一开始已经通过this将内存删掉了,所以两个指针都指向了空,这时已经无法再开辟出新的空间,这样导致赋值失败,所以是错误的做法。

 记住:一定要在拷贝复制函数中检查是否是自我赋值。

  Another way to deal with “self assignment” : Copy and Swap

 复制-交换(copy-swap)方法是利用交换左右两侧的值, 达到赋值的目的;可以处理自我赋值(self assignment)和异常安全(exception safe)问题。
 注意,该方法的核心就是不再使用引用作为赋值运算符参数,而是以值传递的方式,这样的写法会使编译器自动调用拷贝构造函数(由于已经重写了拷贝构造函数,那么就会发生深拷贝,结果就是会有两个指针分别指向自己的内存区域),由于拷贝构造函数的调用,异常安全将在进入函数体之前被避免(若拷贝失败则什么都不会发生)。经过swap后的对象在离开函数体后会自动销毁,因此也就自动调用了析构函数。

String& String::operator = (String str) 
{
	swap(*this, str);
	return *this;
}

  Overloading output operator (<<)(重载输出运算符)

在这里插入图片描述
 重载输出运算符,与之前的语法一样。

为什么重载输出运算符函数要为全局函数:
 重载输出运算符要写在类外,不可定义为成员函数,因为若写在类内则在输出对象时输出顺序与我们习惯的顺序想反,即:s1 << cout;,这是我们不能接受的,我们习惯这样写cout << s1;。
为什么作为成员函数最后的输出顺序会改变呢?
 上图中定义在类外的函数内的参数顺序为:os、str,这样输出顺序为:cout << s1;,若函数为成员函数,成员函数的第一个参数为this,不能是其他,所以参数顺序是这样:this、os,那么自然输出顺序为:s1 << cout;。

  Expertise(专业写法)

 检查拷贝构造函数、拷贝赋值函数、析构函数是否正确书写。

  Code demonstration(编程示例)

#ifndef __MYSTRING__
#define __MYSTRING__

#include <iostream>
#include <cstring>

class String
{
public:
	String(const char* cstr = 0);
	String(const String& str);
	String& operator=(const String& str);
	~String();

	char* get_c_str() 
	{
		return m_data;
	}

private:
	char* m_data;	
};

inline String::String(const char* cstr = 0)
{
	if (str)
	{
		m_data = new char[strlen(cstr) + 1];
		strcpy(m_data, cstr);		
	}
	else
	{	
		m_data = new char[1];
		*m_data = '\0';
	}
}

inline String::String(const String& str)
{
	m_data = new char[strlen(str.m_data) + 1];
	strcpy(m_data, str.m_data);
}

inline String& String::operator=(const String& str)
{
	if (this == &str)
	{	
		return *this;
	}
	delete[] this->m_data;
	m_data = new char[strlen(str.m_data) + 1];
	strcpy(m_data, str.m_data);
	return *this;
}

inline String::~String()
{
	if (nullptr != m_data)
	{
		delete[] m_data;
		m_data = nullptr;
	}
}

inline std::ostream& operator<<(std::ostream os, const String& str)
{
	os << str.get_c_str();
	return os;
}

#endif //__MYSTRING__

引用:String& str(&紧接在类型后面,以后传引用这么写);
取地址:&str(在变量前面)。

 Objects from stack vs. objects from heap(栈和堆)

stack(栈):是存在于某作用域(scope)的一块内存空间(memory space)。例如当你调用函数,函数本身会形成一个stack用来放置它所接收的参数、返回地址以及函数体内声明的任何变量。
heap(堆):是指由操作系统提供的一块global内存空间,程序可动态分配(dynamic allocated)从中获得若干区块(blocks)。
在这里插入图片描述

  Objects lifetime(对象的生命周期)

class Complex { ... };
...
Complex c3(1, 2);	//c3为global object(全局对象),其生命周期在整个程序结束之后才结束。

{
	Complex c1(1, 2);	//c1是stack object(栈对象),其生命周期在作用域结束之际结束,它会被自动清理。
	static Complex c2(1, 2);	
	//c2为static object(静态对象),其生命周期在作用域结束之后仍然存在,直到整个程序结束。
}

在这里插入图片描述
 堆对象的生命周期在它被delete之后结束。若在它的作用域结束之后没有被delete掉,那么将会造成内存泄漏。

  new expression : allocate memory and then invoke ctor(new:先申请内存后调用构造函数)

在这里插入图片描述
new操作符执行过程,一共分为三步:
 1、分配内存:调用operator new函数分布内存,其内部是调用C语言中的malloc函数,传入内存大小,传出void类型;
 2、转型:将void
类型转为想要的类型赋值到指针上;
 3、构造函数:指针调用类的构造函数完成对象的创建。

  delete expression : invoke dtor and then free memory(delete:先调用析构函数后释放内存)

在这里插入图片描述
delete操作符执行过程,一共分为两步:
 1、析构函数:先调用析构函数,清除类的信息,包含类内部开辟的内存;
 2、释放内存:调用opertor delete函数,其内部调用C语言的free函数,释放之前开辟的内存。

  Anatomy of memory blocks from heap(动态分配所得的内存块)

在这里插入图片描述
 上图是在堆区动态开辟的内存空间,左面两个为Complex类,右面两个为String类,其中每个类的左面指的是Debug模式下的内存分配,而右面的则是Release模式。
 以左一为例,上下两个红色的区域为cookie区,存放该内存块的大小,绿色的为Complex类占的内存大小(类中含2个double类型的值,在32位的VC编译器中,它们一共占8字节),灰色的区域包括上面8个4字节和下面1个4字节,这样内存为:8+(48+41)+(4*2)= 52,52不是16的倍数,内存要求是16的倍数,向上找16的倍数为64,然后在灰色块下面添加3个4字节的填充(pad),这样内存为64。cookie区填入的是内存大小,以16进制表示,64字节大小在16进制下是40。由于内存块大小本来就是16的倍数,那么它的16进制下最后一位肯定为0。在cookie中,用最后一位表示开辟内存还是释放内存,1表示开辟内存,0表示释放内存,所以cookie中为41。
 在左二中,是在Release模式下,不含灰色的区块,直接是cookie区、类大小区和填充区(pad,若需要)。

  Allocate an array dynamically(动态分配数组内存)

在这里插入图片描述
调用new[],编译器是如何分配内存的?以32位VC为例。
 首先是上下两块cookie,debug模式下有32+4字节的区域,在VC中有4字节区域表示数组的个数,这里为3,下面就是数组中元素的内存区域,最后若内存大小不是16的倍数则还要增加填充区(pad)。
 在String类中,内部有指针变量,数组中的元素又通过指针指向另外的内存区域,如上图中右侧的两个内存块,指针指向的内存并没有画出来。

  new[] and delete[]

在这里插入图片描述
delete与delete[]的区别:
 delete只会调用一次析构函数,delete[]会调用多次析构函数(数组内有几个对象就调用几次)。
 以String类为例,String类中每个对象通过指针指向了另外的内存区域,若调用delete,则析构函数被调用一次,只会删除一个对象以及指针指向的内存区域,然后再将类所在的内存块整体删除,delete执行结束。但是另外两个对象他们所指的内存区域并没有删除,也没有指针指向他们,这样造成了内存泄漏。如果调用delete[],析构函数被唤醒多次,那么所有对象内部开辟的空间都会被删除,这样就安全了。
delete与new[]搭配一定不安全吗?
 delete与new[]搭配有时候是安全的,例如类中没有指针,类对象没有开辟新的空间,利用new[]开辟的空间都在一整块内存上,这一整块包含了多个对象,使用delete,会将这一整块中的对象释放掉一个,其余对象还在,但是剩余的对象还在这一整块内存上,最后释放这一整块内存时,所有的东西都被释放掉,这样也不会造成内存泄漏,但这样并不建议,还是应该对应搭配使用。
 总结:利用new[]分配的数组空间一定要使用delete[]释放。

 MORE ISSUES :

  static(静态)

在这里插入图片描述
 静态成员函数和静态成员变量属于类,非静态成员函数和非静态成员变量属于类的对象。
 非静态成员函数都有一个this指针,this指针指向调用的对象,而静态成员函数没有this指针。
在这里插入图片描述
静态成员变量需要在类内声明,类外定义。
调用静态函数的方式有两种:
  1、通过对象调用
  2、通过类名调用
静态函数内部只能调用静态成员变量,不可使用非静态成员变量和函数。理由是静态函数和静态变量是属于类的,它们不属于任何一个对象,普通成员变量属于对象,它只能通过对象才可以被创建,而普通成员函数通常包含普通成员变量。若一个类并没有实例化对象,而试图通过静态函数调用成员变量、成员函数是行不通的,因为成员变量还没被创建,但是反过来是可以的,即成员函数可以调用静态成员变量和静态成员函数。

  private ctors(私有构造函数)

在这里插入图片描述
单例模式下,将类的构造函数放在私有属性下,不允许外部调用,只通过一个静态成员函数创建对象,因为静态成员函数只属于这个类,静态成员函数也只有一份,这就保证了类的对象只有一个。

  cout(输出)

在这里插入图片描述
 cout继承自ostream类,重载了许多<<操作符。

  Class template(类模板)

在这里插入图片描述

  Function template(函数模板)

在这里插入图片描述
 两个类之间不能直接进行比较,需要重载操作符后比较。
 类模板在使用时必须给出明确的类型,而函数模板在使用时可以不给出类型,函数模板能推导出具体的类型。

  namespace(命名空间)

在这里插入图片描述
 命名空间是为了防止类、函数、变量等发生重名。
 解开命名空间:using namespace std 或using std::cout,std指的是标准库。

  Standard Library : Introductions and Overviews(标准库)

更多细节深入:
在这里插入图片描述

 Object Oriented(面向对象)

面向对象程序设计
 Object Oriented Programming(OOP)
 Object Oriented Design(OOD)
面向对象程序设计侧重不同类之间的关系,主要有以下三种方式:
Inheritance (继承)
Composition (复合)
Delegation (委托)

  Composition means “has-a”(复合:“ has a ”)

 类的复合,也就类的组合,表示一个类中含有另一个类的对象,即“ has a ”。
在这里插入图片描述
 queue类中包含了deque类的对象,在queue的成员函数中其实都是在调用deque的成员函数。
 类之间复合的UML图表示为上图右上方,queue含有deque,queue实菱形指向deque。

在这里插入图片描述
queue类的大小计算:
 queue类包含一个deque对象,deque类包含两个迭代器类型,一个指针类型(4字节)和一个无符合整型(4字节),迭代器类型包含4个指针(4字节),所占内存大小一共为442+4+4=40字节。

   Construction : from inside to outside(构造函数:由内而外)

在这里插入图片描述
构造函数由内而外:
 Container的构造函数首先调用Component的default构造函数,然后才执行自己。
 代码:Container :: Container( … ) : Component() { … };

   Destruction : from outside to inside(析构函数:由外而内)

析构函数由外而内:
 Container的析构函数首先执行自己,然后才调用Component的析构函数。
 代码:Container :: ~Container( … ) { … ~Component()};

  Delegation means “Composition by reference”(委托:以引用形式复合)

在这里插入图片描述
 委托,与复合相比,复合是类中直接包含函数对象,而委托则是包含类的一个指针,具体需要的时候再实例化对象。委托因为指针的存在相对复合而言更加灵活,因为指针可以指向该类以及该类的子类,这样handle(上图中的String类)不用改变,只需Body(上图中的StringRep类)改变,即用户只关注handle就可以实现需求。

  Inheritance means “is-a”(继承:“ is a ”)

在这里插入图片描述
 继承表示is-a,即子类是父类的一种形式。UML图见上图右面,子类空箭头指向父类,子类右上角有字母“T”表示模板类型。
 继承语法如上,在类名后面+":"+“public/protect/private”+“父类类名”,继承权限最常用public,C++支持多继承。

   Construction : from inside to outside(构造函数:由内而外)

在这里插入图片描述
构造函数由内而外:
 Derived的构造函数首先调用Base的default构造函数,然后才执行自己。
 代码:Derived :: Derived( … ) : Base() { … };

   Destruction : from outside to inside(析构函数:由外而内)

析构函数由内而外:
 Derived的构造函数首先调用Base的default构造函数,然后才执行自己。
 代码:Derived :: ~Derived( … ) { … ~Base() };

  Construction and Destruction, when Inheritance+Composition(继承和复合关系下的构造和析构)

在这里插入图片描述

1、构造函数依旧由内而外
 Derived 的构造函数首先调用 Base 的 default 构造函数,然后调用 Component 的 default 构造函数,最后才执行自己的。
 代码:Derived::Derived(…): Base() , Component() { … };
 即:先继承,再复合,后自己
2、析构函数依旧由外而内
 Derived 的析构函数首先执行自己,然后调用 Component 的析构函数,最后调用 Base 的析构函数。
 代码:Derived::~Derived(…) { … ~Component(), ~Base() }; 。
 即:先自己,再复合,后继承
在这里插入图片描述
构造:Derived::Derived(…): Component(), Base() { … };
析构:Derived::~Derived(…) { … ~Base(), ~Component() };

  Inheritance with virtual functions(继承与虚函数)

在这里插入图片描述
非虚函数:不希望子类重写(override)它。
虚函数:希望子类重写它,但你对它已有默认定义。
纯虚函数:希望子类一定重写它,你对它没有定义(含有纯虚函数的类不能实例化对象)。

  Virtual functions typical usage 1 : Template Method(模板方法模式)

在这里插入图片描述
 上图中CMyDoc继承了类CDocument,并重写虚函数Serialize。上图中main函数的整个执行过程如灰色虚线,CMyDoc实例化对象myDoc,myDoc调用父类的OnFileOpen函数,在函数内部,遇到Serialize函数,又回来调用自己的Serialize函数,调完之后返回到父类的OnFileOpen函数中,执行完OnFileOpen函数后回到main函数。
 当子类对象调用父类函数时,myDoc.OnFileOpen();在编译器显示为CDocument::OnFileOpen(&myDoc);,其中&myDoc就是this指针,执行到Serialize函数时,即为Serialize(&myDoc),也就是this->Serialize( );,这样就调用了CMyDoc类中重写的Serialize函数了。

  Virtual functions typical usage 2 : Polymorphism(多态)

  Virtual functions inside out : vptr, vtbl, and dynamic binding(动态绑定)

  Delegation + Inheritance : Observer(观察者模式)

在这里插入图片描述
 观察者模式:一个物体(Subject)可以有好几个观察者(Observer)。

  Delegation + Inheritance : Composite(组合模式)

在这里插入图片描述
 Composite和Primitive共同继承Component类,并重写add函数,Composite与Component为委托关系,在Composite类中可实现add函数的多态。

  Delegation + Inheritance : Prototype(原型模式)

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值