Escolar Documentos
Profissional Documentos
Cultura Documentos
Algoritmos e complexidade
onde
<tipo> representa qualquer tipo de dados
que a linguagem C suporta;
<oper> representa o operador do tipo
ponteiro que pode ser * ou &;
<nome> denota uma variável;
Capítulo 1 - Ponteiros
Ponteiro
<tipo> <oper> <nome>;
onde
<tipo> representa qualquer tipo de dados
que a linguagem C suporta;
<oper> representa o operador do tipo
ponteiro que pode ser * ou &;
<nome> denota uma variável;
Capítulo 1 - Ponteiros
Ponteiro
O operador unário *, denominado por
operador de acesso indirecto é um ponteiro
que devolve o conteúdo do endereço de
memória do seu operando. Por exemplo,
nas declarações:
int *p;
float *f;
Capítulo 1 - Ponteiros
Ponteiro
as variáveis p e f são ponteiros. O primeiro
armazena o conteúdo do endereço de
memória de objetos do tipo inteiro,
enquanto o segundo armazena o conteúdo
de memória de objectos do tipo real.
Capítulo 1 - Ponteiros
Ponteiro
Um ponteiro só deve armazenar o conteúdo
de um endereço de memória se esse
conteúdo e o ponteiro tiverem o mesmo
tipo de dados. Se essa restrição não for
observada, teremos resultados
imprevisíveis.
Capítulo 1 - Ponteiros
Ponteiro
O operador unário &, denominado por
operador endereço é um ponteiro que
devolve o endereço de memória do seu
operando. Por exemplo, no segmento de
código:
float x = -1.0;
float *ptr;
ptr = &x;
Capítulo 1 - Ponteiros
Ponteiro
Nas duas primeiras linhas declaramos uma
variável x do tipo float que recebe o valor
-1.0 e um ponteiro p do mesmo tipo. Como
temos uma compatibilidade de tipos, na
terceira linha o ponteiro ptr aponta para o
endereço da variável x e, dizemos que ptr
aponta para x.
Capítulo 1 - Ponteiros
Passagem de parâmetros
Por defeito, na linguagem C os parâmetros
são passados por valor. Neste tipo de
passagem, os parâmetros da função e os
parâmetros da chamada estão armazenados
em posições de memória diferentes. Logo,
nenhuma alteração feita aos parâmetros da
função afectam os valores dos parâmetros
da chamada.
Capítulo 1 - Ponteiros
Passagem de parâmetros
Uma das principais vantagens dessa forma
de passagem é que as funções ficam
impedidas de aceder aos conteúdos das
variáveis declaradas em outras funções.
Capítulo 1 - Ponteiros
Passagem de parâmetros
Para que uma função possa alterar os
valores dos parâmetros da chamada é
necessário que eles sejam passados por
referência. Nesse caso, os parâmetros da
chamada e os parâmetros da função
compartilham as mesmas posições de
memória.
Capítulo 1 - Ponteiros
Passagem de parâmetros
Para que uma função possa alterar os
valores dos parâmetros da chamada é
necessário que eles sejam passados por
referência. Nesse caso, os parâmetros da
chamada e os parâmetros da função
compartilham as mesmas posições de
memória.
Capítulo 1 - Ponteiros
Passagem de parâmetros
Para clarificar esse conceito vejamos um
exemplo. A função permutar() que
descrevemos a seguir, troca os conteúdos
dos dois parâmetros passados por
referência.
Capítulo 1 - Ponteiros
Passagem de parâmetros
Observe que na declaração da função os
nomes dos parâmetros são precedidos por
um asterístico, isso quer dizer que a função
recebe como parâmetro os conteúdos dos
endereços representados por esses
ponteiros.
Capítulo 1 - Ponteiros
Passagem de parâmetros
#include <stdio.h>
...
void permutar ( int *p, int *q )
{
int x;
x = *p;
*p = *q;
*q = x;
}
Capítulo 1 - Ponteiros
Passagem de parâmetros
Na chamada dessa função os parâmetros
são precedidos pelo operador endereço &,
isso quer dizer que o compilador vai
transferir para a função os endereços de
memória onde os parâmetros estão
armazenados.
Capítulo 1 - Ponteiros
Passagem de parâmetros
int main()
{
int a = 3, b = 7;
permutar (&a, &b ); /* Chamada da Função */
printf("%d %d", a, b);
return 0;
}
Capítulo 1 - Ponteiros
Aritmética de Ponteiros
A adição e à subtracção são as únicas
operações aritméticas que podem ser
realizadas com ponteiros. Para entender o
que ocorre com a aritmética de ponteiros,
vamos considerar que pt1 é um ponteiro
que aponta para o endereço 2000 e que
esse ponteiro é do tipo inteiro. O valor do
incremento: pt1++
Capítulo 1 - Ponteiros
Aritmética de Ponteiros
Depende da arquitectura do computador.
Suponhamos sem perda da generalidade
que o computador possui uma arquitectura
de 64 bits. Para essa arquitectura, o tipo
caracter é armazenado em um byte, o
inteiro em quatro bytes, o float em quatro
bytes e o double em 8 bytes.
Capítulo 1 - Ponteiros
Aritmética de Ponteiros
A operação anterior deverá apontar para a
posição de memória do próximo elemento
do tipo inteiro. O próximo elemento está no
endereço 2004. O mesmo acontece com a
operação de decremento, para esse caso, o
ponteiro apontaria para o endereço 1996.
Capítulo 1 - Ponteiros
Aritmética de Ponteiros
Também podemos realizar a operação de
diferença de ponteiros. Dados dois
ponteiros do mesmo tipo, a diferença de
ponteiros permite determinar quantos
elementos existem entre eles. Por exemplo,
o comprimento de uma string, pode ser
obtido pela diferença entre o carácter ‘\0’ e
o endereço do primeiro elemento.
Capítulo 1 - Ponteiros
Aritmética de Ponteiros
int strlen ( char *s)
{
char *ptr = s;
while ( *s != ‘\0’)
s++;
return (s - ptr); // diferença entre os endereços
}
Capítulo 1 - Ponteiros
Ponteiros e Vetores
Quando declaramos um vetor, o compilador
reserva automaticamente um bloco de
bytes consecutivos de memória, para
armazenar os seus elementos e, trata o
nome do vetor, como um ponteiro que faz
referencia ao primeiro elemento. Por
exemplo, na declaração:
int x[4];
Capítulo 1 - Ponteiros
Ponteiros e Vetores
Se o primeiro elemento estiver armazenado
no endereço 104, o segundo estará
endereço 108, o terceiro no endereço 112 e
o ultimo no endereço 116. Para além disso,
x é um ponteiro que aponta para o
endereço 104.
Capítulo 1 - Ponteiros
Ponteiros e Vetores
Como o compilador trata o nome de um
vetor como um ponteiro, então, o acesso a
qualquer elemento pode ser feito pela
notação vetorial ou pela notação com de
ponteiro.
Capítulo 1 - Ponteiros
Ponteiros e Vetores
Na notação vetorial, o i-ésimo elemento é
acedido pela variável indexada x[i],
enquanto na notação por ponteiro esse
elemento é acedido pelo ponteiro *(x+i).
Isto que dizer que:
x[0] = *(x + 0);
x[1] = *(x + 1);
x[i] = *(x + i);
Capítulo 1 - Ponteiros
Ponteiros e Vetores
Então, o comando de atribuição múltiplo:
é equivalente à:
main( )
{
imprimeOperacao (soma,3,5);
imprimeOperacao (subtração,5,9);
imprimeOperacao (multiplicacao,10,5);
}
Capítulo 1 - Ponteiros
Ponteiros para Funções
Ponteiros de código permitem que funções
sejam passadas como argumentos a outras
funções; um recurso essencial na
implementação de rotinas polimórficas em
C.
Capítulo 1 - Ponteiros
Ponteiros e Estruturas
Um ponteiro para uma estrutura não é
diferente de um ponteiro para um tipo de
dados elementar. Se p aponta para uma
estrutura que tem um campo m, então
podemos escrever (∗p).m para aceder a
esse campo. Por exemplo:
Capítulo 1 - Ponteiros
Ponteiros e Estruturas
Capítulo 1 - Ponteiros
Ponteiros e Estruturas
Como o operador ponto (.) tem maior
precedência que o operador de acesso
indireto (∗), temos de utilizar
obrigatoriamente parênteses para fazer
referencia a qualquer campo dessa
estrutura no comando printf().
Capítulo 1 - Ponteiros
Ponteiros e Estruturas
Para evitar essa notação pesada, a
linguagem C possui um operador específico
para aceder aos campos das estruturas,
denominado por operador seta. Com esse
operador, em vez de escrevermos
(∗ponteiro).membro passamos a escrever
ponteiro−>membro. Então, o programa
anterior deveria ser escrito como:
Capítulo 1 - Ponteiros
Ponteiros e Estruturas
Capítulo 1 - Ponteiros
Ponteiros e Estruturas
void *ptr;
int *pnumero;
ptr = malloc(1000);
pnumero = (int *)ptr;
•Cap 2 – Gestão Dinâmica de Mem
•Cap 2 – Gestão Dinâmica de Mem
Outro ponto importante é que, como cada
tipo de dados pode requerer uma
quantidade de memória diferente,
dependendo da máquina que executa o
programa, devemos usar o operador sizeof
para especificamos a quantidade de
memória a ser alocada.
Para usar a função malloc(), devemos incluir
o arquivo stdlib.h ou alloc.h.
•Cap 2 – Gestão Dinâmica de Mem
•Cap 2 – Gestão Dinâmica de Mem
O programa cria um vetor cujo tamanho é
determinado pelo próprio usuário, em
tempo de execução. Como o vetor deve ter
capacidade para guardar n valores e cada um
deles ocupa 4 bytes, a área de memória
alocada deve ter 4n bytes de extensão;
justamente o valor da expressão n *sizeof(int).
•Cap 2 – Gestão Dinâmica de Mem
Quando um programa termina, todas as
áreas alocadas pela função malloc() são
automaticamente liberadas. Entretanto, se
for necessário liberar explicitamente uma
dessas áreas, podemos usar a função free().
Essa função exige como argumento um
ponteiro para a área que será liberada.
•Cap 2 – Gestão Dinâmica de Mem
•Cap 2 – Gestão Dinâmica de Mem
•Cap 2 – Gestão Dinâmica de Mem
Às vezes é necessário alterar, durante a
execução do programa, o tamanho de um
bloco de bytes que foi alocado por malloc.
Isso acontece, por exemplo, durante a leitura
de um arquivo que se revela maior que o
esperado. Nesse caso, podemos recorrer à
função realloc para redimensionar o bloco
de bytes.
•Cap 2 – Gestão Dinâmica de Mem
A função realloc recebe o endereço de um
bloco previamente alocado por malloc (ou
por realloc) e o número de bytes que o bloco
redimensionado deve ter. A função aloca o
novo bloco, copia para ele o conteúdo do
bloco original, e devolve o endereço do novo
bloco.
•Cap 2 – Gestão Dinâmica de Mem
Se o novo bloco for uma extensão do bloco
original, seu endereço é o mesmo do original
(e o conteúdo do original não precisa ser
copiado para o novo). Caso contrário, realloc
copia o conteúdo do bloco original para o
novo e libera o bloco original (invocando
free). A propósito, o tamanho do novo bloco
pode ser menor que o do bloco original.
•Cap 2 – Gestão Dinâmica de Mem
•Cap 2 – Gestão Dinâmica de Mem
Suponhamos, por exemplo, que alocamos
um vetor de 1000 inteiros e depois
decidimos que precisamos de duas vezes
mais espaço. Vejamos um caso concreto:
•Cap 2 – Gestão Dinâmica de Mem
Alocação dinâmica de vetores:
Uma das aplicações mais interessantes na
gestão de memória dinâmica é a criação e a
manipulação de vetores.
Para criar um ponteiro para um vetor,
devemos seguir a seguinte sequencia de
passos:
1º- Calcular com a função sizeof() o número
de bytes para armazenar a estrutura;
•Cap 2 – Gestão Dinâmica de Mem
Alocação dinâmica de vetores:
2º- Invocar a função malloc() para retornar
um apontador genérico que aponta para o
primeiro byte desse bloco de memória;
3º- Converter o ponteiro genérico retornado
pela função malloc(), para um ponteiro cujo
tipo de dados é compatível com o tipo do
vetor.
•Cap 2 – Gestão Dinâmica de Mem
Alocação dinâmica de vetores:
Eis como um vetor com n elementos inteiros
pode ser alocado (e depois desalocado)
durante a execução de um programa:
•Cap 2 – Gestão Dinâmica de Mem
Alocação dinâmica de vetores:
A expressão abaixo representa a alocação
dinâmica de vetores:
v = malloc (100 * sizeof (int));
- Análise do Problema;
- Especificação do Problema.
•Cap 3 – Tipo Abstrato de Dados
Tipo Abstracto de Dados
Vamos aplicar os conceitos vistos na secção
anterior para desenvolver um programa, que
irá realizar operações aritméticas de adição e
de multiplicação de números racionais, dados
dois números inteiros. Em primeiro lugar,
vamos extrair do problema a estrutura de
dados.
•Cap 3 – Tipo Abstrato de Dados
Tipo Abstracto de Dados
typedf struct
{
int x;
int y;
} TRacional;
•Cap 3 – Tipo Abstrato de Dados
Tipo Abstracto de Dados
Em segundo, vamos extrair as operações
associadas a essa estrutura de dados.
•Ler um número inteiro;
•Criar um número racional;
•Calcular a soma de dois números racionais;
•Calcular a multiplicação de dois números racionais.
•Verificar se dois números racionais são iguais.
•Imprimir um número racional.
•Cap 3 – Tipo Abstrato de Dados
Tipo Abstracto de Dados
Para finalizar, vamos detalhar para cada
operação, o tipo de dados que recebe, que
devolve, o objectivo da operação e as
condições necessárias para sua realização.
•Cap 3 – Tipo Abstrato de Dados
Tipo Abstracto de Dados
CriarRacional()
Entrada: Dois números inteiros;
Saída : Um número racional;
Pré-Condições: Denominador não pode ser
igual a zero;
Pós-Condições: Criar um número racional;
•Cap 3 – Tipo Abstrato de Dados
Tipo Abstracto de Dados
SomarRacional()
Entrada: Dois números racionais;
Saída : Um número racional;
Pré-Condições: Nenhuma;
Pós-Condições: Somar dois números
racionais;
•Cap 3 – Tipo Abstrato de Dados
Tipo Abstracto de Dados
MultiplicarRacional()
Entrada: Dois números racionais;
Saída : Um número racional;
Pré-Condições: Nenhuma;
Pós-Condições: Multiplicar de dois números
racionais;
•Cap 3 – Tipo Abstrato de Dados
Tipo Abstracto de Dados
TestarIgualdade()
Entrada: Dois números racionais;
Saída : Verdadeiro ou Falso
Pré-Condições: Nenhuma;
Pós-Condições: Verifica se dois números
racionais são iguais;
•Cap 3 – Tipo Abstrato de Dados
Tipo Abstracto de Dados
ImprimirRacional()
Entrada: um números racionais;
Saída : nada
Pré-Condições: Nenhuma;
Pós-Condições: Mostrar na tela um número
racional;
•Cap 3 – Tipo Abstrato de Dados
Implementação do Problema
Cada Tipo Abstracto de Dados deve ser
implementado por um único módulo e cada
módulo deve ser constituído por uma
estrutura de dados e um conjunto de
operações que a manipulam.
•Cap 3 – Tipo Abstrato de Dados
Implementação do Problema
Para garantir a universalidade dos módulos e
a coerência dos dados devolvidos, as
operações devem ser implementadas como
subprogramas com um sistema de controlo
de erros.
•Cap 3 – Tipo Abstrato de Dados
Implementação do Problema
Com este método, cada subprograma deverá
devolver um código de erro a notificar o
estado da operação, e com esse código, o
programador saberá se operação terminou
com sucesso ou não.
•Cap 3 – Tipo Abstrato de Dados
Implementação do Problema
Para esconder as linhas de código dos
subprogramas que compõem cada módulo,
cada TAD deve ser desenvolvido em dois
arquivos diferentes. O arquivo interface e o
arquivo com a implementação. Esses
arquivos serão objecto de estudo nas
próximas secções.
•Cap 3 – Tipo Abstrato de Dados
TDA (Recap)
Uma estrutura de dados é uma forma de
armazenar e organizar os dados de modo que
eles possam ser usados de forma eficiente.
Consiste em:
um conjunto de tipos de dados;
Definição de operações que podem ser
realizadas sobre este conjunto de dados.
•Cap 3 – Tipo Abstrato de Dados
TDA (Recap)
Exemplos:
•arranjos (vetores e matrizes);
•estruturas (struct);
•referências (ponteiros)
Ponto (x,y)
•Coordenada - x
•Coordenada - y
•Cap 3 – Tipo Abstrato de Dados
Exemplo de TAD: Representação de um
ponto
Exercícios
1. Desenvolva uma função iterativa e uma recursiva que devolva o
número de átomos de uma lista encadeada simples.