[C++-Lernen] Vererbung

Inhaltsverzeichnis

1. Das Konzept und die Definition der Vererbung

1. Das Konzept der Vererbung

2. Definition von Vererbung

2.1 Format definieren

2.2 Vererbungsbeziehung und Zugriffsqualifizierer

2.3 Änderungen in den Zugriffsmethoden geerbter Basisklassenmitglieder

2. Zuweisung und Konvertierung von Basisklassen- und abgeleiteten Klassenobjekten

3. Umfang in der Vererbung

4. Standard-Memberfunktionen abgeleiteter Klassen

5. Erbe und Freundschaft

6. Vererbung und statische Mitglieder

7. Komplexe Diamantenvererbung und virtuelle Diamantenvererbung

1. Einzelvererbung: Wenn eine Unterklasse nur eine direkte übergeordnete Klasse hat, wird diese Vererbungsbeziehung als Einzelvererbung bezeichnet.

2. Mehrfachvererbung: Wenn eine Unterklasse zwei oder mehr direkte übergeordnete Klassen hat, wird diese Vererbungsbeziehung als Mehrfachvererbung bezeichnet.

3. Diamant-Vererbung: Die Diamant-Vererbung ist ein Sonderfall der Mehrfachvererbung.

4. Das Prinzip der virtuellen Vererbung zur Lösung von Datenredundanz und -mehrdeutigkeit

5. Im Folgenden wird das Prinzip der virtuellen Diamantenvererbung der oben genannten Personenbeziehung erläutert:

8. Zusammenfassung und Reflexion zum Thema Vererbung


1. Das Konzept und die Definition der Vererbung

1. Das Konzept der Vererbung

        Der Vererbungsmechanismus ist das wichtigste Mittel der objektorientierten Programmierung, um Code wiederverwendbar zu machen. Er ermöglicht es Programmierern, Funktionen zu erweitern und hinzuzufügen und dabei die Eigenschaften der ursprünglichen Klasse beizubehalten und so neue Klassen, sogenannte abgeleitete Klassen, zu generieren. Vererbung stellt die hierarchische Struktur der objektorientierten Programmierung dar und spiegelt den kognitiven Prozess von einfach bis komplex wider. In der Vergangenheit war die Wiederverwendung, mit der wir in Kontakt kamen, die Wiederverwendung von Funktionen, und die Vererbung war die Wiederverwendung auf Klassendesignebene.

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 von Vererbung

2.1 Format definieren

        Unten sehen wir, dass Person die übergeordnete Klasse ist, auch Basisklasse genannt. Student ist eine Unterklasse, auch abgeleitete Klasse genannt.

2.2 Vererbungsbeziehung und Zugriffsqualifizierer

Vererbungsmethode: öffentliche Vererbung, private Vererbung, geschützte Vererbung

Zugriffsqualifizierer: öffentlicher Zugriff, privater geerbter Zugriff, geschützter Zugriff

2.3 Änderungen in den Zugriffsmethoden geerbter Basisklassenmitglieder

Klassenmitglieder/Vererbungsmethoden öffentliches Erbe geschütztes Erbe privates Erbe
öffentliche Mitglieder der Basisklasse öffentliche Mitglieder der Basisklasse geschützte Mitglieder abgeleiteter Klassen Private Mitglieder abgeleiteter Klassen
geschützte Mitglieder der Basisklasse geschützte Mitglieder der Basisklasse geschützte Mitglieder abgeleiteter Klassen Private Mitglieder abgeleiteter Klassen
private Mitglieder der Basisklasse In abgeleiteten Klassen nicht sichtbar In abgeleiteten Klassen nicht sichtbar In abgeleiteten Klassen nicht sichtbar

Zusammenfassen:

  1. Private Mitglieder einer Basisklasse sind in abgeleiteten Klassen nicht sichtbar, unabhängig davon, wie sie vererbt werden. Unsichtbar bedeutet hier, dass die privaten Mitglieder der Basisklasse weiterhin in das abgeleitete Klassenobjekt vererbt werden, die Syntax jedoch den Zugriff des abgeleiteten Klassenobjekts darauf einschränkt, egal ob innerhalb oder außerhalb der Klasse.
  2. Auf private Mitglieder der Basisklasse kann in der abgeleiteten Klasse nicht zugegriffen werden. Wenn auf das Basisklassenmitglied nicht direkt außerhalb der Klasse zugegriffen werden soll, sondern in der abgeleiteten Klasse darauf zugegriffen werden muss, wird es als geschützt definiert. Es ist ersichtlich, dass das geschützte Mitgliedsqualifikationsmerkmal aufgrund der Vererbung angezeigt wird.
  3. Wenn wir die obige Tabelle zusammenfassen, werden wir tatsächlich feststellen, dass die privaten Mitglieder der Basisklasse in der Unterklasse nicht sichtbar sind. Der Zugriffsmodus anderer Mitglieder der Basisklasse in der Unterklasse == Min (Zugriffsqualifikator des Mitglieds in der Basisklasse, Vererbungsmodus), öffentlich > geschützt > privat.
  4. Die Standardvererbungsmethode bei Verwendung des Schlüsselworts class ist privat, und die Standardvererbungsmethode bei Verwendung von struct ist öffentlich. Es ist jedoch am besten, die Vererbungsmethode explizit zu schreiben.
  5. In praktischen Anwendungen wird im Allgemeinen die öffentliche Vererbung verwendet, und die geschützte/private Vererbung wird selten verwendet. Die Verwendung der geschützten/privaten Vererbung wird ebenfalls nicht empfohlen, da von geschützt/privat geerbte Mitglieder nur in abgeleiteten Klassen verwendet werden können. In der Praxis erweitert Die Wartung ist nicht stark.
// 实例演示三种继承关系下基类成员的各类型成员访问关系的变化   
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. Zuweisung und Konvertierung von Basisklassen- und abgeleiteten Klassenobjekten

  • Abgeleitete Klassenobjekte können Basisklassenobjekten/Basisklassenzeigern/Basisklassenreferenzen zugewiesen werden . Es gibt hier einen anschaulichen Begriff namens „Slicen“ oder „Schneiden“. Dies bedeutet, dass der übergeordnete Klassenteil von der abgeleiteten Klasse abgeschnitten und dieser zugewiesen wird.
  • Basisklassenobjekte können nicht abgeleiteten Klassenobjekten zugewiesen werden.
  • Ein Zeiger oder eine Referenz aus einer Basisklasse kann durch Umwandlung einem Zeiger oder einer Referenz aus einer abgeleiteten Klasse zugewiesen werden. Ein Basisklassenzeiger muss jedoch nur dann sicher sein, wenn er auf ein abgeleitetes Klassenobjekt zeigt. Wenn die Basisklasse hier ein polymorpher Typ ist, können Sie Dynamic_cast von RTTI (RunTime Type Information) verwenden , um eine sichere Konvertierung zu identifizieren und durchzuführen.

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. Umfang in der Vererbung

  1. Im Vererbungssystem haben Basisklassen und abgeleitete Klassen unabhängige Bereiche .
  2. Wenn in der Unterklasse und der übergeordneten Klasse Mitglieder mit demselben Namen vorhanden sind, blockieren die Unterklassenmitglieder den direkten Zugriff der übergeordneten Klasse auf die gleichnamigen Mitglieder. Diese Situation wird als Ausblenden oder auch als Neudefinition bezeichnet . (In Unterklassen-Memberfunktionen können Sie Basisklassenmitglieder für den expliziten Zugriff verwenden.)
  3. Es ist zu beachten, dass beim Ausblenden einer Mitgliedsfunktion nur die Funktionsnamen identisch sein müssen, um ein Ausblenden zu bewirken.
  4. Beachten Sie, dass es in der Praxis am besten ist, im Vererbungssystem keine Elemente mit demselben Namen zu definieren .
// 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. Standard-Memberfunktionen abgeleiteter Klassen

        Es gibt 6 Standard-Memberfunktionen. „Standard“ bedeutet, dass der Compiler automatisch eine für uns generiert, wenn wir sie nicht schreiben. Wie werden diese Memberfunktionen also in der abgeleiteten Klasse generiert?

  1. Der Konstruktor der abgeleiteten Klasse muss den Konstruktor der Basisklasse aufrufen, um diesen Teil der Mitglieder der Basisklasse zu initialisieren. Wenn die Basisklasse keinen Standardkonstruktor hat, muss dieser explizit während der Initialisierungslistenphase des abgeleiteten Klassenkonstruktors aufgerufen werden.
  2. Der Kopierkonstruktor der abgeleiteten Klasse muss den Kopierkonstruktor der Basisklasse aufrufen, um die Kopierinitialisierung der Basisklasse abzuschließen.
  3. Der Operator = der abgeleiteten Klasse muss den Operator = der Basisklasse aufrufen, um die Kopie der Basisklasse abzuschließen.
  4. Der Destruktor der abgeleiteten Klasse ruft nach dem Aufruf automatisch den Destruktor der Basisklasse auf, um die Mitglieder der Basisklasse zu bereinigen. Denn dadurch kann sichergestellt werden, dass das abgeleitete Klassenobjekt zuerst die abgeleiteten Klassenmitglieder und dann die Basisklassenmitglieder der Reihe nach bereinigt.
  5. Beim Initialisieren eines abgeleiteten Klassenobjekts wird zuerst der Basisklassenkonstruktor und dann der abgeleitete Klassenkonstruktor aufgerufen.
  6. Um den Destruktor abgeleiteter Klassenobjekte zu bereinigen, rufen Sie zuerst den Destruktor der abgeleiteten Klasse und dann den Destruktor der Basisklasse auf.
  7. Da der Destruktor in einigen nachfolgenden Szenarien neu geschrieben werden muss, ist eine der Bedingungen für das Neuschreiben, dass die Funktionsnamen identisch sind. Anschließend führt der Compiler eine spezielle Verarbeitung für den Destruktornamen durch und ändert ihn in destructor (). Wenn der Destruktor der übergeordneten Klasse also nicht virtuell hinzufügt, bilden der Destruktor der Unterklasse und der Destruktor der übergeordneten Klasse eine versteckte Beziehung.

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. Erbe und Freundschaft

Freundbeziehungen können nicht vererbt werden, was bedeutet, dass Freunde der Basisklasse nicht auf private und geschützte Mitglieder von Unterklassen zugreifen können.

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. Vererbung und statische Mitglieder

        Wenn die Basisklasse statische Mitglieder definiert, gibt es im gesamten Vererbungssystem nur ein solches Mitglied. Unabhängig davon, wie viele Unterklassen abgeleitet werden, gibt es nur eine statische Mitgliedsinstanz.

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. Komplexe Diamantenvererbung und virtuelle Diamantenvererbung

1. Einzelvererbung: Wenn eine Unterklasse nur eine direkte übergeordnete Klasse hat, wird diese Vererbungsbeziehung als Einzelvererbung bezeichnet.

2. Mehrfachvererbung: Wenn eine Unterklasse zwei oder mehr direkte übergeordnete Klassen hat, wird diese Vererbungsbeziehung als Mehrfachvererbung bezeichnet.

3. Diamant-Vererbung: Die Diamant-Vererbung ist ein Sonderfall der Mehrfachvererbung.

        Probleme mit der Rautenvererbung: Aus der Konstruktion des Objektmitgliedsmodells unten können wir ersehen, dass die Rautenvererbung Probleme mit Datenredundanz und Mehrdeutigkeit hat. Im Assistant-Objekt sind zwei Kopien des Person-Elements vorhanden.

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

        Die virtuelle Vererbung kann die Mehrdeutigkeits- und Datenredundanzprobleme der Diamantvererbung lösen. Wie in der Vererbungsbeziehung oben gezeigt, kann die Verwendung der virtuellen Vererbung, wenn Schüler und Lehrer Personen erben, das Problem lösen. Es ist zu beachten, dass die virtuelle Vererbung nicht anderweitig eingesetzt werden sollte.

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. Das Prinzip der virtuellen Vererbung zur Lösung von Datenredundanz und -mehrdeutigkeit

        Um das Prinzip der virtuellen Vererbung zu untersuchen, haben wir ein vereinfachtes Diamant-Vererbungssystem angegeben und dann das Speicherfenster verwendet, um das Modell der Objektmitglieder zu beobachten.
 

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

        Die folgende Abbildung zeigt das Speicherobjekt-Mitgliedsmodell der Diamantvererbung: Hier können Sie die Datenredundanz sehen

        Die folgende Abbildung ist das Speicherobjekt-Mitgliedsmodell der virtuellen Vererbung von Diamanten: Hier können wir analysieren, dass im D-Objekt A am unteren Rand der Objektzusammensetzung platziert ist. Dieses A gehört sowohl zu B als auch zu C, also wie tun B und C? Finden Sie das gemeinsame A? ? Hier ist eine Tabelle, auf die die beiden Zeiger B und C zeigen. Diese beiden Zeiger werden als virtuelle Basistabellenzeiger bezeichnet, und diese beiden Tabellen werden als virtuelle Basistabellen bezeichnet. Der in der virtuellen Basistabelle gespeicherte Offset. A unten kann durch den Versatz gefunden werden.

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

5. Im Folgenden wird das Prinzip der virtuellen Diamantenvererbung der oben genannten Personenbeziehung erläutert:

8. Zusammenfassung und Reflexion zum Thema Vererbung

  1. Viele Leute sagen, dass die C++-Syntax kompliziert ist, aber in Wirklichkeit ist die Mehrfachvererbung ein Ausdruck davon. Bei der Mehrfachvererbung handelt es sich um eine Diamantenvererbung und bei der Diamantenvererbung um eine virtuelle Diamantenvererbung. Die zugrunde liegende Implementierung ist sehr kompliziert. Daher wird im Allgemeinen nicht empfohlen, eine Mehrfachvererbung zu entwerfen, und Sie dürfen keine Diamantvererbung entwerfen. Andernfalls kommt es zu Komplexitäts- und Performance-Problemen.
  2. Mehrfachvererbung kann als einer der Nachteile von C++ angesehen werden. Viele spätere OO-Sprachen haben keine Mehrfachvererbung, wie beispielsweise Java.
  3. Vererbung und Zusammensetzung:
    1. Öffentliche Vererbung ist eine Ist-Ein-Beziehung. Das heißt, jedes abgeleitete Klassenobjekt ist ein Basisklassenobjekt.
    2. Kombination ist eine Hat-ein-Beziehung. Angenommen, B kombiniert A und in jedem B-Objekt gibt es ein A-Objekt.
    3. Bevorzugen Sie die Objektzusammensetzung gegenüber der Klassenvererbung .
    4. Durch Vererbung können Sie die Implementierung einer abgeleiteten Klasse basierend auf der Implementierung der Basisklasse definieren. Diese Art der Wiederverwendung durch Generierung abgeleiteter Klassen wird oft als White-Box-Wiederverwendung bezeichnet. Der Begriff „White Box“ bezieht sich auf Sichtbarkeit: Bei der Vererbung sind die internen Details einer Basisklasse für Unterklassen sichtbar. Durch die Vererbung wird die Kapselung der Basisklasse bis zu einem gewissen Grad zerstört, und Änderungen in der Basisklasse haben große Auswirkungen auf die abgeleitete Klasse. Die Abhängigkeit zwischen abgeleiteten Klassen und Basisklassen ist sehr stark und der Kopplungsgrad hoch.
    5. Die Objektkomposition ist eine Alternative zur Klassenvererbung zur Wiederverwendung. Durch das Zusammenfügen oder Kombinieren von Objekten können neue und komplexere Funktionen erzielt werden. Die Objektkomposition erfordert, dass die zu komponierenden Objekte über klar definierte Schnittstellen verfügen. Diese Art der Wiederverwendung wird Black-Box-Wiederverwendung genannt, da die internen Details des Objekts nicht sichtbar sind. Objekte erscheinen nur als „Black Boxes“. Es besteht keine starke Abhängigkeit zwischen den kombinierten Klassen und der Kopplungsgrad ist gering. Wenn Sie die Objektkomposition bevorzugen, können Sie jede Klasse gekapselt halten.
    6. Versuchen Sie tatsächlich, so viele Kombinationen wie möglich zu verwenden. Die Kombination weist eine geringe Kopplung und eine gute Wartbarkeit des Codes auf. Allerdings hat auch die Vererbung ihren Platz. Einige Beziehungen eignen sich für die Vererbung, also nutzen Sie die Vererbung. Darüber hinaus ist Vererbung auch notwendig, um Polymorphismus zu erreichen. Die Beziehung zwischen Klassen kann vererbt oder kombiniert werden. Verwenden Sie einfach die Kombination.
// 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;                     // 轮胎   
};   

Supongo que te gusta

Origin blog.csdn.net/weixin_44906102/article/details/132942638
Recomendado
Clasificación