C ++ entrevista preguntas clásicas

1: La diferencia entre punteros y referencias


Un puntero es una nueva variable que apunta a la dirección de otra variable, y podemos modificar otra variable accediendo a esta dirección, y una referencia es un alias, y la operación de la referencia es operar sobre la variable misma.  tienen múltiples niveles, y la referencia Cuando solo se pasa el parámetro de primer nivel 
, el parámetro debe ser desreferenciado si se usa el puntero, y el parámetro se puede modificar directamente usando la referencia 
El tamaño del puntero es generalmente de 4 bytes , y el tamaño de la referencia depende del tamaño del objeto al que se hace referencia (Se refiere al resultado obtenido al usar el operador sizeof, y la referencia es esencialmente el uso de un puntero, por lo que la memoria ocupada es la misma que la del puntero) El 
el puntero puede ser nulo, pero la referencia no.

Dos: ¿Cuándo usar punteros y cuándo usar referencias al pasar parámetros de función?

Utilice punteros cuando necesite devolver la memoria de una variable local dentro de una función. El uso de punteros para pasar parámetros necesita abrir la memoria y recuerde liberar los punteros cuando se agote, de lo contrario habrá pérdidas de memoria. Y devolver una referencia a una variable local no tiene sentido 

Use referencias cuando sea sensible al tamaño del espacio de pila (como la recursividad). Pasar por referencia no requiere la creación de variables temporales y la sobrecarga es menor 

Use referencias cuando pase objetos de clase como parámetros, que es la forma estándar de pasar objetos de clase en C++

Tres: ¿Cuál es la diferencia entre montón y pila?

Por definición: el montón es una pieza de memoria abierta por new y malloc, que el programador administra manualmente, y la pila es la memoria administrada automáticamente por el compilador, que almacena parámetros de funciones y variables locales. 

El espacio de almacenamiento dinámico generará fragmentación de la memoria debido a las frecuentes operaciones de asignación y liberación. 

El espacio de crecimiento del montón es hacia arriba, y la dirección se hace cada vez más grande, y el espacio de crecimiento de la pila es hacia abajo, y la dirección se hace cada vez más pequeña.

Cuatro: ¿El montón es más rápido o la pila es más rápida? (los bytes se procesan por lotes por adelantado)

 Apila más rápido. Debido a que el sistema operativo proporcionará soporte para la pila en la capa inferior y asignará un registro especial para almacenar la dirección de la pila, las operaciones de inserción y extracción de la pila también son muy simples y hay instrucciones especiales para ejecutar, por lo que el stack es más eficiente y rápido. La biblioteca de funciones de C/C++ proporciona el funcionamiento del montón. Al asignar la memoria del montón, se requiere cierto [algoritmo]() para encontrar un tamaño adecuado de memoria. Y obtener el contenido del montón requiere dos accesos, el primero para acceder al puntero y el segundo para acceder a la memoria según la dirección guardada por el puntero, por lo que el montón es más lento.

Cinco: cómo se implementan new y delete, las similitudes y diferencias entre new y malloc

Cuando se crea un nuevo objeto, primero se llama a malloc para asignar espacio de memoria para el objeto y luego se llama al constructor del objeto. delete llamará al destructor del objeto y luego llamará a free para recuperar la memoria. 

Tanto new como malloc asignarán espacio, pero new también llamará al constructor del objeto para inicializar, malloc necesita un tamaño de espacio determinado y new solo necesita el nombre del objeto.

Seis: Dado que existe malloc/free, ¿por qué necesita new/delete en C++?

malloc/free y new/delete se utilizan para solicitar y recuperar memoria.

Cuando se utilizan objetos de tipos de datos no básicos, el constructor debe ejecutarse cuando se crea el objeto y el destructor debe ejecutarse cuando se destruye el objeto. Y malloc/free es una función de biblioteca, que ya es código compilado, por lo que las funciones de constructor y destructor no se pueden imponer a malloc/free.

Siete: La diferencia entre C y C++

incluyendo pero no limitado a:

C es un lenguaje procedimental, C++ es un lenguaje orientado a objetos y C++ tiene las características de "encapsulación, herencia y polimorfismo". La encapsulación oculta los detalles de implementación y hace que el código sea modular. La herencia implementa la reutilización de código a través de subclases que heredan los métodos y propiedades de la clase principal. El polimorfismo es "una interfaz, múltiples implementaciones", que implementa la reutilización de la interfaz reescribiendo la función virtual de la clase principal por subclases.

C y C++ tienen diferentes métodos de administración de memoria.C usa malloc/free, y C++ también usa new/delete.

También hay conceptos como sobrecarga de funciones y referencias en C++, pero no en C.

Ocho: la diferencia entre borrar y borrar[]

delete solo llamará al destructor una vez, mientras que delete[] llamará al destructor de cada miembro

La memoria asignada con new se libera con delete, y la memoria asignada con new[] se libera con delete[]

Nueve: La conexión y la diferencia entre C++ y Java, incluidas las características del lenguaje, la recolección de elementos no utilizados, los escenarios de aplicación, etc. (mecanismo de recolección de elementos no utilizados de Java)

incluyendo pero no limitado a:

Tanto C++ como Java son lenguajes orientados a objetos. C++ se compila en archivos ejecutables y se ejecutan directamente. JAVA se compila y ejecuta en la máquina virtual JAVA. Por lo tanto, JAVA tiene buenas características multiplataforma, pero la eficiencia de ejecución no es tan alta como C++. 

El programador gestiona manualmente la gestión de la memoria de C++, la máquina virtual de Java completa la gestión de la memoria de JAVA y su recolección de elementos no utilizados utiliza el [algoritmo]() de recuperación de marcas. 

C++ tiene punteros, Java no tiene punteros, solo referencias 

Tanto JAVA como C++ tienen constructores, pero C++ tiene destructores pero Java no.

Diez: La diferencia entre C++ y python

incluyendo pero no limitado a: 

1. Python es un lenguaje de secuencias de comandos que se interpreta y ejecuta, mientras que C++ es un lenguaje compilado, que debe compilarse y ejecutarse en una plataforma específica. Python puede ser fácilmente multiplataforma, pero no tan eficiente como C++. 

2. Python usa sangría para distinguir diferentes bloques de código, C++ usa llaves para distinguir 

3. El tipo de variables debe definirse de antemano en C++, pero no en python. Los tipos de datos básicos de python son solo números, booleanos, cadenas, listas, tuplas, etc. 

4. Python tiene más funciones de biblioteca que C++, y es muy conveniente llamar

Eleven: La diferencia entre Struct y clase

Al usar struct, los derechos de acceso de sus miembros son públicos de forma predeterminada y los miembros de la clase son privados de forma predeterminada.

la herencia de estructura tiene como valor predeterminado la herencia pública y la herencia de clase tiene como valor predeterminado la herencia privada

La clase se puede usar como plantilla, mientras que la estructura no.

Doce: La conexión y diferencia entre define y const (fase de compilación, seguridad, huella de memoria, etc.)

Enlaces: ambos son una forma de definir constantes.

la diferencia:

Las constantes definidas por define no tienen tipo, sino que simplemente se reemplazan. Puede haber varias copias, lo que ocupa mucho espacio en la memoria. Las constantes definidas por const tienen tipos y se almacenan en el área de almacenamiento estático. Solo hay una copia. , que ocupa espacio en la memoria.

La constante definida por define se reemplaza en la etapa de preprocesamiento, mientras que const determina su valor en la etapa de compilación.

define no realizará la verificación de seguridad de tipo, mientras que const realizará la verificación de seguridad de tipo, que es más segura.

const puede definir funciones pero define no puede.

Trece: Uso de const en C++ (definición, propósito)

Cuando const modifica una variable miembro de una clase, significa que la constante no se puede modificar

Una función miembro de una clase modificada por constante significa que la función no modificará los miembros de datos en la clase y no llamará a otras funciones miembro que no sean constantes

Catorce: uso estático y significado en C++

estático significa estático y se puede utilizar para modificar variables, funciones y miembros de clase.

- Variable: Una variable modificada por estática es una variable estática, que siempre existirá durante la ejecución del programa y se colocará en el área de almacenamiento estático. El alcance de las variables estáticas locales está en el cuerpo de la función y el alcance de las variables estáticas globales está en este archivo. Después de cambiar una variable local a una variable estática, cambia su método de almacenamiento, es decir, su tiempo de vida. Cambiar una variable global a una variable estática cambia su alcance y limita su alcance de uso. Las variables modificadas con la palabra clave estática tienen ámbito de archivo.
- Función: una función modificada por estática es una función estática. Una función estática solo se puede usar en este archivo y no puede ser llamada por otros archivos, ni entrará en conflicto con funciones del mismo nombre en otros archivos.
- Clase: en una clase, una variable miembro modificada por estática es un miembro estático de clase, y este miembro estático será compartido por varios objetos de la clase. La función miembro modificada por estática también es un miembro estático, que no pertenece a un objeto. Para acceder a esta función estática no es necesario hacer referencia al nombre del objeto, sino acceder a ella haciendo referencia al nombre de la clase.

[nota] Cuando una función de miembro estático quiere acceder a un miembro no estático, debe usar un objeto para referirse a él. Las variables estáticas locales no se reciclarán después de que finalice la llamada a la función y permanecerán en la memoria del programa hasta que se vuelva a llamar a la función, y su valor seguirá siendo el valor después de la última llamada.

Tenga en cuenta la diferencia con const. Const enfatiza que el valor no se puede modificar, mientras que static enfatiza la única copia, que es compartida por objetos de todas las clases.

Quince: Introducción a C++ STL (esta serie también es muy importante, recomiendo los libros y videos del Sr. Hou Jie en esta área), incluido el asignador de administración de memoria, la función, el mecanismo de implementación, la implementación de subprocesos múltiples, etc.

C++ STL incluye ampliamente tres categorías: [algoritmos](), contenedores e iteradores.

[Algorithm]() incluye [sort](), [algorithm]() común, como copiar, y [algorithm]() específico para diferentes contenedores. 

El contenedor es la forma de almacenamiento de datos, incluido el contenedor en serie y el contenedor asociativo, el contenedor en serie es una lista, un vector, etc., el contenedor asociativo es un conjunto, un mapa, etc. 

Un iterador es un recorrido de un contenedor sin exponer su estructura interna.

Dieciséis: Implementación de tabla hash en código fuente STL

La tabla hash en STL es unordered_map. Se implementa usando hash (observe la diferencia con el mapa). La clave que registra es el valor hash del elemento, y el valor del elemento se determina comparando el valor hash del elemento. 

La implementación subyacente de unordered_map es hashtable, que utiliza el método de cadena abierta (es decir, el uso de cubos) para resolver conflictos hash. El tamaño predeterminado del cubo es 10.

Diecisiete: ¿Cómo resolver las colisiones de hash?

1. Sondeo lineal. Cuando el cubo correspondiente al valor hash del elemento no puede almacenar el elemento, busque hacia atrás uno por uno hasta que encuentre un cubo vacío. Lo mismo se aplica al buscar. Cuando el elemento en la posición correspondiente del valor hash es diferente del elemento a buscar, luego mire hacia atrás uno por uno hasta que encuentre un elemento coincidente o un cubo vacío. 

2. Exploración secundaria. Cuando el cubo correspondiente al valor hash del elemento no puede almacenar el elemento, buscará las posiciones 1^2, 2^2, 3^2, 4^2.....i^2. 

3. Método de función hash doble. Cuando la primera función hash colisiona, use la segunda función hash para el hash, como tamaño de paso. 

4. Método de cadena abierta. Se mantiene una [lista enlazada]() en cada depósito, el depósito se encuentra por el valor hash del elemento y luego el elemento se inserta en la [lista enlazada]() correspondiente. La tabla hash de STL adopta este método de implementación. 

5. Cree un área de desbordamiento común. Cuando ocurra un conflicto, coloque todos los datos en conflicto en el área de desbordamiento común.

Dieciocho: La diferencia entre unordered_map y map en STL

Unordered_map se implementa mediante hash, que ocupa mucha memoria, y la velocidad de consulta es relativamente rápida, lo que supone una complejidad de tiempo constante. Está desordenado internamente y necesita implementar el operador ==. 

La capa inferior del mapa se implementa mediante [árbol rojo-negro](). La complejidad temporal de insertar y eliminar consultas es O(log(n)), y su interior está ordenado, por lo que el operador de comparación (<) debe ser implementado.

Diecinueve: Implementación de vector en STL

Un vector en STL es un contenedor secuencial que encapsula una matriz dinámica. Sin embargo, a diferencia de las matrices dinámicas, los vectores pueden expandir automáticamente el tamaño de su contenedor según sea necesario. La estrategia específica es volver a solicitar una pieza de memoria del doble del tamaño original cada vez que la capacidad sea insuficiente, copiar los elementos del contenedor original al nuevo contenedor, liberar el espacio original y devolver un puntero al nuevo espacio. 

Cuando el espacio original no es suficiente para almacenar el nuevo valor, cada vez que se llama al método push_back, se reasigna un nuevo espacio para satisfacer la adición de nuevos datos. Si esta operación se realiza con frecuencia en el programa, consumirá más rendimiento.

Veinte: Notas sobre el uso de vectores y sus razones, el impacto y las razones de llamar con frecuencia a push_back() en el rendimiento del vector.

Si necesita insertar con frecuencia, es mejor especificar primero el tamaño del vector, porque cuando el tamaño del contenedor no es suficiente, el vector se volverá a aplicar para un espacio dos veces el tamaño del contenedor original, copie los elementos del contenedor original al nuevo contenedor y liberar el contenedor original.Este proceso consume mucho tiempo y memoria. Llamar con frecuencia a push_back() hará que el programa dedique mucho tiempo a la expansión del vector, lo que se volverá muy lento. En este caso, considere usar una lista.

Veintiuno: La diferencia entre vector y lista en C++

Similar a una matriz, un vector tiene un espacio de memoria contiguo. El vector aplica para una memoria continua.Cuando la memoria para insertar nuevos elementos no es suficiente, generalmente vuelve a solicitar una memoria más grande 2 veces y copia los elementos originales para liberar el espacio anterior. Debido a que el espacio de la memoria es continuo, al insertar y eliminar operaciones, se producirá una copia del bloque de memoria y la complejidad del tiempo estará activada.

La lista se implementa mediante una [lista enlazada]() doble, por lo que el espacio de memoria es discontinuo. Solo se puede acceder a los datos a través de punteros, por lo que el acceso aleatorio de la lista es muy ineficiente y la complejidad del tiempo es o(n); pero debido a las características de [linked list](), la inserción y la eliminación se pueden realizar de manera eficiente.

Vector tiene un espacio de memoria continuo y puede admitir muy bien el acceso aleatorio, por lo que vector::iterator admite "+", "+=", "<" y otros operadores.

El espacio de memoria de list puede ser discontinuo, no admite acceso aleatorio, por lo que list::iterator no admite "+", "+=", "<", etc.

Tanto vector::iterator como list::iterator sobrecargan el operador "++".

En resumen, si necesita un acceso aleatorio eficiente y no le importa la eficiencia de la inserción y eliminación, use vector;

Si necesita muchas inserciones y eliminaciones y no le importa el acceso aleatorio, debe usar list.

Veintidós: La diferencia entre sobrecargar, reescribir y redefinir en C++:

- ==Sobrecarga==: se refiere a la implementación de funciones con el mismo nombre de función y diferentes listas de parámetros. Sus valores de retorno pueden ser diferentes, pero el valor de retorno no se puede usar como un signo para distinguir diferentes funciones sobrecargadas. 
  1. En la misma clase
  2. El nombre de la función es el mismo
  3. <span style="color:red">Los parámetros de la función son diferentes (diferentes tipos, diferentes cantidades, ambos pueden satisfacer uno de ellos)</span >,
  4. No condiciona la sobrecarga de la función a un tipo de retorno diferente.
- ==Redefiniendo (redefiniendo)==: se refiere al método de implementación con el mismo nombre de función, solo el cuerpo del método es diferente. La subclase redefine **función no virtual** con el mismo nombre en la superclase (la lista de parámetros puede ser diferente). El fenómeno de que el método del mismo nombre de la subclase oculta el método de la clase principal se denomina **oculto**. 
  1. Similar a la sobrecarga, pero el alcance es diferente, la subclase reescribe la clase principal
  2. Similar a la anulación, pero el método en la clase principal no es una función virtual.
  3. Si la función de la clase derivada y la función de la clase base tienen el mismo nombre pero diferentes parámetros, en este momento, la función de la clase base está oculta independientemente de si hay virtual.
  4. Si la función de la clase derivada tiene el mismo nombre y los mismos parámetros que la función de la clase base, pero la función de la clase base no tiene la palabra clave virtual, entonces la función de la clase base está oculta.
- ==Override==, utilizado para implementar el polimorfismo en C++:
  1. Ubicado en la clase principal y la subclase respectivamente
  2. <span style="color:red">La subclase reescribe el método virtual en la clase principal</span >;
  3. Lo mismo que el prototipo de función en la clase principal.
  4. La función anulada no puede ser estática.

**Nota: la cobertura de funciones es la relación entre subclases y clases principales, que es una relación vertical; mientras que la sobrecarga es la relación entre diferentes métodos en la misma clase, que es una relación horizontal;**

Veintitrés: Gestión de memoria C++ (Preguntas frecuentes)

En C++, la memoria se divide en 5 áreas: montón, pila, área de almacenamiento global/estático, área de almacenamiento constante y área de código.

- Pila, cuando se ejecuta la función, las unidades de almacenamiento de las variables locales en la función se pueden crear en la pila, y estas unidades de almacenamiento se liberan automáticamente cuando finaliza la ejecución de la función. La operación de asignación de memoria de pila está integrada en el conjunto de instrucciones del procesador, que es muy eficiente, pero la capacidad de memoria asignada es limitada. 
- El heap es el bloque de memoria asignado por new, y su liberación es ignorada por el compilador y controlada por nuestra aplicación Generalmente, un new corresponde a un delete. Si el programador no lo libera, el sistema operativo lo reciclará automáticamente después de que finalice el programa. 
- Área de almacenamiento global/estática, la memoria se ha asignado cuando se compila el programa, y ​​esta memoria existe durante todo el tiempo de ejecución del programa. Almacena principalmente datos estáticos (variables estáticas locales, variables estáticas globales), variables globales y constantes. 
- Área de almacenamiento constante, que es un área de almacenamiento especial, que almacena cadenas constantes, que no se pueden modificar. 
- Área de código, que almacena el código binario del programa 

Hay muchos dichos sobre esto, algunos agregarán un área de almacenamiento libre para almacenar la memoria asignada por malloc, similar al montón.

Veinticuatro: Presente las tres características principales de la orientación a objetos y dé ejemplos de cada una.

Las tres características principales de la orientación a objetos son: encapsulación, herencia y polimorfismo.

- La encapsulación oculta los detalles de implementación y los datos de los miembros de la clase, y realiza la modularización del código, como privado y público en la clase;

- La herencia permite que las subclases reutilicen los miembros y métodos de la clase principal, realizando la reutilización del código;

- El polimorfismo es "una interfaz, múltiples implementaciones". Los miembros de la subclase se llaman a través de la clase principal, que realiza la reutilización de la interfaz, como que el puntero de la clase principal apunta al objeto de la subclase.

Veinticinco: Implementación del polimorfismo (respuesta con la siguiente pregunta)

El polimorfismo de C++ incluye polimorfismo en tiempo de compilación y polimorfismo en tiempo de ejecución. El polimorfismo en tiempo de compilación se refleja en la sobrecarga de funciones y plantillas, y el polimorfismo en tiempo de ejecución se refleja en funciones virtuales.

- Función virtual: agregue la palabra clave virtual antes de la función de la clase base, reescriba la función en la clase derivada y el tiempo de ejecución llamará a la función correspondiente según el tipo real del objeto. Si el tipo de objeto es una clase derivada, se llama a la función de la clase derivada; si el tipo de objeto es una clase base, se llama a la función de la clase base.

Veintiséis: función virtual relacionada con C++ (tabla de función virtual, puntero de función virtual), principio de implementación de la función virtual (popular, importante)

Las funciones virtuales de C++ son un mecanismo para implementar polimorfismo. Se implementa a través de la tabla de funciones virtuales. La tabla de funciones virtuales es una matriz de punteros que almacenan direcciones de funciones virtuales en cada clase. Una instancia de una clase buscará la dirección de la función en la tabla de funciones virtuales al llamar a una función. Si el subclase anula la función de la clase principal, la tabla de funciones virtuales de la subclase apuntará a la dirección de la función implementada por la subclase; de ​​lo contrario, apuntará a la dirección de la función de la clase principal. Todas las instancias de una clase comparten la misma tabla de funciones virtuales.


- Si hay herencia múltiple y herencia múltiple, ¿cómo se ve la tabla de funciones virtuales de la subclase?
  En el caso de herencia múltiple, la función virtual de la clase padre del ancestro es más alta En el caso de herencia múltiple, la función virtual de la clase que está más cerca del nombre de la subclase es más alta en la tabla de funciones virtuales.
- En el caso de herencia múltiple, hay tantos punteros de tabla de funciones virtuales como clases base, siempre que la clase base tenga funciones virtuales para contar como clase base.
  Nota:
  1. La función virtual de la subclase anulará todas las funciones virtuales del mismo nombre de cada clase principal.
  2. La función virtual que no existe en la clase principal pero sí la tiene la subclase, completa la primera tabla de funciones virtuales y no se puede llamar con el puntero de la clase principal.
  3. Si hay funciones virtuales en la clase principal pero no en la subclase, no están cubiertas. Solo se pueden llamar subclases y punteros a la superclase.

Veintisiete: ¿Cómo debe manejar el compilador la tabla de funciones virtuales?

La forma en que el compilador maneja las funciones virtuales es:
si hay una función virtual en la clase, registra la dirección de la función virtual en la tabla de funciones virtuales de la clase. Cuando la clase derivada hereda la clase base, si hay una función virtual que anula la clase base, el puntero de función correspondiente en la tabla de funciones virtuales se establece en la dirección de función de la clase derivada; de lo contrario, apunta a la dirección de función de la base clase.
Agregue un puntero de tabla virtual (vptr) para cada instancia de la clase y el puntero de la tabla virtual apunta a la tabla de funciones virtuales de la clase. Cuando una instancia llama a una función virtual, encuentra la tabla de funciones virtuales en la clase a través del puntero de la tabla de funciones virtuales y encuentra la función correspondiente para llamar.

Veintiocho: la razón por la cual el destructor de la clase base generalmente se escribe como una función virtual

En primer lugar, el destructor puede ser una función virtual. Al destruir un puntero de clase principal a una subclase, el compilador puede encontrar el destructor de la subclase y llamarlo de acuerdo con la tabla de funciones virtuales, liberando así correctamente los recursos del objeto de la subclase. .

Si el destructor no se declara como una función virtual, el compilador implementa el enlace estático.Al eliminar el puntero de la clase principal a la subclase, solo llamará al destructor de la clase principal y no al destructor de la subclase, por lo que hará que la subclase el objeto se destruya de forma incompleta y provoque una fuga de memoria.

Nota: En la mayoría de los casos, el sistema operativo recuperará el objeto de la subclase cuando finalice el programa, pero si la memoria solicitada por new/malloc se usa en la subclase, debe destruirse manualmente, por lo que el destructor del padre la clase debe ser Para llamar correctamente al destructor de la subclase.

Veintinueve: ¿Por qué los constructores generalmente no se definen como funciones virtuales?

1) Porque **crear un objeto necesita determinar el tipo del objeto**, ** y la función virtual es determinar su tipo en tiempo de ejecución **. Al construir un objeto, **debido a que el objeto no se ha creado con éxito, el compilador no puede saber el tipo real del objeto**, si es la clase misma o una clase derivada de la clase, etc.

2) **La llamada de la función virtual requiere el puntero de la tabla de función virtual, y el puntero se almacena en el espacio de memoria del objeto**; si el constructor se declara como una función virtual, como el objeto aún no ha sido creado, no hay espacio de memoria, y ninguna función virtual.La dirección de la tabla de funciones se utiliza para llamar a la función virtual, es decir, el constructor.

Treinta: ¿Qué sucede cuando se llama a una función virtual en un constructor o destructor?

La función virtual se llama en el constructor. Dado que el objeto actual no se ha construido, la función virtual llamada en este momento apunta a la implementación de la función de la clase base.

Cuando se llama a una función virtual en el destructor, la implementación de la función de la subclase se llama en este momento.

Treinta y uno: funciones virtuales puras

Una función virtual pura es una función virtual que solo declara y no implementa, es una restricción en las subclases y es herencia de interfaz

Una clase que contiene una función virtual pura es una clase abstracta, no se puede instanciar, solo las subclases que implementan esta función virtual pura pueden generar objetos

Escenario de uso: cuando no tiene sentido que la clase en sí genere una instancia, implemente las funciones de esta clase como funciones virtuales puras. Por ejemplo, los animales pueden derivar tigres y conejos, pero no tiene sentido crear una instancia de un objeto animal. Y se puede especificar que las subclases derivadas pueden escribir funciones virtuales puras si deben anular ciertas funciones.

Treinta y dos: Introducción al enlace estático y al enlace dinámico

La vinculación estática consiste en vincular las funciones o propiedades relacionadas del objeto con su tipo estático, es decir, el tipo que se declara, determinado en tiempo de compilación. Cuando se le llama, el compilador buscará el tipo al que declara acceder.

La vinculación dinámica consiste en vincular las propiedades o funciones relacionadas con el objeto a su tipo dinámico. Las propiedades o funciones específicas se determinan en tiempo de ejecución, y la vinculación dinámica generalmente se logra a través de funciones virtuales.

Treinta y tres: La diferencia entre copia profunda y copia superficial (por ejemplo, la seguridad de la copia profunda)

La copia superficial es simplemente copiar el puntero del objeto, el objeto original y la copia apuntan al mismo recurso.

La copia profunda es para abrir un nuevo espacio, copiar los recursos del objeto original en el nuevo espacio y devolver la dirección del espacio.

Las copias profundas evitan las liberaciones repetidas y los conflictos de escritura. Por ejemplo, después de usar un objeto de copia superficial para liberar, la liberación del objeto original provocará una fuga de memoria o un bloqueo del programa.

Treinta y cuatro: comprensión de la reutilización de objetos, comprensión de la copia cero

La reutilización de objetos se refiere a patrones de diseño. Los objetos pueden usar diferentes patrones de diseño para lograr el propósito de la reutilización. Los más comunes son los patrones de herencia y composición.

La copia cero se refiere a evitar que la CPU copie de una tienda a otra al realizar operaciones. En Linux, podemos reducir la implementación de la copia de datos de un lado a otro entre el espacio del kernel y el espacio del usuario, como llamando a mmap() en lugar de llamadas de lectura.

> Use el programa para llamar a mmap(), los datos en el disco se copiarán en el búfer del núcleo a través de DMA, y luego el sistema operativo compartirá este búfer del núcleo con la aplicación, por lo que el contenido del búfer del núcleo no necesita para ser enviado a la copia del espacio del usuario. La aplicación vuelve a llamar a write() y el sistema operativo copia directamente el contenido del búfer del núcleo al búfer del zócalo. Todo esto sucede en el modo del núcleo. Finalmente, el búfer del zócalo envía los datos a la tarjeta de red.

Treinta y cinco: presenta todos los constructores de C++

Hay tres tipos principales de constructores en C++: constructores predeterminados, constructores sobrecargados y constructores de copia.

- El constructor por defecto es un constructor proporcionado por el compilador por defecto cuando la clase no implementa su propio constructor. 

- Los constructores sobrecargados también se denominan constructores generales. Una clase puede tener varios constructores sobrecargados, pero requieren diferentes tipos o números de parámetros. Puede personalizar cómo se inicializa una clase en un constructor sobrecargado. 

- El constructor de copia se llama cuando se produce una copia de objeto. 

Treinta y seis: En qué circunstancias se llamará al constructor de copias (tres situaciones)

- Los objetos pasan en parámetros de función por valor   

  > 如 `función nula(Perro perro){};`

- Los objetos se devuelven de funciones como pass-by-value   

  > p.ej. `Perro func(){ Perro d; return d;}`

- El objeto necesita ser inicializado por otro objeto 

Treinta y siete: Estructurar la alineación de la memoria y ¿por qué la alineación de la memoria?

Debido a que los miembros de la estructura pueden tener diferentes tipos de datos, el tamaño que ocupan también es diferente. Al mismo tiempo, dado que la CPU lee los datos en bloques, la alineación de la memoria permite que la CPU lea los datos necesarios al mismo tiempo.

Reglas de alineación:

- El primer miembro está en la dirección cuyo desplazamiento es 0 con la variable de estructura 
- Las otras variables miembro deben estar alineadas a la dirección de un múltiplo entero de un número determinado (número de alineación). 
- Alineación = el menor de la alineación predeterminada del compilador y el tamaño del miembro. 
- El valor predeterminado en Linux es 4 
- El valor predeterminado en vs es 8
  El tamaño total de la estructura es un múltiplo entero del número máximo de alineación (cada variable de miembro excepto el primer miembro tiene un número de alineación) 

Treinta y ocho: La definición de pérdida de memoria, ¿cómo detectarla y evitarla?

El espacio creado por la asignación dinámica de memoria no se libera manualmente después de su uso, lo que da como resultado que la memoria esté ocupada todo el tiempo, lo que es una fuga de memoria.

Hay varias razones para las fugas de memoria:

1) new y delete no coinciden en el constructor y el destructor de la clase

2) Eliminar [] no se usa al liberar la matriz de objetos, se usa eliminar

3) El destructor de la clase base no está definido como una función virtual. Cuando el puntero de la clase base apunta al objeto de la subclase, si el destructor de la clase base no es virtual, no se llamará al destructor de la subclase. Los recursos no se liberan correctamente, lo que provoca una pérdida de memoria

4) No hay punteros de objetos anidados claros correctos

Como evitar:

1. malloc/free debe estar empaquetado 

2. Use punteros inteligentes; 

3. Establezca el destructor de la clase base en una función virtual;

Treinta y nueve: ¿Qué son los punteros inteligentes en C++?

Los punteros inteligentes en C++ son auto_ptr, shared_ptr, débil_ptr y unique_ptr. Los punteros inteligentes son en realidad punteros encapsulados, que se pueden usar como punteros ordinarios y se pueden liberar solos para evitar fugas de memoria causadas por olvidar liberar la dirección de memoria a la que apunta el puntero. 

- auto_ptr es una versión anterior del puntero inteligente. Al copiar y asignar punteros, el puntero nuevo toma directamente los recursos del puntero anterior y apunta el puntero anterior a nulo, pero este método causará problemas cuando el puntero anterior deba ser accedido. .

- unique_ptr es una versión mejorada de auto_ptr, que no se puede asignar ni copiar, lo que garantiza que un objeto tenga solo un puntero inteligente al mismo tiempo.

- shared_ptr permite que un objeto tenga varios punteros inteligentes, que se reciclarán automáticamente cuando se destruyan todos los punteros inteligentes del objeto. (mantenido internamente usando un mecanismo de conteo)

- débil_ptr parece ayudar a shared_ptr. No puede acceder al objeto y solo puede observar el recuento de referencias de shared_ptr para evitar interbloqueos.

Cuarenta: Métodos de depuración de programas

- Depuración mediante el establecimiento de puntos de interrupción

- registro de impresión para la depuración

- imprimir resultados intermedios para la depuración

Cuarenta y uno: cómo depurar cuando se encuentra con volcado de memoria

Un volcado de núcleo es un archivo llamado núcleo generado bajo ciertas condiciones cuando un programa sale o finaliza de manera anormal en tiempo de ejecución debido a una excepción o error. Este archivo de núcleo registra la memoria, el estado de registro, el puntero de memoria y la información de la pila de funciones del programa durante el tiempo de ejecución. Espere . El análisis de este archivo puede ubicar la información de llamada de pila correspondiente cuando el programa es anormal.

- Use el comando gdb para depurar el archivo central

Cuarenta y dos: cuál es la diferencia entre la palabra clave en línea y la definición de macro

En línea significa en línea, puede definir funciones más pequeñas. Debido a que las llamadas frecuentes de funciones ocuparán mucho espacio en la pila, y las operaciones de empujar y sacar de la pila también consumirán recursos informáticos, por lo que puede usar la palabra clave en línea para decorar funciones pequeñas que se llaman con frecuencia. El compilador incrusta el cuerpo del código en el bloque de llamada de una función en línea durante la fase de compilación.

1. Las funciones en línea se expanden en tiempo de compilación, mientras que las macros se expanden en tiempo de precompilación

2. En tiempo de compilación, las funciones en línea se incrustan directamente en el código objeto y las macros son solo un simple reemplazo de texto.

3. Las funciones en línea pueden realizar funciones de compilación, como verificaciones de seguridad de tipo y si las declaraciones son correctas. Las macros no tienen tales funciones.

4. Una macro no es una función, pero un inline es una función

5. Los parámetros de macro deben manejarse con cuidado al definir macros, generalmente encerrados entre paréntesis, de lo contrario, se producirá fácilmente la ambigüedad. Las funciones en línea no parecen ambiguas.

6. En línea no se puede expandir, la macro debe expandirse. Debido a que la directiva en línea es solo una sugerencia para el compilador, el compilador puede optar por ignorar la sugerencia y no expandir la función.

7. La definición de macro es similar a una función en forma, pero cuando se usa, es solo un reemplazo simple en la tabla de símbolos del preprocesador, por lo que no puede realizar la detección de validez de parámetros y no puede disfrutar del tipo estricto del compilador C++. Los beneficios de comprobar, además, su valor de retorno no se puede convertir a un tipo adecuado que se pueda convertir, por lo que su uso tiene una serie de peligros ocultos y limitaciones.

Cuarenta y tres: el uso de plantillas y el principio de implementación de escenarios aplicables

Use la palabra clave de plantilla <typename T> para declarar, y luego puede escribir funciones de plantilla y clases de plantilla

El compilador compilará la plantilla de la función dos veces: la primera compilación compilará el código de la plantilla en el lugar de la declaración, esta compilación solo realizará una verificación de sintaxis y no generará un código específico. Al compilar el código por segunda vez, los parámetros se reemplazan y luego se compilan para generar el código de función específico.

Cuarenta y cuatro: El concepto de lista de inicialización de miembros, ¿por qué es más rápido usar la lista de inicialización de miembros (ventaja de rendimiento)?

Porque si la lista de inicialización de miembros se usa para la inicialización, el constructor de copia de los parámetros pasados ​​se usará directamente para la inicialización, lo que ahorra el proceso de ejecutar una vez el constructor predeterminado de los parámetros pasados; de lo contrario, el constructor predeterminado de los parámetros pasados ​​será llamó una vez. Entonces, usar la lista de inicialización de miembros será más eficiente.

Además, hay tres situaciones en las que se debe utilizar la lista de inicialización de miembros para la inicialización:

- Inicialización de miembros constantes, porque los miembros constantes solo se pueden inicializar y no se pueden asignar 
- Tipos de referencia 
- Los objetos sin un constructor predeterminado deben inicializarse mediante una lista de inicialización de miembros 

Cuarenta y cinco: ¿Ha utilizado C11 y conoce las nuevas características de C11? (Algunos entrevistadores sugieren estar familiarizado con C11)

- Deducción automática de tipo auto: La deducción automática de tipo auto se utiliza para inferir el tipo de datos de la variable a partir de la expresión de inicialización. A través de la deducción automática de tipo de auto, nuestro trabajo de programación se puede simplificar en gran medida

- nullptr
  : nullptr es un nuevo tipo introducido para resolver la ambigüedad de NULL en el C++ original, porque NULL en realidad representa 0, y nullptr es de tipo void*

- expresión lambda: es similar al cierre en Javascript, se puede usar para crear y definir objetos de función anónimos para simplificar la programación. La sintaxis de Lambda es la siguiente:
  `[parámetro de objeto de función] (parámetro de función de sobrecarga del operador) declaración mutable o de excepción -> tipo de valor devuelto {cuerpo de función}`

- clase de hilo y clase mutex

- Nuevos punteros inteligentes unique_ptr y shared_ptr

Cuarenta y seis: convenciones de llamadas de C++ (proceso de apilamiento simple de llamadas a funciones de C++)

 El proceso de llamada de la función: 

1) Asignar espacio de almacenamiento desde el espacio de la pila

2) Copie el valor del espacio de almacenamiento del parámetro real al espacio de pila del parámetro formal

3) Hacer la operación

A los parámetros formales no se les asigna espacio de almacenamiento antes de que se llame a la función.Después de que finaliza la llamada a la función, los parámetros formales se extraen del espacio de la pila y el espacio de parámetros formales se borra.

El método de llamada de función con una matriz como parámetro es pasar la dirección. Tanto el parámetro formal como el parámetro real apuntan al mismo espacio de memoria. Una vez que se completa la llamada, el puntero del parámetro formal se destruye, pero el espacio de memoria apunta a por él todavía existe y no puede y no será destruido.

Cuando la función tiene varios valores de retorno, no se puede implementar mediante el método de retorno ordinario y debe realizarse en forma de retorno de una dirección, es decir, transferencia de dirección/puntero.

Cuarenta y siete: Cuatro tipos de moldes en C++

Los cuatro operadores de conversión son: static_cast, dynamic_cast, const_cast, reinterpret_cast

- 1)emisión estática:

  Se utiliza para varias conversiones implícitas. Específicamente, es la conversión entre varios tipos de datos básicos de usuarios, como cambiar int a char, float a int, etc. Y la conversión del puntero de la clase derivada (subclase) en el puntero de la clase base (superclase).   

  > Características y aspectos destacados:

  1. No tiene verificación de tipo de tiempo de ejecución, por lo que es un riesgo de seguridad. 
  2. Cuando el puntero de clase derivado se convierte en el puntero de clase base, no hay problema.Cuando el puntero de clase base se convierte en el puntero de clase derivado, habrá problemas de seguridad. 
  3. static_cast no puede convertir const, volátil y otros atributos 

- 2) dynamic_cast:
  utilizado para la conversión de tipo dinámico. Específicamente, es la conversión del puntero de la clase base al puntero de la clase derivada, o de la clase derivada al puntero de la clase base.
  dynamic_cast puede proporcionar verificación de tipos en tiempo de ejecución y solo se usa para clases con funciones virtuales.
  dynamic_cast devuelve NULL si no se puede convertir. 

- 3) const_cast: Sirve
  para quitar el atributo constante const para que se pueda modificar, es decir, la variable definida originalmente como const no se puede modificar después de definida, pero después de usar la operación const_cast se puede modificar. modificado a través de este puntero o variable; También hay conversiones para atributos volátiles. 

- 4)reinterpretar_cast

  Casi cualquier cosa se puede convertir, se usa para convertir entre punteros arbitrarios, conversiones entre referencias, conversiones entre punteros y tipos int lo suficientemente grandes, conversiones de enteros a punteros, etc. Pero no lo suficientemente seguro.

Cuarenta y ocho: la implementación subyacente de string

String hereda de basic_string, que en realidad encapsula char*.La cadena encapsulada contiene la matriz char*, la capacidad, la longitud y otros atributos. 

La cadena se puede expandir dinámicamente. En cada expansión, se aplica un espacio adicional (2*n) dos veces el tamaño del espacio original, y luego se copia la cadena original y se agrega el nuevo contenido.

Cuarenta y nueve: ¿Qué es el proceso de generación o proceso de compilación de una función o archivo ejecutable?

Preprocesar, compilar, ensamblar, vincular

- Preprocesamiento: operaciones de preprocesamiento como la sustitución de comandos de preprocesamiento 

- Compilación: optimización de código y generación de código ensamblador 

- Ensamblaje: Convierta el código ensamblador a lenguaje de máquina 

- Enlace: enlace archivos de objetos entre sí

Fifty: Complejidad de inserción de conjunto, mapa y vector

La complejidad de inserción de set y map es la complejidad de inserción de [árbol rojo-negro](), que es log(N). 

La complejidad de inserción de unordered_set, unordered_map es constante, lo peor es O(N).

La complejidad de inserción del vector es O(N), y en el peor de los casos (insertar desde el principio), todos los demás elementos deben moverse o expandirse y copiarse nuevamente.

Supongo que te gusta

Origin blog.csdn.net/m0_56051805/article/details/126573245
Recomendado
Clasificación