[Powered by Google Translate] [Ponteiros] [Rob Bowden] [Harvard University] [Esta é CS50] [CS50.TV] Vamos falar sobre ponteiros. Até agora, nós sempre referida apenas as coisas na memória explicitamente pelo nome. Nós dissemos int n = 42, e então, quando nós queremos usar a variável n, nós apenas chamá-lo pelo nome que damos por escrever algo como n * 2. Mas essa variável deve viver em algum lugar na memória. Quando você quiser usar o valor que está armazenado dentro de n, ou atualizar o valor que n está segurando, o programa precisa saber onde na memória de olhar para n. Onde na memória de vidas variável é chamado de seu endereço. É como um endereço de casa. Eu posso encontrar a casa de alguém, enquanto eu sei que o seu endereço de casa, e um programa de computador pode encontrar uma variável, desde que ele conhece o seu endereço de memória. O ponteiros fornecer é uma maneira de lidar diretamente com esses endereços de memória. Um monte de poder em C vem de ser capaz de manipular a memória como este. Mas com grande poder vem grande responsabilidade. Ponteiros podem ser usados ​​perigosamente o suficiente para que uma grande quantidade de linguagens de programação esconder ponteiros inteiramente. Então, por que não dar-nos ponteiros C, então? Como você sabe, argumentos para uma função C sempre são copiados para os parâmetros da função. Então, chamando algo como troca de algumas variáveis ​​x e y não pode trocar os valores de x e y na função de chamada, apesar de que pode ser útil. Como veremos mais tarde swap, reescrevendo a tomar ponteiros para os locais que precisam ser trocados permite que afetam variáveis ​​seu chamador. Vamos examinar um exemplo relativamente simples do que os ponteiros podem fazer. Vamos dizer que temos int n = 4, e int * pointer_to_n = & n. Uau! Um pouco de nova sintaxe para cobrir. Primeiro, vamos interpretar isso & n. Lembre-se que tudo na memória tem algum endereço. O comercial é chamado de "endereço de" operador. Então, & n se refere ao endereço de memória onde n é armazenado. Agora, estamos mantendo esse endereço em uma nova variável, pointer_to_n. Qual é o tipo desta nova variável? O asterisco é uma parte do tipo da variável, e leremos o tipo int *. * Int significa que pointer_to_n é uma variável que armazena o endereço de um inteiro. Sabemos que & n é um int * desde que n é um inteiro, e nós estamos levando o endereço de n. Int * é um exemplo de um tipo de ponteiro. Assim que você começar a ver asteriscos no tipo, você sabe que você está lidando com ponteiros. Assim como podemos declarar uma variável como int x e y char, podemos dizer int * z e char * w. Int * e * char são apenas novos tipos para usarmos. A localização do * pode ir a qualquer lugar antes do nome da variável. Assim, tanto int * pointer_to_n - com o próximo * para int, como temos aqui - e int * pointer_to_n com o * junto pointer_to_n são válidos. Mas aqui, eu vou colocar o * próximo ao int. Não importa o que você preferir, basta ser coerente. Vamos desenhar um diagrama para isso. Primeiro temos a variável n, que vamos desenhar como uma pequena caixa de memória. Para este exemplo, vamos dizer que essa caixa está localizada no endereço 100. Dentro desta caixa, que está armazenando o valor 4. Agora, nós temos uma nova variável, pointer_to_n. Ele tem sua própria caixa na memória, o que vamos dizer é no endereço 200. Dentro desta caixa, estamos mantendo o endereço de n, que nós já dissemos antes era 100. Freqüentemente em diagramas, você verá esta mostrado como uma seta literal deixando a caixa pointer_to_n apontando para a caixa que armazena n. Agora, o que podemos realmente fazer com pointer_to_n? Bem, se dissermos algo como * pointer_to_n = 8, este é um uso diferente para o asterisco que é completamente separado do uso do asterisco em declarar uma variável de um tipo de ponteiro. Aqui, o asterisco é chamado operador dereference. No nosso diagrama, o que * pointer_to_n = 8 significa é, ir para o pointer_to_n caixa contendo, siga a seta, e depois atribuir a caixa na parte final da seta o valor 8. Isto significa que após essa linha, se tentar usar n terá o valor 8. 'Ponteiro' A palavra é usada em muitos contextos diferentes. Aqui, vamos tentar ser coerente. Um tipo de ponteiro é algo como * int. Neste vídeo, um ponteiro só será usada para significar um valor com um tipo de ponteiro, como pointer_to_n que tem * tipo int. Qualquer lugar que costumava dizer n, podemos agora dizer, em vez pointer_to_n *, e tudo vai funcionar tão bem. Vamos examinar um exemplo simples. Vamos dizer que temos int n = 14; int * ponteiro = &N; n + +, e (* ponteiro) + +. A primeira linha cria uma nova caixa na memória rotulados n. Desta vez, não vamos etiquetar a caixa com um endereço explícito, mas ainda tem um. Dentro da caixa, que está armazenando o número 14. A linha seguinte cria um ponteiro segunda caixa rotulada. E dentro desta caixa, que está armazenando um ponteiro para a caixa n. Então, vamos desenhar a seta de ponteiro para n. Agora, n + + incrementa o valor na caixa rotulada n, assim que nós vamos 14-15. Finalmente, (* ponteiro) + + vai para o ponteiro caixa rotulada dereferences o valor na caixa, o que significa que a seguir a seta que aponta, e incrementa o valor armazenado lá, então vamos 15-16. E é isso. N agora armazena o número 16, depois de ter sido incrementado duas vezes - uma vez diretamente usando os nomes de variáveis ​​n, ea outra através de um pointer_to_n. Teste rápido. O que você acha que isso significa que se eu tentar dizer algo como && n? Bem, vamos reescrever isso como & (& n), que realiza a mesma coisa. O (& n) retorna o endereço da variável n na memória. Mas, então, em seguida, comercial externa tenta retornar o endereço do endereço. Isso é como tentar fazer e 2. Não faz sentido para obter o endereço de apenas um número um pouco uma vez que não está sendo armazenado na memória. Usando dois e comercial em uma linha nunca é a idéia certa. Mas agora, o que significa se eu tentar dizer int ** double_pointer = & ponteiro? Agora, eu estou criando uma nova caixa rotulada double_pointer, e dentro da caixa que eu estou armazenando o endereço do ponteiro, que significa que eu desenhe uma seta da caixa double_pointer para a caixa de ponteiro. Observe o tipo de double_pointer, um ** int. N era um número inteiro de ponteiro, armazenado o endereço de n, e por isso tem * int tipo. Agora, double_pointer armazena o endereço do ponteiro, por isso tem o tipo int. ** Então, o que nós pensamos que isso significa - Double_pointer ** = 23? Repare que eu estou agora dereferencing duas vezes. Basta seguir o diagrama de caixa-e-flecha que já configurada. Primeiro, vamos para a caixa double_pointer. O * primeiro significa seguir a seta uma vez. Agora, estamos no ponteiro caixa rotulada. A segunda estrela diz seguir a seta de novo, e agora estamos no caixa chamada n, e vamos definir o valor desta caixa para 23. Observe que o "endereço" dereference e operadores são inversas uma da outra. Isso me permite fazer algo como * & * & n = 42. Enquanto isso funciona, você nunca deve fazer algo assim na prática. O que estamos realmente fazendo aqui? O primeiro e comercial agarra o endereço da variável n. Então, temos um operador dereference, o que significa que estamos indo para o endereço na memória, por isso estamos de volta ao n. Agora, pegamos o endereço do n novamente e imediatamente dereference, por isso estamos de volta ao n e loja 42. Assim, cada par de * & só cancela. Há um ponteiro especial chamado de ponteiro nulo. Este é um ponteiro que nunca devemos dereference. Esse ponteiro é importante porque nos dá uma maneira de distinguir entre um ponteiro que deve e não deve ser dereferenced. Se você tentar excluir a referência um ponteiro nulo, normalmente o seu programa irá falhar com uma falha de segmentação, que você pode ter visto antes. Então, vamos dizer que temos o código int * x = null; * x = 4. Neste exemplo, pode parecer óbvio que estamos fazendo algo ruim, mas lembre-se que nulo poderia realmente ser um valor retornado de uma chamada para uma função como malloc, se malloc não consegue alocar a memória solicitada pelo usuário. Por esta razão, se em vez disso, defina o valor de x de uma chamada a malloc, como em int * x = malloc (sizeof (int)), então devemos sempre verificar explicitamente para ver se nulo foi devolvido. If (x == null) / / uhoh! retorno; mais que possamos continuar e dizer * x = 4. Então, de novo, por que devemos sempre usar ponteiros? Vejamos um exemplo de um programa em que temos de usar ponteiros - uma função de troca simples. Vamos dizer que eu tenho dois inteiros, int x = 4, e int y = 15; e eu quero escrever uma função chamada de swap que eu posso usar assim: swap (x, y). Após essa linha, os valores dentro da variável x deve ser de 15, e o valor de y no interior variável deve ser 4. Os valores dentro de x e y foram trocados. Sem ponteiros, podemos tentar algo como void swap (int a, int b); int tmp = b, b = a, a = tmp. Mas, você percebe o problema com isso? Lembrar que o valor armazenado na variável a é apenas uma cópia do valor de x, e o valor de b é copiado a partir de y. Quaisquer alterações feitas em um b e não será refletida em x e y. Assim, enquanto que os valores de a e b são correctamente trocados, x e y não mudou nada. Agora, vamos mudar a função swap para que seus argumentos são ponteiros para as variáveis ​​que deve trocar, assim: void swap (int * a, int * b); int tmp = * b; * b * = a; * a = tmp. Lembre-se que os argumentos swaps agora são ponteiros, e por isso temos de passar o endereço de X e Y na chamada para trocar, assim: swap (& x, & y). Isso agora corretamente swaps os valores de x e y. Vamos desenhar um diagrama de caixa-e-flecha para ver por que isso funciona. Começamos com as duas caixas na memória, x e y. Dentro da caixa de x é o número 4, e no interior da caixa para y temos 15. Agora, dentro da chamada para a função de troca, temos mais duas caixas para os argumentos a e b; a aponta para o caixa para x, e os pontos de B para a caixa para y. Uma nova caixa é criado para o tmp variável, e dentro dele nós armazenamos o resultado de dereferencing b, que significa "siga a seta da caixa rotulada b '. Então, nós armazenar 15 dentro de tmp. Então, siga a seta em b e armazenar aqui o resultado de uma dereferencing, que é o valor 4. Finalmente, siga a seta em uma loja e que está atualmente dentro de tmp, que é 15. Note-se que as caixas marcadas x e y têm valores correctamente trocados. Uma vez que nós aprendemos mais sobre malloc e gestão dinâmica de memória, vamos ver que não temos escolha a não ser usar ponteiros. Caminhando pelo diagrama de caixa-e-flecha para qualquer programa pode ajudar você a descobrir o que o programa está realmente fazendo. Meu nome é Rob Bowden, e este é o CS50. [CS50.TV] Este é um uso diferente para o asterisco - bleah, eu odeio essa palavra. Qualquer lugar que costumava dizer n, podemos agora dizer pointer_to_n - não você não pode - pointer_to_n *.