Está en la página 1de 36

4.6.

- Paso de parámetros

Cuando se invoca a una función, en ocasiones es necesario enviarle algunos


elementos indispensables para realizar su tarea. Estos elementos, enviados en la
invocación, se llaman parámetros actuales.

Dependiendo de la naturaleza de los parámetros, clasificaremos el paso de ellos en:

4.6.1.- Paso Por Valor.


4.6.2.- Paso Por Referencia.
4.6.3.- Argumentos Predeterminados.

4.6.1.- Paso por valor

Se realiza un paso de parámetros por valor cuando se envía una copia de los
valores de los parámetros actuales.

En este caso, la función invocada no podrá modificar los valores de las variables
utilizadas como parámetros actuales, sino que trabajará con las copias que se le
envían. Estas copias son recibidas en los parámetros formales, los cuales se
comportan como variables locales pertenecientes a la función invocada.

Al igual que las variables locales, una vez que termina la ejecución de la función
invocada, las variables pertenecientes a los parámetros formales se destruyen; y
por lo tanto, se pierden los valores que envió el módulo invocador.

El listado 4.3 muestra el paso de parámetros por valor.

#include <iostream.h>
#define NL cout << "\n" void precio(double); void main(void) { double
costo; cout << "COSTO : $ "; cin>> costo;
precio(costo); // SE INVOCA A precio() Y SE LE ENVIA UNA
// COPIA DEL VALOR DE costo
NL;
cout << costo; // EL VALOR DE costo SE CONSERVA DESPUES // DE LA
INVOCACION A precio() } void precio(double recibido) {
recibido="recibido" * 1.5; // SE MODIFICA EL VALOR DE LA // COPIA
GURDADA EN recibido NL; cout << recibido; }

Listado 4.3.- Paso de parámetros por valor.


4.6.2.- Paso por referencia

A diferencia del paso por valor, el paso por referencia permite que la función
invocada modifique el valor original de las variables usadas como argumentos.

En C++, el paso de parámetros por referencia se realiza creando un alias del


identificador de la variable que forma el parámetro actual, como se muestra en el
listado 4.4 .

#include <iostream.h>
#include <conio.h>

void oferta(float &);

void main(void)
{
float precio;
clrscr();
gotoxy(20,12);
cout << "¿ CUAL ES EL PRECIO ?. N$ " ; cin>> precio;
oferta(precio);
gotoxy(20,14);
cout << "¿ DIJO USTED N$ " << precio << " ?"; gotoxy(20,16); cout
<< " ESO ES CON EL 20 % DE DESCUENTO"; gotoxy(1,25); } void
oferta(float & bajo) { bajo *="0.8" ; gotoxy(20,24); cout << "PRECIO
REBAJADO: N$ " << bajo; }

Listado 4.4.- Paso de parámetros por referencia.

En el listado 4.4, se puede decir que bajo es otro identificador asociado a la


variable precio, por lo que la función oferta() realmente modifica el valor
contenido en dicha variable.

Según lo anterior, podemos decir que es preferible utilizar una variable existente
en lugar crear una nueva, y eliminar el paso de parámetros por valor.

El paso de parámetros por valor puede sustituirse por un paso de parámetros por
referencia donde se utilice el modificador const, como se muestra en el listado 4.5.

#include <iostream.h>
#include <conio.h>

void seudoferta(const float &);

void main(void)
{
float precio;
clrscr();
gotoxy(20,12);
cout << "¿ CUAL ES EL PRECIO ?. N$ " ; cin>> precio;
seudoferta(precio);
gotoxy(20,14);
cout << "¿ DIJO USTED N$ " << precio << " ?"; gotoxy(20,16); cout
<< " ESO DIJE.... NO HAY DESCUENTO"; gotoxy(1,25); } void
seudoferta(const float & bajo) { float rebajado; rebajado="bajo" * 0.8
; gotoxy(20,24); cout << "PRECIO REBAJADO: N$ " << rebajado; }

Listado 4.5.- Paso por valor utilizando referencias.

4.6.3.- Argumentos predeterminados

Cuando se declara el prototipo de una función, es posible asignar valores iniciales


a los argumentos. A éstos se les llama argumentos predeterminados, y tienen como
objetivo simplificar el manejo de los valores de los parámetros actuales utilizados.

Supóngase el problema de calcular del precio de un artículo. En la solución


utilizaremos una función que recibe dos parámetros: el precio del artículo y el
descuento correspondiente.

El valor del precio cambia para cada artículo, pero el valor del descuento puede
considerarse como un pocentaje fijo del precio para la mayoría de los artículos
(por ejemplo en época de ofertas).

En el listado 4.6 se presenta un programa solución para este caso.

//////////////////////////////////////////////////////
// ARGPRED.CPP : EJEMPLO DEL MANEJO DE ARGUMENTOS //
// CON VALORES PREDETERMINADOS //
//////////////////////////////////////////////////////
#include <iostream.h>
#include <conio.h>

// DECLARACION DEL PROTOTIPO DE LA FUNCION desc().


// AL ARGUMENTO DE TIPO ENTERO porcentaje SE LE ASIGNA
// UN VALOR INICIAL DE 20 Y AL iva UN VALOR INICIAL DE 10.
double desc(double , int porcentaje = 20, int iva = 10);

// DEFINICION DE LA FUNCION main()


void main(void)
{
double precio, rebajado_1, rebajado_2, rebajado_3 ;
clrscr();
gotoxy(20,10);
cout << "PRECIO: N$ "; cin>> precio;
rebajado_1 = desc(precio); // SE ACEPTA EL porcentaje Y EL
// iva PREDETERMINADOS.
gotoxy(40,10);
cout << "PRECIO REBAJADO 1="N$" " << rebajado_1; gotoxy(20,12);
cout << "PRECIO: N$ "; cin>> precio;
rebajado_2 = desc(precio,40); // EL porcentaje CAMBIA A 40,
// SE CONSERVA EL VALOR DEL iva.
gotoxy(40,12);
cout << "PRECIO REBAJADO 2="N$" " << rebajado_2; gotoxy(20,14);
cout << "PRECIO: N$ "; cin>> precio;
rebajado_3 = desc(precio,20,0); // SE CAMBIA EL VALOR
// DE iva. AUNQUE SE CONSERVA EL VALOR
// DE porcentaje, DEBE ESCRIBIRSE
// DE NUEVO.
gotoxy(40,14);
cout << "PRECIO REBAJADO 3="N$" " << rebajado_3; } // DEFINICION
DE LA FUNCION desc() double desc(double p, int d, int i) { return(p *
(1.00 (d-i)/100.00)); }

4.7.- Funciones recursivas

Se dice que una función es recursiva cuando puede invocarse a sí misma. En cada
invocación se crean las variables locales, por lo que es indispensable incluir una
condición de salida, ya que de no hacerlo se agotará la memoria disponible en la
pila.

Como un primer acercamiento al manejo de funciones recursivas en C++, se


presenta el listado 4.7.

#include <iostream.h>
#include <conio.h>
#define NL cout << "\n" #define PULSE cout << "PULSE CUALQUIER TECLA
PARA CONTINUAR." void recursiva(void); void main(void) { clrscr();
recursiva(); NL; PULSE; getch(); NL; } void recursiva(void) { int x;
cout << "TECLEE UN NUMERO ENTERO... ( 0="SALIR)" "; cin>> x ;
if(x)
recursiva();
}

Listado 4.7.- Diseño de una función recursiva.

En el listado 4.7 podemos observar que, para que la función recursiva se invoque a
si misma, es necesario que el valor almacenado en x sea diferente de 0.

El cálculo del factorial de un número es un problema clásico en la aplicación de las


funciones recursivas. En el listado 4.8 se presenta un programa en C++ que calcula
el factorial de un número dado.

#include <iostream.h>
#include <conio.h>

long factorial(unsigned short int);

void main()
{
unsigned short int num;
long result;
clrscr();
do {
gotoxy(20,11);
cout << "El FACTORIAL del número: " ; clreol(); cin>> num
;
} while((num <0) || (num> 19 ));
result = factorial(num);
gotoxy(20,13);
cout << " es : " << result; } long factorial(unsigned short int n)
{ if(n="=0)" return 1; else return n*(factorial(n-1)) ; }

Listado 4.8.- Cálculo del factorial utilizando funciones recursivas.

En el listado 4.8, si el número dado por el usuario es 4, el proceso realizado por el


programa se podría representar de la manera mostrada en la tabla 4.2.

Numero de
Valor de n Resultado
invocación
1 4 4*(factorial(3))
2 3 3*(factorial(2))
3 2 2*(factorial(1))
4 1 1*(factorial(0))
5 0 1

Tabla 4.2.- Resultados de invocar a la función factorial() pasándole como


parámetro el número 4 .

En cada invocación, se crea en la pila una variable cuyo identificador es n y su


valor cambiará como se muestra en la tabla 4.2. Como en las invocaciones 1,2,3 y 4
el valor de n es diferente de 0, la función vuelve a invocarse a sí misma, quedando
sin resolver el resultado.
Cuando el valor de n es igual a 0 (invocación 5), la función retorna el valor 1 la la
invocación 4, por lo que el resultado en ésta se calcula así :

1 * (factorial(0)) = 1 * 1 = 1

La invocación 4 retorna a la invocación 3 el valor 1 y el resultado en la invocación


4 es :

2 * (factorial(1)) = 2 * 1 = 2

A su vez, la invocación 3 retorna a la invocación 2 el valor 2. El resultado en la


invocación 2 es :

3 * (factorial(2)) = 3 * 2 = 6

Posteriormente, la invocación 2 retorna el valor 6 a la invocación 1. El resultado en


la invocación 1 es :

4 * (factorial(3)) = 4 * 6 = 24
Finalmente, la invocación 1 retorna el valor 24 a la función invocadora main(), la
cual asigna a la variable result el valor recibido ( 24 ).

5.- Arreglos.

Los arreglos consisten de un conjunto de elementos del mismo tipo almacenados en


localidades contíguas de memoria. Los elementos de un arreglo comparten el
mismo nombre, pudiéndo distinguirse un elemento de otro a través de un
subíndice.

En esta unidad, describiremos la declaración de los arreglos, el manejo de los


elementos de un arreglo dado, así como lo relacionado con los arreglos que se
utilizan para formar las cadenas de caracteres.

5.1.- Declaración de arreglos

Los elementos de los arreglos son variables de algún tipo dado, que al compartir el
mismo nombre pueden ser tratadas como una sola entidad.

La sintaxis para la declaración de un arreglo es la siguiente:

tipo identificador [ <expresión_constante> ] ;

donde:

tipo es el tipo de los elementos que componen el arreglo

identificador es el nombre del arreglo

expresión_constante es una expresión que al reducirse debe


dar como resultado un valor
entero positivo.

En el ejemplo 5.1 se muestra la declaración de arreglos.

DECLARACION RESULTADO

char nombre[31]; Declara un arreglo unidimensional llamado


nombre compuesto de 31 elementos de tipo
carácter.
int valor[20]; Declara un arreglo unidimensional llamado
valor, compuesto por 20 elementos de tipo
entero con signo.
unsigned long abc[x] Declara un arreglo unidimensional llamado
abc, compuesto por x elementos de tipo
entero largo sin signo. En este caso x
debe ser una constante entera.
double matriz[5][7]; Declara un arreglo bidimensional llamado
matriz, compuesto por 35 elementos de
tipo entero.
int trid[3][5][8]; Declara un arreglo tridimensional llamado
trid, compuesto por 120 elementos de tipo
entero.

Ejemplo 5.1.- Declaraciones de arreglos.

Como se observa en el ejemplo 5.1, la declaración de un arreglo multidimensional


se distingue en que se agrega una pareja de corchetes para cada dimensión, por lo
que la sintaxis, en este caso, toma la siguiente forma:

tipo identificador [ cte1 ][ cte2 ][ cte3 ] ... ;

donde: cte1, cte2, etc. representan los subíndices para cada


dimensión.

El número y tamaño de las dimensiones solo estará restringido por la


disponibilidad de memoria RAM, por lo que se puede tener una declaración como
la siguiente:

double multidim [5][5][5][5][5][5] ;

El espacio de memoria ocupado por el arreglo multidim es el mismo que el


ocupado por:

double unidim [15625

5.2.- Manejo de arreglos

En la sección anterior se ha tratado lo relacionado con la declaración de arreglos


con elementos de diferentes tipos y con una o varias dimensiones.
En esta sección se tratará lo relativo al acceso a los elementos individuales de un
arreglo, ya sea para asignarles valores específicos o para utilizar los valores
almacenados.

5.2.1.- Asignación de valores a los elementos de un


arreglo

Al declarar un arreglo dentro de una función, los valores almacenados en cada uno
de los elementos son desconocidos (se dice que el arreglo "tiene basura"), lo cual
causa que el programa correspondiente arroje resultados inesperados. Para evitar
los valores desconocidos, se recomienda asignar valores iniciales a cada uno de los
elementos de los arreglos, como se muestra a continuación:

Por ejemplo, supongamos la siguiente declaración:


int vector[5];

En este caso, se declara un arreglo de 5 variables de tipo entero agrupadas con el


nombre vector, las cuales pueden representarse con la figura 5.1.

Figura 5.1.- Representación de un arreglo

Como puede observarse en la figura 5.1, el primer subíndice tiene valor cero y el
último tiene valor cuatro.

Lo anterior se debe a que, en C++, el primer subíndice siempre vale cero y el


último tiene un valor menor en uno que el valor de la dimensión del arreglo.

Una vez declarado el arreglo, se pueden asignar valores a cada uno de sus
elementos, como se muestra enseguida:

vector[0] = 100 ;
vector[1] = 101 ;
vector[2] = 102 ;
vector[3] = 103 ;
vector[4] = 104 ;

y el arreglo vector luciría como en la figura 5.2.

Figura 5.2.- El arreglo vector después de asignarle valores.

En este caso particular, pudimos haber asignado los valores por medio de un
estatuto for de la siguiente forma:
for( int x = 0 ; x < 5 ; x++) vector[x] = x + 100 ;

Esta forma es la más conveniente en caso de que la cantidad de elementos sea


grande y que los valores a asignar sean iguales o las diferencia entre elementos
consecutivos sea constante.

Todo lo escrito en este ejemplo es válido para arreglos con elementos de cualquier
tipo.

Por ejemplo, si queremos desplegar en pantalla los caracteres del código ASCII lo
haríamos por medio del siguiente grupo de instrucciones:

unsigned char caracter[256];


for( int x=0 ; x < 256 ; x++ )
printf("%c", caracter[x]) ;

Cuando se manejan arreglos de varias dimensiones, debe recordarse que el


subíndice de cada una de ellas inicia con un valor 0, como se observa en el listado
5.1.

#include <iostream.h>
#include <conio.h>

void main(void)
{
int matriz[3][4];
clrscr();
for(int x=0 ; x < 3 x++)
{
for(int y=0 y< 4 ; y++)
{
matriz[x][y]=x+y+1 ;
gotoxy(y+1,x+1);
cout matriz[x][y];
}
}
}

Listado 5.1.- Arreglo con dos dimensiones.

El resultado de ejecutar el programa del listado 5.1 es el siguiente:

1234
2345
3456

El arreglo matriz puede representarse con la figura 5.3 .


Figura 5.3.- Representación del arreglo matriz .

Obsérvese que el primer subíndice varía de 0 a 2 y el segundo varía de 0 a 3.

En los párrafos anteriores, se ha mostrado la forma de asignar valores a cada uno


de los elementos de un arreglo una vez que se ha escrito su declaración. Aquí
veremos la manera de asignarle valores iniciales a los elementos, en la misma
instrucción que contiene la declaración del arreglo.

Por ejemplo, para declarar un arreglo de 10 enteros y al mismo tiempo asignarle a


cada uno de sus elementos los valores 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, puede escribirse :

int digitos[] = { 0,1,2,3,4,5,6,7,8,9 };

En este caso, aunque no se escribe el tamaño de la dimensión del arreglo digitos,


tiene el mismo efecto que escribir:

int digitos[10];

digitos[0] = 0 ;
digitos[1] = 1 ;
digitos[2] = 2 ;
digitos[3] = 3 ;
digitos[4] = 4 ;
digitos[5] = 5 ;
digitos[6] = 6 ;
digitos[7] = 7 ;
digitos[8] = 8 ;
digitos[9] = 9 ;

Como puede verse, la primera forma es mucho más compacta, aunque , como en
muchas de las instrucciones de C++, la brevedad del código sacrifica la claridad.

5.3.- Cadenas de caracteres

En C++ no existe un tipo predefinido para el manejo de cadenas de caracteres


como en otros lenguajes, sino que tienen que declararse como arreglos de
caracteres.

Lo que distingue a una cadena de caracteres, con respecto a un arreglo de


caracteres cualquiera, es que la cadena de caracteres tiene como último carácter al
carácter nulo \0.

Por ejemplo, si se declara el arreglo:

char cadena[8];

podemos asignar los siguientes valores a cada uno de sus elementos:

cadena[0] = 'A' ;
cadena[1] = 'R' ;
cadena[2] = 'R' ;
cadena[3] = 'E' ;
cadena[4] = 'G' ;
cadena[5] = 'L' ;
cadena[6] = 'O' ;
cadena[7] = '\0';

Al contener el carácter nulo, el arreglo cadena será reconocido por las funciones y
objetos diseñados para manipular cadenas de caracteres. Para manejar un arreglo
de cadenas de caracteres se debe declarar como un arreglo bidimensional de
elementos de tipo char, como puede observarse en el listado 5.2.

#include <iostream.h>
#include <conio.h>

void main()
{
unsigned short int calif[10];
char nombre[10][21]; // Se declara un arreglo bidimensional
// para 10 nombres de 20 caracteres por
// nombre mas un caracter para el nulo.
clrscr();
for( int x=0 ; x < 10 ; x++)
{
gotoxy(10,x+1);
cout << "NOMBRE [" << x << "] = " ;
cin >> nombre[x];
gotoxy(45,x+1);
cout << "CALIFICACION [" << x << "] = " ;
cin >> calif[x];
}
}

Listado 5.2.- Manejo de un arreglo de cadenas de caracteres.

En el listado 5.2, se inicia con el subíndice 0 para no desperdiciar el primer


elemento del arreglo. Además, se debe recordar que el subíndice del último
elemento del arreglo es igual al tamaño de la dimensión menos 1.

5.4.- Asignación de Valores Iniciales a Cadenas

Cuando se tiene un arreglo de cadenas de caracteres, se puede utilizar la forma


compacta mostrada en la sección anterior, sólo que , en lugar de constantes
numéricas manejaríamos constantes de cadena.

Por Ejemplo:

char nombres[][5] = { "HUGO", "PACO", "LUIS" } ;

es equivalente a:
char nombres[3][5];

nombres[0] = "HUGO" ;
nombres[1] = "PACO" ;
nombres[2] = "LUIS" ;

Esto también puede manejarse a nivel de caracteres, así :

char nombres[][5] = { 'H','U','G','O','\0',


'P','A','C','O','\0',
'L','U','I','S','\0' };

o así:

char nombres[3][5];

nombres[0][0] = 'H' ;
nombres[0][1] = 'U' ;
nombres[0][2] = 'G' ;
nombres[0][3] = 'O' ;
nombres[0][4] = '\0' ;
nombres[1][0] = 'P' ;
nombres[1][1] = 'A' ;
nombres[1][2] = 'C' ;
nombres[1][3] = 'O' ;
nombres[1][4] = '\0' ;

nombres[2][0] = 'L' ;
nombres[2][1] = 'U' ;
nombres[2][2] = 'I' ;
nombres[2][3] = 'S' ;
nombres[2][4] = '\0' ;

En los listados 5.3 y 5.4 se muestran las dos primeras formas, observándose que se
obtiene el mismo resultado.

#include <iostream.h>
#include <conio.h>

void main()
{
char nombres[][5] = { "HUGO","PACO","LUIS" };
int calif[] = { 100, 90, 100 };
clrscr();
for( int x=0 ; x < 3;x++)
{
gotoxy(35,x+10);
cout nombres[x];
gotoxy(40,x+10);
cout << calif[x];
}
}

Listado 5.3.- Manejo de arreglos de cadenas.

#include <iostream.h>
#include <conio.h>

void main()
{
char nombres[][5] = { 'H','U','G','O','\0',
'P','A','C','O','\0',
'L','U','I','S','\0' };
int calif[] = { 100, 90, 100 };
clrscr();
for( int x=0 ; x< 3;x++)
{
gotoxy(35,x+10);
cout nombres[x];
gotoxy(40,x+10);
cout << calif[x];
}
}

Listado 5.4.- Manejo de arreglos bidimensionales de caracteres.

En los listados 5.3 y 5.4, se muestra que no es necesario escribir el valor de la


primera dimensión de los arreglos cuando, en su declaración, se asignan valores
constantes a los elementos.

La ventaja que tiene la forma mostrada en 5.3 y 5.4 es que, al no especificar una de
las dimensiones, la cantidad de cadenas a manejar puede variarse con sólo
agregarlas a la lista o eliminarlas de ella.
5.5.- Funciones para el manejo de cadenas

Como se estableció al principio de esta unidad, el lenguaje C++ no cuenta con


algún tipo de dato específico para el manejo de cadenas de caracteres, pero sí
cuenta con un grupo de funciones que se han acumulado durante la evolución del
Lenguaje C.

Para leer una cadena de caracteres desde el teclado existe la función gets(), y para
desplegar una cadena en pantalla se usa la función puts(). Los prototipos de ambas
funciones se encuentran declarados en el archivo STDIO.H.

Por ejemplo, el listado 5.5 muestra un programa que sirve para leer y desplegar
cadenas de caracteres utilizando las funciones gets() y puts().

#include <stdio.h> // Para gets() y puts()


#include <conio.h> // Para clrscr() y gotoxy()
#include <string.h> // Para strupr() y strlen()

void main()
{
char nombre[31]; // Declara un arreglo de 31 caracteres
char saludo1[] = "?? HOLA,"; //Constante de caracteres
char saludo2[] = " !!";
clrscr();
gotoxy(20,10);
puts("¿ Cuál es tu nombre ? "); //Despliega cadena de car.
gotoxy(45,10);
gets(nombre); // Lee cadena de caracteres
strupr(nombre); // Convierte a mayúsculas
gotoxy(20,12);
puts(saludo1);
gotoxy(30,12);
puts(nombre);
gotoxy(30+strlen(nombre),12); // Longitud de la cadena
puts(saludo2);
}

Listado 5.5.- Lectura y desplegado de cadenas de caracteres.

Además de las funciones gets() y puts(), existe otro grupo de funciones para el
manejo de cadenas de caracteres, como strlen() y strupr() utilizadas en el
programa del listado 5.5. Los prototipos de estas funciones se encuentran
declarados en el archivo STRING.H En la tabla 5.1 se describen brevemente
algunas de las funciones para el manejo de cadenas de caracteres en el C++ de
Borland, cuyos prototipos se encuentran declarados en el archivo STRING.H .

FUNCION DESCRIPCION
Copia una cadena de caracteres en otra.Se detiene cuando encuentra el
stpcpy
terminador nulo.
strcat Añade una cadena de caracteres a otra.
strchr Busca, en una cadena, un caracter dado.
strcmp Compara dos cadenas.
Macro que compara dos cadenas sin distinguir entre mayúsculas y
strcmpi
minúsculas.
strcpy Copia una cadena.
Busca segmentos que no contienen un subconjunto de un conjunto
strcspn
especificado de caracteres.
strdup Copia una cadena a una nueva localidad.
_strerror Genera un mensaje de error definido por el programador.
strerror Retorna el apuntador al mensaje asociado con el valor del error.
stricmp Compara dos cadenas sin diferenciar entre mayúsculas y minúsculas
strlen Determina la longitud de una cadena.
strlwr Convierte las mayúsculas de una cadena en minúsculas.
strncat Añade el contenido de una cadena al final de otra.
strncmp Compara parte de una cadena con parte de otra.
Compara parte de una cadena con parte de otra, sin distinguir entre
strncmpi
mayúsculas y minúsculas.
strncpy Copia un un número de bytes dados, desde una cadena hacia otra.
Compara parte de una cadena con parte de otra, sin distinguir entre
strnicmp
mayúsculas y minúsculas.
strnset Hace que un grupo de elementos de una cadena tengan un valor dado.
Busca la primera aparición, en una cadena, de cualquier caracter de un
strpbrk
conjunto dado.
strrchr Busca la última aparición de un caracter en una cadena.
strrev Invierte el orden de los caracteres de una cadena.
strset Hace que los elementos de una cadena tengan un valor dado.
Busca en una cadena el primer segmento que es un subconjunto de un
strspn
conjunto de caracteres dado.
strstr Busca en una cadena la aparición de una subcadena dada.
_strtime Convierte la hora actual a una cadena.
strtod Convierte una cadena a un valor double ó long double.
strtol Convierte una cadena a un valor long.
strtoul Convierte una cadena a un valor unsigned long.
strupr Convierte las minúsculas de una cadena a mayúsculas.

Tabla 5.1.- Funciones para el manejo de cadenas de caracteres.

5.6.- Control de acceso a los elementos de un arreglo


En C++, el acceso a los elementos de un arreglo tiene que ser controlado por el
programador, ya que el lenguaje no restringe la posibilidad de accesar a posiciones
de memoria que están más abajo de la última posición reservada para el arreglo.
Lo mismo sucede cuando se manejan cadenas, donde el programador tiene la
responsabilidad de controlar el acceso a los caracteres de la cadena, tomando como
límite el terminador nulo. En el listado 5.6 se presenta un ejemplo de acceso a los 5
bytes colocados abajo del terminador nulo de una cadena dada por el usuario.

#include <stdio.h> // Para gets() y puts()


#include <conio.h> // Para clrscr() y gotoxy()
#include <string.h> // Para strlen()

void main()
{
char nombre[31];
clrscr();
gotoxy(10,1);
puts("¿ Cuál es tu nombre ? ");
gotoxy(35,1);
gets(nombre);
clrscr();
gotoxy (10,1);
puts("ELEMENTO CARACTER DIRECCION");
for( int x=0 ; (x <strlen(nombre)+5) && (x<23); x++)
{
gotoxy(10,x+2);
printf("nombre[%2d] %c= %4d %9u",
x,nombre[x],nombre[x],&nombre[x]);
}
}

Listado 5.6.- Colocación de los elementos de una cadena en memoria RAM.

6.- Variables automáticas, variables dinámicas y


apuntadores

En esta unidad trabajaremos con los conceptos necesarios para el manejo de


situaciones en que se requiere utilizar direcciones de memoria, ya sea que se trate
de las direcciones correspondientes a las variables manejadas en la pila (variables
automáticas) o a las variables manejadas en el montículo (variables dinámicas).
Para el manejo de esas direcciones de memoria se requiere el uso de apuntadores.

También revisaremos la estrecha relación existente entre arreglos y apuntadores; y


en la parte final retomaremos el tema del paso de parámetros para analizar la
forma en que se pasa un parámetro cuando éste consiste de un arreglo o cuando
consiste de una función.

6.1.- Conceptos básicos


Para efectos de nuestro estudio, llamaremos variables automáticas a las creadas en
la pila (stack) y variables dinámicas a las creadas en el montículo (heap).

Un apuntador es una variable cuyo contenido es un valor entero sin signo( de 16 o


32 bits ), el cual representa una dirección de memoria. La dirección de memoria
puede corresponder a una variable creada en la pila o en el montículo.

Los apuntadores son una de las herramientas más poderosas con que cuenta el
Lenguaje C++ . Desafortunadamente, muchos programadores han creado el mito
de que el estudio de los apuntadores es muy complicado, lo cual ha desarrollado
una fobia entre quienes se inician en el estudio del lenguaje.

En las unidades anteriores se han utilizado variables automáticas. Para el manejo


de las variables dinámicas es indispensable el uso de apuntadores; mientras que
para las variables automáticas, los apuntadores son una alternativa.

En las figuras 6.1, 6.2 y 6.3 se muestra el manejo de la pila y el montículo en los seis
modelos de memoria disponibles en el C++ de Borland.

Figura 6.1.- Modelo TINY

Figura 6.2.- Modelos SMALL y MEDIUM


Figura 6.3.- Modelos COMPACT, LARGE y HUGE

Para cada programa, dependiendo del espacio requerido para el código y para los
datos, se puede utilizar un modelo de memoria específico. El modelo
predeterminado en el C++ de Borland es el modelo SMALL.

A continuación se presenta una breve descripción de los modelos de memoria


manejados por el C++ de Borland :

Con el modelo de memoria TINY se utiliza un solo segmento (64 KB) para el
código, los datos y la pila. La utilización del modelo TINY es aconsejable
únicamente cuando se quiere crear un archivo .COM

En el modelo SMALL, el código se almacena en un segmento y los datos en otro.


Este modelo de memoria se recomienda para programas .EXE de tamaño medio.

Cuando se usa el modelo MEDIUM, los datos están limitados a un segmento pero
el código puede utilizar varios (hasta 16 MB). El uso de este modelo se recomienda
cuando se tiene un programa grande con pocos datos.

El modelo COMPACT es lo contrario de MEDIUM, ya que limita el código a un


segmento mientras que los datos pueden ocupar varios (hasta 16 MB).
Este modelo es recomendable cuando se tienen programas hasta de tamaño medio
con muchas variables ó variables muy grandes.

En los modelos LARGE y HUGE, el código y los datos pueden ocupar varios
segmentos (hasta 16 MB). La diferencia entre estos modelos es que el HUGE puede
manejar variables automáticas que, en total, excedan los 64 KB.

La tabla 6.1 muestra los modificadores predeterminados para los apuntadores a


funciones y apuntadores a datos en los diferentes modelos de memoria.

Modelo Apuntadores Apuntadores


de a a
Memoria Funciones Datos
TINY near near
SMALL near near
MEDIUM far near
COMPACT near far
LARGE far far
HUGE far far

Tabla 6.1.- Modificadores predeterminados para apuntadores.


NOTA: Cuando se compila un módulo, el código resultante no debe ser mayor que
64 KB, ya que debe caber en un segmento de código. Esto debe cumplirse aún
cuando se esté utilizando un modelo de memoria que utilice varios segmentos para
el código (MEDIUM, LARGE, HUGE).

De manera similar, en el caso del modelo HUGE no debe haber datos automáticos
de más de 64 KB en cada módulo.

6.2.- Declaración de apuntadores

Los apuntadores son variables automáticas cuyos valores representan direcciones


de memoria correspondientes a otras variables.

La sintáxis para la declaración de un apuntador es la siguiente:

tipo * identificador ;

Ejemplo: int *apint ; // Declaración del apuntador apint


// Se dice que : "apint va a apuntar a
// variables de tipo int"
donde:
apint es el nombre del apuntador
* es el operador de indirección

Obsérvese que el operador de indirección utiliza el mismo símbolo que el operador


de multiplicación.

En el ejemplo anterior, puede decirse que:

*apint se refiere al objeto apuntado por apint .

apint es un apuntador a objetos de tipo int

Recuerde que: para referirse al objeto apuntado, se antepone el operador de


indirección al nombre del apuntador.

Al declarar al apuntador, se le reserva espacio en la pila. Esto no implica que se


esté reservando espacio para el objeto apuntado.

En la figura 6.4 se representa la asignación de espacio en la pila para el apuntador


apint.
Figura 6.4.- Apuntador en la pila

6.3.- Variables automáticas y apuntadores

Las variables automáticas se crean en tiempo de compilación y se destruyen al


terminar la ejecución del módulo donde fueron declaradas.
Aunque no es estrictamente necesario, se pueden manejar las variables
automáticas por medio de apuntadores, como se muestra en el listado 6.1.

#include <iostream.h>
#include <conio.h>

void main()
{
int automatica ; // Se declara la variable automatica.
int *apunt ; // Se declara el apuntador apunt, que apun-
// tará a objetos de tipo int.

automatica = 100 ; // Se asigna el valor 100 a la variable


// automatica.

apunt = &automatica ; // Se asigna a apunt la dirección de


// automatica ó apunt apunta a
// automatica.
clrscr();

cout << "VALOR=" << automatica << " \n"; // 100 *apunt="200" ; //
Se asigna el valor 200 al objeto apunta- // do por apunt. cout <<
"VALOR=" << automatica << " \n"; // 200 getch(); }

Listado 6.1.- Ejemplo de variables automáticas y apuntadores.

Las instrucciones del listado 6.1 se traducen en la siguiente secuencia, donde los
apuntadores se representan con una flecha (para simular que "apuntan hacia" ó
"señalan" un objeto) y los objetos apuntados se representan por un cuadro (para
simular un recipiente).

INSTRUCCION REPRESENTACION GRAFICA

int automatica ; automatica


|----------------|
| ? |
|----------------|
int *apunt ; ----> ?

automatica = 100 ; automatica


|----------------|
| 100 |
|----------------|

apunt = &automatica ; automatica, *apunt


|----------------|
apunt ---->| 100 |
|----------------|

*apunt = 200 ; automatica, *apunt


|----------------|
apunt ---->| 200 |
|----------------|

El estado final de la zona de memoria correspondiente al objeto apuntado y al


apuntador se representa en la figura 6.5.

Figura 6.5.- Visualización del objeto apuntado y del apuntador en la memoria


RAM.

Nótese que tanto el apuntador como el objeto apuntado se almacenan en la pila.

Las direcciones de memoria FFF0 , FFF2 , FFF4 , son hipotéticas.

Un apuntador es una variable que solo puede contener un valor a la vez, por lo que
solo puede apuntar a un objeto al mismo tiempo.

Por otro lado, una variable cualquiera puede ser apuntada por varios apuntadores,
ya que su dirección de memoria puede ser almacenada en distintas variables a la
vez.

Al declarar un apuntador, se está especificando el tipo de variable al que va a


apuntar.

Por ejemplo, no podrá declararse un apuntador a objetos de tipo int y después


intentar utilizarlo para apuntar a objetos de tipo float.
Cuando se desee manejar un apuntador a cualquier tipo de objeto, se puede
declarar de tipo void, como en:

void *multiusos ;

En el listado 6.2 se muestra un ejemplo de aplicación de un apuntador de tipo void.

#include <iostream.h>
#include <conio.h>
#define NL cout << "\n" void main() { int varent="0" ; float
varflot="0.0" ; void *apmulti="&varent;" // apmulti APUNTA A varent
*(int *)apmulti="2" ; // ASIGNA 2 AL OBJETO NL; // APUNTADO POR
apmulti cout << varent ; apmulti="&varflot" ; // apmulti APUNTA A
varflot *(float *)apmulti="1.1" ; // ASIGNA 1.1 AL OBJETO APUNTADO //
POR apvoid NL; cout << varflot ; NL; getch(); }

Listado 6.2.- Utilización de apuntadores de tipo void.

Del listado 6.2, analicemos la instrucción:

*(int *)apmulti = 2 ;

en donde:

apmulti es un apuntador de tipo void.

(int *)apmulti está forzando a que apmulti apunte a objetos


de tipo int.

*(int *)apmulti se refiere a un objeto de tipo entero apuntado


por apmulti.

En los ejemplos manejados hasta aquí se ha supuesto el modelo de memoria


SMALL, por lo que los apuntadores son near por predeterminación.

El hecho de que sean near significa que solo disponen de dos bytes para almacenar
una dirección de memoria. Al disponer de dos bytes, el número entero sin signo
más grande que pueden almacenar es 65535, el cual corresponde a la dirección
más alta de un segmento de memoria.

Cuando se está utilizando el modelo de memoria SMALL y se quiere accesar una


dirección de memoria de otro segmento, se debe utilizar el modificador far al
declarar el apuntador correspondiente. Un apuntador far dispone de cuatro bytes;
en los dos primeros bytes almacena la dirección del segmento y en los otros dos la
dirección del desplazamiento. De esta forma se puede accesar una dirección de
memoria que se encuentre en un segmento diferente al segmento de datos en uso.

En el listado 6.3 se muestra el manejo de un apuntador far para rellenar la


pantalla con el caracter tecleado. La ejecución del programa termina cuando se
pulsa la tecla .

#include <iostream.h>
#include <conio.h>
void main()
{
int far *aplej ; // Declara un apuntador far a enteros
char c ;
int ren, col ;

clrscr();
cout << "Teclee caracteres ( < Enter >="Salida" ) : " ;
aplej="(int" far *) 0xB8000000 ; while(( c="getche())" !="\r" ) for(
ren="0" ; ren < 25 ; ren ++ ) for( col="0" ; col < 80 ; col++ )
*(aplej + ren*80 + col )="c" | 0x0700 ; clrscr(); }

Listado 6.3.- Utilización de un apuntador far a enteros.

A continuación, se explican algunas lineas del listado 6.3.

En:

int far *aplej ;

se declara un apuntador lejano a objetos de tipo entero. Observe que la sintaxis


que rige la declaración es:

tipo far *identif ;

En la linea:

aplej = (int far *) 0xB8000000 ;

la parte

(int far *) representa un forzamiento de tipo para que la


constante hexadecimal sea manejada por un
apuntador lejano.

0xB8000000 representa la direccón de memoria reservada para


uso exclusivo en los modos de video CGA y EGA.

En este caso, B800 es la dirección del segmento, y 0000 es la dirección del


desplazamiento.

Por último, en la linea:

*(aplej + ren*80 + col ) = c | 0x0700 ;

la parte:

aplej + ren*80 + col calcula la dirección de memoria


correspondiente a cada caracter.

*(aplej + ren*col +80 ) representa al objeto apuntado


(un caracter).

mientras que:
c | 0x0700

es una operación OR a nivel de bits para asegurar que el caracter c tendrá los
atributos de caracter normal.

6.4.- Apuntadores y constantes

Un apuntador o un objeto apuntado pueden declararse con el modificador const, y


en tal caso no se les podrá asignar un nuevo valor.
Por ejemplo, en el listado 6.3 la constante B8000000 se asigna al apuntador aplej.
El apuntador aplej fué declarado como una variable, por lo que sería válido
asignarle cualquier nuevo valor.
Si deseamos que el valor B8000000 permanezca sin cambios en aplej, deberemos
escribir la siguiente declaración:

int far *const aplej = (int far *) 0xB8000000L;

con lo cual no podrá asignarse un nuevo valor al apuntador aplej.


El valor de una constante no puede cambiar, pero sí puede utilizarse para realizar
operaciones, como se observa en la siguiente fracción de línea del listado 6.3 :

aplej + ren*80 + col

En este caso, se utiliza el valor de aplej para calcular la dirección de memoria


donde se va a almacenar cada carácter.

6.5.- Variables dinámicas y apuntadores

Las variables dinámicas se manejan en el montículo, y deben su nombre al hecho


de que pueden ser creadas y destruidas durante el tiempo de ejecución de un
módulo.

Para el manejo de variables dinámicas se hace indispensable la utilización de


apuntadores, así como de funciones especiales para la asignación y liberación de la
memoria correspondiente a dichas variables.

Tanto en C como en C++ existen funciones tales como malloc() y free() para la
asignación y liberación de memoria del montículo.

Además de estas funciones, el C++ cuenta con dos operadores interconstruidos:


new que realiza una labor parecida a la de la función malloc(),
asignando un bloque de memoria.

delete que libera un bloque de memoria asignada en tiempo de


ejecución, de manera semejante a como lo hace free().
En el listado 6.4 se muestra un ejemplo de aplicación de los operadores new y
delete.

#include <iostream.h>

void main()
{
int *apent; // Declara un apuntador a entero

apent = new int ; // Reserva un bloque de memoria dinámica


// de 2 bytes para manejarlo por medio
// de apent.
*apent = 10 ; // Asigna el valor 10 al objeto apuntado
// por apent.
cout << *apent ; // Despliega el contenido del objeto // apuntado
por apent. delete apent ; // Libera el espacio de memoria manejado //
por apent. }

Listado 6.4.- Aplicación de new y delete.

En el programa del listado 6.4, se supone que la reservación será exitosa porque
existe espacio suficiente en el montículo.

Pero ¿quién asegura que el espacio requerido por new está disponible?. Para
controlar esta situación y evitar un mensaje de error por parte del sistema en
tiempo de ejecución, en 6.5 se propone una nueva versión del programa.

#include <iostream.h>
#include <stdlib.h> // Para exit().

void main()
{
int *apent; // Declara un apuntador a entero

if((apent = new int)==NULL)// Intenta reservar un bloque de


{ // memoria dinámica de 2 bytes ra
// manejarlo por medio de apent.
cout << "NO hay espacio suficiente\n"; exit(1); // Finaliza
la ejecución del programa. } *apent="10" ; // Asigna el valor 10 al
objeto apuntado // por apent. cout << *apent ; // Despliega el
contenido del objeto // apuntado por apent. delete apent ; // Libera
el espacio de memoria manejado // por apent. }

Listado 6.5.- Reservación de memoria para una variable dinámica.

Los operadores new y delete pertenecen al Lenguaje C++ , de tal manera que no se
requiere incluir ningún archivo de cabecera para utilizarlos.

Para crear un arreglo de 25 elementos de tipo double, en el montículo, puede


escribirse:

double* dap ;
dap = new double[25];

ó su forma equivalente:
double* dap = new double[25];

En este ejemplo, se está declarando a dap como un apuntador a variables


dinámicas de tipo doble; al tiempo que se le asigna
el valor retornado por new. El valor retornado por new es la dirección del inicio de
un bloque de memoria del tamaño requerido para almacenar 25 elementos de tipo
double.

En caso de que el montículo no disponga del espacio requerido, new retorna el


valor NULL ( nulo ).

Aunque, cuando se requiere memoria a través de new se debe indicar el tamaño


exacto del bloque requerido, el tamaño del bloque se puede dar en tiempo de
ejecución como se muestra en el listado 6.6.

#include <iostream.h>
#include <conio.h>
#include <stdlib.h>

void main()
{
unsigned int n;
clrscr();
gotoxy(20,1);
cout << "NUMERO DE ELEMENTOS DEL ARREGLO: "; cin>> n;
float* apf;
if((apf=new float[n])==NULL)
{
cout << "NO HAY ESPACIO SUFICIENTE EN EL MONTICULO\N";
exit(1); } for(int x="0" ; x < n ; x++) { gotoxy(20,x+3);
*(apf+x)="x+100.25;" cout << *(apf+x) << "\n"; } delete apf; getch();
}

Listado 6.6.- Manejo de un arreglo en el montículo.

6.6.- Apuntadores y arreglos

Como se mencionó en la unidad 5, el tema de arreglos está íntimamente ligado al


de apuntadores; tanto que es posible intercambiarlos en la solución de un
problema.

El nombre de un arreglo corresponde al de un apuntador que almacena un valor


constante. Este valor constante es la dirección de memoria del primer elemento del
arreglo.

Por ejemplo :

int calif[]={100,90,95,80,90}; // Declaración e


// inicialización
// de un arreglo
// de 5 enteros
se puede representar con la figura 6.6.

Figura 6.6.- El arreglo calif[ ] en la pila.

En realidad, el lenguaje manejará al arreglo a través de un apuntador llamado


calif, el cual tiene almacenado el valor 65494, que a su vez corresponde a la
dirección de inicio del elemento calif[0].

La representación del apuntador calif, en la zona de variables globales de la


memoria RAM, es la siguiente :

En el listado 6.7 se presenta el manejo del arreglo calif[ ], a través de la notación de


arreglos, y en el listado 6.8 el manejo con la notación de apuntadores.

#include <iostream.h>

void main()
{
int calif[] = { 100,90,95,80,90};

for(int i=0 ; i <5 ; i++) //Notación de arreglos. cout << "\n"


<< calif[i] ; }

Listado 6.7.- Manejo de calif[] , notación de arreglos.

#include <iostream.h>

void main()
{
int calif[] = { 100,90,95,80,90};

for(int i=0 ; i <5 ; i++) // Notación de apuntadores cout <<


"\n" << *(calif+i) ; }

Listado 6.8.- Manejo de calif[] , notación de apuntadores.

Como puede observarse, la única diferencia entre los listados 6.7 y 6.8 es que el
primero utiliza calif[i] y el segundo *(calif+i).
Debido a que la ejecución de los programas de ambos listados producen resultados
iguales, se deduce que:

calif[i] == *(calif+i)

Para entender esto que a simple vista no es obvio, revisaremos algunos conceptos:
1.- El nombre del arreglo corresponde al de un apuntador que apunta al primer
elemento del arreglo, por lo que:

calif apunta a calif[0]

Visto gráficamente :

2.- Para hacer referencia a un elemento específico del arreglo, se toma como base
la dirección del primer elemento y, con el subíndice del elemento específico, se
calcula su dirección. Por ejemplo, para referirse al segundo elemento del arreglo
puede procederse así :

calif[1] // Notación de arreglos

*(calif+1) // Notación de apuntadores, donde la expresión calif+1sirve para


calcular la dirección del elemento que está una posición más allá del elemento
apuntado por calif.

Para referirse a calif[2] ( tercer elemento ), puede escribirse:

*(calif+2)

Lo que significa: "El objeto que se encuentra dos posiciones después del objeto
apuntado por calif". En este caso, una posición es el espacio requerido por cada
uno de los elementos, de tal manera que, si calif apunta a la dirección 65494,
entonces calif+2 es la expresión que calcula la dirección 65498. La figura 6.7
muestra los elementos del arreglo calif[] con sus nombres en notación de arreglos y
en notación de apuntadores.
Figura 6.7.- El arreglo calif[] en la pila. Notación en arreglos y en apuntadores.

De lo anterior, se infiere la regla:


calif[i] == *(calif+i)
por lo que:
&calif[i] == calif+i

Esto es que, la dirección del i-ésimo elemento de un arreglo se calcula sumándole el


subíndice a la dirección del primer elemento.

Como otro ejemplo, supongamos la siguiente declaración correspondiente a un


arreglo de 10 elementos de tipo float.
float* sueldo[10];

Si la dirección del primer elemento es 65494, entonces:

&sueldo[5] es igual a :

sueldo+5 = 65494 + ( 5 * 4 ) = 65514


^
|
Tamaño de float ---------|

que corresponde a la dirección del elemento que se encuentra 5 posiciones más


adelante de aquél apuntado por sueldo.

Como regla, podemos establecer que el subíndice se refiere a la posición del


elemento en el arreglo. Es por esto que al calcular la dirección por medio del
subíndice, éste debe multiplicarse por el número de bytes que representan el
tamaño de cada elemento ( dado por el tipo utilizado en la declaración del arreglo
).

6.6.1.- Apuntadores y cadenas

Como se vió en la unidad 5, una cadena es un arreglo de caracteres cuyo último


elemento es el caracter nulo.
Utilizando la nomenclatura de arreglos, podemos declarar:

char nombre[ ] = "PACO" ;

Esto mismo puede hacerse por medio de apuntadores, como se muestra en el


listado 6.9.

#include <iostream.h>
#include <conio.h>
#include <stdio.h>

void main()
{
char *nombre = "PACO" ;
clrscr();

gotoxy(30,12);
cout<< "!! HOLA, " ; puts(nombre); gotoxy(43,12); cout << " !!";
getch(); }

Listado 6.9.- Manejo de un arreglo de caracteres (cadena) por medio de


apuntadores.

6.7.- Arreglos de apuntadores

Los apuntadores pueden manejarse en un arreglo, de tal forma que:

char nombres[ ][5] = { "HUGO", "PACO", "LUIS" } ;

es la declaración de un arreglo de cadenas, con asignación de valores iniciales. Su


equivalente en notación de apuntadores es:

char *nombres[ ] = { "HUGO", "PACO", "LUIS" } ;

en el que se declara un arreglo de apuntadores. El listado 6.10 muestra el


programa completo para el manejo de este ejemplo.

#include <iostream.h>
#include <conio.h>
#include <string.h>

void main()
{
char *nombres[ ] = { "HUGO", "PACO", "LUIS" } ;
char invitado[11];
int bandera;

clrscr();
gotoxy(30,10);
cout << "CUAL ES SU NOMBRE ? " ; gotoxy(50,10); cin>> invitado ;
gotoxy(30,12);
for( int x = 0 ; x <3 ; x++ ) if(strcmp(invitado, nombres[x])="="
0) bandera="0;" if(bandera="=" 0) cout << "!! PASE, ESTIMADO " <<
invitado << " !!"; else cout << "!! FUERA DE AQUI, " << invitado << "
!!"; getch(); }

Listado 6.10.- Manejo de un arreglo de apuntadores.

6.8.- Paso de arreglos como parámetros

Un arreglo puede pasarse como parámetro a una función. Si tuviera que pasarse
por valor un arreglo muy grande, sería un desperdicio de memoria. En el Lenguaje
C++ el paso de arreglos se hace por referencia, ya que el nombre del arreglo
corresponde a un apuntador al primer elemento del arreglo.

Al pasar un parámetro correspondiente a un arreglo, se pasa la dirección del


primer elemento, por lo que la función invocada puede modificar cualquier
elemento del arreglo.

El listado 6.11 contiene un programa que maneja una función llamada nputs() , la
cual recibe como parámetro un arreglo de caracteres.

#include <iostream.h>
#include <conio.h>
#include <stdio.h>
#include <string.h>
void nputs(char *);
void main()
{
char cadena[81];
clrscr();
gotoxy(10,10);
cout << "ESCRIBA UNA CADENA: "; gets(cadena); gotoxy(10,12);
nputs(cadena); getch(); } void nputs(char cad[ ]) { int x="0;"
while(cad[x]) { cout << cad[x] ; x++; } }

Listado 6.11.- Manejo de nputs() con arreglos. En el listado 6.12 se muestra el


manejo de la función nputs(), por medio de apuntadores.

#include <iostream.h>
#include <conio.h>
#include <stdio.h>
#include <string.h>

void nputs(char *);

void main()
{
char cadena[81];
clrscr();
gotoxy(10,10);
cout << "ESCRIBA UNA CADENA: "; gets(cadena); gotoxy(10,12);
nputs(cadena); getch(); } void nputs(char *cad) { while(*cad) cout <<
*cad++ ; }

Listado 6.12.- Manejo de nputs() con apuntadores.

6.9.- Paso de funciones como parámetros

Toda función tiene asociada una dirección de inicio de código, la cual puede
pasarse como parámetro en la invocación a otra función, como se muestra en el
listado 6.13.

#include <iostream.h>
#include <string.h>
#include <conio.h>

int cmpcad(char*, char*);


void compara(char*, char*, int(*)(char*, char*));

void main()
{
char cadx[80], cady[80];

clrscr();
gotoxy(10,5);
cout << "ESCRIBA UNA CADENA : " ; cin>> cadx;
gotoxy(10,7);
cout << "ESCRIBA OTRA CADENA : " ; cin>> cady;
gotoxy(10,9);
compara(cadx, cady, cmpcad);
gotoxy(1,24);
}
void compara(char *cad1, char *cad2,
int (*cmpcad)(char*, char*))
{
if(!(*cmpcad)(cad1,cad2))
cout << "LAS CADENAS SON IGUALES"; else cout << "LAS CADENAS
SON DISTINTAS"; } int cmpcad(char *x, char *y) { return(strcmp(x,y));
}

Listado 6.13.- Paso de apuntadores como parámetros a funciones.

En el listado 6.13, la expresión :

int(*cmpcad)(char*, char*)

establece que cmpcad es un apuntador a una función, la cual devuelve un valor de


tipo entero .

6.10.- Apuntadores a apuntadores


Como se vió al principio de la unidad, un apuntador también es una variable. Su
dirección puede ser almacenada por otra variable apuntador, por lo que puede
hablarse de un apuntador a un apuntador.

Esto puede extrapolarse para dos o más variables, como se observa en el listado
6.14.

#include <iostream.h>
#include <conio.h>

void main()
{
int x, *a, **b, ***c ; // 1
clrscr();
a = &x ; // 2
*a = 100 ; // 3
b = &a ; // 4
**b += *a ; // 5
c = &b ; // 6
***c += **b + *a ; // 7
cout << " *a=" << *a << " \n" ; cout << " **b=" << **b << " \n" ;
cout << "***c=" << ***c << " \n" ; getch(); }

Listado 6.14.- Ejemplo de apuntadores a apuntadores. A continuación se analizan las


líneas marcadas en el listado 6.14.

int x, *a, **b, ***c; // 1

Se declaran:

x como una variable de tipo entero. a como un apuntador a objetos de tipo entero. b como
un apuntador a un apuntador, el cual a su vez apuntará a objetos de tipo entero. Se dice que
b es "el apuntador del apuntador". c como un apuntador a un apuntador que apunta a otro
apuntador, el cual a su vez apunta a objetos de tipo entero. Se dice que c es "el apuntador
del apuntador del apuntador".

La pila luciría así:

a = &x ; // 2

Se asigna, al apuntador a, la dirección de x. La pila luciría así:


*a = 100 ; // 3

Al objeto apuntado por a se le asigna el valor 100. La pila luciría así:

b = &a ; // 4

Al apuntador b se le asigna la dirección del apuntador a. La pila luciría así:

**b += *a ; // 5

Al objeto apuntado por el apuntador apuntado por b se le suma el valor del objeto
apuntado por a. La pila luciría así:

c = &b ; // 6

Al apuntador c se le asigna la dirección del apuntador b. La pila luciría así:


***c += **b + *a ; // 7

Se asigna al objeto apuntado por el apuntador apuntado por el apuntador c, el valor


del objeto apuntado por el apuntador apuntado por el apuntador b más el valor del
objeto apuntado por el apuntador a. La pila luciría así:

_ Gif animado

_ Applet animado

El listado 6.15 muestra otro ejemplo de manejo de apuntadores a apuntadores. En


él, la línea de cabecera de la definición de main() contiene argumentos entre los
paréntesis. Esto significa que, al invocar al programa ECO.EXE desde la línea de
comandos, se le pueden pasar argumentos. Estos argumentos serán tomados como
cadenas de caracteres que serán manejadas por **argumentos. La variable entera
contador contiene el número correspondiente a la cantidad de cadenas que se le
pasan a main() en la invocación.

// ECO.CPP : Maneja argumentos en la invocación a main(),


// de manera similar a como trabaja la orden
// ECHO del sistema operativo

#include <iostream.h>

void main(int contador, char **argumentos)


{
cout << "\n"; for( int x="1" ; contador--> 0 ; x++)
{
int y = 0 ;
do{
cout << argumentos[x][y++]; } while(argumentos[x][y]);
cout << " "; } cout << "\n"; }

Listado 6.15.- Paso de argumentos a main(). Por ejemplo, si escribimos en la línea de


comandos:
A:\> ECO HOLA AMIGOS !!!

en la pantalla aparecerá:

HOLA AMIGOS !!!

En este caso:

argumentos[0] = ECO 1 argumentos[1] = HOLA 2 argumentos[2] = AMIGOS 3 argumentos[3]


= !!! 4 <---- Número de argumentos contador="4"

La línea de cabecera de la función main() pudo escribirse de esta otra forma:

void main(int contador, char *argumentos[]) ya que existe la siguiente equivalencia :

Esto puede generalizarse, de manera que:

*cadena == cadena[]
**cadena == *cadena[]
***cadena == **cadena[]

El uso de corchetes vacíos sólo es válido cuando se realiza una asignación ó cuando
se escriben los argumentos en la línea de cabecera de la definición de una función.
Sólo el par de corchetes de la extrema derecha puede escribirse vacío, los demás
deberán contener un valor constante.

La línea de cabecera en la definición de la función main() puede tomar también la


siguiente forma:

void main(int contador, char **argumentos, char **ambiente)

donde:

**ambiente se refiere a un conjunto de cadenas de caracteres correspondientes al


ambiente del sistema operativo, como las asignadas a prompt, path , etc.

También podría gustarte