Introdução a Ponteiros ou Apontadores Créditos: Profa. Karla de Souza - UFTPR Modificada por Prof. Marcus Salerno http://www.dainf.ct.utfpr.edu.br/~karla/ Disciplina IF65A Por que Estudar Ponteiros? • É praticamente impossível desenvolver um programa com algum grau de complexidade ou tamanho significativo em C e C++ sem o uso de apontadores. • C é altamente dependente de ponteiros • Portanto, para ser um bom programador C, é preciso conhecê-los • Mas... O uso descuidado de ponteiros pode levar a sérios bugs e a dores de cabeça terríveis :-). E o que são Ponteiros ou Apontadores? • Os ints guardam inteiros. • Os floats guardam números de ponto flutuante. • Os chars guardam caracteres. • Ponteiros guardam endereços de memória Ponteiros em Linguagem C • Um ponteiro também tem tipo. • Em C quando declaramos ponteiros nós informamos ao compilador para que tipo de variável vamos apontá-lo. • Um ponteiro int aponta para um inteiro, isto é, guarda o endereço de um inteiro. Como Usar Ponteiros • Para declarar: tipo_do_ponteiro *nome_da_variável; • É o asterisco (*) que faz o compilador saber que aquela variável não vai guardar um valor mas sim um endereço para aquele tipo especificado. • Exemplos: – int *pt; – char *temp,*pt2; Como Usar Ponteiros • char *temp,*pt2; • Estas variáveis foram declaradas, mas não inicializadas. • Ou seja, estes ponteiros apontam para um endereço indefinido. • Este endereço pode ser, por exemplo, uma porção de memória reservada ao sistema operacional • Usar um ponteiro sem inicializá-lo pode causar travamento do computador, ou algo pior! Como Usar Ponteiros • Para inicializar um ponteiro podemos igualá-lo a um valor de memória. • Mas, como saber a posição na memória de uma variável do nosso programa? • Seria muito difícil saber o endereço de cada variável que usamos, mesmo porque estes endereços são determinados pelo compilador na hora da compilação e realocados na execução. • Podemos então deixar que o compilador faça este trabalho por nós. Para saber o endereço de uma variável basta usar o operador &. • Exemplo: – float num = 2.5; – float *pt; – pt = # Como Usar Ponteiros float num = 2.5; Variável Valor Endereço num 2.5 ? Como Usar Ponteiros float num = 2.5; Variável Valor Endereço num 2.5 ? Compilador aloca um endereço qualquer disponível Como Usar Ponteiros float num = 2.5; Variável Valor Endereço num 2.5 1000 Compilador aloca um endereço qualquer disponível num Memória 2.5 1000 1001 1002 1003 Como Usar Ponteiros float num = 2.5; float *pt; Variável Valor Endereço num 2.5 1000 pt ? ? num Memória 2.5 1000 1001 1002 1003 Como Usar Ponteiros float num = 2.5; float *pt; Memória Variável Valor Endereço num 2.5 1000 pt ? 1001 num pt 2.5 ? 1000 1001 1002 Também feito pelo compilador, não necessariamente na posição seguinte. 1003 Como Usar Ponteiros float num = 2.5; float *pt; pt=# Qual valor terá a variável pt? Memória Variável Valor Endereço num 2.5 1000 pt ? 1001 num pt 2.5 ? 1000 1001 1002 1003 Como Usar Ponteiros float num = 2.5; float *pt; pt=# O endereço da variável num! Memória Variável Valor Endereço num 2.5 1000 pt 1000 1001 num pt 2.5 1000 1000 1001 1002 1003 Como Usar Ponteiros • Criamos um float com o valor 2.5 e um apontador para um float identificado como pt. • A expressão &num nos dá o endereço de num, o qual armazenamos em pt. • Agora que pt tem o endereço de num, podemos também alterar o valor de num usando pt. Como Usar Ponteiros • Para tanto vamos usar o operador "inverso" do operador &: – É o próprio operador *. • No exemplo acima, uma vez que fizemos pt=&num a expressão *pt é equivalente ao próprio num. • Isto significa que, se quisermos mudar o valor de num para 3.5, basta fazer *pt=3.5. Exemplo 1 // Programa que exemplifica o uso de ponteiros. int main () { float num,valor; float *pt; num=2.5; pt=# valor=*pt; // Pega o endereço de num. // Atribuído a valor de maneira indireta printf ("\n%f\n",valor); printf ("Endereco para onde o ponteiro aponta: %p \n", pt); printf ("Valor da variavel apontada: %f \n",*pt); system("PAUSE"); return(0); } Especificador de formato para ponteiro Exemplo 2 // Outro exemplo para ponteiros. int main () { float num,*pt; num= 2.5; pt= # // Pega o endereco de num printf ("\nValor inicial: %f \n",num); *pt=3.5; // Muda o valor de num de uma maneira indireta printf ("\nValor final: %f \n",num); system("PAUSE"); return(0); } • Exemplos: • Variáveis e Apontadores.cpp • ap_x.c • Operações com Apontador.cpp • http://www.youtube.com/watch?v=6pmWojisM_E&feature=pl ayer_embedded Exercício • Procure determinar quais valores são impressos ao final destes programas. Confira sua resposta testando o programa. #include <stdio.h> #include <stdlib.h> int main() { int a,b,*c; a = 4; b = 3; c = &a; *c = *c + 1; c = &b; b = b+4; printf("%d %d %d",a,b,*c); system(“PAUSE”); return 0; } #include <stdio.h> #include <stdlib.h> int main() { int a,b,*c,*d,*f; a = 4; b = 3; c = &a; d = &b; *c = *c / 2; f = c; c = d; d = f; printf("%d %d",*c,*d); system(“PAUSE”); return 0; } Ponteiros e Vetores • Vetores (inclusive strings) e matrizes já são, por definição, ponteiros. • Por exemplo, uma string pode ser declarada também da seguinte forma: char *s = "Programacao"; • O compilador irá criar a variável s, com tipo apontador de caractere e inicializá-la com o endereço do byte que armazena o primeiro “P". • Repare que a declaração "char s[]" é equivalente a "char *s". Onde Usar Ponteiros? • Funções – Passagem de parâmetros por referência • Alocação Dinâmica – Não sei o tamanho que o vetor precisa ter…. – Não sei o tamanho que cada string precisa ter… – Não sei o tamanho que a matriz precisa ter… Onde Usar Ponteiros? • Funções – Passagem de parâmetros por referência • Alocação Dinâmica – Não sei o tamanho que o vetor precisa ter…. – Não sei o tamanho que cada string precisa ter… – Não sei o tamanho que a matriz precisa ter… Passagem de Parâmetros Por Valor e por Referência Passagem por Valor • Quando chamamos uma função os valores dos parâmetros passados são copiados para os parâmetros ‘formais’ da função. • Isto quer dizer que não são alterados os valores que os parâmetros têm fora da função. • Este tipo de chamada de função é denominado chamada por valor. • Isto ocorre porque são passados para a função apenas os valores dos parâmetros e não os próprios parâmetros. Passagem por Valor int main() { int x, y; // Declaração de funções. void troca(int, int); // Chama função para trocar valores. x=5; y=6; troca(x, y); printf("O valor de x agora eh %d e o de y eh %d \n", x, y); system("PAUSE"); } void troca(int a, int b) { int temp; temp = a; a = b; b = temp; } Passagem por Valor • Funcionou? Por que? • As variáveis a e b da função troca sofrem alterações dentro da função, mas as variáveis a e b da main não. • É a passagem por valor. • Somente os valores são copiados, não havendo alteração nas variáveis. Passagem por Referência • Outro tipo de passagem de parâmetros para uma função ocorre quando as alterações nos parâmetros dentro da função (chamados parâmetros formais) alteram os valores dos parâmetros que foram passados para a função. • Este tipo de passagem de parâmetro tem o nome de “passagem por referência". • Este nome vem do fato de que, neste tipo de chamada, não se passa para a função os valores das variáveis, mas sim suas referências (ou endereços): – a função usa as referências para alterar os valores das variáveis fora da função. Passagem por Referência • Quando queremos alterar as variáveis que são passadas para uma função, nós podemos declarar seus parâmetros como sendo apontadores. • Os apontadores são a "referência" que precisamos para poder alterar a variável fora da função. • O único inconveniente é que, quando usarmos a função, teremos de lembrar de colocar um & na frente das variáveis que estivermos passando para a função. Passagem por Referência // Declaração de funções. void troca(int *, int *); int main() { int x, y; void troca(int *a, int *b) { int temp; temp = *a; *a = *b; *b = temp; } // Chama função para trocar valores x=5; Declara os parâmetros da função como ponteiros y=6; troca(&x, &y); printf("O valor de x agora eh %d e o de y eh %d \n", x, y); Passa os endereços das variáveis system("PAUSE"); } Ver troca de estruturas em: troca_struct.c Passagem por Referência • Funcionou agora? Por que? • Lembra da função scanf()? Das variáveis precedidas por &? • Por que a função scanf() precisa usar a chamada por referência? • Para alterar o valor das variáveis que passamos para ela! Exercício 1 • Escreva uma função que receba duas variáveis inteiras e "zere" o valor das variáveis. – Na função principal, declare e inicie as variáveis inteiras com qualquer valor, chame a função para zerá-los e imprima os valores finais. – Use o que você aprendeu para fazer a implementação. – Teste a passagem por valor e por referência no programa. Exercício 2 • Seja a função troca cujo protótipo está definido abaixo, uma função que permute o valor de uma variável do tipo double por outra, ambas passadas por referência. – Defina uma função main que permute os valores das variáveis a, b e c, declaradas abaixo, de forma que no final a<=b<=c. A função main deve chamar a função troca. • void troca(double *x,double *y) Resumo • O que é um ponteiro? – É uma variável que armazena o endereço na memória do computador onde está outra variável • Operadores relacionados a ponteiros: * (asterisco): informa que uma variável irá armazenar o endereço de outra variável (que a variável é um ponteiro), ou informa ao computador que você deseja o valor que está no endereço armazenado; & (e comercial): retorna o endereço de uma variável; Resumo • operador * – declara-se um ponteiro com * • int *x; – acessa-se (alterar, modificar, ler) o valor da variável apontada também com * • *x = 10; // atribui o valor 10 ao local apontado pelo ponteiro ‘x’ • printf(“%d”, *x); // imprime valor armazenado no local apontado por ‘x’ – observação: strings, vetores e matrizes funcionam de forma diferente: um vetor, string ou matriz é um ponteiro por definição • operador & – acessa (alterar, modificar, ler) o endereço de uma variável (que é um ponteiro) Resumo • Passagem de Parâmetro por Valor – É a passagem de parâmetro ‘default’ da linguagem C. Se nada é especificado, somente os valores dos parâmetros passados são copiados para os parâmetros formais da função • Passagem de Parâmetro por Referência – É a passagem de parâmetro usando ponteiros. Desta forma os endereços das variáveis é que são passados à função e todas as alterações feitas nas variáveis passadas por parâmetro permanecem após o fim da função. • Declaração: void troca (int *a,int *b); • Chamada da função: troca (&num1,&num2); • Definição da função: void troca (int *a, int *b) { \\ Instruções.}