关于C++中的继承

>使用继承的场景

  • - 如果新创建的类与现有的类相似,只是多出若干成员变量或者成员函数时,可以使用继承。
  • - 当需要创建多个类时,如果他们拥有很多相似的成员变量或成员函数,可以将这些类共同成员【向上】提取出来,定义为基类,然后从基类中继承。

>继承方式

- 类的访问权限由高到低依次是:public->protected->private
public成员在类外可以访问
private成员只能在类的成员函数中访问
protected成员在派生类中可以访问
默认情况下是private
在实际开发中,一般使用public继承方式
在派生类中,可以通过基类的公有成员函数简介访问基类的私有成员
使用using关键字可以改变基类成员在派生类中的访问权限,只能改变public和protected成员的访问权限,不能改变private成员的访问权限,因为基类中的private成员在派生类中是不可见的,根本不能使用。

C++
#ifndef CLASSB_H
#define CLASSB_H
#include "classA.h"
class classB :public classA
{
public:
    // 在头文件中声明
    void printHello();
private:
    // 在头文件中声明
    int count = 0;
    // 使用using关键字把classA中的公有成员变量变为B类中的私有变量
    using classA::number;
};
#endif // !CLASSB_H


只能改变public和protected 变为private和public

>继承的对象模型

1. 创建派生类对象时,先调用基类的构造函数,再调用派生类的构造函数
2. 销毁派生类对象时,先调用派生类的析构函数,再调用基类的析构函数
3. 创建派生类对象时只会申请一次内存,派生类对象包含了基类对象的内存空间,this指针相同的
4. 创建派生类对象时,先初始化基类对象,再初始化派生类对象
5. 对派生类对象用sizeof得到的是基类所有成员(包括私有成员)+派生类对象所有成员的大小
6. 在C++中,不同继承方式的访问权限只是语法上的处理
7. 对派生类对象用memset()会清空基类私有成员 `memset(p,0,sizeof(B))`,这个p指针指向类中的一个公有的函数对象,直接通过指针访问内存,突破了类的访问权限
8. 使用cl命令可以查看类的内存模型,但是实际开发中很少用到,cl是vs自带的,我用的vs2019 cl命令需要额外添加一下系统环境变量
9. 用指针可以访问到基类中的私有成员(没有内存对齐,没有占位符)

>如何构造基类

派生类构造函数的要点:

  • - 创建派生类对象时,程序首先调用基类构造函数,然后再调用派生类构造函数
C++
class A{ // 实现一个A类(基类)
public:
void printHello(){
    cout<<"Hello World!"<<endl;
};
private:
}
class A::A(){ // A类的构造函数
    cout<<"A构造函数"<<endl;
}
class A::~A(){ // A的析构函数
    cout<<"A析构函数"<<endl;
}

class B{ // 实现一个B类(A类的子类)
public:
private:
}
class B::B(){ // B类的构造函数
    cout<<"B构造函数"<<endl;
}
class B::~B(){ // B的析构函数
    cout<<"B析构函数"<<endl;
}

int main(){
    B b; // 创建一个B类的对象
    b.printHello();
}


  • - 如果没以制定基类构造函数,将使用基类的默认构造函数
  • - 可以用初始化列表指明要使用的基类构造函数
  • - 基类构造函数负责初始化被继承的数据成员;派生类构造函数主要用于初始化新增的数据成员
C++
// :a(90) 初始化列表 对a成员变量进行初始化,可以直接赋值,也可以使用表达式
    classA::classA():a(90){
        {
            cout << "A构造函数  "<< a << endl;
        }
    };

  • - 派生类的构造函数总是调用一个基类的构造函数,包括拷贝构造函数

基类的成员变量由基类的构造函数初始化、派生类新增的成员变量由派生类的构造函数初始化
private的成员变量在子类中访问不了,在子类中可以初始化public的成员变量语法没错,但不符合语法规范,如果有很多类都要继承基类,那么就会导致都需要写初始化基类的代码导致重复。

>名字遮蔽与类作用域

表象:名字遮蔽
本质:类作用域

如果派生类中的成员(包括成员变量和成员函数)和基类中的成员重名,在派生类对象或者派生类中成员函数中使用时,将使用派生类的新增的成员,而不会使用基类的成员
类作用域
A::a = 10; 等同于 this->a = 10;

  • - 如果有继承关系时,基类的作用域嵌套在派生类的作用域外部,如果成员在派生类的作用域中已经找到,就不会在基类作用域中继续查找;如果没有找到,则继续在基类作用域中查找。如果在成员前加上类名和解析符,就可以直接使用该作用域中的成员。

>继承的特殊关系

派生类和基类之间有一些特殊关系
1. 如果继承方式是公有的,派生类对象可以使用基类成员
2. 可以把派生类对象赋值给基类对象(包括私有成员),但是会舍弃非基类的成员
3. 基类指针可以在不进行显示转换的情况下指向派生类对象
4. 基类引用可以在不进行显示转换的情况下引用派生类对象

C++
int main(){
    B b;
    A* a = &b;
    b.show(); // 调用的是B类中的show函数
    a->show(); // 调用的是A类中的show函数
}

>多继承与虚继承(了解)

多继承语法:

class C:public A,public B,……

  • - 特殊情况:

类的菱形继承
d->b,c  b->a  c->a,b和c中都继承了a中的成员变量,而d又继承了b和c,这种情况叫做菱形继承,

class a{
public:
int myA = 10;
};
class b:public a{};
class c:public a{};
class d:public b,public c{};


运行之后就会报错,程序不知道该使用哪个成员变量myA
菱形继承出现的两个问题:数据冗余和名称的二义性

  • - 解决方法:使用虚继承
C++
class a{
public:
int myA = 10;
};
class b:virtual public a{};
class c:virtual public a{};
class d:public b,public c{};


在内存模型中,代替a基类位置的是虚基类指针,通过虚基类指针指向虚基类表,通过虚基类表找到基类a
注意:Java C# PHP都不支持多继承,在实际C++也不提倡使用多继承,层次很多,关系更复杂

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值