C++继承

继承

  • 继承概念
    • 继承关系和访问限定符
    • 同名成员函数/变量
      • 子类和父类的构造和析构
        • 拷贝构造
          • 多继承/菱形继承

继承概念

在面向对象中,继承这个概念其实和现实生活中的继承没有太大的区别,比如说张三是一个亿万富翁,当有一天张三不幸去世了,那么张三的儿子张小三就会继承张三的亿万财产,也就是说张三的一切都会被张小三继承。
再举个例子,斑马,斑马和普通的马区别是斑马身上是黑白相间的,那么斑马的马崽子也会是黑白相间的,水牛的角又大又弯,所以水牛的牛崽子的角也会又大又弯。
所以,继承就是继承了父辈的财产,特征等。

在计算机语言中,为了让代码有更高的复用性,产生了继承这个概念,比如说现在有一个Person类,里面有一个show函数,当我创建了一个Student的类,而我又不想再写一个show函数,于是我让Student继承Person,当继承了Person后,Student就会继承show函数,即使Student中什么都不写,也可以调用show函数
C++继承_第1张图片

继承不但可以继承函数,也可以继承变量,对象
C++继承_第2张图片
可以看到,虽然Student类中什么都没有写,但是由于他继承了Person类中的成员,所以Student内部实际上是有和Person同样的成员函数和变量,在概念上Person被继承,他就被称为父类或者基类,Student继承了Person对象,Student被称为子类或者派生类

怎么证明子类确实继承了父类的成员呢,我们可以通过计算大小来验证:
没有继承的空类:
C++继承_第3张图片

继承了父类:
C++继承_第4张图片

继承关系和访问限定符

在设计一个类的时候,我们通常会用到三种访问限定符:public private protected
public中的成员可以在类的外部进行访问
private中的成员只能在类里面进行访问
protected中的成员不想在类外直接被访问,但可以派生类中访问

继承的方式:
C++继承_第5张图片
来用一张图解释子类继承父类的权限问题:
C++继承_第6张图片

  1. 基类private成员在派生类中无论以什么方式继承都是不可见的。这里的不可见是指基类的私
    有成员还是被继承到了派生类对象中,但是语法上限制派生类对象不管在类里面还是类外面
    都不能去访问它。
  2. 基类private成员在派生类中是不能被访问,如果基类成员不想在类外直接被访问,但需要在
    派生类中能访问,就定义为protected。可以看出保护成员限定符是因继承才出现的。
  3. 实际上面的表格我们进行一下总结会发现,基类的私有成员在子类都是不可见。基类的其他
    成员在子类的访问方式 == Min(成员在基类的访问限定符,继承方式),public > protected > private。
  4. 使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,不过
    最好显示的写出继承方式。
  5. 在实际运用中一般使用都是public继承,几乎很少使用protetced/private继承,也不提倡
    使用protetced/private继承,因为protetced/private继承下来的成员都只能在派生类的类里
    面使用,实际中扩展维护性不强。

同名成员函数/变量

如果基类和子类拥有同名的成员函数和变量,那么在使用的时候,到底是使用父类的还是子类的呢?
来做个试验:
C++继承_第7张图片
可以看到,在同名的情况下,访问的是基类的成员
为什么呢?简单来说,当基类和子类发生了同名函数和同名变量的情况下,会自动隐藏父类的函数和变量,所以访问的是子类的,如果就是要访问父类的,可以指定父类成员:
C++继承_第8张图片

子类和父类的构造和析构

C++继承_第9张图片
上面的代码中会打印什么,我们直接来看答案:
C++继承_第10张图片
这样看的话,还是有点冗余,我们再简化一下:
C++继承_第11张图片
可以看到,B继承了A,那么在创建B对象的时候,会先调用A的构造函数,然后再调用B的构造函数
B的生命周期结束,先析构B,再析构A,不论是A的构造还是析构都会由B自动调用,所以千万不要在B的内部手动调用A的析构函数,否则很有肯能出现对已经释放的空间进行二次释放的危险动作。

那如果A 的构造函数带有参数呢?
那就需要在B的构造函数中手动初始化A的构造函数,否则就会报错:

class A
{
public:
	A(int a):age(a){ cout << "A构造" << endl; }
	
	int age;

	~A() { cout << "A析构" << endl; }
};

class B : public A
{
public:
	B(int a) 
		:A(a)//手动初始化
	{	
		cout << "B构造" << endl; }

		~B() { cout << "B析构" << endl; 
	}
};

int main()
{
	//A a;
	B b(10);
	return 0;
}

拷贝构造

派生类对象 可以赋值给 基类的对象 / 基类的指针 / 基类的引用。这里有个形象的说法叫切片
或者切割。寓意把派生类中父类那部分切来赋值过去。
基类对象不能赋值给派生类对象。
基类的指针或者引用可以通过强制类型转换赋值给派生类的指针或者引用。但是必须是基类
的指针是指向派生类对象时才是安全的。这里基类如果是多态类型,可以使用RTTI(RunTime Type Information)的dynamic_cast 来进行安全转换

class Person
{
protected :
 string _name; // 姓名
    string _sex;  // 性别
    int _age; // 年龄
};
class Student : public Person
{
public :
 int _No ; // 学号
};
void Test ()
{
 Student sobj ;
 // 1.子类对象可以赋值给父类对象/指针/引用
 Person pobj = sobj ;
 Person* pp = &sobj;
 Person& rp = sobj;
    
 //2.基类对象不能赋值给派生类对象
    sobj = pobj;
    
    // 3.基类的指针可以通过强制类型转换赋值给派生类的指针
    pp = &sobj
    Student* ps1 = (Student*)pp; // 这种情况转换时可以的。
    ps1->_No = 10;
    
    pp = &pobj;
 Student* ps2 = (Student*)pp; // 这种情况转换时虽然可以,但是会存在越界访问的问
题
    ps2->_No = 10;
}

C++继承_第12张图片

多继承/菱形继承

C++继承_第13张图片

一个类继承两个基类,代码复用率很高,但是这个时候也会出现一个问题,如果继承的两个基类中有相同的成员怎么办?
比如说:
C++继承_第14张图片

可以看到,在访问age的时候直接就报错了,因为编译器无法识别到底是哪个age,解决的办法也很简单粗暴,那就是在访问的时候指明是哪个类的age就行了
C++继承_第15张图片
那如果是菱形继承呢,会发生什么?
C++继承_第16张图片

编译器还是无非识别
这里有两种方法,第一,指明哪个类
第二,虚继承
在继承的时候加上virtual关键字,使其成为虚继承:

class A
{
public:
	char name = 'A';
};

class B: virtual public A
{
public:
};

class C :virtual  public A
{
public:
};

class D :  public B,public C
{
};

int main()
{
	D d;
	d.name = 'D';
	cout << d.name << endl;

	return 0;
}

虚继承是一个非常坑的概念,能不用虚继承最好不用这玩意儿!!!!!!

你可能感兴趣的:(c++,开发语言)