[Powered by Google Translate] [Punteros] [Rob Bowden] [Harvard University] [Esta es CS50] [CS50.TV] Vamos a hablar acerca de los punteros. Hasta ahora, siempre hemos acabamos de referirnos a las cosas en la memoria explícitamente por su nombre. Hemos dicho int n = 42, y luego cuando queremos usar la variable n, simplemente lo llaman por el nombre que le demos, escribiendo algo como n * 2. Pero esa variable tiene que vivir en algún lugar de la memoria. Si desea utilizar el valor que se encuentra actualmente almacenado en el interior de n, o actualizar el valor de n que se mantiene, el programa necesita saber dónde buscar en la memoria para n. Cuando en la memoria viva una variable se llama su dirección. Es como una dirección de la casa. Puedo encontrar la casa de alguien, siempre y cuando sé que su domicilio, y un programa de ordenador puede encontrar una variable siempre y cuando se conoce su dirección de memoria. ¿Qué proporcionar punteros es una manera de tratar directamente con estas direcciones de memoria. Gran parte de la energía en C proviene de la capacidad para manipular la memoria de esta manera. Pero con gran poder conlleva una gran responsabilidad. Los punteros se puede utilizar peligrosamente suficiente que una gran cantidad de lenguajes de programación ocultar los punteros del todo. Así que, ¿por qué C darnos consejos, entonces? Como ustedes saben, argumentos a una función C siempre se copian en los parámetros de la función. Por lo tanto, llamar algo así como intercambio en algunas variables x e y no se pueden intercambiar los valores de x e y en la función que llama, a pesar de que podría ser útil. Como veremos más adelante, de intercambio de reescritura para tener punteros a los lugares que necesitan ser cambiados le permite modificar las variables de la persona que llama. Vamos a ver un ejemplo relativamente sencillo de lo que puede hacer sugerencias. Digamos que tenemos int n = 4, y = int * pointer_to_n & n. Whoa! Un poco de nueva sintaxis para cubrir. En primer lugar, vamos a interpretar esto y n. Recuerde que todo en la memoria tiene alguna dirección. El signo se denomina la "dirección de" operador. Así, y n se refiere a la dirección en memoria donde se almacena n. Ahora, estamos almacenando esta dirección en una nueva variable, pointer_to_n. ¿Cuál es el tipo de esta nueva variable? El asterisco es una parte del tipo de la variable, y vamos a leer el tipo como int *. Int * significa que pointer_to_n es una variable que almacena la dirección de un número entero. Sabemos que & n es un int * ya que n es un entero, y estamos tomando la dirección de n. Int * es un ejemplo de un tipo de puntero. Tan pronto como se empiezan a ver los asteriscos en el tipo, usted sabe que usted está tratando con punteros. Al igual que podemos declarar una variable como int x e y char, podemos decir int * z y char * w. * Int y char * son sólo nuevos tipos para que pudiéramos utilizar. La ubicación del * puede ir a ninguna parte antes de que el nombre de la variable. Así, tanto int * pointer_to_n - con la siguiente * a int, como lo hemos hecho aquí - e int pointer_to_n * con el * al lado pointer_to_n son válidos. Pero aquí, voy a poner el siguiente * a int. No importa lo que usted prefiere, acaba de ser consistente. Vamos a dibujar un diagrama para esto. Primero tenemos la variable n, que vamos a dibujar como una pequeña caja de la memoria. Para este ejemplo, vamos a decir que este cuadro se encuentra en la dirección 100. Dentro de este cuadro, estamos almacenando el valor 4. Ahora, tenemos una nueva variable, pointer_to_n. Tiene su propia caja en la memoria, diremos que está en la dirección 200. Dentro de este cuadro, estamos almacenando la dirección de n, que antes decía era 100. Frecuentes en los diagramas, podrás ver esta muestra como una flecha literal llevándose a la pointer_to_n señalando la caja que almacena n. Ahora, ¿qué podemos hacer realmente con pointer_to_n? Pues bien, si decimos algo así como * pointer_to_n = 8, este es un uso diferente para el asterisco que es completamente independiente de la utilización del asterisco en declarar una variable de un tipo de puntero. En este caso, el asterisco se llama el operador de indirección. En nuestro diagrama, ¿cuál pointer_to_n * = 8 significa es, ir a la caja que contiene pointer_to_n, siga la flecha, y luego asignar a la caja en el extremo de la flecha el valor 8. Esto significa que después de esta línea, si tratamos de usar n tendrá el valor 8. 'Puntero' La palabra se usa en muchos contextos diferentes. Aquí, vamos a tratar de ser consistente. Un tipo de puntero es algo así como * int. En este vídeo, un puntero solo se utilizan para designar un valor con un tipo de puntero, como pointer_to_n que tiene * tipo int. En cualquier lugar que solía decir que sólo n, ahora podemos decir que en lugar pointer_to_n *, y todo funcionará igual de bien. Vamos a ver otro ejemplo simple. Digamos que tenemos int n = 14; int * puntero = n + N + +, y (* puntero) + +. La primera línea crea una nueva caja en la memoria de la etiqueta n. Esta vez, no vamos a etiquetar el cuadro con una dirección explícita, pero todavía tiene uno. En el interior de la caja, que está almacenando el número 14. La siguiente línea crea un segundo puntero cuadro con la etiqueta. Y dentro de este cuadro, estamos almacenar un puntero a la casilla n. Por lo tanto, vamos a dibujar la flecha de puntero a n. Ahora, n + + incrementa el valor en la casilla n, así que vamos a partir de 14 a 15. Por último, (* puntero) + + va al puntero cuadro con la etiqueta, desreferencias el valor en la caja, lo que significa seguir la flecha a donde apunta, e incrementa el valor almacenado ahí, así que vamos a partir de 15 a 16. Y eso es todo. N ahora almacena el número 16 después de haber sido incrementado dos veces - una vez directamente con los n nombre de la variable, y el otro a través de un pointer_to_n. Prueba rápida. ¿Qué crees que quiere decir que si yo trato de decir algo como && n? Bueno, vamos a reescribir esto como & (& n) que lleva a cabo la misma cosa. El (+ n) devuelve la dirección de la variable n en la memoria. Pero entonces signo externo intenta devolver la dirección de la dirección. Eso es como tratar de hacer y 2. No tiene sentido para obtener la dirección de tan sólo un número ya que no está siendo almacenado en la memoria. El uso de dos símbolos de unión en una fila no es nunca una buena idea. Pero ahora, ¿qué significa si trato de decir int ** double_pointer = & puntero? Ahora, estoy creando una nueva caja etiquetada double_pointer, y en el interior de esa caja estoy almacenando la dirección de puntero, lo que significa que dibujar una flecha en el cuadro de double_pointer a la caja de puntero. Tenga en cuenta el tipo de double_pointer, un int **. N es un número entero, puntero almacena la dirección de n, y por lo que tiene * tipo int. Ahora, double_pointer almacena la dirección del puntero, por lo que es de tipo int. ** Así que, ¿qué es lo que pensamos que esto significa - Double_pointer ** = 23? Tenga en cuenta que ahora estoy dereferencing dos veces. Sólo tienes que seguir el diagrama de caja y flecha que ya hemos establecido. En primer lugar, vamos a la casilla double_pointer. * El primer medio de seguir la flecha una vez. Ahora, estamos en el puntero cuadro con la etiqueta. La segunda estrella dice seguir la flecha de nuevo, y ahora estamos en la casilla n, y establecer el valor de este cuadro para 23. Tenga en cuenta que la "dirección de" eliminar la referencia y los operadores son inversas entre sí. Esto me permite hacer algo como * & * & n = 42. Aunque esto funciona, usted nunca debe hacer algo como esto en la práctica. ¿Qué estamos haciendo aquí? El signo primero coge la dirección de la variable n. Entonces, tenemos un operador para deshacer referencias, lo que significa que vamos a esa dirección en la memoria, así que estamos de vuelta en el n. Ahora, agarra la dirección de n de nuevo y eliminar la referencia inmediata, así que estamos de vuelta en n y tienda 42. Así, cada par de * y sólo se anula. Hay un puntero especial llamado el puntero nulo. Esto es un puntero que nunca debería eliminar la referencia. Este indicador es importante porque nos da una manera de distinguir entre un puntero que debe y no debe dejar de hacer referencia. Si intenta eliminar la referencia a un puntero nulo, normalmente el programa se bloqueará con un error de segmentación, que es posible que haya visto antes. Así que, digamos que tenemos el código int * x = null; * x = 4. En este ejemplo, puede parecer obvio que estamos haciendo algo mal, pero recuerde que null realmente podría ser un valor devuelto por una llamada a una función como malloc, si malloc no puede asignar la memoria solicitada por el usuario. Por esta razón, si por el contrario nos fijamos el valor de x de una llamada a malloc, como en int * x = malloc (sizeof (int)), entonces siempre hay que comprobar explícitamente para ver si nula fue devuelto. Si (x == null) / / uhoh! retorno, más que podamos seguir adelante y decir * x = 4. Así que, de nuevo, ¿por qué nos utilizan punteros? Veamos un ejemplo de un programa en el que tenemos que utilizar punteros - una función de intercambio simple. Digamos que tengo dos números enteros, int x = 4, y int y = 15; y quiero escribir una función llamada swap que puedo utilizar de esta manera: swap (x, y). Después de esta línea, los valores dentro de la variable x debe ser de 15, y el valor de la variable y en el interior debe ser de 4. Los valores dentro de x e y se han intercambiado. Sin punteros, podríamos intentar algo como intercambio void (int a, int b); int tmp = b, b = a; a = tmp. Pero, ¿te das cuenta el problema con esto? Recuerde que el valor almacenado en la variable a es simplemente una copia del valor de x, y el valor de b se copia de y. Cualquier cambio realizado en a y b no se reflejará en x e y. Así, mientras que los valores de a y b se intercambian correctamente, x e y no han cambiado en absoluto. Ahora, vamos a cambiar la función de intercambio de modo que sus argumentos son punteros a las variables que deben intercambiar, de este modo: intercambio void (int * a, int * b); int tmp = b *, * = b * a, * a = tmp. Recuerde que los argumentos de swaps son ahora punteros, y lo que tenemos que pasar la dirección de x e y en la llamada a cambiar, de este modo: intercambiar (& x, & y). Esto ahora correctamente intercambia los valores de x e y. Vamos a dibujar un diagrama de caja y flecha para ver qué funciona esto. Comenzamos con nuestros dos cajas en la memoria, x e y. En el interior de la caja para que x es el número 4, y en el interior de la caja para y tenemos 15. Ahora, dentro de la llamada a la función de intercambio, tenemos dos cajas más para los argumentos a y b; puntos a la caja para x, y los puntos b al cuadro para y. Una nueva caja se crea para el tmp variable, y dentro de ella se guarda el resultado de la eliminación de referencias b, que significa "seguir la flecha de la casilla b. Por lo tanto, almacenamos 15 en el interior de tmp. Luego, sigue la flecha en B y almacenar aquí el resultado de una eliminación de referencias, que es el valor 4. Por último, seguimos la flecha en una tienda y lo que es en la actualidad dentro de tmp, que es de 15. Tenga en cuenta que las cajas etiquetadas x e y han cambiado los valores correctamente. Una vez que aprendemos más sobre malloc y la gestión de memoria dinámica, vamos a ver que no tenemos más remedio que utilizar punteros. Caminando por el diagrama de caja y flecha para cualquier programa puede ayudarle a averiguar lo que el programa está haciendo en realidad. Mi nombre es Rob Bowden, y esto es CS50. [CS50.TV] Este es un uso diferente para el asterisco - bleah, odio esa palabra. En cualquier lugar que solía decir que sólo n, ahora podemos decir pointer_to_n - no te puedo - pointer_to_n *.