Apuntadores y Cadenas

Anuncio
MIGUEL Á. TOLEDO MARTÍNEZ
CONTENIDO DE LA LECCIÓN 20
APUNTADORES Y CADENAS
1. Introducción
2. Declaración e iniciación de variables de apuntador
3. Operadores de los apuntadores
3.1. Ejemplo 20.1
4. Llamado de funciones por referencia
4.1. Ejemplo 20.2
5. Empleo del calificador const con apuntadores
5.1. Ejemplo 2 20.3, 20.4, 20.5, 20.6, 20.7
6. Ordenamiento de burbuja mediante llamada por referencia
6.1. Ejemplos 20.8, 20.9, 20.10
7. Expresiones de apuntadores y aritmética de apuntadores
8. Relación entre apuntadores y arreglos
8.1. Ejemplos 20.11, 20.12
9. Arreglos de apuntadores
10. Caso de estudio: Simulación de barajado y repartición de naipes
10.1. Ejemplo 20.13
3
37
4
6
6
7
10
11
15
15
18
21
22
24
25
28
11. Apuntadores y funciones
29
11.1. Ejemplos 20.14, 20.15
29
12. Introducción al procesamiento de caracteres y cadenas
12.1. Fundamentos de los caracteres y las cadenas
12.2. Funciones de manipulación de cadenas de la biblioteca de manejo de cadenas
12.2.1. Ejemplo 20.16, 20.17, 20.18, 20.19, 20.20
12.3. Ejercicios resueltos
12.4. Funciones de cadena que utilizan cadenas ubicadas fuera de los 64 kb (far string)
12.5. Número de ocurrencias de un carácter dentro de una cadena
12.6. Contar el número de ocurrencia de una subcadena dentro de una cadena
12.7. Obtener un índice a una subcadena
12.8. Obtener la ocurrencia mas a la derecha de una subcadena
12.9. Remover una subcadena contenida dentro de una cadena
12.10. Reemplazo de una subcadena por otra
12.11. Determinar si un carácter es alfanumérico
12.12. Determinar si un carácter es una letra del alfabeto
12.13. Determinar si un carácter contiene un valor ASCII
12.14. Determinar si un carácter es un carácter de control
12.15. Determinar si un carácter es un digito
12.16. Determinar si un carácter es un carácter gráfico
12.17. Determinar si un carácter es mayúscula o minúscula
12.18. Determinar si un carácter es imprimible
12.19. Determinar si un carácter es un símbolo de puntuación
12.20. Determinar si un carácter es el carácter espacio
12.21. Determinar si un carácter es un valor hexadecimal
12.22. Carácter ASCII válido
13. Pensando en objetos: Iteraciones entre los objetos
14. Errores comunes de programación
15. Buenas prácticas de programación
16. Propuestas de desempeño
APUNTADORES Y CADENA – LECCIÓN 20
33
33
34
36
40
47
47
53
54
54
54
55
56
56
56
56
56
57
57
57
57
58
58
59
64
65
66
67
20-1
MIGUEL Á. TOLEDO MARTÍNEZ
17. Sugerencias de portabilidad
18. Observaciones de Ingeniería de Software
19. Indicaciones de prueba y depuración
20. Lo que necesita saber
21. Preguntas y problemas
21.1. Preguntas
21.2. Problemas
21.2.1.
21.2.2.
21.2.3.
21.2.4.
21.2.5.
Sección especial: construya su propia computadora
Más problemas de apuntadores
Problemas de manipulación de cadenas
Sección especial: manipulación avanzada de cadenas
Interesante proyecto de manipulación de cadenas
APUNTADORES Y CADENA – LECCIÓN 20
67
67
67
68
70
70
73
75
79
84
85
88
20-2
MIGUEL Á. TOLEDO MARTÍNEZ
LECCIÓN 20
APUNTADORES Y CADENAS
INTRODUCCIÓN
En esta lección se estudiará una de las características más poderosas del lenguaje de
programación C++: el apuntador. Los apuntadores son una de las capacidades de C++ más
difíciles de dominar. En otra lección vimos que las referencias pueden servir para hacer llamadas
por referencia. Los apuntadores permiten a los programas simular la llamada por referencia y
crear y manipular estructuras dinámicas de datos, es decir, estructuras de datos que pueden crecer
y encogerse, como las listas vinculadas, colas, pilas y árboles. Esta lección explica los conceptos
básicos acerca de los apuntadores. También refuerza la relación íntima entre los arreglos, los
apuntadores y las cadenas e incluye un amplio conjunto de ejemplos de procesamiento de
cadenas.
En otro semestre se estudiarán el empleo de los apuntadores con estructuras. La
programación orientada a objetos se efectúa con apuntadores y referencias. En ese semestre se
presentan técnicas de administración dinámica de memoria y ejemplos de creación y uso de
estructuras dinámicas de datos.
El enfoque de los arreglos y cadenas como apuntadores se deriva de C. En la especialidad
de 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 apuntadores
contienen direcciones de variables que contienen valores específicos. En este sentido, los
nombres de variables hacen referencia directa a un valor y los apuntadores hacen referencia
indirecta a un valor (figura 20.1) La referencia a un valor a través de un apuntador se llama
indirección.
APUNTADORES Y CADENA – LECCIÓN 20
20-3
MIGUEL Á. TOLEDO MARTÍNEZ
contador
7
contadorPtr
•
contador hace referencia directa a una
variable cuyo valor es 7
contador
7
contadorPtr hace referencia indirecta a una
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. La
declaración:
int *contadorPtr, contador;
declara la variable contadorPtr como de tipo int * (es decir, como un apuntador a un valor
entero) y se lee contadorPtr es un apuntador a int o contadorPtr apunta a un objeto de clase
entero. Además, la variable contador se declara como entero, no como apuntador a un entero. En
esta declaración, el * solamente se aplica a contadorPtr. Cada variable que se declara como
apuntador 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ón
de esta manera indica que la variable que se está declarando es un apuntador. Los apuntadores
pueden 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 de
asignación. Un apuntador puede inicializarse a 0, a NULL o a una dirección. Un apuntador con
el valor 0 o NULL no apunta a nada. NULL es una constante simbólica definida en el archivo de
encabezado <iostream.h> (y en varios archivos de encabezado de la biblioteca estándar) La
iniciació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 valor
entero que puede asignarse directamente a una variable de apuntador sin convertir primero
mediante cast el entero a un tipo de apuntador. En la siguiente sección se estudia la asignación de
la 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 su
operando. Por ejemplo, suponiendo las declaraciones:
int y = 5;
int *yPtr;
la instrucción
yPtr = &y;
APUNTADORES Y CADENA – LECCIÓN 20
20-4
MIGUEL Á. TOLEDO MARTÍNEZ
asigna la dirección de la variable y a la variable de apuntador yPtr. Se dice entonces que la
variable yPtr apunta a y. La figura 20.2 muestra una representación esquemática de la memoria
tras la ejecución de la asignación previa. En la figura mostramos la relación de apuntador
dibujando 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 la
variable 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); el
operador de dirección no puede aplicarse a constantes, a expresiones que no den como resultado
referencias, ni a variables declaradas con la clase de almacenamiento register.
yPtr
500000
y
600000
600000
5
El operador *, conocido comúnmente como operador de indirección u operador de
desreferenciación, devuelve un sinónimo, alias o apodo del objeto hacia el que apunta su
operando (es decir, su apuntador) Por ejemplo (haciendo referencia a la figura 20.2), la
instrucció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 una
instrucció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 para
recibir 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
MIGUEL Á. TOLEDO MARTÍNEZ
Ejemplo 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;
int *aPtr;
//a es un entero
//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
<< "\n\nEl valor de a es: " << a
<< "\nEl valor de *Ptr es: " << *aPtr;
cout
<< "\n\nMostrando 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 por
valor, mediante llamada por referencia con argumentos de referencia y mediante llamada por
referencia con argumentos de apuntador. En lección anterior comparamos e hicimos la
distinción entre llamada por valor y llamada por referencia con argumentos de referencia. En esta
lección nos concentraremos en la llamada por referencia con argumentos de apuntador.
Como antes hemos visto, return puede utilizarse para devolverle al invocador un valor
desde una función llamada (o regresar el control desde una función llamada sin devolver un
valor) También vimos que se le pueden pasar argumentos a una función mediante argumentos de
referencia, permitiendo que la función modifique los valores originales de los argumentos (por lo
tanto, se puede devolver más de un valor desde una función), o pasar objetos de datos grandes a
una función y evitar la sobrecarga de pasar los objetos mediante llamada por valor (que, claro
está, 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 pasar
apuntadores a objetos de datos grandes, evitando la sobrecarga de pasar los objetos mediante
llamada por valor.
APUNTADORES Y CADENA – LECCIÓN 20
20-6
MIGUEL Á. TOLEDO MARTÍNEZ
En C++, los programadores se pueden valer de los apuntadores y del operador de
indirección para simular llamadas por referencia (de la misma manera que se logran las llamadas
por referencia en C) Al llamar una función con argumentos que deben ser modificados, se pasa la
direcció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 se
pasan mediante el operador &, pues el nombre de éstos es la localidad inicial del mismo en
memoria (el nombre del arreglo es equivalente a &nombreArreglo[0], es decir, un nombre de
arreglo ya es un apuntador. Cuando se pasa la dirección de una variable a una función, se puede
emplear el operador de indirección (*) en la función, creando un sinónimo, alias o apodo del
nombre de la variable –con éste, a su vez, es posible modificar el valor de la localidad de
memoria 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;
}//Fin de cuboValor()
//Eleva al cubo la variable local n
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
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;
}//Fin de cuboReferencia()
//Eleva al cubo el número en main()
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()
{
int numero = 5;
numero
5
int cuboValor(int n)
{
return n * n * n;
}//Fin de cuboValor()
n
indefinido
numero = cuboValor(numero);
}//Fin de main()
APUNTADORES Y CADENA – LECCIÓN 20
20-8
MIGUEL Á. TOLEDO MARTÍNEZ
Después de que cuboValor() recibe la llamada:
int main()
{
int numero = 5;
numero
5
int cuboValor(int n)
{
return n * n * n;
}//Fin de cuboValor()
n
5
numero = cuboValor(numero);
}//Fin de main()
Después de que cuboValor() eleve al cubo el parámetro n:
int main()
{
int numero = 5;
numero
5
int cuboValor(int n)
{
125
return n * n * n;
}//Fin de cuboValor()
n
int cuboValor(int n)
{
return n * n * n;
}//Fin de cuboValor()
n
indefinido
numero = cuboValor(numero);
}//Fin de main()
Después de que cuboValor() regresa a main():
int main()
numero
{
5
int numero = 5;
125
numero = cuboValor(numero);
}//Fin de main()
indefinido
Después de que main() completa la asignación de numero:
int main()
{
int numero = 5;
numero
125
int cuboValor(int n)
{
return n * n * n;
}//Fin de cuboValor()
n
indefinido
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()
{
int numero = 5;
numero
5
void cuboReferencia(int *nPtr)
{
nPtr
*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()
{
int numero = 5;
numero
5
void cuboReferencia(int *nPtr)
{
nPtr
*nPtr = * nPtr * *nPtr * *nPtr;
}//Fin de cuboValor()
•
cuboReferencia(&numero);
}//Fin de main()
APUNTADORES Y CADENA – LECCIÓN 20
20-9
MIGUEL Á. TOLEDO MARTÍNEZ
Después de elevar *nPtr al cubo:
int main()
{
int numero = 5;
numero
125
void cuboReferencia(int *nPtr)
{
nPtr
*nPtr = * nPtr * *nPtr * *nPtr;
}//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 debe
modificar 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ámetros
mediante llamada por referencia. ¿Cómo escoger entre las seis posibilidades? Sea la guía el
principio de menor privilegio. Siempre habrá que dar a una función el suficiente acceso a la
informació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 por
valor, se obtiene una copia del argumento (o argumentos) en la llamada de función, la cual es la
que pasa a la función. Si en la función se modifica la copia, el valor original en el invocador se
conserva inalterado. En muchos casos, un valor pasado a una función es modificado para que la
función pueda llevar a cabo su tarea. Sin embargo, en algunos casos, el valor no debe ser alterado
en 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 su
tamaño, y que imprime dicho arreglo. Tal función deberá recorrer el arreglo utilizando un ciclo y
enviar a la salida, uno por uno, los elementos del mismo. El tamaño del arreglo se utiliza en el
cuerpo de la función para determinar el índice más grande del arreglo y poder terminar el ciclo
cuando se ha terminado la impresión. El tamaño del arreglo no cambia en el cuerpo de la
funció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 por
accidente. Si se intenta modificar una valor const, el compilador lo detecta y emite un aviso o un
error, dependiendo del compilador.
Hay cuatro maneras de pasar una apuntador a una función: con un apuntador no
constante hacia datos no constantes, mediante un apuntador no constante hacia datos
constantes, por medio de un apuntador constante hacia datos no constantes y utilizando un
apuntador constante hacia datos constantes. Cada combinación ofrece un nivel distinto de
privilegio 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 se
puede modificar para que apunte hacia otros datos. La declaración de apuntadores no constantes
APUNTADORES Y CADENA – LECCIÓN 20
20-10
MIGUEL Á. TOLEDO MARTÍNEZ
hacia datos no constante no incluye a const. Tal apuntador puede servir para recibir una cadena
en una función que se vale de aritmética de apuntadores para procesar (y tal vez modificar) uno
por uno los caracteres de la cadena. La función convertirMayuscula() en el programa
MAYÚSCULA.CPP declara el parámetro sPtr (char *sPtr) como apuntador no constante hacia
datos no constantes. La función procesa la cadena cadena, carácter por carácter, mediante
aritmética de apuntadores. Los caracteres entre la a y la z son convertidos a sus letras mayúsculas
a través de la función toupper(), los demás se conservan iguales. La función toupper() toma
como argumento un carácter. Si dicho carácter es una letra minúscula, se devuelve la letra
mayú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>
#include <ctype.h>
//Para cout y cin
//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);
++sPtr;
}//Fin del while
}//Fin de convertirMayuscula()
//Convierte a mayúsculas
//Mueve sPtr al siguiente carácter
Un apuntador no constante hacia datos constantes es un apuntador que se puede modificar
para que apunte hacia cualquier elemento de información del tipo apropiado, pero los datos a los
que apunta no pueden modificarse por medio de ese apuntador. Tal apuntador podría servir para
recibir como argumento un arreglo en una función que procesará cada elemento de dicho arreglo
sin modificar los datos.
APUNTADORES Y CADENA – LECCIÓN 20
20-11
MIGUEL Á. TOLEDO MARTÍNEZ
Ejemplo 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);
}//Fin de main()
APUNTADORES Y CADENA – LECCIÓN 20
//f intenta una modificación ilegal
20-12
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 de
datos relacionados del mismo tipo bajo un mismo nombre. En otra lección se estudiará otra
forma de tipo de datos agrupados, llamado estructura (a veces conocido como registro en otros
lenguajes) Una estructura es capaz de almacenar elementos de datos relacionados de distintos
tipos bajo un mismo nombre (por ejemplo, para almacenar información sobre todos los
empleados de una compañía) Al invocar una función con un arreglo como argumento, éste se
pasa de manera automática a la función simulando una llamada por referencia. Sin embargo, las
estructuras 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 elementos
de 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 ésta
se ejecuta) Cuando hay que pasar una estructura a una función, puede utilizarse un apuntador
hacia datos constantes (o una referencia hacia datos constantes), logrando el desempeño de una
llamada por referencia y la protección de una llamada por valor. Cuando el apuntador se pasa
hacia una estructura, sólo hay que copiar la dirección en la que se almacena tal estructura. En una
máquina con direcciones de 4 bytes, se hace una copia de 4 bytes de memoria, en lugar de los, tal
vez, cientos o miles de bytes de la estructura.
Un apuntador constante hacia datos no constantes es un apuntador que siempre apunta a
la misma localidad de memoria; los datos de dicha localidad se pueden modificar a través del
apuntador. Esto es lo predeterminada para un nombre de arreglo, el cual es un apuntador
constante hacia el inicio del arreglo. Es posible acceder y cambiar toda la información del arreglo
por medio de su nombre y sus índices. Un apuntador constante hacia datos no constantes puede
servir 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 ser
inicializados cuando se declaran (si el apuntador es un parámetro de función, se inicializa con un
apuntador que se pasa a la función)
Ejemplo 20.6
El programa MODIFICAR2.CPP, intenta modificar un apuntador constante. El apuntador ptr se declara como de
tipo int * const. Esta declaración se lee de derecha a izquierda como ptr es un apuntador constante hacia un
entero. El apuntador se inicializa con la dirección de la variable entera x. El programa intenta asignarle a ptr la
direcció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
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 datos
constantes. Tal apuntador siempre apunta a la misma localidad de memoria y los datos de dicha
localidad no pueden ser modificados. Esta es la manera en que debe pasarse un arreglo a una
funció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
MIGUEL Á. TOLEDO MARTÍNEZ
ORDENAMIENTO DE burbuja MEDIANTE LLAMADA POR REFERENCIA
Ejemplo 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>
#include <iomanip.h>
//Para cout y cin
//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 original\n";
for(i = 0; i < TAMANO_ARREGLO; i++)
cout << setw(4) << arreglo[i];
clasificBurbuja(arreglo, TAMANO_ARREGLO); //Ordena el arreglo
cout << "\nDatos en orden ascendente\n";
APUNTADORES Y CADENA – LECCIÓN 20
20-15
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 el
APUNTADORES Y CADENA – LECCIÓN 20
20-16
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>
#include <iomanip.h>
APUNTADORES Y CADENA – LECCIÓN 20
//Para cout y cin
//Para setw()
20-17
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 de
asignación y las expresiones de comparación. Sin embargo, no todos los operadores que se
utilizan normalmente en estas expresiones son válidos con variables de apuntador. Esta sección
describe los operadores que pueden tener apuntadores como sus operandos, además de la manera
en que se utilizan.
Es posible llevar a cabo un conjunto limitado de operaciones aritméticas sobre los
apuntadores. Un apuntador puede incrementarse (++) o decrementarse (--), además es posible
sumarle un entero a un apuntador (+ o +=), restar un entero de un apuntador (- o -=) y restar un
apuntador de otro.
Suponga que se ha declarado un arreglo int v[5] y que su primer elemento está en la
localidad 3000 de la memoria. Además, suponga que se ha inicializado el apuntador vPtr para
que apunte a v[0], es decir, el valor de vPtr es 3000. La figura 20.5 es un diagrama de dicha
APUNTADORES Y CADENA – LECCIÓN 20
20-18
MIGUEL Á. TOLEDO MARTÍNEZ
situación en una máquina con enteros de 4 bytes. Observe que vPtr puede inicializarse para que
apunte al arreglo v mediante cualquiera de las siguientes instrucciones:
vPtr = v;
vPtr = &v[0];
Localidad
3000
3004
v[0]
3008
v[1]
3012
v[2]
3016
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 éste
no es el caso con la aritmética de apuntadores. Cuando a un apuntador se le suma o resta un
entero, dicho apuntador no se incrementa o decrementa en la misma cantidad que el entero, sino
en el entero por el tamaño del objeto hacia el que apunta el apuntador. El número de bytes
depende 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
v[0]
v[1]
3008
v[2]
3012
v[3]
3016
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á la
localidad de memoria 3004 (3000 + 2 * 2) Si el arreglo fuera de un tipo de datos distinto, la
instrucción previa incrementaría el apuntador por el doble del número de bytes necesarios para
almacenar un objeto de dicho tipo de datos. Al efectuar aritmética de apuntadores sobre un
APUNTADORES Y CADENA – LECCIÓN 20
20-19
MIGUEL Á. TOLEDO MARTÍNEZ
arreglo de caracteres, los resultados serán consistentes con la aritmética común, pues cada
cará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 o
decrementando en uno un apuntador, es posible utilizar los operadores de incremento (++) y
decremento (--)
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 la
localidad 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. La
aritmética de apuntadores carece de significado a menos que se lleve a cabo sobre un arreglo. No
podemos suponer que dos variables del mismo tipo están almacenadas de manera contigua en la
memoria, 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, es
necesario emplear un operador de conversión mediante cast para convertir el valor del apuntador
del lado derecho de la asignación al tipo de apuntador a la izquierda de la asignación. La
excepción a esta regla es el apuntador a void (es decir, void *), que es un apuntador genérico
capaz de representar cualquier tipo de apuntador. Todos los tipos de apuntadores pueden ser
asignados a void sin necesidad de una conversión mediante cast. Sin embargo, un apuntador a
void no puede asignarse directamente a un apuntador de otro tipo; primero hay que convertir el
apuntador void al tipo de apuntador adecuado.
No es posible desreferenciar un apuntador void *. Por ejemplo, el compilador sabe que un
apuntador a int hace referencia a cuatro bytes de memoria en una máquina con enteros de 4
bytes, pero los apuntadores a void simplemente contienen localidades de memoria para un tipo
de datos desconocido; por lo tanto, el compilador no sabe el número preciso de bytes a los que
hace referencia el apuntador. El compilador debe saber el tipo de datos para determinar el
número de bytes a desreferenciar para un apuntador en particular. En el caso de un apuntador a
void, esta cantidad de bytes no puede determinarse a partir del tipo.
APUNTADORES Y CADENA – LECCIÓN 20
20-20
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 miembros
del mismo arreglo. Las comparaciones de apuntadores comparan las direcciones almacenadas en
los apuntadores. Una comparación de dos apuntadores que apuntan al mismo arreglo podría
mostrar, por ejemplo, que un apuntador apunta a un elemento de índice mayor que el otro
apuntador. Un uso común de la comparación de apuntadores es la determinación de si un
apuntador es 0.
RELACIÓN ENTRE APUNTADORES Y ARREGLOS
Los arreglos y los apuntadores están relacionados íntimamente en C++ y se pueden
utilizar de manera casi intercambiable. Es posible pensar en los nombres de arreglos como si
fueran apuntadores constantes. Los apuntadores pueden servir para realizar cualquier operación
en la que intervengan índices de arreglos.
Suponga que se han declarado tanto el arreglo de entero b[5] como la variable entera de
apuntador bPtr. Debido a que el nombre del arreglo (sin índice) es un apuntador al primer
elemento del mismo, podemos establecer bPtr a la dirección del primer elemento del arreglo b
por 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 de
apuntador
*(bPtr + 3)
El 3 de la expresión previa es el desplazamiento al apuntador. Cuando el apuntador
apunta 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 previa
se conoce como notación de apuntador y desplazamiento. Los paréntesis son necesarios porque
la precedencia de * es mayor que la de +. Sin éstos, la expresión anterior sumaría 3 al valor de la
expresión *bPtr (es decir, se sumaría 3 a b[0]), suponiendo que bPtr apuntara al inicio del
arreglo. 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 de
apuntadores. Por ejemplo, la expresión
*(b + 3)
APUNTADORES Y CADENA – LECCIÓN 20
20-21
MIGUEL Á. TOLEDO MARTÍNEZ
tambié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 que
la instrucción previa no modifica de ninguna manera el nombre del arreglo; b aún apunta al
primer elemento de la misma.
Los puntadores se pueden indexar de la misma manera que los arreglos. Por ejemplo, la
expresión
bPtr[1]
hace referencia al elemento b[1] del arreglo; esta expresión se conoce como notación de
apuntador e índice.
Recuerde que, en esencia, los nombres de arreglos son apuntadores constantes; siempre
apuntan al inicio del arreglo. Por lo tanto, la expresión
b += 3
es inválida debido a que intenta modificar el valor del nombre del arreglo por medio de
aritmé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 índices\n";
for(int i = 0; i < 4; i++)
cout << "b[" << i << "] = " << b[i] << '\n';
cout
<< "\nNotación apuntador/desplazamiento en donde\n"
<< "el apuntador es el nombre del arreglo\n";
for(int desplazamiento = 0; desplazamiento < 4; desplazamiento++)
cout
<< "*(b + " << desplazamiento << ") = "
<< *(b + desplazamiento) << '\n';
APUNTADORES Y CADENA – LECCIÓN 20
20-22
MIGUEL Á. TOLEDO MARTÍNEZ
cout << "\nNotacion de índices de apuntador\n";
for(int i = 0; i < 4; i++)
cout << "bPtr[" << i << "] = " << bPtr[i] << '\n';
cout
<< "\nNotación apuntador/desplazamiento\n";
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
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 es
para formar arreglos de cadenas. Cada entrada del arreglo es una cadena, pero en C++ una
cadena es, en esencia, un apuntador a su primer carácter. Por lo tanto, cada entrada de un arreglo
de cadenas es, de hecho, un apuntador al primer carácter de una cadena. Considere la declaración
del 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 a
poner 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 que
termina con el carácter nulo y cuya longitud es de un carácter más que el número de caracteres
que 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 almacenan
apuntadores (vea la figura 20.7)-cada apuntador apunta al primer carácter de su cadena
correspondiente. Por lo tanto, aun cuando el arreglo palos es de tamaño fijo, da acceso a cadenas
de caracteres de cualquier longitud. Esta flexibilidad es un ejemplo de las poderosas
posibilidades 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 del
palo. Tal estructura de datos debe tener un número fijo de columnas por fila, que tiene que ser tan
grande como la cadena más larga. Por lo tanto, se desperdiciaría una cantidad importante de
memoria si se almacenara un gran número de cadenas cuando la mayoría de ellas fuera más corta
APUNTADORES Y CADENA – LECCIÓN 20
20-24
MIGUEL Á. TOLEDO MARTÍNEZ
que la cadena más larga. Para ayudar a representar una baraja, en la siguiente sección se emplean
arreglos 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 un
programa de simulación de barajado y repartición de naipes. Este programa puede utilizarse
después para implementar programas de distintos juegos de baraja. Para relevar algunos sutiles
problemas de desempeño, hemos utilizado intencionalmente algoritmos de barajado y repartición
subóptimos. En los ejercicios desarrollaremos algoritmos más eficientes.
Mediante el enfoque de refinación descendente paso a paso, se desarrolla un programa
que baraja 52 naipes y luego los reparte. Dicho enfoque es especialmente útil cuando se abordan
problemas 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 (vea
la figura 20.8) Las filas corresponden a los palos: la fila 0 a los corazones, la fila 1 a los
diamantes, la 2 a los tréboles y la 3 a las espadas. Las columnas corresponden a los valores de
los naipes: las columnas 0 a 9 corresponden del as al 10, respectivamente, y las columnas de la
10 a la 12 corresponden a la sota, la reina y el rey. Cargaremos el arreglo de cadenas palos con
las cadenas de caracteres que representan los cuatro palos y el arreglo de cadena cara con las
cadenas 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
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 pone
en 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 que
este naipe va a ser el primero de la baraja en ser repartido. Este proceso continua con la inserción
aleatoria 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 arreglo
paquete con números de naipe, es posible que una carta se seleccione dos veces, es decir, que
paquete[renglón][columna] sea distinto de cero cuando sea seleccionado. Esta selección
simplemente se ignora y se seleccionan al azar otro renglón y otra columna hasta dar con un
naipe no seleccionado. Tarde o temprano los números del 1 al 52 ocuparán los 52 espacios del
arreglo paquete. En este momento se termina el barajado de los naipes.
Este algoritmo de barajado podría ejecutarse durante un tiempo indefinidamente grande
si se seleccionan repetidamente al azar naipes que ya han sido barajados. Este fenómeno se
conoce como aplazamiento indefinido. En los ejercicios se estudiará un mejor algoritmo de
barajado que elimina la posibilidad de dicho fenómeno.
Para repartir el primer naipe se busca en el arreglo la posición
paquete[renglón][columna] que sea igual a 1. Esto se logra mediante una estructura for anidada
que varía renglón de 0 a 3 y columna de 0 a 12. ¿A qué naipe corresponde dicho espacio del
arreglo? El arreglo palos ha sido precargado con los cuatro palos, por lo que, para obtener el
palo, se imprime la cadena de caracteres palos[renglón] De igual manera, para obtener el valor
del 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 la
forma Rey de Tréboles, As de Diamantes, etcétera.
Procedamos mediante la refinación descendente paso a paso. La parte superior
simplemente 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
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 azar
se 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 se
puede 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 eficiente
si las partes de barajado y repartición del algoritmo se combinan, de modo que cada naipe se
reparta a medida que se pone en la baraja. Decidimos programar estas operaciones por separado
debido a que los naipes normalmente se reparten después de haber sido barajados (no a medida
que se barajan)
APUNTADORES Y CADENA – LECCIÓN 20
20-27
MIGUEL Á. TOLEDO MARTÍNEZ
Ejemplo 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>
#include <iomanip.h>
#include <stdlib.h>
#include <time.h>
//Para cout y cin
//Para setw()
//Para srand() y rand()
//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
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 se
encuentra al primer intento, las estructuras for internas continúan buscando dicho número en los elementos restantes
de 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 del
primer elemento del mismo. Del mismo modo, un nombre de función en realidad es la dirección
inicial en memoria del código que lleva a cabo la tarea de la función. Los apuntadores a
funciones pueden ser pasados a las funciones, devueltos de ellas, almacenados en arreglos y
asignados 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>
#include <iomanip.h>
//Para cout y cin
//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]
APUNTADORES Y CADENA – LECCIÓN 20
= {2, 6, 4, 8, 10, 12,
89, 68, 45, 37};
20-29
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 original\n";
for(contador = 0; contador < TAMANO_ARREGLO; contador++)
cout << setw(4) << arreglo[contador];
if(orden == 1)
{
burbuja(arreglo, TAMANO_ARREGLO, ascendente);
cout << "\nDatos en orden ascendente\n";
}//Fin del if
else
{
burbuja(arreglo, TAMANO_ARREGLO, descendente);
cout << "\nDatos en orden descendente\n";
}//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
*elemento2Ptr
}//Fin de intercambiar()
int ascendente(int a, int b)
{
return b < a;
}//Fin de ascendente()
int descendente(int a, int b)
{
return b > a;
}//Fin de descendente()
APUNTADORES Y CADENA – LECCIÓN 20
= *elemento1Ptr;
= *elemento2Ptr;
= temporal;
//Intercambia si b es menor que a
//Intercambia si b es mayor que a
20-30
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 a
funciones que toman un int como argumento y devuelven void". El arreglo se inicializa con los nombres de las
funciones (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
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 cin
void 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()\n\n";
}//Fin de funcion1()
void funcion2(int b)
{
cout
<< "Introdujo " << b
<< " por ello se llamó a funcion2()\n\n";
}//Fin de funcion2()
void funcion3(int c)
{
cout
<< "Introdujo " << c
<< " por ello se llamó a funcion3()\n\n";
}//Fin de funcion3()
APUNTADORES Y CADENA – LECCIÓN 20
20-32
MIGUEL Á. TOLEDO MARTÍNEZ
INTRODUCCIÓN AL PROCESAMIENTO DE CARACTERES Y CADENAS
En esta sección se presentarán algunas de las funciones de la biblioteca estándar que
simplifican el procesamiento de cadenas. Las técnicas que se estudian aquí son adecuadas para el
desarrollo de editores de texto, procesadores de texto, software de formación de páginas,
sistemas tipográficos computarizados y otros tipos de software de procesamiento de texto. Aquí
empleamos cadenas basadas en apuntadores.
FUNDAMENTOS DE LOS CARACTERES Y LAS CADENAS
En C++, los caracteres son los bloques de construcción fundamentales de los programas
fuente. Todos los programas se componen de una secuencia de caracteres que, agrupados de
manera significativa, son interpretados por la computadora como una serie de instrucciones que
sirven para llevar a cabo una tarea. Un programa puede contener constantes de caracteres. Una
constante de carácter es un valor entero representado corno un carácter entre apóstrofos. El valor
de dicha constante es el valor entero del carácter en el conjunto de caracteres de la máquina. Por
ejemplo, ‘ z’ representa el valor entero de z (122 en el conjunto de caracteres ASCII) y ‘ \n’
representa el valor entero del salto de línea (10 en el conjunto de caracteres ASCII)
Una cadena es una serie de caracteres que se trata como unidad. Una cadena puede
incluir letras, dígitos y diversos caracteres especiales, como +, -, *, /, $ y otros. En C++ las
literales de cadena o constantes de cadena se escriben entre comillas, como sigue:
“Miguel Ángel Toledo Martínez”
“Ave. I.P.N. 36”
“México, D. F.”
“(201) 555-12121”
(un nombre)
(una dirección)
(una ciudad y un estado)
(un número telefónico)
Una cadena en C++ es un arreglo de caracteres que termina con el carácter nulo (‘ \0’)
Una cadena se accede por medio de un apuntador al primer carácter de dicha cadena. El valor de
una cadena es la dirección (constante) de su primer carácter. Por lo tanto, en C++ es apropiado
decir que una cadena es un apuntador constante; de hecho, se trata de un apuntador al primer
carácter de la cadena. En este sentido, las cadenas son como los arreglos, pues un nombre de
arreglo también es un apuntador (constante) a su primer elemento.
En una declaración, se puede asignar una cadena a un arreglo de caracteres o a una
variable de tipo char *. Las declaraciones
char color[]
char *colorPtr
= “azul”;
= “azul”;
inicializan una variable con la cadena “azul”. La primera declaración crea un arreglo de 5
elementos llamado color que contiene los caracteres ‘a’, ‘z’, ‘u’, ‘l’ y ‘\’. La segunda crea una
variable de apuntador colorPtr que apunta a la cadena, “azul”, que está en alguna parte de la
memoria.
La declaración char color [] = {“azul”}; también podría escribirse como
char color[] = {‘a’, ‘z’, ‘u’, ‘l’, ‘\0' );
APUNTADORES Y CADENA – LECCIÓN 20
20-33
MIGUEL Á. TOLEDO MARTÍNEZ
Al declarar un arreglo de caracteres para contener una cadena, se debe asegurar que es
lo bastante grande para almacenar dicha cadena y su carácter nulo de terminación. La
declaración anterior determina automáticamente el tamaño del arreglo basándose en el número
de inicializadores indicados en la lista de iniciación.
Es posible asignar una cadena a un arreglo por medio de extracción de flujo utilizando
cin. Por ejemplo, la siguiente instrucción sirve para asignarle una cadena al arreglo de caracteres
palabra[20]
cin >> palabra;
La cadena introducida por el usuario se almacena en palabra. La instrucción previa lee
los caracteres hasta que encuentra un espacio, tabulación, salto de línea o indicador de fin de
archivo. Observe que la cadena no debe ser de más de 19 caracteres, a fin de dejar espacio para
el carácter nulo de terminación. El manipulador de flujo setw() que se estudió en otra lección
puede servir para asegurar que la cadena introducida a palabra no exceda el tamaño del arreglo.
Por ejemplo, la instrucción
cin >> setw( 20 ) >> palabra;
especifica que cin debe leer un máximo de 19 caracteres y ponerlos en el arreglo palabra,
reservando la 20ª localidad del arreglo para el carácter nulo de terminación de la cadena. El
manipulador de flujo setw() sólo se aplica al siguiente valor que se introduzca.
En algunos casos es deseable introducir una línea de texto completa a un arreglo. Con
este fin, C++ ofrece la función cin.getline. Ésta toma tres argumentos -un arreglo de caracteres
en el que se almacenará la línea de texto, una longitud y un carácter delimitador.
Por ejemplo, el segmento de programa
char sentencia[80];
cin.getline( sentencia, 80, ‘\n’);
declara el arreglo sentencia de 80 caracteres, luego lee del teclado una línea de texto y la carga
en el arreglo. La función termina la lectura de los caracteres cuando encuentra el carácter ‘\n’,
cuando se introduce un indicador de fin de archivo o cuando el número de caracteres leídos hasta
el momento es de uno menos que la longitud indicada en el segundo argumento (el último
carácter del arreglo se reserva para el carácter nulo de terminación) Si aparece el carácter
delimitador, se lee y se descarta. El tercer argumento de cin.getline tiene ‘\n’ como valor
predeterminado, por lo que la llamada de función anterior podría haberse escrito así:
cin.getline( sentencia, 80 );
FUNCIONES DE MANIPULACIÓN DE CADENAS DE LA BIBLIOTECA DE MANEJO DE CADENAS
La biblioteca de manejo de cadenas ofrece muchas funciones útiles para la
manipulación de cadenas de datos, la comparación de cadenas, la búsqueda de caracteres y
cadenas dentro de cadenas, la división de cadenas en tokens (separación de cadenas en piezas
lógicas) y la determinación de la longitud de una cadena. Esta sección presenta algunas
APUNTADORES Y CADENA – LECCIÓN 20
20-34
MIGUEL Á. TOLEDO MARTÍNEZ
funciones comunes de manipulación de cadenas de la biblioteca de manejo de cadenas (que
pertenece a la biblioteca estándar) Las funciones se resumen en la figura 20.9.
Observe que varias funciones de la figura 20.9 contienen parámetros con el tipo de
datos size_t. Este tipo se define en el archivo de encabezado <stddef.h> (que es un archivo de
encabezado de la biblioteca estándar incluido en muchos otros archivos de encabezado de la
biblioteca estándar, incluyendo <string.h>) como un tipo entero sin signo, como unsigned int y
unsigned long.
Prototipo de función
Descripción de la función
char *strcpy( char *s1, const char *s2 )
Copia la cadena s2 al arreglo de caracteres s1. Se devuelve el valor de
s1.
char *strncpy( char *s1, const char *s2, size_t n )
Copia cuando mucho n caracteres de la cadena s2 al arreglo de
caracteres s1. Se devuelve el valor de s1.
char *strcat( char *sl, const char *s2 )
Agrega la cadena s2 al final de la cadena s1. El primer carácter de s2
sobrescribe el carácter nulo de terminación de s1. Se devuelve el valor
de s1.
char *strncat( char *sl, const char *s2, size_t n )
Agrega cuando mucho n caracteres de la cadena s2 a la cadena s1. El
primer carácter de s2 sobrescribe el carácter nulo de terminación de s1.
Se devuelve el valor de s1.
int strcmp( const char *sl, const char *s2 )
Compara la cadena s1 con la cadena s2. La función devuelve un valor de
0, menor que 0 o mayor que 0 si s1 es igual, menor o mayor que s2,
respectivamente.
Prototipo de función
Descripción de la función
int strncmp( const char *sl, const char *s2, size_t n )
Compara hasta n caracteres de la cadena s1 con la cadena s2. La función
devuelve un valor de 0, menor que 0 o mayor que 0 si s1 es igual,
menor o mayor que s2, respectivamente.
char *strtok( char *sl, const char *s2 )
Una secuencia de llamadas a strtok divide la cadena s1 en "tokens"
(piezas lógicas como palabras de una línea de texto) separados por los
caracteres contenidos en la cadena s2. La primera llamada contiene s1
como el primer argumento y las siguientes llamadas que dividen la
misma cadena en tokens contienen NULL como el primer argumento.
Cada llamada devuelve un apuntador al token actual. Si no hay más
tokens al llamar a la función, se devuelve NULL.
size_t strlen( const char *s )
Determina la longitud de la cadena s. Devuelve el número de caracteres
que precede al carácter nulo de terminación.
Figura 20.9. Las funciones de manipulación de cadenas de la biblioteca manejo de cadenas.
La función a strcpy() copia el segundo argumento (una cadena) al primer argumento (un
arreglo de caracteres que debe ser lo bastante grande para almacenar la cadena y su carácter nulo
de terminación, el cual también se copia) La función strncpy() es equivalente a strcpy(), excepto
que strncpy() especifica el número de caracteres a copiar de la cadena al arreglo. Observe que la
APUNTADORES Y CADENA – LECCIÓN 20
20-35
MIGUEL Á. TOLEDO MARTÍNEZ
función strncpy() no necesariamente copia el carácter nulo de terminación del segundo
argumento; sólo escribe un carácter nulo de terminación si el número de caracteres a copiar es,
cuando menos, uno más que la longitud de la cadena. Por ejemplo, si el segundo argumento es
“test”, sólo se escribe un carácter nulo de terminación si el tercer argumento de strncpy() es
cuando menos 5 (los 4 caracteres de “test” más el carácter nulo determinación) Si el tercer
argumento es mayor que 5, se agregan caracteres nulos al arreglo hasta que se ha escrito el
número total de caracteres que indica el tercer argumento.
Ejemplo 20.16
El siguiente programa: COPIAR1.CPP, utiliza strcpy() para copiar la cadena completa del arreglo x al
arreglo y, y emplea strncpy() para copiar los primeros 14 caracteres del arreglo x al arreglo z. Un carácter
nulo (‘\0’) se agrega al arreglo z, ya que la llamada a strncpy() no escribe un carácter nulo de terminación
(el tercer argumento es menor que la longitud de la cadena del segundo argumento)
/* El siguiente programa: COPIAR1.CPP, muestra el uso de strcpy() y strncpy(). */
#include <iostream.h>
#include <string.h>
//Para cout y cin
//Para strcpy() y strncpy()
void main (void)
{
char x[] = "Feliz Cumpleaños a Ti";
char y[25], z[15];
cout
<< "La cadena de caracteres en el arreglo x es: " << x
<< "\nLa cadena de caracteres en el arreglo y es: " << strcpy(y, x)
<< '\n';
strncpy(z, x, 16); //No copia el caracter nulo
z[16] = '\0';
cout << "La cadena de caracteres en el arreglo z es: " << z << endl;
}//Fin de main()
La función a strcat() añade el segundo argumento (una cadena) al primero (un arreglo
de caracteres que contiene una cadena) El primer carácter del segundo argumento reemplaza el
carácter nulo (‘\ 0’) que termina la cadena del primer argumento. El programador debe
asegurarse de que el arreglo en el que se encuentra la primera cadena sea lo bastante grande para
guardar la combinación de la primera y segunda cadenas, así como el carácter nulo de
terminación (copiado de la segunda cadena) La función a strncat() agrega una cantidad
específica de caracteres de la segunda cadena a la primera. Se agrega un carácter nulo de
terminación al resultado.
Ejemplo 20.17
El programa siguiente: COPIAR2.CPP, muestra las funciones strcat() y strncat()
/* El siguiente programa: COPIAR2.CPP, muestra el uso de las funciones strcat()
y strncat().
*/
#include <iostream.h>
#include <string.h>
APUNTADORES Y CADENA – LECCIÓN 20
//Para cout y cin
//Para strcat() y strncat()
20-36
MIGUEL Á. TOLEDO MARTÍNEZ
void main(void)
{
char s1[20]
char s2[]
char s3[40]
= "Feliz ";
= "Año Nuevo ";
= "";
cout << "s1 = " << s1 << "\ns2 = " << s2;
cout << "\nstrcat(s1, s2)
= " << strcat(s1, s2);
cout << "\nstrncat(s3, s1, 6) = " << strncat(s3, s1, 6);
cout << "\nstrcat(s3, s1)
= " << strcat(s3, s1) << endl;
}//Fin de main()
Ejemplo 20.18
El siguiente programa, COMPARAR.CPP, compara tres cadenas por medio de strcmp() y strncmp() La
función strcmp() compara el primer argumento con el segundo, carácter por carácter. La función devuelve 0
si las cadenas son iguales, un valor negativo si la primera cadena es menor que la segunda y un valor
positivo si la primera cadena es mayor que la segunda. La función strncmp() es equivalente a strcmp(),
excepto porque strncmp() compara hasta un número específico de caracteres. La función strncmp() no
compara los caracteres que hay después de un carácter nulo. El programa imprime los valores enteros que
las llamadas de la función devuelven.
/* El siguiente programa: COMPARAR.CPP, muestra el uso de las funciones strcmp() y
strncmp().
*/
#include <iostream.h>
#include <iomanip.h>
#include <string.h>
//Para cout y cin
//Para setw()
//Para strcmp() y strncmp()
void main(void)
{
char *s1 = "Feliz Año Nuevo";
char *s2 = "Feliz Año Nuevo";
char *s3 = "Feliz Vacación";
cout
<< "s1 = " << s1 << "\ns2 = " << s2
<< "\ns3 = " << s3 << "\n\nstrcmp(s1, s2) = "
<< setw(2) << strcmp(s1, s2)
<< "\nstrcmp(s1, s3) = " << setw(2)
<< strcmp(s1, s3) << "\nstrcmp(s3, s1) = "
<< setw(2) << strcmp(s3, s1);
cout
<< "\n\nstrncmp(s1, s3, 6) = " << setw(2)
<< strncmp(s1, s3, 6) << "\nstrncmp(s1, s3, 7) = "
<< setw(2) << strncmp(s1, s3, 7)
<< "\nstrncmp(s3, s1, 7) = "
<< setw(2) << strncmp(s3, s1, 7) << endl;
}//Fin de main()
Para entender lo que significa que una cadena sea mayor que o menor que otra cadena, considere el
proceso de alfabetización de una serie de apellidos. El lector, sin duda, pondría González antes que Pérez,
pues en el abecedario la primera letra de González está antes que la primera letra de Pérez. Pero el
abecedario es algo más que una lista de 30 letras; es una lista ordenada de caracteres. Cada letra aparece en
una posición específica de la lista. La Z es algo más que una letra del abecedario; Z es, específicamente el
carácter 30 del alfabeto.
APUNTADORES Y CADENA – LECCIÓN 20
20-37
MIGUEL Á. TOLEDO MARTÍNEZ
¿Cómo sabe la computadora que una letra viene antes que otra? Todos los caracteres se representan
dentro de la computadora como códigos numéricos; cuando la computadora compara dos cadenas, de hecho
está comparando los códigos numéricos de los caracteres que hay en dichas cadenas.
Con el fin de estandarizar las representaciones de los caracteres, la mayoría de los fabricantes de
computadoras han diseñado sus máquinas para que operen con alguno de dos esquemas de codificación: el
ASCII (Código Estándar Americano para el Intercambio de Información) o el EBCDIC (Código de
Intercambio Decimal Codificado en Binario Extendido) Hay otros esquemas de codificación, pero estos
dos son los más difundidos.
ASCII y EBCDIC se llaman códigos de caracteres o conjuntos de caracteres. Las manipulaciones de
cadenas y caracteres de hecho comprenden la manipulación de los códigos numéricos correspondientes, y
no de los caracteres mismos. Esto explica por qué los caracteres y los enteros pequeños son intercambiables
en C++. Debido a que tiene sentido decir que un código numérico es mayor que, menor que o igual a otro
código numérico, es posible relacionar varios caracteres o cadenas entre ellos haciendo referencia a los
códigos de caracteres.
La función strtok() sirve para dividir una cadena en una serie de tokens. Un token es una
secuencia de caracteres separada por caracteres delimitadores (por lo general espacios o marcas
de puntuación) Por ejemplo, en una línea de texto, cada palabra puede considerarse como un
token y los espacios que las separan pueden verse como delimitadores.
Se necesitan varias llamadas a strtok() para dividir en tokens una cadena (suponiendo
que la cadena contenga más de un token) La primera llamada a strtok() contiene dos argumentos:
una cadena a dividir en tokens y una cadena que contiene los caracteres que separan los tokens
(es decir, delimitadores) En el siguiente programa, TOKENS.CPP, la instrucción
tokenPtr = strtok( cadena, “ “l);
asigna tokenPtr como apuntador al primer token de cadena. El segundo argumento de strtok(), “
”, indica que los tokens en cadena están separados por espacios. La función strtok() busca el
primer carácter de cadena que no sea un carácter delimitador (espacio) Éste es el inicio del
primer token. Luego encuentra el siguiente carácter delimitador de la cadena y lo reemplaza con
un carácter nulo ( ‘\ 0’ ) Esto termina el token actual. La función a strtok() guarda un apuntador
al carácter que sigue al token y devuelve un apuntador al token actual.
Las siguientes llamadas a strtok() que continúan dividiendo en tokens a cadena
contienen NULL como primer argumento. El argumento NULL indica que la llamada a strtok()
deberá continuar dividiendo en tokens a partir de la localidad de cadena guardada por la última
llamada a strtok() Si no quedan tokens cuando se llame a strtok(), éste devuelve NULL.
Ejemplo 20.19
El programa TOKENS.CPP, utiliza strtok() para dividir en tokens la cadena “Esta es una oración con 7
tokens”. Cada token se imprime por separado. Observe que strtok() modifica la cadena de entrada, por lo
tanto, debe hacer una copia de la cadena si desea volver a utilizarla en el programa tras las llamadas a
strtok()
/* El siguiente programa: TOKENS.CPP, muestra el uso de la función strtok(). */
#include <iostream.h>
#include <string.h>
APUNTADORES Y CADENA – LECCIÓN 20
//Para cout y cin
//Para strtok()
20-38
MIGUEL Á. TOLEDO MARTÍNEZ
void main(void)
{
char cadena[] = "Esta es una oración con 7 tokens";
char *tokenPtr;
cout
<< "La cadena a dividirse en tokens es:\n" << cadena
<< "\n\nLos tokens son:\n";
tokenPtr = strtok(cadena, " ");
while(tokenPtr != NULL)
{
cout << tokenPtr << '\n';
tokenPtr = strtok(NULL, " ");
}//Fin de while
}//Fin de main()
La función strlen() toma como argumento una cadena y devuelve el número de caracteres que
ésta contiene –en la longitud no se incluye el carácter nulo de terminación.
Ejemplo 20.20
El programa siguiente, LONGITUD.CPP, muestra el uso de la función strlen()
/* El siguiente programa: LONGITUD.CPP, muestra el empleo de la función strlen(). */
#include <iostream.h>
#include <string.h>
//Para cout y cin
//Para strlen()
void main(void)
{
char *cadena1 = "abcdefghijklmnopqrstuvwxyz";
char *cadena2 = "cinco";
char *cadena3 = "México";
cout
<< "La longitud de \"" << cadena1
<< "\" es " << strlen(cadena1)
<< "\nLa longitud de \"" << cadena2
<< "\" es " << strlen(cadena2)
<< "\nLa longitud de \"" << cadena3
<< "\" es " << strlen(cadena3) << endl;
}//Fin de main()
A continuación se resuelven una serie de ejercicios con la intención de que el lector
estudie por si mismo lo que los programas hacen y como lo hacen. Independiente de lo anterior
en el mismo programa se da una breve definición del problema.
APUNTADORES Y CADENA – LECCIÓN 20
20-39
MIGUEL Á. TOLEDO MARTÍNEZ
EJERCICIOS RESUELTOS
/* El siguiente programa: ALFABETO.CPP, define una cadena de 256 caracteres y
luego se le agrega las letras mayúsculas del alfabeto.
*/
#include <iostream.h>
//Para cout y cin
void main (void)
{
char cadena[256];
int i;
for (i = 0; i < 26; i++)
cadena[i] = 'A' + i;
cadena[i] = NULL;
cout << "El contenido de la cadena es: " << cadena;
}//Fin de main()
/* El siguiente programa: ALFABETO2.CPP, asigna las letras mayúsculas de la A a la Z
a una cadena de caracteres. Posteriormente se agrega el caracter NULL al
elemento cadena[10].
*/
#include <iostream.h>
//Para cout y cin
void main (void)
{
char cadena[256];
int i;
for (i = 0; i < 26; i++)
cadena[i] = 'A' + i;
cadena[10] = NULL;
cout << "La cadena contiene: " << cadena;
}//Fin de main()
APUNTADORES Y CADENA – LECCIÓN 20
20-40
MIGUEL Á. TOLEDO MARTÍNEZ
/* El siguiente programa: COMILLAS.CPP, usa la secuencia de escape \" para colocar
comillas dentro de la declaración de una constante.
*/
#include <iostream.h>
//Para cout y cin
void main(void)
{
char cadena[] = "\"!Alto!\", dijo él.";
cout << cadena;
}//Fin de main()
/* El siguiente programa: GETLINE.CPP, utiliza el objeto getline() para leer una
serie de caracteres desde el teclado. Posteriormente visualiza esta cadena
de caracteres, caracter por caracter. Finalmente muestra la longitud de la
cadena.
*/
#include <iostream.h>
//Para cout y cin
void main (void)
{
char cadena[256];
int i;
// Cadena introducida por el usuario
// Índice a utilizar en la cadena
cout << "Digite una cadena de caracteres y oprima enter: ";
cin.getline(cadena, sizeof(cadena), '\n');
// Visualizar cada elemento de la cadena hasta que encuentre el caracter NULL
for (i = 0; cadena[i] != NULL; i++)
cout << cadena[i];
cout << endl << "El número de caracteres de la cadena es de: " << i;
}//Fin de main()
/* El siguiente programa: LONGITUD2.CPP, ilustra el uso de la función strlen().
Esta función regresa la cantidad de caracteres que hay en el parámetro cadena.
El tipo resultante size_t tiene un typedef de tipo unsigned int.
*/
#include <iostream.h>
#include <string.h>
APUNTADORES Y CADENA – LECCIÓN 20
//Para cout y cin
//Para strlen()
20-41
MIGUEL Á. TOLEDO MARTÍNEZ
void main(void)
{
char tituloLibro[] = "\"El Coronel no tiene quien le escriba\"";
cout
<< tituloLibro << " contiene " << strlen(tituloLibro)
<< " caracteres";
}//Fin de main()
/* ************************************************************************
Una posible implementación de la función strlen(), es la siguiente:
size_t strlen(const char *cadena)
{
int i = 0;
while (cadena[i])
i++;
return (i)
}//Fin de strlen()
**************************************************************************** */
/* El siguiente programa: COPIAR3.CPP, ilustra el uso de la función strcpy().
El prototipo para la función strcpy es: char *strcpy(char *destino, const char *fuente)
La función copia los caracteres de la cadena fuente a la cadena destino.
La función supone que la cadena de destino tiene suficiente espacio para
contener la cadena de origen.
*/
#include <iostream.h>
#include <string.h>
//Para cout y cin
//Para strcpy()
void main(void)
{
char titulo[] = "\"El Coronel no tiene quien le escriba\"";
char libro[128];
strcpy(libro, titulo);
cout << "El nombre del libro es: " << libro << endl;
}//Fin de main()
/* ***********************************************************************
Una implementación de la función strcpy() podría ser la siguiente:
char *strcpy(char *destino, const char *fuente)
{
while (*destino++ = *fuente++)
;
return (destino - 1);
}//Fin de *strcpy()
**************************************************************************/
/* El siguiente programa: ANEXAR.CPP, ilustra el uso de la función strcat().
La función añade el contenido de la cadena fuente a la cadena de destino
y regresa el apuntador a la cadena de destino. La función asume que la cadena
de destino puede acomodar los caracteres de la cadena de origen.
*/
APUNTADORES Y CADENA – LECCIÓN 20
20-42
MIGUEL Á. TOLEDO MARTÍNEZ
#include <iostream.h>
#include <string.h>
//Para cout y cin
//Para strcat()
void main(void)
{
char nombre[64] = "Miguel Angel es tan";
strcat(nombre, " feliz");
cout << nombre;
}//Fin de main()
/* ***********************************************************************
Una posible implementación de la función strcat es la siguiente:
char *strcat(char *destino, const char *fuente)
{
char *original = destino;
while (*destino)
destino**;
// Busca el final de la cadena
while (*destino++ = *fuente++)
;
return (original)
}//Fin de *strcat()
************************************************************************** */
/* El siguiente programa: ANEXAR2.CPP, ilustra el uso de la función strncat().
La función añade num caracteres de la cadena origen a la cadena de destino
y regresa un apuntador a la cadena de destino.
*/
#include <iostream.h>
#include <string.h>
//Para cout y cin
//Para strncat()
void main(void)
{
char nombre[64] = "Miguel Angel";
strncat(nombre, " Martínez", 4);
cout << "¿Votaste por?: " << nombre << endl;
}//Fin de main()
/* ***********************************************************************
Una posible implementación de la función strncat() es la siguiente:
char *strncat(char *destino, const char *fuente, int n)
{
char *original = destino;
int i = 0;
while (*destino)
destino++;
while ((i++ < n) && (*destino++ = *fuente++))
;
APUNTADORES Y CADENA – LECCIÓN 20
20-43
MIGUEL Á. TOLEDO MARTÍNEZ
if (i > n)
*destino = NULL;
return (original);
}//Fin de strncat()
************************************************************************** */
/* El siguiente programa: STRXFRM.CPP, ilustra el uso de la función strxfrm() */
#include <iostream.h>
#include <string.h>
//Para cout y cin
//Para strxfrm()
void main(void)
{
char fuente[64] = "\El Coronel no tiene quien le escriba\"";
char destino[64];
int longitud;
longitud = strxfrm(destino, fuente, sizeof(fuente));
cout << "Longitud de la cadena: " << longitud << endl;
cout << "Contenido del destino: " << destino << endl;
cout << "Contenido de la fuente: " << fuente << endl;
}//Fin de main()
/* El siguiente programa: STREQL.CPP, ilustra el uso de la función streql() */
#include <iostream.h>
//Para cout y cin
int streql(char *cadena1, char *cadena2)
{
while ((*cadena1 == *cadena2) && (*cadena1))
{
cadena1++;
cadena2++;
}//Fin de while
return((*cadena1 == NULL) && (*cadena2 == NULL));
}//Fin de streql()
void main(void)
{
cout << "Comparando Abc y Abc : " << streql("Abc", "Abc") << endl;
cout << "Comparando abc y Abc : " << streql("abc", "Abc") << endl;
cout << "Comparando abcd y abc: " << streql("abcd", "abc") << endl;
}//Fin de main()
APUNTADORES Y CADENA – LECCIÓN 20
20-44
MIGUEL Á. TOLEDO MARTÍNEZ
/* El siguiente programa: STRIEQL.CPP, ilustra el uso de la función strieql() */
#include <iostream.h>
#include <ctype.h>
//Para cout y cin
//Para toupper()
int strieql(char *cadena1, char *cadena2)
{
while ((toupper(*cadena1) == toupper(*cadena2)) && (*cadena1))
{
cadena1++;
cadena2++;
}//Fin de strieql()
return((*cadena1 == NULL) && (*cadena2 == NULL));
}//Fin de strieql()
void main(void)
{
cout << "Comparando Abc y Abc : " << strieql("Abc", "Abc") << endl;
cout << "Comparando abc y Abc : " << strieql("abc", "Abc") << endl;
cout << "Comparando abcd y abc: " << strieql("abcd", "abc") << endl;
}//Fin de main()
/* El siguiente programa: STRLWR.CPP, ilustra el uso de la funciones strlwr() y
strupr().
*/
#include <iostream.h>
#include <string.h>
//Para cout y cin
//Para strlwr() y strupr()
void main(void)
{
cout << strlwr("\"El Coronel no tiene quien le escriba\"") << endl;
cout << strupr("\"El Coronel no tiene quien le escriba\"") << endl;
}//Fin de main()
/* *********************************************************************
Una posible implementación de la función strlwr() es la siguiente:
#include <ctype.h>
char *strlwr(char *cadena)
{
char *original = cadena;
while (*cadena)
{
*cadena = tolower(*cadena);
cadena++;
}//Fin de while
return(original)
}//Fin de *strlwr()
************************************************************************** */
APUNTADORES Y CADENA – LECCIÓN 20
20-45
MIGUEL Á. TOLEDO MARTÍNEZ
/* El siguiente programa: STRCHR.CPP, ilustra el uso de la función strchr */
#include <iostream.h>
#include <string.h>
//Para cout y cin
//Para strchar()
void main(void)
{
char titulo[64] = "\"El Coronel no tiene quien le escriba\"";
char *ptr;
if (ptr = strchr(titulo, 'C'))
cout
<< "La primera ocurrencia de C es en el desplazamiento: "
<< (ptr - titulo) << endl;
else
cout << "¡NO SE ENCONTRÓ EL CARÁCTER BUSCADO!" << endl;
}//Fin de main()
/* ***********************************************************************
Una posible implementación de la función strchr() es la siguiente:
char *strchr(const char *cadena, int letra)
{
while ((*cadena != letra) && (*cadena)
cadena++;
return(cadena);
}//Fin de *strchr()
************************************************************************** */
/* El siguiente programa: STRRCHR.CPP, ilustra el uso de la función strrchr */
#include <iostream.h>
#include <string.h>
//Para cout y cin
//Para strrchr()
char *strrchr(char *cadena, char letra)
{
char *ptr = NULL;
while (*cadena)
{
if (*cadena == letra)
ptr = cadena;
cadena++;
}//Fin de while
return(ptr);
}//Fin de *strrchr()
void main(void)
{
char titulo[64] = "\"El Coronel no tiene quien le escriba\"";
char *ptr;
if (ptr = strrchr(titulo, 'n'))
cout
<< "La última vez en que se encontró la letra n a la derecha: "
<< (ptr - titulo) << endl;
else
cout
<< "!NO SE ENCONTRÓ EL CARÁCTER BUSCADO!" << endl;
}//Fin de main()
APUNTADORES Y CADENA – LECCIÓN 20
20-46
MIGUEL Á. TOLEDO MARTÍNEZ
/* ***********************************************************************
Una posible implementación de la función strrchr() es la siguiente:
char *strrchr(const char *cadena, int letra)
{
char *ptr = NULL;
while (*cadena)
{
if (*cadena == letra)
ptr = cadena;
cadena++;
}//Fin de while
return(ptr);
}//Fin de *strrch()
************************************************************************** */
FUNCIONES DE CADENA QUE UTILIZAN CADENAS UBICADAS FUERA DE LOS 64KB (far string)
Algunos compiladores poseen funciones que aceptan apuntadores ubicados fuera de los
64 kbyes. Por ejemplo, para determinar la longitud de una cadena referenciada por un apuntador
far, puede utilizar la función _fstrlen(), como se muestra a continuación:
#include <string.h>
size_t _fstrlen(const char *string)
En el caso de que no exista en el compilador y usted tenga una función que maneje
apuntadores ubicados en los 64 kbytes, puede modificar los mismos como se muestra en el
siguiente ejemplo, para la función fstreql():
int fstreql (char far *str1, char far *str2)
{
while ((*str1 == *str2) && (*str1))
{
str1++;
str2++;
}//Fin de while
return ((*str1 == NULL) && (*str2 ==NULL));
}//Fin de fstreql()
NUMERO DE OCURRENCIAS DE UN CARÁCTER DENTRO DE UNA CADENA.
A continuación escribiremos la función chrcnt() que resuelve el problema planteado:
int chrcnt (const char *cadena, int letra)
{
int contador = 0;
while (*cadena)
if (*cadena == letra)
contador++;
return
(contador);
APUNTADORES Y CADENA
– LECCIÓN
20
}//Fin de chrcnt()
20-47
MIGUEL Á. TOLEDO MARTÍNEZ
A continuación escribiremos la función strrev() que invierta una cadena.
char *strrev (char *cadena)
{
char *original = cadena;
char *adelante = cadena;
char temp;
while (*cadena)
cadena++;
while (adelante < cadena)
{
temp = *(--cadena);
*cadena = *adelante;
*adelante++ = temp;
}//Fin de while()
return (original);
}//Fin de *strrev()
A continuación escribiremos la función strset() sustituye todos los caracteres de una
cadena por un carácter especificado.
char * strset (char *cadena, int letra)
{
char *original = cadena;
while (*cadena)
*cadena++ = letra;
return (original);
}//Fin de *strset()
/* El siguiente programa: STRCMP.CPP, ilustra el uso de la función strcmp(), la
cual compara dos cadenas por menor, mayor o igual. Devolviendo 0 si ambas son
iguales, si la primera es mayor que la segunda devuelve un número negativo,
finalmente si la segunda es mayor que la primera devuelve un número positivo.
*/
#include <iostream.h>
#include <string.h>
//Para cout y cin
//Para strcmp()
void main(void)
{
cout << "Comparando Abc y Abc : " << strcmp("Abc", "Abc") << endl;
cout << "Comparando abc y Abc : " << strcmp("abc", "Abc") << endl;
cout << "Comparando abcd y abc: " << strcmp("abcd", "abc") << endl;
cout << "Comparando Abc y Abcd: " << strcmp("Abc", "Abcd") << endl;
}//Fin de main()
APUNTADORES Y CADENA – LECCIÓN 20
20-48
MIGUEL Á. TOLEDO MARTÍNEZ
/* ***********************************************************************
Una posible implementación de la función strcmp() es la siguiente:
int strcmp(const char *s1, const char *s2)
{
while ((*s1 == *s2) && (*s1))
{
s1++;
s2++;
}//Fin de while
if ((*s1 == *s2) && (! *s1) // Las cadenas son iguales
return(o);
else if ((*s1) && (! *s2)) // cadena s1 > cadena s2
return(-1);
else if ((*s2) && (! *s1)) // cadena s2 > cadena s1
return(1);
else
return ((*s1 > *s2) ? -1; 1);
}//Fin de strcmp()
************************************************************************** */
/* El siguiente programa: STRNCMP.CPP, muestra la función strncpm(), la cual
compara cierto numero de caracteres de una cadena con otra.
*/
#include <iostream.h>
#include <string.h>
//Para cout y cin
//Para strncmp()
void main(void)
{
cout << "Comparando tres letras de Abc con Abc: " <<
strncmp("Abc", "Abc", 3) << endl;
cout << "Comparando tres letras de abc con Abc: " <<
strncmp("abc", "Abc", 3) << endl;
cout << "Comparando tres letras de abcd con abc: " <<
strncmp("abcd", "abc", 3) << endl;
cout << "Comparando cinco letras de Abc con Abcd: " <<
strncmp("Abc", "Abcd", 5) << endl;
}//Fin de main()
APUNTADORES Y CADENA – LECCIÓN 20
20-49
MIGUEL Á. TOLEDO MARTÍNEZ
/* ***********************************************************************
Una posible implementación de la función strncmp() es la siguiente:
int strncmp(const char *s1, const char *s2, int n)
{
int i = o;
while ((*s1 == *s2) && (*s1) && i < n)
{
s1++;
s2++;
i++
}//Fin de while
if (i == n)
// Las cadenas son iguales
return (0);
else if ((*s1 == *s2) && (! *s1) // Las cadenas son iguales
return(o);
else if ((*s1) && (! *s2))
// cadena s1 > cadena s2
return(-1);
else if ((*s2) && (! *s1))
// cadena s2 > cadena s1
return(1);
else
return ((*s1 > *s2) ? -1; 1);
}//Fin de strncmp()
************************************************************************** */
/* El siguiente programa: CMPCASE.CPP, ilustra el uso de las funciones stricmp()
y strncmpi(), las cuales hacen comparaciones sin diferenciar mayúsculas de
minúsculas.
*/
#include <iostream.h>
#include <string.h>
//Para cout y cin
//Para stricmp() y strncmpi()
void main(void)
{
cout
cout
cout
cout
<< "Comparando Abc con Abc: " << stricmp("Abc", "Abc") << endl;
<< "Comparando abc con Abc: " << stricmp("abc", "Abc") << endl;
<< "Comparando tres letras de abcd con ABC: "
<< strncmpi("abcd", "ABC", 3) << endl;
<< "Comparando 5 letras de abc con Abcd: "
<<
strncmpi("abc", "Abcd", 5);
}//Fin de main()
APUNTADORES Y CADENA – LECCIÓN 20
20-50
MIGUEL Á. TOLEDO MARTÍNEZ
/* La mayoría de los compiladores ofrecen un conjunto de funciones para convertir
caracteres ASCII a números, véase la tabla siguiente:
función
atof()
atoi()
atol()
strtod()
strtol()
propósito
Convierte de ascii a punto flotante.
Convierte de ascii a entero.
Convierte de ascii a entero largo.
Convierte de ascii a doble precisión.
Convierte de ascii a entero largo.
El siguiente programa: ASCIINUM.CPP, ilustra el uso de algunas de las funciones anteriores.
*/
#include <iostream.h>
#include <stdlib.h>
#include <iomanip.h>
//Para cout y cin
//Para las funciones anteriores
//Para setprecision()
void main(void)
{
int intResultado;
float floatResultado;
long longResultado;
intResultado = atoi("1234");
floatResultado = atof("12345.678");
longResultado = atol("1234567L");
cout
<< intResultado << ' ' << setprecision(8) << floatResultado
<< ' ' << longResultado;
}//Fin de main()
/* El siguiente programa: STRDUP.CPP, ilustra el uso de la función strdup() para
copiar una cadena de caracteres a un área dinámica de la memoria.
*/
#include <iostream.h>
#include <string.h>
//Para cout y cin
//Para strdup()
void main(void)
{
char *titulo;
if ((titulo = strdup("\"El Coronel no tiene quien le escriba\"")))
cout << "Título: " << titulo << endl;
else
cout << "Error al duplicar la cadena" << endl;
}//Fin de main()
APUNTADORES Y CADENA – LECCIÓN 20
20-51
MIGUEL Á. TOLEDO MARTÍNEZ
/* ***********************************************************************
Una posible implementación de la función strdup() es la siguiente:
#include <string.h>
#include <malloc.h>
//Para cout y cin
//Para malloc()
char *strdup(const char *s1)
{
char *ptr;
if ((ptr = malloc(strlen(s1))))
strcpi(ptr,s1);
// Localiza area dinámica de memoria
return(ptr);
}//Fin de *strdup()
************************************************************************** */
/* El siguiente programa: STRSPN.CPP, ilustra el uso de la función strspn().
Esta función busca en una cadena por la primera ocurrencia de cualquiera
de los caracteres de una subcadena. La función devuelve el desplazamiento
dentro de la cadena del primer caracter que no esta contenido en la segunda
cadena especificada.
*/
#include <iostream.h>
#include <string.h>
//Para cout y cin
//Para strspn()
void main(void)
{
cout
<< "Buscando la subcadena Abc en la cadena AbcDef: "
<< strspn("AbcDef", "Abc") << endl;
cout
<< "Buscando la subcadena cbA en la cadena AbcDef: "
<< strspn("AbcDef", "cbA") << endl;
cout
<< "Buscando la subcadena Def en AbcAbc: "
<< strspn("AbcAbc", "Def");
}//Fin de main()
/* **********************************************************************
Una posible implementación de la función strspn es la siguiente:
size_t strspn(const char *s1, const char *s2)
{
int i, j;
for (i = 0; *s1; i++, s1++)
{
for (j = 0; s2[j]; j++)
if (*s1 == s2[j])
break;
if (s2[j] == NULL);
break;
}//Fin del for
return (i);
}//Fin de sstrspn()
************************************************************************** */
APUNTADORES Y CADENA – LECCIÓN 20
20-52
MIGUEL Á. TOLEDO MARTÍNEZ
/* El siguiente programa: STRSTR.CPP, ilustra el uso de la función strstr(), la
cual busca una subcadena dentro de una cadena. Si la encuentra devuelve un
apuntador a la primera ocurrencia de la subcadena. Si no la encuentra devuelve
NULL.
*/
#include <iostream.h>
#include <string.h>
//Para cout y cin
//Para strstr()
void main(void)
{
cout
<< "Buscando la subcadena Abc en la cadena AbcDef: "
<< (strstr("AbcDef", "Abc") ? "Encontrado" : "No encontrado")
<< endl;
cout
<< "Buscando la subcadena Abc en la cadena abcDef: "
<< (strstr("abcDef", "Abc") ? "Encontrado" : "No encontrado")
<< endl;
cout
<< "Buscando la subcadena Abc en la cadena AbcAbc: "
<< (strstr("AbcAbc", "Abc") ? "Encontrado" : "No encontrado");
}//Fin de main()
/* ***********************************************************************
Una posible implementación de la función strstr() es la siguiente:
char *strstr(const char *s1, const char *s2)
{
int i, j, k;
for (i = 0; s1[i]; i++)
for (j = i, k = 0; s1[j] == s2[k]; j++, k++)
if (! s2[k + 1])
return (s1 + i);
return (NULL);
}//Fin de *strstr()
************************************************************************** */
CONTAR EL NÚMERO DE OCURRENCIA DE UNA SUBCADENA DENTRO DE UNA CADENA
La siguiente función: strstr_cnt() cuenta el número de veces en el que una subcadena se
encuentra dentro de una cadena.
int strstr_cnt (const char *cadena, const char *subcadena)
{
int i, j, k, contador = 0;
for (i = 0; cadena[i]; i++)
for (j = i, k = 0; cadena[j] == subcadena[k]; j++, k++)
if (! subcadena[k + 1])
contador++;
return (contador);
}//Fin de strstr_cnt()
APUNTADORES Y CADENA – LECCIÓN 20
20-53
MIGUEL Á. TOLEDO MARTÍNEZ
OBTENER UN ÍNDICE A UNA SUBCADENA
La siguiente función strstr_index(), devuelve el índice donde se encuentra la subcadena
dentro de la cadena:
char *strstr_index(const char *s1, const char *s2)
{
int i, j, k;
for (i = 0; s1[i]; i++)
for (j = i, k = 0; s1[j] == s2[k]; j++, k++)
if (! s2[k + 1])
return (i);
return (i);
}//Fin de *strstr_index()
OBTENER LA OCURRENCIA MAS A LA DERECHA DE UNA SUBCADENA
La función r_strstr() devuelve un apuntador a la ocurrencia de más a la derecha de una
subcadena dentro de una cadena:
char *r_strstr(const char *s1, const char *s2)
{
int i, j, k , izquierda = 0;
for (i = 0; s1[i]; i++)
for (j = i, k = 0; s1[j] == s2[k]; j++, k++)
if (! s2[k + 1])
izquierda = i;
return ((izquierda) ? s1 + izquierda: NULL);
}//Fin de *r_strstr()
REMOVER UNA SUBCADENA CONTENIDA DENTRO DE UNA CADENA
La siguiente función strstr_rem() remueve la primera ocurrencia de una subcadena
contenida en una cadena.
char *strstr_rem (char *cadena, char *subCadena)
{
int i, j, k, loc = -1;
for (i = 0; cadena[i] && (loc == -1); i++)
for (j =i, k = 0; str[j] == subCadena[k]; j++, k++)
if (! subCadena[k + 1] )
loc = i;
if (loc != -1)
{
// La cadena fue encontrada
for (k = 0; subCadena[k]; k++)
for (j = loc, i = loc + k; cadena[i]; j++, i++)
cadena[j] = cadena[i];
APUNTADORES Y CADENA – LECCIÓN 20
20-54
MIGUEL Á. TOLEDO MARTÍNEZ
cadena [i] == NULL;
}//Fin de if
return (cadena);
}//Fin de *strstr_rem
REEMPLAZO DE UNA SUBCADENA POR OTRA
La siguiente función: strstr_rep(), reemplaza la primera ocurrencia de una subcadena por
otra subcadena.
#include <string.h>
char *strsstr_rep (char *fuente, char *viejo, char *nuevo)
{
char *original = fuente;
char temp[256];
int longitudVieja = strlen(viejo);
int i, j, k, localizacion = -1;
for (i = 0; fuente[i] && (localizacion == -1); ++i)
for (j = i, k = 0; fuente[j] == viejo[k]; j++, k++)
if (! viejo[k + 1])
localizacion = i;
if (localizacion != -1)
{
for (j = 0; j < localizacion; j++)
temp[j] = fuente[j];
for (i = 0; nuevo[i]; i++, j++)
temp[j] = nuevo[i];
for (k = localizacion + longitudVieja; fuente[k]; k++, j++)
temp[j] = fuente[k];
temp[j] = NULL;
for (i = 0; fuente[i] = temp[i]; i++)//Ciclo nulo
;
}//Fin de if
return (original);
} //Fin de *strstr_rep()
APUNTADORES Y CADENA – LECCIÓN 20
20-55
MIGUEL Á. TOLEDO MARTÍNEZ
DETERMINAR SI UN CARÁCTER ES ALFANUMÉRICO
Un carácter es alfanumérico si es letra o dígito. El archivo de cabecera ctype.h contiene
una macro llamada isalnum que determina si un carácter es alfanumérico:
if (isalnum(letra))
Considere la siguiente implementación de dicha macro:
#define isalnum(c) ((toupper( (c) ) >= ‘A’ ) && (toupper ((c)) <= ‘Z’) ||
((c) >= ‘0’ ) && ((c) <= ‘9’))
DETERMINAR SI UN CARÁCTER ES UNA LETRA DEL ALFABETO
El archivo de cabecera ctype.h proporciona la macro isalpha para determinar si un
carácter es una letra:
if (isalpha(carácter))
Considere la siguiente implementación de dicha macro:
#define isalpha(c) (toupper((c)) >= ‘A’ && (toupper((c)) <= ‘Z’)
DETERMINAR SI UN CARÁCTER CONTIENE UN VALOR ASCII
Un valor es ASCII si se encuentra en el rango comprendido entre 0 y 127. El archivo
cabecera ctype.h contiene la macro isascii que le ayuda a determinar si un carácter contiene un
valor ASCII:
if (isascii(carácter))
Considere la siguiente implementación de dicha macro:
#define isascii(ltr) ((unsigned) (ltr) < 128)
DETERMINAR SI UN CARÁCTER ES UN CARÁCTER DE CONTROL
Un carácter de control es un valor comprendido entre ^A y ^Z o ^a y ^z. El archivo
cabecera ctype.h contiene la macro iscntrl que le ayuda a determinar si un carácter es de control:
if (iscntrl(carácter))
DETERMINAR SI UN CARÁCTER ES UN DIGITO
Un dígito es un valor ASCII comprendido entre 0 y 9. El archivo de cabecera ctype.h
contiene la macro isdigit que le ayuda a determinar si un carácter es o no dígito:
if (isdigit(carácter))
Considere la siguiente implementación de dicha macro:
#define isdigit(c) ((c) >= ‘0’ && (c) <= ‘9’)
APUNTADORES Y CADENA – LECCIÓN 20
20-56
MIGUEL Á. TOLEDO MARTÍNEZ
DETERMINAR SI UN CARÁCTER ES UN CARÁCTER GRAFICO
Un carácter es gráfico si es un carácter imprimible (ver isprint), excluyendo el carácter
espacio (ASCII 32) El archivo de cabecera ctype.h contiene la macro isgraph que le ayuda a
determinar si un carácter es o no gráfico:
if (isgraph(carácter))
Considere la siguiente implementación de dicha macro:
#define isgraph(ltr) ((ltr) >=33) && ((ltr) <= 127)
DETERMINAR SI UN CARÁCTER ES MAYÚSCULA O MINÚSCULA
El archivo de cabecera ctype.h contiene las macros islower y isupper que le ayudan a
determinar si un carácter es minúscula o mayúscula respectivamente:
if (islower(carácter))
if(isupper(carácter))
Considere las siguiente implementaciones de dichas macros:
#define islower(c) ((c) >= ‘a’ && (c) <= ‘z’)
#define isupper(c) ((c) >= ‘A’ && (c) <= ‘Z’)
DETERMINAR SI UN CARÁCTER ES IMPRIMIBLE
Un carácter es imprimible si se encuentra en el rango comprendido entre 32 y 127. El
archivo de cabecera ctype.h contiene la macro isprint que le ayuda a determinar si un carácter es
o no imprimible:
if (isprint(carácter))
Considere la siguiente implementación de dicha macro:
#define isprint(ltr) ((ltr) >=32) && ((ltr) <= 127)
DETERMINAR SI UN CARÁCTER ES UN SÍMBOLO DE PUNTUACIÓN
Desde el punto de vista gramatical los símbolos de puntuación incluyen a la coma, punto
y coma, punto, interrogación y así sucesivamente. Desde punto de vista del lenguaje C un
símbolo es de puntuación si es un carácter gráfico que no sea alfanumérico. El archivo de
cabecera ctype.h contiene la macro ispunct que le ayuda a determinar si un carácter es o no
símbolo de puntuación:
if (ispunct(carácter))
Considere la siguiente implementación de dicha macro:
#define ispunct(c) (isgraph(c)) && ! isalphanum((c)))
APUNTADORES Y CADENA – LECCIÓN 20
20-57
MIGUEL Á. TOLEDO MARTÍNEZ
DETERMINAR SI UN CARÁCTER ES EL CARÁCTER ESPACIO
El carácter espacio incluye el espacio, tabulador, retorno de carro, nueva línea, tabulador
vertical y alimentación de hoja. El archivo de cabecera ctype.h contiene la macro isspace que le
ayuda a determinar si un carácter es o no carácter espacio:
if (isspace(carácter))
Considere la siguiente implementación de dicha macro:
#define isspace(c) (((c) == 32) || ((c) == 9) || ((c) == 13))
DETERMINAR SI UN CARÁCTER ES UN VALOR HEXADECIMAL
Un carácter es hexadecimal si es un dígito entre 0 y 9 o una letra entre A y F. El archivo
de cabecera ctype.h contiene la macro isxdigit que le ayuda a determinar si un carácter es o no un
carácter hexadecimal:
if (isxdigit(carácter))
Considere la siguiente implementación de dicha macro:
#define isxdigit(c) (isnum((c)) || (toupper((c)) >= ‘A’ && (toupper((c)) <= ‘F’))
/* El siguiente programa: TOUPPER.CPP, ilustra el uso de la macro _toupper y
la función toupper. Se observa el error que puede ocurrir cuando se utilizan
letras mayúsculas con la macro _toupper.
*/
#include <stdio.h>
#include <ctype.h>
//Para putchar
//Para _toupper() y toupper()
void main (void)
{
char cadena[] = "\"El Coronel No Tiene Quien Le Escriba\"";
int i;
for (i = 0; cadena[i]; i++)
putchar(toupper(cadena[i]));
putchar('\n');
for (i = 0; cadena[i]; i++)
putchar(_toupper(cadena[i]));
putchar('\n');
}//Fin de main()
APUNTADORES Y CADENA – LECCIÓN 20
20-58
MIGUEL Á. TOLEDO MARTÍNEZ
/* El siguiente programa: TOLOWER.CPP, ilustra el uso de la macro _tolower y
la función tolower. Se observa el error que puede ocurrir cuando se utilizan
letras minúscula con la macro _tolower.
*/
#include <stdio.h>
#include <ctype.h>
//Para putchar()
//Para tolower() y _tolower()
void main (void)
{
char cadena[] = "\"El Coronel No Tiene Quien Le Escriba\"";
int i;
for (i = 0; cadena[i]; i++)
putchar(tolower(cadena[i]));
putchar('\n');
for (i = 0; cadena[i]; i++)
putchar(_tolower(cadena[i]));
putchar('\n');
}//Fin de main()
CARÁCTER ASCII VÁLIDO
Para asegurar que un carácter es un carácter ASCII válido, su valor se encuentra entre 0 y
127 utilice la macro toascii contenida en archivo de cabecera ctype.h:
Una implementación de dicha macro es la siguiente:
#define toascii(character) ((character) & 0x7F)
Lo que se logra con el valor hexadecimal 0x7F es hacer que el valor ASCII sea siempre
positivo (0 a 127)
/* La función printf, le permite escribir salida formateada a la pantalla.
Dependiendo de los requerimientos de su programa, existen ocasiones en que
necesita trabajar con una cadena de caracteres que contiene salida formatea
da. Por ejemplo, suponga que sus empleados tienen 5 dígitos como número de
empleado y tres caracteres que identifican la región (tal como OAX para Oaxaca).
Suponga que almacena información acerca de cada empleado en un archivo cuyo
nombre es una combinación de estos dos valores (tal como OAX12345). La
función sprintf le permite escribir salida formateada en una cadena de
caracteres.
El siguiente programa: SPRINTF.CPP, utiliza la función sprintf para crear
un nombre de archivo de 8 caracteres.
*/
#include <stdio.h>
//Para la función sprintf() y printf()
APUNTADORES Y CADENA – LECCIÓN 20
20-59
MIGUEL Á. TOLEDO MARTÍNEZ
void main(void)
{
int numeroEmpleado
= 12345;
char region[]
= "OAX";
char nombreArchivo[64];
sprintf(nombreArchivo, "%s%d", region, numeroEmpleado);
printf("Nombre del empleado: %s\n", nombreArchivo);
}//Fin de main()
/* La función scanf() le permite leer entrada formateada desde stdin. Dependiendo
de su programa, existen ocasiones en las cuales una cadena de caracteres
contiene campos que desea asignar a variables específicas. La función sscanf()
le permite leer valores de una cadena, asignando los valores a variables
específicas.
El siguiente programa: SSCANF.CPP, ilustra el uso de la función sscanf().
*/
#include <stdio.h>
//Para sscanf() y printf()
void main(void)
{
int edad;
float salario;
char cadena[] = "33 25000.00";
sscanf(cadena, "%d %f\n", &edad, &salario);
printf("Edad: %d salario %f\n", edad, salario);
}//Fin de main()
/* El siguiente programa: CADENA1.CPP, utiliza las funciones strlen(), strcat()
busca un caracter en una cadena y lo remplaza por otro caracter.
*/
#include <iostream.h>
#include <string.h>
//Para cout y cin
//Para strlen(), strcat()
const unsigned MAX1 = 40;
const unsigned MAX2 = 80;
void main(void)
{
char cadenaPequena[MAX1 + 1];
char cadenaGrande[MAX2 + 1];
char caracterBuscar, caracterRemplazar;
cout
<< "Introduzca la cadena pequeña: ";
cin.getline(cadenaPequena, MAX1);
cout
<< "Introduzca la cadena grande : ";
cin.getline(cadenaGrande, MAX2);
cout
<< endl << endl;
APUNTADORES Y CADENA – LECCIÓN 20
20-60
MIGUEL Á. TOLEDO MARTÍNEZ
cout
cout
<< "La cadena pequena tiene " << strlen(cadenaPequena)
<< " caracteres" << endl;
<< "La cadena grande tiene " << strlen(cadenaGrande)
<< " caracteres" << endl << endl;
// Encadena cadenaPequena a cadenaGrande
strcat(cadenaGrande, cadenaPequena);
cout
cout
<< "Las cadenas encadenadas son: " << cadenaGrande << endl;
<< "La nueva cadena tiene " << strlen(cadenaGrande)
<< " caracteres" << endl << endl;
// Obtiene los caracteres de búsqueda y reemplazo
cout << "Introduzca el caracter a buscar: ";
cin >> caracterBuscar;
cout << "Introduzca el caracter que reemplaza: ";
cin >> caracterRemplazar;
// Reemplaza caracter en la cadena cadenaGrande
for (int i = 0; i < strlen(cadenaGrande); i++)
if (cadenaGrande[i] == caracterBuscar)
cadenaGrande[i] = caracterRemplazar;
// Despliega la cadena cadenaGrande actualizada
cout
<< "\nLa nueva cadena es: " << cadenaGrande;
}//Fin de main()
/* El siguiente programa: CADENA2.CPP, utiliza el método de ordenamiento de la burbuja
para ordenar un arreglo de cadenas de caracteres.
*/
#include <iostream.h>
#include <string.h>
//Para cout y cin
//Para strcmp(), strcpy()
const unsigned TAMANO_CADENA = 40;
const unsigned TAMANO_ARREGLO = 11;
void main(void)
{
char cadena[TAMANO_ARREGLO][TAMANO_CADENA] = { "California", "Virginia", "Alaska",
"New York", "Michigan", "Nevada",
“Ohio", "Florida", "Washington",
"Oregon", "Arizona" };
cout << "El arreglo desordenado es:" << endl;
for (int i = 0; i < TAMANO_ARREGLO; i++)
cout << cadena[i] << endl;
cout << endl;
// Usa el método de ordenamiento de la burbuja para ordenar el arreglo
for (int i = 0; i < TAMANO_ARREGLO; ++i)
for (int j = 0; j < i; ++j)
if (strcmp(cadena[i], cadena[j]) < 0)
APUNTADORES Y CADENA – LECCIÓN 20
20-61
MIGUEL Á. TOLEDO MARTÍNEZ
{
char temp[TAMANO_CADENA];
strcpy(temp, cadena[i]);
strcpy(cadena[i], cadena[j]);
strcpy(cadena[j], temp);
}//Fin de if
cout << "El arreglo ordenado es:" << endl;
for (int i = 0; i < TAMANO_ARREGLO; i++)
cout << cadena[i] << endl;
}//Fin de main()
/* El siguiente programa: CADENA3.CPP, convierte una cadena de caracteres en minúsculas,
mayúsculas e invierte la cadena. Si se introduce puras minúsculas o puras mayúsculas,
el programa lo advierte. También advierte si las palabras son palíndromos.
*/
#include <iostream.h>
#include <string.h>
//Para cout y cin
//Para strcpy(), strlwr(), strupr()
const unsigned TAMANO_CADENA = 40;
void main(void)
{
char str1[TAMANO_CADENA+1];
char str2[TAMANO_CADENA+1];
bool esMinuscula, esMayuscula, esSimetrico;
cout << "Introduzca una cadena: ";
cin.getline(str1, TAMANO_CADENA);
cout << "\nLa cadena que introdujo fue: " << str1 << endl;
strcpy(str2, str1);
// Copia str1 a str2
strlwr(str2);
// Convierte a minúsculas
esMinuscula = (strcmp(str1, str2) == 0) ? true : false;
cout << "\nMinúsculas: " << str2 << endl;
strupr(str2);
// Convierte a mayúsculas
esMayuscula = (strcmp(str1, str2) == 0) ? true : false;
cout << "Mayúsculas: " << str2 << endl << endl;
strcpy(str2, str1);
// Copia str1 a str2
strrev(str2);
// Invierte los caracteres
esSimetrico = (strcmp(str1, str2) == 0) ? true : false;
cout << "Invertido: " << str2 << endl;
if (esMinuscula)
cout << "Su entrada no tiene letras mayúsculas" << endl;
if (esMayuscula)
cout << "Su entrada no tiene letras minúsculas" << endl;
if (esSimetrico)
cout << "Su entrada tiene caracteres simétricos" << endl;
}//Fin de main()
APUNTADORES Y CADENA – LECCIÓN 20
20-62
MIGUEL Á. TOLEDO MARTÍNEZ
/* El siguiente programa: CADENA4.CPP, busca una subcadena dentro de una cadena e indica
en donde comienza. Busca también un caracter indicando en que posición se encuentra.
*/
#include <iostream.h>
#include <string.h>
//Para cout y cin
//Para strstr(), strchr
const unsigned TAMANO_CADENA = 40;
void main(void)
{
char cadenaPrincipal[TAMANO_CADENA+1];
char subCadena[TAMANO_CADENA+1];
char caracterBuscar;
char *p;
int indice;
int contador;
cout << "Introduzca una cadena
: ";
cin.getline(cadenaPrincipal, TAMANO_CADENA);
cout << "Introduzca una subcadena de búsqueda: ";
cin.getline(subCadena, TAMANO_CADENA);
cout << "Introduzca un caracter de búsqueda : ";
cin >> caracterBuscar;
cout << endl;
cout << "
1
2
3
4" << endl;
cout << "01234567890123456789012345678901234567890" << endl;
cout << cadenaPrincipal << endl << endl;
cout << "Buscando la cadena " << subCadena << endl;
p = strstr(cadenaPrincipal, subCadena);
contador = 0;
while (p)
{
contador++;
indice = p - cadenaPrincipal;
cout << "Localizado en el índice " << indice << endl;
p = strstr(++p, subCadena);
}//Fin de while
if (contador == 0)
cout << "No se localizó la subcadena en la cadena principal" << endl;
cout << "\nBuscando el caracter " << caracterBuscar << endl;
p = strchr(cadenaPrincipal, caracterBuscar);
APUNTADORES Y CADENA – LECCIÓN 20
20-63
MIGUEL Á. TOLEDO MARTÍNEZ
contador = 0;
while (p)
{
contador++;
indice = p - cadenaPrincipal;
cout << "Localizado en el índice " << indice << endl;
p = strchr(++p, caracterBuscar);
}//Fin de while
if (contador == 0)
cout << "No localizado el caracter en la cadena principal" << endl;
}//Fin de main()
PENSANDO EN OBJETOS: Iteraciones entre los objetos
Ésta es la última de las tareas de diseño orientado a objetos antes de iniciar el estudio de la programación
orientada a objetos en otro curso. Después de completar esta tarea, estará preparado (y probablemente ansioso) para
comenzar a codificar su simulador del elevador. Para completar el simulador del elevador como se definió,
necesitará las técnicas de C++ de otro curso.
En esta sección nos concentraremos en las interacciones entre los objetos. Esperamos que le sea de ayuda
para armar el cuadro. Probablemente hará adiciones a la lista de objetos, atributos y comportamientos que antes
fueron desarrollados.
Hemos aprendido que la mayoría de los objetos en C++ no hacen las cosas de manera espontánea. En
cambio, los objetos responden a estímulos, que vienen en forma de mensajes, los cuales de hecho son llamadas de
función que invocan las funciones miembro de los objetos.
Consideremos varias de las interacciones entre las clases de la simulación del elevador. El planteamiento
del problema indica la persona oprime el botón que se encuentra en ese piso. El sujeto del enunciado es una
persona y el objeto es el botón. Éste es un ejemplo de interacción entre clases. Un objeto de la clase persona envía
un mensaje a un objeto botón. A ese mensaje lo llamamos oprimirBotón. Antes hemos hecho que el mensaje fuera
una función miembro de la clase botón.
En este punto, bajo Otros hechos de cada una de las clases de su simulación, todo lo que ha debido poner
han sido las interacciones entre las clases. Algunas de ellas muestran explícitamente las interacciones entre los
objetos de clase. Pero considere la oración
"una persona espera a que se abra la puerta del elevador"
Anteriormente hemos listados los dos comportamientos de la puerta del elevador, abrirPuerta y
cerrarPuerta. Pero ahora queremos determinar qué clase de objetos envían estos mensajes. No se indican
explícitamente en la oración entrecomillada anterior. Así que le damos algunas vueltas a esto y nos damos cuenta de
que el elevador mismo envía el mensaje a la puerta. Estas interacciones entre los objetos de clase están implícitas en
el planteamiento del problema.
Ahora continúe refinando la sección Otros hechos de las distintas clases del simulador del elevador. Estas
secciones ahora deberán contener principalmente las interacciones entre las clases. Vea cada una de ellas como:
1.
2.
3.
un objeto de clase de envío particular
que envía un mensaje particular
a un objeto de clase de recepción particular
Bajo cada clase, añada la sección Mensajes enviados a los objetos de otras clases (a tales mensajes se les
llama colaboraciones; a partir de ahora emplearemos este término) y liste las interacciones entre las clases que
quedan, es decir, bajo la clase persona, incluya la entrada :
APUNTADORES Y CADENA – LECCIÓN 20
20-64
MIGUEL Á. TOLEDO MARTÍNEZ
la persona le envía el mensaje oprimirBotón al botón de ese piso
Bajo la clase botón, bajo Colaboraciones, coloque el mensaje:
el botón le envía el mensaje venPorMí al elevador
A medida que añada estas entradas, podrá agregar atributos y comportamientos a sus objetos. Esto es
perfectamente natural.
Cuando complete este ejercicio de laboratorio, tendrá una lista razonablemente completa de las clases que
tendrá que implementar su simulador del elevador. Y por cada clase tendrá una lista razonablemente completa de sus
atributos y comportamientos, así como de los mensajes que los objetos de dicha clase envían a los objetos de otras
clases
En el curso de la especialidad, estudiará la programación orientada a objetos en C++. Aprenderá cómo
crear nuevas clases. Estará listo para escribir en C++ una parte importante del simulador de elevador. Conforme
avance en sus conocimientos habrá aprendido lo suficiente para implementar un simulador operativo. Finalmente
aprenderá a valerse de la herencia para explotar los puntos comunes entre las clases y minimizar la cantidad de
software que necesitará escribir para resolver un problema.
Hagamos un resumen del proceso de diseño orientado a objetos que hemos presentado:
Escriba el planteamiento del problema en un archivo de texto.
Descarte la información irrelevante.
Extraiga todos los hechos. Disponga cada hecho en una línea independiente de un archivo de hechos.
Busque los sustantivos en los hechos; con gran probabilidad serán muchas de las clases que necesitará.
Por cada clase, poniéndola en primer nivel, escriba una lista de resumen.
5. Ponga cada hecho en el segundo nivel de la lista, bajo la clase apropiada. Si un hecho menciona varias
clases (como sucederá en muchos casos), póngalo bajo todas ellas.
6. Ahora refine el conjunto de hechos bajo cada una de las clases. Liste tres subencabezados bajo cada
clase: Atributos, Comportamientos y Colaboraciones.
7. Bajo Atributos liste la información asociada con cada clase.
8. Bajo Comportamientos liste las acciones que pueden llevar a cabo los objetos de dicha clase en respuesta
a la recepción de un mensaje. Cada comportamiento es una función miembro de la clase.
9. Bajo Colaboraciones liste los mensajes que los objetos de esta clase envían a los objetos de otras clases y
las clases que reciben estos mensajes.
10. En este punto su diseño probablemente tendrá todavía algunas piezas faltantes, que tal vez queden claras a
medida que proceda con la implementación de su simulador en C++ conforme sus conocimientos de la
programación orientada a objetos sean más amplios.
1.
2.
3.
4.
Los atributos y comportamientos con frecuencia se llaman responsabilidades de una clase. La
metodología de diseño que hemos perfilado aquí a veces se llama clases, responsabilidades y colaboraciones, o
simplemente CRC.
ERRORES COMUNES DE PROGRAMACIÓN
1.
2.
3.
4.
5.
6.
Suponer que el * con el que se declara un apuntador se extiende a todos los nombres de variables de una lista de
variables de apuntador separada por comas puede causar que tales apuntadores se declaren como no
apuntadores. Cada apuntador debe declararse con un * como prefijo del nombre.
Desreferenciar un apuntador que no ha sido inicializado de manera apropiada o al que no se le ha indica- do
que apunte a una localidad específica de memoria, puede producir un error fatal de tiempo de ejecución o
modificar accidentalmente información importante, permitiendo que el programa se ejecute hasta el final, lo que
arrojará resultados incorrectos.
El intento por desreferenciar un no apuntador es un error de sintaxis.
Desreferenciar un apuntador 0 por lo general causa un error fatal en tiempo de ejecución.
Es un error no desreferenciar un apuntador cuando es necesario hacerlo con el fin de obtener el valor al que
apunta el apuntador.
No inicializar un apuntador que se ha declarado corno const es un error de sintaxis.
APUNTADORES Y CADENA – LECCIÓN 20
20-65
MIGUEL Á. TOLEDO MARTÍNEZ
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
El empleo del operador sizeof en una función con el fin de determinar el tamaño en bytes de un parámetro de
arreglo da como resultado el tamaño en bytes del apuntador, no el tamaño en bytes del arreglo.
La omisión de los paréntesis en una operación sizeof cuando el operando es un nombre de tipo es un error de
sintaxis.
La utilización de aritmética de apuntadores sobre un apuntador que no hace referencia a un arreglo de valores
normalmente es un error de lógica.
La resta o comparación de dos apuntadores que no hacen referencia a elementos del mismo arreglo por lo
general es un error de lógica.
Salirse de cualquiera de los extremos de un arreglo al aplicar aritmética de apuntadores normalmente es un
error de lógica.
La asignación de un apuntador de un tipo a otro apuntador de un tipo distinto (que no sea void *) sin convertir el
primer apuntador al tipo del segundo es un error de sintaxis.
Desreferenciar un apuntador void * es un error de sintaxis.
Aunque los nombre de arreglos son apuntadores al inicio de éstos y aunque los apuntadores se pueden modificar
con expresiones aritméticas, los nombres de los arreglos no pueden modificarse mediante expresiones
aritméticas, dado que son apuntadores constantes.
Es un error no asignar suficiente espacio en un arreglo de caracteres para almacenar el carácter nulo de
terminación de la cadena.
Es un error crear o utilizar una “cadena” que no contenga un carácter nulo de terminación.
El procesamiento de un solo carácter como cadena puede generar un error fatal en tiempo de ejecución. Una
cadena es un apuntador -probablemente sea un entero de tamaño respetablemente grande. Sin embargo, un
carácter es un entero pequeño (los valores ASCII van de 0 a 255) En muchos sistemas, esto provoca un error,
pues las direcciones de memoria bajas se reservan para fines especiales, como controladores de interrupciones
del sistema operativo -por lo tanto, suceden “violaciones de acceso”.
Si pasa un carácter como argumento a una función cuando se espera una cadena, puede causar un error fatal
en tiempo de ejecución.
Pasar una cadena como argumento de una función cuando se espera un carácter es un error de sintaxis.
Es un error olvidar incluir el archivo de encabezado <string.h> cuando se emplean funciones de la biblioteca
de manejo de cadenas.
No agregar un carácter nulo de terminación al primer argumento de strncpy cuando el tercer argumento es
menor o igual que la longitud de la cadena del segundo argumento puede ser causa de errores fatales en tiempo
de ejecución.
La suposición de que strcmp y strncmp devuelven 1 cuando sus argumentos son iguales es un error de lógica.
Ambas funciones devuelven 0 (que es el valor de falso de C++) cuando hay una igualdad. Por lo tanto, al probar
la igualdad de dos cadenas, el resultado de la función strcmp o strncmp debe compararse con 0 para determinar
si son iguales.
Es un error no notar que strtok modifica la cadena que se está dividiendo en tokens y luego intentar utilizarla
como si fuera la cadena original, sin modificaciones.
BUENAS PRÁCTICAS DE PROGRAMACIÓN
1.
2.
3.
4.
5.
Aunque no se requiere, la inclusión de las letras Ptr en los nombres de variables de apuntador deja claro que
dichas variables son apuntadores y que necesitan manipularse como tales.
Utilice una llamada por valor para pasar argumentos a una función, a menos que el invocador requiera
explícitamente que la función llamada modifique el valor de la variable argumento en el entorno del invocador.
Éste es otro ejemplo del principio de menor privilegio.
Antes de utilizar una función, revise su prototipo de función a fin de determinar los parámetros que puede
modificar.
Utilice la notación de arreglos en lugar de la notación de apuntadores cuando manipule arreglos. Aunque la
compilación del programa tal vez tarde un poco más, con seguridad será más claro.
Al almacenar una cadena de caracteres en un arreglo de caracteres, debe asegurarse de que el arreglo es lo
bastante grande para contener la cadena más grande que almacenará. C++ permite el almacenamiento de
cadenas de cualquier longitud. Si una cadena es más grande que el arreglo en el que la almacenará, los
caracteres que estén después del final del arreglo sobrescribirán la información en memoria que se encuentre a
continuación de dicho arreglo.
APUNTADORES Y CADENA – LECCIÓN 20
20-66
MIGUEL Á. TOLEDO MARTÍNEZ
PROPUESTAS DE DESEMPEÑO
1.
2.
3.
Los objetos grandes, como las estructuras, se deben pasar mediante apuntadores hacia datos constantes o con
referencias hacia datos constantes, con el fin de lograr los beneficios en desempeño de la llamada por referencia
y la seguridad de la llamada por valor.
sizeof es un operador unario de tiempo de compilación, no una función de tiempo de ejecución. Por lo tanto,
la utilización de sizeof no afecta negativamente el desempeño en tiempo de ejecución.
A veces los algoritmos que emergen de manera natural pueden tener sutiles problemas de desempeño, como el
aplazamiento indefinido. Es importante buscar algoritmos que eviten el aplazamiento indefinido.
PROPUESTAS DE PORTABILIDAD
1.
2.
3.
4.
5.
6.
7.
El formato en el que se envía a la salida un apuntador depende de la máquina. Algunos sistemas envían a la
salida los valores de apuntador corno enteros hexadecimales, mientras que otros lo hacen como enteros
decimales.
Aunque const está bien definido en el ANSI C y C++, algunos compiladores no lo aplican adecuadamente.
El número de bytes que se utilizan para almacenar un tipo de datos particular puede variar según el sistema. Al
escribir programas que dependan de los tamaños de los tipos de datos y que se ejecutarán en varios sistemas de
cómputo, hay que utilizar sizeof para determinar el número de bytes en que se almacenan los distintos tipos de
datos.
La mayoría de las computadoras actuales tienen enteros de 2 o 4 bytes. Algunas de las más nuevas utilizan
enteros de 8 bytes. Debido a que los resultados de la aritmética de apuntadores dependen del tamaño de los
objetos a los que apunta un apuntador, la aritmética de apuntadores depende de la máquina.
Cuando se inicializa una variable de tipo char * con una literal de cadena, algunos compiladores pueden poner
dicha cadena en alguna localidad de memoria en la que no puede ser modificado. Si necesitara modificar una
literal de cadena, deberá almacenarla en un arreglo de caracteres, a fin de asegurar la posibilidad de modificarla
en todos los sistemas.
Los códigos numéricos empleados para la representación de caracteres pueden cambiar según la computadora.
No use los códigos ASCII específicos, como en el caso de if ( ch == 65 ) más bien, emplee la constante del
carácter, como en if ( ch = = ‘A’ ).
OBSERVACIONES DE INGENIERÍA DE SOFTWARE
1.
2.
3.
4.
5.
El calificador const puede servir para aplicar el principio de menor privilegio. La utilización del principio de
menor privilegio para diseñar software de la manera correcta puede reducir en gran medida el tiempo de
depuración y los efectos secundarios, así como simplificar la modificación y el mantenimiento de los
programas.
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 por accidente.
Sólo es posible alterar un valor en un invocador cuando se efectúa una llamada por valor. Este valor debe
asignarse a partir del valor de devolución de la función. Si hay que modificar varios valores en un invocador, es
necesario pasar varios argumentos mediante llamada por referencia.
La colocación de prototipos de función en las definiciones de otras funciones aplica el principio de menor
privilegio, restringiendo las llamadas de función a las funciones en las que aparecen los prototipos.
Al pasar un arreglo a una función, también hay que pasar su tamaño (en lugar de integrarlo en la función)
Esto contribuye a hacer más general la función. Las funciones generales con frecuencia son reutilizables en
otros programas.
INDICACIONES DE PRUEBA Y DEPURACIÓN
1.
Los apuntadores se deben inicializar con el fin de evitar que apunten a áreas desconocidas o no inicializadas de
la memoria.
APUNTADORES Y CADENA – LECCIÓN 20
20-67
MIGUEL Á. TOLEDO MARTÍNEZ
LO QUE NECESITA SABER
Antes de continuar con la siguiente lección, asegúrese de haber aprendido lo siguiente:
!"Los apuntadores son variables que contienen como valor la dirección de otras variables.
!"La declaración:
int *ptr;
declara que ptr es un apuntador a un objeto de tipo int, y se lee ptr es un apuntador a int. El * como se
emplea en una declaración, indica que la variable es un apuntador.
!"Hay tres valores que se pueden utilizar para inicializar un apuntador: 0, NULL o la dirección de un objeto
del mismo tipo. Es lo mismo inicializar un apuntador a 0 que a NULL.
!"El único entero que se puede asignar a un apuntador es 0.
!"El operador & (de dirección) devuelve la dirección de su operando.
!"El operando del operador de dirección debe ser un nombre de variable; el operador de dirección no se
puede aplicar a constantes, a expresiones que no devuelvan una referencia ni a variables declaradas con la
clase de almacenamiento register.
!"El operador *, conocido como operador de indirección o de desreferencia, devuelve un sinónimo, alias o
apodo del nombre del objeto al que apunta su operando en memoria. A esto se le llama desreferenciar el
apuntador.
!"Al llamar una función con un argumento que el invocador desea que la función llamada modifique, debe
pasarse la dirección del argumento. La función llamada modifica entonces el valor del argumento del
invocador mediante el operador de indirección (*)
!"Una función que recibe como argumento una dirección debe incluir un apuntador corno su parámetro
correspondiente.
!"No es necesario incluir los nombres de los apuntadores en los prototipos de función; sólo es necesario
incluir los tipos de apuntador. Los nombres de apuntador se pueden agregar con fines de documentación,
pero el compilador los ignora.
!"El calificador const le permite al programador informarle al compilador que no se deberá modificar el
valor de una variable en particular.
!"Si se intenta modificar un valor const, el compilador lo detecta y emite un aviso o un error, según el
compilador de que se trate.
!"Hay cuatro maneras de pasar un apuntador a una función: mediante un apuntador no constante hacia
datos no constantes, por medio de un apuntador no constante hacia datos constantes, utilizando un
apuntador constante hacia datos no constantes y a través de un apuntador constante hacia datos constantes.
!"Los arreglos se pasan de manera automática por referencia, utilizando apuntadores, pues el valor del
nombre del arreglo es la dirección del mismo.
!"Para pasar un solo elemento de un arreglo como llamada por referencia mediante apuntadores, es
necesario pasar la dirección del elemento específico del arreglo.
!"C++ ofrece el operador unario especial sizeof, que determina el tamaño de un arreglo (o de cualquier otro
tipo de datos) en bytes durante la compilación del programa.
!"Cuando se aplica al nombre de un arreglo, el operador sizeof devuelve, como entero, la cantidad total de
bytes que hay en ella.
!"El operador sizeof se puede aplicar a cualquier nombre de variable, tipo o constante.
!"Las operaciones aritméticas que se pueden llevar a cabo sobre los apuntadores son incremento (++),
decremento (--), suma (+ o + =) o resta (- o - =) de un apuntador y un entero, y resta de un apuntador a
otro.
!"Cuando se suma o resta un entero a un apuntador, el apuntador se incremento o decrementa por dicho
entero, multiplicado por el tamaño del objeto al que se apunta.
!"Las operaciones de aritmética de apuntadores sólo deben efectuarse sobre partes contiguas de la
memoria, por ejemplo, sobre un arreglo. Todos los elementos de un arreglo se almacenan en localidades
contiguas de memoria.
APUNTADORES Y CADENA – LECCIÓN 20
20-68
MIGUEL Á. TOLEDO MARTÍNEZ
!"Al llevar a cabo aritmética de apuntadores sobre un arreglo de caracteres, el resultado es como el de la
aritmética normal, pues cada carácter se almacena en un byte de memoria.
!"Se puede asignar un apuntador a otro si ambos son del mismo tipo. De otra manera, debe aplicarse una
conversión mediante cast. La excepción a esto es un apuntador a void, que es un tipo de apuntador general
que puede contener apuntadores de cualquier tipo. A los apuntadores a void se les puede asignar
apuntadores de otros tipos. Es posible asignar un apuntador void a un apuntador de otro tipo sólo a través
de una conversión mediante cast explícita.
!"No se puede desreferenciar un apuntador a void.
!"Los apuntadores se pueden comparar por medio de los operadores de igualdad y relacionales. Las
comparaciones de apuntadores por lo general sólo tienen sentido si los apuntadores apuntan a miembros del
mismo arreglo.
!"Los apuntadores se pueden indexar de la misma manera que los nombres de arreglos.
!"Un nombre de arreglo es equivalente a un apuntador al primer elemento del mismo.
!"En la notación de apuntador/desplazamiento, el desplazamiento es igual a un índice de arreglo.
!"Todas las expresiones de arreglos indexadas se pueden escribir con un apuntador y un desplazamiento,
utilizando corno apuntador el nombre del arreglo o un apuntador separado que apunte a dicho arreglo.
!"Un nombre de arreglo es un apuntador constante que siempre apunta a la misma localidad de memoria.
!"Es posible tener arreglos de apuntadores.
!"Un apuntador a una función es la dirección donde reside el código de la función.
!"Los apuntadores a funciones pueden ser pasados a otras funciones, devueltos de otras funciones,
almacenados en arreglos y asignados a otros apuntadores.
!"Un uso común de los apuntadores de función es en los llamados sistemas operados por menú. Los
apuntadores de función sirven para seleccionar la función a llamar según un elemento particular del menú.
!"La función strcpy copia su segundo argumento (una cadena) a su primer argumento (un arreglo de
caracteres) El programador debe asegurarse de que el arreglo es lo bastante grande para almacenar la
cadena y su carácter nulo de terminación.
!"La función strncpy es equivalente a strcpy, excepto que una llamada a strncpy especifica el número de
caracteres a copiar de la cadena al arreglo. El carácter nulo de terminación sólo se copiará si el número de
caracteres a copiar es de uno más que la longitud de la cadena.
!"La función strcat añade la cadena de su segundo argumento (incluyendo el carácter nulo de terminación) a
la cadena de su primer argumento. El primer carácter de la segunda cadena reemplaza el carácter nulo ( ‘\0’
) de la primera cadena. El programador debe asegurarse de que el arreglo en el que está la primera cadena
es lo bastante grande para almacenar tanto la primera cadena como la segunda.
!"La función strncat agrega un número de caracteres especificado de la segunda cadena a la primera. Se
añade un carácter nulo de terminación al resultado.
!"La función strcmp compara la cadena de su primer argumento con la cadena de su segundo argumento,
carácter por carácter. La función devuelve 0 si ambas son iguales, un resultado negativo si la primera es
menor que la segunda y un valor positivo si la primera es mayor que la segunda.
!"La función strncmp es equivalente a strcmp, excepto que strncmp compara un número especificado de
caracteres. Si el número de caracteres de una cadena es menor que el número especificado, strncmp
compara los caracteres hasta que se encuentra el carácter nulo en la cadena más corta.
!"Una secuencia de llamadas a strtok divide una cadena en tokens separados por los caracteres contenidos en
el segundo argumento. La primera llamada contiene como primer argumento la cadena a dividir en tokens,
y las llamadas siguientes que continúan dividiendo la misma cadena en tokens contienen NULL como
primer argumento. Cada llamada devuelve un apuntador al token actual. Si no hay más tokens al llamar a
strtok, se devuelve NULL.
!"La función strlen toma como argumento una cadena y devuelve el número de caracteres que hay en ella; en
la longitud de la cadena no se incluye el carácter nulo de terminación.
!"Una cadena de caracteres es un arreglo de caracteres que terminan por los caracteres ASCII 0 o NULL.
!"Puede crear una cadena de caracteres dentro de su programa mediante la declaración de un arreglo de tipo
char.
!"Su programa es responsable de agregar el carácter NULL después del último carácter de la cadena.
APUNTADORES Y CADENA – LECCIÓN 20
20-69
MIGUEL Á. TOLEDO MARTÍNEZ
!"Cuando su programa utiliza cadenas constantes entre comillas, el compilador C++ automáticamente
agrega el carácter NULL.
!"C++ le permite inicializar las cadenas al declararlas, especificando los caracteres deseados entre comillas.
!"La mayoría de los compiladores C++ proporcionan un conjunto de funciones para el manejo de cadenas en
las librerías en tiempo de ejecución.
PREGUNTAS Y PROBLEMAS
PREGUNTAS
1.
Llene los siguientes espacios en blanco:
a) Un apuntador es una variable que contiene como valor la ______________ de otra variable.
b) Los tres valores que se pueden utilizar para inicializar un apuntador son __________, _________ y
____________.
c) El único entero que se puede asignar a un apuntador es________________
2.
Indique si las siguientes oraciones son verdaderas o falsas. Si la respuesta es falsa, explique por qué.
a) El operador de dirección & sólo puede aplicarse a constantes, a expresiones y a variables declaradas
con la clase de almacenamiento register.
b) Se puede desreferenciar un operador declarado como void.
c) No es posible asignar un apuntador de un tipo a otro de otro tipo sin una operación de conversión
mediante cast.
3.
Responda las siguientes preguntas. Suponga que los números de punto flotante de precisión sencilla se
almacenan en 4 bytes y que la dirección de inicio del arreglo está en la localidad 1002500 de la memoria. Cada
parte del ejercicio debería utilizar el resultado de las partes previas, donde aplique.
a) Declare un arreglo de tipo float llamado numeros que tenga 10 elementos, los cuales deberá inicializar a
los valores 0.0, 1.1, 2.2, ..., 9.9. Suponga que la constante simbólica TAMANO se ha definido como10.
b) Declare el apuntador nPtr que apunta a un objeto de tipo float.
c) Imprima los elementos del arreglo numeros utilizando la notación de índices de arreglos. Emplee una
estructura for y suponga que se ha declarado la variable de control entera i. Imprima cada número con
una posición de precisión a la derecha del punto decimal.
d) Dé dos instrucciones independientes que asignen la dirección de inicio del arreglo numeros a la variable
de apuntador nPtr.
e) Imprima los elementos del arreglo numeros por medio de notación de apuntador/desplazamiento con el
apuntador nPtr.
f) Imprima los elementos del arreglo numeros utilizando notación de apuntador/desplazamiento con el
nombre del arreglo como apuntador.
g) Imprima los elementos del arreglo numeros indexando el apuntador nPtr.
h) Haga referencia al elemento 4 del arreglo numeros empleando notación de índices de arreglos, notación
de apuntador/desplazamiento con el nombre del arreglo como apuntador, notación de índice de apuntador
con nPtr y notación de apuntador/desplazamiento con nPtr.
i) Suponiendo que nPtr apuntara al inicio del arreglo numeros, ¿a qué dirección hace referencia nPtr + 8?
¿Qué valor se almacena en dicha localidad?
j) Suponiendo que nPtr apunte a numeros[5], ¿qué dirección referencia nPtr después de que se ejecuta nPtr
-= 4?. ¿Qué valor se almacena en esa localidad?
4.
Escriba una sola instrucción que lleve a cabo la tarea indicada para cada uno de los siguientes casos. Suponga
que ya se han declarado las variables de punto flotante numero1 y numero2, y que numero1 se ha inicializado a
7.3. También suponga que la variable ptr es de tipo char * y que los arreglos s1[100] s2[100] son de tipo char.
a)
b)
c)
d)
Declare la variable fPtr como apuntador a un objeto de tipo float.
Asígnele a la variable de apuntador fPtr la dirección de la variable numero1.
Imprima el valor del objeto al que apunta fPtr.
Asígnele a la variable numero2 el valor del objeto al que apunta fPtr.
APUNTADORES Y CADENA – LECCIÓN 20
20-70
MIGUEL Á. TOLEDO MARTÍNEZ
e)
f)
g)
h)
i)
j)
k)
l)
5.
Imprima el valor de numero2.
Imprima la dirección de numero1.
Imprima la dirección almacenada en fPtr. ¿El valor que se imprime es igual a la dirección de numero1?
Copie la cadena almacenada en el arreglo s2 al arreglo s1.
Compare la cadena en s1 con la cadena en s2. Imprima el resultado.
Agregue 10 caracteres de la cadena de s2 a la cadena de s1.
Determine la longitud de la cadena de s1. Imprima el resultado.
Asígnele a ptr la localidad del primer token de s2. Los tokens de s2 se separan mediante comas (,).
Haga lo siguiente:
a) Escriba el encabezado de una función llamada intercambio(), que toma como parámetros los apuntadores
a los números de punto flotante x e y y que no devuelve nada.
b) Escriba el prototipo de la función de la parte (a).
c) Escriba el encabezado de una función llamada evaluar(), que devuelve un entero y toma como parámetros
el entero x y un apuntador a la función poly. Ésta toma un parámetro entero y devuelve un entero.
d) Escriba el prototipo de función de la parte (c)
e) Muestre dos métodos diferentes para inicializar el arreglo de caracteres vocal con la cadena de vocales
“AEIOU”.
6.
Encuentre los errores en los siguientes segmentos de programa. Suponga que
int *zPtr;
// zPtr referenciará el arreglo z
int *aPtr = 0;
void *sPtr = 0;
int numero, i;
int z[5] = { 1, 2, 3, 4, 5}
sPtr = z;
a) ++zPtr;
b) // Utiliza el apuntador para obtener el primer valor del arreglo numero = zPtr;
c) // le asigna a numero el elemento 2 del arreglo (el valor 3)
a. numero = *zPtr[ 2];
d) // imprime todo el arreglo z
a. for ( i = 0; i <= 5; i++ )
i. cout << zPtr[ i ] << endl;
e) // le asigna a numero el valor al que apunta sPtr
f) numero = *sPtr;
g) ++z;
h) char s[10];
i) cout << strncpy( s, “adios”, 5) << endl;
j) char s[12];
k) strcpy( s, “Bienvenido a casa”);
l) if( strcmp( cadena1, cadena2))
cout << “Las cadenas son iguales” << endl;
7.
¿Qué imprimen las siguientes instrucciones cuando se ejecutan (si es que imprimen algo)? Si alguna de estas
contiene un error, descríbalo e indique la manera de corregirlo. Suponga las siguientes declaraciones de
variables:
char s1[50] = “jack”, s2[50] = “jill”, s3[50], *sptr;
a)
b)
c)
d)
cout << strcpy(s3, s2) << endl;
cout << strcat(strcat(strcpy(s3, s1), “ and “ ), s2) << endl;
cout << strlen(s1) + strlen(s2) << endl;
cout << strlen(s3) << endl;
APUNTADORES Y CADENA – LECCIÓN 20
20-71
MIGUEL Á. TOLEDO MARTÍNEZ
8.
Indique si las siguientes oraciones son verdaderas o falsas. Si la respuesta es falsa, explique por qué.
a) La comparación de dos apuntadores que apuntan a arreglos diferentes no tiene sentido.
b) Debido a que el nombre de un arreglo es un apuntador al primer elemento de dicho arreglo, los nombres
de arreglo deben manipularse de la misma manera que los apuntadores.
9.
Conteste a lo siguiente. Suponga que los enteros sin signo se almacenan en 2 bytes y que la dirección inicial del
arreglo es la localidad 1002500 de la memoria.
a) Declare un arreglo de tipo unsigned int llamado valores que cuente con 5 elementos, los cuales deberá
inicializar a los enteros pares del 2 al 10. Suponga que la constante simbólica se ha definido como 5.
b) Declare el apuntador vPtr que apunta a un objeto de tipo unsigned int.
c) Imprima los elementos del arreglo valores utilizando notación de índices de arreglo. Emplee una
estructura for y suponga que se ha declarado la variable de control entera i.
d) Escriba dos instrucciones separadas que asignen la dirección inicial del arreglo valores a la variable de
apuntador vPtr.
e) Imprima los elementos del arreglo valores utilizando notación de apuntador/desplazamiento.
f) Imprima los elementos del arreglo valores utilizando notación de apuntador/desplazamiento con el
nombre del arreglo como apuntador.
g) Imprima los elementos del arreglo valores indexando el apuntador al arreglo.
h) Haga referencia al elemento 5 de valores utilizando notación de índices de arreglo, notación de
apuntador/desplazamiento con el nombre del arreglo como apuntador, notación de índices de apuntador y
notación de apuntador/desplazamiento.
i) ¿A qué dirección hace referencia vPtr + 3? ¿Qué valor se almacena en dicha localidad?
j) Suponiendo que vPtr apunte a valores[4], ¿a qué dirección hace referencia vPtr -= 4? ¿Qué valor se
almacena en dicha localidad?
10. Para cada uno de los siguientes puntos, escriba una instrucción que lleve a cabo la tarea indicada. Suponga que
se han declarado las variables enteras largas valor1 y valor2, y que valor1 se ha inicializado a 200000.
a)
b)
c)
d)
e)
f)
g)
Declare la variable lPtr como apuntador a un objeto de tipo long.
Asigne la dirección de la variable valor1 a la variable de apuntador lPtr.
Imprima el valor del objeto al que apunta lPtr.
Asígnele a la variable valor2 el valor del objeto al que apunta lPtr.
Imprima el valor de valor2.
Imprima la dirección de valor1.
Imprima la dirección almacenada en lPtr. ¿Es igual el valor impreso que la dirección de valor1?
11. Haga lo siguiente.
a) Escriba el encabezado de la función cero, que toma como parámetro el arreglo de enteros largos
enterosLargos y no devuelve nada.
b) Escriba el prototipo de la función de la parte (a)
c) Escriba el encabezado de la función agrega1YSuma, que toma como parámetro el arreglo de enteros
demasiadoPequeno y devuelve un entero.
d) Escriba el prototipo de la función descrita en la parte (c)
APUNTADORES Y CADENA – LECCIÓN 20
20-72
MIGUEL Á. TOLEDO MARTÍNEZ
PROBLEMAS
Nota: Los problemas 1 a 4 son algo complejos. Una vez que los haya hecho, debería poder implementar con
facilidad los juegos de naipes más comunes.
1.
Modifique el programa BARAJAS.CPP, para que la función de barajado de los naipes reparta una mano de
póquer de cinco naipes. Después escriba las funciones que realicen lo siguiente:
a)
b)
c)
d)
e)
f)
Determine si la mano contiene un par.
Determine si la mano contiene dos pares.
Determine si la mano contiene una tercia (por ejemplo, tres sotas)
Determine si la mano contiene un póquer (por ejemplo, cuatro ases)
Determine si la mano contiene un flux (es decir, los cinco naipes del mismo palo)
Determine si la mano contiene una corrida (es decir, cinco naipes del mismo palo con valores
consecutivos)
2.
Utilice las funciones desarrolladas en el problema 1 para escribir un programa que reparta dos manos de
póquer de cinco naipes, las evalúe y determine cuál es la mejor.
3.
Modifique el problema desarrollado en el problema 2 de modo que simule al repartidor. La mano del repartidor
se baraja cerrada, para que el jugador no la pueda ver. El programa deberá evaluar dicha mano y, con base en
su calidad, cambiar las inservibles de la mano original por uno, dos o tres naipes. El programa deberá reevaluar
la mano del repartidor. (Precaución: éste es un problema complicado)
4.
Modifique el programa desarrollado en el problema 3 para que pueda manejar automáticamente la mano del
repartidor, pero que le permita al jugador decidir cuáles de los naipes de su propia mano cambiará. El programa
deberá evaluar ambas manos y decidir quién gana. Ahora utilice este nuevo programa para jugar 20 juegos
contra la computadora. ¿Quién gana más juegos, usted o la computadora? Haga que uno de sus amigos
juegue 20 juegos contra la computadora. ¿Quién gana más juegos? Con base en estos resultados, haga las
modificaciones necesarias para refinar el programa de póquer (éste también es un problema difícil) Juegue 20
juegos más. ¿El programa modificado juega mejor?
5.
En el programa de barajado y repartición de naipes BARAJAS.CPP, utilizamos intencionalmente un algoritmo
de barajado ineficiente que presentó la posibilidad de aplazamiento indefinido. En este problema, creará un
algoritmo de barajado de alto desempeño que evite el aplazamiento indefinido.
Modifique el programa BARAJAS.CPP, como sigue. Inicialice el arreglo paquete como se muestra en la
figura 20.10 . Modifique la función barajar() para que haga un ciclo fila por fila y columna por columna a
través del arreglo, tocando una vez todos los elementos. Cada elemento debe intercambiarse con otro elemento
del arreglo seleccionado al azar. Imprima el arreglo resultante para determinar si se barajó de manera adecuada
(como en la figura 20.11, por ejemplo) Tal vez desee que el programa llame varias veces a la función barajar(),
a fin de asegurar un barajado satisfactorio.
Note que, aunque este enfoque mejor el algoritmo de barajado, éste aún tiene que buscar en el arreglo paquete
el naipe 1, luego el 2, el 3, etc. Es más, aún después de que el algoritmo de barajado encuentra y reparte el
naipe, sigue buscando en el resto de los naipes. Modifique el programa BARAJAS.CPP para que, una vez
repartido un naipe, no se hagan mas intentos por encontrarlo, procediendo de inmediato a repartir el siguiente.
Arreglo paquete sin barajar
0
1
2
3
0
1
14
27
40
1
2
15
28
41
2
3
16
29
42
3
4
17
30
43
4
5
18
31
44
5
6
19
32
45
6
7
20
33
46
7
8
21
34
47
8
9
22
35
48
9
10
23
36
49
10
11
24
37
50
11
12
25
38
51
12
13
26
39
52
Figura 20.10. Arreglo paquete sin barajar
APUNTADORES Y CADENA – LECCIÓN 20
20-73
MIGUEL Á. TOLEDO MARTÍNEZ
Ejemplo del arreglo paquete sin barajado
0
1
2
3
0
19
13
12
50
1
40
28
33
38
2
27
14
15
52
3
25
16
42
39
4
36
21
43
48
5
46
30
23
51
6
10
8
45
9
7
34
11
3
5
8
35
31
29
37
9
41
17
32
49
10
18
24
4
22
11
2
7
47
6
12
44
1
26
20
Figura 20.11. Ejemplo del arreglo paquete sin barajado
6.
(Simulación: la tortuga y la liebre) En este problema, recreará la carrera clásica de la tortuga y la liebre. Se
valdrá de la generación de números aleatorios para desarrollar la simulación de este memorable evento.
Nuestros contendientes comienzan la carrera en el cuadro 1 de una serie de 70 cuadros. Cada cuadro representa
una posición posible en la ruta de la carrera. La línea de meta está en el cuadro 70. El primer contendiente que
llegue o pase el cuadro 70 obtiene como recompensa un cubo de zanahorias y lechuga fresca. La ruta sube
serpenteando por la ladera de una montaña resbalosa, por lo que ocasionalmente los contendientes pierden
terreno.
Hay un reloj que pulsa una vez por segundo. Con cada pulso del reloj, el programa deberá ajustar la posición de
los animales, de acuerdo con las siguientes reglas:
Animal
Tipo de movimiento
Tortuga Paso veloz
Resbalón
Paso lento
Liebre
Duerme
Gran salto
Gran resbalón
Salto pequeño
Resbalón pequeño
Porcentaje del tiempo
50%
20%
30%
20%
20%
10%
30%
20%
Movimiento real
3 cuadros a la derecha
6 cuadros a la izquierda
1 cuadro a la derecha
No se mueve
9 cuadros a la derecha
12 cuadros a la izquierda
1 cuadro a la derecha
2 cuadros a la izquierda
Utilice variables para llevar el registro de las posiciones de los animales (las posiciones son de la 1 a la 70)
Cada animal debe comenzar en la posición 1 (es decir, la puerta de salida) Si un animal resbala hacia la
izquierda, quedando antes del cuadro 1, devuélvalo al cuadro 1.
Genere los porcentajes de la tabla previa produciendo un entero aleatorio, i, que esté en el rango 1 ≤ i ≤ 10.
Para la tortuga, efectúe un paso veloz cuando 1 ≤ i ≤ 5, un resbalón cuando 6 ≤ i ≤ 7 y un paso lento
cuando 8 ≤ i ≤ 10. Utilice una técnica similar para mover a la liebre.
Comience la carrera imprimiendo
¡BANG!
¡Y ARRANCAN!
Por cada pulso del reloj (es decir, cada repetición del ciclo), imprima una línea de 70 posiciones que
muestre la posición de la tortuga mediante la letra T y la de la liebre mediante la letra L. Ocasionalmente
los contendientes caerán en el mismo cuadro. En este caso, la tortuga morderá a la liebre y el programa
deberá imprimir ¡OUCH! en tal posición. Todas las posiciones de impresión que no sean T, L ni ¡OUCH!
(en caso de empate), deben estar en blanco.
Después de la impresión de cada línea, pruebe si alguno de los animales ha llegado o pasado el cuadro 70.
De ser así, imprima el ganador y termine la simulación. Si la tortuga gana, imprima ¡LA TORTUGA
GANA! ¡BRAVO! Si la liebre gana, imprima La liebre gana. Ni hablar. Si ambos animales llegan a la
meta con el mismo pulso de reloj, tal vez usted quiera favorecer a la tortuga (el desvalido) o imprimir Es un
empate. Si ninguno de los animales gana, vuelva a efectuar el ciclo, simulando el siguiente pulso del reloj.
APUNTADORES Y CADENA – LECCIÓN 20
20-74
MIGUEL Á. TOLEDO MARTÍNEZ
SECCIÓN ESPECIAL: Construya su propia computadora
En los siguientes problemas nos desviaremos temporalmente del mundo de la programación con lenguajes
de alto nivel. Abriremos una computadora y veremos su estructura interna. Presentaremos la programación en
lenguaje de máquina y escribiremos varios programas en dicho lenguaje. Para hacer que esta sea una experiencia
especialmente valiosa, después construiremos una computadora (por medio de la técnica de simulación basada en
software) en la que podrá ejecutar sus programas en lenguaje de máquina.
7.
(Programación en lenguaje de máquina) Crearemos una computadora a la que llamaremos Simpletron. Como
su nombre implica, es una máquina sencilla, pero como pronto veremos, también es poderosa. La Simpletron
ejecuta programas escritos en el único lenguaje que entiende directamente; es decir, el lenguaje de máquina
Simpletron, o SML.
La Simpletron contiene un acumulador -es decir, un registro especial en el que se coloca la información antes
de que la Simpletron la tome para efectuar cálculos o examinarla de varias maneras. Toda la informa de la
Simpletron se maneja en términos de palabras. Una palabra es un número decimal de cuatro dígitos con
signo, como +3364, -1293, +0007, -0001, etc. La Simpletron viene equipada con una memoria de 100
palabras, las cuales se referencian por su número de localidad: 00, 01, ..., 99.
Antes de ejecutar un programa SML, debemos cargar el programa en memoria. La primera instrucción de cada
programa SML siempre queda en la localidad 00. El simulador comenzará la ejecución a partir de localidad.
Cada instrucción en SML, ocupa una palabra de la memoria de la Simpletron (por lo tanto, las instrucciones
son números decimales de cuatro dígitos con un signo) Supondremos que el signo de una instrucción SML
siempre es positivo, pero el signo de una palabra de datos puede ser positivo o negativo. Cada localidad de la
memoria de la Simpletron puede contener una instrucción, un valor de datos utilizado por el programa o un
área no utilizada (y, por lo tanto, indefinida) de la memoria. Los primeros dos dígitos de cada instrucción SML
son el código de operación, que especifica que se llevará a cabo. Los códigos de operación de SML aparecen en
la figura 20.12.
Código de operación
Significado
Operaciones de entrada/salida:
const int READ = 10;
Lee a palabra del teclado y la pone en una localidad
específica de la memoria
const int WRITE = 11;
Escribe en la pantalla la palabra ubicada en una localidad
específica de memoria.
Operaciones de carga/almacenamiento:
const int LOAD = 20;
Carga en el acumulador la palabra ubicada en una
localidad específica de memoria.
Const int STORE = 21;
Almacena la palabra que se encuentra en el
acumulador en una localidad específica de memoria.
Operaciones aritméticas:
const int ADD = 30;
Suma la palabra ubicada en una localidad específica
de memoria a la palabra que se encuentra en el
acumulador (deja el resultado en el acumulador).
const int SUBTRACT = 31;
Resta la palabra ubicada en una localidad específica de
memoria de la palabra que se encuentra en el
acumulador (deja el resultado en el acumulador).
const int DIVIDE = 32;
Divide la palabra ubicada en una localidad específica
de memoria entre la palabra que se encuentra en el
acumulador (deja el resultado en el acumulador).
const int MULTIPLY = 33;
Multiplica la palabra ubicada en una localidad
específica de memoria por la palabra que se encuentra
APUNTADORES Y CADENA – LECCIÓN 20
20-75
MIGUEL Á. TOLEDO MARTÍNEZ
Código de operación
Significado
en el acumulador (deja el resultado en el acumulador).
operaciones de transferencia de control:
const int BRANCH = 40;
Bifurca a una localidad específica de memoria.
const int BRANCHNEG = 41;
Bifurca a una localidad específica de memoria si el
acumulador es negativo.
const int BRANCHZERO = 41;
Bifurca a una localidad específica de memoria si el
acumulador es igual a cero.
const int HALT = 43;
Termina, el programa ha completado su trabajo.
Figura 20.12. Códigos de Operación del lenguaje de máquina Simpletron
Los últimos dos dígitos de las instrucciones SML son el operando (la dirección de la localidad específica
de memoria que contiene la palabra a la que se aplica la operación)
Ahora consideremos varios programas SML sencillos. El primero (ejemplo 1) lee del teclado dos números
y calcula e imprime su suma. La instrucción +1007 lee el primer número y lo pone en la localidad 07 (la
cual ha sido inicializada a cero) La instrucción +1008 lee el siguiente número, colocándolo en la localidad
08. La instrucción load, +2007, pone (copia) en el acumulador el primer número y la instrucción add,
+3008, suma el segundo número al que se encuentra en el acumulador. Todas las instrucciones aritméticas
de SML dejan sus resultados en el acumulador. La instrucción store, +2109, pone (copia) el resultado en
la localidad de memoria 09, de donde la toma la instrucción write, +1109, y lo imprime (como número
decimal de cuatro dígitos con signo) La instrucción halt, +4300, termina la ejecución.
Ejemplo 1
Localidad
00
01
02
03
04
05
06
07
08
09
Número
+1007
+1008
+2007
+3008
+2109
+1109
+4300
+0000
+0000
+0000
Instrucción
(Lee A)
(Lee B)
(Carga A)
(Suma B)
(Almacena C)
(Escribe C)
(Terminación)
(Variable A)
(Variable B)
(Resultado C)
El programa SML del ejemplo 2 lee del teclado dos números y determina e imprime el valor más alto. Note
que la instrucción +4107 se utiliza como transferencia de control condicional, a semejanza de la instrucción
if de C++.
Ejemplo 2
Localidad
00
01
02
03
04
05
06
07
08
09
10
APUNTADORES Y CADENA – LECCIÓN 20
Número
+1009
+1010
+2009
+3110
+4107
+1109
+4300
+1110
+4300
+0000
+0000
Instrucción
(Lee A)
(Lee B)
(Carga A)
(Resta B)
(Bifurca con negativo a 07)
(Escribe A)
(Terminación)
(Escribe B)
(Terminación)
(Variable A)
(Variable B)
20-76
MIGUEL Á. TOLEDO MARTÍNEZ
Ahora escriba programas en SML que lleven a cabo las siguientes actividades.
a) Mediante un ciclo controlado por centinela lea 10 números positivos y calcule e imprima su suma.
b) Mediante un ciclo controlado por contador, lea siete números, algunos positivos y otros negativos, y
calcule e imprima su promedio.
c) Lea una serie de números y determine e imprima el mayor. El primer número indicará la cantidad de
números a procesar.
8.
(Simulador de computadora) Al principio parecerá un tanto pretencioso, pero en este problema va a construir
su propia computadora. No, no se encargará de soldar sus componentes. En cambio, se valdrá de la poderosa
técnica de la simulación basada en software para crear un modelo en software de la Simpletron. No sé
decepcionará. Su simulador convertirá en una Simpletron a la computadora en la que esté trabajando y usted de
hecho podrá ejecutar, probar y depurar los programas SML que escribió en el problema 18.
Cuando ejecute su simulador Simpletron, deberá comenzar por imprimir:
*** ¡Bienvenidos a Simpletron! ***
*** Por favor introduzca su programa una instrucción ***
*** (o dato) a la vez. Presentaré ***
*** la localidad y un signo de interrogación (?) . ***
*** Después usted tecleará la palabra para esa localidad. ***
*** Para detener la introducción de su programa, ***
*** teclee el centinela -99999. ***
Simule la memoria de la Simpletron con el arreglo memoria de un solo índice, que tiene 100 elementos.
Supongamos ahora que está ejecutándose el simulador y examinemos el diálogo a medida que ingresamos el
programa del ejemplo 2 del ejercicio 18:
00 ? +1009
01 ? +1010
02 ? +2009
03 ? +3110
04 ? +4107
05 ? +1109
06 ? +4300
07 ? +1110
08 ? +4300
09 ? +0000
10 ? +0000
11 ? -99999
12
*** Se ha completado la carga del programa ***
*** Inicia la ejecución del programa
***
El programa SML ha sido colocado (o cargado) en el arreglo memoria. Ahora la Simpletron ejecuta su
programa SML. La ejecución inicia con la instrucción de la localidad 00 y, como C++, continúa
secuencialmente, a menos que se le dirija a otra parte del programa mediante una transferencia de control.
Mediante la variable acumulador represente el registro del acumulador. Emplee la variable contador para
llevar el registro de la localidad de memoria que contiene la instrucción en ejecución. En la variable
codigoOperacion indique la operación actual, es decir, los dos dígitos de la izquierda de la palabra de
instrucción. Mediante la variable operando indique la localidad de memoria sobre la que opera la
instrucción actual. Así, operando está formado por los dos dígitos más a la derecha de la instrucción que se
está ejecutando en el momento. No ejecute las instrucciones directamente en la memoria. En cambio,
transfiera la siguiente instrucción a ejecutar de la memoria a una variable llamada registroInstruccion.
Luego tome los dos dígitos de la izquierda, poniéndolos en codigoOperacion, y tome los dos de la derecha
y colóquelos en operando. Cuando comienza la operación de la Simpletron, los registros especiales se
inicializan a 0.
APUNTADORES Y CADENA – LECCIÓN 20
20-77
MIGUEL Á. TOLEDO MARTÍNEZ
Ahora hagamos el recorrido por la ejecución de la primera instrucción SML, +1009. que está en la
localidad de memoria 00. A esto se le llama ciclo de ejecución de instrucción.
contador nos dice la localidad de la siguiente instrucción a ejecutar. Obtenemos de la memoria el contenido
de dicha localidad mediante la instrucción de C++.
registroInstruccion = memoria[contador];
El código de operación y el operando se extraen del registro de instrucciones por medio de las
instrucciones:
codigoOperacion = registroInstruccion / 100;
operando = registroInstruccion %100;
Ahora la Simpletron deberá determinar que el código de operación es en realidad una read, o lectura, (en
lugar de write, load, etc.) Una switch se encarga de diferenciar las doce operaciones de SML.
En la estructura switch se simula el comportamiento de varias instrucciones SML como sigue (dejaremos
las demás para que las resuelva el lector):
read:
load:
add:
branch:
halt:
cin >> memoria[ operando];
acumulador = memoria[operando];
acumulador += memoria[operando];
Pronto estudiaremos las instrucciones de bifurcación
Esta instrucción imprime el mensaje
*** Ha terminado la ejecución de Simpletron ***
y luego imprime el nombre y contenido de los registros, así como el contenido completo de la memoria. Tal
impresión con frecuencia es conocida como vaciado de computadora. Para ayudarle a programar su
función de vaciado, la figura 20.13 muestra un ejemplo de formato de vaciado. Observe que el vaciado tras
la ejecución del programa Simpletron mostraría los valores reales de las instrucciones y de los datos al
momento de terminación de la ejecución.
Prosigamos con la ejecución de la primera instrucción de nuestro programa: +1009 en la localidad 00.
Como hemos indicado, la instrucción switch simula esto ejecutando la instrucción C++:
cin >> memoria[ operando ];
Antes de la ejecución de cin, se deberá presentar un signo de interrogación (?) en la pantalla, para pedirle al
usuario una entrada. La Simpletron espera a que el usuario introduzca un valor y oprima la tecla Retorno.
El valor queda en la localidad 09.
En este punto se ha completado la simulación de la primera instrucción. Lo que queda es preparar a
Simpletron para que ejecute la siguiente instrucción. Dado que la instrucción que se acaba de ejecutar no
fue una transferencia de control, simplemente necesitamos incrementar el registro del contador de
instrucciones como sigue:
++contador;
Con esto se completa la ejecución simulada de la primera instrucción. El proceso completo (es decir, el
ciclo de ejecución de instrucción) inicia nuevamente con la obtención de la siguiente instrucción a ejecutar.
Ahora consideremos la manera como se simulan las instrucciones de bifurcación (las transferencias de
control) Todo lo que necesitamos hacer es ajustar el valor de contador de instrucciones. Por lo tanto, se
simula la instrucción de bifurcación incondicional (40) dentro de switch con:
contador = operando;
La instrucción condicional bifurca si el acumulador es igual a cero se simula con:
APUNTADORES Y CADENA – LECCIÓN 20
20-78
MIGUEL Á. TOLEDO MARTÍNEZ
if ( acumulador == 0)
contador = operando;
En este punto, usted deberá implementar el simulador Simpletron y ejecutar los programo SML que
escribió en el problema 18. Puede embellecer el SML con características adicionales e incluirlas en su
simulador.
El simulador deberá buscar varios tipos de errores. Durante la fase de carga del programa, por ejemplo,
cada número tecleado por el usuario para memoria deberá estar en el rango de -9999 a +9999. El simulador
deberá comprobar mediante un ciclo while que cada número digitado esté en este rango y, si no, solicitar al
usuario que lo vuelva a teclear hasta que esté correcto.
REGISTROS
acumulador
contador
registroInstrucción
codigoOperacion
operando
+0000
00
+0000
00
00
MEMORIA
0
10
20
30
40
50
60
70
80
90
0
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
1
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
2
3
4
5
6
+0000 +0000 +0000 +0000 +0000
+0000 +0000 +0000 +0000 +0000
+0000 +0000 +0000 +0000 +0000
+0000 +0000 +0000 +0000 +0000
+0000 +0000 +0000 +0000 +0000
+0000 +0000 +0000 +0000 +0000
+0000 +0000 +0000 +0000 +0000
+0000 +0000 +0000 +0000 +0000
+0000 +0000 +0000 +0000 +0000
+0000 +0000 +0000 +0000 +0000
Figura 20.13. Ejemplo de vaciado
7
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
8
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
9
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
Durante la fase de ejecución, el simulador deberá buscar varios errores serios, como división entre cero,
códigos de ejecución inválidos, sobrecargas del acumulador (es decir, operaciones aritméticas que den
valores mayores que +9999 o menores que -9999) y otros. Tales errores serios se llaman errores fatales. Al
detectar un error fatal, el simulador deberá imprimir un mensaje de error como:
*** Intento de dividir entre cero ***
*** Terminación anormal de Simpletron ***
así como un vaciado completo con el formato que indicamos previamente. Con él se ayudará al usuario a
localizar el error en el programa.
MÁS PROBLEMAS DE APUNTADORES
9.
Modifique el programa de barajado y repartición de naipes del programa BARAJAS.CPP, para que las
operaciones de barajado y repartición se lleven a cabo mediante la misma función (barajarRepartir()) La
función deberá contener una estructura de ciclo anidada semejante a la función barajar() del programa
BARAJAS.CPP.
APUNTADORES Y CADENA – LECCIÓN 20
20-79
MIGUEL Á. TOLEDO MARTÍNEZ
10. ¿Qué hace este programa?
// Problema21.cpp
#include <iostream.h>
//Para cout y cin
void misterio1(char *, const char *);
void main(void)
{
char cadena1[80], cadena2[80];
cout << “Introduzca dos cadenas de caracteres: ”;
cin >> cadena1 >> cadena2;
misterio1(cadena1, cadena2);
cout << cadena1 << endl;
}//Fin de main()
void misterio1(char *s1, const char *s2)
{
while(*s1 != ‘\0’)
++s1;
for(; *s1 = *s2; s1++, s2++)
;
//Instrucción vacía
}//Fin de misterio1()
11. ¿Qué hace este programa?
//Problema11.cpp
#include <iostream.h>
//Para cout y cin
int misterio2(const char *);
void main(void)
{
char cadena[80];
cout << “Introduzca una cadena de caracteres: ”;
cin >> cadena;
cout << misterios2(cadena) << endl;
}//Fin de main()
int misterio2(const char *s)
{
for(int x = 0; *s != ‘\0’; s++)
++x;
return x;
en los siguientes segmentos de programa. Si es posible corregir el error, explique cómo.
12. Encuentre
}//Fineldeerror
misterio2()
13.
a)
b)
c)
d)
e)
f)
g)
int * numero;
cout << numero << endl;
float *realPtr;
long *enteroPtr;
enteroPtr = realPtr;
int *x, y;
x = y;
APUNTADORES Y CADENA – LECCIÓN 20
20-80
MIGUEL Á. TOLEDO MARTÍNEZ
h) char s[] = “éste es un arreglo de caracteres”;
i) for(; *s != ‘\0’; s++)
cout << *s << ‘ ’ ;
j) short *numPtr, resultado;
k) void *genericPtr = numPtr;
a. result = *genericPtr + 7;
l) float x = 19.34;
m) float xPtr = &x;
n) cout << xPtr << endl;
o) char *s;
p) cout << s << endl;
14. (Quicksort) En los ejemplos y ejercicios de la lección 19 estudiamos las técnicas de ordenamiento de burbuja,
ordenamiento en cubetas y ordenamiento por selección. Ahora presentamos la técnica recursiva de
ordenamiento llamada Quicksort. El algoritmo básico para un arreglo de un solo índice es el siguiente:
a) Paso de particionamiento: tome el primer elemento del arreglo desordenado y determine su localidad
final en el arreglo ordenado, es decir, que todos los valores a la izquierda del elemento sean menores que
él y todos los valores a la derecha sean mayores. Ahora tenemos un elemento en su lugar correcto y dos
subarreglos desordenados.
b) Paso recursivo: Efectúe el paso 1 sobre cada subarreglo desordenado.
Cada vez que se ejecuta el paso 1 sobre un subarreglo, se coloca otro elemento en su destino final en el
arreglo ordenado y se crean dos subarreglos desordenados. Cuando un subarreglo consiste de un elemento,
debe estar ordenado, por lo que dicho elemento está en su ubicación final.
El algoritmo básico parece bastante sencillo, pero ¿cómo determinamos la posición final del primer
elemento de cada subarreglo? Como ejemplo, considere el siguiente conjunto de valores (el elemento en
negritas es el elemento de particionamiento; se colocará en su posición en el arreglo ordenado):
37
c)
2
6
4
89
8
10
12
68
45
Comenzando por el elemento más a la derecha del arreglo, compare cada elemento contra 37 hasta que
encuentre uno menor que 37, luego intercambie 37 y dicho elemento. El primer elemento menor que 37 es
12, por lo que 37 y 12 se intercambian. El nuevo arreglo queda así:
12
2
6
4
89
8
10
37
68
45
El elemento 12 está en cursivas, indicando que se acaba de intercambiar con 37.
d) Comenzando por la izquierda del arreglo, pero iniciando con el elemento que sigue a 12, compare cada
elemento contra 37 hasta que se encuentre uno mayor que 37, luego intercambie 37 y dicho elemento. El
primer elemento mayor que 37 es 89, por lo que 37 y 89 se intercambian. El nuevo arreglo queda como:
12
e)
6
4
37
8
10
89
68
45
Comenzando por la derecha, pero iniciando con el elemento anterior a 89, compare los demás elementos
con 37 hasta encontrar uno menor que 37, luego intercambie 37 y dicho elemento. El primer elemento
menor que 37 es 10, por lo que 37 y 10 se intercambian. El nuevo arreglo queda como:
12
f)
2
2
6
4
10
8
37
89
68
45
Comenzando por la izquierda, pero iniciando con el elemento que sigue a 10, compare los elementos
contra 37 hasta encontrar uno mayor que 37, luego intercambie 37 y dicho elemento. No hay más
elementos mayores que 37, así que, cuando comparamos 37 contra él mismo, sabemos que está en su
destino final en el arreglo ordenado.
Una vez aplicada la partición al arreglo anterior, quedan dos arreglos desordenados, El subarreglo con valores
menores que 37 contiene 12, 2, 6, 4, 10 y 8. El subarreglo con valores mayores que 37 contiene 89, 68 y 45. El
ordenamiento continúa con el particionamiento de ambos arreglos de la misma manera que el arreglo original.
APUNTADORES Y CADENA – LECCIÓN 20
20-81
MIGUEL Á. TOLEDO MARTÍNEZ
Basándose en el análisis anterior, escriba la función recursiva quicksort() que ordena un arreglo de enteros con un
solo índice. La función deberá recibir como argumentos un arreglo de enteros, un índice inicial y un índice final.
quicksort() deberá llamar a la función partición() para que lleve a cabo el paso de particionamiento.
14. (Recorrido por un laberinto) El siguiente tramado de # y puntos ( . ) es un arreglo de doble índice que
representa un laberinto.
#
#
.
#
#
#
#
#
#
#
#
#
#
.
.
#
.
#
.
#
.
#
.
#
#
.
#
#
.
#
.
.
.
#
.
#
#
.
.
.
.
#
#
#
.
#
.
#
#
#
#
#
.
.
.
.
.
#
.
#
#
.
.
.
#
#
#
#
.
#
.
#
#
.
#
.
#
.
.
.
.
.
.
#
#
.
#
.
#
#
#
#
.
#
#
#
#
.
#
.
.
.
.
.
.
#
.
#
#
.
#
#
#
#
#
#
#
#
.
#
#
.
.
.
.
.
.
.
.
.
.
#
#
#
#
#
.
#
#
#
#
#
#
#
En este arreglo de doble índice, los # representan las paredes del laberinto y los puntos los cuadros de las
rutas posibles a través del laberinto. Sólo se pueden hacer movimientos a una localidad del arreglo que
contenga un punto.
Hay un algoritmo sencillo de recorrido del laberinto que garantiza encontrar la salida (suponiendo que la
hay) Si no la hay, usted llegará nuevamente al punto de inicio. Ponga su mano derecha sobre la pared que
está a su derecha y avance. Nunca quite su mano de la pared. Si el laberinto da la vuelta a la derecha, siga
por la pared de la derecha. Siempre y cuando no quite su mano de la pared, tarde o temprano llegará a la
salida del laberinto. Podría haber una ruta más corta que la que ha tomado, pero está garantizado que saldrá
del laberinto si sigue este algoritmo.
Escriba la función recursiva travesia() que lo guiará por el laberinto. La función deberá recibir como
argumentos un arreglo de caracteres de 12 por 12 que represente el laberinto, así como el punto de inicio de
dicho laberinto. A medida que travesia() intente localizar la salida del laberinto, deberá poner una x en cada
cuadro de la ruta. La función deberá desplegar el laberinto después de cada movida, de modo que el usuario
pueda ver cómo se resuelve el problema.
15. (Generación aleatoria de laberintos) Escriba la función generadorLaberinto(), que toma como argumento un
arreglo de caracteres de 12 por 12 y genera aleatoriamente un laberinto. Dicha función deberá proporcionar el
punto de inicio y el de terminación del laberinto. Pruebe la función travesía() del problema 25 sobre varios
laberintos generados al azar.
16. (Laberintos de cualquier tamaño) Generalice las funciones travesia() y generadorLaberinto() de los
problemas 14 y 15 para que procesen laberintos de cualquier anchura y altura.
17. (Arreglos de apuntadores a funciones) Rescriba el programa FUNARRE3.CPP, (de la lección 19, página 50)
para que opere mediante una interfaz operada por menú. El programa deberá ofrecer las siguientes 5 opciones al
usuario, como sigue (deben aparecer en la pantalla):
Introduzca una opción:
0. Imprimir un arreglo de calificaciones
1. Encontrar la mínima calificación
2. Encontrar la máxima calificación
3. Imprimir el promedio de cada estudiante
4. Finalizar el programa
Una restricción al uso de arreglos de apuntadores a funciones es que todos los apuntadores deben ser del
mismo tipo. Los apuntadores deben ser a funciones del mismo tipo de devolución y que reciban argumentos
APUNTADORES Y CADENA – LECCIÓN 20
20-82
MIGUEL Á. TOLEDO MARTÍNEZ
del mismo tipo. Por esta razón, las funciones del programa FUNARRE3.CPP se deben modificar para que
todas devuelvan el mismo tipo y tomen los mismos parámetros. Modifique las funciones minimo() y
máximo() de modo que impriman el valor mínimo o máximo y no devuelvan nada. En la opción 3,
modifique la función promedio() del programa FUNARRE3.CPP para que envíe a la salida el promedio de
cada estudiante (no de uno específico) La función promedio() no deberá devolver nada y deberá tomar los
mismos parámetros que mostrarArreglo(), minimo() y máximo(). Almacene los apuntadores a las cuatro
funciones en el arreglo procesarOpcion() y tome la selección del usuario como el índice del arreglo para
llamar a cada función.
18. (Modificaciones al simulador Simpletron) En el problema 7 se escribió un simulador en software de una
computadora que ejecuta programas escritos en el SML (lenguaje de máquina Simpletron) En este problema
proponemos vanas modificaciones y mejoras al simulador Simpletron. Algunas de las siguientes
modificaciones y mejoras podrían ser necesarias para ejecutar los programas generados por el compilador.
a) Aumente la memoria del simulador Simpletron de modo que contenga 1000 localidades de memoria,
permitiendo el manejo de programas más extensos.
b) Haga que el simulador pueda efectuar cálculos de módulo. Esto requiere una nueva instrucción en el
lenguaje de máquina Simpletron.
c) Permita que el simulador realice cálculos de exponenciación. Esto requiere una nueva instrucción en el
lenguaje de máquina Simpletron.
d) Modifique el simulador para que emplee valores hexadecimales, en lugar de valores enteros, para
representar instrucciones en lenguaje de máquina Simpletron.
e) Modifique el simulador de modo que permita enviar a la salida un salto de línea. Esto requiere una nueva
instrucción en el lenguaje de máquina Simpletron.
f) Modifique el simulador para que procese valores de punto flotante, además de valores enteros.
g) Modifique el simulador a fin de que maneje entrada de cadenas. Sugerencia: es posible dividir cada
palabra Simpletron en dos grupos, conteniendo cada una un entero de dos dígitos. Cada entero de dos
dígitos representa el equivalente decimal ASCII de un carácter. Agregue una instrucción en lenguaje de
máquina que acepte la entrada de una cadena y la almacene a partir de cierta localidad de memoria de la
Simpletron. La primera mitad de la palabra en dicha localidad será la cuenta del número de caracteres de
la cadena (es decir, la longitud de la cadena) Cada media palabra subsiguiente contiene un carácter
ASCII, expresado como dos dígitos decimales. La instrucción en lenguaje de máquina convierte cada
carácter en su equivalente ASCII y lo asigna a media palabra.
h) Modifique el simulador para que maneje salida de cadenas almacenadas en el formato de la parte (g)
Sugerencia: agregue una instrucción de lenguaje de máquina que imprima una cadena a partir de cierta
localidad de memoria de la Simpletron. La primera mitad de la palabra de dicha localidad es la cuenta del
número de caracteres de la cadena (es decir, la longitud de la cadena) Cada media palabra subsiguiente
contiene un carácter ASCII expresado como dos dígitos decimales. La instrucción de lenguaje de máquina
comprueba la longitud e imprime la cadena, traduciendo cada número de dos dígitos a su carácter
equivalente.
19. ¿Qué hace este programa?
//Programa19.cpp
#include <iostream.h>
//Para cout y cin
int misterio3(const char *, const char *);
void main(void)
{
char cadena1[80], cadena2[80];
cout << “Introduzca dos cadenas de caracteres: “;
cin >> cadena1 >> cadena2;
cout
<<“El resultado es “
<< misterio3(cadena1, cadena2) << endl;
}//Fin de main()
int misterio3(const char *s1, const char *s2)
APUNTADORES Y CADENA – LECCIÓN 20
20-83
MIGUEL Á. TOLEDO MARTÍNEZ
{
for(; *s1 != ‘\0’ && *s2 != ‘\0’; s1++, s2++)
if(*s1 != *s2)
return 0;
return 1;
}//Fin de misterio3()
PROBLEMAS DE MANIPULACIÓN DE CADENAS
20. Escriba un programa que, mediante la función strcmp, compare dos cadenas introducidas por el usuario. El
programa deberá indicar si la primera cadena es menor, igual o mayor que la segunda.
21. Escriba un programa que compare, mediante la función strncmp, dos cadenas introducidas por el usuario. El
programa deberá aceptar como entrada el número de caracteres a comparar, además de indicar si la primera
cadena es menor, igual o mayor que la segunda.
22. Escriba un programa que se valga de la generación de números aleatorios para crear oraciones. El programa
utilizará cuatro arreglos de apuntadores a char, llamados articulo, sustantivo, verbo y preposicion. Deberá
crear una oración seleccionando una palabra al azar de cada arreglo en el siguiente orden articulo, sustantivo,
verbo, preposicion, articulo y sustantivo. A medida que se selecciona cada palabra, se le concatenará con las
palabras previas en un arreglo lo bastante grande para guardar la oración completa. Las palabras se separarán
por espacios. La impresión de la oración generada comenzará con una letra mayúscula y terminará con un
punto. El programa generará 20 oraciones.
Los arreglos se llenarán como sigue: el arreglo articulo deberá contener los artículos “e”, “un”, “y”, “algún”
y, “cualquier”; el arreglo sustantivo deberá contener los sustantivos “niño”, “señor”, “perro”, “pueblo” y
“auto”; el arreglo verbo deberá contener los verbos “manejo”, “saltó”, “corrió”, “caminó” y “evitó”; el
arreglo preposicion deberá contener las preposiciones “a”, “de”, “sobre”, “bajo” y “en”.
Una vez que el programa haya sido escrito y esté operando, modifíquelo para que genere una historia corta que
consista de varias oraciones. (¿Qué tal la posibilidad de un escritor de cuentos para la clase de literatura?)
23. (Quintillas) Una quintilla es una oración en verso, a veces jocoso, de cinco líneas, en el que el primero y
segundo versos riman con el quinto y el tercero con el cuarto. Mediante técnicas parecidas a las desarrolladas en
el problema 22, escriba un programa en C++ que produzca quintillas al azar. Refinar este programa para que
genere buenas quintillas es un problema complejo, pero el resultado vale el esfuerzo.
24. Escriba un programa que codifique frases en pig Latin. (En inglés, pig Latin es un tipo de lenguaje que se
utiliza con propósitos de juego.) Hay muchas variaciones en los métodos con los que se forman frases en pig
Latin. Por simplicidad, utilice el siguiente algoritmo:
Para formar una frase en pin Latin, divida en palabras dicha frase mediante la función strtok. Para convertir
cada palabra a pig Latin, ponga la primera letra de la palabra al final de ella y agregue las letras “ay”. Así, la
palabra “salta” se convierte en “altasay”, “las” se vuelve “aslay” y “computadora” queda corno
“omputadoracay”. Los espacios entre las palabras quedan igual. Suponga lo siguiente: la frase consiste en
palabras separadas por espacios, no hay signos de puntuación y todas las palabras tienen dos o más letras. La
función mostrarLatinPalabra() deberá desplegar cada palabra. Sugerencia: cada vez que se encuentre un token
durante una llamada a strtok, pase el apuntador del token a la función mostrarLatinPalabra() e imprima la
palabra en pig Latin.
25. Escriba un programa que acepte como entrada una cadena con un número telefónico, en la forma (555)5555555. El programa deberá utilizar la función strtok para obtener como token el código de área, los primeros tres
dígitos del número telefónico y los últimos cuatro dígitos. Los siete dígitos del número telefónico deben
concatenarse para formar una sola cadena. El programa deberá convertir la cadena con el código de área a int y
la cadena con el número telefónico a long. Se deben imprimir el código de área y el número telefónico.
26. Escriba un programa que acepte como entrada una línea de texto, la divida en tokens por medio de la función
strtok y envíe los tokens a la salida en orden inverso.
27. Utilice las funciones de comparación de cadenas estudiadas y las técnicas de ordenamiento de arreglos
desarrollados para escribir un programa que alfabetice una lista de cadenas. Ordene los nombres de 10 o 15
pueblos de su zona.
APUNTADORES Y CADENA – LECCIÓN 20
20-84
MIGUEL Á. TOLEDO MARTÍNEZ
28. Escriba dos versiones de la función de copia de cadenas y de la función de concatenación de cadenas de la
figura 20.9. La primera versión deberá utilizar indización de arreglos, y la segunda apuntadores y aritmética de
apuntadores.
29. Escriba dos versiones de cada función de comparación de cadenas de la figura 20.9. La primera versión debe
emplear indización de arreglos, y la segunda apuntadores y aritmética de apuntadores.
30. Escriba dos versiones de la función strlen de la figura 20.9. La primera versión debe valerse de indización de
arreglos, y la segunda de apuntadores y aritmética de apuntadores.
SECCIÓN ESPECIAL: Manipulación avanzado de cadenas
Los problemas anteriores están ligados a las lecciones y se diseñaron para poner a prueba los conocimientos del
lector sobre los conceptos fundamentales de manipulación de cadenas. Esta sección incluye un conjunto de
problemas de manipulación de cadenas intermedios y avanzados. El lector deberá encontrar interesantes y amenos
estos problemas. La dificultad de los problemas varía considerablemente. Algunos requieren de una hora o dos para
su escritura e implementación. Otros son útiles corno tareas de laboratorio que podrían tardar dos o tres semanas de
estudio e implementación. Varios son proyectos vastos que se extenderán durante todo el curso.
31. (Análisis de texto) La disponibilidad de computadoras con capacidad de manipulación de cadenas ha dado
como resultado algunos enfoques bastante interesantes para el análisis de las obras de los grandes autores. Se ha
puesto mucha atención en si William Shakespeare en realidad existió. Algunos estudiosos creen que hay
bastante evidencia que indica que las obras maestras atribuidas a Shakespeare en realidad fueron escritas por
Christopher Marlowe u otros autores. Los investigadores se han valido de las computadoras para encontrar las
similitudes en los escritos de ambos autores. Este ejercicio examina tres métodos de análisis de texto mediante
computadoras.
a) Escriba un programa que lea del teclado varias líneas de texto e imprima una tabla que indique el número
de veces que sucede cada letra del abecedario en el texto. Por ejemplo, la frase:
Ser o no ser: ésa es la cuestión
contiene dos “a”, ninguna “b”, una “c”, etcétera.
b) Escriba un programa que lea varias líneas de texto e imprima una tabla que indique el número de palabras
de una letra, de dos letras, de tres letras, etc., que hay en el texto. Por ejemplo, la frase
Si es más noble para la mente sufrir
Contiene
c)
Longitud de la palabra
Veces
1
2
3
4
5
6
7
0
3
1
1
2
1
0
Escriba un programa que lea varias líneas de texto e imprima una tabla que indique el número veces que
sucede cada palabra del texto. La primera versión del programa deberá incluir las palabras de la tabla en el
mismo orden en que aparecen en el texto. Por ejemplo, las líneas:
Ser o no ser: ésa es la cuestión
Si es más noble para la mente sufrir
contienen la palabra “ser“ dos veces, la palabra “más” una vez, “no” una vez, etc. Después deberá
intentar una impresión más interesante (y útil) en la que las palabras aparezcan ordenadas alfabéticamente.
32. (Procesamiento de texto) Una importante función de los sistemas de procesamiento de texto es la justificación
tipográfica, es decir, la alineación de las palabras tanto con el margen izquierdo como con el derecho. Esto
APUNTADORES Y CADENA – LECCIÓN 20
20-85
MIGUEL Á. TOLEDO MARTÍNEZ
genera un documento de aspecto profesional que da la apariencia de haber sido formado tipográficamente, en
lugar de escrito en una máquina de escribir. La justificación tipográfica puede lograrse en los sistemas de
cómputo insertando caracteres en blanco entre las palabras, de modo que la palabra más a la derecha quede
alineada con el margen derecho.
Escriba un programa que lea varias líneas de texto y lo imprima en formato justificado. Suponga que texto se
debe imprimir en papel de 8 ½ pulgadas de ancho y que se dejan márgenes de una pulgada tanto al lado
izquierdo como al derecho. Suponga que la computadora imprime 10 caracteres por pulgada horizontal. Por lo
tanto, dicho programa deberá imprimir 6 ½ pulgadas de texto o 65 caracteres por línea.
33. (Impresión de fechas con varios formatos) En la correspondencia comercial, las fechas por lo general se
imprimen en varios formatos. Dos de los más comunes son:
21/07/55 y julio 21, 1955
Escriba un programa que lea una fecha con el primer formato y la imprima con el segundo.
34. (Protección de cheques) Con frecuencia las computadoras se utilizan para ejecutar sistemas de impresión de
cheques, como sucede con las aplicaciones de nómina y cuentas por pagar. Circulan por allí muchas historias
extrañas sobre cheques de nómina emitidos (accidentalmente) que amparan cifras de más de un millón. Los
sistemas de cheques imprimen cifras extrañas debido a errores humanos y/o a fallas de la máquina. Los
diseñadores de sistemas integran controles en sus sistemas para evitar la emisión de tales cheques.
Otro problema serio es que alguien altere intencionalmente la cifra de un cheque para cobrarlo
fraudulentamente. Para evitar la alteración de una cifra, la mayoría de los sistemas de impresión de cheques
utiliza una técnica llamada protección de cheques.
Los cheques diseñados para impresión en una computadora contienen una cantidad fija de espacios en los que se
puede imprimir una cifra. Suponga que un cheque contiene ocho espacios en blanco, donde la computadora
debe imprimir el importe de un cheque de nómina. Si la cifra es grande, entonces se llenarán los ocho espacios.
Por ejemplo:
1,230.60
----------12345678
(importe del cheque)
(posiciones)
Por otra parte, si la cifra es menor que $100.00, entonces quedarían en blanco varios de los espacios. Por
ejemplo,
99.87
-----------12345678
contiene tres espacios en blanco. Si un cheque se imprime con espacios en blanco, es fácil de alterar. Para evitar
esto, muchos sistemas de impresión de cheques protegen la cifra insertando asteriscos iniciales como sigue:
***99.87
----------12345678
Escriba un programa que acepte una cifra y luego la imprima en formato de protección de cheques, con
asteriscos iniciales, de ser necesario. Suponga que se dispone de nueve espacios para imprimir la cifra.
35. (Escritura del importe de un cheque con palabras) Prosiguiendo el estudio del ejemplo previo, reiteramos la
importancia de diseñar sistemas de impresión de cheques que eviten la alteración de sus importes. Un método
de seguridad común requiere que la cifra sea escrita tanto con números como con palabras. Inclusive si alguien
es capaz de alterar la cifra numérica, es muy difícil modificar la cifra en palabras.
APUNTADORES Y CADENA – LECCIÓN 20
20-86
MIGUEL Á. TOLEDO MARTÍNEZ
Muchos sistemas computarizados de impresión de cheques no imprimen la cifra del cheque con palabras. Tal
vez la razón principal de esta omisión es que la mayoría de los lenguajes de alto nivel empleados para las
aplicaciones comerciales no cuentan con características adecuadas de manipulación de cadenas. Otra razón es
que la lógica de la escritura de las cifras con palabras es un tanto complicada.
Escriba un programa en C++ que acepte una cifra numérica y la imprima con palabras. Por ejemplo, la cifra
112.43 deberá escribirse como:
CIENTO DOCE con 43/100
36. (Código Morse) Tal vez el esquema de codificación más famoso de todos es el código Morse, desarrollador por
Samuel Morse en 1832 para el sistema telegráfico. El código Morse asigna una serie de puntos y rayas a cada
letra del abecedario, a cada dígito y a algunos caracteres especiales (punto, coma, dos puntos y punto y coma)
En los sistemas audibles, el punto representa un sonido corto y la raya un sonido largo., Otras representaciones
de puntos y rayas son las empleadas en los sistemas de orientación por luz y los de señalización con
indicadores.
La separación entre palabras se indica mediante un espacio o, sencillamente, por la ausencia de un punto o raya.
En los sistemas sonoros, se indica un espacio mediante un periodo corto en el que no se transmite ningún
sonido. En la figura 20.14 aparece la versión internacional del código Morse.
Escriba un programa que lea una frase en español y la codifique en código Morse. También escriba un
programa que lea una frase en código Morse y la convierta en su equivalente en inglés. Ponga un espacio entre
cada letra codificada en Morse y tres espacios entre cada palabra.
Carácter
Código
Carácter
Código
A
.T
B
-...
U
..C
-.-.
V
...D
-..
W
.-E
.
X
-..F
..-.
Y
-.-G
--.
Z
--..
H
....
I
..
Dígitos
J
.--1
.---K
-.2
..--L
.-..
3
...-M
-4
....N
-.
5
.....
O
--6
-....
P
.--.
7
--...
Q
--.8
---..
R
.-.
9
----.
S
...
0
----Figura 20.14. Las letras del abecedario como se expresan con el código Morse internacional
37. (Programa de conversión entre Sistema métrico y sistema inglés) Escriba un programa que apoye al usuario
en las conversiones entre el sistema métrico y el inglés. El programa deberá permitirle al usuario especificar
como cadenas los nombres de las unidades (es decir, centímetros, litros, gramos, etc., en el sistema métrico y
pulgadas, cuartillos, libras, etc., en el sistema inglés) y deberá responder a preguntas sencillas como:
“¿Cuántas pulgadas hay en 2 metros?”
“¿Cuántos litros hay en 10 cuartillos?”
El programa deberá reconocer las conversiones inválidas. Por ejemplo, la pregunta:
“¿Cuántos pies hay en 5 kilogramos”
APUNTADORES Y CADENA – LECCIÓN 20
20-87
MIGUEL Á. TOLEDO MARTÍNEZ
no tiene sentido, pues los pies, son unidades de longitud, mientras que los kilogramos, son unidades de peso.
INTERESANTE PROYECTO DE MANIPULACIÓN DE CADENAS
38. (Generador de crucigramas) Mucha gente ha resuelto crucigramas, pero pocos han intentado generar uno. La
generación de crucigramas es difícil. Aquí se sugiere como un proyecto de manipulación de cadenas de alta
complejidad y esfuerzo. Hay muchos asuntos que el programador debe resolver para lograr que funcione incluso
el programa generador de crucigramas más sencillo. Por ejemplo, ¿cómo se representa el tramado de un
crucigrama en la computadora? ¿Deberá utilizarse una serie de cadenas o arreglos de doble índice? El
programador necesita una fuente de palabras (es decir, un diccionario computarizado) al que pueda hacer
referencia directa el programa. ¿De qué forma deben almacenarse estas palabras para simplificar las
complejas manipulaciones requeridas por el programa? El lector realmente ambicioso deseará generar la
parte de claves del crucigrama, donde se imprimen las pistas para determinar las palabras horizontales y
verticales. La simple impresión de una versión en blanco del crucigrama es un problema complejo.
APUNTADORES Y CADENA – LECCIÓN 20
20-88
Descargar