ResoluçaoExerAula8_5_14_PGI_2s1314_C.zip

Propaganda
O que é um apontador em C (type pointer in C) ?
Um apontador é uma variável que contém um endereço de outra variável.
Exemplo:
int
x = 10;//variável inteira iniciada com o valor 10
int *px
= &x;//variável apontadora iniciada com o endereço de localização na memória do x
Neste caso temos x, como uma variável que armazena um valor do tipo inteiro e px como um apontador ou
ponteiro do tipo inteiro que que armazena o endereço da variável x do tipo inteiro. Se a variável fosse do tipo
char o apontador teria de ser do tipo char. O valor do apontador armazenado em px é o endereço na
memória da variável x, obtido com o operador &, i.e., o endereço de x é dado por &x.
Definição de variável apontadora?
type* nomeVarApontadora; //o type são os estudados em C
Operador
&
O endereço de uma variável é obtido através do operador &. Operador referenciação de memória.
Exemplo:
int a = 43;
int *iptr;
ptrInt = &a;
// um inteiro iniciado a 43
// apontador para inteiro */
/* ptrInt passa a guardar o endereço de a */
/* definir e iniciar ptrInt na mesma linha com &a*/
int *ptrInt = &a;
Operador
*
O operador * permite aceder ao conteúdo de uma posição memória para onde a apontador “aponta”.
Operador desreferenciação.
Exemplo:
int b;
//definição da variavel b que irá armazenar um valor inteiro
//ptrInt foi definido e iniciado no exemplo anterior com endereço de a
b =*ptrInt;//b é afectado com o valor 43
*ptrInt = 12;
//o conteúdo apontado (referido) por prtInt é alterado com 12. O valor do a,
//exemplo anterior é alterado de 43 para 12.
#include <stdio.h>
int main() {
int y, x = 1; //declara dois inteiros, um com o valor 1
int *px;
//px é apontador para int. Não está iniciado com qualquer endereço
px = &x;
//px fica com o valor do endereço de x
y = *px;//y fica com conteúdo do endereço de memória apontado por px, o valor 1
*px = 0;//conteúdo da posição de memória apontador por px, a variável x, fica a 0
printf("x=%d y=%d\n", x, y); // output: x=0 y=1
int* p;//apontador não iniciado, mas quando for usado o conteúdo terá que estar
*P=2; //usar "conteúdo de …" antes de p iniciado com o "endereço de …" dá ERRO
return 0;
}
NOTA IMPORTANTE: inicie apontador com o endereço de.. antes de usar o seu conteúdo
Uso de apontadores
*
O valor de retorno de uma função pode ser um ponteiro:
int* blabla();
O parametros de uma função pode ser um ponteiro:
int abcd(char* a, int* b);
A prioridade de & e * é superior à dos operadores aritméticos.





int y, x=2; // define y e x com valor de 2
int* px=&x; //define e inicia px com endereço de x
y = *px + 1; //funciona como esperado. Incrementa o valor de
++*px;
//incrementa o valor de x, em que x é a variável apontada por px;
(*px)++;
//(os parênteses são necessários).
Como discutido em aulas anteriores, em C os parâmetros são passados por valor. Por exemplo, no seguinte
caso, o comportamento não é o esperado, a troca de valores apenas ocorre no contexto da função, não se
manifestando fora da mesma.
void trocaS( int a, int b ) {
int aux = a;
a = b;
b = aux;
}
Podemos no entanto passar a ter parâmetros definidos como apontadores que recebem dos argumentos os
valores de endereços das variáveis passadas e dentro da função acedemos aos conteúdos das variáveis
passadas, podendo deste modo alterar o seu valor. No exemplo anterior não é possível alterar os argumentos.
void trocaP( int* a, int* b ) {//recebe valores quer são endereços de
int aux = *a; //acesso aos conteúdos através operação desreferenciação (*a)
*a = *b;
*b = aux;
}
Neste caso, esta função deverá ser chamada da seguinte forma:
int x = 5, y = 6;
trocarP(&x, &y); // Os valores dos endereços de &x e de &y
Podemos também passar em parâmetros um array e dois índices que recebem como argumentos os valores
armazenados do array e os valores dos índices. Uma variável array é naturalmente um endereço de memória
onde se armazena os valores e dentro da função acede-se aos conteúdos da variável array através dos índices.
void trocaA( int a[], int p, int q ) {//recebe em a o array e em p e q os índices
int aux = a[p]; //acesso ao conteúdo de a através do índice p
a[p] = a[q];
a[q] = aux;
}
//o mesmo que
void trocaA( int* a, int p, int q ) {//recebe em a o array e em p e q os índices
int aux = *(a+p); //a[p]; //acesso ao conteúdo de a através do índice p
*(a+p) = *(a+q);
*(a+q) = aux;
}
Ponteiro nulo / Endereço zero
O ponteiro nulo representa ou referencia o endereço 0.
int *prt = NULL;
O NULL está definido em stdlib.h, pelo que é necessário incluir este ficheiro se quisermos utilizar o NULL.
Em C existe uma relação estreita entre apontadores e vectores (array). Exemplo:
#include <stdio.h>
int main() {
int a[6] = {1, 2, 3, 4, 5, 6};
int *pa = a;
printf("%d %d %d\n", a[2], *(a+2), *(pa+2));
return 0;
}
O a é em particular um ponteiro para a primeira posição do vector. Notar que os ponteiros têm uma
aritmética própria, quando fazemos pa+2 estamos a avançar na realidade 2*sizeof(int)bytes, ou seja, pa+2
aponta para uma posição de memória 2*sizeof(int) bytes depois da posição apontada por pa.
Podemos efectuar quer a operação + quer a operação - sobre variáveis apontadoras.
Ainda que exista uma relação estreita entre ponteiros e tabelas, devemos ter em conta que:



ainda que a declaração int* p1; declare o mesmo que int p2[];, temos que p1 pode ser alterado, mas
p2 não pode ser alterado e int p2[]; só pode ser utilizado em certos casos;
a declaração int p3[100]; declara uma tabela com 100 inteiros e aloca memória na quantidade
necessária;
a declaração char *text; não aloca qualquer memória, no entanto char *text = "ola"; aloca.
Qual a diferença entre as duas declarações seguintes?
char t1[] = "ola";
char *t2 = "ola";




Ambas alocam 4 bytes e copiam para essa posição de memória a sequência de caracteres 'o','l','a','\0'
Em ambos os casos é possível modificar o conteúdo da memória alocada.
Não é possível alterar o valor de t1, ou seja não é possível por t1 a apontar para outra posição de
memória.
É possível alterar o valor de t2.
Exercícios e exemplos
Exercício 1: Seja
float a[100];
float *p=a;
Indique para cada uma das seguintes afirmações é verdadeira ou falsa:




a[i] é equivalente a *(a+i)
&a[i] é equivalente a a+i
a[i] é equivalente a p[i]
p[i] é equivalente a *(p+i)

*a, a[0] e a são equivalentes a p[0]
Exemplo 1: Cópia de sequências de caracteres.
void strcpy( char* s, const char* t ){
int i = 0
while( (s[i] = t[i]) != ´\0´ ) i++;
}
ou
void strcpy( char* s, const char* t ){
while( (*s = *t) != ´\0´ ) {
s++;
t++;
}
}
ou
void strcpy(char *s, const char *t){
while( (*s++ = *t++) );
}
Exercício 2: Quando fazemos
int a;
scanf("%d",&a);
o que estamos a passar ao scanf? E Porque não precisamos do & no seguinte caso?
char s[100];
scanf("%s",s);/o s neste caso já é o endereço
Exemplo 2: A passagem por referência consegue-se, porque é passado o apontador:
void leVector(int *v, int tamanho ) {
int i;
for( i=0 ; i<tamanho ; i++)
scanf("%d", &v[i]);
}
Podemos escrever int* v ou int v[]. Como v já é um endereço, podemos alterar o v dentro da função.
Argumentos da linha de comandos
int main( int argc, char* argv[] ) {
int i;
for(i=1; i<argc; i++ ) printf("%s ", argv[i]); printf("\n");
return 0;
}
O argumento argc fica com o número de argumentos passados ao programa na linha de comando,
argv[0] é o nome com que o programa fui invocado e argv[i] é i-ésimo argumento. Se o código
acima for compilado e se o executarmos com a seguir se apresenta produz a escrita em terminal de:
isel@linuxvm:~/exemplo$ teste hello world
hello world
Alocação dinâmica de memória
Até ao momento utilizámos sempre alocação estática:
int tab[100];
Neste caso a memória é alocada durante o scope da variável, não é possível libertar a mesma quando já não é
necessária, e não é possível utilizar a mesma fora do scope. A solução para ultrapassar estas limitações passa
por utilizar alocação dinâmica.
Função malloc
void *malloc(size_t size);
Esta função recebe como argumento o número de bytes o tipo size_t representa uma dimensão em bytes e
devolve um apontado (endereço) para o primeiro byte do bloco de memória contígua alocada. O tipo de
retorno void* indica um ponteiro para um tipo não especificado, o que permite a utilização desta função
com qualquer tipo de dados. Posteriormente faz-se conversão para o tipo correcto por type cast.
Exemplo:
int *vec;
vec = (int*) malloc(sizeof(int)*37);
Função realloc
void *realloc(void *ptr, size_t size);
A função realloc recebe como argumentos um ponteiro ptr para bloco de memória já existente e a
dimensão size que o novo bloco de memória deverá ter. Esta função devolve um ponteiro para novo bloco
de memória e copia os valores do bloco antigo para o novo, em que se novo bloco for mais pequeno, só copia
até caber, se o novo bloco for maior, copia tudo e deixa o resto sem ser inicializado.
Se o argumento ptr for NULL, então a função realloc tem um comportamento idêntico ao da função
malloc.
Função free
Para libertar memória podemos utilizar a função:
void free(void *ptr);
O argumento é um ponteiro para a primeira posição do bloco de memória contígua a libertar. Esta função não
retorna nada.
Exemplo: Como libertar a memória reservada com o malloc anterior?
free(vec);
Todas estas função estão definidas em stdlib.h, pelo que é necessário incluir este ficheiro (#include
<stdlib.h> ).
Outras funções úteis
Existem outras funções úteis para, por exemplo, inicializar a memória alocada ou para copiar segmentos de
memória. De facto, a função void *calloc(size_t nElems, size_t size) permite tal como a função
malloc alocar memória, neste caso para um vector com nElems elementos em que cada elemento tem
size bytes, mas em que a memória é inicializada a zero. Em geral podemos inicializar qualquer segmento de
memória com a função void *memset(void *s, int c, size_t n), definida em string.h, que
inicializa os primeiros n bytes de memória apontada por s com o valor c. Qual deve ser o valor de c se
quisermos inicializar um vector de inteiros com valor -1 em todas as entradas?
A função void *memcpy(void *dest, const void *src, size_t n) é também útil para copiar
segmentos de memória. Dados dois ponteiros src e dest para dois segmentos de memória sem
sobreposição, esta função copia os primeiros n bytes apontados por src para os primeiros n bytes apontados
por dest. É do programador a responsabilidade de garantir que não existe sobreposição entre os segmentos
de memória e que os acessos de leitura e escrita ocorrem dentro dos limites da memória alocada.
Exercício já resolvido: Envolve alocação dinâmica.
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main() {
char *str1=NULL, *str2=NULL;
//definir e iniciar um array dinámico de char
str1 = (char *)malloc(11); //10+1 (1 é para o \0)
strcpy(str1, "ABCDEFGHIJ");//faz cópia para str1 de "ABC
..J"
//O str2 é NULL então esta função tem comportamento do malloc
str2 = (char *)realloc(str2, 20);//20 é o tamanho do novo bloco.
printf("Endereço de str1= %p\n", str1);
printf("Endereço de str2= %p\n", str2);
//Este novo bloco de memória tem a cópia dos valores do str1 que ocupa 11 chars
str1 = (char *)realloc(str1, 200);//novo bloco de memória de tamanho 100
strcat(str1, " acrescentar ABCD");
printf("Novo endereço de str1= %p\n", str1);
printf("Conteudo de str1= %s | tamanho=%d\n", str1, strlen(str1));
char* s=strchr(str1,'Z');
if( s== NULL ) printf("o Z não existe\n");
s=strchr(str1,'t');
if( s!= NULL ) printf("o t existe e a string a partir de t é =%s\n",s);
free(str1);//libertar o bloco de memoria, porque não é mais necessário
free(str2);
return 0;
}
Mais uma funções com uso de alocação dinâmica de memória
//capacidade inicial 10 que vai crescendo com o factor de duplicação
#define MAX 10
/*
*lê valores inteiros para o array a ser criado, preenchido e retornado por quem
*chamar esta função. O total de valores é passado em parâmetro por referência.
*/
int* preencherArray(int* fact ){//fact recebe endereço-passagem por referência
printf("digite inteiros e termine com uma letra\n");
int* v = malloc( sizeof(int)*(*fact) );//alocar um bloco de memória – array v
int i;
for( i=0 ; ; i++ ) {
if( scanf("%d", &v[i])!=1 ) break;//o for(;;) termina quando se digita letra
if( i==*fact-1 ) {
*fact *=2; //factor de duplicação
v = realloc( v, sizeof(int)*(*fact) );//realocar novo bloco de memória
}
}
*fact=i;//actualizar o parametro de referencia com o total
return v; //retorna para quem chama este array de inteiros
}
Download