Page 32
cin >> i;
switch (i) {
case 0:
case 2:
case 4:
case 6:
case 8:
cout << " El número " << i << " es par\n";
break;
case 1:
case 3:
case 5:
case 7:
case 9:
cout << " El número " << i << " es impar\n";
break;
default:
cout << " El número " << i << " no está reconocido\n";
}
}
Estructuras de repetició n
Dentro de las estructuras de repetició n diferenciábamos 3 tipos: con condició n inicial, con
condició n final y con contador.
La sentencia do-while
Es una estructura de repetició n con condició n final. Su sintaxis es:
do
sentencia
while (expresión);
El funcionamiento es simple, entramos en el do y ejecutamos la sentencia, evaluamos la
expresió n y si es cierta volvemos al
do
, si es falsa salimos.
La sentencia while
Es una estructura de repetició n con condició n inicial. Su sintaxis es:
while (expresión)
sentencia
El funcionamiento es simple evaluamos la expresió n y si es cierta ejecutamos la sentencia y
volvemos a evaluar, si es falsa salimos.
La sentencia for
Aunque en la mayoría de los lenguajes la sentencia for es una estructura de repetició n con
contador, en C++ es muy similar a un while pero con características similares. Su sintaxis es:
for (expr1; expr2; expr3)
sentencia
Que es equivalente a:
expr1
while (expr2) {
sentencia
expr3
}
Es decir, la primera expresió n se ejecuta una vez antes de entrar en el bucle, despué s se
comprueba la verdad o falsedad de la segunda expresió n y si es cierta ejecutamos la sentencia y
luego la expresió n 3.
26

Page 33
En el siguiente ejemplo veremos como esto se puede interpretar fácilmente como una repetició n
con contador:
int i;
for (i=0; i<10; i++) {
cout << " Voy por la vuelta " << i << endl;
}
Este fragmento de có digo inicializa la variable i a 0, comprueba que el valor de i es menor que
10, ejecuta la sentencia e incrementa i. A continuació n vuelve a comprobar que i es menor que
10, ejecuta la sentencia e incrementa i, y sigue así hasta que i es mayor o igual que 10.
Hay que indicar que no es necesario poner ninguna de las expresiones en un for, la primera y
tercera expresió n son realmente sentencias ejecutables, por lo que no importa si las ponemos
dentro o fuera del for, pero si no ponemos la segunda el compilador asume que la condició n es
verdadera y ejecuta un bucle infinito.
Por ú ltimo diremos que una característica interesante de esta estructura es que los índices no
modifican su valor al salir del bucle, por lo que se pueden utilizar despué s con el valor que les
ha hecho salir del bucle.
Estructuras de salto
El C++ pretende ser un lenguaje eficiente, por lo que nos da la posibilidad de romper la
secuencia de los algoritmos de una forma rápida, sin necesidad de testear infinitas variables para
salir de un bucle. Disponemos de varias sentencias de ruptura de secuencia que se listan a
continuació n.
La sentencia break
Es una sentencia muy ú til, se emplea para salir de los bucles (do-while, while y for) o de un
switch. De cualquier forma esta sentencia só lo sale del bucle o switch más interior, si tenemos
varios bucles anidados só lo salimos de aquel que ejecuta el break .
La sentencia continue
Esta sentencia se emplea para saltar directamente a evaluar la condició n de un bucle desde
cualquier punto de su interior. Esto es ú til cuando sabemos que despué s de una sentencia no
vamos a hacer nada más en esa iteració n.
Hay que señalar que en los for lo que hace es saltar a la expresió n 3 y luego evaluar la
expresió n 2
La sentencia goto
Sentencia maldita de la programació n, es el salto incondicional. Para emplearla basta definir una
etiqueta en cualquier punto del programa (un identificador seguido de dos puntos) y escribir
goto etiqueta.
Ejemplo:
for (;;) {
do {
do {
if (terminado)
goto OUT;
} while (…);
} while (…);
}
OUT : cout << "He salido" << endl;
27

Page 34
El goto só lo se puede usar dentro de la funció n donde se define la etiqueta y no se puede poner
antes de una declaració n de variables.
La sentencia return
Esta sentencia se emplea en las funciones, para retornar un valor. En el punto en el que aparece
el return la funció n termina y devuelve el valor. Si una funció n no devuelve nada podemos
poner return sin parámetros para terminar (si no lo ponemos la funció n retorna al terminar su
bloque de sentencias).
Ejemplo:
int min (int a, int b) {
if (a<b)
return a; // si entramos aquí la función retorna a y no sigue
// ejecutando lo que hay debajo
return b; // si no hemos salido antes b es el mínimo.
// si pusiéramos más sentencias nunca se ejecutarían
}
FUNCIONES
Declaració n de funciones
La declaració n de una funció n nos da el nombre de la funció n, el tipo del valor que retorna y el
nú mero y tipo de parámetros que deben pasársele. La sintaxis es:
tipo_retorno nom_funcion (lista_tipos_param);
lista_tipos_param = tipo_param_1, tipo_param_2, … , tipo_param_n
Donde los paré ntesis y el punto y coma son obligatorios. El tipo de retorno se puede omitir pero
el compilador asume que es int. En C una funció n sin lista de parámetros se considera que tiene
un nú mero de parámetros indefinidos, mientras que en C++ se entiende que no se le pasa nada
(para pasar un nú mero de parámetros indefinido se ponen tres puntos (...) en la lista de
parámetros). Lo más recomendable para evitar confusiones es poner siempre void como
parámetro cuando no vamos ha pasar nada.
En la lista de parámetros podemos ponerle nombre a los parámetros, pero el compilador los
ignorará.
Definició n de funciones
Una definició n de una funció n es una declaració n en la que se incluye el cuerpo de la misma y
se da nombre a los parámetros. La sintaxis es:
tipo_retorno nom_funcion (lista_param) {
cuerpo de la función
}
lista_param = tipo_param_1 nom_param_1, … , tipo_param_n nom_param_2;
Donde los nombres de los parámetros son obligatorios.
Hay que indicar que una lista de parámetros debe indicar explícitamente el tipo de cada uno de
ellos, así:
int producto (int a, b) { return (a*b); }
sería incorrecto, ya que el segundo parámetro no tiene tipo, habría que escribir:
int producto (int a, int b) { return (a*b); }
28

Page 35
Es decir, una lista de parámetros no es una declaració n de variables.
Si no deseamos usar algú n parámetro podemos indicarlo no ponié ndole nombre en la definició n
de la funció n.
Dentro del cuerpo de la funció n podemos declarar variables, pero no funciones, es decir, no se
permiten funciones anidadas.
Paso de parámetros
En C++ todos los parámetros se pasan por valor, es decir, se hace una copia de los parámetros
cuando llamamos a la funció n. En la llamada, antes de hacer la copia, se chequea que los
parámetros actuales (los valores o variables especificados en la llamada) son del mismo tipo que
los parámetros formales (los que declaramos en la lista de parámetros de la declaració n de la
funció n). Si los tipos son iguales o existe una conversió n implícita la llamada puede realizarse,
si no son iguales deberemos realizar conversiones explícitas.
Veamos un ejemplo de paso de parámetros:
#include <iostream.h>
void f (int val, int& ref) {
val++;
ref++;
}
main (){
int i=1;
int j=1;
f(i.j);
cout << "i vale " << i << " y j " << j << endl;
}
El resultado de ejecutar el programa será i vale 1 y j vale 2. Esto se debe a que cuando pasamos
el parámetro val, esperamos un entero y lo que recibe la funció n es una copia del valor de i (1),
al incrementar val en 1 modificamos la copia y la i queda igual. Sin embargo, el segundo
parámetro es una referencia (la direcció n de una variable entera), y al llamar a la funció n con j lo
que se copia es su direcció n, luego las modificaciones de ref se producen en la posició n en la
que se encuentra j. Como se ve, podemos considerar que al poner un parámetro de tipo
referencia estamos realmente pasando el parámetro por referencia.
Hay que indicar que las referencias se usan igual que las variables del tipo, es decir, el ref++ no
modifica la direcció n, sino que modifica el valor referenciado.
Parámetros array
Hay un caso especial en el que aparentemente no se copia el parámetro, que es al pasar vectores.
Por ejemplo:
void multiplica_matriz (int a[5], int val) {
for (int i=0; i<5; i++)
a[i] *= val;
}
main () {
int m[5] = {1, 2, 3, 4, 5};
multiplica_matriz(m, 2);
}
Despué s de la llamada a multiplica_matriz m valdría {2,4,6,8,10}. ¿ Qué ha sucedido?
29

Page 36
El problema está en la forma de tratar los vectores del C++, ya que el nombre de un vector o
una matriz es en realidad un puntero al primer elemento de la misma, y utilizando el operador []
lo que hacemos es sumarle a ese puntero el tamaño de los elementos del array multiplicado por
el valor entre corchetes. Esta es la razó n de que los vectores comiencen en 0, ya que el primer
elemento es el apuntado por el nombre del vector.
Todo esto implica que realmente su hayamos copiado el parámetro, só lo que el parámetro es el
puntero al primer elemento, no el contenido del vector, y por tanto las modificaciones del
contenido se hagan sobre los valores de la matriz pasada como parámetro.
Veremos en el punto dedicado a variables dinámicas que realmente los tipos vector y puntero
son equivalentes (podremos usarlos indistintamente).
Retorno de valores
Para retornar valores una funció n emplea la instrucció n return, que como ya vimos se puede
colocar en cualquier punto de la misma y más de una vez. Cuando se ejecuta un return la
funció n sale.
Hay que indicar que tambié n se chequea el tipo de los retornos, es decir, lo que retornemos
debe ser del mismo tipo (o convertible implícitamente) que el declarado como de retorno para la
funció n.
Cuando una funció n se declara de manera que retorna algo, es un error no poner ningú n return
en la misma.
Sobrecarga de funciones
Una de las características más interesantes del C++ en cuanto a funciones se refiere es la
posibilidad de definir distintas funciones con el mismo nombre aunque con distintos
parámetros.
Esta capacidad se denomina sobrecarga de funciones y es ú til para llamar de la misma forma a
funciones que realizan operaciones similares pero sobre operandos distintos. En está frase
hemos dado una clave para la definició n de los TAD en C++, los operadores son funciones y
como tales tambié n pueden ser sobrecargadas. Este aspecto se estudiará una vez hayamos visto
las clases, ya que tienen su mayor aplicació n en estas.
El tipo de retorno debe ser igual, ya que de lo contrario se pueden provocar ambigü edades como
que llamemos a una funció n esperando un real y tengamos dos funciones idé nticas, una que
retorna reales y otra enteros, ¿ Cuál debemos usar? Si no hubiera conversiones implícitas estaría
claro, pero podemos querer usar la que retorna enteros y que se transforme en real. La solució n
es no permitir retornos diferentes, lo ú nico que debemos hacer es darles nombres diferentes a
las funciones.
Parámetros por defecto
Algunas veces una funció n necesita determinados parámetros en condiciones excepcionales pero
estos suelen tener unos valores fijos en los casos normales. Para no tener que dar estos
parámetros en los casos normales el C++ permite el uso de parámetros por defecto. La sintaxis
es muy simple, al declarar una funció n ponemos lo mismo que antes só lo que los parámetros
por defecto van al final y se escriben no só lo poniendo el tipo sino tambié n un signo igual y un
valor:
tipo nom_funcion (lista_param, lista_param_por_defecto);
lista_param_por_defecto = tipo_pd_1 = vd1, …, tipo_pd_n = vd_n
Para usar la funció n podemos llamarla especificando só lo los parámetros indefinidos o
poniendo estos y algunos parámetros por defecto con un valor.
30

Page 37
Ejemplo:
void imprime_int (int valor, int base = 10);
// también se puede declarar como
// void imprime_int (int , int = 10);
// los nombres no importan
void imprime_int (int valor, int base) {
switch (base) {
case 8:
cout << oct << i;
break;
case 10:
cout << dec << i;
break;
case 16:
cout << hex << i;
break;
default:
cerr << "Función imprime_int (): Representación en base" << base \
<< " indefinida\n";
}
}
Para imprimir un nú mero en decimal haremos:
imprime_int (num);
Si queremos cambiar la base hacemos:
imprime_int (num, 8); // imprime en octal
imprime_int (num,16); // imprime en hexadecimal
imprime_int (num, 4); // imprime un error.
Parámetros indefinidos
Para determinadas funciones puede ser imposible especificar el nú mero y tipo de todos los
parámetros esperados, por lo que el C++ define un mecanismo para utilizar funciones con un
nú mero de parámetros indefinido. La sintaxis para declarar las funciones es:
tipo nom_funcion (lista_args ...); // al terminar la lista de args no ponemos coma
de esta manera, podemos declarar funciones que reciban siempre una serie de parámetros de
forma normal y luego otros indefinidos.
Para acceder a los parámetros variables empleamos un conjunto de macros definidas en la
cabecera estándar <stdarg.h>. Haremos lo siguiente:
1. Declaramos una variable del tipo va_list que es el tipo que asignamos a la lista de parámetros
indefinidos.
2. Para inicializar la lista de argumentos empleamos la macro va_start que toma como
argumentos la variable de tipo va_list y el ú ltimo parámetro formal.
3. Para ir accediendo a los distintos elementos utilizamos la macro va_arg, que va leyendo los
parámetros en orden y toma como argumentos la lista de tipo va_list y el tipo de la siguiente
variable.
4. Antes de salir de una funció n que ha llamado a va_start debemos llamar a va_end con la
variable de tipo va_list como parámetro.
El problema fundamental es saber que tipo asignar a cada variable y cuantas hay, la solució n
depende del tipo de parámetros que pasemos y lo que haga la funció n. Una forma fácil de saber
31

Page 38
el tipo y nú mero de parámetros variables es pasarle a la funció n una cadena de caracteres con los
tipos.
Veamos como se usa con un ejemplo:
#include <iostream.h>
#include <stdarg.h> // macros parámetros variables
#include <string.h> // función strlen
void param_test (char * ...);
void param_test (char *tipos ...) {
int i;
va_list ap; // ap es la lista de parámetros
va_start (ap, tipos); // inicialización lista de param.
i = strlen (tipos); // la longitud de la cadena es el nº de param
// nuestra función reconoce tipos enteros y tipos reales (los reales sólo de tipo
// double).
for (int j=0; j<i; j++) {
switch (tipos[j]) {
case 'e':
int iv = va_arg(ap, int);
cout << "Parámetro " << j << " = " << iv << " de tipo entero\n";
break;
case 'r':
double dv = va_arg(ap, double);
cout << "Parámetro " << j << " = " << dv << " de tipo real\n";
break;
default:
cout << "Parámetro " << j << " de tipo desconocido\n";
return;
} // end switch
} // end for
va_end(ap); // terminamos con la lista de param.
}
void main (void) {
param_test ("eer", 12, 5, 5.35);
};
La salida de este ejemplo es:
Parámetro 0 = 12 de tipo entero
Parámetro 1 = 5 de tipo entero
Parámetro 2 = 5.35 de tipo real
Hay que decir que en el programa salimos abruptamente cuando no conocemos el tipo porque el
resto de parámetros se leerán mal si realmente nos hemos equivocado. Por otro lado, si le
decimos que el parámetro es de un tipo y lo pasado como parámetro es de otro lo más normal es
que la impresió n sea incorrecta, ya que no hacemos chequeo de tipos y si pasamos un entero
(que, por ejemplo, ocupa 2 bytes) y leemos un real (por ejemplo de 4 bytes), habremos leído
dos bytes de más, que le harán perder el sentido al real y además consumirán 2 bytes del
siguiente parámetro. Es decir, si pasamos un parámetro erró neo lo más probable es que los
demás tambié n se pierdan.
Recursividad
Las funciones del C++ pueden ser recursivas, es decir, se pueden llamar a sí mismas. Lo ú nico
interesante de las funciones recursivas es que las variables declaradas dentro del cuerpo de la
funció n que sean estáticas (static) no pierden su valor entre llamadas, es decir, no se crean y se
destruyen en la pila, sino que ocupan siempre la misma posició n, y por tanto cada modificació n
que se haga de la variable será valida entre sucesivas llamadas. Otra ventaja de esta
aproximació n es que si no importa para la recursividad que la variable mantenga su valor
32

Page 39
despué s de una llamada recursiva (por ejemplo una variable temporal para intercambios), al
declararla estática no tenemos que reservar espacio para ella en cada llamada (las llamadas
recursivas consumen menos pila).
Aunque comento esto para funciones recursivas, la verdad es que esto se cumple para todas las
funciones, luego podemos tener una variable estática que se use para contar el nú mero de veces
que se llama a una funció n (por ejemplo).
Punteros a funciones
Con las funciones só lo podemos hacer dos cosas, llamarlas u obtener su direcció n, es decir,
podemos definir punteros a funciones (que luego nos sirven para llamarlas).
La declaració n de un puntero a funció n se hace:
tipo_retorno (*nom_var) (lista_tipos_argumentos);
El paré ntesis es necesario, ya que el operador de funció n tiene más preferencia que el puntero,
por lo que si escribimos:
tipo_retorno *nom_var (lista_tipos_argumentos);
el compilador interpretará que tenemos una funció n que retorna un puntero a un elemento de
tipo tipo_retorno.
Para llamar a la funció n só lo tenemos que escribir:
(*nom_var) (param_actuales);
si ponemos:
nom_var (param_actuales);
el compilador seguramente identificará que nom_var es una funció n y llamará correctamente a la
funció n, aunque es mejor no fiarse de eso.
Para asignar valor a un puntero a funció n só lo tenemos que escribir:
nom_var= &nom_funcion;
donde nom_función corresponde a una funció n con parámetros y retorno idé nticos a los definidos
en el puntero a funció n.
La funció n main()
Para terminar con las funciones hablaremos de la funció n principal de los programas de C++, la
funció n main().
La funció n main() se puede definir de varias formas distintas:
1. void main (); // no recibe parámetros ni retorna nada
2. int main (); // no recibe parámetros y retorna un entero al SO (un código de
// error (generalmente negativo) o 0 si no hay errores)
main (); // igual que la anterior
3. void main (int argc, char *argv[]); // recibe un array con 'argc' cadenas de
// caracteres y no retorna nada
4. int main (int argc, char *argv[]); // igual que la anterior pero retorna un
// código de error al SO
La tercera y cuarta formas reciben parámetros desde la línea de comandos en Sistemas
Operativos como UNIX o MS-DOS. Es decir, cuando en MS-DOS escribimos un comando
como:
33