Disponible la nueva versión "donationware" 7.3 de OrganiZATOR
Descubre un nuevo concepto en el manejo de la información.
La mejor ayuda para sobrevivir en la moderna jungla de datos la tienes aquí.

Curso C++

[Home]  [Inicio]  [Índice]


Sig.

2.2  Los Tipos C++

§1 Sinopsis

Hemos señalado que los datos, entendidos como objetos que contiene información (que no sea sobre manipulaciones), se encuentran en forma de patrones de bits alojados en memora, y que tienen diversos atributos.

A uno de estos atributos lo hemos denominado tipo (tipo de dato). Hemos indicado que pertenecer a un "tipo" determinado presupone una serie de características concretas (la hemos comparado con pertenecer a una casta de la sociedad C++), y hemos señalado que la "tipología" es uno de los pilares fundamentales en que se asienta el lenguaje. En realidad señalar o definir un tipo de dato viene a significar conocer el comportamiento de los miembros de esta "casta", como se comportan (que operaciones les son permitidas); como interactúan entre sí y con los miembros de las demás castas que pueblan el universo de las entidades C++.

§2 La cuestión de los "Tipos"

Todos los lenguajes de programación tienen un sistema más o menos complejo de clasificación de los objetos en base a una característica denominada "tipo".  La característica distintiva de cada tipo viene determinada por las operaciones que pueden efectuarse con sus integrantes; lo cual es bastante natural e intuitivo  (es fácil entender que no pueden hacerse las misas cosas con una manzana que con un martillo).

Se supone que existe una especie de jerarquía de tipos, desde los más simples a los más complejos, así como de las operaciones que se pueden realizar con ellos. Por ejemplo, un int (entero, al que haremos referencia más adelante) es un tipo de dato simple (lo hemos llamado básico 2.1); del mismo modo, las operaciones aritméticas que se pueden realizar con ellos son simples también. En el caso de C++, este tipo y sus operaciones están predefinidas en el lenguaje. Sin embargo, un tipo de dato como las matrices cuadradas de números enteros, y sus operaciones, como la inversión o el operador determinante (por citar dos ejemplos), se consideran de orden superior y en el caso del C++ ni siquiera está predefinidas en el lenguaje, aunque dispone de recursos -muy potentes- para que el programador pueda utilizarlas [1].

Una característica de C++ es que el compilador asocia a cada objeto información suficiente para que pueda ser obtenido su tipo en runtime.  En C++, la determinación del tipo de un objeto puede efectuarse de dos maneras: la primera, que denominaremos "formal", se realiza cuando se declara expresamente el tipo al compilador. Por ejemplo, mediante la declaración int x; el compilador sabe casi todo lo necesario acerca de la variable x (excepto su valor actual). En otros casos la interpretación del tipo de objeto viene determinada por el contexto (expresión utilizada para acceder al objeto).  Por ejemplo, en la expresión float y = 10.0/3; el valor 10.0/3 es interpretado por el compilador como un double, que posteriormente será sometido a una conversión estrechante para convertirlo en un float.

Según la forma en que los lenguajes se comportan frente a los individuos de las diversas "castas", se clasifican de dos grupos: fuerte y débilmente tipados. Como puede suponerse, los primeros son más estrictos e intolerantes que lo segundos frente a la promiscuidad entre tipos [2], aunque caben todas las posturas intermedias. En los primeros (lenguajes fuertemente tipados) como C/C++, la técnica consiste en que el tipo de objeto queda definido desde el principio de su existencia (por declaración formal o por el contexto).  A partir de aquí, quedan perfectamente establecidas las posibilidades del objeto, y estas posibilidades se utilizan como mecanismo de seguridad.  Por ejemplo, si en la declaración anterior, la variable y, que es un float, se utiliza como argumento de una función que espera un char, el compilador avisará con un mensaje de error. En el extremo opuesto están otros lenguajes, típicamente los denominados "dinámicos" (de tipo script) como JavaScript o Tcl, en los que el tipo de objeto no está completamente definido desde el principio. Es justamente su utilización posterior la que establece sus características. En estos lenguajes es posible establecer expresiones del tipo var z; (z es una variable sin más indicación de sus propiedades). Posteriormente es posible hacer z = 10 o z = "ABCD". Será justamente la asignación la que complete las características de la variable que se dejaron indeterminadas.

C++ es un lenguaje fuertemente tipado desde el punto de vista estático ("Strong static type checking"). Es decir, en tiempo de compilación queda perfectamente definido el tipo de los objetos, y puede advertirnos de cualquier veleidad o error al respecto de tomar un tipo por otro o utilizarlo de forma inadecuada. Sin embargo, en tiempo de ejecución se comporta a veces de forma muy permisiva porque el compilador proporciona algunas conversiones automáticas de tipo (ver a continuación). También es posible que estas conversiones sean forzadas de forma explícita en el código (en realidad el programador puede terminar haciendo casi lo que le venga en gana).

Nota: La razón de que se haya puesto especial énfasis en la comprobación estática de tipos (en tiempo de compilación) en detrimento de la comprobación dinámica (en tiempo de ejecución), se debe a que en C++ todas las premisas de diseño han estado encaminadas en favor de lograr la máxima velocidad de ejecución.

§3 Conversión de tipos

Al llegar a este punto, conviene hacer una advertencia: a veces es necesario y conveniente que el programa permita un poco de "manga ancha" en la cuestión de los tipos. En el caso de C++ esto puede ocurrir de dos formas:

  • Transformaciones realizadas espontáneamente por el compilador .

  • Transformaciones realizados voluntaria y explícitamente por el programador .

§3.1 Transformaciones realizadas automáticamente por el compilador

Son situaciones en las que el compilador se amolda a nuestros deseos. Es (entre otros) el caso de los "asimilables a enteros", con los que el compilador se comporta de forma muy permisiva (nos referiremos a ellos inmediatamente). Por ejemplo, las expresiones:

char uno = '1';      // uno es tipo caracter; '1' es el dígito 1

int x = uno - 48;    // x == valor numérico (decimal) del dígito 1

son perfectamente legales en C++, aunque la segunda sea desde luego bastante escandalosa desde el punto de vista formal.

Esta posibilidad de transformación, realizada muchas veces de forma automática por el compilador, y que a la postre se traduce en una cierta "promiscuidad" con los tipos de datos, es precisamente uno de los reproches que le hacen a C++ sus detractores. En este sentido C++ es un lenguaje débilmente tipado. En el apartado de Conversiones estándar ( 2.2.5) se incluye una continuación de esta "cuestión de los tipos" desde el punto de vista de las conversiones que realiza el compilador de forma rutinaria.

Es importante tener conciencia de esta ambigüedad con que se comportan algunos tipos de datos en determinadas circunstancias. Como hemos visto en el ejemplo, un char puede ser en un momento un carácter, para a continuación comportarse como un int, sin dejar por ello de seguir siendo un auténtico char. Otro ejemplos (de los varios que se podrían citar) de conversión automática de tipos realizada por el compilador ocurre con los tipos bool ( 3.2.1b), y la realizada en la invocación de funciones para ajustar el tipo de los parámetros actuales con los formales ( 4.4.6)

§3.2 Transformaciones realizadas intencionadamente

En otros casos, C++ dispone de operadores específicos que permiten saltarse (hasta cierto punto) las rigideces de la separación de tipos que siempre subsiste en el compilador. Esta característica distintiva de C++ es lo que se conoce como modelado de tipos (casting 4.9.9);  la capacidad de aplicar un operador a un tipo de dato para obtener otro tipo como resultado. Naturalmente esta "adaptación" no siempre es posible; se requiere una mínima similitud entre ambos.

Nota:  aunque hemos señalado que a veces es conveniente un poco de "manga ancha" en la cuestión de los tipos, y que esta permisividad se ve reforzada en C++ por el mecanismo de modelado, debemos advertir que la frecuente necesidad de "castings" es probable indicio de un mal diseño o de una técnica de programación deficiente.

§4 Tipo estático y dinámico

El tipo estático de una expresión es la que resulta de su análisis del fuente sin que intervengan consideraciones de ejecución. El tipo estático solo depende de la redacción del programa, y permanece inalterado durante la ejecución del mismo.

El tipo dinámico de una expresión que es un Rvalue, es su tipo estático.

El tipo dinámico de una expresión que es un Lvalue, es el tipo del objeto más derivado a que se refiera la expresión (esto solo tiene sentido en jerarquías de clases).  Ejemplo: Supongamos que [4]:

B* p;    // p es puntero-a-superclase B

S s;     // s es objeto de subclase S derivada de B

p = &s;  // Ok p señala a s


En este caso decimos que el tipo estático de p es B* (puntero-a-superclase B), y que el tipo dinámico de la expresión *p (que es un Lvalue), es S.

  Inicio.


[1] C++ es un lenguaje de propósito general que no ha sido especialmente diseñado para la computación numérica, por lo que de forma nativa no dispone de tipos de datos de alto nivel, ni por consiguiente, de sus operaciones. Sin embargo, su velocidad de ejecución lo hace muy dotado para todo tipo de aplicaciones de cálculo, además dispone de recursos muy potentes al respecto en su Librería Estándar ( 5).

[2] El tratamiento que hacen de los "Tipos" los diferentes lenguajes si es muy variado. Frente a los fuertemente tipados, como C/C++, existen otros prácticamente sin tipo definido ("Tipeless"), mientras que otro grupo presenta una posición intermedia con una tipología muy simple y permisiva ("Loosely typed").  Un caso extremo está representado por BCPL ( 0.Iw2), uno de los ancestros de C, y una posición intermedia estaría representada por JavaScript, que cuenta solo con cinco tipos distintos y una gran capacidad de adaptación automática entre ellos. En este punto cabe hacer otra observación; en atención a su capacidad de adaptación, más que lenguajes sin tipo ("Tipeless"), algunos autores prefieren decir que se trata de lenguajes con tipos dinámicos o tolerantes ("Dynamic types" o "Tolerant types").

[3] Más detalles respecto a estos constructores para los tipos básicos en el capítulo dedicado al operador new ( 4.9.20).

[4]  Para comprender cabalmente el ejemplo debe consultarse el epígrafe  Punteros en jerarquías de clases ( 4.11.2b1).

[5]  Utilizaremos indistintamente nombre-de-tipo o su acrónimo inglés type-id.

Sig.