Hasta ahora, se han visto los tipos básicos de datos char, int, float, y double, junto con los especificadores signed, unsigned, short, y long, que se pueden utilizar con los tipos básicos de datos en casi cualquier combinación. Ahora se han añadido los punteros y las referencias, que son ortogonales a los tipos básicos de datos y los especificadores, de modo que las combinaciones posibles se acaban de triplicar:
//: C03:AllDefinitions.cpp // All possible combinations of basic data types, // specifiers, pointers and references #include <iostream> using namespace std; void f1(char c, int i, float f, double d); void f2(short int si, long int li, long double ld); void f3(unsigned char uc, unsigned int ui, unsigned short int usi, unsigned long int uli); void f4(char* cp, int* ip, float* fp, double* dp); void f5(short int* sip, long int* lip, long double* ldp); void f6(unsigned char* ucp, unsigned int* uip, unsigned short int* usip, unsigned long int* ulip); void f7(char& cr, int& ir, float& fr, double& dr); void f8(short int& sir, long int& lir, long double& ldr); void f9(unsigned char& ucr, unsigned int& uir, unsigned short int& usir, unsigned long int& ulir); int main() {} ///:~
Listado 3.18. C03/AllDefinitions.cpp
Los punteros y las referencias entran en juego también cuando se pasan objetos a las funciones y cuando éstas los devuelven; aprenderá sobre ello en un capítulo posterior.
Hay otro tipo que funciona con punteros: void. Si se establece que un puntero es un void*, significa que cualquier tipo de dirección se puede asignar a ese puntero (en cambio si tiene un int*, sólo puede asignar la dirección de una variable int a ese puntero). Por ejemplo:
//: C03:VoidPointer.cpp int main() { void* vp; char c; int i; float f; double d; // The address of ANY type can be // assigned to a void pointer: vp = &c; vp = &i; vp = &f; vp = &d; } ///:~
Listado 3.19. C03/VoidPointer.cpp
Una vez que se asigna a un void* se pierde cualquier información sobre su tipo. Esto significa que antes de que se pueda utilizar el puntero, se debe moldear al tipo correcto:
//: C03:CastFromVoidPointer.cpp int main() { int i = 99; void* vp = &i; // Can't dereference a void pointer: // *vp = 3; // Compile-time error // Must cast back to int before dereferencing: *((int*)vp) = 3; } ///:~
Listado 3.20. C03/CastFromVoidPointer.cpp
El molde (int*)vp toma el void* y le dice al compilador que lo trate como un int*, y de ese modo se puede dereferenciar correctamente. Puede observar que esta sintaxis es horrible, y lo es, pero es peor que eso - el void* introduce un agujero en el sistema de tipos del lenguaje. Es decir, permite, o incluso promueve, el tratamiento de un tipo como si fuera otro tipo. En el ejemplo anterior, se trata un int como un int mediante el moldeado de vp a int*, pero no hay nada que indique que no se lo puede moldear a char* o double*, lo que modificaría una cantidad de espacio diferente de la que se había asignado al int, lo que posiblemente provocaría que el programa fallase. En general, los punteros void deberían ser evitados, y utilizados únicamente en raras ocasiones, que no se podrán considerar hasta bastante más adelante en el libro.
No se puede tener una referencia void, por razones que se explicarán en el Capítulo 11.