C++类继承

本文详细介绍了C++中的类继承机制,包括基本概念、不同继承方式下的访问权限控制、构造函数和析构函数的行为,以及多继承和菱形继承的问题及虚继承的解决方案。
摘要由CSDN通过智能技术生成

类继承

  • 继承一笔财产比白手起家要轻松。
  • 重用经过测试的、可靠的代码,比从头开始写代码要好得多。
  • 类继承的目的是代码重用。

类继承-基本概念

继承可以理解为一个类从另一个类获取成员变量和成员函数的过程。
语法:

class 派生类名:[继承方式] 基类名
{
	派生类新增的成员
}

被继承的类称为基类或父类,继承的类称为派生类或子类。
继承和派生是一个概念,只是站的角度不同。
派生类除了拥有基类的成员,还可以定义新的成员,以增强其功能。
使用继承的场景:

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

对于1:
我们设计一个海选报名者类,这个类里面的成员函数只有姓名和联系方式。报名时需要唱一首歌,长的好听就可以留下,正式成为一名超女,所以我们还有一个超女类,继承海选报名表类。

#include <iostream>         // 包含头文件。
using namespace std;        // 指定缺省的命名空间。
                  
class CAIIComers       //海选报名表类。
{
public:
    string m_name;//姓名
    string m_tel;//联系方式
    //构造函数
    CAIIComers() { m_name = "某女";m_tel = "不详"; }
    //报名时需要唱一首歌
    void sing() { cout << "我是一只小小鸟。\n"; }
    //设置姓名
    void setname(const string& name) { m_name = name; }
    //设置电话号码
    void settel(const string& tel) { m_tel = tel; }
};
class CGirl :public CAIIComers {//超女类
public:
    int m_bh;//编号
    CGirl() { m_bh = 8; }
    void show(){ cout << "编号:" << m_bh << ",姓名:" << m_name << ",联系电话:" << m_tel << endl; }


};
                  
int main()
{
    CGirl g;
    g.setname("西施");
    g.show();
}

对于2:例如
在这里插入图片描述

类继承-继承方式

类成员的访问权限由高到低依次为: public -> protected -> private,public成员在类外可以访问,private成员只能在类的成员函数中访问。
如果不考虑继承关系,protected成员和private成员一样,类外不能访问。但是,当存在继承关系时,protectedprivate就不一样了。基类中的protected成员可以在派生类中访问,而基类中的private成员不能在派生类中访问。
继承方式有三种: public(公有的)protected(受保护的)private(私有的)。它是可选的,如果不写,那么默认为private。不同的继承方式决定了在派生类中成员函数中访问基类成员的权限。
在这里插入图片描述
这张图描述了三种继承方式下,派生类成员访问权限的变化。

  1. 基类成员在派生类中的访问权限不得高于继承方式中指定的权限。例如,当继承方式为protected时,那么基类成员在派生类中的访问权限最高也为protected,高于protected 的会降级为protected,但低于protected不会升级。再如,当继承方式为public时,那么基类成员在派生类中的访问权限将保持不变。
    也就是说,继承方式中的publicprotectedprivate是用来指明基类成员在派生类中的最高访问权限的。

  2. 不管继承方式如何,基类中的private成员在派生类中始终不能使用(不能在派生类的成员函数中访问或调用)。

  3. 如果希望基类的成员能够被派生类继承并且毫无障碍地使用,那么这些成员只能声明为publicprotected;只有那些不希望在派生类中使用的成员才声明为private

  4. 如果希望基类的成员既不向外暴露(不能通过对象访问),还能在派生类中使用,那么只能声明为protected
    由于private和protected继承方式会改变基类成员在派生类中的访问权限,导致继承关系复杂,所以实际开发中,一般使用public

    在派生类中,可以通过基类的公有成员函数间接访问基类的私有成员。
    例如:

#include <iostream>         // 包含头文件。
using namespace std;        // 指定缺省的命名空间。
                  
class A       //基类。
{
private:
    int m_a = 10;
public:
    int m_b = 20;
    void func() {
        m_a = 11;
        cout << "m_a" << m_a << endl;
    }
};
class B :public A {//派生类
   

};
                  
int main()
{
    B b;
   // b.ma = 11;
    b.func();
}

**使用`using`关键字可以改变基类成员在派生类中的访问权限。**
注意: `using`只能改变基类中`public`和`protected`成员的访问权限,不能改变`private`成员的访问权限,因为基类中`private`成员在派生类中是不可见的,根本不能使用。
#include <iostream>         // 包含头文件。
using namespace std;        // 指定缺省的命名空间。
                  
class A       //基类。
{
public:
    int m_a = 10;
protected:
    int m_b = 20;
private:
    int m_c = 30;
};
class B :public A {//派生类
public:
    using A::m_b;//把m_b的权限修改为共有的
private:
    using A::m_a;//把m_a的权限修改为私有的
};
                  
int main()
{
    B b;
    //b.m_a = 11;
    b.m_b = 21;
    //b.m_c = 21;
}

类继承-继承的对象模型

  1. 创建派生类对象时,先调用基类的构造函数,再调用派生类的构造函数。
  2. 销毁派生类对象时,先调用派生类的析构函数,再调用基类的析构函数。
#include <iostream>         // 包含头文件。
using namespace std;        // 指定缺省的命名空间。
                  
class A {//基类
public:
    A() { cout << "调用了A的构造函数"; }
    ~A() { cout << "调用了A的析构函数"; }
};

class B :public A {//子类
public:
    B() { cout << "调用了B的构造函数.\n"; }
    ~B() { cout << "调用了B的析构函数.\n"; }
};
class C :public B {//孙类
public:
    C() { cout << "调用了C的构造函数.\n"; }
    ~C() { cout << "调用了C的析构函数.\n"; }
};
int main() {
    C c;
}


  1. 创建派生类对象时只会申请一次内存,派生类对象包含了基类对象的内存空间this指针相同的。
  2. 创建派生类对象时,先初始化基类对象,再初始化派生类对象。
  3. 在VS中,用cl.exe可以查看类的内存模型。
  4. 对派生类对象用sizeof得到的是基类所有成员(包括私有成员)+派生类对象所有成员的大小。
  5. 在C++中,不同继承方式的访问权限只是语法上的处理。
  6. 对派生类对象用memset()会清空基类私有成员。
  7. 用指针可以访问到基类中的私有成员(内存对齐)。

类继承-如何构造基类

派生类构造函数的要点如下:

  1. 创建派生类对象时,程序首先调用基类构造函数,然后再调用派生类构造函数。
  2. 如果没以指定基类构造函数,将使用基类的默认构造函数。
  3. 可以用初始化列表指明要使用的基类构造函数。
#include <iostream>         // 包含头文件。
using namespace std;        // 指定缺省的命名空间。
                  
class A {//基类
public:
    int m_a;
private:
    int m_b;
public:
    A() :m_a(0), m_b(0) {//基类的默认构造函数
        cout << "调用了基类的默认构造函数A().\n";
    }
    A(int a, int b):m_a(a),m_b(b) {//基类的两个参的默认构造函数
        cout << "调用了基类的默认构造函数A(int a, int b).\n";
    }
    A(const A& a) :m_a(a.m_a + 1), m_b(a.m_b + 1) {//基类的拷贝构造函数
        cout << "调用了基类的拷贝构造函数A(const A& a).\n";
    }
    //显示基类A全部的成员
    void showA() {
        cout << "m_a=" << m_a << ",m_b=" << m_b << endl;
    }
};
class B :public A {//派生类
public:
    int m_c;
    B() :m_c(0),A() {//派生类的默认构造函数
        cout << "调用了派生类的构造函数B()\n";
    }
    B(int a, int b, int c) :A(a, b), m_c(0) {//基类的三个构造函数
        cout << "调用了派生类的构造函数B(int a,int b,int c)。\n";
    }
    B(const A& a, int c) :A(a), m_c(c) {//指明基类的拷贝构造函数
        cout << "调用了派生类的拷贝构造函数B(constA &a,int c)。\n";
    }

    //显示派生类B全部的成员
    void showB() {
        cout << "m_c=" << m_c << endl << endl;
    }
};
int main() {
    B b1;//调用基类的默认构造函数
    b1.showA();
    b1.showB();
    B b2(1, 2, 3);//调用两个参数的构造函数
    b2.showA(); b2.showB();

    A a(10, 20);//创建基类对象
    B b3(a, 30);//将调用基类的拷贝构造函数
    b3.showA();b3.showB();

}
  1. 基类构造函数负责初始化被继承的数据成员;派生类构造函数主要用于初始化新增的数据成员。
  2. 派生类的构造函数总是调用一个基类构造函数,包括拷贝构造函数。

类继承-名字遮挡和类作用域

如果派生类中的成员(包括成员变量和成员函数)和基类中的成员重名,通过派生类对象或者在派生类的成员函数中使用该成员时,将使用派生类新增的成员,而不是基类的。
例如:这个执行结果是80;

#include <iostream>         // 包含头文件。
using namespace std;        // 指定缺省的命名空间。
                  
class A {//基类
public:
    int m_a = 30;
    void func() { cout << "调用了A的func()函数.\n"; }
};
class B :public A {//派生类
public:
    int m_a = 80;
    void func() { cout << "调用了B的func()函数.\n"; }
};
int main() {
    B b;
    cout << "m_a的值是:" << b.m_a << endl;
    b.func();
}


注意:基类的成员函数和派生类的成员函数不会构成重载,如果派生类有同名函数,那么就会遮蔽基类中的所有同名函数。

   类是一种作用域,每个类都有它自己的作用域,在这个作用域之内定义成员。
   在类的作用域之外,普通的成员只能通过对象(可以是对象本身,也可以是对象指针或对象引用)来访问,静态成员可以通过对象访问,也可以通过类访问。
   在成员前面加类名和域解析符可以访问对象的成员。

#include <iostream>         // 包含头文件。
using namespace std;        // 指定缺省的命名空间。
                  
class A {//基类
public:
    int m_a = 30;
    int m_b = 50;
    void func() { cout << "调用了A的func()函数.\n"; }
    void func(int m_a) { cout << "调用了A的func(int a)函数";A::m_a = m_a; }
};
int main() {
    A a;
    cout << "m_a的值是:" << a.A::m_a << endl;
    a.A::func();
}


   如果不存在继承关系,类名和域解析符可以省略不写。
   当存在继承关系时,基类的作用域嵌套派生类的作用域中。
如果成员在派生类的作用域已经找到,就不会在基类作用域中继续查找;如果没有找到,则继续在基类作用域中查找。
   如果在成员的前面加上类名和域解析符,就可以直接使用该作用域的成员。
在这里插入图片描述
例如这个:

#include <iostream>         // 包含头文件。
using namespace std;        // 指定缺省的命名空间。
                  
class A {//基类
public:
    int m_a = 10;
    void func() { cout << "调用了A的func()函数.\n"; }
};
class B :public A {//子类
public:
    int m_a = 20;
    void func() { cout << "调用了B的func()函数.\n"; }
};
class C :public B {//孙类
public:
    int m_a = 30;
    void func() { cout << "调用了C的func()函数.\n"; }
};
int main() {
    C c;
    cout << "C::m_a的值是:" << c.C::m_a << endl;
    cout << "B::m_a的值是:" << c.B::m_a << endl;
    cout << "A::m_a的值是:" << c.A::m_a << endl;
    c.C::func();
    c.B::func();
    c.A::func();
}


类继承-继承的特殊关系

派生类和基类之间有一些特殊关系。

  1. 如果继承方式是公有的,派生类对象可以使用基类成员。
  2. 可以把派生类对象赋值给基类对象(包括私有成员),但是,会舍弃非基类的成员。
#include <iostream>         // 包含头文件。
using namespace std;        // 指定缺省的命名空间。
                  
class A {//基类
public:
    int m_a = 0;
private:
    int m_b = 0;
public:
    void show() { cout << "A::show() m_a="<<m_a<<",m_b="<<m_b<<endl; }
    void setb(int b) { m_b = b; }
};
class B :public A {//子类
public:
    int m_c = 0;
    void show() { cout << "B::show() m_a=" << m_a << ",m_c=" << m_c << endl; }
};
int main() {
    A a;
    B b;
    b.m_a = 10;
    b.setb(20);
    b.m_c = 30;

    a.show();

    a = b;
    a.show();
}


在这里插入图片描述

  1. 基类指针可以在不进行显式转换的情况下指向派生类对象。
    如果是指针的话要用->
#include <iostream>         // 包含头文件。
using namespace std;        // 指定缺省的命名空间。
                  
class A {//基类
public:
    int m_a = 0;
private:
    int m_b = 0;
public:
    void show() { cout << "A::show() m_a="<<m_a<<",m_b="<<m_b<<endl; }
    void setb(int b) { m_b = b; }
};
class B :public A {//子类
public:
    int m_c = 0;
    void show() { cout << "B::show() m_a=" << m_a << ",m_c=" << m_c << endl; }
};
int main() {
    B b;
    A* a = &b;
    b.m_a = 10;
    b.setb(20);
    b.m_c = 30;

    a->m_a = 11;
    a->setb(22);
    //a->m_c = 30;

    a->show();
}

  1. 基类引用可以在不进行显式转换的情况下引用派生类对象。
    注意:
  2. 基类指针或引用只能调用基类的方法,不能调用派生类的方法。(主要是多态哪里)
  3. 可以用派生类构造基类。
  4. 如果函数的形参是基类,实参可以用派生类。
  5. C++要求指针和引用类型与赋给的类型匹配,这一规则对继承来说是例外。但是,这种例外只是单向的,不可以将基类对象和地址赋给派生类引用和指针(没有价值,没有讨论的必要)。

类继承-多继承和虚继承

多继承的语法:
class派生类名:[继承方式1] 基类名1,[继承方式2] 基类名2,…
{
派生类新增加的成员
}

#include <iostream>         // 包含头文件。
using namespace std;        // 指定缺省的命名空间。
                  
class A1 {//基类一
public:
    int m_a = 10;
};
class A2  {//基类二
public:
    int m_b = 20;
};
class B :public A1, public A2 {//派生类
public:
    int m_c = 30;
};
int main() {
    B b;
    cout << "m_a的值是:" << b.m_a << ",m_b的值是:" << b.m_b << ",m_c的值是:" << b.m_c << endl;
}


#include <iostream>         // 包含头文件。
using namespace std;        // 指定缺省的命名空间。
                  
class A1 {//基类一
public:
    int m_a = 10;
};
class A2  {//基类二
public:
    int m_a = 20;
};
class B :public A1, public A2 {//派生类
public:
    int m_a = 30;
};
int main() {
    B b;
    cout << "B::m_a的值是:" << b.m_a << endl;
    cout << "A1::m_a的值是:" << b.A1::m_a << endl;
    cout << "A2::m_a的值是:" << b.A2::m_a << endl;
}


菱形继承:
在这里插入图片描述

#include <iostream>         // 包含头文件。
using namespace std;        // 指定缺省的命名空间。
                  
class A {
public:
    int m_a = 10;
};
class B:public A  {
};
class C :public A {
};
class D :public B, public C {};
int main() {
    D d;
    //d.m_a会报错
    d.B::m_a = 30;
    d.C::m_a = 20;
    cout << "B::m_a的地址是:" << &d.B::m_a << ",值是:" << d.B::m_a << endl;
    cout << "C::m_a的地址是:" << &d.C::m_a << ",值是:" << d.C::m_a << endl;

}

对于这个代码:如果直接赋值d.m_a会报错。B,C这是两个地址两个值。
在这里插入图片描述
也就是说,菱形继承存在两个问题:数据冗余和名称的二义性。解决这个问题,C++引入了虚继承技术。语法很简单,在派生类B和C继承A的定义中,加上virtual关键字

#include <iostream>         // 包含头文件。
using namespace std;        // 指定缺省的命名空间。
                  
class A {
public:
    int m_a = 10;
};
class B:virtual public A  {
};
class C:virtual public A {
};
class D:public B, public C {};
int main() {
    D d;
    //d.m_a会报错
    d.B::m_a = 30;
    d.C::m_a = 20;
    cout << "B::m_a的地址是:" << &d.B::m_a << ",值是:" << d.B::m_a << endl;
    cout << "C::m_a的地址是:" << &d.C::m_a << ",值是:" << d.C::m_a << endl;

}

在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值