|
|
Artículo realizado por
Este capítulo va a estar dedicado a ver alguno de los campos que
amplía el lenguaje C++ frente al C. De todos es sabido que, el C++,
es una ampliación al C no sólo por la nueva metodología
que incorpora sino por una serie de características que nos van a permitir
programar de un modo mucho más cómodo y potente. Pese a que en el
capítulo actual no vamos a ver todas las características, sí que
vamos a "abordar" la primera mitad. La otra, se la voy a reservar a todo lo que significa el uso de las clases en C++ cómo forma o camino para entrar "de lleno" en la programación orientada a objetos. Sin más preámbulos, comenzamos a meter "caña" a la primera de las novedades. Las primeras novedades y ampliaciones.
Como citaba al comienzo, en este capítulo no vamos a
hacer el estudio del tipo de datos clase pues, a todas luces, merece un
estudio en exclusiva dada su gran importancia en lo que es la POO en C++. De todos
modos, lo que sí vamos a hacer va a ser ver dos especificadores de tipo
que vienen del C y han sido ampliados en el C++, esto es, vamos a ver el tipo de
datos enum y struct. Después de que entendamos cómo funcionan las novedades en los anteriores especificadores, haremos una parada en el tipo de datos referencia pues, veréis, que va a suavizar, en cierto modo, el uso de punteros. Eso sí, antes pasaremos a comentar la utilidad de dos objetos como son cin y cout y, ya al final, os comentaré si no lo habéis descubierto para entonces, una nueva manera de insertar comentarios en nuestros programas. Para empezar, muchos de vosotros os habréis empezado a preguntar ¿Pero qué demonios son los especificadores de tipo?, bueno, pues todos lo sabéis ya que no son más que las palabras clave con las que definimos los distintos tipos de datos. Así, int es un especificador de tipo que define un entero o, char, un especificador que hace lo propio definiendo un tipo de dato carácter. Pero hay más como ya sabréis, float, double, etc. Una vez "aclarado" el concepto pasamos a la explicación de enum y struct como ampliaciones a lo que venía con el C, junto con la explicación dedicada a las variables de tipo referencia. Una leve, pero interesante, mejora en enum. Como seguro que muchos de vosotros no habéis utilizado mucho este tipo de datos en C, voy a realizar un breve recordatorio de su utilidad. Las constantes enumeradas o definición de un tipo enumerado consiste en la definición de un nuevo tipo de datos cuyo rango de valores se define, a su vez, por medio de una lista de constantes, es decir:
Fernando Rodríguez.
Capítulo 3.
Novedades del C++ frente al C.
enum boolean(FALSE, TRUE); enum semana (LUNES=1, MARTES, MIERCOLES, JUEVES, VIERNES, SABADO, DOMINGO); enum salidaprintf(TABULADOR ='\t', NUEVA_LINEA='\n'); |
Recodaréis que, además, las constantes enumeradas toman como valores por defecto 0,1.... a no ser que nosotros indiquemos lo contrario, como podéis ver en el ejemplo con los días de la semana en los cuales además de hacer que se empiece en 1, el resto de los valores se van a ir asignando de forma automática.
Pues bien, en C++, la palabra clave enum se va a ver incrementada en flexibilidad pues pasa a ser un especificador de tipo real.
Ya se que para muchos esto no puede tener gran "significado", pero tenéis que tener en cuenta que las constantes enumeradas, o lo que es lo mismo enum, puede considerarse como una seria alternativa a las macros definidas con #define y además, podemos utilizar en C++ el especificador sabiendo que es de tipo real. Nuestras posibilidades se ven incrementadas notablemente.
Ampliaciones importantes en struct
De todos es sabido que las estructuras (struct) tienen una importancia vital en la programación pues nos permiten construir complejas "aglutinaciones" de datos que nos van a permitir, después, trabajar con "tablas de datos" distintas de un modo fácil y directo.
Cuando utilizábamos las estructuras en C, no estábamos utilizando un tipo de datos completo, es decir, cuando definimos algo así en C
struct Guerrero { char *nombre; int fuerza; int resistencia; int experiencia; int edad; }; |
y queríamos crear alguna variable de tipo Guerrero, teníamos dos opciones. Una de ellas era definir la variable de este modo:
struct Guerrero jugador1; |
y la otra, la final de la estructura poniendo el nombre de las variables en la misma definición, es decir:
struct Guerrero { char *nombre; int fuerza; int resistencia; int experiencia; int edad; } jugador1; |
En ambos casos, teníamos una variable de tipo Guerrero que era, a su vez, de tipo estructura. La única pega de todo esto es que no vamos a definir un tipo de datos nuevo en el sentido estricto de la palabra, esto es, siempre definimos estructuras no tipos de datos. Con la llegada del C++, las estructuras pasan a ser tipos de datos completos pudiéndose definir directamente variables del tipo estructura. Volvamos a poner la definición anterior:
struct Guerrero { char* nombre; int fuerza; int resistencia; int experiencia; int edad; } |
Ahora, para declarar una variable de tipo Guerrero sólo tenemos que hacer lo siguiente:
Guerrero jugador1; |
Como podéis comprobar ahora la creación de variables del tipo estructura anterior es idéntica que cuando hacemos lo propio con un especificador entero, carácter, etc. Si recordáis lo más parecido en C lo conseguíamos con la palabra reservada typedef.
Sin embargo, no sólo se ha convertido a struct en un tipo de datos completo sino que, además y como mayor diferencia, las estructuras en C++ pueden contener funciones junto a los datos de toda la vida. Así, ya tenemos que empezar a diferenciar el contenido que se puede encontrar en una estructura, por un lado tendremos las variables de miembro que serán todas aquellas variables contenidas en la estructura y, por otro lado, tendremos las funciones miembro que, al encontrarse más relacionadas con las clases las veremos cuando lleguemos a ellas.
De esta manera, recordad que las estructuras en C++ son un tipo de datos completo y nos permiten definir variables de una manera mucho más óptima. Sabed también que podemos incluir funciones (llamadas funciones de miembro) dentro de las estructuras, esto es, junto a las variables (llamadas variables de miembro) que siempre hemos sabido que debemos definir. De todos modos, como ya dije anteriormente, no estudiaremos por ahora las capacidades derivadas de poder utilizar tales funciones incluidas en las estructuras. Por ahora, basta con conocer, por encima, las novedades.
Por si alguno se lo estaba preguntando, el acceso a las estructuras sigue siendo el mismo, esto es, utilizando el punto (.). En el caso de que tengamos punteros a estructuras, podremos utilizar la flecha de toda la vida, (->).
Ya acabando con las estructuras, si os habéis fijado en la última definición que se realizó del tipo de datos Guerrero para mostrar el ejemplo de uso en C++, veréis que la variable nombre es un puntero y que se define con el * después del tipo de variable, esto es, después del char. Esto es así porque es la metodología típica del C++ si bien, los que no queráis utilizarla podéis seguir definiendo las variables como en C, es decir, con el operador unario * justo antes del nombre de la variable.
cout y cin dos novedades muy interesantes.
A la hora de trabajar con C++ la construcción cout << se suele utilizar para la salida de datos apartando a un lado las funciones de la familia printf del C tradicional. Debemos de tener claro que cout no es más que un objeto al cual le mandamos la información numérica y de texto mediante el símbolo <<. Esto es así porque en la representación de cout << el operador << está sobrecargado para actuar de tal modo que se escriban en la salida estándar el contenido de cout. En los sucesivos capítulos del curso de C++ se expondrán todos los pormenores de lo que es la sobrecarga de operadores de todos modos y como avance hasta que lleguemos aclarar que gracias a la sobrecarga de operadores nos podemos permitir "personalizar" símbolos de operaciones como son +,-,= y ++ para que se comporten de forma distinta cuando se utilicen con objetos de clases también diferentes, sirva esta breve introducción.
Es fácilmente imaginable que si podemos enviar datos a la salida mediante cout también existirá en C++ un objeto similar pero para realizar entradas. El objeto en cuestión será cin y su funcionamiento será muy sencillo pues sólo deberemos de utilizar el formato explicado más arriba para cout. Es la operación clásica de un scanf. Así, si quisiésemos realizar una petición de una variable entera, por poner un ejemplo, podríamos poner este código (de paso aprovecho y meto un ejemplo con cout):
#include <iostream.h> // Ejemplo de utilización de cin y cout void main(void) { int valor; cout << "\nIntroduce un valor entero"; // equivalente a hacer scanf("%d",&valor); cin >> valor; cout << "\nHas introducido el valor " << valor; } |
Como podéis observar no hay ningún misterio de utilización de estas dos novedades del C++.
Antes de pasar a las referencias, daros cuenta que para trabajar con cin y cout, debemos de utilizar siempre el
archivo de cabecera "iostream.h". Podéis tomároslo por el equivalente a "stdio.h" del C.
Las referencias.
Las referencias son novedades absolutas del C++ (no se encuentran disponibles en C). Una referencia es un nuevo tipo de datos que nos va a permitir utilizar las características de los punteros pero tratándolos como variables ordinarias. Podéis imaginaros una referencia como un "alias" de una variable o, mejor dicho, como la misma variable disponible pero con un nombre distinto. ¿Vaya lío no? ;).
Pasemos a explicar cómo se utilizan las referencias para no armarnos tanto "bollo" en nuestra cabeza "programadora"...
La inicialización de una referencia es bien fácil ya que sólo tendremos que asociar la referencia que deseemos a otra variable que ya esté creada por nosotros. Una vez que hemos realizado tal inicialización, la referencia va a estar continuamente asociada con su variable correspondiente. Cabe añadir que, si quisiéramos hacer que nuestra referencia fuese el "alias" de otra variable o lo que es lo mismo que referenciase a otra variable no podríamos, tendríamos un error de compilación.
La declaración de una referencia es bien sencilla:
// dato es una variable definida por // nosotros y es de tipo entero. int dato; // referenciaDato es la referencia que hemos creado. int& referenciaDato = dato; |
Como podéis observar para crear una referencia no necesitamos más que la variable a la que queremos referenciar, que en el ejemplo es dato, junto la referencia en sí que se va a definir con el símbolo &. De este modo, referenciaDato es la referencia o alias de la variable dato.
Una vez hechas las dos operaciones anteriores cualquier cambio que hagamos sobre dato se verá reflejado en referenciaDato y viceversa, es decir, si realizamos una modificación en referenciaDato, esta también se va a ver reflejada en la variable dato.
¡Pongamos un ejemplo de todo esto!.
#include <iostream.h> void main() { int dato = 50; int& refDato = dato; cout << ‘\n’; cout << "La variable dato vale " << dato << ’\n’; cout << "La variable refDato vale " << refDato << ’\n’; // multiplicamos la variable // dato por 2, ahora dato = 100 dato *= 2; cout << "La variable dato vale " << dato << ’/n’; cout << "La variable refDato vale " << refDato << ’\n’; // incrementamos el valor de la // referenicia, ahora refDato = 101; refDato ++; cout << "La variable dato vale " << dato << ’\n’; cout << "La variable refDato vale " << refDato; } |
Si ejecutáis este programita veréis que la salida que se obtiene sería:
50 50 100 100 101 101
Con lo que deducimos que los cambios efectuados en dato y refDato se ven involucrados. Debéis de saber que tanto dato como refDato comparten la misma dirección de memoria y por eso, cualquier cambio que efectuemos sobre dato afectará a refDato del mismo modo que si lo hiciéramos al revés.
Para cercioraros de que tanto la variable como la referencia poseen la misma dirección tan sólo tenéis que ejecutar este listado que os muestro a continuación y comprobar que la dirección de dato y refDato es la misma.
#include <iostream.h> void main() { int dato = 50; int& refDato = dato; cout << ‘\n’ << "La dirección de la variable dato es " << &dato << ‘/n’; cout << ‘\n’ << "La dirección de la referencia refDato es " << &refDato << ‘\n’; } |
Si lo ejecutáis veréis que salen los mismos valores.
Una de las cosas que tenemos que tener bien claro es la utilización del operador unario & con una variable y con una referencia. Cuando declaramos una referencia estamos definiendo un tipo de datos que es exclusivo del C++, en este caso, el operador & va junto al tipo de datos que estamos definiendo como referencia así, si nosotros queremos crear una referencia que va a referenciar a una variable de tipo entero pondremos algo como int& referencia.
Otro uso bien distinto es cuando lo que queremos obtener es la dirección en memoria de cierta variable o dato. En este caso tanto en C como en C++ el operador unario & se comporta de igual modo dándonos la dirección en memoria de tal variable o dato. Tan sólo tenéis que ejecutar el último de los listados expuestos el cual nos mostrará, por pantalla, la dirección en memoria de una variable de tipo entero y la de una referencia que tiene con alias precisamente tal variable de tipo entero.
Referencias y punteros.
Todo lo que hacíamos con punteros lo podemos realizar con las referencias de un modo mucho más intuitivo. Pese a que nosotros cuando trabajamos con las referencias lo hacemos como si de otra variable más se tratara, el compilador lo que realmente está haciendo es utilizar esa referencia como un valor asociado a la dirección en memoria de la variable en cuestión, es decir, nosotros utilizamos las referencias como si de otra variable más se tratará mientras que el compilador realmente las utiliza como los tan temidos e importantes punteros del C.
Como lo mejor para ver esto es con un ejemplo ahora os muestro cómo realizar el cambio del valor de una variable local desde una función con C, es decir, con punteros y, después, la versión en C++ utilizando las referencias.
#include <stdio.h> /* Prototipo de la función para cambiar valor */ void CambiaVble (int *punt); void main(void) { int vble = 50; printf("/n El valor de vble es %d",vble); CambiaVble (&vble); printf("\n El valor de vble es %d",vble); } void CambiaVble (int *punt) { *punt = 0; } |
Esta es la forma tradicional de trabajar en C y la salida debería de ser
El valor de vble es 100 El valor de vble es 0
Pasemos ahora a ver cómo hacer lo mismo pero utilizando referencias.
#include <iostream.h> /* Prototipo de la función para cambiar valor */ void CambiaVble (int& ref); void main(void) { int vble = 50; cout << "\n El valor de vble es " << vble; CambiaVble (vble); cout << "\n El valor de vble es " << vble; } void CambiaVble (int& ref) { ref = 0; } |
Si os fijáis bien y lleváis un tiempo trabajando con punteros en C veréis que realmente el utilizar referencias es como utilizar cualquier otro tipo de variable y esto último hace que los "escabrosos" punteros se vuelvan mucho más sencillos de utilizar para el programador.
Estos dos listados lo he realizado pensando en mostrar como ejemplo de comparativa entre punteros y referencias con lo que muy pocas ventajas pueden ser sacadas de ellos. Pese a que su uso es muy recomendado, hay que tener cuidado especialmente cuando uno empieza a codificar un programa de cierta envergadura en el cual, rápidamente comienzan a crecer variables, constantes y demás parámetros que pueden "ocultar" a nuestras referencias.
Cuando trabajábamos en C siempre sabíamos localizar un puntero por el hecho de llevar siempre el operador unario como identificativo frente al resto de variables de nuestra creación. Con todo esto quiero decir que una referencia no tiene que ser identificada utilizando ninguna clase de símbolo especial y que puede ser confundida como una variable más pasando por alto el gran "poder" contenido en ella.
Las referencias, en definitiva, deben de ser utilizadas cuidadosamente procurando en todo lo posible, poner algún comentario explicativo que nos identifique rápidamente que eso a lo que vamos a "tocar" no es una variable como otra cualquiera sino la citada referencia. Estáis avisados, se pueden meter muchas patadas con esto...
Los comentarios en C++
Ya para finalizar este capítulo vamos a echar un vistazo a la novedad referida al formato con el que podemos incluir nuestros comentarios. Como ya habréis podido observar a lo largo de todos los ejemplos incluidos, los comentarios en C++ se van a poner justo después de que pongamos las dos barras invertidas //. Esta forma de poder poner los comentarios nos va a permitir trabajar de una forma mucho más cómoda y rápida para poner nuestras anotaciones. Lógicamente también podemos seguir utilizando las clásicas /* */ para dejar nuestros apuntes en el código.
Y se acabó el capítulo.
Pues sí, ya hemos concluido este segundo capítulo de POO. Hay alguna que otra cosa que me hubiera gustado comentar como era el caso del uso de cualificadores, esto es, const y todas sus aplicaciones así que no os estrañéis si lo meto en un apéndice.
Para la siguiente entrega de Macedonia, espero poder volver con más cosas entre las cuales estará, sin duda, el capítulo sobre las Clases.
|
|