C/Visual C - Punteros

   
Vista:

Punteros

Publicado por Clayder (16 intervenciones) el 03/12/2007 06:03:17
Hola a todos:

Llevo algun tiempo programando en Java. Ahora quiero aprender algo de C; pero estoy teniendo problemas para entender la definicion de punteros. Segun lo que estoy leyendo, esta es la caracteristica mas importante de C, pero no la comprendo del todo. Todos me dicen que los punteros hacen referencia a una direccion de memoria; ¿como puedo entender eso?. Dentro de mi ignorancia yo no le veo mucha diferencia excepto por el *.

Por ejemplo cuando quiero declarar una cadena de caracteres, porque tambien se puede hacer con un puntero de un char, por ejemplo:

char *p = "Hola";

Ademas, ¿¿por que lo objetos creados por una clase de C++, tambien pueden ser declaradas como punteros??

Agradeceria bastante que alguien me ayudara con este tema. Si no es respondiendome directamente, mandandome un link o libro en donde pueda aclarar mis dudas.

Gracias de antemano.

Saludos.
Valora esta pregunta
Me gusta: Está pregunta es útil y esta claraNo me gusta: Está pregunta no esta clara o no es útil
0
Responder

RE:Punteros

Publicado por nayumiorama (53 intervenciones) el 03/12/2007 17:17:36
La eterna duda. A ver si me explico.

Piensa que los punteros no es la característica más importante de c; es la gestión dinámica de memoria; los punteros son la herramienta para hacerlo.

Un puntero no es más que una variable cuyo valor nos es más que una dirección de memoria.
¿Pero que es una dirección de memoria?
Supongo que sabrás que la memoria está organizada de forma lineal de forma que cada byte de la memoria tiene una dirección. El primer byte tiene la direción 0, el segundo la 1, el tercero la 2, ....

Bueno pues a cada uno de esos numeros, 1,2, 3..., le llamamos dirección de memoria. Así, podemos ir directamente al byte 0xB800 o al 0xC0000.

Si declaras una variable int a; el compilador busca en la memoria un hueco donde poder alojar esa variable. Cada vez que va a acceder a ella, pues lo hace a través de su dirección de memoria. Cuando haces una asignación de un valor en una variable, estás grabando en la dirección de memoria asignada a esa variable el valor que quieres grabar.

Esto está muy bien cuando haces aplicaciones normales. Pero si necesitas aplicaciones que cojan memoria segun la necesiten, o recorrer la memoria directamente para ir más rápido, necesitas punteros.

Cuando declaras una variable de tipo puntero, le estás indicando al compilador que en esa variable vas a guardar direcciones de memoria; y cualquier operación que hagas con esa variables la harás con el valor de la dirección de memoria.

Así, si haces int *b = &a; estas guardando la dirección de memoria de la variable A en la variable B. Estas guardando en b algo así como 0x123456.

El contenido de esa dirección de memoria lo manejas con *b.
si haces b++, estas haciendo que b maneje el siguiente byte en la memoria.
(realmente aumenta sizeof(int) bytes).

como ya habrás imaginado, todo lo que se almacena en memoria puede ser accedido mediante un puntero.

Por ejemplo, en los msdos, acceder a la dirección 0x0B800 es acceder a la memoria de la tarjeta gráfica, y poniendo valores en esa dirección y sucesivas, puedes pintar directamente en la memoria de video.

Algo así pondría los dos primeros pixeles a blanco (realmente es más complicado pero coje la idea)
void *p ;
p = 0x0B800;
*p=255;
p++;
*p=255;

Un saludo.
Valora esta respuesta
Me gusta: Está respuesta es útil y esta claraNo me gusta: Está respuesta no esta clara o no es útil
0
Comentar

RE:Punteros (I)

Publicado por fernando.gomez (1604 intervenciones) el 03/12/2007 19:22:01
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.
Valora esta respuesta
Me gusta: Está respuesta es útil y esta claraNo me gusta: Está respuesta no esta clara o no es útil
0
Comentar

RE:Punteros (II)

Publicado por fernando.gomez (1604 intervenciones) el 03/12/2007 19:22:41
Bueno, dada esta extensa explicación que espero te haya servido, me sirvo a explicarte otras cosillas con respecto a los punteros, para responder completamente a tu pregunta.

Ahora ya sabes que un puntero es una dirección de memoria y todo eso ¿verdad? Y por tanto, cualquier objeto (transitorio o persistente) se puede acceder a través de su direccion (es decir, a través de un puntero) ¿correcto? Pues bueno, hay algunos tipos de objetos cuyo acomodo en memoria es particular. Los arrays por ejemplo. En un array, los elementos del mismo se disponen secuencialmente en la memoria. Es decir, el primer elemento se guarda un segmento determinado, y el siguiente, en el inmediatamente posterior: como si todos vivieran en condominios pegados. Entonces, dada una dirección de memoria de (digamos) el primer elemento, es posible acceder a la memoria del elemento contiguo, ¿no? Pues claro, solo te tienes que mover al departamento siguiente (o anterior), lo cuál no es difícil ¿verdad? Pués bien, eso se logra con la llamada aritmética de punteros. Luego entonces además de la notación usual para arrays, también se pueden tratar como punteros puesto que accedo a la dirección del primer elemento y --por ende-- a los demás debido a que están en dirección contigua (hasta que encuentre un elemento nulo: todo array termina en un elemento nulo):

void foo(int* vector)
{
while(vector) // mientras la dirección de memoria no sea nula...
{
cout << *vector << endl; // imprimimos el objeto que reside en dicha dirección
vector++; // nos movemos al condominio contiguo; en caso de que
// se termine el vector, en vez de condominio habrá un terreno vacío: NULL
}
}

Esto es lo mismo que:

void foo(int vector[], int tamano)
{
for (int i = 0; i < tamano; i++)
{
cout << vector[i]; << endl;
}
}

Ahora, la cadena de caracteres es un array de chars (al igual que en Java, por cierto). Solo que tiene un operador especial, ", para facilitar la sintaxis:

char cadena[10] = "Hola";

es lo mismo que

char cadena[10] = { 'H', 'o', 'l', 'a', 0, 0, 0, 0, 0, 0 };

Pero sería muy molesto escribirlo como notación de caracteres ¿verdad? Ahora bien, como una cadena de texto ES un array de caracteres, y como a un array lo puedo acceder como un puntero al primer elemento y eventualmente recorrer todos los segmentos contiguos en memoria dada la aritmética de punteros anteriormente explicada, pues entonces es correcto si yo hago:

char* p = "Hola mundo";

en donde p apunta a la dirección del caracter 'H'. Pero al ser un array de caracteres, tengo también acceso a todos los demás elementos. ¿Sí me explico? He ahí el por qué puedes emplear punteros para muchas cosas y de formas aparentemente distintas, que como habrás visto son en realidad diferentes caras de la misma moneda. Y por ello es que entender los punteros es importante, aunque --dicho sea de paso-- no creo que esa sea la característica más importante de C++. Ciertamente, C++ hace un esfuerzo por tratar de disminuir el uso de punteros, y pues la orientación a objetos se me haría mucho más importante que cómo acceder a los elementos persistentes. En fin.

Pos bueno, no es un tratado sobre punteros, pero espero te sirva de algo. Entender el manejo de memoria es uno de los problemas que cualquiera tiene al iniciarse en C++ y es la causa de sus principales dolores de cabeza para los inexpertos. Y es también la fuente de su potencia y flexibilidad. Espero me hayas extendido, y espero no haber omitido algún detalle.

Saludos.
Valora esta respuesta
Me gusta: Está respuesta es útil y esta claraNo me gusta: Está respuesta no esta clara o no es útil
0
Comentar

RE:Punteros (II)

Publicado por Nelek (818 intervenciones) el 04/12/2007 10:02:37
Muy buena explicacion. Sobre todo lo de "fulanita", como tengas que ir a todas esas fulanitas ara hacer algo indecente... te vas a quedar sin fuerzas ;) :P
Valora esta respuesta
Me gusta: Está respuesta es útil y esta claraNo me gusta: Está respuesta no esta clara o no es útil
0
Comentar

RE:Punteros (II)

Publicado por Clayder (16 intervenciones) el 16/12/2007 05:29:44
Oie, muy buena tu explicación, de verdad muchas gracias...
Valora esta respuesta
Me gusta: Está respuesta es útil y esta claraNo me gusta: Está respuesta no esta clara o no es útil
0
Comentar