继承的作用域

880 阅读3分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第9天,点击查看活动详情

继承中的作用域

在继承体系中基类和派生类都有独立的作用域

1)子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏也叫重定 义。 (在子类成员函数中,可以使用域限定符进行访问 基类::基类成员 显示访问)

例子:

//父类
class Person
{
protected:
    int _num = 111;
};
//子类
class Student : public Person
{
public:
    void fun()
    {
        cout << _num << endl;
    }
protected:
    int _num = 999;
};
int main()
{
    Student s;
    s.fun(); //999   访问的是子类的_num成员变量
    return 0;
}

如果我们就是想访问父类的_num成员:可以使用 基类::基类成员 显示访问)

void fun()
{
    cout << Person::_num << endl; //指定域访问父类的_num成员
}

注意 : 如果是成员函数的隐藏,只需要函数名相同就构成隐藏,参数可以相同,也可以不相同

//父类
class Person
{
public:
    void fun(int x)
    {
        cout << x << endl;
    }
};
//子类
class Student : public Person
{
public:
    void fun(double x)
    {
        cout << x << endl;
    }
};
int main()
{
    Student s;
    s.fun(3.14);       //直接调用子类当中的成员函数fun
    //s.fun(20);//被隐藏了,调不动
    s.Person::fun(20); //通过指定类作用域,指定调用父类当中的成员函数fun
    return 0;
}

父类中的fun函数和子类中的fun函数并不是构成函数重载,二者是隐藏


函数重载的要求:函数要在同一作用域

注意在实际中在继承体系里面最好不要定义同名的成员。


问:父子有同名变量时,当子给父赋值的时候,切割用的是继承的变量还是子自己的变量?

答: 不管有没有同名的,只会切割从父类继承的成员

image-20220313083329926


4.派生类的默认成员函数

6个默认成员函数,“默认”的意思就是指我们不写,编译器会变我们自动生成一个,那么在派生类中,这几个 成员函数是如何生成的呢?

image-20220311211615146


派生类的重点的四个默认成员函数,如果我们不写,编译器会默认生成的会干些什么事情呢?

如果我们要写,要做些什么事情呢?


a.我们不写,默认生成的派生类的构造和析构:

  • 父类继承下来的(调用父类默认构造和析构进行处理)
  • 自己的(内置类型和自定义类型成员) ->跟普通类一样处理

b.我们不写,默认生成的派生类的拷贝构造和赋值重载:

  • 父类继承下来的(调用父类默认拷贝构造和赋值重载)
  • 自己的(内置类型和自定义类型成员) ->跟普通类一样处理

总结:原则:继承下来的,调用父类的进行处理,自己的就按普通类的规则进行处理


什么时候必须我们自己写?

  • 父类没有默认构造,需要我们自己显示写构造
  • 如果子类有资源需要释放,就需要自己显示写析构
  • 如果子类存在浅拷贝,就需要自己实现拷贝构造和赋值解决浅拷贝问题

例子

基类

class Person
{
public:
    //基类的构造函数
    Person(const char* name = "Mango")
        :_name(name)
    {
        cout << "Person(const char* name)" << endl;
    }
    //基类的拷贝构造函数
    Person(const Person& p)
        :_name(p._name)
    {
        cout<<"Person(const Person& p)"<<endl;
    }
    //基类的拷贝构造函数
    Person& operator=(const Person& p)
    {
        cout << "Person& opearator = (const Person & p)" << endl;
        if(this != &p)
        {
            _name = p._name;
        }
        return *this;
    }
    //基类的析构函数
    ~Person()
    {
        cout << "~Person()" << endl;
    }
protected:
    string _name;
};

派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认的构造函数,则必须在派生类构造函数的初始化列表阶段显示调用

//派生类的构造函数
Student(const char* name, int id)
    :Person(name)  //把父类当成一个整体,显示的初始化
        ,_id(id)
    {
        //调用父类构造函数初始化继承的父类的部分
        //再初始化派生类自己的成员
        cout << "Student()" << endl;
    }

派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化

//派生类的拷贝构造
//s2(s1),用s1父类的部分初始化s2父类的部分!
Student(const Student& s)
    :Person(s)  //把子类传给父类的引用,是一种切片的行为
        ,_id(s._id)
    {
        //调用父类拷贝构造函数用于拷贝构造继承的父类的部分
        //再拷贝构造派生类自己的成员
        cout << "Student(const Student& s)" << endl;
    }

image-20220313084959911

派生类的operator=必须要调用基类的operator=完成基类的复制

就近原则,子类的赋值重载函数把父类的隐藏了,所以是死循环不断调用自己,导致栈溢出.

所以需要指定作用域调用父类的赋值重载