Está en la página 1de 9

Los Tipos C++

Contenido

1 Sinopsis 5 Los tipos C++

2 La cuestión de los tipos 5.1 Tipos básicos

3 Conversión de tipos 5.2 Tipos extendidos

3.1 Transformaciones 5.3 Tipos compuestos


automáticas de tipos
5.4 Tipos incompletos
3.2 Transformaciones
intencionadas 5.5 Tipos calificados

4 Tipos estático y dinámico 5.6 Nombres de tipos

5.7 Tipo de funciones

5.8 Tipo de clases

5.9 Tipo de miembros de clase

§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 pueden
hacer), 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 (desde primaria estamos
acostumbrados a que no es lo mismo sumar peras que manzanas).
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
algunos casos la interpretación del tipo de objeto encontrado viene
determinada por la expresión utilizada para acceder al objeto.
Según la forma en que los lenguajes se comportan frente a los individuos de
las diversas "castas", se clasifican de dos formas: 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].
C++ es un lenguaje fuertemente tipado desde el punto de vista estático
("Strong static type checking"), es decir, en tiempo de compilación 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 (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 espontaneamente 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.

2
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 técnica de programación defectuosa.

§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.

§5 Los tipos C++


Aunque las cuestiones de clasificación suelen ser un tanto artificiosas,
dependiendo de la característica distintiva que se aplique, los tipos de
datos C++ pueden clasificarse como sigue:
3
§5.1 Tipos básicos

También llamados fundamentales, primitivos y escalares. No tienen


"descomposición", están predefinidos en el lenguaje. Su álgebra, es decir,
las operaciones que les son permitidas, también están preconstruidas en el
lenguaje, que dispone así mismo de constructores y destructores por defecto
para ellos [3]. Su clasificación es la siguiente:

 Asimilables a enteros
o carácter (char)

o entero (int)

o booleano (bool)

o enumeraciones (enum)

o punteros (no tienen una palabra clave específica, sino


un símbolo *, -calificador de tipo-)

 Fraccionarios (float, double)

 Ausencia de dato (void)

Más detalles sobre los tipos básicos en: ( 2.2.1)

§5.2 Tipos extendidos:


Son "adaptaciones" de detalle sobre los tipos básicos para mejor adaptarse
a necesidades específicas.

 largo (long)
...............................................................................................................................corto (short)

................................................................................................................ con signo (signed)

........................................................................................................... sin signo (unsigned)

Los enteros y fraccionarios (en todas sus variaciones long, signed, etc) se
conocen colectivamente como tipos numéricos.

§5.3 Tipos compuestos


Aparte de los anteriores, C++ soporta tipos compuestos (también
denominados tipos-clase). Son compuestos o agregados de tipos básicos,
por esta razón se les denomina también tipos agregados o abstractos ADTs
("Abstract data types"). El "material" de que están compuestos son los tipos
básicos, bien en estado "puro" o en sus diversas "adaptaciones". El proceso
es recursivo, de forma que un tipo complejo puede contener miembros que
son a su vez tipos complejos y así sucesivamente.

4
Desde el punto de vista semántico la gramática C++ establece como tipos
compuestos ("Compound types") los siguientes:

 Matrices de objetos de cualquier tipo ( 4.3).


 Funciones, que aceptan parámetros de ciertos tipos y
devuelven void u objetos (o referencias a objetos) de cierto
tipo ( 4.4).

 Punteros a-void; punteros a-objetos, o punteros a-función


(incluyendo miembros estáticos de clases) de un tipo
determinado ( 4.2).

 Punteros a miembros no-estáticos de clases (que señalan


miembros de un tipo determinado dentro de objetos de una
clase determinada 4.2.1g1).

 Referencias a objetos o funciones de un tipo determinado (


4.2.3).

 Clases ( 4.11).

 Uniones ( 4.7).

 Enumeraciones ( 4.8).

En la POO los tipos definidos por el usuario, reciben el nombre genérico de


clases, entidades abstractas cuyos miembros son aglomerados de variables
de distintos tipos (propiedades) y las funciones (métodos) que las manejan,
inicializan y destruyen ( 4.11). Este capacidad de crear nuevos tipos de
datos de cualquier nivel de complejidad y sus operaciones, es precisamente
una de las características de la POO y se considera uno de los mayores
avances en la tecnología de los lenguajes de computación.
Nota: Existen dos mecanismos para generar este proceso
recursivo (crear nuevos tipos complejos a partir de otros): la
composición y la herencia; ambos son analizados en detalle al
tratar de las clases ( 4.11.1).

Aunque desde el punto de vista de su Rvalue los punteros son asimilables a


enteros (alojan direcciones de memoria), la gramática del lenguaje los
distingue según el tipo de objeto al que apuntan, por lo que puede
considerarse que constituyen un tipo con múltiples subtipos: Punteros-a-
int; punteros-a-char; punteros-a-void; punteros-a-clase; punteros-a-
función, etc. etc. Virtualmente existen tantas clases de punteros como tipos
de objetos puedan ser señalados.

§5.4 Tipos incompletos


Existen ocasiones en las que el lenguaje permite la declaración incompleta
de tipos. Esto es: entidades de las que el compilador no tiene por el
momento toda la información que sería necesaria para una utilización
normal, pero que a ciertos efectos puede ser suficiente. Son los llamados
5
tipos incompletos ( 4.1.2). Su principal característica es que tanto su
tamaño como el patrón de bits correspondiente son desconocidos para el
compilador.

§5.5 Tipos calificados


Los tipos básicos y complejos descritos anteriormente se denominan
conjuntamente tipos no-calificados. La razón de esta denominación es que
en C++ existen ciertos especificadores (los denominaremos calificadores)
que al ser aplicados a un tipo produce un tipo nuevo (tipo calificado) con
propiedades y reglas de operación distintas del primitivo, pero con una
representación interna exactamente igual a la del tipo no-calificado.
Estos cualificadores son las palabras-clave const, volatile y const
volatile. Podemos suponer que la aplicación de estos calificadores supone
la existencia de una especie de universo paralelo, en el que para cada tipo
no-calificado puede existir la correspondiente versión const ( 3.2.1c),
volatile ( 3.2.1d), o const volatile. Aunque la descripción de cada uno de
estas palabras-clave se realiza en el apartado correspondiente, tienen
ciertas características de actuación comunes:
 Un tipo complejo (por ejemplo una estructura) no se considera un tipo
cualificado porque algunos de sus miembros lo sean.
 La calificación de un tipo complejo implica que todos sus propiedades
tienen la misma calificación salvo que sean estáticas o tengan un
calificador específico.
 Un calificador aplicado a una matriz no altera el tipo de la matriz en sí
misma, sino al tipo de sus miembros.

Recordar que C++ dispone de un sistema que permite obtener el tipo de una
entidad en tiempo de ejecución (mecanismo RTTI). Sin embargo, este
mecanismo no permite obtener la "calificación" en su caso, del objeto. Para
ciertas cuestiones la transformación de un tipo calificado en no calificado, y
viceversa, es realizada automáticamente por el compilador en lo que se
denominan conversiones triviales ( 2.2.5)

§5.6 Nombres de tipos


Tanto externamente, para el programador que debe utilizarlos, como
internamente, para las comprobaciones realizadas por el compilador, y como
argumento de los operadores sizeof ( 4.9.13), new ( 4.9.20) o typeid (
4.9.14), los tipos necesitan un nombre ("Type name") con el que
distinguirlos de entre las infinitas posibilidades pueden existir en C++.
Ejemplos:
new char [5]; // char es un nombre de tipo
typeid (float); // float es un nombre de tipo
sizeof (long double); // long double es un nombre de
tipo
En las declaraciones el nombre del tipo acompaña siempre al nombre de la
entidad que se declara (objeto o función):
char mc[5];
float fl;
long double ld;

6
La gramática C++ denomina type-id [5] al identificador de los tipos, y
señala que un type-id es sintácticamente una declaración de un objeto o
función de ese tipo al que le falta el nombre de la entidad. Ejemplos:
type-id declaración Descripción del tipo
int int m m es un subtipo de los enteros
denominado int ( 2.2.4)
int* int* pi pi es puntero-a-entero
int* [5] int* mi[5] mi es una matriz de cinco
punteros-a-entero
int (*)[5] int (* pf)[5] pf es puntero-a-matriz de
cinco enteros
int* () int* f1() f1 es una función que no
acepta argumentos
devolviendo puntero-a-entero
int* (char*) int* f2  f2 función que acepta un
(char*) puntero-a-char y devuelve un
puntero-a-entero
int (*)  int (* f3) f3 puntero-a-función que
(char) (char) acepta un char y devuelve un
entero

Temas relacionados:

 Especificador typedef ( 3.2.1a)


 Designación de tipos en los compiladores ( Nota).

§5.7 Tipo de funciones


El tipo de las funciones viene determinada por sus argumentos y por el valor
devuelto. En caso de existir argumentos con valores por defecto se
considera que estos últimos no tuvieran dichos valores. Ejemplo:
int foo(int = 0);
...
int (* fptr)(int) = &foo; // Ok!!
int (* fptr)() = &foo // Error: tipo de &foo y
fptr no concuerdan

§5.8 Tipo de clases


Cada clase constituye un tipo en sí mismo; sin ningún otro tipo de atributo y
sin que intervenga su estructura interna en la definición del tipo:
class C { int x; }; // Ok. tipo class C
class C { char c;}; // Error!! tipo ya definido
class D { int x;}; // Ok. tipo class D
La segunda línea es erronea porque dentro de un subespacio de nombres
no pueden declararse dos objetos del mismo tipo con el mismo nombre.

§5.9 Tipo de miembros de clase

7
Es importante señalar que, aparte de la diferencia de tipo introducida por los
calificadores (§5.5 ), los miembros no estáticos de clase constituyen a su
vez submundos aparte en lo que respecta a los tipos. Por ejemplo: un
miembro int m de clase C no se considera del mismo tipo que un int del
espacio global o un miembro int m de otra clase D. Un caso análogo ocurre
con las funciones y los métodos de clase: una función del espacio global
aceptando un int y devolviendo void no es del mismo tipo que un método de
la clase C que acepte un int y devuelva void. El cuadro adjunto muestra
sendos ejemplos:
type-id declaración Descripción del tipo
int int m m es un int (entero)
int C:: int m m es miembro int de-la-clase
C
int* int* pi pi es un puntero-a-int
int C::* int C::* iptr iptr es puntero-a-miembro int
de-la-clase C
int (char*) int f1(char*)  f1 es función que devuelve
int y acepta puntero-a-char
int C:: int  f es una función-miembro de
(char*) C::f(char*) C que devuelve un int y
recibe un puntero-a-char

La declaración se realiza dentro del cuerpo de la clase

Temas relacionados:
 Identificación de tipos en tiempo de ejecución RTTI ( 4.9.14)
 El operador typeid ( 4.9.14)
 Modelado de tipos ( 4.9.9)
 La cuestión de los "Tipos" en las plantillas ( 4.12.2)

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 algunos prácticamente sin tipo
definido ("Tipeless"), mientras que otros presentan 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, mientras que 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).
8
[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.