10 - Feg

Propaganda
Programação de Computadores – 2o Sem- 2013 – Prof. André Amarante Luiz – LAB10
Esse material foi preparado pelos professores Aníbal Tavares de Azevedo e Cassilda
Maria Ribeiro para o curso de Programação de Computadores I – FEG/UNESP
Pense duas vezes e faça uma vez.
Provérbio Chinês.
A sintaxe para se declarar uma variável do
tipo ponteiro é dada por:
tipo *ptr;
PONTEIROS
Um ponteiro nada mais é que uma variável
capaz de armazenar um número hexadecimal que corresponde a um endereço de
memória de outra variável. É importante
lembrar que a declaração de uma variável do
tipo caractere, por exemplo, implica em
reservar um espaço de memória para
armazenar a informação correspondente:
char ch;
ch
||||
100 101 102 ... 5000 5001 ...
Quando um valor é atribuído à variável
caractere, o que ocorre, na verdade, é que o
espaço de memória, cujo endereço1 é 5000,
passa a ser disponível para ocupar um
caractere:
char ch = ‘a’;
ch
‘a’
100 101 102 ... 5000 5001 ...
1
O endereço de memória de uma variável é um número
hexadecimal como, por exemplo, 0022FF77. Mas,
para manter a clareza da discussão e sem perda de
generalidade serão utilizados números na base 10.
onde: tipo – é o tipo da variável cujo
endereço será armazenado pelo ponteiro, * indica que a variável é do tipo ponteiro e ptr
– é o nome da variável. Observe que as três
declarações a seguir são equivalentes:
int * p, i;
int* p, i;
int *p, i;
Porém, a mais utilizada é a última, pois
indica claramente que apenas a variável p é
do tipo ponteiro para variável inteira e i é
variável do tipo inteiro. Como dito
anteriormente, uma variável tipo ponteiro é
responsável por armazenar um endereço de
memória. Para se obter um endereço de
memória de uma variável basta utilizar o
operador &.
PT1: Implemente o programa a seguir e
verifique o valor de i, o endereço da variável
i, o valor armazenado em p e o endereço de
p.
main()
{
int *p, i =4;
p = &i;
printf(“i = %d \n”,i);
printf(“&i = %x \n”,&i);
printf(“p = %p \n”,p);
printf(“&p = %X \n”,&p);
}
Para se inicializar um ponteiro com valor
nulo, basta fazer: int *p = NULL;. Observe
que no programa anterior foi utilizada a tag
%p para se imprimir o conteúdo de uma
variável do tipo ponteiro e %x e %X para
endereço de memória. Na realidade como a
variável ponteiro armazena endereço de
memória, tanto faz utilizar %p ou %x ou
ainda %X. A única diferença fica por conta
da formatação da impressão.
Um outro operador útil é o operador
dereferência *. Com ele é possível saber
qual o valor contido no endereço
armazenado (apontado) por um ponteiro
como *p, por exemplo.
PT2: Verifique os resultados do programa:
main()
{
int i, *p, *q;
i = 9;
p = &i; // p aponta para i.
q = p; // q também aponta para i.
printf(“i = %d \n”,i); // valor de i
printf(“&i = %X\n”, &i); // end de i
printf(“p = %p \n”, p); // end apontado
printf(“q = %p \n”, q); // end apontado
// conteudo do end apontado por p.
printf(“p = %d\n”, *p);
}
Para auxiliar na compreensão do que faz o
programa anterior, sem precisar rodar o
mesmo, é útil o seguinte esquema de
representação de armazenamento de
informações na memória:
Variável
Conteúdo
i
9
p
5000
q
5000 1
Programação de Computadores – 2o Sem- 2013 – Prof. André Amarante Luiz – LAB10
Esse material foi preparado pelos professores Aníbal Tavares de Azevedo e Cassilda
Maria Ribeiro para o curso de Programação de Computadores I – FEG/UNESP
Endereço 5000 ... 7001 ... 7502
É útil, também, verificar quais valores serão
obtidos em cada uma das expressões dadas a
seguir
para
a
representação
de
armazenamento anterior:
Expressão Valor
i
9
&i
5000
p
5000
*p
9
&p
7001
q
5000
*q
9
&q
7502
PONTEIROS DE PONTEIROS
Uma vez que os ponteiros ocupam espaço
em memória, é possível obter a sua posição
através do operador endereço &. É
importante lembrar que a função de um
ponteiro é armazenar o endereço de uma
variável de um dado tipo (int, etc). Ou seja:
int x;
int *ptr_x= &x;
Para criar uma variável que armazene o
endereço de uma variável do tipo ponteiro
para inteiro (int) é necessário fazer:
int x;
int *ptr_x = &x;
int **ptr_ptr_x = &ptr_x;
A utilização de asteriscos e portanto, a
criação de ponteiros para ponteiros
(indireção múltipla) não tem limites.
PT3: Teste o programa abaixo.
#include <stdio.h>
main()
{
int x = 5;
int *p_x = NULL;
// Ponteiro de x.
// Ponteiro para ponteiro de x.
int **p_p_x = NULL;
// Carga inicial dos ponteiros
p_x = &x;
p_p_x = &p_x;
printf(“x= %d -&x = %x \n”,x,&x);
printf(“x= %d -p_x = %p \n”,*p_x,p_x);
printf(“x= %d -*p_x = %d”,**p_p_x,*p_x);
}
PE1: A partir dos resultados do PT3 e das
informações do esquema E1, indique os
resultados a serem mostrados na Tabela T1.
x
p_x
p_p_x
5
... 1000 ...
...
1002
1000 1001 1002 1003 1004 1005
Esquema E1: Usando ponteiros.
Expressão Valor
x
&x
p_x
*p_x
&p_x
p_p_x
*p_p_x
**p_p_x
Tabela T1: Expressões de ponteiros.
Uma observação importante é que não se
deve inicializar um ponteiro com valores
numéricos e sim com endereços de variáveis.
Exemplo:
// p contém lixo, ou seja, aponta para um
// lugar qualquer.
int *p;
// Incorreto !
*p = 234;
Portanto, sempre inicialize ponteiros com
endereços. Caso não tenha um endereço
inicial especificado, use NULL e sempre
atribua endereços para ponteiros. Exemplo:
int a;
int *p = NULL;
p = &x;
ARITMÉTICA DE PONTEIROS
Observe que uma variável do tipo ponteiro
deve possuir um determinado tipo. Por
exemplo:
char a = ‘Z’;
int n = 1234;
float pi = 3.1415;
char *ptr_a = &a;
int *ptr_n = &n;
float *ptr_pi = π
Ou seja, um ponteiro para um dado tipo t
endereça sempre o número de bytes que
esse tipo ocupa em memória, i.e., endereça
sizeof(t) bytes. Os ponteiros são números
que representam posições de memória e
2
Programação de Computadores – 2o Sem- 2013 – Prof. André Amarante Luiz – LAB10
Esse material foi preparado pelos professores Aníbal Tavares de Azevedo e Cassilda
Maria Ribeiro para o curso de Programação de Computadores I – FEG/UNESP
com eles podem ser realizadas as operações
aritméticas dadas na Tabela OP1:
Operação
Exemplo Observações
Atribuição
p=&x
Atribuição
de
p=NULL endereço.
Incremento p = p + 2 Incremento de
2*sizeof(tipo) de
p.
Decremento p = p - 1 Decremento de
10*sizeof(tipo)
de p.
Apontado
*p
O
asterisco
por
permite obter o
valor existente
na posição cujo
endereço
está
em p.
Endereço de &p
O
ponteiro
ocupa
espaço
em memória e é
possível saber
também o seu
endereço.
Diferença
p1– p2
Permite saber
qual o número
de
elementos
entre p1 e p2.
Comparação pt1 > pt2 Verificação da
ordem de dois
elementos
através
dos
endereços.
OP1: Operações aritméticas com ponteiros.
A precedência do operador * é maior que os
operadores aritméticos e menor que os
operadores de incremento. O acréscimo ou
decréscimo é sempre realizado em função
do tamanho de armazenamento do tipo do
ponteiro. Assim, se o tipo for um inteiro e o
mesmo for incrementado em uma unidade,
o mesmo apontará para o próximo endereço
compatível com o tipo inteiro. Alguns
exemplos são:
strings. No caso de vetores, seu nome
corresponde ao endereço do seu primeiro
elemento, isto é: v == &v[0]. Assim,
existem duas formas de se colocar o
ponteiro ptr apontado para o primeiro
elemento de v:
int *pi = 3000; // inteiro ocupa 4 bytes.
char *pc = 4000; // caractere ocupa 1 byte.
double *pt = 5000; // real ocupa 8 bytes.
ptr = &v[0];
ptr = v;
pi++; // pi apontará para o endereço 3004.
pc++; // pi apontará para o endereço 4001.
pf++; // pf apontará para o endereço 5008.
Como os elementos de um vetor ocupam
posições consecutivas de memória, então, é
possível utilizar a aritmética de ponteiros
para acessar os elementos do vetor.
PT4: Teste o seguinte programa:
#include <stdio.h>
main()
{
int x=5, *px = &x;
double y=5.0, *py = &y;
printf(“%d %ld\n”,x,(long) px);
printf(“%d %ld\n”,x+1,(long) (px+1));
printf(“%f %ld\n”,y,(long) py);
printf(“%f %ld\n”,y+1,(long) (py+1));
}
O que significa o resultado apresentado?
Os conceitos de aritmética de ponteiros são
particularmente úteis para acessar elementos
de vetores e matrizes.
PONTEIROS E VETORES
Os ponteiros são normalmente utilizados no
tratamento e manipulação de vetores e
PT5: Teste o seguinte programa:
int v[3] = {10, 20, 30};
int *ptr = NULL;
ptr = v;
printf(“v[0] = %d \n”, *(ptr));
printf(“v[1] = %d \n”, *(++ptr));
printf(“v[2] = %d \n”, *(++ptr));
PE2: O que ocorreria se (++ptr) fosse
trocado por (ptr++)? Teste e discuta os
resultados obtidos.
PT6: Escreva um programa que mostre um
vetor na tela pela ordem original e pela
ordem contrária.
main()
{
int s[100], i, n;
// Aponta para o primeiro elemento de s.
int *ptr = s;
3
Programação de Computadores – 2o Sem- 2013 – Prof. André Amarante Luiz – LAB10
Esse material foi preparado pelos professores Aníbal Tavares de Azevedo e Cassilda
Maria Ribeiro para o curso de Programação de Computadores I – FEG/UNESP
i = 0;
printf("O tamanho do vetor: ");
scanf("%d",&n);
// Leitura.
for (i=0; i < n; i++)
{
scanf("%d",ptr);
ptr++;
}
// Impressão ao contrário.
for (i=0; i < n; i++)
{
ptr = ptr - 1;
printf("%d |",*ptr);
}
printf("\n");
// Impressão original.
for (i=0; i < n; i++)
{
printf("%d |",*ptr);
ptr = ptr + 1;
}
printf("\n");}
PE3: Observe que no PT6, o incremento
do primeiro laço for de impressão dos
elementos foi diferente do segundo for.
Teste e visualize o que ocorreria caso isto
não fosse feito.
Uma observação muito importante é que se
v é um vetor ou um ponteiro para o
primeiro elemento de um vetor, então, para
obter o elemento cujo índice é i deste vetor,
basta fazer:
v[i]  *(v+i)
PE4: Crie um programa que lê e imprime os
elementos de um vetor de inteiros utilizando
a expressão *(v+i). Dica: Quando for usar
scanf, lembre-se: &(*(v+i)) é igual à (v+i).
ALOCAÇÃO DINÂMICA
Para trabalhar com vetores ou matrizes é
necessário saber a priori o número exato de
elementos que serão utilizados. Como isso
nem sempre é possível, o que ocorre, em
geral, é que um vetor tem que ser declarado
com um tamanho muito maior do que será
efetivamente utilizado. Por exemplo:
float x[8]; ou
const int tamanho = 8;
float x[tamanho];
A alocação dinâmica de memória, através de
ponteiros, define em tempo de execução a
quantidade de memória usada por um vetor:
#include <stdlib.h>
main()
{float *x;
int i, n;
printf(“Tamanho do vetor: ”);
scanf(“%d”,&n);
x = (float *) calloc(n, sizeof(float));
A função calloc permite criar n elementos,
cada um deles com o mesmo número de
bytes (especificado por sizeof(float)).
Todos os bytes são alocados com valor 0 e
ou o endereço da área criada é retornado ou
NULL para x(retorno de (float *)).
Outra função de alocação dinâmica é malloc
que aloca o número de bytes indicados (ou
seja, malloc(n* sizeof(float))) e devolve um
ponteiro para o bloco de bytes ou NULL.
Todas estas funções são da biblioteca
<stdlib.h>.
PE5: Refazer o PE4 utilizando o código de
alocação dinâmica discutida anteriormente,
usando calloc e malloc.
A alocação dinâmica pode ser utilizada para
construir matriz:
PT7: Construir um programa que aloca
dinamicamente memória para uma matriz e
preenche os seus elementos.
float **a;
int m,n, i, j;
printf(“Numero de linhas e colunas: ”);
scanf(“%d %d”,&m,&n);
a = (float **) calloc(m, sizeof(float *));
for (i = 0; i < m; i++)
a[i] = (float *) calloc(n,sizeof(float));
for (i = 0; i < m; i++)
for (j = 0; j <n; j++)
a[i][j] = (float) (i+j);
PE6: Complete o PT7, imprindo os
elementos da matriz usando *(*(a+i)+j).
Verifique porque este comando funciona.
A memória dinâmica alocada deve ser
disponibilizada após o uso de outros
programas. Para tanto, antes de terminar a
função main() deve-se executar a função
free() para cada estrutura dinâmica:
PT8: Código que usa o comando free().
float *x;
int **a;
free(x);
for (i = 0; i < m; i++)
free(a[i]);
4
Programação de Computadores – 2o Sem- 2013 – Prof. André Amarante Luiz – LAB10
Esse material foi preparado pelos professores Aníbal Tavares de Azevedo e Cassilda
Maria Ribeiro para o curso de Programação de Computadores I – FEG/UNESP
free(a);
PE7: Inserir código no PE6 que libera a
memória dinâmica, usando o free().
5
Download