继承小结

1、概念

面向对象程序设计使代码可以实现复用的最重要手段,允许程序员在原有类特性的基础上进行扩展,增加新的功能。

2、定义格式:首先是一个冒号,后面紧跟以逗号分隔的基类列表,其中每个基类前面可以有访问说明符。

继承小结_第1张图片

3.继承类型:

(1)public->访问的基类权限不变,可以访问公有的和保护的类型

   public继承的方式称为一个is-a,是一个接口继承

(2)protected->可以使类的成员变量在派生类中被访问,而不在其他类中被访问,

  (相当于降一个级)   基类的公有的变成保护,保护的变成私有的,私有的依旧是私有的;
有关受保护的成员的一些说明: 
1、受保护的成员同私有成员一样,对于类的用户来说是不可访问的
2、受保护的成员同公有函数一样,对于派生类的成员和友元来说是可访问的;
3、派生类的成员或友元只能通过派生类对象来访问基类的受保护成员。

(3)private->只能在基类中被访问,在派生类中访问权限都变成了私有的,此时也体现了基类和派生类是两个不同的作用域。

  protected和private继承的方式称为一个has-a是一个实现继承

如下表格所示:

继承小结_第2张图片

注意:

(1)、同定义类的规则一样,使用关键字class时,默认的是私有的继承方式,使用关键字struct时,默认的是公有的继承。

(2)、如果基类成员不想在类外直接被访问,但需要在派生类中能访问时,就使用protected继承。

(3)、不管使用那种继承方式,在派生类内部都可以访问基类的公有成员和保护成员,基类的私有成员存在,但是在子类之中并不能被访问。

4、派生类的默认成员函数

继承小结_第3张图片

5、构造函数和析构函数的调用次序

继承关系中,派生类继承自基类的,在派生类创建自己的对象时,就存在基类和派生类构造函数的先后顺序,以及对象使用完毕后,调用析构函数的顺序,下面通过一段简单的小代码来说明这两者调用之间的先后次序

class B
{
public:
	B()
	{
		cout<<"B()"<
代码正确编译后的运行结果为:

继承小结_第4张图片

根据运行的结果我可以看出,在创建派生类的对象时,先调用了基类的构造函数,在调用了派生类的构造函数,在析构派生类的对象时,先调用派生类的构造函数,在调用基类的析构函数;从表面上来看这种调用顺序是很合理的,实际上,函数在调用时,依旧先是进到派生类的构造函数,在派生类的构造函数调用时,由于要对初始化列表进行初始化,而继承正好是前一部分是继承自子类的对象,后一部分才是派生类自己真正创建的对象,因此在运行结果中先调用了基类的构造函数,而后才调用了派生类的构造函数;在析构时,由于先创建的后被销毁,后创建的先被销毁,这也和对象实际存在的生命有密切关系。

继承小结_第5张图片

有关派生类构造函数的几点说明:

(1)基类没有缺省构造函数,派生类必须要在初始化列表中显式给出基类名和参数列表。

(2)基类没有定义构造函数,则派生类也可以不用定义,全部使用全缺省构造函数。

(3)基类定义了带有形参表构造函数,派生类就一定定义构造函数。

拓展补充:必须在构造函数的初始化列表初始化的几种情况:

(1)const修饰的变量

(2)引用类型的变量

(3)基类中包括其他类的调用时

(4)基类没有缺省构造函数,派生类处必须要在初始化列表中显式给出基类名和参数列表

6、同名隐藏

所谓同名隐藏就是基类和派生类中存在同名的函数或者同名的成员对象

隐藏规则-->:如果存在两个或多个具有包含关系的作用域,外层声明了一个标识符,而内层没有再次声明同名标识符,那么外层标识符在内层依然可见,如果在内层声明了同名标识符,则外层标识符在内层不可见,这时称内层标识符隐藏了外层同名标识符。

(1)子类和父类中有同名成员,子类成员将屏蔽父类对成员的直接访问。

(2)在子类中要想访问父类中被隐藏的的成员,可以使用(基类::基类成员 访问)这种格式。

注意:在实际中在继承体系里最好不要使用同名成员

class A
{
public:
	int _a;
	void _Funtest()
	{
		cout<<"A::_Funtest()"<

7、复制兼容规则-----public继承

(1)子类对象可以赋值给父类对象;

(2)父类对象不能赋值给子类对象;

(3)父类的指针/引用可以指向子类对象;

(4)子类的指针/引用不能指向父类对象。

class B
{
public:
	int _b;
};

class C:public B
{
public:
	int _c;
};

void FunTest()
{
	B b;
	C c;
	b = c;//可以通过编译,派生类给基类赋值
	//c = b;//不可以通过编译,类型不同
	//(也不会发生隐式的类型转换),如下的强转可以完成派生类给基类赋值
	c = *(C*)&b;
	c._c = 20;

	B* pb = &b;
	pb = &c;
	B& rb = c;

	C* pc = &c;
	pc = (C*)&b;
	C& rc = c;
	//C& rcb = *(C*)
}
8、友元与继承

就像友元关系不能传递一样,友元关系也不能继承,基类的友元在访问派生类成员时不具有特殊性,派生类的友元也不能随意访问基类的成员

class Base
{
	friend class pal;
public:
	int prot_mem;
};

class pal
{
public:
	int f(Base b)
	{
		return b.prot_mem;
	}
	int f1(Sneaky s)
	{
		return s.prot_mem;
	}
};
9、基类与静态成员

基类定义了static成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个子类,都只有一个static成员实例。

class A
{
public:
	A()
	{
		_count++;
	}
	~A()
	{
		_count--;
	}
protected:
	static int _count;
};

int A::_count = 0;

class B:public A
{
public:
	B()
	{
		cout<<"B"<<&_count<
10、多继承

一个子类有两个或以上直接父类时称这个继承关系为多继承。

继承小结_第6张图片

class B1
{
public:
	int _b1;	
};

class B2
{
public:
	int _b2;
};

class C:public B1, public B2
{
public:
	int _c;
};
11、菱形继承

两个子类继承同一个父类,而又有子类同时继承这两个子类。

继承小结_第7张图片
class B
{
public:
	int _b;
};

class C1:public B
{
public:
	int _c1;
};

class C2:public B
{
public:
	int _c2;
};

class D:public C2, public C1
{
public:
	int _d;
}; 
12、虚拟继承----virtual
由于菱形继承时根基类是由至少两个以上的子类来继承的,所有在两个子类中都会有一份关于基类成员变量的成员继承,在不加作用域限定符而直接想访问基类的成员时,就会产生一个指向二义性的错误问题。因此引入虚拟继承来解决这种指向成员对象存在二义性的问题。(解决从不同途径继承来的同名的数据成员在内存中有不同的拷贝造成数据不一致问题
class B
{
public:
	int _b;
};

class C1:virtual public B
{
public:
	int _c1;
};

class C2:virtual public B
{
public:
	int _c2;
};

class D:public C1, public C2
{
public:
	int _d;
}; 
笔试面试中常考的虚继承的知识点:求sizeof(a)及sizeof(b)
继承小结_第8张图片




















你可能感兴趣的:(C++,C++学习总结)