[Programación en C++] Capítulo 5: Herencia y derivación de clases

Tabla de contenido

1. Herencia de clases y derivación de clases

(1) El concepto de herencia

(2) Definición y tamaño de la clase derivada

① Definición de clase derivada

② El tamaño de la clase derivada

(3) La particularidad de la relación de herencia

(4) Acceso entre clases con relación de herencia

(5) especificador de alcance de acceso protegido

(6) herencia múltiple 

2. Control de acceso

(1) herencia pública

(2) Reglas de compatibilidad de tipos

(3) herencia privada

(4) Protección de la herencia  

Tercero, el constructor y destructor de la clase derivada.

(1) Constructor y destructor 

(2) constructor de copias 

(3) Constructores y destructores de herencia múltiple 

En cuarto lugar, la relación entre clases.

(1) La relación entre clases   

(2) Derivación de clases cerradas  

(3) Clases con relación de inclusión mutua 

Cinco, derivación multinivel

6. Conversión mutua entre la clase base y los punteros de clase derivados 




1. Herencia de clases y derivación de clases

  • La herencia y la derivación son los procesos por los cuales las personas entienden el mundo objetivo.
  • En el método de programación, las personas persiguen la reutilización del código (que es un medio importante para mejorar la eficiencia del desarrollo de software) y utilizan la herencia y la derivación en el método de programación, por lo que tienen las características importantes de la programación orientada a objetos.
  • C++ tiene un fuerte soporte para la reutilización de código, y la " herencia " es uno de los mecanismos para soportar la reutilización de código.
  • El proceso de crear una nueva clase a partir de una clase existente se denomina derivación de clases.
  • La clase original se llama clase base, también conocida como clase padre o clase general; la nueva clase se llama clase derivada, también llamada subclase o clase especial.
  • La clase derivada se deriva de la clase base, o hereda de la clase base, y también se puede decir que la clase base se deriva de la clase derivada .
  • El mecanismo de derivación es una de las características importantes del lenguaje C++ y el método de programación orientado a objetos.
  • Una clase derivada se puede usar como clase base para derivar nuevas clases derivadas , y esta colección de clases base y derivadas se denomina jerarquía de herencia de clases.

(1) El concepto de herencia

  • Cuando se usa una clase base para derivar una nueva clase, excepto el constructor y el destructor, todos los miembros de la clase base se convierten automáticamente en miembros de la clase derivada, incluidas las variables miembro y las funciones miembro de la clase base.
  • Al mismo tiempo, las clases derivadas pueden agregar miembros que no están en la clase base, lo que también se refiere a variables miembro y funciones miembro.
  • Puede redefinir o modificar los miembros existentes de la clase base, incluido el cambio de los derechos de acceso de los miembros de la clase base .
  • Por supuesto, las clases derivadas necesitan definir sus propios constructores y destructores.
  • El uso de miembros de la clase base es un proceso de reutilización, mientras que ajustar la clase base, ya sea agregando nuevos miembros o transformando los existentes, es un proceso de expansión.
  • Si una clase derivada define un miembro con el mismo nombre que la clase base, habrá casos en los que la clase base y la clase derivada tengan miembros con el mismo nombre, lo cual está permitido.
  • Un miembro con el mismo nombre puede ser una variable miembro o una función miembro.
  • En este caso, si se accede al miembro del mismo nombre en la función de miembro de la clase derivada, o cuando se accede al miembro del mismo nombre a través del objeto de la clase derivada, a menos que se especifique lo contrario, se accede al miembro de la clase derivada Esta situación se denomina "anulación " Es decir, un miembro de una clase derivada anula a un miembro de la clase base con el mismo nombre.
  • La anulación también se conoce como redefinición o reescritura.
  • Para las funciones miembro, la clase derivada no solo hereda la función miembro del mismo nombre de la clase base, sino que también reescribe esta función miembro en la clase derivada.
  • Esto se denomina redefinición de funciones, también conocida como ocultación de homónimos.
  • "Oculto" significa que cuando se llama a una función miembro con este nombre utilizando un objeto de clase derivada, se llama a la función miembro definida en la clase derivada, es decir, se oculta la función miembro en la clase base.

(2) Definición y tamaño de la clase derivada

①Definición  de clase derivada

[Ejemplo 1]  Definición de clase base y clase derivada

[Código de ejemplo] Ejemplo de herencia de clase C++, en el que DerivedClass el público hereda BaseClass :

class BaseClass // 定义基类 BaseClass
{
    int v1; // 基类的私有成员变量 v1
    int v2; // 基类的私有成员变量 v2
};

class DerivedClass : public BaseClass // 定义派生类 DerivedClass,公有继承自 BaseClass
{
    int v3; // 派生类的私有成员变量 v3
};

【Explicación del código】

  1. BaseClass La clase tiene dos variables miembro privadas  v1 y  v2 solo  BaseClass las funciones miembro de la clase pueden acceder a estas variables.
  2. DerivedClass La herencia pública de clase  BaseClass significa  DerivedClass acceso  BaseClass a miembros públicos y protegidos (si los hay).
  3. DerivedClass Una clase tiene una variable miembro privada  v3 a la que solo pueden  DerivedClass acceder sus propias funciones miembro.
  4. Dado que  DerivedClass el público es heredado  BaseClass , puede BaseClass acceder a las variables y funciones de los miembros públicos, y este acceso se logra a través de punteros generados por el compilador en tiempo de compilación.

Atención especial】

  • En la relación de derivación, la subclase puede usar los miembros públicos y los miembros protegidos de la clase principal (clase base), pero no puede usar los miembros privados de la clase principal.
  • En este ejemplo v1 ,v2 el acceso a la función BaseClass miembro .
  • v3 Y DerivedClass solo se puede acceder a la función miembro.
  • Además, la subclase también puede sobrecargar la función de la clase principal en su propia función miembro, es decir, definir una función en la subclase con el mismo nombre que la función de la clase principal, pero el cuerpo y los parámetros de la función son diferentes. que es la sobrecarga de funciones.
  • Estos son conceptos muy importantes en la herencia de clases.

[Ejemplo 2] Una clase vacía también se puede usar como clase base, es decir, una clase vacía puede derivar subclases

  • Las clases derivadas pueden cambiar los derechos de acceso de los miembros de la clase base

[Código de ejemplo] Un ejemplo de herencia de clases en C++, la siguiente declaración define una clase derivada de una clase base vacía:

class emptyClass{ }; //空基类
class subemptyClass : public emptyClass{ }; //派生类

【Explicación del código】

  • emptyClass ​​​​​​​​​​​​​​​​Es una clase base vacía, que no define ninguna variable miembro ni función miembro.
  • subemptyClass YemptyClass
  • En esta definición  ,subemptyClass   no tiene ningún atributo, por lo que esta herencia no tiene ningún significado práctico.emptyClassemptyClass

【prestar atención】

  • Las clases vacías todavía tienen el valor de ​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​ ​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​ ​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​
  • El tamaño de la clase vacía suele ser 1 ​​​​​​​​​​​​​​.
  • Porque para una clase vacía, el compilador aún necesita asignarle una dirección única.
  • Entonces, una clase vacía en realidad tomará al menos un byte.

②  El tamaño de la clase derivada

  • El objeto de clase derivado contiene variables de miembro de clase base, y la ubicación de almacenamiento de las variables de miembro de clase base se encuentra antes de las variables de miembro recién agregadas del objeto de clase derivado.
  • El espacio de almacenamiento ocupado por el objeto de la clase derivada es igual al espacio de almacenamiento ocupado por las variables miembro de la clase base más el espacio de almacenamiento ocupado por las variables miembro del propio objeto de la clase derivada.
  • El espacio de almacenamiento ocupado por un objeto incluye el espacio de almacenamiento ocupado por cada variable miembro del objeto.
  • En aras de la eficiencia de procesamiento interno de la computadora, al asignar memoria para una variable, la dirección de inicio de la variable se alineará con los límites en el espacio de almacenamiento de acuerdo con su tipo de datos correspondiente.
  • La función    sizeof () se puede utilizar para calcular el número de bytes que ocupa un objeto.
  • El tamaño de un objeto está relacionado con las variables miembro ordinarias y no tiene nada que ver con las funciones miembro y las variables miembro estáticas de la clase, es decir, las funciones miembro ordinarias, las funciones miembro estáticas, las variables miembro estáticas, las variables miembro constantes estáticas, etc. no tienen ningún efecto sobre el tamaño de los objetos de clase.

[Ejemplo] La clase base y la subclase ocupan espacio y alineación de bytes

[Código de muestra] Un ejemplo de herencia de clase en C++, dos clases se definen a través de classla palabra claveBaseClass: Y clases derivadas DerivedClass,sizeofel valor de cada clase ​​​​​​, para observar el tamaño de la memoria que ocupan:

#include<iostream>  // 引入iostream标准库
using namespace std; // 使用命名空间std

class BaseClass // 定义基类 BaseClass
{ 
    int v1;      // 基类私有成员变量 v1
    int v2;      // 基类私有成员变量 v2
    char v4;     // 基类私有成员变量 v4

public:         // 基类公有函数部分
    // 基类公有函数 templ,返回值类型为 int
    int templ()   
    {
        // 函数体略
    }
}; 

class DerivedClass : public BaseClass //定义派生类 DerivedClass,继承自 BaseClass
{ 
    int v3;     // 派生类私有成员变量 v3
    int* p;     // 派生类私有指针变量 p

public:         // 派生类公有函数部分
    // 派生类公有函数 temp, 返回值类型为 int
    int temp()
    {
        // 函数体略
    }
};

int main()  // 主函数
{ 
    // 输出"Base="和sizeof(BaseClass)的值
    cout << "Base=" << sizeof(BaseClass) << endl;  // 12,输出结束后换行
    // 输出"Derived="和sizeof(DerivedClass)的值
    cout << "Derived=" << sizeof(DerivedClass) << endl;   // 20,输出结束后换行
    return 0;   // 返回0,即正常退出程序
}

【Explicación del código】

  1. En primer lugar, presentamos la biblioteca iostreamestándar y using namespace stdla usamos para realizar la importación de espacios de nombres.
  2. Luego definimos una clase base BaseClass, dos de las cuales son variables de miembros privados yv1 una variable privada de tipo . También se define una función pública en la clase , el tipo de retorno es .v2charv4BaseClasstempl()int
  3. Luego definimos una clase derivada DerivedClass, DerivedClassheredada BaseClass, y una de ellas era variables de miembro privadas v3y una pvariable de puntero de entero privado. ​​​. DerivedClassTambién se define una función pública en la clase temp(), el tipo de retorno intes ​​​.
  4. En mainla función , generamos sizeof(BaseClass)el y sizeof(DerivedClass), y una nueva línea después de cada salida.
  5. Finalmente devolvemos 0 y finalizamos la ejecución del programa.

【prestar atención】

  • Dado que la claseDerivedClass derivada ​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​ ​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​ ​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​ ​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​ ​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​ ​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​ ​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​ ​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​ ​​​​​​​​​​​​​​,BaseClass en la clase derivada, no solo tiene sus propias variables miembro y funciones miembro, sino que también incluye los miembros de las variables de clase base y las funciones miembro.
  • sizeof(DerivedClass)Así que ​​​​​​​​​​​​​​​​El tamaño total de las variables miembro, que es ​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​ ​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​ ​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​ ​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​ ​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​ ​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​ ​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​ ​​​​​​​​Sección (Porque la variable del puntero ocupa debajo del sistema) ​​​​​​​4​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​ ​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​BaseClassDerivedClassWindows

(3) La particularidad de la relación de herencia

  • Si la clase base tiene una clase amiga o función amiga, su clase derivada tampoco tendrá esta clase amiga o función amiga debido a la relación de herencia.
  • Si la clase base es amiga de cierta clase, esta amistad se hereda.
  • Es decir, una función miembro heredada por una clase derivada, si es una función amiga de cierta clase, se usará como una clase derivada
  • Una función miembro sigue siendo una función amiga de cierta clase
  • En resumen, los amigos de la clase base no son necesariamente los amigos de la clase derivada; si la función miembro de la clase base es una función amiga de cierta clase, entonces la función miembro heredada como clase derivada sigue siendo una función amiga. de cierta clase
  • Si los miembros de la clase base son estáticos, en las clases derivadas, los miembros heredados también son estáticos, es decir, sus atributos estáticos se heredan junto con los miembros estáticos.
  • Si los miembros estáticos de la clase base son públicos o están protegidos, la clase derivada los hereda como miembros estáticos de la clase derivada.
  • Al acceder a estos miembros, normalmente se hace referencia a ellos o se los llama de la forma "<nombre de clase>::<nombre de miembro>".
  • No importa cuántos objetos se creen, solo hay una copia de estos miembros, que comparten todos los objetos de la clase base y las clases derivadas.

[Ejemplo 1] La clase base y la subclase ocupan espacio y alineación de bytes

[Código de muestra] Ejemplo de programa C++, que muestra cómo definir una clase, heredar una clase, usar variables miembro y funciones miembro, y generar el espacio ocupado por las variables miembro en la clase en la función principal:

#include <iostream> // 引入头文件 iostream
using namespace std; // 使用命名空间 std

class BaseClass // 定义基类 BaseClass
{ 
    int v1;      // 基类私有成员变量 v1
    int v2;      // 基类私有成员变量 v2
    char v4;     // 基类私有成员变量 v4

public:         // 基类公有函数部分
    // 基类公有函数 templ,返回值类型为 int
    int templ()   
    {
        // 函数体略
    }
}; 

class DerivedClass : public BaseClass // 定义派生类 DerivedClass,继承自 BaseClass
{ 
    int v3;     // 派生类私有成员变量 v3
    int* p;     // 派生类私有指针变量 p

public:         // 派生类公有函数部分
    // 派生类公有函数 temp, 返回值类型为 int
    int temp()
    {
        // 函数体略
    }
};

int main()  //主函数
{ 
    cout << "Base=" << sizeof(BaseClass) << endl;         // 输出 "Base=12"
    cout << "Derived=" << sizeof(DerivedClass) << endl;   // 输出 "Derived=20"
    return 0;   // 返回 0,程序正常退出
}

【Explicación del código】

  1. Introduzca el archivo de encabezado iostream y use el espacio de nombres std usando el espacio de nombres std.
  2. Se definen una clase base BaseClass y una clase derivada DerivedClass, donde la clase base tiene tres variables miembro privadas int v1, int v2 y char v4, y una función miembro pública int templ(); la clase derivada hereda la clase base y agrega dos Variables miembro privadas int v3 e int* p, y una función miembro pública int temp().
  3. En la función principal, imprima los tamaños de las dos clases, es decir, el número de bytes ocupados por la clase base BaseClass y la clase derivada DerivedClass, utilice respectivamente la palabra clave sizeof para obtener sus tamaños, y luego genere sus respectivas etiquetas y agregue información de tamaño a través de cout.
  4. Finalmente, el programa finaliza el programa con una declaración de retorno que devuelve un valor de 0.

【prestar atención】

  • Este código utiliza principalmente la idea central de la programación orientada a objetos de C++, es decir, definir clases, heredar clases y usar variables miembro y funciones miembro en las clases.
  • Al mismo tiempo, utilice el operador sizeof para obtener el tamaño del espacio ocupado por cada variable miembro de la clase y utilice el operador cout para enviar la información a la consola.
  • Dicho código puede ayudarnos a comprender mejor el uso de las clases y la herencia, y es muy útil para comenzar con la programación orientada a objetos en C++.
  • BaseClass y  DerivedClass en realidad solo definen la estructura de la clase, no implementan ningún método, por lo que en la definición de la clase, todos los cuerpos de funciones van seguidos de abreviaturas  ....
  • En  main la función, usamos  cout el método para generar el valor de la cadena y la variable, preste atención al uso del  << operador para combinar la cadena y la variable en una declaración de salida. Entonces, al final, el programa genera dos líneas de texto, que son  Base=12 y  Derived=20.

[Ejemplo 2] Herencia de clases y variables miembro estáticas

[Código de ejemplo] Ejemplo de programa C++, que define una clase base Basey una Base clase Derived. Al mismo tiempo, el código también define una staV variable miembro estática nombrada para registrar Base y Derived el número de instancias de la clase:

#include <iostream> // 引用标准输入输出库 iostream
using namespace std; // 使用命名空间 std

class Base // 定义基类 Base
{
private: // 声明私有变量
    float x; // 基类私有成员变量 x

public: // 声明公有函数和变量
    static int staV; // 基类公有静态成员变量 staV
    Base() // 声明默认构造函数
    { 
        staV++; // 每次实例化对象时,静态成员变量 staV 值加1
    }
};

int Base::staV = 0; // 初始化静态成员变量 staV

class Derived : public Base // 定义派生类 Derived,继承自 Base
{
private: // 声明私有变量
    float y; // 派生类私有成员变量 y

public: // 声明公有函数和变量
    Derived() // 声明默认构造函数
    { 
        staV++; // 每次实例化对象时,静态成员变量 staV 值加1
    }
};

int main() // 主函数
{
    Base a; // 实例化一个 Base 类对象 a
    cout << a.staV << endl; // 输出 Base 类对象 a 的 staV 值,输出 1
    Derived d; // 实例化一个 Derived 类对象 d
    cout << d.staV << endl; // 输出 Derived 类对象 d 的 staV 值,输出 3
    return 0; // 返回值 0,表示程序正常退出
}

【Explicación del código】

  1. #include <iostream> Importe la biblioteca estándar  iostreampara usar funciones y objetos relacionados con E/S.
  2. using namespace std; Indica el uso de un espacio de nombres  std, lo cual es conveniente para llamar  std a funciones y objetos bajo el espacio de nombres.
  3. class Base {...}; Defina una clase  Baseque contenga una  x variable privada denominada y una  staV variable de miembro estática pública denominada . En el constructor, cada vez que  Base se instancia un objeto de la clase, staV se incrementará en 1.
  4. int Base::staV = 0; Defina  Base las variables miembro estáticas en la clase  staV e inicialícelas a 0 para registrar  Base y  Derived el número de instancias de la clase.
  5. class Derived: public Base {...}; Defina la clase de herencia  Derived, la clase base es  Base. Derived La clase contiene una  y variable privada llamada , y cuando el objeto de la clase se instancia en el constructor  Derived , staV se incrementará en 1.
  6. int main() {...} La entrada de la función principal contiene las siguientes declaraciones:
  • Base a;Base El objeto de la clase   definida  a, dado que  staV el valor en este momento es 1, aumentará staV en 1.
  • cout<< a.staV << endl; La salida se llama  a y  staVla salida es 1 en este momento, porque solo hay  a un  Base objeto de clase en este momento.
  • Derived d;Derived El objeto de la clase   definida  d, dado que  staV el valor en este momento es 2, aumentará el staV en 1, un total de 3 veces.
  • cout<< d.staV << endl; La salida se llama  d ,  staVy la salida es 3 en este momento, porque hay 3 objetos de la clase  Base y  Derived .

【prestar atención】

  • El objetivo principal de este código es demostrar las variables miembro estáticas y la herencia de clases.
  • La implementación específica es definir una  staV variable de miembro estática llamada , y cuando se crea una instancia del objeto, ya sea una clase base o una clase derivada, el contador se incrementará en 1.
  • staV Por lo tanto, puede ver cuántas clases  Base y  Derived objetos están instanciados actualmente imprimiendo  el valor.
  • Una variable miembro estática pertenece a una clase en lugar de a un objeto, y no tiene nada que ver con una instancia particular de la clase, lo que significa que se puede llamar a través del nombre de la clase y el operador de resolución de alcance :: sin instanciar el objeto.
  • Al definir una clase, puede agregar estática delante de la variable miembro estática para convertirla en una variable miembro estática, que será compartida por la clase y todas las instancias de la clase.

(4) Acceso entre clases con relación de herencia

  • Tanto la clase derivada como la clase base pueden definir sus propias variables miembro y funciones miembro. Las funciones miembro de la clase derivada pueden acceder a las variables miembro públicas de la clase base, pero no pueden acceder directamente a las variables miembro privadas de la clase base.
  • En otras palabras, no puede usar "nombre de objeto de clase base. función de miembro privado de clase base (parámetro real)" o "clase base
  • Nombre del objeto. Variable de miembro privado de la clase base" o "Nombre de la clase base:: miembro privado de la clase base" para acceder a los miembros privados de la clase base.
  • En la jerarquía de derivación de una clase, los miembros de la clase base y los miembros agregados por la clase derivada tienen alcance de clase.
  • El ámbito de acción de los dos es diferente, y son dos capas que se contienen entre sí, la clase derivada está en la capa interna y la clase base está en la capa externa.
  • Si la clase derivada declara un nuevo miembro con el mismo nombre que un miembro de la clase base, el nuevo miembro derivado oculta los miembros externos con el mismo nombre y solo se puede acceder a los miembros de la clase derivada directamente usando el nombre del miembro .
  • Si se declara una nueva función con el mismo nombre que una función miembro de la clase base en la clase derivada, todas las formas sobrecargadas de la función con el mismo nombre heredado de la clase base se ocultarán incluso si la lista de parámetros de la función es diferente.
  • Si desea acceder a miembros ocultos, debe usar el nombre de la clase base y el discriminador de alcance para calificar. 

[Ejemplo] Herencia y control de acceso

[Código de ejemplo] Ejemplo de programa C++, que explica principalmente la herencia y el control de acceso, donde la clase base CB contiene una variable miembro a yshowa(), y la clase CD derivadaCBde,a y una función miembro con el mismo nombre showa(),print2a() función​​​​para

#include<iostream>
using namespace std;

class CB
{
public:
    int a;
    CB(int x)
    {
        a=x;
    }
    void showa()
    {
        cout<<"Class CB--a="<<a<<endl;
    }
};

class CD:public CB
{
public: 
    int a; 
    CD(int x,int y):CB(x) 
    { 
        a=y;
    }
    void showa() 
    { 
        cout << "Class CD--a=" << a << endl; 
    }
    void print2a()
    { 
        cout << "a=" << a << endl; 
        cout << "CB::a=" << CB::a << endl; 
    }
};

int main()
{
    CB CBobj(12);  // 创建基类对象CBobj,调用基类的构造函数CB(int x),其中x为12,用于初始化a的值
    CBobj.showa();  // 调用CB类的成员函数showa(),输出CBobj的成员变量a
    CD CDobj(48, 999);  // 创建派生类对象CDobj,传入的参数用于初始化基类的a和派生类的a
    CDobj.showa();  // 调用CD类的成员函数showa(),输出CDobj的成员变量a
    CDobj.CB::showa();  // 调用CB类的成员函数showa(),输出CBobj的成员变量a,使用作用域限定符
    cout << "CDobj.a=" << CDobj.a << endl;  // 输出CDobj的a
    cout << "CDobj.CB::a=" << CDobj.CB::a << endl;  // 调用CB类的成员变量a,使用作用域限定符
}

【Explicación del código】

  1. Líneas 3-12: Declare y defina la clase base  CB, que tiene una variable de miembro pública  a y un constructor  CB(int x)que acepta un parámetro entero xy lo asigna a la variable de miembro de clase  a , y una función de miembro pública showa()que genera el valor de la  a variable de miembro value.

  2. Líneas 14-23: Declare y defina la clase derivada  CD, que hereda  CB todos los miembros y funciones miembro de la clase base. La clase derivada también tiene una variable miembro  a con  a el mismo nombre que la variable miembro de la clase base. En el constructor de la clase derivada, use los parámetros  x para inicializar las variables miembro de la clase base  a y use los parámetros  y para inicializar las variables miembro ocultas con el mismo nombre en la clase derivada a. Además, dado que ya existe una función miembro showa() con el nombre , la función miembro con el mismo nombre en la clase base se sobrescribe mediante reescritura showa()y se usa para generar la clase derivada El valor a de  la variable miembro en , y se agrega una nueva función miembro para print2a()mostrar las variables miembro con el mismo nombre en la clase base y la clase derivada respectivamente a través de calificadores de alcance.

  3. Líneas 25-35: La función principal  , en la que main()se definen  el objeto de clase base CBobj y el CDobjobjeto , que se inicializan con los parámetros pasados ​​respectivamente. Luego, CB llame CD a las diversas funciones miembro de la clase base y la clase derivada, y genere varias variables miembro de la clase base y la clase derivada.

【prestar atención】

  • Este código examina principalmente los puntos de conocimiento relevantes de herencia y control de acceso.
  • En la clase derivada, la variable de miembro del mismo nombre o la función de miembro ocultará el miembro del mismo nombre en la clase base, y puede usar el calificador de alcance (::) para indicar el acceso al miembro del mismo nombre en la clase base .
  • Además, las funciones miembro de la clase base también se pueden invalidar en la clase derivada.
  • Este código no tiene en cuenta los problemas causados ​​por el conflicto de nombres y la anulación de las variables miembro con el mismo nombre en la clase derivada y la clase base, y se debe prestar especial atención a este punto en la programación real.
  • Además, se recomienda realizar la inicialización de la variable miembro con el mismo nombre que la clase base en la clase derivada a través de la lista de inicialización del constructor para evitar confusiones y errores innecesarios.

[Resultado de la ejecución]  La salida de este código es la esperada:

  • Líneas 1 y 3: muestran respectivamente  las variables miembro  delCBobj objeto de clase base y el objeto de clase derivado  , que son 12 y 999 respectivamente, como se esperaba;CDobja
  • Línea 2: es para generar  CDobj la variable miembro con el mismo nombre del objeto de clase derivado a, el valor es 999, como se esperaba;
  • Línea 4: Es   el valor  deCDobj la clase base que llama al objeto de la clase derivada, el valor es 48, que es el esperado;CBa
  • Líneas 5 y 6: muestran respectivamente  CDobj la variable miembro con el mismo nombre del  objeto de clase derivado a y su  CB variable miembro con el mismo nombre  en la clase base a, que son 999 y 48 respectivamente, como se esperaba.
Class CB--a=12
Class CD--a=999
Class CB--a=48
CDobj.a=999
CDobj.CB::a=48

(5) especificador de alcance de acceso protegido

  • Al definir una clase, los miembros de la clase se pueden decorar con el  especificador de ámbito de acceso protegido  , convirtiéndose así en "miembros protegidos".
  • El rango de acceso de los miembros protegidos es mayor que el de los miembros privados, y se puede acceder a los miembros protegidos donde sea que se pueda acceder a los miembros privados. Además, se puede acceder a los miembros protegidos en una clase base en funciones miembro de clases derivadas.
  • En la clase base, los miembros que deben ocultarse generalmente se describen como miembros protegidos en lugar de miembros privados.
  • Después de modificar el modo de acceso de la variable miembro en la clase base a protegida, se puede acceder directamente en la clase derivada.

(6) herencia múltiple 

  • C++ permite que una clase se derive de varias clases , es decir, una clase derivada puede tener varias clases base al mismo tiempo. Esto se llama herencia múltiple.
  • En consecuencia, la situación en la que una clase derivada se deriva de una clase base se denomina herencia única o herencia única.
  • Si se agrega un nuevo miembro con el mismo nombre a la clase derivada, el miembro de la clase derivada ocultará todos los miembros de la clase base con el mismo nombre.
  • Utilice el método de " nombre de objeto de clase derivada. nombre de miembro " o " puntero de objeto de clase derivada -> nombre de miembro " para identificar de forma única y acceder a los nuevos miembros de la clase derivada. En este caso, no surge ninguna ambigüedad.
  • Si no hay ningún miembro nuevo con el mismo nombre en la clase derivada, cuando se satisfaga el permiso de acceso, use el "objeto de clase derivada
  • Name.Member name" o "Puntero de objeto de clase derivado -> nombre de miembro", el sistema no puede determinar a qué miembro de la clase base se llama, lo que genera ambigüedad.
  • Para evitar la ambigüedad, los miembros deben identificarse por su nombre de clase base y su discriminador de alcance .
  • Cuando desee acceder a una variable en un objeto de clase derivado, agregue "clase base::" como prefijo para indicar desde qué clase base necesita acceder, de modo que se pueda eliminar la ambigüedad.

[Formato] El formato general de una clase derivada de varias clases base es el siguiente:

class 派生类名:继承方式说明符 基类名 1,继承方式说明符 基类名 2,…, 继承
方式说明符 基类名 n
{
    类体
};
【ilustrar】
  • La clase derivada hereda todas las variables miembro y funciones miembro del nombre de clase base 1, nombre de clase base 2, ..., nombre de clase base n, y el especificador de modo de herencia delante de cada nombre de clase base se usa para limitar los miembros en la clase derivada al nombre de la clase base.Los derechos de acceso para los miembros en los nombres de clase siguen las mismas reglas que para la herencia única.
  • En el caso de herencia múltiple, si los miembros de varias clases base tienen el mismo nombre, se manejará de la siguiente manera: para las clases derivadas, si no se agrega el nombre de la clase, el acceso predeterminado es a los miembros de la clase derivada; si desea acceder a los miembros con el mismo nombre en la clase base, esté calificado por el nombre de la clase.

[Ejemplo] Uso de múltiples calificadores de herencia y espacio de nombres

[Código de muestra] Ejemplo de programa C++, que demuestra el uso de la herencia múltiple y los calificadores de espacios de nombres en C++:

#include<iostream>
using namespace std;

class CB1
{
public:
    int a; // 基类成员变量a
    CB1(int x)
    {
        a = x;
    }
    void showa() // 基类成员函数showa
    {
        cout << "Class CB1==>a=" << a << endl;
    }
};

class CB2
{
public:
    int a; // 基类成员变量a
    CB2(int x)
    {
        a = x;
    }
    void showa() // 基类成员函数showa
    {
        cout << "Class CB2==>a=" << a << endl;
    }
};

class CD: public CB1, public CB2
{
public:
    int a; // 派生类成员变量a,与两个基类成员变量a重名
    CD(int x, int y, int z): CB1(x), CB2(y)
    {
        a = z;
    }
    void showa() // 派生类成员函数showa,与两个基类成员函数showa重名
    {
        cout << "Class CD==>a=" << a << endl;
    }
    void print3a()
    {
        cout << "a=" << a << endl; // 访问当前类成员变量a
        cout << "CB1::a=" << CB1::a << endl; // 访问基类CB1的成员变量a
        cout << "CB2::a=" << CB2::a << endl; // 访问基类CB2的成员变量a
    }
};

int main()
{
    CB1 CB1obj(11);
    CB1obj.showa(); // 输出 Class CB1==>a=11
    CD CDobj(101, 202, 909);
    CDobj.showa(); // 输出 Class CD==>a=909
    CDobj.CB1::showa(); // 输出 Class CB1==>a=101
    cout << "CDobj.a=" << CDobj.a << endl; // 输出 CDobj.a=909
    cout << "CDobj.CB2::a=" << CDobj.CB2::a << endl; // 输出 CDobj.CB2::a=202
}

【Explicación del código】

  1. Tanto la clase CB1 como la clase CB2 tienen la variable miembro a y la función miembro showa.
  2. La clase CD hereda públicamente la clase CB1 y la clase CB2, y también define la variable miembro a y la función miembro showa, que tiene el mismo nombre que las dos clases base.
  3. En la clase CD, la función miembro print3a() usa el calificador de espacio de nombres (::) para acceder a la variable miembro a de la clase actual y la clase base CB1, CB2 y la función miembro showa de la clase actual y la clase base CB1.
  4. En la función principal, primero se crea un objeto de clase CB1 CB1obj para probar el acceso de las funciones miembro de la clase base. Luego cree un objeto de clase de CD CDobj y pruebe el acceso de la función miembro showa() de la clase actual y la función miembro showa() de la clase base CB1, y el acceso de la variable miembro a de la clase actual y el clase básica.

【prestar atención】

  • Al acceder a la variable miembro a de la clase actual y la clase base, debe usar el calificador de espacio de nombres para distinguirlos

【Resultados del】

  • Primero emita la cadena "Class CB1==>a=11" impresa en CB1obj.showa(), y luego emita la cadena "Class CD==>a=909" impresa en CDobj.showa().
  • Esto muestra que la función miembro showa() de la clase derivada CD tiene mayor prioridad que la función miembro showa() del mismo nombre de las dos clases base CB1 y CB2.
  • A esto le sigue la salida de la cadena "Class CB1==>a=101" impresa en CDobj.CB1::showa(), que muestra que se puede acceder a las funciones miembro de la clase base usando calificadores de espacio de nombres.
  • Finalmente, se emiten los valores de CDobj.a y CDobj.CB2::a, que son 909 y 202 respectivamente.
  • Esto indica que se puede acceder a las variables miembro de la clase actual y las clases base mediante calificadores de espacio de nombres.
Class CB1==>a=11
Class CD==>a=909
Class CB1==>a=101
CDobj.a=909
CDobj.CB2::a=202


2. Control de acceso

  • Al diseñar una clase heredada, debe usar el especificador del método de herencia para indicar el método de herencia de la clase derivada.
  • El especificador de herencia puede ser  público (herencia pública), privado (herencia privada) o protegido (herencia protegida) .

(1) herencia pública

 


(2) Reglas de compatibilidad de tipos

⚫La regla de compatibilidad de tipos significa que siempre que se requiera un objeto de la clase base, se puede usar en su lugar un objeto de la clase derivada pública, también conocida como regla de compatibilidad de asignación .
⚫ En el caso de derivación pública, existen las siguientes tres reglas de compatibilidad de tipos:
  1. Los objetos de una clase derivada se pueden asignar a objetos de una clase base.
  2. Los objetos de clase derivados se pueden usar para inicializar referencias de clase base.
  3. La dirección del objeto de clase derivada se puede asignar al puntero de clase base, es decir, el puntero de clase derivada se puede asignar al puntero de clase base.
⚫ Las 3 reglas anteriores no son ciertas a la inversa:
  • Por ejemplo, no puede asignar un objeto de clase base a un objeto de clase derivado.
  • Después de la sustitución, el objeto de la clase derivada se puede usar como un objeto de la clase base, pero solo se pueden usar los miembros heredados de la clase base.
  1. Si la clase  es la clase base y la clase  D  es la clase pública derivada de  la clase  B  , entonces la clase contiene todos los miembros de la clase base  excepto el constructor y el destructor.
  2. En este momento, de acuerdo con las reglas de compatibilidad de tipos, cualquier lugar donde pueda aparecer un objeto de la clase base B puede ser  reemplazado por un objeto de la clase derivada D.
  • Supongamos que tiene la siguiente declaración:
class B{…}
class D : public B{…}
B b1, *pb1;
D d1;
  • En este momento, el objeto de la clase derivada se puede convertir implícitamente en el objeto de la clase base, es decir, usar el valor de la variable miembro heredada de la clase base en el objeto de la clase derivada para asignar el valor de la variable miembro de la clase base objeto uno por uno.
b1=d1;
  • Los objetos de clases derivadas también se pueden usar para inicializar referencias a objetos de clases base, es decir,
B &rb=d1;
  • La dirección del objeto de la clase derivada se puede convertir implícitamente en un puntero a la clase base, es decir, la dirección del objeto de la clase derivada se asigna al puntero de la clase base:
pb1=&d1;
  • Debido a la introducción de reglas de compatibilidad de tipos, los objetos de la clase base y sus clases derivadas públicas se pueden procesar de manera uniforme utilizando la misma función. Porque cuando el parámetro formal de la función es el objeto (o referencia, puntero) de la clase base, el parámetro real puede ser el objeto (o puntero) de la clase derivada, por lo que no es necesario diseñar un módulo separado para cada uno . clase, lo que mejora en gran medida la eficiencia del programa.

[Ejemplo 1] Derivación pública de clases y asignación de objetos

[Código de ejemplo] Ejemplo de programa C++, que demuestra la derivación pública de clases en C++ y la asignación de objetos:

#include <iostream>
using namespace std;

class A
{
    int an; // 成员变量

public:
    A() {} // 默认构造函数
    A(int n) // 带参数构造函数
    {
        an = n;
    }
};

class B : public A // 公有派生
{
    int bn; // 成员变量

public:
    B(int n) : A(2 * n) // 构造函数初始化表,初始化基类的数据成员an
    {
        bn = n;
    }
};

int main()
{
    A a(10); // 创建基类A对象a,传入参数10
    B b(20); // 创建派生类B对象b,传入参数20
    a = b; // 派生类对象b的值赋给基类对象a
    return 0;
}

【Explicación del código】

  1. Se definen la clase base A y la clase B derivada.

  2. La clase base A incluye una variable miembro privada an, un constructor predeterminado y un constructor parametrizado. Entre ellos, el constructor con parámetros asignará el parámetro n a la variable miembro an.

  3. La clase derivada B se deriva públicamente de la clase base A, incluida una variable miembro privada bn y un constructor. El constructor usa la tabla de inicialización para llamar al constructor parametrizado de la clase base A y pasarle el valor de 2 * n para inicializar el miembro de datos an de la clase base. Al mismo tiempo, el constructor también asigna el valor del parámetro n a la variable miembro bn.

  4. En la función principal, se crean los objetos a y b de clase A y clase B, y los parámetros 10 y 20 se pasan al constructor del objeto, respectivamente. Debido a que la clase B derivada se deriva públicamente de la clase base A, un objeto de tipo B se puede asignar a un objeto de tipo A. La declaración de asignación a = b puede copiar el valor del objeto de clase derivado b al objeto de clase base a.

  5. Finalmente, la función principal devuelve 0, finalizando el programa.

【Resultados del】

  • Este código no tiene salida, por lo que no hay salida de resultado de ejecución.

[Ejemplo 2] Derivación pública y sobrecarga de funciones de clases, y asignación de objetos de clase derivados a objetos de clase base

[Código de muestra] Ejemplo de programa C++, que demuestra la derivación pública y la sobrecarga de funciones de clases en C++, y la operación de asignar objetos de clase derivados a objetos de clase base:

#include <iostream>
using namespace std;

class A
{
    int an; // 成员变量

public:
    A() {} // 默认构造函数
    A(int n) // 带参数构造函数
    {
        an = n;
    }
    void print() // 输出函数
    {
        cout << "A的对象:";
        cout << "an:" << an;
    }
    void print(int k) // 重载的输出函数
    {
        cout << "an:" << an;
    }
};

class B : public A // 公有派生
{
    int bn; // 成员变量

public:
    B(int n) : A(2 * n) // 构造函数初始化表,初始化基类的数据成员an
    {
        bn = n;
    }
    void print()
    {
        cout << "\nB的对象:";
        A::print(1); //调用基类A的print函数
        cout << ",bn=" << bn << endl;
    }
};

int main()
{
    A a(10); // 创建基类A对象a,传入参数10
    B b(20); // 创建派生类B对象b,传入参数20
    a.print(); // 调用基类 A 的输出函数
    b.print(); // 调用派生类 B 的输出函数
    a = b; // 派生类对象b的值赋给基类对象a
    a.print(); // 调用基类 A 的输出函数
    b.print(); // 调用派生类 B 的输出函数
    return 0;
}

【Explicación del código】

  1. Se definen la clase base A y la clase B derivada.

  2. La clase base A incluye una variable miembro privada an, un constructor predeterminado y un constructor parametrizado. Entre ellos, el constructor con parámetros asignará el parámetro n a la variable miembro an. Al mismo tiempo, la clase A también incluye una función de salida print() y una función de salida sobrecargada print(int k).

  3. La clase derivada B se deriva públicamente de la clase base A, incluida una variable miembro privada bn y un constructor. El constructor usa la tabla de inicialización para llamar al constructor parametrizado de la clase base A y pasarle el valor de 2 * n para inicializar el miembro de datos an de la clase base. Al mismo tiempo, la clase B también incluye una función de salida print(), en la que se llama a la función de salida print() de la clase base A.

  4. En la función principal, se crean los objetos a y b de clase A y clase B, y los parámetros 10 y 20 se pasan al constructor del objeto, respectivamente. a.print() genera la variable miembro an en el objeto de clase base a, b.print() genera las variables miembro an y bn en el objeto de clase derivado b.

  5. Ejecute la instrucción a = b y copie el objeto de clase derivado b en el objeto de clase base a. Dado que la clase derivada hereda todos los miembros de la clase base, la variable miembro an se asigna correctamente, pero el valor de la variable miembro bn no se pasa.

  6. a.print() genera el valor de la variable miembro an en el objeto de clase base a después de la asignación, y b.print() genera las variables miembro an y bn en el objeto de clase derivado b.

【Resultados del】

  • Se puede ver a partir de los resultados que el objeto de la clase base genera el valor de su propia variable miembro an;
  • El objeto de clase derivado genera los valores de sus propias variables miembro an y bn;
  • Una vez que el objeto de clase derivada se asigna al objeto de clase base, an en el objeto de clase base se ajusta al valor del objeto de clase derivada, pero no hay valor bn.
A的对象:an:10
B的对象:A的对象:an:40,bn=20

A的对象:an:40
B的对象:A的对象:an:40,bn=20

(3) herencia privada


(4) Protección de la herencia  

  • En la herencia protegida, los miembros públicos y los miembros protegidos de la clase base aparecen en la clase derivada como miembros protegidos, mientras que no se puede acceder directamente a los miembros privados de la clase base.
  • De esta forma, otros miembros de la clase derivada pueden acceder directamente a los miembros públicos y protegidos heredados de la clase base, pero no se puede acceder a ellos directamente fuera de la clase a través de objetos de la clase derivada.


Tercero, el constructor y destructor de la clase derivada.

  • La clase derivada no hereda el constructor de la clase base, por lo que se debe llamar al constructor de la clase base en el constructor de la clase derivada para completar la inicialización de las variables miembro heredadas de la clase base.
  • Específicamente, cuando se crea un objeto de clase derivada, además de llamar a su propio constructor para la inicialización, también llama al constructor de la clase base para inicializar las variables miembro de la clase base que contiene.
  • Un constructor de clase base siempre se ejecuta antes de que se ejecute un constructor de clase derivada.
  • Cuando el objeto de la clase derivada muere, primero se ejecuta el destructor de la clase derivada y luego se ejecuta el destructor de la clase base.

(1) Constructor y destructor 

El formato general para definir un constructor de clases derivadas es el siguiente:
派生类名::派生类名(参数表):基类名1(基类1 初始化参数表),…,基类名m(基类
m初始化参数表),成员对象名1(成员对象1 初始化参数表),…,成员对象名n(成员
对象n 初始化参数表)
{
    类构造函数函数体  //其他初始化操作
}
El orden general de ejecución de los constructores de clases derivadas es el siguiente:
  1. Los constructores de la clase base se llaman en el orden en que se declararon (de izquierda a derecha) cuando se heredaron .
  2. Para inicializar las variables miembro recién agregadas de la clase derivada, el orden de llamada está de acuerdo con el orden en que se declaran en la clase.
  3. Ejecuta el contenido del cuerpo del constructor de la clase derivada.
El orden entre el nombre de la clase base y el nombre del objeto en la lista de inicialización del constructor es irrelevante, y el orden de sus respectivas apariciones puede ser arbitrario, sin importar cómo estén organizados, la llamada del constructor de la clase base y el orden de inicialización de cada uno. variable miembro son definidas.

[Ejemplo 1] Herencia de clases

[Código de ejemplo] Un ejemplo de herencia de clase C++, que define una clase base BaseClass, una clase derivada pública DerivedClassy una función principal. Se crean dos objetos en la función principal, uno es BaseClass el objeto de la clase baseClay el otro es DerivedClass el objeto de la clase derivedCla. Podemos ver que todos ellos llaman a diferentes constructores:

#include<iostream>
using namespace std;

class BaseClass // 基类
{
protected:
    int v1, v2;

public:
    BaseClass(); // 默认构造函数
    BaseClass(int, int); // 带参数构造函数
    ~BaseClass(); // 析构函数
};

BaseClass::BaseClass()
{
    cout << "BaseClass 无参构造函数" << endl;
}

BaseClass::BaseClass(int m, int n)
{
    v1 = m;
    v2 = n;
    cout << "BaseClass 2个参数构造函数" << endl;
}

BaseClass::~BaseClass()
{
    cout << "BaseClass 析构函数" << endl;
}

class DerivedClass : public BaseClass // 公有派生的派生类
{
    int v3;

public:
    DerivedClass(); // 默认构造函数
    DerivedClass(int); // 带1个参数构造函数
    DerivedClass(int, int, int); // 带3个参数构造函数
    ~DerivedClass(); // 析构函数
};

DerivedClass::DerivedClass()
{
    cout << "DerivedClass 无参构造函数" << endl;
}

DerivedClass::DerivedClass(int k) : v3(k)
{
    cout << "DerivedClass 带1个参数构造函数" << endl;
}

DerivedClass::DerivedClass(int m, int n, int k) : BaseClass(m, n), v3(k)
{
    cout << "DerivedClass 带3个参数构造函数" << endl;
}

DerivedClass::~DerivedClass()
{
    cout << "DerivedClass 析构函数" << endl;
}

int main()
{
    cout << "无参对象的创建" << endl;
    BaseClass baseCla; // 创建一个基类对象,调用默认构造函数
    DerivedClass derivedCla; // 创建一个派生类对象,调用默认构造函数
    return 0;
}

【Explicación del código】

  1. Estas dos líneas de código importan  iostream la biblioteca, permitiéndonos realizar operaciones de entrada y salida en el programa:

    #include<iostream>
    using namespace std;
  2. Aquí se define una clase base  BaseClass, que contiene dos  protected variables miembro  v1 y  v2tres funciones de especificador de acceso: un constructor predeterminado  BaseClass(), un constructor parametrizado  BaseClass(int, int)y un destructor ~BaseClass():

    class BaseClass // 基类
    {
    protected:
        int v1, v2;
    
    public:
        BaseClass(); // 默认构造函数
        BaseClass(int, int); // 带参数构造函数
        ~BaseClass(); // 析构函数
    };
  3. Aquí está el constructor predeterminado, que se implementa para generar una línea de cadenas, lo que indica que se llama al constructor:

    BaseClass::BaseClass()
    {
        cout << "BaseClass 无参构造函数" << endl;
    }
  4. Aquí hay un constructor con parámetros, que tiene dos parámetros  m y  n, asigna estos dos parámetros a las variables miembro de la clase base  v1 y  v2, y luego genera una línea de cadenas, lo que indica que se llama al constructor:

    BaseClass::BaseClass(int m, int n)
    {
        v1 = m;
        v2 = n;
        cout << "BaseClass 2个参数构造函数" << endl;
    }
  5. Aquí está el destructor, que es la función que se ejecuta cuando se destruye el objeto. Esto se implementa para generar una línea de cadenas, lo que indica que se llama al destructor:

    BaseClass::~BaseClass()
    {
        cout << "BaseClass 析构函数" << endl;
    }
  6. Aquí se define una clase derivada públicamente  DerivedClassque  BaseClass hereda públicamente de la clase base. Además, DerivedClass la clase tiene un miembro privado  v3, tres constructores y un destructor:

    class DerivedClass : public BaseClass // 公有派生的派生类
    {
        int v3;
    
    public:
        DerivedClass(); // 默认构造函数
        DerivedClass(int); // 带1个参数构造函数
        DerivedClass(int, int, int); // 带3个参数构造函数
        ~DerivedClass(); // 析构函数
    };
  7. Aquí está el constructor por defecto, que es similar al constructor por defecto de la clase base.Genera una línea de cadenas, indicando que se llama al constructor:

    DerivedClass::DerivedClass()
    {
        cout << "DerivedClass 无参构造函数" << endl;
    }
  8. Aquí hay un constructor con 1 parámetro, que tiene un parámetro  kpara inicializar los miembros privados recién agregados de la propia clase derivada  v3. En el constructor, se genera una línea de cadena que indica que se llama al constructor:

    DerivedClass::DerivedClass(int k) : v3(k)
    {
        cout << "DerivedClass 带1个参数构造函数" << endl;
    }
  9. Aquí hay un constructor con 3 parámetros, que tiene tres parámetros  m, n y  k, donde  m y  n se usa para inicializar las variables miembro heredadas de la clase base  v1 y  v2, y  k se usa para inicializar las variables miembro privadas recién agregadas de la clase derivada  v3. En el constructor, se genera una línea de cadena que indica que se llama al constructor:

    DerivedClass::DerivedClass(int m, int n, int k) : BaseClass(m, n), v3(k)
    {
        cout << "DerivedClass 带3个参数构造函数" << endl;
    }
  10. Similar al destructor de la clase base, aquí el destructor de la clase derivada. Ejecutar cuando se destruya el objeto y generar una línea de cadenas, lo que indica que se llama al destructor:

    DerivedClass::~DerivedClass()
    {
        cout << "DerivedClass 析构函数" << endl;
    }
  11. En la función principal, se genera una línea de cadena que indica que se creará un objeto sin parámetros. A continuación, se crean  BaseClass la clase y  DerivedClass los objetos de la clase . Dado que todos son objetos sin argumentos, los constructores predeterminados de las dos clases se llaman respectivamente. Después de ejecutar el programa, devuelve 0:

    int main()
    {
        cout << "无参对象的创建" << endl;
        BaseClass baseCla; // 创建一个基类对象,调用默认构造函数
        DerivedClass derivedCla; // 创建一个派生类对象,调用默认构造函数
        return 0;
    }

【Resultados del】

  • Primero, se emite la cadena "creación de objeto sin parámetros" en el programa, luego se llama a los constructores de las dos clases en el orden de creación, y se llama a los destructores de las dos clases en el orden inverso de destrucción.
  • Por lo tanto, en el resultado de salida, primero  DerivedClass se muestra el destructor de la clase  y luego BaseClass se muestra el destructor de la clase.
无参对象的创建
BaseClass 无参构造函数
DerivedClass 无参构造函数
DerivedClass 析构函数
BaseClass 析构函数

[Ejemplo 2] Herencia de clases

[Código de muestra] Ejemplo de herencia de clase C++, que define dos clases Base y Derived:

#include<iostream>
using namespace std;

class Base
{
private:
    int Y;
public:
    // 默认构造函数
    Base(int y = 0)
    {
        Y = y;
        cout << "Base(" << y << ")" << endl;
    }
    // 析构函数
    ~Base()
    {
        cout << "~Base()" << endl;
    }
    // 成员函数
    void print()
    {
        cout << Y << " ";
    }
};

class Derived : public Base
{
private:
    int Z;
public:
    // 带参数构造函数
    Derived(int y, int z) : Base(y)
    {
        Z = z;
        cout << "Derived(" << y << "," << z << ")" << endl;
    }
    // 析构函数
    ~Derived()
    {
        cout << "~Derived()" << endl;
    }
    // 成员函数
    void print()
    {
        Base::print();
        cout << Z << endl;
    }
};

int main()
{
    Derived d(10, 20);
    d.print();
    return 0;
}

【Explicación del código】

  1. Base Una clase tiene una variable miembro privada  Y, un constructor predeterminado, un destructor y una  Y función miembro que genera el valor de la variable miembro.
  2. En la función principal, primero se crea un  Derived objeto  d, y el objeto llama  Derived al constructor de la clase con los parámetros 10 y 20, e inicializa  Base las variables miembro heredadas de la clase  Y y las variables miembro privadas recién agregadas  Z. A continuación, la  d.print() función de llamada genera  el valor de la Y suma  Z .
  3. Derived 类从 Base 类公有继承,即 Derived 类包含了 Base 类的全部成员,同时还有一个新增的私有成员 ZDerived 类有一个带有两个参数的构造函数,可以用于初始化从 Base 类继承而来的成员变量 Y 和私有成员变量 Z,以及一个析构函数和一个用于输出 Y 和 Z 值的成员函数。

  4. Finalmente, el programa devuelve 0, finalizando la ejecución.

【Resultados del】

  • El programa genera el constructor y el destructor llamados de acuerdo con el orden de creación y destrucción de objetos. Podemos encontrar que el  Base orden de llamada del constructor y el destructor de la clase en el programa es completamente opuesto al orden de herencia, mientras que  Derived la clase está de acuerdo con el orden de constructor y análisis Los constructores se llaman secuencialmente en el orden inverso de su orden de definición.
  • Además, en  Derived la función miembro de la clase  ,  la variable miembro   heredada de la clase se genera  print() llamando  a la función , y luego se genera la variable miembro privada Base::print()BaseYZ:
Base(10)
Derived(10,20)
10 20
~Derived()
~Base()

(2) constructor de copias 

  • Para una clase, si no hay un constructor de copia definido en el programa, el compilador generará automáticamente un constructor de copia implícito. Este constructor de copia implícito llamará automáticamente al constructor de copia de la clase base. Los objetos miembro realizan la replicación.
  • Si desea escribir un constructor de copia para una clase derivada, generalmente también necesita pasar parámetros al constructor de copia correspondiente de la clase base, pero no es necesario.

[Ejemplo 1] El uso, herencia y reescritura de constructores y constructores de copia

[Código de muestra] demuestra principalmente el uso de constructores y constructores de copia en C++, así como operaciones relacionadas de funciones de herencia y reescritura:

#include<iostream>
using namespace std;

class A
{
public:
    // 默认构造函数
    A()
    {
        i = 100;
        cout << "类A默认构造函数" << endl;
    }
    // 复制构造函数
    A(const A& s)
    {
        i = s.i;
        cout << "类A复制构造函数" << endl;
    }
    int getValue(); // 取值
    void setValue(int); // 设置值

private:
    int i;
};

int A::getValue()
{
    return i;
}

void A::setValue(int k)
{
    i = k;
}

class B : public A // 公有派生类
{
private:
    float f;
public:
    B()
    {
        f = 20.1;
        cout << "类B默认构造函数" << endl;
    }
    B(const B& v) : A(v), f(v.f)
    {
        cout << "类B复制构造函数" << endl;
    }
    // 重写基类函数,改变了返回值类型
    float getValue();
    int getValue1()
    {
        return A::getValue();
    }
};

float B::getValue()
{
    return f;
}

int main()
{
    A a; // 调用类A默认构造函数
    B b; // 调用类A默认构造函数、类B默认构造函数
    B bb(b); // 调用类A复制构造函数、类B复制构造函数
    return 0;
}

【Explicación del código】

  1. #include<iostream>: contiene archivos de encabezado  iostreampara operaciones de entrada y salida.

  2. using namespace std: Utilice un espacio de nombres  stdpara evitar escribir cada vez  std::.

  3. class A: define una clase A que contiene un constructor predeterminado y un constructor de copia. getValue() Al mismo tiempo, una función y una  setValue(int) función también se definen en la clase A. 

  4. class B : public A: define una clase B pública derivada, que hereda de la clase A y contiene un constructor predeterminado y un constructor de copia. La clase B también define una función de clase base de anulación  getValue() y una  getValue1() clase base de llamada  de función getValue().

  5. int A::getValue(): La función de clase A está definida  y su valor de retorno getValue() es una variable privada de clase A. i

  6. void A::setValue(int k): Define  setValue(int) la función de la clase A, que se utiliza para establecer la variable privada de la clase A. i

  7. B::B(): Define el constructor predeterminado de la clase B, en el que se inicializan las variables miembro  f = 20.1y se genera la cadena "constructor predeterminado de la clase B".

  8. B::B(const B& v): El constructor de copia de la clase B está definido, primero llamará al constructor de copia de la clase base  , luego  asignará  A(v)las variables miembro de la clase B  y generará la cadena "constructor de copia de clase B".fv.f

  9. float B::getValue(): Define la función de anulación de la clase B  getValue(), y su valor de retorno es la variable privada de la clase  fB.

  10. int main(): Se inicia la función principal.

  11. A a: se define el objeto a y se llama al constructor predeterminado de la clase A.

  12. B b: se define el objeto b y se llama al constructor predeterminado de la clase A y al constructor predeterminado de la clase B.

  13. B bb(b): Defina el objeto bb y llame al constructor de copias de la clase B con b como parámetro.

  14. return 0: El programa finaliza y el valor de retorno es 0.

【Resultados del】

  • Primero se ejecuta la función  , main() se crean  un A objeto de clase  a y un  B objeto de clase  b.
  • Cuando se crea  un objeto, a se llama  A al constructor predeterminado  ; cuando se crea un objeto, b primero se llama  A al constructor predeterminado  y luego B se llama al constructor predeterminado.
  • A continuación, se crea  un B objeto de clase  bby se inicializa b como  parámetros, donde  se llama A al constructor de copias y  al constructor de copias.B
  • Finalmente, el programa devuelve 0, finalizando la ejecución.
  • Se puede ver que en el constructor de copia de la clase  , B primero se llama  A al constructor de copia  , y luego se inicializa B la variable miembro privada de  f.
  • Cabe señalar que B la clase anula  A las funciones miembro de la clase  getValue, por lo que  la función B del objeto de clase  getValue devuelve  f el valor del objeto de clase, no  el valor A del objeto de clase  i :
类A默认构造函数
类A默认构造函数
类B默认构造函数
类A复制构造函数
类B复制构造函数

[Ejemplo 2] La clase derivada llama al constructor de copia de la clase base y el uso del operador de asignación de la clase base sobrecargada

[Código de muestra] demuestra principalmente el uso de la clase derivada en C++ para llamar al constructor de copias de la clase base y sobrecargar el operador de asignación de la clase base:

#include<iostream>
using namespace std;

class CBase
{
public:
    CBase() {} // 默认构造函数
    // 复制构造函数
    CBase(const CBase& c)
    {
        cout << "CBase::复制构造函数" << endl;
    }
    // 重载赋值运算符
    CBase& operator=(const CBase &b)
    {
        cout << "CBase::operator=" << endl;
        return *this;
    }
};

class CDerived : public CBase
{
public:
    CDerived()
    {
        cout << "CDerived::复制构造函数" << endl;
    }
};

int main()
{
    CDerived d1, d2;
    CDerived d3(d1); // d3 初始化过程中会调用类 CBase 的复制构造函数
    d2 = d1; // 会调用类 CBase 重载的"="运算符
    return 0;
}

【Explicación del código】

  1. #include<iostream>: contiene archivos de encabezado  iostreampara operaciones de entrada y salida.

  2. using namespace std: Utilice un espacio de nombres  stdpara evitar escribir cada vez  std::.

  3. class CBase: Define una clase CBase que contiene un constructor predeterminado, un constructor de copia y un operador de asignación sobrecargado.

  4. CBase(const CBase& c): Define el constructor de copia de la clase CBase, que se usa para llamar cuando se copian objetos, y genera la cadena "CBase::constructor de copia".

  5. CBase& operator=(const CBase &b): Define el operador de asignación sobrecargado de la clase CBase, genera la cadena "CBase::operator=" y devuelve una referencia a un objeto CBase.

  6. class CDerived : public CBase: se define una clase pública derivada CDerived, que hereda de la clase CBase y contiene un constructor predeterminado.

  7. CDerived(): Define el constructor predeterminado de la clase CDerived, generando la cadena "CDerived:: copy constructor".

  8. main(): Se inicia la función principal.

  9. CDerived d1, d2: Defina los objetos d1 y d2 y llame al constructor predeterminado de la clase CDerived.

  10. CDerived d3(d1): Defina el objeto d3 y tome d1 como parámetro, y llame al constructor de copias de la clase CBase durante el proceso de inicialización.

  11. d2 = d1: Para copiar el objeto d1 al objeto d2, se llamará al operador sobrecargado "=" de la clase CBase.

  12. return 0: El programa finaliza y el valor de retorno es 0.

【Resultados del】

  • En  la función, main() primero se crean  dos CDerived objetos de clase  d1 y  d2se llama  CDerived al constructor de la clase  CDerived::复制构造函数 para completar la inicialización del objeto.
  • Luego,  el objeto CDerived d3(d1) se crea a través del método  De hecho, d3 primero se llamará  CBase al constructor de copia  CBase::复制构造函数y luego  se llamará CDerived al constructor  CDerived::复制构造函数para completar la inicialización del objeto y asignar memoria.
  • Cabe señalar que  d1 la referencia del objeto define  CBase algunos miembros de la clase, y CDerived la clase usa estos miembros  d3 para generar una copia de su parte de clase base en el objeto.
  • Por lo tanto, cuando un objeto de clase derivada se usa como parámetro para realizar una operación de asignación o de constructor de copia, su parte de clase base también debe copiarse o asignarse (porque la parte de clase base tiene una ubicación diferente en la memoria).
  •  Finalmente, la inicialización  del objeto d2 = d1 se realiza  a través  del método, y  el operador de asignación sobrecargado por la clase  será llamado primero para completar la inicialización del objeto.d2CBaseCBase::operator=
CDerived::复制构造函数
CBase::复制构造函数
CBase::operator=

(3) Constructores y destructores de herencia múltiple 

  • Al crear un objeto de una clase derivada con varias clases base, llame a sus constructores en secuencia de acuerdo con el orden de las clases base dado en la definición de clase y luego llame al constructor de la clase derivada.
  • Cuando el objeto muere, se llama al destructor en el orden inverso al de las llamadas al constructor.
  • Antes de que se ejecute el constructor de la clase derivada, primero se deben ejecutar los constructores de las dos clases base y el orden de ejecución depende del orden de las clases base enumeradas cuando se define la clase derivada DerivedClass.

[Ejemplo]  La primera declaración al definir una clase derivada es:

class DerivedClass : public BaseClass1, public BaseClass2
Por lo tanto, primero ejecute el constructor de la clase base BaseClass1, luego ejecute el constructor de la clase base BaSeClass2 y luego ejecute el constructor de la clase derivada DerivedClass.


En cuarto lugar, la relación entre clases.

(1) La relación entre clases   

  • Hay dos formas de escribir nuevas clases usando clases existentes: herencia y composición.
  • Esto también forma dos relaciones básicas entre clases: relación de herencia y relación de composición (la relación de composición es también la relación de inclusión mencionada en la Sección 6 del Capítulo 3) .
  • Las relaciones de herencia también se conocen como relaciones " es un " o relaciones "es".
  • La relación de combinación también se denomina relación " tiene una " o relación "tiene", que se expresa como una clase cerrada, es decir, una clase utiliza objetos de otra clase como variables miembro.

(2) Derivación de clases cerradas 

  • Una clase se cierra si las variables miembro de una clase son objetos de otra clase.
  • La forma general de definir un constructor de clase envolvente es la siguiente:
    类名::类名(形参表):内嵌对象1(形参表),内嵌对象2(形参表),…
    {
        类体
    }
  • Entre ellos, "objeto incrustado 1 (tabla de parámetros formales), objeto incrustado 2 (tabla de parámetros formales), ..." es una lista de inicialización y su función es inicializar el objeto incrustado.

(3) Clases con relación de inclusión mutua 

Cuando se trata de problemas relativamente complejos y es necesario considerar la combinación de clases, es probable que se encuentre con la situación en la que dos clases se refieren entre sí, lo que se denomina dependencia circular.

[Ejemplo] relación de inclusión mutua

[Código de ejemplo] En el ejemplo de C++, la clase classA y la clase classB se contienen entre sí. En este caso, generalmente es necesario usar una declaración directa para resolver el problema:

// 前置声明 classB,使得在定义 classA 时可以使用 classB 类型的参数
class classB;

// 定义类 classA
class classA
{
public:
    // 定义类中的一个函数 f,它的参数是类 B 对象 b,无返回值
    void f(classB b);
};

// 定义类 classB
class classB
{
public:
    // 定义类中的一个函数 g,它的参数是类 A 对象 a,无返回值
    void g(classA a);
};

【Explicación del código】

  1. Este código define principalmente dos clases classA y  classB, y llaman a los tipos de los demás como parámetros. En este momento, las declaraciones directas se pueden usar para resolver el problema de la dependencia cruzada, a fin de evitar la dependencia mutua de dos clases y hace que la compilación fallar.

  2. classA Entre ellos, la función pública se declara en f, sus parámetros son  classB objetos de clase b, la función no tiene valor de retorno, y en classB La función pública se declara en g, su parámetro es un  classA objeto  de clase ay la función no tiene valor de retorno.

  3. Para la declaración de avance, en realidad es como una "promesa", que promete que una clase classB se  , y luego la clase se puede definir antes de eso classA.

  4. El objetivo final es permitir que el compilador sepa primero la existencia de la clase, de modo que las clases que aparecen más tarde puedan usarse.

  5. Si no hay una declaración de avance, classA cuando  , no sabe que hay una clase que aparecerá más tarde classB, por lo que se producirá un error de compilación.

【Resultados del】

  • Este código en sí solo define dos clases  classA y  classB, y  una función classA en la que toma un  classB objeto como parámetro  f, y  classB una función en y toma un  classA objeto como parámetro  g, y no instancia ni llama a estas clases.
  • Por lo tanto, no hay salida durante la ejecución del código, pero el compilador pasa la compilación y genera un archivo ejecutable.


Cinco, derivación multinivel

En C++, la derivación puede ser multinivel.
  • Por ejemplo, la clase CStudent deriva la clase CGraduatedStudent, que a su vez puede derivar CDoctorStudent y así sucesivamente.
  • En resumen, la clase A deriva de la clase B, la clase B puede derivar de la clase C, la clase C puede derivar de la clase D, y así sucesivamente.
  • En este caso, se dice que la clase A es la clase base directa de la clase B, la clase B es la clase base directa de la clase C y la clase A es la clase base indirecta de la clase C.
  • Por supuesto, la clase A también es una clase base indirecta de la clase D.
  • Al definir una clase derivada, solo necesita escribir la clase base directa, no la clase base indirecta.
  • Una clase derivada hereda automáticamente todos los miembros de sus clases base directas e indirectas en la jerarquía de clases.
  • En C++, la relación de herencia entre clases es transitiva .
⚫Los miembros de la clase derivada incluyen los miembros definidos por la propia clase derivada, los miembros definidos en la clase base directa y todos los miembros definidos en todas las clases base indirectas.
⚫Al generar un objeto de una clase derivada, todos los constructores de la clase base se ejecutarán capa por capa desde la clase base más alta, y finalmente se ejecutará el propio constructor de la clase derivada; cuando el objeto de la clase derivada muera, se ejecutará su propio constructor primero destructor, luego fondo
Ejecuta los destructores de cada clase base a su vez.


6. Conversión mutua entre la clase base y los punteros de clase derivados 

  • En el caso de la derivación pública, debido a que el objeto de la clase derivada también es el objeto de la clase base, el objeto de la clase derivada se puede asignar al objeto de la clase base. Para el tipo de puntero, puede usar el puntero de la clase base para apuntar al objeto de la clase derivada, o puede asignar directamente el puntero de la clase derivada al puntero de la clase base .
  • Pero incluso si el puntero de la clase base apunta a un objeto de una clase derivada, no puede acceder a las funciones miembro que no están en la clase base sino que solo están definidas en la clase derivada a través del puntero de la clase base.

[Ejemplo] Relación entre la clase derivada y la clase base

[Código de ejemplo] En el ejemplo de C++, demuestra principalmente la relación entre la clase derivada y la clase base:

#include <iostream>
using namespace std;

//定义类 CBase
class CBase {
protected:
    int n;
public:
    // 构造函数
    CBase(int i) : n(i) {}
    // 成员函数
    void print() {
        cout << "CBase:n=" << n << endl;
    }
};

// 定义类 CDerived 继承于 CBase
class CDerived : public CBase {
public:
    int v;
    // 构造函数
    CDerived(int i) : CBase(i), v(2 * i) {}
    // 成员函数
    void Func() {};
    void print() {
        cout << "CDerived:n=" << n << endl;
        cout << "CDerived:v=" << v << endl;
    }
};

int main() {
    // 定义 CDerived 类和 CBase 类的对象
    CDerived objDerived(3);
    CBase objBase(5);
    // 使用基类指针指向派生类对象
    CBase* pBase = &objDerived;
    // 使用派生类指针指向派生类对象
    CDerived* pDerived;
    pDerived = &objDerived;
    cout << "使用派生类指针调用函数" << endl;
    // 调用的是派生类中的函数
    pDerived->print();
    // 基类指针=派生类指针,正确
    pBase = pDerived;
    cout << "使用基类指针调用函数" << endl;
    // 调用是基类中的函数
    pBase->print();
    // 错误,通过基类指针不能调用派生类函数
    // pBase->Func( );
    // 错误 派生类指针=基类指针
    // pDerived = pBase;
    // 强制类型转换,派生类指针=基类指针
    pDerived = (CDerived*)pBase;
    cout << "使用派生类指针调用函数" << endl;
    // 调用的是派生类中的函数
    pDerived->print();
    return 0;
}

【Explicación del código】

  1. Primero define dos clases  CBase y  CDerived, de la que  CDerived se  CBase deriva, tiene una  v variable miembro más y una  Func() función miembro.

  2. En  main() , primero defina una  CDerived clase y un  CBase objeto de clase, luego use el puntero de clase base para apuntar al objeto de clase derivado, luego use el puntero de clase derivado para apuntar al objeto de clase derivado y finalmente realice la conversión de tipo obligatoria.

  3. Una cosa a tener en cuenta es que cuando se usa el puntero de clase base, solo se pueden usar las funciones miembro en la clase base, y las funciones miembro únicas en la clase derivada no se pueden usar. Debe convertirse en un puntero de clase derivada para usar .

  4. Además, aunque es posible convertir un puntero de clase base en un puntero de clase derivada, hacerlo requiere mucho cuidado porque una conversión incorrecta puede provocar errores de programa.

【Resultados del】

  • Los resultados muestran que cuando el puntero de la clase base se usa para apuntar al objeto de la clase derivada, se llama a la función de la clase base, y cuando se usa el puntero de la clase derivada, se llama a la función de la clase derivada, y después del puntero conversión de tipo, el puntero de clase derivada puede llamar a funciones en clases derivadas:
使用派生类指针调用函数
CDerived:n=3
CDerived:v=6
使用基类指针调用函数
CBase:n=3
强制类型转换,派生类指针=基类指针
使用派生类指针调用函数
CDerived:n=3
CDerived:v=6

Supongo que te gusta

Origin blog.csdn.net/qq_39720249/article/details/131383126
Recomendado
Clasificación