1 Sinopsis Los tipos de datos básicos o fundamentales de C++, se caracterizan porque no tienen “descomposición”, y tanto sus características y como las operaciones que se pueden realizar con ellos están predefinidos en el lenguaje (la literatura especializada también suele referirse a ellos diciendo que están “preconstruidos” o “intrínsecamente definidos” en el lenguaje). Nota: al tratar de los tipos abstractos veremos que en realidad, los tipos básicos son considerados también por el compilador como un tipo especial de clases preconstruidas en el lenguaje. Se pueden clasificar en tres grandes grupos, y se designan por palabras clave específicas que, en estos casos, representan especificadores de tipo.
Asimilables a enteros:
• carácter char • enteros int • booleanos bool • enumeraciones enum Son considerados tipos artiméticos, porque en determinadas circunstancias pueden ser promovidos automáticamente a enteros por el compilador. Los enteros, carácter y booleanos son conocidos como “Integral types” en la literatura inglesa.
Fraccionarios: dos versiones: float y double Ausencia de dato, sin valor, void.
Los asimilables a enteros y los fraccionarios son conocidos conjuntamente como tipos numéricos. A excepción de void, los tipos básicos pueden tener modificadores opcionales, que se usan para alterar el significado del tipo base (en cuanto a rango de valores y espacio de almacenamiento), de forma que dan lugar a variantes que se adecuan a ciertas necesidades específicas. Estos modificadores indican: con signo, sin signo, largo y corto, y se aplican con las palabras clave que se señalan.
• largo • corto • con signo • sin signo
2 Los especificadores de tipo, aislados o acompañados de uno o más de los modificadores opcionales, definen completamente un identificador. Por ejemplo, pueden darse las siguientes variedades del tipo int:
int i1; short int i2; long int i3; signed int i4; unsigned int i5; signed short int i6; unsigned short int i7; signed long int i8; unsigned long int i9;
Estas declaraciones presuponen una definición exacta de la cantidad de memoria necesaria para albergar el tipo de dato; como interpreta el programa el conjunto de bits que se almacena, y cuales son las operaciones permitidas con tales datos. La cuestión del almacenamiento es en muchos casos dependiente de la implementación concreta, es decir, depende del compilador utilizado, porque el estándar ANSI C++ deja la cuestión un tanto en el aire. En cualquier caso, el operador sizeof permite calcular el tamaño en bytes de cualquier tipo de dato, ya sea de los tipos básicos (pre-definidos en el lenguaje) o de los tipos complejos (definidos por el usuario).
Observe que los modificadores opcionales aplicados a un tipo básico definen un nuevo tipo, de forma que por ejemplo, short y unsigned short son tipos distintos. 3 Enteros
El especificador de tipo int, junto con sus variantes short y long en sus versiones signed y unsigned, identifican a los enteros.
Sintaxis: [signed|unsigned] int <identificador› ;
Descripción:
El especificador int se utiliza para definir un dato tipo número entero. Junto con sus variantes short y long en sus versiones signed y unsigned, dan lugar a las combinaciones autorizadas que se indican en la tabla, los sinónimos se deben a los valores que (salvo indicación en contrario) se suponen por defecto. Especificadores de enteros (sinónimos en la misma línea)
int signed int unsigned unsigned int short short int signed short int unsigned short unsigned short int long long int signed long int unsigned long unsigned log int
En este cuadro observe que: • signed y unsigned solo pueden usarse con int, short y long. • La palabras clave signed y unsigned, usadas aisladamente, significan signed int y unsigned int. • Con int solo pueden usarse long o short. • Las palabras clave long y short usadas aisladamente significan long int y short int. Los int fueron originariamente pensados para ser del tamaño de la palabra “natural” del procesador, aunque muchos procesadores modernos pueden manejar con igual facilidad palabras de diverso tamaño.
Por una larga tradición de C, el especificador int podía omitirse en la mayoría de los casos, ya que se suponía por defecto. Además, si no se indicaba otra cosa, se suponía también signed int. Sin embargo, en C++ es necesario especificarlo siempre, ya que es un lenguaje que se preocupa más de la integridad de los tipos utilizados (lo que puede prevenir muchos problemas).
int x; // signed int x unsigned x; // unsigned int x short x; // short signed int x unsigned short x; // unsigned short int x long x; // long signed int x unsigned long x; // unsigned long int x
Nota: por una larga tradición C, los tipos carácter y los enteros (int) eran intercambiables.
La longitud (bits) y rango son los indicados (para Borland C++). En cualquier caso, los rangos vienen indicados por las constantes que se señalan (incluidas en <limits.h>):
Tipo Tamaño bits Rango int 32 −2,147,483,648 <= X <= 2,147,483,647 signed short 16 −32767 <= X <= 32767 unsigned short 16 0 <= X <= 65535
El Estándar C++ establece que los denominados enteros con signo (“Signed integer types”) incluyen: signed char, short int, int y long int. Y que cada tipo debe tener almacenamiento suficiente para alojar al precedente en la lista anterior. También establece otra categoría: los enteros sin signo (“Unsigned integer types”) que comprende: unsigned char, unsigned short int, unsigned int y unsigned long int, que deben tener el mismo almacenamiento y alineación que los correspondientes con signo.
4 Carácter El especificador char se utiliza para definir un dato tipo carácter. Se almacenan en 1 byte, es decir, siempre ocurre que sizeof(char) == 1. Esta es la definición ANSI de byte en C/C++: la memoria requerida para almacenar un carácter [4].
Sintaxis: [signed|unsigned] char <nombre-de-variable› Descripción: Un char puede ser con signo (signed), sin signo (unsigned), o no especificado; por defecto se suponen con signo. Los objetos declarados de tipo carácter (char) pueden contener cualquiera de los caracteres del juego de ASCII básico. Son valores que pueden asimilarse fácilmente a los enteros; de hecho se pueden realizar con ellos operaciones aritméticas, el compilador los trata entonces como valores numéricos. En estos casos puede pensarse en char como un número muy pequeño, por lo que pueden ser usados libremente en expresiones aritméticas, lo que proporciona considerable flexibilidad en cierto tipo de transformaciones con caracteres Más sobre los tipos carácter en general
Ejemplos:
char letra, caracter = ‘x’; letra = ‘a’ // carácter es ‘x’ char s[]; // suponemos una matriz de caracteres s[i] - ‘0′ /* valor numérico del carácter en s[] para las
cifras del 0 al 9 */
Especificadores de carácter (sinónimos en la misma línea) char signed char (si char es signed por defecto). unsigned char char unsigned char (si char es unsigned por defecto). signed char
Como se ha señalado, C++ permite que char sea signed o unsigned; por defecto (si no se especifica otra cosa) es signed. Si el defecto se establece a unsigned, entonces la declaración: char ch; declara ch como unsigned, y para modificar el valor por defecto es necesario consignar: signed char ch;. Análogamente, si se ha establecido signed char por defecto, para declarar un unsigned char es necesario especificar explícitamente: unsigned char ch;. En C++Builder, el valor que se tomará por defecto puede ser establecido mediante un comando de compilación: -K- establece signed; -K establece unsigned. En los ejecutables construidos con este compilador, el valor que se está tomando por defecto está descrito en una constante manifiesta.
Además de los tradicionales (heredados del C clásico), el ANSI C++ define un nuevo tipo de dato tipo carácter, el denominado carácter ancho wchar_t, adecuado para representar el juego de caracteres Unicode. 5 Fraccionarios También llamados de coma flotante; son float y double. Solo se permite un modificador adicional (long), dando lugar a los long double, de forma que son en realidad tres tipos: float, double y long double (Tipos básicos y representación interna). Sintaxis: float <nombre-de-variable> [signed|unsigned] double <nombre-de-variable› Ejemplos: float x = 3.14; double y = 6.2; long double z = 66.01; Tanto el especificador float como el double son palabras clave que definen un identificador como de tipo numérico de coma flotante (fraccionario). La razón principal de usar floats es economizar espacio o tiempo, cuando se almacenan grandes arrays de números en máquinas donde el uso de double es muy costoso en términos de tiempo de computación. En ciertos casos los tipos float pueden ser promovidos a double de forma automática por el compilador .
El almacenamiento interno y rango de los tipos fraccionarios es dependiente de la implementación. Es decir, cada compilador C++ es libre de definirlos a su manera
El Estándar solo establece para ellos las siguientes condiciones [5]:
• El tipo double permitirá al menos tanta precisión como el float. • El tipo long double permitirá al menos tanta precisión como el double. • El rango de valores de float será un subconjunto del rango de los double. • El rango de valores de double será un subconjunto del rango de los long double.
6 void void es palabra reservada, indicador de un tipo especial: ausencia de valor. Sintaxis: void identificador Es importante señalar que void es tratado por el compilador como un tipo, del mismo modo que pueden serlo int, char, double, etc. Aunque de tipo un poco especial (un tipo incompleto). El hecho de que signifique ausencia de valor no es inconveniente para que sea un tipo con todas sus características. Podríamos suponer que representa al conjunto de los tipos lo que el cero al conjunto de los números. La única diferencia respecto a cualquier tipo normal es que tiene una sola instancia o representante; él mismo. Dicho en otras palabras:
int entero; // Ok. entero es una instancia de la clase int char caracter; // Ok. caracter es una instancia de la clase char void novalor; // Error. uso no permitido. En cambio si están permitidas con él otras expresiones propias de los tipos: return void(0); // Ok. modelado de tipo void* puntero; // Ok. puntero es puntero-a-void void func(); // Ok. tipo devuelto por función
void se usa de varias formas: • Para indicar que una función no devuelve ningún valor o no acepta ningún parámetro. • Para crear punteros genéricos. • Para modelado de tipo. Ejemplos: int x; void* p; // define un puntero-a-nulo (genérico) p = &x; // p señala a x (pasa a ser puntero-a-int) Uso de void para señalar que una función no acepta ningún parámetro: int main (void) {
…
} Uso de void para para definir una función que no devuelve ningún valor [2]: void saludo(char *nombre) {
printf(“Hola, %s.”,nombre);
} Uso de void para devolver void (se trata en realidad de un modelado de tipo) void saludo(char *nombre) {
printf(“Hola, %s.”,nombre); return (void)0;
} Uso de void para modelado de tipo [1]
- ifdef NDEBUG
- define assert(p) ((void)0)
- else
- define assert(p) ((p) ? (void)0 : _assert(#p, __FILE__, __LINE__))
- endif