Anda di halaman 1dari 65

4.2.

4b Invocar funciones mediante punteros


1 Sinopsis Una vez declarado e iniciado, un puntero a funcin puede ser utilizado de dos formas: Acceder (invocar), a la funcin que representa Usarlo como parmetro en la invocacin de otra funcin.

Como en ltimo extremo los punteros siempre sirven para invocar las funciones que representan, comenzamos la exposicin por el primero de los dos usos.

2 Invocacin En los programas reales, la invocacin de funciones a travs de punteros frecuentemente sigue una lgica larga y complicada (las aplicaciones que lo requieren suelen serlo), sin embargo el proceso siempre se puede esquematizar como sigue: char func(int); ... char (*fptr)(int) = func; ... int x = 5; char c; c = (*fptr)(x); // L.1: declara funcin (prototipo) // suponemos su definicin en algn sitio. // L.2: define puntero-a-funcin

// L.3: invoca func, pasa 5 como argumento

Como puede verse en el prototipo de L.1, la funcin func cumple las condiciones exigidas por el puntero en L.2 (recibir un int y devolver un char). La lnea L.2 declara e inicia el puntero fptr. Podra hacerse igualmente en dos sentencias separadas: char (*fptr)(int); fptr = func; // L.2: declara puntero-a-funcin // L.2b: inicia puntero

A partir de L.2, (*fptr) es equivalente a func, de forma que las dos expresiones que siguen son equivalentes [1]: func(x) (*fptr)(x) (*func)(x) // invoacin estndar // invocacin mediante puntero 2a // invocacin mediante puntero

La ltima expresin es consecuencia de aplicar la igualdad resultante de la expresin L.2b a la expresin 2a (fptr == func). C++ proporciona otra sintaxis simplificada para la invocacin de una funcin mediante su puntero: fptr(x) // Otra forma de invocacin mediante puntero 2b

Nota: esta segunda forma (2b) es coherente con lo indicado anteriormente ( nombre de la funcin puede ser considerado como equivalente a su puntero.

4.2.4a) que el

Observe que en esta ltima sintaxis (2b) es imposible saber que la invocacin de la funcin no es directa, sino indirecta (a travs de un puntero). Adems algunas situaciones pueden dar lugar a expresiones casi "hirientes" a la vista. Por ejemplo, la lnea M.5 del ejemplo de la pgina anterior ( 4.2.4a) podra ser sustituida por: apf[3](); // M.5bis

Por esta razn, algunos programadores prefieren la sintaxis (2a) para recalcar que la invocacin es mediante un puntero, dejando la sintaxis (2b) para cuando se utiliza el nombre de la funcin.

2.1 Un ejemplo bsico: #include <iostream.h> void fun () { cout << "Una funcion cualquiera " << endl; } void (*pf)() = fun; // L.4 puntero-a-funcin void (**ppf)() = &pf; // L.5 puntero-a-puntero-a-funcin int main (void) { fun (); (*pf)(); pf(); (**ppf)(); (*ppf)(); return 0; } Salida: Una Una Una Una Una funcion funcion funcion funcion funcion cualquiera cualquiera cualquiera cualquiera cualquiera // // // // // // ================ M.1 M.2 M.3 M.4 M.5

Comentario: L.4 define un puntero-a-funcin de las caractersticas de fun (funcin que devuelve void y no recibe argumentos). Se inicia con la direccin de la funcin anterior. Observe que esta sentencia es equivalente a: void (*pf)() = &fun; // L.4bis

L.5 declara que ppf es un puntero-a-puntero-a-funcin devolviendo void que no recibe argumentos. Este puntero se inicia con la direccin de pfque es del tipo adecuado.

La funcin main invoca la funcin de cinco formas diferentes, todas ellas equivalentes. La primera, en M.1, es una invocacin directa estndar. Las dems son a travs de punteros. Observe que las sentencias M.2/M.3 son equivalentes, as como M.4/M.5.

2.2 Otro ejemplo compilable: #include <iostream.h> char func(int);

// prototipo de func =========== define puntero fptr invoca func, pasa 65 como argumento << endl;

void main () { // char (*fptr)(int) = func; // int x = 65; char c; c = (*fptr)(x); // cout << "El valor es: " << c return (0); } char func (int p) { // char c = p; // cout << "El valor es: " << c return char(p+32); // } Salida: El valor es: A El valor es: a

definicin de func "casting" implcito << endl; observe el modelado del valor a devolver!!

2.3 Si en lugar de un puntero aislado se hubiese utilizado una matriz de punteros, el esquema seguira siendo muy parecido: char fun1(int); // declara funcin (prototipo) char fun2(int); // idem ... char (*fptr[])(int) = {fun1, fun2}; // define matriz de punteros ... int x = 5, y = 1; char c, d; c = (*fptr[y])(x); // invoca fun2, pasa 5 como argumento d = (*fptr[0])(x); // invoca fun1, pasa 5 como argumento

3 Ejemplo_1 Una vez conocida la notacin bsica para invocacin de funciones a travs de punteros, consideremos un ejemplo ms completo: #include <stdio.h> void aux1(char letra); int main(void) { // prueba de puntero a funcin // prototipo de aux1 L.2 // ===========

void (* fptr)(char); // fptr puntero a funcin L.4 fptr = aux1; // inicia fptr L.5 char c; printf("- Pulse una tecla \n"); while (( c=getchar() ) < 65 || c > 122 || (c > 90 && c < 97)) { //L.8 if (c != 13 && c != 10) printf(" Debe ser una letra!\n"); } (* fptr)(c); // invoca aux1 con un argumento, L.11 return 0; } void aux1(char ch) { // definicin de aux1 L.14 if (ch < 97) printf("\"%c\" es Mayscula !!\n", ch); else printf("\"%c\" es minscula !!\n", ch); }

El programa espera la introduccin de una letra por el teclado (stdin) y declara si es mayscula o minscula; la salida depende de la tecla X/x pulsada: "X" es Mayscula !! "x" es minscula !! En un primer paso (lnea 4), definimos la variable fptr como "puntero a funcin recibiendo carcter y devolviendo void". En lnea 5 iniciamos la variable fptr, asignndole la direccin de la funcin aux1 que, como puede verse en su prototipo de la lnea 2, cumple las condiciones exigidas por el puntero (recibir un char y devolver void). A partir de este momento, (*fptr) es equivalente a aux1. El bucle de las lneas 8 a 10 es para asegurarnos que la tecla pulsada es una letra (mayscula o minscula). La lnea 11 es una invocacin a la funcin aux1(c) a travs de su puntero.

4 Ejemplo_2 Aunque didctico, el ejemplo anterior es un poco tonto, pues para obtener el resultado no habra sido necesario utilizar un puntero a funcin, simplemente invocar auxch(str) directamente. En el ejemplo que sigue aprovechamos el carcter de variable de fptr para hacer una seleccin de la funcin a invocar. El programa es muy parecido al anterior, con la excepcin de que en este caso, tambin anuncia si la tecla pulsada es vocal o consonante. #include <stdio.h> #include <string.h> void aux1(char letter_key); void aux2(char letter_key); int main(void) { // Prueba-2

// prototipo de aux1 // prototipo de aux2 // =========

void (* fptr) (char); // declara fptr puntero a funcin L.6 char c; printf("- Pulse una tecla \n"); while (( c=getchar() ) < 65 || c > 122 || (c > 90 && c < 97)) { if (c != 13 && c != 10) printf(" Debe ser una letra!\n"); } fptr = aux1; (* fptr)(c); // L.12 fptr = aux2; (* fptr)(c); // L.13 return (0); } void aux1(char ch) { // definicin de aux1 char *str = "aeiouAEIOU"; if (strstr(str, &ch) != NULL) printf("\"%c\" es vocal ", ch); else printf("\"%c\" es consonante ", ch); } void aux2(char ch) { // definicin de aux2 if (ch < 97) printf("MAYUSCULA !!\n"); else printf("minuscula !!\n"); }

Se dispone de dos funciones auxiliares aux1 y aux2, cada una de las cuales se encarga de una parte de la salida, ambas aceptan el mismo tipo de argumento y devuelven void, por lo que pueden ser apuntadas por el mismo puntero fptr. En las lneas 12 y 13 asignamos sucesivamente a la variable fptr las direcciones de las funciones aux1 y aux2, y las invocamos a travs del puntero con el mismo parmetro en cada caso.

5 Ejemplo_3 En la versin que sigue del mismo programa; en vez de un nico puntero, que va cambiando sucesivamente de valor segn la funcin que hay que invocar, utilizamos una matriz de punteros. Nota: para simplificar, hemos suprimido el texto de las definiciones de aux1 y aux2, que son exactamente iguales que en el caso anterior; el cuerpo queda como sigue:

#include <stdio.h> #include <string.h> void aux1(char letter_key); void aux2(char letter_key);

// Prueba-3 // prototipo de aux1 // prototipo de aux2

int main(void) { // ========= void (* fptr[2]) (char) = {aux1, aux2}; // L.6 char c;

printf("- Pulse una while ((c=getchar() if (c != 13 && c } (* fptr[0])(c); (* return (0); }

tecla \n"); ) < 65 || c > 122 || (c > 90 && c < 97)) { != 10) printf(" Debe ser una letra!\n"); fptr[1])(c); // L.12

// definiciones de aux1 y aux2 como en el ejemplo anterior

La novedad es que ahora fptr es un array de dos punteros a funcin, que se declara e inicia directamente en la lnea 6. Obsrvese la diferencia de las declaraciones en la lneas 6 de este ejemplo y del anterior: void (* fptr) (char); void (* fptr[2]) (char); // declara puntero a funcin // declara array de punteros a funcion

En le lnea 12 se invoca sucesivamente aux1 y aux2 mediante sus punteros utilizando subndices. Estas matrices de punteros-a-funcin representan tablas de funciones con la que se puede manejar alternativas. En lugar de utilizar las clsicas sentencias del tipo if... then else, se ejecuta una u otra funcin en base a una variable. Esta forma de codificacin, denominada cdigo dirigido por tablas, es especialmente interesante y muy adecuada en determinadas circunstancias.

6 Ejemplo_4 A continuacin, un ejemplo de la tcnica expuesta. Representa un refinamiento del programa anterior, en este caso, la seleccin se realiza en el cuerpo de la funcin main, fuera de las funciones auxiliares. #include <stdio.h> #include <string.h> void void void void aux1(void); aux2(void); aux3(void); aux4(void); // Prueba-4 // prototipos

int main(void) { // ======== void (* fptr[])(void) = {aux1, aux2, aux3, aux4}; char *str = "aeiouAEIOU", c; printf("- Pulse una tecla \n"); while (( c=getchar() ) < 65 || c > 122 || (c > 90 && c < 97)) { if (c != 13 && c != 10) printf(" Debe ser una letra!\n"); } printf("\"%c\" es ", c); (* fptr[(strstr(str, &c) != NULL) ? 0 : 1])(); // L.15 (* fptr[(c < 97) ? 2 : 3])(); // L.16 return (0); }

void void void void

aux1(void) aux2(void) aux3(void) aux4(void)

{ { { {

printf("vocal "); } // definiciones printf("consonante "); } printf("MAYUSCULA !!\n"); } printf("minscula !!\n"); }

Utilizamos cuatro funciones auxiliares representadas en este caso por fptr, un array de cuatro punteros a funcin. La lnea 15 se resuelve en (*fptr[0])() (*fptr[1])() segn el resultado de la clusula strstr(str, &c) != NULL. Del mismo modo, la lnea 16 se resuelve en (*fptr[2])() (*fptr[3])(), segn el resultado de la condicin c < 27.

4.2.4c Paso de funciones como argumento


1 Sinopsis Como se ha sealado ( 4.2.4b), la segunda (aunque no la menos importante) finalidad de los punteros a funcin, es pasar funciones como argumentos en las llamadas a otras funciones. Es necesario utilizar este artificio (punteros) porque en principio, la gramtica de C++ no permite utilizar funciones en la declaracin de parmetros ( 4.4.1). En general el uso de estos punteros permite tcnicas de programacin bastante sofisticadas, y como hemos sealado anteriormente, la posibilidad de utilizar funciones como argumentos en la invocacin de otras funciones, es la principal razn de la introduccin en el lenguaje de este tipo de punteros. El fondo de la cuestin consiste en alargar la funcionalidad de la funcin invocada, ya que a travs de sus argumentos, puede invocar otras funciones. El mecanismo puede esquematizarse como sigue: void func(void (* fp)() ) { // funcion aceptando un puntero-a-funcin fp ... // computacion propia fp(); // acceso a la computacin de la funcin sealada por fp } La utilizacin podra responder al siguiente esquema: void func1() { /* cierto proceso */ } void func(void (* fp)() ) { fp(); } // funcin aceptando un puntero-afuncin void (* fptr)(); // declaracion de puntero-a-funcion ... fptr = func1; // fptr se inicia con la direccion de func1 func(fptr); // func puede ejecutar el cdigo de func1 Naturalmente, en un caso como el anterior, tambin es posible acceder directamente a func1 desde func sin necesidad de punteros: void func1() { /* proceso de func1 */ } void func() { func1(); } func(); // Ok func ejecuta el cdigo de func1

Sin embargo, la utilidad cobra su verdadero significado cuando el argumento no es constante (no se invoca siempre la misma funcin) sino que es una variable. La situacin puede esquematizarse como sigue: void void void ... void func1() { /* proceso-1 */ } func2() { /* proceso-2 */ } func3() { /* proceso-3 */ } funcn() { /* proceso-n */ } // funciones que // efectan // distintas // computaciones

void func(void (* fp)() ) { fp(); } // funcin aceptando puntero-afuncin void (* fptr)(); // declaracin de puntero-a-funcin fptr = ... /* El valor del puntero depende de determinadas condiciones externas por ejemplo, la informacin leda en una interfaz analgico-digital */ func(fptr); // se ejecuta el cdigo adecuado segn el caso

2 Veamos un ejemplo compilable utilizando una mutacin del cdigo del ejemplo anterior ( 4.2.4b Ej.4). #include <stdio.h> #include <conio.h> char auxa(int posicion); // prototipo de auxa void escribe (int posicion, char (* ptr)(int) ); // L.5: prototipo de escribe int main() { // ====================== char (* fptr) (int); // M.1: declara fptr puntero-a-funcin fptr = auxa; // M.2: inicia fptr printf("La letra%2d es una: %c\n", 5, auxa(5)); escribe (5, fptr); // M.4: llamada a escribe return (0); } char auxa (int num) { char * str = "Hola mundo"; return str[num]; } // Definicin de auxa // L.16

void escribe (int pos, char (* fpointer)(int) ) { // definicin de escribe char ch ; printf ("Estoy recibiendo la letra: %c\n", auxa(pos)); ch = (* fpointer)(6); printf ("Estoy inventando la letra: %c\n", ch); } Salida:

La letra 5 es una: m Estoy recibiendo la letra: m Estoy inventando la letra: u Comentario: La funcin auxa recibe un entero y devuelve un carcter. El puntero str a la matriz de caracteres "Hola mundo" es una variable local de esta funcin. El lector observador, percibir que el carcter devuelto en L.16, se obtiene utilizando una notacin de puntero con subndice, algo extrao a la luz de la expuesto hasta ahora. En realidad tambin podramos haber puesto: return *(str+num); // L.16-bis

Al tratar de las matrices ( 4.3.2) justificaremos la notacin de subndices ms compacta. Por el momento el lector puede aceptar la que le resulte ms cmoda. Tambin observar que en este caso hemos suprimido el parntesis en el valor devuelto por no ser realmente necesario. return (str[num]) == return str[num] La funcin escribe recibe dos parmetros: un entero, y un puntero-a-funcin aceptando un entero y devolviendo un carcter. Su prototipo est en L.5, y su definicin en L.18. Observe que esta funcin proporciona dos salidas. La primera de ellas es una invocacin a printf parecida a la realizada en M.3, en la que el segundo argumento es tambin el resultado de una invocacin directa a auxa. La segunda se realiza tambin mediante un printf, pero en este caso, se utiliza directamente un char como segundo argumento, que es proporcionado por la variable local ch. El punto interesante aqu es que su valor se ha obtenido mediante una invocacin a auxa a travs de un puntero. El programa comienza declarando fptr, un puntero-a-funcin que acepta un entero y devuelve un char (M.1). A continuacin se inicia de forma que seale a la funcin auxa (que cumple con las condiciones exigidas por el puntero). En M.3 se escribe la primera salida. Observe que el tercer argumento de printf es el valor devuelto por una invocacin a auxa pasndole un 5 como argumento. En este contexto las sentencias auxa(n); (* fpointer)(n); fpointer(n); son equivalentes. En M.4 se realiza una invocacin a escribe utilizando como segundo argumento es el puntero fptr definido en las lneas anteriores. Esta invocacin es responsable de la segunda y tercera salidas.

4.3 Matrices

1 Sinopsis Desde el punto de vista del programa, una matriz (array vector) es una zona de almacenamiento contiguo, que contiene una serie de elementos del mismo tipo, los elementos de la matriz [1]. Desde el punto de vista lgico podemos considerarlas como un conjunto de elementos ordenados en fila. As pues, en principio todas las matrices son de una dimensin, la dimensin principal, pero veremos que los elementos de esta fila pueden ser a su vez matrices (un proceso que puede ser recursivo), lo que nos permite hablar de la existencia de matrices multi-dimensionales, aunque las ms fciles de "ver" o imaginar son las de dos y tres dimensiones. Nota: aunque en C/C++ los conjuntos ordenados de elementos del mismo tipo se denomina matrices (arreglos), la idea aparece en otros lenguajes bajo distintos nombres. Por ejemplo, vector; lista ("list") o secuencia ("sequence"). En cualquier caso, no confundirlas (las matrices) con los conjuntos de pares nombre/valor, que existen en otros lenguajes bajo los nombres de diccionarios ("dictionarys"); tablas hash ("hash tables"); listas indexadas ("keyed lists") o matrices asociativas ("associative arrays"), pero que como tales, no existen en C++; aunque la Librera Estndar s dispone de tales estructuras ( 5.1.1a). Como advertencia para los lectores que han utilizado matrices en otros lenguajes, sealar que quizs el aspecto ms significativo del manejo de matrices en C++, es que el compilador desconoce su tamao, de forma que el programador debe adoptar precauciones para no salir de sus lmites, ya que el compilador permite referenciar elementos inexistentes, ms all del final de la matriz, con el consiguiente riesgo de error. En este captulo abordaremos todo lo necesario para manejarlas en C++.

2 Comentario Puede afirmarse que las matrices son un recurso de programacin simple y socorrido; en realidad pueden considerarse como las "estructuras" de datos ( 1.8) ms simples que cabe imaginar (todos los elementos del mismo tipo). Presentan la ventaja de que sus elementos son rpidamente accesibles, en especial si utiliza punteros en vez de subndices, pero presentan una notable limitacin: son de tamao fijo; es preciso definir su tamao desde el principio y no pueden ser fcilmente incrementadas o disminuidas sino mediante complejos procesos de copia. Estas estructuras de datos son adecuadas para situaciones en las que el acceso a los datos se realice de forma aleatoria e impredecible. Por el contrario, si los elementos pueden estar ordenados y se va a utilizar acceso secuencial sera ms adecuado utilizar una lista ( 1.8). Las matrices C++ se consideran tipos complejos ( 2.2), y se alojan en zonas de memoria contiguas, aunque tendremos ocasin de ver ( 4.3.6) que C++ permite definir unas seudomatrices que en realidad no se almacenan de esta forma. Los programadores que hayan utilizado lenguajes con matrices dinmicas (incluso multi-dimensionales), sentirn una especial frustracin con las limitaciones de las matrices C++. Sin embargo, el C++ Estndar ofrece en su Librera ( 5) alternativas interesantes a las matrices; en especial las clases vector, string (adaptada al manejo de cadenas de caracteres), list, deque y valarray. Esta ltima especialmente optimizada para el manejo de matrices numricas. Antes de abordar cualquier proyecto medianamente importante en el que se requiera el uso intensivo de matrices, aconsejamos vivamente evaluar las posibilidades que ofrecen al respecto las estructuras de la Librera Estndar, en especial si se trata de matrices cuyo tamao deba cambiar, o no pueda ser conocido en tiempo de compilacin.

NOTA De un tiempo a esta parte, quizs desde que presto atencin al tema, encuentro que prcticamente todos los libros de informtica que consulto, hablan de "arreglos" cuando se refieren a "arrays". Incluyendo no solo los traducidos del ingls (caso muy frecuente). Tambin los escritos originariamente en espaol, incluyendo los de escritores supuestamente espaoles (profesores de alguna Universidad espaola). La verdad es que no entiendo muy bien el porqu. Aunque los anglosajones han utilizado siempre "arrays" para este significado, no es menos verdad que los espaoles hemos utilizado siempre "matrices" para referirnos a ellas, tanto en obras propias como en traducciones. Como todava no he tirado los libros, puedo poneros algunos ejemplos: "lgebra Moderna" de A. Lentin y J. Rivadud. Versin Espaola Editorial Aguilar 1967 (es una traduccin del original francs). "Mecnica Racional" Carlos Mataix (este si es espaol: Catedrtico de la Escuela Tcnica Superior de Ingenieros Industriales de Madrid). "Programacin Lineal" Robert W. Llewellyn (traduccin del original ingls). Edit. Marcombo S.A. 1968. Alguno puede argumentarme que son algo antiguos, y que el lenguaje ha cambiado desde entonces. La verdad es que respeto cualquier opinin sensata, pero no creo que haya cambiado tanto (de ser as no me entendera con mis compatriotas). Sospecho que tal vez sea una peculiaridad del espaol de Amrica, y que los editores obliguen a los autores a utilizar determinadas formas de lenguaje con el fin de que sus libros tengan un mercado ms amplio; aunque tambin pudiera ser el resultado de esa estpida moda de lo "Polticamente correcto" que obliga a llamar "morenos" a los negros. Sea cual sea la razn, permitidme que siga refirindome a los "arrays" como "matrices" (as me lo ensearon). Decir otra cosa me costara tanto trabajo como llamar guagua al autobs.

4.3.1 Declaracin de matrices


1 Sintaxis La declaracin de matrices sigue la siguiente sintaxis: tipoX etiqueta [<expr-const>] tipoX es el tipo de los elementos que sern almacenados en la matriz. Puede ser cualquier type-id ( 2.2) vlido a excepcin de void y de funciones (no pueden existir matrices de funciones, pero s de punteros-a-funcin). etiqueta es el identificador <expr-const>: una expresin cuyo resultado debe ser una constante entera positiva n distinta de cero, que es el nmero de elementos de la matriz. Los elementos estn numerados desde 0 hasta n-1. Nota: no confundir el valor n con el hecho de que los elementos estn numerados de 0 a n-1. Por ejemplo, el elemento a[9] es el ltimo de la matriz definida como: int a[10];. Esta "extraa" ocurrencia es herencia del C clsico y una costumbre muy extendida en la informtica (empezar a contar por el cero); aunque se habita uno a ella rpidamente, la verdad es que resulta un poco extraa al principio [3].

Ejemplos: int a[10]; char ch[10] char* p[10] struct St mst[10] // // // // declara dem de dem de dem de una matriz de 10 elementos enteros 10 elementos char 10 elementos puntero-a-carcter 10 elementos estructuras tipo St

Observe en esta ltima sentencia, que los elementos no tienen porqu ser necesariamente tipos simples (preconstruidos en el lenguaje). Tambin pueden ser tipos abstractos ( 2.2a). Incluso su declaracin puede realizarse en la misma sentencia que declara la matriz. Por ejemplo: struc St { int orden; char* plato; char* descripc; } mst[10]; Esta sentencia declara una matriz mst de 10 estructuras tipo St; ambas se declaran en la misma sentencia. La exigencia de que el resultado de <expr-const> sea un valor constante, es de la mayor trascendencia para entender las limitaciones de las matrices C++. Significa que el tamao de la matriz debe ser conocida en tiempo de compilacin. Por ejemplo, no es posible algo como: unsigned int size; ... char matriz[size]; en su lugar debe hacerse: const unsigned int size = 10; ... char matriz[size]; // Ok. pero entonces es preferible establecer directamente: char matriz[10]; o mejor an: #define ASIZE 10 ... char matriz[ASIZE]; // Ok.

// Error!!

// Ok.

En general, cuando se necesitan matrices que cambien de tamao en "runtime", suele recurrirse a crearlas en el montn mediante el operador new[] ( 4.9.20c) que s permite definir su tamao en funcin de una variable. Por ejemplo: unsigned int size; ... char* mptr = new char[size];

// Ok.

En este caso las matrices no son referenciadas directamente, sino a travs de un puntero, y una vez creadas tampoco es posible cambiar su tamao. El recurso utilizado cuando se necesita cambiar este, es crear otra matriz del tamao adecuado; copiar en su caso los miembros de la antigua a la nueva; borrar la antigua (liberar el espacio asignado), y finalmente, asignar el puntero a la nueva matriz. De esta forma la ilusin del usuario es que realmente se ha cambiado el tamao de la matriz, aunque la realidad subyacente sea muy diferente. Otro recurso, utilizado cuando la matriz "puede" crecer pero no se est muy seguro, es crearla con un tamao inicial algo mayor que lo necesario (por ejemplo un 25%). En estos casos se dispone de cierto espacio de reserva antes que sea necesario proceder a un proceso de copia total. No obstante lo anterior, en C-99 s es lcita la declaracin de matrices de longitud variable ("variable length arrays") que es como se conoce a las que pueden definir su tamao en runtime. Es decir, en este entorno estn permitidas declaraciones del tipo unsigned int size; char matriz[size];

// Ok

Con objeto de garantizar la compatibilidad con el cdigo C existente, algunos compiladores permiten la definicin de estas matrices cuando son de naturaleza automtica (definidas en el interior de una funcin). Es el caso del compilador GNU gcc http://gcc.gnu.org/. Sin embargo, es un recurso que debe evitarse, ya que el cdigo resultante no resulta portable.

2 Como el resto de las variables, la declaracin de matrices puede estar acompaada de los correspondientes especificadores de almacenamiento ( 4.1.8), aunque por su propia naturaleza, con los actuales procesadores no tiene sentido declararlas variables de registro. Ejemplo: extern a1[]; static int a2[2]; register char a3[20]; auto char a4[3]; // // // // L.1: Ok.! Ok.! No tiene sentido!! Ok.!

Nota: por una larga tradicin de C, en algunos compiladores la lnea 1 equivale a extern int a1[], sin embargo en algn compilador puede dar error porque el ANSI C++ prohbe la declaracin de objetos sin la especificacin de tipo.

3 Acceso a elementos Existen dos formas de acceder a los elementos de una matriz: mediante subndices y mediante punteros. En cuanto a la primera, el empleo de subndices es la forma que podemos llamar "cannica". Por ejemplo a[i] representa el elemento i+1avo. Recuerde que la numeracin empieza por 0, de forma que a[1] es en realidad el segundo elemento ( 4.9.16 Operador de elemento de matriz). El acceso a elementos mediante punteros, se detalla en el apartado siguiente ( 4.3.2). Los elementos de la matriz del primer ejemplo se referencian mediante: a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8] y a[9] La numeracin de subndices de una matriz de n elementos es de 0 a n-1

4 En ciertos contextos, el primer declarador de una matriz puede no contener una <expresin> dentro de las llaves, por ejemplo: int array[]; Este tipo de matriz, de tamao indeterminado, resulta aceptable en situaciones donde el tamao no es necesario para reservar espacio, pero siguen siendo necesarias las llaves para indicar que la variable es una matriz. Por ejemplo, una declaracin extern de una matriz no necesita el tamao exacto de la misma; alguna otra declaracin tampoco la necesita, porque el tamao est implcito en el declarador. Por ejemplo: char arr[] = "AEIOU"; // declara matriz de 6 elementos [1] char arr[] = {'A','E','I','O','U'}; // declara matriz de 5 elementos

En otros casos, como en la definicin de parmetros de funciones, no es necesario especificar el tamao de la matriz a referenciada por un puntero. Por ejemplo: int func(char* ptr[]); este prototipo indica que func recibe como argumento una matriz-de-punteros-a-carcter, sin especificar nada sobre su tamao; las llaves [ ] son necesarias para avisar a la funcin que se trata de una matriz de punteros, ya que la expresin: int func(char* ptr); indicara simplemente puntero-a-carcter. Nota: como una extensin especial del ANSI, C++Builder tambin permite una matriz de tamao indefinido como el miembro final de una estructura. Dicha matriz no incrementa el tamao de la estructura, excepto que puede aadirse relleno para asegurar que la matriz est correctamente alineada. Estas estructuras se utilizan normalmente en asignacin dinmica, y debe aadirse explcitamente el tamao actual de la matriz al tamao de la estructura para la asignacin del espacio.

5 Inicializacin En ocasiones la declaracin puede incluir una inicializacin de la matriz como en los siguientes ejemplos: cons int an[5] = {1, 2, 3, 4, 5}; char ak[5] = {'a','e','i','o','u'}; int ax[6] = {1,2,3,4}; char c[2] = {'1','2','3'}; char c[2] = "AEIOU"; char ac[5] = "AEIOU"; char ac[6] = "AEIOU"; int ai[] = {1,2,3,4,5}; char as[] = "AEI"; // // // // // // // // // L.1: L.2: L.3: L.4: L.5: L.6: L.7: L.8: L.9:

== {1,2,3,4,0,0} Error! Error! Ok. == {'A','E','I','O','U','\0'} == {'a','e','i','\0'}

Cuando el tamao sealado es menor que la lista de inicio se produce un error (L.4). Si el tamao es mayor, se rellenan los espacios sobrantes con ceros (L.3). Nota: algunos de estos comportamientos son especficos del compilador y pueden variar segn el entorno. Por ejemplo, la asignacin L.6 es perfectamente vlida en Borland C++ 5.5 para Windows, mientras que da error en el compilador GNU Cpp para Linux; la razn es que este ltimo considera que una cadena del tipo "AEIOU" incluye implcitamente el carcter "\0" de fin de cadena ( 4.3.4) incluso en estos casos de inicializacin de matrices (lo normal es que el terminador sea considerado cuando se trata de asignacin a punteros). Como puede verse en L.8 y L.9, si se incluye una inicializacin de en la misma sentencia, excepcionalmente puede omitirse el tamao en la declaracin. La razn es que el compilador puede deducir lo contando los elementos de la lista de inicio. Las cinco expresiones que siguen son igualmente vlidas y definen st como matriz de caracteres de 6 elementos (el ltimo elemento de la primera matriz es distinto al resto): char char char char char st[] = "AEIOU" // define la matriz "AEIOU\0" st[6] = "AEIOU\""; st[6] = {'A','E','I','O','U','\"'}; st[6] = {'A','E','I','O','U','"'}; st[6] = {'A','E','I','O','U',34};

Una expresin como: char st[4]= "AEIOU"; produce un error: Too many initializers, en cambio: char st[7]= "AEIOU"; no produce error; el espacio sobrante se llena de nulos (2 posiciones en este caso). 5.1 Un ejemplo ejecutable con una inicializacin ms compleja #include <iostream> using namespace std; #define DIMENSION ((int) (sizeof mst / sizeof mst [0])) struct St { int orden; char* plato; char* desc; } mst [] = { 1, "Entrada", "Sopa juliana", 2, "Segundo", "Filete empanado", 3, "Postre ", "Tarta al Whisky", }; int main() { // ========== for (int i = 0 ; i < DIMENSION; i++) cout << mst[i].orden << " " << mst[i].plato << " " << mst[i].desc << endl; return 0; } Salida

1 2 3

Entrada Segundo Postre

Sopa juliana Filete empanado Tarta al Whisky

Comentario Observe que, en la sentencia de salida, la forma de sealar los distintos miembros de cada elemento de la matriz, es congruente con el hecho de que cada elemento es una estructura.

5.2 C++ no tiene operadores para manejar matrices (ni cadenas de caracteres) como una sola unidad, solo quizs en la inicializacin como hemos visto. Por esta razn, aunque es posible establecer expresiones como: char ac[5]="AEIOU"; sin embargo, las sentencias (asignaciones) segunda y tercera que siguen son ilegales. char ac[5]; // L.1: declaracin. ac[5] = "AEIOU"; // L.2: Ilegal!! ac = "AEIOU"; // L.3: Ilegal!!

L.2 produce el error [2]: Cannot convert 'char *' to 'char'. L.3 produce: Lvalue required. La explicacin es que en L.1 se declara ac como array de 5 caracteres; en L.2 el compilador crea una matriz de 6 caracteres "AEIOU", la almacena en algn sitio, e intenta asignar la direccin del primer carcter (un puntero char*) al elemento ac[5] que es (debe ser) un char, para ello debe hacer una conversin de tipo ( 4.9.9) de char* a char, operacin ilegal que es sealada por el compilador. En L.3 las cosas ocurren lo mismo, solo que en ltima instancia, se intenta asignar el puntero char* al nemnico ac. Como veremos en 4.3.2, en la mayora de los casos, ac sera interpretado automticamente por el compilador como la direccin del primer elemento de la matriz, pero en este caso no es as, lo que nos seala el compilador es que en el lado izquierdo (receptor) de la asignacin, debe haber un Lvalue ( 2.1.5) es decir, una direccin de memoria donde comenzar el almacenamiento; direccin que puede ser expresada directamente por un escalar (que represente una direccin de memoria) o una expresin que pueda traducirse a una direccin. En este caso no es lo uno ni lo otro; el compilador interpreta ac simplemente como un nemnico que no tiene Lvalue (zona de almacenamiento). En cambio, las expresiones siguientes son vlidas: char* ac; ac = "AEIOU"; // L.4: // L.5: Ok!!

La explicacin, es que aqu L.4 declara ac como puntero a carcter (sin iniciar); en L.5 el compilador crea la cadena de caracteres "AEIOU\0", la almacena en algn sitio como matriz de 6 caracteres constantes, y asigna al puntero ac la direccin del primer elemento de dicha matriz, lo que es perfectamente legal.

En ocasiones, una asignacin como la anterior puede estar enmascarada y no ser fcilmente

reconocible, con lo que podra interpretarse que la expresin L.3 que sigue compila sin error: ... void func (char ac[]) { ac = "AEIOU"; ... }

es legal. Por ejemplo, el caso

la explicacin que en este caso no se produzca error en la asignacin, es que el compilador considera que el argumento ac es un puntero-a-char ( 4.3.2), con lo que estaramos en el caso L.4/L.5. La veracidad de las afirmaciones anteriores puede ponerse de manifiesto con un sencillo programita de prueba: #include <iostream.h> #include <typeinfo.h> // Prueba de asignacin

void func(char ar[]) { ar = "AEIOU"; cout << ar << endl; cout << "ar es del tipo: " << typeid(ar).name() << endl; // L.7: } int main(void) { // =============== char arr[5] = {'1','2','3','4','5'}; func(arr); return 0; } Salida (ver 4.9.14, para una explicacin de L.7):

AEIOU ar es del tipo: char *

4.3.2 Punteros y matrices


Nota: las indicaciones sobre subndices contenidas en este captulo se refieren a las matrices definidas con los tipos bsicos (preconstruidos en el lenguaje), no a los tipos abstractos, en los que el operador subndice [ ] puede tener un significado distinto.

1 Posicin de un elemento Asumiendo que los elementos de una matriz se almacenan de forma contigua y que z es el tamao en bytes de un elemento tipoX, el desplazamiento d en bytes respecto del comienzo, del elemento a[i] de una matriz tipoX a[n];, viene determinado por: Desplazamiento (en bytes) d = posicin i tamao del elemento z Siendo el tamao del elemento: z = sizeof(tipoX).

Nota: recuerde que i tiene el valor 0 para el primer elemento. Sin embargo, hemos visto que la aritmtica de punteros ( 4.2.2) incluye automticamente el tamao del elemento. Por tanto, para manejar las matrices a travs de punteros no es preciso referirse a las posiciones absolutas d de sus elementos (en bytes desde el comienzo de la matriz), sino en unidades tipoX. Es decir, a efectos de los punteros, la posicin pi (respecto al comienzo) del elemento a[i] es simplemente pi = i. Al primer elemento, a[0], de una matriz de dimensin m le corresponde la posicin 0 y al ltimo la posicin m-1. El significado es que para alcanzar el primer elemento desde el principio el desplazamiento es cero, para el segundo el desplazamiento es un elemento, dos para el tercero, ... y m-1 para el ltimo.

2 Acceso a elementos mediante punteros Por definicin, excepto cuando es operando de los operadores de referencia & ( 4.2.3) o sizeof ( 4.9.13), el identificador de una matriz es considerado un puntero a su primer elemento [2]. Esto tiene importantes implicaciones prcticas, y es el secreto para comprender la mecnica de matrices; de punteros, y de las cadenas de caracteres en C++ (un tipo especial de matrices). Por ejemplo, cuando una matriz es pasada como argumento a una funcin, el identificador es considerado como un puntero al primer elemento, as que coloquialmente suele decirse: "las matrices se pasan por referencia"; por supuesto, dentro de la funcin llamada, el argumento aparece como una variable local tipo puntero. Podramos decir que el nemnico de una matriz C++ encierra una dualidad. En momentos puede ser considerado como representante de una matriz. Por ejemplo, cuando lo utilizamos con la notacin de subndices o en el operador sizeof. En otros casos adquiere la personalidad de puntero. Por ejemplo, cuando utilizamos con l el lgebra de punteros (en cierta forma, me recuerda la famosa dualidad onda-partcula de la luz que estudiamos en bachiller). Esta dualidad hace que a efectos prcticos (salvo las excepciones ya comentadas), el identificador de una matriz sea sinnimo de la direccin de su primer elemento; de modo que si m es una matriz de elementos tipoX y pm un puntero-a-tipoX, la asignacin pm = &m[0]; puede tambin ser expresada como pm = m;, o lo que es lo mismo, para la mayora de los casos prcticos se puede establecer que: pm == &m[0] == m Es decir: m == &m[0] *m == m[0] 2b 2a

En el siguiente ejemplo se muestra como el nombre de la matriz puede ser tomado directamente como sinnimo de puntero al primer elemento. int a[5] = {1,2,3,4,5}; printf("Posicion 0 = %1i\n", *a); printf("Posicion 3 = %1i\n", *(a+3)); salida:

Posicion 0 = 1 Posicion 3 = 4

Como vemos a continuacin, es lo mismo pasar como argumento de una funcin: 1. Meramente el nombre de la matriz 2. La direccin del primer elemento 3. Un puntero al primer elemento Las tres alternativas producen el mismo resultado. char a[11] = "Hola mundo", char* ptr = &a[0]; char* s = "Hola mundo"; printf("%s\n", a); // printf("%s\n", &a[0]); // printf("%s\n", ptr); // printf("%s\n", s); //

1 2 3 3

Nota: recuerde que cuando se trata de representar una cadena (%s) printf espera recibir como argumento un puntero al primer elemento de la misma (consulte el manual de su compilador respecto de esta funcin de la librera clsica C/C++). Las cuatro funciones producen la misma salida, en todas ellas printf imprime desde la primera posicin hasta alcanzar el primer nulo, que es interpretado como fin de cadena. El ltimo es interpretado como no imprimible y automticamente descartado. Obsrvese que en la primera lnea hemos hecho deliberadamente la dimensin de la matriz mayor que la cadena en 1 elemento para que el compilador incluya al final el carcter nulo ( 4.3.1). En cambio, en la definicin de constantes de cadena (tercera lnea) el compilador incluye por su cuenta el carcter de final ( 3.2.3f).

2.2 En el ejemplo siguiente, adems de demostrarse lo anterior, se comprueba como la funcin func supone que el argumento recibido es un puntero, y como tal lo trata en printf(). #include <stdio.h> void func(char* ptr);

// prototipo

void main() { char a[5] = "AEIOU", *ptr = &a[0]; func(a); // Las tres func(ptr); // llamadas son func(&a[0]); // equivalentes } void func(char* arr) { // definicin int i; for (i=0; i<5; i++) { printf("arr[%1i] =%5.0d =%2c\n", i, *arr, *arr); arr++; }

return; } Salida para las tres funciones: arr[0] arr[1] arr[2] arr[3] arr[4] = = = = = 65 69 73 79 85 = = = = = A E I O U

2.3 La dualidad matriz puntero tambin se pone de manifiesto en este sencillo ejemplo: #include <iostream.h> void fun(char*, int); int main() { char* arr = new char[5]; fun(arr, 5); delete[] arr; return 0; } // L.3 // ============ // L.5 // L.6

void fun (char a[], int x) { // L.9 a[x] = static_cast<char>(x+65); // L.10 cout << a[x] << endl; } Observe que aparentemente la declaracin de fun en su prototipo de L.3 y su definicin en L.9 no coinciden, a pesar de lo cual, no se obtiene ninguna protesta por parte del compilador. As mismo, en L.5 se define arr como puntero-a-char y se pasa (L.6) a fun como argumento, aunque es aceptada por esta ltima como matriz de caracteres (char a[]), y utilizada como tal con la correspondiente notacin de subndices en L.10 y L.11 sin que exista tampoco protesta por parte del compilador. Observe que en L.10 se suma 65 al valor x, con los compiladores que estamos utilizando, se trata de valor ASCII 65 + 5 == 70 ( 2.2.1a), con lo que esta lnea equivale a: a[x] = 'F'; // L.10-bis

2.4 Es posible pasar a una funcin parte de una matriz, es decir, un puntero a un elemento distinto del primero. En el ejemplo anterior se podra haber puesto (ambas son equivalentes): func(&a[2]); func(a+2); Compruebe el lector que en este caso sera necesario modificar la definicin de la funcin func para garantizar que el bucle no pretenda escribir fuera del mbito de la matriz, una posible solucin seria cambiar la condicin del bucle: void func(char* arr) { // definicin

int i; for (i=0; *arr != 0; i++) { printf("arr[%1i] =%5.0d =%2c\n", i, *arr, *arr); arr++; } return; }

Siguiendo con la equivalencia enunciada (2a pm == &m[0] == m

):

Si sumamos un entero i al primer miembro, la equivalencia debe mantenerse, con lo que: pm+i == (&m[0])+i == m+i sabemos por la aritmtica de punteros que el segundo miembro equivale a: &m[i], con lo que: pm+i == &m[i] == m+i 2.5a

aplicar el operador de indireccin * a ambos lados: *(pm+i) == *(&m[i]) == *(m+i) es decir: *(pm+i) == m[i] == *(m+i) 2.5.b

De todas estas relaciones podemos deducir algunas consecuencias importantes: m[i] == *(m+i) 2.5c

En efecto; segn K&R [1] "Al evaluar m[i], C la convierte inmediatamente en *(m+i); las dos formas son equivalentes". Puede probarse que tambin es cierto en C++ con un sencillo ejemplo: int dia[] = {1,2,3,4,5,6,7}; pirntf("dia: = %2i\n", dia[2] ); printf("dia: = %2i\n", *(dia+2) );

// -> dia = 3 // -> dia = 3

Precisamente, el operador de elemento de matriz [ <exp1>[exp2] *((exp1) + (exp2))

](

4.9.16) se define como:

donde exp1 es un puntero y exp2 es un entero, o exp1 es un entero y exp2 es un puntero. La expresin &m[i] == m+i (2.5a Evidentemente (2a ) se cumple que: ) es la direccin del i-avo elemento de m.

&m[0] == &m == m == m+0

2.8 Puesto que C++ es consistente y regular en cuanto a su tratamiento de la aritmtica de punteros, de la misma forma que (por definicin) se ha establecido que m[0] es equivalente a *m, y que en consecuencia m[i] es equivalente a *(m+i), tambin se ha establecido la relacin simtrica, esto es: que si pm es un puntero al primer elemento de la matriz m, pm[0] sea equivalente a *pm, y que pm[i] sea equivalente a *(pm+i). Todo esto puede ser expresado con otras palabras: Aplicar subndices a un puntero-a-matriz equivale a aplicarle el operador de indireccin ( 4.9.11a) es decir, devolver el objeto sealado: pm[0] == *pm 2.8a 4.9.11)

Se puede aplicar a los punteros la notacin de subndices: (Ver ejemplo pm[i] == *(pm+i) 2.8b

2.9 Recordemos las expresiones de quivalencia 2.5b *(pm+i) == m[i] == *(m+i) Aadiendo la expresin 2.8b se obtiene:

*(pm+i) == m[i] == *(m+i) == pm[i] La equivalencia entre el segundo y ltimo trmino: m[i] == pm[i] puede enunciarse: una expresin de puntero con subndices es equivalente a una matriz con subndices. Puede ver un ejemplo avanzado de punteros y matrices en: "Punteros-a-miembros de clases" ( 4.2.1g1).

3 La forma de calcular el tamao de una matriz en tiempo de ejecucin es: sizeof ar/sizeof ar[0] // tamao total / tamao del primer elemento

Puede utilizarse una directiva de preprocesador. Por ejemplo: # define SIZEMAT(x) sizeof x/sizeof x[0] Nota: observe que cuando, como en este caso, el identificador de una matriz se utiliza como operando de sizeof ( 4.9.13), es de las pocas veces en que se refiere a la totalidad de la matriz, y no es considerada como puntero a su primer elemento .

4.3.3 Almacenamiento de matrices


1 Sinopsis Puesto que los registros no son adecuados para el albergarlas, las matrices pueden alojarse en el segmento, el montn o la pila ( 2.2.6). El sitio concreto depende de las caractersticas de la matriz como variable, es decir, que sea esttica o dinmica, y dentro de estas ltimas que sea de naturaleza automtica (almacenada en la pila) o persistente (almacenada en el montn o en el segmento).

2 En general las matrices se declaran directamente mediante una expresin del tipo: int m1[10]; char m2[10]; double m3[10]; int* m4[10]; void (* m5[10])(char); // // // // // matriz matriz matriz matriz matriz de de de de de 10 10 10 10 10 enteros char doubles punteros a entero punteros a funcin

En estos casos, aunque no se trata propiamente de definiciones ( 4.1.2), el compilador tiene suficiente informacin para deducir el espacio necesario segn el tipo y nmero de elementos (dimensin) de la matriz, de forma que puede reservar una zona de memoria contigua. En el caso anterior se reservaran los siguientes espacios ( 2.2.4): m1 10 * 32 == 320 bits m2 10 * 8 == 80 bits m3 10 * 64 == 640 bits m4 10 * 32 == 320 bits m5 10 * 32 == 320 bits

En la figura 1 se muestra un esquema de una de estas zonas, que es accedida a travs del identificador, por ejemplo, m1, que hemos visto ( 4.3.2) que representa un puntero al comienzo. O mediante expresiones de subndices, como m1[i], que son traducidos por el compilador a una direccin teniendo en cuenta la del primer elemento y el desplazamiento adicional adecuado: m1 == &m1[0] m1[i] == *(m1+i)

El resultado de declaraciones como las anteriores (2 ) es una variable del tipo matriz de n elementos de tipoX con un Lvalue ( 2.1.5). Salvo indicacin en contrario, o que la declaracin est fuera de cualquier bloque o funcin, ser un objeto de tipo automtico alojado en la pila.

3 Creacin de matrices persistentes Cuando se desea crear una matriz persistente, el procedimiento anterior no es adecuado. Para crear una matriz de naturaleza no automtica caben dos opciones: Declararla esttica, con lo que el compilador se encarga de crearla en el segmento de datos. Crearla en el montn, ya que estos objetos tambin tienen carcter persistente. Como veremos a continuacin (4 ), esta alternativa puede realizarse de dos formas.

Resumimos la situacin general mediante un sencillo ejemplo: #include <iostream.h> int m1[3] = {1,2,3}; void func(); int main () { func(); }

// L.2: // ========

void func() { static int m2[3] = {3,4,5}; // L.8: static int* m3 = new int[3]; // L.9: m3[0] = 1; m3[1] = 2; m3[2] = 3; int m4[3] = {1,2,3}; } Comentario: La matriz m1 es de naturaleza esttica, ya que es global al fichero ( 2.2.6). La especificacin de tipo de almacenamiento est implcita en la propia situacin de la declaracin en L.2, fuera de cualquier funcin. La matriz m2 es esttica por la utilizacin explcita del especificador static en L.8; el compilador se encarga de reservar espacio adecuado en elsegmento de datos. La matriz conserva sus valores entre posibles invocaciones sucesivas a func. m3 es una alternativa a la anterior. Se declara un puntero-a-int de naturaleza esttica; este puntero es almacenado en el segmento, por lo que conservar su valor entre llamadas sucesivas a la funcin. A su vez este puntero seala a un espacio en el montn; espacio que ha sido reservado mediante el operador new, as que los valores almacenados sern tambin persistentes. Nota: el ejemplo es mramente didctico y no sera operativo. Existe una diferencia adicional importante entre las opciones L.8/L.9. En L.8 el espacio es reservado una sola vez por el compilador, y es iniciado una sola vez por el mdulo de inicio. En cambio, la opcin L.9 reservara un nuevo espacio con cada sucesiva invocacin a la funcin. La matriz m4 es de naturaleza automtica, se crear en la pila y ser iniciada y destruida con cada invocacin a func.

4 Dejando aparte la declaracin esttica en sus dos versiones, implcita (L.2) o explcita (L.8), para crear una matriz en el montn existen dos procedimientos, segn el tipo de funciones utilizadas. El que denominaremos "clsico", que utiliza las funciones calloc / free [1] y el "moderno" que utilizara los operadores new / delete respectivamente. En ambos casos la referencia al elemento creado se realiza mediante un puntero.

4.1 Para crear la matriz int m1[10] en el montn segn el sistema moderno, se utilizara la expresin: int* m1; m1 = new int[10]; La consecuencia de estas sentencias es que se declara un puntero-a-int denominado m1; esta variable se crea en la pila (es automtica), y a este Lvalue se le asigna el valor devuelto por el operador new ( 4.9.20) que es la direccin de un espacio de memoria reservado en el montn.

El resultado sera la situacin de memoria esquematizada en la figura 3, con la diferencia respecto al caso de las matrices estticas anteriores (L.2 y L.8 ), de que ahora existen dos objetos: un puntero en la pila, y un espacio annimo en el montn (accesible mediante el puntero). El inconveniente es que hay que acordarse de desasignar la zona de memoria del montn cuando resulte innecesaria [2]. Esto se realizara con la sentencia: delete[] m1;

4.2 Procedimiento para crear la misma matriz segn el sistema clsico: int* m1 = (int*) calloc(10, sizeof(int));

Tambin en este caso m1 se declara como puntero-a-int, y se le asigna el valor devuelto por la funcin calloc; el resultado sera idntico al anterior. Observe que en este caso se necesita un modelado ("casting" 4.9.9) antes de la asignacin a m1, porque el valor devuelto por calloces un puntero-a-void, y no se puede asignar directamente a un puntero-a-int. La versin actualizada del modelado sera: int* m1 = static_cast<int*>( calloc(10, sizeof(int)) );

4.2.1 Como se comprueba a continuacin, aunque se haya definido en trminos de puntero-aint, nada impide considerar a m1 como una autntica matriz a todos los efectos (puede aplicrsele la notacin de subndices):

#include <iostream.h> int main() { // ============= int* m = new int[5]; for (int i = 0; i <5 ; i++) { m[i] = i+10; cout << "m[" << i << "] == " << m[i] << endl; } delete[] m; return 0; } Salida: m[0] m[1] m[2] m[3] m[4] == == == == == 10 11 12 13 14

4.3 No obstante lo anterior, considere los sorpresivos resultados del siguiente programa que crea tres matrices, m1, m2 y m3 utilizando los tres mtodos sealados anteriormente: #include <iostream> using namespace std; void main() { // ============= int m1[5]; int* m2 = new int[5]; int* m3 = static_cast<int*>( calloc(5, sizeof(int)) ); cout << typeid(m1).name() << " tamao: " << sizeof(m1) << endl; cout << typeid(m2).name() << " tamao: " << sizeof(m2) << endl; cout << typeid(m3).name() << " tamao: " << sizeof(m3) << endl; m1[4] = cout << cout << cout << 3; m2[4] "m1[" << "m2[" << "m3[" << = 4 4 4 3; << << << m3[4] "] == "] == "] == = " " " 3; // L.10: << m1[4] << endl; << m2[4] << endl; << m3[4] << endl;

delete[] m2; free(m3); } Salida: int[5] tamao: 20 int * tamao: 4 int * tamao: 4 m1[4] == 3 m2[4] == 3 m3[4] == 3

// L.15 // L.16

Comentario: La primera observacin es que m1 se ha creado en la pila, mientras que m2 y m3 estn en el montn. Observe en L.15 y L.16 la distinta forma de recusar el espacio asignado para estas ltimas. La segunda observacin es que tanto m1 como m2 y m3, se comportan como matrices; en L.10 y siguientes aceptan la notacin de subndices. Sin embargo, observe que, mientras que el operador typeid ( 4.9.14) nos informa que m1 es efectivamente una matriz del tipo y tamao esperados, en cambio m2 y m3 siguen siendo consideradas por el compilador como tales punteros y su tamao es el correspondiente. En estas condiciones la pregunta es inevitable: Que ha pasado con las matrices m2 y m3? Como pueden almacenarse 5 enteros en 4 Bytes?. La respuesta est en que en ambos casos se han creado dos variables tipo puntero-a-int en la pila !!. Se trata de variables automticas del tipo indicado y tamao 4 Bytes (como todos los punteros), que responden a los nemnicos m2 y m3. Simultneamente se ha reservado en el montn dos zonas de memoria de 20 Bytes; sus direcciones se han asignado a los punteros m2 y m3, con lo que deben ser accedidas siempre a travs de ellos. El resultado se esquematiza en la figura 2.

Observe que aunque podemos utilizarlas como si se tratara de autnticas "matrices", para el compilador existen diferencias abismales. Mientras que m1 es una variable automtica de tipo matriz-deenteros; m2 y m3 son variables automticas de tipo puntero-a-int. El hecho que sealen a un espacio persistente en el montn, adecuado para almacenar 5 enteros es circunstancial; incluso puede realizarse una nueva asignacin sobre ellas. Por ejemplo: int x = 5; m2 = &x; m2 sealara ahora a una zona de almacenamiento en la pila. El nico problema es que el bloque de memoria reservado en el montn quedara irremediablemente perdido. La liberacin de estas zonas de memoria, que se realiza en L.15 y L.16, no significa la destruccin de las variables m2 y m3 (de hecho podran seguir siendo utilizadas para otros usos); su destruccin efectiva se realiza cuando salga de mbito el bloque en que han sido definidas (en este caso la funcin main).

4.3.4 Matrices alfanumricas


1 Sinopsis Un tipo muy comn de matrices son las cadenas alfanumricas, tambin llamadas cadenas literales ("Strings literals"). Son matrices de caracteres constantes y clase de almacenamiento static ( 4.1.8c) en las que el ltimo elemento es un nulo, ASCII 0, "\0" ( 3.2.3e). Este carcter es indicativo del fin de la cadena. De lo apuntado se desprende que las tales cadenas alfanumricas combinan las caractersticas de las matrices y de las constantes, por lo que suelen disponer de un apartado especial en la literatura especializada, justo en el captulo de las constantes, bajo el epgrafe: constantes de cadena ( 3.2.3f), captulo al que le remitimos para ms amplia informacin sobre el particular.

2 Definicin Su definicin suele ser un tanto especial, y suele hacerse en el mismo punto que la declaracin. Es una expresin del tipo: char* string = "Hola mundo"; // 3.a

Su acceso se realiza igualmente mediante punteros. Ejemplo: cout << "Saludo: " << string << endl;

Una expresin como 3.a , significa que el compilador crea la cadena como una matriz de caracteres contiguos (incluyendo su carcter de terminacin); la almacena en algn sitio (adecuado para las variables estticas), crea un puntero a carcter (en este caso es la variable string), y lo hace igual a la direccin del primer elemento de la matriz (el carcter 'H' en este caso).

Tambin pueden ser declaradas utilizando el especificador de matriz (de hecho son matrices). Por ejemplo: #include <iostream> using namespace std; void main() { // ================ static char szAppName[] = "Nombre-programa"; // M1 int dim = sizeof(szAppName)/sizeof(szAppName[0]); cout << szAppName << endl; cout << "Es una matriz de " << dim << " elementos" << endl; } Salida: Nombre-programa Es una matriz de 16 elementos

Nota: observe que desde el punto de vista de la gramtica C++, la inicalizacin de la matriz szAppName en M1 es una autntica rareza. Un caso excepcional que no sigue las pautas generales de inicializacin de matrices que se ha mantenido en C++ por compatibilidad con C.

3 Recapitulacin Segn se desprende de lo anterior, en C++ coexisten dos arquitecturas distintas para las cadenas alfanumricas ("Strings"). La primera, heredada de C y mantenida por compatibilidad, a cuyos miembros hemos denominado constantes de cadena ( 3.2.3f), utiliza un carcter nulo de terminacin. La segunda es la descrita en este captulo, cuyos miembros hemos considerado matrices alfanumricas que no utilizan elemento de terminacin (es responsabilidad del programador saber donde terminan). Esta dualidad puede confundir al principiante, y aunque la utilizacin de ambos tipos puede solaparse (generalmente es indiferente usar unas u otras), las tcnicas para su manejo son diferentes; tanto para acceder a su contenido como para determinar su longitud o para establecer cuando la cadena est vaca. En consecuencia hay que ser cuidadoso y tener claros los conceptos, puesto que lo ms probable es que en un mismo programa coexistan ambos tipos de "strings". Las funciones de la librera clsica C++ ( 5) destinadas al manejo de "strings" presuponen que se utilizarn constantes de cadena, y generalmente las utilizan a travs de un puntero a su primer elemento (la propia rutina se encarga de encontrar el final de la cadena). Por su parte, las rutinas de la STL utilizan objetos-cadena (generalmente instancias de la clase string) cuya representacin interna es una matriz alfanumrica.

4.3.5 Matrices de punteros


1 Presentacin Como cualquier otra variable, los punteros (del mismo tipo) pueden estar contenidos en matrices; las declaraciones tienen la forma: int* iptr[10] char* cptr[10]; void (* fptr[10])(char); // matriz de 10 punteros a entero // matriz de 10 punteros a carcter // matriz de 10 punteros a funcin...

Con estas declaraciones, iptr[i] es un puntero a entero, y cptr[j] un puntero a carcter, mientras que *iptr[i] es el int al que apunta el elemento i de la matriz. Del mismo modo, *cptr[j] es el char apuntado por el elemento j. En el tercer caso, fptr es una matriz de 10 punteros a funciones que reciben un char y devuelven void. Es decir, funciones del tipo: void funcion (char ch) { /* ... */ }

1.1 No confundir una matriz de punteros con un puntero a matriz. Observe como los parntesis cambian totalmente el significado de las sentencias ( 6.1): int* pt1[10] int (*pt2)[10] int (* pt3[10])(); // matriz de 10 punteros a entero // puntero a matriz de 10 enteros // matriz de 10 punteros a funcin

Nota: la funcin main (argc, agrv) ( 4.4.4) presenta un primer ejemplo de ambos casos: "Puntero a matriz de punteros" y "Matriz de punteros". En concreto, el argumento argv es un puntero a una matriz de punteros.

2 Inicializacin Es interesante considerar que una matriz de punteros puede definirse directamente en la propia declaracin. Consideremos el siguiente caso, que define un matriz de cuatro punteros a matrices de caracteres: char* est[] = {"Primavera", "Verano", "Otoo", "Invierno"}; El proceso sera como sigue: el compilador crea cuatro cadenas de caracteres de 10, 7, 6 y 9 caracteres respectivamente y las almacena en algn sitio (pueden estar contiguas o no). Tambin crea un matriz de 4 punteros a carcter con nemnico est; sus elementos est[0] a est[3] (que si son contiguos) son las direcciones de los caracteres "P", "V", "O" e "I" respectivamente.

3 El ejemplo que sigue define un matriz de dos punteros a funcin: void f1(int); void f2(int); void (* apt[])(int) = {f1, f2}; // L.1: // L.2: // L.3:

Las lneas L.1 y L.2 no requieren comentario, se trata simplemente de los prototipos de dos funciones ( 4.4.1). L.3 define apt como una matriz de punteros-a-funcin que reciben un int y devuelven void (exactamente del tipo de las funciones f1 y f1declaradas en las lneas anteriores). Esta lnea tambin inicia la matriz apt con dos valores. Observe como los nombres de las funciones se han tomado como sinnimo de sus "direcciones", y que no ha sido necesario declarar el tamao de la matriz, el compilador lo deduce (dos elementos) por el Rvalue ( 2.1.5) de la propia inicializacin.

4 Tambin es importante sealar que para que sus direcciones puedan almacenarse en una matriz, todas las funciones deben recibir los mismos argumentos y devolver el mismo valor, lo que nos deja que deben diferenciarse forzosamente en sus nombres (f1 y f2 en este caso), adems del las diferencias que pueda haber en sus cuerpos.

5 Las matrices de punteros a funciones ( 4.2.4) son especialmente interesantes. Este tipo de matrices representan tablas de funciones, con la que se puede manejar alternativas; en lugar de utilizar las clsicas sentencias del tipo if... then else ( 4.10.2), se ejecuta una u otra funcin en base a una variable. Esta forma de codificacin se denomina "cdigo dirigido por tablas" ( 4.2.4b Ejemplo 3).

6 Tema relacionado Matrices de estructuras ( 4.5.6).

4.3.6 Matrices de matrices


1 Sinopsis: Las matrices de matrices (matrices multidimensionales), son simplemente matrices cuyos elementos son otras matrices. C++ no dispone de elementos incluidos en el propio lenguaje para manejar matrices [2]; mucho menos matrices multidimensionales. Sin embargo, la Librera Estndar ( 5) proporciona potentes recursos para manejo de vectores y matrices. En concreto, existen una serie de clases predefinidas para este fin: vector, list, valarray, string, etc., dotadas de interfaces muy completas y cmodas para el programador.

El propio creador del C++ ( TC++PL C.7.2 ) reconoce que el manejo de matrices multidimensionales "a pelo", tal como estn implementadas en el lenguaje, son una importante fuente de errores y confusin, especialmente para el principiante. Aparte de una posible pesadilla para futuros mantenedores de tales programas, a no ser que estn cuidadosamente documentados. En consecuencia, aconseja utilizar en lo posible los recursos ofrecidos por la Librera Estndar. No obstante lo anterior, incluso para manejar las Libreras, es imprescindible un mnimo conocimiento de las capacidades incluidas en el lenguaje: "cmo considera en realidad C++ las matrices multidimensionales"; aspecto este al que dedicamos el resto del epgrafe.

2 Declaracin Las matrices multidimensionales son matrices de matrices (matrices cuyos elementos son matrices): int dias[2][12] = { {31,28,31,30,31,30,31,31,30,31,30,31}, {31,29,31,30,31,30,31,31,30,31,30,31} }; char lista[5][4] = { "Cat", "Bar", "Cab", "Lab", "Tab" };

3 Sintaxis: La forma de declararlas es mediante una serie de expresiones entre corchetes [1]: tipoX etiqueta [<expr-c1>][<expr-c2>]...[exp-cn]

Ejemplo: int ai[2][3+1][6]; Comentario El resultado de las expresiones <expr-c> representan el tamao de cada submatriz, y deben reducirse a enteros positivos (unsigned int) distintos de cero. Cada submatriz es una "dimensin" de la matriz total. En el caso de matrices de dos dimensiones, <expr-c1> y <expr-c2> se denominan respectivamente filas y columnas. En el caso de ms de dos dimensiones, la expresin <expr-cn> se denomina sencillamente "dimensin-n". El nmero total de elementos de una matriz de n dimensiones es el producto de los valores de todas las dimensiones. En el ejemplo anterior, la matriz ai tiene 2 x 4 x 6 = 48 elementos.

4 Acceso mediante subndices Lo mismo que en el caso de matrices unidimensionales, existen dos formas para acceder los elementos de matrices multidimensionales: mediantesubndices y mediante punteros. En cuanto a la primera, la forma de designar un elemento es: a[f][c], el primer subndice indica la fila y el segundo la columna. Los elementos se almacenan por filas, de forma que el subndice de la derecha (de columna) vara ms rpidamente si los elementos se recorren en orden de almacenamiento. En realidad, una matriz bidimensional como dias, es para el compilador una matriz de una sola fila (dimensin) de elementos contiguos, en la que cada elemento es a su vez una matriz. En este caso una matriz de dos elementos, cada uno de los cuales es a su vez matriz de 12 elementos. El razonamiento puede hacerse recursivo, de forma que una matriz de n dimensiones es una matriz de una sola fila, cada uno de cuyos x1elementos es a su vez una matriz de una fila de x2 elementos, cada uno de los cuales... Si aplicamos este razonamiento a una matriz m de enteros de tres dimensiones: int m[X][Y][Z]; Es la declaracin de la matriz; m[a][b][c] Es un elemento de la matriz (en este caso un int) m Es el identificador de la matriz. Para algunos efectos puede ser considerado identificativo de la matriz como un todo. Para otros efectos puede ser considerado como un puntero que seala al elemento m[0][0][0]: m == &m[0][0][0] m[0][0][0] Es el primer elemento de la matriz m[X-1][Y-1][Z-1] Es el ltimo elemento.

5 Posicin de un elemento Al tratar de punteros y matrices de una dimensin ( 4.3.2), vimos que la posicin Pi (contada en elementos respecto al comienzo) del elementom[ i ] de una matriz de elementos tipoX es simplemente Pi = i.

En caso de una matriz bidimensional m[A][B], la posicin Pa,b respecto del comienzo del elemento m[a][b] viene determinado por: Pa,b = ( a * B ) + b. Es decir: (fila x total de columnas) + columna. En el caso del ejemplo anterior, el elemento dias[1][1] (nmero 29) est en la posicin: P1,1 = (1 * 12)+ 1 == 13. Lo que significa que, para alcanzar el almacenamiento del nmero 29, desde la posicin inicial (nmero 31), hay que desplazarse internamente en la memoria el espacio correspondiente a 13 int. (ver demostracin 4.3.6w1). Por su parte, el elemento lista[3][0] (carcter 'L'), est en la posicin P3,0 = (3 * 4) + 0 == 12 desde el carcter inicial 'C'. Recuerde que cada submatriz de lista es una cadena de 4 elementos, el ltimo es el terminador de cadena "\0" (no representado).

El razonamiento puede ser extendido a la posicin Pa,b,c..z de cualquier elemento m[a][b][c]...[z] de una matriz multidimensional m[A][B][C]...[Z], siendo sus dimensiones A, B, C... Z: Pa,b,c...z == a (BCDE.. Z) + b (CDE...Z) + d (DE...Z) + .... + z 5.a

Nota: el punto (aunque apenas visible) representa aqu el smbolo de multiplicar. Los trminos BCDE...Z representan productos de las dimensiones de la matriz.

5.1 Una observacin importante es que la posicin de un elemento no viene influenciada por el valor de la primera dimensin A (que no aparece en la frmula), lo que tiene cierta trascendencia cuando se trata de pasar matrices como argumento de funciones ( 4.3.8). Desde este punto de vista, una matriz m1 definida como: int m1[2][4][3]; tiene 2 x 4 x 3 = 24 elementos tipo int, cada uno de los cuales ocupa 4 bytes. La matriz ocupa 24 x 4 = 96 bytes ( 2.2.4). Su almacenamiento se esquematiza en la figura 1 (en su interior se ha representado la posicin del elementom1[1][2][0]). La posicin de cualquier elemento m1[a][b][c] viene determinada por la expresin: Pa,b,c == a * (4*3) + b (3) + c == a * 12 + b * 3 + c El elemento m1[0][0][0] est en la posicin P0,0,0 == 0 * 12 + 0 * 3 + 0 == 0. El elemento m1 [1][2][0] en la posicinP1,2,0 == 1 * 12 + 2 * 3 + 0 == 18, y el ltimo elemento, m1[1][3][2] en la posicin P1,3,2 == 1 * 12 + 3 * 3 + 2 == 23

6 Acceso a elementos mediante punteros El operador de elemento de matriz [ ] ( <exp1>[exp2] 4.9.16) se define como:

*((exp1) + (exp2))

donde exp1 es un puntero y exp2 es un entero, o exp1 es un entero y exp2 es un puntero. Segn esta definicin la expresin del elemento a de una matriz m, m[a] sera equivalente a la expresin *(m + a).

6.1 Hemos sealado que en una matriz bidimensional m[A][B], cada elemento m[a] de la primera (y en realidad nica fila), es una matriz m1de B elementos, en la que el elemento m1[b] sera equivalente a *(m1 + b). Sustituyendo en esta ltima frmula m1 por su equivalente, se tiene: m1[b] m[a][b] *( *( m + a ) + b ) 4.9.16). De

El razonamiento puede extenderse a matrices de cualquier nmero de dimensiones ( forma que en general, se tiene la siguiente serie de equivalencias: Matriz de una dimensin: m[a] == *( m + a ) Matriz de 2 dimensiones: m[a][b] == *( *( m + a ) + b ) Matriz de 3 dimensiones: m[a][b][c] == *( *( *( m + a ) + b ) + c ) Matriz de 4 dimensiones: m[a][b][c][d] == *( *( *( *( m + a ) + b ) + c )+ d) etc. Ejemplo: char m[][5] = {{'a','e','i','o','u'},{'A','E','I','O','U'}}; cout << "Caracter " << m[1][3]; // -> Caracter O cout << "Caracter " << *(*(m+1)+3); // -> Caracter O

int dia[][5][2] = { {{ 0, 1},{ 10, 11},{ 20, 21},{ 30, 31},{ 40, 41}}, {{100,101},{110,111},{120,121},{130,131},{140,141}} }; int a =0, b=2, c= 0; cout << "dia [0,2,0] = " << *(*(*(dia+a)+b)+c); // -> dia [0,2,0] = 20 a= 0; b=3; c=1; cout << "dia [0,3,1] = " << *(*(*(dia+a)+b)+c); // -> dia [0,3,1] = 31 a =1; b=2; c= 0; cout << "dia [1,2,0] = " << *(*(*(dia+a)+b)+c); // -> dia [1,2,0] = 120

6.2 Si nos referimos al primer elemento de una matriz de una, dos, tres, etc. dimensiones, las expresiones anteriores se reducen a: Matriz de una dimensin: m[0] == *( m + 0 ) == *m Matriz de 2 dimensiones: m[0][0] == *( *( m + 0 ) + 0 ) == **m Matriz de 3 dimensiones: m[0][0][0] == *( *( *( m + 0 ) + 0 ) + 0 ) == ***m Matriz de 4 dimensiones: m[0][0][0][0] == *( *( *( *( m + 0 ) + 0 ) + 0 )+ 0) == ****m etc.

6.3 Si aplicamos el operador de referencia & ( 4.9.11) a cada lado de las relaciones de equivalencia anteriores, y tenemos en cuenta que la direccin del primer elemento &m[0] es igual a la direccin de la matriz &m, y que los operadores &* se anulan entre s: Matriz de una dimensin: &m[0] == &m == m Matriz de 2 dimensiones: &m[0][0] == &m == *m Matriz de 3 dimensiones: &m[0][0][0] == &m == **m Matriz de 4 dimensiones: &m[0][0][0][0] == &m == ***m etc. La equivalencia 6.3a referida a matrices unidimensionales ya la habamos visto anteriormente ( 4.3.2) enunciada como: el identificador de una matriz es considerado un puntero a su primer elemento. Ejemplo: char m1[] = {'a','e','i','o','u'}; cout << "Caracter " << *m1; // -> Caracter a cout << "Caracter " << m1[0]; // -> Caracter a char m2[][5] = {{'a','e','i','o','u'},{'A','E','I','O','U'}}; cout << "Caracter " << **m2; // -> Caracter a cout << "Caracter " << m2[0][0]; // -> Caracter a 6.3a

6.4 La comprensin del significado de las relaciones 6.1 a 6.3, es de la mayor trascendencia para el manejo de matrices (especialmente multidimensionales). Pueden ser enunciadas de varias formas, aunque en el fondo todas son equivalentes. Por ejemplo, las relaciones 6.3 pueden ser expresadas diciendo: la direccin del primer elemento de una matriz puede obtenerse de la siguiente forma: Matrices unidimensionales: mediante su identificador m. Matrices bidimensionales: mediante la indireccin del identificador *m. Matrices tridimensionales: mediante la doble indireccin del identificador **m. Matrices n-dimensionales: mediante la n-1 indireccin del identificador.

6.5 Teniendo en cuenta que cualquiera que sean las dimensiones de una matriz, el tipo de la direccin a uno de sus elementos es tipoX* (puntero-a-tipoX), las equivalencias contenidas en los enunciados anteriores, pueden traducirse en afirmar que el tipo del identificador m de una matriz de objetos tipoX es: Matrices unidimensionales: tipoX* (puntero-a-tipoX). Matrices bidimensionales: tipoX** (puntero-a-puntero-a-tipoX). Matrices tridimensionales: tipoX*** (puntero-a-puntero-a-puntero-a-tipoX). etc. Expresado en otras palabras:

Declaracin tipoX m[a];

Descripcin de m

Tipo de m Descripcin del tipo Puntero-a-tipoX

Matriz unidimensional de objetos tipoX* tipoX Matriz bidimensional de objetos tipoX Matriz tridimensional de objetos tipoX tipoX**

tipoX m[a][b];

Puntero-a-puntero-tipoX

tipoX m[a][b][c]; Etc.

tipoX*** Puntero-a-puntero-a-punterotipoX

Ver comentario ms extenso respecto de estas afirmaciones: ( Inicio.

4.3.6a2)

[1] Las expresiones con comas, del tipo m[x, y, z], utilizadas en otros lenguajes para representar elementos de matrices, no estn permitidas en C++. [2] Nos referimos a matrices como un todo. En cambio si se dispone de instrumentos para manejo de "elementos" de matrices. Existen no obstante, algunas excepciones notables. Por ejemplo, algunos casos de inicializacin, donde son posibles sentencias del tipo: int m[5] = { 'A','E','I','O','U'}; Ver en pgina siguiente "Iniciacin conjunta"

4.3.7 Inicializacin indirecta de Matrices


1 Sinopsis: En este epgrafe continuamos la exposicin de la iniciacin indirecta de matrices iniciada en el apartado anterior. En concreto, detallamos aqu el caso en que la definicin no puede hacerse en una sola sentencia, lo que ocurre con las matrices que deben ser almacenadas en el montn, y cuyas dimensiones no se conocen en el momento de la declaracin. Hemos sealado que en estos casos, se define un puntero del tipo adecuado. Por ejemplo: char** a; // Ok. puede sealar a matriz de dos dimensiones!

A continuacin hay que reservar el espacio de almacenamiento, y finalmente, inicializar los miembros de forma individualizada. Para esto se requiere un poco de prctica y conocer ciertos trucos (cuyas claves se han apuntado antes) para no perderse en el proceso:

El identificador de una matriz se describe como un puntero. En particular, el identificador de matrices bidimensionales cuyo espacio de almacenamiento se localice en el montn, se describe como puntero-a-puntero (lo mismo para n dimensiones 4.3.6). Los elementos se almacenan por filas ( 4.3.6). Esta frase encierra el secreto de crear y destruir matrices multidimensionales sin necesidad de "verlas", dado que las de ms de tres dimensiones tienen una difcil representacin mental. Como veremos en los ejemplos, el proceso para crear una matriz de n dimensiones (x1, x2, ... xn) empieza por crear la primera fila; una matriz de una dimensin con x1elementos.

2 La creacin de matrices de una dimensin ya se trat en el epgrafe dedicado al almacenamiento de matrices ( 4.3.3); el razonamiento puede hacerse extensivo a matrices de n dimensiones. Por ejemplo, consideremos declarar matrices de tres dimensiones: void func() { int a1[2][4][3]; static int a2[2][4][3]; }

// L1: objeto de duracin automtica // L2: objeto persistente (esttico)

En L1 tenemos un objeto a1 de duracin automtica alojado en la pila; en L2 tenemos un objeto esttico a2 alojado en el segmento. Utilizaremos ahora el procedimiento de "C++ clsico" para crear una matriz ai de tres dimensiones en el montn. En primer lugar, recordaremos que desde ambas pticas (puntero matriz), se tiene:

ai: Puntero-a-puntero-a-puntero-a-int matriz de una fila de dos elementos (la llamaremos bi), cada uno de los cuales es una matriz. Traducido a cdigo: int*** ai = (int***) calloc(2, sizeof(int**)); // I: Traducido a lenguaje coloquial: ai es una variable automtica (4 bytes en la pila) de tipo puntero-apuntero-a-puntero-a-int, sealando un espacio bi (8 bytes en el montn) capaz para albergar dos punterosa-puntero-a-int. La situacin se ha esquematizado en la figura 1.

bi: Puntero-a-puntero-a-int matriz de una fila de cuatro elementos, cada uno de ellos es una matriz (la llamaremos ci) de una dimensin. Traducido a cdigo: int** bi = (int**) calloc(4, sizeof(int*)); // II:

Coloquialmente: bi es una variable automtica (4 bytes en la pila) de tipo puntero-a-puntero-a-int, sealando un espacio ci de 16 bytes en el montn, que es capaz para albergar 4 punteros-a-int.

ci: Puntero-a-int matriz de una fila de tres elementos, cada uno de los cuales: es un int. Traducido a cdigo: int* ci = (int*) calloc(3, sizeof(int)); // III:

En lenguaje coloquial: ci es una variable automtica (4 bytes en la pila) de tipo puntero-a-int, sealando un espacio ci de 12 bytes en el montn, capaz para albergar 3 int.

2.1 Poniendo las sentencias anteriores en forma de una secuencia ejecutable, resultara: int*** ai = (int***) calloc(2, sizeof(int**)); // L1: int i, j; for (i = 0; i < 2; ++i) { // L2: ai[i] = (int**) calloc(4, sizeof(int*)); for (j = 0; j < 3; ++j) // L3: ai[i][j] = (int*) calloc(3, sizeof(int)); } Comentario: El resultado de la ejecucin de la sentencia L1 es el esquematizado en verde claro en la figura 2 (fase 1). En este momento existe en la pila una variable automtica ai tipo int***que seala a un espacio bi de 8 bytes en el montn (suficiente para albergar 2 objetos tipo int**). Los 4 bytes de cada mitad pueden ser referenciados mediante ai[0] y ai[1 ]. El bucle L2 realiza dos asignaciones muy parecidas a L1. Los 8 bytes del montn anteriores son ocupados con las direcciones de dos nuevos espacios ci (tambin en el montn) de 16 bytes cada uno (suficientes para albergar 4 objetos tipo int*). El resultado es el sealado en azul claro (fase 2). Finalmente el bucle L3 crea cuatro espacios de 12 bytes cada uno (sealados en blanco), cuyas direcciones se asignan a los espacios cianteriores (fase 3 dibujada a la derecha en la figura).

Estos espacios son capaces de albergar un total de 3 x 8 = 24 enteros. Justamente los 24 elementos de esta matriz tridimensional. Es interesante resaltar que cuando se designa un elemento, desde ai[0][0][0] hasta ai[1][3][2], el compilador proporciona automticamente el valor almacenado en cada uno de los espacios. Tambin que los elementos no tienen porqu ser contiguos. Observe que el proceso de crear esta seudo-matriz de 24 enteros, ha supuesto la creacin de los siguientes objetos: En el montn: o 8 matrices de 3 int cada una (estas son las que contienen realmente la "carga til" de la matriz). o 2 matrices de cuatro punteros cada una. Sus miembros contienen las direcciones de las anteriores. El tipo de cada miembro es int*(seala al primer miembro de una de las matrices anteriores). o 1 matriz de dos punteros. Sus miembros contienen las direcciones de las dos anteriores, por lo que el tipo de sus miembros es puntero-a-puntero-a-int (int**). En la pila: o Un puntero que seala a un punto del montn (la matriz anterior). Realmente al primer miembro de esta matriz, por lo que su tipo es puntero-a-puntero-a-punteroa-int (int***).

3 Ejemplos Como caso concreto, se muestra la definicin indirecta de una matriz [2] de dos dimensiones matriz[m][n] en el montn. Se ha previsto para tres filas y cinco columnas matriz[3][5]; sus elementos son del tipo long double, pero puede ser adaptada fcilmente para aceptar entradas de usuario en tiempo de ejecucin, de forma que se acepten otros tamaos y/u otros tipos. Se presenta en dos versiones: la primera utiliza las funciones de librera clsicas de asignacin y liberacin de espacio (calloc y free). La segunda ms moderna , utiliza las nuevas funciones de librera (new y delete). Finalmente, a ttulo comparativo, se incluye una tercera versin que supone la definicin directa utilizando el nuevo operador new[] para matrices .

3.1 Versin C clsico El proceso es anlogo al esquematizado en la figura anterior aunque ms sencillo (carece de la tercera fase), ya que la matriz a crear es de dos dimensiones. Consistir en la creacin de los siguientes objetos: En el montn: o 3 matrices de 5 long double cada una, creadas en el bucle M4 (contienen los "datos" de la matriz). o 1 matriz 3 punteros (creada en M3). Cada uno de sus miembros seala al primer elemento de una de las matrices anteriores (la asignacin se realiza en M5), por lo que son de tipo long double*. En la pila:

Un puntero que seala al primer miembro de la matriz anterior, por lo que su tipo es puntero-a-puntero-a-long double (long double**).

#include <stdio.h> #include <stdlib.h> typedef long double TIPO; typedef TIPO** OBJETO; unsigned int fil = 3, col = 5; void des_asigna(OBJETO);

// Versin C clsico // para free & calloc // L3: // L5: // L6: prototipo

int main(void) { // =============== unsigned int i, j; OBJETO matriz; // M2: matriz = (OBJETO) calloc(fil, sizeof(TIPO*)); for (i = 0; i < fil; ++i) // M4: Establecer columnas matriz[i] = (TIPO*) calloc(col, sizeof(TIPO)); for (i = 0; i < fil; i++) for (j = 0; j < col; j++) matriz[i][j] = i + j; // M7: Iniciar elementos // con valores arbitrarios

for (i = 0; i < fil; ++i) { // M11: Mostrar elementos printf("\n"); for (j = 0; j < col; ++j) printf("%5.2Lf", matriz[i][j]); } des_asigna(matriz); // M16: Rehusar espacio return 0; } void des_asigna(OBJETO x) { unsigned int i; for (i = 0; i < fil; i++) free(x[i]); free(x); } El programa produce la siguiente salida: 0.00 1.00 2.00 3.00 4.00 1.00 2.00 3.00 4.00 5.00 2.00 3.00 4.00 5.00 6.00 Comentario El typedef de L3 establece el tipo de elementos que se guardarn en la matriz. Actualmente son del tipo long double, 80 bits en Borland C++ ( 2.2.4), pero puede ser cambiado fcilmente. El typedef de L4 establece una macro para definir un tipo de OBJETO que en realidad est definido como: OBJETO long double**. // liberar memoria // F2: // F3: // F4:

La sentencia M2 declara un objeto automtico matriz tipo OBJETO (long double**), puntero-apuntero-a-long double. M3 reserva espacio en memoria con calloc. Este espacio es suficiente para albergar fil objetos de tipo TIPO* (long double*); tres punteros-a-long double (podemos considerar que este espacio es una matriz de tres elementos). En este punto caben varias consideraciones adicionales: El valor devuelto por calloc (puntero-a-void) es promovido a puntero-a-puntero-a-long double utilizando una promocin "casting" ( 4.9.9) de estilo tradicional C antes de asignarlos al puntero matriz. En un programa para uso real, deberamos incluir entre M3 y M5 una comprobacin de que efectivamente calloc ha podido reservar la memoria solicitada. Por ejemplo, incluyendo una sentencia que comprobara que matriz != NULL [1]. En M5 se reserva memoria para contener col objetos tipo TIPO (long double). El proceso se realiza 3 veces, una para cada fila. Al final del bucle M4 se han reservado 3 espacios para 5 long double cada uno. Las direcciones de inicio de estos bloques se asignan a cada miembro de la matriz creada en M3. Podramos considerar que M5 crea tres matrices de 5 elementos cada una. Este conjunto de 15 elementos constituira la seudo-matriz m[3][5] cuyos miembros se inician con valores arbitrarios en el bucle M7. El bucle M11 es anlogo al anterior (un bucle de 5 iteraciones anidado en otro de 3), y se encarga de mostrar los valores asignados. M16 invoca a la funcin encargada de rehusar el espacio previamente asignado en M3 y M5. Observe que el proceso se realiza en orden inverso al de asignacin. El bucle F2 realiza la tarea inversa a la que se realiz en M4. F3 desasigna los espacios asignados en M5. A continuacin F4 deshace la asignacin realizada en M3. Finalmente el objeto matriz y el resto de objetos automticos sern destruidos en M17 al salir de main.

3.2 Versin C++ tradicional

#include <iostream.h> typedef long double TIPO; typedef TIPO** OBJETO; unsigned int fil = 3, col = 5; void des_asigna(OBJETO); int main(void) { unsigned int i, j; OBJETO matriz;

// Versin C++ moderno // L.3: // L.5: // L.6: prototipo // =============== // M2:

matriz = new TIPO* [fil]; for (i = 0; i < fil; ++i) matriz[i] = new TIPO [col]; for (i = 0; i < fil; i++) for (j = 0; j < col; j++) matriz[i][j] = i + j;

// M3: // M4: // M5: // M7: Inciar elementos // con valores arbitrarios

for (i = 0; i < fil; ++i) { // M11: Mostrar elementos cout << "\n"; for (j = 0; j < col; ++j) cout << matriz[i][j] << " "; } des_asigna(matriz); // M16: Rehusar espacio return 0; } void des_asigna(OBJETO x) { unsigned int i; for (i = 0; i < fil; i++) delete [] x[i]; delete [] x; } La salida: 0 1 2 3 4 1 2 3 4 5 2 3 4 5 6 Comentario: El proceso es exactamente anlogo al del ejemplo anterior. La nica diferencia est en la zona de asignar el espacio. En este caso se utiliza el operador new[] ( 4.9.20c) que devuelve un puntero del tipo adecuado, por lo que no es necesario efectuar ningn "casting" explcito en las asignaciones M3 y M5. Una versin muy parecida de este mismo ejemplo en 4.9.20c. // liberar memoria // F2: // F3: // F4:

3.3 Versin C++ moderna:

#include <iostream.h>

// Versin C++ moderno

typedef long double TIPO; // L.3: typedef long double (*OBJETO)[5]; unsigned int fil = 3, col = 5; // L.5: void des_asigna(OBJETO); // L.6: prototipo int main(void) { unsigned int i, j; OBJETO matriz; matriz = new TIPO [3][5]; // =============== // M3:

for (i = 0; i < fil; i++) for (j = 0; j < col; j++) matriz[i][j] = i + j;

// M7: Inciar elementos // con valores arbitrarios

for (i = 0; i < fil; ++i) { // M11: Mostrar elementos cout << "\n"; for (j = 0; j < col; ++j) cout << matriz[i][j] << " "; } des_asigna(matriz); // M16: Rehusar espacio return 0; } void des_asigna(OBJETO x) { delete [] x; } Comentario: La salida es exactamente igual que las dos anteriores. La secuencia M3, M4 y M5 del primer ejemplo se han reducido a una sola sentencia. Este es un caso de definicin directa en el que se crea una autntica matriz como un objeto nico, y con un espacio de almacenamiento definido y contiguo en el montn. Por contra, el puntero matriz sigue siendo un objeto automtico. Por lo dems, tanto la inicializacin de sus miembros (en el bucle M7) como la secuencia de mostrarlos (bucle M11), son exactamente anlogos a los casos anteriores. La funcin destinada a liberar la memoria asignada ha quedado reducida a una sola sentencia; la invocacin al operador delete[ ] para matrices acompaado del puntero adecuado. En realidad se podra haber suprimido totalmente esta funcin. Observe que no es necesario indicar delete[][], ya que el compilador ha aadido informacin suficiente para conocer que es una matriz bidimensional y el tamao total que deber ser rehusado ( 4.9.20c). // liberar memoria // F4:

4.3.8 Matrices como argumento de funciones


1 Observacin No confundir el paso de una matriz en su conjunto (multidimensional o no) como argumento de una funcin, con el paso de un elemento de una matriz. Ejemplo: int dias[2][12] = { {31,28,31,30,31,30,31,31,30,31,30,31}, {31,29,31,30,31,30,31,31,30,31,30,31} }; .... x = fun1(dias, f, c, d, e); // paso de matriz z = fun2(dias[f][c], d, e); // paso de elemento

2 Sinopsis

Cuando hay que pasar una matriz bidimensional como argumento a una funcin, la declaracin de parmetros formales de esta debe incluir el nmero de columnas, ya que la funcin receptora debe conoce la estructura interna de la matriz, para poder para acceder a sus elementos, y esto solo es posible informndole de su tipo y dimensiones. En el caso que nos ocupa las dimensiones son dos, por lo que la definicin de la funcin llamada sera algo as: func (int dias[2][12]) {...} // 2a

Observe que en la expresin anterior est incluida toda la informacin necesaria: nmero de filas, nmero de columnas y tamao de cada elemento (un int). Desde luego la funcin receptora necesita conocer tambin la direccin de inicio del almacenamiento, pero ya hemos sealado ( 4.3.2) que "el identificador de una matriz puede ser utilizado como un puntero a su primer elemento", con lo que si mentalmente sustituimos dias por un puntero al nmero 31 (primer elemento) de la primera matriz, la informacin pasada es completa.

3 Ya se ha sealado que C++ no contiene operadores para manejar matrices como una sola unidad ( 4.3.1). Al contrario de lo que ocurre en otros lenguajes (por ejemplo Java), en C++ es responsabilidad del programador no salirse del mbito de la matriz al acceder a sus elementos, lo que una vez aceptado, nos conduce a que en realidad es innecesario conocer la primera dimensin. En efecto, en nuestro caso, el primer elemento (primera sub-matriz), es accedido directamente mediante el puntero dias; su tamao ya lo conocemos (12 int), y con esta informacin es suficiente para acceder a cualquier otro elemento n. Solo es necesario desplazar el puntero los las posiciones de memoria correspondientes. La conclusin anterior estaba ya implcitamente establecida en ( 4.3.6), donde hemos visto que, en una matriz bidimensional m[F][C], la posicin Pf,c respecto del comienzo del elemento m[f][c] viene determinado por: Pf,c = ( C * f ) + c expresin que es independiente del valor F (nmero de filas). Como consecuencia, la definicin 2a podra ser del tipo: func (int dias[][12]) {...} // 3a

El primer parntesis vaco es necesario para colocar el segundo, que es el que interesa. La expresin es conceptualmente equivalente ( 4.3.2) a: func (int (*dias)[12]) {...} // 3b

que indica explcitamente el hecho de que el parmetro es un puntero-a-matriz de 12 int.

4 Comprobamos la veracidad de lo expuesto a un sencillo ejemplo: #include <stdio.h> void escribe(int a[][12], int x, int y); void escribo(int (*a)[12], int x, int y); void main () { // =============

// prototipo // prototipo

int dias[2][12] = { { 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12}, {13,14,15,16,17,18,19,20,21,22,23,24} }; escribe(dias, 1, 3); escribe(dias, 0, 7); escribo(dias, 1, 3); escribo(dias, 0, 7); } void escribe(int a[][12], int f, int c) { // definicin printf("Valor [%2i,%2i] = %3i\n", f, c, a[f][c]); } void escribo(int (*a)[12], int f, int c) { // definicin printf("Valor [%2i,%2i] = %3i\n", f, c, a[f][c]); } Salida: Valor Valor Valor Valor [ [ [ [ 1, 0, 1, 0, 3] 7] 3] 7] = 16 = 8 = 16 = 8

4.4 Funciones
1 Sinopsis Desde un punto de vista prctico, podemos decir que una funcin es una parte de un programa (subrutina) con un nombre, que puede ser invocada (llamada a ejecucin) desde otras partes tantas veces como se desee. Un bloque de cdigo que puede ser ejecutado como una unidad funcional. Opcionalmente puede recibir valores; se ejecuta y puede devolver un valor. Desde el punto de vista de la organizacin, podemos decir que una funcin es algo que permite un cierto orden en una maraa de algoritmos. Como resumen de lo anterior podemos concluir que el uso de funciones se justifica en dos palabras: organizacin y reutilizacin del cdigo. Desde este ltimo punto de vista (reutilizacin), puede decirse que son un primer paso de la programacin genrica ( 4.12), ya que representan un algoritmo parametrizado [2]. Una vez expuesta la definicin, sealemos que las funciones son la parte central de la programacin C++. Algunos lenguajes, como Pascal, distinguen entre procedimientos ("Procedures") y funciones. En C++ las funciones desempean ambos papeles, aunque en cierto modo, los ficheros C++ desempean algunas funcionalidades de lo que, en otros lenguajes como Modula-2, se denominan mdulos [3]. Otra diferencia substancial es que C++ no permite el anidamiento de funciones, es decir, definir funciones dentro de otras. En C++ todas las funciones se definen a nivel de fichero, con lo que tienen mbito global al fichero ( 4.1.3 mbito de funcin). Nota: existe una excepcin a esta regla; se refiere a las funciones miembro de las clases, que pueden ser declaradas y definidas dentro de las propias clases. Aunque las clases no son por supuesto funciones en el sentido estricto, si representan cierta compartimentacin de datos y procedimientos (un tipo de "mdulos"). Por ejemplo, en C++ es vlido el siguiente cdigo:

int i; class C { public: char* func1(void) { return p; } char* func2(void); char * p; }; ... C::char* func2(void) { ... }

// definicin de la clase // declaracin-definicin de func // declaracin de func2 // termina definicin de la clase // definicin de func2

2 Funciones dentro de clases En la jerga de la programacin orientada a objetos, las funciones dentro de las clases se denominan funciones-miembro o mtodos, y las variables dentro de clases, variablesmiembro o propiedades. El sentido es el mismo que en la programacin tradicional (la nomenclatura es ms una cuestin de gustos), si bien referirnos a "propiedades" y "mtodos" supone estar utilizando la programacin orientada a objetos y que nos referimos a miembros de una clase. En C++ esta aclaracin puede ser importante, porque es un lenguaje que podramos llamar "hbrido"; en ciertas partes puede utilizarse con tcnicas de programacin tradicional, y en otras con tcnicas de POO.

3 Una funcin de inicio Cada programa C++ debe tener una sola funcin externa denominada main(), principal ( 4.4.4), que desde la ptica del programador define el punto de entrada al programa. Las funciones se declaran en cabeceras (estndar o especficas de usuario) o dentro de los ficheros fuente. Estas declaraciones son denominadas prototipos. En ocasiones la declaracin y definicin se realiza en el mismo punto (como ocurre con las variables), aunque es normal colocar al principio del fuente los "prototipos" de las funciones que sern utilizadas en su interior, y las definiciones en cualquier otro sitio (generalmente al final). En el caso del ejemplo anterior, la declaracin y definicin de func1 se ha realizado en el mismo punto, mientras que la declaracin de func2 se realiza dentro del cuerpo de la clase y la definicin en el exterior de esta [1].

La forma general del prototipo de una funcin es: valor-devuelto nombre-funcin (lista-de-argumentos); La forma general de la definicin es: valor-devuelto nombre-funcin (lista-de-argumentos) { sentencias; // "cuerpo" de la funcin } Ejemplo: float cuadrado (float x); // prototipo float cuadrado (float x) { return x*x; } // definicin

La comunicacin entre el programa y las funciones que lo componen se realiza mediante los argumentos de llamada, los valores devueltos y las variables globales y externas.

4 El operador de invocacin a funcin En ocasiones, en especial al tratar la sobrecarga de operadores ( considerar una invocacin del tipo func(lista-de-argumentos); como un operador binario ( 4.9); el denominado operador de invocacin a funcin ( ) ( 4.9.16) que se aplica entre el primer argumentofunc y el segundo, lista-de-argumentos. En este sentido la invocacin anterior sera equivalente a: func()lista-de-argumentos. 4.9.18), es conveniente

4.4.1 Declaracin de funciones


1 Sinopsis La declaracin ( 4.1.2) da a conocer la funcin al compilador, de forma que a partir del punto de declaracin, ya se pueden realizar invocaciones a la misma. A su vez, la definicin estar en algn otro punto del programa, tal vez en una librera externa (en forma ya compilada) o en otro mdulo de programa (como texto fuente). Una funcin puede ser declarada varias veces en un mismo programa, y las declaraciones pueden aparecer en cualquier orden; en un fichero fuente o en varios, pero en cualquier caso antes de su uso, es decir: antes de cualquier invocacin a la funcin [5]. Adems de declarar el nombre de la funcin y el tipo devuelto (por defecto se supone int ) se declaran tambin el tipo de los parmetros.

2 Sintaxis: [extern] <tipo-devuelto> nombre-funcion () // 2a [extern] <tipo-devuelto> nombre-funcion (<tipo>, ...) // 2b [extern] <tipo-devuelto> nombre-funcion (<tipo> <parametro>, ... ) // 2c 2.1 Ejemplos: extern int funcion1 (); extern int funcion1 (void); funcion2 (char, int); int funcion2 (char, int); char funcion3 (char c, int i); // // // // // no acepta ningn argumento mejor que la anterior por defecto supone que devuelve int mejor que la anterior incluye nombres de parmetros

2.2 Comentario El especificador <tipo-devuelto> es opcional. Por defecto se supone int, as que las declaraciones que siguen son equivalentes [4]: int func (<tipo> <parmetro>, ...) func (<tipo> <parmetro>, ...) Nota: los compiladores MS Visual C++ y Borland C++ admiten que ciertos especificadores opcionales acompaen a la declaracin de funciones y otros objetos. Tales especificadores son de utilidad en circunstancias especficas ( 4.4.1b).

3 Declaraciones Recuerde que las declaraciones deben realizarse antes que cualquier uso de la funcin. A su vez, las definiciones pueden estar en cualquier sitio, aunque en algunos casos puede haber excepciones (sustitucin inline 4.4.6b). Las declaraciones de funciones tienen un nombre especfico: se denominan prototipo . El primero de los anteriores (2a ) es vlido, aunque desaconsejado (herencia del C); es el denominado estilo clsico Kernighan & Ritchie. El segundo (2b ) y tercero (2c ), son los aceptados en C++ [1]. Nota: es importante resaltar que en la declaracin de parmetros no est permitido incluir funciones, es decir, las funciones no pueden pasar como argumentos a otras funciones [6]. Sin embargo, C++ dispone de recursos cuando esto es necesario; pasar un puntero ( 4.2.4) o una referencia ( 4.2.3) a la funcin. El viejo estilo K&R tiene la desventaja de no permitir al compilador comprobar el nmero y tipo de los argumentos utilizados en las llamadas a la funcin. Este problema fue eliminado con la introduccin de los prototipos que utilizan la forma completa [2], en la que se especifica el nmero y tipo de cada argumento aceptado por la funcin. El compilador usa estos datos para comprobar la validez de las llamadas a la funcin y como se ilustra en el ejemplo, es capaz en su caso, de realizar dentro de ciertos lmites, un modelado de tipo ("Casting") de los argumentos para garantizar que coinciden con el tipo esperado. Nota: el mecanismo anterior permite al compilador efectuar una comprobacin de tipos de los argumentos que pasan y del valor devuelto. Los lenguajes en los que no se realizan estas comprobaciones, se denominan de dbilmente tipados ("Weakly typed"), tienen la desventaja de que no conocen exactamente el tipo de cdigo que ejecutarn.

Supongamos que se tiene el siguiente cdigo: extern long lmax(long v1, long v2); // prototipo funcion() { int limit = 32; char ch = 'A'; long mval; mval = lmax(limit, ch); // Llamada a la funcion }

Puesto que se dispone de un prototipo para la funcin lmax, este programa convierte los parmetros limit y ch a long (utilizando las reglas estndar de asignacin) antes de colocarlos en la pila para la llamada a lmax. Si no hubiese existido el prototipo, limit y ch hubieran sido puestos en la pila como entero y carcter respectivamente, en cuyo caso, los valores pasados a limit no hubieran coincidido con el tamao y/o contenido esperados por esta, originando problemas ( 4.4.6 Llamadas a funciones y conversin de argumentos).

3.1 La inclusin del especificador opcional extern, sirve para indicar al compilador que la definicin de la funcin se encuentra en otro fichero distinto del actual. En caso contrario dicha definicin debe encontrarse en algn lugar del fichero (si es que existen llamadas a dicha funcin). Es decir, si aparece el siguiente trozo de cdigo: int alfa (int deg, int min, int sec); // declaracin de alfa ... int gr; gr = alfa( x, y, z); // uso de alfa la definicin de alfa debe estar en algn sitio del fichero que contiene dichas instrucciones. En caso contrario, el compilador devolver un error:Unresolved external 'alfa()' referenced from ....OBJ. Es importante recordar que las funciones tienen mbito global, y que sus declaraciones (prototipos) aunque suelen estar al principio (inmediatamente despus de las directivas de preprocesado), pueden aparecer en cualquier parte del fichero. Ponerlas al principio tiene la ventaja de que sus nombres sean conocidos en la totalidad del fichero, con lo que pueden ser invocadas desde cualquier punto -desde cualquier otra funcin, incluso main- sin tener que declarar un prototipo dentro de cada funcin que las invoque.

4 Prototipos de funciones Los prototipos juegan un rol importante en la programacin C++ y sirven tambin para clarificar y documentar el cdigo. Sobre todo si los nombres de las variables son significativos. Por ejemplo, la funcin strcpy tiene dos parmetros: una cadena fuente y una destino, la cuestin es: Cual es cual? char *strcpy(char* dest, const char* source);

Si se incluye un identificador en el parmetro de un prototipo, solo es utilizado para los posibles mensajes de error relativos a tal parmetro sin ningn otro efecto. De hecho, los identificadores (nombres) de los parmetros suelen ser largos y descriptivos en los prototipos, mientras que en las definiciones suelen ser abreviados, sin que, en este aspecto, tengan que haber coincidencia entre ambos (ver reglas de mbito para los identificadores de parmetros en prototipos de funciones 4.1.3). Como puede deducirse de estas palabras, esto significa que en realidad los nombres de los argumentos no son imprescindibles en los prototipos; solo son necesarios los tipos de los parmetros. Es decir, el prototipo de la funcin anterior podra perfectamente ser sustituido por: char *strcpy(char*, const char*);

Un declarador de funcin con la palabra void entre parntesis: func(void);, indica que la funcin no acepta ningn parmetro. Es equivalente a la expresin como func();, que tambin declara una funcin sin parmetros.

4.1 Prototipos y ficheros de cabecera: Es costumbre que los prototipos de las funciones incluidas en las libreras del lenguaje se agrupen en ficheros especficos, los denominadosficheros de cabecera ("header files"), que son ficheros de texto (en realidad ficheros fuente 1.4) en los que se agrupan todas lasdeclaraciones utilizadas en la librera. Nota: en realidad, los ficheros de cabecera no solo incluyen los prototipos y declaraciones de las funciones, tambin las declaraciones de las estructuras, macros ( 4.9.10b) y clases ( 4.11.2a) utilizadas.

Por otra parte, tambin es frecuente que los programadores C++ construyan sus propias libreras que acompaan a las que vienen preconstruidas en el lenguaje. Para ello se agrupan en ciertos mdulos aquellas funciones o clases ms frecuentemente utilizadas. Estos mdulos son compilados y enlazados de una forma especial de forma que no se obtiene un ejecutable, sino una librera de las que existen varios tipos ( 1.4.4a). En cualquier caso, sean libreras preconstruidas en el lenguaje o de fabricacin propia, los prototipos de las funciones incluidas en ellas se agrupan en ficheros de cabecera. Las que vienen con el lenguaje se localizan en el directorio \Include; las de fabricacin propia se deben mantener en otro directorio distinto del anterior. Puesto que es imprescindible incluir en cada fichero fuente la declaracin de cada funcin antes de que pueda ser utilizada , el hecho de tener agrupadas las declaraciones en un fichero de cabecera es de gran utilidad, porque solo es preciso incluir una directiva include ( 4.9.10g) al principio de cada fuente para tener la seguridad de que todos los prototipos estarn presentes. De otro modo tendra que escribirlos manualmente en cada fuente que utilizara funciones de la librera. En la documentacin que acompaa a toda librera se indica siempre, junto con un ejemplo de uso, el nombre del fichero de cabecera que contiene los prototipos de las funciones utilizadas en la librera. Sin embargo, en ocasiones, cuando no se tiene a mano un buen manual de uso, o existe la sospecha de una errata en el mismo, puede ser til echar un vistazo al fichero de cabecera en que se incluye el prototipo de la funcin, ya que contiene bastante informacin sobre su uso: tipo y nmero de parmetros, valor devuelto, etc. Nota: se ha llegado a afirmar que los ficheros de cabecera contienen toda la informacin necesaria para usar las libreras de cualquier tipo. Aunque desde luego pueden ser de gran ayuda, la afirmacin es un poco exagerada. En lo que respecta a las funciones, los prototipos contienen en realidad toda la "gramtica" de su invocacin, pero poco o nada sobre la "funcionalidad".

5 Nmero variable de argumentos Normalmente los prototipos de funciones declaran un nmero fijo de parmetros (que puede ser ninguno). Para las funciones que pueden aceptar un nmero variable de parmetros (tales

como printf), el prototipo puede terminar en puntos suspensivos ( ...). Esta elipsis indica que la funcin puede ser invocada con diferentes tipos de argumentos en diferentes ocasiones. Los puntos pueden colocarse al final de una sublista de parmetros conocidos. Por ejemplo: func(int *count, long total, ...); Por supuesto esta forma de prototipo reduce la comprobacin que puede efectuar el compilador; los parmetros fijos son comprobados en tiempo de compilacin, y los variables son pasados sin comprobacin [3]. 6 Ejemplos A continuacin algunos ejemplos adicionales de prototipos y declaraciones de funciones. Obsrvese que, para mayor concisin, en todas ellas se han omitido los nombres de los parmetros formales (que como hemos indicado, son opcionales en los prototipos): f(); /* En C, sin datos sobre los parametros, el estilo clsico K&R. Devuelve int */ f(); // En C++, f no recibe argumentos. Devuelve int. int f(void); // f devuelve int, no recibe argumentos. int p(int,long); /* p devuelve int, acepta dos parmetros: El primero un int, el segundo un long */ int __pascal q(void); /* q funcin Pascal, devuelve int, no recibe parmetro */ int printf(char *format,...); /* Devuelve int; un parmetro fijo, puntero a carcter, despus cualquier nmero de argumentos adicionales de tipo desconocido */ char* f(int) // funcin que devuelve puntero a carcter, acepta un int. int* f(int) // funcin que devuelve puntero a int, acepta int. struct str f(int) // funcin que devuelve estructura str acepta un int. int (*f (int))(int); /* funcin que acepta un entero y devuelve un puntero a funcin que acepta un int y devuelve un entero */ int (*(*f())[10])(); /* funcin que no recibe argumentos, devuelve un puntero a un array de 10 punteros a funcin que devuelven enteros */ int f(struct S* Sptr); /* funcin que recibe como argumento un puntero a una estructura y devuelve int */ int (B::* getptr())(); /* funcin que no recibe argumentos, devuelve un puntero a funcin miembro de la clase B que no recibe argumentos y devuelve un int */

7 Polimorfismo Aparte de estas tareas de comprobacin y modelado de tipos, en realidad el objetivo principal de incluir en la declaracin de funciones una descripcin detallada del valor devuelto y de los parmetros aceptados, es permitir lo que se llama sobrecarga (de funciones). Esto significa que dentro del mismo mbito puedan definirse varias funciones con nombres idnticos, pero distintos parmetros y por supuesto distintas definiciones. Ms tarde, el compilador ser capaz de saber a cual de ellas nos estamos refiriendo, precisamente analizando los parmetros que pasamos a la funcin. Por ejemplo, en C++ est permitido el siguiente trozo de cdigo:

int alfa (int deg, int min, int sec); // declaracion-1 de alfa void alfa (int deg); // declaracion-2 de alfa int alfa (char n); // declaracion-3 de alfa ... n = alfa('c'); // invocacin de alfa-3 el compilador conoce que, en este caso, la invocacin se refiere a la tercera declaracin de la funcin alfa, precisamente por el argumento utilizado. Nota: las funciones main, WinMain y LibMain no pueden ser sobrecargadas. Ejemplo: extern "C" void WinMain(int, char*, char*); void WinMain(int, short, char*, char*);

// Error!!

Como veremos inmediatamente ( 4.4.1a), para saber que definicin ajusta mejor con los argumentos actuales, el compilador sigue una serie de reglas denominadas de congruencia estndar de argumentos.

7.1 Temas relacionados: Sobrecarga de funciones ( 4.4.1a) Polimorfismo de funciones-miembro ( 4.11.2a2). Sobrecarga de funciones genricas (plantillas) 4.12.1-2

4.4.1a Sobrecarga de funciones


1 Sinopsis: La sobrecarga de funciones, a la que nos hemos referido brevemente ( 4.4.1), es un mecanismo C++ que permite asignar el mismo nombre afunciones distintas. Para el compilador estas funciones no tienen nada en comn a excepcin del identificador, por lo que se trata en realidad de un recurso semntico del lenguaje que solo tiene sentido cuando se asigna el mismo nombre a funciones que realizan tareas similares en objetos diferentes. Por ejemplo, suponiendo que tuvisemos objetos que fuesen diversos tipos de polgono (tringulo, cuadrado, pentgono, crculo, etc), tendra sentido denominar getArea a todas las funciones que calculasen el rea de las diversas figuras, aunque naturalmente seran funciones distintas en cada caso. Tambin tendra sentido denominar open a las funciones que abrieran un fichero o una lnea de comunicacin. Al tratar de los operadores ( 4.9.18), veremos que para el compilador son en realidad funciones, cuyos nombres y sintaxis de invocacin son un tanto especiales y que la sobrecarga de estas funciones permite, por ejemplo, extender los conceptos de suma (+), asignacin (=) o identidad (==), a objetos distintos de los bsicos (preconstruidos en el lenguaje). El hecho de que acciones distintas pero conceptualmente semejantes, puedan ser representadas por el mismo operador (funcin), resulta a la postre una gran ayuda conceptual. Por ejemplo: puede hablarse de una "suma" de enteros y de una "suma" de complejos que sern gobernadas por versiones distintas del operador suma (+).

Hablando en sentido figurado, si suponemos que para el humano (programador) todas las funciones sobrecargadas son una sola (un solo nombre), entonces puede afirmarse que tal funcin es polimrfica, en el sentido de que el "mismo" mtodo o funcin tiene comportamiento distinto segn los casos [1]. Nota: al tratar de la compilacin, vimos que el mecanismo de "decoracin" o "planchado" de nombres ("Name mangling" 1.4.2) hace que a la postre, ni siguiera los nombres de las funciones "polimrficas" sean iguales. En realidad, el polimorfismo de funciones se presenta cuando estas son miembros de clases (mtodos) y en circunstancias muy especiales; cuando son declaradas virtuales ( 4.11.8a). Es en estos casos donde los tericos del lenguaje utilizan el trmino funcin polimrfica, dado que tales funciones son exactamente iguales en nombre, argumentos y valor devuelto, a pesar de lo cual pueden presentar comportamientos distintos [2].

2 Resolucin de sobrecarga Cuando se realiza la invocacin de una funcin sobrecargada, es decir que existen otras del mismo nombre en el mismo mbito , el compilador decide cual de ellas se utilizar mediante un proceso denominado resolucin de sobrecarga ("Overload resolution") aplicando ciertas reglas para verificar cual de las declaraciones se ajusta mejor al nmero y tipo de los argumentos utilizados. Es decir, donde existe mxima concordancia entre los argumentos actuales y formales ( 4.4.5). El proceso sigue unas reglas denominadas de congruencia estndar de argumentos; son las siguientes (en el orden precedencia sealado): 1. Concordancia exacta en nmero y tipo. Esta concordancia puede incluir conversiones triviales, por ejemplo, nombre de matriz a puntero ( 4.3.2); nombre de funcin a puntero a funcin ( 4.2.4a), y tipoX a const tipoX. 2. Concordancia despus de realizar promociones de los tipos asimilables a enteros ( 2.2.1), por ejemplo: char; short; bool; enum (y sus correspondientes versiones unsigned) a int. Tambin de tipos float a double [3]. 3. Concordancia despus de realizar conversiones estndar. Por ejemplo: int a double; double a long double; clase-derivada* a Superclase* ( 4.2.1f); tipoX* a void* ( 4.2.1d). 4. Concordancia despus de realizar conversiones definidas por el usuario ( 4.9.18k). 5. Concordancia usando la elipsis (...) en funciones con nmero variable de parmetros ( 4.4.1).

Si el anlisis conduce a que dos funciones distintas concuerdan al mismo nivel mximo, entonces se produce un error y la sentencia es rehusada por el compilador declarando que existe una ambigedad. Cuando las versiones sobrecargadas tienen dos o ms argumentos, se procede a obtener la mejor concordancia para cada uno utilizando las reglas anteriores. Si una funcin tiene mejor concordancia que las dems para un argumento y mejor o igual para los restantes, es invocada. En caso contrario la llamada se rehsa declarando ambigedad. El orden en que hayan sido declaradas las versiones sobrecargadas no influye para nada en la precedencia anterior.

Observe que en todo lo expuesto no se menciona para nada el valor devuelto. Esto significa que para que exista sobrecarga las funciones deben diferir en el tipo y/o nmero de argumentos. Por tanto, no es vlido que solo difieran en el tipo de valor devuelto (ver 3 ).

Temas relacionados: Conversiones aritmticas ( 2.2.5). Ejemplo comentado ( 4.9.18t1) Conversin de argumentos en las funciones genricas (plantillas Polimorfismo en funciones genricas (plantillas 4.12.1b) Bsqueda en el espacio de los argumentos ( 4.1.11c) Bsqueda de nombres ( Name-lookup).

4.12.1a)

3 Sobrecarga y valores devueltos Con objeto de que el mecanismo de sobrecarga sea independiente del contexto, los valores devueltos por las funciones no son tenidos en cuenta a efectos del mecanismo de sobrecarga. Esto tiene importantes consecuencias prcticas. Por ejemplo: int func (int, char); int func (float, char); void func (int, char); // L.1: // Ok versin sobrecargada // Error definicin duplicada con L.1

4 Sobrecarga y mbito de nombres Debe tener en cuenta que en C++ no existe el concepto de sobrecarga a travs de mbitos de nombres ( 4.1.11), por lo que las versiones sobrecargadas deben estar en el mismo mbito. Por ejemplo: #include <iostream.h> namespace UNO { // Subespacio exterior void func (int i) { cout << "func(int) " << endl; } namespace UNO_A { // Subespacio anidado void func (float f) { cout << "func(float) " << endl; } } } void main (void) { // ======= int x = 1; float f = 1.1; UNO::func(x); UNO::func(f); } Salida: func(int) func(int)

Comprobamos que en ambos casos no ha existido sobrecarga de la funcin a travs de los subespacios de nombres; en ambos casos se ha invocado la misma versin de func; la del subespacio exterior. Los mbitos de las clases derivadas no son una excepcin de esta regla general ( 4.11.2b2).

4.1 Cuando se precisa que el mecanismo de la sobrecarga funcione a travs de mbitos distintos, debe utilizarse la declaracin using ( 4.1.11c) o la directiva using ( 4.1.11c). Nota: cuando los mbitos se refieren a jerarquas de clases (las funciones a sobrecargar son mtodos), solo puede utilizarse la declaracin using ( 4.1.11.c1).

En el caso del ejemplo anterior, la utilizacin del mecanismo de sobrecarga podra ser como sigue: #include <iostream.h> namespace UNO { // Subespacio exterior void func (int i) { cout << "func(int) " << endl; } namespace UNO_A { // Subespacio anidado void func (float f) { cout << "func(float) " << endl; } } } void main (void) { // ======= int x = 1; float f = 1.1; using UNO::func; using UNO::UNO_A::func; func(x); func(f); } Salida: func(int) func(float)

4.4.1b Especificadores opcionales

1 Sinopsis

Aunque no se trata de caractersticas estndar del lenguaje, incluimos aqu algunas particularidades relativas al compilador MS Visual C++, que (por compatibilidad) tambin existen en el homlogo de Borland [1].
2 __declspec( )

Se trata de una palabra clave ( 3.2.1), especfica de los mencionados compiladores , que es adems un especificador de tipo de almacenamiento aplicable a la declaracin de funciones y variables. Por ejemplo, en la declaracin de clases.

Sintaxis __declspec( <modificador> ) Ejemplo class __declspec(dllimport) X { }; extern "C" __declspec(dllexport) double funcion();

Comentario
<modificador>

puede ser alguno de los siguientes:

dllexport

Sintaxis: __declspec( dllexport ) declarador Este atributo define la interface entre una funcin, variable, u objeto de una librera de enlazado dinmico .DLL ( 1.4.4b2a) y su cliente. Permitiendo que los objetos definidos con l, sean exportables desde la librera, hacindolos accesibles desde un ejecutable o desde otra librera. La declaracin de funciones con este atributo elimina la necesidad de declarar un fichero de definicin (.DEF) del mdulo que contiene a la funcin. Al menos en lo relativo a la especificacin de funciones exportables. Ver ejemplo ( 1.4.4b2a). Nota: dllexport reemplaza a la palabra clave __export.

dllimport

Sintaxis: __declspec( dllimport ) declarador Este atributo permite importar funciones, datos u objetos situados en una librera de enlace dinmico (DLL) desde otro ejecutable ( 1.4.4b2b). Ver ejemplo ( 1.4.4b2a). Nota: dllimport reemplaza la palabra clave __import.

naked

Sintaxis: __declspec( naked ) declarator El atributo naked es aplicable solo a la definicin de una funcin, y no supone un modificador de tipo. Su utilizacin origina la supresin del cdigo de

prlogo y eplogo (de ah su nombre de funciones "desnudas"). Lo que causa a su vez que al ser invocada la funcin no se construya marco de pila de forma estndar. La funcin no preserva los valores de los registros como es usual, y es responsabilidad del programador conformar las convenciones que necesite el invocador de la funcin. Esta posibilidad suele utilizarse junto con cdigo de prlogo y eplogo de cosecha propia en ensamblador (). Las funciones desnudas son especialmente adecuadas cuando hay que escribir manejadores de dispositivos virtuales ("Virtual device drivers"). Ejemplo: __declspec( naked ) int funcion( parametros-formales ) { // cuerpo de la funcin }
noreturn

Sintaxis: __declspec( noreturn ) declarador Este atributo se utiliza para informar al compilador que una funcin no tiene retorno. Como consecuencia, el compilador es informado que el cdigo que siga a la invocacin de dicha funcin no puede ser ejecutado. La justificacin de este atributo hay que buscarlo en que el compilador dispone de un mecanismo controlador de la senda de ejecucin. Este mecanismo genera un mensaje de aviso ("Warning" 1.4) si descubre una funcin que no devuelve el valor esperado. En estos casos, si la senda de ejecucin no sigue su camino normal debido a que se interpone una funcin que no tiene retorno, puede utilizarse este atributo para prevenir la aparicin del error. Adems, en caso que pueda asegurarse que la funcin no tendr retorno el compilador puede generar un cdigo ligeramente ms eficiente. El Estndar define dos funciones que no tienen retorno, son las funciones abort ( 1.5.1) y exit ( 1.5.1) Ejemplo: considere el cdigo siguiente en el que la segunda clusula else no contiene una sentencia return y la declaracin defunc1 como noreturn (sin retorno) para prevenir el error. __declspec( noreturn ) extern void func1 () { ... }

int func2() { if(...) return 1; else if(...) return 0; else func1(); // no hay retorno } Nota: este especificador noreturn no est recogido en el Estndar Ansi C++. Aunque los compiladores Borland; GNU cpp y MS Visual C++ lo utilizan.
nothrow

Sintaxis: __declspec( nothrow ) declarador Este atributo se utiliza exclusivamente en la declaracin de funciones, y sirve para informar al compilador que la funcin as declarada, y las que puedan ser invocadas por ella, no lanzan nunca una excepcin ( 1.6). Ejemplo: las tres declaraciones que siguen son equivalentes: #define WINAPI __declspec(nothrow) __stdcall void WINAPI func1(); void __declspec(nothrow) __stdcall func2(); void __stdcall func3() throw(); // F1 // F2 // F3

Las formas F1 y F2 presentan la ventaja de que puede utilizarse una definicin de la API (de Windows) con la que puede especificarse fcilmente la caracterstica nothrow para una serie de funciones. Por su parte F3 es la forma "cannica" de C++ ( 1.6.4).
novtable

Sintaxis: __declspec( novtable ) declarador Este especificador solo es aplicable a la declaracin de clases, aunque solo es aplicable a clases que sean pura interfaz ( 4.11.8c). En realidad la labor de este especificador consiste en informar al compilador que en la definicin de constructores y destructores no genere el cdigo necesario para inicializar el puntero a la tabla de funciones virtuales (vfptr 4.11.8a). En muchos casos solo existe una referencia asociada a la vtable ( 4.11.5) de la clase, por lo que el enlazador puede eliminarla, lo que se traduce en una significativa reduccin del cdigo.

property selectany Sintaxis:

__declspec( selectany ) declarador Los datos globales ( 4.1.3) solo pueden ser inicializados una vez en cualquier ejecutable EXE o DLL ( 4.1.2). Este atributo puede ser usado para inicializar datos globales definidos en los ficheros de cabecera cuando la referida cabecera aparece en ms de un fuente. Nota: este atributo solo puede usarse en la inicializacin de datos globales que sean visibles externamente.
thread
uuid

4.4.2 Definicin de una funcin


1 Sinopsis La definicin de la funcin se realiza en el cdigo fuente (o en libreras precompiladas), fuera del bloque de cualquier otra funcin, incluyendomain y, a excepcin de la capacidad del C++ de lo que se denomina sobrecarga, solo est permitida en cada programa una definicin para cada funcin [1].

2 Sintaxis La definicin tiene la sintaxis y las secciones que se indican (la gramtica permite casos ms complicados) [<storage-class>] [<tipo-dev>] nombre-func (<tipo-p> <n-param>, ... ) { <declaraciones>; <sentencias>; }

1- <storage-class> especificador opcional de tipo de almacenamiento, extern o static. Por defecto las funciones son externas y accesibles desde cualquier fichero del programa [2], aunque pueden ser restringidas usando el especificador static ( 4.1.8c). Ejemplos:

static int func(char ch, int x) { ... } extern int func(char ch, int x) { ... } int func(char ch, int x) { ... } // equivale a la anterior Como puede verse, es superflua la declaracin de extern ( 4.1.8d) en una funcin.

2- <tipo-dev> especificador opcional del valor devuelto por la funcin, posiblemente void. Por defecto se supone int. Ejemplo, las expresiones que siguen son equivalentes: int func(char ch, int x) { ... } func(char ch, int x) { ... } Nota: en C++Builder, este especificador puede incluir modificadores opcionales: __pascal, __cdecl, __export. El valor por defecto depende de la seleccin de opciones del compilador ( 1.4.3).

3- nombre-func El identificador (nombre) de la funcin. Salvo caso de sobrecarga no se permiten identificadores duplicados, la razn es que al no permitirse funciones dentro de funciones, las funciones son siempre de mbito global al fichero en que se definen (se excluyen de esta consideracin las funciones-miembro de clases, ya que para estos efectos, las clases funcionan como subespacios de nombres).

4- (t n, ...) Lista de declaracin de parmetros entre parntesis. Aunque el viejo estilo de indicar ausencia de parmetros func()tambin es an vlido en C++, se le considera anticuado e inseguro; es preferible poner func(void). La lista indica el tipo y el nombre de cada parmetro, separados por comas.

5- {...} Cuerpo de la funcin; contiene la parte del programa (declaraciones y sentencias) que se ejecutar cuando la funcin sea invocada. Nota: los elementos 1 y 2 pueden intercambiar su orden, es decir, las dos definiciones que siguen son equivalentes: static int func(char ch, int x) { ... } int static func(char ch, int x) { ... }

Como puede verse, algunos elementos de la declaracin son opcionales; una funcin mnima, que no devuelve nada y no hace nada, sera: void minima (void) {} ;

3 La diferencia esencial entre definicin y declaracin es que la definicin incluye el cuerpo de la funcin. Est claro que la declaracin (prototipo) y definicin deben coincidir. No es necesario que coincidan los nombres de los parmetros en el prototipo y en la definicin; de hecho, en C++

no es imprescindible que se incluyan los nombres de los parmetros en el prototipo, sin embargo se recomienda hacerlo, y utilizar nombres lo ms descriptivos posible (ver ejemplo ). Nota: si el prototipo de la funcin no coincide con la definicin, C++ puede detectarlo, si y solo si, la definicin est en la misma unidad de compilacin que el prototipo. Por contra, si como ocurre en muchas ocasiones, la definicin est en una librera o en otro mdulo, este tipo de discordancias pueden no ser detectados, con la consiguiente posibilidad de errores impredecibles. Si se crea una librera de funciones y su correspondiente fichero de cabecera con los prototipos, considere incluir el fichero de cabecera a la hora de compilar la librera; de esta forma podr ser detectada cualquier discrepancia entre los prototipos y las definiciones. C++ proporciona un enlazado de tipo seguro, de forma que las diferencias entre los argumentos formales y actuales pueden ser denunciados por el enlazador. 4 Ejemplo: char* strcpy(char* destino, const char* origen); ... int main() { // ============== .. strcpy(ptr1, ptr2); } char* strcpy(char* d, const char* s){ while (*s != 0) { *d = *s; s++ ; p++ ; } } // prototipo

// definicin

5 Comentario Por lo general, sobre todo cuando se utilizan libreras de terceros, las declaraciones de las funciones, clases y macros de la librera se encuentran en ficheros de cabecera del tipo xxxx.h, mientras que las definiciones se encuentran en la librera propiamente dicha. Es decir, en ficheros tipo.lib; .a; .dll, etc. Para poder utilizar los recursos de la librera, por ejemplo, sus funciones, es necesario incluir la correspondiente directiva#include xxxx.h en el fuente. Sin embargo, esto no es suficiente. Recordemos que adems se debe instruir al linker para que enlace la librera correspondiente junto con los mdulos de nuestros fuentes [3]. De no hacerlo as se obtienen errores de compilacin. El tipo de error depende del compilador, pero generalmente da pistas suficientes sobre las causas. Por ejemplo, si pretendemos utilizar en nuestro fuente una funcin foo() que se encuentra en una librera somelib.dll, y olvidamos incluir el fichero de cabecera correspondiente somelib.h (o el fichero no existe), obtendremos un error del siguiente tenor (compilador GNU gcc): nn C:\.....\fichero.cpp `foo' undeclared (first use this function)

nn es el nmero de lnea del fuente C:\.....\fichero.cpp donde se encuentra la primera referencia a la funcin en cuestin. Aqu la clave es la palabra "undeclared"; se trata de un error de compilacin en el que el compilador indica algo as: "no tengo ni idea de que se trata". Sin embargo, si incluimos la cabecera, pero olvidamos indicar al enlazador que incluya la librera (donde se encuentran las definiciones), el error mostrado sera el siguiente:

[Linker error] undefined reference to `foo' Como vemos, aqu la clave est en la palabra "undefined"; es un error de enlazado, indicativo de que el enlazador no ha encontrado la definicin en ningn sitio a pesar de que la funcin estuviese correctamente declarada. El enlazador viene a decir: "se de qu se trata pero no encuentro los detalles".

4.4.3 Declaracin implcita


1 Nota histrica Si no existe un prototipo previamente declarado de una funcin, el compilador C la supone declarada implcitamente la primera vez que aparece en una expresin. Por ejemplo, si el compilador encuentra una sentencia como: sum += convert(line); sin que hubiese un prototipo previo de convert, asume que se trata de una funcin, la declara; supone que devuelve un entero, y no realiza ninguna suposicin sobre el parmetro line. El compilador puede avisar: Call to function 'convert' with no prototype in .... Si ms adelante se encontrase una definicin de convert del tipo: void convert(const char * string); El compilador s enviara un mensaje de error: Type mismatch in redeclaration of 'convert'. La razn es que como hemos dicho, en la primera lnea declar convert como devolviendo un int, y ahora se encuentra una funcin del mismo nombre, pero devolviendo void, con lo que supone que es una redefinicin de la funcin. La razn de esta permisividad (no exigir una declaracin formal), es para que los programas antiguos puedan ser recompilados con los nuevos compiladores sin necesidad de rescribir el cdigo. La suposicin de que se trata de una funcin, ocurre cada vez que el compilador encuentra un identificador desconocido seguido de un parntesis izquierdo.

2 En C++ Por su parte C++ es ms estricto al respecto, se exige una declaracin (prototipo) de todas las funciones antes de usarlas (lo cual redunda en la seguridad), por lo que un caso como el anterior siempre dara un error al intentar compilar la primera lnea: Call to undefined function 'convert' in .... Como puede verse, en este como en otros casos, la evolucin de C++ respecto de C ha seguido el camino de la seguridad. Sera adems la razn por la que algn programa C no podra ser compilado directamente como C++ sin algn retoque.

4.4.4 La funcin main


1 Sinopsis

Durante la fase de enlazado de la compilacin ( 1.4.4 ), el "linker" aade a cualquier programa C++ un mdulo especial, de inicio, que es realmente el punto de entrada a la ejecucin del programa. Este mdulo realiza diversas tareas preparatorias a la ejecucin propiamente dicha. Por ejemplo, iniciar todas las variables estticas o globales y realizar determinadas verificaciones del hardware. Finalmente pasa el control a una funcin que debe responder al nombre de main y le pasa algunos argumentos en base a datos que ha recibido a su vez del Sistema Operativo; esta es la razn por la que todos los programas C++ deben contener una funcin con este nombre [4]. As pues, main representa el punto de la ejecucin a partir del cual el programador toma el control de la ejecucin, antes de esto ya han sucedido muchas cosas en nuestro programa. A su vez el punto de finalizacin de esta funcin, su punto de retorno ( return) significa el fin del programa [3], pero recuerde que existe otra forma de terminar el programa, que puede ser utilizada desde cualquier punto (sin necesidad de volver a la funcin main), consiste en utilizar la funcin exit( 1.5.1) de la Librera Estndar. Nota: algunos compiladores permiten al programador la opcin de que se llamen determinadas funciones antes que se realice la llamada amain. Estas funciones representan tareas preparatorias adicionales que queremos realizar antes de que se inicie la ejecucin. Esto se consigue con la directiva de inicio #pragma startup. En cualquier caso, las variables estticas son inicializadas antes que estas funciones iniciales ( 1.5).

2 Argumentos La funcin main es imprescindible en cualquier programa C/C++ representa el punto de inicio de su ejecucin. Por lo general, su declaracin adopta la forma: int main(); aunque en realidad, el mdulo de inicio la invoca con dos parmetros (recibidos a su vez del SO), denominados tradicionalmente argc y argv, contracciones de "argument count" y "argument vector" respectivamente. El primero es un entero que representa el nmero de comandos que se pasan; el segundo es un puntero a una matriz de cadenas literales de distintas longitudes (es decir: puntero a matriz de punteros); cada una de estas cadenas representa en ltimo extremo los comandos iniciales que se quieren pasar al programa, generalmente para controlar aspectos de su comportamiento. As pues, la declaracin ms genrica de main es del tipo: int main(int argc, char* argv[]); Nota: el Estndar establece que el compilador debe aceptar para main cualquiera de las dos formas anteriores.

Por convencin, argv[0] es el nombre con que se ha llamado al programa (normalmente ser el nombre del fichero ejecutable incluyendo su direccin completa -path-). Este dato es proporcionado automticamente por el SO; as pues, el valor mnimo para argc es 1. Despus seguirn los que introduzcamos en la lnea de comandos, separados por espacios.

2.1 Como ejemplo, puede usarse el siguiente programa, al que llamaremos prueba.exe: #include <stdio.h> // Prueba de parmetros para funcin main

int main(int argc, char* argv[]) {

int i; printf("Se han pasado %3d argumentos:\n", argc); for(i=0; i<argc; i++) printf("%5d- %s\n", i, argv[i]); return 0; }

Si introducimos en la lnea de comandos: prueba se pasan seis, parametros, se obtiene la siguiente salida [1]: Se han pasado 6 argumentos: 1- D:\ZF\LEARNC\PRUEBA.EXE 2- prueba 3- se 4- pasan 5- seis, 6- parametros

Se ha dicho que argv es un puntero a matriz de punteros, y en el ejemplo hemos utilizado la expresin argv[i], algo que a primera vista puede chocar, ya que argv no es una matriz. En realidad la declaracin completa: char* argv[] significa puntero-a-matriz-de-caracteres, para distinguirlo de char* argv que sera simplemente puntero-a-carcter.

2.2 Los argumentos anteriores son recogidos por el sistema en forma de sendas variables globales _argc y _argv ( 4.1.3a) definidas en la cabecera <dos.h>. A continuacin se expone una variacin del programa anterior que utiliza estas variables: #include <iostream> using namespace std; #include <dos.h>

// necesario para _argc y argv

int main (int argc, char* argv[]) { // ============== cout << "Se han pasado " << _argc << " argumentos:" << endl; for (int i = 0; i < _argc; ++i) cout << " " << i << "- " << _argv[i] << endl; return 0; } La salida es la misma que en el caso anterior.

3 Restricciones La funcin main adolece de ciertas limitaciones que la diferencian del resto de funciones C++: No puede ser invocada explcitamente a lo largo del programa, es invocada de forma automtica por el mdulo de inicio . No puede obtenerse su direccin, por lo tanto no pueden declararse punteros a ella: int (* pmain)() = &main; // Error!! No puede ser sobrecargada ( 4.4.1a).

No puede ser declarada como inline ( 4.4.6b). main debe estar en el espacio global de una de las unidades de compilacin del programa, lo que significa que no puede pertenecer a una clase.

Anda mungkin juga menyukai