resueltos - Beneficios de los punteros?
punteros y arreglos en c (11)
Recientemente desarrollé un interés en la programación en C, así que me compré un libro (K&R) y comencé a estudiar.
Procedentes de un curso universitario en Java (conceptos básicos), los punteros son un capítulo totalmente nuevo y, por lo que leo en línea, es un concepto bastante difícil de entender. Antes de llegar al capítulo de punteros, tenía la impresión de que los punteros son una parte importante de C y proporcionan grandes beneficios.
Al leer el capítulo y tener una idea básica de qué son los punteros y cómo funcionan, los beneficios no son obvios para mí.
Por ejemplo (corríjame si lo entendí totalmente mal) en la introducción de punteros en el libro K&R dice que dado que llamamos por valor, al pasar una variable en una llamada de función pasamos una copia de la variable para el Función para manejar y, por lo tanto, la función no puede hacer nada con la variable original y podemos superar esto con punteros.
En un ejemplo posterior que usa un puntero de caracteres, el libro dice que aumentar el indicador de caracteres es legal ya que la función tiene una copia privada del indicador. ¿No son las "copias privadas" una razón para usar punteros?
Supongo que estoy un poco confundido en todo el uso de punteros. Si me piden, puedo usar punteros en lugar de usar subíndices de matriz, por ejemplo, pero dudo que este sea el uso principal de los punteros.
Linux y la programación de código abierto fue la razón principal por la que entré en C. Obtuve el código fuente de un proyecto C para estudiar (Geany IDE) y puedo ver que los punteros se utilizan en todo el código fuente.
También hice un poco de búsqueda en los foros y encontré un par de publicaciones con preguntas similares. Una respuesta fue (cito):
Si no sabes cuándo debes usar punteros, simplemente no los uses.
Aparecerá cuando necesite usarlos, cada situación es diferente.
¿Es seguro para mí evitar el uso de punteros en el momento y solo usarlos en situaciones específicas (donde la necesidad de punteros será evidente?)
En un ejemplo posterior que usa un puntero de caracteres, el libro dice que aumentar el indicador de caracteres es legal ya que la función tiene una copia privada del indicador.
Yo diría que esto significa que están incrementando el puntero, lo que significa cambiar la dirección (y por lo tanto, apuntar a un valor diferente). Esto podría ser útil si se les pasó el primer elemento de una matriz y desean continuar en la matriz, pero no cambiar los valores.
Desde Java, tendrá una perspectiva ligeramente diferente a la que se presenta en K&R (K&R no asume que el lector conoce ningún otro lenguaje de programación moderno).
Un puntero en C es como una versión ligeramente más capaz de una referencia en Java. Puede ver esta similitud a través de la excepción de Java llamada NullPointerException
. Un aspecto importante de los punteros en C es que puede cambiar lo que apuntan por incremento y decremento.
En C, puede almacenar un montón de cosas en la memoria en una matriz, y sabe que están sentadas una al lado de la otra en la memoria. Si tiene un puntero a uno de ellos, puede hacer que el puntero apunte al "siguiente" incrementándolo. Por ejemplo:
int a[5];
int *p = a; // make p point to a[0]
for (int i = 0; i < 5; i++) {
printf("element %d is %d/n", i, *p);
p++; // make `p` point to the next element
}
El código anterior usa el puntero p
para apuntar a cada elemento sucesivo de la matriz a
en secuencia, y los imprime.
(Nota: el código anterior es solo un ejemplo expositivo, y normalmente no escribiría un bucle simple de esa manera. Sería más fácil acceder a los elementos de la matriz como a[i]
, y no usar un puntero allí).
En este ejemplo:
int f1(int i);
f1(x);
El parámetro i
se pasa por valor, por lo que la función f1
no pudo cambiar el valor de la variable x
de la persona que llama.
Pero en este caso:
int f2(int* i);
int x;
int* px = &x;
f2(px);
Aquí todavía pasamos el parámetro px
por valor, pero al mismo tiempo pasamos x
por refferencia. Por lo tanto, si la persona que llama ( f2
) cambiará su int* i
no tendrá ningún efecto en px
en la persona que llama. Sin embargo, al cambiar *i
la persona que llama cambiará el valor de x
en la persona que llama.
Ignora esa respuesta por favor.
Si no sabes cómo usar punteros, aprende a usarlos. Sencillo.
Los punteros como usted dice le permiten pasar más de una variable a una función a través de un puntero a la misma, como ha observado correctamente. Otro uso de los punteros es en referencia a las matrices de datos, que puede recorrer mediante el uso de aritmética de punteros. Finalmente, los punteros le permiten asignar memoria dinámicamente. Por lo tanto, advertir que no use punteros va a limitar gravemente lo que puede hacer.
Los punteros son la forma de C de hablar sobre las direcciones de memoria. Por eso, son críticos. Como tienes K&R, lee eso.
Para un ejemplo de uso, vea mi respuesta a this . Como digo en esa respuesta, no es necesariamente cómo lo harías, dada la pregunta.
Sin embargo, esa técnica es exactamente cómo funcionan las bibliotecas como MPIR y GMP. libgmp si no lo conoces, utiliza Mathematica, Maple, etc. y es la biblioteca de aritmética de precisión arbitraria. Encontrarás que mpn_t
es un typedef a un puntero; Dependiendo del sistema operativo depende de lo que apunta. También encontrará una gran cantidad de aritmética de punteros en las versiones lentas, C, de este código.
Finalmente, mencioné la gestión de la memoria. Si desea asignar una matriz de algo, necesita malloc
y free
que trate con punteros a espacios de memoria; específicamente malloc
devuelve un puntero a alguna memoria una vez asignada o NULL
en caso de error.
Uno de mis usos favoritos de los punteros aún es hacer que una función de miembro de la clase C ++ actúe como un hilo en Windows usando la API de win32, es decir, hacer que la clase contenga un hilo. Desafortunadamente, CreateThread
en Windows no aceptará las funciones de clase C ++ por razones obvias: CreateThread
es una función C que no comprende las instancias de clase; así que necesitas pasarle una función estática. Aquí está el truco:
DWORD WINAPI CLASSNAME::STATICFUNC(PVOID pvParam)
{
return ((CLASSNAME*)pvParam)->ThreadFunc();
}
(PVOID es un void *
)
Lo que sucede es que devuelve ThreadFunc
que se ejecuta "en lugar de" (en realidad STATICFUNC lo llama) STATICFUNC
y, de hecho, puede acceder a todas las variables de miembros privados de CLASSNAME
. Ingenioso, ¿eh?
Si eso no es suficiente para convencerte de que los punteros son un poco C, no sé qué es. O tal vez no tiene sentido en C sin punteros. O...
Los punteros permiten enviar dinámicamente las condiciones del código o el estado del programa. Una forma sencilla de entender este concepto es pensar en una estructura de árbol donde cada nodo representa una llamada de función, una variable o un puntero a un nodo de subnivel. Una vez que entienda esto, luego utilizará los punteros para apuntar a las ubicaciones de memoria establecidas a las que el programa puede referirse a voluntad para entender el estado inicial y, por lo tanto, la primera falta de referencia y el desplazamiento. Luego, cada nodo contendrá sus propios punteros para comprender mejor el estado mediante el cual puede tener lugar una desreferencia adicional, se puede llamar a una función o se puede tomar un valor.
Por supuesto, esta es solo una forma de visualizar cómo se pueden usar los punteros, ya que un puntero no es más que una dirección de una ubicación de memoria. También puede usar punteros para el paso de mensajes, llamadas de funciones virtuales, recolección de basura, etc. De hecho, los he usado para recrear llamadas de funciones virtuales de estilo c ++. El resultado es que la función virtual c y las funciones virtuales c ++ se ejecutan exactamente a la misma velocidad. Sin embargo, la implementación de c fue mucho más pequeña en tamaño (66% menos en KB) y un poco más portátil. Pero la replicación de características de otros idiomas en C no siempre será ventajosa, obviamente b / c otros idiomas podrían estar mejor equipados para recopilar información que el compilador puede usar para optimizar esa estructura de datos.
En resumen hay mucho que se puede hacer con los punteros. El lenguaje C y los punteros se devalúan en la actualidad b / c la mayoría de los lenguajes de nivel superior están equipados con las implementaciones / estructuras de datos más utilizadas que habría tenido que construir por su cuenta con el uso de punteros. Pero siempre hay ocasiones en que un programador puede necesitar implementar una rutina compleja y, en este caso, saber cómo usar los punteros es algo muy bueno.
Permítanme explicar esto más en términos de referencias de Java (como lo señala la respuesta de @Greg)
En Java, hay tipos de referencia (es decir, referencia a una clase) y tipos de valor (es decir, int
). Al igual que C, Java solo se pasa por valor. Si pasa un tipo primitivo a una función, en realidad pasa el valor del valor (después de todo, es un "tipo de valor") y, por lo tanto, cualquier modificación de ese valor dentro de esa función no se refleja en el código que llama. Si pasa un tipo de referencia a una función, puede modificar el valor de ese objeto, porque cuando pasa el tipo de referencia, pasa la referencia a ese tipo de referencia por valor.
Recuerda que C (y el libro de K&R) son muy antiguos, probablemente más antiguos que cualquier cosa que hayas aprendido antes (definitivamente son más antiguos que Java). Los punteros no son una característica adicional de C, son una parte muy básica de cómo funcionan las computadoras.
La teoría de los punteros no es particularmente difícil de dominar, solo que son muy poderosos, por lo que un error probablemente colapsará su aplicación, y los compiladores tienen dificultades para detectar los errores relacionados con los punteros. Una de las grandes novedades sobre Java fue tener "casi" tanto poder como C sin punteros.
Por lo tanto, en mi opinión, tratar de escribir C evitando punteros es como tratar de andar en bicicleta sin un pedal. Sí, es factible, pero trabajarás el doble de duro.
Si está tratando con memoria asignada dinámicamente, tiene que usar punteros para acceder al espacio asignado. Muchos programas se ocupan de la memoria asignada, por lo que muchos programas tienen que usar punteros.
Teniendo en cuenta que viene de un fondo de Java, esta es la forma más sencilla de entender qué uso tienen los punteros.
Digamos que tienes una clase como esta en Java:
public class MyClass {
private int myValue;
public int getMyValue() { return myValue; }
public void setMyValue(int value) { myValue = value; }
}
Aquí está su función principal, que crea uno de sus objetos y llama a una función en él.
public static void Main(String[] args) {
MyClass myInstance = new MyClass();
myInstance.setMyValue(1);
System.out.printLn(myInstance.getMyValue()); // prints 1
DoSomething(myInstance);
System.out.printLn(myInstance.getMyValue()); // prints 2
}
public static void DoSomething(MyClass instance) {
instance.setMyValue(2);
}
La variable myInstance
que declaró en Main
es una referencia. Básicamente, es un identificador que JVM usa para mantener las pestañas en su instancia de objeto. Ahora, hagamos lo mismo en C.
typedef struct _MyClass
{
int myValue;
} MyClass;
void DoSomething(MyClass *);
int main()
{
MyClass myInstance;
myInstance.myValue = 1;
printf("i", myInstance.myValue); // prints 1
MyClass *myPointer = &myInstance; // creates a pointer to the address of myInstance
DoSomething(myPointer);
printf("i", myInstance.myValue); // prints 2
return 0;
}
void DoSomething(MyClass *instance)
{
instance->myValue = 2;
}
Por supuesto, los punteros son mucho más flexibles en C, pero esta es la esencia de cómo funcionan de una manera Java.
Tu regla resaltada es muy sabia. Te mantendrá fuera de problemas, pero tarde o temprano tendrás que aprender los punteros.
Entonces, ¿por qué queremos usar punteros?
Digamos que abrí un archivo de texto y lo leí en una cadena gigante. No puedo pasarte la cadena gigante por valor porque es demasiado grande para caber en la pila (digamos 10mb). Así que te digo dónde está la cadena y digo "ve a ver mi cadena".
Una matriz es un puntero (bueno, casi).
int [] e int * son sutilmente diferentes pero intercambiables en su mayor parte.
int[] i = new int[5]; // garbage data
int* j = new int[5] // more garbage data but does the same thing
std::cout << i[3] == i + 3 * sizeof(int); // different syntax for the same thing
Un uso más avanzado de los punteros que es extremadamente útil es el uso de punteros de función. En C y C ++, las funciones no son tipos de datos de primera clase, pero sí lo son los punteros. Por lo tanto, puede pasar un puntero a una función a la que desea llamar y ellos pueden hacerlo.
Esperemos que eso ayude, pero lo más probable es que sea confuso.
Una ventaja de los punteros es que cuando los usa en argumentos de función, no necesita copiar grandes trozos de memoria a su alrededor, y también puede cambiar el estado al eliminar la referencia al puntero.
Por ejemplo, puede tener una struct MyStruct
enorme struct MyStruct
y tiene una función a()
.
void a (struct MyStruct* b) {
// You didn''t copy the whole `b` around, just passed a pointer.
}