Punteros genéricos

Es posible declarar punteros sin especificar a qué tipo de objeto apuntan:

void *<identificador>;

Usaremos estos punteros en situaciones donde podemos referirnos a distintos tipos de objetos, ya que podemos hacer que apunten a objetos de cualquier tipo.

Por supuesto, para eso tendremos que hacer un casting con punteros, sintaxis:

(<tipo> *)<variable puntero>

Por ejemplo:

#include <iostream>
using namespace std;
 
int main() { 
   char cadena[10] = "Hola"; 
   char *c; 
   int *n; 
   void *v;
 
   c = cadena; // c apunta a cadena 
   n = (int *)cadena; // n también apunta a cadena 
   v = (void *)cadena; // v también 
   cout << "carácter: " << *c << endl; 
   cout << "entero:   " << *n << endl; 
   cout << "float:    " << *(float *)v << endl; 
   return 0; 
}

El resultado será:

carácter: H
entero:   1634496328 
float:    2.72591e+20 

Vemos que tanto cadena como los punteros n, c y v apuntan a la misma dirección, pero cada puntero tratará la información que encuentre allí de modo diferente, para c es un carácter y para n un entero. Para v no tiene tipo definido, pero podemos hacer casting con el tipo que queramos, en este ejemplo con float.

Punteros a estructuras

Los punteros también pueden apuntar a estructuras. En este caso, para referirse a cada elemento de la estructura se usa el operador (->), en lugar del (.).

Ejemplo:

#include <iostream>
using namespace std;
 
struct stEstructura {
   int a, b; 
} estructura, *e;
 
int main() { 
   estructura.a = 10;
   estructura.b = 32;
   e = &estructura;
 
   cout << "puntero" << endl;
   cout << e->a << endl;
   cout << e->b << endl;
   cout << "objeto" << endl;
   cout << estructura.a << endl; 
   cout << estructura.b << endl; 

   return 0; 
}

Ejemplos

Veamos algunos ejemplos de cómo trabajan los punteros.

Primero un ejemplo que ilustra la diferencia entre un array y un puntero:

#include <iostream>
using namespace std;
 
int main() {
   char cadena1[] = "Cadena 1";
   char *cadena2 = "Cadena 2";
 
   cout << cadena1 << endl;
   cout << cadena2 << endl; 

   //cadena1++; // Ilegal, cadena1 es constante 
   cadena2++; // Legal, cadena2 es un puntero 

   cout << cadena1 << endl; 
   cout << cadena2 << endl;
 
   cout << cadena1[1] << endl;
   cout << cadena2[0] << endl;
 
   cout << cadena1 + 2 << endl; 
   cout << cadena2 + 1 << endl;
 
   cout << *(cadena1 + 2) << endl; 
   cout << *(cadena2 + 1) << endl; 
   
   return 0;
}

Aparentemente, y en la mayoría de los casos, cadena1 y cadena2 son equivalentes, sin embargo hay operaciones que están prohibidas con los arrays, ya que son punteros constantes.

Otro ejemplo:

#include <iostream>
using namespace std;
 
int main() { 
   char Mes[][11] = { "Enero", "Febrero", "Marzo", "Abril", 
      "Mayo", "Junio", "Julio", "Agosto", 
      "Septiembre", "Octubre", "Noviembre", "Diciembre"}; 
   char *Mes2[] = { "Enero", "Febrero", "Marzo", "Abril", 
      "Mayo", "Junio", "Julio", "Agosto", 
      "Septiembre", "Octubre", "Noviembre", "Diciembre"};
 
   cout << "Tamaño de Mes: " << sizeof(Mes) << endl; 
   cout << "Tamaño de Mes2: " << sizeof(Mes2) << endl; 
   cout << "Tamaño de cadenas de Mes2: " 
        << &amp;Mes2[11][10]-Mes2[0] << endl; 
   cout << "Tamaño de Mes2 + cadenas : " 
        << sizeof(Mes2)+&amp;Mes2[11][10]-Mes2[0] << endl;
 
   return 0; 
}

En este ejemplo declaramos un array Mes de dos dimensiones que almacena 12 cadenas de 11 caracteres, 11 es el tamaño necesario para almacenar el mes más largo (en caracteres): "Septiembre".

Después declaramos Mes2 que es un array de punteros a char, para almacenar la misma información. La ventaja de este segundo método es que no necesitamos contar la longitud de las cadenas para calcular el espacio que necesitamos, cada puntero de Mes2 es una cadena de la longitud adecuada para almacenar el nombre de cada mes.

Parece que el segundo sistema es más económico en cuanto al uso de memoria, pero hay que tener en cuenta que además de las cadenas también en necesario almacenar los doce punteros.

El espacio necesario para almacenar los punteros lo dará la segunda línea de la salida. Y el espacio necesario para las cadenas lo dará la tercera línea.

Si las diferencias de longitud entre las cadenas fueran mayores, el segundo sistema sería más eficiente en cuanto al uso de la memoria.

Comentarios de los usuarios (44)

Martin Villagra
2010-12-28 00:10:37

En la parte donde el programa entrega \"Tamaño de cadenas de Mes2\" en mi PC me dice que vale -1... Es imposible que ocupe -1 pero copie exactamente como está ahí el código.

PD: Uso CodeBlocks con MinGW

Salvador Pozo
2011-01-10 16:08:59

¿A alguien le pasa lo mismo que a Martin Villagra?

Si es así, dejad un comentario.

Jorge
2011-01-18 19:15:16

Realmente no entiendo porque la dirección de memoria es &Mes2[11][10] - Mes2[0], realmente son 12 registros de 11 caracteres.¿ no debería ser &Mes2[11][12]?

Salvador Pozo
2011-01-19 09:36:50

Primero, he de decir que Jorge tiene razón en una cosa: hay un error.

Lo que nos interesa es calcular el número de bytes entre el primer elemento del array Mes[0][0] y el último Mes[11][?].

En el caso del array Mes, del mismo modo que el último elemento es el 11, el último carácter es el 10, ya que los índices en C y C++ empiezan en 0.

El error está en que intentamos calcular el espacio del array Mes2. En este caso cada elemento del array es un puntero, que a su vez contiene la dirección de memoria de una cadena. Cada una de las cadenas apuntadas por Mes2 tiene longitud diferente. En concreto, la correspondiente a Mes2[11] (la de diciembre), tiene nueve caracteres, más el nulo de terminación, es decir, 10. Por lo tanto, el último carácter es el Mes2[11][9].

Corregiré ese error hoy.

Gracias.

Martín
2011-02-14 09:28:11

Hay algo que no me queda claro, en el primer ejemplo, ¿por qué la salida de //- cout << cadena2[0] << endl; -// es "a" y no "C" ?

PD: Esta pagina con un foro seria un golazo.

Martín
2011-02-14 09:29:34

No dije nada =P Me olvide del cadena2++;

Juan
2011-02-25 09:32:27

Con respecto a punteros de estructuras... porque usarlos?

no me queda claro ese concepto..

Saludos

Steven
2011-02-25 21:34:50

Hola Juan,

La verdad es que la respuesta a tu pregunta es la misma que la pregunta general: "¿por qué usar punteros?". La respuesta es que a veces debemos usar punteros en ciertos casos y a veces nos interesa manipular la información indirectamente.

Un ejemplo práctico es a la hora de crear memoria dinámicamente. Por ejemplo,

struct Persona
{
  char szNombre[64];
  char szApellidos[128];
};

int main()
{
  Persona *pLista = 0;
  int nCantidad = 0;

  cout << "¿Cuántas personas? ";
  cin >> nCantidad;

  pLista = new Persona[nCantidad];

  // Hacemos algo con nuestro array dinámico de estructuras
  ...

  // Debemos borrar la memoria que creamos
  delete[] pLista;

  return 0;
}

Otro uso es cuando queremos pasar un array (estático o dinámico) como parámetro a una función; por ejemplo,

// Retorna el índice a la estructura encontrada,
//   o -1, si no existe
int BuscarPorNombre( const Persona *ptr, const char *pszNombre );
...
int nRet = BuscarPorNombre( pLista, "Juan" );

Otra posibilidad es la de retornar un puntero al elemento encontrado o nulo si no se encontró. Esto es,

Persona *BuscarPorNombre( const Persona *ptr, const char *pszNombre );
...
Persona *pEncontrada = BuscarPorNombre( pLista, "Juan" );

Existen otros ejemplos prácticos con otras razones para usr punteros, especialmente si usamos estructuras de datos más complejas, como una lista dinámicamente enlazada. En esta estructura, requerimos punteros para enlazar o ligar los diferentes nodos entre sí. Hablamos de este tema en profundidad en nuestro curso de EDD (Estructuras Dinámicas de Datos); puedes dirigirte a: http://c.conclase.net/edd/index.php

Otro ejemplo práctico es cuando queremos usar polimorfismo. Esto lo veremos en detalle en el capítulo 37: http://c.conclase.net/curso/index.php?cap=037#039_polimorfismo

Espero que esto te oriente.

Steven

Japo32
2011-03-21 17:15:05

Si Diciembre tiene 9 caracteres el último no será Mes2[11][8] en vez de Mes2[11][9], ya que el primero de los índices es el 0?

berAS
2011-07-04 14:32:46

Buenas.

Al igual que a Martin Villagra, al compilar y ejecutar el último ejemplo, obtengo como resultado que el tamaño de las cadenas de "Mes2" es -1. También uso Code::Blocks y MingGW.

Carlos
2011-07-06 02:10:53

Buenas!! Al compilar los ejercicios mi compilador (mingw con ZintaI) me suelta warnings siempre que inicio un puntero a char a una cadena de caracteres, como lo haceis en los ejemplos:

el mensaje del compilador es:

warning: deprecated conversion from string constant to 'char*'

y el error es por esta linea:

char *cadena2 = "Cadena 2";

Muchas gracias

berAS
2011-07-06 17:09:57

Hola Carlos.

A mí me pasaba lo mismo, pero este tema ya me lo ha explicado Steven. Deberías escribir los siguiente:

const char *cadena2 = "Cadena 2";

Debido a que la cadena es una constante, el puntero debe apuntar a "const char".

Un saludo.

Carlos
2011-07-06 21:56:47

Gracias, pero si declaras una cadena como constante ya no la vas a poder modificar, o sea que en parte es mejor dejar el warning, pero claro es un warning y no mola tenerlo por ahí pululando. La cosa será no usar las cadenas así, no sé, qué lío !!!!

Salvador Pozo
2011-07-07 10:35:43

Intentaré corregir los ejemplos tan pronto como sea posible.

En realidad, cualquier cadena literal es una constante, de modo que omitir el modificador const en la declaración del puntero es un error.

El valor almacenado de "Cadena 2" no se debería modificar durante la ejecución del programa, aunque el valor del puntero cadena2 sí puede cambiar.

Si declaramos puntero2 de este modo:

const char *puntero2 = "Cadena 2";

Podremos modificar el valor de puntero2, asignándole nuevas direcciones de memoria (haciendo que apunte a otras cadenas), pero en ningún caso podremos modificar las cadenas o caracteres apuntados por ese puntero.

La sentencia:

puntero2[4] = 'x';

provocaría un error.

Otra opción es hacer un casting:

char *puntero2 = (char*)"Cadena 2";

Pero en ese caso, el programador debe tener en cuenta que "Cadena 2" no debe ser modificada.

Aclaro que, al menos en todos los compiladores que he usado, el valor de esa cadena sí se puede modificar, pero hay que tener en cuenta un detalle muy importante: Esa cadena se almacena en una porción de memoria reservada durante la fase de compilación, de modo que aunque los caracteres que la componen pudieran cambiar de valor, no pasa lo mismo con su longitud. tal vez podríamos almacenar cadenas más cortas o de igual longitud, pero nunca más largas.

Otro detalle a tener en cuenta es que existen sistemas en los que el programa se almacena en memoria ROM, por ejemplo en autómatas. En ese caso, la cadena estará almacenada, con toda probabilidad, en esa ROM, de modo que los intentos de modificarla no tendrían ningún efecto.

Del mismo modo, existen sistemas operativos que pueden decidir liberar memoria ocupada por programas que se ejecutan en segundo plano. Si esa memoria está marcada como "descartable", el sistema considera que puede reconstruirla a partir de un fichero, y no la almacena en disco. En ese caso, al recuperar el código del programa, las modificaciones que hubiéramos podido hacer en esas cadenas literales se perderían.

Lo mismo se aplica a memorias caché, etc.

El hecho de que algo como esto funcione en depuración, o las primeras 100 veces que se pruebe, no implica que lo hará siempre ni en todos los sistemas.

Por eso es mejor considerar esas cadenas como constantes, y los punteros creados para manipularlas, también. En este caso, constante significa "sólo de lectura".

Y también es importante hacer caso de los warnings. :)

Hasta pronto.

Salvador Pozo
2011-07-07 11:33:43

Hola:

Con respecto al último ejemplo, después de varias pruebas y de pensar sobre ello, ya sé por qué no funciona como yo esperaba.

Una de las pruebas ha sido ver las direcciones de las cadenas de cada mes, usando este código:

	for(int i=0; i < 12; i++) {
		cout << (void*)Mes2[i] << endl;
	}

El resultado es que las cadenas no se almacenan secuencialmente, siguiendo el orden en que fueron declaradas, sino de una forma aparentemente anárquica.

Ignoro a que se debe esto, probablemente a alguna optimización del compilador (que no existía cuando creé el código por primera vez).

El resultado es que no se puede calcular la memoria usada por ese array usando ese método.

Carlos Medina
2011-08-28 04:39:35

Hola a todos un placer saludarle. mi pregunta es: Porque sinedo que los punteros no son sino un objeto que apunta al tipo al que pertenece. Entoces no entiendo porque es posible asignarle contenido a si mismo sin que este apuntando a ningun objeto. como en uno de los ejmplos de capitulo 12 sobre punteros aqui:

int main() {

char cadena1[] = "Cadena 1";

char *cadena2 = "Cadena 2";

Steven R. Davidson
2011-08-29 13:41:46

Hola Carlos,

Ciertamente, este ejemplo que presentas aparenta contener información; sin embargo, esto no es así. El puntero, 'cadena2', apunta a la información, que en este caso es una cadena de caracteres.

Lo que sucede es que la cadena es literal. Esto significa que el compilador es quien se encarga de crear la memoria necesaria para guardar los caracteres de esta cadena literal. Una cadena en C/C++ se representa como un array y por lo tanto, el tipo de dato es una dirección de memoria del primer elemento. Debido al vínculo íntimo entre arrays y punteros, también podemos decir que el tipo de la cadena puede ser tratado como un puntero constante a 'char'.

En otras palabras, 'cadena2' apunta al primer carácter de la cadena literal, y esta cadena literal es controlada por el compilador.

Espero haber aclarado la duda.

Steven

dearg_due
2012-01-13 13:53:17

Hola!

quería consultar por el segundo ejemplo que habla de los los meses hay algo que no entiendo.

   cout << "Tamaño de cadenas de Mes2: " 
        << &Mes2[11][10]-Mes2[0] << endl; 
   cout << "Tamaño de Mes2 + cadenas : " 
        << sizeof(Mes2)+&Mes2[11][10]-Mes2[0] << endl;

¿donde está declarado & anteriormente? ¿y que hace?

saludos!

dearg_due
2012-01-13 13:58:51

perdón, no salió escrito en el post "&amp" ¿que es? acabo de ver que me lo borró en el otros post y no me deja escribirlo cada vez que lo publico "& a m p" si lo pongo todo junto cuando intento publicarlo me lo borra y deja solo &

saludos otra vez!

Steven R. Davidson
2012-01-13 16:48:49

Hola dearg_due,

Lo de

&amp;

forma parte del código HTML que hace visualmente posible cualquier página web. En nuestro caso, cambiamos algunas cosas internamente en nuestra página, y se nos olvidó cambiar esta parte para que solamente apareciese el símbolo

&

Es decir, el ejemplo debería ser,

   cout << "Tamaño de cadenas de Mes2: " 
        << &Mes2[11][10]-Mes2[0] << endl; 
   cout << "Tamaño de Mes2 + cadenas : " 
        << sizeof(Mes2)+&Mes2[11][10]-Mes2[0] << endl;

En cuanto podamos, corregerimos el ejemplo.

Hasta luego,

Steven

Jordi
2013-06-21 11:35:54

#include <iostream>

using namespace std;

int main() {

char Mes[][11] = { "Enero", "Febrero", "Marzo", "Abril",

"Mayo", "Junio", "Julio", "Agosto",

"Septiembre", "Octubre", "Noviembre", "Diciembre"};

char *Mes2[] = { "Enero", "Febrero", "Marzo", "Abril",

"Mayo", "Junio", "Julio", "Agosto",

"Septiembre", "Octubre", "Noviembre", "Diciembre"};

cout << "Tamaño de Mes: " << sizeof(Mes) << endl;

cout << "Tamaño de Mes2: " << sizeof(Mes2) << endl;

cout << "Tamaño de cadenas de Mes2: "

<< &amp;Mes2[11][10]-Mes2[0] << endl;

cout << "Tamaño de Mes2 + cadenas : "

<< sizeof(Mes2)+&amp;Mes2[11][10]-Mes2[0] << endl;

return 0;

}

me sale error, &amp undeclared (first use of this action)

mocman
2013-12-06 20:28:12

Tienes que borrar "amp;", supongo que eso sale al poner el signo "&"

Milton Parra
2014-04-16 18:48:57

En el tema PUNTEROS GENÉRICOS se utiliza casting para mostrar ciertos valores. En el ejemplo el objeto COUT arroja:

carácter: H

entero: 1634496328

float: 2.72591e+20

Mi inquietud es, de dónde sale este valor entero para una cadena?.

Steven R. Davidson
2014-04-16 19:31:46

Hola Milton,

Los cástings en este ejemplo es de un tipo de puntero a otro tipo de puntero. Como se trata de punteros, apuntamos al mismo dato, pero su tipo implica su forma de interpretarlo. Si decimos que apuntamos a 'char', entonces tratamos el valor guardado (0x48 o 72, en decimal) como un entero, que a su vez es interpretado por 'cout <<' como un código en la tabla ASCII, el cual representa, 'H'.

Ahora bien, si decimos que ese valor es un 'int', entonces tenemos que recoger 4 bytes - suponiendo que 'int' ocupe 4 bytes. Esto significa que apuntamos al primer byte de los 4 bytes totales que comprende el tipo 'int'. Mirando los valores consecutivos: 0x48 0x6F 0x6C 0x61, en procesadores de Intel (y compatibles) tenemos el siguiente valor entero: 0x616C6F48 que es 1634496328, en decimal. Lo mismo ocurre con el resultado de 'float'.

Espero que esto te ayude.

Steven

Lil
2014-07-03 17:44:51

Hola,

En el primer ejemplo de la sección con el mismo nombre no entiendo lo siguiente:

Por que cadena1++; es Ilegal

y cadena1 + 2; no es Ilegal?

Entiendo que cadena1++; sería equivalente a cadena1 + 1;

entonces no entiendo por que cuando sumamos 1 es Ilegal, pero si sumamos 2 no es ilegal.

Gracias

Steven R. Davidson
2014-07-03 20:19:21

Hola Lil,

'cadena1++' es equivalente a,

cadena1 += 1;

que a su vez es equivalente a,

cadena1 = cadena1 + 1;

Esto implica que el operador de incremento es una asignación. Como un array es un valor literal (y constante), no tiene sentido permitir asignar un valor a ello. De hecho, el comentario que hacemos es: "...hay operaciones que están prohibidas con los arrays, ya que son punteros constantes". Es como intentar hacer esto,

500++;

que obviamente está prohibido.

La otra operación,

cadena1 + 2;

es solamente una suma: no existe un intento de modificar 'cadena1'.

Espero que esto aclare la duda.

Steven

Lucas
2014-11-04 14:02:11

Steven, no logro entender lo que quisiste explicarle a Milton: "... apuntamos al primer byte de los 4 bytes totales que comprende el tipo 'int'. Mirando los valores consecutivos: 0x48 0x6F 0x6C 0x61..."

No veo como son consecutivos esos valores y qué relación tienen con lo que se imprime.

Muchas gracias!

Adri
2014-12-31 19:45:42

A ver si me ha quedado claro:

No se puede igualar un puntero de un tipo a una variable de otro tipo, pero si a un puntero de otro tipo haciendo el casting de punteros que se muestra al principio de esta pagina. De modo que, si queremos hacer un puntero hacia una variable de distinto tipo, tendriamos que crear otro puntero del mismo tipo que el de la variable, despues igualar ese puntero a la direccion de la variable, y finalmente igualar el primer puntero a la direccion del puntero del mismo tipo del de la variable haciendo el casting, no?

Si esto es asi, entonces veo un poco absurdo tener que crear otro puntero para apuntar a una variable de distinto tipo por no dejar apuntar directamente a esa variable, porque despues el otro puntero que acabamos de crear no nos serviria para nada mas, y si lo queremos aprovechar para otra cosa, vamos a tener problemas.

Steven R. Davidson
2015-01-02 11:40:32

Hola Adri,

Correcto, aunque es mejor usar el término, "asignar" en lugar de "igualar", porque este último implica una comparación usando el operador ==.

Necesitas el puntero al tipo correcto, porque si no, el compilador no sabe cómo acceder correctamente al dato apuntado. Por ejemplo,

int inum = 10;
int *iptr = &inum;

*iptr += 3;

double dnum = -2.396712384154508;
iptr = &dnum;  // Supongamos que se puede hacer esto en C++

*iptr -= 10;

¿Cómo sabe el compilador acceder a 'dnum' si es de tipo 'double' cuando 'iptr' apunta a un 'int'? La representación interna de un valor de tipo 'int' es muy diferente a la de uno de tipo 'double', aparte de la diferencia de la cantidad de bytes que ocupa cada tipo de dato. Y por supuesto, sería muy difícil conocer tipos de datos personalizados (structs).

Lo mismo sucedería si usáremos punteros genéricos. Podemos usar el puntero como puntero; o sea, manipular la dirección de memoria. Sin embargo, a la hora de manipular el valor apuntado, necesitamos indicar su tipo. Por consiguiente, aplicaríamos la operación de cásting, como se ha visto previamente en este capítulo.

En general, o bien usamos punteros o bien usamos variables. Los punteros sirven para manipular variables, como las variable sirven para manipular valores.

Espero que esto aclare la duda.

Steven

Alfredo
2015-01-05 05:46:03

Hola, en otra documentacion la declaracion de un puntero, es un tanto diferente.

int *pEntero;

pEntero = new int;

Me pregunto si este metodo es lo mismo que hacer:

int *pEntero=0;

Otra pregunta adicional:

En la documentacion tambien lei que, al almacenar datos mediante punteros lo hacemos en la llamada "Memoria dinamica", y es necesario que se haga un delete. Hasta ahora estuve programando en la consola de Windows, todavia no uso punteros...; pero me di cuenta que no importa que tanto se modifiquen tus variables,al reiniciar tu programa va a "empezar desde cero",Mi pregunta es si es posible utilizar la consola como una "base de datos"; o sea modificar variables y que al abrir mi programa de nuevo se hayan guardado los cambios. LOs punteros tendran que ver con eso??

Gracias de antemano. :)

Steven R. Davidson
2015-01-05 11:28:37

Hola Alfredo,

La definición de un puntero es la misma que explicamos en este capítulo. La sintaxis es:

<tipo> * <identificador>;

(Nota: Aquí <tipo> se refiere al tipo de la variable apuntada). Esto no difiere de cualquier definición de una variable:

<tipo> <identificador>;

Por lo tanto, un puntero es una variable, lo que implica que se puede tratar a un puntero como cualquier variable: definición, inicialización, asignación (lectura y escritura), obtener la dirección de memoria que ocupa, cásting, y la aplicación de ciertos operadores.

La inicialización:

int *pEntero = 0;

simboliza que se trata de un puntero nulo: no apunta a nada. Huelga decir que el estándar actual (y relativamente reciente) agrega un vocablo para representar un puntero nulo del tipo adecuado: nullptr; esto es,

int *pEntero = nullptr;  // puntero nulo de tipo 'int *'

Esto contradice el primer ejemplo que das, ya que adjudicas memoria dinámicamente para crear un entero de tipo 'int', y luego asignas la dirección de la memoria que ocupa tal entero al puntero, 'pEntero'. Es posible que no se pueda adjudicar la memoria, lo que conllevaría a una asignación de un puntero nulo.

La consola en MS-Windows no es más que un programa ejecutable, como cualquier otro programa. En MS-Windows (y en otros sistemas opertivos), cada programa supone la creación de un proceso, lo cual implica la adjudicación de ciertos recursos, como una cantidad de memoria para tal proceso. Al terminar la ejecución del programa, se termina el proceso y se libera todos los recursos previamente creados para ello. Esto significa que los datos en tal memoria no son accesibles; y tal memoria puede ser usada por el sistema operativo, y por tanto esos datos pueden ser sobreescritos.

En general, no puedes guardar datos en la consola directamente, porque como he explicado, no es más que un programa. Si tal programa sigue un mecanismo para cargar datos y guardarlos en su programa, entonces sí podrías usarlo. Como se trata de un programa importante, puedes usar variables de entorno que se suelen cargar del registro de MS-Windows. Tendrías que consultar la documentación de "cmd.exe", que es la consola en sí. Existen algunas funciones especiales del API de MS-Windows para controlar la consola desde tu propia aplicación de MS-Windows. De todas maneras, creo que es mejor crear tu propio fichero para guardar los datos que quieras guardar para tu programa, el cual tendría que leerlo para cargar los datos que quieres cada vez que se ejecute; y obviamente, poder guardar los datos justo antes de finalizar su ejecución.

Espero haber aclarado las dudas.

Steven

Diego
2015-01-07 00:31:35

Hola, Buenos días!

En los ejemplos se muestra muy bien, que los punteros pueden ser utilizados como strings, pero creo que se pierde la declaración clásica de un puntero, por eso me genera ciertas dudas.

Yo entendí esto de la siguiente forma:

Si la definicion de un puntero a char es asi(sin iniciarlo en cero necesariamente):

char *puntero = 0;

Sabemos que si al intentar asignar un valor al campo donde apunta el puntero de esta forma:

int *puntero = 15;

normalmente nos sale un error, porque equivale a puntero=15, cuando lo correcto seria escribir *puntero=15(después de obtener un espacio int)o eso entiendo...

Porqué entonces podemos definir un puntero de esta forma:

char *mensaje="Hola Mundo";

Al parecer es un caso especial no?,

Pero lo que en realidad me preocupa es qué está sucediendo con las direcciones de los punteros a char, ya no salen los clásicos hexadecimal(eso creo), salen los carácteres con los que se inicia, incluso si solo defines el puntero, su direccion ya es un caracter.Entonces, estos caracteres actuan como alias?, o que es lo que sucede?,esa es su verdadera direccion?

Steven R. Davidson
2015-01-11 12:07:58

Hola Diego,

En el caso de punteros a 'char' y cadenas literales, no se trata de un caso especial. Lo que ocurre es que el compilador gestiona la cadena literal, creando la memoria necesaria y copiando los caracteres de la cadena en ella.

Como se trata de un literal, la cadena es constante, por lo que el puntero debe ser a un carácter constante; esto es,

const char *szMensaje = "Hola Mundo";

El puntero simplemente apunta al comienzo de la cadena; o sea, al primer carácter.

Espero haber aclarado la duda.

Steven

leonardo
2015-08-09 18:45:31

Hola a todos, corri el siguiente codigo:

#include <iostream>
using namespace std;
int main() { 
   char *Mes2[] = { "Enero", "Febrero", "Marzo", "Abril", 
      "Mayo", "Junio", "Julio", "Agosto", 
      "Septiembre", "Octubre", "Noviembre", "Diciembre"};
   cout<<"el valor de Mes2: "<<Mes2<<endl;
   cout<<"el valor de Mes2[1]-Mes2[0]: "<<Mes2[1]-Mes2[0]<<endl;
   cout<<"el valor de Mes2[0]: "<<Mes2[0]<<endl;
   cout<<"el valor de &Mes2[11][9]: "<<&Mes2[11][9]<<endl;
   cout<<"el valor de &Mes2[11][8]: "<<&Mes2[11][8]<<endl;
   cout<<"el valor de Mes2[11][8]: "<<Mes2[11][8]<<endl; 
   return 0; 
}

y la salida fue esta:

el valor de Mes2: 0x22cab0

el valor de Mes2[1]-Mes2[0]: 6

el valor de Mes2[0]: Enero

el valor de &Mes2[11][9]:

el valor de &Mes2[11][8]: e

el valor de Mes2[11][8]: e

Si char *Mes2[] es un array de dirrecciones de los arrays de meses. entonces:

Mes2[0] no deveria arrojar una direcion algo asi 0x22cad3? en lugar de Enero.

Porque razon &Mes2[11][8] y Mes2[11][8] arrojan el mismo resultado? &Mes2[11][8] no deberia ser una direccion de memoria?

Y por favor una respuesta a esta pregunta, Como consigo la direccion donde esta guardado el ultimo char 'e' de Diciembre.

Muchas gracias por el curso, está muy bueno y lo voy a continuar.

Steven R. Davidson
2015-08-10 08:15:23

Hola Leonardo,

El "problema" tiene que ver con el funcionamiento de 'cout <<' al pasar un array de tipo 'char', el cual se interpreta como una cadena de caracteres (terminada en cero). Para obligar a 'cout <<' a interpretar el array de 'char' como una dirección de memoria y mostrarla como un entero hexadecimal, realiza un cásting a 'void *'. Por ejemplo,

cout << (void *)Mes2[0] << endl;

Espero que esto te aclare las dudas.

Steven

leonardo gamboa
2015-08-10 19:39:16

Hola Steven,

usando:

cout<<(void *)Mes2[0]

efectivamente arroja la dirección de memoria y no el array.

Y por fin entendí que:

&Mes2[11][8];

equivale a:

&(Mes2[11]+8);

Y la suma o resta entre punteros (que son direcciones de memoria) arroja la diferencia de espacios de memoria del tipo de objeto declarado en el puntero.

En el curso esta perfectamente explicado pero lo estaba interpretando mal. Muchas gracias por la respuesta.

Steven R. Davidson
2015-08-11 03:42:16

Hola Leonardo,

Casi. El operador [] realiza la suma al igual que el acceso a tal dirección de memoria. Por lo tanto,

&Mes2[11][8]

es equivalente a:

&( *(Mes2[11] + 8) )

Y en este caso, los dos operandos de memoria y acceso se cancelan, por lo que es equivalente a:

Mes2[11] + 8

Sólo quiero aclarar que la resta de punteros tiene sentido si tales direcciones de memoria forman parte de un bloque de memoria, como el caso de un array.

Espero haber aclarado las dudas.

Steven

Tomas
2015-10-02 21:00:58

Buenas Steven,

Mi duda es la siguiente. Supongamos el siguiente programa.

#include <iostream>
using namespace std;

int main() 
{
	char c[] = "Hello World!";
	char *pchar;
	int x[] = { 0, 1, 2 };
	int *pint;

	pchar = c;
	pint = x;
	
	cout << c << "\n" << x << "\n" << pchar << "\n" << pint << "\n";

	return 0;
}

Por qué al imprimir el puntero c/pchar tipo char me devuelve todo el array "Hello World!" y no la dirección de la primera posición? O en caso contrario. Por qué en el caso del puntero tipo int x/pint me da la dirección y no todo el array de enteros?

Gracias!

Steven R. Davidson
2015-10-02 22:18:15

Hola Tomás,

La funcionalidad de 'cout <<' es distinta cuando el operando es un puntero a carácter (char *) a cuando es un puntero de otro tipo (void *). Si se trata de un puntero a 'char', entonces 'cout <<' lo interpreta como una cadena de caracteres terminada en nulo. Si se trata de otro tipo de puntero, entonces 'cout <<' muestra su valor de una dirección de memoria.

Si quieres mostrar el valor de 'c' o de 'pchar', entonces realiza un cásting a 'void *'; por ejemplo,

cout << (void *) c << endl;

Espero haber aclarado la duda.

Steven

Adrian
2016-09-25 20:47:23

Steven, en uno de tus comentarios mencionas nullptr. ¿Por que no metes eso tambien en el tutorial en lugar de mencionarlo en un comentario?

Carlo
2017-01-05 02:14:05
#include <iostream>
using namespace std;

int main()
{
    char cadena[10]; /// Si aqui se pone: char cadena[10] = "Hola"; tambien c apunta a toda la
    ///cadena y no solo al primer elemento, con arrays de enteros no pasa eso.
    cadena[0] = 'L';
    cadena[1] = 'O';
    cadena[2] = 'C';
    cadena[3] = 'A';
    cadena[4] = '\0';
    int cadeno[] = {3,2,5,20};
    char *c;
    int *n;
    void *v;

    c = cadena; /// c apunta a cadena
    n = cadeno;
    cout << c << endl;
    cout << &cadena[0] << endl;
    cout << *c << endl;
    cout << cadena[0] << endl;
    cout << &c << endl;

    cout << endl << endl;

    cout << n << endl;
    cout << &cadeno[0] << endl;
    cout << *n << endl;
    cout << cadeno[0] << endl;
    cout << &n << endl;
    return 0;
}

Mi duda es: ¿Por que en estas lineas me aparece en consola "Hola" y no la dirección de memoria como sucede con los enteros?

 
cout << c << endl;
cout << &cadena[0] << endl;
Carlo
2017-01-06 07:20:00

Creo que ya resolvi mi duda :P, solo quiero mencionarlo a ver si estoy bien..

Lo que sucede

    cout << (void*)c << endl;
    cout << (void*)&cadena[1] << endl;
    cout << *c << endl;
    cout << cadena[0] << endl;

En la primera linea sino hago el casting me muestra "LOCA", por el simple hecho de ser un puntero constante y por el funcionamiento de cout con las cadenas no?, pero en realidad si esta apuntando al primera caracter que es 'L' correcto?

Por eso en la tercera linea de mi codigo como ahi si especifico que me devuelva el valor apuntado por el puntero con el operador de indireccion, solo me muestra la 'L', pero de nuevo por el cout me muesta 'L' y no una direccion cierto?,

Sin embargo si tambien le hago el casting quedando asi..

cout << (void*)*c << endl;

no coincide la direccion por la de la primera linea, osea esta:

cout << (void*)c << endl;

En su lugar me sale esto: 0x4c

¿A que se debe? Espero me respondan, gracias!

PD: Todo esto no sucede con enteros verdad?

Steven R. Davidson
2017-01-06 15:19:20

Hola Carlo,

El funcionamiento de 'cout <<' depende del tipo del segundo operando. En la primera línea, el tipo es 'void *', por lo que 'cout <<' mostrará el valor, que es una dirección de memoria al primer byte de la cadena, "LOCA", que concuerda con el primer carácter de 'L'.

Para la tercera línea, correctamente describes lo que ocurre: 'cout <<' se comporta de diferente manera al recibir un 'char' como operando.

Ten cuidado con realizar cástings que no tienen sentido. En el caso que presentas, estás intentando cambiar el tipo del valor de 76, que es el código ASCII de 'L', de 'char' a 'void *'. Sin embargo, un puntero suele ocupar más que un 'char', el cual es siempre 1 byte. Por esta razón aparece 0x4c que está en hexadecimal para el valor de 76 (en decimal).

No hay comportamientos especiales de 'cout <<' para otros tipos de punteros a tipos fundamentales, por lo que se convertirán a 'void *' para elegir tal comportamiento. El único tipo de puntero reconocido por 'cout <<', que no sea un puntero genérico ('void *'), es un puntero a caracteres, como por ejemplo, 'char *' y 'const char *'.

Espero haber aclarado las dudas.

Steven

Carlo
2017-01-15 08:29:00

Claro como el agua, muchas gracias por responder!, seguimos...