pr´ actica 1
introducci´ on a la programaci´ on en c++
estructuras de datos y algoritmos facultad de inform´ atica
curso 2009-2010
Objetivos
Conocer el entorno de programaci´ on y familiarizarse con algunos aspectos del lenguaje C++ de cara a poder realizar con ´ el el resto de pr´ acticas de la asignatura. Crear la clase imagen que utilizaremos en pr´ acticas posteriores.
1. Compilando un programa C++
Las pr´ acticas se realizan en el entorno Linux que dispone del compilador g++. Si tenemos el fichero hola.cc siguiente:
1
# i n c l u d e < i o s t r e a m >
2
u s i n g n a m e s p a c e std ;
3
int m a i n () {
4
c o u t < < " H o l a m u n d o !\ n " ;
5
r e t u r n 0;
6
}
podemos ejecutar el comando:
prompt> g++ -o hola hola.cc
donde la opci´ on -o sirve para indicar el nombre del ejecutable producido. Se recomienda utilizar siempre las siguientes opciones de compilaci´ on:
prompt> g++ -o hola hola.cc -ansi -pedantic -Wall
Entre otras cosas, el compilador g++ tiene extensiones no est´ andar del lenguaje C++ que vamos a considerar incorrectas a todos los efectos a lo largo de la asignatura (por ejemplo, reservar un vector dee talla no conocida en tiempo de compilaci´ on).
Otras opciones de compilaci´ on permiten modificar el nivel de optimizaci´ on (para lograr ejecutables m´ as eficientes se aconseja utilizar -O3, observa que la letra “o” tiene significados distintos en may´ uscula y en min´ uscula). Ejecuta:
prompt> man g++
o tambi´ en
1prompt> info g++
para conocer con detalle ´ estas y otras opciones.
Para que el compilador produzca un ejecutable es imprescindible que est´ e definida la funci´ on main.
Tambi´ en es posible compilar ficheros sin la funci´ on main y producir lo que se denominan ficheros objeto
2que suelen tener extensi´ on .o
As´ı, si tenemos el fichero rectangulo.cc siguiente:
1
# i n c l u d e < i o s t r e a m >
2
u s i n g n a m e s p a c e std ;
3
v o i d r e c t a n g u l o ( int alto , int ancho , c h a r l e t r a = ’ * ’ ) {
4
for ( int i = 0; i < a l t o ; i ++) {
5
for ( int j = 0; j < a n c h o ; j ++)
6
c o u t < < l e t r a ;
7
c o u t < < e n d l ;
8
}
9
}
1
Lo puedes llamar desde Emacs con M-x info.
2
Esta denominaci´ on no tiene nada que ver con la programaci´ on orientada a objetos.
1
podemos compilarlo as´ı:
prompt> g++ -c rectangulo.cc -Wall -ansi -pedantic
y nos generar´ a 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
v o i d r e c t a n g u l o ( int alto , int ancho , c h a r l e t r a = ’ * ’ );
2
int m a i n () {
3
r e c t a n g u l o (4 ,5);
4
r e t u r n 0;
5
}
que se podr´ a 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´ on .h, en tal caso escribir´ıamos un fichero denominado
3rectangulo.h que contiene simplemente:
1
v o i d r e c t a n g u l o ( int alto , int ancho , c h a r l e t r a = ’ * ’ );
de manera que ahora la funci´ on principal queda:
1
# i n c l u d e " r e c t a n g u l o . h "
2
int m a i n () {
3
r e c t a n g u l o (4 ,5);
4
}
2. Paso de par´ ametros en la l´ınea de comandos
Observa que la funci´ on main devuelve un valor int. En el entorno Unix y Linux los programas tienen la funci´ on main definida como en el ejemplo siguiente (fichero muestraargumentos.cc)
1
# i n c l u d e < i o s t r e a m >
2
u s i n g n a m e s p a c e std ;
3
int m a i n ( int argc , c h a r * a r g v []) {
4
int i ;
5
for ( i = 0; i < a r g c ; i ++) {
6
c o u t < < " P a r ´ a m e t r o " < < i < < " - e s i m o es : \" " < < a r g v [ i ] < < " \" " < < e n d l ;
7
}
8
r e t u r n 0;
9
}
Para que el programa pueda acceder a los distintos par´ ametros que le pasamos al invocarlo. Si ejecutamos este programa as´ı:
prompt> ./muestraargumentos uno dos y tres aparecer´ a por pantalla lo siguiente:
Par´ ametro 0-esimo es: "./muestraparametros"
Par´ ametro 1-esimo es: "uno"
Par´ ametro 2-esimo es: "dos"
Par´ ametro 3-esimo es: "y"
Par´ ametro 4-esimo es: "tres"
Observa que los argumentos o par´ ametros son cadenas de caracteres. Si uno de los argumentos corresponde a un n´ umero introducido por el usuario y queremos conocer su valor, debemos utilizar alguna funci´ on para convertirlo de cadena a n´ umero, como las funciones atoi, atof, strtod, strtof, strtold, strtol, etc. de la biblioteca est´ andar cstdlib, o incluso con un sstream o con la funci´ on sscanf. Por ejemplo, el siguiente programa asume que todos los argumentos recibidos por la l´ınea de comandos son n´ umeros enteros y devuelve la suma de los mismos:
3
No es imprescindible, aunque s´ı aconsejable, utilizar el mismo nombre para los .h y los .cc asociados.
1
# i n c l u d e < i o s t r e a m >
2
# i n c l u d e < cstdlib >
3
u s i n g n a m e s p a c e std ;
4
int m a i n ( int argc , c h a r * a r g v []) {
5
int i , s u m a =0;
6
for ( i = 1; i < a r g c ; i ++)
7
s u m a += a t o i ( a r g v [ i ]);
8
c o u t < < " La s u m a de los a r g u m e n t o s v a l e " < < s u m a < < e n d l ;
9
r e t u r n 0;
10
} Por ejemplo:
prompt> ./sumaargumentos 10 11 12 La suma de los argumentos vale 33
La funci´ on atoi no permite darse cuenta de que la cadena no es un n´ umero v´ alido, 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´ on strtol, b´ uscala en las p´ aginas del manual o en la red y modifica el programa adecuadamente.
3. Entrada y salida con streams
En los ejemplos anteriores hemos visto la manera de mostrar texto por la salida est´ andar utilizando los streams. Una alternativa consiste en seguir utilizando las funciones de la biblioteca est´ andar ISO-C denominada <stdio.h> que en C++ se conoce tambi´ en como <cstdio> (las funciones printf, scanf, etc.
ya conocidas por todos).
Para trabajar con streams incluimos la biblioteca <iostream> que nos ofrece, entre otras cosas, tres objetos de tipo stream: cout, cerr y cin que corresponden, respectivamente, a salida est´ andar, salida de error y entrada est´ andar. Podemos leer de entrada est´ andar de la manera siguiente:
1
# i n c l u d e < i o s t r e a m >
2
u s i n g n a m e s p a c e std ;
3
int m a i n () {
4
int i , j ;
5
c o u t < < " D a m e dos e n t e r o s s e p a r a d o s por e s p a c i o s : " ;
6
cin > > i > > j ;
7
c o u t < < " El e s t a d o de cin es " < < cin . f a i l () < < e n d l ;
8
c o u t < < " Los e n t e r o s son : " < < i < < " , " < < j < < e n d l ;
9
}
Observa que adem´ as de mostrar por pantalla los valores introducidos, tambi´ en mostramos el valor cin.fail(). Si ejecutas este programa puede resultar interesante ver qu´ e ocurre con este valor cuando no introduces los n´ umeros correctamente. Las funciones scanf, fscanf, sscanf tienen la ventaja de retornar el n´ umero de argumentos correctamente le´ıdos.
3.1. Leer un fichero
Ya sabemos m´ as o menos leer de entrada est´ andar. Ahora vamos a ver c´ omo leer y escribir en ficheros.
Para ello utilizamos la biblioteca <fstream>. Veamos un ejemplo de programa (leecadenas.cc) que espera como ´ unico argumento el nombre de un fichero, lo abre en modo lectura y muestra su contenido cadena a cadena. Esto nos servir´ a para ver c´ omo act´ ua el operador >> de un stream de entrada cuando ponemos un vector de caracteres.
Este programa comprueba que el fichero ha sido correctamente abierto y utiliza el manipulador setw(int) de la biblioteca <iomanip> para ajustar el ancho del campo al mostrar el entero cuenta. Prueba a ejecutar este programa de la manera siguiente:
g++ -o leecadenas leecadenas.cc leecadenas
leecadenas uno dos y tres
leecadenas nosoynombrefichero
leecadenas leecadenas.cc | less
1
# i n c l u d e < i o s t r e a m >
2
# i n c l u d e < iomanip >
3
# i n c l u d e < fstream >
4
u s i n g n a m e s p a c e std ;
5
int m a i n ( int argc , c h a r * a r g v []) {
6
int c u e n t a ;
7
c o n s t int l o n g c a d e n a = 1 0 0 0 ;
8
c h a r c a d e n a [ l o n g c a d e n a ];
9
if ( a r g c != 2) {
10
c e r r < < " Uso del p r o g r a m a : " < < a r g v [0] < < " n o m b r e _ d e _ f i c h e r o \ n " ;
11
} e l se {
12
f s t r e a m f i c h ;
13
f i c h . o p e n ( a r g v [1] , ios :: in ); // a b r i m o s en m o d o l e c t u r a
14
if (! f i c h ) {
15
c e r r < < " He t e n i d o p r o b l e m a s p a r a a b r i r el f i c h e r o \" "
16
< < a r g v [1] < < " \"\ n " ;
17
} e l s e { // m o s t r a m o s c a d e n a por c a d e n a lo que nos o f r e c e el f i c h e r o
18
c u e n t a = 1;
19
w h i l e ( fi c h > > c a d e n a ) {
20
c o u t < < " La c a d e n a " < < s e t w (3) < < c u e n t a
21
< < " es : \" " < < c a d e n a < < " \"\ n " ;
22
c u e n t a ++;
23
}
24
}
25
f i c h . c l o s e ();
26
}
27
r e t u r n 0;
28
}
Puede resultar de inter´ es leer l´ınea a l´ınea. Para ello utilizamos el m´ etodo getline que recibe tres par´ ametros: un puntero char* al principio de la cadena donde guardar el resultado, el tama˜ no m´ aximo de esta cadena y, por ´ ultimo, el caracter que delimita la l´ınea. Este ´ ultimo argumento tiene un valor por defecto igual a ’\n’, es decir, no hace falta ponerlo cuando lo que queremos es leer l´ıneas. Veamos una versi´ on del programa anterior para leer l´ıneas:
1
# i n c l u d e < i o s t r e a m >
2
# i n c l u d e < iomanip >
3
# i n c l u d e < fstream >
4
u s i n g n a m e s p a c e std ;
5
int m a i n ( int argc , c h a r * a r g v []) {
6
int c u e n t a ;
7
c o n s t int l o n g l i n e a = 1 0 0 0 ;
8
c h a r l i n e a [ l o n g l i n e a ];
9
if ( a r g c != 2) {
10
c e r r < < " Uso del p r o g r a m a : " < < a r g v [0] < < " n o m b r e _ d e _ f i c h e r o \ n " ;
11
} e l s e {
12
f s t r e a m f i c h ;
13
f i c h . o p e n ( a r g v [1] , ios :: in ); // a b r i m o s en m o d o l e c t u r a
14
if (! f i c h ) {
15
c e r r < < " He t e n i d o p r o b l e m a s p a r a a b r i r el f i c h e r o \" "
16
< < a r g v [1] < < " \"\ n " ;
17
} e l s e { // m o s t r a m o s l i n e a por l i n e a lo que nos o f r e c e el f i c h e r o
18
c u e n t a = 1;
19
w h i l e ( f i ch . g e t l i n e ( linea , l o n g l i n e a )) {
20
c o u t < < " La l ´ ı n e a " < < s e t w (3) < < c u e n t a
21
< < " es : \" " < < l i n e a < < " \"\ n " ;
22
c u e n t a ++;
23
}
24
}
25
f i c h . c l o s e ();
26
}
27
r e t u r n 0;
28
}
3.2. Escribir en un fichero
El siguiente ejemplo muestra un programa que abre el fichero hola.txt en modo escritura. Si dicho fichero no existe, se crea. Si el fichero ya exist´ıa, se borra su contenido. Una vez abierto, se escribe en ´ el la cadena ya conocida por todos:
1
# i n c l u d e < fstream >
2
# i n c l u d e < iomanip >
3
u s i n g n a m e s p a c e std ;
4
int m a i n () {
5
f s t r e a m f s a l i d a ;
6
f s a l i d a . o p e n ( " h o l a . txt " , ios :: out ); // a b r i m o s en m o d o e s c r i t u r a
7
f s a l i d a < < " H o l a m u n d o !\ n " ;
8
f s a l i d a . c l o s e ();
9
r e t u r n 0;
10
}
Ejercicio Realiza un programa denominado micat que recibe un n´ umero indeterminado de par´ ametros que interpretar´ a como nombres de fichero.
Si el n´ umero de argumentos argc es 1 (el propio nombre del programa) debe finalizar sin m´ as.
Todos los argumentos aparte del propio nombre del programa se refieren a nombres de fichero:
• El primer fichero (argumento argv[1]) es un fichero destino que debes abrir en modo escritura.
• El resto de ficheros son ficheros origen y se deben abrir en modo lectura.
El comportamiento del programa consiste en concatenar los ficheros origen en el fichero destino l´ınea a l´ınea poniendo el nombre del fichero origen al principio de cada l´ınea tal y como muestra el siguiente ejemplo:
prompt> cat a.txt linea uno
linea dos
prompt> cat b.txt linea uno
linea dos linea tres
prompt> ./micat c.txt a.txt b.txt prompt> cat c.txt
a.txt:linea uno a.txt:linea dos b.txt:linea uno b.txt:linea dos b.txt:linea tres
Otra biblioteca que nos podr´ıa ser ´ util 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
# i n c l u d e < i o s t r e a m >
2
# i n c l u d e < iomanip >
3
# i n c l u d e < fstream >
4
# i n c l u d e < sstream >
5
u s i n g n a m e s p a c e std ;
6
int m a i n ( int argc , c h a r * a r g v []) {
7
int c u e n t a ;
8
c o n s t int l o n g l i n e a = 1 0 0 0 ;
9
c h a r l i n e a [ l o n g l i n e a ];
10
c o n s t int l o n g c a d e n a = 1 0 0 ;
11
c h a r c a d e n a [ l o n g c a d e n a ];
12
if ( a r g c != 2) {
13
c e r r < < " Uso del p r o g r a m a : " < < a r g v [0] < < " n o m b r e _ d e _ f i c h e r o \ n " ;
14
} e l s e {
15
f s t r e a m f i c h ;
16
f i c h . o p e n ( a r g v [1] , ios :: in ); // a b r i m o s en m o d o l e c t u r a
17
if (! f i c h ) {
18
c e r r < < " He t e n i d o p r o b l e m a s p a r a a b r i r el f i c h e r o \" "
19
< < a r g v [1] < < " \"\ n " ;
20
} e l s e {
21
// m o s t r a m o s l i n e a por l i n e a lo que nos o f r e c e el f i c h e r o
22
c u e n t a = 1;
23
w h i l e ( f i c h . g e t l i n e ( linea , l o n g l i n e a )) {
24
c o u t < < " L ´ ı n e a " < < s e t w (3) < < c u e n t a
25
< < " : \" " < < l i n e a < < " \"\ n " ;
26
c u e n t a ++;
27
// a h o r a m o s t r a m o s las p a l a b r a s de l ´ ı n e a
28
i s t r i n g s t r e a m f i c h l i n e a ( l i n e a ); // c r e a m o s un s t r e a m c a d e n a
29
int c u e n t a _ c a d e n a = 1;
30
w h i l e ( f i c h l i n e a > > c a d e n a ) {
31
c o u t < < " C a d e n a " < < c u e n t a _ c a d e n a < < " - e s i m a : \" "
32
< < c a d e n a < < " \"\ n " ;
33
c u e n t a _ c a d e n a ++;
34
}
35
}
36
}
37
f i c h . c l o s e ();
38
}
39
}
4. Trabajando con clases
Antes de aprender a crear nuestras propias clases vamos a ver c´ omo utilizar una clase ya existente. Para ello debemos saber c´ omo se declaran instancias de una clase (denominadas “objetos”) llamando a un de los m´ etodos constructores (pueden haber varios). Una vez creado un objeto, podemos utilizarlo invocando m´ etodos sobre ´ el. Para probar estos conceptos os ofrecemos un ejemplo de definici´ on de una clase, denominada muestra, que nos permite hallar distintos par´ ametros estad´ısticos asociados a una muestra. Vamos a suponer que dicha muestra est´ a formada por un conjunto de enteros cuyos valores est´ an entre 0 y un valor m´ aximo.
1
c l a s s m u e s t r a {
2
p u b l i c :
3
m u e s t r a ( int t a m a n y o ); // c o n s t r u c t o r
4
~ m u e s t r a (); // d e s t r u c t o r
5
v o i d i n s e r t a r ( int n u m e r o );
6
int m o d a ();
7
p r i v a t e :
8
int tam ,* v e c t ;
9
};
10
m u e s t r a :: m u e s t r a ( int t a m a n y o ) { // v a l o r e s a i n s e r t a r e n t r e 0 y tam -1
11
int i ;
12
tam = t a m a n y o ;
13
v e c t = new int [ t a m a n y o ];
14
for ( i =0; i < tam ; i ++) v e c t [ i ] = 0;
15
}
16
m u e s t r a ::~ m u e s t r a () {
17
d e l e t e [] v e c t ;
18
}
19
v o i d m u e s t r a :: i n s e r t a r ( int n u m e r o ) {
20
v e c t [ n u m e r o ] + + ;
21
}
22
int m u e s t r a :: m o d a () {
23
int i , max , i m a x ;
24
max = v e c t [ 0 ] ; i m a x = 0;
25
for ( i =1; i < tam ; i ++)
26
if ( v e c t [ i ] > max ) {
27
max = v e c t [ i ];
28
i m a x = i ;
29
}
30
r e t u r n i m a x ;
31
}
Ejercicio Escribe un programa que, utilizando la clase muestra, lea por la entrada est´ andar una secuencia
de n´ umero enteros en el rango [0, 9999] –hasta fin de fichero– y halle la moda. Para ello has de crear un
objeto de tipo muestra y utilizar los m´ etodos disponibles. Recuerda que para invocar un m´ etodo se utiliza
la sintaxis:
vobjeto.nombre_m´ etodo(par´ ametros); // si es un objeto
pobjeto->nombre_m´ etodo(par´ ametros); // si es un puntero a objeto
4.1. Creando una clase, una estructura FIFO
Vamos a ver ahora c´ omo realizar una estructura de datos tipo cola FIFO con una clase que denominamos cola_float y que implementaremos utilizando listas simplemente enlazadas. El objetivo del ejercicio es terminar la implementaci´ on de los m´ etodos de la clase cola_float utilizando listas simplemente enlazadas (con un puntero al ´ ultimo nodo para realizar las inserciones de forma eficiente).
Ejercicio Implementa los m´ etodos de la clase siguiente:
1
s t r u c t n o d o {
2
f l o a t v a l o r ;
3
n o d o * sig ;
4
};
5
c l a s s c o l a _ f l o a t {
6
n o d o * primero ,* u l t i m o ;
7
p u b l i c :
8
c o l a _ f l o a t (); // c o n s t r u c t o r
9
~ c o l a _ f l o a t (); // d e s t r u c t o r
10
v o i d i n s e r t a r ( f l o a t v a l o r );
11
f l o a t e x t r a e r ();
12
b o o l v a c i a () c o n s t ; // i n d i c a si la c o l a e s t a v a c i a
13
v o i d m o s t r a r ( o s t r e a m & str ) c o n s t ; // i m p r i m e el c o n t e n i d o por el
14
// s t r e a m str
15
};
16
c o l a _ f l o a t :: c o l a _ f l o a t () {
17
p r i m e r o = u l t i m o = 0;
18
}
Ejercicio Tras terminar la implementaci´ on, deber´ as comprobar que funciona correctamente creando un programa que reciba de entrada est´ andar una serie de l´ıneas con los siguientes comandos:
inserta seguido de un n´ umero, lo inserta en la cola FIFO,
extrae saca el primer valor de la cola y lo muestra por salida est´ andar, si la cola est´ a vac´ıa da un error, ver visualiza la cola por salida est´ andar.
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´ an por salida est´ andar. 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´ on, pues los m´ etodos ver y extrae no reciben argumentos.
5. Sobrecarga de funciones y de operadores
Ahora que tenemos algo m´ as de soltura, vamos a profundizar en alguna otra caracter´ıstica del lenguaje C++.
La sobrecarga de operadores se puede realizar de dos modos distintos. Si el primer operando es un objeto,
podemos sobrecargar el operador en la propia clase. En cualquier caso, podemos definir el operador como una
funci´ on, aunque algunos operadores s´ olo pueden definirse del primer modo (ver transparencias de teor´ıa).
Sint´ acticamente, sobrecargar un operador es como declarar una funci´ on poniendo como nombre de la misma la secuencia formada por la palabra reservada operator y el operador a sobrecargar. Por ejemplo, vamos a sobrecargar el operador << para insertar elementos en la cola definida anteriormente:
1
c o l a _ f l o a t & o p e r a t o r < < ( f l o a t v a l o r );
y su respectiva implementaci´ on:
1
c o l a _ f l o a t & c o l a _ f l o a t :: o p e r a t o r < < ( f l o a t v a l o r ) {
2
i n s e r t a r ( v a l o r );
3
r e t u r n * t h i s ;
4
}
Observa que el operador devuelve una referencia a un objeto del mismo tipo cola_float. La utilidad de devolver este valor es que podemos enlazar esta operaci´ on de la manera siguiente:
1
c o l a _ f l o a t cf ();
2
cf < < 0.1 < < 0.2 < < 0 . 3 ;
3
f l o a t f1 , f2 , f3 ;
4
cf > > f1 > > f2 > > f3 ;
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.
5.1. Sobrecarga de los operadores para utilizar streams
Si sobrecargamos el operador << para la clase ostream y un objeto de tipo cola_float podremos hacer cosas como esta:
1
c o u t < < " La c o l a es : " < < cf < < e n d l ;
Supondremos que la forma de obtener la salida por pantalla es la siguiente: si tenemos una cola en la que hemos insertado los valores 0,1, 0,2 y 0,3 la salida ser´ıa:
La cola es: cola[0.3,0.2,0.1]
Ejercicio Se pide sobrecargar el operador << tal y como se ha explicado. Una forma de realizar este ejercicio consiste en implementar una funci´ on que se limite a llamar al m´ etodo mostrar corrrespondiente de la clase.
6. La clase imagen
A continuaci´ on vamos a implementar una clase imagen que te ser´ a ´ util en pr´ aticas posteriores. Esta clase representa una imagen en color (RGB con 256 valores de intensidad para cada componente) y dispondr´ a de m´ etodos para cargar y salvar una imagen en formato ppm ascii. Escribe man ppm en la consola (si no encuentra ppm en las p´ aginas de manual puedes buscarlo en la red) para ver una descripci´ on detallada del formato. A nosotros nos interesar´ a 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´ andote en estas cabeceras:
1
# i f n d e f P I X E L _ H
2
# d e f i n e P I X E L _ H
3
s t r u c t C o l o r {
4
u n s i g n e d c h a r r , g , b ;
5
C o l o r ( u n s i g n e d c h a r r =0 , u n s i g n e d c h a r g =0 , u n s i g n e d c h a r b =0)
6
: r ( r ) , g ( g ) , b ( b ) {}
7
v o i d t o _ g r a y _ l e v e l () { } // c o m p l e t a r !
8
};
9
# e n d i f // P I X E L _ H
para definir un pixel y la siguiente para definir la clase ImageColor:
1
# i f n d e f I M A G E _ H
2
# d e f i n e I M A G E _ H
3
4
# i n c l u d e < i o s t r e a m >
5
# i n c l u d e " p i x e l . h "
6
7
c l a s s I m a g e C o l o r {
8
p u b l i c :
9
int g e t W i d t h () c o n s t { }
10
int g e t H e i g h t () c o n s t { }
11
// c o o r d 2 i n d e x r e t u r n s a l i n e a r i n d e x for a p a i r of ( row , col ) c o o r d i n a t e s
12
int c o o r d 2 i n d e x ( int row , int col ) c o n s t { }
13
14
C o l o r & o p e r a t o r ()( int row , int col );
15
b o o l r e a d _ p p m ( std :: i s t r e a m & f i c h _ i n );
16
v o i d w r i t e _ p p m ( std :: o s t r e a m & f i c h _ o u t ) c o n s t ;
17
18
I m a g e C o l o r (): d a t a (0) , w i d t h (0) , h e i g h t (0) {} // n u l l image , 0 x0 , no p i x e l s
19
I m a g e C o l o r ( int width , int h e i g h t ); // c r e a t e s an e m p t y ( b l a c k ) i m a g e
20
I m a g e C o l o r ( std :: i s t r e a m & f i c h _ i n ); // r e a d s a ppm i m a g e f r o m a f i l e
21
~ I m a g e C o l o r ();
22
23
p r i v a t e :
24
C o l o r * d a t a ;
25
int width , h e i g h t ;
26
};
27
28
# e n d i f // I M A G E _ H
El formato pgm ascii es muy f´ acil de manipular. Tras una primera l´ınea con la palabra clave P3 vienen 3 valores correspondientes a las columnas, filas y valor m´ aximo. El valor m´ aximo 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´ aximo, recorriendo la imagen por filas.
Uno de los objetivos de este ejercicio es que implemente el operador de indexaci´ on para poder acceder a los pixels de la imagen como si ´ esta fuese una matrix:
I m a g e C o l o r img ( 1 0 , 2 0 ) ; // c r e a una i m a g e n en n e g r o de 10 x20
Obt´ en una imagen cualquiera, aqu´ı tienes una imagen utilizada en muchos art´ıculos de tratamiento de im´ agenes:
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´ ormula: 0.3*r+0.59*g+0.11*b
7. Introducci´ on a la depuraci´ on de programas
Cuando nuestro programa no funciona, existen herramientas que nos pueden ayudar a encontrar qu´ e es lo que hemos hecho mal. Vamos a presentar brevemente dos de ellas:
7.1. Valgrind
Valgrind es una potente herramienta que ejecuta un programa en una CPU emulada y ofrece varias
utilidades de depuraci´ on. Su uso m´ as com´ un es el de encontrar errores en el uso de memoria din´ amica, 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
// l e a k . cc - R e s e r v a un a r r a y de 100 e n t e r o s y no lo l i b e r a
2
int m a i n ()
3
{
4
int * p = new int [ 1 0 0 ] ;
5
6
r e t u r n 0;
7
}
A la hora de depurar es conveniente generar una version del ejecutable espec´ıfica para este fin, utilizando la opci´ on -g de gcc/g++ y desactivando las optimizaciones. Esto permite a las herramientas disponer de m´ as informaci´ on de forma que pueden, por ejemplo, especificar en qu´ e linea del c´ odigo 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´ on -q ejecuta valgrind en modo silencioso. En este modo se omite gran parte de la salida y se muestran s´ olo los errores. Podemos ver c´ omo 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´ omo detectar un acceso fuera de un bloque reservado.
1
// o v e r f l o w . cc - R e s e r v a un v e c t o r de 100 enteros , y e s c r i b e f u e r a del m i s m o
2
int m a i n ()
3
{
4
int * p = new int [ 1 0 0 ] ;
5
p [ 1 0 0 ] = 1 2 3 4 ; // e r r o r t´ ıpico , el v e c t o r va del 0 al 99 ;)
6
d e l e t e [] p ;
7
8
r e t u r n 0;
9