Notas de estudio condensadas de C ++ (1) -Conceptos básicos del lenguaje

Citas principales del artículo:

Notas de estudio de Axiu (interviewguide.cn)

Nuke.com - Herramienta de búsqueda de empleo | Banco de preguntas de pruebas escritas | Experiencia en entrevistas | Reclutamiento de pasantías recomendado, solución integral para búsqueda de empleo y empleo_Nuke.com (nowcoder.com)

¿Qué libros has leído (Baidu)

C++ Primer Plus, Code Random Notes, libro de texto de la Universidad de Tsinghua;

Redes de computadoras: Redes de computadoras: enfoque de arriba hacia abajo, diagramado HTTP, diagramado TCP/IP;

Sistemas operativos: sistemas operativos modernos, conocimiento profundo de los sistemas informáticos.

1. Configuración de funciones de C ++

1.1 Características

Describe brevemente las características del lenguaje C++.

Respuesta de referencia

  1. C ++ introduce un mecanismo orientado a objetos basado en el lenguaje C y también es compatible con el lenguaje C.

  2. C++ tiene tres características principales (1) Encapsulación. (2) Herencia. (3) Polimorfismo;

  3. El programa escrito en lenguaje C ++ tiene una estructura clara, es fácil de expandir y tiene buena legibilidad del programa .

  4. El código generado por C++ es de alta calidad y eficiente , y es sólo entre un 10% y un 20% más lento que el lenguaje ensamblador;

  5. C++ es más seguro , agregando constantes constantes, referencias, cuatro tipos de conversiones de conversión (static_cast,dynamic_cast, const_cast, reinterpret_cast), punteros inteligentes, try-catch, etc.;

  6. C ++ tiene una alta reutilización . C ++ introdujo el concepto de plantillas . Sobre esta base, se implementó la Biblioteca de plantillas estándar STL (Biblioteca de plantillas estándar) para facilitar el desarrollo.

  7. Al mismo tiempo, C++ es un lenguaje que está en constante evolución . Las versiones posteriores de C++ han desarrollado muchas características nuevas, como nullptr, variables automáticas, funciones anónimas Lambda, referencias rvalue y punteros inteligentes introducidos en C++ 11.

Habla sobre la diferencia entre el lenguaje C y C++.

Respuesta de referencia

  1. El lenguaje C es un subconjunto de C++ y C++ es muy compatible con el lenguaje C. Pero C++ tiene muchas características nuevas , como referencias , punteros inteligentes, variables automáticas, etc.

  2. C++ es un lenguaje de programación orientado a objetos; el lenguaje C es un lenguaje de programación orientado a procesos .

  3. El lenguaje C tiene algunas características de lenguaje inseguro, como los peligros potenciales del uso de punteros, la incertidumbre de las conversiones, las pérdidas de memoria, etc. C ++ ha agregado muchas características nuevas para mejorar la seguridad , como constantes constantes , referencias , conversiones de conversión, punteros inteligentes, try-catch, etc.;

  4. C ++ tiene una alta reutilización . C ++ introdujo el concepto de plantillas . Sobre esta base, se implementó la biblioteca de plantillas estándar STL para facilitar el desarrollo. La biblioteca C++ STL es más flexible y versátil que la biblioteca de funciones del lenguaje C.

ASCLL

Los números del 0 al 9 corresponden a los códigos ASCII 48-57

Las letras mayúsculas AZ corresponden a los códigos ASCII 65-90

Las letras minúsculas az corresponden a los códigos ASCII 97-122

1.2 Configuración

Hable sobre el orden de inclusión de archivos de encabezado y la diferencia entre comillas dobles "" y corchetes angulares <>

Respuesta de referencia

  1. la diferencia:

    (1) El archivo de encabezado entre corchetes angulares <> es un archivo del sistema y el archivo de encabezado entre comillas dobles "" es un archivo personalizado .

    (2) Las rutas utilizadas para buscar archivos de encabezado durante la fase de preprocesamiento del compilador son diferentes.

  2. Encontrar ruta:

    (1) Busque la ruta de los archivos de encabezado usando corchetes angulares <>: ruta del archivo de encabezado establecida por el compilador --> variables del sistema.

    (2) Busque la ruta de los archivos de encabezado utilizando comillas dobles "": directorio actual del archivo de encabezado --> ruta del archivo de encabezado establecida por el compilador --> variables del sistema.

Explicación detallada del archivo de encabezado C++ <bits/c++std.h> - archivo de encabezado universal

ventaja:

  1. Puede ahorrar mucho tiempo al escribir y buscar archivos de encabezado.

  2. Muy conciso, sólo una línea de código es suficiente.

defecto:

  1. Se incluyen demasiados archivos de encabezado y la compilación requiere mucho tiempo.

  2. Es posible que este archivo de encabezado no esté incluido en algunas versiones anteriores del software de compilación.

2. Sintaxis de C++

2.1 Estructuras y clases

Hablemos de la diferencia entre estructura y clase en C++

Respuesta de referencia

  1. La estructura se usa generalmente para describir una colección de estructuras de datos , mientras que la clase es una encapsulación de datos de objetos ;

  2. El permiso de acceso predeterminado en la estructura es público (también se puede decir que no hay una configuración de permiso de acceso), mientras que el permiso de control de acceso predeterminado en la clase es privado.

  3. La palabra clave class se puede usar para definir parámetros de plantilla , al igual que typename, pero struct no se puede usar para definir parámetros de plantilla.

Análisis de respuestas

  1. La estructura en C++ es una expansión de la estructura en C. Las diferencias entre ellas en la declaración son las siguientes:

    C C++
    función miembro No permitido Poder
    miembros estáticos No permitido Poder
    Control de acceso El valor predeterminado es público y no se puede modificar. público/privado/protegido
    relación de herencia no se puede heredar Se puede heredar de clases u otras estructuras.
    inicialización Los miembros de datos no se pueden inicializar directamente Poder
  2. Diferencias de uso: para usar una estructura en C, necesita agregar la palabra clave struct o usar typedef para tomar un alias para la estructura, mientras que en C++ puede omitir la palabra clave struct y usarla directamente, por ejemplo:

    struct Student{ int iAgeNum; string strName; } 
    typedef struct Student Student2; //alias en C 
    struct Student stu1; //Usa Student2 normalmente en C stu2; //Usa 
    Student stu3 
    por alias en C

¿Se puede asignar un valor a una estructura directamente?

Poder.

Se puede inicializar directamente al declarar , y también se pueden asignar directamente diferentes objetos de la misma estructura;

Nota: Cuando hay varios punteros que apuntan a la misma memoria, liberar esta memoria mediante un puntero puede provocar operaciones ilegales en otros punteros. Por lo tanto, asegúrese de que otros punteros ya no utilicen este espacio de memoria antes de liberarlo.

2.2 Ejecución de código

Describa brevemente el proceso de C++ desde el código hasta el archivo binario ejecutable.

 Respuesta de referencia

C++ es similar al lenguaje C. Un programa C++ tiene cuatro procesos desde el código fuente hasta el archivo ejecutable, precompilación, compilación, ensamblaje y vinculación .

Análisis de respuestas

  1. Precompilación: Las principales operaciones de procesamiento de este proceso son las siguientes:

    (1) Eliminar todo #define y expandir todas las definiciones de macro

    (2) Procese todas las instrucciones de precompilación condicional , como #if, #ifdef

    (3) Procese la directiva de precompilación #include e inserte los archivos incluidos en la ubicación de la directiva de precompilación.

    (4) Filtrar comentarios

    (5) Agregue el número de línea y la identificación del nombre del archivo .

  2. Compilación: Las principales operaciones de procesamiento de este proceso son las siguientes:

    (1) Análisis léxico: divida la secuencia de caracteres del código fuente en una serie de tokens.

    (2) Análisis gramatical: realice un análisis gramatical de los tokens para generar un árbol de sintaxis.

    (3) Análisis semántico: determine si la expresión es significativa.

    (4) Optimización del código:

    (5) Generación de código de destino: generar código ensamblador.

    (6) Optimización del código de destino:

  3. Ensamblaje: este proceso convierte principalmente el código ensamblador en instrucciones que la máquina puede ejecutar.

  4. Vinculación: vincular los archivos de destino generados por diferentes archivos fuente para formar un programa ejecutable.

    Los enlaces se dividen en enlaces estáticos y enlaces dinámicos.

Enlace estático y enlace dinámico.

El enlace estático significa que la función o proceso a llamar se ha vinculado al archivo ejecutable generado durante el enlace. Incluso si elimina la biblioteca estática, no afectará la ejecución del programa ejecutable; la biblioteca de enlaces estáticos generada ,

Ventajas: el programa no necesita depender de bibliotecas cuando se lanza y se puede ejecutar de forma independiente;

Desventaja: el tamaño del programa será relativamente grande y habrá varias copias del mismo archivo de destino en la memoria. Y si se actualiza la biblioteca estática, es necesario volver a vincular todos los archivos ejecutables;

El enlace dinámico significa que el código de función llamado no se vincula durante el proceso de vinculación. En cambio, la función a vincular se encuentra durante el proceso de ejecución . El archivo ejecutable generado no tiene el código de función, solo contiene la información de reubicación del función. , por lo que cuando elimina la biblioteca dinámica, el programa ejecutable no puede ejecutarse. La biblioteca de enlaces dinámicos generada tiene un sufijo .dll en Windows y un sufijo .so en Linux.

Ventajas: varios programas pueden compartir el mismo código sin necesidad de almacenar varias copias en el disco. Las actualizaciones son convenientes: solo es necesario reemplazar el archivo de destino original al actualizar.

Desventaja: debido a la carga del tiempo de ejecución, puede afectar el rendimiento de ejecución temprana del programa.

Qué usar en dll, la diferencia entre interfaz y función

Una DLL es una biblioteca que contiene código y datos que pueden ser utilizados por múltiples programas simultáneamente . Cada programa puede utilizar la funcionalidad contenida en una DLL para realizar determinadas operaciones. Esto ayuda a promover la reutilización de código y el uso eficiente de la memoria. La ventaja es que el programa no necesita cargar todo el código al comienzo de la ejecución, solo se extrae de la DLL cuando el programa necesita una determinada función.

Las funciones son paquetes de secuencias de instrucciones;
los métodos son operaciones en miembros de objetos, implementadas por funciones;
las interfaces son la abstracción y generalización de métodos, y los métodos implementan interfaces específicas;

http://t.csdn.cn/a1ayS

compilación condicional

#ifdef, #else, #endif y #ifndef

En circunstancias normales, cada línea del programa fuente debe compilarse al compilar. Pero a veces desea que una determinada parte del programa se compile solo cuando se cumplan ciertas condiciones , es decir, especificar las condiciones para compilar una parte del programa. Si no se cumple esta condición, esta parte del contenido no será compilada. Esta es una "compilación condicional".

Utilice #ifdef y #endif para incluir un módulo de función de programa para proporcionar esta función a usuarios específicos. Los usuarios pueden bloquearlos fácilmente cuando no los necesiten.

Aunque el requisito se puede lograr usando directamente declaraciones if sin comandos de compilación condicional, el programa de destino será más largo (porque todas las declaraciones if están compiladas ) y el tiempo de ejecución será largo (porque las declaraciones if se prueban durante el tiempo de ejecución del programa). El uso de la compilación condicional puede reducir la cantidad de declaraciones compiladas, lo que reduce la duración del programa de destino y el tiempo de ejecución.

2.3 Gramática

Cómo guardar números de coma flotante (Nova)

1000.101 Este tipo de número binario se expresa en forma estandarizada  1.000101 x 2^3, Entre ellos, los más críticos son 000101 y 3, que pueden contener toda la información de este decimal binario:

  • 000101 Se llama mantisa , que es el número que sigue al punto decimal;
  • 3 Llamado exponente , especifica la posición del punto decimal en los datos;

Los números de coma flotante utilizados por la mayoría de las computadoras ahora generalmente adoptan el estándar internacional establecido por IEEE, cuya forma es la siguiente:

El significado de estas tres partes importantes es el siguiente:

  • Bit de signo : indica si el número es positivo o negativo, 0 indica un número positivo, 1 indica un número negativo;
  • Bit exponente : especifica la posición del punto decimal en los datos. El exponente puede ser un número negativo o positivo. Cuanto mayor sea la longitud del bit exponente, mayor será el rango de expresión del valor ;
  • Posición de mantisa : el número a la derecha del punto decimal, que es la parte decimal. Por ejemplo, en binario 1.0011

Los números de punto flotante  32 representados por bits se llaman números de punto flotante de precisión simple , que en nuestro lenguaje de programación  son float variables  , mientras que 64 los números de punto flotante representados por bits se llaman números de punto flotante de doble precisión , que son  double variables. Sus estructuras son como sigue:

puede ser visto:

  • La parte mantisa de double es de 52 bits y la parte mantisa de float es de 23 bits. Dado que ambos tienen un bit implícito fijo (esto se analizará más adelante), double tiene 53 bits binarios significativos y float tiene 24 bits binarios significativos. Por lo tanto , su precisión es log10(2^53) aproximadamente igual  15.95 y log10(2^24) aproximadamente igual a  los bits en el sistema decimal respectivamente  7.22 . Por lo tanto, los dígitos significativos de double son  15~16 bits y los dígitos significativos de float son  7~8 bits. Estos dígitos significativos incluyen la parte entera y la parte decimal;
  • La parte exponente de double es de 11 bits, mientras que la parte exponente de float es de 8 bits, lo que significa que double puede representar un rango de valores mayor que float;

La diferencia entre sizeof y strlen.

  1. sizeof es un operador y strlen es una función de biblioteca .

  2. El compilador calcula el resultado de sizeof en tiempo de compilación , pero la función strlen debe calcularse en tiempo de ejecución . Y sizeof calcula el tamaño de la memoria del tipo de datos , mientras que strlen calcula la longitud real de la cadena .

  3. Los parámetros de sizeof pueden ser tipos de datos o variables , mientras que strlen sólo puede tomar cadenas que terminen en '\0' como parámetros .

¿Cuáles son las palabras clave para importar funciones de C y cuáles son las diferencias entre C++ y C al compilar?

Respuesta de referencia

  1. Palabra clave: en C ++, la palabra clave para importar funciones C es extern y la forma de expresión es extern "C" . La función principal de extern "C" es implementar correctamente el código C ++ para llamar a otros códigos de lenguaje C. Agregar "C" externo le indicará al compilador que compile esta parte del código en lenguaje C en lugar de C++.

  2. Diferencia de compilación: dado que C++ admite la sobrecarga de funciones , cuando el compilador compila una función, el tipo de parámetro de la función también se agregará al código compilado, no solo el nombre de la función , mientras que el lenguaje C no admite la sobrecarga de funciones. Al compilar una función en código de lenguaje C, no se incluirá el tipo de parámetro de la función y, en general, solo se incluirá el nombre de la función .

Análisis de respuestas

//ejemplo externo//Declarar esta función en un programa C++ le indicará al compilador que compile esta parte del código de acuerdo con el lenguaje C extern "C" int strcmp(const char *s1, const char *s2); //En un programa C++ Declara la función extern "C" 
{ #include <string.h>//string.h contiene la declaración de la función C que se llamará} //Dos lenguajes diferentes tienen diferentes reglas de compilación, como función divertida, tal vez sea _fun cuando se compila en lenguaje C y __fun__ en C++

La diferencia entre declaración y definición de variables.

La definición de una variable asigna dirección y espacio de almacenamiento para la variable , mientras que la declaración de la variable no asigna una dirección . Una variable se puede declarar en varios lugares, pero definirse en un solo lugar.

Nota: Agregar extern modifica la declaración de la variable, lo que indica que la variable se definirá fuera del archivo o más adelante en el archivo.

¿Cuál es la diferencia entre funciones ordinarias y funciones miembro?

Las funciones ordinarias se definen fuera de la clase, mientras que las funciones miembro se definen dentro de la clase.

Las funciones ordinarias no pueden acceder directamente a los miembros privados y protegidos de la clase, mientras que las funciones miembro pueden acceder a todos los miembros de la clase, incluidos los miembros privados y protegidos.

Las funciones ordinarias se pueden llamar directamente, mientras que las funciones miembro deben llamarse a través de objetos de la clase.

Las funciones miembro tienen un puntero especial, que apunta al objeto que llama a la función miembro. Las funciones ordinarias no tienen este puntero.

¿Para qué sirve este puntero?

Este puntero es la dirección que apunta al objeto actual . Se utiliza principalmente para acceder a las variables miembro y funciones miembro del objeto actual en las funciones miembro de la clase .

Cuando un objeto llama a su propia función miembro, el compilador pasará implícitamente la dirección del objeto a la función miembro a través de this . A través de este puntero, las funciones miembro pueden acceder y operar variables miembro y funciones miembro del objeto actual.

Este puntero solo se puede utilizar en funciones miembro no estáticas , porque las funciones miembro estáticas no tienen estos punteros y no pertenecen a ningún objeto específico.

Hable sobre la diferencia entre matrices y punteros.

Respuesta de referencia

  1. concepto:

    (1) Matriz: una matriz es una colección que se utiliza para almacenar múltiples datos del mismo tipo. El nombre de la matriz es la dirección del primer elemento.

    (2) Puntero: un puntero es equivalente a una variable, pero es diferente de diferentes variables: almacena la dirección de otras variables en la memoria . El nombre del puntero apunta a la primera dirección de la memoria.

  2. la diferencia:

    (1) Asignación : las variables de puntero del mismo tipo se pueden asignar entre sí; no se pueden asignar matrices, solo se puede asignar o copiar un elemento.

    (2) Método de almacenamiento :

    Matriz: las matrices se almacenan continuamente en la memoria y abren un espacio de memoria continuo. Se accede a la matriz de acuerdo con la parte inferior de la matriz. El espacio de almacenamiento de la matriz está en el área estática o en la pila.

    Punteros: los punteros son muy flexibles y pueden apuntar a cualquier tipo de datos. El tipo de puntero especifica la memoria en el espacio de direcciones al que apunta. Dado que el puntero en sí es una variable y lo que almacena también es una variable, no se puede determinar el espacio de almacenamiento del puntero.

    (3) Encuentra el tamaño de :

    El tamaño de memoria del espacio de almacenamiento ocupado por la matriz: sizeof (nombre de la matriz)/sizeof (tipo de datos)

    ​ En una plataforma de 32 bits, el tamaño de (nombre del puntero) es 4 independientemente del tipo de puntero. En una plataforma de 64 bits, el tamaño de (nombre del puntero) es 8 independientemente del tipo de puntero.

    (4) Inicialización :

    // Array int a[5] = { 0 }; char b[] = "Hola"; // Inicializado por cadena, el tamaño es 6 char c[] = { 'H','e','l', ' l','o','\0' }; // Inicializa int* arr = new int[10]; // Crea dinámicamente una matriz unidimensional   
    // Puntero // Puntero al objeto int* p = new int( 0); eliminar p; // Puntero a la matriz int* p1 = nuevo int[10]; eliminar[] p1; // Puntero a la clase: cadena* p2 = nueva cadena; eliminar p2; // Puntero al puntero Puntero (secundario puntero) int** pp = &p; **pp = 10;

    (5) Operación del puntero:

    ​ Operaciones de puntero en nombres de matrices

    int a[3][4]; int (*p)[4]; //Esta declaración define un puntero de matriz, que apunta a una matriz unidimensional p = a que contiene 4 elementos; //Cambiar el comienzo de la matriz bidimensional matriz dimensional La dirección se asigna a p, es decir, a[0] o &a[0][0] p++; //Después de ejecutar la declaración, es decir, p=p+1; p cruza la línea a[0 ][] y apunta a la línea a [1][] // Por lo tanto, el puntero de matriz también se llama puntero a una matriz unidimensional, también llamado puntero de fila. //Hay varias formas de acceder a un elemento en la fila i y la columna j en la matriz: //*(p[i]+j), *(*(p+i)+j), (*(p+i ))[j], p[i][j]. Entre ellos, prioridad: ()>[]>*. //Estas operaciones son todas legales.

    Operaciones de datos en variables de puntero:

    char *str = "hola,douya!"; 
    str[2] = 'a'; 
    *(str+2) = 'b'; 
    //Ambas operaciones son legales. 

Hablemos sobre qué es un puntero de función, cómo definir un puntero de función y cuáles son sus escenarios de uso.

Respuesta de referencia

  1. Concepto: un puntero de función es una variable de puntero que apunta a una función . Cada función tiene una dirección de entrada, que es la dirección a la que apunta el puntero de función.

  2. La definición es la siguiente:

int func(int a);  
int (*f)(int a);  
f = &#func  
;
  1. Escenarios de aplicación de punteros de función : devolución de llamada . Cuando llamamos a la función API (Interfaz de programación de aplicaciones) proporcionada por otros, se llama Llamada; si la biblioteca de otra persona llama a nuestra función, se llama Devolución de llamada.

Análisis de respuestas

// Tome la función de biblioteca qsort para ordenar como ejemplo, su prototipo es el siguiente: 
void qsort(void *base,//El tipo void* representa la matriz original 
           size_t nmemb, //El segundo es el tipo size_t, que representa el número de datos 
           size_t size, // El tercero es del tipo size_t, que representa el espacio ocupado por un solo dato int 
           (*compar)(const void *,const void *)// El cuarto parámetro es un puntero de función 
          ); 
// El cuarto parámetro le dice a qsort qué función se usa para comparar elementos, es decir, siempre que le digamos a qsort las reglas para comparar tamaños, puede ayudarnos a ordenar matrices de cualquier tipo de datos. La función de biblioteca qsort llama a nuestra función de comparación personalizada, que es la aplicación de devolución de llamada. 
​//
Ejemplo 
int num[100]; 
int cmp_int(const void* _a, const void* _b){//Formato de parámetro fijo 
    int* a = (int*)_a; //Conversión de tipo forzada 
    int* b = (int *)_b; 
    return *a - *b;  
} 
qsort(num,100,sizeof(num[0]),cmp_int); // 
Devolución de llamada

La diferencia entre punteros de función y funciones de puntero en C++.

En C++, puntero de función y puntero a función son dos conceptos diferentes con diferentes significados y usos.

1. Puntero de función : un puntero de función es un puntero a una función . Puede usarse para almacenar y llamar la dirección de una función. La forma de declaración de un puntero de función es: return_type (*pointer_name)(parameter_types), donde return_typees el tipo de retorno de la función y parameter_typeses la lista de tipos de parámetros de la función. A través de punteros de función, se pueden seleccionar dinámicamente diferentes funciones para llamarlas en tiempo de ejecución.

// 声明一个函数指针
int (*funcPtr)(int, int);

// 初始化函数指针
funcPtr = add; // add 是一个函数名

// 使用函数指针调用函数
int result = funcPtr(3, 5); // 调用 add 函数

2. Puntero a una función : una función de puntero es una función que devuelve un puntero y su valor de retorno es un puntero que apunta a un determinado tipo de datos. La forma de declaración de una función de puntero es: return_type* function_name(parameters), donde return_typees el tipo de datos al que apunta el puntero y parameterses la lista de parámetros de la función. Una función de puntero devuelve un puntero que puede apuntar a la memoria u otros datos asignados fuera de la función.

// 声明一个指针函数
int* createIntPointer();

// 定义指针函数
int* createIntPointer() {
    int* ptr = new int(10);
    return ptr;
}

// 使用指针函数
int* ptr = createIntPointer(); // 返回一个指向动态分配的整数的指针

Para resumir las diferencias:

  • Un puntero de función es un puntero a una función que se puede llamar . Una función de puntero es una función que devuelve un puntero y devuelve un puntero a un determinado tipo de datos.
  • Utilice la sintaxis al declarar punteros de función (*ptr)y utilice return_type*la sintaxis al declarar funciones de puntero.
  • Los punteros de función almacenan direcciones de funciones y las funciones de puntero devuelven un valor de puntero .
  • Los punteros de función se utilizan para llamar dinámicamente a diferentes funciones, y las funciones de puntero devuelven valores de puntero para uso externo.

¿Puede nullptr llamar a la función miembro? ¿Por qué?

Respuesta de referencia

capaz.

Motivo: debido a que el objeto está vinculado a la dirección de la función en el momento de la compilación , no importa si el puntero está vacío o no.

Análisis de respuestas

//给出实例
clase animal{ 
público: 
    void dormir(){ cout << "animal dormir" << endl; } 
    void respirar(){ cout << "animal respira jaja" << endl; } 
}; 
pez de clase :animal público{ 
público: 
    void respirar(){ cout << "burbuja de pez" << endl; } 
}; 
int principal(){ 
    animal *pAn=nullptr; 
    pAn->respirar(); // 输出:animal respira jaja 
    pez *pFish = nullptr; 
    pFish->respirar(); // 输出:pez burbuja 
    return 0; 
}  

Motivo: debido a que el objeto está vinculado a la dirección de la función en el momento de la compilación , no importa si el puntero está vacío o no. pAn->breathe(); Al compilar, la dirección de la función está vinculada al puntero pAn; cuando se llama a Breath(*this), esto es igual a pAn. Dado que no es necesario eliminar la referencia a esto en la función, no habrá ningún error al ejecutar la función. Sin embargo, si se usa, se producirá un error porque this=nullptr.

Hablemos de la diferencia entre los operadores i++ y ++i

Respuesta de referencia

Veamos primero el código de implementación:

#include <stdio.h> 
int main(){ 
       int i = 2; 
    int j = 2; 
    j += i++; //Asignar valor primero y luego agregar 
    printf("i= %d, j= %d\n" ,i , j); //i= 3, j= 4 
    i = 2; 
    j = 2; 
    j += ++i; //Suma primero y luego asigna 
    printf("i= %d, j= %d" ,i,j); //i= 3, j= 5 
}
  1. El orden de asignación es diferente : ++ i se agrega primero y luego se asigna; i ++ se asigna primero y luego se agrega; ++i e i++ se completan en dos pasos.

  2. La eficiencia es diferente : post++ se ejecuta más lento que el prefijo.

  3. i++ no se puede utilizar como valor l, pero ++i puedo :

    int i = 0; 
    int* p1 = &(++i);//correcto 
    // int* p2 = &(i++);//incorrecto 
    ++i = 1;//correcto 
    // i++ = 1;// 
    Error
  4. Tampoco lo es una operación atómica.

((nulo ()( ) )0)( ) significado

Parece que este código está intentando llamar a una función utilizando un puntero de función. Expliquemos esta expresión paso a paso:

  1. ((void ()( ) )0): Esta es una llamada a un puntero de función. Expliquemoslo desde adentro hacia afuera:

    • void ()( )Representa el tipo de un puntero de función , donde ( )está la lista de parámetros de la función (parámetro vacío), y el último ( )indica que el tipo de retorno de la función está vacío ( void).
    • ((void ()( ) )0)Indica que todo el tipo de puntero de función se convierte en un puntero de función y se inicializa en 0 (puntero nulo).
  2. ((void ()( ) )0)( ): Aquí, ((void ()( ) )0)representa un puntero de función , y lo que sigue ( )es la llamada a la función real, lo que significa llamar a la función señalada por este puntero de función. Debido a que previamente inicializamos el puntero de función a 0 (puntero nulo), en realidad esto es intentar llamar a una función apuntada por un puntero nulo.

En la mayoría de los casos, dicho código es incorrecto porque intentar llamar a una función a través de un puntero nulo da como resultado un comportamiento indefinido. Un puntero nulo no apunta a ningún código de función válido, por lo que llamarlo provocará una falla o una excepción en el programa.

Describa brevemente los distintos métodos de transferencia de valores en C++ ¿Cuáles son las diferencias entre ellos?

Respuesta de referencia

Hay tres formas de pasar parámetros: paso de valor, paso de referencia y paso de puntero.

  1. Transferencia de valor: incluso si el valor del parámetro formal cambia dentro del cuerpo de la función, no afectará el valor del parámetro real;

  2. Pasando por referencia: si el valor del parámetro formal cambia dentro del cuerpo de la función, afectará el valor del parámetro real;

  3. Paso del puntero: siempre que el puntero no cambie, el valor del parámetro formal en el cuerpo de la función cambiará, lo que afectará el valor del parámetro real;

Análisis de respuestas

Cuando se usa el paso por valor para un objeto, se copiará todo el objeto, lo cual es ineficiente. Cuando se usa el paso por referencia para un objeto, no se produce ningún comportamiento de copia, pero el objeto simplemente se vincula, lo cual es más eficiente. Lo mismo Esto es cierto para el paso de puntero, pero no es tan seguro como el paso por referencia.

ejemplo de código

//Ejemplo de código 
#include <iostream> 
usando el espacio de nombres std; 
​void
testfunc(int a, int *b, int &c){//El valor del parámetro formal a ha cambiado, pero no ha afectado el valor del parámetro real parámetro i, pero el parámetro formal Los valores de *b y c han cambiado, afectando los valores de los parámetros reales *j y k a += 
    1; 
    (*b) += 1; 
    c += 1; 
    printf("a= %d, b= %d , c= %d\n",a,*b,c);//a= 2, b= 2, c= 2 } int main(){ int i = 1; int a = 1 
; 
int 
       * 
    j 
    = &a; 
    int k = 1; 
    testfunc(i, j, k); 
    printf("i= %d, j= %d, k= %d\n",i ,*j,k);//i= 1 , j= 2, k= 2 
    devuelve 0; 
}

形参的int arr[] 和vector<int> vec, vector<int>& vec, vector<int>* vec

  • Int arr[] se pasa a la función la primera dirección de la matriz, por lo que al realizar operaciones como el intercambio, se debe obtener la dirección & arr[i].
  • function1(std::vector<std::vector > vec),传值
  • función2(std::vector<std::vector>& vec), pasar por referencia
  • función3(std::vector<std::vector>* vec), pasar puntero

Describe brevemente la diferencia entre const (asterisco) y (asterisco) const

Respuesta de referencia

//const* es un puntero constante, *const es un puntero constante 
int const *a;//El valor en la memoria señalado por el puntero a permanece sin cambios, es decir, (*a) permanece sin cambios 
int *const a; // El valor señalado por el puntero a La dirección de memoria permanece sin cambios, es decir, a permanece sin cambios

¿Constante toma dirección?

Sólo las variables o constantes almacenadas en la memoria tendrán un número de memoria (dirección ) asociado.

1. Hay dos tipos de constantes en el código: constantes literales y constantes restringidas. Las constantes literales no tienen nombre, por ejemplo: cout<<8, este 8 es una constante literal. Son datos que ocupan una parte de la memoria y tienen una dirección de memoria, pero no podemos obtener la dirección porque no tienen nombre. El símbolo de direccionamiento es &, pero debe ir seguido del nombre de los datos. Por ejemplo &a. Escribir &8 no funciona. Incluso si funciona (es difícil de lograr), no tiene sentido. Porque un dato sin nombre (o que se puede encontrar con la ayuda de otros nombres) significa que no hay información de contacto , lo que significa que ya no podrás usarlo legalmente si abandonas la ubicación literal actual. Por lo tanto, para los datos del código, si hay un nombre, hay información de contacto. O simplemente no los llame por su nombre y trátelos como información de contacto.

2.const int b = 10; ¿puedes obtener la dirección de b? Poder. Definir una cantidad constante es en realidad una variable . Const solo limita que no se puede modificar. Todas las variables pueden obtener sus direcciones (cuando el programa se está ejecutando).

3. El enumerador en el tipo de enumeración es solo una parte de la declaración del tipo de enumeración.No es una variable definida, por lo que no puede tomar un valor.

4.#define PI 3.14 sale como una macro, que es una cosa de preprocesamiento. El compilador borrará rápidamente el PI y lo reemplazará todo con el literal 3.14. La etapa de compilación después del preprocesamiento ya no existe, por lo que es imposible obtenerla. La dirección de la macro.

La diferencia entre punteros y referencias.

Mismo punto:

  1. Una referencia es un alias de la variable referenciada. El espacio ocupado por la referencia misma almacena la dirección de la variable referenciada y no tiene su propia dirección de memoria. Esta es la misma memoria que la variable de puntero. Todo se hace por dirección .

  2. Cuando se utilizan como parámetros formales, se pasan en ambas direcciones y pueden evitar la copia de valores, lo que reduce la sobrecarga de transferencia de datos al llamar a funciones.

diferencia:

  1. Un puntero ordinario se puede asignar varias veces , es decir, el objeto al que apunta se puede cambiar varias veces. Una referencia sólo puede especificar el objeto al que se hace referencia durante la inicialización y no se puede cambiar posteriormente .

  2. Los punteros son un mecanismo de bajo nivel y las referencias son un mecanismo de nivel superior.

Se puede decir con certeza que cualquier función que se pueda lograr mediante referencias también se puede lograr mediante punteros. La esencia de una referencia sigue siendo una constante de puntero.

Para pasar parámetros, usar referencias es más conciso y seguro que los punteros . Pero en muchos casos, los punteros no se pueden reemplazar.

Por ejemplo: cambiar el objeto puntiagudo a mitad de camino, usar un puntero nulo para expresar un significado específico, pero no existen referencias nulas, punteros de función o referencias de función.

¿El azar tomará 0 o 1?

lenguaje C(0,1) java y python[0, 1);

¿Serialización y deserialización?

En pocas palabras, la serialización es el proceso de convertir el estado de una instancia de objeto a un formato que pueda mantenerse o transmitirse . Lo opuesto a la serialización es la deserialización, que reconstruye objetos basándose en flujos . Estos dos procesos se combinan para permitir un fácil almacenamiento y transferencia de datos. Por ejemplo, puede serializar un objeto y luego usar HTTP para transferir el objeto entre el cliente y el servidor a través de Internet.

Serialización: transmite el objeto a un flujo de bytes.

Deserialización: restaura el objeto original del flujo de bytes.

Propósito de la serialización : (1) almacenar el objeto en el disco duro para su posterior deserialización y uso; (2) transmitir la secuencia de bytes del objeto en la red

Ventajas : Comodidad y flexibilidad en la transmisión de la red, ahorrando tiempo en grandes proyectos, como:

Tiene una estructura de datos y los datos almacenados en ella se generan mediante un algoritmo muy complejo a través de muchos otros datos. Debido a que la cantidad de datos es muy grande y el algoritmo es complejo, puede llevar mucho tiempo (quizás varios). veces) para generar los datos utilizados para generar la estructura de datos. Horas, o incluso días), la estructura de datos se genera y se utiliza para otros cálculos. Luego, durante la fase de depuración, cada vez que ejecute un programa, tomará mucho tiempo. Tiempo solo para generar la estructura de datos, el costo es sin duda muy grande. Si está seguro de que el algoritmo para generar la estructura de datos no cambiará ni cambiará con frecuencia, puede utilizar la tecnología de serialización para generar los datos de la estructura de datos y almacenarlos en el disco. La próxima vez que ejecute el programa, solo necesita leer los datos del objeto del disco. Sí, el tiempo que lleva es el tiempo para leer un archivo. Puedes imaginar lo rápido que es y nos ahorra tiempo de desarrollo.

¿Por qué necesitamos tomar módulo al multiplicar números grandes, permutarlos y combinarlos?

  • (10 ^ 9 + 7) 1000000007 es un número primo (número primo). Tomar el resto del número primo puede evitar conflictos/duplicación de resultados en la mayor medida

  • El valor máximo de int32 bits es 2147483647, por lo que 1000000007 es lo suficientemente grande para int32 bits.

  • El valor máximo de los bits int64 es 2 ^ 63-1. Utilice el resultado del módulo de valor máximo 1000000007 para encontrar el cuadrado y no se desbordará en int64.

  • Entonces, en el problema de multiplicar números grandes, porque (a*b)%c=((a%c)*(b%c))%c, al multiplicar, ambos lados son módulo 1000000007, y luego se almacenan en int64 No lo hará Desbordamiento.

2.4 Palabras clave

¿Qué hace la palabra clave volátil?

La palabra clave volátil le dice al compilador que no optimice esta variable , es decir, debe obtener el valor de la dirección absoluta cada vez , pero no del registro .

efecto:

  1. Variables compartidas por múltiples tareas en aplicaciones multiproceso . La modificación de variables en subprocesos múltiples evita que las variables se carguen en registros , lo que permite que cada subproceso lea variables consistentes en la memoria.

¿Puede un parámetro ser constante y volátil?

, puede usar const y volatile para modificar una variable al mismo tiempo, lo que significa que la variable es de solo lectura dentro del programa y no se puede cambiar. Solo cambia cuando cambian las condiciones fuera del programa y el compilador no optimizará esta variable. Cada vez que utilice esta variable, tenga cuidado de ir a la memoria para leer el valor de esta variable, en lugar de ir al registro para leer su copia de seguridad.

volátil [ˈvɑːlətl] adj. Inestable; volátil; cambiante; indefinido; impermanente;

Hablemos de la función de la palabra clave estática.

Respuesta de referencia

  1. Variables estáticas globales : las variables globales se modifican con estática para cambiar su alcance pero no su vida útil . Otros archivos .c pueden hacer referencia a las variables globales ordinarias. Una vez modificadas por estática, solo pueden ser referenciadas por el archivo .c en el que está definida la variable global , lo que reduce el alcance de la variable global .
       Función: cuando una variable global no quiere ser referenciada por otros archivos .c , se puede modificar con estática, de modo que no se pueda acceder a otros archivos a través de extern. Esto es principalmente por seguridad de los datos.
       Resumen: cambiar su alcance no cambia el ciclo de vida. .

  2. Variables estáticas locales: las variables locales son variables definidas dentro de una función. El ciclo de vida finaliza cuando finaliza la función y el último valor no se conservará. Cuando se modifica con estática, la vida útil de la variable local finalizará cuando finalice el programa y se conservará el último valor.
        Aplicación: Dentro de una función, si queremos conservar el último valor de algunas variables , podemos usar estática para modificar las variables. Por ejemplo: si desea contar la cantidad de veces que se ejecuta la función, puede definir una variable int modificada por estática, y la variable será ++ cada vez que se ejecute.
        Resumen: las variables locales modificadas con estática cambian el ciclo de vida, pero no cambian su alcance. La razón para cambiar su vida útil es que las variables locales modificadas por estática se almacenan en el segmento .bss o .data, mientras que las variables locales ordinarias se almacenan en la pila.

  3. Las variables estáticas inicializadas asignarán memoria en el segmento de datos y las variables estáticas no inicializadas asignarán memoria en el segmento BSS. Las variables estáticas siempre mantienen sus valores anteriores hasta el final del programa. Las variables de vida estáticas que no especifican un valor inicial se inicializarán con un valor de 0, mientras que para las variables de vida dinámicas, no especificar un valor inicial significa que el valor inicial es incierto. Es solo que el alcance de las variables estáticas globales y las variables estáticas locales es diferente;

  4. Definir una función estática : agregue la palabra clave estática antes del tipo de retorno de la función, y la función se define como una función estática, cambiando el alcance. Las funciones estáticas solo se pueden usar en este archivo fuente . Las funciones ordinarias pueden ser llamadas por otros archivos a través de declaraciones de archivos de encabezado; algunas funciones no desean proporcionarse al mundo exterior y solo necesitan estar en este archivo.

  5. En C++, la palabra clave estática se puede usar para definir variables miembro estáticas en una clase : al usar miembros de datos estáticos, se puede almacenar como una variable global, pero está oculta dentro de la clase. Los miembros de datos estáticos de una clase tienen un área de almacenamiento separada, independientemente de cuántos objetos de la clase se creen. Los miembros de datos estáticos de todos estos objetos comparten este espacio de almacenamiento estático. Compartido por todos los objetos de la clase , incluidos los subobjetos. Debe inicializarse fuera de la clase y no puede inicializarse dentro del constructor.

  6. En C++, la palabra clave estática se puede utilizar para definir funciones miembro estáticas en una clase : similar a las variables miembro estáticas, las funciones miembro estáticas también se pueden definir en una clase. Simplemente agregue la palabra clave estática antes de la función. Por ejemplo, las funciones miembro estáticas también son parte de la clase, no del objeto. Los miembros de datos estáticos de todos estos objetos comparten este espacio de almacenamiento estático. Esta función es compartida por todos los objetos , no contiene este puntero y no se pueden utilizar miembros no estáticos de la clase.

Análisis de respuestas

Cuando se llama a una función miembro no estática de un objeto, el sistema asigna la dirección inicial del objeto al puntero this de la función miembro . Las funciones miembro estáticas no pertenecen a ningún objeto , por lo que C++ estipula que las funciones miembro estáticas no tienen estos punteros (énfasis añadido, a menudo probado en preguntas de entrevistas). Como no apunta a un objeto, no puede acceder a miembros no estáticos de un objeto.

Cuándo usar variables estáticas

En programación, cuando una variable será compartida por múltiples objetos instanciados de una clase para lograr la comunicación entre múltiples objetos, o para registrar la cantidad de objetos que se han creado, en este caso se usan variables estáticas .

Utilice estática para modificar variables y funciones globales. Además de la seguridad de los datos mencionada anteriormente y la prevención de referencias erróneas , otra función es resolver el problema de los nombres duplicados . Después de modificar las variables y funciones globales con estática, también es posible definir variables y funciones globales con el mismo nombre en otros archivos. En términos generales, si las funciones y variables globales no se proporcionan al mundo exterior, es mejor utilizar la modificación estática.

¿Se pueden llamar funciones modificadas estáticamente desde otros archivos?

Función: La función se modifica con estática, lo que cambia el alcance. Las funciones ordinarias pueden ser llamadas por otros archivos a través de declaraciones de archivos de encabezado. Después de ser modificadas por estática, solo se pueden llamar en este archivo . Esto es por seguridad de los datos. Resumen: Cambiar el alcance no cambia su ciclo de vida.

Esto es diferente de una función normal, pero se puede llamar de otras formas:

  1. Llamada indirecta: defina una función proporcionada externamente en el archivo , y la función llama a la función modificada estáticamente internamente, lo que realiza la llamada indirecta de la función modificada estáticamente.

  2. Llamada directa: pase el puntero de función de la función modificada estáticamente y se pueden llamar a otros archivos a través del puntero de función.

¿Hablar sobre cuándo se inicializan las variables estáticas?

Respuesta de referencia

Para las variables globales y estáticas en lenguaje C , la inicialización ocurre antes de ejecutar cualquier código y pertenece a la inicialización en tiempo de compilación .

El estándar C++ estipula que los objetos globales o estáticos se construyen cuando y solo cuando el objeto se usa por primera vez .

Análisis de respuestas

  1. Alcance : El alcance en C++ se puede dividir en 6 tipos: global, local, clase, declaración, espacio de nombres y alcance de archivo.

    Variables globales estáticas: alcance global + alcance del archivo, por lo que no se pueden usar en otros archivos.

    Variables locales estáticas: alcance local, solo se inicializan una vez hasta el final del programa.

    Variables miembro estáticas de clase: alcance de la clase.

  2. Ubicación : Todo en área de almacenamiento estático . Debido a que las variables estáticas están en el área de almacenamiento estático, aún se puede obtener el valor original la próxima vez que se llame a la función.

  3. Ciclo de vida : Las variables globales estáticas y las variables locales estáticas se encuentran en el área de almacenamiento estático y la memoria no se recuperará hasta el final del programa. Las variables miembro estáticas de la clase se encuentran en el área de almacenamiento estático y la memoria se recicla cuando se excede el alcance de la clase.

Hablemos de variables locales estáticas, variables globales, características de las variables locales y escenarios de uso.

Respuesta de referencia

  1. Primero, considere el alcance : el alcance en C++ se puede dividir en 6 tipos: global, local, clase, declaración, espacio de nombres y alcance de archivo.

    Variables globales: alcance global, que se puede usar en otros archivos fuente no definidos a través de extern.

    Variables globales estáticas: alcance global + alcance del archivo, por lo que no se pueden usar en otros archivos.

    Variables locales: alcance local, como parámetros de función, variables locales dentro de funciones, etc.

    Variables locales estáticas: alcance local, solo se inicializan una vez hasta el final del programa.

  2. Considere el espacio : excepto las variables locales en la pila, todo lo demás está en el área de almacenamiento estático. Debido a que las variables estáticas están en el área de almacenamiento estático, aún se puede obtener el valor original la próxima vez que se llame a la función.

  3. Ciclo de vida : las variables locales están en la pila y la memoria se recupera cuando salen del alcance; las variables globales, las variables globales estáticas y las variables locales estáticas están todas en el área de almacenamiento estático y la memoria no se recupera hasta el final. Del programa.

  4. Escenarios de uso : Puede ver sus respectivos escenarios de aplicación a partir de sus respectivas características, por lo que no entraré en detalles.

Hablemos de la diferencia entre new y malloc, y los principios de implementación subyacentes de cada uno.

Respuesta de referencia

  1. new es un operador y malloc es una función.

  2. new primero asigna memoria cuando se llama, llama al constructor y llama al destructor cuando se libera, mientras que malloc no tiene constructor ni destructor.

  3. malloc necesita especificar el tamaño de la memoria solicitada y el puntero devuelto debe ser forzado; new llamará al constructor sin especificar el tamaño de la memoria y no es necesario forzar el puntero de retorno.

  4. new puede sobrecargarse; malloc no puede

  5. new asigna memoria de forma más directa y segura.

  6. Se produce un error en new y se lanza una excepción y malloc devuelve nulo.

Análisis de respuestas

La implementación subyacente de malloc: cuando el espacio asignado es inferior a 128 K, se llama a la función brk(); cuando el espacio asignado es mayor a 128 K, se llama a mmap(). Malloc utiliza un método de gestión del grupo de memoria para reducir la fragmentación de la memoria. Primero solicite un bloque grande de memoria como área del montón y luego divida el área del montón en varios bloques de memoria. Cuando el usuario solicita memoria, se asigna un bloque libre adecuado directamente desde el área del montón. Se utiliza una lista vinculada implícita para enumerar todos los bloques libres, y cada bloque libre registra una dirección de memoria continua no asignada.

Nueva implementación subyacente: la palabra clave new en realidad realiza los siguientes pasos al llamar al constructor:

  1. Crear un nuevo objeto

  2. Asigne el alcance del constructor a este nuevo objeto (para que apunte a este nuevo objeto)

  3. Ejecute el código en el constructor (agregue propiedades a este nuevo objeto)

  4. Devolver nuevo objeto

Hablemos de la diferencia entre constante y definir.

Respuesta de referencia

const se usa para definir constantes; define se usa para definir macros y las macros también se pueden usar para definir constantes. Cuando ambos se utilizan en definiciones constantes, sus diferencias son:

  1. const entra en vigor en la etapa de compilación ; define entra en vigor en la etapa de preprocesamiento .

  2. Las constantes definidas por const se almacenan en la memoria en lenguaje C y requieren espacio de memoria adicional; las constantes definidas por define son operandos directos en tiempo de ejecución y no se almacenarán en la memoria.

  3. Las constantes definidas por const se escriben; las constantes definidas por define no se escriben. Por lo tanto, las constantes definidas por define no son propicias para la verificación de tipos.

  4. El dominio es diferente: #definelas macros no están restringidas por el dominio, mientras que constlas constantes solo son válidas dentro del dominio.

Hablemos de la diferencia entre funciones en línea y funciones macro.

Respuesta de referencia

la diferencia:

  1. Las definiciones de macros no son funciones , pero se comportan como funciones. El preprocesador reemplaza las llamadas a funciones copiando códigos de macro, lo que elimina el proceso de insertar y colocar funciones en la pila, lo que mejora la eficiencia. Las funciones en línea son esencialmente funciones , y las funciones en línea se usan generalmente cuando el código en el cuerpo de la función es relativamente simple. no pueden contener declaraciones de control complejas, mientras que las funciones switch e inline no pueden llamarse a sí mismas directamente.

  2. Las funciones de macro reemplazan todos los nombres de macro con cuerpos de macro durante la precompilación , que es simplemente un reemplazo de cadenas ; mientras que las funciones en línea insertan código durante la compilación , y el compilador llamará en línea en todas partes Expande el contenido de la función en línea directamente donde se encuentra la función, lo que puede guardar el costo de llamar a la función y mejorar la eficiencia.

  3. No hay verificación de tipos para las definiciones de macros , y se reemplazan directamente ya sea que sean correctas o incorrectas; mientras que las funciones en línea se verificarán durante la compilación y las funciones en línea satisfacen las propiedades de la función, como valores de retorno, listas de parámetros, etc. .

Análisis de respuestas

//Ejemplo de definición de macro 
#define MAX(a, b) ((a)>(b)?(a):(b)) 
MAX(a, "Hello"); //Comparación incorrecta de int y string, sin parámetro verificación de tipos 
//
Ejemplo de función en línea 
#include <stdio.h> 
int int add(int a, int b) { 
    return (a + b); 
} 
int main(void) { 
    int a; 
    a = add(1, 2) ; 
    printf("a+b=%d\n", a); 
    return 0; 
} 
//Lo anterior a = add(1, 2); se expandirá a: a = (a + b) 
;

1. Algunas precauciones al utilizar:

  • No hay verificación de tipos para funciones definidas por macros y se utilizan paréntesis. Los parámetros de macros deben manejarse con cuidado al definir macros. Generalmente, están entre paréntesis, de lo contrario puede ocurrir fácilmente ambigüedad.

  • Las funciones en línea generalmente se usan para funciones relativamente pequeñas y llamadas con frecuencia, lo que puede reducir la sobrecarga causada por las llamadas a funciones . Solo necesita agregar la palabra clave en línea antes del tipo de retorno de la función para especificar la función como una función en línea.

  • A diferencia de otras funciones, es mejor definir la función en línea en el archivo de encabezado en lugar de simplemente declararla, porque cuando el compilador procesa la función en línea, necesita expandir la función en línea en el punto de llamada, por lo que solo se declara la función. no es suficiente..

2. Condiciones para utilizar funciones en línea:

  • La inserción solo ahorra el costo de las llamadas a funciones a expensas de la expansión (duplicación) del código , mejorando así la eficiencia de ejecución de la función. Si el tiempo para ejecutar el código en el cuerpo de la función es mayor que la sobrecarga de las llamadas a funciones, entonces la ganancia de eficiencia será muy pequeña. Por otro lado, cada llamada a función en línea requiere copiar el código, lo que aumentará el tamaño total del código del programa y consumirá más espacio de memoria. La inserción en línea no debe utilizarse en las siguientes situaciones:

  • (1) Si el código en el cuerpo de la función es relativamente largo, el uso de la inserción generará mayores costos de consumo de memoria.

  • (2) Si se produce un bucle en el cuerpo de la función, el tiempo dedicado a ejecutar el código dentro del cuerpo de la función es mayor que el costo de la llamada a la función.

  • La inserción no se puede expandir en ningún momento. Un buen compilador cancelará automáticamente la inserción que no cumpla con los requisitos según la definición de la función.

función en línea en línea

Para funciones cortas que se usan con frecuencia, inlinese debe usar una función en línea, es decir, el compilador inlinereemplaza el código en la función en línea en el lugar donde se llama la función.

Ventajas: (1) Ahorre tiempo en llamadas a funciones , mejorando así la eficiencia de ejecución del programa; (2) En comparación con las funciones macro, el compilador realizará comprobaciones de seguridad de sintaxis o conversiones de tipos de datos cuando el código se expanda para funciones en línea, lo que las hará más seguras de usar. . ;

Desventajas: (1) expansión del código , lo que genera más gastos generales ; (2) si el tiempo de ejecución del bloque de código dentro de la función en línea es mucho más largo que el tiempo de llamada , entonces la mejora de la eficiencia no es tan grande;

Las declaraciones en línea son solo sugerencias. El compilador decide si insertarlas o no, por lo que en realidad no es controlable.

Hablemos de la diferencia entre funciones en línea y funciones, y el papel de las funciones en línea.

Respuesta de referencia

  1. Las funciones en línea tienen más palabras clave que las funciones ordinarias, en línea

  2. Las funciones en línea evitan la sobrecarga de las llamadas a funciones ; las funciones ordinarias tienen la sobrecarga de las llamadas

  3. Cuando se llaman funciones ordinarias, es necesario direccionarlas (dirección de entrada de función) ; no es necesario direccionar las funciones en línea.

  4. Las funciones en línea tienen ciertas restricciones. Los cuerpos de las funciones en línea requieren un código simple y no pueden contener declaraciones de control estructural complejas; las funciones ordinarias no tienen este requisito.

El papel de las funciones en línea : cuando se llama a una función en línea, la expresión de llamada se reemplaza con el cuerpo de la función en línea . Evite la sobrecarga de las llamadas a funciones.

Análisis de respuestas

Al utilizar funciones en línea, debe prestar atención a los siguientes puntos:

  1. Las declaraciones de bucle y las declaraciones de cambio no están permitidas dentro de las funciones en línea. Si la función en línea tiene estas declaraciones, el compilador tratará la función como una función ordinaria y generará un código de llamada de función. Las funciones recursivas no se pueden utilizar como funciones en línea. Las funciones en línea solo son adecuadas para funciones pequeñas con solo 1 a 5 líneas. Para una función grande con muchas declaraciones, la sobrecarga de llamadas y retornos de función es relativamente insignificante, por lo que no es necesario utilizar funciones en línea.

  2. La definición de una función en línea debe aparecer antes de que se llame por primera vez a la función en línea.

¿Cuáles son las desventajas de las funciones en línea?

Las principales desventajas de las funciones en línea son las siguientes:

  • Incremento de código : las funciones en línea provocan el reemplazo de código en todos los lugares donde se llaman, lo que puede provocar un aumento de código. Si el cuerpo de la función en línea es muy grande o se llama con frecuencia, aumentará el tamaño del archivo ejecutable, lo que puede provocar errores de caché y afectar el rendimiento.

  • Mayor tiempo de compilación : las funciones en línea requieren el reemplazo de código en cada punto de llamada , lo que aumenta el tiempo de compilación. Especialmente cuando las funciones en línea se utilizan ampliamente, los tiempos de compilación pueden aumentar significativamente.

  • Legibilidad reducida : las funciones en línea incrustan el cuerpo de la función en el sitio de llamada , lo que puede reducir la legibilidad del código. Los cuerpos de las funciones están dispersos en varios lugares, lo que puede dificultar la comprensión y el mantenimiento del código.

La diferencia entre typedef y define

  1. Uso diferente : typedef se utiliza para definir un alias de un tipo de datos para mejorar la legibilidad del programa . define se utiliza principalmente para definir constantes y escribir macros complejas y de uso frecuente .

  2. El tiempo de ejecución es diferente : typedef es parte del proceso de compilación y tiene una función de verificación de tipo. define es una definición de macro y una parte precompilada . Ocurre antes de la compilación y simplemente reemplaza cadenas sin realizar verificación de tipo .

  3. Los alcances son diferentes : los typedefs tienen alcance. define no está sujeto a restricciones de alcance, siempre que la referencia después de la declaración de definición sea correcta.

    Nota: La definición de typedef es una declaración, porque se agrega un punto y coma al final de la oración. Definir no es una declaración , por lo que no debes agregar un punto y coma al final de la oración.

anular y final

  • Anulación: asegúrese de que la función declarada en la clase derivada tenga la misma firma que la función virtual de la clase base;

  • final: evita una mayor derivación de la clase y una mayor reescritura de funciones virtuales.

Por ejemplo: agregar anulación indica claramente que la función virtual de la clase derivada anula la clase base. Si las firmas de las funciones virtuales de la clase derivada y la clase base son inconsistentes, el compilador informará un error.

class Base { 
public: 
    virtual void Show(int x); // función virtual 
}; 
class
Derivada: public Base { 
public: 
    virtual void Show(int x) const override; // las propiedades constantes son diferentes, nueva función virtual 
} ; 
/ /Se informará un error, por lo que para reducir los errores cuando se ejecuta el programa, se recomienda agregar anulación a las funciones virtuales reescritas.

Por ejemplo: si no desea que se herede una clase o no desea que se anule una función virtual, puede agregar la palabra clave final después del nombre de la clase y la función virtual, y luego agregar la palabra clave final antes de que se herede. o reescrito El compilador informará un error.

class Base { 
public: 
    virtual void Show(int x) final; // función virtual 
}; 
class
Derivada: public Base { 
public: 
    virtual void Show(int x) override; // override error   
}; 
//Por lo tanto, una vez virtual La función se declara final, las clases derivadas no pueden anularla.

2.5 accidente

Hablemos de qué son los punteros salvajes, cómo se generan y cómo evitarlos.

Respuesta de referencia

  1. Concepto: un puntero comodín significa que la posición señalada por el puntero es desconocida (aleatoria, incorrecta y sin restricciones claras)

  2. Causa : Después de liberar la memoria, si el puntero no se borra a tiempo (un puntero salvaje) y aún apunta a la memoria, puede ocurrir un error de acceso ilegal. Debemos prestar atención para evitarlos.

  3. Como evitar:

    (1) Inicializar en NULL

    (2) Después de solicitar memoria, se determina que está vacía.

    (3) NULL se establece después de soltar el puntero

    (4) Utilice punteros inteligentes

Análisis de respuestas

Causa : Después de liberar la memoria, si el puntero no se borra a tiempo (un puntero salvaje) y aún apunta a la memoria, puede ocurrir un error de acceso ilegal. Debemos prestar atención para evitarlos. como:

char *p = (char *)malloc(sizeof(char)*100);   
strcpy(p, "Douya");   
free(p);// La memoria apuntada por p se libera, pero la dirección apuntada por p es todavía no cambia   
...   
if (p != NULL){//No desempeña ningún papel en la prevención de errores   
    strcpy(p, "hello, Douya!");//Error   
}  

Como evitar:

(1) Inicializar en NULL

(2) Después de solicitar memoria, se determina que está vacía.

(3) NULL se establece después de soltar el puntero

int *p = NULL; //Inicializar en NULL 
p = (int *)malloc(sizeof(int)*n); //Aplicación para n int espacios de memoria   
afirmar(p != NULL); //Juicio nulo, prevención de errores Diseño 
p = (int *) realloc(p, 25); //Reasigna memoria, el bloque de memoria señalado por p se liberará y se asignará una nueva dirección de memoria free(p); p = NULL; //Liberar la 
publicación   
Vacía 
int
*p1 = NULL; //Inicializar en NULL 
p1 = (int *)calloc(n, sizeof(int)); //Solicitar n int espacio de memoria e inicializar en 0 al mismo tiempo 
afirmar(p1!= NULL) ; // Detección de nulos, diseño a prueba de errores 
free(p1);   
p1 = NULL; // Liberar el espacio vacío 
int
*p2 = NULL; // Inicializar en NULL 
p2 = new int[n]; //Solicitar n int espacio de memoria   
afirmar (p2! = NULL); // Juicio nulo, diseño a prueba de errores 
eliminar [] p2;   
p2 = nullptr; //  
Nulo

¿Cuál es la diferencia entre el puntero colgante y el puntero salvaje?

Puntero colgante: un puntero colgante significa que un puntero todavía existe, pero la memoria a la que apunta se ha liberado o no es válida . En un programa, cuando libera un bloque de memoria y el puntero al bloque de memoria todavía existe, el puntero se convierte en un puntero colgante. El uso de punteros colgantes puede provocar un comportamiento indefinido porque intenta acceder a un área de memoria que ya ha sido liberada.

int* danglingPtr() {
    int x = 10;
    int* ptr = &x; // ptr指向了一个局部变量
    return ptr;
}

int main() {
    int* p = danglingPtr();
    // 此时p成为了悬挂指针,因为它指向了已经被释放的内存区域
    // 在访问*p时会导致未定义行为
    return 0;
}

Puntero comodín: un puntero comodín se refiere a un puntero no inicializado o liberado, que apunta a una dirección de memoria desconocida . El uso de punteros salvajes puede provocar el acceso a áreas de memoria desconocidas o no válidas , lo que también puede provocar un comportamiento indefinido. Los punteros salvajes generalmente son causados ​​por no inicializar correctamente el puntero o no configurarlo en nulo o en una dirección de memoria legal después de liberar la memoria.

Evite sugerencias salvajes:

  1. Las variables de puntero no se inicializan cuando se declaran. Solución: inicialice el puntero cuando se declare. Puede ser un valor de dirección específico o puede apuntar a NULL.

  2. Una vez liberado o eliminado el puntero p, no se establece en NULL. Solución: una vez liberado el espacio de memoria señalado por el puntero, el puntero debe apuntar a NULL.

  3. Las operaciones de puntero van más allá del alcance de las variables. Solución: libere el espacio de direcciones de la variable y deje que el puntero apunte a NULL antes de que finalice el alcance de la variable.

int* wildPtr() {
    int* ptr; // 未初始化的指针
    *ptr = 5; // 这里会导致未定义行为,因为ptr指向未知的内存地址
    return ptr;
}

int main() {
    int* p = wildPtr();
    // p成为了野指针,因为ptr未初始化,指向未知的内存地址
    return 0;
}

Dime, ¿a qué debes prestar atención cuando usas punteros?

Respuesta de referencia

  1. Al definir un puntero, inicialícelo primero en NULL.

  2. Después de solicitar memoria con malloc o new, debe verificar inmediatamente si el valor del puntero es NULL. Evite el uso de memoria cuyo valor de puntero sea NULL.

  3. No olvide inicializar las matrices y la memoria dinámica . Evita que la memoria no inicializada se utilice como valor r.

  4. Evite subíndices de números o punteros fuera de límites , especialmente tenga cuidado con las operaciones "1 más" o "1 menos".

  5. La aplicación y la versión de memoria dinámica deben estar emparejadas para evitar pérdidas de memoria.

  6. Después de liberar la memoria con liberar o eliminar, establezca inmediatamente el puntero en NULL para evitar "punteros salvajes"

Análisis de respuestas

(1) Inicializar en NULL

(2) Después de solicitar memoria, se determina que está vacía.

(3) NULL se establece después de soltar el puntero

int *p = NULL; //Inicializar en NULL 
p = (int *)malloc(sizeof(int)*n); //Aplicación para n int espacios de memoria   
afirmar(p != NULL); //Juicio nulo, prevención de errores Diseño 
p = (int *) realloc(p, 25); //Reasigna memoria, el bloque de memoria señalado por p se liberará y se asignará una nueva dirección de memoria free(p); p = NULL; //Liberar la 
publicación   
Vacía 
int
*p1 = NULL; //Inicializar en NULL 
p1 = (int *)calloc(n, sizeof(int)); //Solicitar n int espacio de memoria e inicializar en 0 al mismo tiempo 
afirmar(p1!= NULL) ; // Detección de nulos, diseño a prueba de errores 
free(p1);   
p1 = NULL; // Liberar el espacio vacío 
int
*p2 = NULL; // Inicializar en NULL 
p2 = new int[n]; //Solicitar n int espacio de memoria   
afirmar (p2! = NULL); // Juicio nulo, diseño a prueba de errores 
eliminar [] p2;   
p2 = nullptr; //  
Nulo

​​​​​​

Supongo que te gusta

Origin blog.csdn.net/shisniend/article/details/131908934
Recomendado
Clasificación