C++基础3:继承

1、语法

(1)原则

is-a
父类/子类
基类/派生类

(2)语法

class 派生类 : [访问限定符] 基类 {
  成员
}

如果不写继承访问限定符,默认是private

2、成员的访问权限

publicprotectedprivate
类成员函数×
友元函数
子类函数×
类对象××

子类继承了父类所有的成员变量和成员函数。与访问限定符无关。访问限定符只是限制了访问
子类访问父类成员变量,把父类成员变量访问限定符,改为protected。

(1)继承访问权限变化

分为子类内部和子类对象两种访问方式
1、子类内部访问public继承的父类成员变量

#include <iostream>

using namespace std;

class Base{
public:
    int public_data;
protected:
    int protected_data;
private:
    int private_data;    
};

class Derive:public Base{
public:
    void test(){
        cout << public_data <<endl;
	cout << protected_data << endl;
	cout << private_data << endl;
    }
};

这个时候会提示编译出错,不能成功,因为子类Derive继承父类Base的public,protected但是不能访问父类的private。
2、子类内部访问public继承的父类成员函数

#include <iostream>

using namespace std;

class Base{
public:
    int public_data;
    void public_func();
protected:
    int protected_data;
    void protected_func();
private:
    int private_data;    
    void private_func();
};

class Derive:public Base{
public:
    void test(){/*
        cout << public_data <<endl;
	cout << protected_data << endl;
	cout << private_data << endl;*/
	public_func();
	protected_func();
	private_func();
    }
};

这个时候也会出现同样的错误,因为子类Derive是public的,所以不能访问父类中private的函数

(2)子类内部访问父类成员

publicprotectedprivate
public继承×
protected继承×
private继承×

子类内部访问父类成员,只能访问public和protected成员

(3)子类对象访问父类成员

publicprotectedprivate
public继承××
protected继承×××
private继承×××

子类只有public继承父类的时候,才能访问父类的public成员,其他都不能访问,通常子类使用public继承子类

(4)子类对象访问父类成员访问限定符的变化

继承方式/父类成员publicprotectedprivate
public继承publicprotected不可见
protected继承protectedprotected不可见
private继承privateprivate不可见

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

(5)小结

1、public无论类内部还是类对象都可以访问
2、protected类对象不可访问,类内部与继承类的内部都可以访问
3、private只有类内部才可以访问

3、继承关系的构造顺序

(1)顺序

1、派生类的构造函数与析构函数的调用顺序
2、派生类的构造函数调用顺序
子对象构造、成员变量构造、父对象构造的顺序
3、派生类的析构函数调用顺序
子对象析构、成员变量析构、父对象析构的顺序

#include <iostream>
using std::cout;
using std::endl;
class Member{
public:
    Member(){
        cout << "Member Init" <<endl;
    }
    ~Member(){
        cout << "Member Destroy" <<endl;
    }
};
class Parent{
public:
    Parent(){
        cout << "Parent Init" <<endl;
    }
    ~Parent(){
        cout << "Parent Destory" <<endl;
    }
};
class Son : public Parent{
public:
    Son(){
        cout << "Son Init" <<endl;
    }
    ~Son(){
        cout << "Son Destroy" <<endl;
    }
private:
    Member m;
};
int main(){
    Son son;
}

运行结果:

Parent Init
Member Init
Son Init
Son Destory
Member Destory
Parent Destory

这样的输出结果是因为,Son需要先初始化自己的父类,然后自己的成员,最后是自己的类,然后开始析构,,从自己开始析构,然后是成员,最后是父类的析构。
没有默认构造函数的基类在派生类的初始化,必须在初始化列表中初始化

(2)同名隐藏规则

概念:子类的成员函数与基类成员函数同名,子类的函数将会隐藏基类的所有同名函数

#include <iostream>

using namespace std;

class Base{
public:
    void show(int data){
        cout << "Base::show(" << data << ")"<<endl;
    }
};

class Derive:public Base{
public:
    void show(){
        cout << "Derive::show()" <<endl;
    }
};

int main(){
    Derive derive;
    derive.show();
    derive.show(123);
}

这时候编译出错,因为derive.show()调用的时候,父类中也有同名的函数,所以要想办法声明这个调用的show()是父类中的show()
修改成指针方式,试一下

int main(){
    Derive *pderive = new Derive;
    pderive->show();
    pderive->show(123);
}

这个时候还是不行,所以需要想另外的办法来解决问题!
解决办法:
1、Derive类对象调用被隐藏的父类函数时,在函数前面加上父类限定符。例如:

derive.show(123)-->derive.Base::show(123)
pderive->show(123)-->pderive->Base::show(123)

2、Derive类中的名称会隐藏Base类中同名的名称,在public继承中我们可以通过using声明:

class Derive:public Base {
public:
  using Base::show;
  void show(){
    cout<<"Derive::show()"<<endl;
  }
};

隐藏背后原因是为防止在程序库或应用框架内建立新的derived class时从疏远的base class继承重载函数

4、函数同名的情况总结

名称英语
重载overload
重写(覆盖)override
隐藏hide

赋值兼容规则
概念:在任何需要基类对象的地方都可以使用公有的派生类对象来代替。反之,不可。

1、派生类的对象可以赋值给基类对象

Base base;
Derive derive;
base = derive;

对象切割:在赋值时舍弃派生类自己的成员,只进行基类数据成员的赋值
2、派生类的对象可以初始化基类的引用

Derive derive;
Base& base = derive;

3、派生类对象的地址可以赋值给指向基类的指针
指向基类对象的指针变量也可以指向派生类对象

Derive derive;
Base* base = &derive;
向上转换:派生类对象赋值给基类
向下转换:基类对象赋值给派生类

4.1 练习

(1)派生类的对象可以赋值给基类的对象

int main(){
    Base base;
    Derive derive;
    base = derive;
    base.show(123);
}

(2)派生类对象的地址赋值给基类的指针变量

int main(){
    Base *pbase = new Derive;
    pbase->show(123);
}

指针访问派生类中由基类继承来的对象,不能访问派生类中的新成员

(3)派生类对象可以初始化基类的引用

int main(){
    Derive derive;
    Base &base = derive;
    base.show(123);
}

引用访问派生类中由基类继承来的对象,不能访问派生类中的新成员

5、多重继承

一个类可以同时继承多个父类的行为和特征功能。
逗号分割的基类列表

class 类名 : public 基类1,public 基类2{
};

6、钻石继承/菱形继承

6.1 概念

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

6.2 问题1

下面B、C继承与A,D同时继承B、C,那么同时D的实例调用A的成员函数会有什么情况?

#include <iostream>
using namespace std;

class A{
public:
    void test();
private:
    int id;
};

void A::test(){
    cout <<__func__<<endl;
}

class B:public A{};
class C:public A{};
class D:public B,public C{};

int main(){
   cout << "A size:" << sizeof(A) << endl;
   cout << "B size:" << sizeof(B) << endl;
   cout << "C size:" << sizeof(C) << endl;
   cout << "D size:" << sizeof(D) << endl;
}

输出结果:

A size:4
B size:4
C size:4
D size:8

原因:
B与C都继承了A的成员函数test(),D同时继承了B与C,调用test()无法确定时B还是C的。
解决:
给调用的成员函数前加上访问限定符,明确指定调用成员函数所属的类d.B::test();或者d.C::test();

6.3 问题2

不同途径继承来的同名的成员在内存中有不同的拷贝,造成数据不一致
解决:
虚继承和虚基类
虚继承和虚基类:
虚继承:在继承定义中包含了virtual关键字的继承关系
虚基类:在虚继承体系中的通过virtual继承而来的基类

class 类名:public virtual 基类{
}

虚基类是一个相对概念,在虚继承关系中,父类相对于子类是虚基类

6.4 测验

写出下列程序的执行结果,并分析结果。如果编译出错,分析原因并写出解决方案

#include <iostream>

using namespace std;

class Base{
public:
    Base(){
        cout << "Base constuct" << endl;
    }
    ~Base(){
        cout << "Base destuct" <<endl;
    }
};

class Member{
public:
    Member(){
        cout << "Member constuct" << endl;
    }
    ~Member(){
        cout << "Member destuct" << endl;
    }
};

class Derive:public Base{
public:
    Derive(){
        cout << "Derive constuct" << endl;
    }
    ~Derive(){
        cout << "Derive destuct" << endl;
    }
private:
    Member m;
};

int main(){
    Derive d;
}

运行结果:

Base constuct
Member constuct
Derive constuct
Derive destuct
Member destuct
Base destuct

7、关于多重继承

1、什么是多重继承?同时继承多个父类
2、多重继承有什么危害?菱形继承/钻石继承
3、什么是菱形继承/钻石继承?多重继承的两个或多个父类具有相同的祖先类
4、菱形继承/钻石继承有什么危害?因为多重继承的两个或多个父类具有相同的祖先类,所以会有完全相同的属性或方法。因此当前多重继承有两份相同的属性和方法。使用时会出现冲突。
5、如何解决菱形继承/钻石继承导致的冲突?使用虚继承
6、什么是虚继承?父类在继承具有相同的祖先类时,加上virtual

8、对象构造顺序总结

在这里插入图片描述基本原则:
1、先父后子
2、从左到右
3、先虚后实
4、从上到下
5、由内及外
示例:

#include <iostream>
using namespace std;

#define SIMPLE_CLASS(name)\
class name{\
public:\
    name(){cout << #name << "Constructor" << endl;}\
    ~name(){cout << #name << "Destructor" << endl;}\
};

SIMPLE_CLASS(Base1)
SIMPLE_CLASS(Base2)
SIMPLE_CLASS(Base3)

SIMPLE_CLASS(VBase1)
SIMPLE_CLASS(VBase2)
SIMPLE_CLASS(VBase3)


SIMPLE_CLASS(Member1)
SIMPLE_CLASS(Member2)
SIMPLE_CLASS(Member3)

#undef SIMPLE_CLASS

class Test:public Base1,
	   public Base2,
	   public Base3,
	   public virtual VBase1,
	   public virtual VBase2,
	   public virtual VBase3{	   
public:
    Test(){cout << "Test Constructor" << endl;}
    ~Test(){cout << "Test Destructor" << endl;}
private:
    Member1 m1;
    Member2 m2;
    Member3 m3;
};


int main(){
    Test t;

}

运行结果:

VBase1Constructor
VBase2Constructor
VBase3Constructor
Base1Constructor
Base2Constructor
Base3Constructor
Member1Constructor
Member2Constructor
Member3Constructor
Test Constructor
Test Destructor
Member3Destructor
Member2Destructor
Member1Destructor
Base3Destructor
Base2Destructor
Base1Destructor
VBase3Destructor
VBase2Destructor
VBase1Destructor
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值