Sé que esta es una pregunta realmente básica, pero acabo de comenzar con una programación básica de C ++ después de codificar algunos proyectos con lenguajes de alto nivel.
Usamos punteros porque es más fácil darle a alguien la dirección de su hogar que dar una copia de su hogar a todos.
Rishi Dua
14
@RishiDua Esa es la mejor explicación de un puntero que he encontrado. Gracias por eso, ha aumentado mi comprensión :)
Josh
2
Los punteros también se pueden usar cuando desea devolver más de un valor / objeto.
Gaurav
Respuestas:
172
¿Por qué usar punteros sobre variables normales?
La respuesta corta es: no lo hagas. ;-) Los punteros se deben usar donde no se puede usar nada más. Esto se debe a la falta de funcionalidad adecuada, a la falta de tipos de datos o al puro rendimiento. Más abajo ...
¿Cuándo y dónde debo usar punteros?
La respuesta corta aquí es: Donde no puedes usar nada más. En C no tiene soporte para tipos de datos complejos como una cadena. Tampoco hay forma de pasar una variable "por referencia" a una función. Ahí es donde tienes que usar punteros. También puede hacer que apunten a prácticamente cualquier cosa, listas vinculadas, miembros de estructuras, etc. Pero no entremos en eso aquí.
¿Cómo se utilizan punteros con matrices?
Con poco esfuerzo y mucha confusión. ;-) Si hablamos de tipos de datos simples como int y char, hay poca diferencia entre una matriz y un puntero. Estas declaraciones son muy similares (pero no iguales; por ejemplo, sizeofdevolverán valores diferentes):
char* a ="Hello";char a[]="Hello";
Puede alcanzar cualquier elemento de la matriz como este
printf("Second char is: %c", a[1]);
Índice 1 ya que la matriz comienza con el elemento 0. :-)
O igualmente podrías hacer esto
printf("Second char is: %c",*(a+1));
El operador de puntero (el *) es necesario ya que le estamos diciendo a printf que queremos imprimir un carácter. Sin el *, se imprimirá la representación de caracteres de la dirección de memoria. Ahora estamos usando el personaje en su lugar. Si hubiéramos usado% s en lugar de% c, le habríamos pedido a printf que imprima el contenido de la dirección de memoria señalada por 'a' más uno (en este ejemplo anterior), y no habríamos tenido que poner el * Al frente:
printf("Second char is: %s",(a+1));/* WRONG */
Pero esto no solo habría impreso el segundo carácter, sino todos los caracteres en las siguientes direcciones de memoria, hasta que se encuentre un carácter nulo (\ 0). Y aquí es donde las cosas comienzan a ponerse peligrosas. ¿Qué sucede si accidentalmente intenta imprimir una variable del tipo entero en lugar de un puntero de caracteres con el formateador% s?
char* a ="Hello";int b =120;
printf("Second char is: %s", b);
Esto imprimirá lo que se encuentre en la dirección de memoria 120 y continuará imprimiendo hasta que se encuentre un carácter nulo. Es incorrecto e ilegal realizar esta declaración printf, pero probablemente funcionaría de todos modos, ya que un puntero en realidad es del tipo int en muchos entornos. Imagine los problemas que podría causar si usara sprintf () en su lugar y asigne de esta manera "matriz de caracteres" demasiado larga a otra variable, que solo tiene un cierto espacio limitado asignado. Lo más probable es que termine escribiendo sobre otra cosa en la memoria y haga que su programa se bloquee (si tiene suerte).
Ah, y si no asigna un valor de cadena al puntero / matriz de caracteres cuando lo declara, DEBE asignarle suficiente cantidad de memoria antes de asignarle un valor. Usando malloc, calloc o similar. Esto ya que solo declaró un elemento en su matriz / una sola dirección de memoria para señalar. Aquí hay algunos ejemplos:
char* x;/* Allocate 6 bytes of memory for me and point x to the first of them. */
x =(char*) malloc(6);
x[0]='H';
x[1]='e';
x[2]='l';
x[3]='l';
x[4]='o';
x[5]='\0';
printf("String \"%s\" at address: %d\n", x, x);/* Delete the allocation (reservation) of the memory. *//* The char pointer x is still pointing to this address in memory though! */
free(x);/* Same as malloc but here the allocated space is filled with null characters!*/
x =(char*) calloc(6,sizeof(x));
x[0]='H';
x[1]='e';
x[2]='l';
x[3]='l';
x[4]='o';
x[5]='\0';
printf("String \"%s\" at address: %d\n", x, x);/* And delete the allocation again... */
free(x);/* We can set the size at declaration time as well */char xx[6];
xx[0]='H';
xx[1]='e';
xx[2]='l';
xx[3]='l';
xx[4]='o';
xx[5]='\0';
printf("String \"%s\" at address: %d\n", xx, xx);
Tenga en cuenta que aún puede usar la variable x después de haber realizado un free () de la memoria asignada, pero no sabe qué hay allí. También tenga en cuenta que los dos printf () pueden darle direcciones diferentes, ya que no hay garantía de que la segunda asignación de memoria se realice en el mismo espacio que la primera.
Su ejemplo con 120 está mal. Está utilizando% c no% s, por lo que no hay error; simplemente imprime la letra minúscula x. Además, su posterior afirmación de que un puntero es de tipo int está mal y es un muy mal consejo para dar a un programador de C sin experiencia con punteros.
R .. GitHub DEJA DE AYUDAR AL HIELO
13
-1 Empezaste bien pero el primer ejemplo está mal. No, no es lo mismo. En el primer caso aes un puntero y en el segundo caso aes una matriz. ¿Ya lo mencioné? ¡No es lo mismo! Compruébelo usted mismo: Compare sizeof (a), intente asignar una nueva dirección a una matriz. No va a funcionar
sellibitze
42
char* a = "Hello";y char a[] = "Hello";no son iguales, son bastante diferentes. Uno declara un puntero y el otro una matriz. Pruebe sizeofay verá la diferencia.
Patrick Schlüter
12
"No es incorrecto ni ilegal realizar esta declaración printf". Esto es simplemente incorrecto. Esa declaración printf tiene un comportamiento indefinido. En muchas implementaciones causará una infracción de acceso. Los punteros en realidad no son del tipo int, en realidad son punteros (y nada más).
Mankarse
19
-1, te equivocaste con tantos hechos aquí y simplemente diré que no mereces la cantidad de votos a favor o el estado de respuesta aceptado.
asveikau
50
Una razón para usar punteros es para que una variable o un objeto se pueda modificar en una función llamada.
En C ++ es una mejor práctica usar referencias que punteros. Aunque las referencias son esencialmente punteros, C ++ en cierta medida oculta el hecho y hace que parezca que está pasando por valor. Esto facilita cambiar la forma en que la función de llamada recibe el valor sin tener que modificar la semántica de pasarlo.
Considere los siguientes ejemplos:
Usando referencias:
publicvoid doSomething(){int i =10;
doSomethingElse(i);// passes i by references since doSomethingElse() receives it// by reference, but the syntax makes it appear as if i is passed// by value}publicvoid doSomethingElse(int& i)// receives i as a reference{
cout << i << endl;}
Probablemente sea una buena idea mencionar que las referencias son más seguras, ya que no puede pasar una referencia nula.
SpoonMeiser
26
Sí, esa es probablemente la mayor ventaja de usar referencias. Gracias por mencionarlo. Sin juego de palabras :)
trshiv
2
Seguramente puede pasar una referencia nula. Simplemente no es tan fácil como pasar un puntero nulo.
2013
1
coincidir con @ n0rd. Si cree que no puede pasar una referencia nula, está equivocado. Es más fácil pasar una referencia colgante que una referencia nula, pero en última instancia, puede hacerlo de manera bastante fácil. Las referencias no son una bala de plata que protege a un ingeniero de dispararse en el pie. Véalo en vivo .
WhozCraig
2
@ n0rd: Hacer eso es un comportamiento explícitamente indefinido.
Daniel Kamil Kozar
42
Los punteros le permiten referirse al mismo espacio en la memoria desde múltiples ubicaciones. Esto significa que puede actualizar la memoria en una ubicación y el cambio se puede ver desde otra ubicación en su programa. También ahorrará espacio al poder compartir componentes en sus estructuras de datos.
Debe usar punteros en cualquier lugar donde necesite obtener y pasar la dirección a un lugar específico en la memoria. También puede usar punteros para navegar por las matrices:
Una matriz es un bloque de memoria contigua que se ha asignado con un tipo específico. El nombre de la matriz contiene el valor del punto inicial de la matriz. Cuando sumas 1, eso te lleva al segundo lugar. Esto le permite escribir bucles que incrementan un puntero que se desliza hacia abajo en la matriz sin tener un contador explícito para acceder a la matriz.
Aquí hay un ejemplo en C:
char hello[]="hello";char*p = hello;while(*p){*p +=1;// increase the character by one
p +=1;// move to the next spot}
printf(hello);
huellas dactilares
ifmmp
porque toma el valor de cada personaje y lo incrementa en uno.
because it takes the value for each character and increments it by one. ¿Está en representación ascii o cómo?
Eduardo S.
28
Los punteros son una forma de obtener una referencia indirecta a otra variable. En lugar de mantener el valor de una variable, le dicen su dirección . Esto es particularmente útil cuando se trata de matrices, ya que al usar un puntero al primer elemento de una matriz (su dirección) puede encontrar rápidamente el siguiente elemento incrementando el puntero (a la siguiente ubicación de dirección).
La mejor explicación de punteros y aritmética de punteros que he leído está en The C Programming Language de K&R . Un buen libro para comenzar a aprender C ++ es C ++ Primer .
¡gracias! Finalmente, una explicación práctica de los beneficios de usar la pila. ¿Un puntero para colocar en una matriz también mejora el rendimiento al acceder a los valores @ y en relación con el puntero?
Esta es probablemente la mejor explicación que he leído. la gente tiene una forma de "complicar demasiado" las cosas :)
Detilium
23
Déjame intentar responder esto también.
Los punteros son similares a las referencias. En otras palabras, no son copias, sino una forma de referirse al valor original.
Antes que nada, un lugar en el que normalmente tendrá que usar muchos punteros es cuando se trata de hardware integrado . Tal vez necesite alternar el estado de un pin de E / S digital. Quizás esté procesando una interrupción y necesite almacenar un valor en una ubicación específica. Te dan la imagen. Sin embargo, si no está tratando con hardware directamente y solo se pregunta qué tipos usar, siga leyendo.
¿Por qué usar punteros en lugar de variables normales? La respuesta se vuelve más clara cuando se trata de tipos complejos, como clases, estructuras y matrices. Si usara una variable normal, podría terminar haciendo una copia (los compiladores son lo suficientemente inteligentes como para evitar esto en algunas situaciones y C ++ 11 también ayuda, pero por ahora nos mantendremos alejados de esa discusión).
¿Qué sucede si quieres modificar el valor original? Podrías usar algo como esto:
MyType a;//let's ignore what MyType actually is right now.
a = modify(a);
Eso funcionará bien y si no sabes exactamente por qué estás usando punteros, no deberías usarlos. Tenga cuidado con la razón "probablemente sean más rápidos". Ejecute sus propias pruebas y, si realmente son más rápidas, úselas.
Sin embargo, supongamos que está resolviendo un problema en el que necesita asignar memoria. Cuando asigna memoria, debe desasignarla. La asignación de memoria puede o no ser exitosa. Aquí es donde los punteros son útiles: le permiten probar la existencia del objeto que ha asignado y le permiten acceder al objeto para el que se asignó la memoria al hacer referencia al puntero.
MyType*p = NULL;//empty pointerif(p){//we never reach here, because the pointer points to nothing}//now, let's allocate some memory
p =newMyType[50000];if(p)//if the memory was allocated, this test will pass{//we can do something with our allocated arrayfor(size_t i=0; i!=50000; i++){MyType&v =*(p+i);//get a reference to the ith object//do something with it//...}delete[] p;//we're done. de-allocate the memory}
Esta es la clave de por qué usaría punteros: las referencias suponen que el elemento al que hace referencia ya existe . Un puntero no lo hace.
La otra razón por la que usaría punteros (o al menos tendría que lidiar con ellos) es porque son un tipo de datos que existía antes de las referencias. Por lo tanto, si terminas usando bibliotecas para hacer las cosas en las que sabes que son mejores, encontrarás que muchas de estas bibliotecas usan punteros en todo el lugar, simplemente por el tiempo que han existido (mucho de ellos fueron escritos antes de C ++).
Si no usó ninguna biblioteca, podría diseñar su código de tal manera que pudiera mantenerse alejado de los punteros, pero dado que los punteros son uno de los tipos básicos del lenguaje, cuanto más rápido se sienta cómodo al usarlos, más tus habilidades en C ++ serán portátiles.
Desde el punto de vista del mantenimiento, también debo mencionar que cuando usa punteros, debe probar su validez y manejar el caso cuando no son válidos, o simplemente asumir que son válidos y aceptar el hecho de que su el programa se bloqueará o peor CUANDO esa suposición se rompa. Dicho de otra manera, su elección con los punteros es introducir la complejidad del código o más esfuerzo de mantenimiento cuando algo se rompe y está tratando de rastrear un error que pertenece a toda una clase de errores que introducen los punteros, como la corrupción de la memoria.
Entonces, si controla todo su código, manténgase alejado de los punteros y, en su lugar, use referencias, manteniéndolos constantes cuando pueda. Esto lo obligará a pensar en los tiempos de vida de sus objetos y terminará manteniendo su código más fácil de entender.
Solo recuerda esta diferencia: una referencia es esencialmente un puntero válido. Un puntero no siempre es válido.
Entonces, ¿estoy diciendo que es imposible crear una referencia no válida? No. Es totalmente posible, porque C ++ te permite hacer casi cualquier cosa. Es más difícil de hacer sin querer y te sorprenderá cuántos errores son involuntarios :)
Básicamente, la arquitectura estándar de la CPU es una arquitectura de Von Neumann, y es tremendamente útil poder referirse a la ubicación de un elemento de datos en la memoria y hacer operaciones aritméticas con él en dicha máquina. Si conoce alguna variante del lenguaje ensamblador, verá rápidamente cuán crucial es esto en el nivel bajo.
C ++ hace que los punteros sean un poco confusos, ya que a veces los administra por usted y oculta su efecto en forma de "referencias". Si usa C directo, la necesidad de punteros es mucho más obvia: no hay otra forma de hacer una llamada por referencia, es la mejor manera de almacenar una cadena, es la mejor manera de recorrer una matriz, etc.
Un uso de los punteros (no mencionaré cosas que ya están cubiertas en las publicaciones de otras personas) es acceder a la memoria que no ha asignado. Esto no es muy útil para la programación de PC, pero se usa en la programación integrada para acceder a dispositivos de hardware mapeados en memoria.
En los viejos tiempos de DOS, solía poder acceder a la memoria de video de la tarjeta de video directamente declarando un puntero para:
No es una razón para usar un puntero, una estructura similar a un tramo, que también tiene la longitud, es mucho más apropiado. En estos días lo es gsl::span, y pronto lo será std::span.
einpoklum
10
En gran parte, los punteros son matrices (en C / C ++): son direcciones en la memoria y se puede acceder como una matriz si se desea (en casos "normales").
Como son la dirección de un elemento, son pequeñas: solo ocupan el espacio de una dirección. Como son pequeños, enviarlos a una función es barato. Y luego permiten que esa función funcione en el elemento real en lugar de una copia.
Si desea hacer una asignación dinámica de almacenamiento (como una lista vinculada), debe usar punteros, porque son la única forma de obtener memoria del montón.
Creo que es engañoso decir que los punteros son matrices. Realmente, los nombres de matriz son punteros constantes al primer elemento de la matriz. El hecho de que pueda acceder a un punto arbitrario como si fuera una matriz no significa que sea ... podría obtener una infracción de acceso :)
rmeador
2
Los punteros no son matrices. Lea la sección 6 de la FAQ comp.lang.c .
Keith Thompson
Los punteros realmente no son matrices. Además, tampoco hay mucho uso en matrices en bruto, ciertamente no después de C ++ 11 y std::array.
einpoklum
@einpoklum: los punteros están lo suficientemente cerca de las matrices como para ser equivalentes en las operaciones de referencia e iterar a través de las matrices :) .... también: C ++ 11 estaba a 3 años del lanzamiento cuando se escribió esta respuesta en 2008
warren
@warren: Los punteros no son matrices ni están cerca de las matrices, simplemente se descomponen en matrices. Es pedagógicamente inapropiado decirle a la gente que son similares. Además, ciertamente puede tener referencias a matrices, que no son del mismo tipo que los punteros y mantienen la información de tamaño; ver aquí
einpoklum
9
Los punteros son importantes en muchas estructuras de datos cuyo diseño requiere la capacidad de vincular o encadenar un "nodo" a otro de manera eficiente. No "elegirías" un puntero sobre decir un tipo de datos normal como flotante, simplemente tienen diferentes propósitos.
Los punteros son útiles cuando se requiere un alto rendimiento y / o una huella de memoria compacta.
La dirección del primer elemento en su matriz se puede asignar a un puntero. Esto le permite acceder directamente a los bytes asignados subyacentes directamente. Sin embargo, el objetivo de una matriz es evitar que necesites hacer esto.
Una forma de usar punteros sobre las variables es eliminar la memoria duplicada requerida. Por ejemplo, si tiene algún objeto complejo grande, puede usar un puntero para señalar esa variable para cada referencia que haga. Con una variable, debe duplicar la memoria para cada copia.
Realmente, cuando lo piensas, esto tiene sentido. Cuando utiliza el polimorfismo de subtipo, en última instancia, no sabe de antemano qué implementación del método de la clase o subclase se invocará porque no sabe cuál es la clase real.
Esta idea de tener una variable que contenga un objeto de una clase desconocida es incompatible con el modo predeterminado (sin puntero) de C ++ para almacenar objetos en la pila, donde la cantidad de espacio asignado corresponde directamente a la clase. Nota: si una clase tiene 5 campos de instancia frente a 3, será necesario asignar más espacio.
Tenga en cuenta que si está usando '&' para pasar argumentos por referencia, la indirecta (es decir, los punteros) todavía está involucrada detrás de escena. El '&' es solo azúcar sintáctico que (1) le ahorra la molestia de usar la sintaxis de puntero y (2) permite que el compilador sea más estricto (como prohibir punteros nulos).
No, no tiene que usar punteros, puede usar referencias. Y cuando escribes "los punteros están involucrados detrás de escena", eso no tiene sentido. gotolas instrucciones también se utilizan detrás de escena, en las instrucciones de la máquina de destino. Todavía no pretendemos usarlos.
einpoklum
@ einpoklum-reinstateMonica Si tiene un conjunto de objetos sobre los que desea actuar, asignando cada elemento a su vez a una variable temporal y llamando a un método polimórfico en esa variable, entonces NECESITA un puntero porque no puede volver a vincular una referencia.
Sildoreth
Si tiene una referencia de clase base a una clase derivada y llama a un método virtual en esa referencia, se llamará a la anulación de la clase derivada. ¿Me equivoco?
einpoklum
@ einpoklum-reinstateMonica Eso es correcto. Pero no puede cambiar a qué objeto se hace referencia. Entonces, si está recorriendo una lista / conjunto / matriz de esos objetos, una variable de referencia no funcionará.
Sildoreth
5
Porque copiar objetos grandes en todos los lugares desperdicia tiempo y memoria.
¿Y cómo ayudan los punteros con eso? Creo que una persona procedente de Java o .Net no conoce la diferencia entre la pila y el montón, por lo que esta respuesta es bastante inútil ..
Mats Fredriksson
Puedes pasar por referencia. Eso evitará la copia.
Martin York
1
@MatsFredriksson: en lugar de pasar (copiar) una gran estructura de datos y volver a copiar el resultado, simplemente señale dónde está en la RAM y luego modifíquelo directamente.
John U
Así que no copie objetos grandes. Nadie dijo que necesita punteros para eso.
einpoklum
5
Aquí está mi respuesta, y no prometo ser un experto, pero he descubierto que los punteros son excelentes en una de mis bibliotecas que estoy tratando de escribir. En esta biblioteca (es una API de gráficos con OpenGL :-)) puede crear un triángulo con objetos de vértice pasados a ellos. El método de dibujo toma estos objetos triangulares, y bueno ... los dibuja en base a los objetos de vértice que creé. Bueno, esta bien.
Pero, ¿qué pasa si cambio una coordenada de vértice? ¿Moverlo o algo con moveX () en la clase de vértice? Bueno, está bien, ahora tengo que actualizar el triángulo, agregando más métodos y el rendimiento se está desperdiciando porque tengo que actualizar el triángulo cada vez que se mueve un vértice. Todavía no es gran cosa, pero no es tan genial.
Ahora, ¿qué pasa si tengo una malla con toneladas de vértices y toneladas de triángulos, y la malla gira y se mueve? Tendré que actualizar cada triángulo que use estos vértices, y probablemente cada triángulo en la escena porque no sabría cuáles usan qué vértices. Eso requiere mucha computadora, y si tengo varias mallas sobre un paisaje, ¡oh Dios! Estoy en problemas, porque estoy actualizando cada triángulo casi cada cuadro porque estos vértices están cambiando todo el tiempo.
Con punteros, no tiene que actualizar los triángulos.
Si tuviera tres * objetos de vértice por clase de triángulo, no solo estoy ahorrando espacio porque un trillón de triángulos no tienen tres objetos de vértice que sean grandes, sino que estos punteros siempre apuntarán a los vértices a los que están destinados, no importa con qué frecuencia cambian los vértices. Como los punteros todavía apuntan al mismo vértice, los triángulos no cambian y el proceso de actualización es más fácil de manejar. Si te confundiera, no lo dudaría, no pretendo ser un experto, solo pongo mis dos centavos en la discusión.
Aquí se describe la necesidad de punteros en lenguaje C
La idea básica es que muchas limitaciones en el lenguaje (como el uso de matrices, cadenas y la modificación de múltiples variables en las funciones) podrían eliminarse manipulando con las ubicaciones de memoria de los datos. Para superar estas limitaciones, se introdujeron punteros en C.
Además, también se ve que al usar punteros, puede ejecutar su código más rápido y ahorrar memoria en los casos en que transfiere grandes tipos de datos (como una estructura con muchos campos) a una función. Hacer una copia de dichos tipos de datos antes de pasarlos llevaría tiempo y consumiría memoria. Esta es otra razón por la cual los programadores prefieren punteros para tipos de datos grandes.
PD: Consulte el enlace proporcionado para obtener una explicación detallada con un código de muestra.
La pregunta es sobre C ++, esta respuesta es sobre C.
einpoklum
no, la pregunta está etiquetada c AND c ++. Quizás la etiqueta c sea irrelevante, pero está aquí desde el principio.
Jean-François Fabre
3
En java y C # todas las referencias de objetos son punteros, lo que sucede con c ++ es que tiene más control sobre dónde apunta los puntos. Recuerde Con gran poder viene la gran responsabilidad.
Con respecto a su segunda pregunta, generalmente no necesita usar punteros mientras programa, sin embargo, hay una excepción y es cuando hace una API pública.
El problema con las construcciones de C ++ que la gente generalmente usa para reemplazar los punteros depende mucho del conjunto de herramientas que use, lo cual está bien cuando tiene todo el control que necesita sobre el código fuente, sin embargo, si compila una biblioteca estática con Visual Studio 2008, por ejemplo e intente usarlo en un estudio visual 2010, obtendrá una tonelada de errores de vinculador porque el nuevo proyecto está vinculado con una versión más nueva de STL que no es compatible con versiones anteriores. Las cosas se ponen aún más desagradables si compila una DLL y le da una biblioteca de importación que las personas usan en un conjunto de herramientas diferente porque en ese caso su programa se bloqueará tarde o temprano sin razón aparente.
Entonces, con el fin de mover grandes conjuntos de datos de una biblioteca a otra, podría considerar asignar un puntero a una matriz a la función que se supone que copia los datos si no desea obligar a otros a usar las mismas herramientas que usa . Lo bueno de esto es que ni siquiera tiene que ser una matriz de estilo C, puede usar un std :: vector y dar el puntero al dar la dirección del primer elemento y vector [0] por ejemplo, y usar el std :: vector para gestionar la matriz internamente.
Otra buena razón para usar punteros en C ++ nuevamente se relaciona con las bibliotecas, considere tener un dll que no se puede cargar cuando se ejecuta su programa, por lo que si usa una biblioteca de importación, entonces la dependencia no está satisfecha y el programa se bloquea. Este es el caso, por ejemplo, cuando da una API pública en un archivo dll junto con su aplicación y desea acceder a ella desde otras aplicaciones. En este caso, para usar la API, debe cargar el archivo dll desde su ubicación (generalmente está en una clave de registro) y luego debe usar un puntero de función para poder llamar a funciones dentro de la DLL. A veces, las personas que hacen la API son lo suficientemente amables como para darle un archivo .h que contiene funciones auxiliares para automatizar este proceso y darle todos los punteros de funciones que necesita,
En algunos casos, se requieren punteros de función para usar funciones que están en una biblioteca compartida (.DLL o .so). Esto incluye realizar tareas en varios idiomas, donde a menudo se proporciona una interfaz DLL.
Hacer compiladores
Haciendo calculadoras científicas, donde tienes una matriz o un mapa vectorial o de cadenas de punteros de función?
Intentando modificar la memoria de video directamente - haciendo tu propio paquete de gráficos
Haciendo una API!
Estructuras de datos: punteros de enlace de nodo para árboles especiales que está creando
Hay muchas razones para los punteros. Es especialmente importante tener un nombre en C en las DLL si desea mantener la compatibilidad entre idiomas.
Respuestas:
La respuesta corta es: no lo hagas. ;-) Los punteros se deben usar donde no se puede usar nada más. Esto se debe a la falta de funcionalidad adecuada, a la falta de tipos de datos o al puro rendimiento. Más abajo ...
La respuesta corta aquí es: Donde no puedes usar nada más. En C no tiene soporte para tipos de datos complejos como una cadena. Tampoco hay forma de pasar una variable "por referencia" a una función. Ahí es donde tienes que usar punteros. También puede hacer que apunten a prácticamente cualquier cosa, listas vinculadas, miembros de estructuras, etc. Pero no entremos en eso aquí.
Con poco esfuerzo y mucha confusión. ;-) Si hablamos de tipos de datos simples como int y char, hay poca diferencia entre una matriz y un puntero. Estas declaraciones son muy similares (pero no iguales; por ejemplo,
sizeof
devolverán valores diferentes):Puede alcanzar cualquier elemento de la matriz como este
Índice 1 ya que la matriz comienza con el elemento 0. :-)
O igualmente podrías hacer esto
El operador de puntero (el *) es necesario ya que le estamos diciendo a printf que queremos imprimir un carácter. Sin el *, se imprimirá la representación de caracteres de la dirección de memoria. Ahora estamos usando el personaje en su lugar. Si hubiéramos usado% s en lugar de% c, le habríamos pedido a printf que imprima el contenido de la dirección de memoria señalada por 'a' más uno (en este ejemplo anterior), y no habríamos tenido que poner el * Al frente:
Pero esto no solo habría impreso el segundo carácter, sino todos los caracteres en las siguientes direcciones de memoria, hasta que se encuentre un carácter nulo (\ 0). Y aquí es donde las cosas comienzan a ponerse peligrosas. ¿Qué sucede si accidentalmente intenta imprimir una variable del tipo entero en lugar de un puntero de caracteres con el formateador% s?
Esto imprimirá lo que se encuentre en la dirección de memoria 120 y continuará imprimiendo hasta que se encuentre un carácter nulo. Es incorrecto e ilegal realizar esta declaración printf, pero probablemente funcionaría de todos modos, ya que un puntero en realidad es del tipo int en muchos entornos. Imagine los problemas que podría causar si usara sprintf () en su lugar y asigne de esta manera "matriz de caracteres" demasiado larga a otra variable, que solo tiene un cierto espacio limitado asignado. Lo más probable es que termine escribiendo sobre otra cosa en la memoria y haga que su programa se bloquee (si tiene suerte).
Ah, y si no asigna un valor de cadena al puntero / matriz de caracteres cuando lo declara, DEBE asignarle suficiente cantidad de memoria antes de asignarle un valor. Usando malloc, calloc o similar. Esto ya que solo declaró un elemento en su matriz / una sola dirección de memoria para señalar. Aquí hay algunos ejemplos:
Tenga en cuenta que aún puede usar la variable x después de haber realizado un free () de la memoria asignada, pero no sabe qué hay allí. También tenga en cuenta que los dos printf () pueden darle direcciones diferentes, ya que no hay garantía de que la segunda asignación de memoria se realice en el mismo espacio que la primera.
fuente
a
es un puntero y en el segundo casoa
es una matriz. ¿Ya lo mencioné? ¡No es lo mismo! Compruébelo usted mismo: Compare sizeof (a), intente asignar una nueva dirección a una matriz. No va a funcionarchar* a = "Hello";
ychar a[] = "Hello";
no son iguales, son bastante diferentes. Uno declara un puntero y el otro una matriz. Pruebesizeof
ay verá la diferencia.Una razón para usar punteros es para que una variable o un objeto se pueda modificar en una función llamada.
En C ++ es una mejor práctica usar referencias que punteros. Aunque las referencias son esencialmente punteros, C ++ en cierta medida oculta el hecho y hace que parezca que está pasando por valor. Esto facilita cambiar la forma en que la función de llamada recibe el valor sin tener que modificar la semántica de pasarlo.
Considere los siguientes ejemplos:
Usando referencias:
Usando punteros:
fuente
Aquí hay un ejemplo en C:
huellas dactilares
porque toma el valor de cada personaje y lo incrementa en uno.
fuente
because it takes the value for each character and increments it by one
. ¿Está en representación ascii o cómo?Los punteros son una forma de obtener una referencia indirecta a otra variable. En lugar de mantener el valor de una variable, le dicen su dirección . Esto es particularmente útil cuando se trata de matrices, ya que al usar un puntero al primer elemento de una matriz (su dirección) puede encontrar rápidamente el siguiente elemento incrementando el puntero (a la siguiente ubicación de dirección).
La mejor explicación de punteros y aritmética de punteros que he leído está en The C Programming Language de K&R . Un buen libro para comenzar a aprender C ++ es C ++ Primer .
fuente
Déjame intentar responder esto también.
Los punteros son similares a las referencias. En otras palabras, no son copias, sino una forma de referirse al valor original.
Antes que nada, un lugar en el que normalmente tendrá que usar muchos punteros es cuando se trata de hardware integrado . Tal vez necesite alternar el estado de un pin de E / S digital. Quizás esté procesando una interrupción y necesite almacenar un valor en una ubicación específica. Te dan la imagen. Sin embargo, si no está tratando con hardware directamente y solo se pregunta qué tipos usar, siga leyendo.
¿Por qué usar punteros en lugar de variables normales? La respuesta se vuelve más clara cuando se trata de tipos complejos, como clases, estructuras y matrices. Si usara una variable normal, podría terminar haciendo una copia (los compiladores son lo suficientemente inteligentes como para evitar esto en algunas situaciones y C ++ 11 también ayuda, pero por ahora nos mantendremos alejados de esa discusión).
¿Qué sucede si quieres modificar el valor original? Podrías usar algo como esto:
Eso funcionará bien y si no sabes exactamente por qué estás usando punteros, no deberías usarlos. Tenga cuidado con la razón "probablemente sean más rápidos". Ejecute sus propias pruebas y, si realmente son más rápidas, úselas.
Sin embargo, supongamos que está resolviendo un problema en el que necesita asignar memoria. Cuando asigna memoria, debe desasignarla. La asignación de memoria puede o no ser exitosa. Aquí es donde los punteros son útiles: le permiten probar la existencia del objeto que ha asignado y le permiten acceder al objeto para el que se asignó la memoria al hacer referencia al puntero.
Esta es la clave de por qué usaría punteros: las referencias suponen que el elemento al que hace referencia ya existe . Un puntero no lo hace.
La otra razón por la que usaría punteros (o al menos tendría que lidiar con ellos) es porque son un tipo de datos que existía antes de las referencias. Por lo tanto, si terminas usando bibliotecas para hacer las cosas en las que sabes que son mejores, encontrarás que muchas de estas bibliotecas usan punteros en todo el lugar, simplemente por el tiempo que han existido (mucho de ellos fueron escritos antes de C ++).
Si no usó ninguna biblioteca, podría diseñar su código de tal manera que pudiera mantenerse alejado de los punteros, pero dado que los punteros son uno de los tipos básicos del lenguaje, cuanto más rápido se sienta cómodo al usarlos, más tus habilidades en C ++ serán portátiles.
Desde el punto de vista del mantenimiento, también debo mencionar que cuando usa punteros, debe probar su validez y manejar el caso cuando no son válidos, o simplemente asumir que son válidos y aceptar el hecho de que su el programa se bloqueará o peor CUANDO esa suposición se rompa. Dicho de otra manera, su elección con los punteros es introducir la complejidad del código o más esfuerzo de mantenimiento cuando algo se rompe y está tratando de rastrear un error que pertenece a toda una clase de errores que introducen los punteros, como la corrupción de la memoria.
Entonces, si controla todo su código, manténgase alejado de los punteros y, en su lugar, use referencias, manteniéndolos constantes cuando pueda. Esto lo obligará a pensar en los tiempos de vida de sus objetos y terminará manteniendo su código más fácil de entender.
Solo recuerda esta diferencia: una referencia es esencialmente un puntero válido. Un puntero no siempre es válido.
Entonces, ¿estoy diciendo que es imposible crear una referencia no válida? No. Es totalmente posible, porque C ++ te permite hacer casi cualquier cosa. Es más difícil de hacer sin querer y te sorprenderá cuántos errores son involuntarios :)
fuente
Aquí hay una versión ligeramente diferente pero perspicaz de por qué muchas características de C tienen sentido: http://steve.yegge.googlepages.com/tour-de-babel#C
Básicamente, la arquitectura estándar de la CPU es una arquitectura de Von Neumann, y es tremendamente útil poder referirse a la ubicación de un elemento de datos en la memoria y hacer operaciones aritméticas con él en dicha máquina. Si conoce alguna variante del lenguaje ensamblador, verá rápidamente cuán crucial es esto en el nivel bajo.
C ++ hace que los punteros sean un poco confusos, ya que a veces los administra por usted y oculta su efecto en forma de "referencias". Si usa C directo, la necesidad de punteros es mucho más obvia: no hay otra forma de hacer una llamada por referencia, es la mejor manera de almacenar una cadena, es la mejor manera de recorrer una matriz, etc.
fuente
Un uso de los punteros (no mencionaré cosas que ya están cubiertas en las publicaciones de otras personas) es acceder a la memoria que no ha asignado. Esto no es muy útil para la programación de PC, pero se usa en la programación integrada para acceder a dispositivos de hardware mapeados en memoria.
En los viejos tiempos de DOS, solía poder acceder a la memoria de video de la tarjeta de video directamente declarando un puntero para:
Muchos dispositivos integrados todavía usan esta técnica.
fuente
gsl::span
, y pronto lo serástd::span
.En gran parte, los punteros son matrices (en C / C ++): son direcciones en la memoria y se puede acceder como una matriz si se desea (en casos "normales").
Como son la dirección de un elemento, son pequeñas: solo ocupan el espacio de una dirección. Como son pequeños, enviarlos a una función es barato. Y luego permiten que esa función funcione en el elemento real en lugar de una copia.
Si desea hacer una asignación dinámica de almacenamiento (como una lista vinculada), debe usar punteros, porque son la única forma de obtener memoria del montón.
fuente
std::array
.Los punteros son importantes en muchas estructuras de datos cuyo diseño requiere la capacidad de vincular o encadenar un "nodo" a otro de manera eficiente. No "elegirías" un puntero sobre decir un tipo de datos normal como flotante, simplemente tienen diferentes propósitos.
Los punteros son útiles cuando se requiere un alto rendimiento y / o una huella de memoria compacta.
La dirección del primer elemento en su matriz se puede asignar a un puntero. Esto le permite acceder directamente a los bytes asignados subyacentes directamente. Sin embargo, el objetivo de una matriz es evitar que necesites hacer esto.
fuente
Una forma de usar punteros sobre las variables es eliminar la memoria duplicada requerida. Por ejemplo, si tiene algún objeto complejo grande, puede usar un puntero para señalar esa variable para cada referencia que haga. Con una variable, debe duplicar la memoria para cada copia.
fuente
En C ++, si desea utilizar subtipo polimorfismo , se tiene que utilizar punteros. Ver esta publicación: C ++ Polimorfismo sin punteros .
Realmente, cuando lo piensas, esto tiene sentido. Cuando utiliza el polimorfismo de subtipo, en última instancia, no sabe de antemano qué implementación del método de la clase o subclase se invocará porque no sabe cuál es la clase real.
Esta idea de tener una variable que contenga un objeto de una clase desconocida es incompatible con el modo predeterminado (sin puntero) de C ++ para almacenar objetos en la pila, donde la cantidad de espacio asignado corresponde directamente a la clase. Nota: si una clase tiene 5 campos de instancia frente a 3, será necesario asignar más espacio.
Tenga en cuenta que si está usando '&' para pasar argumentos por referencia, la indirecta (es decir, los punteros) todavía está involucrada detrás de escena. El '&' es solo azúcar sintáctico que (1) le ahorra la molestia de usar la sintaxis de puntero y (2) permite que el compilador sea más estricto (como prohibir punteros nulos).
fuente
goto
las instrucciones también se utilizan detrás de escena, en las instrucciones de la máquina de destino. Todavía no pretendemos usarlos.Porque copiar objetos grandes en todos los lugares desperdicia tiempo y memoria.
fuente
Aquí está mi respuesta, y no prometo ser un experto, pero he descubierto que los punteros son excelentes en una de mis bibliotecas que estoy tratando de escribir. En esta biblioteca (es una API de gráficos con OpenGL :-)) puede crear un triángulo con objetos de vértice pasados a ellos. El método de dibujo toma estos objetos triangulares, y bueno ... los dibuja en base a los objetos de vértice que creé. Bueno, esta bien.
Pero, ¿qué pasa si cambio una coordenada de vértice? ¿Moverlo o algo con moveX () en la clase de vértice? Bueno, está bien, ahora tengo que actualizar el triángulo, agregando más métodos y el rendimiento se está desperdiciando porque tengo que actualizar el triángulo cada vez que se mueve un vértice. Todavía no es gran cosa, pero no es tan genial.
Ahora, ¿qué pasa si tengo una malla con toneladas de vértices y toneladas de triángulos, y la malla gira y se mueve? Tendré que actualizar cada triángulo que use estos vértices, y probablemente cada triángulo en la escena porque no sabría cuáles usan qué vértices. Eso requiere mucha computadora, y si tengo varias mallas sobre un paisaje, ¡oh Dios! Estoy en problemas, porque estoy actualizando cada triángulo casi cada cuadro porque estos vértices están cambiando todo el tiempo.
Con punteros, no tiene que actualizar los triángulos.
Si tuviera tres * objetos de vértice por clase de triángulo, no solo estoy ahorrando espacio porque un trillón de triángulos no tienen tres objetos de vértice que sean grandes, sino que estos punteros siempre apuntarán a los vértices a los que están destinados, no importa con qué frecuencia cambian los vértices. Como los punteros todavía apuntan al mismo vértice, los triángulos no cambian y el proceso de actualización es más fácil de manejar. Si te confundiera, no lo dudaría, no pretendo ser un experto, solo pongo mis dos centavos en la discusión.
fuente
Aquí se describe la necesidad de punteros en lenguaje C
La idea básica es que muchas limitaciones en el lenguaje (como el uso de matrices, cadenas y la modificación de múltiples variables en las funciones) podrían eliminarse manipulando con las ubicaciones de memoria de los datos. Para superar estas limitaciones, se introdujeron punteros en C.
Además, también se ve que al usar punteros, puede ejecutar su código más rápido y ahorrar memoria en los casos en que transfiere grandes tipos de datos (como una estructura con muchos campos) a una función. Hacer una copia de dichos tipos de datos antes de pasarlos llevaría tiempo y consumiría memoria. Esta es otra razón por la cual los programadores prefieren punteros para tipos de datos grandes.
PD: Consulte el enlace proporcionado para obtener una explicación detallada con un código de muestra.
fuente
En java y C # todas las referencias de objetos son punteros, lo que sucede con c ++ es que tiene más control sobre dónde apunta los puntos. Recuerde Con gran poder viene la gran responsabilidad.
fuente
Con respecto a su segunda pregunta, generalmente no necesita usar punteros mientras programa, sin embargo, hay una excepción y es cuando hace una API pública.
El problema con las construcciones de C ++ que la gente generalmente usa para reemplazar los punteros depende mucho del conjunto de herramientas que use, lo cual está bien cuando tiene todo el control que necesita sobre el código fuente, sin embargo, si compila una biblioteca estática con Visual Studio 2008, por ejemplo e intente usarlo en un estudio visual 2010, obtendrá una tonelada de errores de vinculador porque el nuevo proyecto está vinculado con una versión más nueva de STL que no es compatible con versiones anteriores. Las cosas se ponen aún más desagradables si compila una DLL y le da una biblioteca de importación que las personas usan en un conjunto de herramientas diferente porque en ese caso su programa se bloqueará tarde o temprano sin razón aparente.
Entonces, con el fin de mover grandes conjuntos de datos de una biblioteca a otra, podría considerar asignar un puntero a una matriz a la función que se supone que copia los datos si no desea obligar a otros a usar las mismas herramientas que usa . Lo bueno de esto es que ni siquiera tiene que ser una matriz de estilo C, puede usar un std :: vector y dar el puntero al dar la dirección del primer elemento y vector [0] por ejemplo, y usar el std :: vector para gestionar la matriz internamente.
Otra buena razón para usar punteros en C ++ nuevamente se relaciona con las bibliotecas, considere tener un dll que no se puede cargar cuando se ejecuta su programa, por lo que si usa una biblioteca de importación, entonces la dependencia no está satisfecha y el programa se bloquea. Este es el caso, por ejemplo, cuando da una API pública en un archivo dll junto con su aplicación y desea acceder a ella desde otras aplicaciones. En este caso, para usar la API, debe cargar el archivo dll desde su ubicación (generalmente está en una clave de registro) y luego debe usar un puntero de función para poder llamar a funciones dentro de la DLL. A veces, las personas que hacen la API son lo suficientemente amables como para darle un archivo .h que contiene funciones auxiliares para automatizar este proceso y darle todos los punteros de funciones que necesita,
fuente
Hay muchas razones para los punteros. Es especialmente importante tener un nombre en C en las DLL si desea mantener la compatibilidad entre idiomas.
fuente