Cursillo de C++ (III).


Aquí está la tercera entrega del cursillo. La siguiente la mandaré sobre el día 2 de Enero (de 1998, por supuesto ;). En esta entrega veremos algo sobre el preprocesador. Todavía no es suficiente para hacer programas útiles, pero es una de los elementos imprescindibles de los programas en C/C++.

He puesto algunos ejemplos, y espero que las explicaciones estén suficientemente dtalladas, pero si no comprendes algo, no dudes en preguntar (sobre todo si te salen dudas sobre #define, que al fin y al cabo es bastante diferente de lo que hay en otros lenguajes).

He metido un par de ejercicios sencillos. Para que te los corrija, mándame tus respuestas por NET al 2:341/66.3, y te devuelvo por NET las correcciones ¿ok?.

He puesto una nota a pie de página... pero está algo fuera del nivel del capítulo, o sea que tampoco te preocupes mucho por ella.

Y aquí lo tienes :)

=== { /*cpp_3.txt*/ ===

3 NOCIONES BASICAS SOBRE EL PREPROCESADOR

En esta sección veremos:

~~~~~~~~~~~~~~~~~~~~~~~~

El preprocesador y el compilador. Qué nos permite hacer el preprocesador. Distinguir las directivas del preprocesador del resto del programa. La directiva #include. La directiva #define para las constantes y las macros. Ejercicios.

Contenido:

~~~~~~~~~~

EL PREPROCESADOR Y EL COMPILADOR

No quería continuar el cursillo sin dejar claro que lo que nosotros llamamos habitualmente el compilador de C/C++, realmente son dos programas diferentes que trabajan en equipo. Los programas son: el preprocesador, que sólo prepara el código fuente con ciertas tareas que podríamos denominar de buscar/reemplazar, y el compilador, que traduce nuestro bonito programa en C/C++ a código objeto (instrucciones del procesador mas algo de información para otros programas que nos permitirán mezclar código de varios compiladores y depurar el programa, por mencionar dos de los usos más importantes).

Mientras que está muy clara cuál es la función del compilador, el que haya un preprocesador puede parecer un poco extraño, sobre todo porque a la mayoría de los lenguajes les va muy bien sin ningún tipo de preprocesador. Vamos a ver entonces qué »servicios« nos proporciona el preprocesador.

QUE NOS PERMITE HACER EL PREPROCESADOR

Las funciones básicas del preprocesador son:

»Incluir« ficheros en el programa (mediante #include).

Definir constantes (por medio de #define).

Definir macros (una especie de constantes parametrizadas, también con #define)

Hacer compilación condicional (dependiendo de si están definidas ciertas constantes, que omita o no ciertas partes del fichero, mediante #ifdef, #ifndef, #if defined(), #elif, #else y #endif)

Provocar errores (mediante #error, su utilidad viene dada cuando se utiliza junto con la compilación condicional)

Activar ciertas características del compilador (mediante #pragma).

Las más importantes, como podrás suponer, son las tres primeras, que son las que vamos a ver en esta entrega. El resto las dejamos para cuando veamos el preprocesador en profundidad.

Una cosa más antes de meternos con algunas de las directivas del preprocesador: en algunos textos de C++ se comenta que se tiende a intentar hacer menos uso del preprocesador, y de hecho, que se aconsejaba que se usasen otras características del lenguaje para el mismo trabajo cuando fuera posible. La explicación de esto es que algunas cosas en C sólo se podían hacer a base de macros, mientras que en C++ se pueden hacer también mediante el uso de programación orientada a objetos (más exactamente mediante herencia y polimorfismo, lo que en estos momentos seguramente te sonará a chino; no te preocupes, que todo llegará). Aún así su uso es más que obligado para muchas tareas básicas de la programación en C/C++.

DISTINGUIR LAS DIRECTIVAS DEL PREPROCESADOR DEL RESTO DEL PROGRAMA

Como ya te habrás podido dar cuanta en la sección anterior, las directivas del preprocesador son palabras precedidas por # (almohadilla).

Además tienen una restricción con respecto a las instrucciones del compilador propiamente dicho: la directiva del preprocesador tiene que estar sola en la línea (con sus parámetrso si es que los tiene), quedando cosas del estilo de:

===[ejemplospp.h]===

#include <iostream.h>

#include <string.h>

#include "misdef.h"

#if defined(__cplusplus)

#define MAX_VELOCIDAD 50

#endif

===[ejemplospp.h]===

LA DIRECTIVA #include

Como ya hemos visto, nos permite »incluir« un fichero de texto dentro de otro. Es algo así cómo »antes de continuar, procesa el contenido de este otro fichero«.

Su sintaxis es:

#include <fichero_a_incluir>

#include »fichero_a_incluir«

La diferencia entre encerrar el fichero a incluir entre mayores y menor-ques (< >) y entre comillas (" "), es en dónde busca el fichero a incluir. Como regla general, para los ficheros que vienen con el compilador (como por ejemplo iostream.h, stdio.h, string.h) se ponen entre mayores y menor-ques (< >), mientras que los ficheros hechos por ti se ponen entre comillas (" "). Esto es así porque los que pones entre comillas los busca primero en el directorio actual (presumiblemente el directorio de tu programa), mientras que los otros los busca en el directorio de nombre include que viene con el compilador.

Así pues quedan de la siguiente forma:

===[ejemplosinc.h]===

#include <stdio.h>

#include <stdlib.h>

#include <strstrea.h>

#include «defmias.hpp»

===[ejemplosinc.h]===

LA DIRECTIVA #define PARA LAS CONSTANTES Y LAS MACROS

Habíamos dicho que la directiva #define nos servía para definir constantes y macros. Veamos cómo se utiliza para cada una de esas tareas.

Una constante es algo que no varía (seguro que esa frase no me acredita para recibir un premio Nobel). Constantes típicas que nos encontramos en el mundo real pueden ser la constante de gravitación universal, el número pi, el número de días que tiene una semana o el año en que naciste.

Una razón para definir constantes en un programa es para evitar los »números mágicos«. Los números mágicos son números que aparecen en el programa y hacen que funcione, pero que no sabes por qué funcionan (debido a que no sabes qué representan). El uso de constantes en vez de números mágicos tiene dos ventajas: si por cualquier causa tienes que modificar el número y se utiliza en varios sitios, el haberlo definido como una constante en un sólo sitio hace más fácil el cambio; por otra parte se entiende mejor el programa, ya que se sabe qué es lo que representa la constante (compara un »si x es mayor que 40 da un pitido« con »si x es mayor que COLUMNAS_PANTALLA da un pitido«).

A efectos prácticos el #define hace, más o menos, un buscar y reemplazar en el programa a partir del punto en el que se han definido.

La sintaxis de #define para definir constantes es:

#define NOMBRE_DE_CONSTANTE

#define NOMBRE_DE_CONSTANTE valor_de_la_constante

El nombre de la constante consiste de letras, dígitos y carácteres de subrayado (_). No debe empezar por un número. Es costumbre escribirlas en mayúsculas, para distinguirlas de las instrucciones y variables, pero no es necesario (pero sí muy aconsejable).

Como puedes ver, se pueden definir constantes sin asociar ningún valor a ellas. Eso sirve para la compilación condicional, ya que se basa en comprobar si una constante ha sido definida, independientemente del valor (y de si tiene un valor asociado o no).

Una vez que has definido una constante, el preprocesador sustituirá el nombre de la constante por el valor de la constante, sin importarle si el valor era un número, un texto, una instruccion o una mezcla de todas ellas (vamos, que el valor de la constante puede ser cualquier cosa, simpre que lo puedas meter en una sola línea).

El ejemplo que voy a poner a continuación no es muy real (no es normal que las cosas se hagan de esta manera), pero ilustra el uso de #define para definir constantes:

===[ejemplospp2.cpp]===

/*

* ejemplospp2.cpp

*/

#include <iostream.h>

#define TEXTO "¡Hola Mundo!"

#define NUMERO_DE_VEZ 2

#define ESCRIBEHOLAMUNDO cout << "Esto es otro hola mundo."

void

main() {

cout << TEXTO << " número " << NUMERO_DE_VEZ << endl;

ESCRIBEHOLAMUNDO;

}

===[ejemplospp2.cpp]===

Este programa es prácticamente igual al que vimos de »Hola Mundo«. Lo único que no te debería de sonar es el endl, que sirve para que siga escribiendo en la siguiente línea.

Cuando el preprocesador llegue a la primera línea después del main(), sustituirá TEXTO por »¡Hola Mundo!«, NUMERO_DE_VEZ por 2 y ESCRIBEHOLAMUNDO por el cout << »Esto es otro hola mundo.«, quedando main() algo del estilo de

===[así queda main después del preprocesado]===

void

main() {

cout << "¡Hola Mundo!" << " número " << 2 << endl;

cout << "Esto es otro hola mundo.";

}

===[así queda main después del preprocesado]===

Sólo queda por explicar las macros. Ya me he cansado de repetirlo, pero por una vez más no pasa nada. Las macros son una especie de constantes parametrizadas. Sólo se diferencian de las constantes porque hay sustitución dentro de ellas. Vamos a ver su sintaxis y un ejemplo sencillo:

La sintaxis de #define para definir macros es (si por ejemplo tiene cuatro parámetros):

#define NOMBREMACRO(x,y,z,t) expresión_con_los_parámetros

Por supuesto el número de parámetros de la macro es el que tú quieras. Algunos ejemplos serían:

===[ejemplosdef.h]===

#define SUMA(a,b) ((a)+(b))

#include RESTA(a,b) ((a)-(b))

#include CUBO(a) ((a)*(a)*(a))

===[ejemplosdef.h]===

Como puedes ver he rodeado en la expresión los parámetros con paréntesis. Eso se hace para evitar efectos laterales (ya que a y b pueden ser cualquier cosa, así nos evitamos problemas, ahora mismo no nos importa cuáles, ya lo veremos la explicación de estos paréntesis cuando veamos los operadores). Una vez que hemos definido las anteriores macros, las líneas

cout << SUMA(5,4);

cout << CUBO(4);

cout << RESTA(SUMA(CUBO(3),4+3),5);

se transformarían en

cout << ((5)+(4));

cout << ((4)*(4)*(4));

cout << ((((3)*(3)*(3))+(4+3))-5);

Y con esto dejamos por ahora el tema del preprocesador. Lo retomaremos un poco cuando demos operadores, que nos permitirán hacer un uso más interesante de las macros.

Ejercicios

1.- Dí para qué sirve la directiva del preprocesador #include

2.- Dí cómo quedará el siguiente fragmento de programa después de pasar el preprocesador:

===[pp_ej1.cpp]===

#define HOLA "Hola"

#define QUE_TAL "qué tal"

#define NUMERO 123

#define SUMA(x,y) ((x)+(y))

void

main(void)

{

cout << HOLA << QUE_TAL << SUMA(NUMERO,34);

}

===[pp_ej1.cpp]===

Aquí termina la 3a entrega del cursillo.

-+-

(1) Por si tienes curiosidad, la razón principal de que se pongan paréntesis es para evitar efectos laterales cuando se utilizan macros con variables y operadores unarios (incremento y decremento, por ejemplo), ya que podrían salir cosas muy extrañas al hacer la sustitución: SUMA(a,++b) quedaría ((a)+(++b)), pero si no hubiésemos puesto los paréntesis hubiera quedado a+++b, que los compiladores la interpretarían como (a++)+(b), que da un resultado completamente diferente del que pretendíamos (incrementamos a en vez de b y el resultado de la suma es un número menor). En estos momentos esto un pequeño galimatías, pero quedará claro cuando veamos los operadores.

=== } /*cpp_3.txt*/ ===

Espero que no te haya extrañado demasiado el preprocesador del C/C++. Lo utilizaremos en todos los programas que hagamos, o sea que te acostumbrarás a él en cuanto empieces a hacer programas.

Nos vemos en la siguiente entrega entonces ;)

Datos del autor/a:


Votaciones en general - Comentario a los relatos (mes anterior) - Comentario a los relatos (mes presente) - Estadísticas de los comentarios y votaciones.

[Indice general] - [Sexo] - [linux] - [humor] - Chat entre usuarios - [miscelanea] - [Novedades] -

Para hacerme llegar tus comentarios, sugerencias o si deseas colaborar con esta página, por favor, envíame un E-mail a marqueze (arroba) marqueze.net Web: http://www.marqueze.net