Material de apoio das aulas de tutoria de Algoritmos e Estrutura de dados da Universidade Federal de Ouro Preto, Campus João Monlevade. O conteúdo abordado é uma revisão sobre ponteiros na linguagem de programação C.
2. 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.
3. 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.
4. 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.
5. Usando um ponteiro
Vamos 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”.
6. Desenhos para analogia
A 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ó recebem
valores (int, char, float...). Uma
caixa que representa uma
variável comum nunca receberá
um endereço como seu valor
(apesar de que toda caixa
possui um endereço de
memória)
7. Desenhos para analogia
A 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 recebem
somente endereços e não
valores! O conteúdo de um
ponteiro nunca será um int,
char, etc. mas sim um endereço
de um tipo. Para este caso, o
endereço é do tipo inteiro
(variável a é do tipo inteiro).
Endereço de a
(valor que o
ponteiro recebeu)
8. Desenhos para analogia
Uma possível representação da relação entre ponteiro e varíavel
para 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!
9. 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.
10. 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.
11. 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
12. 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.
13. 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.
14. 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.
15. 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.
16. 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
17. 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
18. 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
19. 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
20. 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
21. 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
22. 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.
23. 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
24. 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
25. 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
26. 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
27. 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.
28. 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
29. 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.