Pensar en C++ (Volumen 1) - Grupo ARCO

Pensar en C++ (Volumen 1) - Grupo ARCO Pensar en C++ (Volumen 1) - Grupo ARCO

arco.esi.uclm.es
from arco.esi.uclm.es More from this publisher
13.01.2015 Views

✐ ✐ ✐ “Volumen1” — 2012/1/12 — 13:52 — page 150 — #188 ✐ Capítulo 4. Abstracción de Datos de ambas librerías en la misma unidad de traducción Afortunadamente, el compilador C dará un mensaje de error diciéndonos que hay una incoherencia de tipos en las listas de argumentos de ambas declaraciones. No obstante, aunque no incluyamos los archivos de cabecera en la unidad de traducción igual tendremos problemas con el enlazador. Un buen enlazador detectará y avisará cuando se produzca uno de estos conflictos de nombres. Sin embargo, hay otros que simplemente tomarán el primer nombre de la función que encuentren, buscando en los archivos objeto en el orden en el que fueron pasados en la lista de enlazado. (Este comportamiento se puede considerar como una ventaja ya que permite reemplazar las funciones de las librerías ajenas con funciones propias.) En cualquiera de los dos casos, llegamos a la conclusión de que en C es imposible usar dos bibliotecas en las cuales existan funciones con nombres idénticos. Para solucionar este problema, los proveedores de librerías en C ponen un prefijo único a todas las funciones de la librería. En nuestro ejemplo, las funciones initialize() y cleanup() habría que renombrarlas como CStash_initialize() y CStash_cleanup(). Esta es una técnica lógica: decoramos los nombres de las funciones con el nombre de la estructura sobre la cual trabajan. Este es el momento de dirigir nuestros pasos a las primeras nociones de construcción de clases en C++. Como el lector ha de saber, las variables declaradas dentro de una estructura no tienen conflictos de nombres con las variables globales. ¿Por qué, entonces, no aprovechar esta característica de las variables para evitar los conflictos de nombres de funciones declarándolas dentro de la estructura sobre la cual operan O sea, ¿por qué no hacer que las funciones sean también miembros de las estructuras 4.3. El objeto básico Nuestro primer paso será exactamente ese. Meter las funciones C++ dentro de las estructuras como «funciones miembro». Éste es el aspecto que tiene la estructura una vez realizados estos cambios de la versión C de la CStash a la versión en C++, a la que llamaremos Stash: //: C04:CppLib.h // C-like library converted to C++ struct Stash { int size; // Size of each space int quantity; // Number of storage spaces int next; // Next empty space // Dynamically allocated array of bytes: unsigned char* storage; // Functions! void initialize(int size); void cleanup(); int add(const void* element); void* fetch(int index); int count(); void inflate(int increase); }; ///:~ La primera diferencia que puede notarse es que no se usa typedef. A diferencia de C que requiere el uso de typedef para crear nuevos tipos de datos, el compila- 150 ✐ ✐ ✐ ✐

✐ ✐ ✐ “Volumen1” — 2012/1/12 — 13:52 — page 151 — #189 ✐ 4.3. El objeto básico dor de C++ hará que el nombre de la estructura sea un nuevo tipo de dato automáticamente en el programa (tal como los nombres de tipos de datos int, char, float y double). Todos los datos miembros de la estructura están declarados igual que antes; sin embargo, ahora las funciones están declaradas dentro del cuerpo de la struct. Más aún, fíjese que el primer argumento de todas las funciones ha sido eliminado. En C++, en lugar de forzar al usuario a que pase la dirección de la estructura sobre la que trabaja una función como primer argumento, el compilador hará este trabajo, secretamente. Ahora sólo debe preocuparse por los argumentos que le dan sentido a lo que la función hace y no de los mecanismos internos de la función. Es importante darse cuenta de que el código generado por estas funciones es el mismo que el de las funciones de la librería al estilo C. El número de argumentos es el mismo (aunque no se le pase la dirección de la estructura como primer argumento, en realidad sí se hace) y sigue existiendo un único cuerpo (definición) de cada función. Esto último quiere decir que, aunque declare múltiples variables Stash A, B, C; no existirán múltiples definiciones de, por ejemplo, la función add(), una para cada variable. De modo que el código generado es casi idéntico al que hubiese escrito para una versión en C de la librería, incluyendo la «decoración de nombres» ya mencionada para evitar los conflictos de nombres, nombrando a las funciones Stash_initialize(), Stash_cleanup() y demás. Cuando una función está dentro de una estructura, el compilador C++ hace lo mismo y por eso, una función llamada initialize() dentro de una estructura no estará en conflicto con otra función initialize() dentro de otra estructura o con una función initialize() global. De este modo, en general no tendrá que preocuparse por los conflictos de nombres de funciones - use el nombre sin decoración. Sin embargo, habrá situaciones en las que deseará especificar, por ejemplo, esta initialize() pertenece a la estructura Stash y no a ninguna otra. En particular, cuando defina la función, necesita especificar a qué estructura pertenece para lo cual, en C++ cuenta con el operador :: llamado operador de resolución de ámbito (ya que ahora un nombre puede estar en diferentes ámbitos: el del ámbito global o dentro del ámbito de una estructura. Por ejemplo, si quiere referirse a una función initialize() que se encuentra dentro de la estructura Stash lo podrá hacer con la expresión Stash::initialize(int size). A continuación podrá ver cómo se usa el operador de resolución de ámbito para definir funciones: //: C04:CppLib.cpp {O} // C library converted to C++ // Declare structure and functions: #include "CppLib.h" #include #include using namespace std; // Quantity of elements to add // when increasing storage: const int increment = 100; void Stash::initialize(int sz) { size = sz; quantity = 0; 151 ✐ ✐ ✐ ✐

✐<br />

✐<br />

✐<br />

“Volum<strong>en</strong>1” — 2012/1/12 — 13:52 — page 150 — #188<br />

✐<br />

Capítulo 4. Abstracción de Datos<br />

de ambas librerías <strong>en</strong> la misma unidad de traducción Afortunadam<strong>en</strong>te, el compilador<br />

C dará un m<strong>en</strong>saje de error diciéndonos que hay una incoher<strong>en</strong>cia de tipos <strong>en</strong><br />

las listas de argum<strong>en</strong>tos de ambas declaraciones. No obstante, aunque no incluyamos<br />

los archivos de cabecera <strong>en</strong> la unidad de traducción igual t<strong>en</strong>dremos problemas<br />

con el <strong>en</strong>lazador. Un bu<strong>en</strong> <strong>en</strong>lazador detectará y avisará cuando se produzca uno<br />

de estos conflictos de nombres. Sin embargo, hay otros que simplem<strong>en</strong>te tomarán<br />

el primer nombre de la función que <strong>en</strong>cu<strong>en</strong>tr<strong>en</strong>, buscando <strong>en</strong> los archivos objeto <strong>en</strong><br />

el ord<strong>en</strong> <strong>en</strong> el que fueron pasados <strong>en</strong> la lista de <strong>en</strong>lazado. (Este comportami<strong>en</strong>to se<br />

puede considerar como una v<strong>en</strong>taja ya que permite reemplazar las funciones de las<br />

librerías aj<strong>en</strong>as con funciones propias.)<br />

En cualquiera de los dos casos, llegamos a la conclusión de que <strong>en</strong> C es imposible<br />

usar dos bibliotecas <strong>en</strong> las cuales existan funciones con nombres idénticos. Para<br />

solucionar este problema, los proveedores de librerías <strong>en</strong> C pon<strong>en</strong> un prefijo único a<br />

todas las funciones de la librería. En nuestro ejemplo, las funciones initialize()<br />

y cleanup() habría que r<strong>en</strong>ombrarlas como CStash_initialize() y CStash_cleanup().<br />

Esta es una técnica lógica: decoramos los nombres de las funciones<br />

con el nombre de la estructura sobre la cual trabajan.<br />

Este es el mom<strong>en</strong>to de dirigir nuestros pasos a las primeras nociones de construcción<br />

de clases <strong>en</strong> <strong>C++</strong>. Como el lector ha de saber, las variables declaradas d<strong>en</strong>tro de<br />

una estructura no ti<strong>en</strong><strong>en</strong> conflictos de nombres con las variables globales. ¿Por qué,<br />

<strong>en</strong>tonces, no aprovechar esta característica de las variables para evitar los conflictos<br />

de nombres de funciones declarándolas d<strong>en</strong>tro de la estructura sobre la cual operan<br />

O sea, ¿por qué no hacer que las funciones sean también miembros de las estructuras<br />

4.3. El objeto básico<br />

Nuestro primer paso será exactam<strong>en</strong>te ese. Meter las funciones <strong>C++</strong> d<strong>en</strong>tro de<br />

las estructuras como «funciones miembro». Éste es el aspecto que ti<strong>en</strong>e la estructura<br />

una vez realizados estos cambios de la versión C de la CStash a la versión <strong>en</strong> <strong>C++</strong>, a<br />

la que llamaremos Stash:<br />

//: C04:CppLib.h<br />

// C-like library converted to <strong>C++</strong><br />

struct Stash {<br />

int size; // Size of each space<br />

int quantity; // Number of storage spaces<br />

int next; // Next empty space<br />

// Dynamically allocated array of bytes:<br />

unsigned char* storage;<br />

// Functions!<br />

void initialize(int size);<br />

void cleanup();<br />

int add(const void* elem<strong>en</strong>t);<br />

void* fetch(int index);<br />

int count();<br />

void inflate(int increase);<br />

}; ///:~<br />

La primera difer<strong>en</strong>cia que puede notarse es que no se usa typedef. A difer<strong>en</strong>cia<br />

de C que requiere el uso de typedef para crear nuevos tipos de datos, el compila-<br />

150<br />

✐<br />

✐<br />

✐<br />

Hooray! Your file is uploaded and ready to be published.

Saved successfully!

Ooh no, something went wrong!