[C++ Learning] Inheritance

Table of contents

1. The concept and definition of inheritance

1. The concept of inheritance

2. Definition of inheritance

2.1 Define format

2.2 Inheritance relationship and access qualifiers

2.3 Changes in access methods of inherited base class members

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

3. Scope in inheritance

4. Default member functions of derived classes

5. Inheritance and Friendship

6. Inheritance and static members

7. Complex diamond inheritance and diamond virtual inheritance

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

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

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

4. The principle of virtual inheritance to solve data redundancy and ambiguity

5. The following is an explanation of the principle of diamond virtual inheritance of the above Person relationship:

8. Summary and reflection on inheritance


1. The concept and definition of inheritance

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, thereby 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 = "popo"; // 姓名
    int _age = 18;  // 年龄 
};

/*
继承后父类的Person的成员(成员函数+成员变量)都会变成子类的一部分。
这里体现出了Student和Teacher复用了Person的成员。
下面我们使用监视窗口查看Student和Teacher对象,可以看到变量的复用。
调用Print可以看到成员函数的复用。 
*/
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; 
}

2. Definition of inheritance

2.1 Define format

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

2.2 Inheritance relationship and access qualifiers

Inheritance method: public inheritance, private inheritance, protected inheritance

Access qualifiers: public access, private inherited access, protected access

2.3 Changes in access methods of inherited base class members

Class members/inheritance methods public inheritance protected inheritance private inheritance
public members of base class public members of base class protected members of derived classes Private members of derived classes
protected members of the base class protected members of the base class protected members of derived classes Private members of derived classes
private members of base class Not visible in derived classes Not visible in derived classes Not visible in derived classes

Summarize:

  1. Private members of a base class are not visible in derived classes 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 mode of other members of the base class in the subclass == Min (member's access qualifier in the base class, inheritance mode), public > protected > private.
  4. The default inheritance method when using the keyword class is private, and the default inheritance method when using struct is public, but it is best to write the inheritance method explicitly.
  5. In practical applications, public inheritance is generally used, and protected/private inheritance is rarely used. The use of protected/private inheritance is also not recommended, because members inherited from protected/private can only be used in derived classes. In practice, Extended maintenance is not strong.
// 实例演示三种继承关系下基类成员的各类型成员访问关系的变化   
class Person 
{ 
public :    
    void Print ()    
    {        
        cout<<_name <<endl;
    } 
protected :
    string _name ; // 姓名 
private :
    int _age ;   // 年龄 
};

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

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

  • 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.
  • Base class objects cannot be assigned to derived class objects.
  • A pointer or reference from a base class can be assigned to a pointer or reference from a derived class through cast. 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 members for 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 = "popo"; // 姓名
    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

        There are 6 default member functions. "Default" means that if we don't write them, the compiler will automatically generate one for us. So how are these member functions generated in the derived class?

  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, the base class constructor is called first and then the derived class constructor is called.
  6. To clean up the destructor of derived class objects, 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 change it to 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

        If the base class defines static members, there will be only one such member in the entire inheritance system. No matter how many subclasses are derived, there is only 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. Complex diamond inheritance and diamond virtual inheritance

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

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

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

        Problems with diamond inheritance: From the object member model construction below, we can see that diamond inheritance has problems with data redundancy and ambiguity. There will be two copies of the Person member in the Assistant object.

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 Person can solve the problem. It should be noted that virtual inheritance should not be used elsewhere.

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"; 
}

4. The principle of virtual inheritance to solve data redundancy and ambiguity

        In order to study the principle of virtual inheritance, we gave a simplified diamond inheritance system, and then used the memory window to observe the model of object members.
 

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

        The following figure is the memory object member model of diamond virtual inheritance: here we can analyze that in the D object, A is placed at the bottom of the object composition. This A belongs to both B and C, so how do B and C find the common A? ? Here is a table pointed to by the two pointers B and C. These two pointers are called virtual base table pointers, and these two tables are called virtual base tables. The offset stored in the virtual base table. A below can be found by the offset.

/*为什么D中B和C部分要去找属于自己的A?
看看当下面的赋值发生时,d是不是要去找出B/C成员中的A才能赋值过去?*/
    D d; 
    B b = d; 
    C c = d;

5. The following is an explanation of the principle of diamond virtual inheritance of the above Person relationship:

8. Summary and reflection on inheritance

  1. Many people say that C++ syntax is complicated, but in fact multiple inheritance is a manifestation of it. With multiple inheritance, there is diamond inheritance, and with diamond inheritance, there is diamond virtual inheritance. The underlying implementation is very complicated. Therefore, it is generally not recommended to design multiple inheritance, and you must not design diamond inheritance. Otherwise, there will be problems in complexity and performance.
  2. Multiple inheritance can be considered one of the shortcomings of C++. Many later OO languages ​​do not have multiple inheritance, such as Java.
  3. Inheritance and composition:
    1. Public inheritance is an is-a relationship. That is to say, every derived class object is a base class object.
    2. Combination is a has-a relationship. Suppose B combines A, and there is an A object in every B object.
    3. Prefer object composition over class inheritance .
    4. Inheritance allows you to define the implementation of a derived class based on the implementation of the base class. This kind of reuse by generating derived classes is often called white-box reuse. The term "white box" refers to visibility: in inheritance, the internal details of a base class are visible to subclasses. Inheritance destroys the encapsulation of the base class to a certain extent, and changes in the base class have a great impact on the derived class. The dependence between derived classes and base classes is very strong and the degree of coupling is high.
    5. Object composition is an alternative to class inheritance for reuse. New and more complex functionality can be obtained by assembling or combining objects. Object composition requires that the objects being composed have well-defined interfaces. This style of reuse is called black-box reuse because the internal details of the object are not visible. Objects only appear as "black boxes". There is no strong dependency between the combined classes, and the degree of coupling is low. Preferring object composition helps you keep each class encapsulated.
    6. Actually try to use as many combinations as possible. The combination has low coupling and good code maintainability. However, inheritance also has its place. Some relationships are suitable for inheritance, so use inheritance. In addition, inheritance is also necessary to achieve polymorphism. The relationship between classes can be inherited or combined. Just use combination.
// Car和BMW Car和Benz构成is-a的关系   
class Car
{   
protected:
    string _colour = "白色";  // 颜色
    string _num = "陕ABIT00";    // 车牌号   
};

class BMW : public Car
{   
public:
    void Drive()
    {
        cout << "好开-操控" << endl;
    }   
};

class Benz : public Car
{   
public: 
    void Drive() 
    {
        cout << "好坐-舒适" << endl;
    }   
};      

// Tire和Car构成has-a的关系      
class Tire
{   
protected:
    string _brand = "Michelin";  // 品牌
    size_t _size = 17;    // 尺寸      
};
      
class Car
{   
protected:
    string _colour = "白色";      // 颜色
    string _num = "陕ABIT00";     // 车牌号    
    Tire _t;                     // 轮胎   
};   

Guess you like

Origin blog.csdn.net/weixin_44906102/article/details/132942638