C++ inheritance (detailed explanation)

1. The concept of inheritance

1.1. The concept of inheritance

The inheritance mechanism is the most important means of object-oriented programming to make code reusable. It allows programmers to extend and add functions while maintaining the characteristics of the original class, thus generating new classes, called derived classes. Inheritance presents the hierarchical structure of object-oriented programming and reflects the cognitive process from simple to complex. In the past, the reuse we came into contact with was function reuse, and inheritance was reuse at the class design level.

class Person
{
public:
    void Print()
    {
        cout << "name:" << _name << endl;
        cout << "age:" << _age << endl;
    }
protected:
    string _name = "peter"; // 姓名
    int _age = 18; // 年龄
};
class Student : public Person
{
    protected:
    int _stuid; // 学号
};
class Teacher : public Person
{
protected:
    int _jobid; // 工号
};
int main()
{
    Student s;
    Teacher t;
    s.Print();
    t.Print();
    return 0;
}

 After inheritance, the members (member functions + member variables) of the parent class's Person will become part of the subclass. This reflects that
Student and Teacher reuse members of Person. Next, we use the monitoring window to view the Student and Teacher objects, and we can see the reuse of variables. You can see the reuse of member functions by calling Print.

1.2. Definition of inheritance

1.2.1. Define the format

Below we see that Person is the parent class, also called the base class. Student is a subclass, also called a derived class
 

class Student:public Person
{
public:
    int _stuid; //学号
    int _major; //专业
}

1.2.2 Inheritance relationship and access qualifiers

1.2.3 Changes in access methods of inherited base class members

Summarize:

1. The private members of the base class are not visible in the derived class no matter how they are inherited. Invisible here means that the private members of the base class are still inherited into the derived class object, but the syntax restricts the derived class object from accessing it whether inside or outside the class.


2. Private members of the base class cannot be accessed in the derived class. If the base class member does not want to be accessed directly outside the class, but needs to be accessible in the derived class, it is defined as protected. It can be seen that the protected member qualifier appears because of inheritance.


3. In fact, if we summarize the above table, we will find that the private members of the base class are not visible in the subclass. The access method of other members of the base class in the subclass == Min (member's access qualifier in the base class, inheritance method), public > protected
> private.


4. When using the keyword class, the default inheritance method is private, and when using struct, the default inheritance method is public. However,
is best to clearly write out the inheritance method.


5. In actual applications, public inheritance is generally used, and protected/private inheritance is rarely used, and it is not recommended
to use protected/private inheritance because protected/private inheritance is not recommended. The members that come down can only be used in the derived class. In practice, the expansion and maintenance are not strong.

2. Assignment and conversion of base class and derived class objects

1. Derived class objects can be assigned to base class objects/base class pointers/base class references. There is a vivid term here called slicing or cutting. It means cutting off the parent class part of the derived class and assigning it to it.


2. Base class objects cannot be assigned to derived class objects.


3. The pointer or reference of the base class can be assigned to the pointer or reference of the derived class through forced type conversion. But a base class pointer must be safe only when it points to a derived class object. If the base class here is a polymorphic type, you can use dynamic_cast of RTTI (RunTime Type Information) to identify and perform safe conversion.

                       

class Person
{
protected :
    string _name; // 姓名
    string _sex; // 性别
    int _age; // 年龄
};
class Student : public Person
{
public :
    int _No ; // 学号
};
void Test ()
{
    Student sobj ;
    // 1.子类对象可以赋值给父类对象/指针/引用
    Person pobj = sobj ;
    Person* pp = &sobj;
    Person& rp = sobj;
    //2.基类对象不能赋值给派生类对象
    sobj = pobj;
    // 3.基类的指针可以通过强制类型转换赋值给派生类的指针
    pp = &sobj
    Student* ps1 = (Student*)pp; // 这种情况转换时可以的。
    ps1->_No = 10;
    pp = &pobj;
    Student* ps2 = (Student*)pp; // 这种情况转换时虽然可以,但是会存在越界访问的问
题
    ps2->_No = 10;
}

3. Scope in inheritance

1. In the inheritance system, base classes and derived classes have independent scopes.


2. If there are members with the same name in the subclass and the parent class, the subclass members will block the parent class from direct access to the members with the same name. This situation is called hiding, also called redefinition. (In subclass member functions, you can use base class::base class member explicit access)


3. It should be noted that if it is the hiding of a member function, only the function names need to be the same to constitute hiding.


4. Note that in practice it is best not to define members with the same name in the inheritance system.

// Student的_num和Person的_num构成隐藏关系,可以看出这样代码虽然能跑,但是非常容易混淆
class Person
{
protected :
    string _name = "小李子"; // 姓名
    int _num = 111; // 身份证号
};
class Student : public Person
{
public:
void Print()
{
    cout<<" 姓名:"<<_name<< endl;
    cout<<" 身份证号:"<<Person::_num<< endl;
    cout<<" 学号:"<<_num<<endl;
}
protected:
    int _num = 999; // 学号
};
void Test()
{
    Student s1;
    s1.Print();
};
// B中的fun和A中的fun不是构成重载,因为不是在同一作用域
// B中的fun和A中的fun构成隐藏,成员函数满足函数名相同就构成隐藏。
class A
{
public:
    void fun()
    {
        cout << "func()" << endl;
    }
};
class B : public A
{
public:
    void fun(int i)
    {
        A::fun();
        cout << "func(int i)->" <<i<<endl;
    }
};
void Test()
{
    B b;
    b.fun(10);
};

4. Default member functions of derived classes

How are default member functions generated in derived classes?

1. The constructor of the derived class must call the constructor of the base class to initialize that part of the members of the base class. If the base class does not have a default constructor, it must be called explicitly during the initialization list phase of the derived class constructor.


2. The copy constructor of the derived class must call the copy constructor of the base class to complete the copy initialization of the base class.


3. The operator= of the derived class must call the operator= of the base class to complete the copy of the base class.


4. The destructor of the derived class will automatically call the destructor of the base class to clean up the base class members after being called. Because this can ensure that the derived class object first cleans up the derived class members and then cleans up the base class members in order.


5. When initializing a derived class object, first call the base class constructor and then the derived class constructor.


6. When destructing and cleaning up the derived class object, first call the destructor of the derived class and then call the destructor of the base class.


7. Because the destructor in some subsequent scenarios needs to be rewritten, one of the conditions for rewriting is that the function names are the same. Then the compiler will perform special processing on the destructor name and process it into destructor(). Therefore, when the parent class destructor does not add virtual, the subclass destructor and the parent class destructor form a hidden relationship.

class Person
{
public :
    Person(const char* name = "peter")
    : _name(name )
    {
        cout<<"Person()" <<endl;
    }
    Person(const Person& p)
    : _name(p._name)
    {
        cout<<"Person(const Person& p)" <<endl;
    }
    Person& operator=(const Person& p )
    {
        cout<<"Person operator=(const Person& p)"<< endl;
        if (this != &p)
        _name = p ._name;
        return *this ;
    }
    ~Person()
    {
        cout<<"~Person()" <<endl;
    }
protected :
    string _name ; // 姓名
};

class Student : public Person
{
public :
    Student(const char* name, int num)
    : Person(name )
    , _num(num )
    {
        cout<<"Student()" <<endl;
    }
    Student(const Student& s)
    : Person(s)
    , _num(s ._num)
    {
        cout<<"Student(const Student& s)" <<endl ;
    }
    Student& operator = (const Student& s )
    {
        cout<<"Student& operator= (const Student& s)"<< endl;
        if (this != &s)
        {
            Person::operator =(s);
            _num = s ._num;
        }
        return *this ;
    }
    ~Student()
    {
        cout<<"~Student()" <<endl;
    }
protected :
    int _num ; //学号
};
void Test ()
{
    Student s1 ("jack", 18);
    Student s2 (s1);
    Student s3 ("rose", 17);
    s1 = s3 ;
}

5. Inheritance and Friendship

Friend relationships cannot be inherited, which means that base class friends cannot access private and protected members of subclasses.
 

class Student;
class Person
{
public:
    friend void Display(const Person& p, const Student& s);
protected:
    string _name; // 姓名
};

class Student : public Person
{
protected:
    int _stuNum; // 学号
};

void Display(const Person& p, const Student& s)
{
    cout << p._name << endl;
    cout << s._stuNum << endl;
}
void main()
{
    Person p;
    Student s;
    Display(p, s);
}

6. Inheritance and static members

The base class defines static members, and there is only one such member in the entire inheritance system. No matter how many sub-classes are derived, there will only be one static member instance.

class Person
{
public :
    Person () {++ _count ;}
protected :
    string _name ; // 姓名
public :
    static int _count; // 统计人的个数。
};

int Person :: _count = 0;
class Student : public Person
{
    protected :
    int _stuNum ; // 学号
};
class Graduate : public Student
{
protected :
    string _seminarCourse ; // 研究科目
};

void TestPerson()
{
    Student s1 ;
    Student s2 ;
    Student s3 ;
    Graduate s4 ;
    cout <<" 人数 :"<< Person ::_count << endl;
    Student ::_count = 0;
    cout <<" 人数 :"<< Person ::_count << endl;
}

7. Diamond inheritance and diamond virtual inheritance

Single inheritance: When a subclass has only one direct parent class, this inheritance relationship is called single inheritance.

Multiple inheritance: When a subclass has two or more direct parent classes, this inheritance relationship is called multiple inheritance.

                  

Diamond inheritance: Diamond inheritance is a special case of multiple inheritance

The problem of diamond inheritance: From the object member model construction below, it can be seen that diamond inheritance has data redundancy and The problem of ambiguity.
There will be two copies of the Person member in the Assistant object

Ambiguity problems can be avoided by specifying the scope qualification operator::specify the scope

The virtual keyword declares virtual inheritance to avoid data redundancy problems.

class Person
{
public :
    string _name ; // 姓名
};
class Student : public Person
{
    protected :
    int _num ; //学号
};

class Teacher : public Person
{
protected :
    int _id ; // 职工编号
};

class Assistant : public Student, public Teacher
{
protected :
    string _majorCourse ; // 主修课程
};

void Test ()
{
// 这样会有二义性无法明确知道访问的是哪一个
    Assistant a ;
    a._name = "peter";
// 需要显示指定访问哪个父类的成员可以解决二义性问题,但是数据冗余问题无法解决
    a.Student::_name = "xxx";
    a.Teacher::_name = "yyy";
}

Virtual inheritance can solve the ambiguity and data redundancy problems of diamond inheritance. As shown in the inheritance relationship above, using virtual inheritance when Student and
Teacher inherit from Person can solve the problem.

class Person
{
public :
    string _name ; // 姓名
};
class Student : virtual public Person
{
protected :
    int _num ; //学号
};
class Teacher : virtual public Person
{
protected :
    int _id ; // 职工编号
};
class Assistant : public Student, public Teacher
{
protected :
    string _majorCourse ; // 主修课程
};
void Test ()
{
    Assistant a ;
    a._name = "peter";
}

8. Principle of virtual inheritance

class A
{
public:
    int _a;
};
// class B : public A
class B : virtual public A
{
public:
    int _b;
};
// class C : public A
class C : virtual public A
{
public:
    int _c;
};
class D : public B, public C
{
public:
    int _d;
};
int main()
{
    D d;
    d.B::_a = 1;
    d.C::_a = 2;
    d._b = 3;
    d._c = 4;
    d._d = 5;
    return 0;
}

The following figure is the memory object member model of diamond inheritance: here you can see data redundancy

            

We can find that the common part A of B and C is extracted to the bottom position. This A belongs to both B and C. It can also be accessed using d._a1 alone. While storing the member variables of B and C, two pointer variables. These two pointers become virtual base pointers. Each of the two virtual base pointers points to a table that stores the offsets of objects B and C relative to A. This table is called a virtual base table. Through A Find A by using the offset relative to the storage locations of B and C.

Guess you like

Origin blog.csdn.net/m0_69323023/article/details/134785028