Documenti di Didattica
Documenti di Professioni
Documenti di Cultura
Objetivos
Conocer el entorno de programación y familiarizarse con algunos aspectos del lenguaje C++ de cara a poder
realizar con él el resto de prácticas de la asignatura. Crear la clase imagen que utilizaremos en prácticas
posteriores.
1
podemos compilarlo ası́:
prompt> g++ -c rectangulo.cc -Wall -ansi -pedantic
y nos generará el fichero rectangulo.o que podremos enlazar (link ) posteriormente para formar un fichero
ejecutable. Para poder utilizar las funciones de este fichero objeto en nuestros programas basta con decla-
rar las cabeceras de las funciones, no es necesario el cuerpo. De esta manera, podemos tener un fichero
principal.cc siguiente:
1 void rectangulo ( int alto , int ancho , char letra = ’* ’ );
2 int main () {
3 rectangulo (4 ,5);
4 return 0;
5 }
que se podrá compilar con el comando
prompt> g++ -o principal principal.cc rectangulo.o -Wall -ansi -pedantic
para obtener un ejecutable de nombre principal
Normalmente, en lugar de declarar las funciones en el fuente que las vaya a utilizar, se escribe un
fichero de cabecera (header ) que suele tener extensión .h, en tal caso escribirı́amos un fichero denominado3
rectangulo.h que contiene simplemente:
1 void rectangulo ( int alto , int ancho , char letra = ’* ’ );
de manera que ahora la función principal queda:
1 # include " rectangulo . h "
2 int main () {
3 rectangulo (4 ,5);
4 }
3 No es imprescindible, aunque sı́ aconsejable, utilizar el mismo nombre para los .h y los .cc asociados.
1 # include < iostream >
2 # include < cstdlib >
3 using namespace std ;
4 int main ( int argc , char * argv []) {
5 int i , suma =0;
6 for ( i = 1; i < argc ; i ++)
7 suma += atoi ( argv [ i ]);
8 cout << " La suma de los argumentos vale " << suma << endl ;
9 return 0;
10 }
Por ejemplo:
prompt> ./sumaargumentos 10 11 12
La suma de los argumentos vale 33
La función atoi no permite darse cuenta de que la cadena no es un número válido, tal y como muestra el
siguiente ejemplo:
prompt> ./sumaargumentos casa telefono
La suma de los argumentos vale 0
Ejercicio Para detectar estos casos, se recomienda utilizar adecuadamente la función strtol, búscala en
las páginas del manual o en la red y modifica el programa adecuadamente.
Otra biblioteca que nos podrı́a ser útil es <sstream> que nos permite utilizar una cadena de caracteres
como un stream. Veamos un ejemplo. Vamos a combinar dos programas anteriores para que, dado un fichero,
nos muestre cada lı́nea y las palabras que contiene:
1 # include < iostream >
2 # include < iomanip >
3 # include < fstream >
4 # include < sstream >
5 using namespace std ;
6 int main ( int argc , char * argv []) {
7 int cuenta ;
8 const int longlinea = 1000;
9 char linea [ longlinea ];
10 const int longcadena = 100;
11 char cadena [ longcadena ];
12 if ( argc != 2) {
13 cerr << " Uso del programa : " << argv [0] << " nombre_de_fichero \ n " ;
14 } else {
15 fstream fich ;
16 fich . open ( argv [1] , ios :: in ); // abrimos en modo lectura
17 if (! fich ) {
18 cerr << " He tenido problemas para abrir el fichero \" "
19 << argv [1] << " \"\ n " ;
20 } else {
21 // mostramos linea por linea lo que nos ofrece el fichero
22 cuenta = 1;
23 while ( fich . getline ( linea , longlinea )) {
24 cout << " Lı́nea " << setw (3) << cuenta
25 << " : \" " << linea << " \"\ n " ;
26 cuenta ++;
27 // ahora mostramos las palabras de lı́nea
28 istringstream fichlinea ( linea ); // creamos un stream cadena
29 int cuenta_cadena = 1;
30 while ( fichlinea >> cadena ) {
31 cout << " Cadena " << cuenta_cadena << " - esima : \" "
32 << cadena << " \"\ n " ;
33 cuenta_cadena ++;
34 }
35 }
36 }
37 fich . close ();
38 }
39 }
Ejemplo
extrae
> no puedo, la cola esta vacı́a
inserta 3.5
inserta 2.4
ver
>cola[3.5 2.4]
extrae
>3.5
ver
>cola[2.4]
inserta 5.2
inserta 1.1
ver
>cola[1.1 5.2 2.4]
Los valores extraidos se mostrarán por salida estándar. Se debe controlar el caso en que se intenta extraer
de la cola vacı́a. SUGERENCIA! No intentes leer el segundo argumento del comando inserta sin antes
comprobar que efectivamente es una inserción, pues los métodos ver y extrae no reciben argumentos.
Ejercicio Se pide sobrecargar los operadores << y >> para las operaciones de introducir y extraer elementos
de la cola. Observa que al extraer datos con el operador >> no hay forma de saber si la cola estaba vacı́a.
Ejercicio Se pide sobrecargar el operador << tal y como se ha explicado. Una forma de realizar este ejercicio
consiste en implementar una función que se limite a llamar al método mostrar corrrespondiente de la clase.
6. La clase imagen
A continuación vamos a implementar una clase imagen que te será útil en práticas posteriores. Esta clase
representa una imagen en color (RGB con 256 valores de intensidad para cada componente) y dispondrá de
métodos para cargar y salvar una imagen en formato ppm ascii. Escribe man ppm en la consola (si no
encuentra ppm en las páginas de manual puedes buscarlo en la red) para ver una descripción detallada del
formato. A nosotros nos interesará el formato ppm ascii, aquı́ tienes el ejemplo que pone en el manual:
P3
# feep.ppm
4 4
15
0 0 0 0 0 0 0 0 0 15 0 15
0 0 0 0 15 7 0 0 0 0 0 0
0 0 0 0 0 0 0 15 7 0 0 0
15 0 15 0 0 0 0 0 0 0 0 0
Ejercicio Implementa una clase en c++ denominada ImageColor que permita leer o escribir una imagen
en color en formato pgm ascii basándote en estas cabeceras:
1 # ifndef PIXEL_H
2 # define PIXEL_H
3 struct Color {
4 unsigned char r ,g , b ;
5 Color ( unsigned char r =0 , unsigned char g =0 , unsigned char b =0)
6 : r ( r ) , g ( g ) , b ( b ) {}
7 void to_gray_level () { } // completar !
8 };
9 # endif // PIXEL_H
para definir un pixel y la siguiente para definir la clase ImageColor:
1 # ifndef IMAGE_H
2 # define IMAGE_H
3
7 class ImageColor {
8 public :
9 int getWidth () const { }
10 int getHeight () const { }
11 // coord2index returns a linear index for a pair of ( row , col ) coordinates
12 int coord2index ( int row , int col ) const { }
13
18 ImageColor (): data (0) , width (0) , height (0) {} // null image , 0 x0 , no pixels
19 ImageColor ( int width , int height ); // creates an empty ( black ) image
20 ImageColor ( std :: istream & fich_in ); // reads a ppm image from a file
21 ~ ImageColor ();
22
23 private :
24 Color * data ;
25 int width , height ;
26 };
27
28 # endif // IMAGE_H
El formato pgm ascii es muy fácil de manipular. Tras una primera lı́nea con la palabra clave P3 vienen
3 valores correspondientes a las columnas, filas y valor máximo. El valor máximo suele ser 255 y se refiere al
valor del color blanco (el 0 es el color negro). Valores intermedios corresponden al gris. Tras estos valores,
tenemos, por cada pixel, tripletas con valores entre 0 y el valor máximo, recorriendo la imagen por filas.
Uno de los objetivos de este ejercicio es que implemente el operador de indexación para poder acceder a
los pixels de la imagen como si ésta fuese una matrix:
ImageColor img (10 ,20); // crea una imagen en negro de 10 x20
Obtén una imagen cualquiera, aquı́ tienes una imagen utilizada en muchos artı́culos de tratamiento de
imágenes:
wget http://upload.wikimedia.org/wikipedia/en/2/24/Lenna.png
Para poder leerlo en formato ppm ascii primero hay que convertir el fichero a ese formato, para ello
puedes ejecutar el comando:
convert -compress none Lenna.png Lenna.ppm
o bien:
convert -compress none Lenna.png ppm:-| programaQueLeeImagenDeEntradaEstandar
Ejercicio Escribe un programa que lea una imagen, la pase a a gris y la vuelva a escribir, todo en formato
RGB, para ello basta con transformar cada pixel a tres valores iguales de RGB con el valor de luminosidad
que viene dado por la fórmula: 0.3*r+0.59*g+0.11*b
7.1. Valgrind
Valgrind es una potente herramienta que ejecuta un programa en una CPU emulada y ofrece varias
utilidades de depuración. Su uso más común es el de encontrar errores en el uso de memoria dinámica, como
por ejemplo accesos a bloques de memoria que no pertenecen a nuestro programa o bloques de memoria que
han sido reservados y no liberados. Veamos un ejemplo:
1 // leak . cc - Reserva un array de 100 enteros y no lo libera
2 int main ()
3 {
4 int * p = new int [100];
5
6 return 0;
7 }
A la hora de depurar es conveniente generar una version del ejecutable especı́fica para este fin, utilizando
la opción -g de gcc/g++ y desactivando las optimizaciones. Esto permite a las herramientas disponer de más
información de forma que pueden, por ejemplo, especificar en qué linea del código fuente se ha encontrado
un error. Ası́ pues, compilamos nuestro fichero fuente:
g++ -g -o leak leak.cc
y lo lanzamos con valgrind:
$ valgrind -q --leak-check=full ./leak
==29292==
==29292== 400 bytes in 1 blocks are definitely lost in loss record 1 of 1
==29292== at 0x4022F14: operator new[](unsigned) (vg_replace_malloc.c:268)
==29292== by 0x8048490: main (leak.cc:4)
La opción -q ejecuta valgrind en modo silencioso. En este modo se omite gran parte de la salida y se
muestran sólo los errores. Podemos ver cómo valgrind detecta que se ha perdido un bloque de memoria, que
fue reservado por un operator new[] en la funcion main(), en la lı́nea 4 de leak.cc
Veamos otro ejemplo: cómo detectar un acceso fuera de un bloque reservado.
1 // overflow . cc - Reserva un vector de 100 enteros , y escribe fuera del mismo
2 int main ()
3 {
4 int * p = new int [100];
5 p [100]=1234; // error tı́pico , el vector va del 0 al 99 ;)
6 delete [] p ;
7
8 return 0;
9 }
Si compilamos y ejecutamos este programa probablemente no obtengamos ningún error de segmenta-
ción, ya que es posible que el espacio inmediatamente después del bloque todavı́a se encuentre dentro del
espacio de direcciones del proceso. No obstante, escribir fuera del bloque es un error ya que podemos estar
sobreescribiendo otros datos del programa. Valgrind también nos puede ayudar a detectar esta clase de
errores:
$ g++ -g -o overflow overflow.cc
$ valgrind -q --leak-check=full ./overflow
==29291== Invalid write of size 4
==29291== at 0x80484CC: main (overflow.cc:5)
==29291== Address 0x42ab1b8 is 0 bytes after a block of size 400 alloc’d
==29291== at 0x4022F14: operator new[](unsigned) (vg_replace_malloc.c:268)
==29291== by 0x80484C0: main (overflow.cc:4)
En este caso vemos que se ha realizado en la linea 5 de overflow.cc una escritura no válida de 4 bytes,
en una dirección que está justo después (0 bytes after) del bloque reservado en un new[] en la lı́nea 4 del
mismo fichero.
En general, cuando algo va mal o cuando creemos que hemos terminado el programa suele ser buena idea
dar una pasada con valgrind para ver si todo va bien :)
7.2. GDB
Para tareas de depuración más complejas podemos recurrir a gdb (GNU Debugger). gdb nos permite
hacer las mismas cosas que cualquier depurador tradicional: ejecución paso a paso, inspección de variables,
breakpoints, watchpoints...
Un ejemplo completo de uso de gdb serı́a demasiado largo para este boletı́n y además es muy fácil
encontrar tutoriales con una sencilla búsqueda. A continuación se incluye, a modo de referencia, una pequeña
lista con las órdenes más habituales:
help Muestra la ayuda.
run Ejecuta el programa.
break Establece un breakpoint. Ejemplos:
break fichero.cc:38 ←− breakpoint al llegar a una lı́nea de código
break main ←− breakpoint al llegar a una función
break *0x80484b5 ←− breakpoint en una dirección de memoria
list Muestra un trozo de código fuente alrededor de la lı́nea que se esté ejecutando en ese momento. También
permite especificar un nombre de función o un número de lı́nea igual que break.
bt (o backtrace) Muestra la pila de registros de activación de funciones.
up/down Seleccionan el registro anterior/siguiente de la pila.
info locals Muestra las variables locales de la función correspondiente al registro de activación actual.
cont Continúa la ejecución de un programa tras un breakpoint.
step Ejecuta una lı́nea de código. Si esta lı́nea contiene una llamada a función saltaremos a la primera lı́nea
de la función llamada.
next Ejecuta una lı́nea de código. Si la lı́nea contiene una llamada a función saltaremos a la lı́nea de después
de la llamada.
print expresión Muestra el valor de una expresión. Esta expresión puede hacer referencia a variables del
programa.
quit Sale de gdb.