Ok, primero hablas de C y luego mencionas las clases de C++. Ojo, no confundas lenguajes, que C y C++ son diferentes.
Una computadora, dentro de su memoria RAM, tiene dos divisiones, conocidas como "heap" y "stack", o "pila" y "cola". La diferencia entre ambas es la forma en la que se accesa a éstas; para efectos prácticos, esto no nos interesa porque ese acceso lo hace el sistema operativo.
Ahora bien, el stack es una parte de la memoria que tiene una cierta limitación en cuanto a tamaño, ya que está dedicada a almacenar memoria de objetos transitorios --es decir, que serán eliminados una vez que no son requeridos--, mientras que el heap está diseñado para almacenar objetos persistentes: aquellos cuya vida sobrepasará la función en la que fueron creados, y por tanto es la más extensa (i.e. el resto de la memoria).
En C++ es posible crear objetos en ambas partes de la memoria; es decir, es posible crear objetos transitorios y objetos persistentes. En Java --por cierto-- esto también es posible. En efecto, TODOS los objetos-valor (o tipos de datos primarios) se guardan en el stack, mientras que todos los objetos-referencia (derivados de "Object") se guardan en el heap. Ejemplo:
class C
{
}
public void foo()
{
int i = 5;
C c = new C();
} // se elimina i, pero c sigue vivo al finalizar la función
En la función foo, se crea un objeto i, de tipo entero, TRANSITORIO. Este objeto será eliminado al finalizar la función foo, ¿correcto? Sin embargo, el objeto c seguirá vivo más allá de dicha función. Claro, tú no te preocupas (mucho) porque eventualmente cuando ya no lo requieras, el recolector de basura se encargará de eliminar el objeto y listo. Entonces decimos que c es PERSISTENTE (aunque en este ejemplo nos deja de importar esa 'persistencia' fuera de la función), porque técnicamente seguirá vivo hasta que se encuentre con el recolector de basura. De todo esto se desprende que los objetos-valor se guardan en el stack, mientras que los objetos-referencia hacen lo propio en el heap. Por supuesto, todo esto lo hace Java tras bambalinas (y en la JVM).
Como habrás visto, en Java cualquier objeto-valor (int, float, char, etc) es por definición transitorio. Y cualquier tipo referencia (todos los demás, interfases incluídas) son --o tienen que ser-- persistentes, en el sentido de que son almacenados en el heap y quedarán vivos hasta que se encuentren con el GC. En C++ un objeto es por definición transitorio, incluidas las clases y estructuras. Es decir, en C++ una clase es un TIPO VALOR y por tanto es TRANSITORIO y por tanto se crea dentro del STACK y por tanto se ELIMINA al salir de su alcance. Ejemplo:
class C { };
void foo()
{
int i = 5;
C c();
} // tanto i como c son eliminados en este momento
Como ves, a diferencia de Java (que fuerza a que todas sus clases sean objetos persistentes), C++ te da la libertad de crearlos en el stack. Esto tiene la ventaja de que muchas veces los objetos (clases y estructuras) serán empleados de forma transitoria, y no habría necesidad de gastar preciosos segundos en ubicar memoria en el heap, y su posterior liberación (proceso automático en Java pero costoso), aumentando el rendimiento del sistema. Eso está muy bien, pero nos lanza el siguiente problema: ¿qué pasa cuando necesite un objeto persistente? Pues bien, como era de esperarse, C++ te permite crear objetos persistentes y ubicarlos en el heap. Esto se conoce como creación dinámica de memoria. Pero antes veamos un poquito de teoría.
Cuando creas un objeto cualquiera (transitorio o persistente) se tiene que ubicar en algún lugar, ¿de acuerdo? Es decir, el objeto necesita un determinado número de bytes y necesita estar en algún lugar de la memoria. Una memoria RAM cualquiera está dividida en diferentes segmentos de un tamaño mínimo (que depende del procesador; en Windows 93 era una alineación de 2 bytes --16 bits--, en Windows 95 a Windows Vista, la alineación es de 4 bytes --32 bits-- y en los sistemas XP64 y Vista64, pues la alineación es de 8 bytes --64 bits). Así las cosas, cuando creas un objeto cualquiera, el SO determina el tamaño del objeto y 'reserva' una cantidad de memoria 'alineada' con el mínimo número de segmentos necesarios para que el objeto pueda ser creado. Es decir (en un sistema de 32 bits), si tienes un objeto cuyo tamaño es de 3 bytes, te será reservado un segmento (4 bytes), si tienes un objeto de 5 bytes te serán reservados dos segmentos (8 bytes), etcétera (a este proceso en el cuál te reserva por segmentos en lugar del número exacto de memoria se le llama "alineación" y puede variar en diferentes SOs o procesadores). Así pués, nuestro objeto ya tiene su memoria reservada, aunque todavía no ha sido creado. Entonces la pregunta: ¿en DÓNDE se creó ese objeto dentro de la inmensidad de la memoria RAM?
Imaginemos a la RAM como una enorme ciudad (enorme, sí, una RAM de 512 MB implica 436'207,616 bits) en donde cada bit es un cuarto en algún departamento. El byte sería el departamento y el segmento es el condominio. Ahora bien, en una ciudad donde hay 134'217,728 de condominios, no puedo salir a la calle y preguntar al tío de la esquina por fulanita, ¿verdad? Pues bien ¿qué haces? Le llamas por teléfono a fulanita y le pides su dirección. Entonces con esa dirección apuntada en papel y guardada en algún lugar seguro como tu billetera, tomas el "bus" para que te lleve. Una vez que llegas al condominio (segmento), puedes ubicar fácilmente cada departamento (byte) y si las cosas se ponen a gusto, hasta puedes llegar a su cuarto (bit). Jaja, qué chida me está quedando la analogía. Por supuesto, si pierdes la dirección, ya valiste madres porque no podrás llegar siguiera al condominio.
Pués bien, extrapolemos la analogía. La llamada a fulanita es la creación del objeto: al crearlo, obtienes la dirección en memoria, la cuál la "guardas en algún lugar seguro". Entonces con esa dirección guardada podrás llegar al condominio y acceder al departamento y los cuartos. ¿Qué es ese lugar seguro donde se guarda la dirección? Un puntero.
Un puntero es un tipo variable especial que en esencia es un entero (int). Su valor, sin embargo, es la dirección de memoria del objeto: lo que necesitas para llegar hasta el condominio, pués. Todos los objetos (tanto transitorios como persistentes) se crean en la memoria RAM, y por tanto tienen una dirección de memoria a través de la cuál se les pueda localizar. En el caso de los objetos transitorios, ésta dirección está oculta, toda vez que por no ser persistentes, no se requiere tenerla a la mano. Pero siempre puedes llamar a fulanita: el teléfono es el operador &.
void foo()
{
int i = 5;
int* p = &i; // ¡llamando a fulanita, dame tu dirección!
int j = *p; // accediendo al condominio de fulanita
}
En el caso de un objeto persistente, por el mismo hecho de que éste va a "persistir" o perdurar aún después de salir del ámbito en donde se creó, necesita exponer de antemano su dirección de memoria y por eso se te exige que emplees un puntero. A final de cuentas, el condominio ahí estará aún después de que se acabe la función, por lo que siempre necesitarás la dirección en memoria del objeto.
Pues bien, finalmente, la forma en la que se puede crear un objeto persistente en C++ es la siguiente:
class C {} ;
void foo()
{
C* c; // el papel donde apuntamos la dirección de memoria
c = new C(); // creamos el objeto y apuntamos en papel
// la dirección de memoria en c
...
delete c; // eliminamos el objeto a través de la dirección de memoria
}
Así, el objeto se crea dinámicamente y ahí perdurará por siempre jamás, hasta que se le diga explícitamente que ya no se requiere. Es decir, ahora ya tenemos un objeto persistente.
Nota que esto es exactamente lo mismo que en Java cuando haces el "C c = new C();". En ambos lenguajes estás creando un objeto persistente. Las diferencias:
1. En C++ cualquier clase es en principio un objeto transitorio, mientras que en Java es un objeto persistente. Aún así, C++ permite crear objetos persistentes también.
2. C++ necesita guardar siempre la dirección de memoria del objeto creado, porque en algún momento de la vida tiene que ser eliminado explícitamente (con la palabra reservada "delete"), mientras que en Java no se necesita eliminar al objeto explícitamente ya que el colector de basura lo hace automáticamente.
Por 1 es que hay dos tipos diferentes de sintaxis, mientras que por 2 es que en C++ necesitas punteros y en Java no. Esto no quiere decir que Java no maneje direcciones de memoria: por supuesto que lo hace, solo que las mantiene ocultas porque no hay necesidad de exponerlas al programador. Pero si analizas los bytecodes generados al compilar código Java, verás que sí maneja direcciones de memoria.