29
Tutoria AEDSI Pablo Silva

Revisão sobre ponteiros em C

Embed Size (px)

DESCRIPTION

Material de apoio da tutoria de AEDS da Universidade Federal de Ouro Preto

Citation preview

Tutoria AEDSIPablo Silva

Revisão

Nesta revisão, iremos relembrar os conceitos importantes de ponteiros e aplicá-los para entender o que é passagem de parâmetros por referência para uma função e como ela se diferencia da passagem por valor.

Ponteiros?

- Ponteiros são tipos de variáveis que recebem endereços.

- Esses endereços são os lugares da memória onde estão armazenados valores de variáveis.

- O nome ponteiro é uma analogia, referindo que esse tipo de variável “aponta”para um endereço de memória.

Estrutura de um ponteiro

int * pont;

Tipo do ponteiro: como dito o ponteiro é uma variável, logo precisa de um tipo. Este tipo, indica o tipo de valor que está armazendo no endereço associado com este ponteiro, ou em outras palavras, qual o tipo de dado que o espaço de memória aceita.

Operador asterisco: o que diferencia ponteiros das outras variáveis é o operador asterisco.

Nome do ponteiro: segue as mesmas regras de nomeação de variáveis.

Usando um ponteiroVamos ver como é a estrutura de uma função

int main() {

int a = 10;int* p = &a;

return 0;}

Tipos compatíveis: note que o tipo de endereço que estamos passando para o ponteiro é int, uma vez que nosso ponteiro é do tipo inteiro. Se no lugar de a fosse atribuído um caracter por exemplo (char b), o programa não compilaria, pois estaríamos lidando com tipos incompatíveis.

Operador endereço: Uma vez que um ponteiro recebe um endereço de uma variável, foi preciso definir um operador que extraísse esse endereço. O operador que faz isso é o operador de endereço. &a significa “O endereço da variável a”.

Desenhos para analogiaA partir de agora, vamos pensar em variáveis como “caixinhas” que recebem valores dentro delas. Exemplo:

int main() {

int a = 10;

return 0;}

A representação destas variáveis ficariam assim:

10

a

&43jjuiop

a é o nome da caixa.

8 dígitos seguidos do símbolo de endereço, é o endereço desta caixa, chamado também de referência.

Valor da caixa

Um fato importante aqui:variáveis comuns só recebemvalores (int, char, float...). Umacaixa que representa umavariável comum nunca receberáum endereço como seu valor(apesar de que toda caixapossui um endereço dememória)

Desenhos para analogiaA partir de agora, vamos pensar em variáveis como “caixinhas” que recebem valores dentro delas. Exemplo:

int main() {

int a = 10;int* p = &a;

return 0;}

A representação da caixa de um ponteiro:

&43jjuiop

p

&98sd66dd

p é o nome da caixa.

Por ser uma variável, um ponteiro também precisa ser armazenada na memória e por isso tem um endereço de caixa.

Um fato importante aqui:variáveis ponteiros recebemsomente endereços e nãovalores! O conteúdo de umponteiro nunca será um int,char, etc. mas sim um endereçode um tipo. Para este caso, oendereço é do tipo inteiro(variável a é do tipo inteiro).

Endereço de a (valor que o ponteiro recebeu)

Desenhos para analogiaUma possível representação da relação entre ponteiro e varíavelpara qual ele aponta seria:

int main() {

int a = 10;int* p = &a;

return 0;}

&43jjuiop

p

&98sd66dd

10

a

&43jjuiop

O ponteiro aponta para a caixa de a, porque o endereço de a foi passado para ele. Uma característica do ponteiro importantíssima é que por apontar para a caixa de a, ele tem acesso ao seu valor (10) e pode alterá-lo a qualquer momento!

Características e funções de um ponteiro

Como dito, um ponteiro tem acesso ao valor da variável para qual ele aponta. Vamos ver como alterar esse valor através do ponteiro.

int main() {

int a = 10; int* p = &a;*p = 5;

return 0;}

10

a

&43jjuiop

1

2

3

Passo 1:

Variável a é criada.

Características e funções de um ponteiro

Como dito, um ponteiro tem acesso ao valor da variável para qual ele aponta. Vamos ver como alterar esse valor através do ponteiro.

int main() {

int a = 10; int* p = &a;*p = 5;

return 0;}

&43jjuiop

p

&98sd66dd

10

a

&43jjuiop

1

2

3

Passo 2:O ponteiro p é criado e o endereço de a é atribuído à ele. Portanto p aponta para a.

Características e funções de um ponteiro

Como dito, um ponteiro tem acesso ao valor da variável para qual ele aponta. Vamos ver como alterar esse valor através do ponteiro.

int main() {

int a = 10; int* p = &a;*p = 5;

return 0;}

&43jjuiop

p

&98sd66dd

10

a

&43jjuiop

1

2

3

Passo 3:O conteúdo de a é acessado e alterado utilizando o operador de acesso do ponteiro.

O conjunto *(nome_do_ponteiro) é o operador de acesso ao conteúdo da variável para qual o ponteiro aponta. Ao utilizarmos este operador, é possível alterar o valor da variável para o ponteiro aponta ou somente acessá-lo.

5

Passagem por referência

O exemplo anterior é somente uma demonstração. Utilizar um ponteiro para alterar o valor da variável neste caso não seria necessário, pois todo nosso programa está na main e conseguimos acessar esta variável a qualquer momento. Este fato muda, quando estamos trabalhando com funções. Lembre-se que as variáveis dentro do escopo de uma função, podem ser acessadas somente lá dentro. Portanto, se quisermos alterar o valor de uma variável que está na main dentro de uma função, a única maneira seria fazer esta função retornar o valor e atribuirmos este valor a variável para qual queremos alterar o valor. Vamos ver um exemplo.

Passagem por valor

Suponha que queremos alterar o valor de t utilizando uma função e sem utilizar ponteiros. Teríamos que fazer algo do tipo:

int main() {

int t = 10; t = valor(5);return 0;

}

int valor(int a) {

a = 5;return a;

}

O que acontece nesse caso é que t tinha 10 quando foi criado. Chamamos então a função valor com o novo valor que queremos para t (5) e atribuímos a chamada desta função para a variável t. Neste caso, quando o programa terminar t terá 5, que é o retorno função valor.

Passagem por valor

Lembrando, isto é só um exemplo, sem muita utilidade somente para entendermos os conceitos de passagem por valor e referência.

int main() {

int t = 10; t = valor(5);return 0;

}

int valor(int a) {

a = 5;return a;

}

Neste caso o que estamos fazendo, é passando um valor para função (para este exemplo o valor foi 5, mas poderia ter sido qualquer valor de inteiro). Tivemos que atribuir o valor de retorno para t, justamente porque a passagem de parâmetros para a função, foi feita através da passagem por valor. Quando utilizamos esta abordagem, tudo que acontece na função, tem alteração somente dentro dela e quando ela acaba, todos os valores gerados lá são destruídos e o que sobra é somente um retorno.

Passagem por valor

Vamos analisar um outro exemplo de passagem por valor. Vamos ver o que acontece quando tentamos alterar o valor de uma varíavel através de uma função que não tem retorno e utilizamos passagem por valor.

int main() {

int t = 10; valor(t);return 0;

}

void valor(int a) {a = 5;

} 1

2

3

4

5

Vamos ver a execução passo a passo.

Passagem por valor

Vamos analisar um outro exemplo de passagem por valor. Vamos ver o que acontece quando tentamos alterar o valor de uma varíavel através de uma função que não tem retorno e utilizamos passagem por valor.

int main() {

int t = 10; valor(t);return 0;

}

void valor(int a) {a = 5;

} 1

2

3

4

5

Passo 1

10

t

&43jjuiop

Passagem por valor

Vamos analisar um outro exemplo de passagem por valor. Vamos ver o que acontece quando tentamos alterar o valor de uma varíavel através de uma função que não tem retorno e utilizamos passagem por valor.

int main() {

int t = 10; valor(t);return 0;

}

void valor(int a) {a = 5;

} 1

2

3

4

5

Passo 2: a execução na main é interrompida e a função é chamada. O valor de t é enviado para a função.

10

t

&43jjuiop

10

Passagem por valor

Vamos analisar um outro exemplo de passagem por valor. Vamos ver o que acontece quando tentamos alterar o valor de uma varíavel através de uma função que não tem retorno e utilizamos passagem por valor.

int main() {

int t = 10; valor(t);return 0;

}

void valor(int a) {a = 5;

} 1

2

3

4

5

Passo 3: a variável que representa o parâmetro da função é criada. Note que o endereço de a é diferente do endereço de t.

10

t

&43jjuiop

10

10

a

&456699oo

Passagem por valor

Vamos analisar um outro exemplo de passagem por valor. Vamos ver o que acontece quando tentamos alterar o valor de uma varíavel através de uma função que não tem retorno e utilizamos passagem por valor.

int main() {

int t = 10; valor(t);return 0;

}

void valor(int a) {a = 5;

} 1

2

3

4

5

Passo 4: a variável a tem seu valor alterado para 5.

10

t

&43jjuiop

10

a

&456699oo

5

Passagem por valor

Vamos analisar um outro exemplo de passagem por valor. Vamos ver o que acontece quando tentamos alterar o valor de uma varíavel através de uma função que não tem retorno e utilizamos passagem por valor.

int main() {

int t = 10; valor(t);return 0;

}

void valor(int a) {a = 5;

} 1

2

3

4

5

Passo 5: a função termina e a execução na main é recomeçada e o programa se encerra. Neste momento nada que foi feito na função existe mais por que ela acabou.

10

t

&43jjuiop

Passagem por valor

O que aconteceu com a variável t que estava na main? NADA. Não tivemos sucesso em alterar seu valor desta vez, porque o que passamos para função foi somente o valor de t, que não tem nenhuma referência com a variável.

int main() {

int t = 10; valor(t);return 0;

}

void valor(int a) {a = 5;

} 1

2

3

4

5 10

t

&43jjuiop

Passagem por referência

Vamos então agora, tentar utilizar a passagem por referência. Referência para nós é o mesmo que endereço. Vamos então agora, passar o endereço de t para função e não mais seu valor. Vamos alterar as funções para que isto ocorra.

int main() {

int t = 10; valor(&t);return 0;

}

void valor(int* a) {

*a = 5;}

1

2

3

4

5

Vamos novamente analisar a execução passo a passo.

Passagem por referência

Vamos então agora, tentar utilizar a passagem por referência. Referência para nós é o mesmo que endereço. Vamos então agora, passar o endereço de t para função e não mais seu valor. Vamos alterar as funções para que isto ocorra.

int main() {

int t = 10; valor(&t);return 0;

}

void valor(int* a) {

*a = 5;}

1

2

3

4

5

Passo 1

10

t

&43jjuiop

Passagem por referência

Vamos então agora, tentar utilizar a passagem por referência. Referência para nós é o mesmo que endereço. Vamos então agora, passar o endereço de t para função e não mais seu valor. Vamos alterar as funções para que isto ocorra.

int main() {

int t = 10; valor(&t);return 0;

}

void valor(int* a) {

*a = 5;}

1

2

3

4

5

Passo 2

10

t

&43jjuiop

Passo 2: a execução na main é interrompida e a função é chamada. Agora, a caixinha que está sendo mandada para a função possui o endereço de t e não mais o seu valor.

&43jjuiop

Passagem por referência

Vamos então agora, tentar utilizar a passagem por referência. Referência para nós é o mesmo que endereço. Vamos então agora, passar o endereço de t para função e não mais seu valor. Vamos alterar as funções para que isto ocorra.

int main() {

int t = 10; valor(&t);return 0;

}

void valor(int* a) {

*a = 5;}

1

2

3

4

5

10

t

&43jjuiop

Passo 3: a variável a é criada. Note que ela é um ponteiro e não poderia ser diferente, pois passamos para a função um endereço e relembrando: variáveis que recebem endereços são ponteiros! Desta forma a aponta para t!

&43jjuiop

a

&4558899

Passagem por referência

Vamos então agora, tentar utilizar a passagem por referência. Referência para nós é o mesmo que endereço. Vamos então agora, passar o endereço de t para função e não mais seu valor. Vamos alterar as funções para que isto ocorra.

int main() {

int t = 10; valor(&t);return 0;

}

void valor(int* a) {

*a = 5;}

1

2

3

4

5

10

t

&43jjuiop

Passo 4: o operador de acesso é utilizado para alterar o conteúdo da variável para qual a aponta. Neste caso então o valor da variável t será alterado de 10 para 5.

&43jjuiop

a

&4558899

5

Passagem por referência

Vamos então agora, tentar utilizar a passagem por referência. Referência para nós é o mesmo que endereço. Vamos então agora, passar o endereço de t para função e não mais seu valor. Vamos alterar as funções para que isto ocorra.

int main() {

int t = 10; valor(&t);return 0;

}

void valor(int* a) {

*a = 5;}

1

2

3

4

5

5

t

&43jjuiop

Passo 5: a função termina e a execução, todas as suas variáveis são destruídas, a execução na main é continuada e o programa então se encerra.

Passagem por referência

O que aconteceu com a variável t? Teve seu valor alterado, mesmo não estando dentro da função. Isso só foi possível, porque passamos uma referência (endereço) de t para a função e quando passamos uma referência, podemos utilizar o operador de acesso para acessar ou alterar o valor da variável onde quer que ela esteja, pois temos um link com a mesma.

int main() {

int t = 10; valor(&t);return 0;

}

void valor(int* a) {

*a = 5;}

5

t

&43jjuiop

Passagem por referência

É claro que este foi um exemplo muito simples e parece inútil esta função para nós por agora. Porém, quando entrarmos em TAD, ficará claro como, utilizar a passagem por referência utilizando ponteiros, diminuirá a complexidade de nossas funções e evitará que a todo momento tenhamos que fazer re-atribuições de variáveis, o que pode tornar o código ineficiente e time-costing.