c++ - que - ¿Por qué usar#ifndef CLASS_H y#define CLASS_H en archivo.h pero no en.cpp?
ifdef c++ para que sirve (9)
El CLASS_H
es un guardia de inclusión ; se usa para evitar que el mismo archivo de encabezado se incluya varias veces (a través de diferentes rutas) dentro del mismo archivo CPP (o, más exactamente, la misma unidad de traducción ), lo que daría lugar a errores de definición múltiple.
No se necesitan guardias de inclusión en los archivos CPP porque, por definición, los contenidos del archivo CPP solo se leen una vez.
Parece que has interpretado que los guardias include tienen la misma función que import
enunciados de import
en otros idiomas (como Java); ese no es el caso, sin embargo. El #include
sí es más o menos equivalente a la import
en otros idiomas.
Siempre he visto gente escribir
class.h
#ifndef CLASS_H
#define CLASS_H
//blah blah blah
#endif
La pregunta es, ¿por qué no hacen eso también para el archivo .cpp que contiene definiciones para funciones de clase?
Digamos que tengo main.cpp
, y main.cpp
incluye class.h
. El archivo class.h
no importa nada, entonces, ¿cómo main.cpp
sabe qué hay en class.cpp
?
En general, se espera que los módulos de código, como los archivos .cpp
, se .cpp
una vez y se vinculen en varios proyectos, para evitar la compilación repetitiva innecesaria de la lógica. Por ejemplo, g++ -o class.cpp
produciría class.o
que luego podría vincular desde múltiples proyectos a usar g++ main.cpp class.o
.
Podríamos usar #include
como nuestro enlazador, como parece estar implicando, pero eso sería una tontería cuando sepamos cómo vincular correctamente utilizando nuestro compilador con menos pulsaciones de teclas y menos repetición de compilación, en lugar de nuestro código con más teclas y más repetición derrochadora de compilación ...
Sin embargo, los archivos de encabezado aún deben incluirse en cada uno de los proyectos múltiples, ya que esto proporciona la interfaz para cada módulo. Sin estos encabezados, el compilador no sabría sobre ninguno de los símbolos introducidos por los archivos .o
.
Es importante darse cuenta de que los archivos de encabezado son los que introducen las definiciones de símbolos para esos módulos; una vez que se realiza, tiene sentido que las inclusiones múltiples puedan causar redefiniciones de símbolos (lo que causa errores), por lo que usamos incluir guardias para evitar tales redefiniciones.
Es debido a Headerfiles definir lo que contiene la clase (miembros, estructuras de datos) y los archivos cpp lo implementan.
Y, por supuesto, la razón principal de esto es que podría incluir un archivo .h varias veces en otros archivos .h, pero esto daría lugar a múltiples definiciones de una clase, que no es válida.
Esa es la distinción entre declaración y definición. Los archivos de encabezado normalmente incluyen solo la declaración, y el archivo de origen contiene la definición.
Para usar algo solo necesitas saber su declaración, no su definición. Solo el vinculador necesita saber la definición.
Por eso, incluirá un archivo de encabezado dentro de uno o más archivos fuente, pero no incluirá un archivo fuente dentro de otro.
También quieres decir #include
y no importar.
Esto se hace para los archivos de encabezado, de modo que el contenido solo aparezca una vez en cada archivo fuente preprocesado, incluso si se incluye más de una vez (generalmente porque está incluido en otros archivos de encabezado). La primera vez que se incluye, el símbolo CLASS_H
(conocido como protector de inclusión ) aún no se ha definido, por lo que se incluyen todos los contenidos del archivo. Hacer esto define el símbolo, por lo que si se incluye de nuevo, los contenidos del archivo (dentro del bloque #ifndef
/ #endif
) se saltan.
No es necesario hacer esto para el archivo fuente en sí ya que (normalmente) no está incluido en ningún otro archivo.
Para su última pregunta, class.h
debe contener la definición de la clase y las declaraciones de todos sus miembros, funciones asociadas y todo lo demás, de modo que cualquier archivo que lo incluya tenga suficiente información para usar la clase. Las implementaciones de las funciones pueden ir en un archivo fuente separado; solo necesitas las declaraciones para llamarlas.
No lo hace, al menos durante la fase de compilación.
La traducción de un programa c ++ desde el código fuente al código máquina se realiza en tres fases:
- Preprocesamiento : el preprocesador analiza todo el código fuente de las líneas que comienzan con # y ejecuta las directivas. En su caso, el contenido de su
class.h
se inserta en lugar de la línea#include "class.h
. Dado que es posible que incluya su archivo de encabezado en varios lugares, las cláusulas#ifndef
evitan errores de declaración duplicados, ya que La directiva de preprocesador no está definida solo la primera vez que se incluye el archivo de encabezado. - Compilación : el compilador ahora convierte todos los archivos de código fuente preprocesados en archivos de objetos binarios.
- Enlace : el vinculador vincula (de ahí el nombre) los archivos del objeto. Una referencia a su clase o uno de sus métodos (que debe declararse en class.h y definido en class.cpp) se resuelve con el desplazamiento respectivo en uno de los archivos objeto. Escribo ''uno de tus archivos objeto'' ya que tu clase no necesita ser definida en un archivo llamado class.cpp, podría estar en una biblioteca que está vinculada a tu proyecto.
En resumen, las declaraciones se pueden compartir a través de un archivo de encabezado, mientras que el mapeador realiza las correlaciones de las declaraciones a las definiciones.
Primero, para abordar su primera consulta:
Cuando vea esto en el archivo .h :
#ifndef FILE_H
#define FILE_H
/* ... Declarations etc here ... */
#endif
Esta es una técnica de preprocesador para evitar que un archivo de encabezado se incluya varias veces, lo que puede ser problemático por varias razones. Durante la compilación de su proyecto, cada archivo .cpp (generalmente) se compila. En términos simples, esto significa que el compilador tomará su archivo .cpp , abrirá todos los archivos #included
por él, los concatenará a todos en un archivo de texto masivo, y luego realizará un análisis de sintaxis y finalmente lo convertirá en algún código intermedio, optimice / realice otras tareas y finalmente genere el resultado del ensamblaje para la arquitectura de destino. Debido a esto, si un archivo está #included
incluido varias veces debajo de un archivo .cpp , el compilador agregará el contenido de su archivo dos veces, por lo que si hay definiciones dentro de ese archivo, obtendrá un error del compilador que le indicará que ha redefinido una variable. Cuando el archivo es procesado por el paso del preprocesador en el proceso de compilación, la primera vez que se alcanza su contenido, las dos primeras líneas verificarán si se ha definido FILE_H
para el preprocesador. Si no, definirá FILE_H
y continuará procesando el código entre él y la directiva #endif
. La próxima vez que el preprocesador vea el contenido del archivo, el control contra FILE_H
será falso, por lo que se escaneará inmediatamente hacia abajo hasta el #endif
y continuará luego. Esto evita errores de redefinición.
Y para abordar su segunda preocupación:
En la programación C ++ como una práctica general, separamos el desarrollo en dos tipos de archivos. Uno es con una extensión de .h y lo llamamos un "archivo de encabezado". Por lo general, proporcionan una declaración de funciones, clases, estructuras, variables globales, typedefs, preprocesamiento de macros y definiciones, etc. Básicamente, simplemente le brindan información sobre su código. Luego tenemos la extensión .cpp que llamamos un "archivo de código". Esto proporcionará definiciones para esas funciones, miembros de la clase, cualquier miembro de la estructura que necesite definiciones, variables globales, etc. Por lo tanto, el archivo .h declara el código, y el archivo .cpp implementa esa declaración. Por este motivo, generalmente durante la compilación compilamos cada archivo .cpp en un objeto y luego vinculamos esos objetos (porque casi nunca vemos un archivo .cpp que incluya otro archivo .cpp ).
Cómo se resuelven estas externalidades es un trabajo para el vinculador. Cuando su compilador procesa main.cpp , obtiene declaraciones para el código en class.cpp incluyendo class.h . Solo necesita saber cómo se ven estas funciones o variables (que es lo que una declaración le brinda). Por lo tanto, compila su archivo main.cpp en algún archivo objeto ( llámelo main.obj ). Del mismo modo, class.cpp se compila en un archivo class.obj . Para producir el ejecutable final, se invoca un enlazador para vincular esos dos archivos de objeto. Para cualquier variable externa o función no resuelta, el compilador colocará un stub donde ocurre el acceso. El vinculador tomará este código auxiliar y buscará el código o la variable en otro archivo objeto listado, y si se encuentra, combina el código de los dos archivos objeto en un archivo de salida y lo reemplaza con la ubicación final de la función o variable. De esta manera, su código en main.cpp puede invocar funciones y usar variables en class.cpp SI Y SÓLO SI SE DECLARAN EN class.h .
Espero que esto haya sido útil.
.cpp
archivos .cpp
no están incluidos (usando #include
) en otros archivos. Por lo tanto, no es necesario incluir protección. Main.cpp
sabrá los nombres y firmas de la clase que ha implementado en class.cpp
solo porque ha especificado todo eso en class.h
: este es el propósito de un archivo de encabezado. ( class.h
usted asegurarse de que class.h
describa con precisión el código que implementa en class.cpp
.) El código ejecutable en class.cpp
estará disponible para el código ejecutable en main.cpp
gracias a los esfuerzos del enlazador
main.cpp no tiene que saber qué hay en class.cpp . Solo tiene que saber las declaraciones de las funciones / clases que va a usar, y estas declaraciones están en class.h .
Los enlaces enlazadores entre los lugares donde se usan las funciones / clases declaradas en class.h y sus implementaciones en class.cpp