MacLodge's Blogsince 2022
C/C++ / C++ 学习笔记

继承与派生

继承可以理解为一个类从另一个类获取成员变量和成员函数的过程,被继承的类称为基类,继承的类称为派生类。

本文目录(3 节)
  1. 1.1 继承的一般形式
  2. 1.2 继承方式
  3. 2.1 继承方式对成员的影响

1. 继承

继承可以理解为一个类从另一个类获取成员变量和成员函数的过程,被继承的类称为基类,继承的类称为派生类。

派生类除了拥有基类成员,还可以自定义新成员。

1.1 继承的一般形式

class 派生类名:[继承方式] 基类名{
    //派生类新增成员;
};
  • 示例

    #include<iostream>
    using namespace std;
    
    //基类 People
    class People{
        public:
            void setname(char *name);
            void setage(int age);
            char *getname();
            int getage();
        private:
            char *m_name;
            int m_age;
    };
    void People::setname(char *name){ m_name = name; }
    void People::setage(int age){ m_age = age; }
    char* People::getname(){ return m_name; }
    int People::getage(){ return m_age;}
    
    //派生类 Student
    class Student: public People{         //Student类继承People类,public方式;
        public:
            void setscore(float score);
            float getscore();
        private:
            float m_score;
    };
    void Student::setscore(float score){ m_score = score; }
    float Student::getscore(){ return m_score; }
    
    int main(){
        Student stu;
        stu.setname("小明");
        stu.setage(16);
        stu.setscore(95.5f);
        cout<<stu.getname()<<"的年龄是 "<<stu.getage()<<",成绩是 "<<stu.getscore()<<endl;
    
        return 0;
    }

1.2 继承方式

有三种:public(公有)、private(私有)和 protected(受保护),默认 private。

类成员的访问权限:public > protected > private

其中,protected 成员和 private 成员类似,也不能通过对象访问;但是在继承的时候,基类的 protected 成员可以在派生类中使用,但是 private 成员不行。

2.1 继承方式对成员的影响

继承方式/基类成员 public成员 proteced成员 private成员
public 继承 public proteced 不可见
proteced 继承 proteced proteced 不可见
private 继承 private private 不可见

2. 继承中的名字遮蔽

如果派生类中的成员和基类中的成员重名,那么会遮蔽从基类继承过来的成员,即使用该成员时实际上使用的是派生类中的成员。

因此,基类和派生类中的同名函数不构成重载。

例如:

``` C++
#include<iostream>
using namespace std;

//基类Base
class Base{
public:
    void func();
    void func(int);
};
void Base::func(){ cout<<"Base::func()"<<endl; }
void Base::func(int a){ cout<<"Base::func(int)"<<endl; }

//派生类Derived
class Derived: public Base{
public:
    void func(char *);
    void func(bool);
};
void Derived::func(char *str){ cout<<"Derived::func(char *)"<<endl; }
void Derived::func(bool is){ cout<<"Derived::func(bool)"<<endl; }

int main(){
    Derived d;
    d.func("hello C++");
    d.func(true);
//    d.func();  //compile error
//    d.func(10);  //compile error
    d.Base::func();
    d.Base::func(100);

    return 0;
}
```

上例中,构成重载的只是 Base 类的两个 func 或者 Derive 类的两个 func。

所以要是想访问基类的同名函数,需要指定作用域,如 d.Base::func();、d.Base::func(100); 两行所示。

3. 派生类的构造函数

类的构造函数不能被继承,所以在初始化基类的成员变量时,是在派生类的构造函数中调用基类的构造函数来完成初始化。

#include<iostream>
using namespace std;
 
//基类People
class People{
    protected:
        char *m_name;
        int m_age;
    public:
        People(char*, int);
};
People::People(char *name, int age): m_name(name), m_age(age){}
 
//派生类Student
class Student: public People{
    private:
        float m_score;
    public:
        Student(char *name, int age, float score);
        void display();
};

//People(name, age)就是调用基类的构造函数
Student::Student(char *name, int age, float score): People(name, age), m_score(score){ }
void Student::display(){
    cout<<m_name<<"的年龄是"<<m_age<<",成绩是"<<m_score<<"。"<<endl;
}

int main(){
    Student stu("小明", 16, 90.5);
    stu.display();
 
    return 0;
}

People(name, age) 就是调用基类的构造函数,并将 name 和 age 作为实参传递给它;

m_score(score) 是派生类的参数初始化列表,它们之间用逗号隔开。

注:多层派生中,派生类的构造函数中只能调用直接基类的构造函数,不能调用间接基类的。

同理,析构函数也是不能被继承的,并且因为每个类只有一个析构函数,所以派生类的析构函数不需要显式地调用基类的构造函数,编译器会自动选择。

另外,析构函数的执行顺序和构造函数的执行顺序也刚好相反:

  • 创建派生类对象时,构造函数的执行顺序和继承顺序相同,即先执行基类构造函数,再执行派生类构造函数。

  • 而销毁派生类对象时,析构函数的执行顺序和继承顺序相反,即先执行派生类析构函数,再执行基类析构函数。

4. C++指针可以突破权限限制

万能的指针是可以穿透地址,随意修改类的成员,即使是 private、const 成员也无济于事。

5. C++向上转型

将派生类赋值给基类

C/C++ 中对不同类型的变量进行赋值时,编译器会将其转化成同类型,然后再赋值。

同理,类也是一种数据类型,也可以进行数据转化,这种转化一般只发生在基类和派生类之间。

将派生类赋值给基类称为向上转型,将基类赋值给派生类称为向下转型。

向上转型很安全,由编译器完成,向下转型有风险,需要程序员手动干预。

#include <iostream>
using namespace std;
 
//基类
class A{
    public:
        A(int a);
        void display();
        int m_a;
};
A::A(int a): m_a(a){ }
void A::display(){
    cout<<"Class A: m_a="<<m_a<<endl;
}
 
//派生类
class B: public A{
    public:
        B(int a, int b);
        void display();
        int m_b;
};
B::B(int a, int b): A(a), m_b(b){ }
void B::display(){
    cout<<"Class B: m_a="<<m_a<<", m_b="<<m_b<<endl;
}
 
int main(){
    A a(10);
    B b(66, 99);
    //赋值前
    a.display();
    b.display();
    cout<<"--------------"<<endl;
    //赋值后
    a = b;
    a.display();
    b.display();
 
    return 0;
}
  • 运行结果:
Class A: m_a=10
Class B: m_a=66, m_b=99
----------------------------
Class A: m_a=66
Class B: m_a=66, m_b=99

将派生类对象赋值给基类对象时,会舍弃派生类新增的成员:

2018110115574117

注:同一基类的不同派生类对象之间也不能赋值

署名-非商业性使用-相同方式共享 4.0 国际(CC BY-NC-SA 4.0)
本文可自由转载与修改,但需保留作者署名与出处链接,不得用于商业用途,且衍生作品需以相同方式共享。 转载请注明:MacLodge's Blog · 继承与派生
Esc