You are on page 1of 64

2 Ciclo vital El tiempo de vida o ciclo vital ("Lifetime") de un objeto es una propiedad de tiempo de ejecucin ("Runtime").

Viene determinado por el lapso entre su creacin y su destruccin. Por supuesto no puede exceder la duracin de su almacenamiento. El ciclo vital comienza cuando se le asigna espacio de almacenamiento y, si no es un objeto trivial, cuando el objeto es convenientemente iniciado por su constructor. Finaliza cuando es llamado el destructor o se rehsa la zona de almacenamiento que le haba sido asignada. Nota: decimos que un objeto es trivial cuando es, por ejemplo, un tipo simple preconstruido en el lenguaje. En este caso una expresin del tipo int x; basta para que el compilador pueda reservar espacio de almacenamiento.

Observe que el ciclo vital de los objetos automticos y estticos es controlado automticamente por el compilador. En los primeros la destruccin se realiza cuando el objeto sale de mbito. En los segundos la destruccin ocurre con las rutinas de finalizacin del programa. Por su parte el ciclo vital de los objetos dinmicos es controlado por el programador.

4.1.5a Control de recursos


Advertencia didctica: la lectura de este captulo exige un conocimiento previo de las clases ( 4.11), del mecanismo de excepciones ( 1.6) y de los operadores new y delete ( 4.9.20).

1 Sinopsis La duracin de las entidades creadas en un programa, no solo est relacionada con la creacin/destruccin de variables y la correspondiente asignacin y liberacin de memoria. Tambin se relaciona con el manejo y control de determinados recursos externos que son utilizados en runtime. Para situarnos en el asunto desde un punto de vista general, podemos decir que cuando el programa entra en un mbito lxico (bloque o funcin), se crean determinadas entidades (que hemos denominado objetos-valor 4.1.1). Estas entidades deben ser iniciadas correctamente antes de su uso y dependiendo de su duracin, sern posteriormente destruidas o no al salir del bloque. Tambin es frecuente que se asignen determinados recursos para uso del programa durante su estancia en el mbito. Estos recursos, que deben ser correctamente iniciados antes de su utilizacin y desasignados cuando ya no son necesarios, pueden consistir en el establecimiento de una lnea de comunicacin; la apertura de un fichero; la asignacin de un dispositivo determinado, Etc. Por ejemplo: un fichero abierto de forma exclusiva; un registro bloqueado para escritura, o la asignacin de una unidad de almacenamiento para backup.

Nota: como puede verse, en este contexto, el significado de "recurso" es muy amplio. Parafraseando a Bartosz Milewski [2] podramos decir que recurso es cualquier cosa que el programa deba acopiar/adquirir y que deba ser posteriormente desechada. Lo normal es que tales recursos estn controlados y representados por un objeto. Por ejemplo, el manejador ("handle") de un fichero abierto, de forma que existe una ntima relacin entre los recursos y las entidades que los representan. Es fundamental que antes que esta entidad sea destruida, se realice la correcta liberacin del recurso. Por ejemplo, el fichero debe ser cerrado antes que su handle sea destruido, y el objeto creado con new debe ser borrado con delete antes que el puntero desaparezca. Como regla general se acepta que los objetos y recursos deben ser destruidos/desasignados exactamente en orden inverso al que se utiliz para asignarlos y crearlos. El asunto es que, por ejemplo, los objetos creados con new, o los ficheros abiertos con fopen, no se destruyen o cierran, automticamente al salir del mbito. El programador debe ser especialmente cuidadoso, y recordar destruir (con delete), o cerrar (con fclose), el objeto/fichero correspondiente antes que los respectivos manejadores dejen de ser accesibles. La situacin puede ser esquematizada como sigue: // Ejemplo-1 func f1 (char* file_name, char* mode, int size) { char* cbuff = new char[size]; // asignar recursos FILE* fptr = fopen(file_name, mode); ... fclose(fptr); delete[] cbuff; } // usar recursos // liberar recursos

Las precauciones anteriores son tediosas para el programador y propensas a errores. Adems, como pone de manifiesto el siguiente ejemplo, en ocasiones pueden resultar insuficientes. // Ejemplo-2 func foo () { int size = 1000 char* filenam = "miFichero.txt"; char* mode = "w+t"; try { f1(filenam, mode, size); } catch(...) { cout << "Ha ocurrido un error!!" << endl; } }

El problema aqu es que cualquier error en la zona de uso de la funcin f1 (Ejemplo-1), podra lanzar una excepcin que sera recogida en elcatch de foo, con lo que la memoria de cbuff se perdera, y miFichero.txt quedara abierto.

2 Adquirir un recurso es inicializarlo

La propiedad del compilador ya sealada ( 4.1.5), de invocar automticamente los destructores de los objetos automticos cuando estos salen de mbito, puede ser utilizada para la desasignacin de recursos de forma cmoda y segura. El funcionamiento consiste en que los recursos se asocian a instancias de clases diseadas al efecto, conocidas "RAII classes", en cuyos destructores se han incluido los mecanismos de destruccin/desasignacin pertinentes. Esta tcnica, conocida como adquirir un recurso es inicializarlo, RAII [1], es eficaz incluso en presencia del mecanismo de excepciones, ya que en este caso, el proceso de limpieza de la pila ("stack unwinding" 1.6) garantiza la destruccin de los objetos creados desde el comienzo del bloque try hasta el punto de lanzamiento de la excepcin. As pues, la regla de oro para el control de recursos consiste en encapsularlos en objetos de ciertas clases diseadas al efecto; asignndolos en los constructores y desasignndolos en el destructor de la clase correspondiente. El sentido de la frase "adquirir un recurso es inicializarlo" se explica porque el recurso est representado por un objeto, y para adquirirlo basta iniciar el objeto correspondiente (instanciarlo). Para ilustrar la aplicacin de esta tcnica, completaremos los ejemplos anteriores suponiendo que el objeto representado por cbuff y el fichero abierto se asocian a sendos objetos. La operacin de abrir y cerrar el fichero del ejemplo-1 lo vamos a encomendar a un objeto de la clase Fichero: class Fichero { public: FILE* fptr; Fichero(char* file_name, char* mode) { // constructor fptr = fopen(file_name, mode); } ~Fichero() { // destructor fclose(fptr); } }; A su vez, la operacin de recabar memoria para un buffer de caracteres la asociamos a un objeto de la clase CBuffer: class CBuffer { public: char* cbuff; CBuffer(size_t size) { // constructor cbuff = new char[size]; } ~CBuffer() { // destructor delete[] cbuff; } };

Las nuevas definiciones permiten reducir la adquisicin de memoria para un buffer, o la apertura de un fichero, a la operacin de crear un objeto de la clase adecuada. A su vez, la desasignacin de los respectivos recursos se reduce a la destruccin de dichos objetos (lo que ocurrir generalmente a su salida de mbito). Bajo estas premisas, la funcin f1 del ejemplo-1 puede ser modificada en la forma siguiente:

// Ejemplo-1a func f1 (char* file_name, char* mode, int size) { CBuffer cb1(size); // asignar recursos Fichero file1(file_name, mode); ... // usar recursos cout << cb1.cbuff << endl; } // Ok. liberar recursos (esto lo hace el compilador)

Al contrario de lo que ocurre con la primera versin, la utilizacin de este nuevo diseo de f1 en foo , resulta inmune al posible lanzamiento de una excepcin en su zona de uso. El proceso de desmontaje de la pila implicara la llamada a los destructores de los objetos cb1 y file1.

3 Precauciones adicionales Naturalmente el sistema anterior no est totalmente exento de riesgos (nada lo est realmente). Recordemos que el proceso de destruccin relacionado con el "Stack unwinding" solo se realiza con aquellos objetos que hayan sido total y completamente construidos. En nuestro caso podra presentarse un problema de asignacin de memoria durante la construccin de un objeto Cbuffer o en la apertura de un fichero en un objeto Fichero. En consecuencia, deberan adoptarse precauciones adicionales en el diseo de las clases respectivas. class Fichero { public: FILE* fptr; Fichero(const char* file_name, const char* mode) { // constructor fptr = fopen(file_name, mode); if (!fptr) { cout << "Error en apertura de fichero " << file_name; throw 1; } } ~Fichero() { // destructor if (fptr) fclose(fptr); } }; Para la clase CBuffer el diseo podra ser el siguiente: class CBuffer { public: char* cbuff; enum {MAX = 64000} CBuffer(size_t size = 32000) { // constructor if (size == 0 || size > MAX) { cout << "Tamao no vlido << endl; throw 2; } try { cbuff = new char[size]; } catch (const bad_alloc& e) { cout << "Memoria agotada " << e.what() << endl; throw; // relanzar excepcin

} } ~CBuffer() { // destructor if (cbuff) delete[] cbuff; } };

Como puede verse, es posible, e incluso recomendable, lanzar excepciones en los constructores. En nuestro caso la excepcin lanzada por el constructor de Fichero en caso de fallo en la apertura, sera capturada en por el "handler" de foo . Por su parte, la excepcin lanzada porCBuffer en caso de error en la asignacin de memoria, es manejada por el propio constructor para posteriormente relanzarla ( 1.6.1); finalmente la nueva excepcin es capturada por el manejador de foo. Otra precaucin adicional es no encapsular distintos recursos en un solo objeto, de forma que si una clase debe encapsular varios recursos, es necesario colocarlos separadamente en subobjetos de la clase (miembros que son instancias de otras clases). La razn es que generalmente, los recursos son entidades finitas. Por ejemplo, memoria o ancho de banda en una lnea de comunicacin, por lo que su adquisicin responde a operaciones con cierta propensin a fallar. Como hemos visto en los ejemplos anteriores, el diseo se realiza de forma que el fallo en la adquisicin de un recurso se traduce en el lanzamiento de una excepcin, y como seala Milewski, "si est intentando matar dos pjaros con la misma piedra o adquirir dos recursos con el mismo constructor, puede encontrarse con problemas". Es justamente la situacin que se presentara si dos recursos deben ser adquiridos en el mismo constructor, y una vez adquirido el primero, se produce un error en la adquisicin del segundo, lo que origina el lanzamiento de una excepcin. Como en este caso la ejecucin del constructor no ha acabado, no es invocado el destructor, por lo que el primer recurso quedara sin desasignar.

4 Clases centinela Las habilidades de los constructores y destructores para realizar determinadas tareas previas a la creacin de los objetos o en el momento de su destruccin, no solo pueden ser utilizadas para la obtencin y liberacin de recursos, sino en muchas otras circunstancias, lo que ha motivado la aparicin de las denominadas clases centinela ("sentry classes"). Estas clases suponen una generalizacin de las RAII, y realizan determinadas comprobaciones o tareas auxiliares durante la construccin y destruccin de los objetos correspondientes (de ah su nombre). Los flujos ("streams") de la Librera Estndar de plantillas (STL), utilizan estas clases centinela para garantizar que antes o despus de la construccin de determinados flujos se cumplen ciertas condiciones. Ver ejemplos en 5.3.2c y 5.3.2d. Tema relacionado: punteros inteligentes (

4.12.2b1)

4.1.8 Especificadores de clase de almacenamiento


1 Sinopsis Los especificadores de clase de almacenamiento, tambin llamados especificadores de tipo, establecen el tipo (sitio) de almacenamiento de las entidades declaradas y su duracin.

C++ dispone de los siguientes: auto, register, static, extern, mutable y ( 3.2.1a) que son de tipo general. Adems existe un especificador especial, la directiva inline, que solo se utiliza con funciones ( 4.4.6b). Nota: C++Builder tiene un especificador especial: __declspec

Recordemos ( 2.2.6) que el almacenamiento puede realizarse en cuatro sitios distintos: el segmento de datos, el registro, el montn o lapila. Recordemos tambin que puede establecerse de tres formas: De forma explcita, mediante la sintaxis de la declaracin (utilizando alguno de los especificadores anteriores). De forma implcita, por la situacin de la declaracin en el cdigo. Por mezcla de ambas.

Ejemplo: A continuacin se esquematizan las diversas formas de definir las clases de almacenamiento, tanto explcita como implcitamente. #include <iostream.h> typedef char* M_CARACTER; extern int arr1[]; // L.4: const M_CARACTER ptr = "Introduzca cantidad (Intro para terminar):"; void fun1(int); // L.6: void fun2(char*, int); int main() { // ============ auto int x; // L.9: M_CARACTER arr2 = new char[5]; // L.10: cout << ptr << endl; cin >> x; if (x != 0) { fun1(x); fun2(arr2, 5); // L.15 } delete[] arr2; // L.17: return 0; } void fun1 (int n) { static total = 0; // L.22: register int i; // L.23: for (i = 1; i <= n; i++) cout << "Repetimos!" << endl; if ( n > 0) total++; } inline void fun2 (char a[], int n) { // L.27 auto int x = n; // L.28 a[x] = static_cast<char>(x+64); }

Comentario: La matriz arr1 de L.4 se declara extern, no es necesario indicar su tamao, se supone que est definida en otro mdulo del programa (otro fichero). El puntero ptr declarado en L.5 es una variable de carcter global, por tanto de carcter persistente, y posiblemente sea almacenada en elsegmento de datos; Aunque se trata de una variable, en este momento seala a una cadena literal de caracteres constantes. Esta cadena es creada en el momento de la compilacin y no es seguro que tenga un Lvalue, es decir, puede estar almacenada junto con el cdigo del programa e el segmento de cdigo ("code segment"). La variables x de L.9 y L.28 se declaran auto. Este especificador es realmente innecesario en ambos casos, porque de cualquier modo son de naturaleza automtica y sern destruidas en cuanto las respectiva funciones main y fun2 salgan de mbito. La sentencia L.10 crea la variable arr2 que es un puntero a carcter creado en la pila. Seala a un espacio de 20 bytes creado en el montn. Se trata de un objeto persistente cuyo espacio hay que desasignar explcitamente en L.17 (hay una exposicin ms detallada sobre este punto en 4.3.3). La variable total es tambin de un objeto persistente, aunque en este caso se declara explcitamente en L.22, mientras que la persistencia de la zona de memoria sealada por arr2 se declara implcitamente. El contador de bucle i de func1 se declara variable de registro (L.23); esta es una utilizacin tpica para este tipo de variables. La funcin func2 se declara inline, lo que supone el compilador sustituir su invocacin en L.15 por un trozo de cdigo equivalente.

4.1.8a auto
1 Sinopsis La palabra clave auto sirve para definir las variables como locales. Estas variables solo son conocidas en el bloque de cdigo en que se han definido (ni siquiera en el resto de la funcin a la que pertenece el bloque en cuestin). Puesto que este es el valor por defecto, la palabra reservada auto es opcional y muy raramente se usa. Por esta razn a las variables locales se las suele denominar tambin como automticas. Pueden ser definidas en cualquier parte, pero por razones de legibilidad, es costumbre declararlas al principio del bloque. Las variables locales se guardan en la pila, solo se crean cuando son necesarias (se entra en el bloque); son tambin dinmicas, por lo que se pierden sus valores al salir del bloque; si no se desea que esto ocurra hay que recurrir a declararlas estticas ( 4.1.8c). Esto es muy de tener en cuenta en las llamadas a funciones.

2 Sintaxis:

[auto] <definicin-de-la-variable> ; Ejemplo: int main() { auto int i; i = 5; return i; }

4.1.8b register
1 Sinopsis La palabra clave register es un especificador de tipo de almacenamiento ("storage class specifier"); se utiliza para indicar al compilador que ciertas variables (normalmente del tipo int y char) deben ser almacenadas en los registros del procesador en lugar de la pila ( 1.3.2). Son las denominadas variables de registro. La razn de este "capricho" hay que buscarla en la velocidad. Ocurre que los registros, aunque de poca capacidad y en nmero limitado, corresponden a zonas de almacenamiento de los procesadores cuyo acceso es excepcionalmente rpido [1]. Nota: recordemos que los especificadores de almacenamiento permitidos en C++ son: auto; register; static; extern y mutable.

2 Este especificador de almacenamiento solo puede aplicarse a variables automticas y a parmetros de funciones (por consiguiente no pueden ser globales o estticas). De hecho, cuando el especificador register se aplica a la declaracin de variables (por ejemplo, int, char, float), tambin implica duracin automtica ( 4.1.5). Ejemplo: register int i; // Error! register char c; // Error! func( register int p1, register long l2) { register int x; // Ok. } Otro ejemplo: ( 9.1).

// Ok.

Tnga en cuenta que no es posible obtener la direccin de una variable de registro. Es decir, no se puede aplicar a una de estas variables el operador de referencia ( 4.9.11b).

3 El tipo y nmero de variables de registro que pueden usarse es dependiente de la implementacin. Normalmente se utiliza para aumentar la velocidad de ejecucin en determinadas zonas de cdigo (en controles de bucle por ejemplo). El compilador puede ignorarlas completamente o ignorar las que sobrepasen el mximo permitido.

Nota: el programador C++ puede "solicitar" que una variable local entera o un puntero sean situados en un registro si hay alguno disponible; caso de no haber ninguno, la variable es tratada simplemente como un objeto automtico sin que exista ninguna advertencia de error. En realidad, C++ puede ignorar completamente la peticin de colocacin en registros, ya que esta se basa en un anlisis heurstico del compilador sobre el modo de ser utilizada la variable. El Estndar C++ advierte que la mayora de las implementaciones ingnorarn la solicitud de register para una variable si en algn punto del cdigo se obtiene su direccin.

4 Como hemos visto, los modernos compiladores actan con bastante libertad a la hora de realizar determinadas optimizaciones, como puede ser la colocacin automtica de ciertas variables en registros, pero en ocasiones conviene que el compilador no haga tal cosa por su cuenta. Esto puede conseguirse con el modificador volatile ( 4.1.9)

5 Opciones de compilacin Dependiendo de determinadas opciones en el comando de compilacin ( 1.4.3), el compilador C++Builder puede adoptar tres posturas respecto a la forma de utilizar los registros del procesador para almacenar variables: -r -rUtilizar variables de registro cuando se estime pertinente. Este es el valor por defecto. Deshabilitar totalmente el uso de variables de registro.

-rd Utilizar variables de registro solamente cuando se solicite explcitamente con la palabra clave register.

Teniendo en cuenta que los modernos compiladores C++ son bastante "inteligentes" en este sentido, la mejor opcin es adoptar el valor por defecto y dejar que sea el propio compilador el que decida que variables son idneas para ser declaradas "de registro".

Tema relacionado Paso de argumentos de funciones a registros en BC++ ( 4.4.6bW1).

4.1.8c static
1 Sinopsis La caracterstica de las variables automticas ( 4.1.8a) de perder su valor al salir del bloque en que han sido definidas, es un serio inconveniente en algunas ocasiones. Para resolver el problema se inventaron las variables estticas, un tipo especial de variable que no fuese destruida cuando la ejecucin saliese de su mbito y que conservase su valor. Este nuevo tipo se declara mediante el especificador de tipo de almacenamientostatic (una palabra-clave C++), con lo que se previene que una variable pierda su valor cuando se sale del bloque en que se ha definido. Por ejemplo, entre llamadas sucesivas a una funcin. Sin embargo, como veremos a continuacin, static puede aplicarse tanto a variables como a funciones e incluso a miembros de clases.

2 Sintaxis: static <definicin-de-dato> ; static <nombre-de-funcin> <definicin-de-funcin> ; Ejemplos: static int i; static void printnewline(void) { /* ... */ }

Dependiendo del lugar en que se aplique, las entidades static pueden ser globales (externas) o locales. El primer caso es cuando se aplica a entidades del espacio global de un fichero (pueden ser variables o funciones), el segundo cuando se aplica dentro del cuerpo de una funcin (es evidente que aqu no puede aplicarse a funciones). Desafortunadamente, el especificador static tiene dos significados distintos en C++ segn que la entidad sea global o local. En cualquier caso confiere al objeto (que suponemos es una variable) la propiedad antes sealada de ser persistente. Precisamente decimos que es un especificador de almacenamiento porque, para hacerlas persistentes, el compilador sita estas variables en una zona especial del segmento de datos ( 1.3.2). Hay que advertir que desde el punto de vista de la persistencia, la aplicacin de static a entidades globales sera superfluo, ya que "por definicin", las entidades del espacio global de una unidad de compilacin son persistentes, de forma que los apelativos "global", "externo", "persistente" o "duracin esttica" aplicados a un objeto significan lo mismo. Sin embargo ocurre que la aplicacin de static sobre objetos globales tiene adems el efecto de limitar su mbito, de forma que en esta nueva faceta static es un especificador de mbito. (ver una exposicin ms detallada en 4.1.8d)

3.1 Esttica local Una variable esttica local acta como una variable local en cuanto a visibilidad, pero como una externa en cuanto a duracin. Es decir, solo son conocidas en la funcin o bloque de cdigo en el que se declaran, pero conservan su ltimo valor entre llamadas sucesivas a la funcin. Su existencia es muy til en C++, pues permite la existencia de rutinas independientes que conserven ciertos valores entre llamadas. "Recuerdan" cual era su valor la ltima vez que se invoc la funcin. Ejemplo: int func (int n, int b = 0) static int suma = 0; if (!n ) return suma; return (suma += b); } {

3.2 Esttica global Cuando se aplica el modificador static a una variable global se indica al compilador que cree una variable global que ser conocida nicamente en el archivo en que se declara, de forma que las

funciones de otros archivos no las reconocern ni podrn alterar su valor. Dicho en otras palabras: significa que la variable tendr enlazado interno ( 1.4.4). Es inmediato deducir que esta propiedad del especificador static est fuera de contexto, ya que se refiere a la "visibilidad" en vez de a la "permanencia" del objeto (esta caracterstica es una desafortunada herencia del C). En realidad, la declaracin de static sobre una variable global debera ser simplemente redundante, ya que por definicin, las variables globales de un fichero son de duracin esttica. Consciente de esta dificultad, la ltima versin del Estndar (Julio 1998) ha desaconsejado ("Deprecate") la utilizacin destatic como especificador de mbito. En su lugar, cuando se desee limitar la visibilidad de un identificador del espacio global de un fichero al mbito de este, se aconseja utilizar un subespacio annimo ( 4.1.11b). Ejemplo: // Utilizacin antigua static int x = 10; static void func () { /* .... */ } // Forma aconsejada namespace { int x = 10; static void func () { /* .... */ } } Ver comentarios adicionales sobre esta cuestin en: ( 2.2.6 El concepto static)

Recordemos que en C++ las funciones normales (que no son miembros de clases) son globales al fichero, por lo que su declaracin como staticsupone que solo sern conocidas en el fichero en que se hayan declarado. La funcin tiene entonces enlazado interno y por tanto cualquier funcin con el mismo nombre en otro fichero es una entidad distinta. Por contra, en ausencia de este modificador, la existencia de dos funciones iguales en dos mdulos distintos origina una colisin (en el espacio general de nombres 4.1.11) con el consiguiente error de compilacin. Pero recuerde que debido a la posibilidad de sobrecarga ( 4.4.1), para que dos funciones sean iguales en C++, deben tener no solo el mismo nombre, tambin el mismo tipo y nmero de argumentos. Sin estas condiciones no existe colisin porque las funciones son consideradas distintas.

4 El especificador static en clases Las funciones, as como las propiedades y mtodos de clases tambin pueden ser declarados estticos. Tales miembros tienen propiedades especiales ( 4.11.7). Nota: como puede verse en el citado captulo 4.11.7, la utilizacin del especificador static con miembros de clases no tiene ninguna relacin con el que hemos explicado aqu (especificador de mbito local o global). Se trata de otro desafortunado caso de "sobrecarga" de una palabra-clave (o un operador), que es origen de bastante desconcierto inicial en el principiante; cierta dificultad

aadida al aprendizaje del idioma y cidas crticas por parte de sus detractores, al extremo de que alguno ha llegado a referirse al asunto como esquizofrnico ("Statics: Schizophrenia for C++ Programmers" de G. Bowden Wise).

4.1.8d extern
1 Sinopsis Desafortunadamente la palabra reservada extern tiene en C++ dos significados distintos (aunque emparentados) cada uno con su propia sintaxis, lo que lleva aparejado que en espaol la palabra externo/a tenga dos significados distintos cuando se refieren a este lenguaje: Especificador de tipo de almacenamiento [1]. Especificador de tipo de enlazado

En este captulo nos centramos principalmente en la utilizacin como especificador de almacenamiento, permitiendo que ciertas variables puedan ser globales a varias unidades de compilacin [2]. Para esto, las variables se definen en el espacio global de uno de los ficheros, generalmentemain ( 4.4.4) y despus se declaran como externas al comienzo de los dems mdulos (generalmente dentro de ficheros de cabecera).

2 Sintaxis extern <definicin-de-objeto> ; [extern] <prototipo-de-funcin> ; Ejemplos extern val; extern int funcX(int, char); int funcY(int, char);

3 Comentario Este especificador acompaa a declaraciones de objetos o funciones hechas fuera de cualquier funcin, que por tanto, son globales al fichero (no puede aplicarse a miembros de clases ni parmetros de funciones). Significa decirle al compilador algo as: "toma nota de la existencia de una entidad de tipo T con nombre N, cuya definicin est en otro mdulo". Observe que T solo puede referirse a un objeto o al prototipo de una funcin. En el primer caso puede ser un tipo simple (preconstruido en el lenguaje) o abstracto (instancia de una clase). Nota: el especificador static ( 4.1.8c) permite que una variable global sea conocida nicamente en el fichero en que se declara, de forma que las funciones de otros ficheros no las reconocern ni podrn alterar su valor (viene a ser lo contrario de extern).

Existe otro especificador export ( 4.12.1b) que, en cierto sentido, viene a ser simtrico de extern y opuesto a static. Se utiliza en el sitio de la definicin y viene a decirle al compilador: "Esta definicin debe ser conocida tambin por el resto de los mdulos".

3.1 Cuando est aplicado a una variable, significa que est siendo declarada pero no definida (no se le asigna espacio de almacenamiento), por esta razn no deben aadirse iniciadores como parte de la declaracin extern. Por ejemplo: extern int number = 100; extern int mat[3]= {1,2,3}; // incorrecto // incorrecto

Nota: C++ permite tales definiciones, pero avisa de la duplicidad si la variable tambin est definida en otro mdulo. Lo correcto en este tipo de variables es que se definan en un mdulo, y se declaren extern en todos los dems.

Dentro de una declaracin extern no pueden aadirse otros especificadores de clase de almacenamiento tales como auto o register: extern auto val1; extern register val2; extern int val3; // Error // Error // Ok.

Nota: las expresin anterior es una declaracin externa, en el sentido que simplemente informan al compilador de un nombre y tipo de objeto o funcin. Por el contrario, una definicin externa tambin define el objeto o funcin. Es decir, le asigna espacio de almacenamiento. Si un identificador que ha sido declarado como externo (por ejemplo val3), es utilizado en alguna expresin (que no sea el operando de sizeof), es imprescindible que en alguna parte del programa total (en alguno de sus mdulos) exista una definicin externa de dicho identificador.

3.2 La utilizacin del especificador extern con declaraciones de funciones es opcional. En todo caso indicara, como siempre, que la definicin de la funcin se encuentra en otro mdulo del programa. Ejemplo: extern void Factorial(int n); void Factorial(int n); // Ok. // Ok. equivalente

En realidad, y "por definicin" ( 4.4), las funciones comparten el mbito global del fichero y si no existe una definicin concordante en el mismo mdulo en que aparece la declaracin (prototipo) de una funcin, entonces el compilador supone que el especificador extern acompaa implcitamente a la declaracin, por lo que extern es opcional (y redundante) para los prototipos de funciones ( 4.4.1).

Observe que el hecho de que una funcin u otro objeto tenga visibilidad global de fichero, y por tanto pueda decirse coloquialmente que "es visible desde cualquier punto del programa", no significa que esta visibilidad sea automtica. Como veremos, el comportamiento del compilador est lleno de matices [3] que intentaremos explicar. Si el enlazador encuentra una invocacin a una funcin func (que no sea un miembro de clase) y no encuentra su definicin en ese mdulo, nobusca automticamente la definicin en el resto de mdulos o libreras. En su lugar se limita a lanzarnos un mensaje de error: Call to undefined function 'func' in function somefunc()... (aqu somefunc es la funcin desde la que se realiz la invocacin). Si en vez de la invocacin de una funcin se pretende utilizar cualquier otro objeto, por ejemplo una variable x que no ha sido previamente declarada (digamos que se intenta x++;), la respuesta del compilador sera anloga: Undefined symbol 'x' in function somefunc().... Lo anterior con independencia de que func y x estn perfectamente definidas en el espacio global de otro fichero. La razn de estos errores es que para el compilador, los identificadores func y x que hemos pretendido usar, no tienen existencia semntica en esta unidad de compilacin (dicho en otras palabras: en este contexto "no sabe de que le estamos hablando"). La forma de darle existencia semntica a ambos objetos es declararlos en cada unidad de compilacin en que daban usarse (algo as com "presentarlos" formalmente 4.1.2). Entonces el compilador sabe que x es una variable de tal tipo, y que func es una funcin de caractersticas cuales. Observe que el hecho de que el error seale que func es una funcin, es una suposicin del compilador, motivada en que estbamos aplicando el operador de invocacin de funcin al referido identificador. Una vez que el compilador sabe "quienes son" func y x, es necesario que tengan existencia fsica. Que estn "realmente" en algn sitio, lo que para el compilador supone conocer su localizacin, y que en ese sitio est realmente quien se dice que est. Dicho en otras palabras: que ambos objetos estn correctamente definidos. A partir de aqu el programa puede usarlos. Nota: si son objetos-dato, para que su utilizacin no cause problemas se exige una condicin adicional: que el contenido sea correcto (no sea basura), lo que formalmente se traduce en decir que estn correctamente iniciados.

Si los objetos son "conocidos" en cuanto a sus caractersticas generales, pero de paradero desconocido, el compilador seguir teniendo problemas y producir los correspondientes mensajes de error. La forma de darles existencia fsica es definirlos, lo que puede y debe hacerse en un solo punto del programa (existe una regla C++ en este sentido 4.1.2 segn la cual para cada entidad del programa pueden existir mltiples declaraciones, pero una sola definicin). Esto obliga a definir func y x solo en uno de los mdulos, lo que conduce al problema de qu hacer con el resto de unidades de compilacin en las que queramos usar estos objetos. Es precisamente en el aspecto "localizacin" donde intervienen los especificadores extern (y static). Como se ha sealado, anteponiendo la palabra extern a una declaracin, se est indicando al compilador que las entidades estn en otra unidad de compilacin. Es en este contexto en el que se puede utilizar la frase original de que los objetos de mbito global al fichero "son visibles desde cualquier punto del programa", porque si el compilador

encuentra esta indicacin (y solo entonces), repasa el espacio global del resto de ficheros en busca de la definicin correspondiente. Nota: si se desea que, a pesar de estar en el espacio global de un fichero, una entidad no sea incluida en esta bsqueda desde el resto de unidades de compilacin, se debe anteponer el declarador static a su definicin.

El funcionamiento descrito puede esquematizarse en los ficheros adjuntos de un mismo programa que se compilan separadamente:

// modulo1.cpp extern int x2; declarada extern int x3; declarada extern int a[2]; declarada void f1(); declarada extern void f2(); superfluo

// Ok. // Ok. // Ok. // Ok. // Ok. extern

void main() { ... x1++ // Error!! indefinida x2++ // Error!! visible x3++ // Ok. cout << a[1]; // 1082130432 !? f1(); // Ok. f2(); // Error!! indefinida }

x1 x2 no

// modulo2.cpp ... static int x2 = 31; // visibilidad local int x3 = 13; // visibilidad global float a[2] = {1, 2} // visibilidad global void f1 { x3++; } // declarada & definida ... void f() { x2++ // Ok (visible desde aqu) }

->

f2

Observe que la bsqueda en otras unidades de compilacin de la definicin correspondiente a una entidad declarada extern, no solo exige una concordancia en la etiqueta, tambin en el tipo del objeto. Es el caso de a[2] del mdulo1 (matriz de int) que no concuerda con la definicin proporcionada en el mdulo2 de a[2] (matriz de float). Observe que en este caso no se ha producido un error de compilacin porque, en ausencia de otra definicin, el compilador dispone de suficiente informacin para construir el objeto con la declaracin proporcionada (en el mdulo1), aunque no para iniciarlo adecuadamente (el espacio asignado est lleno de basura 4.1.2).

4 extern con tipos abstractos Como se indic al principio, extern tambin puede aparecer en una declaracin, precediendo al nombre de un objeto abstracto (instancia de una clase). El significado para el compilador es el de siempre: "toma nota de la existencia de una entidad de tipo C con nombre n, cuya definicin est en otro mdulo". Sin embargo, las especiales caractersticas de los objetos abstractos hacen que en estos casos, el comportamiento del compilador presente diferencias sutiles respecto a las declaraciones extern de tipos simples. extern int x1; extern C c1; // L.1 Ok. int es un tipo simple // L.2 Ok. C es un tipo abstracto (clase)

Estas diferencias pueden ser fuente de errores y desconcierto en el principiante que intentaremos aclarar aqu. Para entenderlas, hay que tener en cuenta que los compiladores C/C++ actuales realizan su anlisis exclusivamente en base a la informacin contenida en el mdulo que se compila (una vez completadas las modificaciones derivadas de las directivas de preproceso), y que la labor de resolver las dependencias entre los distintos mdulos del ejecutable se realiza en la fase de enlazado. Con esto en mente, recordar que en ausencia de ms informacin, ante una sentencia como L.1, el compilador conoce toda la informacin necesaria sobre el tipo int y en consecuencia, sobre el objeto x1, a excepcin de su valor y la direccin de su almacenamiento (el primero se obtendr en run-time, el segundo durante el enlazado). Por contra, ante la expresinL.2 no dispone de absolutamente ninguna informacin, por lo que al llegar a ella generar un error: 'C' does not name a type Declaration syntax error // GNU gcc-g++ // Borland BC++ 5.5

Para salvar el escollo podramos incluir una sentencia que indicara al compilador que C es una clase (un tipo particular): extern int x1; class C; extern C c1; // L.1 Ok. // declaracin adelantada // L.2 Ok.

Lo anterior puede bastar en algunos casos, pero si existe alguna referencia posterior al objeto c1. se producir un nuevo error: extern int x1; class C; extern C c1; // L.1 Ok. // L.2 Ok. // L.3 Ok. // L.4 Error!!

void show() { std::cout << x1 << std::endl; std::cout << c1.a << std::endl; }

Suponiendo que las definiciones correctas se encuentren en otros mdulos, la sentencia L.3 funciona sin problema; en cambio L.4 produce un error de compilacin: invalid use of undefined type `struct C' // GNU gcc-g++ 'a' is not a member of 'C', because the type is not yet defined in function foo() // Borland BC++ 5.5

El mensaje de Borland explica de forma casi perfecta la causa del error. Decimos "casi" porque en realidad, el compilador no necesita la definicin completa de la clase, solo la estrictamente necesaria para conocer los componentes del objeto c1. En este caso, conocer que a es un miembro y su tipo. En consecuencia, para sortear el nuevo escollo debemos incluir en el mdulo la informacin pertinente. Por ejemplo: extern int x1; class C { public: int a; ... void foo(); }; extern C c1; // L.1 Ok. // Definicion (parcial) de la clase

// L.2 Ok. // L.3 Ok. // L.4 Ok.

void show() { std::cout << x1 << std::endl; std::cout << c1.a << std::endl; }

Observe que hemos sustituido la declaracin adelantada (indicacin escueta de que C es una clase) por una informacin ms detallada, pero que esta no es completa. Se refiere exclusivamente a los miembros de instancia (que estn presentes en cada instancia de la clase). Las definiciones de los miembros de clase, como los mtodos, no son realmente necesarios, solo sus prototipos [4]. En el ejemplo, es el caso del mtodo foo, del que solo se incluye su declaracin. Suponemos que su definicin (off-line), se encuentra en otro mdulo junto con la definicin completa de la clase. Nota: como veremos en el ejemplo prctico que sigue, este detalle es de suma importancia. Si una clase se utiliza en distintos mdulos y se desea incluir su definicin mediante un fichero de cabecera comn, este no debe contener las definiciones de los mtodos "off-line". De lo contrario con algunos compiladores podran obtenerse errores del tipo "Multiple definition of ...".

4.1 Para ilustrar lo anterior con un ejemplo prctico, considere la siguiente aplicacin distribuida en dos mdulos: main.cpp y modulo1.cpp. En ambos se hace referencia a una misma clase C , por lo que decidimos incluir su definicin en un fichero de cabecera defines.h, que ser incluido en ambos:

// main.cpp #include <iostream> #include "defines.h" C c1(32); int x1 = 23; void show(); (prototipo) // definicion // definicion // declaracion

// defines.h class C { public: int a; C(int); // constructor offline void foo(); // mtodo off-line };

int main(int argc, char *argv[]) { show(); // Ok. en modulo1.cpp std::cout << "x1: = " << x1 << std::endl; std::cout << "c1.a = " << c1.a << std::endl; return EXIT_SUCCESS; }

C::C(int x=0) { a = x; } void C::foo() { std::cout << "Hola mundo" << std::endl; }

// modulo1.cpp #include <iostream> #include "defines.h" extern int x1; extern C c1; void show() { // definicin std::cout << "x1: = " << x1 << std::endl; std::cout << "c1.a = " << c1.a << std::endl; }

Aunque el mencionado conjunto compila sin dificultad con Borland BC++ 5.5, la versin gcc-g++ de MinGW produce sendos errores: multiple definition of `C::C(int)' multiple definition of `C::foo()' la razn es que las definiciones del constructor C() y del mtodo foo() est presente en ambos mdulos por la accin de la directiva include(recuerde la regla ODR de una sola definicin 4.1.2) y que el mencionado compilador parece no reconocer la regla de redefinicin benigna[5]. Una posible solucin sera desglosar la definicin de la clase entre dos ficheros de cabecera; el primero defines1.h contendra la informacin suficiente para las declaraciones extern de las instancias de C. El segundo, defines2.h, contendra las definiciones de todos sus mtodos offline. Es decir, separamos la declaracin de la clase de su implementacin ( 4.1.2).

// main.cpp #include <iostream> #include "defines1.h" #include "defines2.h"

// defines1.h // declaracin de la clase class C { public:

C c1(32); int x1 = 23; void show();

// definicion // definicion // declaracion

int a; C(int); // constructor offline void foo(); // mtodo off-line };

int main(int argc, char *argv[]) { show(); // Ok. en modulo1.cpp std::cout << "x1: = " << x1 << std::endl; std::cout << "c1.a = " << c1.a << std::endl; return EXIT_SUCCESS; }

// modulo1.cpp #include <iostream> #include "defines1.h" extern int x1; extern C c1; void show() { std::cout << "x1: = " << x1 << std::endl; std::cout << "c1.a = " << c1.a << std::endl; }

// defines2.h // implementacin de la clase C::C(int x=0) { a = x; } void C::foo() { std::cout << "Hola mundo" << std::endl; }

El fichero defines1.h sera incluido en todos los mdulos que utilizaran objetos externos de tipo C (es el caso de modulo1.cpp), mientras que en el mdulo que contiene la definicin, se incluiran ambos (es el caso de main.cpp). Si de todos modos se desea mantener en un solo fichero de cabecera la declaracin y la implementacin de la clase, cabe una segunda solucin, consistente en incluir un fichero fuente auxiliar, defines.cpp y dejar que el preprocesador evite la duplicidad introduciendo una nueva directiva de guarda en el fichero defines.h:

// main.cpp #include <iostream> #include "defines.h" C c1(32); int x1 = 23; void show(); // definicion // definicion // declaracion

// defines.h // declaracin & implementacin class C { public: int a; C(int); line

// constructor off-

void foo(); // mtodo off-line int main(int argc, char *argv[]) { show(); // Ok. en modulo1.cpp std::cout << "x1: = " << x1 << std::endl; std::cout << "c1.a = " << c1.a << std::endl; return EXIT_SUCCESS; } }; #ifdef ALGO_QUE_NO_SEA_CONFUNDIDO C::C(int x=0) { a = x; } void C::foo() { std::cout << "Hola mundo" << std::endl; } #endif

// modulo1.cpp #include <iostream> #include "defines.h" extern int x1; extern C c1; void show() { std::cout << "x1: = " << x1 << std::endl; std::cout << "c1.a = " << c1.a << std::endl; }

// defines.cpp #define ALGO_QUE_NO_SEA_CONFUNDIDO #include defines.h

En estos casos, el macro identificador utilizado en el define debe elegirse de forma que no exista posibilidad de colisin con cualquier otro utilizado en algn otro mdulo del proyecto; en sus libreras de apoyo, o en las que acompaan al compilador. 4.2 extern con plantillas La mecnica de funcionamiento expuesta para los tipos abstractos tambin es aplicable cuando estos son especializaciones de clases genricas (plantillas). A continuacin se muestra la sintaxis utilizada si en el ejemplo anterior C fuese una clase genrica.

// main.cpp #include <iostream> #include "defines.h" C<int> c1(32); // definicion int x1 = 23; // definicion void show(); // declaracion int main(int argc, char *argv[]) { show(); // Ok. en

// defines.h template<class T> class C { public: int a; C(int); // constructor offline void foo(); // mtodo off-line }; template<class T> C<T>::C(int x=0)

modulo1.cpp std::cout << "x1: = " << x1 << std::endl; std::cout << "c1.a = " << c1.a << std::endl; return EXIT_SUCCESS; }

{ a = x; } template<class T> void C<T>::foo() { std::cout << "Hola mundo" << std::endl; }

// modulo1.cpp #include <iostream> #include "defines.h" extern int x1; extern C<int> c1; void show() { // definicin std::cout << "x1: = " << x1 << std::endl; std::cout << "c1.a = " << c1.a << std::endl; }

La nica precaucin es utilizar la declaracin adecuada, que incluye el tipo de parmetro <int>, utilizado para la especializacin de la clase. Como dato curioso sealaremos que en este caso, el compilador gnu-g++ no produce el error de definiciones duplicadas anteriormente sealado.

5 El compilador C++Builder permite declaraciones posteriores de variables externas, tales como matrices, estructuras y uniones, de forma que se aada informacin a la contenida en la declaracin previa. Ejemplo: extern int a[]; // struct mystruct; // ... int a[3] = {1, 2, 3}; // struct mystruct { int i, j; }; // no especifica tamao (decl. externa) no especifica miembros se especifica tamao y se inicializa se aade declaracin de miembros

6 Existe otro uso de extern distinto del que hemos comentado (directiva de tipo de almacenamiento). A este respecto recordar que enlazadoexterno ( 1.4.4) y enlazado "C" tienen un sentido distinto. Esta ltima, que se indica mediante extern "c", seala al "linker" que debe realizar un tipo especial de enlazado conocido como enlazado "C" ( 1.4.4), que tiene entre otras misiones prevenir que los nombres de funciones sean planchados ( 1.4.2). Ejemplo:

extern "c" void cfunc(int); extern "C" __declspec(dllexport) double changeValue(double, bool);

4.1.8e mutable
1 Sinopsis Se trata de una palabra clave que solo puede ser aplicada a miembros de clases. Para entender su significado y utilidad, es necesario conocer el significado del especificador const cuando se aplica a miembros de clases ( 3.2.1c).

2 Sintaxis: mutable <nombre-de-propiedad>;

3 Comentario mutable es un especificador de tipo de almacenamiento cuyo propsito es informar al compilador que el miembro sealado (que debe ser una propiedad) pueden ser modificado bajo cualquier circunstancia. El Dr. Stroustrup indica al respecto [1]: "El especificador de almacenamientomutable especifica que el miembro debe ser almacenado de forma que pueda ser actualizado -incluso cuando es miembro de un objeto-const. En otras palabras, mutable significa 'nunca podr ser constante'". A pesar de lo anterior, y de que el Estndar no seala ninguna restriccin al respecto, el compilador BC++ 5.5 indica que mutable no afecta la duracin del miembro al que se aplica, y que no puede utilizarse con miembros declarados static o const (C++ Builder Languaje Guide), lo que tiene cierta lgica, ya que mutable y const aplicados a una misma propiedad implicaran atributos contradictorios. Los miembros-mutable pueden ser modificados desde mtodos-const ( 3.2.1c) a pesar que normalmente estos mtodos no pueden modificar propiedades no-const. Ejemplo: class C { mutable int x; int y; void func (int a) const { x += a; y += a; } }; Ejemplo: #include <iostream> using std::cout;

// // // // //

propiedad no-const y mutable propiedad no-const mtodo-const Ok. modifica a x!! Error!!

class Alpha { mutable int count; // int mutable mutable const int* iptr; // puntero mutable a-constante int public: int func1(int i = 0) const { // Promete no acceder miembros no-const count = i++; // count puede ser accedido iptr = &i; // Ok iptr puede ser asignado cout << "Valor: " << *iptr; return count; } }; int main(void) { // ============== Alpha a; a.func1(0); return 0; } Salida: Valor: 1

4.1.9 Modificadores auxiliares


1 Sinopsis Adems de los especificadores de tipo de almacenamiento, en las declaraciones se pueden usar ciertos modificadores auxiliares para modificar ciertos aspectos de las variables y/o las funciones. Su efecto es variado, dependiendo del modificador y del elemento sobre el que se aplique (variable o funcin). Son los que se resumen en la tabla adjunta: Modificador const volatile Se usa con: Variables Variables Descripcin Previene cambios en el objeto .

Previene de posible colocacin en el registro y alguna optimizacin. Avisa al compilador que el objeto puede sufrir cambios por dispositivos externos al programa . Fuerza paso de argumentos segn las convenciones C. Afecta al enlazado y a los nombres de enlazado ( 4.4.6a). Fuerza discriminacin maysculas/minsculas para identificadores globales y guiones bajos ("underscore")

__cdecl

Funciones

__cdecl

Variables

iniciales en C. __pascal Funciones Fuerza paso de argumentos segn las convenciones Pascal. Afecta al enlazado y a los nombres de enlazado. Fuerza discriminacin maysculas/minsculas para identificadores globales sin guiones bajos iniciales en C.

__pascal

Variables

__import

Funciones/clases Seala al compilador que funciones o clases debe importar. Funciones/clases Seala al compilador que funciones o clases debe exportar.

__export

__declspec(dllimport) Funciones/clases Seala al compilador que funciones o clases debe importar. Este es el mtodo aconsejado. Aplicado sobre una funcin hace que la funcin sea accesible desde Windows [1]. __declspec(dllexport) Funciones/clases Seala al compilador que funciones o clases debe exportar. Este es el mtodo aconsejado. Aplicado a una funcin la hace exportable desde Windows [1]. __fastcall Funciones Fuerza al enlazador a la convencin de paso de parmetros de registro. Afecta al enlazador y nombres de tiempo de enlazado. Las funciones declaradas con este modificador tienen nombres diferentes que las correspondientes sin dicho modificador; el compilador antepone un @ al nombre de la funcin y este prefijo se aplica tanto a los nombres de funciones de C "no planchados" como a los "planchados" de C++ ( 1.4.2). __stdcall Funciones Fuerza el paso de parmetros segn la convencin estndar de Windows-32.

2 Convenciones para llamadas a/desde otros lenguajes C++ permite fcilmente llamar a rutinas escritas en otros lenguajes y viceversa, pero cuando se mezclan lenguajes, deben considerarse dos cuestiones importantes: el tratamiento de Identificadores y la convencin utilizada para las llamadas a funciones, en especial lo relativo al paso de parmetros, dado que no todos los lenguajes siguen el mismo criterio respecto a estos puntos. Para facilitar esta interaccin entre lenguajes, algunos compiladores ofrecen modificadores auxiliares especficos, que modifican el tratamiento que dan a las variables globales y a las llamadas a funcin. C++ Builder es especialmente rico en posibilidades a este respecto. El tratamiento puede realizarse a nivel de entidades individuales (variables o funciones) o a nivel global, para todo el programa ( 4.4.6a).

3 const La palabra clave const se utiliza para hacer que un identificador no pueda ser modificado a lo largo del programa (sea constante) y opcionalmente, para asignarle un valor inicial. El apellido const se puede aplicar a cualquier objeto de cualquier tipo, dando lugar a un nuevo tipo con idnticas propiedades que el original pero que no puede ser cambiado despus de su inicializacin. Generalmente esto implica que la inicializacin debe hacerse en el mismo acto de declaracin de const. Ejemplo: const float pi = 3.14; const maxint = 12345; // una constante float // una constante int

Ver una completa descripcin y ejemplos de esta palabra clave:

3.2.1c const.

El atributo const puede ser reversible. C++ dispone de un operador especfico que puede poner o quitar este atributo de un objeto ( 4.9.9aoperador const_cast).

4 volatile La palabra volatile es un modificador para advertir al compilador que una variable puede ser modificada por una rutina en segundo plano (background), por una rutina de interrupcin, o por un dispositivo de entrada salida. La declaracin de un objeto con este atributo, previene al compilador de hacer ninguna asuncin sobre el valor del objeto mientras se ejecutan instrucciones en las que est involucrado, dado que supone que dicho valor puede ser cambiado en cualquier momento. Tambin previene al compilador de que no debe hacer de dicho objeto una variable de registro. Ejemplo: volatile int ticks; Ver una completa descripcin y ejemplo de esta palabra clave: 3.2.1d volatile

El atributo volatile puede ser reversible. C++ dispone de un operador especfico que puede poner o quitar este atributo de un objeto ( 4.9.9aoperador const_cast).

4.1.11 Espacios de nombres


1 Introduccin: Como se ver a continuacin, los espacios de nombres son en realidad un recurso de C++ para resolver algunos problemas existentes en el C clsico, relativos al manejo de identificadores. Es una de las razones por las que C++ es tan adecuado para la programacin de grandes sistemas.

En C, y en algunos otros lenguajes de programacin, ocurra con frecuencia que cuando el programa alcanzaba un gran tamao, empezaban a presentarse problemas de colisin de los nombres (identificadores) asignados a los objetos [3]. Tngase en cuenta que existen aplicaciones con millones de lneas de cdigo con decenas de miles de identificadores, y que adems de las libreras externas, pueden comprender centenares de mdulos mantenidos por muchos programadores independientes, cada uno de los cuales se ocupa del mantenimiento de unas cuantas miles de lneas de cdigo. Nota: para minorar estos problemas, el C clsico haba establecido implcitamente el concepto de espacio de nombres del Sistema, reservando para este los identificadores que comenzaran con guin bajo "_" ("Underscore").

Los espacios de nombres son posibles porque C++ permite dividir el espacio total de los identificadores de los objetos del programa (lo que se denomina el Espacio general de nombres ) en subespacios distintos e independientes. Para ello dispone de una palabra-clave especfica:namespace. En sntesis, el proceso consiste en declarar un espacio de nombres asignndole un identificador y delimitndolo por un bloque entre llaves. Dentro de este cuerpo pueden declararse los objetos correspondientes al mismo. Despus, los objetos pueden ser accedidos mediante diversas tcnicas; la ms directa mediante el operador ::, denominado precisamente por esto "de resolucin de mbito" ( 4.9.19). Ejemplo: namespace ALPHA { ... long double LD; float f(float y) { return y; } } namespace BETA { ... long double LD; float f(float z) { return z; } } ALPHA::LD = 1.1; ALPHA::f(1.1); BETA::LD = 1.0; BETA::f(1.1); BETA::X = 1 // // // // // Aceso a variable LD del espacio ALPHA Aceso a funcin f de ALPHA Aceso a variable LD del espacio BETA Aceso a funcin f de BETA Error: Elemento X no definido en BETA

En el trozo de cdigo anterior no existe ningn conflicto de nombres con las variables LD o con las funciones f. Por supuesto, la ltima lnea contiene un error que es avisado por el compilador, ya que intentamos acceder a un miembro no declarado en dicho espacio. Nota: a continuacin veremos que incluso est permitida la declaracin de funciones dentro de tales subespacios , y como las clases y estructuras C++ [2] son en realidad un tipo especial de subespacios con algunas propiedades aadidas (ser cerrados y con capacidad de herencia).

Observe que el mecanismo de subespacios de nombres no elimina por completo el problema

de los identificadores: Aunque marginal, todava persiste el peligro potencial de la posible colisin de nombres de subespacios en el espacio global.

2 Sinopsis En C/C++, las aplicaciones medianas y grandes se componen de varios ficheros que se compilan separadamente, en lo que se denomina unaunidad de compilacin ( 1.4.2); despus se enlazan para producir un resultado final ( 1.4). Cada unidad de compilacin constituye un espacio, el denominado espacio global de nombres o simplemente espacio global. Por esta razn, a las entidades definidas fuera de cualquier otro subespacio, bloque, o funcin, se las denomina "globales". En principio los nombres globales de distintas unidades de compilacin no deberan presentar colisin entre s, ya que pertenecen a espacios distintos. Sin embargo hemos sealado ( 1.4.4) que los identificadores tienen una caracterstica, denominada atributo de enlazado, segn la cual, los que tienen el denominado enlazado externo, pueden ser visibles desde las dems unidades de compilacin (por defecto los nombres de funciones tienen este tipo de enlazado). Aunque exceptuando los nombres de funciones, esta "visibilidad" no es automtica para el resto de identificadores ( 4.1.8d), la organizacin tradicional de ficheros motivaba que a la hora de enlazar todos los mdulos, fuese frecuente el descubrimiento de definiciones de nombres duplicados (sobre todo de funciones). Como solucin para los frecuentes problemas de colisin, se aadi a C++ el sistema de espacio de nombres. Este mecanismo permite compartimentar una aplicacin en varios espacios, cada uno de los cuales puede definir y operar dentro de su propio mbito. El desarrollador es libre de introducir los identificadores que necesite en su subespacio sin preocuparse si tales nombres estn siendo usados por alguien ms. El mbito del subespacio es conocido por la aplicacin a travs de un nico identificador. En este sentido, el espacio global o "raz" [1] es el primer espacio; por esta razn, casi siempre nos referimos a los espacios de nombres como subespacios (del principal).

3 Espacios de nombres estndar Hay que tener en cuenta que, incluso sin utilizar explcitamente el recurso de los subespacios, dentro de un mismo mbito C++ distingue automticamente cuatro espacios de nombres distintos (que podemos considerar estndar): [1] El espacio de nombres de etiquetas ( 4.10.1). [2] El espacio de nombres de miembros de clases ( 4.11). [3] El espacio de nombres de miembros de estructuras ( 4.5). [4] El espacio de nombres de miembros de uniones ( 4.7). [5] El espacio de nombres sencillo (que engloba las variables, las funciones, nombres typedef y los enumeradores).

Esta distincin tiene diversas consideraciones prcticas inmediatas. Por ejemplo, miembros de diferentes estructuras pueden tener los mismos nombres sin conflicto, ya que habitan en subespacios distintos. Por la misma causa, tambin pueden repetir nombre con miembros del espacio sencillo, por ejemplo con variables o funciones. Ejemplo:

int x = 1, y = 2, z = 3; // [5] struct E { int x; int y; int z; } e1 = {1, 2, 3}; class C { int x; int y; int z; } c1 = {1, 2, 3}; union U { int x; char y; float z; } u1 = { x }; func(1, 2, 3); ... int func(int x, int y, int z) { if (x == 0) goto x; return (x + y + z); x: // [1] retrun (1 + y + z); }

// [3] // [2] // [4]

En el trozo de cdigo anterior no existe ningn conflicto de nombres con las variables x, y, z del espacio sencillo, de la funcin func ni de las estructura E; la clase C, o la unin U, ya que por principio todos ellos son espacios distintos para el compilador. Observe que la unin u1 se inicializa con el valor del int x del espacio sencillo (esto lo deduce el compilador por el contexto). Observe tambin que dentro de func no existe conflicto entre la variable x y la etiqueta x, ya que ambas pertenecen por principio a espacios de nombres distintos.

4 El subespacio como mbito y clasificacin Como hemos sealado, el mecanismo de subespacios de nombres tienen un sentido de mbito, que permite resolver el problema de colisin de nombres de variables globales, pero adems, este mismo sentido permite establecer principios de clasificacin (de cualquier tipo) dentro de los elementos de un programa, de formar que puede aportar claridad al cdigo y cierto nivel de seguridad adicional, al permitir ciertas comprobaciones por parte del compilador como veremos ms adelante ( 4.1.11c). Debemos recordar que en programacin es importante mantener un principio de clasificacin claro y coherente. Tanto para el planteamiento terico-conceptual de una aplicacin (especialmente si es grande), como para la subdivisin concreta de la misma en sus diversas partes; y que en este sentido, el mecanismo de subespacios de nombres es un buen candidato para constituir un primer nivel de clasificacin. Este ha sido precisamente el criterio seguido por los propios diseadores del lenguaje, y la razn por la que las Libreras Estndar de C++ se han desarrollado utilizando un subespacio especfico (para usar sus funciones hay que tener en cuenta este detalle 4.1.11c2). En realidad, cualquier programa C++ de cierto tamao bien estructurado, debera contar con una adecuada divisin en subespacios de todos sus elementos ( ); esta divisin constituira un primer nivel de clasificacin conceptual entre sus partes, a continuacin como subniveles se tendran las jerarquas de clases y las propias clases. A excepcin de la funcin main(), que siempre debe pertenecer al subespacio raz para que pueda ser manejada por el mdulo de inicio.

5 Subespacios, funciones y clases El mecanismo de subespacios [4] permite incluso la definicin de funciones fuera del espacio global de nombres; algo que hasta entonces no estaba permitido ( 4.1.3). As mismo, veremos

que las clases C++ comparten algunas de las propiedades de estos subespacios, tanto en aspectos del acceso como en la posibilidad de incluir funciones. Para ilustrar la idea anterior, considere que el siguiente ejemplo que compila sin dificultad en C++: #include <iostream> using namespace std; namespace NE { void fun() { cout << "Soy func del espacio NE" << endl; } } class CLASE { public: static void fun(); class CL { public: static void fun(); }; }; void CLASE::fun() { cout << "Soy func de clase CLASE" << endl; CL::fun(); } void CLASE::CL::fun() { cout << "Soy func de clase CL en clase CLASE" << endl; } void fun() { cout << "Soy func del espacio Global" << endl; } int main() { fun(); NE::fun(); CLASE::fun(); return 0; } Salida: Soy Soy Soy Soy func func func func del espacio Global del espacio NE de clase CLASE de clase CL en clase CLASE // =================

4.1.11a Declarar un subespacio


1 Sinopsis Utilizar subespacios de nombres en C++ es muy sencillo y requiere solo dos pasos: El primero, declarar biunvocamente un subespacio con la palabra clave namespace; el segundo, acceder a los elementos del mismo ( 4.1.11c).

2 Declarar un subespacio En cuanto a la primera parte, existen tres formas de definir un espacio de nombres con la palabra namespace (el cuerpo del subespacio es opcional).

2.1 Definicin inicial: namespace identificador { cuerpo-de-subespacio } 2.2 Ampliacin de un subespacio existente ( 4.1.11d):

namespace nombre-de-subespacio-existente { cuerpo-de-subespacio } 2.3 Definicin de espacio de nombres annimo ( namespace { cuerpo-de-subespacio } 4.1.11b):

3 Sintaxis: namespace <nombre-de-subespacio> { ... cuerpo del subespacio ...} namespace { ... cuerpo del subespacio ...} nombre-de-subespacio. Un identificador (generalmente en MAYSCULAS) que no est siendo usado previamente por un identificador de mbito global. Ejemplo: namespace ALPHA { // ALPHA es el identificador del subespacio /* declaraciones */ long double LD; float f(float y) { return y; } } 4 Comentario El boque delimitado por { } comprende el subespacio. El identificador del subespacio debe ser conocido en todas las unidades de compilacin desde donde se deba acceder a alguno de sus elementos. Si se omite el identificador se tiene un subespacio annimo ( 4.1.11b).

Los subespacios son de mbito global, por lo que deben declararse fuera de cualquier funcin o clase, aunque a su vez pueden ser divididos en subespacios, es decir, son permitidos subespacios anidados dentro de otros. Ejemplo: namespace UNO { int x; char c; namespace UNO_A { int x; char c; namespace UNO_A1 { inxt x; char c; } } namespace UNO_B { int x; char c; // Ok subespacio exterior // Ok. anidado primer nivel // Ok. anidado segundo nivel

// Ok. anidado primer nivel

} long lo; } ... void main (void) { // =================== int x = 1 namespace TRES { // ERROR: subespacio en funcin int x ; } ... UNO::x = 1; UNO::UNO_A::x = 2; UNO::UNO_A::UNO_A1::x = 3; ... } Observe que se utiliza el operador de resolucin de acceso :: ( elementos de subespacios, incluso anidados. 4.9.19) para acceder a

5 Alias de subespacios Se puede utilizar un nombre alternativo (alias) para referirse a un subespacio de nombres. Un alias es til para referirse a un identificador de subespacio de nomenclatura farragosa. Ejemplo: namespace COMUNICACIONES { /* cuerpo del subespacio */ namespace COMUNICACIONES_TCPIP { // subespacio anidado /* cuerpo del subespacio anidado */ } } ... namespace PC = COMUNICACIONES; // alias de subespacio // alias de subespacio anidado namespace PCTCPIP = COMUNICACIONES::COMUNICACIONES_TCPIP;

Los alias deben usarse con moderacin, dado que su uso indiscriminado puede dar lugar a confusin. No obstante, lo mismo que en el caso detypedef ( 3.2.1a), los alias de subespacios tambin pueden ser tiles para facilitar el cambio de una librera por otra, localizando todas las referencias concretas en un solo punto (donde se declara el alias), de forma que los posibles cambios posteriores solo requieren cambiar una lnea de cdigo [1]. Por ejemplo: supongamos que en un programa utilizamos la librera de comunicaciones lib-v11 que tiene declarado su propio subespacio como LV11 por lo que utilizamos en nuestro cdigo expresiones tales como: LV11::tcpip = 12; LV11::connect(tcpip); ... Si tuvisemos que cambiar la librera por una versin ms moderna, digamos lib-v12 que cuyo subespacio se ha definido como LV12, tendramos que renombrar todas las lneas al nuevo tenor:

LV12::tcpip = 12; LV12::connect(tcpip); ... En estas circunstancias es ms fcil hacer desde el principio: namespace LV11 = COM COM::tcpip = 12; COM::connect(tcpip); ... El cambio a la nueva versin solo habra exigido cambiar una lnea de cdigo: namespace LV12 = COM COM::tcpip = 12; COM::connect(tcpip); ... Observe que el alias de un subespacio realiza una funcin parecida a la de typedef, aunque de uso ms especfico.

Tema relacionado: El mbito de nombres y la sobrecarga de funciones ( 4.4.1a)

4.1.11b Subespacio annimo

1 La gramtica de C++ permite definir subespacios annimos. Para esto se utiliza la palabra namespace sin ningn identificador antes del corchete de apertura: namespace { // subespacio annimo ... // Declaraciones }

2 Todos los subespacios annimos de mbito global (no anidados) de la misma unidad de compilacin comparten el mismo espacio de nombres. Es decir, adems de los posibles subespacios nominados existe otro nico, de los sin nombre. Ejemplo: namespace { // subespacio annimo int x; } namespace ALFA { // subespacio nominado int x; // esta x es distinta de la anterior } namespace { // subespacio annimo (ampliacin del primero) int y; int x; // Error: declaracin duplicada }

3 Todas las variables declaradas en el subespacio annimo son conocidas en todo el mbito del fichero, a no ser que otra definicin posterior las oculte [1]. Sin embargo, los subespacios annimos de las diferentes unidades de compilacin son independientes, y no existe forma de acceder a un elemento del subespacio annimo de un fichero desde otro fichero [2]. Considere los resultados del siguiente ejemplo: #include <iostream> using namespace std; namespace { int x = 1; } namespace ALFA { int y = 10 } // subespacio annimo // subespacio nominado

void main (void) { // ==================== { int x = 2; cout << "X = " << x << << endl; // Ok. } cout << "X = " << x << endl; // Ok. cout << "Y = " << y << endl; // M.6: Error!! cout << "Y = " << ALFA::y << endl; // M.7: Ok. }

En M.7 se obtiene un error: Undefined symbol 'y' in function main, ya que la variable y del subespacio ALFA no es conocida desde el exterior a no ser que se incluya un especificador explcito, como es el caso de la lnea M.7. En cambio, la variable x del subespacio annimo es automticamente conocida en el fichero sin necesidad de un especificador explcito. Una vez eliminada la sentencia errnea, la salida es: X = 2 X = 1 Y = 10

La compartimentacin antes aludida entre los miembros de subespacios annimos de diferentes ficheros, permite hacer declaraciones estticas sin utilizar el especificador static ( 4.1.8c), es decir, variables o funciones de mbito global que solo sean conocidas en el fichero en que son declaradas. Ejemplo: supongamos un programa compuesto por dos ficheros fuente: Fuente1.C y Fuente2.C En el fichero Fuente1.C #include <iostream> extern void func(void); namespace { float pi = 3.14; }

// func est definida en Fuente2.C // Subespacio annimo // pi es conocido solo en este fichero

int main() { float pi = 0.1; // este pi es distinto del anterior std::cout << "pi = " << pi << std::endl; func(); // llamada a func (definida en algn sitio) return 0; } En el fichero Fuente2.C #include <iostream> namespace { // Subespacio annimo float pi = 10.01; // pi es conocido solo en este fichero void func(void) { // func es conocida solo en este fichero std::cout << "Versin local de func(): pi = " << pi; } } void func(void) { // puede ser conocida en otros ficheros std::cout << "Versin global de func(): pi = " << pi; } Salida del programa: pi = 0.1 Versin global de func(): pi = 10.01

4.1.11c Acceso a elementos de un subespacio


1 Sinopsis: Existen tres formas de acceder a los elementos de un subespacio: Una declaracin explcita . La declaracin using . La directiva using . Bsqueda en el espacio de los argumentos

Las dos primeras se utilizan para accesos de elementos individuales. La tercera permite un cmodo acceso a todos los elementos de un subespacio. La ltima es un caso especial, y se refiere a un mecanismo de acceso automtico implcito en todas las funcines C++. Recurdese que no importa que se aadan subespacios al mbito local. Los identificadores de mbito global (que es otro subespacio -raz-) son todava accesibles utilizando el operador de acceso a mbito :: ( 4.9.19). Ver ejemplo .

2 Acceso explcito

Se puede referenciar o acceder a cualquier miembro de un subespacio utilizando el nombre del mismo junto con el operador de resolucin de mbito :: seguido del nombre del miembro. Por ejemplo, para acceder a un miembro especfico del subespacio ALPHA: namespace ALPHA { ... long double LD; float f(float y); class C { public: int x; } } ALPHA::LD = 1.0; // ALPHA::f(1.1); // float ALPHA::f(float y) // { ... } ALPHA::C::x = 1 // Aceso a variable LD Invocar funcin f definicin de funcin f de ALPHA

Acceso a miembro x de C en ALPHA

Esta forma de acceso explcito puede y debe utilizarse siempre para evitar ambigedades; no importa de que subespacio se trate (excepto annimos naturalmente). Puede utilizarse el operador :: para acceder a identificadores de cualquier subespacio (incluso los que ya se han declarado en el mbito local). As pues, cualquier identificador de una aplicacin es accesible si se utiliza un direccionamiento adecuado.

2.1 Identificador cualificado Las expresiones de elementos en las que se indica el subespacio al que pertenecen mediante el especificador de mbito ::, se denominan nombres, etiquetas o identificadores cualificados. Ejemplo: class CL { ... UNO::func() funcC(){...} ... } ALPHA::LD ALPHA::fun(...) void func(ALPHA::char c);

// miembro cualificado

// identificador cualificado // funcin cualificada // argumento cualificado

Esta tcnica de acceso y referencia es siempre recomendable para elementos que hayan sido previamente declarados en un subespacio, pues permite al compilador realizar la comprobacin de que se quiere acceder a un miembro del subespacio, y no una nueva declaracin. Por ejemplo, si escribimos:

void ALPHA::ff() { ... }

// definicin de ff

el compilador supone que estas sentencias son la definicin de la funcin ff que ha sido previamente declarada en ALPHA, y nos avisar del error si ff no ha sido declarada antes en dicho subespacio. En cambio, si hacemos: namespace ALPHA { void ff() { ... } } El compilador puede creer que deseamos ampliar el subespacio, y no puede avisar del error en caso de no existir un prototipo de ff previamente declarado en ALPHA. Esta tcnica la vamos a utilizar repetidamente al definir funciones miembro de clases ( que son un tipo especial de subespacios. Por ejemplo: class ALPHA { void ff(); ... } ... void ALPHA::ff() { ... } 4.11.2a),

// declaracin de ff

// definicin de ff

2.2 Acceso explcito al mbito global El mbito global de un fichero es otro subespacio, con la peculiaridad de que generalmente no es necesario referirse a l con un identificador especial. Por ejemplo, si en el cuerpo de una funcin declaramos x = 3; y no existe una variable x en el mbito de la funcin, automticamente se busca si existe tal identificador en el en el mbito global. No obstante, existen casos en que es necesario referirse explcitamente a una variable del mbito global. Esto puede hacerse con el operador :: de resolucin de mbito sin ningn prefijo. Ejemplo: #include <iostream> namespace ALPHA { int x = 1; } int x = 2; int suma(int); void main() { int x = 3; std::cout << std::cout << std::cout << std::cout <<

// Variable global // Prototipo de funcin suma // Variable local << x << std::endl; << ::x << std::endl; << ALPHA::x << std::endl; << suma(x) << std::endl;

"X "X "X "X

= = = =

" " " "

} int suma(int x) { // Definicin de funcin suma return (x + ALPHA::x + ::x); } Salida: X X X X = = = = 3 2 1 6

Es buena tcnica de programacin C++ no abusar de las variables globales. En caso necesario, englobarlas en un subespacio y referenciarlas a travs de su identificador correspondiente.

3 Declaracin using Los miembros de un subespacio pueden ser accedidos individualmente con la declaracin using (palabra reservada). La sintaxis es: using :: identificador; Ejemplo: using ALPHA::x;

Cuando se utiliza esta frmula, el identificador declarado es aadido al subespacio local, lo que quiere decir que, una vez establecida la declaracin, en adelante el identificador declarado es identificado correctamente, como si perteneciera al subespacio local. Este recurso es muy til cuando un identificador aparece muchas veces en un contexto, pues se evita tener que referenciarlo repetidamente mediante un identificador cualificado .

El ejemplo que sigue muestra el uso de la declaracin using con una funcin declarada en dos subespacios. #include <iostream> namespace UNO { // Subespacio float f(float y) { return y; } void g(void) { std::cout << "Versin UNO" << std::endl; } } namespace DOS { // Subespacio void g(void) { std::cout << "Versin DOS" << std::endl; } } void main(void) { using UNO::f; // declaracin titulada using DOS::g; // declaracin titulada // en adelante no son necesarios los especificadores para f ni para g float x = 0; // x pertenece al subespacio local

x = f(2.1); g(); }

// se refiere a f del subespacio UNO // se refiere a g del subespacio DOS

La declaracin using tambin puede ser utilizada para el acceso a miembros de subespacios en objetos de clases derivadas ( 4.11.2b2).

4 Directiva using C++ proporciona un medio cmodo de acceder a la totalidad del subespacio mediante la directiva using (palabra reservada). La sintaxis es como sigue: using namespace <identificador-de-subespacio>; Ejemplo: using namespace ALPHA ; using namespace COMUNICACIONES::COMUNICACIONES_TCPIP;

La directiva using establece que a partir del punto en que se utiliza, todos los identificadores del subespacio indicado son visibles. El subespacio debe ser declarado antes de utilizar la directiva. Ejemplo: using namespace BETA; ... namespace BETA { ... } // Error BETA no declarado todava

El efecto de esta directiva es como si los objetos del subespacio quedaran incluidos en el espacio local [1]. Aunque como veremos a continuacin ( ), los elementos homnimos del subespacio local siguen existiendo y tienen precedencia sobre los intrusos. Ejemplo: #include <iostream> using namespace std; namespace ALPHA { void f(int i) { cout << "Entero: " << i << endl; } }; void f(char c) { cout << "Caracter: " << c << endl; } int main() { // =============== f('a'); // M.1 f(45); // M.2 using namespace ALPHA; // M.3 f('a'); // M.4 f(45); // M.5 // L.5 // L.7

return 0; } Salida: Caracter: a Caracter: Caracter: a Entero: 45 Comentario: M.1 y M.2 son responsables de las dos primeras salidas. En este momento solo es visible la definicin de f proporcionada por L.7, de forma que en la invocacin de M.2, el compilador debe realizar un modelado para adecuar el tipo de argumento actual ( int 45) con el esperado (char). El resultado de la segunda salida es el carcter ASCII "-", correspondiente a dicho valor numrico ( 2.2.1a). La directiva de la sentencia M.3 hace que a partir de este punto, sea visible el contenido del subespacio. As, en la invocacin M.5 el compilador encuentra que la versin de f en L.5, concuerda mejor con el argumento actual que la de L.7, as que esta es la definicin utilizada. El resultado corresponde a la ltima salida.

4.1 La directiva using es transitiva, lo que significa que si se aplica a un subespacio que a su vez contiene directivas using, tambin se tienen acceso a todos esos subespacios. Por ejemplo: namespace CERO { using namespace UNO; using namespace DOS; ... } ... using namespace CERO; ...

// UNO ha sido definido previamente // DOS tambin definido previamente

// a partir de aqu son visibles CERO, UNO y DOS

Dentro de una clase no puede utilizarse la directiva using (Accesos a subespacios en clases 4.1.11c1).

4.2 La directiva using no aade ningn identificador al mbito local, por tanto, un identificador definido en ms de un subespacio no debe ser un problema hasta que pretenda usarlo. Las declaraciones de mbito local tienen precedencia, ocultando todas las dems declaraciones homnimas. Ejemplo: #include <iostream> namespace F { float x = 9; } namespace G {

using namespace F; float y = 2.0; namespace ANIDADO_G { float z = 10.01; } } int main() { using namespace G; using namespace float x = std::cout std::cout std::cout return 0; } Salida: x = 19.1 y = 2 z = 10.01 Nota: el efecto de la directiva using es desde el punto de la declaracin hasta el final de la unidad de compilacin (lo mismo que la directiva define 4.9.10b), pero con el inconveniente de que el efecto no es reversible (no puede eliminarse). Por esta razn, no es conveniente colocar la directiva using en ficheros de cabecera, ya que de hacerlo as, se corre el riesgo de eliminar inadvertidamente la proteccin que representa el mecanismo de espacios de nombres. Adems de lo anterior, son muchos los autores que aconsejan no usarla o usarla solo en ocasiones muy puntuales. La razn es que con frecuencia, originan problemas de colisin de nombres ("name clashes"), sobre todo para el principiante. Por ejemplo, si al principio de nuestro cdigo establecemos (como suele ser muy frecuente) la sentencia using namespace std; puede ocurrir que con un poco de suerte utilicemos un identificador que haya sido declarado en la citada librera, lo que originara errores de compilacin aparentemente inexplicables. Recientemente, en un foro de Internet alguien solicitaba ayuda porque obtena una serie de errores de compilacin en un programa cuyas primeras lneas eran las siguientes: #include <iostream> using namespace std; template <class T> class pair { T a, b; public: pair (T first, T second) {a=first; b=second;} T getmax (); }; ... 19.1; << "x << "y << "z /* esta directiva proporciona acceso a todo lo declarado en "G" (y por tanto en "F")*/ G::ANIDADO_G; /* esta directiva solo proporciona acceso al subespacio anidado "INNER_G" */ // Este especificador local tiene precedencia = " << x << std::endl; = " << y << std::endl; = " << z << std::endl;

El resto del cdigo era correcto; el nico problema era que el identificador pair ya estaba definido en la librera <iostream>.

5 Bsqueda en el espacio de los argumentos Nos referimos a un mecanismo automtico de bsqueda que existe implcitamente en todas las funciones C++. Ocurre con frecuencia, que las funciones estn declaradas en subespacios distintos de los objetos que se le pasan como argumentos; en cuyo caso es necesario especificar los subespacios correspondientes al declarar la lista de argumentos formales de la funcin. Por ejemplo: void fun1 (ALPHA::CA a, BETA::CB b); En estas circunstancias, cuando en el cuerpo de la funcin fun1, aparece una invocacin a otra funcin fun2 no reconocida en el subespacio actual (el de fun1), se realiza una bsqueda automtica de dicho identificador en los subespacios de sus argumentos (en nuestro ejemplo se buscara en ALPHA y BETA).

Recordando que los operadores son en realidad funciones "disfrazadas" en las que los argumentos son los operandos ( 4.9.18), lo anterior resulta tambin vlido para los operadores; de forma que tambin se realiza una bsqueda de la definicin correspondiente a un operador en base al tipo de sus operandos. Esta regla resulta muy til, ya que simplifica la escritura de cdigo; en especial en los casos de operadores y argumentos de plantillas [3]. Ejemplo: supongamos dos clases en las que hemos definido versiones sobrecargasas de los operadores suma (+) y asignacin (=) para los miembros de la clase: class Vector { ... }; class Complex { ... }; ... void func () { Vector v1, v2, v3; Complex c1, c2; ... v3 = v1 + v2; c3 = c1 + c2; } Las dos ltimas expresiones son posibles porque el compilador busca la definicin de los operadores + y = en los subespacios Vector y Complexde sus argumentos. Nota: la bsqueda de nombres de funciones y operadores basada en los argumentos ("argument based lookup") es conocida tambin por el acrnimo ADL ("argument-dependent lookup") y de Koenig [2] ("Koenig-lookup"), porque fue este miembro del comit el que propuso este esquema de bsqueda. Ejemplo:

#include <iostream.h> namespace ALPHA { class CA { public: int x; }; class CB { public: int y; }; CA Ca1; // instancia de CA (en ALPHA) CB Cb1; // instancia de CB (en ALPHA) int fun1(CA& c) { return c.x; } int fun2() { return Cb1.y; } } void funG (ALPHA::CA ca); // prototipo (global): int main (void) { ALPHA::Ca1.x = 10; ALPHA::Cb1.y = 5; funG(ALPHA::Ca1); } void funG int x = int y = int y = necesario cout << cout << } (ALPHA::CA ca) { fun1(ca); fun2(); ALPHA::fun2(); "x = " << x << endl; "y = " << y << endl; // ========================

// // // //

definicin Ok: ALPHA::fun1 Error: fun2 fuera de mbito Ok especificador de subespacio

Salida (una vez eliminada la sentencia errnea): x = 10 x = 5

Temas relacionados: Resolucin de sobrecarga ( 4.4.1a) Bsqueda de nombres ( Name-lookup).

4.1.11c1 Acceso a subespacios en clases

1 Lo mismo que ocurre con las funciones ( 4.1.11a), dentro de una clase no puede declararse un espacio de nombres. Tampoco puede utilizarse la directiva using: class A { int x; namespace UNO { ... } using namespace DOS; }

// Error: no permitido // Error: no permitido

2 Sin embargo, s est permitida la declaracin using (con ciertas limitaciones) y puede resultar

muy til; de hecho, las clases son autnticossubespacios de nombres [1], a los que nos podemos referir como tales utilizando la declaracin using con las siguientes condiciones: La declaracin using en la definicin de una clase debe referirse siempre a elementos de la clase base [*]. La declaracin using no puede ser utilizada para acceder a informacin inaccesible [**].

Ejemplo: class A { char ch; public: char func() { return ch; } ... }; class B { char ch; public: char func(char c) { return c; } ... }; class C : public A { int x; public: int func(int i) {return i+x; } using B::func; // Error: B no es ancestro de C * using A::ch; // Error: ch es privado en A ** using A::func() // Ok: sobrecarga C::func };

Obsrvese como en las declaraciones using precedentes, se utilizan los nombres de las clases como subespacios. La cuestin de los subespacios de nombres en el interior de clases derivadas se contempla tambin en los captulos dedicados a la herencia simple ( 4.11.2b) y mltiple ( 4.11.2c). Ejemplo 2: Nota: el comportamiento exacto de los ejemplos de este captulo depende del compilador. El que se muestra corresponde a BC++ 5.5 y MS VC++ 6.0 para Win 32. En cambio GNU Cpp 2.95.2 produce errores: cannot adjust access to 'void A::func(char)' in 'class B' because of the local method 'void B::fun(char*)' with same name. Nuestra opinin es que algunas caractersticas "++" de este compilador, derivado del C, no estn suficientemente desarrolladas en estas versiones tan tempranas. En el programa que sigue se utiliza un subespacio de nombre dentro de una clase [ 2], lo que permite sobrecargar una funcin miembro: #include <iostream> using namespace std; class A { // Superclase public: void func(char ch) { cout << "carcter = " << ch << endl; }

}; class B : public A { // Clase derivada public: void func(char* str) { cout << "cadena = " << str << endl; } using A::func; // declaracin using, sobrecarga B::func() }; int main() { B b; b.func('c'); b.func("c"); return 0; } Salida: Carcter = c Cadena = c // // // // ========================= instancia B M.2: Invoca A::func() M.3: Invoca B::func()

Observe como el efecto de la declaracin using en la definicin de la subclase, hace que sea visible la versin heredada de func, de forma que son visibles simultneamente ambas versiones (privativa y heredada 4.11.2b), lo que tiene un efecto anlogo a sobrecargar la funcin privativa. Esto hace posible que en M.2 y M.3 se invoquen las versiones correspondientes segn el tipo de argumento utilizado. Ejemplo-3 Se trata de una variacin del ejemplo anterior para demostrar que, como hemos sealado anteriormente ( 4.1.11c), aunque sean visibles dos miembros dentro del mismo objeto con identificadores iguales, los nombres del subespacio local tienen precedencia sobre los heredados (se dice que los miembros privativos dominan sobre los heredados 4.11.2c). #include <iostream> using namespace std; class B { // superclase public: void fun(int x) { cout << "Superclase: " << x << endl; } }; class D : public B { // subclase public: void fun (int x) { cout << "Subclase: " << x << endl; } using B::fun; // L.12 }; int main() { D d; d.fun(10); d.B::fun(20); return 0; } // ============== // M.2 // M.3

Salida: Subclase: 10 Superclase: 20 Comentario: Como se demostr en el ejemplo anterior, gracias a la declaracin using de L.12 en el espacio de la subclase D, son visibles ahora dos versiones (privativa y heredada) del mtodo fun. Es importante sealar que, a pesar que las "firmas" de ambas funciones son idnticas, no se produce ningn error de compilacin por declaracin mltiple en D. La primera salida demuestra que la invocacin directa de fun, utiliza la versin privativa (que podramos considerar est en el espacio "local" deD). En M.3 comprobamos que la utilizacin de la versin heredada (que podramos considerar existe en un "subespacio" del anterior), exige el uso de un identificador cualificado para la funcin ( 4.1.11c). Nota: esta imagen mental de "subespacios", que contienen los miembros heredados dentro del espacio definido por la clase, se refiere a una compartimentacin realizada automticamente por el compilador que no est permitida al programador, y que por lo dems, sigue las reglas de los subespacios estndar C++. En efecto: a la luz de los expuesto en este ejemplo, podra argumentarse que la ausencia de error de compilacin, a pesar que ambas funciones tienen la misma firma, es que pertenecen a subespacios distintos, y el argumento sera correcto. A su vez, en el ejemplo anterior, vimos que la utilizacin de los mecanismos de sobrecarga entre estos "subespacios" exige la utilizacin de la declaracin using, ya que de otro modo, C++ no permite la sobrecarga entre espacios de nombres distintos ( 4.4.1a).

4.1.11c2 Acceso a miembros de la Librera Estndar

1 Las funciones de la Librera Estndar de C++ ( 5) se han desarrollado utilizando un subespacio especfico denominado std, as que para utilizar estas funciones, adems de declarar las directivas correspondientes para incluir sus ficheros de cabecera, hay que especificar tambin los espacios de nombres adecuados [1]. Esto se puede hacer de dos formas: mediante declaraciones explcitas o utilizando la directiva using.

1.1 Ejemplo de declaracin explcita: #include <iostream> void main () { // ================ std::cout << "Hola mundo" << std::endl; }

1.2 Ejemplo de directiva using: #include <iostream> using namespace std;

void main () { // ================ cout << "Hola mundo" << endl; }

Como la inclusin del mecanismo de espacios de nombres es una de las adopciones ms tardas de C++, los primitivos compiladores no utilizaban este recurso, es decir, las Libreras Estndar estaban construidas al estilo del viejo C; son los clsicos ficheros de cabecera .h (las que se adaptan al nuevo estndar no utilizan esta terminacin). No obstante, por razn de compatibilidad con el cdigo existente, los compiladores actuales incluyen la posibilidad de utilizar "includes" de formato antiguo (ficheros de cabecera .h). En estos casos, una declaracin como: #include <iostream.h> Equivale a: #include <iostream> using namespace std;

Es decir: en un compilador que se adhiera al estndar actual, un cdigo como el que sigue produce un error de enlazado (cout y endl son desconocidos a pesar de estar definidos en la librera <iostream>). #include <iostream> void main () { cout << "Hola mundo" << endl; } mientras que este otro se compila sin problema. #include <iostream.h> void main () { cout << "Hola mundo" << endl; } // Ok. // Error:

No obstante, tenga muy presente que el fichero <iostream.h>, aunque sea soportado por algunos compiladores, no es estndar, por lo que puede ocurrir que esta ltima forma no sea soportada en alguna plataforma concreta, y sea necesario acudir a alguna de las dos formas cannicas 1.1 o 1.2 anteriores . Lo mismo ocurre con algunas otras libreras .h, por ejemplo <typeinfo.h>, de forma que siempre que sea posible, lo mejor es utilizar las Libreras Estndar, con lo que a este respecto no tendremos problemas de portabilidad con nuestras aplicaciones.

4.1.11d Ampliar un subespacio

1 Los espacios de nombres pueden ser discontinuos, y estn abiertos a desarrollos adicionales. Si se redeclara un subespacio, el efecto es justamente ampliar el existente aadindole las nuevas declaraciones. Ejemplo:

namespace ALPHA { long double LD; float f(float y) { return y; } } ... namespace ALPHA { int X; char g(int i) { ... } } // ahora ALPHA incluye las variables LD, X y las funciones f y g

2 Un alias no puede ser usado para ampliar un subespacio. Ejemplo. namespace ALPHA { ... long double LD; float f(float y) { return y; } } ... namespace AL = ALPHA; ... namespace AL { // Error: declaracin mltiple de AL int x; }

3 No obstante lo anterior, advertir que cualquier extensin de un subespacio que se haga despus de una declaracin using ( 4.1.11c) puede ser ignorada a partir del punto de la declaracin. Por consiguiente, las versiones sobrecargadas de ciertas funciones deben ser incluidas en el subespacio antes de declarar que est "en uso". Dicho en otras palabras: una vez que el subespacio est en uso, las ampliaciones pueden ser ignoradas. Considere el siguiente ejemplo: #include <iostream> struct S { }; class C { }; namesace ALPHA { // declaracin inicial de ALPHA void g(struct S) { std::cout << "Procesando una estructura" << std::endl; } } using ALPHA::g; // Se declara en uso /* Despues de la declaracin anterior, los intentos siguientes de sobrecargar la funcin g() son ignorados */ namespace ALPHA { // Aqu se extiende el subespacio ALPHA void g( C& ) { // sobrecarga de la funcin g std::cout << "Procesando una clase" << std::endl; } } int main() { S mystruct; C myclass; g(mystruct); // // // // ================ se instancia la estructura se instancia la clase llamada correcta a la versin visible de g

// La llamada a funcin que sigue da error de compilacin // porque no existe una versin sobrecargada para este caso. g(myclass); // Error! return 0; } Salida: Procesando una estructura

4.2 Punteros
"Los punteros, junto con los goto, son una forma estupenda de crear programas imposibles de entender" Brian W. Kernighan y Dennis M. Ritchie. "The C Programming Language" Prentice Hall 2 Ed. 1988 p. 93.

1 Presentacin Al hablar de punteros es inevitablemente hablar de la memoria, as que permitidme una pequea introduccin al tema. A efectos del programador, la memoria RAM puede suponerse como una sucesin de contenedores capaces de albergar datos (podramos imaginarlos como una sucesin de vagones de tren). Estos contenedores tienen dos atributos: direccin y contenido. Direccin: un identificativo que sirve para distinguirlos. Para esto es suficiente un nmero entero positivo progresivamente creciente desde la posicin ms baja, la direccin 0 (que correspondera al primer vagn), a la posicin ms alta XXXXX (que correspondera al ltimo). Es tradicin informtica que estos nmeros se representen en hexadecimal ( 2.2.4b), de forma que las direcciones se suelen representar como xxxxxh en los textos de programacin. Los ordenadores modernos utilizan un modelo de memoria denominado "plano" ("Flat memory model") en el cual la memoria se presenta como un todo continuo desde el punto ms bajo hasta el mximo soportado por el hardware (mximo que puede direccionar). En la prctica la RAM instalada en un equipo es siempre inferior a este valor mximo permitido por su arquitectura [3], pero el mecanismo de memoria virtual ( H5.1) puede simular la existencia de RAM adicional mientras exista espacio en disco. Contenido: el contenido correspondiente a cada direccin est siempre en binario (la memoria fsica solo puede contener este tipo de variables 0.1), y la capacidad de cada vagn depende de la plataforma. En la arquitectura PC, el espacio sealado por cada direccin puede contener un byte (octeto). Como esto es muy poco (solo los tipos char caben en un octeto) para representar datos se utilizan vagones sucesivos en nmero suficiente. Por ejemplo, si ordenamos al compilador que traiga un dato que est en la direccin xxxxh y es unint. El compilador ya sabe que tiene que traer el contenido de esa celda y las tres siguientes ( 2.2.4).

Como corolario de lo anterior, recordemos que al hablar de la "direccin de un objeto" nos referimos siempre a la direccin donde comienza su almacenamiento. Por contra, la expresin "direccin de memoria" se refiere a un vagn especfico. Como veremos a continuacin, los punteros son un tipo de dato que sirven para almacenar direcciones de memoria, y su importancia radica en que la programacin de mquinas de Von Newmann tal como las conocemos hoy ( 2), gira en torno al concepto de direccin de memoria.

2 Sinopsis "The most important new concept in computer science that was not already in mathematics is the concept of address". A Stepanov. En la presentacin del libro: "STL tutorial and Reference Guide". D.R. Musser y otros. Addison Wesley Septiembre 2001.

Los punteros son un tipo especial de datos C++ cuyo fin especfico es almacenar direcciones de objetos. Comparten las caractersticas de las variables. Es decir: tienen un valor (tienen Rvalue); pueden ser asignados (tienen Lvalue); tienen un lgebra especfica (se pueden realizar con ellos determinadas operaciones); pueden ser almacenados en matrices; pasados como parmetros a funciones y devueltos por estas. Comprenden dos categoras principales: punteros-aobjeto y punteros-a-funcin segn el tipo de objeto sealado. Ambas categoras comparten ciertas operaciones, pero tienen uso, propiedades y reglas de manipulacin distintas. Nota: ms que un "tipo" deberamos decir mejor una "familia de tipos". Tendremos ocasin de ver que existen tantos sub-tipos de puntero como tipos de objetos distintos pueden ser sealados por ellos. Tambin veremos que cuando se declara un puntero, se especifica a que "tipo" de objeto puede sealar; en el futuro se limitar a contener direcciones de objetos de ese tipo.

3 Generalidades El tratamiento de punteros en C++ utiliza su propio vocabulario con el que es aconsejable familiarizarse desde el principio. En general decimos coloquialmente que un puntero "apunta" o "seala" a un objeto determinado cuando su valor (del puntero) es la direccin del objeto (direccin de memoria donde comienza su almacenamiento). El objeto sealado por el puntero se denomina referente. Por esta razn tambin se dice que el puntero "referencia" al objeto. El operador que permite obtener la direccin de un objeto (para asignarlo a un puntero) se denomina operador de "referencia" ( 4.9.11), y la operacin de obtener el referente a partir del puntero se denomina "deferenciar" ( 4.9.11). Como veremos a lo largo de este captulo, los punteros representan magnitudes escalares (nmeros) con casi todas las propiedades de los enteros sin signo, pero tienen sus propias reglas y restricciones para asignacin, conversin, y aritmtica (disponen de ciertos operadores aritmticos). Por supuesto, su tamao es el suficiente para almacenar una direccin de memoria en la arquitectura de la computadora utilizada. Nota: en la mayora de compiladores de 32 bits los punteros ocupan 4 bytes, es decir, coincide con el de un entero, de forma quesizeof(int) == sizeof(void*) no obstatante, no tiene porqu ser as necesariamene. Por ejemplo, en algunos compiladores para 64 bits, el tamao del puntero es mayor que el tamao de un entero.

En cualquier caso, el tamao de un puntero Ptr puede comprobarse mediante el operador sizeof ( 4.9.13) y una sentencia del tipo: cout << sizeof Ptr << endl; Considere el siguiente ejemplo: char * ptc = "A"; int entero = 100; int * pti = &entero; printf("Tamao de puntero a carcter:%4d bytes\n", sizeof(ptc) ); printf("Tamao de puntero a entero:%4d bytes\n", sizeof(pti) );

Salida (compilador C++Builder de Borland para Windows 32): Tamao de puntero a carcter: 4 bytes Tamao de puntero a entero: 4 bytes

4 En el ordenador digital todo est almacenado en memoria y/o en los registros del procesador. Incluso los datos almacenados en dispositivos externos deben ser volcados a memoria para su utilizacin. Habida cuenta que esta memoria es manejada por el procesador y su circuitera asociada (chipset) por medio de direcciones, parece evidente que los punteros son un tipo de dato especial e importante, tanto en C++ como en casi cualquier lenguaje de programacin [1]. Los registros del procesador reciban un tratamiento especial para su acceso (distinto del de la memoria), lo que explica que los punteros no sirvan para contener las direcciones de tales registros, y que el operador de referencia & ( 4.9.11) tampoco pueda ser aplicado para obtener su direccin (de los registros). Como consecuencia directa de lo anterior, los punteros tampoco pueden ser utilizados para almacenar las direcciones de valores devueltos por funciones, dado que estos valores se almacenan en los registros. Los punteros son un recurso que en cierta forma podra considerarse de muy bajo nivel, ya que permiten manipular directamente contenidos de memoria. Por esta razn, su utilizacin puede presentar algunos problemas y exigen que se preste una especial atencin a aquellas secciones del cdigo que los utilizan. Recuerde que los errores ms insidiosos y difciles de depurar que se presentan en los programas C++, estn relacionados con el uso descuidado de punteros. Precisamente por razn del comentario de K&R apuntado al principio y porque son frecuentemente origen de errores, algunos lenguajes prefieren evitarlos por completo, al menos su utilizacin explcita, y en otros casos su utilizacin est muy restringida [2]. En cambio el lenguaje ensamblador est plagado de ellos y desde luego, en C++, que no se distingue precisamente por soslayar los peligros, constituyen un captulo extraordinariamente importante.

Como eplogo de lo anterior y aclaracin para el estudiante, en especial los que no se han enfrentado antes a estos conceptos, o los hayan utilizado indirectamente (en lenguajes que los encapsulan en un envoltorio ms amable), digamos que la utilizacin de punteros exige un cierto entrenamiento mental, ya que en principio son difciles de "ver" [4], en especial los casos en que se utilizan punteros-a-punteros y que cuesta un cierto esfuerzo distinguir fluidamente entre valor del puntero, el valor del objeto sealado por este valor, y el significado de las operaciones asociadas (referencia y deferencia). Si este es su caso "Don't panic", sepa que es bastante normal y que basta un poco de prctica para que estos conceptos fluyan rpidamente y sin problema.

4.2.1 Puntero a objeto


Deberas hacer el bien y no el mal. Deberas encontrar el perdn para ti y perdonar a los dems. Deberas compartir libremente, no tomando nunca ms de lo que das. D. Richard Hipp www.sqlite.org en sustitucin de la nota copyright de su obra.

1 Sinopsis: Un "puntero-a-tipoX" (puntero a un objeto de tipoX), almacena la direccin de un objeto del tipo sealado; es decir: seala a un objetotipoX. Puesto que los punteros son objetos, puede haber un puntero apuntando a un puntero (as sucesivamente). Los objetos a los que se apunta suelen ser matrices, estructuras, uniones y clases. Nota: aqu consideramos que un "objeto" es una regin especfica de memoria que puede contener un valor (o conjunto de valores) fijo o variable. Esta acepcin del vocablo es diferente (ms amplia) que cuando designa la instancia de una clase.

Las explicaciones que siguen son vlidas para punteros objetos de cualquier tipo (que no sean funciones), pero cuando se trata especficamente de punteros a clases e instancias de clases, existen ciertas particularidades en la notacin, as que les hemos dedicado un epgrafe especfico con algunos comentarios ( 4.2.1f). Nota: tradicionalmente se considera que punteros y matrices estn estrechamente relacionados [1], hasta el punto de ser estudiados simultneamente. De hecho, cualquier operacin que puede efectuarse entre subndices de matrices puede efectuarse con punteros, en general de forma ms rpida (aunque algo ms difcil de entender por el principiante). Adems, el operador de elemento de matriz [ ] se define en trminos de ser un puntero ( 4.9.16).

Los punteros a objeto tiene su propia aritmtica (rudimentaria), de forma que pueden ser incrementados, disminuidos y comparados cuando se exploran matrices u otras estructuras complejas.

2 Puntero nulo Un puntero nulo es un puntero que apunta a una direccin que no corresponde a ningn objeto del programa. Asignando la constante entera 0 a un puntero se le asigna el valor nulo. Para mejor ligibilidad puede utilizarse la constante manifiesta NULL (definida en la librera de cabeceras estndar <stdio.h>). Ejemplo:

int* punt1; punt1 = 0; punt1 = NULL;

// L.1: Declara punt1 // L.2: define punt1 como puntero nulo // Equivalente a la anterior

Nota: la cuestin de la inicializacin del puntero nulo es uno de los puntos de controversia para los tericos del lenguaje. En rigor, la sentencia L.2 anterior es errnea, ya que el tipo de punt1 es puntero-a-int, mientras que 0 es una constante numrica (int por defecto); a pesar de lo cual es aceptada sin rechistar por el compilador [2]. Lo sintcticamente correcto sera realizar un modelado adecuado ( 4.9.9): punt1 = static_cast<int*> (0);

Es interesante observar que los punteros nulos de tipos distintos son distintos entre si. Por ejemplo, un puntero nulo-a-int es de tipo distinto de un puntero nulo-a-char (aunque ambos coincidan en que su Rvalue es 0), por tanto no pueden ser comparados. La comparacin de dos punteros nulos del mismo tipo conduce a que son iguales. Ejemplo: int* pint = NULL; char* pchar = NULL; if (pint == pchar) cout << "Iguales";

// Error!! Nonportable pointer

La ltima sentencia produce un error de compilacin: Error: comparison in...

Es un error deferenciar ( 4.9.11) un puntero nulo, adems, el intento de utilizar el valor obtenido puede dar lugar a un error de ejecucin. Por ejemplo, refirindonos al caso anterior: int x = *pint // Error:

Recordar que cuando falla el operador de modelado dynamic_cast ( 4.9.9c) sobre un puntero, el resultado es un puntero nulo. Tambin que algunas funciones de la Librera Estndar devuelven punteros; incluso el operador new devuelve un puntero, y en algunas implementaciones antiguas, el aviso de error en la operacin de este operador se realizaba devolviendo un puntero nulo.

3 Inicializar punteros En general los punteros pueden ser inicializados como otra variable cualquiera; desde luego solo tiene sentido que el valor sea la direccin de inicio del almacenamiento de un objeto del tipo adecuado. En otras palabras: un valor que sea una direccin de memoria. Como existe un operador que precisamente proporciona la "direccin" de una variable (operador de referencia & 4.9.11), la forma usual de inicializar un puntero suele ser asignndole el valor devuelto por dicho operador. Ejemplo: int x = 100; int* ptr; ptr = &x;

// declaracin de ptr como puntero-a-int // se le asigna la "direccin" de una variable

Tambin es muy frecuente iniciarlos con el valor devuelto por el operador new ( precisamente devuelve un puntero a objeto. Ejemplo: class C { .... }; C* cptr; cptr = new C(...)

4.9.20) que

// definicin de una clase C // declara cptr como puntero-a-C // new crea una instancia de la clase y devuelve un // puntero al objeto, cuyo valor que es asignado a cptr

Nota: los enteros y los punteros no son intercambiables, con excepcin del cero. El cero es un valor que nunca puede adoptar un puntero en condiciones normales, por esta razn se utiliza para significar "no asignado" u otra circunstancia especial. Recordar que la aritmtica de punteros permite comparar dos punteros, para igualdad o desigualdad, con NULL.

4 Punteros como argumentos En muchas ocasiones los punteros se utilizan como argumentos a funciones. De hecho, la mayora de las veces en que las matrices o las cadenas de caracteres pasan como argumentos, lo que en realidad pasa es un puntero al primer elemento. Por ejemplo: char* str = "La Tacita de Plata"; printf("La ciudad es: \"%s\"\n", str); En este caso, la funcin printf recibe como segundo argumento str, que es un puntero a la posicin de memoria donde comienza el almacenamiento de la cadena "La Tacita de Plata\0".

En cualquier caso, es necesario declarar esta circunstancia en el prototipo y en la definicin de la funcin para que el compilador conozca que el argumento pasado es una variable tipo puntero. Ejemplo: # include <iostream.h> void func(int *); // prototipo: argumento definido como puntero-a-int

void main () { // ============= int x = 35; int * ptr = &x; func (ptr); // se pasa puntero-a-int como argumento cout << "El valor es ahora: " << x << endl; } void func (int * p) { // el argumento se define como puntero-a-int *p += 10; // el argumento es tratado como puntero } Salida: El valor es ahora: 45

4.2.1a Declaracin de punteros


1 Presentacin La declaracin de punteros utiliza un asterisco *, que en este caso acta como calificador de tipo, en una sintaxis muy parecida a la utilizada en la declaracin de objetos normales. En este captulo nos referimos exclusivamente a declaracin de punteros a objetos; la declaracin de punteros a funcin ser comentado en otro apartado ( 4.2.4). Cuando los punteros que sealan a miembros de clase (propiedades o mtodos) presentan ciertas caractersticas especiales por lo que tambin sern tratados aparte ( 4.2.1g).

2 La sintaxis general de la declaracin de puntero a objeto es: <tipo_objeto> * <etiqueta_puntero> [ = <iniciador> ]

<tipo_objeto> tipo del objeto al que apuntar el puntero que se declara. Puede ser cualquiera, incluso otro puntero; de hecho existen tantas clases de punteros como tipos de objetos. Un puntero debe ser declarado como apuntando a algn tipo particular, incluso si el tipo es void (que en realidad significa puntero a nada), o a un tipo definido por el usuario (por ejemplo una clase). En lo sucesivo, el puntero declarado queda constreido a apuntar a este tipo de objeto. Ejemplos: int * ptr; void * ptr; char * ptr; // declara ptr puntero a entero (int) // declara ptr puntero a void (genrico) // declara ptr puntero a carcter (char)

En estas declaraciones se utiliza el asterisco (*) como especificador de tipo, indicando que la variable es de tipo puntero en alguna de sus variedades (ver comentario 4.2.1aw1). Considere como modifica el asterisco el significado de las siguientes sentencias: int ptr; int* ptr; // declara que ptr es int // declara que ptr es puntero-a-int

Tambin hay que advertir que es indiferente la colocacin de un espacio antes o despus del asterisco. Las expresiones que siguen son equivalentes [1]: int * ptr; int* ptr; int *ptr; int*ptr;

Recordemos que declaraciones de este tipo no son definiciones (no inician el puntero) y que es necesario iniciar los punteros antes de su utilizacin. Recordar tambin que, adems de

especificador de tipo, el operador "*" puede ser usado como operador de indireccin ( 4.9.11a) y como operador de multiplicacin ( 4.9.1),

2.1 Aunque segn lo dicho hasta aqu, una expresin con int* no tiene significado, es frecuente encontrar este tipo de expresiones. Por ejemplo, void* para indicar, en abstracto, un puntero-avoid. En general, la expresin tipoX* significa puntero-a-tipoX. Ejemplos: "the void* pointer type was invented for ANSI C and first implemented in C++" ( Stroustrup 1 Ed. Pg. 5). double func(char*); indica que func devuelve un double y acepta por argumento un puntero-a-carcter (recuerde que los prototipos de funciones pueden omitir los nombres de los parmetros). return (char*)ptr; indica que el valor a devolver, ptr, debe ser promovido a puntero-a-carcter (modelado 4.2.1b). p = (int *) malloc(SIZE * sizeof(int)); obtener memoria para almacenar SIZE enteros en forma de un puntero-a-intsealando al comienzo de dicho espacio.

3 Peligro en la declaracin de punteros Aunque las declaraciones mltiples (en la misma sentencia) son aceptables en C++, con los punteros deben evitarse, porque es ms que probable que el compilador interprete errneamente las declaraciones sucesivas. Ejemplo: int * ptr1 ,ptr2; // Posible error !!

En estos casos es mejor hacer: int * ptr1; int * ptr2; Ver ejemplo al respecto ( 4.9.14).

4 Creacin y destruccin Los punteros siguen las reglas de creacin y destruccin del resto de las variables, sin embargo hay que recordar que los objetos tienen duracin independiente de los posibles que los sealan, de forma que cuando un puntero-a-objeto sale de mbito, no se invoca implcitamente ningn destructor para el objeto sealado. A la inversa, la destruccin del objeto sealado no supone necesariamente la destruccin de los punteros que los referencian. En la prctica pueden presentarse ambas circunstancias, ocasionando situaciones potencialmente peligrosas o errneas. Por ejemplo, el primer caso puede ocurrir con objetos persistentes creados con los operadores new o new[]. Este tipo de objetos son accesibles a travs de un puntero, generalmente un objeto automtico que es destruido automticamente al salir de mbito. Pero el objeto sealado permanece ocupando memoria, por lo que se hace necesaria una invocacin explcita al operador delete ( 4.11.2d2) para destruirlo antes que sea destruido el puntero, pues de lo contrario se producira una perdida irrecuperable de memoria.

El segundo caso, la destruccin de un objeto sin que sean destruidos los punteros que lo sealan, tambin es bastante frecuente, dando lugar a los denominados punteros descolgados ("Dangling pointers"), cuyo uso inadvertido es especialmente peligroso, pues sealan a zonas de contenido errneo.

5 Punteros constantes y a-constantes La expresin de declaracin de un puntero puede llevar opcionalmente el especificador const ( 4.2.1e) para indicar que es: Puntero-a-constante: Apunta a un objeto de tipo constante. El puntero puede variar, con lo que apuntara a otro objeto; en cambio el objeto sealado no puede cambiar su valor. En consecuencia, el puntero no puede ser utilizado para cambiar indirectamente el valor del objeto al que seala. int * pt1; // declara pt1 puntero variable a int variable const int * pt2; // declara pt2 puntero variable a constante tipo int int const * pt2; // equivalente al anterior char const * pt3; // declara pt3 puntero variable a constante tipo char const char * pt4; // equivalente al anterior Como se ha sealado ( 3.2.1c), int y const int son tipos distintos, por lo que a su vez pt1 y pt2 son punteros de tipo distinto. Puntero-constante: El puntero es constante; el objeto sealado podra variar, pero el valor del puntero -direccin de memoria que seala- no. Esto significa que est indefectiblemente ligado a un mismo objeto. int* const pt5 = &e1; // define pt5 puntero constante a int variable char* const pt6 = &e2; // define pt6 puntero constante a char variable Naturalmente ambas condiciones pueden darse simultneamente: puntero-constante (que no puede alterar su valor) sealando a un objeto-constante: const int* const pt7 = &kte; // define pt7 puntero constante a int constante

Observe que, como ocurre con otros tipos de constantes, cuando el puntero es constante, la iniciacin debe realizarse forzosamente en el momento de su declaracin. Ejemplo: const int ki = 33; int* const pk; pk = &ki; int* const pk = &ki

// Error!! asignacin a constante // Ok.

6 Ejemplos:

Como puede verse, la sintaxis de declaracin de punteros sigue reglas anlogas a la declaracin de objetos, con la diferencia de incluir el smbolo de declaracin de puntero * entre el calificador de tipo y el nombre del objeto. Con objeto de familiarizar al lector con la notacin utilizada, se exponen varios casos de declaraciones y definiciones de punteros de tipos diversos. Declaracin de objeto int x; x es un entero (no iniciado) int x = 25; x es un entero (iniciado) int* ptr = &x; ptr puntero-a-entero (iniciado) tipoX x; x es un objeto tipoX const int ci = 7; ci es una constante tipo int iniciada a7 tipoX m[3]; m es una matriz de 3 objetos tipoX Declaracin de puntero-al-objeto int* ptr; ptr puntero-a-entero (no iniciado) int* ptr = &x; ptr puntero-a-entero (iniciado) int** pptr = &ptr; pptr puntero-a-puntero a entero (iniciado) tipoX* ptx; ptx es un puntero a tipoX (no iniciado) const int* pci; pci es un puntero a constante tipo int tipoX* ptr = &m[0]; ptr es un puntero al primer elemento de m (iniciado). Es por tanto un puntero a tipoX tipoX (*ptr)[] = &m; ptr es puntero-a-matriz abierta de tipoX (iniciado) tipoX (*ptr)[3] = &m; ptr es puntero-a-matriz de tres objetos tipoX (iniciado) tipoX* mp[3]; mp es una matriz de 3 punteros-atipoX char* aP[] = {"Bien", "Mal"}; aP matriz de punteros-achar (iniciada) char (* mi[2])[3]; char (*(* ptr)[2])[3] = &mi; tipoX* (*ptr)[] = &mp; ptr es puntero-a-matriz abierta de punteros-a-tipoX (iniciado) char* (*aPp) [] = &aP; aPp puntero-a-matriz de punteros-a-char (iniciado)

mi matriz de dos punteros-a-matrizde-tres char

ptr puntero-a-matriz de dos punteros-a-matriz de tres char

6.1 Otras expresiones con punteros tipoX (*ptr)[5][7]; void* ptr; ptr es puntero-a-matriz de tipoX de dimensiones 5 y 7 ptr puntero-a-void (genrico Ejemplo: int n = 33; void* ptr = &n; const void* ptr; ptr puntero-a-void (genrico) a constante Ejemplo: const int ki = 33; const void* ptr = &ki; tipoX* func(); void*& pword(int); Funcion devolviendo puntero-a-tipoX Funcin que acepta un int y devuelve una referencia puntero genrico 4.2.1d). Funcin devolviendo void y recibiendo puntero-a-int ptr es puntero a funcin que devuelve tipoX ( ptr es puntero constante a cadena-de-char ( ptr es puntero-a-constante tipo cadena-de-char Equivalente a la anterior 4.2.4) 3.2.3f) 4.2.3) a un 4.2.1d)

void func(int*); tipoX (*ptr)(); char* const ptr = "Hola"; char const* ptr = "Hola"; const char* ptr = "Hola";

Observe que definiciones como las dos ltimas, crean una constante tipo cadena (en este caso, de contenido "Hola\0"), alojada en algn lugar de la memoria, pero sin un identificador para manejarla. Solo es posible referenciarla (dirigirse a ella) a travs del puntero. En otras palabras, no es posible usarla por su nombre, sino por su direccin. Observe que en las declaraciones de punteros en las que intervienen matrices y/o funciones, es muy importante la correcta situacin de los parntesis: char* ptr []; char (* ptr) []; char* (* ptr) []; // Matriz de punteros-a-char // Puntero-a-matriz de char // Puntero-a-matriz de punteros a char

Ejemplo: #include <iostream> using namespace std; char achar[5] = {'A','E','I','O','U'}; char * punt1 [5]; char (* punt2) [5]; char* (* punt3) [5]; int main (void) { // ================== punt1[0] = &achar[0]; punt1[4] = &achar[4]; cout << "Comienzo: == " << *punt1[0] << endl; cout << "Final: == " << *punt1[4] << endl; punt2 = &achar; cout << "Contenido: == " << *punt2 << endl; punt3 = &punt1; cout << "Contenido: == " << *punt3 << endl; cout << "Contenido: == " << **punt3 << endl; return 0; } Salida: Comienzo: == A Final: == U Contenido: == AEIOU Contenido: == 0047A85C Contenido: == AEIOU

7 Expresiones peligrosas Se ha dicho que "No hay nada ms temible que un puntero descontrolado". Generalmente dan lugar a uno de los problemas ms difciles de depurar en un programa y en consecuencia, hay que prestar especial atencin en todas las sentencias relacionadas con ellos. Por ejemplo, sentencias como las que siguen pueden causar un desastre. int* iptr; *iptr = 100;

//muy peligroso!!

La razn es que en la primera lnea declaramos iptr como puntero-a-entero, no se inicia, por lo que no sabemos a donde apunta. Posiblemente el Lvalue de la variable est ocupado por basura, aunque puede tratarse de una zona sensible (actualmente apunta a un sitio impredecible). En la segunda lnea el sitio apuntado por iptr lo sustituimos con 100, posiblemente machaquemos una zona de memoria equivocada con las consecuencias fciles de prever. Es ms correcto hacer: int z; int* iptr = &z; *iptr = 100; // L.1 // L.2 // L.3

La explicacin es que en L1 el compilador ha iniciado una variable z con una direccin de memoria adecuada, aunque momentneamente no inicializada (contiene basura 4.1.2). En L2 el puntero apunta a ese valor. Finalmente, la basura original es sustituida por un valor conocido en L3.

4.2.1b Modelado de tipos con punteros


1 Sinopsis Al tratar con punteros es importante tener muy claro que los punteros sealando a objetos de tipo distinto, son objetos de tipo distinto entre s. Por ejemplo: int *pt1; // puntero-a-entero int const *pt2; // puntero-a-entero constante

En este caso pt1 y pt2 son punteros de distinto tipo (aunque ambos sealen a valores de tipo int) y por tanto no pueden realizarse asignaciones directas entre ellos. Por ejemplo: int int pt1 int pt2 const peso = 50; // declara peso constante tipo int const *pt1; // declara pt1 puntero-a-int-constante = &peso; // Ok asignacin permitida. *pt2; // declara pt2 puntero-a-int = &peso; // Error: el puntero no es adecuado

2 Puntero genrico Una vez declarado, un puntero permanece encasillado; no puede sealar a objetos de tipo distinto que el original. La excepcin, con ciertas limitaciones ( 4.2.1c Punteros genricos) son los punteros declarados inicialmente como puntero-a-void. Por esta razn, este tipo de punteros son considerados punteros genricos (no debe ser confundido con puntero nulo 4.2.1). Ejemplo: int x = 20; void* pt3; pt3 = &x; // declara x tipo int // declara pt3 puntero-a-void (genrico) // Ok asignacin permitida

3 Modelado de punteros No obstante el "encasillado" antes aludido, el modelado de tipos ( 4.9.9) puede utilizarse tambin con punteros, permitiendo reasignarlos [2]. Por ejemplo, siguiendo con el caso anterior, sera posible hacer: int* pt4; pt4 = (int*) &peso; // declara pt4 puntero-a-int // Ok: asignacin permitida

Al realizar operaciones con punteros modelados, es posible que se produzcan corrupciones de

memoria o machacamiento de datos, ya que la aritmtica de punteros tiene en cuenta el tamao del objeto sealado. Ver ejemplo ( 4.9.9d) La asignacin entre un puntero-a-tipo1 y un puntero-a-tipo2 sin un moldeado apropiado, puede producir un mensaje de aviso, o error del compilador, cuando tipo1 y tipo2 son distintos. Si tipo1 es una funcin y tipo2 no, o viceversa, la asignacin ser siempre ilegal. Es decir, los punteros-a-valor no pueden ser modelados a punteros-a-funcin (algoritmo) y viceversa. Por otra parte, si tipo1 es un puntero-a-void, el "casting" no es necesario (con la excepcin indicada, 4.2.1d, si tipo2 es un puntero-a-constante).

Ver "Modelado de tipos" ( 4.9.9), en especial los operadores dynamic_cast y static_cast para convertir un puntero al tipo deseado. Adelantemos aqu, que el operador reinterpret:cast permite prcticamente cualquier tipo de conversin, incluso las mentadas anteriormente entre puntero-afuncin y puntero-a-valor. Aunque su uso es extremadamente peligroso y solo debe utilizarse en circunstancias muy puntuales. Nota: si te sorprendes a ti mismo necesitando uno de estos modelados, seguramente se debe a alguna de estas dos causas: a.- Eres un virtuoso de la programacin C++; b.- Tu anlisis es defectuoso, te ests metiendo en una zona de la que no podrs salir fcilmente (ests a punto de volarte la pierna).

4.2.1c Puntero a puntero


"Ive come to realize that understanding pointers in C is not a skill, its an aptitude. In first year computer science classes, there are always about 200 kids at the beginning of the semester, all of whom wrote complex adventure games in BASIC for their PCs when they were 4 years old. They are having a good ol time learning C or Pascal in college, until one day they professor introduces pointers, and suddenly, they dont get it. They just dont understand anything any more. 90% of the class goes off and becomes Political Science majors, then they tell their friends that there werent enough good looking members of the appropriate sex in their CompSci classes, thats why they switched. For some reason most people seem to be born without the part of the brain that understands pointers. Pointers require a complex form of doublyindirected thinking that some people just cant do, and its pretty crucial to good programming. A lot of the script jocks who started programming by copying JavaScript snippets into their web pages and went on to learn Perl never learned about pointers, and they can never quite produce code of the quality you need". Joel Spolsky. "The Guerrilla Guide to Interviewing (version 3.0)" www.joelonsoftware.com/

1 Sinopsis Al presentar los punteros a objeto ( 4.2.1) se seal que, puesto que los punteros tambin son objetos y en consecuencia, pueden existir punteros que sealen a otros punteros (as sucesivamente). El lenguaje C++ permite que se puedan declarar punteros con mltiples niveles de indireccin mediante un nmero adecuado de asteriscos. Por ejemplo: char letra = 'A'; char* cPtr = &letra; char** cPtr_bis = &cPtr;

// define puntero-a-carcter // define puntero-a-puntero-a-char

cPtr_bis* cPtr_bix = &cPtr_bis; definido cout << "Letra " << *cPtr; cout << "Letra " << **cPtr_bis;

// Error!! cPtr_bis no es un tipo // -> Letra A // -> Letra A

Naturalmente, las expresiones ** solo tienen sentido para apuntar a un puntero, no sirven a ningn otro tipo. Ejemplo: char letra = 'A'; char** cPtr = &letra; 2 Ejemplo Supongamos que en un programa nos interesa presentar el estado de un proceso, caben tres posibilidades: "Bien"; "Regular" y "Mal". char* aP[] = {"Bien", "Regular", "Mal"}; // aP matriz de punteros a char char* (*aPp) [] = &aP; // aPp puntero a matriz de punteros-achar

// Error. letra NO es un puntero-a-char

Ntese que en la primera sentencia, el compilador crea tres cadenas de caracteres: "Bien/0", "Regular/0" y "Mal/0"; las almacena en tres posiciones de memoria (no necesariamente consecutivas 3.2.3f); crea una matriz de tres punteros-a-carcter (cuyo nemnico es aP), y rellena los tres elementos (consecutivos) de esta matriz, con las direcciones de los primeros caracteres ('B', 'R' y 'M') de las tres cadenas. Hemos sealado que en un compilador de 32 bis el tamao de un puntero es de 4 bytes ( 4.2), por lo que el tamao de aP es 3 x 4 = 12 bytes. Ntese tambin que &aP es lo mismo que &aP[0], por lo que en principio, aPp apunta al primer elemento de la matriz aP. Las diversas posibilidades pueden ser expresadas como elementos de la matriz aP. Si estos elementos los expresamos mediante notacin de subndices [0] se tiene: cout << aP[i] << endl; // i == 0; 1; 2

2.1 Lo anterior puede ser expresado en forma de ejecutable: #include <iostream> using namespace std; int main() { // ================= char* aP[] = {"Bien", "Regular", "Mal"}; char* (*aPp) [] = &aP; int n = sizeof(aP) / sizeof(aP[0]); // tamao de la matriz aP for(int i=0; i<n; i++) { cout << aP[i] << endl; // M.5 } }

Salida: Bien Regular Mal En este ejemplo, el primer elemento ("Bien") de la matriz aP se ha referenciado mediante el operador de elemento de matriz en la forma aP[0], pero dicho elemento tambin puede ser referenciado mediante una doble indireccin del puntero aPp: cout << **aPp << endl; // -> Bien

Sin embargo, el intento de referenciar los otros dos elementos ("Regular" y "Mal") en funcin del referido puntero conlleva una serie de dificultades tericas cuya solucin es del mayor inters para comprender el mecanismo de punteros C++ y su lgebra.

2.2 Consideremos un primer intento de mimetizar el ejemplo anterior sustituyendo la expresin aP[i] de M.5 , por una expresin que contengaaPp: #include <iostream> using namespace std; void main() { // ================= char* aP[] = {"Bien", "Regular", "Mal"}; // M.1 char* (*aPp) [] = &aP; // M.2 int n = sizeof(aP) / sizeof(aP[0]); for(int i=0; i<n; i++) { cout << **aPp << endl; // M.5 aPp++; // M.6 } } El primer problema que se presenta al intentar compilar este mdulo, es que se obtiene un error en M.6: Size of the type 'char *[]' is unknown or zero.... La razn es que en la definicin del puntero aPp en M.2, se ha dejado abierta la dimensin del objeto sealado, y la aritmtica de punteros implcita en M.6 ( 4.2.2), exige que el compilador conozca el tamao del objeto sealado. En este caso, el objeto sealado por aPp es una matriz de 3 punteros-a-char. El problema se resuelve sustituyendo la sentencia M.2 por una que indique el tamao: char* (*aPp) [3] = &aP; // M.2bis

Observe que el tamao de aP tambin se ha dejado abierto en M.1; sin embargo, el compilador puede deducir su dimensin de la propia definicin. Desde luego podra argumentarse que en M.2 el compilador tambin puede conocer por la definicin el tamao del objeto sealado, lo que es cierto; sin embargo la solucin M.2bis es necesaria sencillamente por que en el estado actual, los compiladores utilizados [2] no son suficientemente "inteligentes". Con M.2bis el programa compila sin dificultad, pero solo la primera salida es correcta. Las dos siguientes contienen basura [1]. La razn es que despus de la primera salida, la sentencia M.6

suma una unidad al valor actual del puntero, que seala al primer elemento (aP[0]) de la matriz aP, que seala a su vez al primer carcter de la cadena "Bien". Sumarle una unidad implica desplazar el puntero el tamao correspondiente al objeto sealado aP; en este caso una matriz de tres punteros a carcter, cuyo tamao es 4 bytes [3]. El resultado es que el puntero no seala ahora al elemento aP[1] como pretendamos, ya que este elemento de la matriz est separado solo 4 bytes del anterior -los punteros-a-char (como el resto) ocupan solo 4 bytes ( 4.2)-.

2.3 La versin modificada que sigue funciona correctamente, proporcionando las salidas esperadas, pero se han utilizado algunos artificios para conseguir expresar las salidas en funcin del puntero-a-puntero aPp. #include <iostream> using namespace std; void main() { // ============= char* aP[] = {"Bien", "Regular", "Mal"}; char* (*aPp) [] = &aP; // M.2 int n = sizeof(aP) / sizeof(aP[0]); int dir; for(int i=0; i<n; i++) { cout << **aPp << endl; dir = reinterpret_cast<int> (aPp); // M.6bis aPp = reinterpret_cast<char* (*)[]> (dir+4); // M.7 } } Comentario: En esta versin, la lnea M.6 del ejemplo anterior , se ha sustituido por M.6bis y M.7.

Mediante un "casting" ( 4.9.9d) adecuado, en M.6 asignamos el valor del puntero aPp a un entero dir, que almacena la misma direccin pero como un valor int. De esta forma, en M.7 podemos sumarle el desplazamiento adecuado, y (mediante el modelado inverso), volverlo a asignar al puntero aPp, con lo que efectivamente, en cada iteracin del bucle seala sucesivamente a los miembros aP[0]; aP[1] y aP[2]; de forma que la doble indireccin **aPp proporciona en cada caso la salida adecuada. Observe la expresin utilizada en M.7, para transformar un entero en un puntero a matriz de punteros a carcter. Y como no es necesario en M.2 conocer el tamao del objeto sealado por aPp, ya que en este ltimo ejemplo no existe ninguna expresin que implique lgebra de punteros.

You might also like