C++中的继承

类的继承允许创建基于现有类的新类,分为接口继承(虚函数实现多态)和实现继承(继承函数实现)。继承提供了代码复用和多态性,子类可以有父类的公共成员,但不能直接访问私有成员。构造函数、析构函数、拷贝构造函数和拷贝赋值函数在继承中都有特定的行为规则。多重继承允许一个子类继承多个父类,而虚继承解决了菱形继承中的成员歧义问题,确保公共基类只有一份副本。
摘要由CSDN通过智能技术生成

1.类的继承

1.1 概念

基于一个已有的类,去重新定义一个新的类,这种方式就叫做继承。

接口继承和实现继承:
普通函数的继承是一种实现继承,派生类继承了基类函数,可以使用函数,继承的是函数的实
现。虚函数的继承是一种接口继承,派生类继承的是基类虚函数的接口,目的是为了重写,达成
多态,继承的是接口。所以如果不实现多态,不要把函数定义成虚函数。
所以就有了 子类虚函数没有写virtual,依旧是虚函数;子类虚函数使用的是父类虚函数的缺省参数,只是重写了实现

1.2 意义

1.可以实现代码复用,减少了写重复的代码的劳动量;

2.继承是实现多态的必要条件。

1.3 继承时的称呼

一个类B 继承自类A 我们一般称呼

A类:父类 基类

B类:子类 派生类

B继承自A    A派生了B

1.4 继承的格式

格式:

class 子类名:继承方式 父类名{

子类新增的成员;

};

例:

class B:public A{

B类新增的成员

};

1.5 继承方式

复习访问控制权限:

                   类内                 子类中                 类外

private:       !                         X                       X

protected:   !                         !                        X

public:         !                         !                         !

继承方式也有三种:public protected private

三种继承方式下,成员的属性的变化:

父类 public protected private    public protected private      public protected private

继承方式 public                                 protected                                 private

子类 public protected -------     protected protected ----           private private --------

实际开发中,一般继承时都采用public继承方式

如果不写继承方式,默认使用的都是private方式继承。

1.6 注意

1.子类中会继承父类中的所有成员,包括私有成员,只不过在子类中不能直接访问父类私有成员

需要父类中提供公有的函数来访问父类的私有成员;

2.当父子类中出现重名的函数时,访问其来也不会冲突,即使形参不同,也不构成重载关系,原因是两个函数不在同一块空间,父子类中成员变量重名也不会冲突

对于重名的成员的访问:

如果不加任何修饰,默认的都是使用this指针访问成员,访问的是子类的成员。

如果想访问父类的成员,需要加 父类名:: 来修饰

类外:

对象名.父类名::变量;

对象名.父类名::函数名(函数的实参表);

类内:

父类名::变量;

父类名::函数名(函数的实参表);

#include <iostream>
using namespace std;
#include <string>

class Person{
    private:
        string name;//名字
    protected:
        int age;//年龄
    public:
        int money;//存款
        Person(){cout<<"Person 无参构造"<<endl;}
        Person(string n, int a, int m):name(n), age(a), money(m){
            cout<<"Person 有参构造"<<endl;
        }
        void show(){
            cout<<"Person:"<<name<<" "<<age<<" "<<money<<endl;
        }
        void p_set_name(string n){
            name = n;
        }
};

class Teacher:public Person{
    private:
        string subject;//所教学科
    public:
        Teacher(){cout<<"Teacher 无参构造"<<endl;}
        Teacher(string n, int a, int m, string s):
            //在继承时,需要在子类的构造函数的初始化表中
            //显性的调用父类的构造函数,来完成对从父类中继承过来的成员的初始化
            Person(n, a, m), subject(s){
                cout<<"Teacher 有参构造"<<endl;
        }
        void t_set_name(string n){
            //name = n;//子类中不能直接访问 父类的private和成员
        }
        void t_set_money(int m){
            money = m;//子类中可以直接访问 父类的public和成员
        }
        void t_set_age(int a){
            age = a;//子类中可以直接访问 父类的protected成员
        }
        void show(){//和父类的show重名,但是访问也不会冲突
            cout<<"Teacher:"<<subject<<endl;
        }
        void test(){
            cout<<"test"<<endl;
            show();//子类的show
            Person::show();//类内访问父类的show的写法
        }
};

int main()
{
    Teacher t1("张三", 25, 1000, "C++");
    t1.p_set_name("李四");//可以通过父类提供的public函数来访问父类私有成员
    t1.Person::show();//类内访问父类的show的写法

    return 0;
}

1.7 继承中的构造函数

1.父类的构造函数不会被子类继承(父子类的构造函数不一样);

2.需要在子类的构造函数的初始化表中, 显性的调动父类的构造函数,完成对从父类中继承过来的成员的初始化;

3.如果没有在子类的构造函数的初始化表中调用父类的构造函数,默认会调用父类的无参构造来完成从父类中继承过来的成员的初始化,如果此时父类中没有无参构造函数,会报错;

4.构造函数的调用顺序:先调用父类的构造函数,再调用子类的构造函数。

1.8 继承中的析构函数

1.父类的析构函数也不会被子类继承(父子类的析构函数不一样);

2.不管子类中是否显性的调动父类的析构函数,父类的析构函数都会被调用,完成对从父类中继承过来的成员的善后工作;

3.子类的析构函数中无需调用父类的析构函数;

4.析构函数的调用顺序:先调用子类的析构函数,再调用父类的析构函数。

1.9 继承中的拷贝构造函数

1.如果子类中没有显性的定义拷贝构造函数,编译器会给子类提供一个默认的拷贝构造函数,而且默认提供的拷贝构造函数,会自动调用父类的拷贝构造函数,完成对从父类中继承过来的成员的初始化。

2.如果子类中显性的定义了拷贝构造函数,需要在子类的拷贝构造函数的初始化表中显性的显性的调用父类的拷贝构造函数,如果没有显性的调用父类的拷贝构造函数,默认会调用父类的无参构造来完成对从父类中继承过来的成员的初始化。

3.如果父类中没有指针成员,使用默认的拷贝构造函数是没问题的如果有指针成员,就需要自己手动给定拷贝构造函数,并且在子类的拷贝构造函数的初始化表中显性的调用父类的拷贝构造函数。----因为涉及深浅拷贝的问题

1.10 继承中的拷贝赋值函数

1.如果子类中没有显性的定义拷贝赋值函数,编译器会给子类提供一个默认的拷贝赋值函数,而且默认提供的拷贝赋值函数,会自动调用父类的拷贝赋值函数,完成对从父类中继承过来的成员的赋值。

2.如果子类中显性的定义了拷贝赋值函数,需要在子类的拷贝赋值函数的函数体中显性的调用父类的拷贝赋值函数,如果没有显性的调用父类的拷贝赋值函数,那么那些从父类中继承过来的成员的值保持不变。

3.如果父类中没有指针成员,使用默认的拷贝赋值函数是没问题的,如果有指针成员,就需要自己手动给定拷贝赋值函数,并且在子类的拷贝赋值函数的函数体中显性的调用父类的拷贝赋值函数。----因为涉及深浅拷贝的问题

2. 多重继承

2.1 概念

一个子类可以由多个直接父类共同派生,这种派生方式,叫做多重继承,子类中会继承每个基类的成员。

2.2 格式

class 子类名:继承方式1 父类名1, 继承方式2 父类名2 ... 继承方式n 父类名n{

//子类新扩充的成员

};

例:

class C:public B, public A{};

3. 虚继承

虚继承----是用来解决菱形继承的问题的。

菱形继承:

一个类由多个基类共同派生,而这多个基类又有共同的基类

此时,在汇聚子类中就会有多份公共基类的成员,访问起来有歧义,会报错。

 A 公共基类

 / \

B C 中间子类

 \ /

  D 汇聚子类

所谓的虚继承,就是在公共基类派生中间子类的时候加关键字 virtual

此时,这种继承方式就叫做虚继承。

虚继承的作用是保证中间子类派生汇聚子类的时候,只保留一份公共基类的成员。

采用了虚继承的方式时,公共基类被叫做 ----虚基类

例:

class A{
    protected:
        int v_a;
    public:
        A(){}
        A(int a):v_a(a){}
        ~A(){}
};

//生成中间子类时 加virtual
class B:virtual public A{
    protected:
        int v_b;
    public:
        B(){}
        //汇聚子类实例化对象时,此处调动虚基类的构造函数会被省略
        //但是还必须写,因为中间子类实例化对象时 需要用到
        B(int a, int b):A(a), v_b(b){}
        ~B(){}
};

//生成中间子类时 加virtual
class C:virtual public A{
    protected:
        int v_c;
    public:
        C(){}
        //汇聚子类实例化对象时,此处调动虚基类的构造函数会被省略
        //但是还必须写,因为中间子类实例化对象时 需要用到
        C(int a, int c):A(a), v_c(c){}
        ~C(){}
};

//生成汇聚子类时 不加virtual
class D:public B, public C{
    private:
        int v_d;
    public:
        D(){}
        //虚继承时,虚基类的成员由汇聚子类调用虚基类的构造函数来初始化
        D(int a, int b, int c, int d):A(a), B(a, b), C(a, c), v_d(d){}
        ~D(){}
        void show(){
            cout<<v_a<<endl;//因为采用了虚继承 访问虚基类的成员时不会有歧义
            cout<<v_b<<endl;
            cout<<v_c<<endl;
            cout<<v_d<<endl;
        }
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值