MIGUEL Á. TOLEDO MARTÍNEZ                  CONTENIDO DE LA LECCIÓN 20                            APUNTADORES Y CADENAS1. I...
MIGUEL Á. TOLEDO MARTÍNEZ17. Sugerencias de portabilidad                                          6718. Observaciones de I...
MIGUEL Á. TOLEDO MARTÍNEZ                                             LECCIÓN 20                            APUNTADORES Y ...
MIGUEL Á. TOLEDO MARTÍNEZ                                           contador                                              ...
MIGUEL Á. TOLEDO MARTÍNEZasigna la dirección de la variable y a la variable de apuntador yPtr. Se dice entonces que lavari...
MIGUEL Á. TOLEDO MARTÍNEZEjemplo 20.1        El programa siguiente: APUNTADOR1.CPP, muestra los operadores de los apuntado...
MIGUEL Á. TOLEDO MARTÍNEZ        En C++, los programadores se pueden valer de los apuntadores y del operador deindirección...
MIGUEL Á. TOLEDO MARTÍNEZ /* El siguiente programa: CUBO2.CPP, eleva al cubo una variable mediante una llamada    por refe...
MIGUEL Á. TOLEDO MARTÍNEZ        Después de que cuboValor() recibe la llamada: int main()                 numero          ...
MIGUEL Á. TOLEDO MARTÍNEZ        Después de elevar *nPtr al cubo: int main()                 numero                  void ...
MIGUEL Á. TOLEDO MARTÍNEZhacia datos no constante no incluye a const. Tal apuntador puede servir para recibir una cadenaen...
MIGUEL Á. TOLEDO MARTÍNEZEjemplo 20.4        La función imprimirCaracteres() del programa IMPRIMIR.CPP, declara que los pa...
MIGUEL Á. TOLEDO MARTÍNEZ // En f, xPtr es un apuntador hacia una constante entera void f(const int *xPtr)           {    ...
MIGUEL Á. TOLEDO MARTÍNEZ /* El siguiente programa: MODIFICAR2.CPP, intenta modificar un apuntador constante hacia    dato...
MIGUEL Á. TOLEDO MARTÍNEZORDENAMIENTO DE burbuja MEDIANTE LLAMADA POR REFERENCIAEjemplo 20.8        El programa BURBUJA1.C...
MIGUEL Á. TOLEDO MARTÍNEZ                   for(i = 0; i < TAMANO_ARREGLO; i++)                            cout << setw(4)...
MIGUEL Á. TOLEDO MARTÍNEZ        arreglo como un valor de tipo size_t, que generalmente es un unsigned int. La computadora...
MIGUEL Á. TOLEDO MARTÍNEZ void main(void)         {                   char c;                   short s;                  ...
MIGUEL Á. TOLEDO MARTÍNEZsituación en una máquina con enteros de 4 bytes. Observe que vPtr puede inicializarse para queapu...
MIGUEL Á. TOLEDO MARTÍNEZarreglo de caracteres, los resultados serán consistentes con la aritmética común, pues cadacaráct...
MIGUEL Á. TOLEDO MARTÍNEZ        Los apuntadores se pueden comparar por medio de operadores de igualdad y relacionales,per...
MIGUEL Á. TOLEDO MARTÍNEZtambién se refiere al elemento del arreglo b[3]. En general, todas las expresiones de arreglos co...
MIGUEL Á. TOLEDO MARTÍNEZ                   cout << "nNotacion de índices de apuntadorn";                   for(int i = 0;...
MIGUEL Á. TOLEDO MARTÍNEZ        La función copia1() utiliza notación de índices de arreglo para copiar la cadena s2 al ar...
MIGUEL Á. TOLEDO MARTÍNEZque la cadena más larga. Para ayudar a representar una baraja, en la siguiente sección se emplean...
MIGUEL Á. TOLEDO MARTÍNEZ        En la figura 20.8 cada columna (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12) representan: As...
MIGUEL Á. TOLEDO MARTÍNEZ        Reparte los 52 naipes se puede expandir como sigue:        Por cada uno de los 52 naipes ...
MIGUEL Á. TOLEDO MARTÍNEZEjemplo 20.13        En el programa BARAJAS.CPP, se muestra el barajado y repartición de naipes. ...
MIGUEL Á. TOLEDO MARTÍNEZ void repartir(const int wPaquete[][13], const char *wCara[],               const char *wPalos[])...
MIGUEL Á. TOLEDO MARTÍNEZ                  cout    << "Teclee 1 para ordenar en forma ascendente,n"                       ...
MIGUEL Á. TOLEDO MARTÍNEZ          El siguiente parámetro aparece en el encabezado de la función burbuja():               ...
MIGUEL Á. TOLEDO MARTÍNEZ                                              ( *f[opcion]) (opcion);        En la llamada, f[opc...
Ejercicios Resueltos - Fundamentos de Programación.
Ejercicios Resueltos - Fundamentos de Programación.
Ejercicios Resueltos - Fundamentos de Programación.
Ejercicios Resueltos - Fundamentos de Programación.
Ejercicios Resueltos - Fundamentos de Programación.
Ejercicios Resueltos - Fundamentos de Programación.
Ejercicios Resueltos - Fundamentos de Programación.
Ejercicios Resueltos - Fundamentos de Programación.
Ejercicios Resueltos - Fundamentos de Programación.
Ejercicios Resueltos - Fundamentos de Programación.
Ejercicios Resueltos - Fundamentos de Programación.
Ejercicios Resueltos - Fundamentos de Programación.
Ejercicios Resueltos - Fundamentos de Programación.
Ejercicios Resueltos - Fundamentos de Programación.
Ejercicios Resueltos - Fundamentos de Programación.
Ejercicios Resueltos - Fundamentos de Programación.
Ejercicios Resueltos - Fundamentos de Programación.
Ejercicios Resueltos - Fundamentos de Programación.
Ejercicios Resueltos - Fundamentos de Programación.
Ejercicios Resueltos - Fundamentos de Programación.
Ejercicios Resueltos - Fundamentos de Programación.
Ejercicios Resueltos - Fundamentos de Programación.
Ejercicios Resueltos - Fundamentos de Programación.
Ejercicios Resueltos - Fundamentos de Programación.
Ejercicios Resueltos - Fundamentos de Programación.
Ejercicios Resueltos - Fundamentos de Programación.
Ejercicios Resueltos - Fundamentos de Programación.
Ejercicios Resueltos - Fundamentos de Programación.
Ejercicios Resueltos - Fundamentos de Programación.
Ejercicios Resueltos - Fundamentos de Programación.
Ejercicios Resueltos - Fundamentos de Programación.
Ejercicios Resueltos - Fundamentos de Programación.
Ejercicios Resueltos - Fundamentos de Programación.
Ejercicios Resueltos - Fundamentos de Programación.
Ejercicios Resueltos - Fundamentos de Programación.
Ejercicios Resueltos - Fundamentos de Programación.
Ejercicios Resueltos - Fundamentos de Programación.
Ejercicios Resueltos - Fundamentos de Programación.
Ejercicios Resueltos - Fundamentos de Programación.
Ejercicios Resueltos - Fundamentos de Programación.
Ejercicios Resueltos - Fundamentos de Programación.
Ejercicios Resueltos - Fundamentos de Programación.
Ejercicios Resueltos - Fundamentos de Programación.
Ejercicios Resueltos - Fundamentos de Programación.
Ejercicios Resueltos - Fundamentos de Programación.
Ejercicios Resueltos - Fundamentos de Programación.
Ejercicios Resueltos - Fundamentos de Programación.
Ejercicios Resueltos - Fundamentos de Programación.
Ejercicios Resueltos - Fundamentos de Programación.
Ejercicios Resueltos - Fundamentos de Programación.
Ejercicios Resueltos - Fundamentos de Programación.
Ejercicios Resueltos - Fundamentos de Programación.
Ejercicios Resueltos - Fundamentos de Programación.
Ejercicios Resueltos - Fundamentos de Programación.
Ejercicios Resueltos - Fundamentos de Programación.
Ejercicios Resueltos - Fundamentos de Programación.
Upcoming SlideShare
Loading in...5
×

Ejercicios Resueltos - Fundamentos de Programación.

8,324

Published on

Published in: Education
1 Comment
3 Likes
Statistics
Notes
No Downloads
Views
Total Views
8,324
On Slideshare
0
From Embeds
0
Number of Embeds
0
Actions
Shares
0
Downloads
397
Comments
1
Likes
3
Embeds 0
No embeds

No notes for slide

Ejercicios Resueltos - Fundamentos de Programación.

  1. 1. MIGUEL Á. TOLEDO MARTÍNEZ CONTENIDO DE LA LECCIÓN 20 APUNTADORES Y CADENAS1. Introducción 32. Declaración e iniciación de variables de apuntador 373. Operadores de los apuntadores 4 3.1. Ejemplo 20.1 64. Llamado de funciones por referencia 6 4.1. Ejemplo 20.2 75. Empleo del calificador const con apuntadores 10 5.1. Ejemplo 2 20.3, 20.4, 20.5, 20.6, 20.7 116. Ordenamiento de burbuja mediante llamada por referencia 15 6.1. Ejemplos 20.8, 20.9, 20.10 157. Expresiones de apuntadores y aritmética de apuntadores 188. Relación entre apuntadores y arreglos 21 8.1. Ejemplos 20.11, 20.12 229. Arreglos de apuntadores 2410. Caso de estudio: Simulación de barajado y repartición de naipes 25 10.1. Ejemplo 20.13 2811. Apuntadores y funciones 29 11.1. Ejemplos 20.14, 20.15 2912. Introducción al procesamiento de caracteres y cadenas 33 12.1. Fundamentos de los caracteres y las cadenas 33 12.2. Funciones de manipulación de cadenas de la biblioteca de manejo de cadenas 34 12.2.1. Ejemplo 20.16, 20.17, 20.18, 20.19, 20.20 36 12.3. Ejercicios resueltos 40 12.4. Funciones de cadena que utilizan cadenas ubicadas fuera de los 64 kb (far string) 47 12.5. Número de ocurrencias de un carácter dentro de una cadena 47 12.6. Contar el número de ocurrencia de una subcadena dentro de una cadena 53 12.7. Obtener un índice a una subcadena 54 12.8. Obtener la ocurrencia mas a la derecha de una subcadena 54 12.9. Remover una subcadena contenida dentro de una cadena 54 12.10. Reemplazo de una subcadena por otra 55 12.11. Determinar si un carácter es alfanumérico 56 12.12. Determinar si un carácter es una letra del alfabeto 56 12.13. Determinar si un carácter contiene un valor ASCII 56 12.14. Determinar si un carácter es un carácter de control 56 12.15. Determinar si un carácter es un digito 56 12.16. Determinar si un carácter es un carácter gráfico 57 12.17. Determinar si un carácter es mayúscula o minúscula 57 12.18. Determinar si un carácter es imprimible 57 12.19. Determinar si un carácter es un símbolo de puntuación 57 12.20. Determinar si un carácter es el carácter espacio 58 12.21. Determinar si un carácter es un valor hexadecimal 58 12.22. Carácter ASCII válido 5913. Pensando en objetos: Iteraciones entre los objetos 6414. Errores comunes de programación 6515. Buenas prácticas de programación 6616. Propuestas de desempeño 67APUNTADORES Y CADENA – LECCIÓN 20 20-1
  2. 2. MIGUEL Á. TOLEDO MARTÍNEZ17. Sugerencias de portabilidad 6718. Observaciones de Ingeniería de Software 6719. Indicaciones de prueba y depuración 6720. Lo que necesita saber 6821. Preguntas y problemas 70 21.1. Preguntas 70 21.2. Problemas 73 21.2.1. Sección especial: construya su propia computadora 75 21.2.2. Más problemas de apuntadores 79 21.2.3. Problemas de manipulación de cadenas 84 21.2.4. Sección especial: manipulación avanzada de cadenas 85 21.2.5. Interesante proyecto de manipulación de cadenas 88APUNTADORES Y CADENA – LECCIÓN 20 20-2
  3. 3. MIGUEL Á. TOLEDO MARTÍNEZ LECCIÓN 20 APUNTADORES Y CADENASINTRODUCCIÓN En esta lección se estudiará una de las características más poderosas del lenguaje deprogramación C++: el apuntador. Los apuntadores son una de las capacidades de C++ másdifíciles de dominar. En otra lección vimos que las referencias pueden servir para hacer llamadaspor referencia. Los apuntadores permiten a los programas simular la llamada por referencia ycrear y manipular estructuras dinámicas de datos, es decir, estructuras de datos que pueden crecery encogerse, como las listas vinculadas, colas, pilas y árboles. Esta lección explica los conceptosbásicos acerca de los apuntadores. También refuerza la relación íntima entre los arreglos, losapuntadores y las cadenas e incluye un amplio conjunto de ejemplos de procesamiento decadenas. En otro semestre se estudiarán el empleo de los apuntadores con estructuras. Laprogramación orientada a objetos se efectúa con apuntadores y referencias. En ese semestre sepresentan técnicas de administración dinámica de memoria y ejemplos de creación y uso deestructuras dinámicas de datos. El enfoque de los arreglos y cadenas como apuntadores se deriva de C. En la especialidadde computación se estudiarán los arreglos y las cadenas como objetos en toda forma. Los objetivos de esta lección son: • Aprender a utilizar los apuntadores. • Emplear los apuntadores para pasar argumentos a las funciones mediante llamada por referencia. • Entender la estrecha relación entre los apuntadores, los arreglos y las cadenas. • Comprender la utilidad de los apuntadores a funciones. • Declarar y utilizar arreglos de cadenas. • Asignar caracteres a una cadena de caracteres. • Comprender la importancia del carácter NULL. • Inicializar cadena de caracteres. • Pasar cadena de caracteres a una función. • Utilizar las funciones de las librerías en tiempo de ejecución para manejar cadenas de caracteres.DECLARACIÓN E INICIACIÓN DE VARIABLES DE APUNTADOR Las variables de apuntador contienen direcciones de memoria como sus valores.Normalmente las variables contienen valores específicos. Por otra parte, los apuntadorescontienen direcciones de variables que contienen valores específicos. En este sentido, losnombres de variables hacen referencia directa a un valor y los apuntadores hacen referenciaindirecta a un valor (figura 20.1) La referencia a un valor a través de un apuntador se llamaindirección.APUNTADORES Y CADENA – LECCIÓN 20 20-3
  4. 4. MIGUEL Á. TOLEDO MARTÍNEZ contador contador hace referencia directa a una variable cuyo valor es 7 7 contadorPtr contador contadorPtr hace referencia indirecta a una • 7 variable cuyo valor es 7. Figura 20.1. Referencias directa e indirecta a una variable Los apuntadores, como cualquier otra variable, se deben declarar antes de utilizarlos. Ladeclaración: int *contadorPtr, contador;declara la variable contadorPtr como de tipo int * (es decir, como un apuntador a un valorentero) y se lee contadorPtr es un apuntador a int o contadorPtr apunta a un objeto de claseentero. Además, la variable contador se declara como entero, no como apuntador a un entero. Enesta declaración, el * solamente se aplica a contadorPtr. Cada variable que se declara comoapuntador debe ir precedida por un asterisco (*) Por ejemplo, la declaración: float *xPtr, *yPtr;indica que tanto xPtr como yPtr son apuntadores a valores float. El uso de * en una declaraciónde esta manera indica que la variable que se está declarando es un apuntador. Los apuntadorespueden declararse para que apunten a objetos de cualquier clase de datos. Los apuntadores se deben inicializar, ya sea al declararlos o mediante una instrucción deasignación. Un apuntador puede inicializarse a 0, a NULL o a una dirección. Un apuntador conel valor 0 o NULL no apunta a nada. NULL es una constante simbólica definida en el archivo deencabezado <iostream.h> (y en varios archivos de encabezado de la biblioteca estándar) Lainiciación a NULL de un apuntador es equivalente a inicializarlo a 0, pero en C++ se prefiere 0.Cuando se asigna 0, se convierte en un apuntador del tipo adecuado. El valor 0 es el único valorentero que puede asignarse directamente a una variable de apuntador sin convertir primeromediante cast el entero a un tipo de apuntador. En la siguiente sección se estudia la asignación dela dirección de una variable a un apuntador.OPERADORES DE LOS APUNTADORES El &, u operador de dirección, es un aperador unario que devuelve la dirección de suoperando. Por ejemplo, suponiendo las declaraciones: int y = 5; int *yPtr;la instrucción yPtr = &y;APUNTADORES Y CADENA – LECCIÓN 20 20-4
  5. 5. MIGUEL Á. TOLEDO MARTÍNEZasigna la dirección de la variable y a la variable de apuntador yPtr. Se dice entonces que lavariable yPtr apunta a y. La figura 20.2 muestra una representación esquemática de la memoriatras la ejecución de la asignación previa. En la figura mostramos la relación de apuntadordibujando una flecha del apuntador hacia el objeto al que apunta. y 5 yPtr • Figura 20.2. Representación gráfica de un apuntador que apunta a una variable entera en la memoria La figura 20.3 muestra la representación del apuntador en la memoria, suponiendo que lavariable entera y está almacenada en la localidad 600000 y que la variable de apuntador yPtr estáalmacenada en la localidad 500000. El operando del operador de dirección debe ser un lvalue(left value) (es decir, algo a lo que puede asignarse un valor, como un nombre de variable); eloperador de dirección no puede aplicarse a constantes, a expresiones que no den como resultadoreferencias, ni a variables declaradas con la clase de almacenamiento register. yPtr y 500000 600000 600000 5 El operador *, conocido comúnmente como operador de indirección u operador dedesreferenciación, devuelve un sinónimo, alias o apodo del objeto hacia el que apunta suoperando (es decir, su apuntador) Por ejemplo (haciendo referencia a la figura 20.2), lainstrucción: cout << *yPtr << endl;imprime el valor de la variable y, es decir 5, de la misma manera que lo haría la instrucción: cout << y << endl; El empleo de * de esta manera se conoce como desreferenciación de un apuntador.Observe que también puede utilizarse un apuntador desreferenciado del lado izquierdo de unainstrucción de asignación, como: *yPtr = 9;que asignará 9 a y en la figura 20.3. El apuntador desreferenciado también podría usarse pararecibir un valor de entrada como en el caso de: cin >> *yPtr; El apuntador desreferenciado es un lvalue, o valor izquierdo.APUNTADORES Y CADENA – LECCIÓN 20 20-5
  6. 6. MIGUEL Á. TOLEDO MARTÍNEZEjemplo 20.1 El programa siguiente: APUNTADOR1.CPP, muestra los operadores de los apuntadores. En este ejemplo, las localidades de memoria se envían a la salida como enteros hexadecimales. /* El siguiente programa: APUNTADOR1.CPP, muestra el empleo de los operadores & y * */ #include <iostream.h> //Para cout y cin void main(void) { int a; //a es un entero int *aPtr; //aPtr es un apuntador a un entero a = 7; aPtr = &a; //aPtr se establece a la dirección de a cout << "La dirección de a es: " << &a << "nEl valor de aPtr es: " << aPtr; cout << "nnEl valor de a es: " << a << "nEl valor de *Ptr es: " << *aPtr; cout << "nnMostrando que * y & son inversos " << "entre sí. n&*aPtr = " << &*aPtr << "n*&aPtr = " << *&aPtr << endl; }//Fin de main() Observe que la dirección de a y el valor de aPtr son idénticos en la salida, lo que confirma que la dirección de a efectivamente está asignada a la variable de apuntador aPtr. Los operadores & y * son inversos entre ellos –cuando se aplican ambos consecutivamente a aPtr en cualquier orden, se imprime el mismo resultado.LLAMADO DE FUNCIONES POR REFERENCIA En C++ hay tres maneras de pasarle argumentos a una función: mediante llamada porvalor, mediante llamada por referencia con argumentos de referencia y mediante llamada porreferencia con argumentos de apuntador. En lección anterior comparamos e hicimos ladistinción entre llamada por valor y llamada por referencia con argumentos de referencia. En estalección nos concentraremos en la llamada por referencia con argumentos de apuntador. Como antes hemos visto, return puede utilizarse para devolverle al invocador un valordesde una función llamada (o regresar el control desde una función llamada sin devolver unvalor) También vimos que se le pueden pasar argumentos a una función mediante argumentos dereferencia, permitiendo que la función modifique los valores originales de los argumentos (por lotanto, se puede devolver más de un valor desde una función), o pasar objetos de datos grandes auna función y evitar la sobrecarga de pasar los objetos mediante llamada por valor (que, claroestá, involucra llevar a cabo una copia del objeto) Los apuntadores, como las referencias,también pueden servir para modificar una o más variables del invocador o para pasarapuntadores a objetos de datos grandes, evitando la sobrecarga de pasar los objetos mediantellamada por valor.APUNTADORES Y CADENA – LECCIÓN 20 20-6
  7. 7. MIGUEL Á. TOLEDO MARTÍNEZ En C++, los programadores se pueden valer de los apuntadores y del operador deindirección para simular llamadas por referencia (de la misma manera que se logran las llamadaspor referencia en C) Al llamar una función con argumentos que deben ser modificados, se pasa ladirección de los argumentos. Por lo general, esto se logra aplicándole el operador de dirección(&) al nombre de la variable que se habrá de modificar. Como hemos visto, los arreglos no sepasan mediante el operador &, pues el nombre de éstos es la localidad inicial del mismo enmemoria (el nombre del arreglo es equivalente a &nombreArreglo[0], es decir, un nombre dearreglo ya es un apuntador. Cuando se pasa la dirección de una variable a una función, se puedeemplear el operador de indirección (*) en la función, creando un sinónimo, alias o apodo delnombre de la variable –con éste, a su vez, es posible modificar el valor de la localidad dememoria del invocador (si la variable no está declarada como const)Ejemplo 20.2 Los dos programas siguientes: CUBO1.CPP y CUBO2.CPP, son dos versiones de una función que eleva al cubo un entero: cuboValor() y cuboReferencia() CUBO1.CPP, pasa la variable numero a la función cuboValor() mediante una llamada por valor. La función cuboReferencia() eleva al cubo su argumento y le devuelve el valor a main() mediante una instrucción return. El nuevo valor se asigna a numero en main() Existe la oportunidad de examinar el resultado de la llamada de función antes de modificar el valor de una variable. Por ejemplo, en este programa se podría haber almacenado el resultado de cuboValor() en otra variable, examinando su valor y asignando el resultado a numero tras comprobar que el resultado es razonable. /* El siguiente programa: CUBO1.CPP, eleva al cubo una variable mediante una llamada por valor. */ #include <iostream.h> //Para cout y cin int cuboValor(int); //Prototipo void main(void) { int numero = 5; cout << "El valor original del número es: " << numero; numero = cuboValor(numero); cout << "nEl nuevo valor del número es: " << numero << endl; }//Fin de main() int cuboValor(int n) { return n * n * n; //Eleva al cubo la variable local n }//Fin de cuboValor() El programa CUBO2.CPP pasa la variable numero mediante una llamada por referencia (se pasa la dirección de numero) a la función cuboReferencia() Esta toma nPtr (que es un apuntador a int) como argumento. La función desreferencia el apuntador y eleva al cubo el valor al que apunta nPtr. Esto cambia el valor de numero en main()APUNTADORES Y CADENA – LECCIÓN 20 20-7
  8. 8. MIGUEL Á. TOLEDO MARTÍNEZ /* El siguiente programa: CUBO2.CPP, eleva al cubo una variable mediante una llamada por referencia, con un argumento de apuntador. */ #include <iostream.h> //Para cout y cin void cuboReferencia(int *); //Prototipo void main(void) { int numero = 5; cout << "El valor original del número es: " << numero; cuboReferencia(&numero); cout << "nEl nuevo valor del número es: " << numero << endl; }//Fin de main() void cuboReferencia(int *nPtr) { *nPtr = *nPtr * *nPtr * *nPtr; //Eleva al cubo el número en main() }//Fin de cuboReferencia() Una función que recibe como argumento una dirección debe definir un parámetro de apuntador para recibir dicha dirección. Por ejemplo, el encabezado de la función cuboReferencia() es: void cuboReferencia(int *nPtr); El encabezado de la función especifica que cuboReferencia() contiene int * entre paréntesis. Como sucede con los demás tipos de variables, no es necesario incluir los nombres de los apuntadores en los prototipos de función. El compilador ignorará los nombres incluidos con fines de documentación. En el encabezado de la función y en el prototipo de una función que espera como argumento un arreglo de un solo índice, puede emplearse la notación de apuntadores en la lista de parámetros de cuboReferencia() El compilador no hace ninguna distinción entre una función que recibe un apuntador y otra que recibe un arreglo de un solo índice. Por supuesto, esto significa que la función debe saber cuándo está recibiendo un arreglo y cuándo se trata simplemente de una variable sencilla sobre la que debe efectuar una llamada por referencia. Cuando el compilador encuentra un parámetro de función para un arreglo de un solo índice, de la forma int b[], lo convierte a la notación de apuntadores int * const b (que se pronuncia como b es un apuntador constante a un entero); const se explica en la sección siguiente (empleo del calificador const con apuntadores) Ambas formas de declaración de un parámetro de función como arreglo de un solo índice son intercambiables. Las figuras 20.3 y 20.4 son análisis gráficos de los programas CUBO1.CPP y CUBO2.CPP. Antes de que main() llame a cuboValor(): int main() numero int cuboValor(int n) n { { int numero = 5; 5 return n * n * n; indefinido }//Fin de cuboValor() numero = cuboValor(numero); }//Fin de main()APUNTADORES Y CADENA – LECCIÓN 20 20-8
  9. 9. MIGUEL Á. TOLEDO MARTÍNEZ Después de que cuboValor() recibe la llamada: int main() numero int cuboValor(int n) n { { int numero = 5; 5 return n * n * n; 5 }//Fin de cuboValor() numero = cuboValor(numero); }//Fin de main() Después de que cuboValor() eleve al cubo el parámetro n: int main() numero int cuboValor(int n) n { { 125 int numero = 5; 5 return n * n * n; indefinido }//Fin de cuboValor() numero = cuboValor(numero); }//Fin de main() Después de que cuboValor() regresa a main(): int main() numero int cuboValor(int n) n { { int numero = 5; 5 return n * n * n; indefinido 125 }//Fin de cuboValor() numero = cuboValor(numero); }//Fin de main() Después de que main() completa la asignación de numero: int main() numero int cuboValor(int n) n { { int numero = 5; 125 return n * n * n; indefinido }//Fin de cuboValor() numero = cuboValor(numero); }//Fin de main() Figura 20.3. Análisis de una llamada por valor típica. Antes de la llamada por referencia a cuboReferencia(): int main() numero void cuboReferencia(int *nPtr) { { nPtr int numero = 5; 5 *nPtr = * nPtr * *nPtr * *nPtr; indefinido }//Fin de cuboValor() cuboReferencia(&numero); }//Fin de main() Después de la llamada por referencia a cuboReferencia() y antes de elevar *nPtr al cubo: int main() numero void cuboReferencia(int *nPtr) { { nPtr 5 *nPtr = * nPtr * *nPtr * *nPtr; int numero = 5; • }//Fin de cuboValor() cuboReferencia(&numero); }//Fin de main()APUNTADORES Y CADENA – LECCIÓN 20 20-9
  10. 10. MIGUEL Á. TOLEDO MARTÍNEZ Después de elevar *nPtr al cubo: int main() numero void cuboReferencia(int *nPtr) { { nPtr 125 *nPtr = * nPtr * *nPtr * *nPtr; int numero = 5; • }//Fin de cuboValor() cuboReferencia(&numero); }//Fin de main() Figura 20.4. Análisis de una llamada por referencia típica con argumento de apuntador.EMPLEO DEL CALIFICADOR const CON APUNTADORES El calificador const le permite al programador informarle al compilador que no se debemodificar el valor de una variable en particular. Existen seis posibilidades para utilizar (o no utilizar) const con parámetros de función –dos con paso de parámetros mediante llamada por valor y cuatro con paso de parámetrosmediante llamada por referencia. ¿Cómo escoger entre las seis posibilidades? Sea la guía elprincipio de menor privilegio. Siempre habrá que dar a una función el suficiente acceso a lainformación de sus parámetros para que pueda llevar a cabo la tarea encomendada, pero no más. En lección anterior, explicamos que, cuando se invoca una función mediante llamada porvalor, se obtiene una copia del argumento (o argumentos) en la llamada de función, la cual es laque pasa a la función. Si en la función se modifica la copia, el valor original en el invocador seconserva inalterado. En muchos casos, un valor pasado a una función es modificado para que lafunción pueda llevar a cabo su tarea. Sin embargo, en algunos casos, el valor no debe ser alteradoen la función llamada, aún si la función llamada sólo manipula una copia del valor original. Considere una función que toma como argumentos un arreglo de un solo índice y sutamaño, y que imprime dicho arreglo. Tal función deberá recorrer el arreglo utilizando un ciclo yenviar a la salida, uno por uno, los elementos del mismo. El tamaño del arreglo se utiliza en elcuerpo de la función para determinar el índice más grande del arreglo y poder terminar el ciclocuando se ha terminado la impresión. El tamaño del arreglo no cambia en el cuerpo de lafunción. Si un valor no cambia (o no debe cambiar) en el cuerpo de una función a la cual se pasa,el parámetro debe ser declarado como const con el fin de asegurar que no sea modificado poraccidente. Si se intenta modificar una valor const, el compilador lo detecta y emite un aviso o unerror, dependiendo del compilador. Hay cuatro maneras de pasar una apuntador a una función: con un apuntador noconstante hacia datos no constantes, mediante un apuntador no constante hacia datosconstantes, por medio de un apuntador constante hacia datos no constantes y utilizando unapuntador constante hacia datos constantes. Cada combinación ofrece un nivel distinto deprivilegio de acceso. El mayor acceso se otorga mediante un apuntador no constante hacia datos no constantes–la información puede modificarse a través del apuntador desreferenciado y dicho apuntador sepuede modificar para que apunte hacia otros datos. La declaración de apuntadores no constantesAPUNTADORES Y CADENA – LECCIÓN 20 20-10
  11. 11. MIGUEL Á. TOLEDO MARTÍNEZhacia datos no constante no incluye a const. Tal apuntador puede servir para recibir una cadenaen una función que se vale de aritmética de apuntadores para procesar (y tal vez modificar) unopor uno los caracteres de la cadena. La función convertirMayuscula() en el programaMAYÚSCULA.CPP declara el parámetro sPtr (char *sPtr) como apuntador no constante haciadatos no constantes. La función procesa la cadena cadena, carácter por carácter, mediantearitmética de apuntadores. Los caracteres entre la a y la z son convertidos a sus letras mayúsculasa través de la función toupper(), los demás se conservan iguales. La función toupper() tomacomo argumento un carácter. Si dicho carácter es una letra minúscula, se devuelve la letramayúscula correspondiente; de otro modo, se devuelve el carácter original. La función toupper()es parte de la biblioteca de manejo de caracteres ctype.h.Ejemplo 20.3 El siguiente programa: MAYÚSCULA.CPP, ilustra el uso de un apuntador no constante a datos no constantes. /* El siguiente programa: MAYUSCULA.CPP, convierte letras minúsculas a mayúsculas, por medio de un apuntador no constante hacia datos no constantes. */ #include <iostream.h> //Para cout y cin #include <ctype.h> //Para toupper() void convertirMayuscula(char *); void main(void) { char cadena[] = "caracteres y $32.98"; cout << "La cadena de caracteres antes de la conversión es : " << cadena; convertirMayuscula(cadena); cout << "nLa cadena de caracteres después de la conversión es: " << cadena << endl; }//Fin de main() void convertirMayuscula(char *sPtr) { while(*sPtr != 0) { if(*sPtr >= a && *sPtr <= z) *sPtr = toupper(*sPtr); //Convierte a mayúsculas ++sPtr; //Mueve sPtr al siguiente carácter }//Fin del while }//Fin de convertirMayuscula() Un apuntador no constante hacia datos constantes es un apuntador que se puede modificarpara que apunte hacia cualquier elemento de información del tipo apropiado, pero los datos a losque apunta no pueden modificarse por medio de ese apuntador. Tal apuntador podría servir pararecibir como argumento un arreglo en una función que procesará cada elemento de dicho arreglosin modificar los datos.APUNTADORES Y CADENA – LECCIÓN 20 20-11
  12. 12. MIGUEL Á. TOLEDO MARTÍNEZEjemplo 20.4 La función imprimirCaracteres() del programa IMPRIMIR.CPP, declara que los parámetros sPtr son del tipo const char *. La declaración se lee de derecha a izquierda como sPtr es un apuntador hacia una constante de caracteres. El cuerpo de la función utiliza una estructura for con la que envía a la salida, uno por uno, los caracteres de la cadena, hasta que encuentra el carácter nulo. Tras la impresión de un carácter, se incremente sPtr para apuntar al siguiente carácter de la cadena. /* El siguiente programa: IMPRIMIR.CPP, imprime una cadena, caracter tras caracter, mediante un apuntador no constante hacia datos constantes. */ #include <iostream.h> //Para cout y cin void imprimirCaracteres(const char *); void main(void) { char cadena[] = "imprime caracteres de una cadena"; cout << "La cadena es:n"; imprimirCaracteres(cadena); cout << endl; }//Fin de main() /* En imprimirCaracteres(), sPtr es el apuntador hacia una constante de caracteres. Los caracteres no pueden modificarse por medio de sPtr(es decir, sPtr es un apuntador de solo lectura). */ void imprimirCaracteres(const char *sPtr) { for(; *sPtr != 0; sPtr++) // No hay inicialización cout << *sPtr; }//Fin de imprimirCaracteres()Ejemplo 20.5 El siguiente programa: MODIFICAR.CPP, muestra los mensajes de error que se generan al intentar compilar una función que recibe un apuntador hacia datos constantes y luego intenta emplear dicho apuntador para modificar los datos. /* El siguiente programa: MODIFICAR.CPP, intenta modificar la información por medio de un apuntador no constante hacia datos constantes. */ #include <iostream.h> //Para cout y cin void f(const int *); void main(void) { int y; f(&y); //f intenta una modificación ilegal }//Fin de main()APUNTADORES Y CADENA – LECCIÓN 20 20-12
  13. 13. MIGUEL Á. TOLEDO MARTÍNEZ // En f, xPtr es un apuntador hacia una constante entera void f(const int *xPtr) { *xPtr = 100; // No se puede modificar un objeto const }//Fin de f() Los mensajes que el compilador envía son los siguientes: Cannot modify a const object Parameter ‘xPtr’ is never used Como sabemos, los arreglos son tipos de datos agrupados que almacenan elementos dedatos relacionados del mismo tipo bajo un mismo nombre. En otra lección se estudiará otraforma de tipo de datos agrupados, llamado estructura (a veces conocido como registro en otroslenguajes) Una estructura es capaz de almacenar elementos de datos relacionados de distintostipos bajo un mismo nombre (por ejemplo, para almacenar información sobre todos losempleados de una compañía) Al invocar una función con un arreglo como argumento, éste sepasa de manera automática a la función simulando una llamada por referencia. Sin embargo, lasestructuras siempre se pasan mediante llamada por valor –se pasa una copia de toda la estructura.Esto requiere la sobrecarga en tiempo de ejecución que es causada por copiar todos los elementosde datos de la estructura y almacenarlos en la pila de llamadas de función de la computadora(que es el lugar donde se almacenan las variables locales empleadas en la función mientras éstase ejecuta) Cuando hay que pasar una estructura a una función, puede utilizarse un apuntadorhacia datos constantes (o una referencia hacia datos constantes), logrando el desempeño de unallamada por referencia y la protección de una llamada por valor. Cuando el apuntador se pasahacia una estructura, sólo hay que copiar la dirección en la que se almacena tal estructura. En unamáquina con direcciones de 4 bytes, se hace una copia de 4 bytes de memoria, en lugar de los, talvez, cientos o miles de bytes de la estructura. Un apuntador constante hacia datos no constantes es un apuntador que siempre apunta ala misma localidad de memoria; los datos de dicha localidad se pueden modificar a través delapuntador. Esto es lo predeterminada para un nombre de arreglo, el cual es un apuntadorconstante hacia el inicio del arreglo. Es posible acceder y cambiar toda la información del arreglopor medio de su nombre y sus índices. Un apuntador constante hacia datos no constantes puedeservir para recibir como argumento un arreglo en una función que accede a elementos del mismo,empleado sólo notación de índices. Los apuntadores que se declaran como const deben serinicializados cuando se declaran (si el apuntador es un parámetro de función, se inicializa con unapuntador que se pasa a la función)Ejemplo 20.6El programa MODIFICAR2.CPP, intenta modificar un apuntador constante. El apuntador ptr se declara como detipo int * const. Esta declaración se lee de derecha a izquierda como ptr es un apuntador constante hacia unentero. El apuntador se inicializa con la dirección de la variable entera x. El programa intenta asignarle a ptr ladirección de y, lo cual genera un mensaje de error. Observe que al asignarle el valor 7 a *ptr no se produce un error–el valor al que apunta ptr sigue siendo modificable.APUNTADORES Y CADENA – LECCIÓN 20 20-13
  14. 14. MIGUEL Á. TOLEDO MARTÍNEZ /* El siguiente programa: MODIFICAR2.CPP, intenta modificar un apuntador constante hacia datos no constantes. */ #include <iostream.h> //Para cout y cin void main(void) { int x, y; /* ptr es un apuntador constante hacia un entero. Se puede modificar un entero a través de ptr, pero ptr siempre apunta a la misma localidad de memoria. */ int *const ptr = &x; *ptr = 7; ptr = &y; }//Fin de main() Los mensajes que envía el compilador son los siguientes: Cannot modify a const object ‘y’ is declared but never used El menor privilegio de acceso se otorga mediante un apuntador constante hacia datosconstantes. Tal apuntador siempre apunta a la misma localidad de memoria y los datos de dichalocalidad no pueden ser modificados. Esta es la manera en que debe pasarse un arreglo a unafunción que sólo consulta dicho arreglo por medio de notación de índices y que no lo modifica.Ejemplo 20.7 El programa MODIFICAR3.CPP, declara la variable de apuntador ptr como de tipo const int *const. Esta declaración se lee de derecha a izquierda como ptr es un apuntador constante hacia un entero constante. El programa muestra los mensajes de error generados al intentar modificar los datos hacia los que apunta ptr y al intentar alterar la dirección almacenada en la variable de apuntador. Observe que al intentar enviar a la salida el valor al que apunta ptr no se genera un error, pues en la instrucción de salida no se está modificando nada. /* El siguiente programa: MODIFICAR3.CPP, intenta modificar un apuntador constante hacia datos constantes. */ #include <iostream.h> //Para cout y cin void main(void) { int x = 5, y; / /* ptr es un apuntador constante hacia un entero constante. ptr siempre apunta a la misma localidad y el entero de dicha localidad no puede ser modificado. */ const int *const ptr = &x; cout << *ptr << endl; *ptr = 7; ptr = &y; }//Fin de main()APUNTADORES Y CADENA – LECCIÓN 20 20-14
  15. 15. MIGUEL Á. TOLEDO MARTÍNEZORDENAMIENTO DE burbuja MEDIANTE LLAMADA POR REFERENCIAEjemplo 20.8 El programa BURBUJA1.CPP, de la lección 18 se modificará para que utilice dos funciones clasificBurbuja() e intercambiar() (véase el programa BURBUJA.CPP) La función clasificBurbuja() se encarga de ordenar el arreglo. Llama a la función intercambiar() para que intercambie los elementos del arreglo arreglo[j] y arreglo[j + 1] Recuerde que C++ aplica el ocultamiento de información entre funciones, por lo que intercambiar() no tiene acceso a los elementos individuales del arreglo clasificBurbuja() Debido a que clasificBurbuja() quiere que intercambiar() tenga acceso a los elementos del arreglo que se intercambiarán, pasa cada uno de ellos a intercambiar() mediante una llamada por referencia; la dirección de cada elemento del arreglo se pasa explícitamente. Aunque los arreglos completos se pasan automáticamente mediante llamada por referencia, los elementos individuales del arreglo son escalares y por lo general se pasan mediante una llamada por valor. Así que clasificBurbuja() utiliza el operador de dirección (&) en cada elemento del arreglo en la llamada de intercambiar(), como sigue: Intercambiar(&arreglo[j], &arreglo[j + 1]); Poniendo en práctica la llamada por referencia. La función intercambiar() recibe &arreglo[j] en la variable de apuntador elemento1Ptr. Gracias al ocultamiento de información, intercambiar() no puede saber el nombre arreglo[j], pero puede utilizar *elemento1Ptr como sinónimo de arreglo[j] Por lo tanto, cuando intercambiar() hace referencia a *elemento1Ptr, de hecho está referenciando a arreglo[j] en clasificBurbuja() De igual manera, cuando intercambiar() hace referencia a *elemento2Ptr, en realidad está referenciando a arreglo[j + 1] en clasificBurbuja() Aun cuando no se permite que intercambiar() diga: temporal = arreglo[j]; arreglo[j] = arreglo[j + 1]; arreglo[j + 1] = temporal; se logra exactamente el mismo efecto mediante: en la función intercambiar del programa BURBUJA.CPP /* El siguiente programa: BURBUJA.CPP, pone los valores en un arreglo, los ordena en orden ascendente e imprime el arreglo resultante. */ #include <iostream.h> //Para cout y cin #include <iomanip.h> //Para setw() void clasificBurbuja(int *, const int); void main(void) { const int TAMANO_ARREGLO = 10; int arreglo[TAMANO_ARREGLO] = {2, 6, 4, 8, 10, 12, 89, 68, 45, 37}; int i; cout << "Datos en el orden originaln"; for(i = 0; i < TAMANO_ARREGLO; i++) cout << setw(4) << arreglo[i]; clasificBurbuja(arreglo, TAMANO_ARREGLO); //Ordena el arreglo cout << "nDatos en orden ascendenten";APUNTADORES Y CADENA – LECCIÓN 20 20-15
  16. 16. MIGUEL Á. TOLEDO MARTÍNEZ for(i = 0; i < TAMANO_ARREGLO; i++) cout << setw(4) << arreglo[i]; cout << endl; }//Fin de main() void clasificBurbuja(int *arre, const int tamano) { void intercambiar(int *, int *); for(int pasada = 0; pasada < tamano - 1; pasada++) for(int j = 0; j < tamano -1; j++) if(arre[j] > arre[j+1]) intercambiar(&arre[j], &arre[j+1]); }//Fin de clasificBurbuja() void intercambiar(int *elemento1Ptr, int *elemento2Ptr) { int temporal = *elemento1Ptr; *elemento1Ptr = *elemento2Ptr; *elemento2Ptr = temporal; }//Fin de intercambiar() Deben observarse varias características de la función clasificBurbuja() El encabezado de la función declara arreglo como int *arre, en lugar de como int arre[], indicando que clasificBurbuja() recibe como argumento un arreglo de un solo índice (nuevamente, estas notaciones son intercambiables) El parámetro tamano se declara como const, aplicando el principio de menor privilegio. Aunque el parámetro tamano recibe una copia de un valor de main() y la modificación de la copia no puede cambiar el valor en main() clasificBurbuja() no necesita alterar tamano para que cumpla su tarea. El tamaño del arreglo permanece fijo durante la ejecución de clasificBurbuja() Por lo tanto, tamano se declara como const para asegurarse de que no se pueda modificar. Si el tamaño del arreglo se modificara durante el proceso de ordenamiento, el algoritmo de ordenamiento no se ejecutaría correctamente. El prototipo de la función intercambiar() está incluido en el cuerpo de la función clasificBurbuja() porque es la única función que llama a intercambiar() La colocación del prototipo en clasificBurbuja() restringe las llamadas correctas a intercambiar() a aquellas que se llevan a cabo desde clasificBurbuja() Otras funciones que intentan llamar a intercambiar() no tienen acceso a un prototipo de función apropiado. Por lo general, éste es un error de sintaxis, pues C++ requiere prototipos de función. Observe que la función clasificBurbuja() recibe como parámetro el tamaño del arreglo. Dicha función debe saber el tamaño del arreglo para poder ordenarlo. Cuando se pasa un arreglo a una función, ésta recibe la dirección de memoria del primer elemento del arreglo. El tamaño del arreglo se debe pasar por separado. Al definir la función clasificBurbuja() para que reciba como parámetro el tamaño del arreglo, se logra que cualquier programa que ordene arreglos de enteros de un solo índice de tamaño arbitrario utilice dicha función. El tamaño del arreglo podría haberse programado directamente en la función. Esto limita el uso de la función a un arreglo de un tamaño específico y reduce su capacidad de reutilización. Sólo los programas que procesen arreglos de enteros de un solo índice y que sean del tamaño especificado podrán utilizar la función.Ejemplo 20.9 C++ ofrece el operador unario sizeof, el cual determina el tamaño en bytes de un arreglo (o de cualquier otro tipo de datos) durante la compilación del programa. Cuando al nombre de un arreglo se le aplica el operador sizeof, como en el programa SIZEOF.CPP, éste devuelve la cantidad total de bytes que hay en elAPUNTADORES Y CADENA – LECCIÓN 20 20-16
  17. 17. MIGUEL Á. TOLEDO MARTÍNEZ arreglo como un valor de tipo size_t, que generalmente es un unsigned int. La computadora empleada aquí almacena las variables de tipo float en 4 bytes de memoria, y arreglo se ha declarado como de 20 elementos, por lo que arreglo ocupa 80 bytes de memoria. Al aplicarle el operador sizeof a un parámetro de apuntador de una función que recibe un arreglo como argumento, dicho operador devuelve el tamaño en bytes (4) del apuntador, no el tamaño del arreglo. /* El siguiente programa: SIZEOF.CPP, utiliza el operador sizeof() aplicado a un nombre de arreglo, devuelve la cantidad de bytes del arreglo. */ #include <iostream.h> //Para cout y cin size_t obtenerTamano(float *); void main(void) { float arreglo[20]; cout << "El número de bytes en el arreglo es " << sizeof(arreglo) << "nEl número de bytes devuelto por obtenerTamano() es " << obtenerTamano(arreglo) << endl; }//Fin de main() size_t obtenerTamano(float *ptr) { return sizeof(ptr); }//fin de obtenerTamano() El número de elementos de un arreglo también puede determinarse por medio de los resultados de dos operaciones de sizeof. Por ejemplo, considere la siguiente declaración de arreglo: double arregloReal[22]; Si las variables del tipo de datos double se almacenan en 8 bytes de memoria, el arreglo arregloReal contiene un total de 176 bytes. Para determinar el número de elementos del arreglo, puede utilizarse la siguiente expresión: sizeof (arregloReal) / sizeof(double); La expresión determina el número de bytes en el arreglo arregloReal y divide este valor entre el número de bytes que se utiliza en memoria para almacenar un valor double.Ejemplo 20.10 El programa SIZEOF2.CPP se vale del operador sizeof para calcular el número de bytes utilizados para almacenar cada uno de los tipos de datos estándar de la computadora particular que se está empleado. /* El siguiente programa: SIZEOF2.CPP, muestra el uso del operador sizeof */ #include <iostream.h> //Para cout y cin #include <iomanip.h> //Para setw()APUNTADORES Y CADENA – LECCIÓN 20 20-17
  18. 18. MIGUEL Á. TOLEDO MARTÍNEZ void main(void) { char c; short s; int i; long l; float f; double d; long double ld; int arreglo[20], *ptr = arreglo; cout << "sizeof c = " << sizeof c << "tsizeof(char) = " << sizeof(char) << "nsizeof s = " << sizeof s << "tsizeof(short) = " << sizeof(short) << "nsizeof i = " << sizeof i << "tsizeof(int) = " << sizeof(int) << "nsizeof l = " << sizeof l << "tsizeof(long) = " << sizeof(long) << "nsizeof f = " << sizeof f << "tsizeof(float) = " << sizeof(float) << "nsizeof d = " << sizeof d << "tsizeof(double) = " << sizeof(double) << "nsizeof ld = " << sizeof ld << "tsizeof(long double) = " << sizeof(long double) << "nsizeof arreglo = " << sizeof arreglo << "nsizeof ptr = " << sizeof ptr << endl; }//Fin de main() El operador sizeof se puede aplicar a cualquier nombre de variable, nombre de tipo o valor constante. Cuando se aplica a un nombre de variable (que no sea un nombre de arreglo) o valor constante, se devuelve el número de bytes que se emplean para almacenar el tipo de variable o constante específicos. Observe que los paréntesis que se utilizan con sizeof son obligatorios si se indica como operando un nombre de tipo; no se requieren si se indica como operando un nombre de variable. Es importante recordar que sizeof es un operador, no una función.EXPRESIONES DE APUNTADORES Y ARITMÉTICA DE APUNTADORES Los apuntadores son operandos válidos en las expresiones aritméticas, las expresiones deasignación y las expresiones de comparación. Sin embargo, no todos los operadores que seutilizan normalmente en estas expresiones son válidos con variables de apuntador. Esta seccióndescribe los operadores que pueden tener apuntadores como sus operandos, además de la maneraen que se utilizan. Es posible llevar a cabo un conjunto limitado de operaciones aritméticas sobre losapuntadores. Un apuntador puede incrementarse (++) o decrementarse (--), además es posiblesumarle un entero a un apuntador (+ o +=), restar un entero de un apuntador (- o -=) y restar unapuntador de otro. Suponga que se ha declarado un arreglo int v[5] y que su primer elemento está en lalocalidad 3000 de la memoria. Además, suponga que se ha inicializado el apuntador vPtr paraque apunte a v[0], es decir, el valor de vPtr es 3000. La figura 20.5 es un diagrama de dichaAPUNTADORES Y CADENA – LECCIÓN 20 20-18
  19. 19. MIGUEL Á. TOLEDO MARTÍNEZsituación en una máquina con enteros de 4 bytes. Observe que vPtr puede inicializarse para queapunte al arreglo v mediante cualquiera de las siguientes instrucciones: vPtr = v; vPtr = &v[0]; Localidad 3000 3004 3008 3012 3016 v[0] v[1] v[2] v[3] v[4] • Variable de apuntador vPtr Figura 20.5. El arreglo v y la variable de apuntador vPtr que apunta a v. En la aritmética convencional, la suma de 3000 + 2 da el valor 3002. Normalmente ésteno es el caso con la aritmética de apuntadores. Cuando a un apuntador se le suma o resta unentero, dicho apuntador no se incrementa o decrementa en la misma cantidad que el entero, sinoen el entero por el tamaño del objeto hacia el que apunta el apuntador. El número de bytesdepende del tipo de datos del objeto. Por ejemplo, la instrucción vPtr += 2;producirá 3008 (3000 + 2 * 4), suponiendo que los enteros se almacenan en 4 bytes de memoria.En el arreglo v, vPtr ahora apuntará a v[2] (vea la figura 20.6) Localidad 3000 3004 3008 3012 3016 v[0] v[1] v[2] v[3] v[4] • Variable de apuntador vPtr Figura 20.6. El apuntador vPtr después de la aritmética de apuntadores. Si se almacena un entero en 2 bytes de memoria, entonces el cálculo anterior dará lalocalidad de memoria 3004 (3000 + 2 * 2) Si el arreglo fuera de un tipo de datos distinto, lainstrucción previa incrementaría el apuntador por el doble del número de bytes necesarios paraalmacenar un objeto de dicho tipo de datos. Al efectuar aritmética de apuntadores sobre unAPUNTADORES Y CADENA – LECCIÓN 20 20-19
  20. 20. MIGUEL Á. TOLEDO MARTÍNEZarreglo de caracteres, los resultados serán consistentes con la aritmética común, pues cadacarácter tiene un byte de longitud. Si vPtr se hubiera incrementado a 3016, que apunta a v[4], la instrucción vPtr -= 4;establecería vPtr nuevamente a 3000 –el comienzo del arreglo. Si se está incrementando odecrementando en uno un apuntador, es posible utilizar los operadores de incremento (++) ydecremento (--) Las instrucciones ++vPtr; vPtr++;incrementan el apuntador para que apunte a la siguiente localidad del arreglo. Las instrucciones --vPtr; vPtr--;decrementan el apuntador para que apunte al elemento previo del arreglo. Las variables de apuntador pueden restarse entre ellas. Por ejemplo, si vPtr contiene lalocalidad 3000 y v2Ptr la dirección 3008, la instrucción x = v2Ptr –vPtr;le asignará a x el número de elementos del arreglo de vPtr a v2Ptr, que es 2 en este caso. Laaritmética de apuntadores carece de significado a menos que se lleve a cabo sobre un arreglo. Nopodemos suponer que dos variables del mismo tipo están almacenadas de manera contigua en lamemoria, a menos que sean elementos adyacentes de un arreglo. Es posible asignar un apuntador a otro, si ambos son del mismo tipo. De otro modo, esnecesario emplear un operador de conversión mediante cast para convertir el valor del apuntadordel lado derecho de la asignación al tipo de apuntador a la izquierda de la asignación. Laexcepción a esta regla es el apuntador a void (es decir, void *), que es un apuntador genéricocapaz de representar cualquier tipo de apuntador. Todos los tipos de apuntadores pueden serasignados a void sin necesidad de una conversión mediante cast. Sin embargo, un apuntador avoid no puede asignarse directamente a un apuntador de otro tipo; primero hay que convertir elapuntador void al tipo de apuntador adecuado. No es posible desreferenciar un apuntador void *. Por ejemplo, el compilador sabe que unapuntador a int hace referencia a cuatro bytes de memoria en una máquina con enteros de 4bytes, pero los apuntadores a void simplemente contienen localidades de memoria para un tipode datos desconocido; por lo tanto, el compilador no sabe el número preciso de bytes a los quehace referencia el apuntador. El compilador debe saber el tipo de datos para determinar elnúmero de bytes a desreferenciar para un apuntador en particular. En el caso de un apuntador avoid, esta cantidad de bytes no puede determinarse a partir del tipo.APUNTADORES Y CADENA – LECCIÓN 20 20-20
  21. 21. MIGUEL Á. TOLEDO MARTÍNEZ Los apuntadores se pueden comparar por medio de operadores de igualdad y relacionales,pero tales comparaciones no tienen significado a menos que los apuntadores apunten a miembrosdel mismo arreglo. Las comparaciones de apuntadores comparan las direcciones almacenadas enlos apuntadores. Una comparación de dos apuntadores que apuntan al mismo arreglo podríamostrar, por ejemplo, que un apuntador apunta a un elemento de índice mayor que el otroapuntador. Un uso común de la comparación de apuntadores es la determinación de si unapuntador es 0.RELACIÓN ENTRE APUNTADORES Y ARREGLOS Los arreglos y los apuntadores están relacionados íntimamente en C++ y se puedenutilizar de manera casi intercambiable. Es posible pensar en los nombres de arreglos como sifueran apuntadores constantes. Los apuntadores pueden servir para realizar cualquier operaciónen la que intervengan índices de arreglos. Suponga que se han declarado tanto el arreglo de entero b[5] como la variable entera deapuntador bPtr. Debido a que el nombre del arreglo (sin índice) es un apuntador al primerelemento del mismo, podemos establecer bPtr a la dirección del primer elemento del arreglo bpor medio de la instrucción bPtr = b; Esto es equivalente a tomar la dirección del primer elemento del arreglo, como sigue bPtr = &b[0]; El elemento b[3] del arreglo se puede referencia de manera alterna con la expresión deapuntador *(bPtr + 3) El 3 de la expresión previa es el desplazamiento al apuntador. Cuando el apuntadorapunta al inicio de un arreglo, el desplazamiento indica el elemento del arreglo al que se haráreferencia y el valor de dicho desplazamiento es idéntico al índice del arreglo. La notación previase conoce como notación de apuntador y desplazamiento. Los paréntesis son necesarios porquela precedencia de * es mayor que la de +. Sin éstos, la expresión anterior sumaría 3 al valor de laexpresión *bPtr (es decir, se sumaría 3 a b[0]), suponiendo que bPtr apuntara al inicio delarreglo. Así como el elemento del arreglo se puede referenciar con una expresión de apuntador,la dirección &b[3]se puede escribir con la expresión de apuntador bPtr + 3 El arreglo mismo puede ser tratado como apuntador y utilizado con aritmética deapuntadores. Por ejemplo, la expresión *(b + 3)APUNTADORES Y CADENA – LECCIÓN 20 20-21
  22. 22. MIGUEL Á. TOLEDO MARTÍNEZtambién se refiere al elemento del arreglo b[3]. En general, todas las expresiones de arreglos coníndices se pueden escribir con un apuntador y un desplazamiento. En este caso, se utilizónotación de apuntador y desplazamiento con el nombre del arreglo como apuntador. Observe quela instrucción previa no modifica de ninguna manera el nombre del arreglo; b aún apunta alprimer elemento de la misma. Los puntadores se pueden indexar de la misma manera que los arreglos. Por ejemplo, laexpresión bPtr[1]hace referencia al elemento b[1] del arreglo; esta expresión se conoce como notación deapuntador e índice. Recuerde que, en esencia, los nombres de arreglos son apuntadores constantes; siempreapuntan al inicio del arreglo. Por lo tanto, la expresión b += 3es inválida debido a que intenta modificar el valor del nombre del arreglo por medio dearitmética de apuntadores.Ejemplo 20.11 El programa APUNTADORES.CPP, utiliza los cuatro métodos que hemos explicado para hacer referencia a los elementos de un arreglo (indización de arreglos, apuntador y desplazamiento con el nombre del arreglo como apuntador, indización de apuntadores y apuntador y desplazamiento con un apuntador) para imprimir los cuatro elementos del arreglo de enteros b. /* El siguiente programa: APUNTADORES.CPP, utiliza las notaciones de índices y por apuntadores con arreglos. */ #include <iostream.h> //Para cout y cin void main(void) { int b[] = {10, 20, 30, 40}; int *bPtr = b; //Establece bPtr para que apunte al arreglo b cout << "Arreglo b impreso con:n" << "Notación de índicesn"; for(int i = 0; i < 4; i++) cout << "b[" << i << "] = " << b[i] << n; cout << "nNotación apuntador/desplazamiento en donden" << "el apuntador es el nombre del arreglon"; for(int desplazamiento = 0; desplazamiento < 4; desplazamiento++) cout << "*(b + " << desplazamiento << ") = " << *(b + desplazamiento) << n;APUNTADORES Y CADENA – LECCIÓN 20 20-22
  23. 23. MIGUEL Á. TOLEDO MARTÍNEZ cout << "nNotacion de índices de apuntadorn"; for(int i = 0; i < 4; i++) cout << "bPtr[" << i << "] = " << bPtr[i] << n; cout << "nNotación apuntador/desplazamienton"; for(int desplazamiento = 0; desplazamiento < 4; desplazamiento++) cout << "*(bPtr + " << desplazamiento << ") = " << *(bPtr + desplazamiento) << n; }//Fin de main()Ejemplo 20.12 Para ilustrar aún más que los arreglos y los apuntadores son intercambiables, vea las dos funciones de copia de cadenas (copia1() y copia2()) del programa APUNTADORES2.CPP. Ambas funciones copian una cadena a un arreglo de caracteres. Tras una comparación de los prototipos de función de copia1() y copia2(), ambas funciones parecen idénticas (gracias a que los arreglos y los apuntadores son intercambiables) Estas funciones llevan a cabo la misma tarea, pero se implementan de manera diferente. /* El siguiente programa: APUNTADORES2.CPP, copia una cadena por medio de notación de arreglos y notación de apuntadores. */ #include <iostream.h> //Para cout y cin void copia1(char *, const char *); void copia2(char *, const char *); void main(void) { char cadena1[10], *cadena2 = "Hola", cadena3[10], cadena4[] = "Adiós"; copia1(cadena1, cadena2); cout << "cadena1 = " << cadena1 << endl; copia2(cadena3, cadena4); cout << "cadena3 = " << cadena3 << endl; }//Fin de main() //Copia s2 a s1 mediante notación de arreglos void copia1(char *s1, const char *s2) { for(int i = 0; (s1[i] = s2[i]) != 0; i++) ; //El cuerpo no hace nada }//Fin de copia1() //Copia s2 a s1 mediante notación de apuntadores void copia2(char *s1, const char *s2) { for(; (*s1 = *s2) != 0; s1++, s2++) ; //El cuerpo no hace nada }//Fin de copia2()APUNTADORES Y CADENA – LECCIÓN 20 20-23
  24. 24. MIGUEL Á. TOLEDO MARTÍNEZ La función copia1() utiliza notación de índices de arreglo para copiar la cadena s2 al arreglo de caracteres s1. Además, declara una variable entera i, que funciona como contador, la cual será el índice del arreglo. El encabezado de la estructura for realiza la operación completa de copia –su cuerpo es la instrucción vacía. El encabezado especifica que i se inicializa a cero y se incrementa en uno con cada iteración del ciclo. La condición for, (s1[i] = s2[i]) != ‘0’, realiza la operación de copia, carácter por carácter, de s2 a s1. Al llegar al carácter nulo en s2, éste es asignado a s1 y termina el ciclo, pues el carácter nulo es igual a ‘0’. Recuerde que el valor de una instrucción de asignación el valor asignado al argumento de la izquierda. La función copia2() se vale de apuntadores y aritmética de apuntadores para copiar la cadena de s2 al arreglo de caracteres s1. Otra vez, el encabezado de la estructura for efectúa toda la operación de copia. Dicho encabezado no incluye ninguna iniciación de variables. Como en la función copia1(), la condición (*s1 = *s2) != ‘0’ realiza la operación de copia. El apuntador s2 es desreferenciado y el carácter resultante es asignado al apuntador desreferenciado s1. Tras la asignación que sucede en la condición, se incrementan los apuntadores para que apunten al siguiente elemento del arreglo s1 y al siguiente carácter de la cadena s2, respectivamente. Al encontrar el carácter nulo en s2, se le asigna al apuntador desreferenciado s1 y termina el ciclo. Observe que el primer argumento, tanto de copia1() como de copia2(), debe ser un arreglo lo bastante grande para contener la cadena del segundo argumento. De otra manera, puede suceder un error cuando se intente escribir en una localidad de memoria fuera de los límites del arreglo. Además observe que el segundo parámetro de cada función se declara como const char * (cadena constante) En ambas funciones se copia el segundo argumento al primero –los caracteres se copian del segundo argumento, uno a la vez, pero nunca se modifican. Por lo tanto, se declara el segundo parámetro de modo que apunte a un valor constante, aplicando así el principio de menor privilegio. Ninguna de las dos funciones necesita modificar el segundo argumento, por lo tanto no tienen la capacidad de hacerlo.ARREGLOS DE APUNTADORES Los arreglos pueden contener apuntadores. Un uso común de tal estructura de datos espara formar arreglos de cadenas. Cada entrada del arreglo es una cadena, pero en C++ unacadena es, en esencia, un apuntador a su primer carácter. Por lo tanto, cada entrada de un arreglode cadenas es, de hecho, un apuntador al primer carácter de una cadena. Considere la declaracióndel arreglo de cadena palos, que podría ser útil para representar una baraja de naipes. char *palos[4] = {“Corazones”, “Diamantes”, “Tréboles”, “Espadas”}; La parte palos[4] de la declaración indica un arreglo de 4 elementos. La parte char *indica que cada elemento del arreglo palos es del tipo apuntador a char. Los cuatro valores aponer en el arreglo son los palos de la baraja, “Corazones”, “Diamantes”, “Tréboles” y“Espadas”. Cada uno de ellos se almacena en memoria como una cadena de caracteres quetermina con el carácter nulo y cuya longitud es de un carácter más que el número de caracteresque hay entre comillas. Las cadenas son de 10, 10, 9 y 8 caracteres de longitud, respectivamente.Aunque parece que estas cadenas se ponen en el arreglo palos, de hecho sólo se almacenanapuntadores (vea la figura 20.7)-cada apuntador apunta al primer carácter de su cadenacorrespondiente. Por lo tanto, aun cuando el arreglo palos es de tamaño fijo, da acceso a cadenasde caracteres de cualquier longitud. Esta flexibilidad es un ejemplo de las poderosasposibilidades de estructuración de datos que tiene C++. Las cadenas de los distintos palos de la baraja podrían ponerse en un arreglo de dobleíndice en el que cada fila representa un palo y cada columna una de las letras del nombre delpalo. Tal estructura de datos debe tener un número fijo de columnas por fila, que tiene que ser tangrande como la cadena más larga. Por lo tanto, se desperdiciaría una cantidad importante dememoria si se almacenara un gran número de cadenas cuando la mayoría de ellas fuera más cortaAPUNTADORES Y CADENA – LECCIÓN 20 20-24
  25. 25. MIGUEL Á. TOLEDO MARTÍNEZque la cadena más larga. Para ayudar a representar una baraja, en la siguiente sección se empleanarreglos de cadenas. palos[0] • ‘C’ ‘o’ ‘r’ ‘a’ ‘z’ ‘o’ ‘n’ ‘e’ ‘s’ ‘0’ palos[0] • ‘D’ ‘i’ ‘a’ ‘m’ ‘a’ ‘n’ ‘t’ ‘e’ ‘s’ ‘0’ palos[0] • ‘T’ ‘r’ ‘e’ ‘b’ ‘o’ ‘l’ ‘e’ ‘s’ ‘0’ palos[0] • ‘E’ ‘s’ ‘p’ ‘a’ ‘d’ ‘a’ ‘s’ ‘0’ Figura 20.7. Representación gráfica del arreglo palos.CASO DE ESTUDIO: Simulación de barajado y repartición de naipes En esta sección utilizamos la generación aleatoria de números para desarrollar unprograma de simulación de barajado y repartición de naipes. Este programa puede utilizarsedespués para implementar programas de distintos juegos de baraja. Para relevar algunos sutilesproblemas de desempeño, hemos utilizado intencionalmente algoritmos de barajado y reparticiónsubóptimos. En los ejercicios desarrollaremos algoritmos más eficientes. Mediante el enfoque de refinación descendente paso a paso, se desarrolla un programaque baraja 52 naipes y luego los reparte. Dicho enfoque es especialmente útil cuando se abordanproblemas mayores y más complejos de los que hemos visto en las primeras lecciones. Para representar la baraja, utilizamos un arreglo paquete de doble índice, de 4 por 13 (veala figura 20.8) Las filas corresponden a los palos: la fila 0 a los corazones, la fila 1 a losdiamantes, la 2 a los tréboles y la 3 a las espadas. Las columnas corresponden a los valores delos naipes: las columnas 0 a 9 corresponden del as al 10, respectivamente, y las columnas de la10 a la 12 corresponden a la sota, la reina y el rey. Cargaremos el arreglo de cadenas palos conlas cadenas de caracteres que representan los cuatro palos y el arreglo de cadena cara con lascadenas de caracteres que corresponden a los trece valores de los naipes. 0 1 2 3 4 5 6 7 8 9 10 11 12 Corazones 0 Diamantes 1 Tréboles 2 Espadas 3 Figura 20.8. Arreglo (paquete) de doble índice que representa una baraja.APUNTADORES Y CADENA – LECCIÓN 20 20-25
  26. 26. MIGUEL Á. TOLEDO MARTÍNEZ En la figura 20.8 cada columna (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12) representan: As,Dos, Tres, Cuatro, Cinco, Seis, Siete, Ocho, Nueve, Diez, Sota, Reina y Rey respectivamente. Así paquete[2][12] (parte sombreada de la figura 20.8) representa al rey de tréboles. Tréboles Rey Esta baraja simulada puede barajarse como sigue. Primero, el arreglo paquete se poneen ceros. Luego se seleccionan al azar una fila (renglón, 0 a 3) y una columna (columna, 0 a 12)El número 1 se inserta en el elemento del arreglo paquete[renglón][columna], indicando queeste naipe va a ser el primero de la baraja en ser repartido. Este proceso continua con la inserciónaleatoria de los números 2, 3, ..., 52 en el arreglo paquete, con lo que se indica el segundo,tercero, ... y quincuagésimo segundo naipe a repartir. A medida que se va llenando el arreglopaquete con números de naipe, es posible que una carta se seleccione dos veces, es decir, quepaquete[renglón][columna] sea distinto de cero cuando sea seleccionado. Esta selecciónsimplemente se ignora y se seleccionan al azar otro renglón y otra columna hasta dar con unnaipe no seleccionado. Tarde o temprano los números del 1 al 52 ocuparán los 52 espacios delarreglo paquete. En este momento se termina el barajado de los naipes. Este algoritmo de barajado podría ejecutarse durante un tiempo indefinidamente grandesi se seleccionan repetidamente al azar naipes que ya han sido barajados. Este fenómeno seconoce como aplazamiento indefinido. En los ejercicios se estudiará un mejor algoritmo debarajado que elimina la posibilidad de dicho fenómeno. Para repartir el primer naipe se busca en el arreglo la posiciónpaquete[renglón][columna] que sea igual a 1. Esto se logra mediante una estructura for anidadaque varía renglón de 0 a 3 y columna de 0 a 12. ¿A qué naipe corresponde dicho espacio delarreglo? El arreglo palos ha sido precargado con los cuatro palos, por lo que, para obtener elpalo, se imprime la cadena de caracteres palos[renglón] De igual manera, para obtener el valordel naipe, se imprime la cadena de caracteres cara[columna] También se imprime la cadena “ de“. La impresión de esta información en el orden correcto permite imprimir cada naipe en laforma Rey de Tréboles, As de Diamantes, etcétera. Procedamos mediante la refinación descendente paso a paso. La parte superiorsimplemente es: Baraja y reparte 52 naipes. La primera refinación da: Inicializa el arreglo de palos (palos) Inicializa el arreglo de valores (cara) Inicializa el arreglo de la baraja (paquete) Baraja los naipes. Reparte los 52 naipes. Baraja los naipes se puede expandir como sigue: Por cada uno de los 52 naipes Coloca el número del naipe en un espacio desocupado de la baraja seleccionada al azar.APUNTADORES Y CADENA – LECCIÓN 20 20-26
  27. 27. MIGUEL Á. TOLEDO MARTÍNEZ Reparte los 52 naipes se puede expandir como sigue: Por cada uno de los 52 naipes Encuentra un número de naipe en el arreglo de la baraja e imprime su valor y palo. La incorporación de estas expansiones da la segunda refinación completa: Inicializa el arreglo de palos. Inicializa el arreglo de valores. Inicializa el arreglo de la baraja. Por cada uno de los 52 naipes Coloca el número del naipe en un espacio desocupado de la baraja seleccionando al azar. Por cada uno de los 52 naipes Encuentra un número de naipe en el arreglo de la baraja e imprime su valor y palo. Coloca el número del naipe en un espacio desocupado de la baraja seleccionado al azarse puede expandir como sigue: Selecciona al azar el espacio de la baraja. Mientras ya haya sido seleccionado el espacio de la baraja Selecciona al azar dicho espacio. Coloca el número del naipe en el espacio seleccionado de la baraja. Encuentra el número de naipe en el arreglo de la baraja e imprime su valor y palo sepuede expandir como sigue: Por cada espacio del arreglo de la baraja Si el espacio contiene un número de naipe Imprime el valor y el palo del naipe. La incorporación de estas expansiones da la tercera refinación: Inicializa el arreglo de palos. Inicializa el arreglo de valores. Inicializa el arreglo de la baraja. Por cada uno de los 52 naipes Selecciona al azar dicho espacio. Coloca el número de naipe en el espacio seleccionado de la baraja. Por cada uno de los 52 naipes Por cada espacio del arreglo de la baraja Si el espacio contiene un número de naipe Imprime el valor y el palo del naipe. Con esto se termina el proceso de refinación. Observe que este programa es más eficientesi las partes de barajado y repartición del algoritmo se combinan, de modo que cada naipe sereparta a medida que se pone en la baraja. Decidimos programar estas operaciones por separadodebido a que los naipes normalmente se reparten después de haber sido barajados (no a medidaque se barajan)APUNTADORES Y CADENA – LECCIÓN 20 20-27
  28. 28. MIGUEL Á. TOLEDO MARTÍNEZEjemplo 20.13 En el programa BARAJAS.CPP, se muestra el barajado y repartición de naipes. Observe el formato de salida que se utiliza en la función repartir(): cout << setw(5) << setiosflags(ios::right) << wCara[columna] << “ de “ << setw(8) << setiosflags(ios::left) << wPalos[renglon] << (baraja % 2 == 0 ? ‘n’ : ‘t’); La instrucción de salida anterior provoca que el valor del naipe se envíe a la salida justificado a la derecha en un campo de 5 caracteres, y que el palo se envíe a la salida justificado a la izquierda en un campo de 8 caracteres. La salida se imprime en formado de dos columnas. Si el naipe que se envía a la salida está en la primera columna, se envía una tabulación a la salida tras él para saltar a la segunda columna: de otra manera, se envía un salto de línea. /* El siguiente programa: BARAJAS.CPP, baraja y reparte naipes. */ #include <iostream.h> //Para cout y cin #include <iomanip.h> //Para setw() #include <stdlib.h> //Para srand() y rand() #include <time.h> //Para Time() void barajar(int[][13]); void repartir(const int [][13], const char *[], const char *[]); void main(void) { const char *palos[4] = {"Corazones", "Diamantes", "Tréboles", "Espadas"}; const char *cara[13] = {"As", "Dos", "Tres", "Cuatro", "Cinco", "Seis", "Siete", "Ocho", "Nueve", "Diez", "Sota", "Reina", "Rey"}; int paquete[4][13] = {0}; srand(time(0)); barajar(paquete); repartir(paquete, cara, palos); }//Fin de main() void barajar(int wPaquete[][13]) { int renglon, columna; for(int baraja = 1; baraja <= 52; baraja++) { do { renglon = rand() % 4; columna = rand() % 13; } while(wPaquete[renglon][columna] != 0); wPaquete[renglon][columna] = baraja; }//Fin del for }//Fin de barajar()APUNTADORES Y CADENA – LECCIÓN 20 20-28
  29. 29. MIGUEL Á. TOLEDO MARTÍNEZ void repartir(const int wPaquete[][13], const char *wCara[], const char *wPalos[]) { for(int baraja = 1; baraja <= 52; baraja++) for(int renglon = 0; renglon <= 3; renglon++) for(int columna = 0; columna <= 12; columna++) if(wPaquete[renglon][columna] == baraja) cout << setw(5) << setiosflags(ios::right) << wCara[columna] << " de " << setw(8) << setiosflags(ios::left) << wPalos[renglon] << (baraja % 2 == 0 ? n : t); }//Fin de repartir()Hay una debilidad en el algoritmo de repartición. Una vez encontrado el número que se busca, incluso si seencuentra al primer intento, las estructuras for internas continúan buscando dicho número en los elementos restantesde paquete. En los ejercicios se corrige esta deficiencia.APUNTADORES Y FUNCIONES Un apuntador a una función contiene la dirección que tiene la función en la memoria.Según hemos visto el nombre de un arreglo es, en realidad, la dirección inicial en memoria delprimer elemento del mismo. Del mismo modo, un nombre de función en realidad es la direccióninicial en memoria del código que lleva a cabo la tarea de la función. Los apuntadores afunciones pueden ser pasados a las funciones, devueltos de ellas, almacenados en arreglos yasignados a otros apuntadores a funciones.Ejemplo 20.14 Para ilustrar el uso de los apuntadores a funciones, hemos modificado el programa de ordenamiento de burbuja de esta lección al cual llamamos BURBUJA.CPP, dando lugar al siguiente programa, BURBUJA2.CPP. Este nuevo programa consiste en main() y en las funciones burbuja(), intercambiar(), ascendente() y descendente() La función burbuja() recibe como argumento un apuntador a una función – sea ascendente() o descendente()- además de un arreglo de enteros y su tamaño. El programa le pide al usuario que seleccione si el arreglo debe ordenarse de manera ascendente o descendente. Si el usuario indica 1, a burbuja() se le pasa un apuntador a la función ascendente(), lo que causa que el arreglo se ordena de menor a mayor. Si el usuario indica 2, a burbuja() se le pasa un apuntador a la función descendente(), lo que causa que el arreglo se ordene de mayor a menor. /* El siguiente programa: BURBUJA2.CPP, de ordenamiento de usos múltiples que utiliza apuntadores a funciones. */ #include <iostream.h> //Para cout y cin #include <iomanip.h> //Para setw() void burbuja(int[], const int, int (*)(int, int)); int ascendente(int, int); int descendente(int, int); void main(void) { const int TAMANO_ARREGLO = 10; int orden, contador, arreglo[TAMANO_ARREGLO] = {2, 6, 4, 8, 10, 12, 89, 68, 45, 37};APUNTADORES Y CADENA – LECCIÓN 20 20-29
  30. 30. MIGUEL Á. TOLEDO MARTÍNEZ cout << "Teclee 1 para ordenar en forma ascendente,n" << "Teclee 2 para ordenar en forma descendente: "; cin >> orden; cout << "nDatos en el orden originaln"; for(contador = 0; contador < TAMANO_ARREGLO; contador++) cout << setw(4) << arreglo[contador]; if(orden == 1) { burbuja(arreglo, TAMANO_ARREGLO, ascendente); cout << "nDatos en orden ascendenten"; }//Fin del if else { burbuja(arreglo, TAMANO_ARREGLO, descendente); cout << "nDatos en orden descendenten"; }//Fin del else for(contador = 0; contador < TAMANO_ARREGLO; contador++) cout << setw(4) << arreglo[contador]; cout << endl; }//Fin de main() void burbuja(int trabajar[], const int tamano, int (*comparar)(int, int)) { void intercambiar(int *, int *); for(int pasada = 1; pasada < tamano; pasada++) for(int contador = 0; contador < tamano -1; contador++) if((*comparar)(trabajar[contador], trabajar[contador + 1])) intercambiar(&trabajar[contador], &trabajar[contador + 1]); }//Fin de burbuja void intercambiar(int *elemento1Ptr, int *elemento2Ptr) { int temporal; temporal = *elemento1Ptr; *elemento1Ptr = *elemento2Ptr; *elemento2Ptr = temporal; }//Fin de intercambiar() int ascendente(int a, int b) { return b < a; //Intercambia si b es menor que a }//Fin de ascendente() int descendente(int a, int b) { return b > a; //Intercambia si b es mayor que a }//Fin de descendente()APUNTADORES Y CADENA – LECCIÓN 20 20-30
  31. 31. MIGUEL Á. TOLEDO MARTÍNEZ El siguiente parámetro aparece en el encabezado de la función burbuja(): int ( *comparar )( ínt, int) Esto le dice a burbuja() que espere un parámetro que es un apuntador a una función que recibe dos parámetros enteros y devuelve un resultado entero. Se necesitan paréntesis alrededor de *comparar debido a que * tiene menor precedencia que el paréntesis que encierra los parámetros de la función. Si no se hubieran incluido los paréntesis, la declaración habría sido int *comparar( int, int ) que declara una función que recibe dos enteros como parámetros y le devuelve un apuntador a un entero. El parámetro correspondiente del prototipo de función de burbuja() es Int (*) ( int, int) Observe que sólo se han incluido los tipos, pero, con fines de documentación, el programador puede incluir nombres que ignorará el compilador. La función que se le pasa a burbuja() es llamada en una instrucción if como sigue if((*comparar )( trabajar[ contador], trabajar[contador + 1])) Así como se desreferencia un apuntador a una variable para acceder al valor de ésta, se desreferencian los apuntadores a funciones para ejecutar dichas funciones. La llamada a la función podría haberse hecho sin desreferenciar el apuntador, como en if ( *comparar( trabajar[ contador ], trabajar[ contador + 1])) que utiliza el apuntador directamente como nombre de función. Preferirnos el primer método, donde la llamada a una función es a través de un apuntador, pues explícitamente ilustra que compare es un apuntador a una función que se desreferencia para llamar a la función. El segundo método para llamar a la función a través de un apuntador da la apariencia de que comparar() de hecho es una función. Esto puede confundir a algún usuario del programa que quisiera ver la definición de la función comparar() y descubriera que no se definió en ninguna parte del archivo. Uno de los usos de los apuntadores a funciones es en los sistemas operados por menús. Al usuario se le solicita desde un menú que dé una opción (por ejemplo, de 1 a 5) Cada opción es atendida por una función distinta. En un arreglo de apuntadores a funciones se almacenan los apuntadores a todas las funciones. Se toma la selección del usuario como índice del arreglo y se emplea el apuntador del mismo para llamar a la función.Ejemplo 20.15 El programa siguiente: APUNTADORES3.CPP presenta un ejemplo general de la mecánica de declaración y empleo de un arreglo de apuntadores a funciones. Se definen tres funciones (funcion1(), funcion2() y funcion3()) que toman un argumento entero y no devuelven nada. Los apuntadores a estas tres funciones se almacenan en el arreglo f, que se declara como sigue: void( *f[ 3] ) ( int) = { funcion1, funcion2, funcion3};La declaración se lee comenzando por el par de paréntesis de la izquierda, f es un arreglo de 3 apuntadores afunciones que toman un int como argumento y devuelven void". El arreglo se inicializa con los nombres de lasfunciones (que, nuevamente, son apuntadores) Cuando el usuario introduce un valor entre 0 y 2, se toma comoíndice del arreglo de apuntadores a funciones. La llamada de la función se hace como sigue:APUNTADORES Y CADENA – LECCIÓN 20 20-31
  32. 32. MIGUEL Á. TOLEDO MARTÍNEZ ( *f[opcion]) (opcion); En la llamada, f[opcion] selecciona el apuntador que se encuentra en la localidad opcion del arreglo. El apuntador se desreferencia para que llame a la función, y opcion se pasa como argumento de la función. Cada función imprime el valor de su argumento y su nombre, indicando que se llamó de manera correcta. En los ejercicios se desarrollará un sistema operador por menús./* El siguiente programa: APUNTADORES3.CPP, muestra un arreglo de apuntadores a funciones. */#include <iostream.h> //Para cout y cinvoid funcion1(int);void funcion2(int);void funcion3(int);void main(void) { void(*f[3])(int) = {funcion1, funcion2, funcion3}; int opcion; cout << "Introduzca un número entre 0 y 2, 3 para terminar: "; cin >> opcion; while(opcion >= 0 && opcion < 3) { (*f[opcion])(opcion); cout << "Introduzca un número entre 0 y 2, 3 para terminar: "; cin >> opcion; }//Fin de while cout << "Ejecución del programa finalizada." << endl; }//Fin de main()void funcion1(int a) { cout << "Introdujo " << a << " por ello se llamó a funcion1()nn"; }//Fin de funcion1()void funcion2(int b) { cout << "Introdujo " << b << " por ello se llamó a funcion2()nn"; }//Fin de funcion2()void funcion3(int c) { cout << "Introdujo " << c << " por ello se llamó a funcion3()nn"; }//Fin de funcion3()APUNTADORES Y CADENA – LECCIÓN 20 20-32

×