2.2 Los Tipos C++ (II)
§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:
§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
carácter (char)
entero (int)
booleano (bool)
enumeraciones (enum)
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.
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. 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; 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 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 (denominados 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;
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
(![]() |
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 (char*) | f2 función que acepta un puntero-a-char y devuelve un puntero-a-entero |
int (*) (char) | int (* f3)(char) | f3 puntero-a-función que acepta un char y devuelve un entero |
Temas relacionados:
§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
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::(char*) | int C::f(char*) | f es una función-miembro de 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)
[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.