En programación informática , una declaración anticipada es una declaración de un identificador (que denota una entidad como un tipo, una variable, una constante o una función) para la cual el programador aún no ha dado una definición completa .
Es necesario que un compilador conozca ciertas propiedades de un identificador (tamaño para la asignación de memoria , tipo de datos para la comprobación de tipos, como la firma de tipo de las funciones), pero no otros detalles, como el valor particular que contiene (en el caso de variables o constantes) o la definición (en el caso de funciones). Esto es particularmente útil para compiladores de una sola pasada y compilación separada.
La declaración adelantada se utiliza en lenguajes que requieren una declaración antes de su uso; es necesaria para la recursión mutua en dichos lenguajes, ya que es imposible definir dichas funciones (o estructuras de datos) sin una referencia adelantada en una definición: una de las funciones (respectivamente, las estructuras de datos) debe definirse primero. También es útil para permitir una organización flexible del código, por ejemplo, si se desea colocar el cuerpo principal en la parte superior y las funciones llamadas debajo.
En otros lenguajes, las declaraciones anticipadas no son necesarias, lo que generalmente requiere un compilador de múltiples pasadas y que se posponga parte de la compilación hasta el momento del enlace . En estos casos, los identificadores deben definirse (se deben inicializar las variables, se deben definir las funciones) antes de que se puedan emplear durante el tiempo de ejecución sin necesidad de una definición previa en el código fuente para la compilación o la interpretación: no es necesario resolver los identificadores inmediatamente en una entidad existente.
Un ejemplo básico en C es:
vacío printThisInteger ( int );
En C y C++ , la línea anterior representa una declaración adelantada de una función y es el prototipo de la función . Después de procesar esta declaración, el compilador permitiría que el código del programa haga referencia a la entidad printThisInteger
en el resto del programa. La definición de una función debe proporcionarse en algún lugar (el mismo archivo u otro, donde sería responsabilidad del enlazador hacer coincidir correctamente las referencias a una función particular en uno o varios archivos de objeto con la definición, que debe ser única, en otro):
void imprimirEsteEntero ( int x ) { printf ( "%d \n " , x ); }
Las variables solo pueden tener una declaración previa y carecer de definición. Durante el tiempo de compilación, se inicializan mediante reglas específicas del lenguaje (a valores indefinidos, 0, punteros NULL, etc.). Las variables que se definen en otros archivos de origen/objeto deben tener una declaración previa especificada con una palabra clave extern
:
int foo ; //foo podría estar definido en algún lugar de este archivo extern int bar ; //bar debe estar definido en algún otro archivo
En Pascal y otros lenguajes de programación Wirth , es una regla general que todas las entidades deben ser declaradas antes de su uso, y por lo tanto, la declaración hacia adelante es necesaria para la recursión mutua, por ejemplo. En C, se aplica la misma regla general, pero con una excepción para las funciones no declaradas y los tipos incompletos. Por lo tanto, en C es posible (aunque no es prudente) implementar un par de funciones recursivas mutuas de la siguiente manera:
int primero ( int x ) { si ( x == 0 ) devuelve 1 ; de lo contrario devuelve segundo ( x -1 ); // referencia hacia adelante al segundo } int segundo ( int x ) { si ( x == 0 ) devuelve 0 ; de lo contrario devuelve primero ( x -1 ); // referencia hacia atrás al primero }
En Pascal, la misma implementación requiere una declaración adelantada de second
antes de su uso en first
. Sin la declaración adelantada, el compilador producirá un mensaje de error que indica que el identificador second
se ha utilizado sin declararse.
En algunos lenguajes orientados a objetos como C++ y Objective-C , a veces es necesario declarar las clases de forma anticipada. Esto se hace en situaciones en las que es necesario saber que el nombre de la clase es un tipo, pero no es necesario conocer la estructura.
En C++, las clases y estructuras se pueden declarar de manera anticipada de la siguiente manera:
clase MiClase ; estructura MiEstructura ;
En C++, las clases se pueden declarar de forma anticipada si solo se necesita usar el tipo de puntero a esa clase (ya que todos los punteros de objetos tienen el mismo tamaño y esto es lo que le importa al compilador). Esto es especialmente útil dentro de las definiciones de clase, por ejemplo, si una clase contiene un miembro que es un puntero (o una referencia) a otra clase.
La declaración adelantada se utiliza para evitar acoplamientos innecesarios, lo que ayuda a reducir el tiempo de compilación al reducir la cantidad de encabezados incluidos. Esto tiene una triple ventaja:
La declaración anticipada de una clase no es suficiente si necesita utilizar el tipo de clase real, por ejemplo, si tiene un miembro cuyo tipo es esa clase directamente (no un puntero), o si necesita usarlo como una clase base, o si necesita utilizar los métodos de la clase en un método.
En Objective-C, las clases y los protocolos se pueden declarar de manera anticipada de la siguiente manera:
@class MiClase ; @protocol MiProtocolo ;
En Objective-C, las clases y los protocolos se pueden declarar de forma anticipada si solo se necesitan utilizar como parte de un tipo de puntero a objeto, p. ej., MyClass * o id<MyProtocol> . Esto es especialmente útil dentro de las definiciones de clase, p. ej., si una clase contiene un miembro que es un puntero a otra clase; para evitar referencias circulares (es decir, que esa clase también podría contener un miembro que sea un puntero a esta clase), simplemente declaramos de forma anticipada las clases.
La declaración anticipada de una clase o protocolo no es suficiente si necesita subclasificar esa clase o implementar ese protocolo.
El término referencia hacia adelante se utiliza a veces como sinónimo de declaración hacia adelante . [1] Sin embargo, más a menudo se toma para referirse al uso real de una entidad antes de cualquier declaración; es decir, la primera referencia en second
el código anterior es una referencia hacia adelante. [2] [3] Por lo tanto, podemos decir que debido a que las declaraciones hacia adelante son obligatorias en Pascal, las referencias hacia adelante están prohibidas.
Un ejemplo de referencia hacia adelante (válida) en C++ :
clase C { público : void mutador ( int x ) { miValor = x ; } int descriptor de acceso () const { devolver miValor ; } privado : int miValor ; };
En este ejemplo, hay dos referencias a myValue
antes de que se declare. C++ generalmente prohíbe las referencias hacia delante, pero se permiten en el caso especial de los miembros de clase. Dado que la función miembro accessor
no se puede compilar hasta que el compilador conozca el tipo de la variable miembro myValue
, es responsabilidad del compilador recordar la definición de accessor
hasta que vea myValue
la declaración de .
Permitir referencias futuras puede aumentar en gran medida la complejidad y los requisitos de memoria de un compilador y, generalmente, evita que el compilador se implemente en una sola pasada .