PROGRAMACION Y M´ ETODOS NUM´ ERICOS´ … › ~vmunoz › homepage › cursos › ...El fin de...

74
Apuntes de un curso de PROGRAMACI ´ ON Y M ´ ETODOS NUM ´ ERICOS AVANZADO Departamento de F´ ısica Facultad de Ciencias Universidad de Chile ıctor Mu˜ noz G. Jos´ e Rogan C.

Transcript of PROGRAMACION Y M´ ETODOS NUM´ ERICOS´ … › ~vmunoz › homepage › cursos › ...El fin de...

Page 1: PROGRAMACION Y M´ ETODOS NUM´ ERICOS´ … › ~vmunoz › homepage › cursos › ...El fin de una instrucci´on est´a determinado por el cierre de un par´entesis o por un punto

Apuntes de un curso de

PROGRAMACION Y METODOS NUMERICOSAVANZADO

Departamento de FısicaFacultad de CienciasUniversidad de Chile

Vıctor Munoz G.Jose Rogan C.

Page 2: PROGRAMACION Y M´ ETODOS NUM´ ERICOS´ … › ~vmunoz › homepage › cursos › ...El fin de una instrucci´on est´a determinado por el cierre de un par´entesis o por un punto
Page 3: PROGRAMACION Y M´ ETODOS NUM´ ERICOS´ … › ~vmunoz › homepage › cursos › ...El fin de una instrucci´on est´a determinado por el cierre de un par´entesis o por un punto

Indice

1. Una breve introduccion a C++ 11.1. Estructura basica de un programa en C++ . . . . . . . . . . . . . . . . . . . . 1

1.1.1. El programa mas simple . . . . . . . . . . . . . . . . . . . . . . . . . . 11.1.2. Haciendo el codigo mas legible . . . . . . . . . . . . . . . . . . . . . . . 21.1.3. Nombres de variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31.1.4. Tipos de variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41.1.5. Ingreso de datos desde el teclado . . . . . . . . . . . . . . . . . . . . . 61.1.6. Operadores aritmeticos . . . . . . . . . . . . . . . . . . . . . . . . . . . 81.1.7. Operadores relacionales . . . . . . . . . . . . . . . . . . . . . . . . . . . 81.1.8. Operadores de asignacion . . . . . . . . . . . . . . . . . . . . . . . . . 91.1.9. Conversion de tipos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10

1.2. Control de flujo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111.2.1. if, if... else, if... else if . . . . . . . . . . . . . . . . . . . . . 111.2.2. Expresion condicional . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121.2.3. switch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 131.2.4. while/do... while . . . . . . . . . . . . . . . . . . . . . . . . . . . . 131.2.5. for . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 141.2.6. goto . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16

1.3. Funciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 161.3.1. Definicion de funciones . . . . . . . . . . . . . . . . . . . . . . . . . . . 161.3.2. Funciones tipo void . . . . . . . . . . . . . . . . . . . . . . . . . . . . 181.3.3. Funciones con parametros . . . . . . . . . . . . . . . . . . . . . . . . . 191.3.4. Sobrecarga . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 191.3.5. Parametros por valor y por referencia . . . . . . . . . . . . . . . . . . . 211.3.6. Variables tipo const . . . . . . . . . . . . . . . . . . . . . . . . . . . . 241.3.7. Parametros default . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 251.3.8. Alcance, visibilidad, tiempo de vida . . . . . . . . . . . . . . . . . . . . 261.3.9. Variables estaticas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 261.3.10. Recursion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27

1.4. Matrices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 281.4.1. Declaracion e inicializacion . . . . . . . . . . . . . . . . . . . . . . . . . 281.4.2. Matrices como parametros de funciones . . . . . . . . . . . . . . . . . . 291.4.3. Matrices multidimensionales . . . . . . . . . . . . . . . . . . . . . . . . 291.4.4. Matrices de caracteres: cadenas (strings) . . . . . . . . . . . . . . . . . 29

1.5. Punteros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31

iii

Page 4: PROGRAMACION Y M´ ETODOS NUM´ ERICOS´ … › ~vmunoz › homepage › cursos › ...El fin de una instrucci´on est´a determinado por el cierre de un par´entesis o por un punto

iv INDICE

1.6. Argumentos de main . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 321.7. Manejo de archivos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 331.8. Funciones matematicas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 361.9. Clases . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37

1.9.1. Definicion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 381.9.2. Variables miembros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 391.9.3. Funciones miembro; miembros publicos y privados . . . . . . . . . . . . 401.9.4. Creacion de header files . . . . . . . . . . . . . . . . . . . . . . . . . . 441.9.5. Constructor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 461.9.6. Destructor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 491.9.7. Matrices de clases . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 491.9.8. Constructor de copia . . . . . . . . . . . . . . . . . . . . . . . . . . . . 501.9.9. Sobrecarga de funciones . . . . . . . . . . . . . . . . . . . . . . . . . . 521.9.10. Sobrecarga de operadores . . . . . . . . . . . . . . . . . . . . . . . . . 531.9.11. Coercion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 541.9.12. Interaccion con la pantalla/teclado o archivos . . . . . . . . . . . . . . 56

1.10. Algunas clases utiles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 571.10.1. string . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 571.10.2. complex . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59

1.11. Templates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 591.11.1. Funciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 591.11.2. Clases . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 621.11.3. Standard Template Library (STL) . . . . . . . . . . . . . . . . . . . . . 65

1.12. Herencia . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 691.13. Make . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70

Page 5: PROGRAMACION Y M´ ETODOS NUM´ ERICOS´ … › ~vmunoz › homepage › cursos › ...El fin de una instrucci´on est´a determinado por el cierre de un par´entesis o por un punto

Capıtulo 1

Una breve introduccion a C++

version 14 junio 2013

El objetivo de este curso es entregar algunos elementos basicos de programacion cientıfica.Para ello hemos escogido el lenguaje de programacion C++ , un lenguaje compilado, moderno ymultiproposito. Los lenguajes interpretados (perl, python, ruby, etc.) pueden ser muy utilespara realizar tareas relacionadas con investigacion cientıfica, en particular por su facilidad deuso y la rapidez con la que se puede escribir el codigo y obtener resultados.

Sin embargo, cuando se trata de resolver un problema de mayor escala, un lenguajecompilado es lo adecuado. A pesar de que puede ser mas lento desarrollar el codigo, este, unavez compilado, es ejecutado mucho mas rapidamente.

En esta seccion entregaremos una descripcion breve de los principales elementos del len-guaje de programacion C++ , que posteriormente utilizaremos para resolver problemas deinteres en Fısica.

1.1. Estructura basica de un programa en C++

1.1.1. El programa mas simple

El primer ejemplo de todo manual es el que permite escribir “Hola” en la pantalla delcomputador. Utilizando cualquier editor de texto, creemos un archivo hola.cc, con el si-guiente contenido:

#include <iostream>

using namespace std;

int main(){

cout << "Hola." << endl;

return 0;

}

La primera lınea define las instrucciones adecuadas para enviar luego un mensaje a pan-talla, que estan contenidas en el header iostream. La funcion main es donde comienza aejecutarse el programa. Puede haber codigo antes o despues de la aparicion de main, pero

1

Page 6: PROGRAMACION Y M´ ETODOS NUM´ ERICOS´ … › ~vmunoz › homepage › cursos › ...El fin de una instrucci´on est´a determinado por el cierre de un par´entesis o por un punto

2 CAPITULO 1. UNA BREVE INTRODUCCION A C++

siempre debe haber una (y solo una) funcion main en nuestro programa. Luego, cout envıael mensaje a pantalla, y endl agrega un cambio de lınea al final del mensaje. Por ultimo, sedevuelve un valor entero al sistema operativo para indicarle que la ejecucion del programatermino correctamente (mas detalles sobre esto, mas adelante).

Como crear este archivo y como compilarlo para obtener un ejecutable, dependen delsistema operativo en el que trabajemos. En un sistema Linux, lo usual es que los archivosque contienen el codigo fuente tengan extension cc, y se compilan con la instruccion:

g++ hola.cc

Esto da como resultado un archivo a.out, que puede ser ejecutado con la instruccion:

./a.out

En el caso del codigo anterior, el resultado sera que, en pantalla, aparezca la palabra Hola.Normalmente se desea que el ejecutable tenga un nombre mas significativo que el generico

a.out. Para indicar el nombre del archivo de salida se usa el flag -o, de modo que

g++ -o hola hola.cc

da como resultado un archivo ejecutable hola en el directorio actual.

1.1.2. Haciendo el codigo mas legible

El codigo anterior es solo un ejemplo de como podrıa escribirse el archivo hola.cc. C++otorga amplia libertad al programador para escribir el codigo fuente. En efecto, los cambiosde lınea son equivalentes a espacios en blanco y caracteres de tabulacion. Por lo tanto,

return 0;

es equivalente a

return 0;

y a

return

0

;

El fin de una instruccion esta determinado por el cierre de un parentesis o por un puntoy coma, no por el fin de una lınea.

Tampoco es necesario que los parentesis cursivos vayan en la posicion del ejemplo:

int main(){ cout << "Hola." << endl;

return 0;}

Page 7: PROGRAMACION Y M´ ETODOS NUM´ ERICOS´ … › ~vmunoz › homepage › cursos › ...El fin de una instrucci´on est´a determinado por el cierre de un par´entesis o por un punto

1.1. ESTRUCTURA BASICA DE UN PROGRAMA EN C++ 3

habrıa funcionado igual de bien.Toda esta libertad, por supuesto, tiene su precio, ya que un codigo fuente formateado

con demasiada libertad puede terminar siendo inentendible para el propio autor, haciendo elcodigo difıcilmente reutilizable y por lo tanto, en la practica, inutil.

Nuestra recomendacion es que el programador encuentre el modo de escribir su codigoque sea mas logico y legible para sı mismo, y que mantenga esa estrategia consistentementeen todos sus archivos.

Otro modo de hacer el codigo mas legible es introduciendo comentarios. Hay dos modosde hacerlo en C++ . Para comentarios de una lınea, //, y para comentarios de mas de unalınea, el par /*-*/. Usando el ejemplo anterior:

/* En este codigo, enviamos la palabra ’Hola’ a pantalla.

Es un codigo muy breve, en realidad */

#include <iostream>

using namespace std;

// El programa esta a punto de comenzar

int main(){

cout << "Hola." << endl; // Aqui enviamos el mensaje

return 0; /* Esta es la ultima linea que

se ejecutara en nuestro codigo */

}

Todo lo que esta despues de un //, hasta el final de la lınea respectiva, y todo lo queesta entre un /* y un */ es ignorado por el compilador, de modo que este ultimo codigo escompletamente equivalente al primero.

Sin embargo, la legibilidad de un codigo puede aumentar considerablemente con el usoadecuado de comentarios, por lo tanto es otra practica que todo buen programador deberıatener.

En el ejemplo anterior tambien podemos notar que no hay ninguna necesidad de quelos comentarios esten en una lınea separada, lo cual permite al programador insertar loscomentarios del modo y en el punto del codigo que le parezca mas conveniente.

1.1.3. Nombres de variables

Como en todo lenguaje de programacion, en un codigo C++ se pueden definir variables, queluego pueden alojar valores determinados, que pueden o no ser modificados en el transcursodel programa. Estas variables pueden tener en principio cualquier nombre, pero naturalmentees recomendable que tengan nombres intuitivos, ya que a medida que hagamos codigos maslargos sera cada vez mas importante no perder tiempo en recordar que significa cada variable.Las unicas reglas para los nombres de variables son los siguientes:

– Deben comenzar con una letra.

Page 8: PROGRAMACION Y M´ ETODOS NUM´ ERICOS´ … › ~vmunoz › homepage › cursos › ...El fin de una instrucci´on est´a determinado por el cierre de un par´entesis o por un punto

4 CAPITULO 1. UNA BREVE INTRODUCCION A C++

– Mayusculas y minusculas son distintas.

– Pueden contener numeros.

– Pueden contener el sımbolo _ (underscore).

– Pueden tener longitud arbitraria.

– No pueden corresponder a una de las palabras reservadas de C++ (en el ejemplo sencillode la Sec. 1.1.1, encontramos, por ejemplo, include, int, main, return).

1.1.4. Tipos de variables

Las variables, en todo codigo, en cualquier lenguaje de programacion, pueden ser dedistintos tipos, segun las necesidades del programa en cuestion. Por ejemplo, puede habervariables que alojen numeros, texto, direcciones de memoria, etc. Los numeros, a su vez,podrıan ser por ejemplo enteros, reales o complejos. Distintos lenguajes de programaciontienen distintas reglas para manejar variables.

En particular, C++ es un lenguaje que, tecnicamente, se denomina en ingles strongly typed .Esto significa que cada variable tiene un tipo bien definido, el cual no puede ser cambiadoen ningun punto del programa. En particular, si una variable es de un tipo determinado nose puede intentar asignarle un valor que pertenece a otro tipo (por ejemplo, intentar asignarun numero real a una variable entera).1

Para conseguir lo anterior, todas las variables a usar deben ser declaradas de acuerdo a sutipo. Una vez declaradas pueden ser inicializadas Por ejemplo, si usamos una variable i quees un numero entero, debemos, antes de usarla, declararla, y solo entonces podemos asignarleun valor:

int i;

i=10;

No es posible, como dijimos, cambiar el tipo de una variable, de modo que si luegoqueremos que i sea un numero real con doble precision (double), el codigo

int i;

i=10;

double i;

es incorrecto, y el compilador reclamara que la variable i ya fue definida. De hecho, es-ta caracterıstica es tan estricta que ni siquiera intentar declararla nuevamente como int

esta permitido.Lo que sı esta permitido es reunir las acciones de declaracion e inicializacion en una misma

lınea:

1Existen diversas razones por las cuales uno sı querrıa asignar, por ejemplo, un numero real a un entero o

viceversa. C++ proporciona una protocolo especıfico para conseguirlo, pero lo importante es tener muy claro

que C++ espera que toda variable tenga un tipo conocido, y que solo se permiten aquellos cambios que esten

explıcitamente permitidos.

Page 9: PROGRAMACION Y M´ ETODOS NUM´ ERICOS´ … › ~vmunoz › homepage › cursos › ...El fin de una instrucci´on est´a determinado por el cierre de un par´entesis o por un punto

1.1. ESTRUCTURA BASICA DE UN PROGRAMA EN C++ 5

int i = 10;

Los tipos de variables disponibles son2:

int Enteros entre −215 = −32768 y 215 − 1 = 32767o entre −231 = −2147483648 y 231 − 1 = 2147483647

short int

short Enteros entre −215 y 215 − 1.long int

long Enteros entre −231 y 231 − 1.unsigned int Enteros entre 0 y 216 − 1

o entre 0 y 232 − 1.unsigned short Enteros entre 0 y 216 − 1.unsigned long Enteros entre 0 y 232 − 1.char Caracteres.float Reales en los intervalos [−1.7 · 1038,−0.29 · 10−38],

[0.29 · 10−38, 1.7 · 1038](Precision de unos 7 dıgitos decimales.)

double Reales en los mismos intervalos que float,pero con precision de 16 decimales,o en los intervalos [−0.9 · 10308,−0.86 · 10−308]y [0.86 · 10−308, 0.9 · 10308], con precision de15 decimales.

Al ver esta tabla, aunque sea referencial, podemos entender por que es conveniente quecada variable tenga su tipo bien definido. En efecto, al crear una variable se reserva un ciertoespacio de memoria que alojara el eventual valor que se le asigne (el valor 10, en el ejemplode mas arriba). Pero distintos valores exigen distintos tamanos de memoria, y ası por ejemploun entero requiere mucha menos memoria que un real de precision simple, y este menos queun real de precision doble. Sin embargo, si necesitamos que una variable i recorra los enterosde 1 a 10, por ejemplo, es un desperdicio de recursos reservar la misma memoria que si fuerannumeros reales. Por lo tanto, al declarar los tipos de cada variable, le decimos al programaque reserve la cantidad de memoria estrictamente necesaria para nuestros propositos. De estemodo, los recursos de la maquina utilizada, tanto en espacio de memoria ocupada como entiempo de ejecucion pueden ser optimizados.

Un ejemplo interesante es la version unsigned de los tipos de variable. Hay muchasocasiones en que sabemos que una variable entera debe ser siempre positiva (el numero deprotones en un nucleo atomico, el numero de letras en una palabra, etc.). Al usar el tipounsigned int, decimos que no requerimos guardar informacion sobre el signo del numeroentero, pequena optimizacion que puede no ser importante para programas sencillos, peroque puede hacer una gran diferencia en codigos mas complejos y que usan masivamente losrecursos de memoria de la maquina.

Todos los tipos de variables anteriores estan preparados para alojar numeros, con laexcepcion de char, que aloja caracteres. En su caso, la inicializacion es un poco diferente:

2Los valores de los rangos indicados son simplemente representativos y dependen de la maquina utilizada.

Page 10: PROGRAMACION Y M´ ETODOS NUM´ ERICOS´ … › ~vmunoz › homepage › cursos › ...El fin de una instrucci´on est´a determinado por el cierre de un par´entesis o por un punto

6 CAPITULO 1. UNA BREVE INTRODUCCION A C++

char c = ’a’;

Ademas de los caracteres usuales (mayusculas, minusculas, sımbolos como &, *, :, etc.,)hay algunos caracteres especiales (caracteres de escape) que es posible asignar a una variablechar. Ellos son:

newline \n

horizontal tab \t

vertical tab \v

backspace \b

carriage return \r

form feed \f

alert (bell) \a

backslash \\

single quote \’

double quote \"

Por ejemplo, la lınea:

cout << "Primera columna\t Segunda columna\n

Segunda linea" << endl;

produce el output

Primera columna Segunda columna

Segunda linea

Notar que el caracter ’\n’ tiene el mismo efecto que endl: ambos producen un cambio delınea. Sin embargo, ’\n’ siempre significa un cambio de lınea si esta dentro de una variabletipo char, en tanto que endl solo introduce un cambio de lınea si esta encadenado con uncout previo.

1.1.5. Ingreso de datos desde el teclado

Para ingresar datos desde el teclado basta usar la instruccion cin:

#include <iostream>

using namespace std;

int main(){

int i;

cout << "Ingrese un numero entero: ";

cin >> i;

cout << "El numero ingresado fue: " << i << endl;

return 0;

}

Page 11: PROGRAMACION Y M´ ETODOS NUM´ ERICOS´ … › ~vmunoz › homepage › cursos › ...El fin de una instrucci´on est´a determinado por el cierre de un par´entesis o por un punto

1.1. ESTRUCTURA BASICA DE UN PROGRAMA EN C++ 7

Este codigo escribe en pantalla el mensaje "Ingrese un numero entero: ", y quedaesperando que el usuario ingrese algo desde el teclado. Si el usuario ingresa un numero enteroy luego Enter, entonces dicho numero quedara asignado a la variable i.

Si se desea que el usuario ingrese mas de un numero desde el teclado, basta encadenarvarios >>. El usuario, por su parte, debe ingresarlos separados por espacios o por cambios delınea:

#include <iostream>

using namespace std;

int main(){

int i,j;

cout << "Ingrese dos numeros enteros: ";

cin >> i >> j;

cout << "Los numeros ingresados fueron: " << i << " y " << j << endl;

return 0;

}

Como dijimos, separar los numeros ingresados por espacios:

5 10

o por cambios de lınea:

5

10

es equivalente para cin. Lo que debemos imaginarnos es, primero, que espacios, tabulacionesy cambios de lınea son iguales para efectos de cin, y que uno o mas espacios en blanco sonequivalentes a uno solo. Y luego, que el ingreso de datos desde el teclado es como un flujo deelementos, separados entre sı por espacios en blanco.

Lo que hace cin, entonces, es atender a este flujo de informacion que ingresa desde elteclado, y cada vez que recibe un blanco (espacio/tabulacion/cambio de lınea) coloca elelemento recibido en la variable correspondiente (en el ejemplo anterior, el primer elementoen i, el segundo en j), hasta que se le acaban las variables.

Por lo tanto, si uno ingresa menos “palabras” que las que el codigo espera, el programase mantendra esperando desde el teclado hasta tener el input suficiente. Por el contrario, siuno ingresa mas “palabras” que las que el codigo espera, el programa solo considerara lasque necesita, y el resto se perdera. Ası, si en el codigo anterior ingresamos tres numeros:

5 10 15

el output sera el mismo que si el 15 se omite:

Los numeros ingresados fueron: 5 y 10

Page 12: PROGRAMACION Y M´ ETODOS NUM´ ERICOS´ … › ~vmunoz › homepage › cursos › ...El fin de una instrucci´on est´a determinado por el cierre de un par´entesis o por un punto

8 CAPITULO 1. UNA BREVE INTRODUCCION A C++

1.1.6. Operadores aritmeticos

Existen operadores para la suma, la resta, la multiplicacion, la division:

+ - * /

Otro operador util es %, que entrega el resto de una division por un entero:

int r = 10%3

deja en r el valor 1, pues ese es el resto de la division de 10 por 3.Observar que este operador nos permite determinar rapidamente si un numero es divisible

por otro (por ejemplo, si un numero es par, o una potencia de 10).Varios operadores pueden aparecer en una misma lınea de codigo, posiblemente con

parentesis:

int a=2+5*20-6+8/2*3+(2-5)*2;

Para poder calcular esto, el codigo se ejecutara de acuerdo a las siguientes reglas de prece-dencia:

1. Primero, se calculan las operaciones dentro de cada uno de los parentesis.

2. Luego, las multiplicaciones y divisiones.

3. Luego, las sumas y restas

4. Si hay varias operaciones con igual prioridad (varias multiplicaciones y divisiones, porejemplo), se calcularan desde izquierda a derecha.

Con estas reglas, el valor de a en el ejemplo anterior es 102.Todos los operadores, los operadores aritmeticos que acabamos de ver y otros que revi-

saremos mas adelante tienen una precedencia especıfica, de modo que el programa siempresabe en que orden debe ejecutar las operaciones para cualquier lınea de codigo. Por ejemplo,el operador % tiene la misma precedencia que * y /.

1.1.7. Operadores relacionales

Los sımbolos para las relaciones de igualdad, desigualdad, menor, menor o igual, mayory mayor o igual son:

== != < <= > >=

Para las relaciones logicas AND, OR y NOT:

&& || !

Estos operadores entregan un resultado 1 si la expresion es verdadera, 0 si es falsa. Porejemplo:

int i=3, j=2, k=1;

int v = (i==(j+k));

int f = (i==j);

Las lıneas anteriores son equivalentes a v=1 y f=0.

Page 13: PROGRAMACION Y M´ ETODOS NUM´ ERICOS´ … › ~vmunoz › homepage › cursos › ...El fin de una instrucci´on est´a determinado por el cierre de un par´entesis o por un punto

1.1. ESTRUCTURA BASICA DE UN PROGRAMA EN C++ 9

1.1.8. Operadores de asignacion

El signo = ya lo hemos usado abundantemente y sabemos lo que la siguiente instruccionhace:

int i = 1;

int j = i;

Pero analicemos esto con mas detalle.Para hacerlo, notemos que cada variable en nuestro programa, representa una direccion

de memoria, en la cual se aloja un cierto valor. Ahora, entonces, entendemos que = es unoperador, que toma el valor de la variable que esta a su derecha (1 en ambas lıneas delejemplo), y lo coloca en la direccion de memoria de la variable que esta a su izquierda. Estoes lo que denominamos asignacion simple.

Notemos, en particular, que en la primera lınea se usa la direccion de memoria asociadaa i, y en la segunda se usa el valor contenido en dicha direccion de memoria.

Esto es interesante, porque es justamente lo que permite que una expresion como

x = x + 2

tenga sentido, aunque matematicamente no lo tenga. En efecto, a la derecha, x participacomo el valor contenido en ella, y a la izquierda participa como su direccion de memoria.

Expresiones como la anterior se utilizan habitualmente en programacion, y por ello enC++ existe un operador especial para ella, =*. As\’{\i}, \verb*x=x2* se puede reemplazarpor

x += 2

Esto se denomina asignacion compuesta. Existen varios operadores analogos: Existen losoperadores

+= -= *= /=

Aun mas habitual que lo anterior es cuando se desea incrementar una variable en 1:

x = x + 1

Esto se puede reescribir con el operador:

x += 1

o bien con el operador de incremento:

x++

Analogamente, existe el operador --, equivalente a x -= 1 o a x = x - 1.Los operadores de incremento o decremento se pueden utilizar como prefijos o sufijos .

Observemos el siguiente codigo:

int i=1;

int j=i++;

int k=++i;

Page 14: PROGRAMACION Y M´ ETODOS NUM´ ERICOS´ … › ~vmunoz › homepage › cursos › ...El fin de una instrucci´on est´a determinado por el cierre de un par´entesis o por un punto

10 CAPITULO 1. UNA BREVE INTRODUCCION A C++

Al cabo de estas tres lıneas de codigo, i tiene el valor 3, claramente, porque fue incrementadoen 1 dos veces. Pero la segunda y tercera lınea son muy diferentes. En la segunda, el operador* aparece como sufijo, por lo tanto primero se hace la asignacion simple (j toma el valoractual de i), y a continuacion i es incrementado en 1. En la tercera lınea, en cambio, * esprefijo: primero se hace el incremento de i, y luego se hace la asignacion a k. Como resultado,al final del codigo i vale 3, j vale 1 y k vale 3.

1.1.9. Conversion de tipos

C++ es estricto respecto a los tipos de las variables, y eso significa, en particular, que lasoperaciones matematicas entre variables deben ser tambien entre variables de un mismo tipo:se deben sumar int entre sı, double entre sı, etc. Sin embargo, es evidente que se necesitauna manera de obviar esta dificultad, porque matematicamente tiene sentido sumar enteroscon reales.

Observemos, por ejemplo, el siguiente codigo:

int i = 3;

float x = 43.8;

cout << "Suma = " << x + i << endl;

Aquı el computador debe sumar dos variables de tipos distintos. Como solo se pueden sumarentre sı variables del mismo tipo, para que el codigo tenga sentido debe primero debenconvertirse ambas a un tipo comun antes de efectuar la suma. Existen dos modos de proceder:

a) Conversion explıcita.

Se puede convertir la variable i a una tipo float, con float(i):

int i = 3;

float x = 43.8;

cout << "Suma = " << x + float(i) << endl;

Naturalmente, tambien serıa posible convertir x en una variable tipo int:

int i = 3;

float x = 43.8;

cout << "Suma = " << int(x) + i << endl;

Esto, sin embargo, harıa perder precision, ya que int(x) va a tomar solo la parte enterade x (43). Normalmente, no es lo que queremos, pero el lenguaje nos da la posibilidadde hacerlo si lo deseamos.

La conversion explıcita es una solucion que puede ser bastante tediosa, por cuantoel programador debe realizar el trabajo de conversion personalmente cada vez que senecesita.

Page 15: PROGRAMACION Y M´ ETODOS NUM´ ERICOS´ … › ~vmunoz › homepage › cursos › ...El fin de una instrucci´on est´a determinado por el cierre de un par´entesis o por un punto

1.2. CONTROL DE FLUJO 11

b) Conversion implıcita.

El compilador realiza las conversiones de modo automatico, prefiriendo siempre la con-version desde un tipo de variable de menor precision a uno de mayor precision (de inta double, de short a int, etc.).

Es interesante notar como las conversiones implıcitas de tipos pueden tener consecuen-cias insospechadas.

Consideremos las tres expresiones:

i) x = (1/2) * (x + a/x) ;

ii) x = (0.5) * (x + a/x) ;

iii) x = (x + a/x)/2 ;

Si inicialmente x=0.5 y a=0.5, por ejemplo, i) entrega el valor x=0, mientras ii) y iii)entregan el valor x=0.75. Lo que ocurre es que 1 y 2 son enteros, de modo que 1/2 = 0.De acuerdo a lo que dijimos, uno esperarıa que en i), como conviven numeros realescon enteros, los numeros enteros fueran convertidos a reales y, por tanto, la expresiontuviera el resultado esperado, 0.75. El problema, sin embargo, es la prioridad de lasoperaciones (ver Sec. 1.1.6).

En efecto, en i), la primera operacion es 1/2, una division entre enteros, i.e. cero. Enii) no hay problema, porque todas son operaciones entre reales. Y en iii) la primeraoperacion es el parentesis, que es una operacion entre reales. Al dividir por 2 este esconvertido a real antes de calcular el resultado.

i) aun podrıa utilizarse, cambiando el prefactor del parentesis a 1.0/2.0, una practicaque serıa conveniente adoptar como standard cuando queremos utilizar enteros dentrode expresiones reales, para evitar errores que pueden llegar a ser muy oscuros.

1.2. Control de flujo

En principio, un codigo se podrıa ejecutar desde la primera hasta la ultima lınea, sinalteraciones. Sin embargo, tal codigo serıa muy limitado, pues harıa siempre lo mismo. Porlo tanto, todo lenguaje de programacion debe tener un modo de alterar el flujo de ejecucion,dependiendo de que ciertas condiciones sean verdaderas o falsas. Ello permite adaptar elmismo codigo a diversas situaciones y hacerlo, por ende, mas util. Las siguientes estructurasde control de flujo estan disponibles en C++ .

1.2.1. if, if... else, if... else if

a) Esta es la version mas sencilla: si una cierta expresion es verdadera, ejecuta una deter-minada porcion de codigo. Si es falsa, la ejecucion prosigue a continuacion del parentesisde cierre }.

Page 16: PROGRAMACION Y M´ ETODOS NUM´ ERICOS´ … › ~vmunoz › homepage › cursos › ...El fin de una instrucci´on est´a determinado por el cierre de un par´entesis o por un punto

12 CAPITULO 1. UNA BREVE INTRODUCCION A C++

if (a==b){

cout << "a es igual a b" << endl;

}

b) En este caso, si la expresion es falsa se ejecuta el codigo entre los parentesis asociadosa else.

if (c!=d){

cout << "c es distinto de d" << endl;

}

else{

cout << "c es igual a d" << endl;

}

c) En este tercer caso, si la primera expresion es falsa, se verifica si una segunda expresiones verdadera o falsa. Puede haber una cantidad arbitraria de else if a continuacion,para verificar sendas condiciones adicionales. El ultimo else, que es opcional, se ejecutasolo si ninguna de las expresiones anteriores resulto verdadera.

if (e > f){

cout << "e es mayor que f" << endl;

}

else if (e== f){

cout << "e es igual a f" << endl;

}

else{

cout << "e es menor que f" << endl;

}

1.2.2. Expresion condicional

Estructuras del tipo:

if (a==b){

c = 1;

}

else{

c = 0;

}

son muy habituales. Por ello, C++ ofrece una expresion abreviada equivalente, la expresion

condicional . En efecto, la estructura anterior se puede reemplazar por

c = (a==b) ? 1 : 0;

(a==b) puede ser reemplazada por cualquier expresion que pueda ser verdadera o falsa.1 y 0 pueden ser reemplazados por cualquier expresion que devuelva un numero del mismotipo que c. Podrıan ser porciones mas complicadas de codigo, o incluso funciones:

Page 17: PROGRAMACION Y M´ ETODOS NUM´ ERICOS´ … › ~vmunoz › homepage › cursos › ...El fin de una instrucci´on est´a determinado por el cierre de un par´entesis o por un punto

1.2. CONTROL DE FLUJO 13

c = verdadero_o_falso()?funcion_si_verdadero():funcion_si_falso()

La expresion condicional no solo permite abreviar codigo, sino que su ejecucion es tambienmas eficiente.

1.2.3. switch

Esta expresion permite seleccionar una accion a realizar dependiendo del valor de unavariable entera. Por ejemplo, si dicha variable es i, la siguiente estructura escribe en pantalla"Caso 1" si i=1, "Caso 2" si i=2, y "Otro caso" si i tiene cualquier otro valor:

switch (i){

case 1:

{

cout << "Caso 1." << endl;

}

break;

case 2:

{

cout << "Caso 2." << endl;

}

break;

default:

{

cout << "Otro caso." << endl;

}

break;

}

La instruccion break permite que la ejecucion del programa salte a la lınea siguientedespues de la serie de instrucciones asociadas a switch. Por ejemplo, si no existieran losbreak, e i=1, entonces verıamos en pantalla las lıneas Caso 1., Caso 2. y Otro caso. Lainstruccion default es opcional. Podemos observar que el ultimo break es innecesario, perolo hamos puesto por completitud y consistencia con el resto de la estructura.

Notar que la expresion anterior es equivalente a una estructura if/else if. Pero eso sedebe a la presencia de los break. Si no estuvieran, como se indico en el parrafo anterior, laejecucion continuarıa durante todo el resto del codigo contenido dentro de switch, lo cualpuede ser precisamente lo que queremos, y que serıa mas difıcil de hacer con if/else if.

1.2.4. while/do... while

Otro tipo de situacion usual es tener que ejecutar una seccion del codigo mientras algunacondicion se cumpla. Esto se hace con while. Por ejemplo:

int i=0;

while (i < 3){

Page 18: PROGRAMACION Y M´ ETODOS NUM´ ERICOS´ … › ~vmunoz › homepage › cursos › ...El fin de una instrucci´on est´a determinado por el cierre de un par´entesis o por un punto

14 CAPITULO 1. UNA BREVE INTRODUCCION A C++

cout << i << endl;

i++;

}

envıa a pantalla los numeros 0, 1, 2 antes de detenerse.A veces deseamos que el codigo a repetir se ejecute siempre al menos una vez, en cuyo

caso es mejor poner la verificacion de la condicion al final. Esto se hace con do... while:

int i=3;

do{

cout << i << endl;

i++;

} while (i < 3)

En este caso, se alcanza a enviar a pantalla el numero 3, porque primero se ejecuta el codigo yluego se estudia la condicion. Si se hubiera puesto la condicion al principio, con while(i<3),esta porcion de codigo no se habrıa ejecutado y no habrıa habido salida a pantalla.

1.2.5. for

La estructura mas general para repetir codigo en un programa C++ esta dada por for,que necesita tres argumentos: una condicion de inicio, una condicion de continuacion y unainstruccion a ejecutar al final de cada iteracion, antes de comenzar la siguiente. Por ejemplo,si queremos mostrar en pantalla todos los numeros enteros entre 1 y 9, podemos hacerlo ası:

for (i = 1; i < 10; i++){

cout << "Valor del indice: " << i << endl;

}

Los argumentos de for pueden tener muchas formas, lo cual da a for una enorme flexibilidad.Por ejemplo, notemos que en el codigo anterior la variable i debio haber sido necesariamentedeclarada antes del for. Pero es posible, en el primer argumento, declarar las variables quese usaran al interior del for, las cuales seran locales al mismo:

int i=-3;

for (int i=1; i < 10; i++){

cout << "Valor del indice: " << i << endl;

}

cout << "Valor de i: " << i << endl;

En este caso, la variable i que esta dentro de for se incrementa en 1 cada vez, y a la salidadel for se imprime el valor -3, que nunca fue alterado.

Pero tambien podrıa hacerse un for usando dos variables, las cuales pueden ser ambaslocales, la condicion de termino puede (o no) involucrar a ambas, y la instruccion a ejecutaral final de cada iteracion puede (o no) involucrar a ambas tambien. Por ejemplo:

for (int i=0, k=20; (i<10) && (k<100); i++, k+=6){

cout << "i + k = " << i + k << endl;

}

Page 19: PROGRAMACION Y M´ ETODOS NUM´ ERICOS´ … › ~vmunoz › homepage › cursos › ...El fin de una instrucci´on est´a determinado por el cierre de un par´entesis o por un punto

1.2. CONTROL DE FLUJO 15

Se inicializan dos variables, i a cero, k a 20. En cada iteracion se imprime la suma de ellas,y luego se incrementa i en 1 y k en 6. El proceso se repite mientras i sea menor que 10 y k

sea menor que 100.Es claro que for puede reemplazar a while en general. En efecto, el codigo de ejemplo

que se mostro en la Sec. 1.2.4 es equivalente a

for (int i=0; i < 3; i++){

cout << "Valor del indice: " << i << endl;

}

for es tan general, que cualquiera de sus argumentos puede ser vacıo. Por ejemplo, elcodigo anterior es equivalente a

int i=0;

for(; i < 3; i++){

cout << "Valor del indice: " << i << endl;

}

donde el primer argumento no se ha usado porque fue reemplazado por una instruccionanterior.

Y tambien es equivalente a

for(int i=0; i < 3;){

cout << "Valor del indice: " << i << endl;

i++;

}

Aquı, la instruccion a ejecutar al final de cada iteracion ha sido puesta al final del ciclo for, locual tiene el mismo efecto. Naturalmente, tambien podrıa haberse omitido simultaneamenteel primer argumento:

int i=0;

for(; i < 3;){

cout << "Valor del indice: " << i << endl;

i++;

}

Omitir el segundo argumento tambien es posible, pero en ese caso, al no haber ningunacondicion para que el ciclo continue o se detenga, el codigo se ejecutara infinitas veces:

for(int i=0;;i++){

cout << "Valor del indice: " << i << endl;

}

Esto imprimira todos los numeros, sin detenerse nunca (a menos que reciba una senal delsistema operativo), desde 0 en adelante.

Un uso mas util de for es introducir una pausa en un codigo, durante suficientes itera-ciones para que el codigo no avance, y tengamos, por ejemplo, tiempo de leer los resultadosescritos en pantalla. En este caso, el cuerpo del for puede estar vacıo, pues solo se deseaincrementar el valor del contador muchas veces:

Page 20: PROGRAMACION Y M´ ETODOS NUM´ ERICOS´ … › ~vmunoz › homepage › cursos › ...El fin de una instrucci´on est´a determinado por el cierre de un par´entesis o por un punto

16 CAPITULO 1. UNA BREVE INTRODUCCION A C++

for(int i=0;i<1000000;i++){

}

Un caso extremo de la ausencia de todo argumento en el for serıa el siguiente ciclo infinito:

for(;;);

1.2.6. goto

Existe tambien en C++ una instruccion goto que permite saltar de un punto a otrodel programa (goto salto; permite saltar a la lınea que contiene la instruccion salto:).Sin embargo, se considera una mala tecnica de programacion usar goto, y siempre se puededisenar un programa evitandolo. Altamente no recomendable.

1.3. Funciones

Las funciones nos permiten programar partes del procedimiento por separado. De modoanalogo a las variables, las funciones deben ser primero declaradas y luego implementadas . Ladeclaracion define el nombre de la funcion (que tiene las mismas restricciones que el nombrede las variables vistas en la Sec. 1.1.3), los argumentos de la funcion y el tipo de retorno (sise espera que el resultado de la funcion sea un entero, real de precision simple o doble, etc.).La implementacion es el codigo que debe ejecutarse cada vez que se llama la funcion.

1.3.1. Definicion de funciones

Consideremos un ejemplo muy sencillo, de una funcion que solo entrega el valor de unacierta variable entera:

#include <iostream>

using namespace std;

int f(){

int i=2;

return i;

}

int main(){

int j = f();

cout << "El valor de la variable i es " << j << endl;

cout << "El valor de la variable i es " << f() << endl;

return 0;

}

f() es una funcion que no espera argumentos (notar que, aunque no tiene argumentos, losparentesis son obligatorios) y entrega como resultado un numero entero. Este resultado se

Page 21: PROGRAMACION Y M´ ETODOS NUM´ ERICOS´ … › ~vmunoz › homepage › cursos › ...El fin de una instrucci´on est´a determinado por el cierre de un par´entesis o por un punto

1.3. FUNCIONES 17

entrega a traves de la instruccion return, que puede tener a continuacion una constante, unavariable, una operacion, o incluso otra funcion:

return 2;

return i;

return 5+j;

return otra_funcion();

Naturalmente, lo que sea que haya luego de return debe ser algo que tenga el mismo tipoque la funcion.

Una vez que la funcion se termina, su valor de retorno queda disponible para ser usadopor el codigo que la llamo. En nuestro ejemplo, f() se usa de dos modos: o bien se asignasu resultado a una variable entera, o bien se envıa directamente a pantalla. En cualquiera delos dos casos, se obtiene el mismo resultado: en pantalla aparece el mensaje:"El valor de la variable i es 2".

En el ejemplo anterior, la funcion fue declarada e implementada simultaneamente. Tam-bien es posible, al igual que con las variables, separar la declaracion y la implementacion.Ası, el ejemplo anterior puede reescribirse:

#include <iostream>

using namespace std;

int f();

int main(){

int j = f();

cout << "El valor de la variable i es " << j << endl;

cout << "El valor de la variable i es " << f() << endl;

return 0;

}

int f(){

int i=2;

return i;

}

La implementacion no solo esta separada de la declaracion, sino que incluso esta des-

pues de main, que es donde se usa. Podrıamos haber colocado la implementacion entre ladeclaracion de f() y el comienzo de main, pero esto nos permite observar lo siguiente:

Para que la compilacion sea exitosa, lo unico que necesita el compilador es que las funcio-nes esten declaradas antes de usarlas o implementarlas. La razon es que el compilador solonecesita verificar que una funcion con ese nombre (f() es este caso) existe, y que tiene losargumentos y el tipo de retornos correctos. En nuestro ejemplo, la primera lınea en main

intenta asignar el resultado de f() a una variable int, por lo tanto el compilador necesitaverificar que existe una funcion f() que devuelve una variable int.

Page 22: PROGRAMACION Y M´ ETODOS NUM´ ERICOS´ … › ~vmunoz › homepage › cursos › ...El fin de una instrucci´on est´a determinado por el cierre de un par´entesis o por un punto

18 CAPITULO 1. UNA BREVE INTRODUCCION A C++

Luego, es al ejecutar el codigo que se necesita realmente la implementacion de f(), porquees en ese momento que el programa necesita saber que es lo que hace f(). Por eso necesitamosponer la declaracion de f() antes de main, y podemos poner su implementacion despues demain.3

Con todo lo dicho, entendemos ahora claramente que main() es una funcion, como cual-quier otra del lenguaje, salvo porque es una palabra reservada, y no puede haber otra funcionmain() en nuestro codigo. Pero aparte de eso, de todos los ejemplos anteriores se deduce quemain() es una funcion, que es llamada sin argumentos, y que debe devolver un entero, int.Ese valor es devuelto al sistema operativo, que toma dicho valor para detectar si el codigo seha ejecutado correctamente o no, y por eso se requiere una instruccion del tipo return 0;

al final de main().

Por ultimo, hacemos notar que la variable i en el ejemplo anterior es local a la funcion.Si hubiera otra variable i en main, no se interferirıan.

1.3.2. Funciones tipo void

Un caso especial de funciones es aquel en que el programa que llama la funcion no esperaque esta le entregue ningun valor al terminar. Por ejemplo, digamos que en nuestro sencilloprograma que dice Hola en pantalla, Sec. 1.1.1, delegamos el envıo de dicho mensaje a unafuncion. La funcion no necesita argumentos, pero tampoco tiene que devolver nada a main,porque solo envıa un mensaje a pantalla. Pero entonces esta funcion no tendrıa tipo deretorno, lo cual impedirıa verificar que la funcion es usada correctamente cuando se llame(ya que, como dijimos, esa verificacion implica la consistencia del nombre de la funcion, susargumentos y su tipo de retorno). Para solucionar esto, C++ tiene un tipo especial de retornopara aquellas funciones que no devuelven nada: void.

En el ejemplo citado, podemos reescribir el codigo de la siguiente manera:

#include <iostream>

using namespace std;

void PrintHola();

int main(){

PrintHola();

return 0;

}

void PrintHola(){

cout << "Hola." << endl;

}

3Mas adelante, en la Sec. 1.13, refinaremos estos conceptos.

Page 23: PROGRAMACION Y M´ ETODOS NUM´ ERICOS´ … › ~vmunoz › homepage › cursos › ...El fin de una instrucci´on est´a determinado por el cierre de un par´entesis o por un punto

1.3. FUNCIONES 19

1.3.3. Funciones con parametros

Las funciones de los ejemplos anteriores entregan siempre el mismo resultado, ya queejecutan el mismo codigo incondicionalmente, siempre de la misma manera y con los mismosvalores de las variables involucradas.

Por supuesto, tambien es posible definir funciones que acepten parametros de entrada.Tambien ejecutaran el mismo codigo cada vez que sean llamadas, pero su resultado depen-dera en general de los parametros que se le entreguen.

Por ejemplo, podemos definir una funcion para encontrar la pendiente de una recta quepasa por los puntos (x1, y1) y (x2, y2). Sus argumentos deberıan ser cuatro numeros reales, ysu resultado debe ser otro numero real:

#include <iostream>

using namespace std;

double pendiente(double,double,double,double);

int main(){

cout << "La pendiente de la recta que pasa por (3.0,-2.5) y

(11.1,2.25) es " << pendiente(3.0,-2.5,11.1,2.25) << endl;

return 0;

}

double pendiente(double x1,double x2,double y1,double y2){

return (x2-x1)/(y2-y1);

}

Observar como en la declaracion solo se indican cuantos parametros tiene la funcion, yde que tipo debe ser cada uno. Ni siquiera se necesita el nombre de cada parametro. Eso serequiere en la implementacion de la funcion.

Es importante notar que el nombre de los parametros es local a la definicion de la funcion,lo cual permite no preocuparse de posibles conflictos entre el codigo que implementa unafuncion y el resto del codigo. Podrıa haber otra variable x1 en el codigo, sin problemas.

1.3.4. Sobrecarga

Como C++ es muy estricto respecto a los tipos de las variables, si deseamos definir lapendiente para puntos cuyas coordenadas son enteras, entonces tendremos que definir unanueva funcion que acepte numeros enteros:

double pendiente2(int,int,int,int);

double pendiente2(int x1,int x2,int y1,int y2){

return (x2-x1)/(y2-y1);

}

Page 24: PROGRAMACION Y M´ ETODOS NUM´ ERICOS´ … › ~vmunoz › homepage › cursos › ...El fin de una instrucci´on est´a determinado por el cierre de un par´entesis o por un punto

20 CAPITULO 1. UNA BREVE INTRODUCCION A C++

Notemos que el tipo de retorno debe seguir siendo double.Naturalmente, esto no es muy conveniente, porque ademas deberıamos definir una tercera

funcion en caso de que solo el segundo argumento sea entero, una cuarta en caso de que soloel segundo y el tercero, y un aburrido etcetera.

Afortunadamente esto es innecesario, porque el compilador convertira automaticamenteentre int y double cuando lo necesite. Entonces, habiendo definido solodouble pendiente(double,double,double,double), si la llamamos con uno o mas de susargumentos tipo int, el compilador promovera dicho int a double, y luego llamara la funcionpendiente correctamente.

Sin embargo, este comportamiento solo es posible si el compilador sabe convertir de untipo a otro. ¿Que sucede si eso no es posible, ya sea porque no hay una regla escrita paraconvertir de un tipo a otro, o porque no existe una unica manera de hacerlo, o porque noexiste una manera matematicamente correcta de hacerlo, etc.?

Por ejemplo, consideremos la funcion “potencia”, que eleva un numero a cierto exponente.Podrıamos definir una funcion potencia, en la forma:

double potencia(double,int);

El primer argumento es la base de la potencia, el segundo (en nuestro ejemplo, un entero), elexponente. Si se invoca a potencia(5.3,4), el codigo de implementacion deberıa entregar elresultado de multiplicar 5.3 cuatro veces por sı mismo. Pero ¿que sucede si queremos definirla potencia de una matriz cuadrada? Es una operacion perfectamente valida, pero no existeuna manera de convertir un entero en una matriz, y por lo tanto no deberıamos esperar —yno sucede— que el compilador tenga un modo de conversion automatico entre estos tipos devariable.4

En este caso, definir una segunda funcion sera imprescindible. Tendremos entonces unafuncion para encontrar la potencia de un real:

potencia(5, 2)

y otra funcion para calcular la potencia de una matriz. Simbolicamente:

potencia2

((

2 3−4 2

)

, 2

)

Aunque esta es una solucion posible, no es nada de elegante, porque, matematicamente,no hay ninguna diferencia profunda entre elevar un real o una matriz a una potencia, y dehecho se usa la misma notacion para ambas:

52 ,

(

2 3−4 2

)2

.

¿No serıa mas conveniente, entonces, usar el mismo nombre para ambas funciones? Bueno,ello es en efecto posible, y este procedimiento se llama sobrecargar una funcion, y se lograsimplemente declarando ambas funciones con el mismo nombre. Uno podrıa tener, entonces,

4Si uno tiene un real x, uno podrıa pensar en multiplicar x por la matriz identidad, pero no hay una

manera unica de hacerlo, porque la dimension de la matriz aun estarıa indeterminada.

Page 25: PROGRAMACION Y M´ ETODOS NUM´ ERICOS´ … › ~vmunoz › homepage › cursos › ...El fin de una instrucci´on est´a determinado por el cierre de un par´entesis o por un punto

1.3. FUNCIONES 21

varias versiones de la misma funcion, distinguidas entre sı solo por su tipo de retorno, elnumero y/o el tipo de sus argumentos. Cada vez que la funcion sea llamada, el compiladorverificara si hay alguna version de la funcion que sea compatible con esa llamada, y esa sera laversion que se ejecutara. Por ejemplo, si deseamos tener una funcion que envıe un mensajediferente dependiendo de si una variable es entera o no, podrıamos crear el siguiente codigo:

#include <iostream>

using namespace std;

void mensaje(int);

void mensaje(double);

int main(){

int i=3;

double r=6.3;

mensaje(i);

mensaje(r);

return 0;

}

void mensaje(int j){

cout << "La variable " << j << "es entera." << endl;

}

void mensaje(double j){

cout << "La variable " << j << "es real de doble precision." << endl;

}

Se dice que la funcion mensaje() es polimorfica, ya que adquiere distintas expresiones de-pendiendo de como es llamada.

Es importante notar que la sobrecarga de funciones es solo posible justamente porque C++es estricto en cuanto al control de tipos de variables. Solo ası es posible resolver la ambiguedadde tener funciones “distintas” que se llamen igual.

1.3.5. Parametros por valor y por referencia

Cada vez que se declara una variable, ello implica reservar un cierto espacio de memoria.Luego, cuando se inicializa, se aloja un determinado valor en dicho espacio de memoria.Cuando se llama a una funcion con argumentos, se crean copias de dichos argumentos, loscuales son usados al interior de la funcion. Al terminar la funcion, dichos espacios de memoriason liberados. Esquematicamente:

Page 26: PROGRAMACION Y M´ ETODOS NUM´ ERICOS´ … › ~vmunoz › homepage › cursos › ...El fin de una instrucci´on est´a determinado por el cierre de un par´entesis o por un punto

22 CAPITULO 1. UNA BREVE INTRODUCCION A C++

1964

k

1964

k

1964

k

1964

kint main(){

k = 1964;

...

f(k);

fmain

}

A este procedimiento se le llama pasar los argumentos de la funcion por valor .

Como en cada llamada de la funcion se realiza una copia del contenido de la variableoriginal en otra direccion de memoria, los argumentos son locales a dicha funcion, y por esose puede usar el mismo nombre sin conflictos entre el cuerpo de f y de main. Por ejemplo,si dentro de la funcion se incrementa el valor del parametro, nada sucede con la variableoriginal. En el ejemplo siguiente, la salida a pantalla sera 1964:

#include <iostream>

using namespace std;

void f(int);

int main(){

int k=1964;

f(k);

cout << k << endl;

return 0;

}

void f(int k){

k++;

}

Esquematicamente:

Page 27: PROGRAMACION Y M´ ETODOS NUM´ ERICOS´ … › ~vmunoz › homepage › cursos › ...El fin de una instrucci´on est´a determinado por el cierre de un par´entesis o por un punto

1.3. FUNCIONES 23

1964

k

1964

k

1964

k

1964

k

1964

k

1965

k

int main(){

k = 1964;

...

f(k);

fmain

}

k++;

void f(int k){

}

Por todas estas razones, pasar argumentos por valor parece una estrategia muy razonable,pero ¿que pasa si queremos llamar una funcion con un argumento matricial? ¿Una matrizcuadrada de 106×106? En este caso, es evidente que es un modo muy ineficiente de proceder,porque se va gastar una cantidad enorme de memoria para albergar la copia de la matriz, yuna gran cantidad de tiempo en llenar dicha copia con los contenidos originales. El problemaes claro cuando queremos hacer calculos de grandes proporciones. En este caso, lo adecuadoes usar otra estrategia: pasar los argumentos por referencia. Esto significa que no se hace unacopia de la variable, sino que se le entrega a la funcion la direccion de memoria de la variableoriginal. De este modo, la funcion no necesita duplicar la informacion, sino que sabe dondebuscarla, ahorrando todo ese tiempo de creacion de memoria y de copia de contenidos.

Para hacerlo, basta incluir en la declaracion de la funcion el sımbolo &:

#include <iostream>

using namespace std;

void f(int &);

int main(){

int k=1964;

f(k);

cout << k << endl;

return 0;

}

void f(int & k){

k++;

}

Page 28: PROGRAMACION Y M´ ETODOS NUM´ ERICOS´ … › ~vmunoz › homepage › cursos › ...El fin de una instrucci´on est´a determinado por el cierre de un par´entesis o por un punto

24 CAPITULO 1. UNA BREVE INTRODUCCION A C++

En este caso no hay ninguna duplicacion de memoria, lo cual resulta muy conveniente paraahorrar recursos computacionales, de memoria y tiempo de ejecucion.

Junto con las ventajas mencionadas, los parametros pasados por referencia pueden sermodificados, precisamente porque la funcion tiene acceso a la direccion de memoria de lavariable original, y no a una copia:

1965

k

1964

k

1964

k

1964

k

1964

k

1965

k

1965

k

int main(){

k = 1964;

...

f(k);

fmain

}

k++;

void f(int & k){

}

Un uso tıpico del paso de parametros por referencia es intercambiar entre sı el valor dedos variables, digamos a1=1 y a2=3. Luego de ejecutar la funcion queremos que a1=3 y a1=1,es decir, precisamente que el valor de las variables originales cambie. El uso de parametrospor referencia es la tecnica a usar en esta situacion.

1.3.6. Variables tipo const

Por otra parte, esta capacidad se puede convertir en un riesgo, ya que es en la declaracionde la funcion que se determina si un argumento se pasa por referencia o no, no habiendo modode distinguirlo en la llamada. Por lo tanto, una variable podrıa ser modificada por error.Para evitar este problema, cada vez que se desea definir una funcion con parametros pasadospor referencia, y asegurarse al mismo tiempo que dichos parametros no sean modificados alinterior de la funcion, se puede anteponer la palabra reservada const:

#include <iostream>

using namespace std;

void f(const int &);

int main(){

int k=1964;

f(k);

Page 29: PROGRAMACION Y M´ ETODOS NUM´ ERICOS´ … › ~vmunoz › homepage › cursos › ...El fin de una instrucci´on est´a determinado por el cierre de un par´entesis o por un punto

1.3. FUNCIONES 25

cout << k << endl;

return 0;

}

void f(const int & k){

...

}

En este caso, el codigo de implementacion solo puede tener instrucciones que no modifiquenel parametro k. Una instruccion como k++ dara un error de compilacion.

const, de hecho, se puede usar antepuesta a la declaracion de cualquier variable, no solo losargumentos de funciones. Al declarar una variable tipo const, el compilador se asegurara deque nunca se intente cambiar su valor. Por ejemplo, si queremos definir el valor de π ennuestro codigo, para mayor comodidad, podemos declararla ası:

const double pi = 3.14159

1.3.7. Parametros default

Hay ocasiones en que deseamos definir una funcion que tome uno o mas parametros pordefecto, es decir, que si no los especificamos, asuman determinados valores predeterminados.Por ejemplo, consideremos las raıces:

√5, 3√5. Si no lo indicamos, se supone que es el numero

tal que elevado al cuadrado nos da el subradical. Si hicieramos una funcion en C++ , nosgustarıa entonces llamarla como raiz(5), en cuyo caso se entiende que es la raız cuadrada,o, digamos, como raiz(5,3), en cuyo caso el segundo argumento indica el grado de la raız.

En C++ esto se hace en la declaracion, del siguiente modo:

double raiz(double,int = 2);

int main()

{

double r1 = raiz(5);

double r2 = raiz(5,3);

...

return 0;

}

double raiz(double x,int n){

...

}

Observar que es en la declaracion solamente que se indica el valor default, no en la imple-mentacion.

Las funciones pueden tener un numero arbitrario de parametros default. La unica restric-cion es que si los hay, todos deben estar al final de la lista de parametros. Por ejemplo, si unafuncion tiene 5 parametros, tres de los cuales son opcionales, una declaracion posible serıa:

Page 30: PROGRAMACION Y M´ ETODOS NUM´ ERICOS´ … › ~vmunoz › homepage › cursos › ...El fin de una instrucci´on est´a determinado por el cierre de un par´entesis o por un punto

26 CAPITULO 1. UNA BREVE INTRODUCCION A C++

double f(int,int,double = 0.0,double=1.0,int=-3);

De este modo, todas las siguientes formas de llamar a la funcion son aceptables:

double x = f(0,0); // Equivale a f(0,0,0.0,1.0,-3);

double x = f(0,0,9.0); // Equivale a f(0,0,9.0,1.0,-3);

double x = f(0,0,9.0,-6.0); // Equivale a f(0,0,9.0,-6.0,-3);

double x = f(0,0,9.0,-6.0,2);

Notar que no es posible llamar a la funcion especificando solo el tercer y quinto parametro.Un intento como el siguiente:

double x = f(0,0,9.0,5);

no sera equivalente a f(0,0,9.0,1.0,5);, sino que el cuarto parametro sera promovido deint a double, y resultara ser equivalente a f(0,0,9.0,5.0,-3). Los parametros opcionalesson asignados de izquierda a derecha en estricto orden de aparicion.

1.3.8. Alcance, visibilidad, tiempo de vida

Con el concepto de funcion hemos apreciado que es posible que coexistan variables con elmismo nombre en puntos distintos del programa, y que signifiquen cosas distintas. Convieneentonces tener en claro tres conceptos que estan ligados a esta propiedad:

Alcance La seccion del codigo durante la cual el nombre de una variable puede ser usado.Comprende desde la declaracion de la variable hasta el final del cuerpo de la funciondonde es declarada.

Si la variable es declarada dentro de una funcion es local . Si es definida fuera de todaslas funciones (incluso fuera de main), la variable es global.

Visibilidad Indica cuales de las variables actualmente al alcance pueden ser accesadas. Ennuestros ejemplos (subseccion 1.3.3), la variable i en main aun esta al alcance dentrode la funcion funcion, pero no es visible, y por eso es posible reutilizar el nombre.

Tiempo de vida Indica cuando las variables son creadas y cuando destruidas. En generaleste concepto coincide con el alcance (las variables son creadas cuando son declaradas ydestruidas cuando la funcion dentro de la cual fueron declaradas termina), salvo porquees posible definir: (a) variables dinamicas , que no tienen alcance, sino solo tiempo devida (un ejemplo de ello son las variables creadas como punteros, como veremos masadelante, en la Sec. 1.5) ; (b) variables estaticas , que conservan su valor entre llamadassucesivas de una funcion (estas variables tienen tiempo de vida mayor que su alcance).

1.3.9. Variables estaticas

La palabra reservada static permite definir variables que conservan su valor entre lla-madas sucesivas de una funcion.

Consideremos el siguiente ejemplo:

Page 31: PROGRAMACION Y M´ ETODOS NUM´ ERICOS´ … › ~vmunoz › homepage › cursos › ...El fin de una instrucci´on est´a determinado por el cierre de un par´entesis o por un punto

1.3. FUNCIONES 27

#include <iostream>

using namespace std;

int f();

int main(){

cout << f() << endl;

cout << f() << endl;

return 0;

}

int f(){

static int x=0;

x++;

return x;

}

Al declarar x como static, se consigue que la primera vez que sea llamada la funcion,la variable sea creada, asignandosele el valor 0. Luego su valor es incrementado en uno.Al terminar esa primera llamada, x no es destruida, de modo que cuando f() es llamadapor segunda vez, x no es inicializada de nuevo, y su valor es nuevamente incrementado. Elresultado en pantalla son los numeros 1 y 2.

1.3.10. Recursion

C++ soporta un tipo especial de tecnica de programacion, la recursion, que permite queuna funcion se llame a sı misma. Esto permite definir de modo muy compacto una funcionque calcule el factorial de un numero entero n:

int factorial(int n)

{

return (n<2) ? 1: n * factorial(n-1);

}

Es natural el uso de la recursion, ya que el factorial se puede definir recursivamente:n! = n · (n− 1)!.

La estrategia entonces es la siguiente: para calcular el factorial de, por ejemplo, 5, sedebe llamar factorial(5). Como el argumento es mayor que 2, la expresion condicional seevalua a 2*factorial(4); y ası sucesivamente hasta llegar a factorial(2), que se evaluaa 2*factorial(1). Aquı pasa algo distinto, porque el argumento de esta ultima llamadaes 1, y por tanto factorial(1) se evalua como 1. No hay una nueva llamada a la funcion,y la cadena se devuelve: factorial(1) se evalua como 1; luego factorial(2) se evalua como2*factorial(2)=2*1, y ası sucesivamente, hasta llegar a factorial(5) = 5*(4*(3*(2*1))).

Page 32: PROGRAMACION Y M´ ETODOS NUM´ ERICOS´ … › ~vmunoz › homepage › cursos › ...El fin de una instrucci´on est´a determinado por el cierre de un par´entesis o por un punto

28 CAPITULO 1. UNA BREVE INTRODUCCION A C++

Notamos que es importante, para cualquier llamada recursiva, que exista una condicionpara detener la recursion. De otro modo, se entrara en un ciclo infinito y el programa secaera. En el caso de la funcion factorial descrita, la detencion se logra con la condicionn<2, que, cuando es cierta, impide que se llame nuevamente a la misma funcion.

1.4. Matrices

1.4.1. Declaracion e inicializacion

Podemos declarar (e inicializar inmediatamente) matrices (o vectores, o arreglos, arraysen ingles) de enteros, reales de doble precision, caracteres, etc., segun nuestras necesidades.

int a[5];

double r[3] = {3.5, 4.1, -10.8};

char palabra[5];

Una vez declarada la matriz (digamos a[5]), los valores individuales se accesan con a[i],con i desde 0 a 4. Por ejemplo, podemos inicializar los elementos de la matriz ası:

a[0] = 3;

a[3] = 5; ...

o si queremos ingresarlos desde el teclado:

for (int i = 0; i < 5; i++){

cin >> a[i];

}

Y si deseamos escribirlos en pantalla:

for (int i = 0; i < 5; i++){

cout << a[i];

}

Podemos pensar en un arreglo como una serie de direcciones de memoria consecutivas.Por ejemplo, en el caso de r[3] definido mas arriba:

4.1 −10.83.5

a

a[0] a[1] a[2]

Un error comun es tratar de accesar o asignar un arreglo fuera del rango que le correspon-de. Por ejemplo, a[-1] o a[3] en el ejemplo anterior. En ese caso, se estara invadiendo unaregion de memoria que no se deberıa estar accesando, y el resultado en general no tendra sen-tido o causara un error durante la ejecucion.

Page 33: PROGRAMACION Y M´ ETODOS NUM´ ERICOS´ … › ~vmunoz › homepage › cursos › ...El fin de una instrucci´on est´a determinado por el cierre de un par´entesis o por un punto

1.4. MATRICES 29

1.4.2. Matrices como parametros de funciones

Una matriz puede ser entregada como argumento a una funcion. En ese caso, es necesarioagregar [] al tipo de variable en la declaracion. Ademas, se debe entregar como parametroindependiente la dimension de la matriz, ya que esa es una informacion que no esta contenidadentro de la propia matriz (de otro modo, se correrıa el riesgo de accesar regiones incorrectasde memoria). Por ejemplo, deleguemos a una funcion el mostrar en pantalla los elementos deuna matriz:

#include <iostream>

using namespace std;

void PrintMatriz(int,double []);

int main()

{

int dim = 5;

double matriz[dim] = {3.5, 5.2, 2.4, -0.9, -10.8};

PrintMatriz(dim, matriz);

return 0;

}

void PrintMatriz(int,double a []){

for (int j = 0; j < i; j++){

cout << "Elemento " << j << " = " << a[j] << endl;

}

}

1.4.3. Matrices multidimensionales

Tambien es posible utilizar matrices de dos dimensiones:

double arreglo_1[10][8];

int arreglo_2[2][3] = {{1, 2, 3},

{4, 5, 6}};

Al declarar arreglo_1[10][8], se esta indicando, en el fondo, que arreglo_1 es un vectorde dimension 10, cada uno de los cuales, a su vez, es un vector de dimension 8. Esto permiteentender la forma de inicializar arreglo_2 en los ejemplos anteriores: dos vectores, cada unode dimension 3.

1.4.4. Matrices de caracteres: cadenas (strings)

Un caso especial de arreglos son las “cadenas” (strings , en ingles), las cuales permitenrepresentar texto. Ahora bien, esto introduce un problema nuevo: nos gustarıa que un stringpueda contener texto de longitud variable, por ejemplo que sea definida primero como "Hola",

Page 34: PROGRAMACION Y M´ ETODOS NUM´ ERICOS´ … › ~vmunoz › homepage › cursos › ...El fin de una instrucci´on est´a determinado por el cierre de un par´entesis o por un punto

30 CAPITULO 1. UNA BREVE INTRODUCCION A C++

y luego como "Hola de nuevo". Como los arreglos no tienen incorporada la informacion sobresu propia dimension, se requiere otra manera de indicar cuando termina la variable. Ello selogra con un caracter de termino: ’\0’. Ası, una cadena es un arreglo de caracteres quetermina con el char nulo: ’\0’:

char palabra[5] = {’H’, ’o’, ’l’, ’a’, ’\0’};

’H’ ’o’ ’l’ ’a’ ’\0’

palabra

Como con cualquier arreglo, se pueden accesar los elementos individuales (los caracteres)con [], y si queremos enviar esta variable a pantalla, podemos hacerlo caracter por caracter:

for (i = 0; i < 5; i++){

cout << palabra[i];

}

o, equivalentemente, con cout << "Hola". De hecho, la declaracion de palabra podrıa ha-berse escrito:

char palabra[5] = "Hola";

Notemos que, en cualquier caso, la dimension del arreglo debe ser uno mas que el numero decaracteres visibles.

<string>

El modo anterior para manejar texto es correcto, pero incomodo por diversas razones. Porello, C++ proporciona un tipo especial de variable, string, que cumple el mismo objetivo, ycon muchas otras caracterısticas que la hacen mas practica. Para usarla, se debe incluir elheader <string>. Una variable tipo string se puede usar del mismo modo que los otros tiposbasicos que hemos visto hasta el momento. En el siguiente ejemplo, se definen dos variablestipo string, una de ellas se ingresa desde el teclado, y luego ambas se escriben en pantalla:

#include<iostream>

#include<string>

using namespace std;

int main(){

string palabra1="Hola";

string palabra2;

cin >> palabra2;

cout << "La primera palabra es: " << palabra1 << endl;

cout << "La segunda palabra es: " << palabra2 << endl;

Page 35: PROGRAMACION Y M´ ETODOS NUM´ ERICOS´ … › ~vmunoz › homepage › cursos › ...El fin de una instrucci´on est´a determinado por el cierre de un par´entesis o por un punto

1.5. PUNTEROS 31

return 0;

}

Mas adelante, en la Sec. 1.10.1, revisaremos caracterısticas adicionales de string.

1.5. Punteros

Una de las ventajas de C++ es permitir el acceso directo del programador a zonas dememoria, ya sea para crearlas, asignarles un valor o destruirlas. Para ello, ademas de los tiposde variables ya conocidos (int, double, etc.), C++ proporciona un nuevo tipo: el puntero.El puntero no contiene el valor de una variable, sino la direccion de memoria en la cual dichavariable se encuentra.

Un pequeno ejemplo nos permite ver la diferencia entre un puntero y la variable a la cualese puntero “apunta”:

#include <iostream>

using namespace std;

int main(){

int i = 42;

int * p_i = &i;

cout << "El valor del puntero es: " << p_i << endl;

cout << "Y apunta a la variable: " << *p_i << endl;

return 0;

}

En este programa definimos una variable i entera, y luego un puntero a esa variable, queen este caso denominamos p_i. Observemos la presencia de los sımbolos * y &: int * es unpuntero a una variable tipo int; por su parte, en el lado derecho de esa asignacion, &i es ladireccion de memoria de la variable i. De ahı en adelante, la variable p_i aloja la direccion dememoria en la que se encuentra la variable i; y si se quiere acceder a los contenidos dentro deesa direccion de memoria, se obtiene con *p_i (que, en este caso, serıa equivalente a usar lavariable original, i). Se dice que & es el operador de referencia (observar que no es casualidadsu uso al pasar argumentos de funciones como referencia, visto en Sec. 1.3.5), y que * es eloperador de derreferencia.

Al ejecutar este programa, se obtiene una salida en pantalla de la forma:

El valor del puntero es: 0x7fff953449e4

Y apunta a la variable: 42

La primera lınea nos da el valor del puntero, que es algun numero hexadecimal imposiblede determinar a priori , y que corresponde a la direccion de memoria donde quedo ubicadala variable i. La segunda lınea nos da el valor de la variable que esta en esa direccion dememoria: 42.

Page 36: PROGRAMACION Y M´ ETODOS NUM´ ERICOS´ … › ~vmunoz › homepage › cursos › ...El fin de una instrucci´on est´a determinado por el cierre de un par´entesis o por un punto

32 CAPITULO 1. UNA BREVE INTRODUCCION A C++

En el ejemplo anterior, utilizamos un puntero a una variable previamente declarada.Tambien es posible declarar un puntero a una variable no existente, usando new. Ası, elcodigo anterior se puede reescribir ası:

#include <iostream>

using namespace std;

int main(){

int * p = new int;

*p = 42;

cout << "El valor del puntero es: " << p << endl;

cout << "Y apunta a la variable: " << *p << endl;

delete p;

return 0;

}

En la primera lınea, declaramos p como un puntero a entero, y le asignamos una nueva

direccion de memoria suficiente para alojara un int. Ahora esta variable existe solo comopuntero, y el unico modo de accesarlas es a traves de su direccion de memorial. Salvo poreso, se pueden manipular como cualquier variable, y aca vemos como se le puede asignar yrecuperar un valor. Finalmente, delete libera la memoria asignada a p. A estas variables,solo existen como punteros que se pueden crear y destruir, se les llama variables dinamicas , yfueron mencionadas antes, en la Sec. 1.3.8, como variables que no tienen alcance, solo tiempode vida, determinado este por el tiempo de ejecucion del programa desde que se crean connew hasta que se destruyen con delete u otro evento como el fin de la funcion main.

Los punteros tienen gran importancia cuando de manejar datos dinamicos se trata, esdecir, objetos que son creados durante la ejecucion del programa, en numero imposible depredecir al momento de compilar. Por ejemplo, un ambiente grafico como Gnome o KDEque crea una, dos, tres, etc. ventanas a medida que el usuario lo requiere. En este caso, cadaventana es un objeto dinamico, creado durante la ejecucion, y la unica forma de manejarloes a traves de un puntero a ese objeto.

Los punteros son sin duda elementos muy interesantes, que permiten una gran flexibilidaden la programacion. Sin embargo, si no son usados con cuidado pueden ser fuente de grandesproblemas, ya sea por la legibilidad del codigo o por un mal manejo de la memoria.

1.6. Argumentos de main

En la Sec. 1.3.1 se hizo notar que main no es sino una funcion (con nombre reservado), conun tipo de retorno entero. Ahora bien, si es una funcion, deberıa poder recibir argumentos.Eso es ası, en efecto, y esos argumentos se pueden usar para entregarle parametros a nuestrocodigo desde la lınea de comandos. Nosotros hemos usado eso muchas veces. Por ejemplo,para copiar un archivo con otro nombre, se utiliza un comando del tipo:

Page 37: PROGRAMACION Y M´ ETODOS NUM´ ERICOS´ … › ~vmunoz › homepage › cursos › ...El fin de una instrucci´on est´a determinado por el cierre de un par´entesis o por un punto

1.7. MANEJO DE ARCHIVOS 33

cp archivo1.txt archivo2.txt

Pues bien, cp es en realidad un archivo ejecutable que es capaz de recibir dos argumentos ,correspondientes a los archivos de origen y destino. Esos argumentos son pasados al codigocomo argumentos de la funcion main.

Para ejemplificarlo, consideremos un codigo, que simplemente escribe en pantalla los ar-gumentos que se le entregaron desde la lınea de comando. Por ejemplo, si el codigo esta enun archivo argumentos.cc, el resultado de ./argumentos uno dos tres sera:

uno

dos

tres

El resultado anterior se puede conseguir con el siguiente codigo:

#include <iostream>

using namespace std;

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

for (int i=0;i<argc;i++){

cout << argv[i] << endl;

}

return 0;

}

El primer argumento de main es siempre un int, que registra el numero de argumentos quele fueron entregados (en el ejemplo, tres). El segundo argumento de main es un puntero a un

arreglo de caracteres , donde el elemento i-esimo de dicho arreglo es el argumento i-esimo enla lınea de comandos.

Es interesante notar como distintas caracterısticas de C++ que hemos revisado hasta ahora(caracteres, arreglos, punteros), convergen en este punto para hacer posible entregar argu-mentos desde la lınea de comandos, donde cada argumento es un arreglo de caracteres, cadauno de dimension variable (numero de letras arbitrario en cada argumento), y a su vez sepuede tener un numero arbitrario de estos arreglos de caracteres (numero arbitrario de ar-gumentos).

1.7. Manejo de archivos

Hasta el momento toda la salida de nuestros codigos ha sido ha pantalla, y todo el ingresode datos ha sido desde el teclado. Naturalmente la entrada y salida de datos se puede hacerdesde archivos, y esas capacidades son las que revisaremos a continuacion.

(Hacemos notar de inmediato que la sintaxis que se utiliza para manejar archivos es unpoco diferente de la que hemos usado hasta ahora. No intentaremos justificarla por ahora,y sera completamente evidente una vez revisada la Sec. 1.9. Sin embargo, es importanteque aprendamos a manejar rapidamente archivos y strings, por razones practicas, antes de

Page 38: PROGRAMACION Y M´ ETODOS NUM´ ERICOS´ … › ~vmunoz › homepage › cursos › ...El fin de una instrucci´on est´a determinado por el cierre de un par´entesis o por un punto

34 CAPITULO 1. UNA BREVE INTRODUCCION A C++

continuar con aspectos mas tecnicos del lenguaje, y por esa razon preferimos presentar estetopico en este momento, antes de tener todos los fundamentos.)

El siguiente codigo de ejemplo muestra como escribir dos variables, una definida dentrodel codigo, la otra ingresada desde el teclado, en un archivo:

#include <iostream>

#include <fstream>

using namespace std;

int main(){

ofstream nombre_logico("nombre_fisico.dat");

int i = 3, j;

nombre_logico << i << endl;

cin >> j;

nombre_logico << j << endl;

nombre_logico.close();

return 0;

}

Observamos que se debe incluir el header fstream (por file stream, analogo al input-outputstream que ya conocıamos. Para abrir un archivo, lo declaramos con ofstream (output fi-

le stream), que corresponde a un archivo llamado nombre_fisico.dat, el cual sera creado(si no existe) o sobreescrito (si existe) en disco, y que sera identificado dentro del codi-go como una variable llamada nombre_logico. El nombre "nombre_fisico.dat" puedeser cualquier nombre valido para el sistema operativo, incluyendo el uso de paths abso-lutos o relativos, de modo que expresiones tales como "../directorio/archivo.dat" o"/home/usuario/otro_archivo.txt" son completamente aceptables.

Luego, para escribir en el archivo, se usa la misma instruccion que usarıamos para escribiren pantalla, reemplazando cout por nombre_logico.

Ası que, en definitiva, la escritura en archivos sigue las mismas reglas que la escritura enpantalla, reemplazando el output stream por un file stream.

Al final, cerramos el stream con close. (En rigor, no es completamente necesario que lohagamos explıcitamente, porque el compilador cerrara el stream automaticamente si nosotrosno lo hacemos. Lo presentamos mas bien por completitud.)

Para ingresar datos desde un archivo la idea es exactamente la misma, pero en vez de ha-cerlo desde cin, que representa al teclado, lo hacemos desde un input file stream (ifstream):

#include <iostream>

#include <fstream>

using namespace std;

Page 39: PROGRAMACION Y M´ ETODOS NUM´ ERICOS´ … › ~vmunoz › homepage › cursos › ...El fin de una instrucci´on est´a determinado por el cierre de un par´entesis o por un punto

1.7. MANEJO DE ARCHIVOS 35

int main(){

ifstream nombre_logico("nombre_fisico.dat");

int i,j,k,l;

nombre_logico >> i >> j >> k >> l;

cout << i << "," << j << "," << k << "," << l << endl;

nombre_logico.close();

return 0;

}

En este caso, el mismo archivo anterior es usado como un objeto ifstream, y cuatro variablesenteras se leen desde el.

En los dos codigos anteriores, un mismo archivo se uso primero como salida y luego comoentrada, pero esto sucedio en dos programas separados. ¿Sera posible usar el mismo archivotanto para entrada como para salida, dentro del mismo codigo? La respuesta es sı. En esecaso, no se puede declarar el archivo como ofstream ni como ifstream, sino como algogenerico: fstream. Este stream generico puede ser abierto como entrada o salida segun lonecesitemos. Observemos el siguiente ejemplo:

#include <iostream>

#include <fstream>

using namespace std;

int main(){

fstream nombre_logico;

nombre_logico.open("nombre_fisico.dat",ios::out);

int i = 4,j;

nombre_logico << i << endl;

nombre_logico.close();

nombre_logico.open("nombre_fisico.dat",ios::in);

nombre_logico >> j;

j = j++;

cout << j << endl;

nombre_logico.close();

return 0;

}

Page 40: PROGRAMACION Y M´ ETODOS NUM´ ERICOS´ … › ~vmunoz › homepage › cursos › ...El fin de una instrucci´on est´a determinado por el cierre de un par´entesis o por un punto

36 CAPITULO 1. UNA BREVE INTRODUCCION A C++

Notemos que la variable nombre_logico se declara como un fstream. Hasta el momentono pasa nada, no esta asociado a ningun archivo fısico. En la lınea siguiente sucede eso,cuando se abre, con open, que toma dos argumentos: el primero es el nombre del archivoen disco (nombre_fisico.dat), y el segundo es una variable, definida en alguna parte delas librerıas del sistema, que determina el modo de apertura. En este caso, ios::out es unaconstante, que lo unico que importa por ahora es que significa “abrir en modo de escritura”.La siguiente lınea escribe la variable i en el archivo.

A continuacion deseamos leer el valor escrito en el archivo, y ponerlo en una nueva variablej. Para ello, debemos cerrar el stream (ahora close no es opcional), y volver a abrirlo, peroen modo de lectura (ios::in).

Estas operaciones de apertura y cierre se pueden hacer un numero arbitrario de veces,permitiendonos interactuar con el mismo archivo del modo que nos convenga.

(Notemos que las dos primeras lıneas podrıan haberse escrito en una sola lınea ası:fstream nombre_logico("nombre_fisico.dat",ios::out); En el ejemplo lo escribimosseparado para que se viera la completa simetrıa entre los casos de lectura y de escritura.)

Abrir un archivo como ofstream o, equivalente, como fstream en modo ios::out, tieneel efecto de sobreescribir los contenidos. Si uno desea agregar contenidos, el stream debeabrirse en modo append :

#include <iostream>

#include <fstream>

using namespace std;

int main(){

ofstream nombre_logico("nombre_fisico.dat",ios::app);

int i = 3, j;

nombre_logico << i << endl;

cin >> j;

nombre_logico << j << endl;

return 0;

}

1.8. Funciones matematicas

Otro header que es importante conocer es cmath. Con el se tiene acceso a multiplesfunciones y constantes matematicas. Algunos ejemplos:

#include <iostream>

#include <cmath>

using namespace std;

Page 41: PROGRAMACION Y M´ ETODOS NUM´ ERICOS´ … › ~vmunoz › homepage › cursos › ...El fin de una instrucci´on est´a determinado por el cierre de un par´entesis o por un punto

1.9. CLASES 37

int main(){

double a = M_PI; // valor de la constante ’pi’

double b = sqrt(a); // raiz cuadrada

double c = log(b); // logaritmo natural

double d = sin(c); // seno

double e = cos(d); // coseno

double f = fabs(e); // valor absoluto

double g = exp(f); // exponencial

// etc.

return 0;

}

1.9. Clases

C++ dispone de una serie de tipos de variables con los cuales nos esta permitido operar:int, double, char, etc. Creamos variables de estos tipos y luego podemos trabajar con ellas:

int x, y;

x = 3;

y = 6;

int z = x + y;

No hay, sin embargo, en C++ , una estructura predefinida que corresponda a numeroscomplejos, vectores de dimension n o matrices, por ejemplo. Y sin embargo, nos agradarıadisponer de numeros complejos que pudieramos definir como

z = (3,5);

w = (6,8);

y que tuvieran sentido las expresiones

a = z + w;

b = z * w;

c = z / w;

d = z + 3;

f = sqrt(z);

Todas estas expresiones son completamente naturales desde el punto de vista matematico,y serıa bueno que el lenguaje las entendiera. Esto es imposible en el estado actual, pues, porejemplo, el signo + es un operador que espera a ambos lados suyos un numero. Sumar cualquiercosa con cualquier cosa no significa nada necesariamente, ası que solo esta permitido operarcon numeros. Pero los humanos sabemos que los complejos son numeros. ¿Como decırseloal computador? ¿Como convencerlo de que sumar vectores o matrices es tambien posiblematematicamente, y que el mismo signo + deberıa servir para todas estas operaciones?

La respuesta es: a traves del concepto de clases . Lo que debemos hacer es definir una clasede numeros complejos. Llamemosla Complejo. Una vez definida correctamente, Complejo

Page 42: PROGRAMACION Y M´ ETODOS NUM´ ERICOS´ … › ~vmunoz › homepage › cursos › ...El fin de una instrucci´on est´a determinado por el cierre de un par´entesis o por un punto

38 CAPITULO 1. UNA BREVE INTRODUCCION A C++

sera un tipo mas de variable que el compilador reconocera, igual que int, double, char, etc.Y sera tan facil operar con los Complejos como con todos los tipos de variables preexistentes.Esta facilidad es la base de la extensibilidad de que es capaz C++ , y por tanto de todas laspropiedades que lo convierten en un lenguaje muy poderoso.

Las clases responden a la necesidad del programador de construir objetos o tipos de datosque respondan a sus necesidades. Si necesitamos trabajar con vectores de 5 coordenadas,sera natural definir una clase que corresponda a vectores con 5 coordenadas; si se trata deun programa de administracion de personal, la clase puede corresponder a un empleado, consus datos personales como elementos; si es una simulacion de vida artificial, la clase puedecorresponder a una celula.

Si bien es cierto uno puede trabajar con clases en el contexto de orientacion al procedi-miento, las clases muestran con mayor propiedad su potencial con la orientacion al objeto,donde cada objeto corresponde a una clase. Por ejemplo, para efectuar una aplicacion para unambiente grafico, cada uno de los objetos involucrados (la ventana principal, las ventanas delos archivos abiertos, la barra de menu, las cajas de dialogo, los botones, etc.) estara asociadoa una clase.

1.9.1. Definicion

En lo que sigue, crearemos una clase para numeros complejos, a traves de sucesivos pasosque la haran cada vez mas funcional. Naturalmente, esto es solo una ilustracion. De hecho,en C++ ya existe, en la librerıa estandar, una clase para numeros complejos. Desde ese puntode vista, puede parecer un ejercicio inutil, pero tiene varias ventajas: (a) tenemos una claraintuicion de que caracterısticas necesitamos que tenga la clase; (b) nos permitira introducirconceptos aplicables a cualquier clase que deseemos crear; (c) construir nuestra propia clasenos permitira evitar el aura de “caja negra” que suelen tener las soluciones que vienenpreviamente instaladas con el sistema.

Digamos entonces que queremos una clase para representar a los numeros complejos.Llamemosla Complejo. Usaremos la convencion de que los nombres de las clases comiencencon mayuscula. Esto es porque las clases, recordemos, corresponderan a tipos de variablestan validos como los internos de C++ (int, char, etc.). Al usar nombres con mayusculadistiguimos visualmente los nombres de un tipo de variable interno y uno definido por elusuario.

El siguiente codigo muestra la estructura mınima necesaria para que la clase Complejo

exista:

#include<iostream>

using namespace std;

class Complejo{

};

int main(){

Page 43: PROGRAMACION Y M´ ETODOS NUM´ ERICOS´ … › ~vmunoz › homepage › cursos › ...El fin de una instrucci´on est´a determinado por el cierre de un par´entesis o por un punto

1.9. CLASES 39

Complejo z;

return 0;

}

Eso es todo. Todas las caracterısticas de la clase se definiran luego entre los parentesiscursivos, pero con la estructura anterior logramos la funcionalidad mas basica: podemosdeclarar un objeto tipo Complejo del mismo modo como declaramos cualquier otra variableestandar del lenguaje:

int i;

double f;

Al menos a este nivel, nuestra clase y los tipos de variables estandar disponibles son indis-tinguibles. Naturalmente, esta clase es bastante limitada, porque no podemos hacer nadamas que declarar objetos tipo Complejo, y en lo que sigue iremos agregandole cada vez masfuncionalidad.

1.9.2. Variables miembros

Para que la clase anterior represente efectivamente numeros complejos, debe necesaria-mente tener, de alguna forma, la informacion sobre las partes real e imaginaria de dichonumero. Para lograrlo, definiremos dos variables miembros de la clase Complejo, una de lascuales correspondera a la parte real y la otra a su parte imaginaria:

class Complejo{

public:

double real;

double imag;

};

Cualquier tipo de variable aceptada por el sistema puede ser variable miembro de una clase.En este caso, necesitamos dos variables tipo double. La palabra public que las antecede esnecesaria para que las variables sean accesibles “desde el exterior” de la clase (mas sobre esoen la Sec. ??).

El codigo anterior permite que cada vez que se declara un objeto de tipo Complejo,tendra sus propias variables real e imag. Como siempre se llaman igual para todos losobjetos Complejo, para distinguir las variables real e imag de objetos distintos, se utiliza eloperador de seleccion (.):

#include<iostream>

using namespace std;

class Complejo{

public:

double real;

Page 44: PROGRAMACION Y M´ ETODOS NUM´ ERICOS´ … › ~vmunoz › homepage › cursos › ...El fin de una instrucci´on est´a determinado por el cierre de un par´entesis o por un punto

40 CAPITULO 1. UNA BREVE INTRODUCCION A C++

double imag;

};

int main(){

Complejo z;

z.real=2;

z.imag=-3;

Complejo w;

w.real=1;

w.imag=0;

cout << z.real << endl;

cout << z.imag << endl;

return 0;

}

El resultado sera que en pantalla apareceran los numeros 2 y 3, que son las partes real eimaginaria del objeto z.

1.9.3. Funciones miembro; miembros publicos y privados

En este momento, los miembros real e imag de nuestra clase estan disponibles para sumodificacion y acceso desde main y cualquier otra funcion que llame a objetos tipo Complejo.Sin embargo, esto no es necesariamente una buena idea. Por ejemplo, digamos que nuestraclase es usada por muchos codigos distintos, incluso por muchas personas distintas. Luego,unos meses despues, nos damos cuenta de que era mas conveniente haber definido los numeroscomplejos en terminos de sus coordenadas polares, modulo y fase. O algo mucho menosprofundo: decidimos que es mejor escribir menos, y queremos cambiar los nombres real eimag a x e y, respectivamente.

Introducir mejoras como esas serıa realmente impractico, porque todos los codigos esperanque las variables de todo Complejo sean la parte real e imaginaria, y que se llamen real eimag. Esto limita profundamente las capacidades de mejora del diseno de una clase, ya quelas decisiones iniciales se vuelven, en la practica, definitivas.

Para evitar lo anterior, lo usual es que las variables miembros no sean accesibles desdefuera de la clase, y que la interaccion con el exterior sea exclusivamente a traves de funciones.Tecnicamente, la clase contara con una estructura interna privada, accesible solo a variablesy funciones propias de la clase, y una interfase externa publica, que es la que los usuariosde la clase tienen permitido conocer y usar. De este modo, la estructura privada podrıaser modificada y optimizada repetidas veces, para hacer la clase cada vez mejor, pero si lainterfase publica se mantiene invariable, los usuarios o codigos que utilicen la clase recibiran

Page 45: PROGRAMACION Y M´ ETODOS NUM´ ERICOS´ … › ~vmunoz › homepage › cursos › ...El fin de una instrucci´on est´a determinado por el cierre de un par´entesis o por un punto

1.9. CLASES 41

todos los beneficios de la nueva estructura, sin las dificultades asociadas a los cambios dediseno, que permaneceran invisibles para ellos.

Todo esto se logra definiendo miembros privados y publicos de una clase, identificadoscon las palabras private y public. En el ejemplo de la seccion anterior, la clase tiene dosvariables miembros publicas y ningun miembro privado.

Si queremos ahora esconder la estructura interna de la clase, debemos definir dichasvariables como privadas, y proporcionar funciones que permitan accesar y/o modificar dichasvariables:

class Complejo{

private:

double real;

double imag;

public:

void set_real(double);

void set_imag(double);

double get_real();

double get_imag();

};

Ahora la clase tiene cuatro funciones miembro, todas publicas. Dos de ellas van a permitircambiar el valor de las partes real e imaginaria (deben aceptar como argumento una variabletipo double, que correspondera al nuevo valor de la parte real o imaginaria segun correspon-da); dichas funciones son tipo void, ya que no requieren devolver nada. En cuanto a las otrasdos, ellas permitiran obtener el valor de las partes real e imaginaria actuales; no necesitanargumentos, pero deben devolver una variable double, justamente el valor de la parte real oimaginaria segun corresponda. Cualquier tipo de retorno o de argumentos es aceptable parafunciones miembros de una clase; incluso es posible usar el propio tipo Complejo, como si yaexistiera, de modo que una hipotetica funcion miembro llamada mi_funcion(), que aceptecomo argumento un Complejo y devuelva un Complejo, se podrıa declarar de la siguienteforma:

class Complejo{

private:

double real;

double imag;

public:

void set_real(double);

void set_imag(double);

double get_real();

double get_imag();

Complejo mi_funcion(Complejo);

};

En este momento, las funciones han sido solo declaradas. Ahora deben ser implementa-das. La implementacion podrıa hacerse dentro del codigo de la clase, pero nosotros elegire-mos separar el codigo e implementarlas fuera de la clase. Como podrıa existir otra funcion

Page 46: PROGRAMACION Y M´ ETODOS NUM´ ERICOS´ … › ~vmunoz › homepage › cursos › ...El fin de una instrucci´on est´a determinado por el cierre de un par´entesis o por un punto

42 CAPITULO 1. UNA BREVE INTRODUCCION A C++

set_real(double), para identificar que queremos definir la funcion set_real(double) co-rrespondiente a la clase Complejo, se usa el operador ::. El codigo completo que declara laclase e implementa sus cuatro funciones miembros es el siguiente:

class Complejo{

private:

double real;

double imag;

public:

void set_real(double);

void set_imag(double);

double get_real();

double get_imag();

};

void Complejo::set_real(double x){

real = x;

}

void Complejo::set_imag(double x){

imag = x;

}

double Complejo::get_real(){

return real;

}

double Complejo::get_imag(){

return imag;

}

Ahora estamos listos para usar esta clase desde main o cualquier otra funcion que lorequiera. Para acceder a las funciones miembro usamos el operador de seleccion, como antes.Presentamos el codigo completo:

#include<iostream>

using namespace std;

class Complejo{

private:

double real;

double imag;

public:

void set_real(double);

void set_imag(double);

Page 47: PROGRAMACION Y M´ ETODOS NUM´ ERICOS´ … › ~vmunoz › homepage › cursos › ...El fin de una instrucci´on est´a determinado por el cierre de un par´entesis o por un punto

1.9. CLASES 43

double get_real();

double get_imag();

};

void Complejo::set_real(double x){

real = x;

}

void Complejo::set_imag(double x){

imag = x;

}

double Complejo::get_real(){

return real;

}

double Complejo::get_imag(){

return imag;

}

int main(){

Complejo z;

z.set_real(2);

z.set_imag(-3);

cout << z.get_real() << endl;

cout << z.get_imag() << endl;

return 0;

}

Si bien es cierto para esta clase particular, en el estado en que se encuentra en estemomento, el cambio que hemos introducido parece puramente cosmetico, en realidad es unaestrategia de programacion que recomendamos enormemente, cuyo valor es cada vez mayoren la medida que los codigos se van complicando.

Como dijimos, si en el futuro decidimos que es mejor utilizar las coordenadas polares,entonces podremos redefinir la clase de la forma:

#include<iostream>

using namespace std;

class Complejo{

private:

Page 48: PROGRAMACION Y M´ ETODOS NUM´ ERICOS´ … › ~vmunoz › homepage › cursos › ...El fin de una instrucci´on est´a determinado por el cierre de un par´entesis o por un punto

44 CAPITULO 1. UNA BREVE INTRODUCCION A C++

double modulo;

double fase;

public:

void set_real(double);

void set_imag(double);

double get_real();

double get_imag();

};

Y ahora, por ejemplo, set_real(double) sera una funcion mas complicada, porque elusuario querra que z.set_real(2) siga teniendo el efecto de cambiar la parte real de z. Paralograrlo, la funcion debera transformar modulo y fase simultaneamente, de modo que la partereal sea ahora 2. Y cuando el usuario pida la parte real, con get_real(), la implementacionde esta funcion tambien tendra que hacer algo un poco mas complicado:

double Complejo::get_real(){

return modulo*sin(fase);

}

Pero lo importante es que, como hemos dicho, independiente de las decisiones de disenointerno de la clase, la interaccion con el exterior se ha mantenido invariante, y ninguna lıneade main() necesita ser cambiada.

1.9.4. Creacion de header files

El codigo anterior es correcto, pero aparte de que la clase Complejo tiene muy poca fun-cionalidad, tiene un problema importante: la idea de definir un nuevo tipo de variable esinteresante, pero un poco inutil si ese nuevo tipo no se puede hacer disponible a muchosotros codigos. Tal como esta definida la clase ahora, si necesitamos escribir otro codigo queuse nuestra clase Complejo, tendrıamos que copiar todo el codigo correspondiente a la claseen el archivo correspondiente. En este momento eso no es problema en principio, porque sonunas pocas lıneas, pero a medida que la clase se vaya complicando esto sera cada vez menospractico. Ademas, incluso si copiaramos todo el codigo en cada archivo que lo necesite, hayotro problema aun mas grave: si en el futuro decidimos modificar la clase para mejorarlaprogresivamente (lo que haremos en las secciones siguientes), si queremos que esas modifi-caciones se propaguen a todos los codigos que la usan deberıamos copiar las modificacionesuna vez mas en todos los archivos involucrados. No es una manera eficiente de trabajar.

La solucion a todos estos problemas es escribir todo el codigo correspondiente a nuestraclase en archivos separados, que son luego llamados por cada codigo que lo necesite. Ası, cadamodificacion a la clase Complejo podra ser propagada de inmediato a todos los codigos quela requieran.

Lo que haremos entonces es escribir toda la declaracion de la clase en un header filellamado, digamos, complejo.h, de la siguiente forma:

//----------Archivo complejo.h----------

#ifndef COMPLEJO_H

#define COMPLEJO_H

Page 49: PROGRAMACION Y M´ ETODOS NUM´ ERICOS´ … › ~vmunoz › homepage › cursos › ...El fin de una instrucci´on est´a determinado por el cierre de un par´entesis o por un punto

1.9. CLASES 45

using namespace std;

class Complejo{

private:

double real;

double imag;

public:

void set_real(double);

void set_imag(double);

double get_real();

double get_imag();

};

#endif

Las instrucciones ifndef, define y endif son directivas de preprocessador. Aunque no sonnecesarias para los ejemplos que revisaremos a continuacion, por razones que seran evidentesmas adelante (ver Sec. 1.13) es bueno incluirlas.

Por su parte, la implementacion de las funciones miembros va en el archivo asociadocomplejo.cc:

//----------Archivo complejo.cc----------

#include "complejo.h"

void Complejo::set_real(double x){

real = x;

}

void Complejo::set_imag(double x){

imag = x;

}

double Complejo::get_real(){

return real;

}

double Complejo::get_imag(){

return imag;

}

Hacemos notar que la separacion en dos archivos, uno con extension .h y otro con ex-tension .cc, no es exclusiva para clases. Podrıamos tambien hacer un header file con cual-quier funcion (por ejemplo, la funcion PrintHola() de la Sec. 1.1.1). La estrategia es lamisma: poner la declaracion de ella y otras funciones que necesitemos en un archivo comofunciones_utiles.h, y sus respectivas implementaciones en funciones_utiles.cc.

Page 50: PROGRAMACION Y M´ ETODOS NUM´ ERICOS´ … › ~vmunoz › homepage › cursos › ...El fin de una instrucci´on est´a determinado por el cierre de un par´entesis o por un punto

46 CAPITULO 1. UNA BREVE INTRODUCCION A C++

Notemos que este es un nivel mas alto de encapsulamiento que el que hemos visto. No solopodemos definir funciones, y no solo podemos separar su declaracion y su implementacion,sino que ademas podemos separar su declaracion y su implementacion en archivos distintos.

Con esta estructura de archivos, el codigo de nuestro programa queda de la siguienteforma:

#include<iostream>

#include "complejo.h"

using namespace std;

int main () {

Complejo z;

z.set_real(2);

z.set_imag(-3);

cout << z.get_real() << endl;

cout << z.get_imag() << endl;

return 0;

}

Para tener acceso a la funcionalidad de Complejo, basta con incluir el archivo complejo.h.Las comillas permiten distinguirlo de los headers de sistema, que son delimitados por <>.Entre comillas puede ir el nombre de archivo con path, relativo o absoluto. Si no se indica elpath, el compilador asume que debe buscar el archivo complejo.h en el directorio actual.

Notemos que complejo.h tiene solo la declaracion de las funciones. Para que las imple-mentaciones esten disponibles, se debe incluir complejo.cc en la lınea de compilacion. Ası,si el codigo anterior esta en un archivo llamado programa.cc, la lınea de compilacion serıa:

g++ -o programa programa.cc complejos.cc

1.9.5. Constructor

Al declarar una variable, el programa crea el espacio de memoria suficiente para alojarla.Cuando se trata de variables de tipos predefinidos en C++ esto no es problema, pero cuandoson tipos definidos por el usuario C++ debe saber como construir ese espacio. La funcionque realiza esa tarea se denomina constructor.

El constructor es una funcion publica de la clase, que tiene el mismo nombre que ella.Agreguemos un constructor a la clase Complejo:

//----------Archivo complejo.h----------

#ifndef COMPLEJO_H

#define COMPLEJO_H

Page 51: PROGRAMACION Y M´ ETODOS NUM´ ERICOS´ … › ~vmunoz › homepage › cursos › ...El fin de una instrucci´on est´a determinado por el cierre de un par´entesis o por un punto

1.9. CLASES 47

using namespace std;

class Complejo{

private:

double real;

double imag;

public:

Complejo();

void set_real(double);

void set_imag(double);

double get_real();

double get_imag();

};

#endif

Y en complejo.cc deberıa agregarse el siguiente codigo:

Complejo::Complejo(){

}

Notemos que el constructor es una funcion especial, ya que no tiene tipo de retorno (nisiquiera void). Ademas tiene que ser una funcion publica, ya que debe poder ser accesibledesde fuera de la clase, naturalmente. El constructor anterior es bastante inutil, porque nohace nada. De hecho, podrıamos omitirlo (como lo hicimos antes), porque si una clase no tieneconstructor, el compilador creara un constructor default , suficiente para los propositos massencillos. Por lo tanto, agregar este constructor, que no hace nada realmente, no es necesario.En este momento, solo podemos construir un objeto Complejo con una lınea como esta:

Complejo z;

Pero los tipos de variable preexistentes tambien permiten implementar junto con declarar.La manera de lograrlo con nuestra clase es hacer un constructor no default, que acepteargumentos. Lo logico, en nuestro caso, es que esos argumentos sean dos: uno que sera laparte real del nuevo objeto, y el otro su parte imaginaria.

//----------Archivo complejo.h----------

#ifndef COMPLEJO_H

#define COMPLEJO_H

using namespace std;

class Complejo{

private:

double real;

double imag;

public:

Complejo();

Page 52: PROGRAMACION Y M´ ETODOS NUM´ ERICOS´ … › ~vmunoz › homepage › cursos › ...El fin de una instrucci´on est´a determinado por el cierre de un par´entesis o por un punto

48 CAPITULO 1. UNA BREVE INTRODUCCION A C++

Complejo(double,double);

void set_real(double);

void set_imag(double);

double get_real();

double get_imag();

};

#endif

Notemos que no hay ningun conflicto entre los dos constructores, ya que el numero y ti-po de argumentos son distintos, y el programa siempre sabra cual version del constructordebera llamar cada vez que lo necesite. En complejo.cc la funcionalidad se implementa ası:

Complejo::Complejo(double x,double y){

real = x;

imag = y;

}

Con esto, las siguientes declaraciones e implementaciones son validas:

Complejo w(10.5,-8.0);

Complejo r = Complejo(-3.6,4.75);

La segunda forma es interesante, porque es la que se parece mas a la forma usual en quedeclaramos e inicializamos una variable de tipo predefinido:

int i = 10;

No se ve exactamente igual, pero recordemos que la siguiente sintaxis tambien es correcta:

int i = int(10);

Y esta sı es exactamente igual a la forma en que se definio el objeto r. Hemos conseguido que,al menos desde el punto de vista de la inicializacion, nuestras variables son indistinguibles delos tipos de variables predefinidas.

En general, los constructores pueden ejecutar tareas bien elaboradas. Parte de ello puedeser inicializar variables privadas con los argumentos entregados. Para ejecutar ese trabajo, laimplementacion se puede hacer con una sintaxis alternativa:

Complejo::Complejo(double x,double y):real(x),imag(y){

}

En el cuerpo de la funcion, entre los parentesis cursivos, pueden ir todas esas tareas adicionalesque no consisten simplemente en asignar valores a las variables privadas.

Page 53: PROGRAMACION Y M´ ETODOS NUM´ ERICOS´ … › ~vmunoz › homepage › cursos › ...El fin de una instrucci´on est´a determinado por el cierre de un par´entesis o por un punto

1.9. CLASES 49

1.9.6. Destructor

Ası como es necesario crear espacio de memoria al definir una variable, hay que deshacersede ese espacio cuando la variable deja de ser necesaria. En otras palabras, la clase necesitatambien un destructor . Esta es otra funcion publica de la clase, cuyo nombre es igual al dela clase, precedida por un ~:

//----------Archivo complejo.h----------

#ifndef COMPLEJO_H

#define COMPLEJO_H

using namespace std;

class Complejo{

private:

double real;

double imag;

public:

Complejo();

Complejo(double,double);

~Complejo();

void set_real(double);

void set_imag(double);

double get_real();

double get_imag();

};

#endif

En complejo.cc debe estar la implementacion:

Complejo::~Complejo(){]

}

En este caso, el destructor no hace nada muy interesante, y serıa equivalente a no haberlodeclarado. En ese caso, igual que con los constructores, el compilador genera un destructordefault , suficiente para las tareas mas sencillas.

1.9.7. Matrices de clases

Una clase es un tipo de variable como cualquier otro de los predefinidos en C++ . Es posibleconstruir matrices con ellas, del mismo modo que uno tiene matrices de enteros o caracteres.Ası, el siguiente codigo es valido:

Complejo v[3];

v[0]=Complejo(2.1,4.1);

v[1]=Complejo(6.3,-2.1);

Page 54: PROGRAMACION Y M´ ETODOS NUM´ ERICOS´ … › ~vmunoz › homepage › cursos › ...El fin de una instrucci´on est´a determinado por el cierre de un par´entesis o por un punto

50 CAPITULO 1. UNA BREVE INTRODUCCION A C++

v[2]=Complejo(4.1,-3.8);

Complejo w[2]={Complejo(3.5,-0.8),Complejo(-2,4)};

1.9.8. Constructor de copia

Ademas del constructor usual, existe un constructor especial, llamado constructor de

copia, y que es llamado especıficamente cuando se necesita copiar los contenidos de un objetoen el otro. Como con los otros constructores y con los destructores, si uno no lo define elcompilador proporciona uno por defecto, que es el que hemos estado usando por defecto.

El constructor de copia acepta como argumento una referencia a un objeto del mismo tipo(lo cual permita que pueda acceder a sus variables privadas, cuyos contenidos podra luegocopiar en otro objeto). Como se debe pasar ese argumento por referencia, es buena ideadeclararlo tambien const, para no correr el riesgo de modificar los contenidos de la variableoriginal por error. La declaracion de la clase quedarıa ası:

//----------Archivo complejo.h----------

#ifndef COMPLEJO_H

#define COMPLEJO_H

using namespace std;

class Complejo{

private:

double real;

double imag;

public:

Complejo();

Complejo(double,double);

Complejo(const Complejo &); // Constructor de copia

~Complejo();

void set_real(double);

void set_imag(double);

double get_real();

double get_imag();

};

#endif

Y su implementacion en complejo.cc:

Complejo::Complejo(const Complejo & z){

real = z.real;

imag = z.imag;

}

Page 55: PROGRAMACION Y M´ ETODOS NUM´ ERICOS´ … › ~vmunoz › homepage › cursos › ...El fin de una instrucci´on est´a determinado por el cierre de un par´entesis o por un punto

1.9. CLASES 51

De este modo, los valores de las partes real e imaginaria del objeto de origen son copiadosa las variables respectivas del objeto que se esta creando.

Una manera sencilla de verificar que el constructor de copia (y no otro) esta siendo llamadoes alterarlo, de modo que no haga lo esperado. Por ejemplo, consideremos el siguiente codigo:

Complejo::Complejo(const Complejo & z){

real = z.real*z.real;

imag = z.imag*z.imag;

}

Es decir, en vez de copiar las variables se copian sus cuadrados. Con el constructor de copiaası modificado, podremos observar la salida del siguiente codigo:

#include<iostream>

#include "complejo.h"

using namespace std;

void f(Complejo);

int main () {

Complejo z = Complejo(-3.6,4.75);

Complejo w=z;

f(z);

cout << z.get_real() << endl;

cout << z.get_imag() << endl;

cout << w.get_real() << endl;

cout << w.get_imag() << endl;

return 0;

}

void f(Complejo w){

cout << w.get_real() + 2 << endl;

}

Podremos verificar una salida de la forma:

-3.6

4.75

12.96

22.5625

14.96

Page 56: PROGRAMACION Y M´ ETODOS NUM´ ERICOS´ … › ~vmunoz › homepage › cursos › ...El fin de una instrucci´on est´a determinado por el cierre de un par´entesis o por un punto

52 CAPITULO 1. UNA BREVE INTRODUCCION A C++

Esto muestra que en la primera lınea se usa el constructor usual para z. Luego, en la asignacionw=z es el constructor de copia el que se usa para pasar los valores de z a w. Y tambien se usacuando se deben duplicar una variable cuando esta se entrega como parametro por valor auna funcion.

1.9.9. Sobrecarga de funciones

Para que la definicion de nuevos objetos sea realmente util, hay que ser capaz de hacercon ellos muchas acciones que nos serıan naturales. Como ya comentamos al introducir elconcepto de clase, nos gustarıa sumar numeros complejos, y que esa suma utilizara el mismosigno + de la suma usual. O extraerles la raız cuadrada, y que la operacion sea tan facil comoescribir sqrt(z). Ya sabemos, por lo visto en la Sec. 1.3.4, que lo que debemos hacer essobrecargar las funciones, de modo que sqrt(z) sea aceptable tambien si z es un Complejo.

Para hacerlo, basta con declarar la nueva funcion sqrt() en complejos.h:

//----------Archivo complejo.h----------

#ifndef COMPLEJO_H

#define COMPLEJO_H

using namespace std;

class Complejo{

private:

double real;

double imag;

public:

Complejo();

Complejo(double,double);

Complejo(const Complejo &);

~Complejo();

void set_real(double);

void set_imag(double);

double get_real();

double get_imag();

};

Complejo sqrt(Complejo); // sobrecarga de sqrt()

#endif

Mientras la nueva declaracion no entre en conflicto con ninguna definicion previa de sqrt(),este codigo es valido.

Luego, en complejo.cc, lo implementamos:

Complejo sqrt(Complejo z){

double a = z.get_real();

Page 57: PROGRAMACION Y M´ ETODOS NUM´ ERICOS´ … › ~vmunoz › homepage › cursos › ...El fin de una instrucci´on est´a determinado por el cierre de un par´entesis o por un punto

1.9. CLASES 53

double b = z.get_imag();

double real = sqrt((a+sqrt(a*a+b*b))/2.0);

double imag = sqrt((-a+sqrt(a*a+b*b))/2.0);

return Complejo(real,imag);

}

Notemos que intencionalmente hemos usado los nombres real e imag para las partes real eimaginaria del valor de retorno, para ilustrar que estas variables no estan en conflicto con laspartes real e imaginaria de z. La funcion sqrt() esta definida dentro del archivo complejo.h,pero como fue declarada fuera del cuerpo de la clase Complejo, no tiene acceso a las variablesprivadas.

Con la definicion anterior podemos obtener la raız cuadrada de un numero complejosimplemente con las instrucciones:

Complejo z(1,3);

Complejo raiz = sqrt(z);

Observemos finalmente que en la definicion anterior hemos usado el polimorfismo desqrt() a nuestro favor, ya que la definicion de la raız cuadrada para numeros complejosusa, a su vez, la definicion de la raız cuadrada para numeros reales. Y no hay confusionrespecto a cual funcion llamar en cada instancia, ya que las distintas versiones de la funcionse distinguen por el numero y tipo de sus argumentos.

1.9.10. Sobrecarga de operadores

¿Como le decimos al computador que el signo + tambien puede aceptar numeros com-plejos? La respuesta es facil, porque para C++ un operador no es sino una funcion (llama-da operator +), que acepta dos argumentos (los dos sumandos). Por lo tanto, sobrecargarun operador es tan sencillo como sobrecargar una funcion. Por lo tanto, bastara poner encomplejo.h el siguiente codigo:

#ifndef COMPLEJO_H

#define COMPLEJO_H

#include <cmath>

#include <iostream>

using namespace std;

class Complejo{

private:

double real;

double imag;

public:

Complejo();

Page 58: PROGRAMACION Y M´ ETODOS NUM´ ERICOS´ … › ~vmunoz › homepage › cursos › ...El fin de una instrucci´on est´a determinado por el cierre de un par´entesis o por un punto

54 CAPITULO 1. UNA BREVE INTRODUCCION A C++

Complejo(double,double=0);

Complejo(const Complejo &);

~Complejo();

void set_real(double);

void set_imag(double);

double get_real();

double get_imag();

};

Complejo sqrt(Complejo);

Complejo operator + (Complejo,Complejo); // sobrecarga de ’+’

En efecto, deseamos que el operador + acepte dos sumandos de tipo Complejo, y devuelvaun nuevo Complejo.

En complejo.cc estara la implementacion:

Complejo operator + (Complejo z1,Complejo z2){

return Complejo(z1.get_real()+z2.get_real(), z1.get_imag()+z2.get_imag());

}

Ahora el siguiente codigo es completamente valido:

Complejo z = Complejo(-3.6,4.75);

Complejo w = Complejo(2,9);

Complejo r = z+w;

1.9.11. Coercion

Sabemos definir a+b, con a y b complejos. Pero ¿que pasa si a o b son enteros? ¿O reales?Pareciera que tendrıamos que definir no solo

Complejo operator + (Complejo a, Complejo b);

sino tambien todas las combinaciones restantes:

Complejo operator + (Complejo a, int b);

Complejo operator + (Complejo a, float b);

Complejo operator + (int a, Complejo b);

etcetera.En realidad esto no es necesario. En efecto, un numero real es un numero complejo

con parte imaginaria nula. Entonces, si encontramos una expresion como z+2.3, con z unnumero complejo, entonces una solucion sencilla serıa promover automaticamente al 2.3 aun numero complejo (2.3 + i · 0), y entonces la suma estarıa bien definida. Sabemos queesta accion, llamada coercion sucede con los tipos de variables predefinidas (ver Sec. 1.1.9).Lo que queremos, entonces, es que haya coercion desde los numeros reales a los numeros

Page 59: PROGRAMACION Y M´ ETODOS NUM´ ERICOS´ … › ~vmunoz › homepage › cursos › ...El fin de una instrucci´on est´a determinado por el cierre de un par´entesis o por un punto

1.9. CLASES 55

complejos (claramente es todo lo que necesitamos, porque como la coercion desde los int,float, etc. a los double ya existe, entonces sumar cualquier tipo de numero con un Complejo

va a funcionar).Recordando lo visto en la Sec. 1.3.7, notamos que la manera de lograr esta coercion

entre double y Complejo es definir un constructor tal que su segundo parametro (la parteimaginaria) tenga un valor default igual a cero. De este modo, las dos expresiones:

Complejo z1(2);

Complejo z2(2,0);

seran equivalentes.Ya sabemos que lograr esto es sencillo: basta modificar el constructor con dos argumentos

ya declarado:

//----------Archivo complejo.h----------

#ifndef COMPLEJO_H

#define COMPLEJO_H

using namespace std;

class Complejo{

private:

double real;

double imag;

public:

Complejo();

Complejo(double,double=0); // Constructor con parametro default

Complejo(const Complejo &);

~Complejo();

void set_real(double);

void set_imag(double);

double get_real();

double get_imag();

};

Complejo sqrt(Complejo);

#endif

Los contenidos de complejo.cc quedan intactos.Con esta pequena modificacion, el siguiente codigo es valido y hace lo que uno espera:

Complejo z = Complejo(-3.6,4.75);

Complejo r = 2+z;

Gracias entonces a los constructores con parametro default, solo es necesario definir lasuma entre numeros complejos, complementada con una coercion.

Page 60: PROGRAMACION Y M´ ETODOS NUM´ ERICOS´ … › ~vmunoz › homepage › cursos › ...El fin de una instrucci´on est´a determinado por el cierre de un par´entesis o por un punto

56 CAPITULO 1. UNA BREVE INTRODUCCION A C++

1.9.12. Interaccion con la pantalla/teclado o archivos

Nuestra clase Complejo tiene ya bastante funcionalidad interesante, que la hace equiva-lente a cualquier otro tipo de variable predefinida en el sistema. Hay un aspecto que todavıano hemos implementado: la salida a pantalla o archivo. Porque el siguiente codigo:

int k = 3;

cout << k << endl;

es valido, pero si k fuera una variable de tipo Complejo, no lo serıa.Para solucionar esto, basta con notar que << es un operador, como cualquier otro, y por

lo tanto puede ser sobrecargado. Esta sobrecarga se hace de la siguiente forma: primero, ladeclaracion en complejo.h:

//----------Archivo complejo.h----------

#ifndef COMPLEJO_H

#define COMPLEJO_H

using namespace std;

class Complejo{

private:

double real;

double imag;

public:

Complejo();

Complejo(double,double=0);

Complejo(const Complejo &);

~Complejo();

void set_real(double);

void set_imag(double);

double get_real();

double get_imag();

};

Complejo sqrt(Complejo);

ostream & operator << (ostream &,Complejo); // Sobrecarga de << para

// salida a pantalla

#endif

Analicemos la situacion: En una expresion como cout << z << endl, se realiza primero laoperacion mas a la izquierda, es decir cout << z. Como con todo operador, esto es equiva-lente a operator << (cout,z). Esto es lo que esta definiendo nuestra sobrecarga: << es unoperador que espera un primer argumento de tipo ostream (ahora podemos ver que cout noes sino un objeto de cierto tipo, ostream, definido en <iostream>; endl es otro objeto de

Page 61: PROGRAMACION Y M´ ETODOS NUM´ ERICOS´ … › ~vmunoz › homepage › cursos › ...El fin de una instrucci´on est´a determinado por el cierre de un par´entesis o por un punto

1.10. ALGUNAS CLASES UTILES 57

tipo ostream), y un segundo argumento de tipo Complejo (o del tipo que queramos enviar apantalla).

El resultado de esta operacion (llamemoslo output_parcial) es del mismo tipo ostream,ya que eso permite que la siguiente operacion operator << (output_parcial,endl) sea unaoperacion entre dos objetos ostream, para lo cual ya existe una sobrecarga en <iostream>.

Por lo tanto, lo unico que se requiere definir es la sobrecarga de << actuando sobre unsegundo argumento de tipo Complejo, y de todo lo demas se encargan las sobrecargas yadefinidas en <iostream>.

Notemos, incidentalmente, que en realidad el objeto ostream (cout) es pasado por re-ferencia, ya que el flujo de salida debe ser modificado como resultado del propio acto desalida.

Solo queda por implementar la sobrecarga en complejo.cc, lo que podrıa ser de la forma:

ostream & operator << (ostream & os,Complejo c){

os << c.get_real() << " + " << c.get_imag() << "i";

return os;

}

Con esto logramos finalmente que el codigo:

Complejo z = Complejo(-3.6,4.75);

cout << z << endl;

sea valido, dando como resultado la salida en pantalla:

-3.6 + 4.75i

Notemos que tenemos completa libertad para formatear la salida a pantalla del modo quenos parezca. Si decidieramos que la salida debe ser en la forma de par ordenado, basta concambiar la implementacion de la sobrecarga de << adecuadamente.

De modo analogo se pueden definir sobrecargas para el operador de entrada, >> (hayque reemplazar el tipo ostream por istream, y las sobrecargas para entrada y salida desdearchivos, en cuyo caso se pueden usar los tipos ofstream y ifstream segun corresponda.

1.10. Algunas clases utiles

1.10.1. string

En la Sec. 1.4.4 mostramos que existe el tipo de variable string, que permite manejararreglos de caracteres de modo muy conveniente. Ahora que tenemos el lenguaje adecuado,podemos advertir que este tipo de variable corresponde a una clase string, definida en elheader <string>. Por una parte, tiene una funcionalidad parecida a las matrices de caracteres,en el sentido que permite alojar texto, y los caracteres individuales se pueden extraer comosi fuera un vector:

string palabra="ejemplo";

string c = palabra[3];

Page 62: PROGRAMACION Y M´ ETODOS NUM´ ERICOS´ … › ~vmunoz › homepage › cursos › ...El fin de una instrucci´on est´a determinado por el cierre de un par´entesis o por un punto

58 CAPITULO 1. UNA BREVE INTRODUCCION A C++

El resultado sera que c contendra "m" (como con los otros vectores de C++ , el primer elementocorresponde al ındice 0.

Pero ademas hay muchas otras caracterısticas que no poseen las matrices de caracteresusuales. De acuerdo a lo visto en la Sec. 1.4.4, existen sobrecargas de los operadores << y>> para objetos tipo string. Pero tambien existen numerosas otras sobrecargas y funcionesutiles. Por ejemplo, se pueden “sumar” dos string:

string texto1 = "Primera parte";

string texto2 = "Segunda parte";

cout << texto1 + ", " + texto2 << endl;

El resultado en pantalla sera

Primera parte, Segunda parte

Tambien existen funciones miembro de la clase que permiten extraer texto desde unaposicion hasta otra determinada, encontrar la primera aparicion de un caracter en un string,o la ultima:

string texto = "Un texto de prueba";

string texto1 = texto.substr(6,4);

string texto2 = texto.find("e");

string texto3 = texto.rfind("e");

En el codigo anterior, texto1 sera igual a "to d": 4 caracteres a partir de la posicion 6,ocupada por "t"; texto2 sera igual a 4 (la posicion de la "e" en "texto"); y texto3 sera iguala 15 (la posicion de la "e" en "prueba").

Ademas, la clase proporciona diversas funciones utiles para manejar palabras (convertirtodas las letras en mayusculas, contar el numero de caracteres, etc.).

Una funcion interesante, en particular, es la que permite ingresar lıneas completas:

string texto;

cout << "Ingrese una frase completa: " << endl;

getline(cin,texto);

Como resultado de este codigo, la variable texto contendra el texto que el usuario ingresehasta que pulse Enter. Esto permite resolver el problema de que el flujo de datos de entraday salida siempre esta delimitado por espacios en blanco, lo cual harıa que el siguiente codigo

string texto;

cout << "Ingrese una frase completa: " << endl;

cin >> texto;

deje en texto solo una palabra, ya que el flujo se cortarıa al encontrar el primer espacio enel input.

Page 63: PROGRAMACION Y M´ ETODOS NUM´ ERICOS´ … › ~vmunoz › homepage › cursos › ...El fin de una instrucci´on est´a determinado por el cierre de un par´entesis o por un punto

1.11. TEMPLATES 59

1.10.2. complex

En la Sec. 1.9 hemos ilustrado la utilidad de las clases en C++ mediante la construccion deuna clase para numeros complejos. En realidad, en la librerıa estandar de C++ sı existe unaclase para numeros complejos, con toda la funcionalidad que necesitamos. Esta contenida enel header complex, y el siguiente codigo muestra algunos ejemplos de su uso:

#include<iostream>

#include<cmath>

#include<complex>

using namespace std;

int main () {

complex<double> z(-3.6,4.75);

complex<double> r = 2.0+z;

cout << r << endl;

cout << sqrt(r) << endl;

cout << sin(z*r) << endl;

return 0;

}

Inicialmente se define un complejo z, con parte real igual a -3.6 y parte imaginaria 4.75.La sintaxis es similar a la que nosotros usamos: un constructor con dos argumentos (verSec. 1.9.5. Luego, construimos un segundo complejo r, que es el anterior mas el numero real2 (el segundo parametro del constructor tiene un valor default igual a cero, ver Sec. 1.9.11; yel operador de suma esta sobrecargado para complejos, ver Sec. 1.9.10). Finalmente, diversasfunciones matematicas estan ya sobrecargadas para los complejos (ver Sec. 1.9.9).

1.11. Templates

La sintaxis presentada en la Sec. 1.10.2 es curiosa, porque no habıamos visto antes, salvoal incluir headers, el uso de <...>. En realidad, la clase complex es un ejemplo de una tecnicapermitida por C++ , que es el uso de templates . En esta seccion revisaremos algunas de suscaracterısticas y su utilidad.

1.11.1. Funciones

Supongamos que queremos implementar una funcion para intercambiar entre sı dos va-riables, tal como lo describimos en la Sec. 1.3.5. Si las variables son, digamos, tipo int, unmodo de hacerlo serıa el siguiente:

void intercambio(int & a, int & b){

int temp=a;

a=b;

Page 64: PROGRAMACION Y M´ ETODOS NUM´ ERICOS´ … › ~vmunoz › homepage › cursos › ...El fin de una instrucci´on est´a determinado por el cierre de un par´entesis o por un punto

60 CAPITULO 1. UNA BREVE INTRODUCCION A C++

b=temp;

}

¿Que pasa ahora si queremos intercambiar dos variables tipo double? Tendrıamos que du-plicar el codigo para sobrecargar la funcion, y el codigo final podrıa quedar ası:

#include <iostream>

using namespace std;

void intercambio(int &, int &);

void intercambio(double &, double &);

int main(){

int i=3, j=5;

intercambio(i,j);

cout << i << " " << j << endl;

double r=3.5, s=10.9;

intercambio(r,s);

cout << r << " " << s << endl;

return 0;

}

void intercambio(int & a, int & b){

int temp=a;

a=b;

b=temp;

}

void intercambio(double & a, double & b){

double temp=a;

a=b;

b=temp;

}

Hay algo claramente no eficiente en este codigo, ya que el cuerpo de ambas funciones esexactamente el mismo. Lo unico diferente es el tipo de las variables.

La solucion para este problema es el uso de templates : son funciones abstractas, definidaspara un tipo de variable generico. El template adecuado para nuestro problema es el siguiente:

template <class T>

void intercambio(T & a, T & b){

T temp=a;

Page 65: PROGRAMACION Y M´ ETODOS NUM´ ERICOS´ … › ~vmunoz › homepage › cursos › ...El fin de una instrucci´on est´a determinado por el cierre de un par´entesis o por un punto

1.11. TEMPLATES 61

a=b;

b=temp;

}

La estructura es la misma que antes, pero ahora el tipo de variable no es ni int, ni double,ni ningun tipo conocido en particular, sino uno generico que llamamos (arbitrariamente, solopor simplicidad) T. Para usar esta funcion con variables tipo int (digamos i y j), basta conllamarla de esta forma:

intercambio<int>(i,j);

El codigo completo que utiliza ambas versiones, para int y double, serıa:

#include <iostream>

using namespace std;

template <class T>

void intercambio(T & a, T & b){

T temp=a;

a=b;

b=temp;

}

int main(){

int i=3, j=5;

intercambio<int>(i,j);

cout << i << " " << j << endl;

double r=3.5, s=10.9;

intercambio<double>(r,s);

cout << r << " " << s << endl;

return 0;

}

Esto es muy util, porque de hecho, por el solo hecho de haberla definido como template, lafuncion se puede utilizar con cualquier tipo de variable conocido por nuestro codigo, no solopor los dos en los que habıamos pensado originalmente. Por ejemplo, podemos intercambiardos numeros complejos z y w:

intercambio<complex<double> > (z,w);

(El unico cuidado que hay que tener es no olvidar el espacio entre el > de complex y el > deintercambio, ya que eso serıa interpretado como el operador >>, que tiene otro significado.)

Page 66: PROGRAMACION Y M´ ETODOS NUM´ ERICOS´ … › ~vmunoz › homepage › cursos › ...El fin de una instrucci´on est´a determinado por el cierre de un par´entesis o por un punto

62 CAPITULO 1. UNA BREVE INTRODUCCION A C++

Aparte de anteponer template <class T>, la otra diferencia con las declaraciones usualesde funciones es que la declaracion no puede ir separada de la implementacion en templates.esa es la razon por la cual, mientras en el codigo de la pag. 60 la implementacion se pusodespues de main, en el codigo con template, en la pag. 61 no fue ası.

La razon de este comportamiento “inusual” es que, como hemos indicado antes, al intentarusar una funcion en el codigo, el compilador requiere la declaracion para revisar consistenciade tipos de retorno y argumentos. Cuando se trata de un template, en cambio, esa revisiones imposible, precisamente porque es generica, y en definitiva se requiere la implementacionexplıcito de la funcion para que se pueda insertar el codigo adecuado en elpunto en que lafuncion es llamada, y la compilacion sea exitosa.

Una consecuencia adicional de esto es que, si se desea colocar la definicion de templatesen header files , no se puede poner la declaracion en el archivo .h y la implementacion en el.cc correspondiente; todo debe estar en el .h.

1.11.2. Clases

Tambien es posible definir templates para clases. Digamos que necesitamos una clasePunto, que aloja las coordenadas cartesianas (un par ordenado) de un punto en el espacio.Podrıamos tener algo basico con este codigo, si las coordenadas fueran int:

class Punto{

private:

int x;

int y;

public:

void set_x(int);

void set_y(int);

int get_x();

int get_y();

};

El mismo problema de la Sec. 1.11.1 aparece: si quisieramos ahora un Punto con coor-denadas double, deberıamos crear una nueva clase, y escribir las implementaciones de lasfunciones correspondientes.

Con templates, dicho trabajo es innecesario. El siguiente codigo muestra como serıa laclase Punto definida como un template, junto con las implementaciones de todas sus funcionesmiembro, y una sobrecarga para la salida a pantalla. Digamos que este codigo esta contenidoen un archivo punto.h:

//----------Archivo punto.h----------

#ifndef PUNTO_H

#define PUNTO_H

#include <iostream>

using namespace std;

Page 67: PROGRAMACION Y M´ ETODOS NUM´ ERICOS´ … › ~vmunoz › homepage › cursos › ...El fin de una instrucci´on est´a determinado por el cierre de un par´entesis o por un punto

1.11. TEMPLATES 63

template <class T>

class Punto{

private:

T x;

T y;

public:

void set_x(T);

void set_y(T);

T get_x();

T get_y();

};

template <class T>

ostream & operator << (ostream & os, Punto<T> p){

os << "[" << p.get_x() << ", " << p.get_y() << "]";

return os;

}

template <class T>

void Punto<T>::set_x(T a){

x=a;

}

template <class T>

void Punto<T>::set_y(T a){

y=a;

}

template <class T>

T Punto<T>::get_x(){

return x;

}

template <class T>

T Punto<T>::get_y(){

return y;

}

Dos observaciones:

– Si la clase es declarada como template (con un tipo generico T en nuestro ejemplo)todas las funciones miembros que necesiten, ya sea como tipo de retorno o como algunode sus argumentos, el tipo T, deben ser tambien declaradas como templates.

– Las implementaciones de las funciones y las sobrecargas deben referirse a la clase comoPunto<T>.

Page 68: PROGRAMACION Y M´ ETODOS NUM´ ERICOS´ … › ~vmunoz › homepage › cursos › ...El fin de una instrucci´on est´a determinado por el cierre de un par´entesis o por un punto

64 CAPITULO 1. UNA BREVE INTRODUCCION A C++

El siguiente ejemplo muestra como se usarıa la clase Punto, tanto con int como double:

#include <iostream>

#include "punto.h"

using namespace std;

int main(){

int i=3, j=5;

Punto<int> p_i;

p_i.set_x(i);

p_i.set_y(j);

cout << "Punto de enteros: " << p_i << endl;

double r=3.6, s=-5e3;

Punto<double> p_d;

p_d.set_x(r);

p_d.set_y(s);

cout << "Punto de dobles: " << p_d << endl;

return 0;

}

Del ejemplo anterior podemos entender que la clase de complejos definida en <complex> esen realidad un template, y cuando declaramos un numero complejo como complex<double>,estamos indicando que usaremos dicha clase con partes real e imaginaria dadas por variablestipo double, pero del mismo modo podrıamos tener variables int o float, por ejemplo, y lasdeclaraciones correspondientes serıan complex<int> y complex<float>, respectivamente.

El output del programa anterior es:

[3, 5]

[3.6, -5000]

La primera lınea es un punto con coordenadas enteras, la segunda un punto con coordenadasreales.

Pero, como dijimos, lo interesante es que, habiendo definido esta clase como un template,esta habilitada para funcionar con coordenadas de cualquier tipo previamente definido. Porejemplo, teniendo nuestra clase de numeros complejos, podrıamos crear, con el mismo codigo,una clase de puntos con coordenadas complejas:

complex<double> r_c(3.6,2);

complex<double> s_c(-5e3,-3);

Page 69: PROGRAMACION Y M´ ETODOS NUM´ ERICOS´ … › ~vmunoz › homepage › cursos › ...El fin de una instrucci´on est´a determinado por el cierre de un par´entesis o por un punto

1.11. TEMPLATES 65

Punto<complex<double> > p_c;

p_c.set_x(r_c);

p_c.set_y(s_c);

cout << p_c << endl;

que tendrıa como salida a pantalla:

[(3.6,2), (-5000,-3)]

O incluso una clase de puntos, en que sus coordenadas sean, a su vez, puntos:

double r=3.6, s=-5e3;

Punto<double> p_d;

p_d.set_x(r);

p_d.set_y(s);

Punto<double> p_d2;

p_d2.set_x(s);

p_d2.set_y(r);

Punto<Punto<double> > p_p;

p_p.set_x(p_d);

p_p.set_y(p_d2);

cout << p_p << endl;

cuyo output serıa:

[[3.6, -5000], [-5000, 3.6]]

1.11.3. Standard Template Library (STL)

Aprovechando el poder de los templates, las instalaciones normales de C++ incluyen unalibrerıa estadar de templates (Standard Template Library , o STL. Destacamos algunas de lasclases disponibles en la STL, y algunas de sus funciones miembros mas utiles. Existen muchasclases y funciones disponibles, cuyo uso se puede consultar en la documentacion respectiva.

vector

Funcionalidad similar a los arreglos de C++ (Sec. 1.4), pero con muchas propiedades adi-cionales muy utiles. Para usarlos, se debe incluir el header <vector>. En su forma mas simple,se declaran y se usan de modo analogo a los arreglos usuales:

Page 70: PROGRAMACION Y M´ ETODOS NUM´ ERICOS´ … › ~vmunoz › homepage › cursos › ...El fin de una instrucci´on est´a determinado por el cierre de un par´entesis o por un punto

66 CAPITULO 1. UNA BREVE INTRODUCCION A C++

#include<iostream>

#include<vector>

using namespace std;

int main () {

vector<int> w(5);

for(int i=0;i<5;i++){

w[i]=i*i;

}

for(int i=0;i<5;i++){

cout << w[i] << " ";

}

cout << endl;

return 0;

}

Primero se define un vector de variables int, de dimension 5. Luego cada elemento se llenay se envıa a pantalla. Para acceder al elemento i-esimo, basta con escribir w[i].

Sin embargo, a diferencia de los arreglos usuales, un vector tiene dimension variable. Ası,por ejemplo, se pueden agregar elementos al final de un vector preexistente con la funcionmiembro push_back():

vector<int> w(5);

for(int i=0;i<5;i++){

w[i]=i*i;

}

w.push_back(-10);

Al ejecutar este codigo, los elementos de w son:

0 1 4 9 16 -10

Esto permite, por ejemplo, partir con un vector vacıo, e irle agregando elementos progre-sivamente:

vector<int> w;

for(int i=0;i<5;i++){

w.push_back(i*i);

}

Tambien se puede eliminar el ultimo elemento de un vector, con pop_back():

Page 71: PROGRAMACION Y M´ ETODOS NUM´ ERICOS´ … › ~vmunoz › homepage › cursos › ...El fin de una instrucci´on est´a determinado por el cierre de un par´entesis o por un punto

1.11. TEMPLATES 67

vector<int> w;

for(int i=0;i<5;i++){

w.push_back(i*i);

}

w.pop_back();

Al ejecutar este codigo, los elementos de w son:

0 1 4 9

De modo analogo, existen las funciones push_front() y pop_front() que agregan oquitan elementos del comienzo del vector.

A diferencia de los arreglos usuales, el largo de un vector sı se puede saber, gracias a lafuncion size(). Por ejemplo:

int n = w.size();

cout << w.size() << endl;

La funcion unique(), por su parte, permite crear un nuevo vector que tiene los elementosde otro vector, pero eliminando todos los duplicados. Por ejemplo, en el siguiente codigocreamos un vector con dos elementos repetidos, y, con unique, otro sin dichos duplicados:

vector<int> w(5);

for(int i=0;i<5;i++){

w[i]=i*i;

}

w.push_back(4);

v=unique(w);

Con este codigo, v y w tienen, respectivamente, los elementos:

0 1 4 9 9

0 1 4 9

La funcion clear() permite borrar todos los elementos de un vector:

vector<int> w(5);

for(int i=0;i<5;i++){

w[i]=i*i;

}

int n=w.size();

Page 72: PROGRAMACION Y M´ ETODOS NUM´ ERICOS´ … › ~vmunoz › homepage › cursos › ...El fin de una instrucci´on est´a determinado por el cierre de un par´entesis o por un punto

68 CAPITULO 1. UNA BREVE INTRODUCCION A C++

w.clear();

int m=w.size();

Con este codigo, las variables n y m son 5 y 0, respectivamente.

list

Existe tambien otro tipo de objeto, llamado list, definido en el header <list>. Es pa-recido a vector, y tiene muchas funciones iguales de hecho. La gran diferencia es que loselementos individuales no se accesan con los parentesis [], sino a traves de objetos auxiliaresllamados iteradores . En el siguiente ejemplo, creamos una lista vacıa, y la llenamos, usandopush_back(), con los cuadrados de los primeros 5 numeros enteros. Hasta este punto, elcodigo es el mismo que con vector. Pero a continuacion, se muestran los elementos de lalista en pantalla, y el codigo es muy diferente al que usarıamos con vector:

#include<iostream>

#include<list>

using namespace std;

int main () {

list<int> v;

for(int i=0;i<5;i++){

v.push_back(i*i);

}

for(list<int>::iterator it=v.begin();it!=v.end();it++){

cout << *it << endl;

}

return 0;

}

Para entender la logica de esto, recordemos que los arreglos en C++ estan alojados enzonas contiguas de memoria. Para los arreglos usuales y los objetos vector, esas zonas dememoria se recorren con un numero entero. Las listas, en cambio, son objetos mas abstrac-tos, y se deben recorrer con un iterador. Como las listas son templates, los elementos de unalista podrıan ser de cualquier tipo de variable, y por tanto se requiere un iterador asociadoa cada lista posible, con cada tipo posible de variable. Por eso, el iterador no se denomi-na simplemente iterator, sino que list<int>::iterator (un “iterador para una lista deenteros”).

Podemos imaginarnos un iterador como un puntero, que indica la direccion de memoriaque aloja a cada elemento de la lista. Existen algunos iteradores especiales: v.begin() es eliterador (“puntero”) asociado al primer elemento de v, y v.end() es el iterador asociado alultimo elemento de v. Para avanzar en la lista, se incrementa el iterador en uno.

Page 73: PROGRAMACION Y M´ ETODOS NUM´ ERICOS´ … › ~vmunoz › homepage › cursos › ...El fin de una instrucci´on est´a determinado por el cierre de un par´entesis o por un punto

1.12. HERENCIA 69

Todo lo anterior nos permite entender el ciclo for en el codigo anterior: definimos uniterador para listas de enteros (list<int>::iterator it), y partimos definiendolo como elcomienzo de la lista (it=v.begin()); luego lo incrementamos en uno (it++), para recorrertodos los elementos de la lista, y el ciclo se detiene cuando el iterador llega al final de la lista(it!=v.end()).

Una vez posicionados en un elemento de la lista, usamos el operador de derreferencia (verSec. 1.5) para acceder al elemento alojado en dicha posicion (*it).

Ası como push_back(), las funciones pop_back(), push_front(), pop_front() y muchasotras que estan definidas para vectores, estan tambien disponibles para listas.

1.12. Herencia

Herencia es el mecanismo mediante el cual es posible definir clases a partir de otras,preservando parte de las propiedades de la primera y agregando o modificando otras.

Por ejemplo, si definimos la clase Persona, toda Persona tendra una variable miembroque sea su nombre. Si definimos una clase Hombre, tambien sera Persona, y por tanto deberıatener nombre. Pero ademas puede tener esposa. Y ciertamente no toda Persona tiene esposa.Solo un Hombre.

C++ provee mecanismos para implementar estas relaciones logicas y poder definir unaclase Hombre a partir de Persona. Lo vemos en el siguiente ejemplo:

class Persona

{

private:

char nombre[20];

public:

Persona(char [] = "");

~Persona(void);

char getname();

}

class Hombre : public Persona

{

private:

char esposa[20];

public:

Hombre(char a[]) : Persona(a)

{ };

char getwife();

void setwife();

}

Primero definimos una clase Persona que tiene nombre. Luego definimos una clase Hombrea partir de Persona (con la lınea class Hombre : public Persona). Esto permite de modoautomatico que Hombre tenga tambien una variable nombre. Y finalmente, dentro de la clase

Page 74: PROGRAMACION Y M´ ETODOS NUM´ ERICOS´ … › ~vmunoz › homepage › cursos › ...El fin de una instrucci´on est´a determinado por el cierre de un par´entesis o por un punto

70 CAPITULO 1. UNA BREVE INTRODUCCION A C++

Hombre, se definen todas aquellas caracterısticas adicionales que una Persona no tiene peroun Hombre sı: esposa, y funciones miembros para modificar y obtener el nombre de ella.

Un ejemplo de uso de estas dos clases:

Persona cocinera("Maria");

Hombre panadero("Claudio");

panadero.setwife("Estela");

cout << cocinera.getname() << endl;

cout << panadero.getname() << endl;

cout << panadero.getwife() << endl;

Observemos que panadero tambien tiene una funcion getname(), a pesar de que la claseHombre no la define explıcitamente. Esta funcion se ha heredado de la clase de la cual Hombrese ha derivado, Persona.

1.13. Make