Siempre he visto gente escribir
clase.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
, e main.cpp
incluye class.h
. El class.h
archivo no contiene include
nada, entonces, ¿cómo main.cpp
sabe qué hay en el archivo class.cpp
?
FILE_H
, noCLASS_H
.Respuestas:
Primero, para abordar su primera consulta:
Cuando vea esto en el archivo .h :
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
, los concatenará en un solo archivo de texto masivo y luego realizará un análisis de sintaxis y finalmente lo convertirá en algún código intermedio, optimizará / realizará otro tareas, y finalmente generar la salida del ensamblaje para la arquitectura de destino. Debido a esto, si un archivo está#included
varias veces bajo un .cpparchivo, el compilador agregará su contenido de archivo dos veces, por lo que si hay definiciones dentro de ese archivo, recibirá un error del compilador que le indica que redefinió 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 siFILE_H
se ha definido para el preprocesador. De lo contrario, definiráFILE_H
y continuará procesando el código entre este y la#endif
directiva. La próxima vez que el preprocesador vea el contenido de ese archivo, la verificación contraFILE_H
será falsa, por lo que se escaneará inmediatamente#endif
y continuará después. Esto evita errores de redefinición.Y para abordar su segunda preocupación:
En la programación en C ++ como 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, solo 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 clase, cualquier miembro de 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 esta razón, generalmente durante la compilación compilamos cada .cpparchivo en un objeto y luego vincular esos objetos (porque casi nunca ve un archivo .cpp incluye otro archivo .cpp ).
Cómo se resuelven estos elementos externos es un trabajo para el vinculador. Cuando su compilador procesa main.cpp , obtiene declaraciones para el código en class.cpp al incluir class.h . Solo necesita saber cómo se ven estas funciones o variables (que es lo que le da una declaración). Por lo tanto, compila su archivo main.cpp en algún archivo de objeto ( llámelo main.obj ). Del mismo modo, class.cpp se compila en un class.objexpediente. Para producir el ejecutable final, se invoca un vinculador para vincular esos dos archivos de objetos. Para cualquier variable o función externa no resuelta, el compilador colocará un código auxiliar donde ocurre el acceso. El enlazador luego tomará este código auxiliar y buscará el código o la variable en otro archivo de objeto listado, y si se encuentra, combina el código de los dos archivos de objeto en un archivo de salida y reemplaza el código auxiliar 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 SOLO SI SE DECLARAN EN class.h .
Espero que esto haya sido útil.
fuente
El
CLASS_H
es un guardia incluido ; 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 conduciría a errores de definición múltiple.No se necesitan incluir protectores en los archivos CPP porque, por definición, el contenido del archivo CPP solo se lee una vez.
Parece haber interpretado que los guardias de inclusión tienen la misma función que las
import
declaraciones en otros lenguajes (como Java); ese no es el caso, sin embargo. El#include
sí mismo es más o menos equivalente alimport
de otros idiomas.fuente
No lo hace, al menos durante la fase de compilación.
La traducción de un programa c ++ del código fuente al código máquina se realiza en tres fases:
class.h
se inserta en lugar de la línea#include "class.h
. Dado que puede estar incluido en su archivo de encabezado en varios lugares, las#ifndef
cláusulas evitan errores de declaración duplicados, ya que la directiva del preprocesador no está definida solo la primera vez que se incluye el archivo de encabezado.En resumen, las declaraciones se pueden compartir a través de un archivo de encabezado, mientras que el mapeador de declaraciones a definiciones lo realiza el vinculador.
fuente
Esa es la distinción entre declaración y definición. Los archivos de encabezado generalmente incluyen solo la declaración, y el archivo fuente contiene la definición.
Para usar algo solo necesitas saber que es declaración, no es definición. Solo el vinculador necesita conocer la definición.
Por eso, incluirá un archivo de encabezado dentro de uno o más archivos de origen, pero no incluirá un archivo de origen dentro de otro.
También te refieres
#include
y no importas.fuente
Esto se hace para los archivos de encabezado para que el contenido solo aparezca una vez en cada archivo fuente preprocesado, incluso si se incluye más de una vez (generalmente porque se incluye desde 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 incluye todo el contenido del archivo. Hacer esto define el símbolo, por lo que si se incluye nuevamente, el contenido del archivo (dentro de#ifndef
/#endif
se omiten bloque ).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 cualquier otra cosa, para 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 llamarlos.fuente
main.cpp no tiene que saber qué hay en class.cpp . Solo tiene que conocer las declaraciones de las funciones / clases que va a usar, y estas declaraciones están en class.h .
El enlazador enlaza entre los lugares donde se usan las funciones / clases declaradas en class.h y sus implementaciones en class.cpp
fuente
.cpp
los archivos no se incluyen (usando#include
) en otros archivos. Por lo tanto, no necesitan incluir vigilancia.Main.cpp
conocerá los nombres y las firmas de la clase en la que ha implementadoclass.cpp
solo porque ha especificado todo esoclass.h
; este es el propósito de un archivo de encabezado. (Depende de usted asegurarse de queclass.h
describe con precisión el código en el que implementaclass.cpp
). El código ejecutableclass.cpp
estará disponible para el código ejecutablemain.cpp
gracias a los esfuerzos del vinculador.fuente
En general, se espera que los módulos de código, como los
.cpp
archivos, se compilen una vez y se vinculen en múltiples proyectos, para evitar la compilación repetitiva innecesaria de lógica. Por ejemplo,g++ -o class.cpp
produciríaclass.o
lo que luego podría vincular desde múltiples proyectos para usarg++ main.cpp class.o
.Podríamos usarlo
#include
como nuestro vinculador, como parece estar insinuando, pero eso sería una tontería cuando sepamos cómo vincularnos correctamente utilizando nuestro compilador con menos pulsaciones de teclas y una repetición de compilación menos derrochadora, en lugar de nuestro código con más pulsaciones de teclas y más derrochador repetición de compilación ...Sin embargo, aún se requiere que los archivos de encabezado se incluyan en cada uno de los proyectos múltiples, ya que esto proporciona la interfaz para cada módulo. Sin estos encabezados, el compilador no conocería ninguno de los símbolos introducidos por los
.o
archivos.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 haya realizado, entonces tiene sentido que las inclusiones múltiples puedan causar redefiniciones de símbolos (lo que provoca errores), por lo que utilizamos guardias de inclusión para evitar tales redefiniciones.
fuente
debido a que Headerfiles define 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 como resultado múltiples definiciones de una clase, que no es válida.
fuente