Você está na página 1de 241

PROGRAMAO

ESTRUTURAS
DE DADOS
E

ALGORITMOS
EM

Professor Doutor Antnio Manuel Adrego da Rocha


Professor Doutor Antnio Rui Oliveira e Silva Borges
Departamento de Electrnica e Telecomunicaes
Universidade de Aveiro

Prefcio
Este texto serve de suporte disciplina de Programao II, cujo objectivo o de fornecer uma
familiarizao com o ambiente de programao fornecido pelo Unix, na sua variante mais popularizada
Linux e o domnio da linguagem C, na sua norma ANSI, para o desenvolvimento de programas de
mdia e elevada complexidade.
Comeamos por apresentar os aspectos essenciais da linguagem C em dois captulos. Depois
introduzirmos as construes mais complexas da linguagem de forma gradual, medida que so
necessrias construo de estruturas de dados mais complexas, bem como para a optimizao e
generalizao de algoritmos. Os aspectos fundamentais apresentados no texto so os seguintes:
x A familiarizao progressiva com a linguagem de programao C e com as suas bibliotecas.
x A apresentao de algoritmos recursivos e sua comparao com os algoritmos iterativos
equivalentes.
x A introduo da metodologia de decomposio modular das solues, ou seja, o paradigma da
programao modular.
x O estudo da organizao da Memria de Acesso Aleatrio (RAM), nas suas implementaes
esttica e semiesttica, e, de um conjunto significativo de algoritmos de pesquisa e de ordenao.
x O estudo da organizao de memrias mais complexas que a Memria de Acesso Aleatrio, como
por exemplo, a Memria Fila de Espera (FIFO), a Memoria Pilha (Stack) e a Memria Associativa
(CAM), nas suas implementaes esttica, semiesttica e dinmica, e, dos algoritmos associados
para pesquisa, introduo e retirada de informao.
Assume-se que os alunos frequentaram a disciplina de Programao I, e portanto, j esto
familiarizados com a metodologia de decomposio hierrquica das solues, estabelecendo
dependncias de informao e no encapsulamento da informao com a criao de novas instrues no
mbito da linguagem Pascal, ou seja, com o paradigma da programao procedimental. Bem como,
com a criao de estruturas de dados estticas com alguma complexidade que modelam correctamente a
resoluo dos problemas. Pelo que, a apresentao da linguagem C feita por comparao com a
linguagem Pascal.
Pretende-se ainda, que os alunos se familiarizem com a terminologia informtica apresentada nos textos
de referncia da rea das Cincias da Computao, pelo que, se tenha optado pela apresentao
sistemtica, em itlico e entre parntesis, dos nomes dos algoritmos e das estruturas de dados em ingls.

Captulo 1
INTRODUO AO C

Sumrio
Este captulo dedicado introduo das primeiras noes sobre a gramtica da linguagem
C. Comeamos por apresentar a estrutura de um programa e os seus elementos bsicos.
Explicamos os tipos de dados bsicos existentes, a definio de constantes e de variveis.
Apresentamos os vrios tipos de expresses e operadores existentes e a instruo de
atribuio, que a instruo bsica de uma linguagem imperativa. Apresentamos de seguida
as estruturas de controlo, que permitem alterar o fluxo da sequncia das instrues.
Apresentamos ainda as instrues de leitura de dados do teclado scanf e de escrita de
dados no monitor printf. Finalmente, apresentamos as bibliotecas que contm as funes
mais usuais e que estendem a operacionalidade da linguagem.

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

1.1 Introduo
Em 1972, Dennis M. Ritchie desenvolveu a linguagem C, nos Laboratrios Bell da
companhia AT & T, que a principal empresa de telecomunicaes dos Estados Unidos da
Amrica, como uma linguagem de programao concebida para a escrita de sistemas
operativos, aquilo que se designa por Programao de Sistemas. Como a linguagem C era
to flexvel e permitia que os compiladores produzissem cdigo em linguagem mquina
muito eficiente, em 1973, Dennis M. Ritchie e Ken Thompson reescreveram quase
totalmente o sistema operativo Unix em C. Devido a esta ligao ntima, medida que o
Unix se tornou popular no meio acadmico, tambm a linguagem C se tornou a linguagem
preferida para o desenvolvimento de aplicaes cientficas. Pelo que, apesar de ter sido
concebida para a escrita de sistemas operativos, a linguagem C hoje encarada como uma
linguagem de uso geral.
A principal caracterstica da linguagem C que combina as vantagens de uma linguagem de
alto nvel descendente do AlGOL 68, com a eficincia da linguagem assembly, uma vez que
permite a execuo de operaes aritmticas sobre ponteiros e operaes sobre palavras
binrias. A linguagem C tambm tem uma sintaxe muito compacta e permite que
operadores de tipos diferentes possam ser combinados livremente.
Esta liberdade e poder da linguagem C, permite aos programadores experientes escreverem
cdigo compacto e eficiente que dificilmente poderiam ser escritos noutras linguagens de
programao. Mas, como fracamente estruturada em termos semnticos, tambm permite
que construes sem sentido aparente, escritas por programadores inexperientes, sejam
aceites pelo compilador como vlidas. O facto da linguagem C ser muito poderosa, exige
portanto, do programador muita disciplina e rigor na utilizao das construes da
linguagem, para que o cdigo escrito seja legvel e facilmente altervel.
Apesar da linguagem C ter sido desenvolvida no princpio da dcada de 1970, a norma
ANSI (American National Standards Institute) foi apenas aprovada em 1989 (norma
ISO/IEC 9899-1990).

1.2 A estrutura de um programa em C


Ao contrrio do que se passa em Pascal, em que um programa apresenta uma organizao
hierrquica que reflecte directamente o algoritmo que lhe deu origem, na linguagem C, um
programa organizado horizontalmente como um agrupamento de variveis e funes
colocadas todas ao mesmo nvel, uma estrutura conhecida pelo nome de mar de funes.
Neste contexto, a diferenciao entre o programa principal e os diversos subprogramas
associados feita pelo facto de existir uma funo particular, de nome main, que sempre
invocada em primeiro lugar aquando da execuo do programa. Assim, a funo main
desempenha na prtica o papel do programa principal do Pascal. Segundo a norma ANSI, a
funo main uma funo de tipo inteiro, em que o valor devolvido serve para informar
sobre o estado de execuo do programa.

CAPTULO 1 : INTRODUO AO C

De facto, a noo de devolver um valor associado ao estado de execuo de um programa


corresponde filosofia subjacente arquitectura de comandos do Unix, em que a
linguagem de comandos (shell) , no fundo, uma verdadeira linguagem de programao, que
permite construir comandos mais complexos por combinao de comandos mais simples
(shell scripts), usando um conjunto de estruturas de controlo muito semelhantes aos
encontrados na linguagem C ou Pascal. Neste contexto, os comandos mais simples so
programas, de cujo sucesso de execuo vai eventualmente depender a continuao das
operaes.
Ora, como esta uma rea que no ser explorada no mbito desta disciplina, em muitos
programas, sobretudo naqueles que sero desenvolvidos nesta disciplina, no se coloca a
questo de devolver um valor. Pelo que, para evitar a mensagem de aviso do compilador,
recomenda-se terminar o main com a instruo return 0. Um programa em C tem a
estrutura apresentada na Figura 1.1.
aluso a funes e definies externas
aluso a funes e definies locais
int main ( void )
{
declarao de variveis
aluso a funes
sequncia de instrues
return 0;
}
definio de funes locais

Figura 1.1 - Estrutura de um programa em C.

Vamos analisar as diversas partes de um programa em C atravs do exemplo do programa


de converso de distncias de milhas para quilmetros apresentado na Figura 1.2. O
ficheiro fonte que contm o programa comea por mencionar as funes e estruturas de
dados externas necessrias execuo do programa, que esto implementadas noutros
ficheiros providenciados pela linguagem C, as chamadas bibliotecas da linguagem, ou que
em alternativa so desenvolvidos pelo utilizador. A linguagem C foi concebida tendo em
mente facilitar a construo descentralizada de aplicaes, atravs do fraccionamento do
cdigo de um programa por diferentes ficheiros fonte. Por isso, necessrio e inevitvel,
mesmo em programas muito simples, recorrer aluso a funes e definies feitas
externamente, ou seja, noutros ficheiros. Para tornar mais rigorosa esta referncia, foi
criado o conceito de ficheiro de interface, onde todas as aluses e definies associadas a
um dado tipo de funcionalidade so colocadas. Estes ficheiros de interface distinguem-se
dos ficheiros fonte, propriamente ditos, por terem a terminao .h em vez de .c.
A norma ANSI fornece um conjunto muito variado de ficheiros de interface que
descrevem as diferentes funcionalidades fornecidas pelas bibliotecas de execuo ANSI.
Em Unix, por defeito, todos eles esto armazenados no directrio /usr/include. Da, no ser
necessrio referenciar este caminho de um modo explcito. Quando o ficheiro em causa
est neste directrio, basta colocar o seu nome entre os smbolos < e >. Em todos os
outros casos, a especificao do caminho deve ser includa no nome do ficheiro e o
conjunto ser colocado entre aspas duplas.

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

A incluso de ficheiros de interface num dado ficheiro fonte feita usando a directiva do
pr-processador #include numa das suas duas variantes:
x No caso do ficheiro de interface pertencer linguagem C, ento ele est armazenado no
directrio por defeito e usa-se a directiva #include <nome do ficheiro de interface>.
x No caso do ficheiro de interface ter sido criado pelo utilizador e no estar armazenado
no directrio por defeito, usa-se a directiva #include "nome do ficheiro de interface".
No mnimo, todos os ficheiros que contenham cdigo que faa acesso aos dispositivos
convencionais de entrada e de sada tm que incluir o ficheiro de interface stdio.h, que
descreve as funes e que contm as definies associadas com o acesso aos dispositivos
de entrada e de sada e aos ficheiros. Normalmente, o dispositivo de entrada o teclado e o
dispositivo de sada o monitor. Portanto, qualquer programa interactivo tem pelo menos
a aluso a este ficheiro de interface, tal como se apresenta na Figura 1.2.
A seguir s definies de objectos externos segue-se a aluso s funes locais que vo ser
usadas na funo main, bem como a definio de estruturas de dados e constantes locais.
Locais para a aplicao, mas que para o ficheiro, se comportam como definies globais.
Repare que a estruturao do programa, muito diferente do Pascal, sendo que as funes
so primeiramente aludidas ou referidas, para se tornarem visveis em todo o ficheiro e s
depois da funo main que so definidas. Neste exemplo, define-se apenas, atravs da
directiva #define, um identificador constante MIL_QUI, que representa o factor de
converso de milhas para quilmetros. Ele visvel para todo o cdigo do ficheiro, ou seja,
uma constante global. A funo main implementada com instrues simples, e com
recurso apenas s funes de entrada e de sada de dados da biblioteca stdio.
/* Programa de converso de distncias de milhas para quilmetros */
/* Instrues para o pr-processador */
#include <stdio.h>
#define

MIL_QUI

/* interface com a biblioteca de entrada/sada */

1.609

/* factor de converso */

/* Instrues em linguagem C propriamente ditas */


int main ( void )
{
double MILHAS,
QUILOMETROS;
do
{

/* distncia expressa em milhas */


/* distncia expressa em quilmetros */

/* Leitura com validao de uma distncia expressa em milhas */

printf ("Distncia em milhas? ");


scanf ("%lf", &MILHAS);
} while (MILHAS < 0.0);
QUILOMETROS = MIL_QUI * MILHAS;

/* Converso da distncia */

/* Impresso da distncia expressa em quilmetros */


printf ("Distncia em quilmetros %8.3f\n", QUILOMETROS);
return 0;
}

Figura 1.2 - Programa da converso de distncias.

Vamos agora analisar com detalhe na Figura 1.3 a definio da funo main, que tal como
qualquer outra funo na linguagem C, supe a especificao do seu cabealho e do seu
corpo. No cabealho, indica-se o tipo do valor devolvido, que como j foi referido
anteriormente sempre do tipo inteiro, o nome, e entre parnteses curvos, a lista de

CAPTULO 1 : INTRODUO AO C

parmetros de comunicao. Neste caso a funo no comunica directamente com o


exterior, pelo que, no existe lista de parmetros de comunicao. Quando tal acontece,
utiliza-se o identificador void. O corpo da funo delimitado pelos separadores { e },
correspondentes, respectivamente, aos separadores begin e end do Pascal, e contm a
declarao das variveis locais, a aluso a funes usadas na sequncia de instrues e a
sequncia de instrues propriamente dita. Constitui aquilo que em linguagem C se designa
por um bloco.

int main ( void )

cabealho
corpo

declarao de variveis locais


double MILHAS,
QUILOMETROS;
sequncia de instrues
do
{
printf ("Distncia em milhas? ");
scanf ("%lf", &MILHAS);
} while (MILHAS < 0.0);
QUILOMETROS = MIL_QUI * MILHAS;
printf ("Distncia em quilmetros %8.3f\n", QUILOMETROS);
return 0;

Figura 1.3 - A funo main.

1.3 Elementos bsicos da linguagem C


Os identificadores so nomes que so usados para designar os diferentes objectos
existentes no programa, como por exemplo, o nome do programa, os nomes das funes,
os nomes das constantes, tipos de dados e variveis. Os identificadores obedecem regra
de produo apresentada na Figura 1.4.
identificador ::= letra do alfabeto | carcter underscore |
identificador letra do alfabeto |
identificador carcter underscore |
identificador algarismo decimal
Figura 1.4 - Definio formal de um identificador.

Ou seja, so formados por uma sequncia de caracteres alfanumricos e o carcter


underscore, em que o primeiro carcter obrigatoriamente uma letra do alfabeto ou o
carcter underscore. Embora seja possvel comear um identificador pelo carcter underscore,
tal deve ser evitado. Este tipo de notao , em princpio, reservado para o compilador.
No h limite para o comprimento de um identificador. Na prtica, esse limite imposto
pelo compilador. A norma ANSI exige um comprimento mnimo de 31 e 6 caracteres,
respectivamente, para os identificadores internos e externos.

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

Na linguagem C, os alfabetos maisculo e minsculo so distintos, ou seja, a linguagem


sensvel ao tipo de letra (case sensitive). Assim sendo, o identificador conv_dist diferente do
identificador CONV_DIST. Embora no seja obrigatrio, costume designar todos os
identificadores da responsabilidade do programador com caracteres maisculos de maneira
a distingui-los dos identificadores da linguagem C, que tm de ser escritos obrigatoriamente
com caracteres minsculos. Em alternativa, h programadores que gostam de usar um
carcter maisculo no primeiro carcter de cada palavra que compe o identificador e os
restantes caracteres minsculos. Nesse caso, devem designar pelo menos os nomes das
constantes em caracteres maisculos. Mas, o importante que cada programador defina o
seu prprio estilo, e que exista uma certa coerncia nas regras que adopte.
As palavras reservadas da linguagem C so: auto; break; case; char; const; continue;
default; do; double; else; enum; extern; float; for; goto; if; int; long; register; return;
short; signed; sizeof; static; struct; switch; typedef; union; unsigned; void; volatile; e
while. As palavras reservadas aparecem a cheio ao longo do texto e no cdigo apresentado.
Para melhorar a legibilidade do programa, devem ser introduzidos comentrios relevantes,
que expliquem o significado dos diferentes objectos, ou que operao efectuada por
grupos bem definidos de instrues. Um comentrio uma qualquer sequncia de
smbolos inserida entre /* e */ e que no contenha */. Isto significa que no se pode
nunca encapsular comentrios. A Figura 1.5 apresenta a definio formal do comentrio.
comentrio ::= /* qualquer sequncia de smbolos que no contenha */ */
Figura 1.5 - Definio formal do comentrio.

O uso adequado de comentrios melhora extraordinariamente a legibilidade e a


compreenso de um segmento de cdigo. Assim, devem introduzir-se comentrios, pelo
menos, nas situaes seguintes:
x Em ttulo, para explicar o que faz o segmento de cdigo e descrever, caso exista, o
mecanismo de comunicao associado.
x Sempre que se declarem constantes ou variveis, para explicar o seu significado, a
menos que este seja trivial.
x A encabear as pores de cdigo correspondentes decomposio algortmica que lhe
deu origem.

1.4 Representao da informao


Em Pascal, os tipos de dados predefinidos esto directamente relacionados com o tipo de
informao neles armazenado. Assim, para informao numrica, existem os tipos inteiro
(integer) para a representao exacta e real (real) para a representao aproximada; para
quantidades lgicas existe o tipo booleano (boolean); e para a representao de smbolos
grficos existe o tipo carcter (char).
Em linguagem C, pelo contrrio, os tipos de dados predefinidos reflectem apenas o
formato de armazenamento. So sempre tipos numricos, embora em alguns casos
possibilitem uma interpretao alternativa, funo do contexto em que so usados.

CAPTULO 1 : INTRODUO AO C

A Figura 1.6 apresenta os tipos de dados simples existentes na linguagem C. Estes tipos de
dados tambm se designam por escalares, uma vez que, todos os seus valores esto
distribudos ao longo de uma escala linear. Dentro dos tipos de dados simples, temos o tipo
ponteiro (pointer), o tipo enumerado (enum) e os tipos aritmticos, que se dividem em
tipos inteiros e tipos reais. Os tipos aritmticos e o tipo enumerado designam-se por tipos
bsicos. Os tipos aritmticos inteiros podem armazenar valores negativos e positivos, que
o estado por defeito ou usando o qualificativo signed, ou em alternativa, podem
armazenar apenas valores positivos, usando para o efeito o qualificativo unsigned que lhe
duplica a gama dinmica positiva. O tipo aritmtico int pode ainda ser qualificado como
short, reduzindo-lhe a capacidade de armazenamento. O qualificativo long pode ser usado
para aumentar a capacidade de armazenamento do tipo inteiro int e do tipo real double.
Tipos de Dados Simples
Qualificativos
Aritmticos

Ponteiro

short

pointer
long
Tipos Inteiros

Tipos Reais

signed

int

char

float

double

Tipos Bsicos

Enumerado

unsigned

enum

Figura 1.6 - Tipos de dados simples existentes na linguagem C.

1.4.1 Tipos de dados inteiros


Na linguagem C existem os seguintes tipos de dados inteiros:
x O tipo char permite a representao de quantidades com sinal num byte e portanto,
permite armazenar valores entre -128 a 127.
x O tipo unsigned char permite a representao de quantidades sem sinal num byte e
portanto, permite armazenar valores entre 0 e 255.
x O tipo short [int] permite a representao de nmeros negativos e positivos em 2 bytes e
portanto, permite armazenar valores entre -32768 e 32767.
x O tipo unsigned short [int] permite a representao de nmeros positivos em 2 bytes e
portanto, permite armazenar valores entre 0 e 65535.
x O tipo int permite a representao de nmeros negativos e positivos em 2 bytes ou 4
bytes, consoante o computador.
x O tipo unsigned int permite a representao de nmeros positivos em 2 bytes ou 4 bytes,
consoante o computador.
x O tipo long [int] permite a representao de nmeros negativos e positivos em 4 bytes
ou 8 bytes, consoante o computador.
x O tipo unsigned long [int] permite a representao de nmeros positivos em 4 bytes ou
8 bytes, consoante o computador.

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

O tamanho dos tipos inteiros int e long e a sua gama dinmica dependem do compilador e
do hardware utilizados. Esta informao indicada no ficheiro limits.h localizado no
directrio include do ambiente de desenvolvimento. No caso do computador utilizado nesta
disciplina, cujo processador de 32 bits, os tipos int e unsigned int so representados em
4 bytes, pelo que, permitem armazenar respectivamente valores entre -2147483648 e
2147483647 e valores entre 0 e 4294967295. Os tipos long e unsigned long so tambm
representados em 4 bytes.

1.4.2 Tipos de dados reais


Na linguagem C existem os seguintes tipos de dados reais: float; double; e long double. O
tamanho, a preciso e a gama dinmica dos tipos reais dependem do compilador e do
hardware utilizados. Esta informao indicada no ficheiro float.h localizado no directrio
include do ambiente de desenvolvimento. Para o caso do computador utilizado nesta
disciplina, o tamanho, a preciso e a gama dinmica dos tipos reais so os seguintes:
x O tipo float utiliza 4 bytes, o que permite armazenar valores entre 1.2x10-38 e 3.4x1038,
com uma mantissa de 6-7 algarismos significativos.
x O tipo double utiliza 8 bytes, o que permite armazenar valores entre 2.2x10-308 e
1.8x10308, com uma mantissa de 15-16 algarismos significativos.
x O tipo long double utiliza 12 bytes, o que permite armazenar valores entre 3.4x10-4932 e
1.2x104932, com uma mantissa de 18-19 algarismos significativos.

1.4.3 Representao de caracteres e inteiros


A maioria das linguagens de programao, entre as quais se inclui o Pascal, faz a distino
entre o tipo inteiro e o tipo carcter. Mesmo, apesar de nessas linguagens, os caracteres
serem armazenados na memria em numrico, usando para o efeito o cdigo ASCII.
Na linguagem C no existe tal distino. O tipo char que utiliza um byte permite armazenar
quer um carcter quer um valor inteiro. A Figura 1.7 apresenta dois exemplos que
exemplificam esta polivalncia. No primeiro caso a atribuio do valor 65, que o cdigo
ASCII do carcter A, equivalente atribuio do prprio carcter 'A' varivel CAR. No
segundo caso a atribuio do valor 3 varivel NUM diferente da atribuio varivel
CAR do carcter '3', cujo cdigo ASCII 51. Devido a esta polivalncia de interpretao do
valor inteiro armazenado na varivel, o valor escrito no monitor depende do especificador
de formato que seja empregue na instruo de sada de dados. O formato %d representa o
valor decimal, enquanto que o formato %c representa o carcter.
char CAR;
/* declarao da varivel CAR de tipo char */
...
CAR = 'A';
/* ambas as atribuies armazenam */
CAR = 65;
/* na posio de memria CAR o valor 65 */

char NUM, CAR; /* declarao das variveis NUM e CAR de tipo char */
...
NUM = 3;
/* armazena na posio de memria NUM o valor 3 */
CAR = '3';
/* armazena na posio de memria CAR o valor 51 */

Figura 1.7 - Utilizao do tipo char.

CAPTULO 1 : INTRODUO AO C

1.5 Constantes e variveis


Uma constante um objecto, cujo valor se mantm invariante durante a execuo do
programa. A utilizao de um valor constante num programa, no fornece qualquer
indicao sobre o seu significado ou finalidade. Pelo que, a utilizao de uma mnemnica,
ou seja, um nome associado a um valor constante, permite aumentar a legibilidade de um
programa. Por outro lado, se um valor constante aparecer mais do que uma vez ao longo
do programa, pode acontecer que o programador cometa algum lapso na repetio do valor
e ter um erro algortmico que no detectvel pelo compilador e que pode ser muito difcil
de detectar pelo prprio programador. A utilizao de uma constante permite assim
parametrizar um programa, melhorar a legibilidade, j que os valores so substitudos por
nomes com significado explcito e, a robustez, porque a alterao do valor realizada de
um modo centralizado.
Ao contrrio do que se passa em Pascal, a linguagem C no contempla a definio explcita
de identificadores associados com constantes. Esta restrio pode ser ultrapassada, tal
como se apresenta na Figura 1.2, usando a seguinte directiva do pr-processador.
#define

identificador de constante

expresso

O que o pr-processador faz, ao encontrar esta directiva no ficheiro fonte, efectuar a


partir desse ponto a substituio de todas as ocorrncias do identificador de constante pela
expresso, o que se designa por uma macro de substituio. Como se trata de um processo
de substituio puro, e no de clculo do valor associado, torna-se necessrio, sempre que a
expresso no for um literal, coloc-la entre parnteses curvos para garantir o clculo
correcto do seu valor, independentemente do contexto em que est localizada.
#define

identificador de constante

( expresso )

Um dos erros mais frequentemente cometido, por programadores que se esto a iniciar na
utilizao da linguagem C, quando utilizam esta directiva na definio de um identificador
constante a sua terminao com o ;. Nesse caso o ; torna-se parte da substituio
podendo gerar situaes de erro.
As constantes numricas inteiras podem ser representadas no sistema decimal, no sistema
octal, em que a constante precedida pelo dgito 0 ou no sistema hexadecimal, em que a
constante precedida pelo dgito 0 e pelo carcter x. Quando as constantes esto
representadas nos sistemas octal ou hexadecimal, o sinal normalmente expresso de uma
maneira implcita, usando a representao em complemento verdadeiro. A Figura 1.8
apresenta alguns exemplos, considerando uma representao do tipo int em 32 bits.
Sistema decimal
54
-135
em complemento verdadeiro
0

Sistema octal
066
-0207
037777777571
00

Sistema hexadecimal
0x36
-0x87
0xFFFFFF79
0x0

Figura 1.8 - Exemplos de constantes inteiras.

O compilador atribui por defeito o tipo int a uma constante numrica inteira. Quando este
tipo no tem capacidade de armazenamento suficiente, os tipos seguintes so
sucessivamente atribudos. No caso de uma constante decimal, usa-se o tipo long, ou se
ainda for insuficiente o tipo unsigned long. No caso de uma constante octal ou
hexadecimal, usa-se pela seguinte ordem o tipo unsigned int, ou o tipo long, ou o tipo
unsigned long.

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

10

A atribuio do tipo pode ser forada pelos sufixos U para unsigned e L para long. Uma
constante seguida do sufixo U do tipo unsigned int ou do tipo unsigned long. Uma
constante seguida do sufixo L do tipo long ou do tipo unsigned long. Uma constante
seguida do sufixo UL do tipo unsigned long.
As constantes numricas reais so sempre expressas no sistema decimal, usando quer a
representao em parte inteira e parte fraccionria, quer a chamada notao cientfica. O
compilador atribui por defeito o tipo double a uma constante numrica real. A atribuio
do tipo pode ser forada pelos sufixos F e L. Uma constante seguida do sufixo F do tipo
float. Uma constante seguida do sufixo L do tipo long double. So exemplos de
constantes reais os valores 0.0148, 1.48e-2 e 0.0.
As constantes de tipo carcter podem ser expressas indiferentemente pelo respectivo
smbolo grfico, colocado entre aspas simples, ou atravs do valor do seu cdigo de
representao nos sistemas octal e hexadecimal, precedidas do carcter '\', tal como se
mostra na Figura 1.9.
Sistema decimal
'B'

Sistema octal
'\102'

Sistema hexadecimal
'\x42'

Figura 1.9 - Exemplo de constante de tipo carcter.

Para alguns caracteres de controlo, pode ainda ser usada uma representao alternativa que
consiste numa letra do alfabeto minsculo precedida do carcter '\'. Por exemplo o carcter
de fim de linha o '\n', o carcter de backspace o '\b', o carcter de tabulao o '\t', o
carcter aspas duplas o '\' e o carcter ponto de interrogao o '\?'.
As constantes de tipo cadeia de caracteres so expressas como uma sequncia de caracteres,
representados por qualquer dos mtodos anteriores, colocados entre aspas duplas. Por
exemplo Ola malta!\n.
Uma varivel um objecto, cujo valor se altera em princpio durante a execuo do
programa, excepo eventualmente feita s variveis de entrada, cujo valor depois de lido
do teclado , em princpio, mantido inalterado at ao fim da execuo do programa.
Todas as variveis usadas num programa tm que ser previamente definidas ou declaradas.
O objectivo da declarao simultaneamente a reserva de espao em memria para o
armazenamento dos valores que as variveis vo sucessivamente tomar, e a associao de
cada identificador com a rea de memria correspondente.
A Figura 1.10 apresenta a definio formal da declarao de variveis na linguagem C. Para
declarar variveis comea-se por identificar o tipo de dados seguido da varivel, ou lista de
variveis que se pretendem declarar desse tipo, separadas por vrgulas, terminando a
declarao com o separador ;. conveniente agrupar a declarao de variveis do mesmo
tipo na mesma linha para aumentar a legibilidade do programa.
A reserva de espao em memria no pressupe, em princpio, a atribuio de um valor
inicial varivel. Em consequncia, nada deve ser presumido sobre o seu valor antes que
uma primeira atribuio tenha sido efectivamente realizada. No entanto, a linguagem C
permite combinar a definio de uma varivel com a atribuio de um valor inicial, usando
para o efeito o operador de atribuio seguido da expresso de inicializao.

11

CAPTULO 1 : INTRODUO AO C

declarao de variveis ::= declarao de variveis de um tipo |


declarao de variveis |declarao de variveis de um tipo
declarao de variveis de um tipo ::= tipo de dados lista de variveis ;
tipo de dados ::= qualquer tipo de dados vlido na linguagem C
lista de variveis ::= identificador de varivel genrico |
lista de variveis , identificador de varivel genrico
identificador de varivel genrico ::= identificador de varivel |
identificador de varivel = expresso de inicializao
identificador de varivel ::= identificador vlido na linguagem C
Figura 1.10 - Definio formal da declarao de variveis.

Ao contrrio de outras linguagens de programao a linguagem C usa apenas um nico


smbolo, o smbolo =, como operador de atribuio. A diferena do operador de atribuio
em relao ao Pascal um dos erros mais frequentemente cometido por programadores
que se esto a iniciar na utilizao da linguagem C.
A regio de reserva de espao em memria principal no necessariamente contgua, pelo
que, frequente surgirem buracos resultantes do alinhamento das variveis, em reas de
endereos mltiplos de 4, para maximizar a taxa de transferncia de informao entre o
processador e a memria principal. A Figura 1.11 apresenta alguns exemplos de declarao
de variveis e sua colocao na memria.

A
char A, B = 'G';
unsigned int C = 1324;
double D = -2.5, E;

rea no reservada

'G'

1324

4 bytes

-2.5

8 bytes

4 bytes

rea reservada
mas no inicializada

rea reservada
e inicializada

8 bytes

Figura 1.11 - Alguns exemplos de declarao de variveis e sua colocao na memria.

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

12

1.6 Sequenciao
Tal como foi referido anteriormente, o corpo de uma funo delimitado pelos
separadores { e }, correspondentes, respectivamente, aos separadores begin e end do
Pascal, e contm a declarao das variveis locais, a aluso a funes usadas na sequncia
de instrues e a sequncia de instrues propriamente dita. A Figura 1.12 apresenta a
definio formal de uma sequncia de instrues. Ao contrrio do que se passa em Pascal,
na linguagem C cada instruo simples obrigatoriamente terminada com o separador ;, a
menos que o ltimo smbolo da instruo seja o separador }.
sequncia de instrues simples ::= instruo simples |
sequncia de instrues simples instruo simples
instruo simples ::= instruo de atribuio |
instruo decisria |
instruo repetitiva |
instruo de entrada-sada |
invocao de uma funo
Figura 1.12 - Definio formal da sequncia de instrues.

Em Pascal, a invocao de uma funo, sendo uma expresso, no pode ser considerada
uma instruo. Na linguagem C, contudo, o conceito de procedimento no tem uma
existncia separada. Define-se como sendo uma funo de um tipo especial, o tipo void.
Pelo que, a invocao de um procedimento , por isso, em tudo semelhante invocao de
uma funo de qualquer outro tipo, quando o valor devolvido no tido em considerao.
Logo, nestas circunstncias, na linguagem C, a pura e simples invocao de uma funo de
qualquer tipo considerada uma instruo.
A sequenciao de instrues a forma mais simples de controlo de fluxo num programa,
em que as instrues so executadas pela ordem em que aparecem no programa. Dentro
das instrues simples, apenas as instrues de atribuio, de entrada-sada e de invocao
de uma funo, so verdadeiramente instrues de sequenciao, j que, as instrues
decisrias e repetitivas permitem alterar a ordem do fluxo do programa.
Tal como no Pascal, na linguagem C tambm existe o conceito de instruo composta, cuja
definio formal se apresenta na Figura 1.13, e que composta por uma sequncia de
instrues simples encapsuladas entre os separadores { e }. Uma instruo composta um
bloco de instrues simples que se comporta como uma instruo nica e usada em
instrues decisrias e repetitivas.
instruo composta ::= { sequncia de instrues simples }
Figura 1.13 - Definio formal da instruo composta.

1.6.1 Expresses
A Figura 1.14 apresenta a definio formal de uma expresso. Uma expresso uma
frmula que produz um valor. Pode assumir as seguintes formas: ser uma constante; ser
uma varivel; ser o resultado da invocao de uma funo; ser uma expresso composta
por operandos e operadores, sendo que existem operadores unrios e binrios; e ser uma
expresso entre parnteses curvos.

13

CAPTULO 1 : INTRODUO AO C

expresso ::= constante | varivel | invocao de uma funo |


operador unrio expresso | expresso operador unrio
expresso operador binrio expresso | ( expresso )
Figura 1.14 - Definio formal de uma expresso.

Os primeiros trs tipos de expresses so a frmula mais simples de produzir um valor e


resumem-se atribuio a uma varivel, do valor de uma constante, ou do valor de uma
varivel ou do valor devolvido por uma funo. As expresses mais complexas envolvem
operadores unrios ou binrios. Uma expresso pode ser composta por uma expresso
colocada entre parnteses curvos. Este tipo de expresso usa-se para compor expresses
mais complexas, ou para modificar a prioridade do clculo de expresses parcelares.
Vamos agora analisar as expresses aritmticas. O clculo de uma expresso complexa
supe um processo de decomposio prvio em expresses mais simples, que
determinado pela precedncia e pela associatividade dos operadores presentes.
Precedncia significa importncia relativa entre os operadores. Operadores de maior
precedncia foram a ligao a si dos operandos, antes dos operadores de menor
precedncia Por exemplo, como a multiplicao tem precedncia sobre a adio, ento a
expresso a + b * c tem subjacente o agrupamento a + (b * c).
Quando numa expresso todos os operadores tm a mesma precedncia, a associatividade
permite determinar a ordem de ligao dos operandos aos operadores, da direita para a
esquerda, ou da esquerda para a direita. Por exemplo, como a associatividade da adio da
direita para a esquerda, ento a expresso a + b + c tem subjacente o agrupamento
(a + b) + c.
No entanto, qualquer que seja o agrupamento imposto pela precedncia e pela
associatividade dos operadores presentes, ele pode ser sempre alterado pela introduo de
parnteses curvos.
No caso de aritmtica inteira, sempre que numa expresso surgem constantes, variveis, ou
se invocam funes de tipo char ou short, os seus valores so automaticamente
convertidos pelo compilador em quantidades de tipo int. Do mesmo modo, constantes,
variveis, ou funes de tipo unsigned char ou unsigned short, so automaticamente
convertidas pelo compilador em quantidades de tipo int, ou de tipo unsigned int, se o
primeiro tipo no tiver capacidade de armazenamento suficiente. No caso de aritmtica
real, sempre que numa expresso surgem constantes, variveis, ou se invocam funes de
tipo float, os seus valores so automaticamente convertidos pelo compilador em
quantidades de tipo double.
Os operadores binrios supem normalmente operandos do mesmo tipo. Quando so de
tipo diferente, a expresso do tipo com menor capacidade de armazenamento,
automaticamente convertida no tipo da outra expresso. A hierarquia dos diferentes tipos
de dados, expressa por ordem decrescente da sua capacidade de armazenamento, a que se
apresenta a seguir.

long double o double o float o unsigned long o long o unsigned int o int
Alm das converses automticas que foram referidas, a converso de uma expresso num
tipo especfico qualquer pode ser sempre forada atravs do operador cast.
( qualquer tipo de dados escalar vlido em C ) ( expresso numrica )

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

14

A Figura 1.15 apresenta um exemplo da utilizao do operador cast. A diviso de duas


variveis inteiras d um resultado inteiro. Para forar a diviso real preciso forar um dos
operandos a double, fazendo um cast de um dos operandos, neste caso da varivel A.
int A = 5, B = 2; double DIVISAO;
...
DIVISAO = A / B;
/* DIVISAO = 2.0 */

DIVISAO = (double) A / B;
/* DIVISAO = 2.5 */

Figura 1.15 - Exemplo da utilizao do operador cast.

Se a converso se efectua no sentido crescente da hierarquia, no h risco de overflow ou de


perda de preciso. Caso contrrio, estes problemas podem ocorrer. Concretamente, quando
a converso se efectua no sentido decrescente da hierarquia, podemos ter as situaes
apresentadas na Figura 1.16.
Tipos de dados de partida
Tipos de dados de chegada
long double
double ou float
existe arredondamento e portanto pode ocorrer overflow
double
float
existe arredondamento e portanto pode ocorrer overflow
qualquer tipo real
qualquer tipo inteiro
existe truncatura e portanto pode ocorrer overflow
tipo inteiro signed (unsigned)
mesmo tipo inteiro unsigned (signed)
mudana na interpretao do valor
tipo inteiro unsigned
tipo hierarquicamente inferior unsigned
resto do mdulo do registo de chegada
tipo inteiro signed

tipo hierarquicamente inferior signed


pode ocorrer overflow

Figura 1.16 - Situaes possveis se a converso se efectuar no sentido decrescente da hierarquia.

Vamos agora apresentar alguns exemplos representativos destes problemas. A Figura 1.17
apresenta um exemplo da situao em que se atribui um valor negativo de uma varivel int
a uma varivel unsigned int. O valor armazenado na memria em binrio vai ser
interpretado como sendo positivo, pelo que, h uma mudana na interpretao do valor.
int A = -1024;
...
B = A;

unsigned int B;

/* A = -102410 = 1111 1111 1111 1111 1111 1100 0000 00002 */


/* B = 1111 1111 1111 1111 1111 1100 0000 00002 = 429496627210*/

Figura 1.17 - Mudana na interpretao do valor.

A Figura 1.18 apresenta um exemplo da situao em que se atribui uma varivel unsigned
int a uma varivel unsigned char, que tem uma menor capacidade de armazenamento. O
valor que vai ser armazenado em B constitudo pelos ltimos 8 bits de A, ou seja, o resto
da diviso de A por 256, que o mximo valor que se pode armazenar num byte.

15

CAPTULO 1 : INTRODUO AO C

unsigned int A = 1025;


...
B = A;

unsigned char B;

/* A = 102510 = 0000 0000 0000 0000 0000 0100 0000 00012 */


/* B = 0000 00012 = 110*/

Figura 1.18 - Resto do mdulo do registo de chegada.

A Figura 1.19 apresenta um exemplo da situao em que se atribui uma varivel int a uma
varivel char, que tem uma menor capacidade de armazenamento. Como o valor de A
excede a capacidade de armazenamento de B, ento temos uma situao de overflow. Esta
situao facilmente detectada, uma vez que, o valor de chegada negativo, quando o
valor de partida era positivo.
int A = 1152;
...
B = A;

char B;

/* A = 102510 = 0000 0000 0000 0000 0000 0100 1000 00002 */


/* B = 1000 00002 = -12810*/

Figura 1.19 - Ocorrncia de overflow.

No clculo de uma expresso complexa, uma vez fixado o agrupamento, segundo as regras
da prioridade e da associatividade dos operadores envolvidos, modificadas ou no pela
introduo de parnteses curvos, a ordem pela qual o compilador calcula as diferentes
subexpresses em larga medida arbitrria. O compilador pode mesmo reorganizar a
expresso, se isso no afectar o resultado final. Em geral, esta questo no acarreta
consequncias graves.
Contudo, sempre que o clculo de uma expresso envolva operadores com efeitos
colaterais, ou seja, uma expresso em que o valor de uma ou mais variveis afectado pelo
processo de clculo, normalmente devido utilizao dos operadores unrios incremento e
decremento, o cdigo resultante pode deixar de ser portvel. Assim, de extrema
importncia organizar cuidadosamente a formao das expresses para se evitar que tais
situaes ocorram.
Quando no clculo de uma expresso existe o risco de ocorrncia de overflow em resultado
de uma possvel transformao da expresso numa equivalente, tal como se mostra na
Figura 1.20, isso deve ser impedido por decomposio do clculo da expresso em duas ou
mais expresses parcelares, seno o cdigo resultante pode deixar de ser portvel.
int X, K1 = 1024, K2 = 4096, K3 = 4094;
...
X = K1 * K1 * (K2 K3);
/* se o compilador transformar a expresso na expresso */
/* aparentemente equivalente X = K1 * K1 * K2 K1 * K1 * K3 */
/* vai ocorrer overflow para uma representao int em 32 bits */

Figura 1.20 - Exemplo de uma expresso onde existe o risco de ocorrncia de overflow.

A linguagem C permite ainda a construo de um tipo especial de expresso, cuja finalidade


fornecer o tamanho em bytes do formato de um tipo de dados particular. Esse tipo pode
ser representado, explicitamente, pelo seu identificador, ou, implicitamente, por qualquer
expresso desse tipo.

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

16

sizeof ( qualquer tipo de dados vlido em C ) ou sizeof ( expresso )


No segundo caso, o valor da expresso nunca calculado, sendo unicamente determinado
o seu tipo. A norma ANSI exige que o tipo do resultado do operador sizeof seja do tipo
inteiro e unsigned, ou seja, do tipo unsigned int ou do tipo unsigned long. Este tipo o
tipo size_t que est definido na biblioteca stdlib.

1.6.2 Operadores
A Figura 1.21 apresenta os operadores aritmticos disponveis na linguagem C.
Operadores Unrios
Operador
Simtrico
Incremento de 1

Smbolo
+
++

Decremento de 1

--

Sintaxe
+x
-x
++x
x++
--x
x--

Observaes

x tem que ser


uma varivel
x tem que ser
uma varivel

Operadores Binrios
Operador
Smbolo
Sintaxe
Observaes
Adio
+
x + y
Subtraco
x - y
Multiplicao
*
x * y
Diviso
/
x / y
y 0
Resto da diviso inteira
%
x % y
y 0
e x e y tm de ser expresses inteiras

Figura 1.21 - Operadores aritmticos.

Os operadores unrios incremento e decremento, s podem ser usados com variveis e,


incrementam e decrementam o valor da varivel de uma unidade. Podem ser colocados
antes da varivel, o que se designa por pr-incremento e pr-decremento. Nesse caso o
valor da varivel alterado antes da sua utilizao na expresso em que a varivel est
inserida. Ou, podem ser colocados depois da varivel, o que se designa por ps-incremento
e ps-decremento. Nesse caso o valor da varivel s alterado depois da sua utilizao na
expresso em que a varivel est inserida.
A Figura 1.22 apresenta um exemplo da utilizao do operador unrio incremento. Na
primeira expresso, uma vez que estamos perante o ps-incremento de X, ento no clculo
de Y utilizado o valor de X antes deste ser incrementado, pelo que, Y fica com o valor 10
e s depois que o valor de X incrementado. Na segunda expresso, uma vez que
estamos perante o pr-incremento de X, ento em primeiro lugar X incrementado e s
depois calculado o valor de Y, pelo que, Y fica com o valor 15. Em ambas as expresses
o valor final de X igual a 3.
int X = 2, Y;
...
Y = 5 * X++;
/* aps o clculo da expresso X = 3 e Y = 10 */

Y = 5 * ++X;
/* aps o clculo da expresso X = 3 e Y = 15 */

Figura 1.22 - Exemplo da utilizao do operador unrio incremento.

17

CAPTULO 1 : INTRODUO AO C

A Figura 1.23 apresenta uma expresso que utiliza o operador ps-incremento


incorrectamente, uma vez que, o cdigo resultante pode deixar de ser portvel. Neste tipo
de expresso impossvel prever o valor final da expresso, uma vez que tal depende da
ordem de clculo dos operandos. Pelo que, quando numa expresso existe um operador
com efeito colateral, a varivel afectada s pode ser usada uma e uma nica vez.
int X, K = 3;
...
X = K * K++;
/* se a ordem de clculo for da esquerda para a direita, X = 9 */
/* se a ordem de clculo for da direita para a esquerda, X = 12 */

Figura 1.23 - Exemplo de uma expresso com utilizao incorrecta do operador ps-incremento.

O operador diviso usado simultaneamente para representar o quociente da diviso


inteira e da diviso real. Tal como se mostra na Figura 1.15, tratar-se- de uma diviso
inteira, sempre que os operandos forem inteiros e tratar-se- de uma diviso real, quando
pelo menos um dos operandos for real.
Quando as expresses do quociente e do resto da diviso inteira so quantidades negativas,
o resultado no univocamente determinado e depende do compilador utilizado. No caso
do quociente da diviso inteira, a norma ANSI possibilita dois tipos de aproximao, a
truncatura ou a aproximao ao maior inteiro, menor ou igual ao correspondente quociente
real. Se os operandos so ambos positivos ou negativos, o resultado obtido pelos dois
mtodos idntico. Se um dos operandos negativo, tal no se passa. Por exemplo, 7/(-3)
ou -7/3, tanto pode produzir um quociente de -2, como de -3. Para evitar esta ambiguidade
e garantir-se a portabilidade, sempre que haja a possibilidade do quociente ser negativo, a
operao x/y deve ser substituda, por exemplo, pela expresso seguinte, de maneira a
forar a aproximao por truncatura.
(tipo inteiro) ((double) x/y)
Para o resto da diviso inteira a norma ANSI impe que se verifique a seguinte condio.
x = x % y + (x / y) * y
Isto significa que, conforme o tipo de aproximao usado em x/y e a localizao do
operando negativo, diferentes restos so possveis quando um dos operandos negativo.
7 % (-3) = 1 -7 % 3 = -1
7 % (-3) = -2 -7 % 3 = 2

(truncatura)
(aproximao ao maior inteiro menor ou igual)

Na prtica, o problema no to grave, porque a expresso x%y no faz matematicamente


sentido para y negativo e, por isso, no deve nunca ser usada neste contexto. Alm disso,
quando x negativo, fcil verificar que os dois resultados possveis so congruentes.
Contudo, para se obter uma portabilidade completa, conveniente substituir a expresso
x%y, pela construo condicional seguinte.
if (x >= 0) r = x % y ;
else r = y - (-x) % y ;
A Figura 1.24 apresenta os operadores relacionais disponveis na linguagem C. Em relao
ao Pascal existe apenas diferena nos operadores de igualdade e de desigualdade.

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

Operador
Igual
Diferente
Maior
Menor
Maior ou igual
Menor ou igual

Smbolo
==
!=
>
<
>=
<=

18

Sintaxe
x == y
x != y
x > y
x < y
x >= y
x <= y

Figura 1.24 - Operadores relacionais.

A expresso resultante de tipo int e assume o valor zero, se o resultado da comparao


for falso, e o valor um, se o resultado for verdadeiro. Ao contrrio do que se passa
geralmente, a precedncia dos diversos operadores no a mesma. Os operadores maior
ou igual, menor ou igual, maior e menor tm maior precedncia do que os operadores igual
e diferente. Dado que os tipos reais fornecem apenas uma representao aproximada das
quantidades numricas, nunca deve ser usado o operador igualdade com operandos desse
tipo. Mesmo os operadores maior ou igual e menor ou igual devem ser usados com
extremo cuidado. A expresso booleana de terminao de um processo de contagem,
nomeadamente, nunca deve ser formada com expresses de tipo real.
A Figura 1.25 apresenta os operadores lgicos que se aplicam tanto a expresses numricas
inteiras como a reais. Os operandos so interpretados como representando o valor falso, se
forem iguais a zero, e como representando o valor verdadeiro, em todos os restantes casos.
A expresso resultante de tipo int e assume o valor zero, se o resultado da comparao
for falso, e o valor um, se o resultado for verdadeiro. Ao contrrio do Pascal, uma
expresso formada por operadores lgicos nem sempre calculada at ao fim. O clculo
procede da esquerda para a direita e o processo interrompido logo que o resultado esteja
definido.
Operador Unrio
Operador
Negao (not)

Smbolo
!

Sintaxe
!x

Operadores Binrios
Operador
Conjuno (and)
Disjuno inclusiva (or)

Smbolo
&&
||

Sintaxe
x && y
x || y

Figura 1.25 - Operadores lgicos.

A Figura 1.26 apresenta os operadores para manipulao de bits que se aplicam apenas a
expresses numricas inteiras.
Operador Unrio
Operador
Complemento (not)

Smbolo
~

Sintaxe
~x

Operadores Binrios
Operador
Conjuno (and)
Disjuno inclusiva (or)
Disjuno exclusiva (xor)
Deslocamento direita
Deslocamento esquerda

Smbolo
&
|
^
>>
<<

Figura 1.26 - Operadores de manipulao de bits.

Sintaxe
x & y
x | y
x ^ y
x >> y
x << y

19

CAPTULO 1 : INTRODUO AO C

Os operadores lgicos actuam isoladamente sobre cada um dos bits dos operandos. Para os
operadores lgicos binrios, as operaes so efectuadas em paralelo sobre os bits
localizados em posies correspondentes de cada um dos operandos.
O resultado da operao uma quantidade inteira do tipo do operando com maior
capacidade de armazenamento, no caso dos operadores lgicos binrios, ou do tipo do
operando x, no caso do operador complemento booleano ou dos operadores de
deslocamento. Para os operadores de deslocamento, o operando y representa o nmero de
posies a deslocar no sentido pretendido. O resultado da operao no est definido,
quando y maior ou igual do que o comprimento em bits do operando x, ou negativo. A
norma ANSI impe a realizao de um deslocamento lgico, quando x for de um tipo
qualquer unsigned. Contudo, nada garantido quando x for de um tipo signed, embora
normalmente o deslocamento seja ento aritmtico. Assim, para se obter uma portabilidade
completa, deve fazer-se sempre um cast para tipos unsigned.
(unsigned int ou unsigned long) x >> y
(unsigned int ou unsigned long) x << y
A Figura 1.27 apresenta a tabela de associatividade e de precedncia, por ordem
decrescente, entre os operadores das expresses numricas.
Operadores na classe

Associatividade

Precedncia

operadores unrios
operador cast
operador sizeof
+
++ ! ~

direita
direita
direita
direita

o
o
o
o

esquerda
esquerda
esquerda
esquerda

maior

operadores binrios
* / %
+
>> <<
>= <= >
=! ==
&
^
|
&&
||

<

esquerda
esquerda
esquerda
esquerda
esquerda
esquerda
esquerda
esquerda
esquerda
esquerda

o
o
o
o
o
o
o
o
o
o

direita
direita
direita
direita
direita
direita
direita
direita
direita
direita

menor

Figura 1.27 - Precedncia e associatividade entre os operadores das expresses numricas.

1.6.3 Instrues de atribuio


Ao contrrio do Pascal que s tem uma forma da instruo de atribuio, na linguagem C
existem as quatro variantes da instruo de atribuio que se apresentam na Figura 1.28.
Regra geral, os tipos da varivel e da expresso podem ser quaisquer, desde que sejam tipos
escalares. Aps o clculo da expresso e antes que a atribuio tenha lugar, o valor da
expresso automaticamente convertido para o tipo da varivel.

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

20

instruo de atribuio ::= identificador de varivel = expresso ; |


identificador de varivel operador binrio= expresso ; |
operador unrio inc_dec identificador de varivel ; |
identificador de varivel operador unrio inc_dec ; |
identificador de varivel = expresso-1 ? expresso_2 : expresso_3 ;
operador unrio inc_dec ::= operador unrio ++ | operador unrio
Figura 1.28 - Definio formal da instruo de atribuio.

A primeira variante da instruo de atribuio a instruo de atribuio tpica das


linguagens imperativas, em que se atribui o valor de uma expresso a uma varivel.
semelhante encontrada em Pascal. A nica diferena que o operador de atribuio ,
neste caso o =, em vez de :=. A Figura 1.29 apresenta trs exemplos.
int
...
Y =
X =
Z =

X, Y, Z;
X + 2 * Z;
X + 1;
pow (Y, 2);

/* pow a funo potncia, donde Z = Y2 */

Figura 1.29 - Exemplos da primeira variante da instruo de atribuio.


A segunda variante uma construo tpica da sintaxe compacta da linguagem C, onde o
operador de atribuio precedido por um qualquer operador binrio. Esta variante
definida pela expresso geral operador binrio=, em que o operador binrio representa
qualquer operador binrio aritmtico (*, /, %, +, ), ou de manipulao de bits
(&, |, ^, >>, <<). A instruo equivalente operao binria da expresso com a prpria
varivel como segundo operando, ou seja, equivalente seguinte instruo de atribuio.
identificador de varivel = identificador de varivel operador binrio expresso
A importncia desta notao tem a ver no s com uma maior clareza na indicao da
operao a realizar, particularmente quando o identificador representa um campo de uma
varivel de tipo complexo, como tambm com a possibilidade fornecida ao compilador de
proceder mais facilmente a uma optimizao do cdigo gerado. A Figura 1.30 apresenta
dois exemplos.
int X, Y, Z;
...
Y += 5;
Z *= 5 + X;

/* equivalente a Y = Y + 5 */
/* equivalente a Z = Z * (5 + X) */

Figura 1.30 - Exemplos da segunda variante da instruo de atribuio.

A terceira variante a utilizao dos operadores unrios incremento e decremento sobre


uma varivel. Independentemente se serem utilizados na situao de pr ou ps actuao
sobre a varivel, os efeitos colaterais apresentados por este tipo de operadores resultam no
incremento ou decremento de uma unidade ao valor da varivel. As instrues
++identificador de varivel; e identificador de varivel ++; so equivalentes seguinte instruo.
identificador de varivel = identificador de varivel + 1 ;
E, as instrues identificador de varivel; e identificador de varivel; so equivalentes
seguinte instruo.
identificador de varivel = identificador de varivel 1 ;

21

CAPTULO 1 : INTRODUO AO C

A importncia desta notao tem a ver de novo com a compactao resultante e com a
possibilidade fornecida ao compilador de proceder mais facilmente a uma optimizao do
cdigo gerado. A Figura 1.31 apresenta dois exemplos.
int X, Y;
...
Y++;
--X;

/* equivalente a Y = Y + 1 */
/* equivalente a X = X - 1 */

Figura 1.31 - Exemplos da terceira variante da instruo de atribuio.

A quarta variante uma instruo de atribuio condicional, onde o valor da primeira


expresso avaliada e caso seja verdadeira, ento atribudo o valor resultante do clculo
da segunda expresso varivel, seno atribudo o valor resultante do clculo da terceira
expresso varivel. Comporta-se assim como a instruo condicional binria if then else
e equivalente ao cdigo em linguagem C que se apresenta a seguir.
if (expresso_1) identificador de varivel = expresso_2 ;
else identificador de varivel = expresso_3 ;
Em termos formais, trata-se de um caso particular da primeira variante em que a expresso
a indicada do tipo expresso_1 ? expresso_2 : expresso_3, ou seja, o agrupamento das trs
expresses pelo operador ? :. Este operador constitui o nico exemplo de operador
ternrio existente na linguagem C. Enquanto que a segunda e a terceira expresses podem
ser de qualquer tipo vlido na linguagem C, desde que compatveis, segundo as regras de
converso, com o tipo da varivel, a primeira expresso de um tipo escalar bsico. A
precedncia deste operador surge imediatamente abaixo dos operadores descritos at ao
momento e a sua associatividade da direita para a esquerda. A Figura 1.32 apresenta um
exemplo.
int X, ABSX;
...
ABSX = X < 0 ? X : X;
/* equivalente a if (X < 0)

then ABSX = -X;

else ABSX = X; */

Figura 1.32 - Exemplo da quarta variante da instruo de atribuio.

Uma caracterstica notvel da Linguagem C que a instruo de atribuio tambm uma


expresso. O seu valor o valor atribudo ao operando da esquerda, ou seja, ao
identificador de varivel. O que faz com que os operadores = e operador binrio=
apresentem uma precedncia imediatamente abaixo do operador condicional e uma
associatividade igualmente da direita para a esquerda. Tornam-se por isso possveis
instrues de atribuio mltipla, cuja sintaxe a que se apresenta a seguir.
identificador de varivel_1 = ... = identificador de varivel_N = expresso ;
Note-se que, devido associatividade e regra geral de converso automtica, relevante a
ordem pela qual surgem na instruo as variveis, quando pertencentes a tipos escalares
diferentes. Qualquer alterao a esta ordem pode conduzir a valores distintos atribudos a
algumas delas. tambm perfeitamente possvel substituir, em qualquer ponto da cadeia de
atribuies, o operador = pelo operador operador binrio=, numa das suas mltiplas
formas. Isto, contudo, no deve nunca ser feito, porque a interpretao da instruo
resultante torna-se muito difcil e, portanto, muito sujeita a erros. A Figura 1.33 apresenta
dois exemplos de instrues de atribuio mltipla.

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

int X, Y, Z;
...
X = Y = Z;
X += Y -= Z;

22

/* equivalente a X = (Y = Z); */
/* equivalente a X = (X + (Y = (Y-Z)); */

Figura 1.33 - Exemplos de instrues de atribuio mltipla.

A Figura 1.34 apresenta a precedncia e a associatividade entre os operadores de atribuio.


Operadores na classe
operador condicional ? :
= += -= *= /= %=
>>= <<= &= ^= |=

Associatividade
direita o esquerda

Precedncia
maior

direita o esquerda

menor

Figura 1.34 - Precedncia e associatividade entre os operadores de atribuio.

1.7 Estruturas de controlo


1.7.1 Instrues decisrias
Na linguagem C existem dois tipos de instrues de tomada de deciso. A instruo
decisria binria if e a instruo decisria mltipla switch.

1.7.1.1 A instruo decisria binria if


A instruo decisria binria if (se), cuja definio formal se apresenta na Figura 1.35, tem
duas variantes que so fundamentalmente semelhantes s encontradas em Pascal. A nica
diferena reside no facto do separador then no surgir aqui. Em consequncia, a expresso
decisria obrigatoriamente colocada entre parnteses curvos para estabelecer a separao
da instruo a executar. A expresso decisria deve ser de um tipo escalar bsico. A
expresso falsa se for igual a zero e verdadeira se assumir qualquer outro valor. Nestas
condies, o compilador aceita qualquer expresso numrica como expresso decisria
vlida. Porm, por questes de clareza, isto deve ser evitado. de bom estilo que a
expresso decisria represente sempre uma expresso booleana.
instruo decisria binria ::= if ( expresso ) instruo simples ou composta |
if ( expresso ) instruo simples ou composta
else instruo simples ou composta
expresso ::= expresso decisria de um tipo escalar bsico
Figura 1.35 - Definio formal da instruo if.

Uma questo muito importante para editar programas legveis o alinhamento das
instrues. A Figura 1.36 apresenta como se deve alinhar a instruo if. No caso da
variante mais simples e se existir apenas uma instruo simples curta, ento a instruo if
pode ser toda escrita na mesma linha, mas se a instruo simples for longa deve ser escrita
na linha seguinte mais alinhada para a direita. Caso a instruo seja composta, ento os
separadores { e } devem ser alinhados com o if. No caso da variante completa, devemos
alinhar o separador else com o if.

23

CAPTULO 1 : INTRODUO AO C

if ( expresso ) instruo simples;

if ( expresso )
{
instruo simples;
...
instruo simples;
}

if ( expresso )
{
instruo simples;
...
instruo simples;
}
else
{
instruo simples;
...
instruo simples;
}

Figura 1.36 - Alinhamento da instruo if.

A Figura 1.37 apresenta um encadeamento de instrues if, o que se designa por


instrues if encadeadas (nested if structures). Neste tipo de agrupamento, a regra de
agrupamento semelhante de Pascal. O compilador associa sempre o separador else
instruo if que ocorreu imediatamente antes.
if

((CAR >= 'A') && (CAR <= 'Z'))


printf ("Carcter Maiusculo\n");
else if ((CAR >= 'a') && (CAR <= 'z'))
printf ("Carcter Minusculo\n");
else if ((CAR >= '0') && (CAR <= '9'))
printf ("Carcter Numerico\n");
else printf ("Outro Carcter\n");

Figura 1.37 - Construo de instrues if encadeadas.

Vamos agora considerar a situao apresentada na Figura 1.38, em que queremos ter o
separador else para o primeiro if, mas em que o segundo if no o tem. Neste tipo de
situao, que se designa por else desligado (dangling else), o separador else vai ser atribudo
pelo compilador ao segundo if, independentemente de ter sido alinhado com o primeiro if.
if (V1 > 0)
if (V2 > 0)
else V1++;

V2++;
/* situao do else desligado */

Figura 1.38 - Situao do else desligado.

Para resolver este problema existem as duas solues apresentadas na Figura 1.39. A
primeira, consiste em usar uma instruo composta a abraar o segundo if, de maneira a
informar o compilador onde acaba o segundo if. A segunda, consiste em usar um
separador else com uma instruo nula, para emparelhar com o segundo if, e assim forar
o emparelhamento do segundo separador else com o primeiro if. A instruo nula o ;.

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

if
{

24

(V1 > 0)

if (V2 > 0) V2++;


}
else V1++;

if (V1 > 0)
if (V2 > 0) V2++;
else ;
else V1++;

Figura 1.39 - Solues para a situao do else desligado.

Devido ao facto das instrues de atribuio constiturem tambm expresses, um erro


muito frequentemente cometido por programadores que se esto a iniciar na utilizao da
linguagem C e que no sinalizado pelo compilador, consiste em trocar o operador
identidade pelo operador de atribuio na escrita da condio decisria, tal como se
apresenta na Figura 1.40. O valor da expresso A = 5 5 e como um valor diferente de
zero, ento a expresso verdadeira e vai ser executada a instruo B = 2. Pelo que, aps a
execuo da instruo, as variveis A e B vo assumir respectivamente, os valores 5 e 2.
if (A = 5) B = 2;
else B = 4;

/* o que se pretendia era if (A == 5)

B = 2; */

Figura 1.40 - Erro devido troca do operador identidade pelo operador de atribuio.

1.7.1.2 A instruo decisria mltipla switch


Muitas das situaes de estruturas if encadeadas podem ser resolvidas atravs da instruo
decisria mltipla switch (comutador), cuja definio formal se apresenta na Figura 1.41.
instruo decisria mltipla ::= switch ( expresso )
{
bloco de execuo
}
bloco de execuo ::= bloco de execuo selectivo |
bloco de execuo bloco de execuo terminal
bloco de execuo selectivo ::= lista de constantes sequncia de instrues simples |
bloco de execuo selectivo
lista de constantes sequncia de instrues simples
lista de constantes ::= case constante inteira : |
lista de constantes
case constante inteira :
bloco de execuo terminal ::= default : sequncia de instrues simples
Figura 1.41 - Definio formal da instruo switch.

Embora as palavras reservadas na linguagem C sejam distintas das do Pascal, a estrutura


sintctica da instruo de deciso mltipla essencialmente a mesma nas duas linguagens.
Na norma ANSI, a expresso de deciso, bem como as constantes que formam a lista de
constantes, so de qualquer tipo escalar bsico inteiro. A principal diferena decorre do
modo como os vrios ramos de seleco esto organizados. Em Pascal, eles so
mutuamente exclusivos, enquanto que, na linguagem C, a execuo sequencial a partir do

25

CAPTULO 1 : INTRODUO AO C

ponto de entrada. Para se conseguir o mesmo tipo de execuo encontrado em Pascal, a


ltima instruo de cada sequncia de instrues ter que ser obrigatoriamente a instruo
break, tal como se mostra na Figura 1.43.
A Figura 1.42 apresenta como se deve alinhar a instruo. Para aumentar a legibilidade, o
bloco de execuo selectivo e o bloco de execuo terminal devem ser alinhados mais
direita. As sequncias de instrues simples devem ser todas alinhadas, permitindo uma
melhor anlise das instrues que vo ser executadas.
switch ( expresso )
{
case V1 : instruo
case V2 : instruo
break;
case V3 : instruo
instruo
break;
case V4 : instruo
break;
default : instruo
}

simples;
simples;
simples;
simples;
simples;
simples;

Figura 1.42 - Exemplo da utilizao e alinhamento da instruo switch.

A Figura 1.43 faz a comparao da instruo switch com a instruo case.


case CAR of
/* na linguagem Pascal */
'a', 'e', 'o' : writeln ('Vogais speras');
'i', 'u'
: writeln ('Vogais doces');
else
writeln ('Outros smbolos grficos')
end;

switch (CAR)
/* na linguagem C */
{
case 'a' :
case 'e' :
case 'o' : printf ("Vogais speras\n");
break;
case 'i' :
case 'u' : printf ("Vogais doces\n");
break;
default : printf ("Outros smbolos grficos\n");
}

Figura 1.43 - Comparao da instruo switch com a instruo case.

preciso ter em considerao que a instruo switch no to poderosa como a instruo


case do Turbo Pascal, uma vez que a ltima permite que a lista de valores enumerada
possa ser constituda por um literal, ou por um conjunto de literais separados pela vrgula,
ou por um intervalo de valores, ou por combinaes de todas estas situaes.

1.7.2 Instrues repetitivas


Tal como na linguagem Pascal, na linguagem C existem dois tipos de instrues de
repetio. As instrues while e do while, cujo nmero de iteraes previamente
desconhecido, tm uma estrutura de controlo condicional. A instruo for, cujo nmero de
iteraes previamente conhecido, tem normalmente uma estrutura de controlo contadora.

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

26

1.7.2.1 As instrues repetitivas while e do while


A Figura 1.44 apresenta a definio formal das instrues while (enquanto fazer), e do
while (fazer enquanto).
instruo while ::= while ( expresso )
instruo simples ou composta

instruo do while ::= do


{
sequncia de instrues simples
} while ( expresso ) ;
Figura 1.44 - Definio formal das instrues while e do while.

As instrues while e do while correspondem, respectivamente, s sintaxes while do e


repeat until encontradas no Pascal. A expresso decisria deve ser de um tipo escalar
bsico. A expresso falsa se for igual a zero e verdadeira se assumir qualquer outro
valor. Nestas condies, o compilador aceita qualquer expresso numrica como expresso
decisria vlida. Porm, por questes de clareza, isto deve ser evitado. de bom estilo que
a expresso decisria represente sempre uma expresso booleana. Ao contrrio da
instruo repeat until do Pascal, a sequncia de instrues simples da instruo do while
colocada entre os separadores { e }.
No entanto, existe uma diferena semntica importante entre a instruo repeat until do
Pascal e a instruo do while da linguagem C. Em Pascal, o ciclo repetitivo executado at
a expresso decisria de terminao ser verdadeira. Mas, na linguagem C, o ciclo repetitivo
executado enquanto a expresso decisria de terminao for verdadeira. Ou seja, em
situaes equivalentes, uma necessariamente a negao da outra. Tal como em Pascal, a
utilizao correcta de qualquer das instrues supe que a expresso decisria de
terminao possa ser modificada, durante a execuo do ciclo repetitivo, para que o seu
valor passe eventualmente de verdadeiro a falso. Caso contrrio, o ciclo repetitivo seria
infinito. preciso igualmente garantir que todas as variveis que constituem a expresso
decisria so previamente inicializadas.
A Figura 1.45 apresenta como se deve alinhar a instruo while. No caso da variante mais
simples e se existir apenas uma instruo simples curta, ento a instruo while pode ser
toda escrita na mesma linha, mas se a instruo simples for longa deve ser escrita na linha
seguinte mais alinhada para a direita. No caso da variante, em que, o corpo do ciclo
repetitivo constitudo por uma instruo composta, os separadores { e } que definem a
instruo composta devem ser alinhadas pela palavra reservada while, e a sequncia de
instrues simples so escritas, uma por linha, todas alinhadas mais direita de maneira a
que seja legvel onde comea e acaba o ciclo repetitivo.
while ( expresso ) instruo simples;

while ( expresso )
{
instruo simples;
...
instruo simples;
}

Figura 1.45 - Alinhamento da instruo while.

27

CAPTULO 1 : INTRODUO AO C

A Figura 1.46 apresenta como se deve alinhar a instruo do while. As palavras reservadas
do e while devem ser alinhadas e a condio booleana de terminao deve ser escrita
frente do terminador while. As instrues que constituem o corpo do ciclo repetitivo so
escritas uma por linha e todas alinhadas mais direita de maneira a que seja legvel onde
comea e acaba o ciclo repetitivo.
do
{
instruo simples;
...
instruo simples;
} while ( expresso );

Figura 1.46 - Alinhamento da instruo do while.

A Figura 1.47 faz a comparao dos ciclos repetitivos do while e while, para calcular a
mdia de um nmero indeterminado de nmeros lidos do teclado, sendo que, a leitura
termina quando lido o valor zero. No caso do ciclo repetitivo do while, as instrues
constituintes do corpo do ciclo repetitivo, contagem do nmero til de nmeros lidos e sua
soma, necessitam de ser protegidas quando lido o valor de terminao, para no provocar
um clculo errneo.
SOMA = 0.0;
/* clculo da mdia com o ciclo do while */
do
{
printf ("Introduza um numero? ");
scanf ("%lf", &NUMERO);
if (NUMERO != 0.0)
{
SOMA += NUMERO;
N++;
}
} while (NUMERO != 0.0);

SOMA = 0.0;
/* clculo da mdia com o ciclo while */
printf ("Introduza um numero? ");
scanf ("%lf", &NUMERO);
while (NUMERO != 0.0)
{
SOMA += NUMERO;
N++;
printf ("Introduza um numero? ");
scanf ("%lf", &NUMERO);
}

Figura 1.47 - Exemplo comparativo da utilizao dos ciclos repetitivos do while e while.

1.7.2.2 A instruo repetitiva for


A Figura 1.48 apresenta a definio formal da instruo repetitiva for (para fazer enquanto),
cujo nmero de iteraes previamente conhecido. A instruo for da linguagem C
constitui uma superinstruo que no tem correspondncia em mais nenhuma linguagem.
A parte da inicializao executada em primeiro lugar e uma s vez. Em geral, a sua funo
atribuir valores iniciais a uma ou mais variveis usadas no ciclo repetitivo. A parte da
terminao uma expresso que calculada antes do incio de cada nova iterao e que
determina a continuao, se for verdadeira, ou no, se for falsa, do processo repetitivo.

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

28

A expresso deve ser de um tipo escalar bsico. A expresso falsa se for igual a zero e
verdadeira se assumir qualquer outro valor. Nestas condies, o compilador aceita qualquer
expresso numrica como expresso decisria vlida. Porm, por questes de clareza, isto
deve ser evitado. de bom estilo que a expresso decisria represente sempre uma
expresso booleana. Finalmente, a parte de actualizao executada no fim de cada
iterao. Em geral, a sua funo actualizar os valores de uma ou mais variveis usadas no
ciclo repetitivo, entre as quais, sempre a varivel contadora. Normalmente, as expresses de
actualizao das variveis recorrem aos operadores unrios incremento e decremento ou ao
operador de atribuio precedido por um qualquer operador binrio.
instruo for ::= for ( inicializao ; terminao ; actualizao )
instruo simples ou composta
inicializao ::= identificador de varivel = expresso |
inicializao , identificador de varivel = expresso
terminao ::= expresso
actualizao ::= identificador de varivel operador binrio= expresso |
operador unrio inc_dec identificador de varivel |
identificador de varivel operador unrio inc_dec |
actualizao , identificador de varivel operador binrio= expresso |
actualizao , operador unrio inc_dec identificador de varivel |
actualizao , identificador de varivel operador unrio inc_dec
operador unrio inc_dec ::= operador unrio ++ | operador unrio
Figura 1.48 - Definio formal da instruo for.

A instruo for deve ser alinhada da mesma maneira que a instruo while. A Figura 1.49
faz a comparao da instruo for do Pascal e do C usando como exemplo, o clculo das
primeiras N potncias de 2.
POT := 1;
/* na linguagem Pascal */
for I := 1 to N do
begin
writeln ('Potencia de 2 = ', POT:10);
POT := POT * 2
end;

for (POT = 1, I = 1 ; I <= N ; I++, POT *= 2)


/* na linguagem C */
printf ("Potncia de 2 = %10d\n", POT);

Figura 1.49 - Exemplo do clculo das potncias de 2 com um ciclo repetitivo for.

Qualquer uma das trs partes componentes da instruo for, a inicializao, a terminao
ou a actualizao pode ser omitida. Situaes especiais correspondem aos seguintes casos:
x Quando todos os elementos so omitidos, ou seja, for ( ; ; ), temos um ciclo repetitivo
infinito.
x Quando a inicializao e a actualizao so omitidas, ou seja, for ( ; terminao ; ),
temos um ciclo repetitivo funcionalmente equivalente ao ciclo repetitivo while.
Ao contrrio do que se passa em Pascal, a varivel contadora tem um valor bem definido
aps o esgotamento do ciclo repetitivo, que ser aquele que conduziu a um valor falso da
expresso de terminao.

29

CAPTULO 1 : INTRODUO AO C

Devido sua grande versatilidade, a instruo for deve ser utilizada com extremo cuidado.
de bom estilo us-la apenas como uma generalizao da instruo for de Pascal. O que
significa, nomeadamente, que o valor da varivel contadora nunca deve ser modificado
dentro do ciclo repetitivo.
Um dos erros mais frequentemente cometido por programadores que se esto a iniciar na
utilizao da linguagem C, consiste em esquecer que o ciclo repetitivo for actua enquanto a
expresso de terminao for verdadeira. Se no exemplo anterior fosse utilizada a condio
I == N, o ciclo repetitivo no seria executado uma nica vez, a no ser que N fosse 1,
quando se pretende que o ciclo repetitivo seja executado N vezes.
Os ciclos repetitivos podem ser encadeados, tal como a instruo condicional binria,
dando origem a estruturas repetitivas em que, uma instruo do corpo do ciclo repetitivo
ela mesmo um ciclo repetitivo. A este tipo de construo d-se o nome de ciclos
imbricados (nested loops).

1.7.2.3 Instrues nula, break e continue


H ainda trs instrues que so muitas vezes usadas em conjunto com as instrues
repetitivas. So elas a instruo nula ou muda, a instruo break e a instruo continue.
A instruo nula, expressa pela colocao do separador ;, surge muitas vezes associada com
as instrues while e for quando se pretende que o corpo do ciclo repetitivo no tenha
qualquer instruo. A Figura 1.50 apresenta um exemplo que determina o comprimento de
uma cadeia de caracteres. Uma vez que uma cadeia de caracteres um agregado de
caracteres terminado obrigatoriamente com o carcter '\0', ento preciso detectar o ndice
do agregado onde ele est armazenado. O que possvel fazer atravs de uma instruo
repetitiva em que a expresso de teste, recorrendo ao operador unrio incremento, provoca
o deslocamento dentro do agregado e, portanto, o ciclo repetitivo no carece de qualquer
instruo.
COMP = -1;
while (s[++comp] != '\0') ;
/* ou em alternativa com o ciclo for */
for (comp = 0; s[comp] != '\0'; comp++) ;

Figura 1.50 - Clculo do comprimento de uma cadeia de caracteres.

A instruo break uma instruo que, genericamente, permite a sada intempestiva do


bloco que est a ser executado. A sua utilizao no caso da instruo de deciso mltipla
switch possibilitou, como se viu, transformar operacionalmente esta instruo e adequ-la
ao tipo de funcionalidade encontrado na instruo case do Pascal. A sua aplicao em
conjuno com as instrues while e do while tambm muito interessante, j que
possibilita em muitas situaes prticas reduzir a complexidade da expresso de terminao,
aumentando por isso a clareza e a legibilidade do cdigo correspondente.
A Figura 1.51 apresenta um exemplo em que se pretende obter a soma de um mximo de
10 valores no negativos lidos do dispositivo de entrada, sendo que a leitura parar mais
cedo se for lido um valor negativo. Compare as solues e repare na simplificao da
condio de terminao da verso em linguagem C, devido utilizao da instruo break.

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

30

N := 0;
/* na linguagem Pascal */
SOMA := 0.0;
readln (NUMERO);
while (N < 10) and (NUMERO >= 0.0) do
begin
N := N + 1;
SOMA := SOMA + NUMERO;
readln (NUMERO)
end;

N = 0;
/* na linguagem C */
SOMA = 0.0;
scanf ("%lf", &NUMERO);
while (N < 10)
{
if (NUMERO < 0.0) break;
N++;
SOMA += NUMERO;
scanf ("%lf", &NUMERO);
}

Figura 1.51 - Exemplo da utilizao da instruo break.

A instruo continue uma instruo que permite interromper a execuo do ciclo


repetitivo, forando o fim da iterao presente. A sua aplicao em conjuno com a
instruo for possibilita em muitas situaes prticas reduzir a complexidade do ciclo
repetitivo, aumentando por isso tambm a clareza e a legibilidade do cdigo
correspondente.
A Figura 1.52 apresenta um exemplo em que se pretende contar o nmero de caracteres
alfabticos de uma linha de texto e converter as vogais a existentes de minsculas para
maisculas e vice-versa. O exemplo apresentado a seguir socorre-se das rotinas de
manipulao de caracteres e de cadeias de caracteres das bibliotecas de execuo ANSI e
supe, por isso, a incluso dos ficheiros de interface ctype.h e string.h.
N := 0;
/* na linguagem Pascal */
for I := 1 to length (LINHA) do
if LINHA[I] in ['A'..'Z','a'..'z']
then begin
N := N + 1;
if LINHA[I] in ['A','E','I','O','U','a','e','i','o','u']
then if LINHA[I] in ['A','E','I','O','U']
then LINHA[I] := chr(ord(LINHA[I])-ord('A')+ord('a'))
else LINHA[I] := chr(ord(LINHA[I])-ord('a')+ord('A'))
end;

for (I = 0, N = 0; I < strlen (LINHA); I++)


/* na linguagem C */
{
if (!isalpha (LINHA[I])) continue;
N++;
if ((toupper(LINHA[I]) != 'A') && (toupper(LINHA[I]) != 'E') &&
(toupper(LINHA[I]) != 'I') && (toupper(LINHA[I]) != 'O') &&
(toupper(LINHA[I]) != 'U')) continue;
if (isupper (LINHA[I])) LINHA[I] = LINHA[I]-'A'+'a';
else LINHA[I] = LINHA[I]-'a'+'A';
}

Figura 1.52 - Exemplo da utilizao da instruo continue.

31

CAPTULO 1 : INTRODUO AO C

Compare as solues e repare que as instrues condicionais na verso em linguagem C


esto simplificadas, uma vez que no necessitam de ser encadeadas, para evitar as situaes
em que os caracteres no precisam de ser convertidos. Se o carcter no um carcter
alfabtico, ou se um carcter alfabtico, mas no uma vogal, ento fora-se a prxima
iterao do ciclo repetitivo, usando para esse efeito a instruo continue.

1.7.2.4 Ciclos repetitivos infinitos


Um ciclo repetitivo infinito um ciclo repetitivo que no tem condio de terminao, ou
cuja condio de terminao est mal implementada. A maior parte desses ciclos repetitivos
so criados por engano. Mas, existem situaes em que se pretende de facto implementar
um ciclo repetitivo infinito. Por exemplo, quando se pretende uma aplicao que repete
sistematicamente uma determinada operao at que uma condio particular se verifica.
Nessa situao mais fcil construir um ciclo repetitivo, que terminado atravs do
recurso instruo break. A Figura 1.53 apresenta as duas formas mais simples de
implementar ciclos repetitivos infinitos.
while ( 1 )
/* com o ciclo while */
instruo simples ou composta

for ( ; ; )
/* com o ciclo for */
instruo simples ou composta

Figura 1.53 - Ciclos repetitivos infinitos.

1.8 Entrada e sada de dados


Tal como em qualquer outra linguagem, as instrues de entrada e de sada da linguagem C
so instrues especiais que estabelecem uma interface com o sistema operativo na
comunicao com os diferentes dispositivos do sistema computacional, em particular com
os dispositivos convencionais de entrada e de sada. Normalmente, o dispositivo de entrada
o teclado e mencionado pelo identificador stdin, e o dispositivo de sada o monitor e
mencionado pelo identificador stdout.
Os dispositivos de entrada e de sada funcionam da seguinte forma. Sempre que se prime
uma tecla do teclado, gerada uma palavra binria, representando o valor atribudo ao
carcter correspondente, em cdigo ASCII por exemplo, que armazenada num registo do
controlador do teclado. O sistema operativo alertado para esse facto e, eventualmente, vai
ler o contedo do registo e armazenar sequencialmente o valor lido numa regio da
memria principal que se designa por armazenamento tampo de entrada (input buffer).
O valor tambm quase sempre colocado no armazenamento tampo de sada do monitor,
para posterior envio para o monitor, produzindo-se assim a interaco visual com o
utilizador. Em termos da instruo de entrada, o armazenamento tampo de entrada visto
como uma sequncia ordenada de caracteres, organizada em linhas que consistem em zero
ou mais caracteres, com representao grfica, ou com funes de controlo, seguidos do
carcter de fim de linha que o '\n'. A esta sequncia de caracteres d-se o nome de fluxo de
caracteres (text stream). De um modo geral, s linhas completas presentes no fluxo de
caracteres de entrada esto disponveis para leitura. medida que os caracteres vo sendo
processados pela instruo de entrada, vo sendo retirados do fluxo de caracteres de
entrada.

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

32

Por outro lado, o fluxo de texto de sada implementado numa regio da memria
principal, directamente acessvel pelo sistema operativo, que se designa por
armazenamento tampo de sada (output buffer). Aps a escrita de uma sequncia de
caracteres pela instruo de sada, o sistema operativo alertado para esse facto e,
eventualmente, vai transferir essa sequncia, carcter a carcter, para um registo do
controlador do monitor. Em resultado disso, a mensagem associada reproduzida no
monitor a partir da posio actual do cursor e da esquerda para a direita. Alm de
caracteres com representao grfica, as sequncias escritas podem conter caracteres de
controlo diversos que possibilitam, entre outras aces mais ou menos especficas, o
deslocamento do cursor para uma outra posio do monitor.
Na linguagem C, as instrues de entrada e de sada so funes de tipo int que pertencem
biblioteca de execuo ANSI e cuja descrio est contida no ficheiro de interface stdio.h.

1.8.1 A funo scanf


Na linguagem C, a entrada de dados implementada pela funo scanf cuja sintaxe se
apresenta na Figura 1.54. A funo scanf l sequncias de caracteres do fluxo de caracteres
de entrada (stdin) e processa-as segundo as regras impostas pelo formato de leitura,
armazenando sucessivamente os valores convertidos nas variveis, cuja localizao
indicada na lista de ponteiros de variveis.
Salvo em duas situaes especiais adiante referidas, deve existir uma relao de um para um
entre cada especificador de converso e cada varivel da lista de ponteiros de variveis. Se o
nmero de variveis da lista de ponteiros de variveis for insuficiente, o resultado da
operao no est definido. Se, ao contrrio, o nmero de variveis for demasiado grande,
as variveis em excesso no so afectadas. O tipo da varivel e o especificador de
converso devem ser compatveis, j que a finalidade deste ltimo indicar, em cada caso,
que tipos de sequncias de caracteres so admissveis e como devem ser tratadas. Quando o
especificador de converso no vlido, o resultado da operao no est definido.
O processo de leitura s termina quando o formato de leitura se esgota, lido o carcter de
fim de ficheiro, ou existe um conflito de tipo entre o que est indicado no formato de leitura e
a correspondente quantidade a ser lida. Neste ltimo caso, o carcter que causou o conflito
mantido no fluxo de caracteres de entrada. A funo devolve o nmero de valores lidos e
armazenados, ou o valor fim de ficheiro (EOF), se o carcter de fim de ficheiro lido antes que
qualquer converso tenha lugar. Se, entretanto, ocorreu um conflito, o valor devolvido
corresponde ao nmero de valores lidos e armazenados at ocorrncia do conflito.
Pondo de parte o facto de se tratar de uma instruo de leitura formatada, scanf
semanticamente equivalente instruo read de Pascal. No existe, contudo, na linguagem
C um equivalente directo para a instruo readln. O papel de um carcter separador no
formato de leitura forar a eliminao do fluxo de caracteres de entrada de todos os
caracteres deste tipo at primeira ocorrncia de um carcter distinto. O papel de um literal
no formato de leitura estabelecer a correspondncia com sequncias idnticas de
caracteres existentes no fluxo de caracteres de entrada, e, por consequncia, elimin-las do
processo de leitura. A correspondncia com o carcter %, que no um literal, obtida
atravs do especificador de converso %% e no h lugar a qualquer armazenamento. O
supressor de atribuio, que o carcter *, possibilita que uma sequncia de caracteres seja
lida e convertida, mas o seu valor no seja armazenado em qualquer varivel.

33

CAPTULO 1 : INTRODUO AO C

int scanf ( formato de leitura , lista de ponteiros de variveis )


formato de leitura ::= "cadeia de caracteres de definio"
cadeia de caracteres de definio ::= especificador de converso | carcter separador | literal |
cadeia de caracteres de definio especificador de converso |
cadeia de caracteres de definio carcter separador |
cadeia de caracteres de definio literal
especificador de converso ::= %carcter de converso |
%modificador de converso carcter de converso
carcter de converso ::= i l uma quantidade inteira genrica
d l uma quantidade inteira em decimal (signed)
u l uma quantidade inteira em decimal (unsigned)
o l uma quantidade inteira em octal (unsigned)
x l uma quantidade inteira em hexadecimal (unsigned)
f e g l uma quantidade real em decimal
c l caracteres
s l uma cadeia de caracteres
[lista de caracteres] l uma cadeia de caracteres imposta pela lista de caracteres.
Se o primeiro carcter for o ^ ento l uma cadeia de caracteres at encontrar um
carcter da lista de caracteres
p l o valor de um ponteiro para void
n permite contar o nmero de caracteres lidos at ao seu
aparecimento na cadeia de caracteres de definio
modificador de converso ::= largura mxima de campo | especificador de dimenso |
largura mxima de campo especificador de dimenso |
supressor de atribuio |
supressor de atribuio largura mxima de campo |
supressor de atribuio especificador de dimenso |
supressor de atribuio largura mxima de campo especificador de dimenso
largura mxima de campo ::= valor decimal positivo que indica o nmero mximo de
caracteres a serem lidos
especificador de dimenso ::= h com i, d, u, o, x, indica tipo short
l com i, d, u, o, x, indica tipo long
com f, e, g, indica tipo double
L com f, e, g, indica tipo long double
supressor de atribuio ::= *
carcter separador ::= carcter espao, carcter tabulador '\t' e carcter fim de linha '\n'
literal ::= um ou mais caracteres diferentes do carcter separador ou do carcter %
lista de ponteiros de variveis ::= ponteiro de varivel de tipo aritmtico |
ponteiro de varivel de tipo void |
lista de ponteiros de variveis , ponteiro de varivel de tipo aritmtico |
lista de ponteiros de variveis , ponteiro de varivel de tipo void
ponteiro de varivel de tipo aritmtico ::= & varivel de tipo aritmtico
ponteiro de varivel de tipo void ::= & varivel de tipo void
Figura 1.54 - Definio formal da funo scanf.

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

34

Na funo scanf as variveis so parmetros de entrada-sada, pelo que, tm de ser


passadas funo por referncia, ou seja, por endereo. Para passar o endereo de uma
varivel de tipo simples, temos que preceder a varivel pelo operador endereo que o
carcter &. No caso das variveis de tipo agregado, o prprio nome da varivel representa
o endereo do elemento inicial da varivel, e portanto, so usados no scanf normalmente.

1.8.1.1 Leitura de caracteres


Para a leitura de um carcter, o especificador de converso deve ser %c ou %1c. Para a
leitura de mais do que um carcter, o nmero de caracteres a ser lido ser igual largura
mxima de campo e ser necessrio garantir que o ponteiro da varivel referencia uma
regio de memria com capacidade para o armazenamento dos caracteres lidos,
tipicamente, um agregado de caracteres.
A Figura 1.55 apresenta um exemplo de leitura de um carcter para a varivel CAR e de
oito caracteres para um agregado de caracteres. A declarao char ARRAYCAR[8] define
um agregado de 8 caracteres, sendo que o ndice do primeiro elemento do agregado o
ndice zero. A invocao da funo, para uma linha de dados introduzida pelo teclado e
constituda pelos caracteres a1234567890'\n', resulta no armazenamento do carcter 'a' na
varivel CAR e dos caracteres '1' a '8' nos elementos sucessivos do agregado ARRAYCAR.
Para passar o endereo da varivel CAR usa-se o operador &. Para indicar o incio do
agregado de caracteres ARRAYCAR, ou se menciona apenas o nome do agregado, ou se
indica o endereo do primeiro elemento do agregado &ARRAYCAR[0].
char CAR, ARRAYCAR[8];
...
scanf ("%c%8c", &CAR, ARRAYCAR);
/* ou em alternativa scanf ("%c%8c", &CAR, &ARRAYCAR[0]); */

Figura 1.55 - Exemplo da leitura de um carcter.

A Figura 1.56 apresenta um excerto de cdigo em que se utiliza o scanf para ler uma
varivel de tipo carcter, mas, de forma equivalente ao readln do Pascal.
#include <stdio.h>
int main (void)
{
char CAR,
/* escolha da opo do menu
CARX;
/* varivel auxiliar para teste de fim de linha
...
do
{
printf ("1 - Opo_1\n");
printf ("2 - Opo_2\n");
printf ("3 - Opo_3\n");
printf ("Qual a sua escolha? ");
scanf ("%c", &CAR);
/* ler o carcter que representa a opo
if (CAR != '\n')
do
/* descartar todos os eventuais restantes caracteres
{
/* da linha, incluindo o carcter fim de linha
scanf ("%c", &CARX);
} while (CARX != '\n');
} while ((CAR < '1') || (CAR > '3'));
...
}

Figura 1.56 - Leitura de um carcter equivalente instruo readln do Pascal.

*/
*/

*/
*/
*/

35

CAPTULO 1 : INTRODUO AO C

Depois da leitura da varivel de tipo carcter, caso tenha sido lido um carcter diferente de
fim de linha ento vo ser lidos todos os caracteres que eventualmente existam no
armazenamento tampo de entrada at leitura do carcter fim de linha inclusive, usando
para o efeito uma varivel auxiliar de tipo carcter.

1.8.1.2 Leitura de uma cadeia de caracteres


Uma cadeia de caracteres (string) tipicamente um agregado de caracteres, terminado com o
carcter especial '\0'. Este carcter especial serve de indicador de fim da cadeia de
caracteres, e obviamente, ocupa uma posio de armazenamento do agregado. Pelo que, se
necessitarmos de armazenar uma cadeia de caracteres com MAX_CAR caracteres, devemos
definir um agregado de caracteres com o tamanho MAX_CAR+1. O subdimensionamento
de uma cadeia de caracteres um dos erros mais frequentemente cometido por
programadores que se esto a iniciar na utilizao da linguagem C.
A leitura de mais do que um carcter usando o especificador de converso %c no muito
prtica, pelo que, prefervel efectu-la como a leitura de uma cadeia de caracteres. Para a
leitura de uma sequncia de caracteres que no contenha um carcter separador como
elemento constituinte, no fundo, para a leitura de uma palavra, deve-se usar o seguinte
especificador de converso.
%nmero mximo de caracteres a lers
O processo de leitura elimina todas as instanciaes do carcter separador que surjam no
princpio e agrupa os caracteres seguintes at ao nmero mximo de caracteres indicados
para a leitura, ou at ocorrncia de um carcter separador, o que significa que no
possvel ler-se uma cadeia de caracteres nula.
O ponteiro da varivel correspondente tem que referenciar uma regio de memria com
capacidade para o armazenamento dos caracteres lidos e do carcter '\0', que
automaticamente colocado no fim.
Assim, fundamental incluir sempre no especificador de converso a largura mxima de
campo, caso contrrio, corre-se o risco de, se a palavra for demasiado longa, ultrapassar-se
a regio de armazenamento estabelecida e afectar indevidamente o valor de outras variveis.
Note-se que o valor do nmero mximo de caracteres a ler tem que ser, pelo menos,
inferior a uma unidade ao comprimento do agregado, para garantir que o carcter
terminador da cadeia de caracteres sempre armazenado.
A Figura 1.55 apresenta um exemplo de leitura de uma cadeia de caracteres, com
capacidade para armazenar 7 caracteres teis. A invocao da funo para uma linha de
dados introduzida pelo teclado e constituda pelos caracteres joana'\n', resulta no
armazenamento dos caracteres 'j', 'o', ... , 'a', '\0' no agregado NOME. No caso de uma
linha de dados constituda pelos caracteres universidade'\n', resulta no armazenamento dos
caracteres 'u', 'n', ... , 's', '\0' no agregado NOME.
char NOME[8];
...
scanf ("%7s", NOME); /* ou em alternativa scanf ("%7s", &NOME[0]); */

Figura 1.57 - Exemplo da leitura de uma cadeia de caracteres com o especificador de converso %s.

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

36

Alternativamente, a leitura de uma sequncia de caracteres pode ser realizada dividindo


dicotomicamente os caracteres em duas classes, a classe de caracteres admissveis para a
sequncia e a classe de caracteres no admissveis, e descrevendo uma delas no
especificador de converso %[lista de caracteres], nas suas duas variantes que se
apresentam a seguir. Entre parnteses rectos descreve-se extensivamente numa ordem
qualquer os caracteres pertencentes classe de caracteres admissveis ou alternativamente
os caracteres pertencentes classe de caracteres no admissveis precedidos pelo carcter ^.
%nmero mximo de caracteres a ler [classe de caracteres admissveis]
%nmero mximo de caracteres a ler [^classe de caracteres no admissveis]
O processo de leitura termina quando for lido o nmero mximo de caracteres a ler ou
ocorrer um conflito. Se o conflito acontecer logo no princpio, a varivel correspondente
no afectada, o que significa que no possvel ler-se uma cadeia de caracteres nula. De
novo, o ponteiro da varivel correspondente tem que referenciar uma regio de memria
com capacidade para o armazenamento dos caracteres lidos e do carcter '\0', que
automaticamente colocado no fim. Pelas razes apontadas anteriormente, a largura mxima
de campo dever tambm ser sempre includa no especificador de converso.
A Figura 1.58 apresenta dois exemplos da utilizao do especificador de converso %[ ]
para a leitura de cadeias de caracteres. No primeiro caso pretende-se ler um nmero de
telefone, que uma sequncia de 9 caracteres exclusivamente composta por caracteres
numricos. No segundo exemplo, pretende-se efectuar a leitura de uma sequncia de
caracteres quaisquer at ao aparecimento do carcter fim de linha, no mximo de 79
caracteres.
char NUMTEL[10];
...
scanf ("%9[0123456789]", NUMTEL);
/* ou em alternativa scanf ("%9[0123456789]", &NUMTEL[0]); */

char FRASE[80];
...
scanf ("%79[^\n]", FRASE);
/* ou em alternativa scanf ("%79[^\n]", &FRASE[0]); */

Figura 1.58 - Exemplos da leitura de cadeias de caracteres com o especificador de converso %[ ].

Um dos erros mais frequentes dos programadores que se esto a iniciar na utilizao da
linguagem C, consiste em usar simultaneamente os dois especificadores de converso de
cadeia de caracteres, inventando o especificador de converso %s[ ]. Estes dois
especificadores de converso so alternativos, e, portanto, no podem ser combinados.
A Figura 1.59 apresenta um excerto de cdigo em que se utiliza o scanf para ler uma
varivel de tipo cadeia de caracteres, mas, de forma equivalente ao readln do Pascal. A
funo invocada para ler uma sequncia de caracteres quaisquer, constituda no mximo
por 79 caracteres. Depois descartam-se todos os caracteres que eventualmente existam no
armazenamento tampo de entrada at leitura do carcter fim de linha. De seguida l-se e
descarta-se o carcter fim de linha. Estas duas aces no podem ser combinadas numa s,
porque caso no existam caracteres extras, ento a instruo de leitura terminaria
abruptamente sem efectuar a leitura do carcter fim de linha, que ficaria no armazenamento
tampo de entrada disponvel para posteriores invocaes da funo. Caso no tenha sido
lido qualquer carcter para a varivel FRASE, o que pode ser indagado aferindo o resultado
devolvido pelo scanf e armazenado na varivel T, que zero nesse caso, ento constri-se

37

CAPTULO 1 : INTRODUO AO C

uma cadeia de caracteres nula, colocando o carcter terminador na primeira posio da


cadeia de caracteres.
#include

<stdio.h>

int main (void)


{
char FRASE[80];
/* frase a ser lida
int T;
/* sinalizao do estado de leitura
...
printf ("Escreva a frase: ");
T = scanf ("%79[^\n]", FRASE);
/* leitura da frase
scanf ("%*[^\n]");
/* descartar todos os outros caracteres
scanf ("%*c");
/* descartar o carcter fim de linha
/* construir uma cadeia de caracteres nula
if (T == 0) FRASE[0] = '\0';
...
}

*/
*/
*/
*/
*/
*/

Figura 1.59 - Leitura de uma cadeia de caracteres equivalente instruo readln do Pascal.

1.8.1.3 Leitura de valores numricos


Na leitura de valores numricos, a introduo do especificador de dimenso no
especificador de converso permite atribuir os valores lidos a variveis de tipo short ou de
tipo long, em vez de tipo int, ou a variveis de tipo double ou de tipo long double, em
vez de tipo float. O recurso largura mxima de campo s deve ser efectuado quando se
pretende ler valores com um formato bem definido. Caso contrrio, no deve ser usado
para possibilitar uma leitura em formato livre. Se os valores lidos forem demasiado grandes
para poderem ser armazenados nas variveis que lhes esto associadas, vai ocorrer overflow.
O processo de leitura termina quando for lido um nmero de caracteres vlidos igual
largura mxima de campo, caso ela tenha sido especificada, surgir entretanto um carcter
separador, ou qualquer outro carcter no vlido, que funciona neste caso tambm como
carcter separador, ou ocorrer um conflito. Nesta ltima situao, a varivel
correspondente no afectada. A Figura 1.60 apresenta as sequncias de caracteres
admissveis para cada carcter de converso.
Carcter de converso
i (tipo signed)
d
u
x
o

(tipo
(tipo
(tipo
(tipo

signed)
unsigned)
unsigned)
unsigned)

Sequncia admissvel
tipos inteiros
[+/-]#...#
[+/-]0x#...#
[+/-]0#...#
[+/-]#...#
[+/-]#...#
[+/-]0x#...#
[+/-]0#...#

Observao
#
#
#
#
#
#
#

algarismo
algarismo
algarismo
algarismo
algarismo
algarismo
algarismo

decimal
hexadecimal
octal
decimal
decimal
hexadecimal
octal

tipos reais
f,e,g

[+/-]#...#
# - algarismo decimal
[+/-][#...#][.]#...#
[+/-][#...#][.]#...#[e/E[+/-]#...#]

Figura 1.60 - Tabela de sequncias admissveis em funo do carcter de converso.

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

38

A Figura 1.61 apresenta alguns exemplos de leituras de valores numricos. A execuo da


funo para a linha de dados introduzida pelo teclado e constituda pelos seguintes
caracteres -123 0xF2'\t'-1.47e1'\n', resulta no armazenamento das quantidades -123 na
varivel I, 242 na varivel S e -14.7 na varivel D. No caso de uma linha de dados
constituda pelos caracteres '\t'12 -023 e-4'\n', resulta no armazenamento das quantidades
12 na varivel I, -19 na varivel S e nenhum valor na varivel D, porque ocorreu um
conflito. O conflito deve-se ao facto de que a sequncia e-4 no ser uma sequncia de
caracteres admissvel para representar uma quantidade real em notao cientfica. Para
corrigir este conflito deve-se usar a sequncia de caracteres 1e-4.
int I;
short S;
double D;
...
scanf ("%d%hi%lf", &I, &S, &D);

Figura 1.61 - Exemplo de leitura de valores numricos.

O uso do carcter separador no formato de leitura deve ser evitado porque, ou


irrelevante, quando colocado no princpio, ou entre especificadores de converso, j que o
carcter separador funciona como elemento de separao de sequncias numricas, ou tem
efeitos colaterais nocivos, quando colocado no fim, j que obriga deteco de um carcter
distinto no fluxo de texto de entrada, antes da concluso da instruo. Procure descobrir o
que vai acontecer quando forem executadas as funes scanf apresentadas na Figura 1.62,
para as duas linhas de dados introduzidas pelo teclado e constitudas pelos seguintes
caracteres 1 2'\n' e 3'\n'.
int A, B, C;
...
printf ("Valores 1 e 2? ");
scanf ("%d%d\n", &A, &B);
printf ("Valor 3? ");
scanf ("%d", &C);

Figura 1.62 - Exemplo incorrecto de leitura de valores numricos.

Por causa do literal '\n' no fim da cadeia de caracteres de definio, a primeira invocao da
funo scanf s termina quando se tecla o carcter 3, pelo que, a mensagem da segunda
invocao da funo printf s escrita no monitor depois de introduzida a segunda linha
de dados, quando deveria aparecer antes.
A Figura 1.63 apresenta um excerto de cdigo em que se utiliza o scanf para ler uma
varivel de tipo numrica, mas, de forma equivalente ao readln do Pascal. A funo
invocada para ler uma varivel numrica, que neste caso inteira de tipo int. Depois
descartam-se todos os caracteres que eventualmente existam no armazenamento tampo de
entrada at leitura do carcter fim de linha. De seguida l-se e descarta-se o carcter fim de
linha. Estas duas aces no podem ser combinadas numa s, porque caso no existam
caracteres extras, ento a instruo de leitura terminaria abruptamente sem efectuar a leitura
do carcter fim de linha, que ficaria no armazenamento tampo de entrada disponvel para
posteriores invocaes da funo. Caso no tenha sido lido qualquer valor numrico para a
varivel VAL, o que pode ser indagado aferindo o resultado devolvido pelo scanf e
armazenado na varivel T, que zero nesse caso, ento repete-se o processo at que seja
efectivamente lido um valor numrico.

39

CAPTULO 1 : INTRODUO AO C

#include

<stdio.h>

int main (void)


{
int VAL;
/* valor a ser lido
int T;
/* sinalizao do estado de leitura
...
printf ("Valor? ");
do
{
T = scanf ("%d", &VAL);
/* ler o valor
scanf ("%*[^\n]");
/* descartar todos os outros caracteres
scanf ("%*c");
/* descartar o carcter fim de linha
} while (T == 0);
/* repetir enquanto um valor numrico no tiver sido lido
...
}

*/
*/

*/
*/
*/
*/

Figura 1.63 - Leitura de uma varivel de tipo numrica equivalente instruo readln do Pascal.

A Figura 1.64 apresenta o cdigo necessrio para ler uma informao horria vlida, de tipo
HH:MM:SS. O processo de leitura tem que ler trs quantidades numricas positivas da o
tipo de dados utilizado ser unsigned. Como os valores no excedem 23 para as horas e 60
para os minutos e segundos, ento podem ser de tipo short. A leitura repetida enquanto
houver um erro de formato e enquanto houver valores fora dos limites.
#include

<stdio.h>

int main (void)


{
unsigned short H, M, S; /* hora, minuto e segundo a serem lidos
int T;
/* sinalizao do estado de leitura
...
do
{
do
{
/* leitura da informao horria
printf ("Informao horria HH:MM:SS? ");
T = scanf ("%2hu:%2hu:%2hu", &H, &M, &S);
scanf ("%*[^\n]");
/* descartar todos os outros caracteres
scanf ("%*c");
/* descartar o carcter fim de linha
} while (T != 3); /* repetir enquanto houver um erro de formato
} while ((H > 23) || (M > 59) || (S > 59));
/* repetir enquanto houver valores fora dos limites
...
}

*/
*/

*/
*/
*/
*/
*/

Figura 1.64 - Leitura de uma informao horria.

O especificador de converso %n permite contar a nmero de caracteres lidos at ao seu


aparecimento na cadeia de caracteres de definio. utilizado quando queremos verificar
se o formato dos dados est de acordo com a cadeia de caracteres de definio que se est a
utilizar. A Figura 1.65 apresenta um exemplo da sua aplicao. Vamos considerar que
queremos ler um valor numrico mas, que este se encontra a seguir a outro valor, e que este
primeiro valor deve ser descartado porque no nos interessa. Se por algum motivo existir
um erro de formato e existir apenas um valor para ser lido, esse valor descartado e no
lido nem armazenado na varivel NUM. Para detectar se h ou no um erro no formato
esperado, podemos determinar o nmero de caracteres lidos antes da leitura da varivel
NUM. Se o valor de N for nulo sinal que no havia nenhum valor inicial, e, portanto, o
formato previsto dos dados de entrada no se verifica.

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

int NUM,
/* valor a ser lido
N;
/* contador de caracteres lidos
...
N = 0;
/* inicializao do contador de caracteres lidos
scanf ("Numero = %*d%n%d", &N, &NUM);
/* leitura do valor e utilizao do contador

40

*/
*/
*/
*/

Figura 1.65 - Exemplo da utilizao do especificador de converso %n.

1.8.2 A funo printf


Na linguagem C, a sada de dados implementada pela funo printf cuja sintaxe se
apresenta na Figura 1.66. A funo printf escreve sucessivamente sequncias de caracteres
no fluxo de texto de sada (stdout), representativas de texto e dos valores das expresses que
formam a lista de expresses, segundo as regras impostas pelo formato de escrita.
O texto especificado no formato de escrita pela introduo de literais, que so copiados
sem modificao para o fluxo de texto de sada. O modo como o valor das expresses
convertido, descrito pelos especificadores de converso. Deve, por isso, existir uma
relao de um para um entre cada especificador de converso e cada expresso da lista de
expresses. Se o nmero de expresses for insuficiente, o resultado da operao no est
definido. Se, ao contrrio, esse nmero for demasiado grande, as expresses em excesso
so ignoradas.
O tipo da expresso e o especificador de converso devem ser compatveis, j que a
finalidade deste ltimo indicar, em cada caso, o formato da sequncia convertida. Quando
o especificador de converso no vlido, o resultado da operao no est definido.
O processo de escrita s termina quando o formato de escrita se esgota, ou ocorreu um
erro. A funo devolve o nmero de caracteres escritos no fluxo de texto de sada, ou o
valor -1, se ocorreu um erro.
Como o formato de escrita pode conter literais que so directamente copiados sem
modificao para o fluxo de texto de sada, a instruo printf muito geral, podendo ser
tornada semanticamente equivalente instruo write de Pascal, quando o formato de
escrita no contm o carcter '\n', ou instruo writeln, quando este precisamente o
ltimo carcter presente. De um modo geral, a incluso de literais no formato de escrita
possibilita melhorar a compreenso dos dados impressos e organizar a sua apresentao de
um modo mais regular. A impresso do carcter %, que no um literal, obtida atravs
do especificador de converso %%.
A largura mnima de campo indica o nmero mnimo de caracteres usados na
representao do valor da expresso. Quando a expresso pode ser representada com
menos caracteres, so introduzidos espaos ou zeros at perfazer o nmero indicado.
Quando a largura mnima de campo insuficiente, o campo alargado para conter o valor
convertido.

41

CAPTULO 1 : INTRODUO AO C

int printf ( formato de escrita , lista de expresses )


formato de escrita ::= "cadeia de caracteres de definio"
cadeia de caracteres de definio ::= especificador de converso | literal |
cadeia de caracteres de definio especificador de converso |
cadeia de caracteres de definio literal
especificador de converso ::= %carcter de converso |
%modificador de converso carcter de converso
carcter de converso ::= i d escreve uma quantidade inteira decimal (signed)
u escreve uma quantidade inteira em decimal (unsigned)
o escreve uma quantidade inteira em octal (unsigned)
x escreve uma quantidade inteira em hexadecimal (unsigned)
f escreve uma quantidade real em notao PI.PF
e E escreve uma quantidade real em notao cientfica. O
carcter indicador de expoente e ou E conforme se usa o carcter de converso e ou E
g G escreve uma quantidade real no formato f ou e dependente
do valor. O carcter indicador de expoente e ou E conforme se usa o carcter de
converso g ou G
c escreve um caracter
s escreve uma cadeia de caracteres
p escreve o valor de um ponteiro para void
modificador de converso ::= modificador de formato | especificador de preciso |
especificador de dimenso |
modificador de formato especificador de preciso |
modificador de formato especificador de dimenso |
especificador de preciso especificador de dimenso |
modificador de formato especificador de preciso especificador de dimenso
modificador de formato ::= modificador de aspecto | largura mnima de campo |
modificador de aspecto largura mnima de campo
modificador de aspecto ::= qualquer combinao de espao, , +, #, 0
largura mnima de campo ::= valor decimal positivo que indica o nmero mnimo de
caracteres a serem escritos
especificador de preciso ::= . | . valor decimal positivo
especificador de dimenso ::= h com i, d, u, o, x, indica tipo short
l com i, d, u, o, x, indica tipo long
L com f, e, g, indica tipo long double
literal ::= carcter diferente de % | literal carcter diferente de %
lista de expresses ::= expresso de tipo aritmtico | expresso de tipo ponteiro para void |
lista de expresses , expresso de tipo aritmtico |
lista de expresses , expresso de tipo ponteiro para void
Figura 1.66 - Definio formal da funo printf.

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

42

O papel do modificador de aspecto controlar a apresentao dos valores convertidos.


Assim, tem-se que:
x O carcter , significa justificao da sequncia convertida esquerda no campo. Por
defeito, ela justificada direita.
x O carcter 0, significa que quando for necessrio introduzir caracteres extra para
perfazer a largura mnima de campo na representao de valores numricos, so
introduzidos zeros, em vez de espaos, para uma justificao direita.
x O carcter +, significa que todos os valores numricos passam a ter sinal, os positivos, o
sinal + e os negativos, o sinal . Por defeito, s os valores negativos so representados com
sinal.
x O carcter espao, significa que os valores numricos positivos passam a ser
representados com um espao na posio do sinal. Por defeito, s os valores negativos so
representados com sinal.
x O carcter #, significa que os valores numricos no sistema hexadecimal ou octal so
representados precedidos de '0x' ou '0', respectivamente. Os valores numricos reais so
representados com o separador ., mesmo que no haja parte fraccionria. Alm disso,
quando for usado o carcter de converso g, os zeros direita na parte fraccionria so
representados.
O papel do especificador de preciso est associado de alguma forma com a preciso com
que os valores convertidos so expressos. Assim, tem-se que:
x Para valores inteiros, indica o nmero mnimo de algarismos usados na representao
do valor. Se surgir apenas o carcter ., significa um nmero zero de algarismos.
x Para valores reais:
x Com os caracteres de converso e ou f indica o nmero de algarismos usados na
representao da parte fraccionria. Se surgir apenas o carcter ., significa um
nmero zero de algarismos. Quando no est presente, esse nmero de 6.
x Com o carcter de converso g indica o nmero mximo de algarismos
significativos. Se surgir apenas o carcter ., significa um nmero zero de algarismos.
x Quando o valor no puder ser expresso no nmero de algarismos indicado pelo
especificador de preciso, ento o valor ser arredondado.
x Para valores de tipo cadeia de caracteres, indica o nmero mximo de caracteres a
imprimir.
preciso ter em ateno que no existe uma total simetria nos formatos de converso das
funes scanf e printf. Assim, a funo scanf exige o formato de converso %lf para ler
um valor double, enquanto que a funo printf usa o formato de converso %f para
escrever uma expresso de tipo double. Esta assimetria deve-se ao facto de a linguagem C
converter automaticamente expresses reais para double e portanto, no existir de facto a
impresso de expresses de tipo float. Esta assimetria um dos erros mais frequentemente
cometido por programadores que se esto a iniciar na utilizao da linguagem C.
A Figura 1.67 apresenta alguns exemplos da aplicao de vrios formatos de escrita de
valores numricos e as respectivas sequncias de caracteres escritas no monitor.

43

CAPTULO 1 : INTRODUO AO C

printf ("->%12d\n", 675);


printf ("->%012d\n", 675);
printf ("->%12.4d\n", 675);

/* ->
675 */
/* ->000000000675 */
/* ->
0675 */

printf ("->%12f\n", 675.95);


printf ("->%12.4f\n", 675.95);
printf ("->%12.1f\n", 675.95);

/* ->
/* ->
/* ->

printf
printf
printf
printf
printf
printf
printf

/*
/*
/*
/*
/*
/*
/*

("->%12.6g\n", 675.0000);
("->%#12.6g\n", 675.0000);
("->%12.3g\n", 675.0);
("->%12.2g\n", 675.0);
("->%12.1g\n", 675.0);
("->%#12.1g\n", 675.0);
("->%#12.1G\n", 675.0);

->
->
->
->
->
->
->

675.950000 */
675.9500 */
676.0 */
675
675.000
675
6.8e+02
7e+02
7.e+02
7.E+02

*/
*/
*/
*/
*/
*/
*/

Figura 1.67 - Exemplos de formatos de escrita.

1.8.2.1 Escrita de caracteres


Para a escrita de um carcter, deve-se utilizar o seguinte especificador de converso.
%nmero de colunas de impressoc
Sendo que o modificador de aspecto , usado quando se pretender a justificao do
carcter esquerda dentro do nmero de colunas de impresso indicado. No possvel
escrever-se mais do que um carcter de cada vez. A expresso argumento de tipo int
convertida para tipo unsigned char e o valor obtido impresso. A Figura 1.68 apresenta
alguns exemplos de escrita do carcter *. No primeiro caso no se especifica a largura
mnima de campo, pelo que, o carcter escrito numa nica coluna de impresso. Nos
segundo e terceiro casos, especifica-se cinco colunas de impresso, com justificao
direita, que a situao por defeito, e com justificao esquerda usando o modificador de
aspecto .
printf ("->%c<-\n", '*');
printf ("->%5c<-\n", '*');
printf ("->%-5c<-\n", '*');

/* ->*<- */
/* ->
*<- */
/* ->*
<- */

Figura 1.68 - Escrita de um carcter com vrios formatos.

1.8.2.2 Escrita de cadeias de caracteres


Para a escrita de uma cadeia de caracteres, deve-se utilizar o seguinte especificador de
converso.
% nmero mnimo de colunas de impresso.nmero mximo de colunas de impressos
Sendo que o modificador de aspecto , usado quando se pretender a justificao da
cadeia de caracteres esquerda dentro do nmero mnimo de colunas de impresso
indicado. A expresso argumento deve referenciar a regio de memria onde a sequncia
de caracteres, terminada necessariamente pelo carcter '\0', est armazenada, sendo
tipicamente um agregado de caracteres. O nmero de caracteres a imprimir igual ao
nmero de caracteres da cadeia de caracteres, se o especificador de preciso no tiver sido
usado, ou se tiver um valor maior ou igual ao comprimento da cadeia de caracteres, ou,
alternativamente, igual ao valor do especificador de preciso, em caso contrrio.

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

44

A Figura 1.69 apresenta alguns exemplos de escrita de uma cadeia de caracteres. No


primeiro caso no se usa qualquer formato, pelo que, a escrita ocupa o nmero de colunas
de impresso igual ao comprimento da cadeia de caracteres. Nos segundo e terceiro casos
como o nmero mnimo de colunas insuficiente, ento ignorado. No quarto caso
apenas imprimido o nmero de caracteres indicado pelo especificador de preciso. No
quinto caso no impresso nada, uma vez que o especificador de preciso nulo. Nos
sexto e stimo casos usa-se um nmero mnimo de colunas de impresso igual ao nmero
mximo de colunas de impresso, pelo que, a cadeia de caracteres impressa com esse
nmero de caracteres. Como a cadeia de caracteres tem um nmero de colunas inferior ao
pretendido, ento so acrescentados espaos direita ou esquerda, conforme a
justificao pedida.
char FRASE[30] = "pim pam pum, cada bola
...
printf ("->%s<-\n", FRASE);
/* ->pim
printf ("->%11s<-\n", FRASE);
/* ->pim
printf ("->%-11s<-\n", FRASE); /* ->pim
printf ("->%.11s<-\n", FRASE);
printf ("->%.s<-\n", FRASE);
printf ("->%8.8s<-\n", "pim");
printf ("->%-8.8s<-\n", "pim");

...";
pam pum, cada bola ...<pam pum, cada bola ...<pam pum, cada bola ...</* ->pim pam pum</* -></* ->
pim</* ->pim
<-

*/
*/
*/
*/
*/
*/
*/

Figura 1.69 - Escrita de uma cadeia de caracteres com vrios formatos.

1.8.2.3 Escrita de valores numricos


A introduo do especificador de dimenso no especificador de converso permite
caracterizar o tipo do valor que vai ser convertido, o tipo short ou o tipo long em vez do
tipo int, ou o tipo long double em vez do tipo double. Note-se o significado distinto
atribudo ao especificador de preciso, tratando-se de valores inteiros ou de valores reais.
No primeiro caso significa o nmero mnimo de algarismos da representao. No segundo
caso significa o nmero de algarismos da parte fraccionria, quando so usados os
caracteres de converso f ou e, ou o nmero mximo de algarismos significativos, que 6
por defeito, quando usado o carcter de converso g. Segundo a norma ANSI, a seleco
do estilo de representao, quando usado o carcter de converso g, baseia-se na regra
seguinte. A notao cientfica utilizada, quando o expoente resultante for inferior a -4 ou
maior ou igual preciso estabelecida. Em todos os outros casos utilizada a representao
em parte inteira, a vrgula, que nos computadores o . e a parte fraccionria.
A Figura 1.70 apresenta alguns exemplos da aplicao de vrios formatos de escrita de
valores numricos inteiros e as respectivas sequncias de caracteres escritas no monitor.
printf ("->%ld***%ld<-\n", 1095L, -1095L);
/* ->1095***-1095<printf ("->%+li***%+li<-\n", 1095L, -1095L); /* ->+1095***-1095<printf ("->% .2ld***% .2ld<-\n", 1095L, -1095L);
/* -> 1095***-1095<printf ("->%5.5hd***%5.hd<-\n", -13505, -13505);
/* ->-13505***-13505<printf ("->%05u***%5u<-\n", 17, 17);
/* ->00017***
17<printf ("->%-5.2x***%#-5.2x<-\n", 17, 17);
/* ->11
***0x11<printf ("->%5.2ho***%#5.2o<-\n", 17, 17);
/* ->
21*** 021<-

Figura 1.70 - Escrita de valores numricos inteiros com vrios formatos.

*/
*/
*/
*/
*/
*/
*/

45

CAPTULO 1 : INTRODUO AO C

A Figura 1.71 apresenta alguns exemplos da aplicao de vrios formatos de escrita de


valores numricos reais e as respectivas sequncias de caracteres escritas no monitor.
printf ("->%f***%e<-\n", 13.595, 13.595);
/* ->13.595000***1.359500e+01<printf ("->%.f***%.e<-\n", 13.595, 13.595);
/* ->14***1e+01<printf ("->%#.f***%#.e<-\n", 13.595, 13.595);
/* ->14.***1.e+01<printf ("->%g***%g<-\n", -13.595, -0.000011);
/* ->-13.595***-1.1e-05<printf ("->%8.2f***%10.2e<-\n", -13.345, -13.345);
/* -> -13.35*** -1.33e+01<printf ("->%08.2f***%010.2e<-\n", -13.345, -13.345);
/* ->-0013.35***-01.33e+01<printf ("->%8.2g***%#08.2g<-\n", -13.345, -13.345);
/* ->
-13***-000013.<-

*/
*/
*/
*/
*/
*/
*/

Figura 1.71 - Escrita de valores numricos reais com vrios formatos.

1.9 Bibliotecas de execuo ANSI


J referimos a biblioteca de execuo ANSI stdio, que descreve as funes de acesso aos
dispositivos de entrada e de sada. Agora vamos apresentar mais algumas das bibliotecas de
execuo ANSI.

1.9.1 Biblioteca ctype


A biblioteca ctype contm um conjunto de funes para processamento de caracteres.
Tem a particularidade de as rotinas poderem ser tornadas independentes do cdigo usado
na representao dos caracteres. Para utilizar as suas funes preciso fazer a incluso do
ficheiro de interface ctype.h com a seguinte directiva do pr-processador.
#include <ctype.h>
As funes dividem-se funcionalmente em dois grupos. O grupo das funes de teste de
caracteres, que determinam se um dado carcter pertence ou no a uma dada classe. O
grupo das funes de converso, que permitem a converso de um carcter do alfabeto
maisculo num carcter do alfabeto minsculo e vice-versa.
As funes de teste de caracteres tm o seguinte mecanismo de comunicao com o
exterior.
int nome da funo (int carcter);
O valor devolvido no nulo, ou seja, verdadeiro, se o carcter em argumento pertencer
classe associada e nulo, ou seja, falso, caso contrrio. Se o valor em argumento no puder
ser representado como um unsigned int, o resultado indefinido.
As funes de converso tm o seguinte mecanismo de comunicao com o exterior.
int nome da funo (int carcter);
O valor devolvido o do carcter em argumento, se o carcter j estiver convertido, ou no
se tratar de um carcter alfabtico e o do carcter convertido, no caso contrrio.

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

46

A Figura 1.72 apresenta as funes de teste de caracteres e as funes de converso.


funes de teste de caracteres
Nome da funo
isalnum
isalpha
iscntrl
isdigit
isgraph
islower
isprint

Classe de pertena
caracteres alfabticos e algarismos decimais
caracteres alfabticos
caracteres de controlo
algarismos decimais
todos os caracteres com representao grfica
caracteres alfabticos minsculos
todos os caracteres com representao grfica
mais o carcter espao
ispunct
todos os caracteres com representao grfica
menos os caracteres alfabticos e os algarismos decimais
isspace
espao, '\f', '\n', '\r', '\t' e '\v'
isupper
caracteres alfabticos maisculos
isxdigit
algarismos hexadecimais
funes de converso

Nome da funo
tolower
toupper

Tipo de converso
do alfabeto maisculo para o alfabeto minsculo
do alfabeto minsculo para o alfabeto maisculo

Figura 1.72 - Funes da biblioteca ctype.

1.9.2 Biblioteca math


A biblioteca math contm um conjunto de funes matemticas. Para utilizar as suas
funes preciso fazer a incluso do ficheiro de interface math.h com a seguinte directiva
do pr-processador.
#include <math.h>
Quando da invocao destas funes podem ocorrer dois tipos de erros. Erros de domnio,
quando o valor do argumento est fora da gama permitida. Erros de contradomnio
(ERANGE), quando o valor devolvido no pode ser representado por uma varivel de tipo
double. Quando tal acontece, a varivel global errno assume o valor EDOM ou
ERANGE.
A norma IEEE 754, usada na caracterizao dos tipos inteiros no computador utilizado na
disciplina, permite a deteco destas situaes por inspeco do valor devolvido. Se o erro
for EDOM e o valor devolvido for nan, ento isso significa que no um nmero. Se o
erro for ERANGE podemos ter os seguintes valores devolvidos: inf que significa que um
nmero positivo muito grande; -inf que significa que um nmero negativo muito grande;
e 0 que significa que um nmero muito pequeno.
Durante a compilao de um programa que referencia a biblioteca math.h, para que o
processo de ligao funcione, a biblioteca tem que ser explicitamente referenciada no
comando de compilao. Para tal, preciso acrescentar ao comando de compilao a opo
de compilao -lm, da seguinte forma.
cc programa.c -o programa ... -lm
As funes da biblioteca matemtica dividem-se funcionalmente em trs grupos: funes
trigonomtricas e hiperblicas; funes exponenciais e logartmicas; e funes
diversas.

47

CAPTULO 1 : INTRODUO AO C

A Figura 1.73 apresenta as funes trigonomtricas e hiperblicas.


Nome da funo
Significado
double acos (double x);
x [-1, 1] arco coseno(x) [0,S]
double asin (double x);
x [-1, 1] arco seno(x) [-S/2,S/2]
double atan (double x);
arco tangente(x) [-S/2,S/2]
double atan2 (double y, double x);
xz0 ou yz0
arco tangente(y/x) ]-S,S]
double cos (double x);
coseno(x)
double sin (double x);
seno(x)
double tan (double x);
tangente(x)
double cosh (double x);
(ex+e-x)/2
double sinh (double x);
(ex-e-x)/2
x
double tanh (double x);
(e -e-x)/(ex+e-x)

Figura 1.73 - Funes trigonomtricas e hiperblicas.

A Figura 1.74 apresenta as funes exponenciais e logartmicas.


Nome da funo
double exp (double x);
double frexp (double x, int *y);
double
double
double
double
double
double

Significado
ex
x = frexp (x,y) * 2*y, com
frexp (x,y) ]0.5,1]
ldexp (double x, int y);
x * 2y
log (double x);
loge(x), com x>0
log10 (double x);
log10(x), com x>0
modf (double x, double *y);
x = modf (x,y) + *y, com
|modf (x,y)| [0,1[
pow (double x, double y);
xy, com xz0 ou y>0
e xt0 ou ento y no tem parte fraccionria
sqrt (double x);
x

Figura 1.74 - Funes exponenciais e logartmicas.

A Figura 1.75 apresenta as funes diversas.


Nome da funo
Significado
double ceil (double x);
truncatura por excesso
double fabs (double x);
|x|
double floor (double x);
truncatura por defeito
double fmod (double x, double y); resto da diviso de x por y (yz0)

Figura 1.75 - Funes diversas.

1.9.3 Biblioteca errno


Algumas funes da biblioteca de execuo ANSI, particularmente as funes matemticas,
para alm de devolverem um valor especfico em situaes de erro, afectam uma varivel
global de tipo int, designada por errno, com um cdigo de erro. A biblioteca errno contm
a aluso varivel errno e a definio dos cdigos de erro. Para utilizar as suas funes
preciso fazer a incluso do ficheiro de interface errno.h com a seguinte directiva do prprocessador.
#include <errno.h>

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

48

A sua aplicao prtica vem muitas vezes associada com o envio da mensagem de erro
correspondente para o dispositivo convencional de sada de erro, que normalmente o
mesmo que o dispositivo convencional de sada, ou seja, o monitor.
Isto feito usando a funo perror, descrita no ficheiro de interface stdio.h, que imprime no
dispositivo convencional de sada de erro uma combinao de duas mensagens, separadas
pelo carcter :.
mensagem definida pelo programador : mensagem associada ao valor de errno
A Figura 1.76 apresenta um exemplo da utilizao da funo perror.
#include <stdio.h>
#include <errno.h>
...
errno = 0;
Z = log (X);
if (errno != 0)
/* ocorreu uma situao de erro */
{
perror ("erro no clculo de um logaritmo na rotina ...");
...
}
...

Figura 1.76 - Exemplo da utilizao da funo perror.

1.9.4 Biblioteca stdlib


A biblioteca stdlib contm funes de utilidade geral e define quatro tipos de dados. Para
utilizar as suas funes preciso fazer a incluso do ficheiro de interface stdlib.h com a
seguinte directiva do pr-processador.
#include <stdlib.h>
As funes dividem-se funcionalmente em vrios grupos. As funes para gerao de
sequncias pseudo-aleatrias, que permitem fazer uma aproximao gerao de uma
sequncia de valores que segue uma funo densidade de probabilidade uniformemente
distribuda. As funes para realizao de operaes inteiras elementares, como a
obteno do valor absoluto e do quociente e do resto de divises inteiras. As funes para
converso de cadeias de caracteres em quantidades numricas.
A Figura 1.77 apresenta as funes para gerao de sequncias pseudo-aleatrias.
Nome da funo
int rand (void);
void srand (unsigned int s);
sucessivas invocaes de rand()
pseudo-aleatrios determinada por

rand()
aps a
produzem
s, sendo

Significado
{0, 1, ... , RAND_MAX}
invocao desta funo,
uma sequncia de valores
s a semente de gerao

Figura 1.77 - Funes para gerao de sequncias pseudo-aleatrias.

49

CAPTULO 1 : INTRODUO AO C

A Figura 1.78 apresenta as funes para realizao de operaes inteiras elementares.


Nome da funo
int abs (int x);
long labs (long x);
div_t idiv (int x, int y);
ldiv_t ldiv (long x, long y);

Significado
|x|
|x|
quociente e resto da diviso
inteira de x por y
quociente e resto da diviso
inteira de x por y

Figura 1.78 - Funes para realizao de operaes inteiras elementares.

Para as duas ltimas funes, os valores do quociente e do resto da diviso de operandos


de tipo int ou de tipo long so devolvidos conjuntamente numa varivel de tipo complexo,
div_t ou ldiv_t, assim definida para o primeiro caso.
typedef struct
{
int quot;
int rem;
} div_t;

/* quociente */
/* resto */

A definio do tipo ldiv_t formalmente semelhante, substitui-se apenas int por long em
todas as ocorrncias.
A Figura 1.79 apresenta as funes para converso de cadeias de caracteres em quantidades
numricas.
Nome da funo
Significado
double atof (const char *str);
converte a cadeia de caracteres
apontada por str numa quantidade real de tipo double
int atoi (const char *str);
converte a cadeia de caracteres
apontada por str numa quantidade inteira de tipo int

Figura 1.79 - Funes para converso de cadeias de caracteres em quantidades numricas.

Para alm dos tipos div_t e ldiv_t, a biblioteca stdlib define ainda o tipo size_t que o tipo
do resultado do operador sizeof. Contm tambm dois identificadores que podem ser
usados pela funo exit para indicar o estado de execuo de um programa. A constante
EXIT_FAILURE, que vale 1, pode ser usada para indicar a finalizao do programa sem
sucesso. A constante EXIT_SUCCESS, que vale 0, pode ser usada para indicar a
finalizao do programa com sucesso.
Esta biblioteca contm ainda as funes para gesto de memria, que permitem adjudicar e
libertar memria dinmica, que sero apresentadas mais tarde.

1.10 Leituras recomendadas


x 3, 4, 5 e 6 captulos do livro C A Software Approach, 3 edio, de Peter A.
Darnell e Philip E. Margolis, da editora Springer-Verlag, 1996.

Captulo 2
COMPLEMENTOS SOBRE C

Sumrio
Este captulo dedicado aos aspectos mais avanados da linguagem C. Comeamos por
apresentar o conceito de funo generalizada, que o nico tipo de subprograma existente
e como atravs dela se implementam funes e procedimentos. Explicamos a passagem de
parmetros s funes e introduzimos os primeiros conceitos sobre ponteiros.
De seguida, apresentamos o modo como se definem os tipos de dados estruturados,
nomeadamente, os agregados unidimensionais, bidimensionais e tridimensionais, as cadeias
de caracteres e a biblioteca string que providencia funes para a sua manipulao e os
registos, que na linguagem C se designam por estruturas. Apresentamos tambm como se
definem tipos de dados enumerados.
Devido interaco entre os agregados e os ponteiros, o que se costuma designar por
dualidade ponteiro agregado, e necessidade de implementar programas mais eficientes
recorrendo a este novo tipo de dados, vamos expondo as suas caractersticas mais
avanadas medida que explicamos os tipos de dados estruturados. Apresentamos,
designadamente, os seus operadores especficos, a aritmtica de ponteiros, a dualidade
ponteiro agregado, para agregados unidimensionais e multidimensionais e a construo de
estruturas de dados versteis envolvendo agregados e ponteiros.
Apresentamos tambm as classes de armazenamento das variveis e redefinimos os
conceitos de objectos locais e globais, bem como dos nveis de visibilidade dos objectos
aplicados ao facto da linguagem C permitir a construo de aplicaes distribudas por
vrios ficheiros fonte.

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

2.1 Funes
No estabelecimento de solues de problemas complexos essencial controlar o grau de
complexidade da descrio. A descrio deve ser organizada de uma forma hierarquizada,
tambm designada de decomposio do topo para a base (Top-Down Decomposition), de
modo a que corresponda, a cada nvel de abstraco considerado, uma decomposio num
nmero limitado de operaes, com recurso a um nmero tambm limitado de variveis.
S assim possvel:
x Minimizar a interaco entre as diferentes operaes, impondo regras estritas na
formulao das dependncias de informao.
x Desenvolver metodologias que, de uma forma simples, conduzam demonstrao da
correco dos algoritmos estabelecidos.
x Conceber um desenho da soluo que:
x Promova a compactao atravs da definio de operaes reutilizveis em
diferentes contextos.
x Enquadre a mudana ao longo do tempo, possibilitando a alterao de
especificaes com um mnimo de esforo.
A transcrio da soluo numa linguagem de programao deve, depois, procurar reflectir a
hierarquia de decomposio que foi explicitada na descrio. Isto consegue-se fazendo um
uso intensivo dos mecanismos de encapsulamento de informao, presentes na
linguagem utilizada, para implementar cada operao como uma seco autnoma de
cdigo, ou seja, com um subprograma e promover, assim, uma visibilidade controlada dos
detalhes de implementao.
Desta forma, possvel:
x Separar a especificao da operao, ou seja, a descrio da sua funcionalidade interna e
do mecanismo de comunicao com o exterior, constituda pelas variveis de entrada e de
sada, da sua implementao.
x Introduzir caractersticas de robustez e de desenho para o teste no cdigo produzido.
x Validar de uma maneira controlada e integrar de um modo progressivo os diferentes
componentes da aplicao
x Planear com eficcia a distribuio de tarefas pelos diversos membros da equipa de
trabalho.
Na linguagem Pascal existem dois tipos de subprogramas. O procedimento, que se
apresenta na Figura 2.1, um mecanismo de encapsulamento de informao que permite a
construo de operaes mais complexas a partir de uma combinao de operaes mais
simples. Aps a sua definio, a nova operao identificada por um nome e por uma lista
opcional de parmetros de comunicao. Os parmetros de entrada, que representam
valores necessrios realizao da operao, e os parmetros de sada, que representam
valores produzidos pela realizao da operao.

CAPTULO 2 : COMPLEMENTOS SOBRE C

Parmetros
de
Entrada

Procedimento

Parmetros
de
Sada

(0)

(0)
Figura 2.1 - Esquema de um procedimento.

A funo, que se apresenta na Figura 2.2, um mecanismo de encapsulamento de


informao que permite a descrio de um processo de clculo complexo a partir de uma
combinao de operaes mais simples. Aps a sua definio, a nova operao
identificada por um nome e por uma lista opcional de parmetros de comunicao. Os
parmetros de entrada, que representam valores necessrios realizao da operao e pela
indicao do tipo do resultado de sada, ou seja, do valor calculado e devolvido pela funo.

Parmetros
de
Entrada

Resultado
de
Sada

Funo

(0)
Figura 2.2 - Esquema de uma funo.

Na linguagem C s temos um tipo de subprograma que a funo generalizada, que se


apresenta na Figura 2.3. A funo generalizada um mecanismo de encapsulamento de
informao que combina as caractersticas apresentadas pelo procedimento e pela funo.
Semanticamente, pode ser encarada como um procedimento que devolve o estado de
realizao da operao. Pode ser visto como uma caixa preta que recebe informao
entrada e que produz informao sada, sendo que os detalhes associados com a
implementao da operao so invisveis externamente e no originam qualquer interaco
com o exterior. A informao de entrada, quando necessria realizao da operao,
fornecida funo generalizada atravs de um conjunto de parmetros de entrada. A
informao de sada, quando produzida pela realizao da operao, fornecida atravs
de um conjunto de parmetros de sada e/ou pela produo de um resultado de sada.

Parmetros
de
Entrada
(0)

Funo
Generalizada

Parmetros
de
Sada
(0)
Resultado
de Sada

Figura 2.3 - Esquema de uma funo generalizada da linguagem C.

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

Aps a sua definio, a nova operao identificada por um nome, por uma lista opcional
de parmetros de comunicao, ou seja, os parmetros de entrada, os parmetros de sada,
e pela indicao do tipo do valor devolvido.
A Figura 2.4 apresenta a definio da funo de converso de distncias de milhas para
quilmetros no Pascal e na linguagem C.
function CONVERTE_DISTANCIA (ML: real): real;
(* no Pascal *)
const MIL_QUI = 1.609;
begin
CONVERTE_DISTANCIA := MIL_QUI * ML
end;

#define MIL_QUI 1.609


/* na linguagem C */
...
double CONVERTE_DISTANCIA (double ML)
/* definio da funo */
{
return ML * MIL_QUI;
}

Figura 2.4 - Exemplo da funo da converso de distncias de milhas para quilmetros.

Uma funo pode aparecer num programa, de trs formas diferentes:


x A definio a declarao que define o que a funo faz, bem como o nmero e tipo
de parmetros e o tipo de resultado de sada. Normalmente colocada depois da funo
main, ou num ficheiro de implementao.
x A invocao invoca a execuo da funo.
x A aluso, ou prottipo a declarao que define a interface da funo, ou seja, o tipo, o
nome e a lista de parmetros. Normalmente colocada no incio do ficheiro fonte logo
aps as directivas de include, ou num ficheiro de interface, que tem a extenso .h.

2.1.1 Definio de uma funo


Tal como j foi mostrado para a funo main na Figura 1.3, qualquer funo na linguagem
C, supe a especificao do seu cabealho e do seu corpo. A Figura 2.5 apresenta
detalhadamente os vrios componentes da funo CONVERTE_DISTANCIA.
No cabealho, indica-se o tipo do valor devolvido, o nome da funo, e entre parnteses
curvos, a lista de parmetros de comunicao. Se a funo no tiver lista de parmetros
utilizada a declarao de void. Se a funo no tiver resultado de sada utilizada a
declarao de tipo void, como sendo o tipo do valor devolvido.
O corpo da funo delimitado pelos separadores { e }. Para devolver o valor calculado
pela funo, utiliza-se a palavra reservada return seguido da expresso a devolver. O valor
de retorno pode ser de qualquer tipo, excepto um agregado ou uma funo. possvel
devolver uma estrutura ou unio, mas no recomendado porque ineficiente. Uma
funo pode conter qualquer nmero de instrues return. Se no houver instruo de
return, a execuo da funo termina quando atingido o separador }. Nesse caso, o valor
devolvido indefinido. O valor devolvido na instruo de return deve ser compatvel em
termos de atribuio com o tipo de sada da funo. Para determinar se o valor devolvido
aceitvel, o compilador usa as mesmas regras de compatibilidade da instruo de atribuio.

CAPTULO 2 : COMPLEMENTOS SOBRE C

tipo de
sada

nome da
funo

lista de
parmetros
cabealho

double CONVERTE_DISTANCIA (double ML)


{
return

ML * MIL_QUI;

corpo

Figura 2.5 - A funo CONVERTE_DISTANCIA.

Normalmente os parmetros de uma funo so parmetros de entrada e como tal so


passados por valor. Ou seja, a funo no altera o valor da varivel passada funo. A
passagem por valor consiste em referir apenas a varivel, ou a expresso, na invocao da
funo.
Mas, por vezes necessrio usar parmetros de entrada-sada numa funo. Ou seja, o
valor da varivel necessita de ser alterada durante a execuo da funo. Nesses casos
estamos perante a passagem de parmetros por referncia, tambm designada por
endereo. Nesta situao passado o endereo, ou seja, a localizao da varivel na
memria. Isso feito usando um ponteiro para a varivel na invocao da funo.

2.1.2 Introduo aos ponteiros


Uma caracterstica saliente da linguagem C permitir referenciar de um modo muito
simples a localizao de variveis em memria e, a partir dessa localizao, aceder ao seu
contedo. Para isso existem os dois operadores unrios apresentados na Figura 2.6 que so
aplicados directamente a uma varivel.
localizao em memria da varivel ::= & identificador de varivel
referncia indirecta varivel ::= * identificador de varivel de tipo ponteiro
Figura 2.6 - Definio formal dos operadores para manipulao de variveis atravs de ponteiros.

O operador endereo, cujo smbolo o &, d a localizao em memria da varivel.


localizao de uma varivel chama-se habitualmente ponteiro para a varivel, j que o
valor que lhe est associado aponta para, ou referencia, a regio de memria onde essa
varivel est armazenada. Mais especificamente, o valor que est associado a um ponteiro
representa o endereo do primeiro byte da regio de armazenamento da varivel. Assim, um
ponteiro, embora assuma o valor de um endereo, no propriamente um endereo. O que
est subjacente ao conceito no a localizao de um byte, que define a diviso fsica da
memria, mas de uma varivel, ou seja, da diviso lgica da memria. Portanto, no faz
sentido falar-se em ponteiros em abstracto, porque um ponteiro est sempre associado a
um tipo de dados bem definido. Como nos processadores actuais, o barramento de
endereos tem uma dimenso de 32 bits, o tamanho do formato do tipo ponteiro igual a 4
bytes. Por isso, tem-se que sizeof (ponteiro para um tipo de dados genrico) = 4.
O operador apontado por, cujo smbolo o *, d acesso varivel, cuja localizao em
memria est armazenada numa varivel de tipo ponteiro, ou seja, uma referncia
indirecta varivel. Este operador tem outro significado quando usado na declarao de

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

uma varivel. Nessa situao indica que a varivel de tipo ponteiro. E quando utilizado
na lista de parmetros, precedendo um parmetro, indica que o parmetro um parmetro
de entrada-sada, ou seja, que uma passagem por referncia.
A Figura 2.7 apresenta um exemplo da manipulao de uma varivel atravs de um
ponteiro. A primeira linha de cdigo declara uma varivel A de tipo int e uma varivel PA
de tipo ponteiro para int. A atribuio PA = &A, coloca na varivel PA o endereo da
varivel A, da que, a varivel PA fica a apontar para a varivel A. A atribuio *PA = 23
coloca o valor 23 na varivel A, atravs do ponteiro PA.
int A, *PA;
...
PA = &A;
*PA = 23;

23

PA

Figura 2.7 - Exemplo de manipulao de uma varivel atravs de um ponteiro.

2.1.3 Invocao de uma funo


Aquando da invocao de uma funo, o controlo do programa passa para a funo. A
invocao de uma funo uma expresso e como tal pode aparecer em qualquer stio
onde possa aparecer uma expresso. A funo devolve sempre um valor, a no ser quando
o valor devolvido void. No entanto, possvel invocar uma funo sem utilizar o valor
devolvido.
A Figura 2.8 apresenta a invocao da funo CONVERTE_DISTANCIA. Podemos
invocar a funo numa expresso de atribuio do seu valor varivel QUILOMETROS,
ou em alternativa podemos invoc-la directamente na instruo de sada de dados printf. A
definio da funo deve ser colocada aps a funo main.
#include <stdio.h> /* interface com a biblioteca de entrada/sada */
#define MIL_QUI 1.609

/* definio do factor de converso */

double CONVERTE_DISTANCIA (double);


int main ( void )
{
double MILHAS,
QUILOMETROS;
do
{

/* aluso funo */

/* distncia expressa em milhas */


/* distncia expressa em quilmetros */

/* leitura com validao da distncia expressa em milhas */

printf ("Distncia em milhas? ");


scanf ("%lf", &MILHAS);
} while (MILHAS < 0.0);
/* invocao da funo */
QUILOMETROS = CONVERTE_DISTANCIA (MILHAS);
/* impresso da distncia expressa em quilmetros */
printf ("Distncia em quilmetros %8.3f\n", QUILOMETROS);
return 0;
}
...

Figura 2.8 - Programa da converso de distncias utilizando a funo CONVERTE_DISTANCIA.

CAPTULO 2 : COMPLEMENTOS SOBRE C

2.1.4 Aluso de uma funo


A aluso de uma funo, permite ao compilador assegurar que o nmero correcto de
parmetros so passados na invocao. Probe a passagem de parmetros que no possam
ser convertidos para o tipo correcto. Por outro lado, converte pacificamente os parmetros
quando isso possvel. Permite tambm verificar se a atribuio do resultado da funo
est de acordo com o tipo do valor devolvido pela funo. O cdigo apresentado na Figura
2.8 faz a aluso funo CONVERTE_DISTANCIA na terceira linha. Na lista de
parmetros indica-se apenas o tipo dos parmetros e omite-se os seus nomes, uma vez que
o compilador necessita apenas de saber o tipo dos parmetros, para verificar se a passagem
de parmetros est correcta quando se invoca a funo.

2.1.5 Procedimentos na linguagem C


Na linguagem C um procedimento implementado por uma funo que no devolve
qualquer valor, ou seja, cujo tipo do resultado de sada de tipo void. No corpo de uma
funo que devolve o tipo void, no necessrio utilizar a instruo return. A no ser que,
por razes de algoritmo haja a necessidade de abandonar a funo em stios diferentes.
Nesse caso usa-se a instruo return sem expresso.
A Figura 2.9 apresenta o procedimento TROCA em Pascal e a sua implementao com
uma funo na linguagem C.
procedure TROCA (var X, Y: integer);
(* no Pascal *)
var TEMP : integer;
begin
TEMP := X; X := Y; Y := TEMP
end;

void TROCA (int *X, int *Y)


/* na linguagem C */
{
int TEMP;
TEMP = *X; *X = *Y; *Y = TEMP;
}

Figura 2.9 - Procedimento na linguagem C.

Na definio da funo os dois parmetros X e Y so parmetros de entrada-sada, pelo


que, so passados por referncia atravs de ponteiros para as variveis. Para indicar que o
parmetro um parmetro de entrada-sada, coloca-se o operador apontado por atrs do
parmetro. Na invocao da funo necessrio passar a localizao em memria das
variveis actuais, atravs de ponteiros para as variveis. Para passar a localizao da varivel,
coloca-se o operador endereo atrs da varivel, tal como se mostra na Figura 2.10.
void TROCA (int *X, int *Y);
...
int A, B;
...
TROCA (&A, &B);

/* aluso funo */
/* definio das variveis */
/* invocao da funo */

Figura 2.10 - Exemplo da invocao da funo TROCA.

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

2.2 Agregados ( arrays )


O construtor agregado ( array ) um mecanismo usado por muitas linguagens de
programao para criar tipos de dados estruturados, ou seja, tipos de dados mais
complexos a partir de tipos de dados mais simples, designados de tipo base. O tipo
agregado concebido como um conjunto ordenado de objectos do tipo base. Cada objecto
do tipo base, designado genericamente por elemento, referenciado pela posio que
ocupa no agregado. Estamos perante um acesso por indexao.
A Figura 2.11 apresenta graficamente um agregado unidimensional com 11 elementos e um
agregado bidimensional com 15 elementos.
Agregado unidimensional com
11 elementos do tipo base,
com o 3 elemento marcado.

tipo base

Agregado bidimensional com


15 elementos do tipo base,
com o elemento da 4 coluna
da 3 linha marcado.

Figura 2.11 - Visualizao grfica de agregados unidimensionais e bidimensionais.

A declarao de variveis de tipo agregado segue a regra geral de declarao de variveis da


linguagem C e a sua definio formal apresentada na Figura 2.12.

tipo de dados lista de variveis de tipo agregado ;


tipo de dados ::= qualquer tipo de dados vlido na linguagem C
lista de variveis de tipo agregado ::= identificador de varivel de tipo agregado |
lista de variveis de tipo agregado , identificador de varivel de tipo agregado
identificador de varivel de tipo agregado ::= identificador de varivel descritor de dimenso |
identificador de varivel descritor de dimenso = expresso de inicializao
identificador de varivel ::= qualquer identificador vlido na linguagem C
descritor de dimenso ::= [ ] | [ constante numrica inteira positiva ] |
descritor de dimenso [ ] |
descritor de dimenso [ constante numrica inteira positiva ]
expresso de inicializao ::= { listas de constantes do tipo base }
Figura 2.12 - Definio formal da declarao de um tipo agregado.

A Figura 2.13, a Figura 2.41 e a Figura 2.42 apresentam respectivamente exemplos da


declarao de agregados unidimensionais, bidimensionais e tridimensionais, bem como da
sua colocao na memria. Uma varivel de tipo agregado distingue-se de uma varivel
simples, devido aos descritores de dimenso, ou seja, aos parnteses rectos a seguir ao
nome da varivel. A constante numrica inteira positiva, includa no descritor de dimenso,
indica o tamanho da dimenso. Se se tratar de um agregado unidimensional, isto
equivalente a indicar o nmero de elementos do agregado. Para um agregado
multidimensional, o nmero de elementos do agregado calculado pelo produto dos
tamanhos das diferentes dimenses.

CAPTULO 2 : COMPLEMENTOS SOBRE C

Quando o descritor de dimenso no inclui a constante numrica positiva, diz-se que se


est perante uma definio incompleta, j que a reserva de espao de armazenamento no
feita. O seu uso , por isso, bastante restrito e geralmente usado quando se faz a
inicializao no momento da declarao. A inicializao feita atravs de listas de
constantes do tipo base, separadas pela vrgula e inseridas entre chavetas. O nmero de
listas de inicializao e de nveis de chavetas depende do nmero de dimenses do
agregado.
Um elemento particular do agregado referenciado atravs da indicao do nome da
varivel, seguido do valor de um ou mais ndices, tantos quantas as dimenses do agregado,
colocados individualmente entre parnteses rectos. Na linguagem C, os ndices so
variveis numricas inteiras positivas e o ndice do elemento localizado mais esquerda em
cada dimenso o ndice zero.

2.2.1 Agregados unidimensionais


A Figura 2.13 apresenta exemplos da declarao e inicializao de agregados
unidimensionais, bem como, da sua colocao na memria.
int A[4] = {10, 21, -5, 13};
double B[3] = {10.2, 11.8};
int C[] = {22, 12};

A[0]

10

A[1]

21

A[2]

-5

A[3]

13

B[0]

10.2

B[1]

11.8

B[2]

0.0

C[0]

22

C[1]

12

Figura 2.13 - Declarao e inicializao de agregados unidimensionais.

A expresso de inicializao para variveis de tipo agregado unidimensional de um tipo


base consiste numa lista de constantes do tipo base, separadas pela vrgula e colocadas
entre chavetas. A atribuio das constantes aos elementos do agregado processa-se da
esquerda para a direita e realizada at ao esgotamento da lista.
Assim, a constante numrica positiva, includa no descritor de dimenso, ter que ser
maior, ou quando muito igual, ao nmero de constantes da lista. Se for igual, como o
caso do agregado A, todos os elementos do agregado sero inicializados com os valores
indicados. Se for maior, como o caso do agregado B, s os K primeiros elementos, em
que K igual ao nmero de constantes da lista, sero inicializados com os valores
indicados, sendo que os restantes elementos so inicializados a zero. Alternativamente,
como o caso do agregado C, o descritor de dimenso pode ser omitido e ento ser
reservado espao, e sero inicializados, tantos elementos quanto o nmero de constantes da
lista de inicializao.

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

10

2.2.2 Dualidade ponteiro agregado


Em geral, quando numa instruo se referencia o nome de uma varivel, pretende-se aludir
como um todo regio de memria que est reservada para armazenamento dos seus
valores. Tal no se passa, contudo, quando se referencia o nome de uma varivel de tipo
agregado unidimensional. O significado atribudo , neste caso, o de um ponteiro para o
primeiro elemento do agregado.
TIPO_BASE A[N]; A { &A[0]
Na linguagem C existe aquilo que se costuma designar por dualidade ponteiro agregado,
j que, quer uma varivel de tipo agregado unidimensional de um dado tipo base, quer uma
varivel de tipo ponteiro para o mesmo tipo base, so formas equivalentes de referenciar
uma regio de memria formada por um conjunto contguo de variveis do tipo base.
Consequentemente, possvel aceder a um elemento do agregado unidimensional, quer da
forma usual em Pascal, quer atravs do operador apontado por.
A+i { &A[i] *(A+i) { *&A[i] { A[i], com 0 d i < N
Ou seja, a localizao do elemento de ndice i do agregado A pode ser alternativamente
expressa por &A[i] ou por A+i, e o valor do mesmo elemento por A[i] ou por *(A+i).
Por conseguinte, uma varivel de tipo ponteiro para um tipo base pode ser encarada como
uma varivel de tipo agregado unidimensional do mesmo tipo base na referncia a uma
regio de memria formada por um conjunto contguo de variveis do tipo base. Pelo que,
a seguinte declarao do agregado A e do ponteiro para o agregado PA inicializado com o
endereo inicial do agregado, resulta no mapa de reserva de espao em memria que se
apresenta na Figura 2.14 e permite a utilizao do ponteiro PA para aceder aos elementos
do agregado.
TIPO_BASE A[N], *PA = A;
PA+i { &PA[i] = &A[i] *(PA+i) { PA[i] = A[i], com 0 d i < N
A

TIPO_BASE A[8], *PA = A;


A[0]
A[1]
A[2]
A[3]
A[4]
A[5]
A[6]
A[7]
PA

Figura 2.14 - Declarao de um agregado unidimensional e de um ponteiro para o agregado.

Existe, porm, uma diferena subtil entre as variveis de tipo agregado unidimensional de
um tipo base e as variveis de tipo ponteiro para o mesmo tipo base. Como se verifica do
mapa de reserva de espao em memria apresentado na Figura 2.14, no existe espao
directamente associado com a varivel A e, portanto, A um ponteiro constante, cujo valor
no pode ser modificado. Assim, instrues do tipo A = expresso; so ilegais.

11

CAPTULO 2 : COMPLEMENTOS SOBRE C

2.2.3 Agregados como parmetros de funes


Na linguagem C, dentro de uma funo no possvel saber com quantos elementos foi
declarado um agregado que foi passado como argumento para essa funo. Pelo que, para
alm do agregado deve ser passado o nmero de elementos a processar. A Figura 2.15
apresenta o cdigo da definio de uma funo que inicializa a zero todos os elementos de
um agregado. A funo tem um parmetro de entrada-sada que o agregado a inicializar e
um parmetro de entrada que a dimenso do agregado. Para indicar que o parmetro de
entrada-sada um agregado, usa-se o nome do parmetro formal seguido dos parnteses
rectos. Quando da invocao da funo, o endereo inicial do agregado passado funo
usando o nome do agregado.
void INICIALIZAR (int A[], int N)
/* definio da funo */
{
int I;
for (I = 0; I < N; I++) A[I] = 0;
}

int X[10];
...
INICIALIZAR (X, 10);
/* invocao da funo */

Figura 2.15 - Passagem de um agregado a uma funo.

possvel obter o nmero de elementos de um agregado dividindo o tamanho em bytes do


agregado pelo tamanho em bytes de um dos seus elementos, ou seja, pelo tamanho do tipo
base, utilizando para tal a seguinte expresso envolvendo o operador sizeof.
sizeof ( agregado ) / sizeof ( elemento do agregado )
A expresso funciona independentemente do tipo de elementos do agregado. A Figura 2.16
apresenta uma aplicao desta expresso. O agregado C declarado sem descritor de
dimenso, sendo no entanto inicializado. Na altura da invocao da funo FUNC usamos
a expresso para passar o nmero de elementos do agregado ao parmetro de entrada N.
void FUNC (int A[], int N);
/* aluso da funo */
...
int C[] = {20, 21, 22, 23};
...
/* invocao da funo FUNC para o agregado C */
FUNC (C, sizeof (C) / sizeof (C[0]));

Figura 2.16 - Utilizao do operador sizeof para calcular o nmero de elementos de um agregado.

Um dos erros mais frequentemente cometido, por programadores que se esto a iniciar na
utilizao da linguagem C, ultrapassar a dimenso de um agregado, nomeadamente em
ciclos repetitivos for. No exemplo apresentado na Figura 2.17, o for vai processar o
agregado desde o elemento de ndice 0 at ao elemento de ndice 10, que no existe no
agregado. Acontece que nesta posio de memria que est armazenada a varivel I que
controla o for, pelo que, o seu valor vai ser reinicializado a zero e portanto o for de novo
repetido. Estamos perante um ciclo repetitivo infinito criado por engano.
int I, AR[10];
...
for (I = 0; I <= 10; I++) AR[I] = 0;

/* situao de erro */

Figura 2.17 - Situao de erro na utilizao do ciclo for para processar um agregado.

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

12

2.3 Cadeias de caracteres ( strings )


Na linguagem C, uma cadeia de caracteres ( string ) entendida como um agregado
unidimensional de caracteres terminado obrigatoriamente pelo carcter nulo '\0', tambm
reconhecido pelo identificador NUL. A expresso de inicializao para variveis de tipo
cadeia de caracteres, pode por isso ser estabelecida alternativamente segundo as regras
anteriores de inicializao de agregados unidimensionais, tendo o cuidado de garantir que a
lista de constantes inclui em ltimo lugar o carcter nulo, ou atravs de uma constante de
tipo cadeia de caracteres, ou seja, uma sequncia de caracteres delimitada por aspas duplas,
onde o compilador coloca automaticamente no fim o carcter nulo. Quando se declara e
inicializa uma cadeia de caracteres e o descritor de dimenso maior do que o nmero de
caracteres de inicializao, ento todos os restantes caracteres da cadeia so inicializados
com o carcter nulo.
A Figura 2.18 apresenta alguns exemplos de declarao e inicializao de cadeias de
caracteres. A varivel VOGAIS no uma cadeia de caracteres porque no foi terminada
com o carcter nulo e foi declarada para armazenar apenas as cinco vogais. Pelo que,
apenas um agregado de caracteres. A varivel CIDADE tambm est declarada como um
agregado de caracteres, mas, como foi declarado com o tamanho de 10 caracteres, ento
todos os caracteres no inicializados, que so quatro, so colocados a '\0'. Pelo que, de
facto uma cadeia de caracteres. A varivel OLA uma cadeia de caracteres e est
explicitamente declarada com o carcter nulo. A varivel CARTA no uma cadeia de
caracteres, porque apesar de ser inicializada com uma constante de tipo cadeia de
caracteres, foi declarada com um tamanho insuficiente para armazenar o carcter nulo. A
varivel TEXTO est declarada sem especificao de tamanho e portanto, criado com a
dimenso exacta para conter a cadeia de caracteres de inicializao mais o carcter nulo, ou
seja, com a dimenso de 16 caracteres.
char VOGAIS[5] = {'a', 'e', 'i', 'o', 'u'};
/* VOGAIS no uma cadeia de caracteres */
char CIDADE[10] = {'A', 'v', 'e', 'i', 'r', 'o'};
/* CIDADE uma cadeia de caracteres */
char OLA[4] = {'o', 'l', 'a', '\0'};
/* OLA uma cadeia de caracteres */
char CARTA[4] = "tres";
/* CARTA no uma cadeia de caracteres */
char TEXTO[] = "era uma vez ...";
/* TEXTO uma cadeia de caracteres */

Figura 2.18 - Exemplos de declarao e inicializao de cadeias de caracteres.

A Figura 2.19 apresenta o mapa de reserva de espao em memria da declarao de duas


cadeias de caracteres. A varivel OLA foi declarada e inicializada com quatro caracteres. A
varivel TB inicializada com uma constante de 8 caracteres e o carcter nulo
automaticamente colocado no fim.

13

CAPTULO 2 : COMPLEMENTOS SOBRE C

char OLA[4] = {'o', 'l', 'a', '\0'};


char TB[] = "tudo bem";

OLA[0]

'o'

OLA[1]

'l'

OLA[2]

'a'

OLA[3]

'\0'

TB[0]

't'

TB[1]

'u'

TB[2]

'd'

TB[3]

'o'

TB[4]

' '

TB[5]

'b'

TB[6]

'e'

TB[7]

'm'

TB[8]

'\0'

Figura 2.19 - Mapa de reserva de espao em memria de duas cadeias de caracteres.

possvel criar um ponteiro para uma cadeia de caracteres e simultaneamente inici-lo com
uma cadeia de caracteres constante, usando para esse efeito a seguinte instruo.
char *ponteiro = "constante cadeia de caracteres"
A Figura 2.20 apresenta o mapa de reserva de espao em memria de um exemplo deste
tipo de declarao. O ponteiro PTS aponta para a cadeia de caracteres Aveiro, que uma
cadeia de caracteres constante, pelo que, no pode ser alterada. O valor de PTS pode ser
alterado, por exemplo, atribuindo-lhe outra constante, ou uma varivel, de tipo cadeia de
caracteres, e caso o seja, perde-se o acesso cadeia de caracteres Aveiro.
char *PTS = "Aveiro";

'A'
'v'
'e'
'i'
'r'
'o'
'\0'

PTS

4 bytes

Figura 2.20 - Mapa de reserva de espao em memria de um ponteiro para uma cadeia de caracteres
inicializado com uma cadeia de caracteres constante.

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

14

2.3.1 Atribuio de cadeias de caracteres


Uma cadeia de caracteres um agregado de caracteres, portanto, uma constante de tipo
cadeia de caracteres tambm um ponteiro para o seu primeiro carcter. Pelo que, quando
uma varivel de tipo cadeia de caracteres declarada com o operador [ ], no possvel
atribuir-lhe directamente uma constante de tipo cadeia de caracteres, uma vez que o nome
da varivel cadeia de caracteres um ponteiro constante, logo no pode ser alterado. Mas,
j possvel, atribuir uma constante de tipo carcter a um elemento da varivel, indicando
para esse efeito o ndice do elemento entre parnteses rectos. Para atribuir a uma varivel
de tipo cadeia de caracteres, uma constante de tipo cadeia de caracteres ou outra varivel de
tipo cadeia de caracteres, existe a funo strcpy da biblioteca de execuo ANSI string.
Mas j possvel atribuir a uma varivel de tipo ponteiro para char uma constante de tipo
cadeia de caracteres ou outra varivel de tipo cadeia de caracteres. A Figura 2.21 apresenta
um exemplo que mostra esta diferena.
char CIDADE[10] = "aveiro";
/* CIDADE igual a "aveiro"
char *PTS = "Aveiro";
/* PTS aponta para "Aveiro"
...
CIDADE = "Lisboa";
/* ERRO no se pode fazer esta atribuio
CIDADE[0] = 'A';
/* agora CIDADE igual a "Aveiro"
strcpy (CIDADE, "Lisboa");
/* agora CIDADE igual a "Lisboa"
PTS = "Lisboa";
/* agora PTS aponta para "Lisboa"

*/
*/
*/
*/
*/
*/

Figura 2.21 - Exemplo da atribuio de cadeias de caracteres.

2.3.2 Cadeias de caracteres versus caracteres


Uma constante de tipo carcter colocado entre pelicas, por exemplo 'a', enquanto que
uma constante de tipo cadeia de caracteres colocada entre aspas, por exemplo "a". Existe
uma diferena importante entre um carcter e uma cadeia de caracteres, mesmo quando
esta apenas constituda por um nico carcter. Enquanto que um carcter ocupa apenas
um byte, uma cadeia de caracteres ocupa tantos bytes quantos os caracteres, mais um byte
para armazenar o carcter nulo '\0'. A Figura 2.22 apresenta alguns exemplos da utilizao
de um ponteiro para carcter. Pode-se atribuir um carcter a um dos elementos de uma
cadeia de caracteres atravs de um ponteiro para carcter, desde que a cadeia de caracteres
no seja constante, como o caso do exemplo apresentado. Portanto, a instruo de
atribuio *PTS = 'A' d erro de execuo, devido a violao de memria protegida. A
instruo de atribuio *PTS = "A" est errada, uma vez que, no se pode atribuir um
ponteiro para carcter a um carcter. preciso ter em ateno que *PTS do tipo char,
enquanto que, "A" do tipo char *. Como j foi referido, possvel atribuir uma constante
de tipo cadeia de caracteres a um ponteiro para carcter, mas no possvel atribuir-lhe um
carcter, pelo que, a instruo de atribuio PTS = 'A' est errada.
char *PTS = "aveiro";
/* PTS aponta para "aveiro" */
...
*PTS = 'A'; /* ERRO "Aveiro" uma cadeia de caracteres constante */
*PTS = "A";
/* ERRO no se pode fazer esta atribuio */
PTS = 'A';
PTS = "A";

/* ERRO no se pode fazer esta atribuio */


/* agora PTS aponta para "A" */

Figura 2.22 - Exemplo da manipulao de um ponteiro para carcter.

15

CAPTULO 2 : COMPLEMENTOS SOBRE C

2.3.3 Cadeias de caracteres como parmetros de funes


A passagem de uma cadeia de caracteres a uma funo semelhante passagem de um
agregado. Existem duas maneiras para indicar que um parmetro de uma funo uma
cadeia de caracteres. A notao recomendada pela norma ANSI utiliza o operador [ ],
declarando que o parmetro um agregado de caracteres. Em alternativa pode-se utilizar o
operador *, declarando que o parmetro um ponteiro para carcter. Quando se passa uma
cadeia de caracteres a uma funo, no necessrio indicar o comprimento da cadeia de
caracteres, uma vez que este valor pode ser calculado recorrendo funo strlen da
biblioteca de execuo ANSI string. A Figura 2.23 apresenta um exemplo da definio e
invocao de uma funo que processa uma cadeia de caracteres.
int FUNC (char PST[])
/* definio da funo FUNC */
{
/* ou em alternativa int FUNC (char *PST) */
...
return ...;
}

char ST[] = "bom dia";


...
/* invocao da funo FUNC para a cadeia de caracteres ST */
FUNC (ST);

Figura 2.23 - Funo com um parmetro de tipo cadeia de caracteres.

2.3.4 Codificao circular de caracteres


Na linguagem C, o tipo char permite armazenar quer um carcter quer um valor inteiro.
Devido a esta polivalncia possvel misturar numa mesma expresso caracteres e valores
numricos inteiros, sem ter a necessidade de recorrer a funes de converso de tipos. Pelo
que, a expresso para implementar a codificao circular de um carcter alfabtico
simplificada, quando comparada com o Pascal. A Figura 2.24 apresenta o deslocamento
circular do carcter z trs posies para a frente, sendo transformado no carcter c. Para
obtermos o efeito de deslocamento circular num conjunto linear, temos que utilizar o
operador resto da diviso, que na linguagem C o smbolo %.
a + ( z - a + 3 ) % 26 = c

alfabeto minsculo

...
Figura 2.24 - Deslocamento circular de um carcter trs posies para a frente.

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

16

A Figura 2.25 apresenta a funo que codifica um carcter minsculo. A funo no testa
se o carcter ou no minsculo, pelo que, s deve ser invocada para caracteres
minsculos. Se o valor do deslocamento circular, que representado pelo parmetro de
entrada K, for positivo ento o carcter codificado K posies para a frente, se for
negativo ento o carcter codificado K posies para a trs, e se K for zero o carcter sai
inalterado. Apresentam-se as solues em Pascal e na linguagem C.
(* no Pascal *)
function CODIFICAR_MINUSCULO (CAR: char; K: integer): char;
var CAR_S : char;
begin
CAR_S := chr(ord('a') + (ord(CAR) - ord('a') + 26 + K) mod 26);
CODIFICAR_MINUSCULO := CAR_S
end;

char CODIFICAR_MINUSCULO (char CAR, int K)


/* na linguagem C */
{
return 'a' + (CAR - 'a' + 26 + K ) % 26;
}

Figura 2.25 - Funo que faz a codificao circular de um carcter minsculo.

2.3.5 Biblioteca string


A biblioteca string contm funes de manipulao de cadeias de caracteres. Para utilizar
as suas funes preciso fazer a incluso do ficheiro de interface string.h com a seguinte
directiva do pr-processador.
#include <string.h>
As funes dividem-se funcionalmente em cinco grupos: funes de cpia; funes de
concatenao; funes de comparao; funes de busca; e funes diversas.
A Figura 2.26 apresenta as funes de cpia.
void
void
char
char

*memcpy (void *zd, void *zp, size_t n);


*memmove (void *zd, void *zp, size_t n);
*strcpy (char *zd, char *zp);
*strncpy (char *zd, char *zp, size_t n);

Figura 2.26 - Funes de cpia.

Todas as funes copiam byte a byte o contedo da regio de memria apontada por zp para
a regio de memria apontada por zd e devolvem a localizao de zd. Est subjacente a
qualquer das funes que zd referencia uma regio de memria, cuja reserva de espao de
armazenamento foi previamente efectuada e que tem tamanho suficiente para que a
transferncia seja efectivamente possvel.
Para as funes memcpy e memmove, a transferncia realizada sem ser atribuda
qualquer interpretao ao contedo dos bytes transferidos, enquanto que, para strcpy e
strncpy, se supe que zp referencia uma cadeia de caracteres, ou seja, um agregado de
caracteres terminado obrigatoriamente pelo carcter nulo.

17

CAPTULO 2 : COMPLEMENTOS SOBRE C

No caso de memcpy e de memmove, so transferidos n bytes da regio apontada por zp


para a regio apontada por zd. A diferena entre elas est no facto de que, em memcpy, as
duas regies tm que ser disjuntas, enquanto que, em memmove, o processo de cpia
feito primeiramente para uma regio intermdia, o que possibilita a sobreposio parcelar
das duas regies.
No caso de strcpy e de strncpy, as regies apontadas por zp e zd tm que ser disjuntas. O
nmero de caracteres transferidos por strcpy varivel e depende do tamanho da cadeia de
caracteres, concretamente, so copiados todos os caracteres que formam a cadeia de
caracteres referenciada por zp, incluindo o carcter nulo final. Por outro lado, strncpy
copia um mximo de n caracteres, incluindo o carcter nulo final, da cadeia de caracteres
referenciada por zp. Se o tamanho da cadeia de caracteres for menor do que n-1, as
posies restantes da regio apontada por zd sero preenchidas com caracteres nulos. Se,
pelo contrrio, o tamanho da cadeia de caracteres for maior do que n-1, o resultado da
cpia para a regio de memria apontada por zd no constituir uma cadeia de caracteres,
porque falta o carcter nulo final.
A Figura 2.27 apresenta as funes de concatenao.
char *strcat (char *zd, char *zp);
char *strncat (char *zd, char *zp, size_t n);

Figura 2.27 - Funes de concatenao.

Ambas as funes copiam a cadeia de caracteres referenciada por zp, incluindo um carcter
nulo final, para o fim da cadeia de caracteres referenciada por zd e devolvem a localizao
de zd. O ponto de juno a localizao do carcter nulo final da cadeia de caracteres
referenciada por zd, que substitudo pelo primeiro carcter da cadeia de caracteres
referenciada por zp. Est subjacente a qualquer das funes que zd aponta para uma regio
de memria, cuja reserva de espao de armazenamento tem tamanho suficiente para conter
a cadeia de caracteres resultante. No caso da funo strncat, so copiados no mximo n
caracteres, excluindo o carcter nulo final, da cadeia de caracteres referenciada por zp. As
regies de memria apontadas por zp e zd tm que ser disjuntas.
A Figura 2.28 apresenta um exemplo da utilizao das funes strcpy e strcat.
char MENS1[] = "Ola", MENS2[] = "bom dia", MENS3[15];
...
strcpy (MENS3, MENS1);
strcat (MENS3, " ");
strcat (MENS3, MENS2);

/* MENS3 igual a "Ola" */


/* agora MENS3 igual a "Ola " */
/* agora MENS3 igual a "Ola bom dia" */

Figura 2.28 - Exemplo da utilizao das funes strcpy e strcat.

A Figura 2.29 apresenta as funes de comparao.


int memcmp (void *z1, void *z2, size_t n);
int strcmp (char *z1, char *z2);
int strncmp (char *z1, char *z2, size_t n);

Figura 2.29 - Funes de comparao.

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

18

As funes comparam byte a byte o contedo das regies de memria referenciadas por z1 e
z2. O processo de comparao termina logo que uma deciso possa ser univocamente
tomada.
O contedo da regio z1 diz-se menor do que o contedo da regio z2 quando, para o
primeiro par de posies correspondentes que so distintas, se verifica que o contedo da
posio de z1 menor do que o contedo da posio de z2, ambos os contedos
interpretados como unsigned char. Adicionalmente, o contedo da regio z1 menor do
que o contedo da regio z2 quando o tamanho da regio z1 menor do que o tamanho da
regio z2 e o contedo de cada posio de z1 igual ao contedo da posio
correspondente de z2.
O contedo da regio z1 diz-se igual ao contedo da regio z2 quando as duas regies tm
o mesmo tamanho e o contedo de cada posio de z1 igual ao contedo da posio
correspondente de z2. O contedo da regio z1 diz-se maior do que o contedo da regio
z2 quando se tem em alternativa que o contedo da regio z2 menor do que o contedo
da regio z1.
O valor devolvido ser positivo, quando o contedo da regio z1 for maior do que o
contedo da regio z2, zero, quando o contedo da regio z1 for igual ao contedo da
regio z2, e, negativo, quando o contedo da regio z1 for menor do que o contedo da
regio z2.
No caso da funo memcmp, comparam-se sempre n bytes sem ser atribuda qualquer
interpretao ao seu contedo, enquanto que, para strcmp e strncmp, se supe que z1 e
z2 referenciam cadeias de caracteres, ou seja, agregados de caracteres terminados
obrigatoriamente pelo carcter nulo, e o processo de comparao decorre at tomada
unvoca de uma deciso, no caso da funo strcmp, ou at um mximo de n caracteres
terem sido comparados no caso da funo strncmp.
A Figura 2.30 apresenta um exemplo que l e processa frases de texto, at um mximo de
NMAX frases ou at ao aparecimento da frase FIM. Quando se detecta a frase de
terminao, usando para esse efeito a funo strcmp, o ciclo repetitivo while
interrompido com a instruo break, de forma a evitar o processamento da frase de
terminao. Como a funo devolve 0 quando as duas cadeias de caracteres so iguais,
ento usa-se o operador ! para que a expresso seja verdadeira. Ou seja, a expresso
!strcmp(FRASE, FIM) equivalente expresso booleana strcmp(FRASE, FIM) == 0.
#define NMAX 10
...
char FRASE[80], FIM[] = "FIM"; int N = 0;
...
do
{
N++;
printf ("Escreva a frase -> ");
scanf ("%79[^\n]", FRASE);
/* leitura da frase
scanf ("%*[^\n]");
/* descartar todos os outros caracteres
scanf ("%*c");
/* descartar o carcter de fim de linha
if ( !strcmp (FRASE, FIM) ) break;
...
/* processar a frase lida
} while ( N < NMAX );

Figura 2.30 - Exemplo da utilizao da funo strcmp.

*/
*/
*/
*/

19

CAPTULO 2 : COMPLEMENTOS SOBRE C

A Figura 2.31 apresenta as funes de busca.


void *memchr (void *z, int c, size_t n);
char *strchr (void *z, int c);
char *strrchr (void *z, int c);
size_t strspn (char *z, char *zx);
size_t strcspn (char *z, char *zx);
char *strpbrk (char *z, char *zx);
char *strstr (char *z, char *zx);
char *strtok (char *z, char *zx);

Figura 2.31 - Funes de busca.

A funo memchr localiza a primeira ocorrncia do valor c, previamente convertido para


unsigned char, entre os n primeiros bytes da regio de memria referenciada por z. O
valor devolvido a localizao do valor em z, ou o ponteiro nulo, caso o valor no tenha
sido encontrado. O ponteiro nulo, que est definido no ficheiro de interface stddef.h,
reconhecida pelo identificador NULL e representa o endereo da posio de memria 0.
As funes strchr e strrchr localizam, respectivamente, a primeira e a ltima ocorrncia do
valor c, previamente convertido para char, na cadeia de caracteres referenciada por z.
Neste contexto, supe-se que o carcter nulo final faz parte da cadeia de caracteres. O
valor devolvido a localizao do valor em z, ou o ponteiro nulo, caso o valor no tenha
sido encontrado.
As funes strspn e strcspn calculam e devolvem o comprimento do segmento inicial
mximo da cadeia de caracteres referenciada por z que consiste inteiramente de caracteres,
respectivamente, presentes e no presentes, na cadeia de caracteres referenciada por zx.
A funo strpbrk localiza a primeira ocorrncia, na cadeia de caracteres referenciada por z,
de qualquer carcter presente na cadeia de caracteres referenciada por zx. O valor
devolvido a localizao do carcter, ou o ponteiro nulo, caso nenhum carcter tenha sido
encontrado.
A funo strstr localiza a primeira ocorrncia, na cadeia de caracteres referenciada por z, da
sequncia de caracteres que constitui a cadeia de caracteres referenciada por zx. O valor
devolvido a localizao da cadeia de caracteres, ou o ponteiro nulo, caso nenhum carcter
tenha sido encontrado. Quando zx referencia uma cadeia de caracteres nula, ou seja,
formado apenas pelo carcter nulo, o valor devolvido z.
A funo strtok permite decompor por invocaes sucessivas a cadeia de caracteres
referenciada por z num conjunto de subcadeias de caracteres. A decomposio baseia-se no
princpio de que a cadeia de caracteres original formado por uma sequncia de palavras
delimitadas por um ou mais caracteres separadores descritos na cadeia de caracteres
referenciada por zx. O conjunto dos caracteres separadores pode variar de invocao para
invocao.
A primeira invocao de strtok procura na cadeia de caracteres referenciada por z a
primeira ocorrncia de um carcter no contido na cadeia de caracteres referenciada por zx.
Se tal carcter no existe, ento no h palavras na cadeia de caracteres e a funo devolve
um ponteiro nulo. Se existir, esse carcter representa o incio da primeira palavra. A seguir,
a funo procura na cadeia de caracteres a primeira ocorrncia de um carcter contido na
cadeia de caracteres separadores. Se tal carcter no existe, ento a palavra actual estende-se
at ao fim da cadeia de caracteres e as invocaes subsequentes da funo devolvem um

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

20

ponteiro nulo. Se existir, esse carcter substitudo pelo carcter nulo e constitui o fim da
palavra actual, cuja localizao devolvida pela funo. Antes de terminar, porm, a funo
strtok armazena internamente a localizao do carcter seguinte ao carcter nulo. Esta
referncia vai constituir o ponto de partida da prxima pesquisa que, para ser feita, exige a
invocao da funo com um ponteiro nulo em substituio de z.
A Figura 2.32 apresenta um excerto de cdigo da utilizao da funo strtok para
decompor uma cadeia de caracteres constituda por palavras separadas pelos caracteres $ e
#, e a Figura 2.33 apresenta a sua visualizao grfica. A primeira invocao da funo
detecta o incio da primeira palavra a seguir ocorrncia do carcter #. Enquanto a funo
no devolver o ponteiro nulo, e o agregado de ponteiros para char tiver capacidade de
armazenamento de informao, a funo invocada para encontrar o incio da prxima
palavra a seguir a um dos caracteres separadores. A segunda palavra comea a seguir ao
carcter #, a terceira palavra comea a seguir ao carcter $ e a quarta palavra comea a
seguir ao carcter #. Quando a funo acaba de processar a frase, ela ficou decomposta em
palavras, pelo que, ficou corrompida.
char FRASE[] = "#era$#uma$vez#um$"; char *PALAVRA[4], *PS; int N;
...
N = 0;
PS = strtok (FRASE, "$#");
while ( (PS != NULL) && (N < 4) )
{
PALAVRA[N] = PS;
N++;
PS = strtok (NULL, "$#");
}

Figura 2.32 - Exemplo da utilizao da funo strtok.


FRASE
INICIAL

'#'

'e' 'r'

'a'

'#'

'u' 'm'

'a'

'v'

'e'

'z'

FRASE
FINAL

'#'

'e' 'r'

'a' '\0' '#'

'u' 'm'

'a' '\0' 'v'

'e'

'z' '\0' 'u'

'$'

'$'

'#'

'u'

'm'

'm' '\0' '\0'

PALAVRA[0]
PALAVRA[1]
PALAVRA[2]
PALAVRA[3]

Figura 2.33 - Visualizao grfica do funcionamento da funo strtok.

A Figura 2.34 apresenta as funes diversas.


void *memset (void *z, int c, size_t n);
char *strerror (int errnum);
size_t strlen (char *z);

Figura 2.34 - Funes diversas.

'$' '\0'

21

CAPTULO 2 : COMPLEMENTOS SOBRE C

A funo memset copia o valor c, previamente convertido para unsigned char, para os n
primeiros bytes da regio de memria referenciada por z. A funo strerror devolve a
mensagem associada com a varivel global errno. A funo strlen devolve o comprimento
da cadeia de caracteres, ou seja, o nmero de caracteres armazenados at ao carcter nulo.
A Figura 2.35 apresenta um exemplo da utilizao da funo strerror. Esta funo devolve
uma cadeia de caracteres que pode ser usada para escrever mensagens de erro associadas
varivel global de erro. Neste exemplo, logo aps a invocao da funo matemtica raiz
quadrada, a varivel global de erro testada para em caso de erro escrever a mensagem
respectiva no monitor atravs do printf. No clculo de uma raiz quadrada, h erro se o
valor de X for negativo, pelo que, nesse caso a mensagem indicar que o argumento X est
fora da gama permitida para o clculo da raiz quadrada.
#include <stdio.h>
#include <errno.h>
#include <math.h>
#include <string.h>
...
errno = 0;
Z = sqrt (X);
if (errno != 0)
/* se ocorreu uma situao de erro */
{
printf("ERRO -> %s\n", strerror(errno));
...
-> Numerical argument out of domain */
}

Figura 2.35 - Exemplo da utilizao da funo strerror.

2.3.6 Converso de cadeias de caracteres


A biblioteca stdio contm funes de converso de cadeias de caracteres. Para utilizar essas
funes preciso fazer a incluso do ficheiro de interface stdio.h com a seguinte directiva do
pr-processador.
#include <stdio.h>
A Figura 2.36 apresenta as funes de converso, que permitem criar e decompor cadeias
de caracteres.
int sscanf (char *z, const char *formato, lista de ponteiros);
int sprintf (char *z, const char *formato, lista de expresses);

Figura 2.36 - Funes de converso de cadeias de caracteres.

A funo sscanf decompe uma cadeia de caracteres referenciada por z, segundo as regras
impostas pelo formato de leitura indicado por formato, armazenando sucessivamente os
valores convertidos nas variveis, cuja localizao indicada na lista de ponteiros de
variveis. As definies do formato e da lista de ponteiros de variveis so as mesmas que
para as funes scanf e fscanf.
A funo sscanf fundamentalmente equivalente a fscanf. A diferena principal que a
sequncia de caracteres a converter obtida da cadeia de caracteres referenciada por z, em
vez de ser lida do ficheiro. Assim, a deteco do carcter nulo, que caracteriza a situao de
se ter atingido o fim da cadeia de caracteres, vai corresponder situao de deteco do
carcter de fim de ficheiro.

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

22

A Figura 2.37 apresenta um exemplo da utilizao da funo sscanf para decompor uma
cadeia de caracteres em subcadeias de caracteres. A cadeia de caracteres FRASE, que
armazena uma data decomposta nas suas componentes, CIDADE, DIA, MES e ANO,
para posterior impresso no monitor com outro formato.
char FRASE[] = "Aveiro, 25 de Fevereiro de 2003";
char CIDADE[10], MES[10]; int DIA, ANO;
...
sscanf (FRASE, "%9[^,],%d de %9s de %d", CIDADE, &DIA, MES, &ANO);
/* CIDADE igual a "Aveiro", DIA = 25, */
/* MES igual a Fevereiro e ANO = 2003 */
...
printf ("(%s) %2d/%s/%4d\n", CIDADE, DIA, MES, ANO);
/* impresso no monitor (Aveiro) 25/Fevereiro/2003 */

Figura 2.37 - Exemplo da utilizao da funo sscanf.

A funo sprintf cria uma cadeia de caracteres referenciada por z, constituda por texto e
pelos valores das expresses que formam a lista de expresses, segundo as regras impostas
pelo formato de escrita, indicado por formato. As definies do formato e da lista de
expresses so as mesmas que para as funes printf e fprintf.
A funo sprintf fundamentalmente equivalente a fprintf. A diferena principal que a
sequncia de caracteres convertida armazenada na regio de memria referenciada por z,
em vez de ser escrita no ficheiro. Assim, torna-se necessrio garantir que foi reservado
previamente espao de armazenamento suficiente para a sequncia de caracteres convertida
e para o carcter nulo final.
A Figura 2.38 apresenta um exemplo da utilizao da funo sprintf para a construo
dinmica de um formato de leitura para a funo scanf. Se pretendermos fazer a leitura da
cadeia de caracteres FRASE, que foi declarada com uma dimenso parametrizada pela
constante MAX_CAR, no podemos usar o formato %MAX_CARs, uma vez que o
formato de leitura da funo scanf s aceita literais. A soluo passa pela construo
dinmica do formato de leitura. A cadeia de caracteres FORMATO constituda pela
concatenao do carcter %, atravs do especificador de converso %%, com o valor de
MAX_CAR, atravs do especificador de converso %d, e com o carcter s, ou seja,
armazena o formato de leitura %40s.
#define MAX_CAR 40
...
char FRASE[MAX_CAR+1], FORMATO[20];
...
sprintf (FORMATO, "%%%ds", MAX_CAR); /* FORMATO igual a "%40s" */
...
scanf (FORMATO, FRASE);
/* equivalente a scanf ("%40s", FRASE); */

Figura 2.38 - Exemplo da utilizao da funo sprintf.

23

CAPTULO 2 : COMPLEMENTOS SOBRE C

2.4 Agregados bidimensionais e tridimensionais


Um agregado unidimensional uma sequncia linear de elementos que so acedidos atravs
de um ndice. No entanto, existem problemas em que a informao a ser processada
melhor representada atravs de uma estrutura de dados com um formato bidimensional,
como por exemplo, uma tabela com vrias colunas de informao, ou uma matriz no caso
de aplicaes matemticas. Para esse tipo de aplicaes precisamos de um agregado
bidimensional.
Um agregado bidimensional pois um agregado de agregados. A sua definio respeita as
mesmas regras de um agregado unidimensional, sendo a nica diferena o facto de ter dois
descritores de dimenso em vez de um. Pode ser visto como uma estrutura composta por
linhas, cujo nmero definido pelo primeiro descritor de dimenso, e por colunas, cujo
nmero definido pelo segundo descritor de dimenso. Mas apesar desta viso
bidimensional ele armazenado na memria de forma linear, em endereos de memria
contguas, de maneira a simplificar o acesso aos seus elementos.
O acesso a um elemento do agregado bidimensional feito atravs de dois ndices, o
primeiro para a linha e o segundo para a coluna, onde se encontra o elemento a que se
pretende aceder. preciso ter sempre em considerao que na linguagem C, os ndices dos
agregados so variveis numricas inteiras positivas e o ndice do elemento localizado mais
esquerda em cada dimenso o zero. Ao contrrio do Turbo Pascal onde o acesso pode
ser feito indiscriminadamente por A[L][C] ou A[L,C], na linguagem C deve ser feito
obrigatoriamente por A[L][C].
A Figura 2.39 apresenta um exemplo da declarao de dois agregados, um unidimensional
com 3 elementos inteiros e outro bidimensional com 6 elementos inteiros, bem como, da
sua colocao na memria.
int A[3], B[2][3];

A[0]
A[1]
A[2]
B[0][0]
B[0][1]
B[0][2]
B[1][0]
B[1][1]
B[1][2]

Figura 2.39 - Declarao de agregados unidimensionais e bidimensionais.

A Figura 2.40 apresenta um excerto de cdigo que atribui valores aos agregados A e B. Para
aceder a todos os elementos do agregado bidimensional B necessitamos de um duplo ciclo
repetitivo for. Procure descobrir quais os valores que so armazenados nos agregados.
int A[3], B[2][3]; unsigned int I, J;
...
for (I = 0; I < 3; I++) A[I] = I;
for (I = 0; I < 2; I++)
for (J = 0; J < 3; J++)
...

B[I][J] = A[ (I+J)%3 ];

Figura 2.40 - Manipulao dos agregados.

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

24

A expresso de inicializao para variveis de tipo agregado com N dimenses de um tipo


base baseia-se no pressuposto que um tal agregado pode ser entendido como um agregado
unidimensional de objectos, que so por sua vez, agregados com N1 dimenses do tipo
base. Assim, a aplicao sistemtica deste pressuposto e as regras de inicializao de
agregados unidimensionais permitem a construo da expresso de inicializao adequada a
cada caso. Cada linha de elementos de inicializao deve ser inserida entre chavetas, de
maneira a aumentar a legibilidade da inicializao.
A Figura 2.41 apresenta a declarao e inicializao de dois agregados bidimensionais, bem
como da sua colocao na memria.
int B[2][3] = {
{0, 1, 2},
{3, 4, 5}
};
int C[3][3] = {
{10},
{13, 14}
};

B[0][0]

B[0][1]

B[0][2]

B[1][0]

B[1][1]

B[1][2]

C[0][0]

10

C[0][1]

C[0][2]

C[1][0]

13

C[1][1]

14

C[1][2]

C[2][0]

C[2][1]

C[2][2]

Figura 2.41 - Declarao e inicializao de agregados bidimensionais.

O agregado B, que tem 23 elementos, inicializado com duas listas de inicializao, cada
uma delas constituda por trs constantes inteiras. Pelo que, todos os seus elementos so
inicializados. O agregado C, que tem 33 elementos, inicializado com apenas duas listas
de inicializao. A primeira lista de inicializao constituda apenas por uma constante,
pelo que, o elemento C[0][0] inicializado a 10 e os elementos C[0][1] e C[0][2] so
inicializados a 0. A segunda lista de inicializao constituda por duas constantes, pelo
que, os elementos C[1][0] e C[1][1] so inicializados a 13 e a 14 respectivamente, e o
elemento C[1][2] inicializado a 0. Como no existe a terceira lista de inicializao, os
elementos C[2][0], C[2][1] e C[2][2] so inicializados a 0.
Para alm dos agregados bidimensionais, por vezes existe a necessidade de utilizar
agregados tridimensionais. Por exemplo, para a simulao de um campo electromagntico
no espao. A Figura 2.42 apresenta a declarao e inicializao de dois agregados
tridimensionais, bem como da sua colocao na memria. Tal como na declarao de
agregados unidimensionais possvel omitir o descritor de dimenso, ou seja, fazer uma
definio incompleta, mas apenas da primeira dimenso, tal como feito na declarao do
agregado E. A primeira dimenso pode ser inferida pelo compilador a partir da expresso
de inicializao, e neste exemplo 2.

25

CAPTULO 2 : COMPLEMENTOS SOBRE C

int D[2][2][2] = {
{
{0,
{2,
},
{
{4,
{6,
}
};

1},
3}
5},
7}

int E[ ][2][2] = {
{
{10},
{12, 13}
},
{
{14}
}
};

D[0][0][0]

D[0][0][1]

D[0][1][0]

D[0][1][1]

D[1][0][0]

D[1][0][1]

D[1][1][0]

D[1][1][1]

E[0][0][0]

10

E[0][0][1]

E[0][1][0]

12

E[0][1][1]

13

E[1][0][0]

14

E[1][0][1]

E[1][1][0]

E[1][1][1]

Figura 2.42 - Declarao e inicializao de agregados tridimensionais.

O agregado D, que tem 222 elementos, inicializado com quatro listas de inicializao,
cada uma delas constituda por 2 constantes inteiras. Pelo que, todos os seus elementos so
inicializados. O agregado E, que tem a mesma dimenso, inicializado com apenas trs
listas de inicializao com um total de 4 constantes. A primeira lista de inicializao
constituda apenas por uma constante, pelo que, o elemento E[0][0][0] inicializado a 10 e
o elemento E[0][0][1] inicializado a 0. A segunda lista de inicializao constituda por
duas constantes, pelo que, os elementos E[0][1][0] e E[0][1][1] so inicializados a 12 e a 13
respectivamente. A terceira lista de inicializao constituda por apenas uma constante,
pelo que, o elemento E[1][0][0] inicializado a 14 e o elemento E[1][0][1] inicializado a 0.
Como no existe a quarta lista de inicializao, os elementos E[1][1][0] e E[1][1][1] so
inicializados a 0.
Mas, apesar de normalmente no serem necessrios agregados com mais de duas ou trs
dimenses, a linguagem C tal como o Pascal no limita o nmero de dimenses de um
agregado. Segundo a norma ANSI, os compiladores devem suportar pelo menos seis
dimenses.
Na passagem de agregados multidimensionais a uma funo preciso passar um ponteiro
para o incio do agregado, usando para o efeito o nome do agregado seguido dos parnteses
rectos, tal como, na passagem de um agregado unidimensional. Mas, como o elemento
inicial de um agregado multidimensional tambm um agregado, obrigatrio indicar o
nmero de elementos de cada uma das N1 dimenses direita. Ou seja, apenas a
dimenso mais esquerda pode ser omitida. A Figura 2.43 apresenta a definio de uma
funo que inicializa a zero todos os elementos de um agregado tridimensional. A funo
tem um parmetro de entrada-sada que o agregado a inicializar e um parmetro de
entrada que a primeira dimenso do agregado.

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

26

void INICIALIZAR (int AM[][M][N], int NE) /* definio da funo */


{
int I, J, K;
for (I = 0; I < NE; I++)
for (J = 0; J < M; J++)
for (K = 0; K < N; K++)
AM[I][J][K] = 0;
/* para aceder ao elemento AM[I][J][K] */
}

int AMULT[L][M][N];
...
INICIALIZAR (AMULT, L);
/* invocao da funo */

Figura 2.43 - Passagem de um agregado tridimensional a uma funo.

Uma forma alternativa de fazer a passagem do agregado, consiste em passar explicitamente


um ponteiro para o primeiro elemento do agregado e indicar todas as dimenses do
agregado como parmetros de entrada auxiliares. A Figura 2.44 apresenta esta forma
alternativa. O parmetro de entrada-sada que representa o incio do agregado passado
como sendo um ponteiro para ponteiro para ponteiro para inteiro, atravs da declarao
int AM[ ][ ][ ], ou em alternativa int * * *AM.
void INICIALIZAR (int AM[][][], int X, int Y, int Z)
{
/* definio da funo */
int I, J, K;
for (I = 0; I < X; I++)
for (J = 0; J < Y; J++)
for (K = 0; K < Z; K++)
*((int *) AM + I*Y*Z + J*Z + K) = 0;
}
/* para aceder ao elemento AM[I][J][K] */

int AMULT[L][M][N];
...
INICIALIZAR (AMULT, L, M, N);
/* invocao da funo */

Figura 2.44 - Passagem de um agregado tridimensional a uma funo (verso alternativa).

A vantagem desta soluo que a implementao no tem a necessidade de saber quais so


as dimenses do agregado e, portanto, a funo genrica. Mas, tem a desvantagem de o
programador necessitar de executar a aritmtica de ponteiros para aceder aos elementos do
agregado. Uma vez que o parmetro passado funo do tipo int * * *, para aceder aos
elementos do agregado usando o operador apontado por, o endereo calculado tem de ser
convertido explicitamente num ponteiro para o tipo de elementos do agregado, neste caso
o tipo inteiro, da o uso do operador cast (int *).

2.5 Ponteiros
Como j foi referido anteriormente, um ponteiro embora assuma o valor de um endereo,
no propriamente um endereo, porque ele est sempre associado a um tipo de dados
bem definido. Da que duas variveis de tipo ponteiro podem conter o mesmo valor, ou
seja, o mesmo endereo, e no entanto constiturem de facto entidades distintas.

27

CAPTULO 2 : COMPLEMENTOS SOBRE C

Esta diferena apresentada no exemplo da Figura 2.45. Embora as variveis PI e PD


contenham o mesmo valor aps a execuo das instrues de atribuio, elas constituem de
facto entidades distintas. O ponteiro PI referencia a regio de memria de armazenamento
de uma varivel de tipo int, representada na figura pela rea mais escura com o tamanho de
4 bytes, enquanto que o ponteiro PD referencia a regio de memria de armazenamento de
uma varivel de tipo double, representada na figura pelas duas reas escura e clara com o
tamanho de 8 bytes.
int *PI;
double *PD, VD;
...
PI = (int *) &VD;
PD = &VD;

PI
PD
VD

Figura 2.45 - Exemplo da diferena entre um ponteiro e um endereo.

A declarao de variveis de tipo ponteiro ( pointer ) segue a regra geral de declarao de


variveis na linguagem C, cuja definio formal se apresenta na Figura 2.46.

tipo de dados genrico lista de variveis de tipo ponteiro ;


tipo de dados genrico ::= qualquer tipo de dados vlido na linguagem C
lista de variveis de tipo ponteiro ::= identificador de varivel de tipo ponteiro |
lista de variveis de tipo ponteiro , identificador de varivel de tipo ponteiro
identificador de varivel de tipo ponteiro ::= * identificador de varivel genrico |
*identificador de varivel tipo ponteiro
identificador de varivel genrico ::= identificador de varivel |
identificador de varivel = expresso de inicializao
Figura 2.46 - Definio formal da declarao de variveis de tipo ponteiro.

Podemos declarar um ponteiro de um qualquer tipo de dados genrico. O tipo de dados


genrico inclui os tipos de dados nativos numricos anteriormente referidos e os tipos de
dados definidos pelo utilizador que sero referidos adiante. Alm destes, inclui ainda um
tipo de dados nativo no numrico, o tipo void, que tem um significado muito especial.
Este tipo representa literalmente um tipo de dados que coisa nenhuma. Assim, no
possvel definir-se variveis deste tipo, mas unicamente ponteiros para variveis deste tipo.
Trata-se da forma encontrada pela linguagem C para caracterizar ponteiros genricos e,
portanto, o seu uso bastante restrito.
A Figura 2.47 apresenta um exemplo da utilizao de um ponteiro de tipo void. A primeira
linha de cdigo declara uma varivel A de tipo int. A segunda linha declara uma varivel V
de tipo void, o que ilegal e portanto, d erro de compilao. A terceira linha declara uma
varivel de tipo ponteiro para void, ou seja, um ponteiro genrico. A atribuio *PV = 5
uma instruo ilegal, apesar de PV estar a apontar para A, porque o ponteiro PV de tipo
void. Para colocar o valor 5 na varivel A que de tipo int, atravs do ponteiro PV,
preciso fazer um cast do ponteiro PV, como se mostra na ltima linha.

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

28

int A;
void V; /* declarao ilegal porque a varivel V no tem sentido
void *PV;
/* a varivel PV representa um ponteiro genrico
...
PV = &A;
*PV = 5; /* instruo ilegal porque a varivel *PV no tem sentido
*((int *) PV) = 5;
/* agora A = 5

*/
*/
*/
*/

Figura 2.47 - Exemplo da declarao e utilizao de um ponteiro de tipo void.

Como se mostra na Figura 2.48, a declarao de variveis de tipo ponteiro no precisa de


ser feita numa linha separada, pelo que, a lista de variveis pode incluir conjuntamente
variveis do tipo indicado, ponteiros para variveis do tipo indicado, ponteiros para
ponteiros para variveis do tipo indicado, e assim sucessivamente. A expresso de
inicializao para variveis de tipo ponteiro consiste alternativamente na localizao de uma
varivel, previamente declarada, do tipo para o qual aponta a varivel de tipo ponteiro,
usando o operador endereo, ou na constante NULL, que o ponteiro nulo e que
representa o valor de um ponteiro que no localiza qualquer regio de memria.
int A, *PA = &A, **PPA = NULL;
/* A uma varivel de tipo int, PA uma varivel de tipo ponteiro
para int, inicializada com o endereo de A e PPA uma varivel de
tipo ponteiro para ponteiro para int inicializada a NULL */

Figura 2.48 - Exemplo da declarao de variveis e de ponteiros de tipo int.

Dado que ponteiros em abstracto no fazem sentido, na atribuio de um valor a uma


varivel de tipo ponteiro obrigatrio que o tipo de dados associado varivel e
expresso seja o mesmo. As nicas excepes so a atribuio a uma varivel de tipo
ponteiro para void de uma expresso que um ponteiro para um tipo qualquer, e a
atribuio a uma varivel que um ponteiro para um tipo qualquer de uma expresso de
tipo ponteiro para void. A Figura 2.49 apresenta um exemplo demonstrativo.
int A, *PA = &A, **PPA = &PA;
double *PD;
void *PV, **PPV;
...
PD = PA;
/* incorrecto porque so ponteiros distintos
PD = (double *) PA;
/* correcto aps converso forada
PV = PA;
/* correcto porque PV um ponteiro de tipo void
PD = PV;
/* correcto porque PV um ponteiro de tipo void
PPV = PPA;
/* incorrecto porque so ponteiros distintos

*/
*/
*/
*/
*/

Figura 2.49 - Exemplos da declarao e utilizao de ponteiros de tipos diferentes.

Na Figura 2.49 a varivel PPA foi declarada do tipo ponteiro para ponteiro para int. A
Figura 2.50 apresenta a sua visualizao grfica e a atribuio do valor 45 varivel A,
atravs de uma dupla referncia indirecta com a instruo de atribuio **PPA = 45.
int A, *PA = &A, **PPA = &PA;
...
**PPA = 45;

45

PA
PPA

Figura 2.50 - Visualizao grfica de um ponteiro para ponteiro para int.

29

CAPTULO 2 : COMPLEMENTOS SOBRE C

A Figura 2.51 apresenta um erro frequentemente cometido, por programadores que se


esto a iniciar na utilizao da linguagem C. Nunca se deve fazer a atribuio de um valor a
uma varivel apontado por um ponteiro, sem que este tenha sido previamente inicializado.
Por uma questo de segurana deve-se sempre inicializar um ponteiro, quanto mais no
seja com a constante NULL.
int A, *PA;
...
*PA = 23;
/* incorrecto porque PA no aponta para lado nenhum */

int A, *PA = &A;


...
*PA = 23;
/* correcto A = 23 */

Figura 2.51 - Exemplo de m utilizao de um ponteiro devido a falta de inicializao.

2.5.1 Aritmtica de ponteiros


Como um ponteiro armazena um valor inteiro, que representa o endereo do primeiro byte
da regio de armazenamento da varivel para o qual ele aponta, sobre ele pode ser realizada
a operao aritmtica da adio de um valor inteiro. O significado atribudo operao
colocar o ponteiro a referenciar a regio de memria que se encontra localizada mais
abaixo, mais frente, se a expresso inteira for positiva, ou mais acima, mais atrs, se a
expresso inteira for negativa, e a uma distncia que medida em termos de unidades de
armazenamento de variveis do tipo associado ao ponteiro. Logo, a realizao da operao
s faz sentido para ponteiros para tipos de dados especficos, pelo que, no pode por isso
ser aplicada a expresses de tipo ponteiro para void. O ponteiro avana ou recua o nmero
de bytes equivalente ao sizeof (tipo de dados) por cada unidade adicionada. Sobre um
ponteiro, tambm se pode aplicar a operao de subtraco de um valor inteiro, cujo
significado obviamente o inverso da adio.
Tambm possvel fazer a subtraco de dois ponteiros do mesmo tipo. O significado
atribudo operao determinar a distncia entre os dois ponteiros, medida em termos de
unidades de armazenamento de variveis do tipo associado. De novo, a realizao da
operao s faz sentido para ponteiros para tipos de dados especficos, pelo que, no pode
ser aplicada a expresses de tipo ponteiro para void.
Sobre ponteiros tambm possvel fazer operaes de comparao, usando os operadores
relacionais, sendo que, os dois ponteiros tm de ser do mesmo tipo. A Figura 2.52
apresenta alguns exemplos destas operaes.
int A[8], *PA = A, N;
/* PA aponta para A[0]
...
PA++;
/* agora PA aponta para A[1], ou seja PA avana 4 bytes
PA += 4; /* agora PA aponta para A[5], ou seja PA avana 16 bytes
PA -= 2;
/* agora PA aponta para A[3], ou seja PA recua 8 bytes
...
N = &A[3] - &A[0];
/* N = 3
N = &A[0] - &A[3];
/* N = -3
...
if (PA > &A[2]) ...
/* a expreso decisria verdadeira

Figura 2.52 - Operaes aritmticas sobre ponteiros.

*/
*/
*/
*/
*/
*/
*/

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

30

2.5.2 Agregados de ponteiros e ponteiros para agregados


A linguagem C potencia a construo de tipos de dados muito versteis, onde h a
combinao do construtor agregado [ ] com o operador apontado por * na sua definio.
A legibilidade resultante por vezes, contudo, muito pobre e exige uma compreenso
rigorosa da associatividade e da precedncia dos operadores envolvidos. A Figura 2.53
apresenta a precedncia e a associatividade dos novos operadores.
Operadores na classe

Associatividade

Precedncia

operadores primrios
( )
[ ]
referncia a campo ->
acesso a campo .

esquerda
esquerda
esquerda
esquerda

o
o
o
o

direita
direita
direita
direita

operadores unrios
operador
operador
operador
operador

cast
sizeof
endereo &
apontado por *

maior

direita
direita
direita
direita

o
o
o
o

esquerda
esquerda
esquerda
esquerda

menor

Figura 2.53 - Precedncia e associatividade entre os operadores.

Os operadores parnteses curvos ( ) e parnteses rectos [ ] so designados de operadores


primrios. Tm associatividade da esquerda para a direita e maior precedncia que os
operadores unrios e binrios. Os operadores endereo & e apontado por * so operadores
unrios, cuja associatividade da direita para a esquerda. Tm uma precedncia maior do
que os operadores binrios, mas menor do que os operadores unrios anteriormente
apresentados, ou seja, menor do que os operadores cast e sizeof. Os operadores referncia
a campo -> e acesso a campo . sero explicados no item de acesso a campos de estruturas.
Aplicando as regras de associatividade e precedncia dos operadores, temos que a
declarao TIPO_BASE *AP[4]; declara um agregado de 4 ponteiros para TIPO_BASE,
cujo mapa de reserva de espao em memria se apresenta na Figura 2.54.
TIPO_BASE *AP[4];

AP[0]
AP[1]
AP[2]
AP[3]

Figura 2.54 - Visualizao grfica de um agregado de ponteiros.

Uma das aplicaes dos agregados de ponteiros a construo de agregados de cadeias de


caracteres, que apresentado na Figura 2.57 e na Figura 2.58.
Aplicando as regras de associatividade e precedncia dos operadores, temos que a
declarao TIPO_BASE (*PA)[4]; declara um ponteiro para um agregado de 4 elementos
de TIPO_BASE, cujo mapa de reserva de espao em memria se apresenta na Figura 2.55.

31

CAPTULO 2 : COMPLEMENTOS SOBRE C

TIPO_BASE (*PA)[4];

PA

(*PA)[0]
(*PA)[1]
(*PA)[2]
(*PA)[3]

Figura 2.55 - Visualizao grfica de um ponteiro para um agregado.

2.5.3 Dualidade ponteiro agregado


J referimos que devido dualidade ponteiro agregado, quer uma varivel de tipo
agregado unidimensional de um dado tipo base, quer uma varivel de tipo ponteiro para o
mesmo tipo base, so formas equivalentes de referenciar uma regio de memria formada
por um conjunto contguo de variveis do tipo base. Pelo que, a localizao do elemento de
ndice i do agregado A pode ser alternativamente expressa por &A[i] ou por A+i, e o valor
do mesmo elemento por A[i] ou por *(A+i).
Se entendermos um agregado bidimensional do tipo base como um agregado
unidimensional de objectos que so, por sua vez, agregados unidimensionais do tipo base,
temos que.
TIPO_BASE B[ N1 ][ N2 ]; B[i] { &B[i][0] , B { &B[0]
Neste contexto, B[i] representa o agregado unidimensional, de ndice i, de N2 elementos
do tipo base ou, o que equivalente, um ponteiro para o primeiro elemento desse agregado
e, B representa um ponteiro para o primeiro agregado unidimensional de N2 elementos do
tipo base. Ou seja, a localizao do elemento, colocado na linha de ndice i e na coluna de
ndice j do agregado B, pode ser alternativamente expressa por &B[i][j] ou por B[i]+j, e o
valor do mesmo elemento por B[i][j] ou por *(B[i]+j), ou ainda por *(*(B+i)+j).
Ou seja, uma varivel de tipo ponteiro para um agregado unidimensional de N2 elementos
do tipo base pode ser encarada como uma varivel de tipo agregado bidimensional do
mesmo tipo base, com tamanho N2 na segunda dimenso, na referncia a uma regio de
memria formada por um conjunto contguo de variveis do tipo base. Pelo que, a seguinte
declarao do agregado B e do ponteiro para o agregado PB inicializado com o endereo
inicial do agregado, resulta no mapa de reserva de espao em memria que se apresenta na
Figura 2.56 e permite a utilizao do ponteiro PB para aceder aos elementos do agregado.
TIPO_BASE B[N1][N2], (*PB)[N2] = B;
PB+i { &PB[i] = &B[i] *(PB+i) { PB[i] = B[i] *(PB+i)+j { PB[i]+j = &B[i][j]
*(*(PB+i)+j) { *(PB[i]+j) { (*(PB+i))[j] { PB[i][j] = B[i][j], com 0 d i < N1, 0 d j < N2
B

TIPO_BASE B[2][4], (*PB)[4] = B;


B[0][0]
B[0][1]
B[0][2]
B[0][3]
B[1][0]
B[1][1]
B[1][2]
B[1][3]
PB

B[0]

B[1]

Figura 2.56 - Declarao de um agregado bidimensional e de um ponteiro para o agregado

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

32

Existe, porm, uma diferena subtil entre as variveis de tipo agregado bidimensional de
um tipo base e as variveis de tipo ponteiro para um agregado unidimensional, de tamanho
igual segunda dimenso do agregado bidimensional do mesmo tipo base. Como se
verifica do mapa de reserva de espao em memria apresentado na Figura 2.56, no existe
espao directamente associado com a varivel B e, portanto, B, B[0] e B[1] so ponteiros
constantes, cujos valores no podem ser modificados. Assim, instrues do tipo
B = expresso; ou B[i] = expresso; so ilegais e tem-se que *(B+i) = B[i].
Perante a declarao TIPO_BASE A[N1], B[N1][N2], C[N1][N2][N3]; tem-se que:
x A um ponteiro constante para TIPO_BASE.
x B um ponteiro constante para um agregado unidimensional de N2 elementos do
TIPO_BASE e B[i] um ponteiro constante para TIPO_BASE, com 0 d i < N1.
x C um ponteiro constante para um agregado bidimensional de N2N3 elementos do
TIPO_BASE, C[i] um ponteiro constante para um agregado unidimensional de N3
elementos do TIPO_BASE, com 0 d i < N1, e, C[i][j] um ponteiro constante para
TIPO_BASE, com 0 d i < N1 e 0 d j < N2.

2.5.4 Agregados de cadeias de caracteres ( arrays of strings )


Uma das aplicaes dos agregados de ponteiros a construo de agregados de cadeias de
caracteres (array of strings) constantes. A Figura 2.57 apresenta a visualizao grfica do
agregado FLORES, declarado da seguinte forma.
char *FLORES[4] = { rosa, dahlia, cravo };
O agregado FLORES um agregado de ponteiros para char, em que cada elemento aponta
para o primeiro carcter de uma cadeia de caracteres. Como o quarto elemento no foi
inicializado, ento aponta para NULL.

FLORES[0]
FLORES[1]
FLORES[2]
FLORES[3]

'r'

'd'

'c'

'o'

'a'

'r'

's'

'h'

'a'

'a'

'l'

'v'

'\0'

'i'

'o'

'a'

'\0'

'\0'
representao grfica
de ponteiro nulo

Figura 2.57 - Visualizao grfica de um agregado de cadeias de caracteres.

Uma aplicao de um agregado de cadeias de caracteres constantes a converso do ms


em numrico para o ms por extenso, cuja funo se apresenta na Figura 2.58. Esta uma
situao em que natural que o agregado comece no ndice um, de modo a poupar a
operao de subtraco de uma unidade ao nmero do ms, quando se retira do agregado a
cadeia de caracteres pretendida. Pelo que, o agregado tem treze cadeias de caracteres e
utiliza-se o ndice zero para armazenar uma cadeia de caracteres que vai ser usada para
assinalar situaes de erro.

33

CAPTULO 2 : COMPLEMENTOS SOBRE C

Quando a funo invocada para um ms incorrecto, a funo devolve a cadeia de


caracteres de ndice zero, ou seja, MsErrado. Quando o nmero do ms est correcto, a
funo devolve a cadeia de caracteres, cujo ndice o ms numrico.
type TMES = string[9];
(* no Pascal *)
...
function MES_EXTENSO (MES: integer): TMES;
begin
case MES of
1: MES_EXTENSO := 'Janeiro';
2: MES_EXTENSO := 'Fevereiro';
3: MES_EXTENSO := 'Maro';
4: MES_EXTENSO := 'Abril';
5: MES_EXTENSO := 'Maio';
6: MES_EXTENSO := 'Junho';
7: MES_EXTENSO := 'Julho';
8: MES_EXTENSO := 'Agosto';
9: MES_EXTENSO := 'Setembro'; 10: MES_EXTENSO := 'Outubro';
11: MES_EXTENSO := 'Novembro'; 12: MES_EXTENSO := 'Dezembro';
else MES_EXTENSO := 'MesErrado'
end
end;

char *MES_EXTENSO (int MES)


/* na linguagem C */
{
char *MES_EXTENSO[13] = { "MsErrado", "Janeiro", "Fevereiro",
"Maro", "Abril", "Maio", "Junho", "Julho", "Agosto",
"Setembro", "Outubro", "Novembro", "Dezembro" };
if ( (MES < 1)

|| (MES > 12) )

return MES_EXTENSO[0];

return MES_EXTENSO[MES];
}

Figura 2.58 - Exemplo da funo da converso do ms em numrico para o ms por extenso.

2.6 Estruturas ( structs )


Apesar de um agregado ser uma estrutura de dados muito til, porque permite a
manipulao de uma grande quantidade de dados atravs de um acesso indexado, tem no
entanto a limitao de todos os seus elementos serem do mesmo tipo. Em muitas situaes
precisamos de armazenar informao relacionada entre si, mas que so de tipos diferentes.
Por exemplo, o registo de um aluno universitrio necessita de conter a informao relativa
ao nome do aluno, a informao sobre o seu bilhete de identidade, a morada, a
nacionalidade, a data de nascimento, o curso em que est inscrito, o nmero
mecanogrfico, o ano que est a frequentar, a lista de disciplinas a que est inscrito, e para
cada uma delas o cdigo, o nome e o nmero de crditos, e o histrico das disciplinas a que
j esteve inscrito. Parte desta informao texto e portanto, pode ser armazenada em
cadeias de caracteres, outra de tipo numrica e a lista de disciplinas e o histrico das
disciplinas de tipo agregado. Para podermos armazenar toda esta informao numa nica
estrutura de dados, precisamos de um tipo de dados que permita que os seus elementos
possam ser de tipos diferentes. Para esse efeito a linguagem C providencia o tipo de dados
estrutura (struct ), que equivalente ao registo ( record ) do Pascal.
Uma estrutura distingue-se assim de um agregado pelo facto de permitir que os seus
elementos, que se designam por campos, possam ser de tipos diferentes e porque o acesso
a cada um dos campos no feito atravs da sua localizao na estrutura, mas sim atravs
do nome do campo a que se pretende aceder. Estamos perante um acesso por nomeao.

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

34

2.6.1 Declarao, inicializao e atribuio de estruturas


A Figura 2.59 apresenta a definio formal da declarao de uma estrutura, que recorre ao
construtor struct. A lista de campos inserida entre chavetas, e segue a regra geral de
declarao de variveis da linguagem C. Cada lista de campos comea com a declarao do
tipo de dados a que pertencem os campos, seguida do nome de um ou mais campos
separados por vrgulas, e terminada com o separador ;.
struct identificador da estrutura
{
lista de campos ;
};
identificador da estrutura ::= identificador vlido na linguagem C
lista de campos ::= elemento campo | lista de campos ; elemento campo
elemento campo ::= tipo de dados lista de nomes de campos
tipo de dados ::= qualquer tipo de dados vlido na linguagem C
lista de nomes de campos ::= identificador de campo |
lista de nomes de campos , identificador de campo
identificador de campo ::= identificador vlido na linguagem C
Figura 2.59 - Definio formal da declarao de uma estrutura ( struct ).

A Figura 2.60 apresenta o exemplo da declarao da estrutura tdados_pessoa, que permite


armazenar os dados pessoais de uma pessoa, composta pelo nome, sexo e data de
nascimento, que por sua vez composta pelo dia, ms e ano.
struct tdados_pessoa
{
char NOME[60];
char SEXO;
unsigned int DIA;
unsigned int MES;
unsigned int ANO;
};

Figura 2.60 - Exemplo da declarao de uma estrutura.

A declarao da estrutura tdados_pessoa indica que de agora em diante o compilador


reconhece um novo tipo de dados designado por struct tdados_pessoa. Pelo que, para
declararmos variveis deste tipo, aplicam-se as mesmas regras da declarao de variveis
dos tipos nativos da linguagem C. A Figura 2.61 apresenta a declarao de uma varivel, de
um agregado e de um ponteiro deste novo tipo de dados.
struct tdados_pessoa PESSOA, GRUPO_PESSOAS[10], *PPESSOA;

Figura 2.61 - Exemplo da declarao de variveis e ponteiros do tipo struct tdados_pessoa.

Em alternativa possvel definir o tipo de dados struct tdados_pessoa e declarar variveis


desse tipo na mesma instruo, tal como se apresenta na Figura 2.62.

35

CAPTULO 2 : COMPLEMENTOS SOBRE C

struct tdados_pessoa
{
char NOME[60];
char SEXO;
unsigned int DIA;
unsigned int MES;
unsigned int ANO;
} PESSOA, GRUPO PESSOAS[10], *PPESSOA;

Figura 2.62 - Exemplo da declarao de uma estrutura e da declarao de variveis desse tipo.

Existe uma outra forma de criar um novo tipo de dados em linguagem C usando para esse
efeito a instruo typedef, que equivalente instruo type do Pascal. A Figura 2.63
apresenta esta forma alternativa.
typedef struct
{
char NOME[60];
char SEXO;
unsigned int DIA;
unsigned int MES;
unsigned int ANO;
} TDADOS_PESSOA;

Figura 2.63 - Exemplo da definio de uma estrutura usando o typedef.

Enquanto que o identificador tdados_pessoa definia apenas a estrutura, agora


TDADOS_PESSOA um identificador de toda a declarao da estrutura incluindo a
palavra reservada struct. Para distinguir o tipo de dados do identificador da estrutura,
normalmente usa-se o mesmo identificador, mas em caracteres maisculos. Quando se
define uma estrutura atravs do typedef possvel omitir o identificador da estrutura
anteriormente usado, ou seja, o identificador tdados_pessoa.
Na definio de uma estrutura usando o typedef, no podem ser declaradas, nem
inicializadas, variveis. Estas tm de ser declaradas parte. A Figura 2.64 apresenta a
declarao de uma varivel, de um agregado e de um ponteiro do tipo de dados
TDADOS_PESSOA.
TDADOS_PESSOA

PESSOA, GRUPO_PESSOAS[10], *PPESSOA;

Figura 2.64 - Exemplo da declarao de variveis e ponteiros do tipo TDADOS_PESSOA.

As definies de tipos de dados usando o typedef, so colocados no incio dos ficheiros


fonte logo aps as directivas de include, de maneira a tornar o tipo de dados visvel por todo
o programa, ou num ficheiro de interface, que tem a extenso .h, que depois aludido nos
ficheiros fonte onde o tipo de dados necessrio.
possvel inicializar uma estrutura da mesma forma que se inicializa um agregado. A
Figura 2.65 apresenta a declarao e inicializao de uma estrutura de dados do tipo
TDADOS_PESSOA.
TDADOS_PESSOA

PESSOA = { "Vincent Van Gogh", 'M', 30, 3, 1853 };

Figura 2.65 - Exemplo da declarao e inicializao de uma varivel do tipo TDADOS_PESSOA.

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

36

Na linguagem C possvel atribuir uma estrutura a outra estrutura, tal como no Pascal. A
Figura 2.66 apresenta alguns exemplos de instrues de atribuio envolvendo estruturas.
TDADOS_PESSOA FUNC_PESSOA (void); /* aluso funo FUNC_PESSOA */
...
TDADOS_PESSOA PESSOA1 = { "Vincent Van Gogh", 'M', 30, 3, 1853 };
TDADOS_PESSOA PESSOA2, *PPESSOA;
...
PESSOA2 = PESSOA1;
...
PPESSOA = &PESSOA1;
PESSOA2 = *PPESSOA;
...
PESSOA2 = FUNC_PESSOA ();

Figura 2.66 - Exemplos da atribuio de estruturas.

2.6.2 Acesso aos campos de estruturas


Na linguagem C existem duas formas de aceder aos campos de uma estrutura. Se
estivermos na presena de uma varivel do tipo struct, usa-se o nome da varivel e o nome
do campo separados por um ponto, tal e qual como no Pascal. Vamos designar o operador
. por operador acesso a campo. Se estivermos na presena de um ponteiro para uma
varivel do tipo struct, ento usa-se o nome do ponteiro para a varivel e o nome do
campo separados pelo operador ->. Vamos designar o operador -> por operador
referncia a campo. Este operador uma abreviatura da linguagem C, que combina o
operador apontado por * com o operador acesso a campo ., pelo que, a instruo
PPESSOA->DIA equivalente a (*PPESSOA).DIA.
A Figura 2.67 mostra a atribuio do valor 30 ao campo DIA, directamente atravs de uma
varivel, usando o operador acesso a campo, e por referncia indirecta atravs de um
ponteiro, usando o operador referncia a campo.
TDADOS_PESSOA

PESSOA, *PPESSOA = &PESSOA;

...
/* acesso ao campo DIA atravs da varivel PESSOA */
PESSOA.DIA = 30;
...
/* acesso ao campo DIA atravs do ponteiro PPESSOA */
PPESSOA->DIA = 30;
/* equivalente a (*PPESSOA).DIA = 30; */

Figura 2.67 - Acesso aos campos de uma estrutura.

Na formao de expresses de acesso aos campos de uma estrutura, preciso ter em conta
que os operadores de acesso aos campos de estruturas so operadores primrios, com
associatividade da esquerda para a direita e com menor precedncia do que os operadores
primrios ( ) e [ ], tal como se mostra na Figura 2.53.

2.6.3 Estruturas hierrquicas


Tal como no Pascal, tambm a linguagem C permite que um campo de uma estrutura possa
ser uma estrutura, o que se designa por estruturas hierrquicas (nested structures). Uma vez
que a data constitui um tipo de informao muito frequente em programao, faz sentido
que seja previamente declarada como uma estrutura autnoma e depois seja usada na
declarao de outras estruturas.

37

CAPTULO 2 : COMPLEMENTOS SOBRE C

A Figura 2.68 apresenta a decomposio


autonomizando a data na estrutura TDATA.

da

estrutura

TDADOS_PESSOA,

typedef struct
{
unsigned int DIA;
unsigned int MES;
unsigned int ANO;
} TDATA;
typedef struct
{
char NOME[60];
char SEXO;
TDATA DATA_NASCIMENTO;
} TDADOS_PESSOA;

Figura 2.68 - Exemplo da declarao de estruturas hierrquicas.

Esta declarao hierarquizada em dois nveis provoca algumas consequncias na


inicializao da estrutura TDADOS_PESSOA, bem como, no acesso aos campos da data.
Como se mostra na Figura 2.69, agora a inicializao da estrutura TDADOS_PESSOA,
implica a inicializao da estrutura TDATA entre chavetas, num segundo nvel de
inicializao, tal como se de um agregado bidimensional se tratasse.
TDADOS_PESSOA

PESSOA = { "Vincent Van Gogh", 'M', {30, 3, 1853} };

Figura 2.69 - Exemplo da declarao e inicializao de uma estrutura hierrquica.

A Figura 2.70 apresenta as consequncias em termos de acesso aos campos da estrutura


TDATA, que agora so campos de um campo da estrutura TDADOS_PESSOA.
TDADOS_PESSOA

PESSOA, *PPESSOA = &PESSOA;

...
/* acesso ao campo DIA da DATA atravs da varivel PESSOA */
PESSOA.DATA_NASCIMENTO.DIA = 30;
...
/* acesso ao campo DIA da DATA atravs do ponteiro PPESSOA */
PPESSOA->DATA_NASCIMENTO.DIA = 30;

Figura 2.70 - Acesso aos campos de uma estrutura interna a outra estrutura.

2.6.4 Estruturas ligadas


Uma estrutura no pode conter uma instanciao da prpria estrutura, mas pode conter
ponteiros para a estrutura, permitindo assim a criao de estruturas ligadas. No entanto,
neste caso necessrio usar um identificador para designar a estrutura, que depois vai ser
usado como identificador do tipo de dados dos campos de tipo ponteiro, tal como se
mostra na Figura 2.71. O tipo TNODO para alm dos campos que vo conter a
informao a armazenar na estrutura, tem um campo de tipo ponteiro que aponta para a
prpria estrutura TNODO, atravs do identificador de tipo struct tnodo.

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

38

typedef struct tnodo


{
...;
...;
struct tnodo *PNODO;
} TNODO;

Figura 2.71 - Definio de uma estrutura com referncia a si prpria.

Uma estrutura pode conter ponteiros para estruturas ainda no definidas. A Figura 2.72
apresenta duas estruturas que se referenciam mutuamente. Cada estrutura tem campos que
vo conter a informao a armazenar na estrutura, e um campo de tipo ponteiro que
aponta para a outra estrutura.
typedef struct ts1
{
...;
...;
struct ts2 *PST;
} TS1;
typedef struct ts2
{
...;
...;
struct ts1 *PST;
} TS2;

Figura 2.72 - Definio de duas estruturas que se referenciam mutuamente.

2.6.5 Estruturas como parmetros de funes


Uma estrutura pode ser passada a uma funo por valor ou por referncia. Passar uma
estrutura por referncia normalmente mais rpido, porque apenas feito a cpia do
ponteiro para a estrutura. Enquanto que a passagem por valor implica a criao de uma
cpia local da estrutura na funo. Portanto, uma estrutura passado por valor quando ela
no necessita de ser alterada pela execuo da funo e se quer garantir que a funo no
altera a informao nela armazenada, ou quando a estrutura muito pequena. Caso
contrrio, a estrutura deve ser passada por referncia.
A Figura 2.73 apresenta a funo de leitura da estrutura TDADOS_PESSOA. Como a
estrutura um parmetro de entrada-sada da funo, tem de ser passada por referncia.
void LER_DADOS_PESSOA (TDADOS_PESSOA *PESSOA)
{
/* definio da funo */
...
}

TDADOS_PESSOA PESSOA;
...
LER DADOS PESSOA (&PESSOA);
/* invocao da funo */

Figura 2.73 - Definio e invocao de uma funo com uma estrutura passada por referncia.

A Figura 2.74 apresenta a funo de escrita da estrutura TDADOS_PESSOA. Como a


estrutura um parmetro de entrada da funo, pode ser passada por valor.

39

CAPTULO 2 : COMPLEMENTOS SOBRE C

void ESCREVER_DADOS_PESSOA (TDADOS_PESSOA PESSOA)


{
/* definio da funo */
...
}

TDADOS_PESSOA PESSOA;
...
ESCREVER DADOS PESSOA (PESSOA);
/* invocao da funo */

Figura 2.74 - Definio e invocao de uma funo com uma estrutura passada por valor.

2.6.6 Estruturas como resultado de sada de funes


Uma funo pode devolver uma estrutura. No entanto, mais eficiente devolver um
ponteiro para a estrutura. Mas, para isso, temos que declarar a estrutura como sendo de
durao permanente, usando para o efeito o qualificativo static. Seno, a estrutura deixa de
existir assim que a funo termina a sua execuo, uma vez que por defeito as variveis das
funes so automticas, ou seja, so alocadas quando a funo invocada e destrudas
quando a funo termina a execuo. A Figura 2.75 apresenta as duas solues para uma
funo que devolve um resultado de tipo TDATA.
TDATA FUNC_DATA_E (void)
{
/* o resultado de sada da funo de tipo TDATA */
TDATA DATA;
...
return DATA;
/* retorno da estrutura */
}

TDATA *FUNC_DATA_P (void)


{ /* o resultado de sada da funo de tipo ponteiro para TDATA */
static TDATA DATA;
...
return &DATA;
/* retorno do ponteiro para a estrutura */
}

Figura 2.75 - Devoluo de uma estrutura por uma funo.

2.6.7 Agregados de estruturas


Uma vez definida uma estrutura possvel declarar agregados de estruturas. A Figura 2.76
apresenta uma funo que processa um agregado de estruturas de tipo
TDADOS_PESSOA e que calcula o nmero de pessoas do sexo masculino existentes no
agregado. Nesta primeira verso, o acesso aos elementos do agregado feito atravs do
ndice do elemento pretendido.
int NUMERO_HOMENS (TDADOS_PESSOA GRUPO_PESSOAS[ ], int N)
{
int I, NUM = 0;
for (I = 0; I < N; I++)
if (GRUPO_PESSOAS[I].SEXO == 'M') NUM++;
return NUM;
}

Figura 2.76 - Exemplo de uma funo que processa um agregado de estruturas (1 verso).

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

40

A Figura 2.77 apresenta uma segunda verso, em que o acesso aos elementos do agregado
feito atravs de um ponteiro que aponta para o elemento pretendido.
int NUMERO_HOMENS (TDADOS_PESSOA GRUPO_PESSOAS[ ], int N)
{
int I, NUM = 0; TDADOS_PESSOA *P = GRUPO_PESSOAS;
for (I = 0; I < N; P++, I++)
if (P->SEXO == 'M') NUM++;
return NUM;
}

Figura 2.77 - Exemplo de uma funo que processas um agregado de estruturas (2 verso).

A Figura 2.78 apresenta o programa que invoca a funo NUMERO_HOMENS. O


nmero de elementos do agregado determinado recorrendo ao operador sizeof. O tipo
de dados TDADOS_PESSOA o da Figura 2.68.
int NUMERO_HOMENS (TDADOS_PESSOA [ ], int);
/* aluso funo */
...
int main (void)
{
TDADOS_PESSOA PESSOAS[] = {
{"Vincent Van Gogh",'M',{30, 3, 1853}},
{"Vieira da Silva",'F',{13, 6, 1908}}
{"Amedeo Modigliani",'M',{12, 7, 1884}},
{"Claude Monet",'M',{14, 11, 1840}},
{"Georgia O'Keeffe",'F',{15, 11, 1887}}
};
int NHOMENS, NEST;
NEST = sizeof (PESSOAS) / sizeof (PESSOAS[0]);
NHOMENS = NUMERO_HOMENS (PESSOAS, NEST); /* invocao da funo */
...
return 0;
}

Figura 2.78 - Programa que invoca a funo NUMERO_HOMENS.

2.7 Tipo enumerado ( enum )


Na linguagem C tambm possvel declarar tipos de dados enumerados ( enum ), cuja
definio formal se apresenta na Figura 2.79. um tipo de dados escalar, em que se
enumera a lista de valores associados a uma varivel deste tipo. Cada valor definido como
um identificador constante no bloco que contm a definio de tipo, bloco esse que
inserido entre chavetas. Os identificadores constantes so numerados pelo compilador,
sendo atribudo ao primeiro identificador da lista o valor 0, ao segundo o valor 1, e assim
sucessivamente. Podemos no entanto, alterar esta numerao e indicar para cada
identificador um valor especfico, de forma a utilizar valores mais explanatrios. Sempre
que no se atribui um valor a um dos identificadores, o compilador atribui-lhe o valor do
identificador anterior incrementado de uma unidade.
Tal como no caso da definio de estruturas, a definio de enumerados tambm pode ser
feita usando a instruo typedef.

41

CAPTULO 2 : COMPLEMENTOS SOBRE C

enum identificador do enumerado { lista de identificadores } ;


identificador do enumerado ::= identificador vlido na linguagem C
lista de identificadores ::= identificador genrico | lista de identificadores , identificador genrico
identificador genrico ::= identificador definido pelo utilizador |
identificador definido pelo utilizador = valor de inicializao
identificador definido pelo utilizador ::= identificador vlido na linguagem C
valor de inicializao ::= valor decimal positivo ou nulo
Figura 2.79 - Definio formal da declarao de um tipo enumerado.

A Figura 2.80 apresenta exemplos da declarao de tipos enumerados. O primeiro exemplo


declara o enumerado t_cor e na mesma instruo a varivel COR. O segundo exemplo
define o enumerado t_dia_util e numa declarao parte variveis desse tipo.
enum t_cor { BRANCO, AZUL, VERDE, ROSA, PRETO } COR;

enum t_dia_util { SEGUNDA=2, TERCA=3, QUARTA=4, QUINTA=5, SEXTA=6 };


...
enum t_dia_util DIA_SEMANA, DIAS[5], *PDIA;

Figura 2.80 - Exemplos da declarao de variveis de tipos enumerados.

A Figura 2.81 apresenta os mesmos exemplos, mas recorrendo ao typedef para definir
primeiro os tipos de dados enumerados T_COR e T_DIA_UTIL, ficando a declarao
das variveis para depois. Como os valores atribudos aos identificadores da lista do dia da
semana so seguidos, ento atribui-se apenas o valor inicial ao primeiro elemento da lista.
typedef enum { BRANCO, AZUL, VERDE, ROSA, PRETO } T_COR;
...
T_COR COR;

typedef enum { SEGUNDA=2, TERCA, QUARTA, QUINTA, SEXTA } T_DIA_UTIL;


...
T_DIA_UTIL DIA_SEMANA, DIAS[5], *PDIA;

Figura 2.81 - Exemplos da definio de tipos enumerados usando o typedef.

Normalmente, as definies de tipos de dados enumerados usando o typedef, so


colocados no incio dos ficheiros fonte logo aps as directivas de include, de maneira a
tornar o tipo de dados visvel por todo o programa, ou num ficheiro de interface, que
depois aludido nos ficheiros fonte onde o tipo de dados necessrio.
O tipo de dados enumerado um tipo escalar, cujos identificadores so reconhecidos
atravs do seu valor numrico. Pelo que, possvel ler variveis deste tipo directamente do
teclado, usando o formato decimal e o valor numrico correspondente ao identificador.
Tambm possvel escrever variveis deste tipo directamente no monitor, se bem que
neste caso o valor impresso poder no ser muito esclarecedor. Tambm possvel
executar operaes aritmticas sobre variveis enumeradas, bem como, operaes de
comparao utilizando os operadores relacionais. A Figura 2.82 apresenta alguns exemplos.

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

42

T_DIA_UTIL DIA;
...
do
{
printf ("Dia da semana (SEGUNDA=2 ... SEXTA=6)? ");
scanf ("%1d", &DIA);
} while (DIA<2 || DIA>6);
/* admitindo que foi introduzido o valor 2, ento DIA = SEGUNDA */
DIA += 4;
/* agora DIA = SEXTA */
DIA--;
/* agora DIA = QUINTA */
...
if (DIA > SEXTA)
/* a expresso falsa */
printf ("*** Aleluia Fim de Semana ***\n");

Figura 2.82 - Exemplos de operaes sobre variveis de tipo enumerado.

2.8 Classes de armazenamento


Normalmente uma varivel declarada numa funo, designa-se por varivel automtica,
porque a memria alocada automaticamente quando da invocao da funo e libertada
quando termina a execuo da funo. Pelo que, o seu endereo pode ser diferente para
cada invocao da funo. Como existe apenas durante a execuo da funo tambm
designada de varivel com durao temporria. No entanto, possvel criar uma varivel
com durao permanente. Ou seja, alocar um endereo de memria fixo para a varivel.
Para tal declara-se a varivel dentro da funo com o qualificativo static. A memria,
necessria para armazenar uma varivel static, alocada no incio do programa e o seu
endereo fixo at ao fim do programa. Por outro lado, a varivel s inicializada da
primeira vez que a funo invocada, permitindo assim que a varivel conserve o seu valor
em invocaes sucessivas da funo. A Figura 2.83 mostra um exemplo que demonstra a
diferena entre a varivel temporria TEMP e a varivel permanente PERM.
void INCREMENTAR (void)
{
int TEMP = 1; static int PERM = 1;
TEMP++;
PERM++;
printf("Temporaria = %d <-> Permanente = %d\n", TEMP, PERM);
}

int main (void)


{
INCREMENTAR ();
/* Temporaria = 2 <-> Permanente = 2 */
INCREMENTAR ();
/* Temporaria = 2 <-> Permanente = 3 */
INCREMENTAR ();
/* Temporaria = 2 <-> Permanente = 4 */
return 0;
}

Figura 2.83 - Exemplo demonstrativo da diferena entre variveis temporrias e permanentes.

Quando a funo invocada pela primeira vez as variveis so inicializadas a 1 e depois so


ambas incrementadas adquirindo o valor 2. Na segunda invocao da funo, a varivel
TEMP de novo inicializada a 1 e toma o valor 2, enquanto que, a varivel PERM
apenas incrementada e como conserva o valor anterior, agora toma o valor 3. Na terceira
invocao da funo esta actuao repetida, pelo que, a varivel TEMP toma outra vez o
valor 2, enquanto que, a varivel PERM toma o valor 4.

43

CAPTULO 2 : COMPLEMENTOS SOBRE C

Uma varivel de durao permanente s pode ser inicializada com uma expresso que
contenha apenas literais. Se a varivel no for inicializada dentro de um funo, ento fica
automaticamente inicializada a zero.
A linguagem C permite que o programador pea ao compilador para colocar o contedo de
uma varivel num registo do processador. As operaes envolvendo dados armazenados
em registos so mais rpidas que as operaes envolvendo dados armazenados em posies
de memria, porque, evitam a perda de tempo necessria a que que os dados sejam trazidos
da memria para o processador e que o resultado da operao seja colocado de novo na
memria. Para esse efeito declara-se a varivel com o qualificativo register. Infelizmente, o
nmero de registos existentes no processador limitado, pelo que, no h qualquer garantia
que o pedido seja respeitado.
Porque as variveis declaradas com o qualificativo register, podem no existir na memria,
o operador endereo no pode ser usado sobre essas variveis, ou seja, os registos no so
endereveis. S podem ser declaradas com o qualificativo register variveis declaradas
dentro de funes. Esta prorrogativa deve ser apenas usada para variveis que so acedidas
frequentemente.
A linguagem C tambm permite declarar variveis, cujo valor no pode ser alterado depois
da sua inicializao. Nesse caso estamos perante uma constante que ao contrrio de uma
constante definida com a directiva de define, que designada por constante simblica, ocupa
espao em memria. Para declarar uma constante usa-se o qualificativo const. A Figura
2.84 apresenta uma nova verso da funo CONVERTE_DISTANCIA em que o factor
de converso uma constante real local.
double CONVERTE_DISTANCIA (double ML)
{
const double MIL_QUI = 1.609;

/* definio da funo */

return ML * MIL_QUI;
}

Figura 2.84 - Exemplo da utilizao de uma constante.

A grande vantagem de declarar uma varivel constante assegurar que a varivel est
protegida contra escrita e portanto, no perde o seu valor original. Estas variveis so
muitas vezes utilizadas como parmetros de entrada de funes, como por exemplo em
funes da biblioteca de execuo ANSI string, de forma a assegurar que o parmetro no
corrompido pela execuo da funo. Veja por exemplo o prottipo da funo strcpy.
char *strcpy (char *zd, const char *zp);
No caso da declarao de ponteiros, a palavra reservada const, pode aparecer de duas
formas distintas, tendo por isso significados diferentes. A seguinte declarao, declara um
ponteiro constante para um inteiro, ou seja, um ponteiro que aponta sempre para o mesmo
endereo de memria.
int *const PCONSTANTE;
No entanto, a seguinte declarao, declara um ponteiro que aponta para um inteiro
constante, ou seja, um ponteiro que pode apontar para qualquer endereo, desde que este
seja o de uma varivel de tipo inteiro e constante.
int const *PINTCONST;

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

44

2.9 Visibilidade dos objectos


Uma vez que a linguagem C permite a construo de aplicaes distribudas por vrios
ficheiros fonte, muito importante a forma como possvel partilhar informao entre
eles, bem como possibilitar a duplicao de identificadores.
O nvel de visibilidade de uma varivel depende do stio onde esta declarada. Existem
quatro nveis de visibilidade. As variveis declaradas antes da funo main so variveis
globais que tm um alcance para todos os ficheiros fonte da aplicao. Vamos design-las
por variveis globalmente globais ( Program Scope ). As variveis declaradas antes da
funo main com o qualificativo static so variveis globais que tm um alcance apenas no
ficheiro fonte onde esto declaradas. Vamos design-las por variveis localmente globais
( File Scope ). As variveis declaradas dentro das funes so variveis locais que tm o
alcance limitado funo onde so declaradas. Vamos design-las por variveis locais
( Function Scope ). As variveis declaradas dentro de um bloco, ou seja, dentro de um
conjunto de intrues inseridas entre chavetas so variveis que tm o alcance limitado ao
bloco onde esto declaradas. Vamos design-las por variveis de bloco ( Block Scope ). Por
uma questo de bom estilo de programao, estas variveis devem ser evitadas.
A Figura 2.85 apresenta exemplos de declarao de variveis com diferentes nveis de
visibilidade.
#include <stdio.h>
TIPO1 VARIAVEL_1;
static TIPO2 VARIAVEL_2;
int main (void)
{
TIPO3 VARIAVEL_3;
...
}

/* Varivel Globalmente Global */


/* Varivel Localmente Global */

/* Varivel Local */

Figura 2.85 - Exemplos de declarao de variveis com diferentes nveis de visibilidade.

preciso ter em ateno ao duplo significado do qualificativo static. Quando uma varivel
static declarada fora de uma funo, significa que a varivel global mas com alcance
circunscrito ao ficheiro. Quando uma varivel static declarada dentro de uma funo,
significa que a varivel local mas de durao permanente.
As variveis globais so armazenadas na memria RAM, enquanto que as variveis locais
so armazenadas na memria stack. Uma funo como uma caixa preta, em que os
detalhes associados com a implementao da operao, nomeadamente as variveis locais,
so invisveis externamente. Mas, por outro lado, todas as variveis declaradas globalmente
so visveis dentro das funes. Em caso de conflito, a varivel instanciada aquela que
est declarada mais perto.
Numa funo no possvel declarar uma varivel local com o mesmo nome de um
parmetro da funo. Mas, como as variveis locais so invisveis externamente, pode-se
declarar variveis locais com o mesmo nome em funes diferentes. A Figura 2.86
apresenta exemplos de declarao e utilizao de variveis com diferente visibilidade.

45

CAPTULO 2 : COMPLEMENTOS SOBRE C

static int I, J;

/* variveis localmente globais */

int main (void)


{
int I;
...
FUNC (I);
...
}

/* varivel local I do main */


/* varivel local I */

int FUNC (int N)


{
int N;
/* ilegal, porque N um parmetro de entrada */
int I;
/* varivel local I da funo FUNC */
...
I = N*J; /* varivel local I = parmetro N * varivel global J */
...
}

Figura 2.86 - Exemplos de declarao e utilizao de variveis com diferente visibilidade.

Quando temos uma aplicao distribuda por vrios ficheiros fonte, por vezes necessrio
que uma funo de um ficheiro aceda a variveis globalmente globais que foram declaradas
noutros ficheiros. At agora estivemos a considerar que cada declarao de uma varivel
produz alocao de memria para a varivel. No entanto, a alocao de memria s feita
quando h uma definio de uma varivel. Uma varivel global pode ser declarada sem
produzir alocao de memria. Essa declarao chama-se aluso e usa-se para o efeito o
qualificativo extern. A Figura 2.87 apresenta a aluso a uma varivel globalmente global. O
objectivo de uma aluso permitir que o compilador faa a verificao de tipos. As aluses
de variveis so normalmente colocadas em ficheiros cabealho, assegurando assim aluses
consistentes. Para definir uma varivel global, a varivel deve ser declarada sem o
qualificativo extern e deve incluir a inicializao da varivel. Para aludir uma varivel global
ela deve ser declarada com o qualificativo extern e no deve incluir qualquer inicializao.
int main (void)
{
int I;
extern int J;
...
}

/* declarao da varivel local I */


/* aluso varivel globalmente global J */

Figura 2.87 - Exemplo da aluso a uma varivel global externa.

Por vezes tambm existe a necessidade de que uma funo seja apenas visvel no ficheiro
onde est definida. Desta forma, pode-se definir funes com o mesmo nome em ficheiros
diferentes. Para esse efeito, coloca-se o qualificativo static antes da definio da funo.
normalmente aplicado a funes internas a outras funes, que portanto, no tm a
necessidade de serem reconhecidas fora do ficheiro onde esto definidas.

2.10 Leituras recomendadas


x 7, 8, 9 e 10 captulos do livro C A Software Approach, 3 edio, de Peter A.
Darnell e Philip E. Margolis, da editora Springer-Verlag, 1996.

Captulo 3
FICHEIROS EM C

Sumrio
Em muitas aplicaes prticas, como por exemplo na utilizao de bases de dados, a
quantidade de informao de entrada tanta, que o simples facto de termos que a
introduzir de novo sempre que executamos o programa torna-o pouco funcional. Por
outro lado, neste tipo de aplicaes os resultados de sada precisam de ser salvaguardados
para posterior utilizao. O tipo ficheiro um tipo estruturado que tem como suporte de
armazenamento a memria de massa, e no a memria principal do computador, pelo que,
um ficheiro no s armazena dados a ttulo definitivo, como tambm permite a
comunicao de informao entre programas. Como um ficheiro tambm uma estrutura
de dados dinmica, permite tambm ultrapassar a limitao das estruturas de dados
estticas, como so os agregados de registos, pelo que, a estrutura de dados adequada
para suportar o processamento de bases de dados.
A norma ANSI da linguagem C cria um modelo uniforme de acesso aos diferentes
dispositivos do sistema computacional. Quer se trate dos dispositivos de entrada e de sada,
ou seja, dos perifricos, ou de ficheiros localizados na memria de massa, o acesso
sempre efectuado associando um fluxo de comunicao ao dispositivo. Um fluxo de
comunicao uma sequncia ordenada de bytes, armazenada numa dada regio da
memria principal, que materializa o fluxo de dados entre o programa e o dispositivo.
Portanto, ao contrrio da linguagem Pascal, em que um ficheiro visto como tendo uma
estrutura interna ou registo associado, na linguagem C um ficheiro no mais do que uma
sequncia ordenada de bytes. Ler de, ou escrever para, um dado ficheiro, significa portanto,
ler do, ou escrever no, fluxo de comunicao associado ao ficheiro. Na linguagem C
existem dois tipos de fluxos de comunicao, que so os fluxos de texto e os fluxos
binrios. Vamos apresentar os dois atravs de exemplos de aplicao.

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

3.1 Fluxos de comunicao


A norma ANSI cria um modelo uniforme de acesso aos diferentes dispositivos de entrada e
de sada do sistema computacional. Quer se trate dos dispositivos convencionais de
entrada, que o teclado, de sada que o monitor, scanners, impressoras, ou de ficheiros
localizados na memria de massa, o acesso sempre efectuado associando um fluxo de
comunicao (stream) ao dispositivo. Um fluxo de comunicao visto como uma
sequncia ordenada de bytes, armazenada numa dada regio da memria principal, que
materializa o fluxo de dados entre o programa e o dispositivo. A transferncia de
informao pode ser unidireccional ou bidireccional. Ler de, ou escrever para, um dado
dispositivo, significa portanto, ler do, ou escrever no, fluxo de comunicao associado ao
dispositivo. o sistema operativo que se encarrega da transferncia propriamente dita
entre o fluxo de comunicao e o dispositivo, garantindo-se assim a portabilidade.
Em transferncias unidireccionais, a leitura ou a escrita so sequenciais, iniciando-se,
respectivamente, no princpio ou no fim da informao a residente, enquanto que, em
transferncias bidireccionais, h por vezes a possibilidade de acesso aleatrio para leitura
e/ou para escrita.
A norma ANSI define no ficheiro de interface stdio.h uma estrutura de dados, de nome
FILE, que mantm toda a informao necessria ao controlo de um fluxo de comunicao
e que consiste, entre outros elementos:
x Num indicador de posio de leitura ou de escrita.
x Num ponteiro para a localizao do armazenamento tampo (buffer) associado ao fluxo
de comunicao.
x Num sinalizador de erro, que indica se ocorreu um erro de leitura ou de escrita.
x Num sinalizador de fim de ficheiro, que indica que o fim da informao armazenada no
dispositivo foi atingido.
Assim, sempre que um programa pretender aceder a um dispositivo tem que declarar uma
varivel de tipo ponteiro para FILE para armazenamento do identificador do fluxo de
comunicao que devolvido pela operao de estabelecimento de comunicao. A partir
da, esse identificador usado em todas as operaes de leitura e/ou de escrita no
dispositivo, bem como no controlo e encerramento da comunicao.
A norma ANSI distingue dois tipos de fluxos de comunicao. Os fluxos de texto e os
fluxos binrios.
x Nos fluxos de texto, a sequncia ordenada de bytes interpretada como uma sequncia
de caracteres, organizada em linhas que consistem em zero ou mais caracteres, com
representao grfica ou com funes de controlo, seguidos do carcter de fim de linha, que
o carcter '\n'.
x Nos fluxos binrios, a sequncia ordenada de bytes no sofre qualquer interpretao e
exprime o modo de representao da informao em memria.
Em geral, quando um programa posto em execuo, automaticamente estabelecida a
comunicao com os dispositivos convencionais de entrada e de sada. No caso da
linguagem C, a norma ANSI impe a inicializao automtica dos trs fluxos de texto
seguintes:

CAPTULO 3 : FICHEIROS EM C

x O fluxo de texto stdin est associado com o dispositivo convencional de entrada, que
o teclado.
x O fluxo de texto stdout est associado com o dispositivo convencional de sada, que o
monitor.
x O fluxo de texto stderr est associado com o dispositivo convencional de sada de erro,
que tambm o monitor.
Para garantir a portabilidade, o ficheiro de interface stdio.h, define ainda duas constantes.
A constante EOF (end of file), que sinaliza o fim de ficheiro e a constante NULL, que o
ponteiro nulo, ou seja o valor de um ponteiro que no localiza qualquer regio de memria.

3.2 Abertura ou criao de um fluxo de comunicao


Como j foi referido, para que seja possvel ler ou escrever num dispositivo, necessrio
associar-se-lhe um fluxo de comunicao. Isso feito invocando a funo fopen, que se
apresenta na Figura 3.1, e cuja descrio est contida no ficheiro de interface stdio.h.
FILE *fopen ( const char *nome do dispositivo , const char *modo de acesso ) ;
nome do dispositivo ::= nome vlido para um dispositivo genrico
modo de acesso ::= "r" | "rb" | "w" | "wb" | "a" | "ab"
"r+" | "rb+" | "w+" | "wb+" | "a+" | "ab+"
Figura 3.1 - Funo fopen.

A funo fopen estabelece a associao de um fluxo de comunicao com o dispositivo


pretendido, segundo as regras impostas pelo modo de acesso. A associao feita criando e
inicializando em memria uma estrutura de dados de tipo FILE. A funo devolve,
quando bem sucedida, a localizao em memria da estrutura definida, que vai funcionar na
prtica como identificador do fluxo de comunicao correspondente, ou, quando falha, um
ponteiro nulo. No caso de insucesso, a causa sinalizada na varivel global de erro errno.
O nome do dispositivo essencialmente uma cadeia de caracteres que identifica um
dispositivo especfico do sistema computacional ou um ficheiro do sistema de ficheiros. O
formato concreto depende do sistema operativo presente. A diferena entre os modos de
acesso sem e com o carcter b, que no primeiro caso o fluxo de comunicao associado
de tipo fluxo de texto, enquanto que no segundo caso de tipo fluxo binrio. A funo
tem os seguintes modos de acesso:
x r

Abertura de um dispositivo j existente para leitura.

A leitura comea no princpio do contedo armazenado no ficheiro, ou da informao que


for entretanto introduzida no dispositivo convencional de entrada.
x r+ Abertura de um dispositivo j existente para leitura e escrita.
A leitura ou a escrita comeam no princpio do contedo a armazenado, por cima do
contedo anterior. Este modo usado tipicamente para ficheiros.
x w

Abertura ou criao de um dispositivo para escrita.

Qualquer contedo previamente armazenado destrudo e a escrita comea numa situao


de ficheiro vazio, ou de incio de comunicao no caso do dispositivo convencional de
sada.

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

x w+ Abertura ou criao de um dispositivo para leitura e escrita.


Qualquer contedo previamente armazenado destrudo e a leitura ou a escrita comeam
numa situao de dispositivo vazio. Este modo usado tipicamente para ficheiros.
x a

Abertura ou criao de um dispositivo para escrita no fim.

A escrita s pode ser efectuada no fim do contedo j existente. Este modo usado
tipicamente para ficheiros.
x a+ Abertura ou criao de um dispositivo para escrita no fim e leitura.
A escrita s pode ser efectuada no fim do contedo j existente, a leitura pode ocorrer em
qualquer ponto. Este modo usado tipicamente para ficheiros.
A Figura 3.2 sumaria as permisses dos modos de acesso.
Modo de acesso
Existncia prvia do dispositivo
Dispositivo aberto ou criado sem contedo
Leitura do fluxo permitida
Escrita no fluxo permitida
Escrita permitida s no fim do fluxo

r
*

*
*
*

*
*

r+ w+ a+
*
*
*
*
*
*
*
*
*

Figura 3.2 - Permisses dos modos de acesso.

As condies em que se processa a comunicao com o dispositivo podem ser


monitorizadas atravs de um conjunto de funes que testam os elementos, sinalizador de
erro e sinalizador de fim de ficheiro da estrutura de dados FILE, e a varivel global de erro
errno. Estas funes, cuja descrio est contida no ficheiro de interface stdio.h, so
apresentadas na Figura 3.3.
void clearerr ( FILE *fluxo ) ;
int ferror ( FILE *fluxo ) ;
int feof ( FILE *fluxo ) ;
void perror ( const char *mensagem definida pelo programador ) ;
fluxo ::= varivel de tipo ponteiro para FILE, inicializada pela invocao prvia de
fopen, freopen, stdin, stdout ou stderr
mensagem definida pelo programador :: = texto elucidativo da situao de ocorrncia de erro
Figura 3.3 - Funes que testam situaes de erro.

A funo clearerr limpa os indicadores de fim de ficheiro e de erro associados com o


identificador do fluxo fornecido. A funo ferror testa o erro associado com o
identificador do fluxo fornecido, ocorrido numa instruo de leitura ou de escrita anterior.
A funo perror imprime no dispositivo convencional de sada de erro, a combinao de
uma mensagem fornecida pelo programador, com a mensagem de erro associada varivel
global de erro errno, separadas pelo carcter :. A funo feof testa o elemento sinalizador
de fim de ficheiro da estrutura de dados FILE, correspondente ao identificador do fluxo
fornecido, e devolve 0 (zero), se a sinalizao no foi activada, ou seja, se o fim de ficheiro
no foi atingido, ou um valor diferente de 0, em caso contrrio. No entanto, esta funo s
detecta o carcter de fim de ficheiro aps uma tentativa de leitura falhada, pelo que, em
determinadas situaes produz resultados incorrectos, nomeadamente quando se tenta
processar um ficheiro vazio.

CAPTULO 3 : FICHEIROS EM C

A Figura 3.4 apresenta um exemplo de abertura de um fluxo de comunicao para leitura


de um ficheiro de texto. Comea-se por declarar uma varivel de tipo ponteiro para FILE
e uma cadeia de caracteres para o nome do ficheiro. Deve-se assegurar que se l de facto
um nome vlido para nome do ficheiro, para evitar situaes anormais na tentativa de
abertura do ficheiro. Quando se abre um ficheiro, seja para leitura, seja para escrita deve-se
verificar sempre se a abertura ou a criao do ficheiro foi bem sucedida e caso contrrio
imprimir uma mensagem de erro. A mensagem de erro deve ser enviada para o dispositivo
convencional de sada de erro, uma vez que, o dispositivo convencional de sada pode estar
redireccionado para um ficheiro e ento perder-se-ia a mensagem de erro. Em caso de erro
o programa deve ser terminado com indicao de erro, o que pode ser feito atravs da
funo exit. Aps a leitura e processamento do contedo do ficheiro este deve ser fechado,
usando a funo fclose que se apresenta a seguir.
...
FILE *FPENT; char NOMEFICH[81]; int ST;
...
do
{
printf ("Nome do ficheiro de entrada ");
ST = scanf ("%80s", NOMEFICH);
/* leitura do nome do ficheiro
scanf ("%*[^\n]");
/* descartar todos os outros caracteres
scanf ("%*c");
/* descartar o carcter de fim de linha
} while (ST == 0);
/* abertura do ficheiro
if ( (FPENT = fopen (NOMEFICH, "r") ) == NULL )
{
fprintf (stderr, "O Ficheiro %s no existe\n", NOMEFICH);
exit (EXIT_FAILURE);
}
...
/* leitura do contedo do ficheiro e seu processamento
fclose (FPENT);
/* fecho do ficheiro
...

*/
*/
*/
*/

*/
*/

Figura 3.4 - Exemplo da abertura de um ficheiro de texto para leitura.

3.3 Fecho de um fluxo de comunicao


Aps a comunicao ter tido lugar, necessrio dissociar-se o fluxo de comunicao do
dispositivo, para se garantir o encerramento da comunicao. Isso feito invocando a
funo fclose, que se apresenta na Figura 3.5, e cuja descrio est contida no ficheiro de
interface stdio.h.
int fclose ( FILE *fluxo ) ;
fluxo ::= varivel de tipo ponteiro para FILE, inicializada pela invocao prvia de
fopen ou freopen, stdin, stdout ou stderr
Figura 3.5 - Funo fclose.

A funo fclose dissocia o fluxo de comunicao do dispositivo, encerra a comunicao e


fecha o dispositivo. Se se tratar de um fluxo de comunicao, cujo modo de acesso de
abertura pressupunha escrita, garantido que o sistema operativo transfere para o
dispositivo, toda a informao nova existente no armazenamento tampo associado ao
fluxo de comunicao. A funo devolve 0 (zero), quando bem sucedida, e EOF, em caso
contrrio. No caso de insucesso, a causa sinalizada na varivel global de erro errno.

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

3.4 Fluxos de texto


Os fluxos de texto esto organizados em linhas, cada linha contendo um nmero varivel
de caracteres com representao grfica, ou com funes de controlo. Portanto, mesmo
quando o dispositivo associado ao fluxo de comunicao supe armazenamento interno de
informao, s possvel escrever-se no fim da informao l existente e ler-se a partir do
princpio da informao l existente. Os fluxos de texto so, por isso, quase sempre abertos
ou criados nos modos de leitura ou escrita apenas, com os modos de acesso r, w ou a, e
supem um acesso puramente sequencial.

3.4.1 Leitura de um fluxo de texto


A operao bsica para leitura de fluxos de texto a funo fscanf, cuja definio formal se
apresenta na Figura 3.6, e cuja descrio est contida no ficheiro de interface stdio.h.
int fscanf ( identificador do fluxo de entrada , formato de leitura , lista de ponteiros de variveis ) ;
identificador do fluxo de entrada ::= varivel de tipo ponteiro para FILE, inicializada pela
invocao prvia de fopen, nos modos de acesso r, r+, w+ ou a+, ou stdin
Figura 3.6 - Definio formal da funo fscanf.

A funo fscanf l do fluxo de texto, cujo nome indicado pelo identificador do fluxo de
entrada, sequncias de caracteres e processa-as segundo as regras impostas pelo formato de
leitura, armazenando sucessivamente os valores convertidos nas variveis, cuja localizao
indicada na lista de ponteiros de variveis. As definies de formato de leitura e de lista de
ponteiros de variveis so as mesmas que para a funo scanf.
Salvo em duas situaes especiais, deve existir uma relao de um para um entre cada
especificador de converso e cada varivel da lista de ponteiros de variveis. Se o nmero
de variveis da lista de ponteiros de variveis for insuficiente, o resultado da operao no
est definido. Se, pelo contrrio, o nmero de variveis for demasiado grande, as variveis
em excesso no so afectadas. O tipo da varivel e o especificador de converso devem ser
compatveis, j que a finalidade deste ltimo indicar, em cada caso, que tipos de
sequncias de caracteres so admissveis e como devem ser tratadas. Quando o
especificador de converso no vlido, o resultado da operao no est definido. O
processo de leitura s termina quando o formato de leitura se esgota, quando lido o
carcter de fim de ficheiro, ou quando existe um conflito de tipo entre o que est indicado no
formato de leitura e a correspondente quantidade a ser lida. Neste ltimo caso, o carcter
que causou o conflito mantido no fluxo de texto. A funo devolve o nmero de valores
lidos e armazenados, ou o valor EOF (end of file), se o carcter de fim de ficheiro lido antes
que qualquer converso tenha lugar. Se, entretanto, ocorreu um conflito, o valor devolvido
corresponde ao nmero de valores lidos e armazenados at ocorrncia do conflito.
O maior problema na leitura de um ficheiro a correcta deteco do carcter de fim de
ficheiro. Na linguagem Pascal a deteco do carcter de fim de ficheiro feito em avano
quando se l o ltimo carcter til do ficheiro, da que se pode ler um ficheiro com um
ciclo repetitivo repeat until. Como na linguagem C o carcter de fim de ficheiro s
detectado aps uma tentativa frustrada de leitura, ento o ciclo de leitura no pode ser do
tipo do while, tem que ser do tipo while. Por outro lado, a forma mais eficaz de o
detectar, consiste em aproveitar o facto da funo fscanf devolver o valor EOF quando o
carcter de fim de ficheiro lido antes que qualquer converso tenha lugar.

CAPTULO 3 : FICHEIROS EM C

Vamos apresentar um exemplo de leitura de um ficheiro de texto, que se supe ter um


formato bem definido, composto por linhas com NITEMS de informao com formatos
bem definidos. O ficheiro deve obviamente ser lido at que seja atingido o fim do ficheiro,
pelo que, importante a sua correcta deteco. O processo de leitura tambm deve prever
a situao de que uma linha possa eventualmente no estar de acordo com o formato
esperado. O que fazer nesta situao? Temos duas solues que vamos apresentar de
seguida. Vamos considerar que o ficheiro foi aberto com sucesso e que FPENT o
ponteiro para FILE identificador do fluxo de texto.
A Figura 3.7 apresenta a soluo mais simples, que consiste em terminar a leitura assim que
se detecte uma linha desformatada, ou seja, uma linha que no contenha os NITEMS que
era esperado ler. Nesse caso assinalada a situao de erro e fecha-se o ficheiro.
#define NITEMS 4
...
FILE *FPENT; int NLIDOS;
...
/* leitura da linha completa de acordo com o formato esperado */
while ((NLIDOS = fscanf (FPENT, "formato\n", ponteiros)) == NITEMS)
{
... ;
/* processamento da informao lida */
}
/* verificao da causa de paragem da leitura */
if ( NLIDOS != EOF )
/* EOF ou linha desformatada? */
fprintf (stderr, "Leitura parada devido a linha desformatada\n");
...
fclose (FPENT);
/* fecho do ficheiro */
...

Figura 3.7 - Leitura de um ficheiro de texto at deteco de uma linha desformatada.

A Figura 3.8 apresenta a segunda soluo, que consiste em assinalar com uma mensagem
de erro sempre que se l uma linha desformatada, e tenta passar por cima dela de forma a
continuar a ler, se possvel, o ficheiro at ao fim.
#define NITEMS 4
...
FILE *FPENT; int NLIDOS;
...
/* leitura da linha completa de acordo com o formato esperado */
while ( (NLIDOS = fscanf (FPENT, "formato\n", ponteiros)) != EOF )
{
/* no ocorreu EOF mas preciso aferir a consistncia da linha */
if ( NLIDOS != NITEMS )
/* lido o nmero esperado de items? */
fprintf (stderr, "Linha desformatada\n");
else ... ;
...
}
...
fclose (FPENT);
...

/* processamento da informao lida */

/* fecho do ficheiro */

Figura 3.8 - Leitura de um ficheiro de texto ignorando linhas desformatadas.

A Figura 3.9 apresenta a soluo normalmente apresentada em livros de programao que


recorre funo feof, e que em determinadas situaes produz resultados incorrectos,
nomeadamente quando se tenta processar um ficheiro vazio. Pelo que, esta soluo nunca
deve ser usada.

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

#define NITEMS 4
...
FILE *FPENT; int NLIDOS;
...
while ( !feof(FPENT) )
{
/* leitura da linha completa de acordo com o formato esperado */
NLIDOS = fscanf (FPENT, "formato\n", ponteiros)
/* no ocorreu EOF mas preciso aferir a consistncia da linha */
if ( NLIDOS != NITEMS )
/* lido o nmero esperado de items? */
fprintf (stderr, "Linha desformatada\n");
else ... ;
}
...
fclose (FPENT);
...

/* processamento da informao lida */


/* fecho do ficheiro */

Figura 3.9 - Leitura de um ficheiro de texto recorrendo funo feof.

3.4.2 Escrita num fluxo de texto


A operao bsica para escrita em fluxos de texto a funo fprintf, cuja definio formal
se apresenta na Figura 3.10, e cuja descrio est contida no ficheiro de interface stdio.h.
int fprintf ( identificador do fluxo de sada , formato de escrita , lista de expresses ) ;
identificador do fluxo de sada ::= varivel de tipo ponteiro para FILE, inicializada pela
invocao prvia de fopen, nos modos de acesso r+, w, w+, a ou a+, ou stdout ou

stderr
Figura 3.10 - Definio formal da funo fprintf.

A funo fprintf escreve sucessivamente no fluxo de texto, cujo nome indicado pelo
identificador do fluxo de sada, sequncias de caracteres, representativas de texto e dos
valores das expresses que formam a lista de expresses, segundo as regras impostas pelo
formato de escrita. As definies de formato de escrita e de lista de expresses so as
mesmas que para a funo printf.
O texto especificado no formato de escrita pela introduo de literais, que so copiados
sem modificao para o fluxo de sada. O modo como o valor das expresses convertido,
descrito pelos especificadores de converso. Salvo numa situao especial, deve existir
uma relao de um para um entre cada especificador de converso e cada expresso da lista
de expresses. Se o nmero de expresses for insuficiente, o resultado da operao no
est definido. Se, pelo contrrio, esse nmero for demasiado grande, as expresses em
excesso so ignoradas.
O tipo da expresso e o especificador de converso devem ser compatveis, j que a
finalidade deste ltimo indicar, em cada caso, o formato da sequncia convertida. Quando
o especificador de converso no vlido, o resultado da operao no est definido. O
processo de escrita s termina quando o formato de escrita se esgota, ou quando ocorre
um erro. A funo devolve o nmero de caracteres escritos no fluxo de sada, ou o valor
1, se ocorreu um erro.

CAPTULO 3 : FICHEIROS EM C

A Figura 3.11 apresenta um exemplo em que vamos ler um ficheiro de texto formatado e
escrever a informao lida noutro ficheiro de texto formatado, eventualmente com um
formato diferente. Vamos considerar que o ficheiro de entrada foi aberto com sucesso,
sendo FPENT o ponteiro para FILE identificador do fluxo de texto de entrada, e que o
ficheiro para escrita foi aberto com sucesso, sendo FPSAI o ponteiro para FILE
identificador do fluxo de texto de sada. S as linhas que esto de acordo com o formato de
leitura esperado sero escritas no ficheiro de sada com o formato pretendido.
#define NITEMS 4
...
FILE *FPENT, *FPSAI; int NLIDOS;
...
/* leitura da linha completa de acordo com o formato esperado */
while ( (NLIDOS = fscanf (FPENT, "formato\n", ponteiros)) != EOF )
{
/* no ocorreu EOF mas preciso aferir a consistncia da linha */
if ( NLIDOS != NITEMS )
/* lido o nmero esperado de items? */
fprintf (stderr, "Linha desformatada\n");
else
{
... ;
/* processamento da informao lida */
/* escrita da linha completa de acordo com o formato pretendido */
fprintf (FPSAI, "formato\n", expresses);
}
...
}
...
fclose (FPENT);
fclose (FPSAI);
...

/* fecho do ficheiro de entrada */


/* fecho do ficheiro de sada */

Figura 3.11 - Escrita num ficheiro de texto.

3.4.3 Consideraes finais sobre fluxos de texto


As instrues seguintes de leitura do fluxo de texto associado com o dispositivo
convencional de entrada so equivalentes.
int fscanf ( stdin , formato de leitura , lista de ponteiros de variveis ) ;
int scanf ( formato de leitura , lista de ponteiros de variveis ) ;
As instrues seguintes de escrita no fluxo de texto associado com o dispositivo
convencional de sada so equivalentes.
int fprintf ( stdout , formato de escrita , lista de expresses ) ;
int printf ( formato de escrita , lista de expresses ) ;
As instrues seguintes de escrita no fluxo de texto associado com o dispositivo
convencional de sada de erro so aproximadamente equivalentes.
int fprintf ( stderr , "mensagem definida pelo programador :%s\n" , strerror (errno) ) ;
void perror ( mensagem definida pelo programador ) ;
Existem ainda as duas funes, que se apresentam na Figura 3.12, para leitura e escrita de
linhas de texto, e cuja descrio est contida no ficheiro de interface stdio.h.

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

10

char *fgets ( char *s, int n, FILE *fluxo ) ;


int fputs ( const char *s, FILE *fluxo ) ;
Figura 3.12 - Funes para leitura e escrita de linhas de texto.

A funo fgets l caracteres do fluxo de texto especificado por fluxo, para a cadeia de
caracteres referenciada por s. O fluxo de texto foi inicializado pela invocao prvia de
fopen, nos modos de acesso que permitam a leitura. Os caracteres so lidos at que seja
detectado o carcter de fim de linha, ou o carcter de fim de ficheiro, ou at terem sido lidos
n1 caracteres. A funo adiciona automaticamente o carcter nulo final. Se bem sucedida,
a funo devolve um ponteiro para a cadeia de caracteres s. Se foi encontrado o carcter de
fim de ficheiro antes da leitura de qualquer carcter, a cadeia de caracteres s fica inalterada e
devolvido o ponteiro nulo. Se, entretanto, ocorreu um erro, devolvido o ponteiro nulo,
mas o contedo da cadeia de caracteres imprevisvel.
A funo fputs escreve a cadeia de caracteres referenciada por s, no fluxo de texto
especificado por fluxo. A cadeia de caracteres tem de ser obrigatoriamente terminada com
o carcter nulo final, carcter esse que no escrito no fluxo de texto. preciso ter em
ateno que, a funo no escreve o carcter de fim de linha. Se bem sucedida, a funo
devolve o valor 0 (zero), ou um valor diferente de 0, em caso contrrio.

3.5 Passagem de argumentos na linha de comando


Quando se coloca em execuo um programa desenvolvido na linguagem C, a funo
main, que se comporta como o programa principal, a primeira funo a ser invocada e a
partir da qual todas as restantes funes so invocadas. assim possvel, e desejvel em
certas situaes, passar-lhe informao de entrada, tal como para qualquer outra funo. A
responsabilidade de lhe passar essa informao do sistema operativo atravs da linha de
comando, o que se designa por passagem de argumentos na linha de comando.
Este mecanismo permite funo main comunicar directamente com o exterior e receber
os argumentos que foram escritos na linha de comando. Para tal, acrescenta-se ao
cabealho da funo, dois parmetros de entrada com a seguinte sintaxe.
int main ( int argc, char *argv[ ] )
x O primeiro parmetro chama-se normalmente argc, de tipo int e indica o nmero de
argumentos que foram escritos na linha de comando, nmero esse que inclui o nome do
programa invocado.
x O segundo parmetro chama-se normalmente argv, de tipo char * [ ], ou char ** ,
ou seja, um agregado de cadeias de caracteres. Cada cadeia de caracteres armazena um
argumento da linha de comando, sendo que o primeiro argumento de ndice zero o nome
do programa invocado.
Este mecanismo de comunicao muito til, principalmente quando queremos
desenvolver programas que vo manipular ficheiros, porque simplifica o cdigo necessrio
obteno dos nomes dos ficheiros. Em vez do programa ter de ler o nome de cada
ficheiro, necessitando para tal de uma varivel para o armazenar e de assegurar que no l
uma cadeia de caracteres nula, pode-se passar a informao ao invocar o programa. Mas, a
utilizao deste mecanismo implica alguns cuidados. Nomeadamente preciso assegurar

11

CAPTULO 3 : FICHEIROS EM C

que todos os argumentos necessrios execuo do programa foram de facto passados na


linha de comando.
Vamos considerar que queremos escrever um programa que utiliza dois ficheiros. Neste
caso o programa tem de receber pelo menos trs argumentos, que so, o nome do
programa e os nomes dos dois ficheiros. A Figura 3.13 apresenta o excerto de cdigo que
valida os argumentos de entrada. Se o nmero de argumentos for inferior a trs, o
programa escreve no dispositivo convencional de sada de erro, identificado por stderr,
como que o programa deve ser invocado e termina a execuo do programa com
indicao de finalizao sem sucesso.
...
int main (int argc, char *argv[])
{
if ( argc < 3 )
/* nmero de argumentos suficiente? */
{
fprintf (stderr, "Uso: %s ficheiro ficheiro\n", argv[0]);
exit (EXIT_FAILURE);
}
...
/* processamento dos ficheiros */
return EXIT_SUCESS;
}

Figura 3.13 - Utilizao da passagem de argumentos na linha de comando.

3.6 Exemplos de processamento de ficheiros de texto


Pretende-se escrever um programa que leia um ficheiro de texto e que escreva noutro
ficheiro de texto, todo o texto alfabtico em minsculas. Ou seja, que codifique todos os
caracteres alfabticos maisculos do ficheiro de entrada em caracteres alfabticos
minsculos no ficheiro de sada. Apesar de um ficheiro de texto estar organizado como um
conjunto de linhas, o seu processamento pode ser feito como uma sequncia de caracteres,
uma vez que o carcter de fim de linha pode ser tratado como um carcter normal. O que
torna a organizao do ficheiro em linhas transparente para o programador.
A Figura 3.14 apresenta o programa. Temos dois ficheiros de texto, logo necessitamos de
dois ponteiros para FILE. Vamos designar o ponteiro para o ficheiro de entrada por
FPENT e o ponteiro para o ficheiro de sada por FPSAI. Os nomes dos ficheiros vo ser
passados na linha de comando. Vamos considerar que o primeiro argumento argv[1], o
nome do ficheiro de entrada e o segundo argumento argv[2], o nome do ficheiro de
sada. O nmero de argumentos argc, bem como, a abertura dos ficheiros validada e em
qualquer situao de erro o programa termina com indicao de finalizao sem sucesso.
No caso da abertura sem sucesso do ficheiro de sada, o programa deve fechar o ficheiro de
entrada, que entretanto foi aberto, antes de terminar. A leitura do ficheiro de entrada feita
carcter a carcter at ser atingido o fim do ficheiro. Cada carcter escrito no ficheiro de
sada, transformado para minsculo atravs da funo tolower. Como a funo s converte
o carcter se ele for um carcter alfabtico maisculo, ento no h a necessidade de testar
previamente o carcter. Se o carcter for o carcter de fim de linha, o \n, ele escrito no
ficheiro de sada provocando uma mudana de linha. Assim transporta-se o formato do
ficheiro de entrada para o ficheiro de sada, com excepo dos caracteres alfabticos
maisculos que passam para caracteres alfabticos minsculos. O carcter \ o carcter
continuador de linha, que se utiliza para decompor linhas compridas.

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

12

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
int main (int argc, char *argv[])
{
FILE *FPENT, *FPSAI; char CAR;
if ( argc < 3 )
/* o nmero de argumentos suficiente? */
{
fprintf (stderr, "Uso: %s ficheiro ficheiro\n", argv[0]);
exit (EXIT_FAILURE);
}
/* abertura do ficheiro de entrada cujo nome argv[1] */
if ( (FPENT = fopen (argv[1], "r") ) == NULL )
{
fprintf (stderr, "No foi possvel abrir o ficheiro %s\n"\
, argv[1]);
exit (EXIT_FAILURE);
}
/* criao do ficheiro de sada cujo nome argv[2] */
if ( (FPSAI = fopen (argv[2], "w") ) == NULL )
{
fprintf (stderr, "No foi possvel criar o ficheiro %s\n"\
, argv[2]);
fclose (FPENT);
/* fecho do ficheiro de entrada */
exit (EXIT_FAILURE);
}
/* leitura do carcter do ficheiro de entrada e escrita do */
/* carcter convertido para minsculo no ficheiro de sada */
while ( fscanf (FPENT, "%c", &CAR) != EOF )
fprintf (FPSAI, "%c", tolower(CAR));
fclose (FPENT);
fclose (FPSAI);

/* fecho do ficheiro de entrada */


/* fecho do ficheiro de sada */

return EXIT_SUCCESS;
}

Figura 3.14 - Programa de codificao do ficheiro.

Pretende-se escrever um programa que l um ficheiro de texto que armazena informao


relativa a um conjunto de pessoas e que determina as pessoas que nasceram no dia 29 de
Fevereiro, escrevendo no monitor o nome da pessoa e o seu nmero de telefone. O
ficheiro constitudo por linhas, sendo que cada linha contm a informao relativa a uma
pessoa, com o seguinte formato. O nmero de registo da pessoa, que do tipo inteiro
positivo, o nome que uma cadeia de caracteres com 40 caracteres no mximo, a data de
nascimento constituda pelo dia, ms em numrico e ano, e o nmero de telefone que
uma cadeia de 9 caracteres. Estes elementos de informao esto separados pelo carcter :.
A Figura 3.15 apresenta o programa. Uma vez que o nome de uma pessoa constitudo
por nomes separados pelo espao, no se pode usar o formato de leitura %s. Tem que se
utilizar o formato alternativo para a leitura de cadeias de caracteres que o %[]. Como o
nmero de registo da pessoa e o ano de nascimento no so precisos, a leitura pode
descartar esses itens, no havendo a necessidade de declarar variveis para esses itens. A
linha deve ser lida de forma a colocar o indicador de posio do ficheiro no incio da linha
seguinte, pelo que, o carcter de fim de linha tambm deve ser lido. Portanto, o formato
apropriado deve ser %*d:%40[^:]:%d:%d:%*d:%9s\n. O nome do ficheiro vai ser
passado na linha de comando. O nmero de argumentos e a abertura do ficheiro so

13

CAPTULO 3 : FICHEIROS EM C

validados, e em qualquer situao de erro o programa termina com indicao de finalizao


sem sucesso. A leitura do ficheiro de entrada feita linha a linha at ser atingido o fim do
ficheiro e se for detectada uma linha desformatada, o programa ignora essa linha e continua
a leitura do ficheiro.
#include <stdio.h>
#include <stdlib.h>
#define NITEMS 4
int main (int argc, char *argv[])
{
FILE *FPENT; char NOME[41], TEL[10];

int NLIDOS, DIA, MES;

if ( argc < 2 )
/* o nmero de argumentos suficiente? */
{
fprintf (stderr, "Uso: %s nome do ficheiro\n", argv[0]);
exit (EXIT_FAILURE);
}
/* abertura do ficheiro de entrada cujo nome argv[1] */
if ( (FPENT = fopen (argv[1], "r") ) == NULL )
{
fprintf (stderr, "No foi possvel abrir o ficheiro %s\n"\
, argv[1]);
exit (EXIT_FAILURE);
}
printf ("Aniversariantes a 29 de Fevereiro\n");
/* leitura da linha do ficheiro de entrada */
while ( (NLIDOS = fscanf (FPENT, "%*d:%40[^:]:%d:%d:%*d:%9s\n"\
, NOME, &DIA, &MES, TEL)) != EOF )
if ( NLIDOS != NITEMS )
/* lido o nmero esperado de items? */
fprintf (stderr, "Linha desformatada\n");
/* escrita no monitor dos aniversariantes a 29/Fev */
else if ( DIA == 29 && MES == 2)
printf ("NOME -> %-40.40s TELEFONE -> %s\n", NOME, TEL);
fclose (FPENT);
return EXIT_SUCCESS;

/* fecho do ficheiro */

Figura 3.15 - Processamento de um ficheiro constitudo por linhas com um formato especfico.

Como exerccio de treino, altere o programa para escrever a informao de sada num
ficheiro de texto, cujo nome passado na linha de comando.

3.7 Fluxos binrios


Nos fluxos binrios, a transferncia de informao processa-se byte a byte segundo o
formato usado para representao dos valores em memria. Quando o dispositivo
associado ao fluxo de comunicao supe armazenamento interno da informao,
comum ter-se uma organizao em duas partes, o cabealho e o corpo. O cabealho
contm informao genrica relativa ao fluxo como um todo. O corpo concebido como
uma sequncia de registos de um tipo previamente definido. A leitura e a escrita do
cabealho ou de um registo particular podem ocorrer em qualquer ponto da sequncia de
operaes, j que possvel determinar-se com rigor a sua localizao no ficheiro. Os
fluxos binrios so, por isso, muitas vezes abertos ou criados nos modos de leitura ou
escrita mistos, com os modos de acesso rb+, wb+ ou ab+, e supem um acesso aleatrio.

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

14

3.7.1 Leitura e escrita de fluxos binrios


As operaes bsicas para leitura e escrita em fluxos binrios so respectivamente as
funes fread e fwrite, cuja definio formal se apresenta na Figura 3.16, e cuja descrio
est contida no ficheiro de interface stdio.h.
int fread ( localizao , tamanho do elemento , nmero de elementos , identificador do fluxo ) ;
int fwrite ( localizao , tamanho do elemento , nmero de elementos , identificador do fluxo ) ;
localizao ::= varivel de tipo ponteiro para o tipo elemento que localiza o incio da
regio de armazenamento em memria
tamanho do elemento ::= tamanho em bytes de uma varivel de tipo elemento
nmero de elementos ::= nmero de elementos a serem lidos ou escritos
identificador do fluxo ::= varivel de tipo ponteiro para FILE, inicializada pela invocao
prvia de fopen ou freopen
Figura 3.16 - Definio formal das funes fread e fwrite.

A funo fread l do fluxo binrio, cujo nome indicado pelo identificador do fluxo, o
nmero de elementos indicado, todos com o mesmo tamanho em bytes, e armazena-os na
regio de memria referenciada pela localizao indicada. A regio de memria referenciada
tem que ter capacidade para o armazenamento da informao solicitada. O processo de
leitura termina quando lido o nmero total de bytes pretendido, ou quando lido o
carcter de fim de ficheiro, ou quando ocorreu um erro. A funo devolve o nmero de
elementos que foram efectivamente lidos e armazenados, que pode ser menor do que o
nmero de elementos pretendidos, se o carcter de fim de ficheiro lido antes, ou se ocorreu
um erro. Se o nmero de elementos lidos for zero, ento o valor devolvido 0 (zero) e,
quer a regio de memria referenciada, quer o fluxo binrio no so afectados.
A funo fwrite escreve no fluxo binrio, cujo nome indicado pelo identificador do fluxo,
o nmero de elementos indicado, todos com o mesmo tamanho em bytes, que esto
armazenados na regio de memria referenciada pela localizao indicada. O processo de
escrita termina quando escrito o nmero total de bytes pretendido, ou quando ocorre um
erro. A funo devolve o nmero de elementos que foram efectivamente escritos no fluxo
de sada, que pode ser menor do que o nmero de elementos pretendidos, se ocorreu um
erro. Se o nmero de elementos escritos for zero, o valor devolvido 0 (zero) e o fluxo
binrio no afectado.
A Figura 3.17 exemplifica a utilizao das funes fread e fwrite para ler e escrever uma
estrutura de tipo TDADOS_PESSOA em ficheiros binrios.
TDADOS_PESSOA PESSOA;
...
/* leitura de uma estrutura */
fread (&PESSOA, sizeof (TDADOS_PESSOA), 1, FPENT);
...
/* escrita de uma estrutura */
fwrite (&PESSOA, sizeof (TDADOS_PESSOA), 1, FPOUT);

Figura 3.17 - Utilizao das funes fread e fwrite.

15

CAPTULO 3 : FICHEIROS EM C

3.8 Funes para colocao do indicador de posio


Quando o dispositivo associado ao fluxo de comunicao supe o armazenamento interno
de informao, ou seja, estamos perante um ficheiro, o ponto onde se efectua a prxima
operao de leitura ou de escrita pode ser modificado atravs da manipulao do indicador
de posio do ficheiro. As operaes bsicas para controlo e monitorizao do indicador de
posio do ficheiro so as funes fseek, ftell e rewind, que se apresentam na Figura 3.18,
e cuja descrio est contida no ficheiro de interface stdio.h.
int fseek ( FILE *fluxo, long deslocamento, int referncia ) ;
long ftell ( FILE *fluxo ) ;
void rewind ( FILE *fluxo ) ;
fluxo ::= varivel de tipo ponteiro para FILE, inicializada pela invocao prvia de
fopen ou freopen
deslocamento ::= nmero de bytes a deslocar o indicador de posio do ficheiro
referncia ::= SEEK_SET | SEEK_CUR | SEEK_END
Figura 3.18 - Funes para colocao do indicador de posio do ficheiro.

A funo fseek posiciona o indicador de posio do ficheiro indicado, no byte referenciado


pelo deslocamento pretendido a partir do ponto de referncia. O ponto de referncia pode
ser o princpio da informao armazenada, reconhecido pelo identificador SEEK_SET, a
posio actual do indicador, reconhecido pelo identificador SEEK_CUR ou o fim da
informao armazenada, reconhecido pelo identificador SEEK_END. A funo devolve 0
(zero), em caso de sucesso, e 1, em caso de erro. No caso de insucesso, a causa
sinalizada na varivel global de erro errno.
Embora a sua aplicao possa ser feita com qualquer tipo de fluxo de comunicao, o seu
uso com fluxos de texto muito restrito. A norma ANSI impe que, neste caso, o ponto
de referncia obrigatoriamente SEEK_SET e o deslocamento ou 0 (zero), ou o valor
devolvido por uma invocao prvia da funo ftell.
A funo ftell devolve o valor do indicador de posio do ficheiro indicado. Para os fluxos
binrios, o valor devolvido representa a distncia em bytes a partir do princpio da
informao armazenada. Para os fluxos de texto, o valor devolvido contm um valor, que
depende da implementao e que pode ser usado posteriormente por uma invocao de
fseek para posicionamento do indicador de posio do ficheiro na mesma posio. A
funo devolve o valor do indicador de posio do ficheiro, em caso de sucesso, e o valor
1L, em caso de erro. No caso de insucesso, a causa sinalizada na varivel global de erro
errno.
A funo rewind posiciona o indicador de posio do ficheiro no incio da informao
armazenada. A funo equivalente a invocar a funo fseek para o incio do ficheiro,
mas, com a vantagem de limpar os indicadores de erro e de fim de ficheiro, e sem devolver
qualquer valor.

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

16

3.9 Esvaziamento do armazenamento tampo


Por vezes necessrio garantir que a ltima informao produzida e colocada no
armazenamento tampo associado ao fluxo de comunicao enviada para o dispositivo.
Isso feito invocando a funo fflush, que se apresenta na Figura 3.19, e cuja descrio
est contida no ficheiro de interface stdio.h.
int fflush ( FILE *fluxo ) ;
Figura 3.19 - Funo fflush.

A funo fflush permite o esvaziamento do armazenamento tampo associado ao fluxo de


comunicao indicado, transferindo toda a informao nova a existente para o dispositivo.
Se bem sucedida, a funo devolve o valor 0 (zero), ou um valor diferente de 0, em caso
contrrio.

3.10 Funes para operar sobre ficheiros


A Figura 3.20 apresenta as funes que a biblioteca stdio fornece para operar directamente
sobre ficheiros.
int remove ( const char *nome do ficheiro ) ;
int rename ( const char *nome antigo, const char *nome novo ) ;
FILE *tmpfile ( void ) ;
char *tmpnam ( char *nome do ficheiro ) ;
Figura 3.20 - Funes para operar sobre ficheiros.

A funo remove remove do sistema de ficheiros o ficheiro cujo nome indicado por
nome do ficheiro. A funo devolve 0 (zero), se tiver sucesso, e um valor no nulo, em
caso contrrio.
A funo rename altera o nome do ficheiro cujo nome indicado por nome antigo para o
nome novo. A funo devolve 0 (zero), se tiver sucesso, e um valor no nulo, em caso
contrrio.
A funo tmpfile cria um ficheiro binrio temporrio que automaticamente apagado
quando fechado, ou quando o programa termina. O ficheiro criado no modo de acesso
wb+.
A funo tmpnam gera um nome vlido de ficheiro que distinto de qualquer nome j
existente. At TMP_MAX vezes, no mnimo 25, garantido que os nomes sucessivamente
gerados so diferentes. Se o nome do ficheiro for um ponteiro nulo, a funo devolve a
localizao de uma regio de memria interna onde est armazenado o nome, caso
contrrio, dever apontar para uma regio de memria com capacidade de armazenamento
de L_tmpnam caracteres, e ser esse o valor devolvido. O valor de L_tmpnam est
definido no ficheiro de interface limits.h.

17

CAPTULO 3 : FICHEIROS EM C

3.11 Exemplo da manuteno de uma base de dados


Pretende-se desenvolver um programa que crie e mantenha uma agenda telefnica. A
agenda deve ser concebida como uma sequncia de registos que contenham a seguinte
informao. O nome de uma pessoa, com 40 caracteres no mximo, o seu nmero de
telefone, com 15 caracteres no mximo e a sua data de aniversrio, constituda pelo dia,
ms e ano. O programa deve funcionar de forma repetitiva, apresentando um menu que
permita as seguintes opes: a introduo de um registo novo na agenda; a eliminao de
um registo preexistente da agenda, sendo que o registo a eliminar identificado pelo
nmero de telefone; a listagem do contedo de todos os registos da agenda, por ordem
alfabtica do nome; e terminar a execuo do programa. A Figura 3.21 apresenta o
algoritmo em pseudocdigo e linguagem natural da agenda telefnica.
nome: Base de dados da agenda telefnica manuseada num ficheiro
begin
Abrir ou criar o ficheiro da agenda telefnica;
do
Escrita do menu de operaes disponveis e leitura da operao;
Processamento da operao escolhida;
while no for seleccionada a operao para terminar;
Fechar o ficheiro da agenda telefnica;
end

Figura 3.21 - Algoritmo da agenda telefnica manuseada num ficheiro.

Uma vez que se pretende efectuar a listagem do ficheiro por ordem alfabtica do nome,
ento temos que manter a agenda telefnica sempre ordenada inserindo cada novo registo
no stio certo. O que implica pesquisar o ficheiro procura do local de insero do novo
registo e depois deslocar todos os registos que se encontram abaixo do local de insero,
um registo para baixo, de forma a abrir espao para escrever o novo registo. A Figura 3.22
apresenta a estrutura do ficheiro binrio, que constitudo por um cabealho que indica o
nmero de registos presentes no ficheiro e o corpo que um conjunto de registos idnticos
que esto ordenados por ordem alfabtica do nome, para facilitar a listagem do ficheiro.
cabealho

nmero de registos armazenados

registo base

nome
nmero de telefone
data de aniversrio

corpo

Figura 3.22 - Formato do ficheiro da agenda telefnica.

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

18

A Figura 3.23 apresenta o programa da agenda telefnica, ou seja, a funo main. Para a sua
implementao o programa tem como variveis de entrada, o nome do ficheiro e a opo
pretendida pelo utilizador. Como o ficheiro binrio vai ser utilizado como estrutura de
dados de suporte da agenda telefnica, ento o ficheiro, ou melhor o fluxo binrio
associado com o ficheiro, uma varivel interna do programa.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct
{
unsigned int DIA, MES, ANO;
} TDATA;
#define
#define

NMX
TMX

41
16

/* definio do tipo TDATA */

/* nmero mximo de caracteres do nome + 1 */


/* nmero mximo de caracteres do telefone + 1 */

typedef struct
{
char NOME[NMX];
char NTELEF[TMX];
TDATA ANIVERSARIO;
} TREG;

/* definio do tipo TREG */


/* nome */
/* nmero de telefone */
/* data de aniversrio */
/* aluso a funes */

void
void
void
void
void
void
void

ESCREVER_MENU_E_LER_OPCAO (int *);


LER_NOME_FICHEIRO (char []);
ABRIR_CRIAR_FICHEIRO (char [], FILE **);
FECHAR_FICHEIRO (FILE *);
INSERIR_REGISTO (FILE *);
ELIMINAR_REGISTO (FILE *);
LISTAR_FICHEIRO (FILE *);

int main (void)


{
char NOMEFICH[NMX];
int OPCAO;
FILE *FP;

/* nome do ficheiro */
/* escolha da opo */
/* ponteiro para o fluxo binrio */

LER_NOME_FICHEIRO (NOMEFICH);

/* ler o nome do ficheiro */

ABRIR_CRIAR_FICHEIRO (NOMEFICH, &FP);

/* abrir/criar o ficheiro */

do
{

/* processamento */

/* apresentao do menu e escolha da opo */


ESCREVER_MENU_E_LER_OPCAO (&OPCAO);
switch (OPCAO)
/* realizao da operao */
{
case 1: INSERIR_REGISTO (FP); break;
case 2: ELIMINAR_REGISTO (FP); break;
case 3: LISTAR_FICHEIRO (FP);
}
} while (OPCAO != 4);
FECHAR_FICHEIRO (FP);

/* fechar o ficheiro */

return EXIT_SUCCESS;
}

Figura 3.23 - Programa da agenda telefnica.

19

CAPTULO 3 : FICHEIROS EM C

Vamos agora apresentar as funes necessrias para a implementao do programa. A


funo ESCREVER_MENU_E_LER_OPCAO, que se apresenta na Figura 3.24, como o
nome indica escreve o menu de operaes e l a escolha do utilizador, que o parmetro
de sada da funo. A validao destina-se a assegurar que a opo escolhida uma das
opes permitidas, pelo que, o programa principal no tem que se proteger contra dados de
entrada incorrectos.
void ESCREVER_MENU_E_LER_OPCAO (int *OP)
{
do
{
fprintf (stdout, "\nAgenda de datas de aniversrio\n\n");
fprintf (stdout, "1 - Introduzir um registo novo\n");
fprintf (stdout, "2 - Eliminar um registo preexistente\n");
fprintf (stdout, "3 - Listar a agenda telefnica\n");
fprintf (stdout, "4 - Sada do programa\n");
fprintf (stdout, "\nQual a sua escolha? ");
fscanf (stdin,
fscanf (stdin,
fscanf (stdin,
} while (*OP < 1

"%1d", OP);
"%*[^\n]");
"%*c");
|| *OP > 4);

/* ler a opo */
/* ler e descartar outros dados */
/* ler e descartar o fim de linha */

Figura 3.24 - Funo para escrever o menu de operaes e ler a opo pretendida.

A funo LER_NOME_FICHEIRO, que se apresenta na Figura 3.25, um procedimento


que tem como parmetro de sada o nome do ficheiro que lido do teclado.
void LER_NOME_FICHEIRO (char NOMEF[])
{
fprintf (stdout, "\nNome do ficheiro? ");
fscanf (stdin, "%40s", NOMEF);
/* ler NMX-1 caracteres */
fscanf (stdin, "%*[^\n]"); /* ler e descartar outros caracteres */
fscanf (stdin, "%*c");
/* ler e descartar o fim de linha */
}

Figura 3.25 - Funo para ler o nome do ficheiro.

A funo ABRIR_CRIAR_FICHEIRO, que se apresenta na Figura 3.26, admite que o


ficheiro, cujo nome o parmetro de entrada NOMEF, existe e tenta abri-lo. Caso ele no
exista, pergunta ao utilizador se o pretende criar. No caso da criao de um ficheiro novo,
o cabealho inicializado com zero registos. O ponteiro para o fluxo associado ao ficheiro
binrio um parmetro de sada, pelo que, tem de ser passado por referncia. Mas, como
ele um ponteiro para FILE, ento tem de ser passado um ponteiro para o ponteiro para
FILE, da a declarao FILE **FP. Em caso de situao de erro na criao do ficheiro ou
na escrita do cabealho so escritas mensagens de erro no dispositivo convencional de
sada de erro.
A funo FECHAR_FICHEIRO, que se apresenta na Figura 3.27, fecha o ficheiro, cujo
ponteiro para o fluxo associado o parmetro de entrada FP. O fecho do ficheiro
validado e em caso de situao de erro escrita uma mensagem de erro no dispositivo
convencional de sada de erro.
A vantagem da utilizao da funo perror, na escrita de mensagens de erro, deve-se ao
facto da funo acrescentar mensagem do utilizador, a mensagem associada varivel
global de erro errno, informando-nos da causa do erro.

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

20

void ABRIR_CRIAR_FICHEIRO (char NOMEF[], FILE **FP)


{
char OPC;
/* escolha da opo */
unsigned int ZERO = 0;
/* sinalizao do nmero de registos */
if ( (*FP = fopen (NOMEF, "rb+")) != NULL ) return;
do
{
fprintf (stdout, "\nO ficheiro no existe.\n"\
"Deseja cri-lo (s/n)? ");
fscanf (stdin, "%c", &OPC);
/* ler a opo */
fscanf (stdin, "%*[^\n]");
/* ler e descartar caracteres */
fscanf (stdin, "%*c");
/* ler e descartar o fim de linha */
} while ( OPC != 's' && OPC != 'n');
if (OPC == 'n') exit (EXIT_SUCCESS);
if ( (*FP = fopen (NOMEF, "wb+")) == NULL )
{
perror ("erro na criao do ficheiro"); exit (EXIT_FAILURE);
}
if (fwrite (&ZERO, sizeof (ZERO), 1, *FP) != 1)
{
fprintf (stderr, "erro na inicializao do ficheiro\n");
exit (EXIT_FAILURE);
}
}

Figura 3.26 - Funo para abrir ou criar o ficheiro da agenda telefnica.


void FECHAR_FICHEIRO (FILE *FP)
{
if ( fclose (FP) == EOF )
{
perror ("erro no fecho do ficheiro"); exit (EXIT_FAILURE);
}
}

Figura 3.27 - Funo para fechar o ficheiro da agenda telefnica.

A Figura 3.28 apresenta o algoritmo em linguagem natural da funo


INSERIR_REGISTO. Aps a leitura do contedo do novo registo digitado no teclado
pelo utilizador recorrendo funo LER_REGISTO_TECLADO, preciso determinar o
seu local de insero no ficheiro, em funo do nmero de registos actualmente
armazenados no ficheiro. A funo LER_NUMREG determina o nmero de registos. A
funo POS_INSERCAO implementa a operao de pesquisa e devolve o ndice do
registo do ficheiro onde o novo registo deve ser colocado. O processo de comparao
rudimentar, usando para o efeito a funo de comparao de cadeias de caracteres strcmp,
e por isso, supe-se que o nome escrito em maisculas, com o nmero mnimo de
espaos a separar as palavras. A insero do registo no ficheiro exige deslocar para baixo os
registos do ficheiro a partir do elemento que foi detectado como local de insero do novo
registo, o que feito pela funo DESLOCAR_PARA_BAIXO. A no ser que o local de
insero do registo novo seja o fim do ficheiro. Finalmente preciso actualizar o nmero
de registos armazenados no ficheiro, o que feito pela funo ESCREVER_NUMREG.
A Figura 3.29 apresenta a implementao da funo INSERIR_REGISTO. As funes
auxiliares sero posteriormente apresentadas.

21

CAPTULO 3 : FICHEIROS EM C

nome: Inserir um registo novo na agenda telefnica


begin
Leitura do contedo do novo registo do teclado;
Obteno do nmero de registos armazenados no ficheiro;
Determinao do ponto de insero do novo registo no ficheiro;
Escrita do novo registo no ficheiro;
Actualizao do nmero de registos armazenados no ficheiro;
end

Figura 3.28 - Algoritmo da insero de um registo novo na agenda telefnica.


void INSERIR_REGISTO (FILE *FP)
{
TREG REGISTO;
/* registo novo a ser lido do teclado */
unsigned int NREG,
/* nmero de registos armazenados */
PREG;
/* ponto de insero do registo novo */
/* aluso a funes */
void LER_REGISTO_TECLADO (TREG *);
void LER_NUMREG (FILE *, unsigned int *);
void ESCREVER_NUMREG (FILE *, unsigned int);
unsigned int POS_INSERCAO (FILE *, unsigned int, TREG);
void DESLOCAR_PARA_BAIXO (FILE *, unsigned int, unsigned int);
void ESCREVER_REGISTO_FICHEIRO (FILE *, unsigned int, TREG);
/* leitura do contedo do novo registo do teclado
LER_REGISTO_TECLADO (&REGISTO);
/* obteno do nmero de registos actualmente armazenados
LER_NUMREG (FP, &NREG);
/* determinao do ponto de insero
PREG = (NREG == 0) ? 0 : POS_INSERCAO (FP, NREG, REGISTO);
/* escrita do novo registo no ficheiro
if (PREG != NREG) DESLOCAR_PARA_BAIXO (FP, NREG, PREG);
ESCREVER_REGISTO_FICHEIRO (FP, PREG, REGISTO);
/* actualizao do nmero de registos armazenados
ESCREVER_NUMREG (FP, NREG+1);

*/
*/
*/
*/
*/

Figura 3.29 - Funo para inserir um registo novo na agenda telefnica.

A Figura 3.30 apresenta o algoritmo em linguagem natural da funo


ELIMINAR_REGISTO. Para eliminar um registo preexistente no ficheiro, em primeiro
lugar temos que verificar se existem de factos registos armazenados no ficheiro, o que
feito pela funo LER_NUMREG. Depois preciso determinar se o registo pretendido
existe no ficheiro e em que posio se encontra. Para pesquisar o registo, l-se do teclado a
chave de pesquisa, que o nmero de telefone, com a funo LER_TELEFONE. A
funo POS_ELIMINACAO implementa a operao de pesquisa e devolve o ndice do
primeiro registo do ficheiro, cujo nmero de telefone igual ao nmero indicado. Caso no
haja qualquer registo nestas condies, ser devolvido o nmero de registos do ficheiro. A
eliminao do registo no ficheiro exige deslocar para cima os registos do ficheiro a partir do
elemento que foi detectado como local de eliminao do novo registo, o que feito pela
funo DESLOCAR_PARA_ACIMA. A no ser que o registo seja o ltimo registo do
ficheiro. Finalmente preciso actualizar o nmero de registos armazenados no ficheiro.
A Figura 3.31 apresenta a implementao da funo ELIMINAR_REGISTO. As funes
auxiliares sero posteriormente apresentadas.

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

22

nome: Eliminar um registo preexistente da agenda telefnica


begin
Obteno do nmero de registos armazenados no ficheiro;
se o ficheiro est vazio, ento sair da funo;
Leitura da chave de pesquisa;
Determinao do ponto de eliminao do registo no ficheiro;
Eliminao do registo do ficheiro;
Actualizao do nmero de registos armazenados no ficheiro;
end

Figura 3.30 - Algoritmo da eliminao de um registo preexistente da agenda telefnica.


void ELIMINAR_REGISTO (FILE *FP)
{
char NUMTEL[TMX];
/* nmero de telefone */
unsigned int NREG,
/* nmero de registos armazenados */
PREG;
/* ponto de eliminao do registo */
/* aluso a funes */
void LER_TELEFONE (char []);
void LER_NUMREG (FILE *, unsigned int *);
void ESCREVER_NUMREG (FILE *, unsigned int);
unsigned int POS_ELIMINACAO (FILE *, unsigned int, char []);
void DESLOCAR_PARA_CIMA (FILE *, unsigned int, unsigned int);
/* obteno do nmero de registos actualmente armazenados */
LER_NUMREG (FP, &NREG);
if (NREG == 0)
{
fprintf (stdout, "\nO ficheiro est vazio!\n"); return;
}
LER_TELEFONE (NUMTEL);
/* leitura da chave de pesquisa */
/* determinao do ponto de eliminao */
PREG = POS_ELIMINACAO (FP, NREG, NUMTEL);
if (PREG == NREG)
{
fprintf (stdout, "\nNo existe qualquer registo com esse "\
"nmero de telefone no ficheiro!\n");
return;
}
/* eliminao do registo */
if (PREG < NREG-1) DESLOCAR_PARA_CIMA (FP, NREG, PREG);
/* actualizao do nmero de registos armazenados */
ESCREVER_NUMREG (FP, NREG-1);
}

Figura 3.31 - Funo para eliminar um registo preexistente da agenda telefnica.

A Figura 3.32 apresenta o algoritmo em linguagem natural da funo


LISTAR_FICHEIRO. Em primeiro lugar temos que verificar se existem de factos registos
armazenados no ficheiro, o que feito pela funo LER_NUMREG. Depois preciso
fazer a impresso um a um do contedo dos registos. Para isso temos a funo
LER_REGISTO_FICHEIRO que l o registo do ficheiro e a funo
ESCREVER_REGISTO_MONITOR que escreve o seu contedo no monitor.
A Figura 3.33 apresenta a implementao da funo LISTAR_FICHEIRO. As funes
auxiliares sero posteriormente apresentadas.

23

CAPTULO 3 : FICHEIROS EM C

nome: Listar a agenda telefnica


begin
Obteno do nmero de registos armazenados no ficheiro;
se o ficheiro est vazio, ento sair da funo;
para todos os registos existentes no ficheiro fazer
begin
Ler o registo do ficheiro;
Escrever o contedo do registo no monitor;
end
end

Figura 3.32 - Algoritmo da listagem da agenda telefnica.


void LISTAR_FICHEIRO (FILE *FP)
{
TREG REGISTO;
/* registo lido do ficheiro e escrito no monitor */
unsigned int NREG,
/* nmero de registos armazenados */
REG;
/* contador do ciclo for */
/* aluso a funes */
void LER_NUMREG (FILE *, unsigned int *);
void LER_REGISTO_FICHEIRO (FILE *, unsigned int, TREG *);
void ESCREVER_REGISTO_MONITOR (unsigned int, TREG);
/* obteno do nmero de registos actualmente armazenados
LER_NUMREG (FP, &NREG);
if (NREG == 0)
{
fprintf (stdout, "\nO ficheiro est vazio!\n"); return;
}
/* impresso um a um do contedo dos registos
fprintf (stdout, "\nImpresso do contedo dos registos\n");
for (REG = 0; REG < NREG; REG++)
{
/* leitura do contedo de um registo do ficheiro
LER_REGISTO_FICHEIRO (FP, REG, &REGISTO);
/* escrita no monitor do contedo de um registo
ESCREVER_REGISTO_MONITOR (REG, REGISTO);
}

*/

*/

*/
*/

Figura 3.33 - Funo para listar a agenda telefnica no monitor.

Vamos agora apresentar as funes auxiliares, que apareceram durante a descrio


algortmica das trs operaes do programa principal. A Figura 3.34 apresenta a funo
LER_REGISTO_TECLADO. Tem um parmetro de sada que o registo, cuja
informao vai ser lida do teclado. A informao validada, de maneira a assegurar que o
utilizador no se esquece de preencher qualquer campo do registo. Um nmero de telefone
constitudo apenas por caracteres numricos, pelo que o formato de leitura impe essa
limitao aos caracteres aceites. Por uma questo de simplificao da funo, a data no
validada.
A Figura 3.35 apresenta a funo ESCREVER_REGISTO_MONITOR. Tem dois
parmetros de entrada que so o registo, cuja informao vai ser escrita no monitor, e o seu
nmero de ordem no ficheiro.
A Figura 3.36 apresenta a funo LER_TELEFONE. Tem um parmetro de sada que o
nmero de telefone lido do teclado, que vai servir de chave de pesquisa para a eliminao
do registo.

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

24

void LER_REGISTO_TECLADO (TREG *REG)


{
int ST;
/* sinalizao do estado de leitura */
fprintf (stdout, "\nLeitura dos dados de um registo\n");
do
{
fprintf (stdout, "Nome? ");
ST = fscanf (stdin, "%40[^\n]", REG->NOME);
/* NMX-1 */
fscanf (stdin, "%*[^\n]");
fscanf (stdin, "%*c");
} while (ST != 1);
do
{
fprintf (stdout, "Nmero de telefone? ");
ST = fscanf (stdin, "%15[0123456789]", REG->NTELEF); /* TMX-1 */
fscanf (stdin, "%*[^\n]");
fscanf (stdin, "%*c");
} while (ST != 1);
do
{
fprintf (stdout, "Data de aniversrio (DD-MM-AAAA)? ");
ST = fscanf (stdin, "%2u-%2u-%4d", &(REG->ANIVERSARIO.DIA),\
&(REG->ANIVERSARIO.MES), &(REG->ANIVERSARIO.ANO));
fscanf (stdin, "%*[^\n]");
fscanf (stdin, "%*c");
} while (ST != 3);
}

Figura 3.34 - Funo para ler os dados de um registo do teclado.


void ESCREVER_REGISTO_MONITOR (unsigned int NREG, TREG REG)
{
fprintf (stdout, "\nRegisto n%u\n", NREG);
fprintf (stdout, "Nome: %s\n", REG.NOME);
fprintf (stdout, "Nmero de telefone: %s\n", REG.NTELEF);
fprintf (stdout, "Data de aniversrio: %02u-%02u-%04d\n",\
REG.ANIVERSARIO.DIA, REG.ANIVERSARIO.MES, REG.ANIVERSARIO.ANO);
fprintf (stdout, "\nCarregue em Enter para continuar");
fscanf (stdin, "%*[^\n]");
fscanf (stdin, "%*c");
}

Figura 3.35 - Funo para escrever os dados de um registo no monitor.


void LER_TELEFONE (char NUMTELEF[])
{
int ST;
/* sinalizao do estado de leitura */
do
{
fprintf (stdout, "Nmero de telefone? ");
ST = fscanf (stdin, "%15[0123456789]", NUMTELEF);
fscanf (stdin, "%*[^\n]");
fscanf (stdin, "%*c");
} while (ST != 1);
}

Figura 3.36 - Funo para ler um nmero de telefone do teclado.

/* TMX-1 */

25

CAPTULO 3 : FICHEIROS EM C

A Figura 3.37 apresenta a funo POS_INSERCAO. Tem como parmetros de entrada o


ficheiro, o nmero de registos armazenados e o registo. Tem como resultado de sada o
ndice do registo do ficheiro onde o novo registo vai ser colocado. Para ler o registo do
ficheiro, usa a funo LER_REGISTO_FICHEIRO e o processo de comparao usa a
funo strcmp. A pesquisa termina quando se encontra um registo cujo nome seja
alfabeticamente posterior ao nome do novo registo, o que implica que no caso de j existir
um registo com esse nome, o novo registo ser colocado a seguir. Se o registo estiver
alfabeticamente depois de todos os que j se encontram no ficheiro, ento a funo devolve
o nmero de registos armazenados no ficheiro, ou seja, NR, como indicao que o novo
registo dever ser colocado no fim do ficheiro.
unsigned int POS_INSERCAO (FILE *FP, unsigned int NR, TREG REG)
{
TREG REGAUX;
/* registo auxiliar */
unsigned int R = 0;
/* posio de insero determinada */
void LER_REGISTO_FICHEIRO (FILE *, unsigned int, TREG *);
do
{
LER_REGISTO_FICHEIRO (FP, R, &REGAUX);
if ( strcmp (REG.NOME, REGAUX.NOME) < 0 ) break;
R++;
} while (R < NR);
return R;
}

Figura 3.37 - Funo para determinar a posio de insero do registo.

A Figura 3.38 apresenta a funo POS_ELIMINACAO. Tem como parmetros de entrada


o ficheiro, o nmero de registos armazenados e a chave de pesquisa. Tem como resultado
de sada o ndice do registo do ficheiro onde se encontra o registo a ser eliminado. Mais
uma vez o processo de comparao usa a funo strcmp. A pesquisa termina quando se
encontra o primeiro registo cujo nmero de telefone igual ao nmero indicado. Se no
existir, ento a funo devolve o nmero de registos armazenados no ficheiro, ou seja, NR,
como indicao de registo inexistente no ficheiro.
unsigned int POS_ELIMINACAO (FILE *FP, unsigned int NR, char NTEL[])
{
TREG REGAUX;
/* registo auxiliar */
unsigned int R = 0;
/* posio de eliminao determinada */
void LER_REGISTO_FICHEIRO (FILE *, unsigned int, TREG *);
do
{
LER_REGISTO_FICHEIRO (FP, R, &REGAUX);
if ( !strcmp (NTEL, REGAUX.NTELEF) ) break;
R++;
} while (R < NR);
return R;
}

Figura 3.38 - Funo para determinar a posio de eliminao do registo.

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

26

A insero do registo no ficheiro exige deslocar para baixo os registos do ficheiro a partir
do ndice que foi detectado como local de insero do novo registo. A Figura 3.39
apresenta a funo DESLOCAR_PARA_BAIXO. Tem como parmetros de entrada o
ficheiro, o nmero de registos armazenados e o ndice de insero do registo. Cada registo
lido do ficheiro e escrito de novo no ficheiro mas uma posio mais abaixo. O
deslocamento dos registos feito do fim do ficheiro at ao ndice de insero, como se
exemplifica na parte esquerda da Figura 3.41.
void DESLOCAR_PARA_BAIXO (FILE *FP, unsigned int NR, unsigned int P)
{
TREG REGAUX;
/* registo auxiliar */
unsigned int R;
/* contador do ciclo for */
void LER_REGISTO_FICHEIRO (FILE *, unsigned int, TREG *);
void ESCREVER_REGISTO_FICHEIRO (FILE *, unsigned int, TREG);
for (R = NR; R > P; R--)
{
LER_REGISTO_FICHEIRO (FP, R-1, &REGAUX);
ESCREVER_REGISTO_FICHEIRO (FP, R, REGAUX);
}
}

Figura 3.39 - Funo para deslocar para baixo os registos do ficheiro.

A eliminao do registo no ficheiro exige deslocar para cima os registos do ficheiro a partir
do ndice que foi detectado como local de eliminao do novo registo, a no ser que o
registo seja o ltimo registo do ficheiro. A Figura 3.40 apresenta a funo
DESLOCAR_PARA_ACIMA. Tem como parmetros de entrada o ficheiro, o nmero de
registos armazenados e o ndice de eliminao do registo. Cada registo lido do ficheiro e
escrito de novo no ficheiro mas uma posio mais acima. O deslocamento dos registos
feito do ndice de eliminao at ao fim do ficheiro, como se exemplifica na parte direita da
Figura 3.41.
void DESLOCAR_PARA_CIMA (FILE *FP, unsigned int NR, unsigned int P)
{
TREG REGAUX;
/* registo auxiliar */
unsigned int R;
/* contador do ciclo for */
void LER_REGISTO_FICHEIRO (FILE *, unsigned int, TREG *);
void ESCREVER_REGISTO_FICHEIRO (FILE *, unsigned int, TREG);
for (R = P; R < NR-1; R++)
{
LER_REGISTO_FICHEIRO (FP, R+1, &REGAUX);
ESCREVER_REGISTO_FICHEIRO (FP, R, REGAUX);
}
}

Figura 3.40 - Funo para deslocar para cima os registos do ficheiro.

O nmero de registos do ficheiro est armazenado no seu cabealho. Para determinar o


nmero de registos, a funo LER_NUMREG, que se apresenta na Figura 3.42, limita-se a
ler o cabealho. A funo tem como parmetro de entrada o ficheiro e como parmetro de
sada o nmero de registos lido do cabealho do ficheiro. Para actualizar o nmero de
registos, a funo ESCREVER_NUMREG, que se apresenta na Figura 3.43, escreve no
cabealho o nmero actualizado de registos. A funo tem como parmetros de entrada o
ficheiro e o nmero de registos a escrever no cabealho do ficheiro.

27

CAPTULO 3 : FICHEIROS EM C

deslocar para baixo

NR
NR+1

ler
escrever
ler
escrever
ler
escrever
ler
escrever

deslocar para cima

P
4
3
2

NR-1
1

NR

escrever
ler
escrever
ler
escrever
ler
escrever
ler

1
2
3
4

Figura 3.41 - Visualizao grfica das operaes de deslocamento.

A funo fseek permite colocar o ponteiro de posio de leitura/escrita no incio do


ficheiro. Aps a escrita do cabealho e uma vez que a sua actualizao feita aps a
insero ou eliminao de um registo necessrio usar a funo fflush, para que o
contedo do ficheiro seja imediatamente actualizado, ou seja, para assegurar que as
alteraes acabadas de fazer esto visveis para a operao seguinte.
void LER_NUMREG (FILE *FP, unsigned int *NR)
{
if ( fseek (FP, 0, SEEK_SET) != 0 )
{
perror ("erro no posicionamento do ficheiro - cabealho");
exit (EXIT_FAILURE);
}
if ( fread (NR, sizeof (unsigned int), 1, FP) != 1 )
{
fprintf (stderr, "erro na leitura do cabealho do ficheiro\n");
exit (EXIT_FAILURE);
}
}

Figura 3.42 - Funo para ler o nmero de registos armazenado no cabealho do ficheiro.
void ESCREVER_NUMREG (FILE *FP, unsigned int NR)
{
if ( fseek (FP, 0, SEEK_SET) != 0 )
{
perror ("erro no posicionamento do ficheiro - cabealho");
exit (EXIT_FAILURE);
}
if ( fwrite (&NR, sizeof (unsigned int), 1, FP) != 1 )
{
fprintf (stderr, "erro na escrita do cabealho do ficheiro\n");
exit (EXIT_FAILURE);
}
if ( fflush (FP) != 0 )
{
perror ("erro na escrita efectiva no ficheiro");
exit (EXIT_FAILURE);
}
}

Figura 3.43 - Funo para escrever o nmero de registos no cabealho do ficheiro.

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

28

Para ler ou escrever um registo no ficheiro primeiro necessrio colocar o ponteiro de


posio de leitura/escrita no registo com o ndice pretendido, usando a funo fseek. A
Figura 3.44 apresenta a funo LER_REGISTO_FICHEIRO, que tem como parmetros
de entrada o ficheiro e o nmero de ordem do registo a ler e como parmetro de sada o
registo lido. A Figura 3.45 apresenta a funo ESCREVER_REGISTO_FICHEIRO, que
tem como parmetros de entrada o ficheiro, o nmero de ordem do registo e o registo a
escrever no ficheiro.
void LER_REGISTO_FICHEIRO (FILE *FP, unsigned int P, TREG *REG)
{
if (fseek (FP, sizeof(unsigned int)+P*sizeof(TREG), SEEK_SET)!=0)
{
perror ("erro no posicionamento do ficheiro - registo");
exit (EXIT_FAILURE);
}
if ( fread (REG, sizeof (TREG), 1, FP) != 1 )
{
fprintf (stderr, "erro na leitura do registo do ficheiro\n");
exit (EXIT_FAILURE);
}
}

Figura 3.44 - Funo para ler um registo do ficheiro.


void ESCREVER_REGISTO_FICHEIRO (FILE *FP, unsigned int P, TREG REG)
{
if (fseek (FP, sizeof(unsigned int)+P*sizeof(TREG), SEEK_SET)!=0)
{
perror ("erro no posicionamento do ficheiro - registo");
exit (EXIT_FAILURE);
}
if (fwrite (&REG, sizeof (TREG), 1, FP) != 1)
{
fprintf (stderr, "erro na escrita do registo no ficheiro\n");
exit (EXIT_FAILURE);
}
}

Figura 3.45 - Funo para escrever um registo no ficheiro.

3.12 Leituras recomendadas


x 12 captulo do livro C A Software Approach, 3 edio, de Peter A. Darnell e Philip
E. Margolis, da editora Springer-Verlag, 1996.

Captulo 4
RECURSIVIDADE

Sumrio
Neste captulo apresentamos exemplos da implementao de funes recursivas. Uma
funo recursiva, ou recorrente, uma funo que se invoca a si prpria. Tal como a
decomposio hierarquizada, a recursividade permite ao programador decompor um
problema em problemas mais pequenos, que tm a particularidade de serem exactamente
do mesmo tipo do problema original.
O que no significa que os algoritmos recursivos sejam mais eficientes que os algoritmos
iterativos equivalentes. Pelo contrrio, alguns algoritmos recursivos desencadeiam um
nmero arbitrariamente grande de invocaes sucessivas da funo, pelo que, nestes casos
temos uma ineficincia acrescida associada invocao de funes. Por outro lado, alguns
algoritmos recursivos so computacionalmente ineficientes, devido ao facto de as mltiplas
invocaes recursivas repetirem o clculo de valores.
No entanto, os algoritmos recursivos so apropriados para resolver problemas que so por
natureza recursivos. Por vezes, existem problemas que tm solues recursivas que so
simples, concisas, elegantes e para os quais difcil esboar solues iterativas com a
mesma simplicidade e clareza. Mas, como alguns algoritmos recursivos so menos
eficientes que os algoritmos iterativos equivalentes, a verdadeira importncia da
recursividade consiste em resolver esses problemas para os quais no existem solues
iterativas simples.
Vamos analisar alguns exemplos clssicos de algoritmos recursivos, com excepo de
algoritmos de ordenao recursivos, que so algoritmos de ordenao muito eficientes, mas
que sero tratados mais tarde no captulo de Pesquisa e Ordenao.

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

4.1 Funes recursivas


Tipicamente, a soluo de um problema repetitivo pode ser resolvido de forma iterativa
utilizando explicitamente um ciclo repetitivo, ou de forma recursiva utilizando a invocao
sucessiva da soluo. A linguagem C, tal como outras linguagens de programao de alto
nvel, permite a definio de funes recursivas. Uma funo recursiva, ou recorrente,
uma funo que definida em termos de si prprio, ou seja, a funo invoca-se a si prpria
na parte executiva. Este tipo de recursividade, ou recorrncia, designa-se por
recursividade directa.
A grande vantagem da criao de uma funo recursiva, utilizando para o efeito um
algoritmo recursivo, o de permitir desencadear um nmero arbitrariamente grande de
repeties de instrues, sem contudo, usar explicitamente estruturas de controlo
repetitivo. O que no significa que os algoritmos recursivos sejam mais eficientes que os
algoritmos iterativos equivalentes.
Tal como a decomposio hierarquizada, a recursividade permite ao programador
decompor um problema em problemas mais pequenos, que tm a particularidade de serem
exactamente do mesmo tipo do problema original. Portanto, um algoritmo recursivo
resolve o problema resolvendo uma instncia menor do mesmo problema.
Consequentemente, a complexidade do problema ser diminuda at que a soluo seja
bvia ou conhecida.
Os problemas que podem ser resolvidos recursivamente tm normalmente as seguintes
caractersticas. Um ou mais casos de paragem, em que a soluo no recursiva e
conhecida, e casos em que o problema pode ser diminudo recursivamente at se atingirem
os casos de paragem. Portanto, quando se implementa uma funo recursiva temos que
assegurar que existe uma instruo que para a invocao recursiva e que calcula
efectivamente um valor, e por outro lado, que o valor das sucessivas invocaes alterado
de maneira a atingir esse valor de paragem. Usualmente um algoritmo recursivo usa uma
instruo condicional if com a forma que se apresenta na Figura 4.1.
nome: Algoritmo recursivo
begin
se o caso de paragem atingido?
ento Resolver o problema
seno Partir o problema em problemas mais pequenos, usando
para esse efeito, uma ou mais invocaes recursivas
end

Figura 4.1 - Soluo genrica de um algoritmo recursivo.

Devemos ter sempre presente, que a invocao de uma funo, produz um desperdcio de
tempo e de memria devido criao de uma cpia local dos parmetros de entrada que
so passados por valor, bem como na salvaguarda do estado do programa na altura da
invocao, na memria de tipo pilha (stack), para que, o programa possa retomar a
execuo na instruo seguinte invocao da funo, quando a execuo da funo
terminar. Este problema ainda mais relevante no caso de funes recursivas,
principalmente quando existem mltiplas invocaes recursivas. Geralmente, quando
existem solues recursivas e iterativas para o mesmo problema, a soluo recursiva requer
mais tempo de execuo e mais espao de memria devido s invocaes extras da funo.

CAPTULO 4 : RECURSIVIDADE

4.2 Clculo do factorial


Vamos considerar o problema do clculo do factorial. A soluo iterativa, cuja funo se
apresenta
na
Figura
4.2,
decorre
directamente
da
definio
n! = n (n1) (n2) ... 2 1, e calcula o produto acumulado de forma repetitiva
para a varivel auxiliar FACT, utilizando explicitamente um ciclo repetitivo for.
unsigned int FACTORIAL (int N)
{
int I, FACT = 1;
if (N < 0) return 0;

/* invocao anormal (valor devolvido 0) */

for (I = 2; I <= N; I++) FACT *= I;


return FACT;

/* clculo do produtrio */

/* devoluo do valor calculado */

Figura 4.2 - Funo factorial implementada de forma iterativa.

Mas, se tivermos em conta que n! = n (n1)!, sendo que 0! = 1, ento a funo factorial
pode ser implementada de forma recursiva, tal como se apresenta na Figura 4.3.
unsigned int FACTORIAL (int N)
{
if (N < 0) return 0;
/* invocao anormal (valor devolvido 0) */
if (N == 0) return 1;
/* condio de paragem */
return N * FACTORIAL (N-1);
/* invocao recursiva */
}

Figura 4.3 - Funo factorial implementada de forma recursiva.

A primeira condio de teste evita que a funo entre num processo recursivo infinito, caso
a funo seja invocada para um valor de N negativo. Uma das instrues da funo
factorial recursiva a invocao recursiva, quando a funo se invoca a si prpria. Para
cada nova invocao, o valor do factorial a calcular diminudo de uma unidade, at que
acabar por atingir o valor zero. Nesse caso, a funo no se invoca mais e retorna o valor
1. Assim temos que N ser igual a zero, funciona como condio de paragem da
recursividade. Quando a recursividade termina, ento calculado o valor final da funo,
no retorno das sucessivas invocaes da funo.
A Figura 4.4 representa a anlise grfica da invocao da funo factorial recursiva para o
clculo de 3!. Cada caixa representa o espao de execuo de uma nova invocao da
funo. Se considerarmos cada caixa como um n estamos perante o que se designa por
rvore de recorrncia da funo. Temos que 3! = 3 2!, por sua vez 2! = 2 1!, por sua
vez 1! = 1 0!, e finalmente a invocao recursiva termina com a condio de paragem,
sendo 0! = 1. O valor de 3! calculado no retorno das sucessivas invocaes da funo
recursiva, pelo que, 3! = 1 1 2 3 = 6.
A implementao recursiva no tem qualquer vantagem sobre a implementao repetitiva.
Mas, tem a desvantagem associada invocao sucessiva de funes, que o tempo gasto
na invocao de uma funo e o eventual esgotamento da memria de tipo pilha. Apesar da
implementao recursiva no usar variveis locais, no entanto, gasta mais memria porque
a funo tem de fazer a cpia do parmetro de entrada que passado por valor. Pelo que, a
verso iterativa mais eficiente e portanto, deve ser usada em detrimento da verso
recursiva. O maior factorial que se consegue calcular em aritmtica inteira de 32 bits o 12!
que igual a 479001600.

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

printf ("%d", FACTORIAL(3));


6

FACTORIAL(3) = 3 * FACTORIAL(2)
3 * 2

FACTORIAL(2) = 2 * FACTORIAL(1)
2 * 1

FACTORIAL(1) = 1 * FACTORIAL(0)
1 * 1

FACTORIAL(0) = 1

Figura 4.4 - Visualizao grfica da invocao da funo factorial recursiva.

4.3 Expanso em srie de Taylor


No computador as funes trigonomtricas e logartmicas so implementadas atravs da
expanso em srie de Taylor. O termo geral da expanso em srie de Taylor da funo seno
dado pela seguinte expresso esquerda, sendo apresentada direita a sua expanso para
os primeiros cinco termos da srie.
N 1

seno( x, N )

x 2 n 1

 1 n u

2n  1 !
n 0

seno( x,5 )

x 

x3
x5
x7
x9
...



3!
5!
7!
9!

Como bem sabido, a funo factorial explode rapidamente, assumindo valores de tal
maneira elevados que s podem ser armazenados em variveis de tipo real, com a
consequente perda de preciso. Pelo que, uma forma de resolver o problema, passa por
eliminar a necessidade do clculo do factorial recorrendo a um processo recorrente. Uma
vez que se trata de uma srie geomtrica, poderamos calcular cada elemento da srie a
partir do elemento anterior e adicionar os elementos calculados. Mas, se aplicarmos de um
modo sistemtico a propriedade distributiva da multiplicao relativamente adio,
obtemos uma expresso, em que o clculo do factorial desaparece.
seno( x, N )

x2
x2
x2
x2
u 1 
u 1 
u 1 
u ...
x u 1 
2u3
4
u
5
6
u
7
8
u
9

Esta expresso pode ser calculada de duas maneiras. Podemos calcul-la de baixo para cima
(bottom-up), ou seja, do termo P4 para o termo P1, atravs de um processo iterativo, ou em
alternativa podemos calcular a expresso de cima para baixo (top-down), ou seja, do termo P1
para o termo P4, atravs de um processo recursivo.
Vamos comear por analisar a implementao iterativa. Neste caso vamos calcular de
forma repetitiva a expresso entre parntesis da direita para a esquerda, ou seja, do termo
P4 at ao termo P1, utilizando o passo iterativo dado pela seguinte expresso.

CAPTULO 4 : RECURSIVIDADE

Pi 1

1

x2
u Pi
2 u i u 2 u i  1

Como estamos perante o clculo de um produto acumulado, Pi tem de ser inicializado a


1.0, que o elemento neutro do produto. Depois do clculo repetitivo, temos que o valor
final do seno seno( x, N ) x u P1 . A Figura 4.5 apresenta a funo iterativa.
double SENO_TAYLOR_ITERATIVO (double X, int N)
{
int I; double TAYLOR = 1.0;
for (I = N; I > 0; I--)
TAYLOR = 1 - X*X / (2*I * (2*I+1)) * TAYLOR;
return X * TAYLOR;
}

Figura 4.5 - Funo iterativa que calcula a expanso em srie de Taylor da funo seno.

Vamos agora analisar a implementao recursiva. Neste caso vamos calcular a expresso
entre parntesis da esquerda para a direita, ou seja, do termo P1 at ao termo P4, sendo que,
cada termo calculado em funo do termo seguinte ainda por calcular. Da a necessidade
do processo ser recursivo. O passo recursivo dado pela seguinte expresso.
Seno( x, N , i )

1

x2
u Seno( x, N , i  1)
2 u i u 2 u i  1

O processo recursivo terminado quando o termo a calcular o ltimo termo desejado, ou


seja, quando i igual a N, cujo valor dado pela seguinte expresso.

Seno( x, N , N )

1

x2
2 u N u 2 u N  1

Caso o nmero de termos pretendidos na expanso seja apenas um, o valor final do seno
x e no h a necessidade de invocar o clculo recursivo. Caso, o nmero de termos seja
maior do que um, ento o valor final do seno igual a x vezes o valor obtido pelo clculo
recursivo do primeiro elemento, ou seja, seno( x, N ) x u Seno( x, N ,1) .
Pelo que, para implementar o clculo de forma recursiva precisamos de decompor a
soluo em duas funes, tal como se apresenta na Figura 4.6. Uma a funo recursiva
propriamente dita que implementa a expanso em srie de Taylor, e a outra a funo que
calcula o valor final e que invoca, caso seja necessrio, a funo recursiva.
double TAYLOR (double X, int N, int I)
{
/* condio de paragem */
if (I == N) return 1 - X*X / (2*N * (2*N+1));
return 1 - X*X / (2*I * (2*I+1)) * TAYLOR (X, N, I+1);
}
double SENO_TAYLOR_RECURSIVO (double X, int N)
{
if (N == 1) return X;
return X * TAYLOR (X, N, 1);
}

Figura 4.6 - Funo recursiva que calcula a expanso em srie de Taylor da funo seno.

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

Neste exemplo, a verso recursiva para alm do desperdcio de tempo gasto com a
invocao sucessiva da funo, tem uma implementao mais extensa em termos de
cdigo, uma vez que a soluo tem de ser decomposta em duas funes. Pelo que, a verso
iterativa mais eficiente e simples de programar.

4.4 Nmeros de Fibonacci


Um exemplo de clculo recursivo mais complexo do que o clculo do factorial o clculo
dos nmeros de Fibonacci, que so definidos pela seguinte relao de recorrncia e cuja
implementao recursiva se apresenta na Figura 4.7.
Fibonacci( n)

0, se n 0

1, se n 1
Fibonacci( n - 1)  Fibonacci( n - 2), se n t 2

unsigned int FIBONACCI (int N)


{
if (N <= 0) return 0;
/* Fibonacci de 0 */
if (N <= 2) return 1;
/* Fibonacci de 1 e de 2 */
return FIBONACCI (N-1) + FIBONACCI (N-2); /* Fibonacci de n > 2 */
}

Figura 4.7 - Funo que calcula os nmeros de Fibonacci de forma recursiva.

A Figura 4.8 apresenta a rvore de recorrncia do clculo do Fibonacci de 5.


FIBONACCI(5)
FIB(5) = FIB(4)+FIB(3)

FIBONACCI(4)

FIBONACCI(3)

FIB(4) = FIB(3)+FIB(2)

FIB(3) = FIB(2)+FIB(1)

FIBONACCI(3)

FIBONACCI(2)

FIBONACCI(2)

FIBONACCI(1)

FIB(3) = FIB(2)+FIB(1)

FIB(2) = FIB(1)+FIB(0)

FIB(2) = FIB(1)+FIB(0)

FIB(1) = 1

FIBONACCI(2)

FIBONACCI(1)

FIBONACCI(1)

FIBONACCI(0)

FIBONACCI(1)

FIBONACCI(0)

FIB(2) = FIB(1)+FIB(0)

FIB(1) = 1

FIB(1) = 1

FIB(0) = 0

FIB(1) = 1

FIB(0) = 0

FIBONACCI(1)

FIBONACCI(0)

FIB(1) = 1

FIB(0) = 0

Figura 4.8 - Visualizao grfica da invocao da funo Fibonacci recursiva.

Como se pode ver, esta soluo computacionalmente ineficiente, porque para calcular o
Fibonacci de 5, calcula repetidamente alguns valores intermdios. Mais concretamente,
duas vezes o Fibonacci de 3, trs vezes o Fibonacci de 2, cinco vezes o Fibonacci de 1 e
trs vezes o Fibonacci de 0. Por outro lado, devido dupla invocao recursiva o nmero
de operaes explode rapidamente. Como se pode ver na Figura 4.8, o clculo do
Fibonacci de 5 custa 7 adies. Para se calcular o Fibonacci de 6, temos que calcular o
Fibonacci de 5 e o Fibonacci de 4, o que d um total de 12 adies. O Fibonacci de 7 custa
20 adies, o Fibonacci de 8 custa 33 adies. Ou seja, o nmero de adies quase que
duplica quando se incrementa o argumento da funo de uma unidade. Pelo que, o tempo
de execuo desta funo praticamente exponencial.

CAPTULO 4 : RECURSIVIDADE

Uma forma de resolver problemas recursivos de maneira a evitar o clculo repetido de


valores consiste em calcular os valores de baixo para cima e utilizar um agregado para
manter os valores entretanto calculados. Este mtodo designa-se por programao
dinmica e reduz o tempo de clculo custa da utilizao de mais memria para armazenar
valores internos. Esta soluo apresentada na Figura 4.9, sendo o clculo efectuado do
Fibonacci de 0 at ao Fibonacci de N. Os trs primeiros valores, ou seja, o Fibonacci de 0,
de 1 e de 2, so colocados na inicializao do agregado.
unsigned int FIBONACCI (int N)
{
int I; unsigned int FIB[50] = {0,1,1};
if (N <= 0) return 0;
if (N <= 2) return 1;

/* Fibonacci de 0 */
/* Fibonacci de 1 e de 2 */

for (I = 3; I <= N; I++)


/* para calcular o Fibonacci de n > 2 */
FIB[I] = FIB[I-1] + FIB[I-2];
return FIB[N];
}

Figura 4.9 - Funo que calcula os nmeros de Fibonacci de forma dinmica.

Mas, se repararmos bem na soluo dinmica verificamos que de facto a clculo do


Fibonacci de N s precisa dos valores do Fibonacci de N-1 e do Fibonacci de N-2, pelo
que, podemos substituir a utilizao do agregado por apenas trs variveis simples. Uma
para armazenar o valor a calcular, que designamos por PROXIMO, outra para armazenar o
valor acabado de calcular, que designamos por ACTUAL e outra para armazenar o valor
calculado anteriormente, que designamos por ANTERIOR. A Figura 4.10 apresenta esta
verso iterativa. O valor inicial de ANTERIOR 0 e corresponde ao Fibonacci de 0, o
valor inicial de ACTUAL 1 e corresponde ao Fibonacci de 1 e o valor de PROXIMO
corresponde ao Fibonacci que se pretende calcular de forma iterada, e igual soma do
ANTERIOR com o ACTUAL.
unsigned int FIBONACCI (int N)
{
int I; unsigned int ANTERIOR = 0, ACTUAL = 1, PROXIMO;
if (N <= 0)
if (N == 1)

return 0;
return 1;

for (I = 2; I <= N; I++)


{
PROXIMO = ACTUAL + ANTERIOR;
ANTERIOR = ACTUAL;
ACTUAL = PROXIMO;
}

/* Fibonacci de 0 */
/* Fibonacci de 1 */
/* Fibonacci de n >= 2 */

return PROXIMO;
}

Figura 4.10 - Funo que calcula os nmeros de Fibonacci de forma iterativa.

Esta soluo iterativa a melhor das trs solues. a mais rpida e a que gasta menos
memria. O tempo de execuo desta soluo linear, ou seja, o clculo do Fibonacci de
2N custa aproximadamente o dobro do tempo do clculo do Fibonacci de N. O maior
nmero de Fibonacci que se consegue calcular em aritmtica inteira de 32 bits o Fibonacci
de 47 que igual a 2971215073.

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

4.5 Clculo dos coeficientes binomiais


O coeficiente binomial C(n,k), que representa o nmero de combinaes de n elementos a
k elementos dado pela seguinte expresso.
C(n, k)

n!
(n - k)! k!

Em muitas situaes, mesmo para valores finais representveis em variveis inteiras, os


valores parcelares podem ficar corrompidos, uma vez que o factorial rapidamente d
overflow em aritmtica inteira. Por exemplo, duas situaes limites so o C(n,0) e o C(n,n),
em que o nmero de combinaes igual a 1 (n!/n!). Pelo que, a utilizao da definio
para calcular C(n,k) est normalmente fora de questo e neste caso nem sequer podemos
recorrer a aritmtica real, devido consequente perda de preciso. Uma forma de resolver
o problema, passa por eliminar a necessidade do clculo do factorial recorrendo a um
processo iterativo, descoberto por Blaise Pascal e que conhecido pelo Tringulo de
Pascal. Como se pode ver na Figura 4.11, cada elemento com excepo dos elementos
terminais de cada linha, ou seja C(n,0) e C(n,n), calculado atravs da soma dos valores da
linha anterior. Ou seja, C(n,k) = C(n-1,k) + C(n-1,k-1), com n>k>0. A Figura 4.11
representa de forma grfica o clculo de C(5,3) que igual a 10. Repare que existem valores
que so usados duas vezes, o que significa que tambm vo ser calculados duas vezes.
1
1
1
1
1
1

2
3

4
5

1
1
3
6

10

1
4

10

1
5

Figura 4.11 - Tringulo de Pascal.

A funo recursiva para calcular o coeficiente binomial, que se apresenta na Figura 4.12,
invoca-se recursivamente duas vezes e tem como condies de paragem os elementos
terminais, cujo valor 1. A primeira condio de teste evita que a funo entre num
processo recursivo infinito, caso a funo seja invocada para k menor do que n.
unsigned int COMBINACOES (int N, int K)
{
if (K > N) return 0;
/* invocao anormal (valor devolvido 0) */
if ((K == 0) || (K == N)) return 1;
/* condies de paragem */
return COMBINACOES (N-1, K) + COMBINACOES (N-1, K-1);
}

Figura 4.12 - Funo recursiva para calcular os coeficientes binomiais.

CAPTULO 4 : RECURSIVIDADE

Esta implementao no entanto pouco prtica para valores de n e k elevados, antes de


mais porque, tal como no caso dos nmeros de Fibonacci, repete o clculo de certos
valores e portanto, computacionalmente ineficiente. Mas, o grande problema deste
algoritmo que devido dupla invocao recursiva a invocao tambm explode
rapidamente.
A soluo dinmica, que se apresenta na Figura 4.13, implica a utilizao de um agregado
bidimensional para armazenar os coeficientes binomiais, medida que estes so calculados.
unsigned int COMBINACOES (int N, int K)
{
int I, J, MIN; unsigned int BICOEF[36][36];
if (K > N) return 0;
/* invocao anormal (valor devolvido 0) */
if ((K == 0) || (K == N)) return 1;
/* C(n,0) = C(n,n) = 1 */
for (I = 0; I <= N; I++)
{
MIN = (I <= K) ? I : K;
/* mnimo de I e K */
for (J = 0; J <= MIN; J++)
if ( (J == 0) || (J == I) ) BICOEF[I][J] = 1;
else BICOEF[I][J] = BICOEF[I-1][J-1] + BICOEF[I-1][J];
}
return BICOEF[N][K];
}

Figura 4.13 - Funo dinmica para calcular os coeficientes binomiais.

Enquanto que a soluo recursiva calcula 2C(n,k)1 termos, a soluo dinmica calcula
aproximadamente, por defeito, NK termos. Os maiores coeficientes binomiais que se
conseguem calcular em aritmtica inteira de 32 bits so o C(35,16) e o C(35,19), que so
iguais a 4059928950.

4.6 Clculo das permutaes


Consideremos agora que se pretende imprimir todas as permutaes de um conjunto de N
caracteres. Por exemplo, se o conjunto de caracteres for {a,b,c}, ento o conjunto das
permutaes { (a,b,c), (a,c,b), (b,a,c), (b,c,a), (c,a,b), (c,b,a) }, ou seja, existem N!
permutaes. Podemos obter um algoritmo simples para gerar todas as permutaes de um
conjunto de caracteres, se construirmos um algoritmo recursivo.
Por exemplo, as permutaes do conjunto {a,b,c,d} so os quatro seguintes grupos de
permutaes: a seguido das permutaes do conjunto {b,c,d}; b seguido das permutaes
do conjunto {a,c,d}; c seguido das permutaes do conjunto {b,a,d}; e d seguido das
permutaes do conjunto {b,c,a}.
ento possvel resolver o problema para N caracteres, se tivermos um algoritmo
recursivo que funcione para N1 caracteres. Em cada passo do processo coloca-se um
carcter esquerda do conjunto e calculam-se recursivamente as permutaes dos N1
restantes caracteres direita. Depois troca-se sucessivamente o carcter da esquerda com
um dos caracteres da direita, de forma a ter experimentado todos os N grupos possveis de
permutaes dos N1 caracteres. Durante o processo recursivo, quando se tiver atingido o
carcter mais direita, imprime-se a sequncia de permutaes acabada de gerar.

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

10

A Figura 4.14 apresenta a funo de clculo das permutaes. Assume-se que LISTA uma
cadeia de N caracteres terminada com o carcter nulo, de maneira a simplificar a sua escrita
no monitor. Para trocar os caracteres utiliza-se a funo TROCA.
void TROCA (char *CAR_I, char *CAR_J)
{
char TEMP;
TEMP = *CAR_I; *CAR_I = *CAR_J; *CAR_J = TEMP;
}
void PERMUTACOES (char LISTA[], int I, int N)
{
int J;
if (I == N)
/* condio de paragem
printf ("%s\n", LISTA[J]);
/* imprimir a permutao gerada
else for (J = I; J <= N; J++)
/* para todos os caracteres
{
TROCA (&LISTA[I], &LISTA[J]); /* por o carcter direita
PERMUTACOES (LISTA, I+1, N);/* permutar os n-1 caracteres
TROCA (&LISTA[J], &LISTA[I]);/* repor o carcter no stio
}

*/
*/
*/
*/
*/
*/

Figura 4.14 - Funo recursiva que gera as permutaes de um conjunto de caracteres.

A Figura 4.15 mostra a utilizao da funo. A invocao inicial da funo


PERMUTACOES (LISTA, 0, N1);, para permutar todos os caracteres que se
encontram na cadeia de caracteres LISTA, sendo que o primeiro carcter est na posio 0
e o ltimo carcter est na posio N1.
#include <stdio.h>
#include <string.h>
void PERMUTACOES (char [], unsigned int, unsigned int);
int main (void)
{
char LISTA[11];
unsigned int K;

/* cadeia de caracteres LISTA */


/* nmero de caracteres de LISTA */

printf ("Caracteres a permutar -> ");


scanf ("%10s", LISTA); /* leitura da cadeia de caracteres LISTA */
K = strlen (LISTA);

/* determinao do nmero de caracteres */

printf ("Permutaes\n\n");
PERMUTACOES (LISTA, 0, K-1);
return 0;
}
void TROCA (char *CAR_I, char *CAR_J)
...
void PERMUTACOES (char LISTA[], unsigned int I, unsigned int N)
...

Figura 4.15 - Programa que utiliza a funo que gera as permutaes.

11

CAPTULO 4 : RECURSIVIDADE

4.7 Clculo do determinante de uma matriz quadrada


O clculo do determinante de uma matriz com dimenso elevada normalmente
complexo, a no ser que a matriz seja uma matriz diagonal. Pelo que, uma maneira de
simplificar o clculo consiste em transformar a matriz, numa matriz diagonal. Esta
transformao da matriz pode ser feita recursivamente, atravs de operaes de adio e
subtraco de colunas da matriz, operaes essas que no afectam o valor do seu
determinante. A Figura 4.16 apresenta o algoritmo em pseudocdigo e linguagem natural
do clculo recursivo do determinante de uma matriz quadrada de dimenso N.
Quando o elemento da ltima linha e ltima coluna da matriz nulo a coluna tem de ser
trocada com outra coluna, cujo ltimo elemento no seja nulo, de forma a colocar um valor
no nulo na diagonal. Caso no haja nenhuma coluna nessa situao, ento sinal que
todos os elementos da ltima linha so nulos, pelo que, o determinante nulo. Sempre que
se trocam duas colunas de uma matriz o determinante tem de ser multiplicado por 1. Ao
dividir-se a ltima coluna pelo ltimo elemento pe-se em evidncia esse elemento e depois
para anular a ltima linha da matriz, basta subtrair todas as colunas menos a ltima, pela
ltima coluna multiplicada pelo ltimo elemento da coluna a processar. Uma vez que,
depois deste processamento a ltima linha da matriz constituda apenas por zeros, com
excepo do ltimo elemento da linha, ou seja, o elemento que est na diagonal, agora o
determinante desta matriz igual a este valor multiplicado pelo determinante da matriz de
ordem N1. O processo invocado recursivamente at N ser igual a 1. Nessa altura o
determinante o prprio valor. Em alternativa pode-se parar o processo recursivo quando
N igual a 2, uma vez que fcil calcular o determinante dessa matriz.
nome: Calculo do determinante (MATRIZ, N)
Funo
begin
if (N = 1) then Calculo do determinante := MATRIZ[1,1]
else begin
if (Matriz[N,N] = 0)
then if existe coluna com ltimo elemento diferente de zero?
then Trocar essa coluna com a ltima
e multiplicar o determinante por -1
else Calculo do determinante := 0;
if (Matriz[N,N] <> 0)
then begin
Dividir a ltima coluna pelo ltimo elemento pondo-o
em evidncia;
Subtrair todas as colunas menos a ltima,
pela ltima coluna multiplicada pelo ltimo elemento
da coluna a processar;
Calculo do determinante := Matriz[N,N]
* Calculo do determinante (MATRIZ, N-1);
end
end
end

Figura 4.16 - Algoritmo do clculo recursivo do determinante.

A Figura 4.17 apresenta a funo e a Figura 4.18 apresenta a aplicao da funo sobre uma
matriz quadrada de 44 e as sucessivas matrizes, equivalentes para efeito do clculo do
determinante, que vo sendo obtidas aps cada invocao recursiva. Quando se atinge a
matriz de um s elemento, a matriz original foi completamente transformada numa matriz
diagonal. Agora o determinante calculado no retorno das sucessivas invocaes da funo
recursiva e igual ao produto dos elementos na diagonal. Logo, o valor do determinante
2.0 -1.0 -1.5 5.0, ou seja, igual a 15.

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

12

#define NMAX 10
...
double CALC_DETERMINANTE (double MATRIZ[][NMAX], unsigned int N)
{
int COL_AUX, NC, NL, UE = N-1; double ELEMENTO;
if (N == 1) return MATRIZ[0][0];
/* condio de paragem */
else
{
COL_AUX = UE;
/* procurar coluna com ltimo elemento 0 */
while ( (COL_AUX >= 0) && (MATRIZ[UE][COL_AUX] == 0) )
COL_AUX--;
if (COL_AUX >= 0)
/* se existir tal coluna */
{
if (COL_AUX != UE)
/* se no for a ltima coluna */
for (NL = 0; NL < N; NL++)
/* trocar as colunas */
{
ELEMENTO = MATRIZ[NL][UE];
MATRIZ[NL][UE] = MATRIZ[NL][COL_AUX];
MATRIZ[NL][COL_AUX] = -ELEMENTO;
}
/* dividir a coluna N-1 pelo ltimo elemento pondo-o em evidncia */
for (NL = 0; NL < UE; NL++)
MATRIZ[NL][UE] /= MATRIZ[UE][UE];
/* subtrair todas as colunas menos a ltima pela ltima coluna */
/* multiplicada pelo ltimo elemento da coluna a processar */
for (NC = 0; NC < UE; NC++)
for (NL = 0; NL < UE; NL++)
MATRIZ[NL][NC] -= MATRIZ[NL][UE] * MATRIZ[UE][NC];
/* invocao recursiva para a matriz de dimenso N-1 */
return MATRIZ[UE][UE] * CALC_DETERMINANTE (MATRIZ, N-1);
}
else return 0;
}
}

Figura 4.17 - Funo recursiva que calcula o determinante de uma matriz quadrada.

3.0

4.0

2.0

5.0

4.0

2.0

2.0

1.0

1.0
0.0

3.0
5.0

2.0
3.0

2.0
2.0

-2.5 2.5
4.5 -1.5

0.0
0.0

0.0
0.0

0.0

0.0 -1.0

0.0

0.0

0.0

2.0

0.0

=>

=>

3.0 -8.5 -5.5

0.0

4.0 -0.5

0.5

0.0

1.0 -2.0 -1.0


0.0 0.0 0.0

0.0
2.0

5.0 0.0
0.0 -1.5

0.0
0.0

0.0
0.0

0.0

0.0 -1.0

0.0

0.0

0.0

2.0

0.0

Figura 4.18 - Execuo da funo recursiva que calcula o determinante de uma matriz quadrada.

13

CAPTULO 4 : RECURSIVIDADE

4.8 Torres de Hani


Vamos agora apresentar um problema cuja nica soluo conhecida a soluo recursiva.
As Torres de Hani o puzzle que se apresenta na primeira linha da Figura 4.19. Temos
um conjunto de discos todos de tamanho diferente enfiados na Torre A. Pretende-se
mud-los para a Torre B, mas, s se pode mudar um disco de cada vez, s se pode mudar o
disco de cima e nunca se pode colocar um disco sobre outro mais pequeno. Para
efectuarmos a mudana podemos usar a Torre C como torre auxiliar.

Torre A

Torre B

Torre C

Torre A

Torre B

Torre C

Torre A

Torre B

Torre C

Torre A

Torre B

Torre C

Figura 4.19 - Torres de Hani.

A soluo para este problema trivial caso o nmero de discos seja um. Nesse caso basta
mud-lo da Torre A para a Torre B. No entanto se tivermos mais do que um disco a
soluo no bvia e at bastante dispendiosa medida que o nmero de discos
aumenta. A soluo que se apresenta na Figura 4.20 a soluo recursiva que resolve o
puzzle para o caso de N discos colocados na Torre A. Consiste em diminuir a
complexidade do problema at situao em que temos apenas um disco e para o qual
conhecemos a soluo.

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

14

Assim para o caso apresentado na Figura 4.19 em que temos quatro discos na Torre A, a
soluo passa por movimentar os trs discos de cima da Torre A para a Torre C, depois
mudar o quarto disco da Torre A para a Torre B e finalmente mudar os trs discos que se
encontram na Torre C para a Torre B.
nome: Torres de Hanoi (N, TORRE_A, TORRE_B, TORRE_C)
Procedimento
begin
if (N = 1)
then Mover o disco da TORRE_A para a TORRE_B
else begin
Torres de Hanoi (N-1, TORRE_A, TORRE_C, TORRE_B);
Torres de Hanoi (1, TORRE_A, TORRE_B, TORRE_C);
Torres de Hanoi (N-1, TORRE_C, TORRE_B, TORRE_A);
end
end

Figura 4.20 - Algoritmo das Torres de Hani.

Obviamente que esta soluo funciona se for aplicada recursivamente, porque, resolver o
problema de mudar os trs primeiros discos da Torre A para a Torre C, resolve-se da
mesma forma, s que agora a torre de chegada a Torre C, a torre auxiliar a Torre B e o
nmero de discos a mudar menos um do que no problema inicial. A soluo passa por
mudar os dois discos de cima da Torre A para a Torre B, depois mudar o terceiro disco da
Torre A para a Torre C e finalmente mudar os dois discos que se encontram na Torre B
para a Torre C. Assim estamos perante o problema de mudar dois discos da Torre A para a
Torre B, o que implica mudar o primeiro disco da Torre A para a Torre C, o segundo disco
da Torre A para a Torre B e finalmente o primeiro disco da Torre C para a Torre B.
Aps termos mudado os trs primeiros discos da Torre A para a Torre C e termos atingido
a situao apresentada na terceira linha da Figura 4.19, ento para mudar os trs discos que
se encontram na Torre C para a Torre B aplica-se de novo a soluo recursiva, mas agora a
torre de partida a Torre C, a torre de chegada a Torre B e a torre auxiliar a Torre A.
A Figura 4.21 e a Figura 4.22 apresentam o programa que simula as Torres de Hani. Para
implementar as trs torres utilizam-se trs agregados, sendo os discos representados por
nmeros inteiros de 1 a N. O programa principal l do teclado o nmero de discos, valida o
seu valor, constri a situao inicial, imprime-a no monitor e depois invoca a funo para
simular a mudana dos discos.
A funo INICIALIZAR coloca os N discos na Torre A e nenhum disco na Torre B e na
Torre C. A funo MUDAR_DISCOS implementa o algoritmo recursivo e aps cada
mudana de um disco invoca a funo IMPRIMIR. A funo IMPRIMIR vai imprimir no
monitor o estado das torres, no incio e aps cada mudana de um disco. A funo no tem
parmetros, porque utiliza variveis globais. Uma vez que o algoritmo recursivo, em cada
nova invocao as torres vo mudando de posio, pelo que, a nica forma de poder
imprimir o estado das torres A, B e C, usando variveis globais para as torres e para os
contadores do nmero de discos armazenados em cada torre. Para declarar variveis
globais, elas tm de ser declaradas antes da funo main com o qualificativo static.

15

CAPTULO 4 : RECURSIVIDADE

#include <stdio.h>
#define D_MAX 10
/* agregados para as torres */
static int TORREA[D_MAX], TORREB[D_MAX], TORREC[D_MAX];
static int NDA, NDB, NDC;
/* nmero de discos de cada torre */
void INICIALIZAR (int, int [], int *, int [], int *, int [], int *);
void IMPRIMIR (void);
void MUDAR_DISCOS (int, int [], int *, int [], int *, int [], int *);
int main (void)
{
int NDISCOS;

/* nmero de discos a colocar na Torre A */

do
{
printf ("Numero de discos = "); scanf ("%d", &NDISCOS);
} while ( (NDISCOS <= 0) || (NDISCOS > DISCOS_MAX) );
INICIALIZAR (NDISCOS, TORREA, &NDA, TORREB, &NDB, TORREC, &NDC);
printf ("---------------------------------\n");
printf ("|
Torres de Hanoi
|\n");
printf ("|
Numero de discos = %2d
|\n", NDISCOS);
printf ("---------------------------------\n");
printf ("| TORRE A
TORRE B
TORRE C |\n");
printf ("---------------------------------\n");
IMPRIMIR ();
MUDAR_DISCOS (NDISCOS, TORREA, &NDA, TORREB, &NDB, TORREC, &NDC);
return 0;
}
void INICIALIZAR (int ND, int TA[], int *NDA, int TB[], int *NDB,\
int TC[], int *NDC);
{
int I;
for (I = 0; I < D_MAX; I++)
{
TA[I] = 0; TB[I] = 0; TC[I] = 0;
/* limpar os agregados */
}
for (I = 0; I < ND; I++) TA[I] = ND - I;
/* discos na Torre A */
*NDA = ND;

*NDB = 0;

*NDC = 0;

}
void IMPRIMIR (void)
{
int I, CMAX = NDA;
if (NDB > CMAX) CMAX = NDB;
if (NDC > CMAX) CMAX = NDC;
for (I = CMAX; I > 0; I--)
{
if (NDA >= I) printf ("%10d", TORREA[I-1]);
else printf ("%10c", ' ');
if (NDB >= I) printf ("%10d", TORREB[I-1]);
else printf ("%10c", ' ');
if (NDC >= I) printf ("%10d", TORREC[I-1]);
else printf ("%10c", ' ');
printf ("\n");
}
printf ("---------------------------------\n"); scanf ("%*c");
}

Figura 4.21 - Programa das Torres de Hani (1 parte).

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

16

void MUDAR_DISCOS (int ND, int TA[], int *NDA, int TB[], int *NDB,\
int TC[], int *NDC);
{
if (ND == 1)
/* condio de paragem */
{
/* mudar o disco da Torre A para a Torre B */
(*NDB)++; TB[*NDB-1] = TA[*NDA-1]; (*NDA)--;
IMPRIMIR ();
}
else
{
/* mudar os N-1 discos de cima da Torre A para a Torre C */
MUDAR_DISCOS (ND-1, TA, NDA, TC, NDC, TB, NDB);
/* mudar o ltimo disco da Torre A para a Torre B */
(*NDB)++; TB[*NDB-1] = TA[*NDA-1]; (*NDA)--;
IMPRIMIR ();
/* mudar os N-1 discos da Torre C para a Torre B */
MUDAR_DISCOS (ND-1, TC, NDC, TB, NDB, TA, NDA);
}
}

Figura 4.22 - Programa das Torres de Hani (2 parte).

O nmero de movimentos necessrios para mudar N discos igual a 2N 1. Pelo que, o


nmero de invocaes da funo recursiva aumenta exponencialmente com o aumento do
nmero de discos. Por isso, no se deve invocar o programa para um nmero de discos
maior do que 10. A Figura 4.23 apresenta a execuo do programa para trs discos.
--------------------------------|
Torres de Hanoi
|
|
Numero de discos = 3
|
--------------------------------| TORRE A
TORRE B
TORRE C |
--------------------------------1
2
3
--------------------------------2
3
1
--------------------------------3
1
2
--------------------------------1
3
2
--------------------------------1
3
2
--------------------------------1
3
2
--------------------------------2
1
3
--------------------------------1
2
3
---------------------------------

Figura 4.23 - Execuo do programa Torres de Hani para 3 discos.

17

CAPTULO 4 : RECURSIVIDADE

4.9 Questes sobre a eficincia das solues recursivas


Alguns algoritmos recursivos desencadeiam um nmero arbitrariamente grande de
invocaes sucessivas da funo, pelo que, nestes casos temos uma ineficincia acrescida
associada invocao de funes. Por outro lado, existem algoritmos recursivos que so
computacionalmente ineficientes, porque calculam certos valores repetidamente devido s
mltiplas invocaes recursivas. No caso dos algoritmos em que a invocao recursiva
explode rapidamente, existe ainda o perigo de a memria de tipo pilha esgotar e do
programa terminar a sua execuo abruptamente. Portanto, o programador deve ter sempre
em conta estas ineficincias e limitaes, antes de optar por uma soluo recursiva em
detrimento de uma soluo iterativa, caso ela exista.
Os algoritmos recursivos so apropriados para resolver problemas que so normalmente
definidos de forma recursiva, ou seja, problemas que so por natureza recursivos. Por
vezes, existem problemas que tm solues recursivas que so simples, concisas, elegantes
e para os quais difcil esboar solues iterativas com a mesma simplicidade e clareza.
Mas, tal como vimos nos exemplos apresentados, alguns algoritmos recursivos so menos
eficientes que os algoritmos iterativos equivalentes. Pelo que, a verdadeira importncia da
recursividade consiste em resolver esses problemas para os quais no existem solues
iterativas simples.

4.10 Exerccios
1. Pretende-se escrever um programa que imprima no monitor, uma tabela em que se
compara os valores da funo co-seno calculada pela expanso em srie de Taylor para 5,
10 e 15 termos e pela funo matemtica cos. Os valores inicial e final da tabela, bem como
o nmero de elementos da tabela so lidos do teclado. O termo geral da expanso em srie
de Taylor da funo co-seno dado pela seguinte expresso esquerda, sendo apresentada
direita a sua expanso para os primeiros cinco termos da srie.
N 1

coseno( x, N )

2n

 1 n u x

2n
n 0

coseno( x,5 )

1

x2
x4
x6
x8



...
2!
4!
6!
8!

Desenhe a tabela com o formato que a seguir se apresenta.


---------------------------------------------------------------------|
x
| coseno(x,5) | coseno(x,10) | coseno(x,15) |
cos(x)
|
---------------------------------------------------------------------| ##.#### | #.######### | #.######### | #.######### | #.######### |
---------------------------------------------------------------------...
---------------------------------------------------------------------| ##.#### | #.######### | #.######### | #.######### | #.######### |
----------------------------------------------------------------------

Faa duas verses do programa. Na primeira verso, implemente a expanso da srie de


Taylor da funo co-seno com uma funo iterativa, e na segunda verso com uma funo
recursiva.

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

18

2. Pretende-se escrever um programa que escreva no monitor as permutaes da cadeia


de caracteres composta pelos caracteres numricos de 0 a 9. Altere o programa para
escrever apenas as permutaes que so compostas alternadamente por caracteres
numricos pares e mpares.
3. Pretende-se escrever um programa que l do teclado dois nmeros inteiros positivos,
que calcula e imprime no monitor o seu mximo divisor comum. O mximo divisor
comum de dois nmeros inteiros positivos pode ser calculado de forma repetitiva,
utilizando para o efeito o mtodo de Euclides, cujo algoritmo dado pela seguinte
expresso.
mdc(m, n)

m, se n 0

mdc(n, mod(m, n)), se n z 0

Faa duas verses do programa. Na primeira verso, implemente o clculo do mximo


divisor comum com uma funo iterativa, e na segunda verso com uma funo recursiva.
4. Pretende-se escrever um programa que l do teclado dois nmeros inteiros positivos,
que calcula e imprime no monitor o valor da funo de Ackermann, que se apresenta na
seguinte expresso.

Ackermann(m, n)

n  1, se m 0

Ackermann(m - 1,1), se n 0
Ackermann(m - 1, Ackermann(m, n - 1)) , com n ! 0 e m ! 0

Faa duas verses do programa. Na primeira verso, implemente o clculo da funo de


Ackermann com uma funo recursiva, e na segunda verso com uma funo iterativa
dinmica, usando um agregado bidimensional.

4.11 Leituras recomendadas


x 3 captulo do livro Data Structures, Algorithms and Software Principles in C, de
Thomas A. Standish, da editora Addison-Wesley Publishing Company, 1995.

Captulo 5
MEMRIAS

Sumrio
Neste captulo comeamos por introduzir o paradigma da programao modular e a
construo de mdulos na linguagem C, apresentando as suas caractersticas e a
necessidade de criao de mdulos genricos. A ttulo de exemplo, desenvolvemos um
exemplo de um mdulo abstracto com mltipla instanciao para operaes sobre nmeros
complexos.
Seguidamente mostramos a organizao da Memria de Acesso Aleatrio (RAM), da
Memria Fila (Queue/FIFO), da Memria Pilha (Stack/LIFO) e da Memria Associativa
(CAM) e os seus ficheiros de interface.
Depois de descrevermos as particularidades e limitaes dos tipos de implementao de
memrias, fazemos uma abordagem s estruturas de dados que servem de suporte
implementao esttica e semiesttica da Memria de Acesso Aleatrio, e implementao
esttica, semiesttica e dinmica da Memria Fila, da Memria Pilha e da Memria
Associativa.
Finalmente, apresentamos as funes da biblioteca de execuo ANSI stdlib que permitem a
atribuio e libertao de memria, durante a execuo do programa.

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

5.1 Programao modular


O paradigma de programao procedimental enunciado da seguinte forma decide os
procedimentos que precisas e usa os melhores algoritmos possveis. Consiste na
decomposio hierrquica da soluo do problema, tambm designada de decomposio
do topo para a base (Top-Down Decomposition), ou seja, implementa a estratgia do dividir
para conquistar. A nica maneira de lidar com um problema complexo consiste em
decomp-lo num conjunto de problemas mais pequenos, cada um deles de resoluo mais
fcil que o problema original. Outra tcnica que permite decompor a complexidade de um
problema, em problemas mais pequenos que tm a particularidade de serem exactamente
do mesmo tipo do problema original, a construo de solues recursivas.
A estratgia da decomposio hierrquica implementada atravs da definio de novas
operaes no mbito da linguagem, ou seja, atravs da criao de subprogramas. No caso
da linguagem Pascal temos dois tipos de subprogramas, que so o procedimento e a
funo. No caso da linguagem C, temos apenas a funo generalizada que um tipo de
subprograma que combina as caractersticas apresentadas pelo procedimento e pela funo.
O subprograma pode ser visto como uma caixa preta, que recebe informao entrada e
que produz informao sada, escondendo no entanto os detalhes da implementao da
operao, ou seja, as aces que dentro do subprograma transformam a informao de
entrada na informao de sada. Estas aces so invisveis externamente e, portanto, no
originam qualquer interaco com o exterior. Este conjunto de aces detalhadas, que
representam a soluo do problema, designa-se por algoritmo. Ao encapsulamento do
algoritmo dentro de um subprograma, designa-se por abstraco procedimental.
A abstraco procedimental permite fazer a separao entre o objectivo de um
subprograma da sua implementao. Aps a definio do subprograma, a nova operao
identificada por um nome e por uma lista de parmetros de comunicao com o exterior.
Do ponto de vista operacional, tudo o que o programador precisa de saber para utilizar o
subprograma o seu nome e a sua interface com o exterior, que deve estar bem
documentada. O programador no precisa de conhecer a sua implementao, ou seja, o
algoritmo utilizado. Da que, as linguagens de alto nvel providenciam facilidades para a
passagem de informao, de e para os subprogramas, de maneira a estabelecer o fluxo dos
dados atravs das subprogramas e por conseguinte, ao longo do programa.
Uma questo essencial a ter em conta durante o desenvolvimento de um subprograma,
que ele deve ser implementado de forma a ser, se possvel, completamente autnomo do
ambiente externo onde vai ser utilizado, tornando-o verstil. Assim assegura-se que ele
pode ser testado separadamente e, mais importante, que pode ser reutilizado noutro
contexto. portanto, uma nova operao que estende a operacionalidade da linguagem.
Com a criao de subprogramas autnomos possvel criar bibliotecas de subprogramas
que podero ser reutilizados mais tarde noutros programas.
Neste paradigma de programao, os subprogramas podem ser vistos como blocos que
podem ser interligados para construir programas mais complexos, fazendo uma montagem
do programa da base para o topo (Bottom-Up Assembly). Permitindo assim, validar a soluo
do problema de uma maneira controlada e integrar de um modo progressivo os diferentes
subprogramas, avaliando possveis solues alternativas atravs de diferentes arranjos de
subprogramas.

CAPTULO 5 : MEMRIAS

Assim, se potenciarmos ao mximo a reutilizao de subprogramas, o esforo que


necessrio despender para criar novos programas menor do que implement-los de raiz.
No entanto, a autonomia de um subprograma est limitada ao facto de a estrutura de dados
que o seu algoritmo manipula ser exterior ao subprograma. Pelo que, a utilizao do
subprograma tem que ter sempre em conta no s a lista de parmetros de comunicao
com o exterior, mas tambm a prpria estrutura de dados para o qual foi desenvolvido. O
que implica, que se um programa necessitar de uma estrutura de dados que mesmo sendo
semelhante implementada de forma diferente, ento o subprograma tem de ser
modificado antes de ser reutilizado. Assim a reutilizao do subprograma com um mnimo
de esforo pode estar comprometida.
Por outro lado, ao longo dos anos, a nfase na implementao do software dirigiu-se em
direco organizao de estruturas de dados cada vez mais complexas. Para estruturas de
dados complexas no faz sentido implementar subprogramas de uma forma isolada, mas
sim providenciar um conjunto de subprogramas que as manipulam e que permitem que as
estruturas de dados sejam encaradas como um novo tipo de dados da linguagem. Um tipo
de dados permite a declarao de variveis desse tipo e tem associado um conjunto de
operaes permitidas sobre essas variveis.
Este paradigma de programao, a que se d o nome de programao modular,
enunciado da seguinte forma decide os mdulos que precisas e decompe o programa
para que as estruturas de dados sejam encapsuladas nos mdulos. Nesta filosofia de
programao j no escondemos apenas os algoritmos, mas tambm as estruturas de dados
que so processadas pelos algoritmos. Ou seja, abstraco procedimental acrescenta-se
tambm a abstraco das estruturas de dados. Com a abstraco das estruturas de dados o
programador concentra a sua ateno na operacionalidade das operaes que processam as
estruturas de dados, ou seja, na aco das operaes sobre as estruturas de dados, em vez
de se preocupar com os detalhes da implementao das operaes.
Portanto, o paradigma de programao modular consiste na decomposio do programa
em estruturas autnomas interactivas, onde existe uma separao clara entre a definio do
mdulo, ou seja, a sua interface com o exterior e a respectiva implementao do mdulo.
Do ponto de vista operacional, tudo o que o programador precisa de saber para utilizar o
mdulo o seu nome e a sua interface com o exterior, que deve estar bem documentada.
O programador no precisa de conhecer a sua implementao, ou seja, a organizao da
estrutura de dados interna do mdulo e os algoritmos utilizados pelas operaes.
Esta filosofia de programao permite a criao de estruturas de dados abstractas, uma vez
que os detalhes da sua implementao esto escondidos, ou seja encapsulados no mdulo.
Permite tambm a sua proteco uma vez que no esto acessveis a partir do exterior a
no ser atravs das operaes de processamento disponibilizadas pelo mdulo. Permite
ainda, a criao de operaes virtuais, uma vez que se podem experimentar vrios
algoritmos alternativos de manipulao das estruturas de dados, atravs da criao de
mdulos de implementao alternativos, para a mesma interface.
Os mdulos so assim entidades mais autnomas do que os subprogramas e por
conseguinte mais reutilizveis. So blocos construtivos mais poderosos, tanto mais
poderosos quanto a sua estrutura de dados for reconfigurvel em funo das necessidades
da aplicao, ou seja, quanto mais abstracta for a estrutura de dados interna do mdulo. A
abstraco tem a vantagem de esconder completamente a implementao da estrutura de
dados, protegendo-a assim de operaes que no so fornecidas pelo mdulo.

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

Logo, uma questo essencial a ter em conta durante o desenvolvimento de um mdulo,


que ele deve ser implementado da forma mais abstracta possvel, tornando-o assim
genrico. Um mdulo genrico um mdulo cuja estrutura de dados pode ser do tipo de
dados determinado pelo programador, em funo da aplicao onde o mdulo vai ser
utilizado, sem que ele tenha que modificar a implementao do mdulo. Um mdulo
genrico assegura uma maior reutilizao.
Resumindo, os mdulos apresentam quatro caractersticas muito importantes: permitem
agrupar estruturas de dados e as operaes de processamento associadas; apresentam
interfaces bem definidas para os seus utilizadores, onde so reveladas as operaes de
processamento disponveis; escondem os detalhes da implementao das mesmas focando
a ateno do programador na sua funcionalidade; e podem ser compilados separadamente.

5.2 Mdulos na linguagem C


Antes de analisarmos a composio de um mdulo, devemos ter em considerao que
devemos ento criar, sempre que possvel, um mdulo genrico, de modo a que seja o mais
reutilizvel possvel. preciso tambm ter em ateno, que por vezes o programador no
tem acesso implementao do mdulo, mas apenas sua interface. Como ento
possvel criar um mdulo genrico? Existem duas maneiras.
Uma consiste em criar um mdulo de dados abstractos, ou seja, um mdulo em que a
estrutura de dados fica indefinida recorrendo declarao de ponteiros de tipo void.
Aquando da utilizao de um mdulo abstracto preciso concretiz-lo para o tipo de
dados pretendido atravs da disponibilizao de uma funo de criao do mdulo, que
responsvel pela caracterizao do tamanho em bytes do elemento bsico da estrutura de
dados. Uma vez que a biblioteca de execuo ANSI string providencia funes de cpia de
memria, sem ser atribudo qualquer interpretao ao contedo dos bytes copiados, ento
possvel manipular estruturas de dados sem que o cdigo C se tenha que preocupar com o
tipo de dados que est a processar. A implementao abstracta tem a vantagem de esconder
completamente a representao da informao, protegendo a estrutura de dados interna do
mdulo. Se o programador no conhecer os pormenores da implementao da estrutura de
dados, ento no pode, nomeadamente, desenvolver funes que actuam sobre ela.
A alternativa criao de um mdulo abstracto, consiste na criao de um mdulo
concreto, mas, em que o tipo de dados dos elementos da estrutura de dados definido
parte num ficheiro de interface, que vamos designar por elemento.h, e que depois
includo no ficheiro de interface do mdulo. Em certas aplicaes, este ficheiro para alm
da definio do tipo de dados dos elementos, pode tambm definir as constantes que
parametrizam a dimenso das estruturas de dados. Assim o utilizador do mdulo, pode
concretizar o mdulo para uma estrutura de dados que corresponda s suas necessidades,
quer em termos de dimenso quer em termos do tipo dos elementos, sem ter a necessidade
de reprogramar o ficheiro de implementao do mdulo. Em relao criao de um
mdulo abstracto, esta soluo exige a recompilao do mdulo, sempre que este ficheiro
de interface modificado.
A Figura 5.1 apresenta um exemplo do ficheiro de interface do elemento constituinte de
uma estrutura de dados, considerando que os elementos so do tipo TIPO_ELEMENTO,
bem como da definio da constante N_ELEMENTOS que dimensiona a estrutura de
dados do mdulo.

CAPTULO 5 : MEMRIAS

/*********** Interface da Estrutura de Dados do Mdulo ***********/


/* Nome: elemento.h */
/* Definio da dimenso da estrutura de dados e do tipo de dados
dos seus elementos. Este ficheiro deve ser modificado para adequar a
definio a cada implementao especfica.
*/
#ifndef _ELEMENTO
#define _ELEMENTO
/****** Constantes de Parametrizao das Estruturas de Dados ******/
#define

N_ELEMENTOS

100

/* nmero de elementos */

/************* Definio do Tipo de Dados do Elemento *************/


typedef

...

TIPO_ELEMENTO;

/* tipo de dados dos elementos */

#endif

Figura 5.1 - Ficheiro de interface do elemento constituinte da estrutura de dados.

Os mdulos na linguagem C so compostos por dois ficheiros. O ficheiro de interface, que


se apresenta na Figura 5.2, tem a extenso .h. O ficheiro de implementao, que se
apresenta na Figura 5.3, tem a extenso .c.
/*********************** Interface do Mdulo *********************/
/* Nome: modulo.h */
#ifndef _MODULO
#define _MODULO
/** Incluso do Ficheiro de Interface do Tipo de Dados do Mdulo **/
#include "elemento.h" /* caracterizao do tipo elemento do mdulo */
/******************** Definio de Constantes ********************/
#define OK 0
/* operao realizada com sucesso */
...
/************ Aluso s Funes Exportadas pelo Mdulo ************/
void INICIALIZAR (TIPO_ELEMENTO [], int);
...
#endif

Figura 5.2 - Ficheiro de interface do mdulo.

O ficheiro de interface declara as entidades do mdulo que so visveis no exterior e que


so utilizveis pelos utilizadores. Consiste na definio de constantes que representam
identificadores de cdigos de erro devolvidos pelas funes do mdulo e que servem para
assinalar o estado de execuo das funes. Consiste tambm nas aluses, ou prottipos,
das funes exportadas pelo mdulo. Apenas estas funes podem ser utilizadas e atravs
delas que as aplicaes manipulam as estruturas de dados internas do mdulo.
Por vezes um mdulo includo noutro mdulo, que por sua vez includo noutro
mdulo e assim sucessivamente at que um ou mais destes mdulos so includos nos
ficheiros fonte da aplicao. De maneira a evitar uma possvel mltipla incluso de um
mdulo na aplicao, ele deve ser includo condicionalmente. Para tal, existe a directiva do
pr-processador #ifndef _MODULO #define _MODULO endif que assegura que o
mdulo, cujo nome MODULO, includo apenas uma vez.

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

/******************** Implementao do Mdulo ********************/


/* Nome: modulo.c */
/************ Incluso das Bibliotecas da Linguagem C ************/
#include <stdlib.h>
...
/********** Incluso do Ficheiro de Interface do Mdulo **********/
#include "modulo.h"
/***** Declarao das Estruturas de Dados Internas do Mdulo *****/
static TIPO_ELEMENTO ESTRUTURA_DADOS_MODULO[N_ELEMENTOS];
...
/******************* Implementao das Funes ********************/
static void TROCA (int *, int *)
{ ... }

/* funo interna do mdulo */

/* funo exportada pelo mdulo */


void INICIALIZAR (TIPO_ELEMENTO [], int)
{ ... }
...

Figura 5.3 - Ficheiro de implementao do mdulo.

O ficheiro de implementao implementa a funcionalidade do mdulo. Comea por incluir


as bibliotecas de execuo ANSI da linguagem C, que so necessrias e o ficheiro de
interface do mdulo, de maneira a incluir as constantes que representam os cdigos de erro
devolvidos pelas funes e do tipo de dados dos elementos da estrutura de dados. Declara
as estruturas de dados internas do mdulo, que so o suporte ao armazenamento da
informao interna do mdulo. A declarao das estruturas de dados no ficheiro de
implementao, com o qualificativo static, torna-as globais no ficheiro de implementao
mas invisveis no exterior. Pelo que, podem ser manipuladas pelas funes do mdulo sem
terem de ser passadas pela lista de parmetros, mas, esto protegidas de serem manipuladas
atravs de funes exteriores ao mdulo. No caso das estruturas de dados do mdulo
serem estticas ou semiestticas elas so parametrizadas por constantes que definem a sua
dimenso. Essas constantes encontram-se definidas no ficheiro de interface do tipo de
dados dos elementos do mdulo, ou seja, no ficheiro elemento.h.
Aps a definio das estruturas de dados, o ficheiro de implementao faz a definio das
funes exportadas pelo mdulo, ou seja, das funes que foram declaradas no ficheiro de
interface. Por vezes, para implementar uma funo necessrio recorrer a funes
auxiliares de maneira a estruturar melhor a sua funcionalidade. Se estas funes auxiliares
forem partilhadas por vrias funes, nesse caso devem ser aludidas logo a seguir
declarao das estruturas de dados. Estas funes auxiliares, sendo funes internas do
mdulo, devem ser declaradas com o qualificativo static. Desta forma so invisveis para o
exterior, pelo que, os seus nomes podem ser utilizados noutros mdulos para implementar
outras funes internas a esses mdulos.
Os mdulos so compilados separadamente, com a opo c, para criar o ficheiro objecto,
que tem a extenso .o, tal como se mostra na linha seguinte.
cc c nome_do_mdulo.c
Para que uma aplicao utilize um mdulo, o seu ficheiro de interface deve ser includo nos
ficheiros da aplicao, tal como se apresenta na Figura 5.4.

CAPTULO 5 : MEMRIAS

/********************* Ficheiro da Aplicao *********************/


/************ Incluso das Bibliotecas da Linguagem C ************/
#include <stdio.h>
...
/********** Incluso do Ficheiro de Interface do Mdulo **********/
#include "modulo.h"
/******************** Definio de Constantes ********************/
#define NP 10
...
/*********** Aluso s Funes definidas neste Ficheiro ***********/
...
int main (void)
{
...
/* invocao das funes do mdulo e de outras funes */
return 0;
}
/******* Implementao das Funes definidas neste Ficheiro *******/
...

Figura 5.4 - Utilizao de um mdulo.

O ficheiro objecto depois acrescentado no comando de compilao do programa que


utiliza o mdulo, tal como se mostra na linha seguinte.
cc nome_do_ficheiro.c nome_do_mdulo.o o nome_do_ficheiro_executvel

5.3 Exemplo de um mdulo


Vamos agora mostrar a implementao de um mdulo que implementa a criao de
nmeros complexos e de operaes sobre nmeros complexos. A Figura 5.5 apresenta o
seu ficheiro de interface. Como se pode ver, o programador apenas sabe que o mdulo
define um tipo de dados PtComplexo que um ponteiro para uma estrutura, no sabendo
no entanto, se a estrutura que armazena o nmero complexo utiliza uma representao em
parte real e parte imaginria ou se utiliza a representao em coordenadas polares. Esta
declarao prende-se com a necessidade do programador ter que declarar mais do que um
nmero complexo na aplicao que vai desenvolver, pelo que, sem a exportao deste tipo
de dados o programador no o poderia fazer. Portanto, estamos perante um mdulo
abstracto com possibilidade de mltipla instanciao.
O mdulo providencia um conjunto de funes necessrias para criar e operar nmeros
complexos. Assim temos uma operao de inicializao que cria um nmero complexo
com um valor especificado, uma operao de leitura que l o nmero complexo
introduzido pelo teclado na forma R+jI e uma operao de escrita no monitor de um
complexo na mesma forma. A operao de leitura s l informao do teclado para o
nmero complexo, caso ele tenha sido criado previamente e a operao de escrita s
escreve o nmero complexo se ele existir. Qualquer uma das quatros operaes bsicas,
adio, subtraco, multiplicao e diviso, cria um novo nmero complexo para
armazenar o resultado da operao. Quando uma funo cria um nmero complexo, faz a
atribuio de memria para o seu armazenamento e devolve um ponteiro para a sua
localizao na memria. A partir da o nmero complexo pode ser manipulado no
programa atravs deste ponteiro.

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

O mdulo tambm fornece uma funo que permite libertar a memria atribuda para um
nmero complexo, quando ele no mais preciso, ou seja, uma funo para apagar o
nmero complexo. Para evitar divises por zero, preciso uma funo que verifica se um
nmero complexo o complexo nulo. Existem ainda funes para extrair a parte real e a
parte imaginria do nmero complexo.
/* Ficheiro de interface do mdulo de nmeros complexos */
/* Nome: complexo.h */
#ifndef _COMPLEXO
#define _COMPLEXO
typedef

struct

complexo

*PtComplexo;

PtComplexo Inicializar_Complexo (double R, double I);


/* Funo que cria e inicializa um complexo na forma R+jI */
void Ler_Complexo (PtComplexo PC);
/* Funo que cria e l do teclado um complexo na forma R+jI */
void Escrever_Complexo (PtComplexo PC);
/* Funo que escreve no monitor um complexo na forma R+jI */
PtComplexo Somar_Complexos (PtComplexo PC1, PtComplexo PC2);
/* Funo que soma dois nmeros complexos */
PtComplexo Subtrair_Complexos (PtComplexo PC1, PtComplexo PC2);
/* Funo que subtrai dois nmeros complexos */
PtComplexo Multiplicar_Complexos (PtComplexo PC1, PtComplexo PC2);
/* Funo que multiplica dois nmeros complexos */
PtComplexo Dividir_Complexos (PtComplexo PC1, PtComplexo PC2);
/* Funo que divide dois nmeros complexos */
void Apagar_Complexo (PtComplexo *PC);
/* Funo que apaga o nmero complexo e devolve o ponteiro a NULL */
int Complexo_Nulo (PtComplexo PC);
/* Funo que testa se o nmero complexo nulo (0+j0). Devolve 0 em
caso afirmativo e 1 em caso contrrio */
double Parte_Real (PtComplexo PC);
/* Funo que devolve a parte real de um nmero complexo */
double Parte_Imaginaria (PtComplexo PC);
/* Funo que devolve a parte imaginria de um nmero complexo */
#endif

Figura 5.5 - Ficheiro de interface do mdulo de nmeros complexos.

A Figura 5.6 e a Figura 5.7 apresentam o ficheiro de implementao do mdulo. A Figura


5.8 apresenta um exemplo da utilizao do mdulo para simular uma mquina de calcular
de nmeros complexos. O programa comea por criar dois nmeros complexos, Comp1 e
Comp2, com o valor nulo. Depois tem um funcionamento repetitivo, que consiste em
escrever o menu de operaes no monitor e executar a operao escolhida pelo utilizador.
A mquina de calcular permite ler do teclado valores para os dois operadores complexos
Comp1 e Comp2 e executar as quatro operaes bsicas aritmticas. A operao de diviso
s feita se o divisor que Comp2 no for nulo. Quando qualquer uma das quatro
operaes aritmticas realizada, o resultado armazenado no nmero complexo
Resultado. Este complexo depois impresso no monitor e apagado. Aps o utilizador
visualizar o resultado da operao e premir uma tecla, o monitor limpo e o menu de
operaes reescrito. Quando o utilizador escolher a opo de fim de execuo do
programa, os dois nmeros complexos Comp1 e Comp2 so apagados.

CAPTULO 5 : MEMRIAS

/* Ficheiro de implementao do mdulo de nmeros complexos */


/* Nome: complexo.c - Primeira Parte */
#include <stdio.h>
#include <stdlib.h>
#include "complexo.h"

/* Ficheiro de interface do mdulo */

struct complexo
{
double Real; double Imag;
};
/* Funo que cria e inicializa um complexo na forma R+jI */
PtComplexo Inicializar_Complexo (double R, double I)
{
PtComplexo PC = (PtComplexo) malloc (sizeof (struct complexo));
PC->Real = R;

PC->Imag = I;

return PC;
}
/* Funo que l do teclado um complexo na forma R+jI */
void Ler_Complexo (PtComplexo PC)
{
if (PC == NULL) return;
printf ("Parte Real "); scanf ("%lf", &PC->Real);
printf ("Parte Imaginria "); scanf ("%lf", &PC->Imag);
}
/* Funo que escreve no monitor um complexo na forma R+jI */
void Escrever_Complexo (PtComplexo PC)
{
if (PC != NULL) printf ("%f +j %f\n", PC->Real, PC->Imag);
}
/* Funo que soma dois nmeros complexos */
PtComplexo Somar_Complexos (PtComplexo PC1, PtComplexo PC2)
{
PtComplexo PC = (PtComplexo) malloc (sizeof (struct complexo));
PC->Real = PC1->Real + PC2->Real;
PC->Imag = PC1->Imag + PC2->Imag;
return PC;
}
/* Funo que subtrai dois nmeros complexos */
PtComplexo Subtrair_Complexos (PtComplexo PC1, PtComplexo PC2)
{
PtComplexo PC = (PtComplexo) malloc (sizeof (struct complexo));
PC->Real = PC1->Real - PC2->Real;
PC->Imag = PC1->Imag - PC2->Imag;
return PC;
}

Figura 5.6 - Ficheiro de implementao do mdulo de nmeros complexos (1 parte).

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

10

/* Ficheiro de implementao do mdulo de nmeros complexos */


/* Nome: complexo.c - Segunda Parte */
/* Funo que multiplica dois nmeros complexos */
PtComplexo Multiplicar_Complexos (PtComplexo PC1, PtComplexo PC2)
{
PtComplexo PC = (PtComplexo) malloc (sizeof (struct complexo));
PC->Real = PC1->Real * PC2->Real - PC1->Imag * PC2->Imag;
PC->Imag = PC1->Real * PC2->Imag + PC1->Imag * PC2->Real;
return PC;
}
/* Funo que divide dois nmeros complexos */
PtComplexo Dividir_Complexos (PtComplexo PC1, PtComplexo PC2)
{
double QUO = PC2->Real * PC2->Real + PC2->Imag * PC2->Imag;
PtComplexo PC = (PtComplexo) malloc (sizeof (struct complexo));
PC->Real = (PC1->Real * PC2->Real + PC1->Imag * PC2->Imag) / QUO;
PC->Imag = (PC1->Imag * PC2->Real - PC1->Real * PC2->Imag) / QUO;
return PC;
}
/* Funo que apaga o nmero complexo e devolve o ponteiro a NULL */
void Apagar_Complexo (PtComplexo *PC)
{
PtComplexo TPC = *PC;
if (TPC == NULL) return;
*PC = NULL;
free (TPC);
}
/* Funo que devolve 0 se o nmero complexo nulo (0+j0) ou 1 no
caso contrrio */
int Complexo_Nulo (PtComplexo PC)
{
if (PC->Real == 0 && PC->Imag == 0) return 0;
else return 1;
}
/* Funo que devolve a parte real de um nmero complexo */
double Parte_Real (PtComplexo PC)
{
return PC->Real;
}

/* Funo que devolve a parte imaginria de um nmero complexo */


double Parte_Imaginaria (PtComplexo PC)
{
return PC->Imag;
}

Figura 5.7 - Ficheiro de implementao do mdulo de nmeros complexos (2 parte).

11

CAPTULO 5 : MEMRIAS

/* Mquina de calcular de nmeros complexos */


#include <stdio.h>
#include "complexo.h"
int main (void)
{
PtComplexo Comp1, Comp2, Resultado;

int Opcao, Oper;

Comp1 = Inicializar_Complexo (0.0, 0.0);


Comp2 = Inicializar_Complexo (0.0, 0.0);
do
{
system ("clear");
printf ("\t1 - Ler o primeiro complexo\n");
printf ("\t2 - Ler o segundo complexo\n");
printf ("\t3 - Somar os complexos\n");
printf ("\t4 - Subtrair os complexos\n");
printf ("\t5 - Multiplicar os complexos\n");
printf ("\t6 - Dividir os complexos\n");
printf ("\t7 - Sair do programa\n");
do
{
printf ("\n\tOpo -> "); scanf ("%d", &Opcao);
scanf ("%*[^\n]"); scanf ("%*c");
} while (Opcao<1 && Opcao>7);
Oper = 0;
switch (Opcao)
{
case 1 : printf("\n\n"); Comp1 = Ler_Complexo(); break;
case 2 : printf("\n\n"); Comp2 = Ler_Complexo(); break;
case 3 : Resultado = Somar_Complexos(Comp1, Comp2);
printf ("Adio dos complexos -> ");
Escrever_Complexo (Resultado); Oper = 1; break;
case 4 : Resultado = Subtrair_Complexos(Comp1, Comp2);
printf ("Subtraco dos complexos -> ");
Escrever_Complexo (Resultado); Oper = 1; break;
case 5 : Resultado = Multiplicar_Complexos(Comp1, Comp2);
printf ("Multiplicao dos complexos -> ");
Escrever_Complexo (Resultado); Oper = 1; break;
case 6 : if ( Complexo_Nulo (Comp2) )
{
Resultado = Dividir_Complexos(Comp1, Comp2);
printf ("Diviso dos complexos -> ");
Escrever_Complexo (Resultado); Oper = 1;
}
else printf ("O divisor o complexo nulo!!!\n");
break;
}
if (Opcao != 7)
{
printf ("\nPrima uma tecla para continuar\n"); scanf ("%*c");
}
if (Oper) Apagar_Complexo (&Resultado);
} while (Opcao != 7);
Apagar_Complexo (&Comp1);
Apagar_Complexo (&Comp2);
return 0;
}

Figura 5.8 - Mquina de calcular de nmeros complexos.

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

12

5.4 Tipos de memrias e suas caractersticas


Vamos apresentar as caractersticas dos seguintes quatro tipos de memrias: a Memria de
Acesso Aleatrio (RAM ); a Memria Fila (Queue/FIFO ); a Memria Pilha
(Stack/LIFO ); e a Memria Associativa (CAM ).

5.4.1 Memria de acesso aleatrio (RAM )


Uma memria de acesso aleatrio (Random Access Memory) uma memria em que no
existe nenhuma organizao especial de acesso aos elementos armazenados, antes pelo
contrrio, possvel aceder em qualquer instante a qualquer posio da memria, indicando
para o efeito o endereo, ou seja, o ndice da posio de memria a que se pretende aceder.
Estamos perante um acesso indexado, pelo que, uma memria de acesso aleatrio s pode
ser implementada por uma estrutura de dados que permita este tipo de acesso. A Figura 5.9
apresenta o ficheiro de interface de uma memria de acesso aleatrio abstracta.
/******************** Interface do Mdulo RAM ********************/
#ifndef _RAM
#define _RAM
/******************** Definio de Constantes ********************/
#define
#define
#define
#define
#define
#define
#define
#define

OK
NULL_PTR
NULL_SIZE
NO_MEM
RAM_EMPTY
RAM_FULL
RAM_EXISTS
NO_RAM

0
1
2
3
4
5
6
7

/*
/*
/*
/*
/*
/*
/*
/*

operao realizada com sucesso */


ponteiro nulo */
tamanho nulo */
memria esgotada */
RAM vazia */
RAM cheia */
j foi instanciada uma RAM */
ainda no foi instanciada qualquer RAM */

/********************* Prottipos das Funes *********************/


int RAM_Create (unsigned int sz);
/* Concretiza a RAM para elementos de sz bytes. Valores de retorno:
OK, NULL_SIZE ou RAM_EXISTS. */
int RAM_Destroy (void);
/* Destri a RAM. Valores de retorno: OK ou NO_RAM. */
int RAM_Write (void *pelemento, unsigned int pos);
/* Escreve o contedo do elemento apontado por pelemento na posio
pos da RAM. Valores de retorno: OK, NO_RAM, NULL_PTR, RAM_FULL ou
NO_MEM. */
int RAM_Read (void *pelemento, unsigned int pos);
/* L o contedo do elemento da posio pos da RAM para o elemento
apontado por pelemento. Valores de retorno: OK, NO_RAM, NULL_PTR ou
RAM_EMPTY. */
int RAM_Search (void *pelemento, int *pos);
/* Procura o primeiro elemento da RAM com contedo igual ao elemento
apontado por pelemento. Coloca em pos o ndice do elemento
encontrado ou -1 caso no exista tal elemento. Valores de retorno:
OK, NO_RAM, NULL_PTR ou RAM_EMPTY. */
int RAM_Sort (void);
/* Ordena a RAM. Valores de retorno: OK, NO_RAM, ou RAM_EMPTY. */
#endif

Figura 5.9 - Ficheiro de interface da memria de acesso aleatrio abstracta.

13

CAPTULO 5 : MEMRIAS

Sendo a caracterstica principal da memria de acesso aleatrio o acesso indexado, o


posicionamento para a operao de leitura, que vamos designar por RAM_Read, ou para a
operao de escrita, que vamos designar por RAM_Write, feito atravs do ndice do
elemento de memria onde se pretende ler ou escrever. Para alm das operaes de leitura
e de escrita, normalmente tambm necessrio procurar um elemento com um
determinado valor, que vamos designar por RAM_Search e ordenar a memria, que
vamos designar por RAM_Sort. A ordenao da memria facilita a pesquisa de
informao, permitindo por exemplo, utilizar a pesquisa binria em alternativa pesquisa
sequencial.
As operaes de leitura e de escrita neste tipo de memria no afectam o seu tamanho. A
memria de acesso aleatrio tem sempre o tamanho que foi decidido na altura da sua
definio, o que permite aceder a qualquer um dos seus elementos para ler a informao
armazenada ou para escrever nova informao. No entanto, conveniente no tentar ler
informao de elementos onde ainda no foi feita qualquer operao de escrita. Logo,
aconselhvel implementar uma poltica de escrita em elementos sucessivos da memria e
manter um indicador de posio que indique qual o ndice do ltimo elemento da memria
que contem informao til.
As operaes de criao RAM_Create e de destruio RAM_Destroy s existem para
implementaes abstractas e tm como funo respectivamente, concretizar a memria
para o tipo de elementos pretendidos e repor a situao inicial de memria ainda por
concretizar de maneira a poder ser reutilizada, eventualmente para um novo tipo de dados.

5.4.2 Memria fila (Queue/FIFO )


Uma memria fila, do ingls queue, uma memria em que s possvel processar a
informao pela ordem de chegada. Da que, tambm seja apelidada de memria do
primeiro a chegar primeiro a sair, do ingls First In First Out. A Figura 5.10 apresenta o
ficheiro de interface de uma memria fila abstracta.
Numa fila, o posicionamento para a colocao de um novo elemento, que vamos designar
por FIFO_In, a cauda da fila (fifo tail ), e o posicionamento para a remoo de um
elemento, que vamos designar por FIFO_Out, a cabea da fila (fifo head ). A colocao
de um novo elemento consiste na adio de um novo elemento no fim da fila, ficando este
novo elemento a ser a cauda da fila, e na escrita da informao nesse elemento. A remoo
de um elemento consiste na leitura da informao armazenada no elemento que se
encontra na cabea da fila e da eliminao desse elemento da fila. Consequentemente, o
elemento seguinte, caso o haja, passa a ser a cabea da fila. Quando retirado o ltimo
elemento, a fila fica vazia, sendo apenas possvel efectuar a operao de colocao de
elementos na fila. A situao inversa de fila cheia tambm pode acontecer para certo tipos
de implementaes, mais concretamente para implementaes estticas e semiestticas.
Como o acesso memria fila est limitado aos elementos posicionados nos extremos da
memria, a fila mantm dois indicadores de posio para a cabea e a cauda da fila. Ao
contrrio da memria de acesso aleatrio, o tamanho da fila dinmico e depende do
nmero de elementos colocados e do nmero de elementos retirados da fila.
As operaes de criao FIFO_Create e de destruio FIFO_Destroy s existem para
implementaes abstractas e tm como funo respectivamente, concretizar a fila para o
tipo de elementos pretendidos e repor a situao inicial de fila ainda por concretizar de
maneira a poder ser reutilizada, eventualmente para um novo tipo de dados.

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

14

/******************** Interface do Mdulo FIFO ********************/


#ifndef _FIFO
#define _FIFO
/******************** Definio de Constantes ********************/
#define
#define
#define
#define
#define
#define
#define
#define

OK
NULL_PTR
NULL_SIZE
NO_MEM
FIFO_EMPTY
FIFO_FULL
FIFO_EXISTS
NO_FIFO

0
1
2
3
4
5
6
7

/*
/*
/*
/*
/*
/*
/*
/*

operao realizada com sucesso */


ponteiro nulo */
tamanho nulo */
memria esgotada */
fila vazia */
fila cheia */
j foi instanciada uma fila */
ainda no foi instanciada qualquer fila */

/********************* Prottipos das Funes *********************/


int FIFO_Create (unsigned int sz);
/* Concretiza a fila para elementos de sz bytes. Valores de retorno:
OK, NULL_SIZE ou FIFO_EXISTS. */
int FIFO_Destroy (void);
/* Destri a fila. Valores de retorno: OK ou NO_FIFO. */
int FIFO_In (void *pelemento);
/* Coloca o elemento apontado por pelemento na cauda da fila.
Valores de retorno: OK, NO_FIFO, NULL_PTR, FIFO_FULL ou NO_MEM. */
int FIFO_Out (void *pelemento);
/* Retira o elemento da cabea da fila para o elemento apontado por
pelemento. Valores de retorno: OK, NO_FIFO, NULL_PTR ou FIFO_EMPTY.
*/
#endif

Figura 5.10 - Ficheiro de interface da memria fila abstracta.

5.4.3 Memria pilha (Stack/LIFO )


Uma memria pilha, do ingls stack, uma memria em que s possvel processar a
informao pela ordem inversa ordem de chegada. Da que, tambm seja apelidada de
memria do ltimo a chegar primeiro a sair, do ingls Last In First Out. A Figura 5.11
apresenta o ficheiro de interface de uma memria pilha abstracta.
Numa pilha, o posicionamento para a colocao de um novo elemento, que se designa por

STACK_Push, e o posicionamento para a remoo de um elemento, que se designa por


STACK_Pop, o topo da pilha (top of the stack). A colocao de um novo elemento
consiste na adio de um novo elemento, em cima do topo da pilha e na escrita da
informao nesse elemento, ficando este novo elemento a ser o topo da pilha. A remoo
de um elemento consiste na leitura da informao armazenada no elemento que se
encontra no topo da pilha e da eliminao desse elemento da pilha, ficando o elemento
anterior, caso o haja, a ser o topo da pilha. Quando retirado o ltimo elemento, a pilha
fica vazia, sendo apenas possvel efectuar a operao de colocao de elementos na pilha. A
situao inversa de pilha cheia tambm pode acontecer para certo tipos de implementaes,
mais concretamente para implementaes estticas e semiestticas.
Como o acesso memria pilha est limitado a este elemento posicionado no extremo da
memria, a pilha mantm um indicador de posio para o topo da pilha. Tal como na fila, o
tamanho da pilha tambm dinmico.

15

CAPTULO 5 : MEMRIAS

As operaes de criao STACK_Create e de destruio STACK_Destroy s existem


para implementaes abstractas e tm como funo respectivamente, concretizar a pilha
para o tipo de elementos pretendidos e repor a situao inicial de pilha ainda por
concretizar de maneira a poder ser reutilizada, eventualmente para um novo tipo de dados.
/******************* Interface do Mdulo STACK *******************/
#ifndef _STACK
#define _STACK
/******************** Definio de Constantes ********************/
#define
#define
#define
#define
#define
#define
#define
#define

OK
NULL_PTR
NULL_SIZE
NO_MEM
STACK_EMPTY
STACK_FULL
STACK_EXISTS
NO_STACK

0
1
2
3
4
5
6
7

/*
/*
/*
/*
/*
/*
/*
/*

operao realizada com sucesso */


ponteiro nulo */
tamanho nulo */
memria esgotada */
pilha vazia */
pilha cheia */
j foi instanciada uma pilha */
ainda no foi instanciada qualquer pilha */

/********************* Prottipos das Funes *********************/


int STACK_Create (unsigned int sz);
/* Concretiza a pilha para elementos de
retorno: OK, NULL_SIZE ou STACK_EXISTS. */

sz

bytes.

Valores

de

int STACK_Destroy (void);


/* Destri a pilha. Valores de retorno: OK ou NO_STACK. */
int STACK_Push (void *pelemento);
/* Coloca o elemento apontado por pelemento no topo da pilha.
Valores de retorno: OK, NO_STACK, NULL_PTR, STACK_FULL ou NO_MEM. */
int STACK_Pop (void *pelemento);
/* Retira o elemento do topo da pilha para o elemento apontado por
NULL_PTR
ou
pelemento.
Valores
de
retorno:
OK,
NO_STACK,
STACK_EMPTY. */
#endif

Figura 5.11 - Ficheiro de interface da memria pilha abstracta.

5.4.4 Memria associativa (CAM )


Uma memria associativa (Content Access Memory) uma memria em que o acesso aos seus
elementos, feita pelo contedo a que se pretende aceder, indicando para o efeito a chave
do elemento a que se pretende aceder. Estamos perante um acesso por chave, pelo que,
uma memria associativa s pode ser implementada por uma estrutura de dados em que a
informao armazenada est sempre ordenada. A Figura 5.12 apresenta o ficheiro de
interface de uma memria associativa abstracta.
Numa memria associativa, o posicionamento para a colocao de um novo elemento, que
vamos designar por CAM_In, e o posicionamento para a remoo de um elemento, que
vamos designar por CAM_Out, feito atravs da chave do elemento que se pretende
processar. A colocao de um novo elemento consiste na adio de um novo elemento na
posio correspondente chave de acesso do novo elemento, de maneira a manter a
memria ordenada e na escrita da informao nesse elemento. A remoo de um elemento
consiste na leitura da informao armazenada no primeiro elemento com a chave
pretendida e da eliminao desse elemento da memria. Tal como nas memrias fila e
pilha, o tamanho da memria associativa tambm dinmico.

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

16

Como a memria tem que estar sempre ordenada pela chave de pesquisa, as operaes de
colocao e de remoo de elementos podem implicar deslocamentos dos elementos da
memria para permitir a colocao do elemento na memria ou para compensar a
eliminao do elemento da memria. O facto da memria estar sempre ordenada, permite
tambm implementar de forma eficiente operaes de leitura de informao, que so
normalmente muito importantes neste tipo de memria. Essas operaes so a leitura do
primeiro elemento da memria que tem uma determinada chave, que vamos designar por
CAM_Read_First, e a leitura de elementos sucessivos com a mesma chave, que vamos
designar por CAM_Read_Next.
/******************** Interface do Mdulo CAM ********************/
#ifndef _CAM
#define _CAM
/******************** Definio de Constantes ********************/
#define
#define
#define
#define
#define
#define
#define
#define
#define
#define

OK
NULL_PTR
NULL_SIZE
NO_MEM
CAM_EMPTY
CAM_FULL
CAM_EXISTS
NO_CAM
NO_KEY
NO_FUNC

0
1
2
3
4
5
6
7
8
9

/*
/*
/*
/*
/*
/*
/*
/*
/*
/*

operao realizada com sucesso */


ponteiro nulo */
tamanho nulo */
memria esgotada */
CAM vazia */
CAM cheia */
j foi instanciada uma CAM */
ainda no foi instanciada qualquer CAM */
no existe elemento com a chave indicada */
no foi comunicada a funo de comparao */

/********************* Prottipos das Funes *********************/


int CAM_Create (unsigned int sz, CompFunc cmpf);
/* Concretiza a CAM para elementos de sz bytes e indica a funo de
comparao cmpf, que permite manter a memria ordenada. Valores de
retorno: OK, NULL_SIZE, CAM_EXISTS ou NULL_PTR. */
int CAM_Destroy (void);
/* Destri a CAM. Valores de retorno: OK ou NO_CAM. */
int CAM_In (void *pelemento);
/* Coloca o elemento apontado por pelemento na posio da CAM com a
chave indicada. Permite-se a existncia na CAM de elementos
distintos com a mesma chave. Elementos com a mesma chave so
armazenados por ordem cronolgica da colocao na CAM. Valores de
retorno: OK, NO_CAM, NULL_PTR, CAM_FULL, NO_MEM ou NO_FUNC. */
int CAM_Out (void *pelemento);
/* Retira o elemento da CAM com a chave indicada para o elemento
apontado por pelemento. Havendo mais do que um elemento com a chave
indicada ser retirado o primeiro que foi introduzido. Valores de
retorno: OK, NO_CAM, NULL_PTR, CAM_EMPTY, NO_KEY ou NO_FUNC. */
int CAM_Read_First (void *pelemento);
/* L o contedo do primeiro elemento da CAM que contm a chave
indicada para o elemento apontado por pelemento. Valores de retorno:
OK, NO_CAM, NULL_PTR, CAM_EMPTY, NO_KEY ou NO_FUNC. */
int CAM_Read_Next (void *pelemento);
/* L o contedo do elemento seguinte da CAM que contm a chave
indicada para o elemento apontado por pelemento. Os elementos so
lidos sucessivamente a partir do primeiro. Valores de retorno: OK,
NO_CAM, NULL_PTR, CAM_EMPTY, NO_KEY ou NO_FUNC. */
#endif

Figura 5.12 - Ficheiro de interface da memria associativa abstracta.

17

CAPTULO 5 : MEMRIAS

As operaes de criao CAM_Create e de destruio CAM_Destroy s existem para


implementaes abstractas e tm como funo respectivamente, concretizar a memria
para o tipo de elementos pretendidos e indicar a funo de comparao dos elementos, e
repor a situao inicial de memria ainda por concretizar de maneira a poder ser reutilizada,
eventualmente para um novo tipo de dados.

5.5 Tipos de implementao


Existem trs tipos de implementao de memrias:
x Numa implementao esttica a memria definida partida e fixa. A sua dimenso,
ou seja o nmero de elementos que pode armazenar, previamente conhecida e no pode
ser alterada. As implementaes estticas so baseadas em agregados de elementos. Os
elementos so de tipos de dados simples ou estruturas, e so definidos partida, pelo que,
s podem armazenar dados desse tipo. Independentemente da utilizao da memria, ou
seja, do nmero de elementos que contm de facto informao, a ocupao de memria do
computador sempre a mesma. A implementao esttica a mais simples, mas, a menos
verstil, uma vez que a memria tem uma dimenso inaltervel e no pode ser
reconfigurada em termos do tipo dos elementos que armazena, durante a execuo do
programa.
x Numa implementao semiesttica a dimenso da memria definida partida e fixa.
Tal como na implementao esttica, a sua dimenso previamente conhecida e no pode
ser alterada. As implementaes semiestticas so baseadas em agregados de ponteiros para
elementos, que so atribudos dinamicamente. Os elementos so criados por atribuio de
memria dinmica quando a informao colocada na memria, e so eliminados quando
a informao retirada da memria, por libertao da memria dinmica. A ocupao de
memria do computador tem uma componente constante, que o agregado de ponteiros
que serve de estrutura bsica de suporte e, uma componente varivel que depende do
nmero de elementos que contm de facto informao. A memria tem uma organizao
mais complexa que a implementao esttica. Mas mais verstil, porque com a utilizao
de ponteiros para void possvel criar memrias de tipos de dados indefinidos, ou seja,
tipos de dados que podem ser concretizados na altura da criao da memria. Este tipo de
realizao designa-se por memria de dados abstractos. Mas, a memria continua a ter uma
dimenso inaltervel, devido estrutura de dados de suporte ser esttica.
x Numa implementao dinmica, a dimenso da memria inicialmente nula e depois vai
crescendo medida das necessidades. As implementaes dinmicas so baseadas em
estruturas ligadas, de tipo lista ligada, lista biligada ou rvore binria. Estas estruturas so
constitudas por ns ou elos interligados entre si, que so criados ou eliminados na
memria dinmica medida que a memria vai respectivamente crescendo ou decrescendo.
Os elementos que contm a informao a armazenar na memria, esto dependurados nos
ns, atravs de um ponteiro para o elemento. Os elementos tambm so criados ou
eliminados dinamicamente quando respectivamente se coloca ou retira a informao da
memria. A ocupao de memria do computador depende do nmero de elementos
efectivamente armazenados na memria. A memria tem uma organizao mais complexa
que a implementao semiesttica, mas, a implementao mais verstil, porque permite a
realizao de uma memria de dados abstractos e mais importante ainda, porque a sua
dimenso no est limitada.

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

18

5.6 Memria de acesso aleatrio (RAM )


Como a memria de acesso aleatrio uma memria de acesso indexado, ento
implementada atravs de um estrutura de dados de tipo agregado. A implementao
esttica baseada num agregado de elementos do tipo que se pretende armazenar, como se
apresenta na Figura 5.13.
As funes de leitura e de escrita tm um parmetro de entrada que o ndice do elemento
de memria que vai ser processado. Uma vez que a memria permite o acesso a qualquer
um dos seus elementos, para procurar um elemento especfico, a funo de pesquisa tanto
pode usar a pesquisa sequencial como a pesquisa binria, caso a memria esteja ordenada.
Para ordenar a memria pode ser usado qualquer algoritmo de ordenao.
RAM[0]

RAM[1]

RAM[2]

...

RAM[I]

...

RAM[N-1]

Elemento Elemento Elemento Elemento Elemento Elemento Elemento

ltimo elemento

RAM_Read (I)
RAM_Write (I)

RAM esttica

Figura 5.13 - Implementao esttica da memria de acesso aleatrio.

A implementao semiesttica baseada num agregado de ponteiros, como se apresenta na


Figura 5.14. A operao de escrita de informao responsvel pela atribuio de memria
para o elemento. Portanto, as operaes de leitura, de pesquisa e de ordenao s podem
aceder aos elementos existentes de facto na memria, pelo que, muito importante
disponibilizar um contador que indica o nmero de elementos teis existentes na memria.
Este contador comporta-se assim como um ponteiro indicador do ltimo elemento da
memria onde foi escrita informao. A actualizao deste contador fica responsabilidade
da operao de escrita, que deve utilizar uma poltica de escrita em elementos sucessivos do
agregado para que no haja elementos sem informao na parte utilizada do agregado.
ltimo elemento
RAM[0]

RAM[1]

RAM[2]

RAM semiesttica
...

RAM[I]

...

RAM[N-1]

Ponteiro Ponteiro Ponteiro Ponteiro Ponteiro Ponteiro Ponteiro

Elemento Elemento Elemento

Figura 5.14 - Implementao semiesttica da memria de acesso aleatrio.

No caso da implementao semiesttica possvel criar uma memria de acesso aleatrio


abstracta. Para isso, necessrio providenciar a funo de criao da memria
RAM_Create que concretiza o tipo de elementos, atravs da especificao do seu tamanho
em bytes, e a funo de destruio da memria RAM_Destroy que alm de libertar a
memria dinmica atribuda para os elementos, coloca o indicador de tamanho dos
elementos de novo a zero e o contador de elementos teis a zero. Ou seja, coloca o
indicador do ltimo elemento til da memria no incio do agregado.

19

CAPTULO 5 : MEMRIAS

Para implementar uma memria de acesso aleatrio dinmica seria preciso recorrer a uma
lista ligada de elementos. S que depois a memria s poderia ser acedida sequencialmente
a partir do elemento inicial e perder-se-ia o acesso indexado, pelo que, a memria deixaria
ento de ter as caractersticas de acesso aleatrio. Portanto, no existe implementao
dinmica da memria de acesso aleatrio.

5.7 Memria fila (Queue/FIFO )


A Figura 5.15 apresenta a implementao esttica da memria fila, que baseada num
agregado de elementos do tipo que se pretende armazenar, usado de forma circular. A fila
tem dois indicadores numricos que indicam os ndices dos elementos que so, em cada
instante, a cabea da fila e a cauda da fila. Por uma questo de implementao, a cauda da
fila indica a primeira posio livre para a prxima operao de colocao de um elemento.
Sempre que se coloca um elemento na fila o indicador de cauda da fila deslocado para o
elemento seguinte do agregado. Sempre que se retira um elemento da fila o indicador de
cabea da fila deslocado para o elemento seguinte do agregado.
Esta implementao no a forma habitual de funcionamento de um fila, onde sempre que
o elemento da cabea da fila sai da fila, toda a fila deslocada para a frente. Mas, para evitar
deslocamentos de elementos no agregado, a cabea da fila que se desloca para o elemento
seguinte. Quando ao colocar um novo elemento na fila, a cauda da fila se sobrepor cabea
da fila, ento sinal que a fila est cheia. Quando ao retirar um elemento da fila, a cabea
da fila se sobrepor cauda da fila, ento sinal que a fila ficou vazia.

FIFO[0]

FIFO[1]

FIFO[2]

...

FIFO[I]

...

FIFO[N-1]

Elemento Elemento Elemento Elemento Elemento Elemento Elemento

FILA esttica

cabea
da fila

cauda
da fila

FIFO_Out

FIFO_In

Figura 5.15 - Implementao esttica da memria fila.

A implementao semiesttica, que se apresenta na Figura 5.16, baseada num agregado de


ponteiros usado de forma circular e comporta-se da mesma forma que a implementao
esttica, mas, com as seguintes diferenas. A operao de colocao de um elemento na fila
responsvel pela atribuio de memria para o elemento e a operao de remoo de um
elemento da fila responsvel pela libertao da memria ocupada pelo elemento.

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

20

cabea da fila

FIFO[0]

FIFO[1]

FIFO[2]

cauda da fila

...

FIFO[I]

...

FIFO[N-1]

Ponteiro Ponteiro Ponteiro Ponteiro Ponteiro Ponteiro Ponteiro

Elemento Elemento Elemento

FILA semiesttica

FIFO_Out

FIFO_In

Figura 5.16 - Implementao semiesttica da memria fila.

A implementao dinmica, que se apresenta na Figura 5.17, baseada numa lista ligada de
elementos. Uma lista ligada uma estrutura constituda por elementos, a que vamos chamar
ns, ligados atravs de ponteiros. Cada n da lista ligada constitudo por dois ponteiros.
Uma para o elemento que armazena a informao e outro para o n seguinte da lista.
Repare que o ltimo n da lista aponta para NULL, para servir de indicador de finalizao
da fila. A memria para os ns e para os elementos da lista atribuda, quando um
elemento colocado na fila e libertada quando um elemento retirado da fila. Os
indicadores de cabea e cauda da fila so ponteiros. A cabea da fila aponta sempre para o
elemento mais antigo que se encontra na fila e que o primeiro a ser retirado. A cauda da
fila aponta sempre para o elemento mais recente que se encontra na fila e frente do qual
um novo elemento colocado. Quando so ambos ponteiros nulos, sinal que a fila est
vazia. Uma fila dinmica nunca est cheia. Quando muito, pode no existir memria para
continuar a acrescentar-lhe mais elementos.
cabea
da fila

PtSeg

PtSeg

PtSeg

PtSeg

PtEle

PtEle

PtEle

PtEle

Elemento

Elemento

Elemento

Elemento

FIFO_Out

FILA dinmica

cauda
da fila

FIFO_In

Figura 5.17 - Implementao dinmica da memria fila.

No caso das implementaes semiesttica e dinmica possvel criar uma fila abstracta.
Para isso, necessrio providenciar a funo de criao da fila FIFO_Create que
concretiza o tipo de elementos, atravs da especificao do seu tamanho em bytes, e a
funo de destruio da fila FIFO_Destroy que alm de libertar toda a memria dinmica
atribuda, coloca o indicador de tamanho dos elementos de novo a zero e recoloca os
indicadores de cabea e cauda da fila nas condies iniciais.

21

CAPTULO 5 : MEMRIAS

5.8 Memria pilha (Stack/LIFO )


Tal como a memria fila, a memria pilha tem as implementaes esttica e semiesttica,
que se apresentam na Figura 5.18, baseadas em agregados. A pilha tem um indicador
numrico que indica o ndice do elemento que o topo da pilha. Por uma questo de
implementao, o topo da pilha indica a primeira posio livre para a prxima operao de
colocao de um elemento. Sempre que se coloca um elemento na pilha o indicador de
topo da pilha deslocado para o elemento seguinte do agregado. Quando o topo da pilha
atinge o elemento a seguir ao fim do agregado, ou seja, o ndice N, ento sinal que a pilha
est cheia. Sempre que se retira um elemento da pilha, primeiro o indicador de topo da
pilha deslocado para o elemento anterior do agregado e depois o elemento retirado da
pilha. Quando o indicador de topo da pilha atinge o elemento inicial do agregado, ou seja, o
ndice 0, ento sinal que a pilha ficou vazia.
A implementao semiesttica comporta-se da mesma forma que a implementao esttica,
mas, com as seguintes diferenas. A operao de colocao de um elemento na pilha
responsvel pela atribuio de memria para o elemento e a operao de remoo de um
elemento da pilha responsvel pela libertao da memria ocupada pelo elemento.
PILHA esttica

STACK_Push
STACK_Pop

topo da
pilha

Ponteiro
Elemento STACK[N-1]
Elemento

...

Ponteiro

Elemento

STACK[I]

Ponteiro

Elemento

...

Ponteiro

Elemento

STACK[2]

Ponteiro

Elemento

STACK[1]

Ponteiro

Elemento

STACK[0]

Ponteiro

PILHA semiesttica

topo da
pilha

STACK_Push

Elemento

STACK_Pop

Elemento
Elemento

Figura 5.18 - Implementaes esttica e semiesttica da memria pilha.

A implementao dinmica de uma pilha, que se apresenta na Figura 5.19, baseada numa
lista ligada de elementos. Mas, enquanto que na fila cada n da lista ligada aponta para o n
seguinte, na pilha cada n aponta para o n anterior, sendo que o primeiro n da lista
aponta para NULL, para servir de indicador de finalizao da pilha. A memria para os ns
e para os elementos da lista atribuda, quando um elemento colocado na pilha e
libertada quando um elemento retirado da pilha. O indicador de topo da pilha um
ponteiro. O topo da pilha aponta sempre para o elemento mais recente que se encontra na
pilha, que o primeiro elemento a ser retirado e frente do qual um novo elemento
colocado. Quando o topo da pilha um ponteiro nulo, sinal que a pilha est vazia. Uma
pilha dinmica nunca est cheia. Quando muito, pode no existir memria para continuar a
acrescentar-lhe mais elementos.

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

22

PILHA dinmica
topo da
pilha

STACK_Push
PtEle

Elemento

STACK_Pop

PtAnt

PtEle

Elemento

PtAnt

PtEle

Elemento

PtAnt

Figura 5.19- Implementao dinmica da memria pilha.

No caso das implementaes semiesttica e dinmica possvel criar uma pilha abstracta.
Para isso, necessrio providenciar a funo de criao da pilha STACK_Create que
concretiza o tipo de elementos, atravs da especificao do seu tamanho em bytes, e a
funo de destruio da pilha STACK_Destroy que alm de libertar a memria dinmica
atribuda, coloca o indicador de tamanho dos elementos de novo a zero e recoloca o
indicador de topo da pilha na condio inicial.

5.9 Memria associativa (CAM )


As implementaes esttica e semiesttica da memria associativa, que se apresentam na
Figura 5.20, so baseadas em agregados. Quando se pretende colocar um elemento na
memria necessrio pesquisar a memria para encontrar a posio de colocao do
elemento, que depende da sua chave e dos elementos j existentes na memria. Quando se
pretende retirar um elemento da memria tambm necessrio pesquisar a memria para
encontrar a posio onde o elemento se encontra. Para implementar a pesquisa da memria
necessrio que a memria mantenha um contador que indica o nmero de elementos
teis armazenados, ou seja, um indicador do ltimo elemento da memria onde foi escrita
informao. Este contador actualizado pelas operaes de colocao e remoo de
elementos na memria. Por questes de eficincia, tambm conveniente utilizar a
pesquisa binria. Para manter a memria sempre ordenada, colocar ou retirar um elemento
da memria pode implicar deslocar os elementos no agregado.
No caso da implementao semiesttica, estes deslocamentos so deslocamentos de
ponteiros, ou seja, de entidades de 4 bytes, pelo que, so operaes menos custosas que na
implementao esttica. Na implementao semiesttica, as operaes de colocao e
remoo de elementos so ainda responsveis respectivamente, pela atribuio de memria
para o elemento e pela libertao da memria ocupada pelo elemento.

23

CAPTULO 5 : MEMRIAS

CAM[0]

CAM[1]

CAM[2]

CAM[3]

CHAVE 1

CHAVE 2

CHAVE 4

CHAVE 6

pesquisa

binria

...

CAM[N-1]

Elemento Elemento Elemento Elemento Elemento Elemento Elemento

CAM_In (CHAVE)
CAM_Out (CHAVE)

ltimo
elemento

CAM esttica

ltimo elemento
CAM[0]

CAM[1]

CAM[2]

CAM[3]

...

CAM[N-1]

Ponteiro Ponteiro Ponteiro Ponteiro Ponteiro Ponteiro Ponteiro

CHAVE 1

CHAVE 2

CHAVE 4

CHAVE 6

Elemento Elemento Elemento Elemento


pesquisa

binria

CAM_In (CHAVE)
CAM_Out (CHAVE)

CAM semiesttica

Figura 5.20 - Implementaes esttica e semiesttica da memria associativa.

No caso da implementao semiesttica possvel criar uma memria associativa abstracta.


Para isso, necessrio providenciar a funo de criao da memria associativa
CAM_Create que concretiza o tipo de elementos, atravs da especificao do seu tamanho
em bytes, e a funo de destruio da memria associativa CAM_Destroy que alm de
libertar a memria dinmica atribuda para os elementos, coloca o indicador de tamanho
dos elementos de novo a zero e o contador de elementos teis a zero. Ou seja, coloca o
indicador do ltimo elemento til da memria associativa no incio do agregado.
Para obter uma implementao mais eficiente da memria associativa, essencial utilizar
uma estrutura de dados que evite a necessidade de fazer deslocamentos dos elementos,
quando se pretende colocar ou retirar um elemento da memria. Para isso precisamos de
uma estrutura de dados dinmica em que os elementos podem ser colocados e retirados
por ajustamento de ligaes entre os elementos. Mas, no pode ser uma lista ligada, porque
para pesquisar a memria procura de um elemento com uma determinada chave, a
memria tem de ser pesquisada em ambos os sentidos. Pelo que, a lista tem de ser biligada.
Uma lista biligada uma lista em que cada n tem um ponteiro para o n seguinte e um
ponteiro para o n anterior. Sendo que, o n inicial aponta para trs para NULL e o n
final aponta para a frente para NULL, para servirem de indicadores de finalizao da lista.
Colocar ou retirar um elemento numa lista biligada, implica fazer ou desfazer mais ligaes.
Mas, em contrapartida todas as operaes de ligao ou desligao ao n de atrs e ao n
da frente so possveis de fazer, tendo apenas um ponteiro a indicar, o n atrs ou frente
do qual se vai colocar o novo elemento, ou o n do elemento que vai ser eliminado.
A Figura 5.21 apresenta esta implementao dinmica linear, que mantm um ponteiro para
o primeiro n da lista, que se designa por cabea da memria. O inconveniente desta
implementao tem a ver com a eficincia da pesquisa de informao. Numa lista, seja ela
ligada ou biligada, s se pode implementar a pesquisa sequencial, a partir da cabea da lista.

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

cabea
da CAM

PtSeg

PtSeg

PtAnt

PtAnt

PtEle

PtEle

24

PtSeg
PtAnt
PtEle

PtSeg
PtAnt
PtEle

CHAVE 1

CHAVE 2

CHAVE 4

CHAVE 6

Elemento

Elemento

Elemento

Elemento

pesquisa

sequencial

CAM_In (CHAVE)
CAM_Out (CHAVE)

CAM dinmica linear

Figura 5.21 - Implementao dinmica linear da memria associativa.

Para optimizarmos a pesquisa pode-se optar por outra estrutura de dados que implemente
uma pesquisa ainda mais eficiente que a pesquisa binria, mas, que seja ainda uma estrutura
de dados em que os elementos possam ser colocados e retirados sem custos de
deslocamentos de elementos. Tal estrutura de dados, que se apresenta na Figura 5.22,
uma tabela de disperso (hashing table ).
CAM_In (CHAVE)
CAM_Out (CHAVE)
CAM[0] Ponteiro

PtSeg
PtEle

PtSeg
PtEle

1 pesquisa por disperso

CAM[1] Ponteiro

.
.
.

.
.
.

CAM[N-2] Ponteiro
CAM[N-1] Ponteiro

CHAVE 1

CHAVE 41

Elemento

Elemento

2 pesquisa sequencial

PtSeg
PtEle

CHAVE 2

CAM semiesttica/dinmica

Elemento

Figura 5.22 - Implementao da memria associativa com tabela de disperso.

Uma tabela de disperso um agregado, onde os elementos so colocados em posies


determinadas por uma funo de disperso (hashing function ). Uma funo de disperso
uma funo aritmtica que determina a posio de colocao do elemento, usando a chave
do elemento. Portanto, os elementos no so colocados em posies seguidas do agregado,
mas sim em posies que dependem da chave do elemento.
Uma vez que o nmero de posies de armazenamento, ou seja, a dimenso do agregado
tipicamente vrias ordens de grandeza menor do que o nmero total de chaves possveis,
podem ocorrer as situaes de overflow e coliso. A situao de overflow acontece quando a
tabela fica completamente preenchida e como o agregado uma estrutura esttica no tem
soluo, a no ser com um bom dimensionamento do agregado.

25

CAPTULO 5 : MEMRIAS

A situao de coliso acontece quando dadas duas chaves diferentes, a funo de disperso
calcula a mesma a posio de colocao. O que compromete a implementao da memria,
uma vez que, no se pode colocar dois elementos na mesma posio da tabela. Portanto,
tem que se resolver o problema das colises. Uma maneira passa por reduzir o seu nmero,
utilizando uma funo de disperso que assegure uma boa disperso.
Mas, independentemente da funo usada vo existir sempre colises. Portanto, tem de ser
criada uma estrutura de dados que permita colocar mais do que um elemento na mesma
posio da tabela. Uma soluo possvel consiste em criar uma estrutura semiesttica em
que cada elemento do agregado um ponteiro que aponta para uma lista ligada de
elementos. Temos assim uma tabela de listas ligadas.
Esta implementao semiesttica, sob o ponto de vista da estrutura de suporte, mas
dinmica sob o ponto de vista da colocao e remoo dos elementos. Permite resolver o
problema das colises e permite ainda a existncia na memria associativa de elementos
distintos com a mesma chave. Elementos com a mesma chave vo ficar na mesma posio
da tabela e podem ser colocados na lista ligada por ordem cronolgica da sua colocao na
memria associativa, se por exemplo, a lista for implementada como uma fila.
Para pesquisar a existncia de um elemento nesta implementao da memria associativa,
seja para colocar um novo elemento, seja para retirar um elemento j existente, aplica-se a
funo de disperso para a chave do elemento e obtm-se a posio da tabela onde o
elemento deve estar colocado. Depois utiliza-se a pesquisa sequencial para analisar a lista
ligada de elementos at detectar a posio de colocao ou remoo do elemento com a
chave pretendida. Como o nmero de elementos existente na lista ligada pequena, a
pesquisa sequencial aceitvel. Por outro lado, nesta implementao a leitura sucessiva de
elementos com a mesma chave aplicada facilmente.
No entanto, a grande limitao da tabela de disperso tem a ver com o facto da estrutura de
suporte ser um agregado, que uma estrutura de dados esttica. Se a memria associativa
necessitar de crescer esta soluo no a mais adequada. Portanto, necessria uma
estrutura de dados dinmica que permita colocar e retirar elementos de forma eficiente,
como na lista biligada, mas que suporte tambm uma pesquisa eficiente, como o caso da
pesquisa binria. Tal estrutura de dados a rvore binria de pesquisa. A Figura 5.23
apresenta esta implementao dinmica hierrquica.
Uma rvore uma estrutura de dados constituda por uma coleco de ns. Esta coleco
pode ser nula, ou constituda por um n inicial, que se designa por raiz da rvore, e zero ou
mais subrvores. Portanto, uma estrutura de dados com uma organizao recursiva, em
que cada n tambm uma rvore. Uma rvore diz-se binria se cada n no tiver mais do
que duas subrvores, a subrvore da esquerda e a subrvore da direita. Numa rvore binria
de pesquisa, um elemento colocado na rvore de maneira que, os elementos da sua
subrvore da esquerda tm uma chave menor do que a sua chave e os elementos da sua
subrvore da direita tm uma chave maior do que a sua chave.
Para implementar uma rvore binria, cada n da rvore constitudo por trs ponteiros.
Um para o elemento que armazena a informao e os outros dois para os ns seguintes da
rvore, ou seja, para as subrvores da esquerda e da direita. Quando o n seguinte no
existe, ento o respectivo ponteiro aponta para NULL, para servir de indicador de
inexistncia da subrvore.

PtEle

PtEsq

PtDir

CHAVE 4

PtEsq

PtEle

Elemento

PtDir

CHAVE 2

PtEsq

PtEle

PtDir

Elemento

PtEsq

PtEle

PtDir

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

CAM_In (CHAVE)
CAM_Out (CHAVE)

raiz
da CAM

pesquisa binria

Figura 5.23 - Implementao dinmica hierrquica da memria associativa.

CAM dinmica hierrquica

CHAVE 6

PtEsq

PtEle

PtDir

Elemento

PtEsq

PtEle

CHAVE 1

CHAVE 3

CHAVE 8

Elemento

Elemento

Elemento

PtDir

26

27

CAPTULO 5 : MEMRIAS

Uma rvore binria diz-se completa, ou completamente balanceada, quando, para um


nmero de nveis previamente fixado, contm o nmero mximo de ns, pelo que, se
verifica que N = 2K1, em que N o nmero de ns e K o nmero de nveis da rvore. Se
uma rvore estiver balanceada, ento tambm as suas subrvores da esquerda e da direita o
estaro. Por definio, considera-se que uma rvore vazia, ou seja, uma rvore sem
qualquer n, est balanceada.
Numa rvore binria de pesquisa pode ser aplicada a pesquisa binria de forma recursiva,
mas, s em rvores completamente balanceadas que se consegue obter uma pesquisa
proporcional a log2N. Portanto, fundamental garantir-se em cada instante que a rvore se
encontra organizada numa estrutura to prxima quanto possvel do balanceamento
completo. Daqui resulta que, para se garantir a operacionalidade mxima de uma
organizao hierrquica em rvore binria, se deve conceber as operaes para colocar e
retirar elementos, como operaes invariantes em termos de balanceamento.

5.10 Atribuio dinmica de memria


Para permitir a construo de estruturas de dados semiestticas e dinmicas, a linguagem C
permite a atribuio dinmica de memria durante a execuo do programa. A biblioteca
de execuo ANSI stdlib providencia as quatro funes que se apresentam na Figura 5.24.
Nome da funo
Significado
void *malloc (size_t sz);
atribui
sz
bytes
na
memria. Devolve um ponteiro para o incio do bloco atribudo ou
NULL no caso contrrio
void *calloc (size_t nelem, size_t sz); atribui
um
espao
de
memria contguo de nelem objectos de sz bytes cada. Todos os bits
do espao de memria atribudo so inicializados a zero. Devolve um
ponteiro para o incio do bloco atribudo ou NULL no caso contrrio
void *realloc (void *ptr, size_t nsz); altera o tamanho do espao
de memria atribudo anteriormente e apontado por ptr para nsz
bytes. Devolve um ponteiro para o incio do bloco atribudo ou NULL
no caso contrrio
void free (void *ptr);
liberta o espao de memria
previamente atribudo pelas funes anteriores e apontado por ptr

Figura 5.24 - Funes para gesto da memria dinmica.

A Figura 5.25 apresenta um exemplo de atribuio dinmica de memria. Pretende-se


escrever um programa que leia um ficheiro de texto constitudo por nmeros inteiros, um
por linha, para os ordenar e imprimir no monitor. Vamos admitir, que o nmero de
nmeros inteiros armazenados no ficheiro depende de ficheiro para ficheiro e se encontra
armazenado na primeira linha do ficheiro, que se comporta como cabealho do ficheiro.
Uma vez que, a dimenso do agregado dependente do tamanho do ficheiro, no podemos
declarar o agregado de forma esttica, sob pena de o dimensionarmos com tamanho
insuficiente. A no ser que se dimensione o agregado por excesso, prevendo o pior caso.
Normalmente, no possvel implementar esta estratgia, uma vez que, na maioria das
aplicaes praticamente impossvel prever o pior caso. Nem to pouco desejvel,
porque gera um desperdcio de memria na maioria dos casos.

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

28

A soluo correcta passa por criar o agregado dinamicamente, recorrendo funo calloc,
assim que se saiba o nmero de nmeros contido no ficheiro. Para tal declara-se o ponteiro
PARINT que vai receber o endereo devolvido pela funo, que o endereo inicial do
bloco de memria atribudo para o agregado. Devido dualidade ponteiro agregado, o
ponteiro PARINT utilizado para aceder aos elementos do agregado, tal como se fosse um
agregado. O programa antes de terminar, deve libertar a memria atribuda dinamicamente,
usando para o efeito a funo free.
#include <stdio.h>
#include <stdlib.h>
int main (int argc, char *argv[])
{
FILE *FP; int *PARINT, N, I;
if ( argc < 2 )
/* o nmero de argumentos suficiente? */
{
fprintf (stderr, "Uso: %s nome do ficheiro\n", argv[0]);
exit (EXIT_FAILURE);
}
/* abertura do ficheiro de entrada cujo nome argv[1] */
if ( (FP = fopen (argv[1], "r")) == NULL )
{
fprintf (stderr, "No foi possvel abrir o ficheiro %s\n"\
, argv[1]);
exit (EXIT_FAILURE);
}
/* leitura do nmero de nmeros inteiros armazenado no ficheiro */
fscanf (FP, "%d", &N);
/* atribuio de memria para um agregado de N inteiros */
if ( (PARINT = (int *) calloc (N, sizeof (int))) == NULL )
{
fprintf (stderr, "No foi possvel atribuir o agregado\n");
fclose (FP);
/* fecho do ficheiro de entrada */
exit (EXIT_FAILURE);
}
for (I = 0; I < N; I++)
/* leitura do ficheiro para o agregado */
fscanf (FP, "%d", &PARINT[I]);
/* fecho do ficheiro de entrada */

fclose (FP);

/* processamento do agregado */

...

free (PARINT);/* libertao da memria atribuda para o agregado */


return EXIT_SUCCESS;
}

Figura 5.25 - Exemplo de aplicao da funo calloc.

5.11 Leituras recomendadas


x 4 captulo do livro Data Structures, Algorithms and Software Principles in C, de
Thomas A. Standish, da editora Addison-Wesley Publishing Company, 1995.
x 8 captulo do livro C A Software Approach, 3 edio, de Peter A. Darnell e Philip E.
Margolis, da editora Springer-Verlag, 1996.

Captulo 6
PESQUISA E ORDENAO

Sumrio
Uma das tarefas mais habituais na programao a pesquisa de informao, o que exige o
desenvolvimento de algoritmos eficientes de pesquisa. Neste captulo introduzimos os
mtodos de pesquisa sequencial, de pesquisa binria e de pesquisa por tabela.
Apresentamos vrios algoritmos que utilizam o mtodo de pesquisa sequencial e o
algoritmo de pesquisa binria, nas verses iterativa e recursiva.
Por outro lado a pesquisa fortemente condicionada pela organizao da informao, e por
conseguinte, a ordenao uma tarefa ainda mais frequente. Apresentamos as classes mais
simples dos algoritmos de ordenao, que so, a ordenao por seleco, a ordenao por
troca e a ordenao por insero. Para cada uma delas expomos os algoritmos mais
simples, que apesar de no serem os mais eficientes, so contudo suficientemente rpidos
para pequenos agregados e apropriados para mostrar as caractersticas dos princpios de
ordenao. Explicamos os algoritmos de ordenao Sequencial ( Sequential ), Seleco
( Selection ), Bolha ( Bubble ), Concha ( Shell ), Crivo ( Shaker ) e Insero ( Insertion ),
mostramos a sua execuo com um pequeno agregado e fazemos a comparao do
desempenho de cada um deles. Apresentamos tambm o algoritmo de ordenao Fuso de
Listas ( Merge List ) que muito verstil, porque pode ser aplicado, quer na ordenao de
agregados, quer na ordenao de ficheiros. Apresentamos ainda os algoritmos de
ordenao recursivos Fuso ( Merge ) e Rpido ( Quick ), que so muito eficientes.
Finalmente, explicamos como generalizar os algoritmos de ordenao e como contabilizar
as instrues de comparao e de atribuies de elementos, de modo a poder fazer testes
de desempenho dos algoritmos.

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

6.1 Pesquisa
Uma das tarefas mais comuns no dia a dia a pesquisa de informao. Mas, a pesquisa
depende muito da forma como a informao est organizada. Se a informao estiver
completamente desordenada no temos outra alternativa que no seja analisar toda a
informao por ordem, seja ela do princpio para o fim ou vice-versa, at encontrar o que
pretendemos. Este processo de pesquisa normalmente lento. Imagine, por exemplo,
procurar a nota de um teste numa pauta no ordenada, no caso de uma disciplina com
algumas centenas de alunos. Mas, se a informao estiver ordenada por uma ordem, seja ela
crescente ou decrescente no caso de informao numrica, ascendente ou descendente no
caso de informao textual, como por exemplo a mesma pauta listada por ordem do
nmero mecanogrfico, ento possvel fazer uma procura mais ou menos selectiva,
partindo a informao a procurar em intervalos sucessivos cada vez menores, evitando
assim analisar informao irrelevante e acelerar o processo de pesquisa. E, se a informao
alm de estar ordenada por ordem, estiver ainda composta com entradas especficas, como
por exemplo, uma pauta de uma disciplina com alunos de vrios cursos, que se apresenta
dividida em pautas por cursos, cada uma delas ordenada por nmero mecanogrfico, ento
possvel fazer uma procura dirigida pauta do respectivo curso e depois fazer uma
procura em funo do nmero mecanogrfico, optimizando ainda mais o processo de
pesquisa. Portanto, o mtodo de pesquisa inevitavelmente dependente da forma como a
informao est organizada e apresentada. Quanto mais ordenada estiver a informao,
mais eficiente poder ser o mtodo de pesquisa.
Tal como no dia a dia, a pesquisa de informao tambm uma tarefa trivial em
programao. Pesquisar um agregado procura da localizao de um determinado valor, ou
de uma determinada caracterstica acerca dos seus valores, uma tarefa muito frequente e
simples. Mas, computacionalmente dispendiosa porque, o agregado pode ser constitudo
por centenas ou mesmo milhares de elementos. Pelo que, exige o desenvolvimento de
algoritmos eficientes.
A maneira mais simples de pesquisar um agregado a pesquisa sequencial
( sequencial search ), tambm chamada de pesquisa linear. Consiste em analisar todos os
elementos do agregado de maneira metdica. A pesquisa comea no elemento inicial do
agregado e avana elemento a elemento at encontrar o valor procurado, ou at atingir o
elemento final do agregado. Este mtodo de pesquisa normalmente demorado,
dependente do tamanho do agregado, mas, no depende do arranjo interno dos elementos
no agregado. Independentemente da forma como a informao est armazenada no
agregado, o valor procurado ser encontrado, caso exista no agregado.
No entanto, se tivermos informao priori sobre os elementos do agregado possvel
acelerar a pesquisa. Se por exemplo, os elementos estiverem ordenados por ordem
crescente ou decrescente, ento possvel fazer uma pesquisa binria ( binary search ),
tambm chamada de pesquisa logartmica. Este tipo de pesquisa comea por seleccionar
o elemento central do agregado e compara-o com o valor procurado. Se o elemento
escolhido for menor ento podemos excluir a primeira metade do agregado e analisamos
apenas a segunda metade. Caso contrrio, ento podemos excluir a segunda metade do
agregado e analisamos apenas a primeira metade. Em cada passo da pesquisa, o nmero de
elementos do agregado que tm de ser analisados reduzido a metade, pelo que, este
mtodo de pesquisa mais eficiente. O processo repetido at que o elemento
seleccionado seja o valor procurado, ou at que o nmero de elementos que tm de ser

CAPTULO 6 : PESQUISA E ORDENAO

analisados seja reduzido a zero, o que significa, que o valor procurado no existe no
agregado.
Se os elementos do agregado, em vez de estarem colocados em posies sucessivas do
agregado, estiverem colocados em posies predeterminadas do agregado, posies essas
que so determinadas por uma funo de disperso ( hashing function ), ento possvel fazer
uma pesquisa por tabela ( table search ), tambm chamada de pesquisa por disperso
( hashing ). Uma funo de disperso uma funo aritmtica que determina a posio de
colocao do elemento no agregado, tendo em considerao o valor do elemento, ou no
caso de um elemento estruturado, usando um campo do elemento como chave de
colocao no agregado. No entanto, normalmente uma funo de disperso produz
situaes de coliso. A situao de coliso acontece quando dados dois valores diferentes, a
funo de disperso calcula a mesma a posio de colocao. Portanto, o agregado tem de
permitir colocar mais do que um elemento numa mesma posio. Uma soluo possvel
consiste em criar uma estrutura semiesttica em que cada elemento do agregado um
ponteiro que aponta para uma lista ligada de elementos. Temos assim um agregado de listas
ligadas. Este modelo de estrutura de dados, que se designa por uma tabela de disperso
com encadeamento de elementos, pesquisado em dois tempos. Para procurar um
elemento, primeiro usada a funo de disperso para, dado o valor do elemento desejado,
calcular a sua posio de colocao no agregado e aceder directamente a essa posio.
Depois utiliza-se a pesquisa sequencial para analisar a lista ligada de elementos at detectar
o elemento com o valor pretendido. Este mtodo de pesquisa ser abordado mais tarde a
propsito da implementao de memrias associativas.
Para os algoritmos apresentados neste captulo vamos considerar que a estrutura de dados a
pesquisar ou ordenar, um agregado de elementos inteiros de nome seq, com capacidade
para armazenar NMAX elementos, tendo no entanto apenas nelem elementos teis.
Tendo em considerao este agregado de elementos inteiros, vamos analisar vrios
algoritmos que utilizam a pesquisa sequencial e o algoritmo de pesquisa binria nas suas
verses iterativa e recursiva. Vamos considerar que as funes s so invocadas para
agregados que contm de facto elementos com informao, ou seja, nelem maior do que
zero, pelo que, os algoritmos podem ser simplificados, uma vez que no tm que se
preocupar com esta situao anmala.

6.1.1 Pesquisa Sequencial


Com o objectivo de tornar as funes, o mais versteis possveis, elas tm uma varivel de
entrada que indica o ndice do agregado onde deve comear a pesquisa. As funes
calculam e devolvem o ndice do elemento onde est armazenado o valor procurado.

6.1.1.1 Procurar o maior valor de um agregado


A Figura 6.1 apresenta a funo que determina, a partir de um ndice inicial de pesquisa, o
ndice do elemento com o maior valor armazenado na parte restante do agregado. No caso
de existir mais do que um elemento com o mesmo valor a funo devolve o ndice do
primeiro elemento encontrado. No incio assume-se que o maior valor o elemento de
ndice inicial e depois o agregado analisado at ao ltimo elemento, procura de um valor
ainda maior. Se pretendermos procurar o maior valor armazenado no agregado, basta
invocar a funo a partir do primeiro elemento do agregado.

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

unsigned int ProcurarMaior (int seq[], unsigned int nelem,\


unsigned int inicio)
{
unsigned int indmaior = inicio, indactual;
for (indactual = inicio+1; indactual < nelem; indactual++)
if (seq[indactual] > seq[indmaior]) indmaior = indactual;
return indmaior;
}

Figura 6.1 - Funo que determina o elemento de maior valor do agregado.

6.1.1.2 Procurar o menor valor de um agregado


A Figura 6.2 apresenta a funo que determina, a partir de um ndice inicial de pesquisa, o
ndice do elemento com o menor valor armazenado na parte restante do agregado. No caso
de existir mais do que um elemento com o mesmo valor a funo devolve o ndice do
primeiro elemento encontrado. No incio assume-se que o menor valor o elemento de
ndice inicial e depois o agregado analisado at ao ltimo elemento, procura de um valor
ainda menor. Se pretendermos procurar o menor valor armazenado no agregado, basta
invocar a funo a partir do primeiro elemento do agregado.
unsigned int ProcurarMenor (int seq[], unsigned int nelem,\
unsigned int inicio)
{
unsigned int indmenor = inicio, indactual;
for (indactual = inicio+1; indactual < nelem; indactual++)
if (seq[indactual] < seq[indmenor]) indmenor = indactual;
return indmenor;
}

Figura 6.2 - Funo que determina o elemento de menor valor do agregado.

6.1.1.3 Procurar um valor no agregado


A Figura 6.3 apresenta a funo que determina, a partir de um ndice inicial de pesquisa, o
ndice do elemento que armazena o valor que se pretende procurar na parte restante do
agregado. Como no temos a certeza de que o valor existe de facto no agregado, se a
pesquisa ultrapassar o ltimo elemento do agregado sem o ter encontrado, ento a funo
devolve o ndice 1 como sinal de pesquisa falhada. A pesquisa deve acabar assim que a
primeira ocorrncia do valor pretendido seja encontrado, pelo que, se usa a instruo de
return dentro do ciclo for, para terminar a pesquisa assim que se encontrar um elemento
com o valor pretendido. Se pretendermos pesquisar todo o agregado, basta invocar a
funo a partir do primeiro elemento do agregado.
int ProcurarValor (int seq[], unsigned int nelem,\
unsigned int inicio, int valor)
{
unsigned int indactual;
for (indactual = inicio; indactual < nelem; indactual++)
if (seq[indactual] == valor) return indactual;
return -1;
}

Figura 6.3 - Funo de pesquisa sequencial que procura um valor no agregado.

CAPTULO 6 : PESQUISA E ORDENAO

6.1.1.4 Procurar o primeiro que serve


Existem alguns problemas concretos do dia a dia, cuja soluo optimizada requer que se
procure um determinado valor num agregado, mas, se este valor no existir podemos em
alternativa utilizar um valor prximo do valor pretendido. Nesta situao podemos aplicar
uma de trs estratgias. A primeira e mais simples consiste em procurar no agregado, o
primeiro valor que no excede o valor procurado. Este algoritmo designa-se por o primeiro
que serve ( first fit ). A Figura 6.4 apresenta a funo que determina, a partir de um ndice
inicial de pesquisa, o ndice do primeiro elemento do agregado com um valor que no
excede o valor procurado. Como nesta pesquisa, no temos a certeza de que existe de facto
tal valor, se a pesquisa ultrapassar o ltimo elemento do agregado sem ter encontrado um
valor, ento a funo devolve o ndice 1 como sinal de pesquisa falhada. Como o nome
indica, a pesquisa deve acabar assim que seja encontrado o primeiro valor que sirva, pelo
que, se usa a instruo de return dentro do ciclo for, para terminar a pesquisa assim que se
encontrar um elemento com o valor pretendido. Se pretendermos pesquisar todo o
agregado, basta invocar a funo a partir do primeiro elemento do agregado.
int ProcurarPrimeiro (int seq[], unsigned int nelem,\
unsigned int inicio, int valor)
{
unsigned int indactual;
for (indactual = inicio; indactual < nelem; indactual++)
if (seq[indactual] <= valor) return indactual;
return -1;
}

Figura 6.4 - Funo que determina o elemento do agregado com o primeiro valor que serve.

6.1.1.5 Procurar o melhor que serve


A segunda estratgia de optimizao consiste em procurar no agregado, o valor mais
prximo do valor pretendido, ou seja, o melhor valor. Este algoritmo designa-se por o
melhor que serve ( best fit ). A Figura 6.5 apresenta a funo que determina, a partir de um
ndice inicial de pesquisa, o ndice do elemento do agregado com o melhor valor que no
excede o valor pretendido. Comea-se por procurar o primeiro valor que serve. Caso ele
no exista, a funo devolve o ndice 1 como sinal de pesquisa falhada. Caso contrrio
procura-se at ao ltimo elemento do agregado um elemento com um valor melhor, ou
seja, um elemento com um valor que seja ainda maior do que o j encontrado e que no
exceda o valor procurado. Se pretendermos pesquisar todo o agregado, basta invocar a
funo a partir do primeiro elemento do agregado.
int ProcurarMelhor (int seq[], unsigned int nelem,\
unsigned int inicio, int valor)
{
int indmelhor; unsigned int indactual;
indmelhor = ProcurarPrimeiro (seq, nelem, inicio, valor);
if (indmelhor == -1 ) return -1;
for (indactual = indmelhor+1; indactual < nelem; indactual++)
if (seq[indactual] > seq[indmelhor] && seq[indactual] <= valor)
indmelhor = indactual;
return indmelhor;
}

Figura 6.5 - Funo que determina o elemento do agregado com o melhor valor que serve.

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

6.1.1.6 Procurar o pior que serve


A terceira estratgia de optimizao consiste em procurar no agregado, o valor menos
prximo do valor pretendido, ou seja, o pior valor. Este algoritmo designa-se por o pior
que serve ( worst fit ). Por vezes, uma estratgia pela negativa melhor do que pela positiva.
Imagine-se por exemplo, que se pretende aproveitar uma sobra de 100 cm de uma calha de
alumnio e que temos a necessidade de cortar seces de 80 cm, 50 cm e 40 cm. Uma
estratgia o melhor que serve escolher o valor 80 cm, desperdiando 20 cm, enquanto que
uma estratgia o pior que serve escolher sucessivamente os valores 40 cm e 50 cm,
desperdiando apenas 10 cm.
A Figura 6.6 apresenta a funo que determina, a partir de um ndice inicial de pesquisa, o
ndice do elemento do agregado com o pior valor que no excede o valor pretendido.
Comea-se por procurar o primeiro valor que serve. Caso ele no exista, a funo devolve
o ndice 1 como sinal de pesquisa falhada. Caso contrrio procura-se at ao ltimo
elemento do agregado um elemento com um valor pior, ou seja, um elemento com um
valor que seja ainda menor do que o j encontrado. Se pretendermos pesquisar todo o
agregado, basta invocar a funo a partir do primeiro elemento do agregado.
int ProcurarPior (int seq[], unsigned int nelem,\
unsigned int inicio, int valor)
{
int indpior; unsigned int indactual;
indpior = ProcurarPrimeiro (seq, nelem, inicio, valor);
if (indpior == -1 ) return -1;
for (indactual = indpior+1; indactual < nelem; indactual++)
if (seq[indactual] < seq[indpior]) indpior = indactual;
return indpior;
}

Figura 6.6 - Funo que determina o elemento do agregado com o pior valor que serve.

6.1.1.7 Exemplificao dos algoritmos de pesquisa sequencial


A Figura 6.7 apresenta a aplicao destes algoritmos num agregado com vinte elementos
teis. Se pesquisarmos o agregado a comear no elemento de ndice 7, temos que, o maior
valor o elemento com ndice 19, cujo valor 250, e o menor valor o elemento com
ndice 11, cujo valor 1. Se procurarmos um elemento no agregado com valor 55, a
comear no elemento de ndice 7, encontramos o elemento com ndice 16. Mas, se
procurarmos um elemento com valor 40, no conseguimos encontrar nenhum valor, pelo
que, a funo devolve o ndice 1.
O primeiro elemento com valor que no excede 40, a comear no elemento de ndice 7, o
elemento com ndice 7, cujo valor 32. Nas mesmas circunstncias, o elemento com o
melhor valor o elemento com ndice 15, cujo valor 39, e o elemento com o pior valor
o elemento com ndice 11, cujo valor 1. No entanto, se comearmos a pesquisa no
elemento de ndice 17 do agregado, no encontraremos nenhum valor que no exceda 40,
pelo que, as funes, o primeiro que serve, o melhor que serve e o pior que serve devolvem
o ndice 1.

CAPTULO 6 : PESQUISA E ORDENAO

Posio
Inicial de
Pesquisa

20
0

3 330 25 22 24 15 32 42
1

Primeiro
Valor<=40

Menor
Valor

Valor
55

10

11

12

13

14

Pior
Valor<=40

Maior
Valor

39 55 145 209 250


15

16

17

18

19

Melhor
Valor<=40

Figura 6.7 - Resultados da utilizao dos algoritmos de pesquisa sequencial.

6.1.2 Pesquisa Binria


Uma forma de acelerar a pesquisa consiste em utilizar uma estratgia de partio sucessiva
do agregado ao meio para diminuir o nmero de elementos a analisar. Mas, este mtodo de
pesquisa s funciona se os elementos estiverem ordenados. A Figura 6.8 apresenta a funo
de pesquisa binria, na sua verso iterativa, que calcula o ndice do elemento com o valor
procurado. Como no temos a certeza que o valor procurado existe de facto no agregado, a
funo devolve o ndice 1 como sinal de pesquisa falhada. Com o objectivo de tornar a
funo o mais verstil possvel, a funo tem duas variveis de entrada que indicam os
ndices dos elementos onde deve comear e acabar a pesquisa.
int ProcuraBinariaIterativa (int seq[], unsigned int inicio,\
unsigned int fim, int valor)
{
unsigned int minimo = inicio, maximo = fim, medio;
while (minimo <= maximo)
{
medio = (minimo + maximo) / 2;

/* clculo da posio mdia */

if (seq[medio] == valor) return medio; /* pesquisa com sucesso */


/* actualizao dos limites do intervalo de pesquisa */
if (seq[medio] > valor) maximo = medio - 1;
else minimo = medio + 1;
return -1;

/* pesquisa falhada */

Figura 6.8 - Funo de pesquisa binria que procura um valor no agregado (verso iterativa).

Vamos mostrar na Figura 6.9, o funcionamento deste algoritmo, para o caso de um


agregado com vinte elementos ordenado por ordem crescente. Pretendemos procurar o
valor 34. Vamos invocar a funo para todos os elementos teis do agregado, ou seja, do
elemento de ndice 0 at ao elemento nelem1, que neste caso o elemento de ndice 19.
Calcula-se o elemento mdio do agregado, atravs da diviso inteira da soma das posies
mnima e mxima, e que neste caso o elemento de ndice 9. Como o valor procurado, que
34, menor do que valor armazenado no elemento mdio, neste caso 44, ento isso
significa que ele se encontra na primeira metade do agregado, pelo que, a posio mxima
passa para o elemento esquerda da posio mdia, ou seja, a nova posio mxima
agora o elemento de ndice 8. Se pelo contrrio, o valor procurado fosse maior do que 44
ento isso significava que ele se encontrava na segunda metade do agregado, pelo que, a
nova posio mnima passaria para o elemento direita da posio mdia, ou seja, a nova

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

posio mnima seria o elemento de ndice 10. Agora que estamos a analisar a primeira
metade do agregado, calcula-se a nova posio mdia, que o elemento de ndice 4. Como
o valor procurado maior do que valor armazenado no elemento mdio, que 26, ento
isso significa que ele se encontra na segunda metade do intervalo em anlise, pelo que, a
posio mnima passa para o elemento direita da posio mdia, ou seja, para o elemento
de ndice 5. Agora, a nova posio mdia passa a ser o elemento de ndice 6. Como o valor
procurado ainda maior do que valor armazenado no elemento mdio, que 30, ento isso
significa que ele se encontra na segunda metade do intervalo em anlise, pelo que, a posio
mnima passa para o elemento direita da posio mdia, ou seja, para o elemento de
ndice 7. A nova posio mdia passa agora a ser o elemento de ndice 7, cujo valor o
valor procurado, pelo que, a pesquisa termina com sucesso e devolve o ndice 7. Para
obtermos este resultado, foi preciso analisar quatro elementos do agregado enquanto que a
pesquisa sequencial necessitaria de analisar oito elementos.
MIN

MED

20 25 26 28 30 34 42 44 50 60 68 75 86 99 125 145 209 250


2

MIN

MED

10

11

12

13

14

15

16

17

18

19

MAX

20 25 26 28 30 34 42 44 50 60 68 75 86 99 125 145 209 250


2

MIN MED

MAX

10

11

12

13

14

15

16

17

18

19

MAX

20 25 26 28 30 34 42 44 50 60 68 75 86 99 125 145 209 250


2

10

11

12

13

14

15

16

17

18

19

MIN MAX

20 25 26 28 30 34 42 44 50 60 68 75 86 99 125 145 209 250


2

10

11

12

13

14

15

16

17

18

19

o valor 34 foi encontrado ao


fim de 4 tentativas

MED

MAX MIN

20 25 26 28 30 34 42 44 50 60 68 75 86 99 125 145 209 250


2

MED

10

11

12

13

14

15

16

17

18

o valor 40 no foi encontrado


ao fim de 5 tentativas

Figura 6.9 - Utilizao do algoritmo de pesquisa binria num agregado ordenado.

19

CAPTULO 6 : PESQUISA E ORDENAO

Se por exemplo, o valor procurado fosse 40, que maior do que 34, ento a nova posio
mnima passaria para o elemento direita da posio mdia, ou seja, para o elemento de
ndice 8, exactamente igual ao valor da posio mxima. Consequentemente, a nova
posio mdia passaria a ser o elemento de ndice 8, cujo valor 42, pelo que, a posio
mxima passaria para o elemento esquerda da posio mdia, ou seja, para o elemento de
ndice 7 e portanto, as posies mnima e mxima trocavam de posio parando o ciclo de
pesquisa. Como o valor armazenado no elemento cujo ndice a posio mdia final, que
o elemento de ndice 8, diferente do valor procurado, ento a pesquisa terminaria sem
sucesso e devolveria o ndice 1 como sinal de pesquisa falhada. Neste caso a funo
analisava cinco elementos do agregado enquanto que a pesquisa sequencial necessitaria de
analisar os vinte elementos do agregado para chegar ao mesmo resultado.
Esta estratgia de pesquisa tambm pode ser implementada de forma recursiva, sendo que
cada nova pesquisa analisa uma das metades do agregado anteriormente analisado at que o
valor seja encontrado ou at no existirem mais elementos para analisar. A verso recursiva
apresentada na Figura 6.10. Compare-a com a verso iterativa que foi apresentada na
Figura 6.8. A funo comea por testar a situao de paragem no caso da pesquisa sem
sucesso, o que acontece quando as posies inicial e final trocam de posio. Nesse caso, a
funo devolve o ndice 1 como sinal de pesquisa falhada. Caso tal no se verifique,
calcula o ndice do elemento central do agregado em anlise. Se o elemento for o valor
procurado, estamos perante a situao de paragem com sucesso e a funo devolve o ndice
do elemento central do agregado. Caso contrrio, em funo da comparao do valor
procurado com o valor do elemento central, a funo invoca-se recursivamente de forma
alternativa para a primeira metade ou para a segunda metade do agregado.
int ProcuraBinariaRecursiva (int seq[], unsigned int inicio,\
unsigned int fim, int valor)
{
unsigned int medio;
/* condio de paragem no caso de pesquisa sem sucesso */
if (inicio > fim) return -1;
medio = (inicio + fim) / 2;

/* clculo da posio mdia */

/* condio de paragem no caso de pesquisa com sucesso */


if (seq[medio] == valor) return medio;
if (seq[medio] > valor)
ProcuraBinariaRecursiva (seq, inicio, medio-1, valor);
/* invocao recursiva para a primeira metade do agregado */
else ProcuraBinariaRecursiva (seq, medio+1, fim, valor);
/* invocao recursiva para a segunda metade do agregado */
}

Figura 6.10 - Funo de pesquisa binria que procura um valor no agregado (verso recursiva).

A implementao recursiva no tem qualquer vantagem sobre a implementao repetitiva,


quando aplicada a agregados. No entanto normalmente aplicada para pesquisar estruturas
de dados dinmicas organizadas de forma binria, como o caso das rvores binrias de
pesquisa, permitindo combinar a eficincia da pesquisa binria com a flexibilidade destas
estruturas de dados dinmicas.

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

10

6.1.3 Comparao entre as pesquisas sequencial e binria


Dada a dificuldade em calcular com exactido a eficincia de um algoritmo, para exprimir a
sua eficincia em funo do nmero de elementos processados, utiliza-se a notao
matemtica conhecida por ordem de magnitude ou notao O maisculo (Big O Notation).
A ordem de magnitude de uma funo igual ordem do seu termo que cresce mais
rapidamente. Por exemplo, a funo f(n) = n2+n de ordem de magnitude O(n2), uma vez
que para grandes valores de n, o termo n2 cresce mais rapidamente que o termo n e
portanto, domina a funo.
Para um agregado com N elementos, o pior caso da pesquisa sequencial quando o valor
procurado est no ltimo elemento do agregado ou no existe no agregado, o que exige a
anlise dos N elementos do agregado. Se considerarmos ainda, que a probabilidade do
valor procurado estar em qualquer um dos elementos do agregado igual, ento a pesquisa
sequencial analisa em mdia (N+1)/2 elementos. Se considerarmos que a probabilidade do
valor no existir no agregado igual a ser qualquer um dos elementos do agregado, ento a
pesquisa sequencial analisa em mdia (N+2)/2 elementos. Portanto, a eficincia do
algoritmo de pesquisa sequencial de ordem O(N).
Para o mesmo agregado, o pior caso da pesquisa binria quando se reduz o intervalo em
anlise a apenas um elemento do agregado, o que exige a anlise de log2(N+1) elementos
do agregado. Se considerarmos ainda, que a probabilidade do valor estar em qualquer um
dos elementos do agregado igual, ento a pesquisa binria analisa em mdia log2(N+1) 1
elementos. Se considerarmos que a probabilidade do valor no existir no agregado igual a
ser qualquer um dos elementos do agregado, ento a pesquisa binria analisa em mdia
log2(N+1) elementos. Portanto, a eficincia do algoritmo de pesquisa binria de
ordem O(log2 N).

6.2 Ordenao
A ordenao o processo de organizar um conjunto de objectos segundo uma determinada
ordem. Como j foi dito anteriormente, se a informao estiver ordenada possvel utilizar
algoritmos de pesquisa mais eficientes, como por exemplo a pesquisa binria ou a pesquisa
por tabela. Pelo que, a ordenao uma tarefa muito importante no processamento de
dados e feita para facilitar a pesquisa.
Os algoritmos de ordenao so classificados em dois tipos. A ordenao de informao
armazenada em agregados designa-se por ordenao interna. Enquanto que, a ordenao
de informao armazenada em ficheiros, designa-se por ordenao externa. Neste
captulo vamos apresentar alguns algoritmos de ordenao interna, se bem que alguns
tambm possam ser utilizados para ordenao externa.
Existem algoritmos de ordenao muito eficientes para ordenar agregados de grandes
dimenses, mas so normalmente complexos. Pelo contrrio, os algoritmos de ordenao
que vamos apresentar so simples, mas apropriados para mostrar as caractersticas dos
princpios de ordenao. So normalmente pouco eficientes, mas so suficientemente
rpidos para agregados de pequenas dimenses. Os algoritmos de ordenao que ordenam
os elementos do agregado, no prprio agregado, fazendo para o efeito um rearranjo interno
dos seus elementos, enquadram-se numa das trs seguintes categorias: ordenao por
seleco; ordenao por troca; e ordenao por insero.

11

CAPTULO 6 : PESQUISA E ORDENAO

Para implementar os algoritmos de ordenao baseados em trocas de elementos vamos


utilizar a funo Swap que se apresenta na Figura 6.11. A troca de dois elementos do
agregado exige uma varivel temporria do mesmo tipo dos elementos do agregado e custa
trs instrues de atribuio. Os elementos a trocar so parmetros de entrada-sada, pelo
que, so passados por referncia.
void Swap (int *x, int *y)
{
int temp;
temp = *x; *x = *y; *y = temp;
}

Figura 6.11 - Funo para trocar dois elementos de um agregado de inteiros.

6.2.1 Ordenao por seleco


Os algoritmos de ordenao por seleco utilizam uma pesquisa sequencial. Fazem
passagens sucessivas sobre todos os elementos de uma parte do agregado e em cada
passagem seleccionam o elemento de menor valor de todos os elementos analisados, no
caso de se pretender uma ordenao crescente, ou em alternativa, o elemento de maior
valor de todos os elementos analisados, no caso de se pretender uma ordenao
decrescente, colocando esse valor nos sucessivos elementos iniciais do agregado. Em cada
passagem, um elemento, de entre todos os restantes elementos do agregado ainda no
ordenados, colocado no stio certo. Mais concretamente, no caso de uma ordenao
crescente, na primeira passagem, o menor valor de todos fica colocado no primeiro
elemento do agregado, na segunda passagem, o segundo menor valor fica colocado no
segundo elemento do agregado, e assim sucessivamente at que na ltima passagem o
penltimo menor valor, ou seja, o segundo maior valor, fica colocado no penltimo
elemento do agregado, e, consequentemente, o maior valor fica automaticamente colocado
no ltimo elemento do agregado. Vamos agora apresentar dois algoritmos que aplicam este
mtodo de ordenao.
A Figura 6.12 apresenta o algoritmo de Ordenao Sequencial ( Sequencial Sort ) para a
ordenao crescente do agregado. Cada elemento do agregado comparado com os
restantes elementos do agregado abaixo dele. Se o elemento de cima, cujo ndice indi for
menor que o elemento de baixo, cujo ndice indj, ento trocam-se os elementos. Para
efectuar a troca dos elementos do agregado utilizada a funo Swap. S so analisados os
primeiros nelem1 elementos, porque, quando se ordena o penltimo elemento do
agregado, o ltimo elemento fica automaticamente ordenado.
void Sequential_Sort (int seq[], unsigned int nelem)
{
unsigned int indi, indj;
for (indi = 0; indi < nelem-1; indi++)
for (indj = indi+1; indj < nelem; indj++)
if (seq[indi] > seq[indj])
Swap (&seq[indi], &seq[indj]);
/* trocar os elementos */
}

Figura 6.12 - Algoritmo de ordenao Sequencial.

A Figura 6.13 apresenta a execuo do algoritmo para um agregado com 10 elementos. Em


cada passagem o elemento ordenado fica a sombreado para melhor se observar a parte
ordenada do agregado. Na nona passagem para alm do nono elemento, tambm o dcimo

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

12

elemento fica ordenado. O algoritmo executa nove passagens e faz um total de 45


comparaes e de 25 trocas para ordenar o agregado.
209
depois
da
primeira
passagem

330

25

15

42

32

55

145

330

209

25

42

15

32

55

145

I
2

NC=9 NT=3

depois
da
segunda
passagem

I
2

330

NC=8 NT=4

depois
da
terceira
passagem
NC=7 NT=4

depois
da
quarta
passagem
NC=6 NT=4
depois
da
quinta
passagem
NC=5 NT=3
depois
da
sexta
passagem
NC=4 NT=2
depois
da
stima
passagem
NC=3 NT=2
depois
da
oitava
passagem
NC=2 NT=2
depois
da
nona
passagem
NC=1 NT=1
TOTAL
NC = 45
NT = 25

15

209

42

25

32

15

55

145
J

330

209

42

32

25

55

145
J

I
2

15

25

330

209

42

32

55

145
J

I
2

15

25

32

330

209

42

55

145
J

I
2

15

25

32

42

330

209

55

145
J

I
2

15

25

32

42

55

330

209

145
J

I
2

15

25

32

42

55

145

330

209

I
2

15

25

32

42

55

145

209

330
J

15

25

32

42

55

145

209

Figura 6.13 - Execuo do algoritmo de ordenao Sequencial.

330

13

CAPTULO 6 : PESQUISA E ORDENAO

Para um agregado com N elementos, todos os elementos do agregado so comparados


com todos os outros elementos, pelo que, este algoritmo faz (N 2N)/2 comparaes e o
nmero de trocas se bem que dependente do grau de desordenao dos elementos, no pior
caso pode atingir tambm as (N2N)/2 trocas. Portanto, este algoritmo tem uma eficincia
de comparao de ordem O(N2) e uma eficincia de trocas de ordem O(N2), pelo que,
pertence classe O(N2).
Este algoritmo tem a desvantagem de eventualmente fazer trocas desnecessrias de
elementos. Pelo que, para reduzir o nmero de trocas, em cada passagem deve-se detectar
o elemento de menor valor, memorizando-se para o efeito o ndice onde ele se encontra
armazenado e s depois de comparar o elemento com os restantes elementos do agregado,
que se faz a troca do elemento analisado com o elemento de menor valor.
A Figura 6.14 apresenta o algoritmo de Ordenao Seleco ( Selection Sort ), que
implementa esta optimizao das trocas, para a ordenao crescente do agregado. Existe a
varivel auxiliar indmin que no incio de cada passagem inicializada a indi e que representa
o ndice do elemento de menor valor detectado durante a passagem. Cada elemento do
agregado comparado com os restantes elementos do agregado abaixo dele. Se o elemento
de menor valor, cujo ndice indmin for menor que o elemento, cujo ndice indj, ento
este elemento com ndice indj o novo menor valor at ento detectado, pelo que, o ndice
indmin actualizado a indj. No final da passagem pelo agregado, se indmin for diferente de
indi, ento sinal que o elemento de menor valor no est na posio inicial indi e,
portanto, preciso trocar os dois elementos.
void Selection_Sort (int seq[], unsigned int nelem)
{
unsigned int indi, indj, indmin;
for (indi = 0; indi < nelem-1; indi++)
{
indmin = indi;
/* o menor valor est na posio i */
for (indj = indi+1; indj < nelem; indj++)
if (seq[indmin] > seq[indj])
indmin = indj;
/* o menor valor est na posio j */
if (indmin != indi)
Swap (&seq[indi], &seq[indmin]);

/* trocar os elementos */

}
}

Figura 6.14 - Algoritmo de ordenao Seleco.

A Figura 6.15 apresenta a execuo do algoritmo para um agregado com 10 elementos e o


algoritmo executa nove passagens para ordenar o agregado, sendo o nmero total de
comparaes igual ao do algoritmo anterior. Na quarta e na nona passagens no
efectuada qualquer troca e nas restantes passagens efectuada apenas uma troca, pelo que,
o nmero de trocas de apenas 7 contra as 25 do algoritmo anterior.
Para um agregado com N elementos, este algoritmo faz tantas comparaes como o
algoritmo Sequencial. No entanto, como faz no mximo uma troca por passagem, o
nmero de trocas no pior caso pode atingir as N1 trocas. Este algoritmo tem uma
eficincia de comparao de ordem O(N2) e uma eficincia de trocas de ordem O(N).
Apesar de ser mais eficiente no nmero de trocas efectuadas, no entanto, um algoritmo
que pertence classe O(N2).

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

209
depois
da
primeira
passagem

330

25

15

330

depois
da
segunda
passagem

I
2

25

15

25

15

depois
da
terceira
passagem
NC=7 NT=1

MIN

15

25

depois
da
stima
passagem
NC=3 NT=1
depois
da
oitava
passagem
NC=2 NT=1
depois
da
nona
passagem
NC=1 NT=0
TOTAL
NC = 45
NT = 7

55

145

42

209

32

55

145

MIN

depois
da
sexta
passagem
NC=4 NT=1

32

NC=8 NT=1

2
MIN

depois
da
quinta
passagem
NC=5 NT=1

42

NC=9 NT=1

depois
da
quarta
passagem
NC=6 NT=0

14

42

209

32

330

55

145
J

42

209

32

330

55

145
J

I
2

15

25

42

MIN

209

15

25

32

330

55

MIN
209

42

330

55

15

25

32

MIN

42

209

330

55

15

15

25

25

32

32

42

42

55

55

145
J

I
8

145
J

145
J

I
2

32

MIN
330

209

145

MIN

145

209

330

I
2

15

15

25

25

32

32

42

42

55

55

145

145

209

330

MIN

209

330

Figura 6.15 - Execuo do algoritmo de ordenao Seleco.

15

CAPTULO 6 : PESQUISA E ORDENAO

6.2.2 Ordenao por troca


Os algoritmos de ordenao por troca, fazem passagens sucessivas sobre o agregado,
comparando elementos que se encontram a uma distncia fixa, que caso estejam fora de
ordem so trocados. Quando durante uma passagem no se tiver efectuado qualquer troca,
ento sinal de que os elementos que se encontram distncia de comparao j esto
ordenados, pelo que, a ordenao termina. Vamos agora apresentar trs algoritmos que
aplicam este mtodo de ordenao.
A Figura 6.16 apresenta o algoritmo de Ordenao Bolha ( Bubble Sort ) para a ordenao
crescente do agregado. Existe a varivel auxiliar indinicial que inicializada com o ndice do
segundo elemento do agregado e que representa o ndice do elemento da parte inicial do
agregado onde termina a comparao de elementos em cada passagem. Para todos os
elementos finais do agregado, desde o ltimo elemento at ao elemento de ndice indinicial,
cada elemento comparado com o elemento atrs dele, ou seja, compara-se o elemento de
ndice indi com o elemento de ndice indi1, e caso o valor seja menor trocam-se os
elementos. Deste modo os elementos de menor valor vo sendo deslocados em direco
parte inicial do agregado. Em cada passagem, contabiliza-se o nmero de trocas efectuadas,
e quando uma passagem no tiver efectuado qualquer troca, isso sinal de que o agregado
j est ordenado. Em cada passagem, pelo menos um novo elemento fica ordenado, pelo
que, a varivel indinicial incrementada de uma unidade, de maneira a evitar fazer-se
comparaes desnecessrias com os elementos que j esto ordenados na parte inicial do
agregado. Portanto, a ordenao tambm acaba quando a varivel indinicial excede o fim
do agregado, ou seja, ao fim de nelem1 passagens.
void Bubble_Sort (int seq[], unsigned int nelem)
{
unsigned int indi, indinicial, ntrocas;
indinicial = 1;

/* inicializar o limite superior de ordenao */

do
{
ntrocas = 0;
/* inicializar o contador de trocas */
for (indi = nelem-1; indi >= indinicial; indi--)
if (seq[indi-1] > seq[indi])
{
Swap (&seq[indi], &seq[indi-1]);
/* trocar os elementos */
ntrocas++;

/* actualizar o nmero de trocas efectuadas */

}
indinicial++;
/* actualizar o limite superior de ordenao */
} while (ntrocas && indinicial < nelem);
}

Figura 6.16 - Algoritmo de ordenao Bolha.

A Figura 6.17 apresenta a execuo do algoritmo para um agregado com 10 elementos.


Uma vez que o agregado est bastante desordenado, o algoritmo executa nove passagens,
se bem que na ltima no efectua qualquer troca. No entanto mesmo que efectuasse trocas
na nona passagem, o algoritmo terminaria na mesma, uma vez que, o indicador de posio
inicial excedeu o fim do agregado. O algoritmo faz um total de 45 comparaes e de 25
trocas para ordenar o agregado.

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

209
depois
da
primeira
passagem

209

depois
da
quarta
passagem
NC=6 NT=2
depois
da
quinta
passagem
NC=5 NT=2
depois
da
sexta
passagem
NC=4 NT=2
depois
da
stima
passagem
NC=3 NT=2
depois
da
oitava
passagem
NC=2 NT=2
depois
da
nona
passagem
NC=1 NT=0
TOTAL
NC = 45
NT = 25

15

42

32

55

145

330

25

15

42

32

55

145

I
INICIAL

209

330

25

15

42

32

55

145

NC=8 NT=5
depois
da
terceira
passagem
NC=7 NT=4

25

INICIAL

NC=9 NT=6
depois
da
segunda
passagem

330

16

I
INICIAL

15

209

330

25

32

42

55

145

I
INICIAL

15

25

209

330

32

42

55

145

I
INICIAL

15

25

32

209

330

42

55

145

I
INICIAL

15

25

32

42

209

330

55

145

I
INICIAL

15

25

32

42

55

209

330

145

I
INICIAL

15

25

32

42

55

145

209

330

I
INICIAL

15

25

32

42

55

145

209

330
I
INICIAL

15

25

32

42

55

145

209

330

Figura 6.17 - Execuo do algoritmo de ordenao Bolha.

17

CAPTULO 6 : PESQUISA E ORDENAO

Para um agregado com N elementos, este algoritmo faz no melhor caso apenas uma
passagem, fazendo N1 comparaes e faz no pior caso N1 passagens, fazendo (N2N)/2
comparaes. Em mdia faz aproximadamente N2/3 comparaes. O nmero de trocas se
bem que dependente do grau de desordenao dos elementos, no pior caso pode atingir as
(N2N)/2 trocas. Portanto, este algoritmo tem uma eficincia de comparao de ordem
O(N2), uma eficincia de trocas de ordem O(N2), pelo que, pertence classe O(N2).
Como este algoritmo tem a capacidade de aps cada passagem determinar se o agregado
est ou no ordenado, ento indicado para ordenar agregados que estejam parcialmente
desordenados. No entanto, caso o agregado esteja muito desordenado, ele o pior dos
algoritmos de ordenao, uma vez que faz muitas trocas. De maneira a diminuir o nmero
de trocas, Donald L. Shell criou uma variante deste algoritmo, que em vez de comparar
elementos adjacentes, compara elementos distanciados de um incremento que vai sendo
progressivamente diminudo, at que nas ltimas passagens compara elementos adjacentes.
A Figura 6.18 apresenta o algoritmo de Ordenao Concha ( Shell Sort ) para a ordenao
crescente do agregado. Existe a varivel auxiliar incremento que representa a distncia de
comparao e cujo valor inicial metade do comprimento do agregado. Para os ltimos
elementos do agregado, mais concretamente para os nelemincremento ltimos elementos,
compara-se o elemento de ndice indi, com o elemento distanciado incremento elementos,
ou seja com o elemento de ndice indiincremento, e caso o valor seja menor trocam-se os
elementos. Deste modo os elementos de menor valor vo sendo deslocados em direco
parte inicial do agregado. Em cada passagem, contabiliza-se o nmero de trocas efectuadas,
e quando uma passagem no tiver efectuado qualquer troca, isso sinal de que os
elementos que esto separados da distncia de comparao, j esto ordenados e a distncia
de comparao reduzida, sendo dividida ao meio. O algoritmo repetido at que a ltima
distncia de comparao usada seja igual a um. Quando numa passagem com distncia de
comparao unitria, no se efectuar qualquer troca de elementos, ento sinal que o
agregado est ordenado. Ao contrrio dos outros algoritmos de ordenao, at esta
passagem final no h garantia que algum elemento do agregado j esteja ordenado. A srie
de incrementos utilizada a que foi proposta pelo Shell.
void Shell_Sort (int seq[], unsigned int nelem)
{
unsigned int indi, ntrocas, incremento;

/* 1 verso */

for (incremento = nelem/2; incremento > 0; incremento /= 2)


do
{
ntrocas = 0;
/* inicializar o contador de trocas */
for (indi = incremento; indi < nelem; indi++)
if (seq[indi-incremento] > seq[indi])
{
/* trocar os elementos */
Swap (&seq[indi], &seq[indi-incremento]);
ntrocas++; /* actualizar o nmero de trocas efectuadas */
}
} while (ntrocas);
}

Figura 6.18 - Algoritmo de ordenao Concha (1 verso).

A Figura 6.19 apresenta a execuo do algoritmo para um agregado com 10 elementos. O


algoritmo executa 7 passagens. Nas primeiras duas passagens, o incremento de 5
elementos, nas trs passagens seguintes o incremento de 2 elementos e nas duas

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

18

passagens finais o incremento de 1 elemento. Faz um total de 52 comparaes e de 11


trocas para ordenar o agregado. Comparando com a ordenao Bolha, temos menos 2
passagens, mais algumas comparaes, mas, menos de metade das trocas.
209
depois
da
primeira
passagem

330

25

15

42

32

55

145

15

42

209

330

25

55

145

INCREMENTO=5
2

32

NC=5 NT=3
depois
da
segunda
passagem

I
INCREMENTO=5
2

32

15

42

NC=5 NT=0

209

330

25

55

depois
da
terceira
passagem
NC=8 NT=4

INCREMENTO=2

depois
da
quarta
passagem
NC=8 NT=1

INCREMENTO=2

depois
da
quinta
passagem
NC=8 NT=0

INCREMENTO=2

depois
da
sexta
passagem
NC=9 NT=3

INCREMENTO=1

depois
da
stima
passagem
NC=9 NT=0

INCREMENTO=1

TOTAL
NC = 52
NT = 11

15

32

42

25

55

145

330

15

15

25

42

32

55

145

330

15

25

42

32

55

145

330

25

32

42

55

145

209

330
I

15

25

32

42

55

145

209

209
I

209
I

209
I

145

330
I

15

25

32

42

55

145

209

330

Figura 6.19 - Execuo do algoritmo de ordenao Concha.

Normalmente, o algoritmo Concha mais eficiente do que o algoritmo Bolha, uma vez
que, as primeiras passagens analisam apenas parte dos elementos do agregado e portanto,
fazem poucas trocas, mas trocam elementos que esto muito fora de stio. Quando as
ltimas passagens, que analisam os elementos adjacentes, so efectuadas, ento o agregado
j se encontra parcialmente ordenado, pelo que, so necessrias poucas passagens e poucas
trocas para acabar a ordenao. No entanto, para passar de incremento em incremento esta
verso do algoritmo exige uma passagem sem trocas.

19

CAPTULO 6 : PESQUISA E ORDENAO

Existe uma implementao alternativa deste algoritmo, que se apresenta na Figura 6.20, que
no necessita de fazer esta passagem extra, para determinar a passagem ao incremento
seguinte. Utiliza uma tcnica de insero de elementos em vez de troca de elementos. Pega
no agregado constitudo pelos elementos que esto distncia de comparao e para cada
um desses elementos faz a sua insero na posio correcta, de maneira que este agregado
fique ordenado. Depois passa distncia de comparao seguinte. Quando a distncia de
comparao unitria, ento estamos perante o algoritmo de Insero, que se apresenta na
Figura 6.23. Para ordenar o mesmo agregado, esta verso faz um total de 29 comparaes e
de 55 instrues de atribuio, ou seja, cpias de elementos, que correspondem a
aproximadamente 18 trocas. Se por um lado esta verso mais eficiente nas comparaes,
menos 23 o que d uma eficincia na ordem dos 50%. Por outro lado, ela menos eficiente
nas trocas, com aproximadamente mais 7 trocas para trocar os 11 elementos do agregado
que esto fora do stio, ou seja, existe uma ineficincia na ordem dos 70%. Esta ineficincia
caracterstica da tcnica de insero.
void Shell_Sort (int seq[], unsigned int nelem)
{
unsigned int indi, indj, incremento; int temp;

/* 2 verso */

for (incremento = nelem/2; incremento > 0; incremento /= 2)


for (indi = incremento; indi < nelem; indi++)
{
temp = seq[indi];
/* copiar o elemento a ordenar */
for (indj = indi; indj >= incremento; indj -= incremento)
if (temp < seq[indj-incremento])
seq[indj] = seq[indj-incremento]; /* deslocar elementos */
else break;
seq[indj] = temp; /* inserir o elemento a ordenar na posio */
}
}

Figura 6.20 - Algoritmo de ordenao Concha (2 verso).

O desempenho deste algoritmo depende da srie de incrementos. Nesta verso e para um


agregado com N elementos, em relao ao nmero de comparaes, este algoritmo no pior
caso e usando a srie de incrementos propostos por Donald Shell, pertence classe O(N2).
Com a srie de incrementos propostos por Hibbard pertence classe O(N3/2) e com a srie
de incrementos propostos por Sedgewick pertence classe O(N4/3).
Voltando ao algoritmo de ordenao Bolha, foi dito que em cada passagem pelo agregado,
pelo menos um novo elemento fica ordenado. De facto todos os elementos atrs do
elemento onde foi feita a ltima troca, j esto ordenados, pelo que, podemos colocar a
varivel indinicial, que representa o ndice do elemento da parte inicial do agregado onde
termina a comparao de elementos em cada passagem ascendente, na posio seguinte
posio onde foi feita a ltima troca. Desta forma evitam-se fazer ainda mais comparaes
desnecessrias com os elementos que j esto ordenados na parte inicial do agregado. Se
aplicarmos esta tcnica fazendo tambm passagens descendentes no agregado, alternadas
com as passagens ascendentes, usando uma varivel auxiliar indfinal que representa o ndice
do elemento da parte final do agregado onde termina a comparao de elementos em cada
passagem descendente, podemos assim ordenar o agregado com menos comparaes de
elementos. Estas variveis auxiliares podem ainda ser usadas para determinar quando o
agregado est ordenado, pelo que, no necessrio contar o nmero de trocas efectuadas.
A Figura 6.21 apresenta o algoritmo de Ordenao Crivo ( Shaker Sort ) para a ordenao
crescente do agregado. Existe a varivel auxiliar indinicial que inicializada com o ndice do

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

20

segundo elemento do agregado e que representa o ndice do elemento da parte inicial do


agregado onde termina a comparao de elementos em cada passagem ascendente. Existe
tambm a varivel auxiliar indfinal que inicializada com o ndice do ltimo elemento do
agregado e que representa o ndice do elemento da parte final do agregado onde termina a
comparao de elementos em cada passagem descendente. Na passagem ascendente, cada
elemento final do agregado, desde o elemento de ndice indfinal at ao elemento de ndice
indinicial, comparado com o elemento atrs dele, ou seja, compara-se o elemento de
ndice indi com o elemento de ndice indi1, e caso o valor seja menor trocam-se os
elementos. O ndice do elemento onde foi feita a ltima troca armazenado e aps ter sido
completada a passagem ascendente, o ndice indinicial colocado na posio seguinte
posio da ltima troca. Deste modo os elementos de menor valor vo sendo deslocados
em direco parte inicial do agregado. Depois na passagem descendente, cada elemento
inicial do agregado, desde o elemento de ndice indinicial at ao elemento de ndice indfinal,
comparado com o elemento atrs dele, ou seja, compara-se o elemento de ndice indi com
o elemento de ndice indi1, e caso o valor seja menor trocam-se os elementos. O ndice do
elemento onde foi feita a ltima troca armazenado e aps ter sido completada a passagem
descendente, o ndice indfinal colocado na posio anterior posio da ltima troca.
Deste modo os elementos de maior valor vo sendo deslocados em direco parte final
do agregado.
Em cada passagem ascendente e descendente, ordena-se pelo menos dois elementos, um na
parte inicial do agregado e outro na parte final do agregado. Quando no feita qualquer
troca durante uma passagem ascendente, o ndice indinicial ser colocado depois do ndice
indfinal. Por outro lado, quando no feita qualquer troca durante uma passagem
descendente, o ndice indfinal ser colocado antes do ndice indinicial. Qualquer destas
situaes sinal de que os elementos entre o ndice indinicial e o ndice indfinal tambm
esto ordenados e a ordenao termina, porque o agregado est todo ordenado.
void Shaker_Sort (int seq[], unsigned int nelem)
{
unsigned int indi, indinicial = 1, indfinal = nelem-1, utroca;
do
{
/* passagem ascendente */
for (indi = indfinal; indi >= indinicial; indi--)
if (seq[indi-1] > seq[indi])
{
Swap (&seq[indi], &seq[indi-1]);
/* trocar os elementos */
utroca = indi;
/* actualizar a posio da ltima troca */
}
indinicial = utroca+1;
/* actualizar o limite superior */
/* passagem descendente */
for (indi = indinicial; indi <= indfinal; indi++)
if (seq[indi-1] > seq[indi])
{
Swap (&seq[indi], &seq[indi-1]);
/* trocar os elementos */
utroca = indi;
/* actualizar a posio da ltima troca */
}
indfinal = utroca-1;
/* actualizar o limite inferior */
} while (indinicial < indfinal);
}

Figura 6.21 - Algoritmo de ordenao Crivo.

21

CAPTULO 6 : PESQUISA E ORDENAO

A Figura 6.22 apresenta a execuo do algoritmo para um agregado com 10 elementos. O


algoritmo executa 6 passagens, 3 em cada sentido. Na terceira passagem descendente no
efectuada qualquer troca, pelo que, o ndice de posio final passa para trs do ndice de
posio inicial parando o processo de ordenao. O algoritmo faz um total de 39
comparaes e de 25 trocas para ordenar o agregado. Comparando com a ordenao
Bolha, temos menos 3 passagens e menos 6 comparaes, e o mesmo nmero de trocas.
Os elementos do agregado que vo ficando ordenados, esto a sombreado para melhor se
observar as duas extremidades ordenadas do agregado.
209
depois
da
primeira
passagem

25

15

209

330

55

145

25

15

42

32

55

145
I

INICIAL
2

209

25

FINAL
15

42

32

55

145

330
UT

INICIAL

TOTAL
NC = 39
NT = 25

depois
da
terceira
passagem
NC=7 NT=4

depois
da
sexta
passagem
NC=4 NT=0

32

UT

depois
da
quinta
passagem
NC=5 NT=2

FINAL

NC=8 NT=7

depois
da
quarta
passagem
NC=6 NT=6

42

INICIAL

NC=9 NT=6
depois
da
segunda
passagem

330

209

FINAL
25

15

42

32

55

145

330

UT

I
INICIAL

25

15

FINAL
42

32

55

145

209

330

UT

INICIAL
2

15

25

FINAL
32

42

55

145

209

330

209

330

209

330

UT

I
INICIAL

15

25

32

FINAL
42

55

145

UT

I
FINAL
2

15

INICIAL
25

32

42

55

145

Figura 6.22 - Execuo do algoritmo de ordenao Crivo.

O desempenho deste algoritmo complexo de analisar. Em comparao com o algoritmo


Bolha, tem o mesmo desempenho em relao ao nmero de trocas e mais eficiente em
relao ao nmero de comparaes. No entanto, tambm pertence classe O(N2).

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

22

6.2.3 Ordenao por insero


A ordenao por insero a tcnica de ordenao que normalmente usamos,
nomeadamente, para ordenar uma mo de cartas. Consiste em determinar para cada
elemento do agregado, a posio de insero para que ele fique ordenado e inseri-lo nessa
posio, deslocando para o efeito os restantes elementos.
A Figura 6.23 apresenta o algoritmo de Ordenao Insero ( Insertion Sort ) para a
ordenao crescente do agregado. Durante o processo de ordenao, o agregado est
dividido em duas partes. A parte inicial do agregado est ordenada e a parte final est
desordenada. O algoritmo comea por considerar que o primeiro elemento do agregado j
est ordenado e depois ordena os restantes elementos do agregado, ou seja, desde o
segundo ao ltimo elemento. Cada elemento a ordenar previamente copiado para uma
varivel temporria, de forma a abrir uma posio livre que permita o deslocamento dos
elementos que esto atrs dele e que so maiores do que ele. Enquanto houver elementos
na parte ordenada do agregado, ou seja, elementos que esto atrs do elemento a ordenar
que sejam maiores do que ele, estes elementos so deslocados uma posio para a frente.
Quando o deslocamento dos elementos terminar, o elemento a ordenar colocado na
posio que foi aberta pelo deslocamento dos elementos. Se o elemento j estiver na
posio correcta, porque maior do que todos os que esto atrs de si, ento no h
deslocamento de elementos e o elemento colocado sobre ele prprio. Nesta situao, so
feitas duas instrues de cpia inutilmente. Esta ineficincia caracterstica desta
implementao do algoritmo de insero. O algoritmo faz praticamente tantas
comparaes como deslocamentos. Mais concretamente, para ordenar cada elemento, faz
mais uma comparao, para poder detectar o fim dos deslocamentos.
void Insertion_Sort (int seq[], unsigned int nelem)
{
unsigned int indi, indd; int temp;
for (indi = 1; indi < nelem; indi++)
{
temp = seq[indi];
/* copiar o elemento a ordenar */
/* deslocar os elementos atrs dele que lhe so maiores */
for (indd = indi; indd > 0 && seq[indd-1] > temp; indd--)
seq[indd] = seq[indd-1];
seq[indd] = temp;

/* inserir o elemento a ordenar na posio */

}
}

Figura 6.23 - Algoritmo de ordenao Insero.

Para comparar este algoritmo com os anteriores, temos que ter em considerao, que para
ordenar cada elemento, so precisas duas instrues de atribuio. Uma para copiar o
elemento para uma varivel temporria e outra para o inserir na posio definitiva. E o
deslocamento de cada elemento custa ainda uma instruo de atribuio. Pelo que, o
nmero de instrues de atribuio NA = ND + 2 * (N1). Como cada instruo de
atribuio equivalente, a um tero das instrues de atribuio que so necessrias para
efectuar a troca de dois elementos do agregado, nos algoritmos anteriores baseados em
trocas, ento temos que NT = ( ND + 2 * (N1) )/3.
A Figura 6.24 apresenta a execuo do algoritmo para um agregado com 10 elementos. O
algoritmo faz um total de 34 comparaes e de 43 instrues de atribuio para efectuar os

23

CAPTULO 6 : PESQUISA E ORDENAO

25 deslocamentos de elementos que so necessrios para ordenar o agregado, o que


equivalente a aproximadamente 14 trocas.
209
ordenar
o
segundo
elemento

25

15

42

32

55

145

25

15

42

32

55

145

I
209

330
PI

NC=1 ND=0
ordenar
o
terceiro
elemento

330

PI

o segundo elemento j est no stio


I

209

330

25

15

42

32

55

145

NC=3 ND=2

25

209

330

15

42

32

55

145

ordenar
o
quarto
elemento

PI

42

32

55

145

32

55

145

NC=4 ND=3
ordenar
o
quinto
elemento

25

209

330

15

15

25

209 330

42

PI

15

25

209 330

42

32

55

145

NC=3 ND=2

15

25

42

330

32

55

145

ordenar
o
sexto
elemento

PI
15

25

42

209

330

32

55

145

NC=6 ND=5

15

25

42

209

330

32

55

145

ordenar
o
stimo
elemento
NC=4 ND=3
ordenar
o
oitavo
elemento
NC=7 ND=6
ordenar
o
nono
elemento
NC=3 ND=2
ordenar
o
dcimo
elemento

209

PI

15

25

42

209

330

32

55

145

15

25

32

42

209

330

55

145

PI

15

25

32

42

209

330

55

145

15

25

32

42

209

330

55

145

PI

15

25

32

42

209

330

55

145

15

25

32

42

55

209

330

145

PI

15

25

32

42

55

209

330

145

NC=3 ND=2

15

25

32

42

55

145

209

330

TOTAL
NC = 34
NA = 43

15

25

32

42

55

145

209

330

Figura 6.24 - Execuo do algoritmo de ordenao Insero.

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

24

Para um agregado com N elementos, este algoritmo faz no pior caso (N2N)/2
comparaes e deslocamentos, mas, em termos mdios faz aproximadamente N 2/4
comparaes e deslocamentos, pelo que, pertence classe O(N 2). Se o agregado estiver
parcialmente desordenado este o algoritmo de ordenao mais indicado.

6.2.4 Comparao dos algoritmos de ordenao


A Tabela 6.1 apresenta os resultados dos algoritmos de ordenao apresentados. O pior
algoritmo o Sequencial, j que faz o nmero mximo de comparaes necessrias para
ordenar um agregado, e faz trocas s cegas, fazendo por vezes trocas que so feitas e
refeitas vezes sem conta. O algoritmo Seleco permite minimizar o nmero de trocas e
neste aspecto o melhor de todos, compensando desta forma o excesso de comparaes.
O algoritmo Bolha tem um desempenho mau, a no ser quando o agregado est
parcialmente desordenado, o que no o caso do exemplo apresentado. A primeira verso
do algoritmo Concha tem, para o exemplo utilizado, um nmero de comparaes maior do
que os outros algoritmos, se bem que com um nmero de trocas baixo. Mas, no caso da
segunda verso, ele o melhor algoritmo em nmero de comparaes. Como o resultado
deste algoritmo depende muito da srie de incrementos utilizado, estes valores podem
eventualmente ser melhorados com outra srie de incrementos. O nmero de comparaes
s desce abaixo de N2/2, para os algoritmos Concha (2 verso), Crivo e Insero. O
primeiro tem um nmero de trocas aceitvel, o segundo tem um nmero de trocas elevado,
enquanto que o terceiro tem um nmero de trocas pior do que o Concha (1 verso), mas
que aceitvel se tivermos em conta a reduo do nmero de comparaes. Portanto, os
algoritmos Insero e Concha (2 verso) so os melhores algoritmos analisados.
Algoritmo
N de Comparaes N de Trocas
Sequencial
45
25
Seleco
45
7
Bolha
45
25
Concha (1 verso)
52
11
Concha (2 verso)
29
18
Crivo
39
25
Insero
34
14
Tabela 6.1 - Comparao dos algoritmos de ordenao.

Para a escolha da verso do algoritmo Concha, devemos optar pela primeira verso se a
funo de comparao dos elementos do agregado for simples, por exemplo uma
comparao numrica e se os elementos do agregado forem estruturas pesadas, ou seja,
com muitos bytes, com vista a minorar o nmero de trocas. Se pelo contrrio, a funo de
comparao dos elementos do agregado for pesada, por exemplo uma comparao
alfanumrica com muitos caracteres e se os elementos do agregado forem tipos simples,
estruturas pequenas, ou ponteiros, ento devemos optar pela segunda verso com vista a
minorar o nmero de comparaes. Estas consideraes aplicam-se igualmente escolha
de qualquer algoritmo de ordenao.
Para a anlise dos algoritmos devemos ter em conta que o pior caso na ordenao de um
agregado acontece quando o agregado est invertido em relao ordenao pretendida,
enquanto que o melhor caso acontece quando o agregado est ordenado de acordo com a
ordenao pretendida. Para avaliar o caso mdio deve-se utilizar agregados gerados
aleatoriamente e fazer uma estimativa mdia.

25

CAPTULO 6 : PESQUISA E ORDENAO

6.3 Ordenao por fuso


Uma forma de optimizar a ordenao de um agregado com muitos elementos, consiste em
partir o agregado em vrios agregados mais pequenos, orden-los separadamente e depois
fundi-los num agregado nico. Para fundir dois ou mais agregados j ordenados, usa-se um
algoritmo de ordenao por fuso. A Figura 6.25 apresenta o algoritmo de Ordenao
Fuso de Listas ( Merge List Sort ) para a fuso de dois agregados ordenados por ordem
crescente, considerando que eles no tm elementos repetidos. O agregado de sada tem
que ter capacidade suficiente para armazenar os elementos dos dois agregados de entrada.
Comea-se por apontar para o primeiro elemento dos agregados a fundir, ou seja, para o
ndice 0. O ndice do agregado de sada indk aponta sempre para a primeira posio livre
do agregado, que inicialmente a posio zero. Enquanto existirem elementos nos dois
agregados, compara-se o elemento do agregado seqa, de ndice indi, com o elemento do
agregado seqb, de ndice indj. O menor dos valores copiado para o agregado de sada
seqc, cujo ndice indk previamente incrementado para contabilizar mais um elemento. O
ndice do agregado, cujo elemento copiado para o agregado de sada tambm
incrementado de uma posio, para sinalizar que o elemento j foi ordenado. Quando um
dos agregados de entrada estiver esgotado, copiam-se os restantes elementos do outro
agregado para o agregado de sada. No final da fuso, o indicador de elementos do
agregado de sada nelemc, que passado por referncia, deve conter o nmero de
elementos armazenados no agregado, que igual a nelema mais nelemb, uma vez que
estamos a considerar que no existem elementos repetidos nos agregados de entrada.
void Merge_List_Sort (int seqa[], unsigned int nelema,\
int seqb[], unsigned int nelemb,\
int seqc[], unsigned int *nelemc)
{
unsigned int indi = 0, indj = 0, indk = 0, indc;
/* copiar o elemento do agregado A ou o elemento do agregado B */
while (indi < nelema && indj < nelemb)
if (seqa[indi] < seqb[indj])
seqc[indk++] = seqa[indi++];
else seqc[indk++] = seqb[indj++];
if (indi < nelema)
for (indc = indi; indc < nelema; indc++)
seqc[indk++] = seqa[indc];
/* copiar os restantes elementos do agregado A */
else for (indc = indj; indc < nelemb; indc++)
seqc[indk++] = seqb[indc];
/* copiar os restantes elementos do agregado B */
*nelemc = indk; /* armazenar o nmero de elementos do agregado C */
}

Figura 6.25 - Algoritmo de ordenao Fuso de Listas.

Este algoritmo muito verstil e eficiente, uma vez que, pode ser generalizado para fazer a
fuso de mais do que dois agregados. No caso da existncia de elementos repetidos nos
agregados de entrada, podemos considerar solues alternativas, em que a ocorrncia de
elementos repetidos eliminada ou mantida. Tambm pode ser utilizado como algoritmo
de ordenao externa para fundir dois ou mais ficheiros. Para executar a fuso de dois
agregados j ordenados, com N/2 elementos cada, este algoritmo faz no melhor caso N/2
comparaes e no pior caso N1 comparaes.

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

26

A Figura 6.26 apresenta a execuo do algoritmo na fuso de dois agregados previamente


ordenados, com 5 elementos cada. O algoritmo faz 8 comparaes, at que o agregado B
fica esgotado, aps o qual, os restantes elementos do agregado A so copiados para o
agregado C.
I
antes
da
ordenao
comear

J
32

42

209

330

15

25

55

145

25

55

145

25

55

145

C
K

depois
de copiar
o primeiro
elemento
do
agregado B

depois
de copiar
o primeiro
elemento
do
agregado A

depois
de copiar
mais dois
elementos
do
agregado B

depois
de copiar
mais dois
elementos
do
agregado A

depois
de esgotar
os
elementos
do
agregado B
depois
de copiar
os
restantes
elementos
do
agregado A

I
A

J
32

42

209

330

15

K
I
A

32

J
42

209

330

15

K
I
A

32

42

209

15

25

330

15

25

55

145

K
I

32

42

209

330

15

25

32

42

15

25

55

145

K
I

32

42

209

330

15

25

32

42

2
55

15

25

55

145

145
K

32

42

209

330

15

25

32

42

2
55

15
145

25
209

55

145

330

Figura 6.26 - Execuo do algoritmo de ordenao Fuso de Listas.

27

CAPTULO 6 : PESQUISA E ORDENAO

6.4 Algoritmos de ordenao recursivos


Os algoritmos de ordenao recursivos so algoritmos de ordenao que aplicam o
princpio do dividir para conquistar. So algoritmos complexos, mas muito eficientes e
pertencem classe O(N log2 N). Vamos apresentar dois algoritmos.

6.4.1 Algoritmo de ordenao Fuso


O algoritmo de Ordenao Fuso ( Merge Sort ) pode ser utilizado de forma recursiva para
ordenar um agregado, partindo-o sucessivamente ao meio e fazendo a fuso dos agregados
parcelares j ordenados. Este princpio quando aplicado recursivamente, at que os
agregados obtidos so apenas compostos por um nico elemento, acaba por ordenar o
agregado. Esta verso recursiva, para a ordenao crescente do agregado, apresentada na
Figura 6.27.
void Merge_Sort (int seq[], unsigned int inicio, unsigned int fim)
{
unsigned int medio;
if (inicio < fim)
{
medio = (inicio + fim) / 2;

/* condio de paragem */
/* partio do agregado */

/* invocao recursiva para ordenar a primeira metade do agregado */


Merge_Sort (seq, inicio, medio);
/* invocao recursiva para ordenar a segunda metade do agregado */
Merge_Sort (seq, medio+1, fim);
/* fuso das duas metades ordenadas do agregado */
Merge_List_Sort (seq, inicio, medio, fim);
}
}

void Merge_List_Sort (int seq[], unsigned int inicio,\


unsigned int medio, unsigned int fim)
{
unsigned int inica = inicio, inicb = medio+1, indi = 0, indc;
/* atribuio de memria para o agregado local */
int *seqtemp = (int *) calloc (fim-inicio+1, sizeof (int));
while (inica <= medio && inicb <= fim)
if (seq[inica] < seq[inicb])
seqtemp[indi++] = seq[inica++];
else seqtemp[indi++] = seq[inicb++];

/* elemento da 1 parte */
/* elemento da 2 parte */

/* copiar os restantes elementos da primeira parte do agregado */


while (inica <= medio) seqtemp[indi++] = seq[inica++];
/* copiar os restantes elementos da segunda parte do agregado */
while (inicb <= fim) seqtemp[indi++] = seq[inicb++];
/* copiar o resultado para o agregado a ordenar */
for (indc = 0, inica = inicio; indc < indi; indc++, inica++)
seq[inica] = seqtemp[indc];
free (seqtemp);

/* libertao da memria do agregado local */

Figura 6.27 - Algoritmo de ordenao Fuso.

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

28

A invocao inicial da funo de ordenao Merge_Sort (seq, 0, nelem-1);. Cada


invocao da funo Merge_Sort precisa de saber o primeiro e o ltimo elementos do
agregado parcelar que est a ser ordenado, pelo que, o procedimento tem como parmetros
de entrada o incio e o fim do agregado. A funo Merge_List_Sort que constitui a parte
no recursiva do algoritmo faz a fuso das duas partes j ordenadas para um agregado local
e no fim copia-o de volta para o agregado a ordenar. uma verso ligeiramente diferente
do algoritmo apresentado na Figura 6.25, uma vez que faz a fuso de duas partes do
mesmo agregado em vez de dois agregados distintos. Da que tem como parmetros de
entrada os limites das duas partes do agregado, definidos pelos elementos inicial, mdio e
final do agregado. O agregado local atribudo dinamicamente com o tamanho necessrio
para fazer a fuso das duas metades do agregado e libertado quando no mais necessrio.
Para simplificar no feita a validao da atribuio de memria.
Para comparar este algoritmo com os algoritmos que ordenam fazendo trocas dos
elementos do agregado a ordenar, temos que ter em considerao, que para ordenar cada
elemento preciso copi-lo de e para o agregado. Cada cpia custa uma instruo de
atribuio, o que equivalente, a um tero das instrues de atribuio que so necessrias
para efectuar a troca de dois elementos do agregado. Ou seja, NT = NA/3.
A Figura 6.28 apresenta a execuo do algoritmo para um agregado com 10 elementos. O
algoritmo faz um total de 21 comparaes para fundir os agregados parcelares e executa 68
instrues de atribuio, ou seja, cpias de valores entre o agregado a ordenar e o agregado
local da funo de fuso de listas, metade das quais se devem ao facto da necessidade de
usar um agregado extra para fazer a fuso. Este nmero de cpias equivalente a
aproximadamente 23 trocas.
Quando comparado com os algoritmos, cujos resultados esto coligidos na Tabela 6.1, ele
o melhor algoritmo de ordenao no que diz respeito ao nmero de comparaes. Tem, no
entanto, um nmero elevado de trocas quando comparado com os algoritmos Insero e
Concha (2 verso), que so os algoritmos com o melhor desempenho global de todos os
algoritmos apresentados anteriormente.
Para executar a ordenao de um agregado com N elementos, sendo N mltiplo de 2, este
algoritmo faz no melhor caso (N log2 N)/2 comparaes e no pior caso N log2 N N + 1
comparaes, pelo que, pertence classe O(N log2 N).

6.4.2 Algoritmo de ordenao Rpido


O algoritmo de Ordenao Rpido ( Quick Sort ) foi criado em 1960 por C. A. R. Hoare e
utiliza um principio muito simples que quando aplicado recursivamente, acaba por ordenar
o agregado. O princpio o seguinte. Vamos escolher um elemento do agregado, que
vamos designar por pivot e dividir o agregado em duas partes. Na parte da esquerda
colocam-se os valores menores do que o pivot e na parte direita colocam-se os valores
maiores do que o pivot. Se cada parte do agregado for sucessivamente dividida ao meio e
for aplicado este princpio de separao dos elementos, ento quando o processo recursivo
terminar ao se atingir agregados com trs ou menos elementos, o agregado est ordenado
por ordem crescente.
A Figura 6.29 apresenta o algoritmo para a ordenao crescente do agregado.

29

CAPTULO 6 : PESQUISA E ORDENAO

209

209

209

209

209

209

25

15

TOTAL
NC = 21
NA = 68

330

330

330

330

25

25

25

25

15

15

15

15

42

42

42

42

330

25

32

42

209

15

42

25

42

32

55

55

55

145

145

145

32

330

145

32

15

55

32

32

330

330

209

32

55

32

145

32

55

55

145

145
d

209 330

Figura 6.28 - Execuo do algoritmo de ordenao Fuso.

Se o nmero de elementos do agregado for menor do que dois, ento o agregado est
automaticamente ordenado. Se existirem apenas dois elementos, eles so ordenados atravs
de uma simples comparao e eventual troca dos dois elementos. Quando o agregado tem
mais do que dois elementos, escolhido o elemento de ndice mdio para servir de pivot.
Os elementos das extremidades e o pivot so trocados para ficarem por ordem crescente.
Se s existirem trs elementos, ento esta operao constituda por trs comparaes e
eventuais trs trocas, ordena o agregado. Seno, h que assegurar que todos os elementos
esquerda do pivot so menores do que ele e que todos os elementos sua direita so
maiores do que ele. Os elementos que se encontrem fora do stio so trocados da parte
direita para a parte esquerda e vice-versa, ou ento so trocados com o pivot provocando
que ele se desloque mais para a esquerda ou mais para a direita do que a posio mdia
inicialmente calculada. Pelo que, quando o processamento se separao terminar no h
garantia que o agregado est partido em duas metades com um nmero semelhante de
elementos. Aps este processamento, o algoritmo invocado recursivamente para o
agregado constitudo pelos elementos esquerda do pivot e para o agregado constitudo

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

30

pelos elementos direita do pivot. A invocao inicial da funo de ordenao


Quick_Sort (seq, 0, nelem-1);.
void Quick_Sort (int seq[], unsigned int inicio, unsigned int fim)
{
unsigned int medio, nelem = fim-inicio+1, indi, indj;
if (nelem <= 1) return;
/* o agregado tem no mximo 1 elemento */
if (nelem == 2)
/* o agregado s tem 2 elementos */
{
if (seq[inicio] > seq[fim]) Swap (&seq[inicio], &seq[fim]);
return;
}
medio = (inicio + fim) / 2;

/* clculo do ndice do pivot */

/* colocar os extremos e o pivot por ordem crescente */


if (seq[inicio] > seq[medio]) Swap (&seq[inicio], &seq[medio]);
if (seq[inicio] > seq[fim]) Swap (&seq[inicio], &seq[fim]);
if (seq[medio] > seq[fim]) Swap (&seq[medio], &seq[fim]);
/* se o agregado s tem 3 elementos, ento j est ordenado */
if (nelem == 3) return;
indi = inicio+1; indj = fim-1;
while (indi < indj)
{
/* procurar elementos na parte esquerda maiores do que pivot */
while (indi < medio)
if (seq[indi] > seq[medio]) break; else indi++;
/* procurar elementos na parte direita menores do que pivot */
while (indj > medio)
if (seq[indj] < seq[medio]) break; else indj--;
if (indi != medio && indj != medio)
Swap (&seq[indi], &seq[indj]);

/* trocar os elementos */

else if (indi == medio && indj != medio)


{
/* trocar o elemento de ndice indj com o pivot */
/* e deslocar o pivot uma posio para a direita */
Swap (&seq[medio++], &seq[indj]);
Swap (&seq[medio], &seq[indj]);
indi = medio;
}
else if (indi != medio && indj == medio)
{
/* trocar o elemento de ndice indi com o pivot */
/* e deslocar o pivot uma posio para a esquerda */
Swap (&seq[medio--], &seq[indi]);
Swap (&seq[medio], &seq[indi]);
indj = medio;
}
}
/* invocao recursiva para a parte esquerda do agregado */
Quick_Sort (seq, inicio, medio-1);
/* invocao recursiva para a parte direita do agregado */
Quick_Sort (seq, medio+1, fim);
}

Figura 6.29 - Algoritmo de ordenao Rpido (1 verso).

31

CAPTULO 6 : PESQUISA E ORDENAO

A Figura 6.30 apresenta a execuo do algoritmo para um agregado com 10 elementos. Os


elementos que servem de pivot, aparecem a cheio para se distinguirem dos restantes
elementos e as invocaes recursivas so identificadas pela sigla IR. O algoritmo faz um
total de 28 comparaes e de 19 trocas de elementos. Como exemplo, vamos ver como se
comporta o algoritmo quando a funo invocada. Uma vez que o agregado inicial tem
dez elementos, de ndices 0 a 9, o elemento de ndice 4, cujo valor 42, escolhido para
pivot. Ao ordenar os valores 209, 42 e 145, o valor 145 passa a ser o novo pivot, porque
o valor mediano. Todos os valores menores do que 145 so colocados sua esquerda,
enquanto que, todos os valores maiores do que 145 so colocados sua direita. Pelo que, o
pivot vai ser deslocado para a posio de ndice 7. Depois so feitas as invocaes
recursivas dos agregados formados pelos elementos 0 a 6 e pelos elementos 8 a 9.
Quando comparado com os algoritmos anteriores, constatamos que ele tem um
desempenho praticamente semelhante ao Concha (2 verso) e globalmente equivalente ao
Fuso, com a vantagem de no necessitar de um agregado auxiliar. Quando comparado
com o algoritmo de Insero, ele tem um maior nmero de trocas compensado com um
menor nmero de comparaes, tendo no entanto uma implementao mais complexa.
209

330

25

15

42

32

55

145

209

330

25

15

42

32

55

145

145

330

209

aps processamento
42

55

25

15
I

42

55

25

32

15

I
8

32

209

330

209

330

aps processamento
15

2
I

15

25

32

55
I

25

42
R

42

55

aps processamento
2
I

8
R

TOTAL
NC = 28
NT = 19

25

15
R

15

25

15

25

15

25

32

42

55

15

25

32

42

55

145

Figura 6.30 - Execuo do algoritmo de ordenao Rpido.

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

32

Com o objectivo de diminuir o nmero de trocas, existe uma verso optimizada, que se
apresenta na Figura 6.31 e que frequentemente apresentada na literatura. A optimizao
consiste em evitar as trocas que envolvem o pivot e o deslocam da posio inicial. Aps a
escolha do pivot ele escondido na penltima posio do agregado. Os elementos so
analisados e trocados at serem todos comparados com o pivot. Quando a anlise do
agregado terminar, ou seja, quando indi for maior ou igual do que indj, o pivot colocado
no stio, por troca com o elemento que est na posio mais esquerda do agregado que
maior do que ele, ou seja, com o elemento que est na posio indi. Esta verso do
algoritmo faz um total de 30 comparaes e de 17 trocas de elementos. Quando
comparado com a primeira verso temos mais 2 comparaes, mas menos 2 trocas.
void Quick_Sort (int seq[], unsigned int inicio, unsigned int fim)
{
unsigned int medio, nelem = fim-inicio+1, indi, indj;
if (nelem <= 1) return;
/* o agregado tem no mximo 1 elemento */
if (nelem == 2)
/* o agregado s tem 2 elementos */
{
if (seq[inicio] > seq[fim]) Swap (&seq[inicio], &seq[fim]);
return;
}
medio = (inicio + fim) / 2;

/* clculo do ndice do pivot */

/* colocar os extremos e o pivot por ordem crescente */


if (seq[inicio] > seq[medio]) Swap (&seq[inicio], &seq[medio]);
if (seq[inicio] > seq[fim]) Swap (&seq[inicio], &seq[fim]);
if (seq[medio] > seq[fim]) Swap (&seq[medio], &seq[fim]);
/* se o agregado s tem 3 elementos, ento j est ordenado */
if (nelem == 3) return;
/* esconder o pivot na penltima posio do agregado */
Swap (&seq[medio], &seq[fim-1]);
indi = inicio; medio = indj = fim-1;
for ( ; ; )
{
/* procurar elementos na parte esquerda maiores do que pivot */
while (seq[++indi] < seq[medio]) ;
/* procurar elementos na parte direita menores do que pivot */
while (seq[--indj] > seq[medio]) ;
if (indi < indj) Swap (&seq[indi], &seq[indj]);

else break;

}
/* recuperar o pivot para a posio mdia do agregado */
medio = indi;
Swap (&seq[medio], &seq[fim-1]);
/* invocao recursiva para a parte esquerda do agregado */
Quick_Sort (seq, inicio, medio-1);
/* invocao recursiva para a parte direita do agregado */
Quick_Sort (seq, medio+1, fim);
}

Figura 6.31 - Algoritmo de ordenao Rpido (2 verso).

Para executar a ordenao de um agregado com N elementos, este algoritmo faz no pior
caso (N2N)/2 comparaes. No entanto, no caso mdio faz aproximadamente
1.4(N+1) log2 N comparaes, pelo que, considera-se que pertence classe O(N log2 N).

33

CAPTULO 6 : PESQUISA E ORDENAO

6.5 Generalizao dos algoritmos de ordenao


Os algoritmos de ordenao foram apresentados e aplicados sobre agregados de nmeros
inteiros, com vista a melhor visualizar o seu funcionamento. No entanto, em muitas
aplicaes h a necessidade de armazenar bases de dados em agregados e de as ordenar de
acordo com as diferentes caractersticas dos seus elementos. Por vezes existe mesmo a
necessidade de ordenar sucessivamente um agregado de registos por diversas chaves de
ordenao diferente, como por exemplo, fazer ordenaes alfabticas, cronolgicas e
numricas e fazer ordenaes crescentes ou ascendentes e decrescentes ou descendentes.
Como podemos ento implementar estas ordenaes recorrendo ao mesmo algoritmo de
ordenao? E de preferncia com o mnimo de esforo. Ou seja, como podemos
generalizar um algoritmo de ordenao para que ele possa ordenar um agregado com
elementos de um qualquer tipo de dados, ordenar por diferentes critrios e ordenar por
ordem crescente ou decrescente. Para poder generalizar um algoritmo de ordenao e de
modo a estruturar melhor a soluo, precisamos de um ficheiro de interface que defina o
tipo de elementos constituintes do agregado e que providencie as funes de comparao
de acordo com os diferentes critrios de ordenao que se pretendem efectuar.
Vamos utilizar o exemplo concreto que se apresenta na Figura 6.32. Vamos considerar que
se pretende gerir uma base de dados constituda por elementos com a seguinte informao.
O nmero de registo de uma pessoa na base de dados que do tipo inteiro, um nome que
uma cadeia de caracteres e uma data constituda por dia, ms e ano. Vamos considerar
ainda que pretendemos ordenar a base de dados por ordem numrica do nmero de
registo, por ordem alfabtica do nome e por ordem cronolgica da data. Assim sendo,
precisamos de implementar as trs funes de comparao CompNRegisto, CompNome e
CompData.
Para implementar no mesmo algoritmo de ordenao, a ordenao crescente ou ascendente
e decrescente ou descendente, vamos passar funo de ordenao o parmetro de entrada
adicional inteiro tord que representa o tipo de ordenao a efectuar. Vamos definir que este
parmetro indica que se pretende a ordenao crescente com o valor 1 e indica que se
pretende a ordenao decrescente com o valor 1.
Pelo que, as funes de comparao tm a particularidade de devolver um valor inteiro que
um dos seguintes trs valores: o valor 0 no caso dos elementos serem iguais conforme o
item comparado; o valor 1 no caso do primeiro elemento ser maior do que segundo
elemento conforme o item comparado, ou seja, um maior nmero de registo ou um nome
alfabeticamente posterior ou uma data maior, o que significa uma data mais recente; e o
valor 1 no caso do primeiro elemento ser menor do que segundo elemento conforme o
item comparado, ou seja, um menor nmero de registo ou um nome alfabeticamente
anterior ou uma data menor o que significa uma data mais antiga.
Finalmente, para poder generalizar os algoritmos de ordenao precisamos de um
mecanismo que permita utilizar uma qualquer funo de comparao dentro da funo de
ordenao, de acordo com a ordenao pretendida num dado momento do programa. Para
tal, a linguagem C providencia os ponteiros para funes. Um ponteiro para uma funo
permite invocar de uma forma simples e elegante diferentes funes. A seguinte instruo
define um ponteiro para uma funo inteira com dois parmetros de entrada do tipo
TElem, que reconhecido pelo identificador PtFComp.
typedef int (*PtFComp) (TElem, TElem);

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

34

/********** Interface da Estrutura de Dados do Agregado **********/


/* Nome: elemento.h */
/* Definio do tipo dos elementos do agregado e das funes de
comparao necessrias. Este ficheiro deve ser modificado para
adequar a definio e as funes a cada implementao especfica. */
#include <string.h>
#ifndef _ELEMENTO
#define _ELEMENTO
/************* Definio do Tipo de Dados do Elemento *************/
typedef struct
{
unsigned int dia;
unsigned int mes;
unsigned int ano;
} TData;
typedef struct
{
unsigned int nreg;
char nome[60];
TData data;
} TElem;
/***** Definio do Tipo Ponteiro para a Funo de Comparao *****/
typedef int (*PtFComp) (TElem, TElem);
/******* Definio das Funes de Comparao dos Elementos *******/
int CompNRegisto (TElem a, TElem b)
{
if ( a.nreg > b.nreg ) return 1;
else if ( a.nreg < b.nreg ) return -1;
else return 0;
}
int CompNome (TElem a, TElem b)
{
int comp = strcmp (a.nome, b.nome);
if ( comp > 0 ) return 1;
else if ( comp < 0 ) return -1;
else return 0;
}
int CompData (TElem a, TElem b)
{
if ( a.data.ano > b.data.ano ) return 1;
else if ( a.data.ano < b.data.ano) return -1;
else if ( a.data.mes > b.data.mes ) return 1;
else if (a.data.mes < b.data.mes ) return -1;
else if ( a.data.dia > b.data.dia ) return 1;
else if ( a.data.dia < b.data.dia ) return -1;
else return 0;
}
#endif

Figura 6.32 - Ficheiro de interface do elemento constituinte do agregado a ordenar.

35

CAPTULO 6 : PESQUISA E ORDENAO

A partir desta definio podem-se declarar variveis do tipo PtFComp e atribuir-lhe uma
qualquer funo, desde que seja uma funo inteira com dois parmetros de entrada do
tipo TElem. A Figura 6.34 apresenta um exemplo da passagem de um parmetro de
entrada deste tipo para uma funo de ordenao. A Figura 6.35 apresenta a utilizao de
uma varivel deste tipo para ser colocada a apontar para diferentes funes ao longo da
execuo do programa, de forma a parametrizar a ordenao de um agregado. Tal como
nos agregados, o nome de uma funo tambm um ponteiro para a funo, pelo que, a
instruo de atribuio fcomp = CompNome; coloca o ponteiro para funo fcomp a
apontar para a funo CompNome.
A funo de troca de elementos do agregado que foi apresentada na Figura 6.11, tem de ser
alterada de maneira a poder trocar dois elementos do tipo TElem. A Figura 6.33 apresenta
a nova verso que vamos designar por SwapElementos.
void SwapElementos (TElem *x, TElem *y)
{
TElem temp;
temp = *x; *x = *y; *y = temp;
}

Figura 6.33 - Funo para trocar dois elementos de um agregado de elementos do tipo TElem.

A Figura 6.34 apresenta a verso generalizada do algoritmo de ordenao Sequencial.


Escolhemos este algoritmo, apenas porque o mais simples em termos de cdigo. O
primeiro parmetro da funo o parmetro de entrada-sada que representa o agregado a
ordenar, o segundo parmetro o parmetro de entrada que representa o nmero de
elementos do agregado, o terceiro parmetro o parmetro de entrada que representa a
funo de comparao e o quarto parmetro o parmetro de entrada que representa o
tipo de ordenao a efectuar. Estes dois ltimos parmetros de entrada configuram o
algoritmo de ordenao, tornando-o assim genrico e reutilizvel.
void Sequential_Sort (TElem seq[], unsigned int nelem,\
PtFComp fcomp, int tord)
{
unsigned int indi, indj;
for (indi = 0; indi < nelem-1; indi++)
for (indj = indi+1; indj < nelem; indj++)
if ( fcomp (seq[indi], seq[indj]) == tord )
SwapElementos (&seq[indi], &seq[indj]);
}

Figura 6.34 - Algoritmo de ordenao Sequencial generalizado.

A Figura 6.35 apresenta a utilizao sucessiva deste algoritmo de ordenao, para ordenar
um agregado de elementos do tipo TElem. Antes de cada invocao, atribudo ao
ponteiro fcomp a funo de comparao necessria para obter a ordenao desejada e
depois a funo de ordenao invocada indicando tambm o tipo de ordenao
pretendido. Para aumentar a legibilidade do programa, definimos as constantes simblicas
CRESCENTE e DECRESCENTE.
Como exerccio de treino escreva a funo Display, cuja funcionalidade imprimir no
monitor a informao relativa a um agregado de estruturas do tipo TElem, com o
prottipo Display (TElem seq[], unsigned int nelem); e teste o programa.

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

36

#include <stdio.h>
#include <stdlib.h>
#include "elemento.h"

/* caracterizao do tipo elemento */

#define CRESCENTE 1
#define DECRESCENTE -1
void SwapElementos (TElem *, TElem *);
void Sequential_Sort (TElem [], unsigned int, PtFComp, int);
void Display (TElem [], unsigned int);
int main (void)
{
TElem pintores[] = {
{
{
{
{
{

1,
2,
3,
4,
5,

"Vincent Van Gogh", {30, 3, 1853} },


"Vieira da Silva", {13, 6, 1908} },
"Amedeo Modigliani", {12, 7, 1884} },
"Claude Monet", {14, 11, 1840} },
"Georgia O'Keeffe", {15, 11, 1887} }

};
int nelem = sizeof (pintores) / sizeof (pintores[0]);
/* ponteiro para a funo de comparao inicializado a NULL */
PtFComp fcomp = NULL;
fcomp = CompNome;
/* ordenao alfabtica ascendente */
Sequential_Sort (pintores, nelem, fcomp, CRESCENTE);
printf ("Ordenao Alfabtica Ascendente\n");
Display (pintores, nelem);
fcomp = CompData;
/* ordenao cronolgica decrescente */
Sequential_Sort (pintores, nelem, fcomp, DECRESCENTE);
printf ("Ordenao Cronolgica Decrescente\n");
Display (pintores, nelem);
fcomp = CompNRegisto;
/* ordenao crescente por registo */
Sequential_Sort (pintores, nelem, fcomp, CRESCENTE);
printf ("Ordenao Numrica Crescente\n");
Display (pintores, nelem);
return EXIT_SUCCESS;
}
...

/* Definio das funes */

Figura 6.35 - Exemplo da utilizao sucessiva do algoritmo de ordenao Sequencial.

6.6 Avaliao do desempenho dos algoritmos


Para podermos avaliar o desempenho dos algoritmos de ordenao necessrio
contabilizar o nmero de operaes de comparao e o nmero de operaes de trocas de
elementos. Ou, uma vez que existem algoritmos de ordenao que em vez de trocas fazem
cpias de elementos, contabilizar em alternativa o nmero de instrues de atribuio, de
maneira a ter uma mtrica uniforme.
Para calcular o nmero de operaes efectuadas em invocaes sucessivas das funes de
troca e de comparao de elementos, utilizam-se variveis contadoras de durao
permanente, ou seja, variveis que so declaradas com o qualificativo static. Como a
funo de ordenao pode ser repetidamente invocada, ento conveniente poder reiniciar
a contagem, assim como necessrio reportar o resultado final da contagem. Pelo que, as

37

CAPTULO 6 : PESQUISA E ORDENAO

funes tm o parmetro de entrada modo de tipo inteiro, que indica o modo de actuao
sobre a varivel contadora.
De maneira a aumentar a legibilidade das funes aconselhvel utilizar constantes
simblicas que representam o modo de actuao sobre a varivel contadora. Temos as trs
constantes simblicas seguintes: REP para reportar o valor da varivel contadora; INIC
para inicializar a varivel contadora; e NORM para realizar a operao da funo e para
incrementar o valor da varivel contadora.
Para calcular o nmero de instrues de atribuio utiliza-se uma varivel de durao
permanente na funo SwapCount, tal como se mostra na Figura 6.36. No modo NORM a
funo troca os dois elementos e incrementa o valor da varivel contadora de trs unidades,
para contabilizar as trs instrues de atribuio.
unsigned int SwapCount (TElem *x, TElem *y, int modo)
{
static unsigned int cont;
/* varivel contadora */
TElem temp;
if (modo == REP) return cont;
else if (modo == INIC) cont = 0;
else if (modo == NORM)
{
temp = *x; *x = *y; *y = temp;
/* efectuar a troca */
cont += 3; /* contagem das 3 instrues de atribuio */
}
return 0;
}

Figura 6.36 - Funo para trocar elementos do agregado com contabilizao de atribuies.

Para calcular o nmero de comparaes utiliza-se uma varivel de durao permanente em


conjuno com a funo de comparao pretendida. Assim encapsula-se a funo de
comparao dentro de uma nova funo CCount, tal como se mostra na Figura 6.37, que
alm de efectuar a comparao pretendida, usando para o efeito o ponteiro para a funo
de comparao, tambm contabiliza o nmero de vezes que invocada. Os elementos a
comparar so passados funo por referncia, ou seja, atravs de ponteiros. No modo
NORM a funo compara os dois elementos e incrementa a varivel contadora uma
unidade, para contabilizar mais uma comparao.
int CCount (TElem *x, TElem *y, PtFComp fcomp, int modo)
{
static unsigned int cont;
/* varivel contadora */
if (modo == REP) return cont;
else if (modo == INIC)
{
cont = 0; return 0;
}
else if (modo == NORM)
{
cont++;
/* contagem de 1 instruo de comparao */
return fcomp (*x, *y);
/* efectuar a comparao */
}
}

Figura 6.37 - Funo para comparar elementos do agregado com contabilizao de comparaes.

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

38

A Figura 6.38 apresenta a verso generalizada e com contabilizao de instrues do


algoritmo de ordenao Sequencial. Antes da ordenao comear, as funes SwapCount e
CCount so invocadas para inicializar as variveis contadoras. Durante a ordenao as
funes so invocadas no modo normal para desempenharem a sua tarefa. Depois da
ordenao terminar, as funes so invocadas para reportar o nmero de comparaes e de
instrues de atribuio efectuadas durante a ordenao, que so armazenados nos
parmetros de sada nc e na respectivamente. Quando as funes so invocadas para
inicializar ou reportar a varivel contadora, os restantes parmetros so passados como
sendo ponteiros nulos.
void Sequential_Sort (TElem seq[], unsigned int nelem,\
PtFComp fcomp, int tord, int *nc, int *na)
{
unsigned int indi, indj;
/* inicializao das variveis contadoras */
CCount ((TElem *) NULL, (TElem *) NULL, (PtFComp) NULL, INIC);
SwapCount ((TElem *) NULL, (TElem *) NULL, INIC);
/* execuo da ordenao com contabilizao das operaes */
for (indi = 0; indi < nelem-1; indi++)
for (indj = indi+1; indj < nelem; indj++)
if (CCount (&seq[indi], &seq[indj], fcomp, NORM) == tord )
SwapCount (&seq[indi], &seq[indj], NORM);
/* relatrio das variveis contadoras */
*nc = CCount ((TElem *) NULL, (TElem *) NULL, (PtFComp) NULL, REP);
*na = SwapCount ((TElem *) NULL, (TElem *) NULL, REP);
}

Figura 6.38 - Algoritmo de ordenao Sequencial generalizado e com contabilizao de operaes.

Como exerccio de treino altere o programa apresentado na Figura 6.35 de modo a utilizar
estas novas verses das funes SwapCount, CCount e Sequential_Sort. Acrescente ainda
ao programa a impresso no monitor do nmero de comparaes e do nmero de trocas,
que so um tero das instrues de atribuio, depois de cada ordenao.

6.7 Exerccios
1. Pretende-se escrever uma funo de ordenao Fuso de Listas que contemple a
situao da existncia de elementos repetidos nos agregados de entrada, e que nessa
situao copie apenas um dos elementos repetidos para o agregado de sada.

6.8 Leituras recomendadas


x 13 captulo do livro Data Structures, Algorithms and Software Principles in C, de
Thomas A: Standish, da editora Addison-Wesley Publishing Company, 1995.
x 7 captulo do livro Data Structures and Algorithm Analysis in C, 2 edio, de Mark
Allen Weiss, da editora Addison-Wesley Publishing Company, 1997.

Captulo 7
FILAS E PILHAS

Sumrio
Este captulo dedicado s estruturas de dados lineares que so as filas e as pilhas.
Apresentamos as implementaes esttica e semiesttica baseadas em agregados e a
implementao dinmica baseada em listas ligadas. Mostramos exemplos de aplicao que
utilizam filas e pilhas como elementos de armazenamento e que tiram partido da sua
organizao interna para a resoluo de problemas. Finalmente apresentamos uma
implementao abstracta, dinmica e com capacidade de mltipla instanciao.

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

7.1 Introduo
As filas e as pilhas so estruturas de dados lineares que tm implementaes em tudo
semelhantes, sendo que diferem apenas na prioridade da retirada de informao. Enquanto
que uma fila implementa a poltica do primeiro a chegar primeiro a sair ( first in first out ), a
pilha implementa a poltica do ltimo a chegar primeiro a sair ( last in first out ).
As filas so muito usadas nos sistemas operativos para funcionarem como reas de
armazenamento de informao que deve ser processada por ordem de chegada, como por
exemplo, o atendimento de pedidos de utilizao de uma impressora de rede que recebe
ficheiros para imprimir, enviados por diferentes computadores instalados na rede. Para
sincronizar a interaco entre processos concorrentes que executam a diferentes
velocidades de processamento. So tambm utilizadas para simular modelos que descrevem
o comportamento de situaes reais de atendimento de pedidos que so processados por
ordem de chegada, como por exemplo, simular o atendimento de uma fila de espera num
qualquer servio do dia a dia.
As pilhas so usadas para gerir algoritmos em que existem processos que invocam
subprocessos do mesmo tipo, como o caso dos algoritmos recursivos. Para processar
estruturas imbricadas, ou seja, estruturas que contm outras estruturas do mesmo tipo
dentro delas, como por exemplo, expresses aritmticas que so compostas por
subexpresses aritmticas do mesmo tipo. Assim uma pilha pode ser usada para verificar o
balanceamento dos parnteses numa expresso aritmtica, que pode conter vrios nveis de
parnteses, que podem ser de tipos diferentes como parnteses curvos, rectos e chavetas.
Podem ser usadas para fazer a avaliao de expresses, que contm expresses internas que
tm de ser avaliadas previamente, antes do clculo da expresso final. Por exemplo, para
calcular uma expresso em notao polaca, que utiliza os smbolos das operaes depois
dos operadores. Para fazer a anlise sintctica durante a compilao de um programa, onde
existem ciclos repetitivos dentro de ciclos repetitivos, blocos dentro de blocos e portanto,
existem estruturas imbricadas que tm de estar balanceadas e correctamente imbricadas.
Vamos apresentar as implementaes esttica, semiesttica e dinmica de filas e de pilhas,
considerando que estamos perante estruturas de dados concretas, cujo tipo dos elementos
de armazenamento concretizado pelo utilizador atravs de um ficheiro de interface, que
vamos designar por elemento.h.
Este ficheiro de interface, que se apresenta na Figura 7.1, define a constante que
parametriza a dimenso da estrutura de dados de suporte, necessrio apenas no caso das
implementaes esttica e semiesttica, bem como o tipo de dados do elemento
constituinte da memria. No caso da implementao dinmica, este ficheiro precisa apenas
de definir o tipo de dados do elemento constituinte da memria. Assim o utilizador do
mdulo, pode concretiz-lo para uma estrutura de dados que corresponda s suas
necessidades, sem ter a necessidade de reprogramar o ficheiro de implementao do
mdulo. Em relao criao de um mdulo abstracto, esta soluo exige a recompilao
do mdulo, sempre que este ficheiro modificado.

CAPTULO 7 : FILAS E PILHAS

/*********** Interface da Estrutura de Dados do Mdulo ***********/


/* Nome: elemento.h */
/* Definio da dimenso da estrutura de dados para o caso das
implementaes esttica e semiesttica e definio do tipo de dados
dos seus elementos. Este ficheiro deve ser modificado para adequar a
definio a cada implementao especfica.
*/
#ifndef _ELEMENTO
#define _ELEMENTO
/****** Constantes de Parametrizao das Estruturas de Dados ******/
#define

N_ELEMENTOS

100

/* nmero de elementos do agregado */

/************* Definio do Tipo de Dados do Elemento *************/


typedef

...

TElem;

/* tipo de dados dos elementos */

#endif

Figura 7.1 - Ficheiro de interface do elemento constituinte da memria.

7.2 Filas
Uma memria fila ( queue/FIFO ) uma memria em que s possvel processar a
informao pela ordem de chegada, da que, tambm seja apelidada de memria do
primeiro a chegar primeiro a sair. Numa memria fila, o posicionamento para a colocao
de um novo elemento na fila, que vamos designar por Fifo_In, a cauda da fila (fifo tail ),
e o posicionamento para a remoo de um elemento da fila, que vamos designar por
Fifo_Out, a cabea da fila (fifo head ).

7.2.1 Implementao esttica


A Figura 7.2 apresenta o ficheiro de interface da implementao esttica de uma fila, que
baseada num agregado de elementos usado de forma circular. Os indicadores de cabea e
cauda da fila so variveis de tipo inteiro positivo.
Como um agregado tem uma dimenso fixa, antes de se colocar um elemento na fila
necessrio verificar se ela est cheia. Em caso afirmativo, mais nenhum elemento pode ser
colocado na fila e assinalada a situao de erro, usando o cdigo de erro FIFO_FULL. O
elemento que se pretende copiar para a fila passado funo Fifo_In por valor. Por
outro lado, o elemento que vai receber a cpia do elemento que se pretende retirar da fila
passado funo Fifo_Out por referncia. Pode acontecer que o ponteiro passado
funo seja um ponteiro nulo. Nestas circunstncias a funo no pode retirar o elemento
da fila, pelo que, no faz nada e assinala esta anomalia, usando o cdigo de erro
NULL_PTR. Antes de se retirar um elemento da fila preciso detectar se ela est vazia.
Em caso afirmativo, nenhum elemento pode ser retirado da fila e assinalada a situao de
erro, usando o cdigo de erro FIFO_EMPTY. Sempre que colocado ou retirado um
elemento da fila devolvido o cdigo OK sinalizando que a operao foi realizada com
sucesso.
De seguida vamos apresentar graficamente o comportamento das operaes de colocao
de um elemento na fila e de remoo de um elemento da fila.

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

/******************* Interface da FILA Esttica *******************/


/* Nome : fila_est.h */
#ifndef _FILA_ESTATICA
#define _FILA_ESTATICA
#include "elemento.h"

/* caracterizao do tipo elemento da fila */

/* Definio de Constantes */
#define
#define
#define
#define

OK
NULL_PTR
FIFO_EMPTY
FIFO_FULL

0
1
4
5

/*
/*
/*
/*

operao realizada com sucesso */


ponteiro nulo */
fila vazia */
fila cheia */

/* Aluso s Funes Exportadas pelo Mdulo */


int Fifo_In (TElem elemento);
/* Coloca o elemento elemento na cauda da fila. Valores de retorno:
OK ou FIFO_FULL. */
int Fifo_Out (TElem *pelemento);
/* Retira o elemento da cabea da fila para o elemento apontado por
pelemento. Valores de retorno: OK, NULL_PTR ou FIFO_EMPTY. */
#endif

Figura 7.2 - Ficheiro de interface da fila esttica.

A Figura 7.3 mostra o estado inicial da fila. Por uma questo de implementao, vamos
considerar que a cauda da fila indica sempre a primeira posio livre para a prxima
operao de colocao de um elemento na fila, pelo que, inicialmente aponta para a posio
0 do agregado, enquanto que a cabea da fila indica sempre a posio da prxima operao
de remoo de um elemento da fila, pelo que, inicialmente aponta para a posio N do
agregado, ou seja, a posio depois do fim do agregado, como indicao de fila vazia. A
Figura 7.3 mostra tambm a colocao do primeiro elemento na fila e o estado aps a
operao. A cabea da fila vai ficar a apontar para o elemento acabado de colocar, que a
posio 0 do agregado, e, fica nesta posio enquanto este elemento no for retirado da fila.
A cauda da fila deslocada para a posio 1 do agregado, que a primeira posio livre
para colocar o prximo elemento.

FIFO[0]

...

FIFO[N-1]

Elemento Elemento Elemento Elemento Elemento Elemento Elemento

cauda
da fila

cabea
da fila

estado inicial

FIFO[0]

...

FIFO[N-1]

Elemento Elemento Elemento Elemento Elemento Elemento Elemento

cabea
da fila

cauda
da fila

colocao do primeiro elemento

Figura 7.3 - Situao inicial da fila e aps a colocao do primeiro elemento.

CAPTULO 7 : FILAS E PILHAS

Sempre que se coloca um elemento na fila, a cauda da fila deslocada para o elemento
seguinte da fila. Existem duas situaes distintas. Na primeira situao, que se apresenta na
Figura 7.4, a posio seguinte da fila est ainda dentro da dimenso do agregado, pelo que,
a cauda continua atrs da cabea.

FIFO[0]

...

FIFO[N-1]

Elemento Elemento Elemento Elemento Elemento Elemento Elemento

cabea
da fila

FIFO[0]

cauda
da fila

colocao de um elemento

...

FIFO[N-1]

Elemento Elemento Elemento Elemento Elemento Elemento Elemento

cabea
da fila

cauda
da fila

Figura 7.4 - Colocao de um elemento na fila.

Na segunda situao, que se apresenta na Figura 7.5, a posio seguinte da fila est fora da
dimenso do agregado, pelo que, a cauda da fila passa para o incio do agregado e fica
frente da cabea. O que parece uma situao anmala quando comparada com o
funcionamento de uma fila de espera no dia a dia, em que a cabea est sempre frente da
cauda. Mas, preciso ter em considerao que estamos perante uma fila circular. Para
conseguir este efeito circular da cauda da fila utiliza-se o operador mdulo, para calcular
sempre um valor que est entre 0 e N_ELEMENTOS1. Em alternativa utilizao
circular do agregado, a fila poderia ser implementada de forma linear, o que implicaria
deslocar todos os elementos da fila, para o incio do agregado, sempre que se retirasse um
elemento da fila. Mas, tal implementao seria muito ineficiente para filas grandes.

FIFO[0]

...

FIFO[N-1]

Elemento Elemento Elemento Elemento Elemento Elemento Elemento

colocao de um elemento

FIFO[0]

cabea
da fila

cauda
da fila

...

FIFO[N-1]

Elemento Elemento Elemento Elemento Elemento Elemento Elemento

cauda
da fila

cabea
da fila

Figura 7.5 - Colocao de um elemento na fila com movimentao circular.

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

A Figura 7.6 apresenta a situao de colocao do ltimo elemento na fila. Nesta situao a
cauda da fila fica a apontar para o mesmo elemento que a cabea da fila, o que significa que
a fila ficou cheia. Enquanto este estado durar, no possvel colocar mais elementos na fila.

FIFO[0]

...

FIFO[N-1]

Elemento Elemento Elemento Elemento Elemento Elemento Elemento

cauda
da fila

cabea
da fila

colocao do ltimo elemento

FIFO[0]

...

FIFO[N-1]

Elemento Elemento Elemento Elemento Elemento Elemento Elemento

cabea da fila

FILA
CHEIA

cauda da fila

Figura 7.6 - Colocao do ltimo elemento na fila.

Sempre que um elemento retirado da fila, o indicador de cabea da fila deslocado para o
elemento seguinte. Como j foi referido, esta implementao no a forma habitual de
funcionamento de um fila, onde sempre que o elemento da cabea da fila sai da fila, toda a
fila deslocada para a frente. Existem duas situaes distintas quando se retira um
elemento da fila.
Na primeira situao, que se apresenta na Figura 7.7, a posio seguinte da fila est ainda
dentro da dimenso do agregado, pelo que, a cabea ainda no deu a volta ao agregado.
Neste caso ainda est atrs da cauda, porque a cauda j excedeu o fim do agregado.

FIFO[0]

...

FIFO[N-1]

Elemento Elemento Elemento Elemento Elemento Elemento Elemento

cauda
da fila

FIFO[0]

cabea
da fila

remoo de um elemento

...

FIFO[N-1]

Elemento Elemento Elemento Elemento Elemento Elemento Elemento

cauda
da fila

cabea
da fila

Figura 7.7 - Remoo de um elemento da fila.

CAPTULO 7 : FILAS E PILHAS

Na segunda situao, que se apresenta na Figura 7.8, a posio seguinte da fila est fora da
dimenso do agregado, pelo que, a cabea passa para o incio do agregado e neste caso fica
de novo frente da cauda. Para conseguir este efeito circular da cabea da fila utiliza-se o
operador mdulo, para calcular sempre um valor que est entre 0 e N_ELEMENTOS1.

FIFO[0]

...

FIFO[N-1]

Elemento Elemento Elemento Elemento Elemento Elemento Elemento

remoo de um elemento

cauda
da fila

FIFO[0]

cabea
da fila

...

FIFO[N-1]

Elemento Elemento Elemento Elemento Elemento Elemento Elemento

cabea
da fila

cauda
da fila

Figura 7.8 - Remoo de um elemento da fila com movimentao circular.

A Figura 7.9 apresenta a situao de remoo do ltimo elemento da fila. Nesta situao a
cabea da fila fica a apontar para o mesmo elemento que a cauda da fila, o que significa que
a fila ficou vazia. Enquanto este estado durar, no possvel retirar mais elementos da fila.
Nesta situao, a cabea da fila deve ficar a apontar para a posio N do agregado, ou seja,
a posio depois do fim do agregado, como indicao de fila vazia. Assim, fica reposta o
estado inicial da fila, com a diferena que neste caso, a cauda da fila no est a apontar para
a posio 0 do agregado. Quando se colocar um elemento na fila, na posio apontada pela
cauda da fila, a cabea da fila ser colocada de novo a apontar para o elemento acabado de
colocar.

...

FIFO[0]

FIFO[N-1]

Elemento Elemento Elemento Elemento Elemento Elemento Elemento

cabea
da fila

cauda
da fila

FIFO[0]

remoo do ltimo elemento

...

FIFO[N-1]

Elemento Elemento Elemento Elemento Elemento Elemento Elemento

FILA
VAZIA

cauda da fila
cabea da fila

Figura 7.9 - Remoo do ltimo elemento da fila.

cabea
da fila

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

A Figura 7.10 apresenta o ficheiro de implementao da fila esttica. A estrutura de dados


do mdulo constituda pelo agregado FILA, que um agregado de elementos do tipo
TElem definido no ficheiro de interface elemento.h, e pelas variveis inteiras positivas
head para a cabea da fila e tail para a cauda da fila. A estrutura de dados declarada com
o qualificativo static, de modo a estar protegida do exterior.
/***************** Implementao da FILA Esttica *****************/
/* Nome : fila_est.c */
#include <stdio.h>
#include "fila_est.h"

/* Ficheiro de interface do mdulo */

/* Definio da Estrutura de Dados Interna da FILA */


static TElem FILA[N_ELEMENTOS];
static unsigned int head = N_ELEMENTOS;
static unsigned int tail = 0;

/* rea de armazenamento */
/* cabea da fila */
/* cauda da fila */

/* Definio das Funes */


int Fifo_In (TElem elemento)
{
if (head == tail) return FIFO_FULL;
FILA[tail] = elemento;
if (head == N_ELEMENTOS) head = tail;
tail = ++tail % N_ELEMENTOS;
return OK;
}
int Fifo_Out (TElem *pelemento)
{
if (pelemento == NULL) return NULL_PTR;
if (head == N_ELEMENTOS) return FIFO_EMPTY;
*pelemento = FILA[head];
head = ++head % N_ELEMENTOS;
if (head == tail) head = N_ELEMENTOS;
return OK;
}

Figura 7.10 - Ficheiro de implementao da fila esttica.

7.2.2 Implementao semiesttica


A Figura 7.11 e a Figura 7.12 apresentam respectivamente o ficheiro de interface e o
ficheiro de implementao da fila semiesttica. A implementao semiesttica da fila
baseada no agregado FILA, que um agregado de ponteiros para elementos do tipo de
dados TElem, que est definido no ficheiro de interface elemento.h, e pelas variveis
inteiras positivas head para a cabea da fila e tail para a cauda da fila. Tal como na
implementao esttica, o agregado usado de forma circular.
A implementao em tudo semelhante da implementao esttica. A nica diferena
que quando se coloca um elemento na fila necessrio atribuir memria para o
armazenamento do elemento. A atribuio da memria feita recorrendo funo malloc,
uma vez que, a atribuio feita elemento a elemento. Se no existir memria para essa
atribuio, o elemento no pode ser colocado na fila, pelo que, assinalada a situao de
inexistncia de memria, usando o cdigo de erro NO_MEM. Por outro lado, quando se
retira um elemento da fila necessrio libertar a memria ocupada com o armazenamento
do elemento, recorrendo funo free e colocar o elemento do agregado a apontar para
NULL.

CAPTULO 7 : FILAS E PILHAS

/***************** Interface da FILA Semiesttica *****************/


/* Nome : fila_semiest.h */
#ifndef _FILA_SEMIESTATICA
#define _FILA_SEMIESTATICA
#include "elemento.h"

/* caracterizao do tipo elemento da fila */

/* Definio de Constantes */
#define
#define
#define
#define
#define

OK
NULL_PTR
NO_MEM
FIFO_EMPTY
FIFO_FULL

0
1
3
4
5

/*
/*
/*
/*
/*

operao realizada com sucesso */


ponteiro nulo */
memria esgotada */
fila vazia */
fila cheia */

/* Aluso s Funes Exportadas pelo Mdulo */


int Fifo_In (TElem elemento);
/* Coloca o elemento elemento na cauda da fila. Valores de retorno:
OK, FIFO_FULL ou NO_MEM. */
int Fifo_Out (TElem *pelemento);
/* Retira o elemento da cabea da fila para o elemento apontado por
pelemento. Valores de retorno: OK, NULL_PTR ou FIFO_EMPTY. */
#endif

Figura 7.11 - Ficheiro de interface da fila semiesttica.


/*************** Implementao da FILA Semiesttica ***************/
/* Nome : fila_semiest.c */
#include <stdio.h>
#include <stdlib.h>
#include "fila_semiest.h"

/* Ficheiro de interface do mdulo */

/* Definio da Estrutura de Dados Interna da FILA */


static TElem *FILA[N_ELEMENTOS];
static unsigned int head = N_ELEMENTOS;
static unsigned int tail = 0;

/* rea de armazenamento */
/* cabea da fila */
/* cauda da fila */

/* Definio das Funes */


int Fifo_In (TElem elemento)
{
if (head == tail) return FIFO_FULL;
if ((FILA[tail] = (TElem *) malloc (sizeof (TElem))) == NULL)
return NO_MEM;
*FILA[tail] = elemento;
if (head == N_ELEMENTOS) head = tail;
tail = ++tail % N_ELEMENTOS;
return OK;
}
int Fifo_Out (TElem *pelemento)
{
if (pelemento == NULL) return NULL_PTR;
if (head == N_ELEMENTOS) return FIFO_EMPTY;
*pelemento = *FILA[head];
free (FILA[head]);
FILA[head] = NULL;
head = ++head % N_ELEMENTOS;
if (head == tail) head = N_ELEMENTOS;
return OK;
}

Figura 7.12 - Ficheiro de implementao da fila semiesttica.

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

10

7.2.3 Implementao dinmica


A Figura 7.13 apresenta o ficheiro de interface da implementao dinmica de uma fila, que
baseada numa lista ligada de elementos. Uma lista ligada uma estrutura constituda por
elementos, a que vamos chamar ns, ligados atravs de ponteiros. Cada n da lista ligada
constitudo por dois ponteiros, um para o elemento que armazena a informao e outro
para o n seguinte da lista. O ltimo n da lista aponta para NULL, para servir de
indicador de finalizao da fila. A memria para os ns e para os elementos atribuda,
quando um elemento colocado na fila e libertada quando um elemento retirado da fila.
Os indicadores de cabea e cauda da fila so ponteiros. A cabea da fila aponta para o
elemento mais antigo que se encontra na fila e que o primeiro a ser retirado. A cauda da
fila aponta sempre para o elemento mais recente que se encontra na fila e frente do qual
se insere um novo elemento. Quando so ambos ponteiros nulos, sinal que a fila est
vazia. Uma fila dinmica nunca est cheia. Quando muito, pode no existir memria para
continuar a acrescentar-lhe mais elementos. Portanto, as situaes de erro so as mesmas
da implementao semiesttica, com excepo que no existe o cdigo de erro
FIFO_FULL.
/******************* Interface da FILA Dinmica *******************/
/* Nome : fila_din.h */
#ifndef _FILA_DINAMICA
#define _FILA_DINAMICA
#include "elemento.h"

/* caracterizao do tipo elemento da fila */

/* Definio de Constantes */
#define
#define
#define
#define

OK
NULL_PTR
NO_MEM
FIFO_EMPTY

0
1
3
4

/*
/*
/*
/*

operao realizada com sucesso */


ponteiro nulo */
memria esgotada */
fila vazia */

/* Aluso s Funes Exportadas pelo Mdulo */


int Fifo_In (TElem elemento);
/* Coloca o elemento apontado por elemento na cauda da fila. Valores
de retorno: OK ou NO_MEM. */
int Fifo_Out (TElem *pelemento);
/* Retira o elemento da cabea da fila para o elemento apontado por
pelemento. Valores de retorno: OK, NULL_PTR ou FIFO_EMPTY. */
#endif

Figura 7.13 - Ficheiro de interface da fila dinmica.

Vamos apresentar de forma grfica as operaes de colocao e remoo de elementos


numa fila dinmica. A Figura 7.14 apresenta o estado inicial da fila e o estado aps a
colocao do primeiro elemento. Para colocar um elemento na fila primeiro necessrio
atribuir memria para o armazenamento do n da lista e depois para o armazenamento do
elemento. Se no existir memria para essas atribuies, ento o elemento no pode ser
colocado na fila e assinalada esta situao de erro. Aps a atribuio da memria, o
elemento ligado ao n e o n ligado fila. O n fica a apontar para NULL, uma vez que
o ltimo n da fila, ou seja, ele o elemento finalizador da fila. A cauda da fila posta a
apontar para ele e finalmente, a informao a armazenar copiada para o elemento.
No caso da colocao do primeiro elemento na fila, ele no precisa de ser ligado fila, mas,
em contrapartida a cabea da fila que estava a apontar para NULL colocada a apontar
para o n deste elemento. Ele simultaneamente o primeiro e ltimo elemento da fila.

11

CAPTULO 7 : FILAS E PILHAS

cabea
da fila
cabea
da fila

cauda
da fila

estado inicial

PtSeg
PtEle

cauda
da fila

colocao
do primeiro
elemento

Elemento
1

Figura 7.14 - Situao inicial da fila e aps a colocao do primeiro elemento.

A Figura 7.15 apresenta a colocao de mais um elemento na fila. Neste caso, este
elemento tem de ser ligado fila, pelo que, o n do elemento que est apontado pela cauda
e que aponta para NULL, posto a apontar para o novo n, que passa agora a ser o ltimo
elemento da fila. A cauda da fila actualizada e a cabea da fila continua inalterada.
cabea
da fila

PtSeg

PtSeg

PtEle

PtEle

Elemento
1

Elemento
2

cauda
da fila
cabea
da fila

PtSeg

PtSeg

colocao
de um
elemento

PtSeg

PtEle

PtEle

PtEle

Elemento
1

Elemento
2

Elemento
3

cauda
da fila

Figura 7.15 - Colocao de mais um elemento na fila.

A Figura 7.16 apresenta a remoo de um elemento da fila. A informao armazenada no


elemento copiada e depois a cabea da fila colocada a apontar para o elemento seguinte
que vai passar agora a ser a nova cabea da fila. Esta operao feita atribuindo cabea da
fila o valor apontado pelo ponteiro PtSeg, que aponta para o n seguinte da fila. Toda a
memria ocupada pelo elemento e pelo n libertada.
A Figura 7.17 mostra a remoo do ltimo elemento da fila. Como este ltimo n aponta
para NULL, ao ser atribudo o valor NULL cabea da fila isso sinal de que a fila ficou
vazia, pelo que, a cauda da fila tambm colocada a apontar para NULL. Aps esta
operao, a fila fica num estado igual ao estado inicial.

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

cabea
da fila

PtSeg

12

PtSeg

PtSeg

PtEle

PtEle

PtEle

Elemento
1

Elemento
2

Elemento
3

cauda
da fila
cabea
da fila

remoo
de um
elemento

PtSeg

PtSeg

PtEle

PtEle

Elemento
2

Elemento
3

cauda
da fila

Figura 7.16 - Remoo de um elemento da fila.


cabea
da fila

PtSeg
PtEle

cauda
da fila
Elemento
3

cabea
da fila

cauda
da fila

remoo
do ltimo
elemento

FILA
VAZIA

Figura 7.17 - Remoo do ltimo elemento da fila.

A Figura 7.18 apresenta o ficheiro de implementao da fila dinmica. A estrutura de dados


do mdulo constituda por uma lista ligada de ns do tipo struct no, sendo cada n
constitudo pelo ponteiro pelemento para um elemento do tipo TElem, que est definido
no ficheiro de interface elemento.h, e pelo ponteiro pseg para fazer a ligao do n ao n
seguinte da fila, caso ele exista. Para controlar a fila existem os ponteiros, head e tail
respectivamente para a cabea e para a cauda da fila, que so declarados com o qualificativo
static, de modo a estarem protegidas do exterior. Como a fila inicialmente est vazia, estes
ponteiros so inicializadas a NULL. Quando um elemento colocado na fila, a atribuio
de memria para o seu crescimento, comea pela atribuio de memria para o n, que
caso seja bem sucedida, prossegue com a atribuio de memria para o elemento. Caso no
exista memria para o elemento, a colocao de mais um elemento na fila cancelada e a
memria anteriormente atribuda para o n libertada, antes da funo terminar a sua
execuo com o cdigo de erro NO_MEM. No faz sentido ter um n na fila, seno
podemos colocar tambm o elemento que vai armazenar a informao. O n do novo
elemento aponta para NULL, uma vez que o ltimo elemento da fila. A funo prossegue
com a ligao do ltimo elemento que estava na fila ao novo elemento e com a actualizao
do ponteiro cauda da fila que fica a apontar para o novo elemento. Finalmente feita a

13

CAPTULO 7 : FILAS E PILHAS

cpia da informao para o elemento. Quando um elemento retirado da fila, primeiro


verificado se o ponteiro passado no nulo e s depois que a informao armazenada no
elemento retirada da fila. Caso a fila no esteja vazia, o ponteiro cabea da fila colocado
a apontar para o elemento seguinte. Quando o ltimo elemento retirado da fila, ento a
cabea da fila fica a apontar para NULL, o que significa que a fila ficou vazia, pelo que, a
cauda da fila tambm colocada a apontar para NULL. Depois a memria ocupada pelo
elemento libertada e s depois que memria ocupada pelo n libertada.
/***************** Implementao da FILA Dinmica *****************/
/* Nome : fila_din.c */
#include <stdio.h>
#include <stdlib.h>
#include "fila_din.h"
/* Ficheiro de interface do mdulo */
/* Definio da Estrutura de Dados Interna da FILA */
typedef struct no *PtNo;
struct no
{
TElem *pelemento;
PtNo pseg;
};

/* ponteiro para o elemento */


/* ponteiro para o n seguinte */

static PtNo head = NULL;


static PtNo tail = NULL;

/* cabea da fila */
/* cauda da fila */

/* Definio das Funes */


int Fifo_In (TElem elemento)
{
PtNo tmp;
if ((tmp = (PtNo) malloc (sizeof (struct no))) == NULL)
return NO_MEM;
if ((tmp->pelemento = (TElem *) malloc (sizeof(TElem))) == NULL)
{
free (tmp); return NO_MEM;
}
tmp->pseg = NULL;
if (tail == NULL) head = tmp;
else tail->pseg = tmp;
tail = tmp;
*tail->pelemento = elemento;
return OK;
}
int Fifo_Out (TElem *pelemento)
{
PtNo tmp;
if (pelemento == NULL) return NULL_PTR;
if (head == NULL) return FIFO_EMPTY;
*pelemento = *head->pelemento;
tmp = head;
head = head->pseg;
if (head == NULL) tail = NULL;
free (tmp->pelemento);
free (tmp);
return OK;
}

Figura 7.18 - Ficheiro de implementao da fila dinmica.

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

14

7.3 Pilhas
Uma memria pilha ( stack/LIFO ) uma memria em que s possvel processar a
informao pela ordem inversa ordem de chegada. Da que, tambm seja apelidada de
memria do ltimo a chegar primeiro a sair. Numa memria pilha, o posicionamento para
a colocao de um elemento na pilha, que vamos designar por Stack_Push, e o
posicionamento para a remoo de um elemento da pilha, que vamos designar por
Stack_Pop, o topo da pilha (top of the stack).

7.3.1 Implementao esttica


A Figura 7.19 apresenta o ficheiro de interface da implementao esttica de uma pilha, que
baseada num agregado de elementos. O indicador de topo da pilha uma varivel de tipo
inteiro positivo.
Como um agregado tem uma dimenso fixa, antes de se colocar um elemento na pilha
necessrio verificar se a pilha est cheia. Em caso afirmativo, mais nenhum elemento pode
ser colocado na pilha e assinalada a situao de erro, usando o cdigo de erro
STACK_FULL. O elemento que se pretende copiar para a pilha passado funo
Stack_Push por valor. Por outro lado, o elemento que vai receber a cpia do elemento
que se pretende retirar da pilha passado funo Stack_Pop por referncia. Pode
acontecer que o ponteiro passado funo seja um ponteiro nulo. Nestas circunstncias a
funo no pode retirar o elemento da pilha, pelo que, no faz nada e assinala esta
anomalia, usando o cdigo de erro NULL_PTR. Antes de se retirar um elemento da pilha
preciso detectar se a pilha est vazia. Em caso afirmativo, nenhum elemento pode ser
retirado da pilha e assinalada a situao de erro, usando o cdigo de erro
STACK_EMPTY. Sempre que colocado ou retirado um elemento da pilha devolvido o
cdigo OK sinalizando que a operao foi realizada com sucesso.
/****************** Interface do PILHA Esttica ******************/
/* Nome : pilha_est.h */
#ifndef _PILHA_ESTATICA
#define _PILHA_ESTATICA
#include "elemento.h"

/* caracterizao do tipo elemento da pilha */

/* Definio de Constantes */
#define
#define
#define
#define

OK
NULL_PTR
STACK_EMPTY
STACK_FULL

0
1
4
5

/*
/*
/*
/*

operao realizada com sucesso */


ponteiro nulo */
pilha vazia */
pilha cheia */

/* Aluso s Funes Exportadas pelo Mdulo */


int Stack_Push (TElem elemento);
/* Coloca o elemento apontado por elemento no topo da pilha. Valores
de retorno: OK ou STACK_FULL. */
int Stack_Pop (TElem *pelemento);
/* Retira o elemento do topo da pilha para o elemento apontado por
pelemento. Valores de retorno: OK, NULL_PTR ou STACK_EMPTY. */
#endif

Figura 7.19 - Ficheiro de interface da pilha esttica.

15

CAPTULO 7 : FILAS E PILHAS

De seguida, vamos apresentar graficamente o comportamento das operaes de colocao


de um elemento na pilha e de remoo de um elemento da pilha.
A Figura 7.20 mostra o estado inicial da pilha. Por uma questo de implementao, vamos
considerar que o topo da pilha indica sempre a primeira posio livre para a prxima
operao de colocao de um elemento na pilha, pelo que, inicialmente aponta para a
posio 0 do agregado. A Figura 7.20 mostra tambm a colocao do primeiro elemento na
pilha e o estado da pilha aps a operao. O topo da pilha deslocado para a posio 1 do
agregado, que a primeira posio livre para colocar o prximo elemento.
estado inicial

colocao do
primeiro elemento
STACK[N-1] Elemento

STACK[N-1] Elemento

.
.
.

Elemento

.
.
.

Elemento

STACK[1]

Elemento

STACK[0]

Elemento

topo da
pilha

Elemento
Elemento

STACK[1]

Elemento

STACK[0]

Elemento

topo da
pilha

Figura 7.20 - Situao inicial da pilha e aps a colocao do primeiro elemento.

Sempre que se coloca um elemento na pilha, o topo da pilha deslocada para a posio
seguinte da pilha, depois da cpia do elemento para a pilha. A Figura 7.21 apresenta a
situao limite de utilizao da pilha, quando se coloca o ltimo elemento na pilha. Nesta
situao o topo da pilha fica a apontar para a posio do agregado de ndice N, o que
significa que a pilha ficou cheia. Enquanto este estado durar, no possvel colocar mais
elementos na pilha.
colocao do ltimo elemento

STACK[N-1] Elemento

.
.
.

Elemento

Elemento

topo da
pilha

PILHA
CHEIA

topo da
pilha

STACK[N-1] Elemento

.
.
.

Elemento
Elemento

STACK[1]

Elemento

STACK[1]

Elemento

STACK[0]

Elemento

STACK[0]

Elemento

Figura 7.21 - Colocao do ltimo elemento na pilha.

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

16

Sempre que um elemento retirado da pilha, primeiro preciso deslocar o topo da pilha
para a posio anterior da pilha, onde est armazenado o ltimo elemento da pilha. S
depois que o elemento copiado para fora da pilha.
A Figura 7.22 apresenta a situao de remoo de um elemento, quando existe mais do que
um elemento na pilha. O topo da pilha deslocado para a posio anterior e a informao
armazenada no elemento retirado da pilha.
remoo de um elemento
STACK[N-1] Elemento

STACK[N-1] Elemento

.
.
.

Elemento

topo da
pilha

Elemento

.
.
.

Elemento
Elemento

STACK[1]

Elemento

STACK[1]

Elemento

STACK[0]

Elemento

STACK[0]

Elemento

topo da
pilha

Figura 7.22 - Remoo de um elemento da pilha.

A Figura 7.23 apresenta a situao de remoo do ltimo elemento da pilha. Aps a


operao, o topo da pilha fica a apontar para a posio do agregado de ndice 0, o que
significa que ficou reposta o estado inicial da pilha, ou seja, a pilha ficou vazia. Enquanto
este estado durar, no possvel retirar mais elementos da pilha.
remoo do ltimo elemento
STACK[N-1] Elemento

.
.
.

STACK[N-1] Elemento

Elemento

.
.
.

Elemento

STACK[1]

Elemento

STACK[0]

Elemento

topo da
pilha

Elemento

Elemento

STACK[1]

Elemento

STACK[0]

Elemento

PILHA
VAZIA
topo da
pilha

Figura 7.23 - Remoo do ltimo elemento da fila.

A Figura 7.24 apresenta o ficheiro de implementao da pilha esttica. A estrutura de dados


do mdulo constituda pelo agregado PILHA, que um agregado de elementos do tipo
TElem, que est definido no ficheiro de interface elemento.h, e pela varivel inteira positiva
top para o topo da pilha. A estrutura de dados declarada com o qualificativo static, de
modo a estar protegida do exterior.

17

CAPTULO 7 : FILAS E PILHAS

/**************** Implementao da PILHA Esttica ****************/


/* Nome : pilha_est.c */
#include <stdio.h>
#include "pilha_est.h"

/* Ficheiro de interface do mdulo */

/* Definio da Estrutura de Dados Interna da PILHA */


static TElem PILHA[N_ELEMENTOS];
static unsigned int top = 0;

/* rea de armazenamento */
/* topo da pilha */

/* Definio das Funes */


int Stack_Push (TElem elemento)
{
if (top == N_ELEMENTOS) return STACK_FULL;
PILHA[top++] = elemento;
return OK;
}
int Stack_Pop (TElem *pelemento)
{
if (pelemento == NULL) return NULL_PTR;
if (top == 0) return STACK_EMPTY;
*pelemento = PILHA[--top];
return OK;
}

Figura 7.24 - Ficheiro de implementao da pilha esttica.

7.3.2 Implementao semiesttica


A Figura 7.25 e a Figura 7.26 apresentam respectivamente o ficheiro de interface e o
ficheiro de implementao da pilha semiesttica. A implementao semiesttica da pilha
baseada no agregado PILHA, que um agregado de ponteiros para elementos do tipo de
dados TElem, que est definido no ficheiro de interface elemento.h, e pela varivel inteira
positiva top para o topo da pilha. O agregado usado tal como na implementao esttica.
A implementao em tudo semelhante da implementao esttica. A nica diferena
que quando se coloca um elemento na pilha necessrio atribuir memria para o
armazenamento do elemento. A atribuio da memria feita recorrendo funo malloc,
uma vez que, a atribuio feita elemento a elemento. Se no existir memria para essa
atribuio, o elemento no pode ser colocado na pilha e assinalada a situao de
inexistncia de memria, usando o cdigo de erro NO_MEM. Por outro lado, quando se
retira um elemento da pilha necessrio libertar a memria ocupada com o
armazenamento do elemento, recorrendo funo free e colocar o elemento do agregado a
apontar para NULL.

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

18

/**************** Interface do PILHA Semiesttica ****************/


/* Nome : pilha_semiest.h */
#ifndef _PILHA_SEMIESTATICA
#define _PILHA_SEMIESTATICA
#include "elemento.h"

/* caracterizao do tipo elemento da pilha */

/* Definio de Constantes */
#define
#define
#define
#define
#define

OK
NULL_PTR
NO_MEM
STACK_EMPTY
STACK_FULL

0
1
3
4
5

/*
/*
/*
/*
/*

operao realizada com sucesso */


ponteiro nulo */
memria esgotada */
pilha vazia */
pilha cheia */

/* Aluso s Funes Exportadas pelo Mdulo */


int Stack_Push (TElem elemento);
/* Coloca o elemento apontado por elemento no topo da pilha. Valores
de retorno: OK, STACK_FULL ou NO_MEM. */
int Stack_Pop (TElem *pelemento);
/* Retira o elemento do topo da pilha para o elemento apontado por
pelemento. Valores de retorno: OK, NULL_PTR ou STACK_EMPTY. */
#endif

Figura 7.25 - Ficheiro de interface da pilha semiesttica.


/************** Implementao da PILHA Semiesttica **************/
/* Nome : pilha_semiest.c */
#include <stdio.h>
#include <stdlib.h>
#include "pilha_semiest.h"

/* Ficheiro de interface do mdulo */

/* Definio da Estrutura de Dados Interna da PILHA */


static TElem *PILHA[N_ELEMENTOS];
static unsigned int top = 0;

/* rea de armazenamento */
/* topo da pilha */

/* Definio das Funes */


int Stack_Push (TElem elemento)
{
if (top == N_ELEMENTOS) return STACK_FULL;
if ((PILHA [top] = (TElem *) malloc (sizeof (TElem))) == NULL)
return NO_MEM;
*PILHA[top++] = elemento;
return OK;
}
int Stack_Pop (TElem *pelemento)
{
if (pelemento == NULL) return NULL_PTR;
if (top == 0) return STACK_EMPTY;
*pelemento = *PILHA[--top];
free (PILHA[top]);
PILHA[top] = NULL;
return OK;
}

Figura 7.26 - Ficheiro de implementao da pilha semiesttica.

19

CAPTULO 7 : FILAS E PILHAS

7.3.3 Implementao dinmica


A Figura 7.27 apresenta o ficheiro de interface da implementao dinmica, que baseada
numa lista ligada de elementos. Uma lista ligada uma estrutura constituda por elementos,
a que vamos chamar ns, ligados atravs de ponteiros. Cada n da lista ligada constitudo
por dois ponteiros, um para o elemento que armazena a informao e outro para o n
anterior da lista. O primeiro n da lista aponta para NULL, para servir de indicador de
finalizao da pilha. A memria para os ns e para os elementos da pilha atribuda,
quando um elemento colocado na pilha e libertada quando um elemento retirado da
pilha. O indicador de topo da pilha um ponteiro, que aponta para o elemento mais
recentemente colocado na pilha e que o primeiro a ser retirado. Quando o topo da pilha
um ponteiro nulo, sinal que a pilha est vazia. Uma pilha dinmica nunca est cheia.
Quando muito, pode no existir memria para continuar a acrescentar-lhe mais elementos.
Portanto, as situaes de erro so as mesmas da implementao semiesttica, com excepo
que no existe o cdigo de erro STACK_FULL.
/****************** Interface da PILHA Dinmica ******************/
/* Nome : pilha_din.h */
#ifndef _PILHA_DINAMICA
#define _PILHA_DINAMICA
#include "elemento.h"

/* caracterizao do tipo elemento da pilha */

/* Definio de Constantes */
#define
#define
#define
#define

OK
NULL_PTR
NO_MEM
STACK_EMPTY

0
1
3
4

/*
/*
/*
/*

operao realizada com sucesso */


ponteiro nulo */
memria esgotada */
pilha vazia */

/* Aluso s Funes Exportadas pelo Mdulo */


int Stack_Push (TElem elemento);
/* Coloca o elemento apontado por elemento no topo da pilha. Valores
de retorno: OK ou NO_MEM. */
int Stack_Pop (TElem *pelemento);
/* Retira o elemento do topo da pilha para o elemento apontado por
pelemento. Valores de retorno: OK, NULL_PTR ou STACK_EMPTY. */
#endif

Figura 7.27 - Ficheiro de interface da pilha dinmica.

Vamos apresentar de forma grfica as operaes de colocao e remoo de elementos


numa pilha dinmica. A Figura 7.28 apresenta o estado inicial da pilha e o estado aps a
colocao do primeiro elemento.
Para colocar um elemento na pilha necessrio atribuir memria, primeiro para o
armazenamento do n da lista e depois para o armazenamento do elemento. Se no existir
memria para essas atribuies, ento o elemento no pode ser colocado na pilha e
assinalada esta situao de erro. Aps a atribuio da memria, o elemento ligado ao n e
o n ligado pilha. O n fica a apontar para o n anterior que o topo da pilha, que
depois actualizado, sendo colocado a apontar para este novo n. Finalmente, a
informao a armazenar copiada para o elemento.
No caso da colocao do primeiro elemento na pilha, ele no precisa de ser ligado pilha,
mas, em contrapartida colocado a apontar para NULL, indicando que ele o primeiro
elemento da pilha. Ele simultaneamente o primeiro e o ltimo elemento da pilha.

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

estado inicial

20

colocao do
primeiro elemento
topo da
pilha

topo da
pilha

PtEle

Elemento
1

PtAnt

Figura 7.28 - Situao inicial da pilha e aps a colocao do primeiro elemento na pilha.

A Figura 7.29 apresenta a colocao de mais um elemento na pilha. O n do novo


elemento posto a apontar para o antigo topo da pilha, atravs do ponteiro PtAnt, e
depois passa a ser o novo topo da pilha. Ou seja, o topo da pilha actualizado, ficando a
apontar para este novo n.
colocao de um elemento

topo da
pilha

PtEle

Elemento
2

PtAnt

topo da
pilha

PtEle

Elemento
1

PtEle

PtAnt

Elemento
1

PtAnt

Figura 7.29 - Colocao de mais um elemento na pilha.

A Figura 7.30 apresenta a remoo de um elemento da pilha. A informao armazenada no


elemento copiada. Depois o topo da pilha colocada a apontar para o elemento anterior
que vai passar agora a ser o novo topo da pilha. Esta operao feita atribuindo ao topo da
pilha o valor apontado pelo ponteiro PtAnt, que aponta para o n anterior da pilha. Depois
toda a memria ocupada pelo elemento e pelo n libertada.
remoo de um elemento
topo da
pilha

PtEle

Elemento
2

PtAnt

PtEle
PtAnt

Elemento
1

topo da
pilha

PtEle
PtAnt

Figura 7.30 - Remoo de um elemento da pilha.

Elemento
1

21

CAPTULO 7 : FILAS E PILHAS

A Figura 7.31 mostra a remoo do ltimo elemento da pilha. Como este ltimo n aponta
para NULL, ao ser atribudo o valor NULL ao topo da pilha isso sinal de que a pilha
ficou vazia, e a pilha fica num estado igual ao estado inicial.
remoo do ltimo elemento
topo da
pilha

PtEle

Elemento
1

topo da
pilha

PILHA
VAZIA

PtAnt

Figura 7.31 - Remoo do ltimo elemento da pilha.

A Figura 7.32 apresenta o ficheiro de implementao da pilha dinmica. A estrutura de


dados do mdulo constituda por uma lista ligada de ns do tipo struct no, sendo cada
n constitudo pelo ponteiro pelemento para um elemento do tipo TElem, que est
definido no ficheiro de interface elemento.h, e pelo ponteiro pant para fazer a ligao do
n ao n anterior da pilha, caso ele exista. Para controlar a pilha existe o ponteiro top para
o topo da pilha, que declarado com o qualificativo static, de modo a estar protegido do
exterior. Como a pilha inicialmente est vazia, este ponteiro inicializado a NULL.
Quando um elemento colocado na pilha, a atribuio de memria para o seu crescimento,
comea pela atribuio de memria para o n, que caso seja bem sucedida, prossegue com
a atribuio de memria para o elemento. Caso no exista memria para o elemento, a
colocao de mais um elemento na pilha cancelada e a memria anteriormente atribuda
para o n libertada, antes da funo terminar a sua execuo com o cdigo de erro
NO_MEM. No faz sentido ter um n na pilha, seno podemos colocar tambm o
elemento que vai armazenar a informao. A funo continua com a ligao do novo
elemento ao ltimo elemento que estava na pilha, pelo que, o n do primeiro elemento da
pilha fica a apontar para NULL. Depois feita a actualizao do ponteiro topo da pilha que
fica a apontar para o novo elemento e finalmente feita a cpia da informao para o
elemento.
Quando um elemento retirado da pilha, primeiro verificado se o ponteiro passado no
nulo e s depois que a informao armazenada no elemento retirada da pilha. O
ponteiro topo da pilha colocado a apontar para o elemento anterior. Quando o ltimo
elemento retirado da pilha, ento o topo da pilha fica a apontar para NULL, o que
significa que a pilha ficou vazia. Depois a memria ocupada pelo elemento libertada e s
depois que a memria ocupada pelo n da pilha libertada.

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

22

/**************** Implementao da PILHA Dinmica ****************/


/* Nome : pilha_din.c */
#include <stdio.h>
#include <stdlib.h>
#include "pilha_din.h"

/* Ficheiro de interface do mdulo */

/* Definio da Estrutura de Dados Interna da PILHA */


typedef struct no *PtNo;
struct no
{
TElem *pelemento;
PtNo pant;
};

/* ponteiro para o elemento */


/* ponteiro para o n anterior */

static PtNo top = NULL;

/* topo da pilha */

/* Definio das Funes */


int Stack_Push (TElem elemento)
{
PtNo tmp;
if ((tmp = (PtNo) malloc (sizeof (struct no))) == NULL)
return NO_MEM;
if ((tmp->pelemento = (TElem *) malloc (sizeof(TElem))) == NULL)
{
free (tmp); return NO_MEM;
}
tmp->pant = top;
top = tmp;
*top->pelemento = elemento;
return OK;
}
int Stack_Pop (TElem *pelemento)
{
PtNo tmp;
if (pelemento == NULL) return NULL_PTR;
if (top == NULL) return STACK_EMPTY;
*pelemento = *top->pelemento;
tmp = top;
top = top->pant;
free (tmp->pelemento);
free (tmp);
return OK;
}

Figura 7.32 - Ficheiro de implementao da pilha dinmica.

7.4 Exemplos de aplicao de filas e pilhas


Uma das aplicaes das pilhas a anlise e processamento de estruturas imbricadas.
Estruturas imbricadas so estruturas que so compostas internamente por outras estruturas
do mesmo tipo, onde existe a necessidade de garantir que uma estrutura interna finalizada
antes da estrutura externa. Um exemplo tpico de uma estrutura imbricada uma expresso
aritmtica que normalmente composta por subexpresses aritmticas entre parnteses

23

CAPTULO 7 : FILAS E PILHAS

curvos. Por vezes para melhor identificar os vrios nveis de subexpresses, utilizam-se
parnteses curvos, rectos e chavetas, tal se mostra na seguinte expresso.
{ A + B * [ C / (F+A) + (C+D) * (EF) ] } / [ (A+B) * C ]
Para que uma expresso aritmtica possa ser correctamente calculada necessrio assegurar
o correcto balanceamento dos parnteses, como o caso da expresso anterior. Uma
expresso com um balanceamento correcto de parnteses, tem tantos parnteses a abrir, ou
seja, parnteses esquerdos, como parnteses a fechar, ou seja, parnteses direitos. Mas
tambm preciso assegurar que um nvel interno de parnteses fechado antes que um
nvel externo e com um parntese direito equivalente ao parntese esquerdo. Pelo que, a
anlise no pode apenas limitar-se a contar o nmero de parnteses direitos e esquerdos de
cada tipo. Por exemplo, a expresso seguinte tem o mesmo nmero de parnteses direitos e
esquerdos e no est balanceada, porque a chaveta fechada antes do parntese recto.
{ A + B * [ C / (F+A) + (C+D) * (EF) } ] / [ (A+B) * C ]
Uma vez que necessrio assegurar que cada parntese direito equivalente ao ltimo
parntese esquerdo, esta anlise pode ser feita usando uma pilha O algoritmo o seguinte.
Sempre que aparece um parntese esquerdo na expresso, este colocado na pilha.
Quando aparece um parntese direito, tira-se da pilha o ltimo parntese esquerdo l
colocado e verifica-se se so equivalentes. Caso o teste seja positivo ento este nvel interno
de parnteses est balanceado e ambos os parnteses so descartados. Caso os parnteses
no sejam equivalentes, ento o processo interrompido porque este nvel interno de
parnteses no est balanceado. Se por acaso no existir um parntese esquerdo na pilha,
ento sinal que existem mais parnteses direitos que esquerdos at esta posio da
expresso, pelo que, a expresso tambm no est balanceada. Quando a anlise da
expresso terminar preciso verificar se a pilha est vazia. Porque, caso no esteja vazia,
ento sinal que existem mais parnteses esquerdos que direitos na expresso, pelo que, a
expresso tambm no est balanceada.
A Figura 7.34 apresenta o programa que implementa este algoritmo. De maneira a
estruturar melhor o programa, foi implementada uma funo inteira que compara o
parntese esquerdo com o parntese direito e que devolve 1, caso eles sejam equivalentes e
0 no caso contrrio. Neste caso a pilha utilizada a implementao esttica, mas podia ser
qualquer outra implementao. Para utilizar a pilha, primeiro preciso editar o ficheiro de
interface elemento.h, e definir o nmero de elementos da estrutura de dados da pilha com
um valor suficiente para armazenar os parnteses da expresso, que no pior caso podem ser
tantos quantos o nmero de caracteres da expresso e definir tambm o tipo dos elementos
da pilha como sendo do tipo char. Depois o mdulo compilado, com a opo c, para
ficar concretizado para uma pilha de caracteres. Finalmente, o programa compilado,
indicando no comando de compilao o ficheiro objecto do mdulo pilha_est.o. A Figura
7.33 apresenta o resultado de execuo do programa para vrias expresses.
Expresso algbrica -> {A+B*[C/(F+A)+(C+D)*(EF)]}/[(A+B)*C]
Expresso com parnteses balanceados
Expresso algbrica -> {A+B*[C/(F+A)+(C+D)*(EF)}]/[(A+B)*C]
Parnteses [ e } discordantes
Expresso algbrica -> {A+B*[C/(F+A)+(C+D)*(EF)]}/[(A+B)*C]}
Mais parnteses direitos do que esquerdos
Expresso algbrica -> {A+B*[C/(F+A)+(C+D)*(EF)]}/[(A+B)*C
Mais parnteses esquerdos do que direitos

Figura 7.33 - Exemplos de utilizao do programa.

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

24

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "pilha_est.h" /* ficheiro de interface do mdulo da pilha */
int parenteses_equivalentes (char, char);
int main (void)
{
char exp[81],
cpilha;
int nc,
c,
st;

/* expresso lida do teclado para analisar


/* parntese esquerdo armazenado na pilha
/* nmero de smbolos da expresso
/* contador do ciclo for
/* estado de realizao da operao da pilha

*/
*/
*/
*/
*/

printf ("Expresso algbrica -> ");


scanf ("%80s", exp);
nc = strlen (exp);
for (c = 0; c < nc; c++)
if ( exp[c] == '(' || exp[c] == '[' || exp[c] == '{' )
Stack_Push (exp[c]);
else if ( exp[c] == ')' || exp[c] == ']' || exp[c] == '}' )
{
st = Stack_Pop (&cpilha);
if (st == STACK_EMPTY)
{
printf ("Mais parnteses direitos do que esquerdos\n");
return EXIT_SUCCESS;
}
else if ( !parenteses_equivalentes (cpilha, exp[c]) )
{
printf ("Parnteses %c e %c discordantes\n",\
cpilha, exp[c]);
return EXIT_SUCCESS;
}
}
st = Stack_Pop (&cpilha);
if (st == STACK_EMPTY)
printf ("Expresso com parnteses balanceados\n");
else printf ("Mais parnteses esquerdos do que direitos\n");
return EXIT_SUCCESS;
}
int parenteses_equivalentes (char pesquerdo, char pdireito)
{
switch (pesquerdo)
{
case '(' : return pdireito == ')'; break;
case '[' : return pdireito == ']'; break;
case '{' : return pdireito == '}'; break;
default : return 0;
}
}

Figura 7.34 - Programa que verifica o balanceamento dos parnteses numa expresso.

25

CAPTULO 7 : FILAS E PILHAS

Pretende-se determinar se uma palavra um palndromo, que uma palavra que se l da


mesma maneira da esquerda para a direita e da direita para esquerda. Ou seja, aquilo que
normalmente se designa por capicua. Para que uma palavra seja uma capicua, os seus
caracteres esto reflectidos em relao ao carcter central. Uma forma de detectar esta
caracterstica, consiste em comparar cada carcter com o seu simtrico. Por outras palavras,
se a palavra for comparada com a palavra invertida, carcter a carcter, temos que os
caracteres so coincidentes. Podemos fazer esta deteco utilizando uma fila e uma pilha
conjuntamente. O algoritmo o seguinte. Coloca-se todos os caracteres da palavra na fila e
na pilha. Depois retira-se o carcter da cabea da fila e o carcter do topo da pilha e
comparam-se. Desta maneira, compara-se o primeiro carcter da palavra, ou seja, o carcter
que se encontra na fila com o ltimo carcter da palavra, ou seja, o carcter que se encontra
na pilha. Se os caracteres forem iguais at se esgotar os caracteres da fila e da pilha, ento a
palavra uma capicua. Caso contrrio, a palavra no uma capicua.
A Figura 7.35 apresenta o programa que implementa este algoritmo. A pilha e a fila
utilizadas so a implementao esttica. Para utilizar a fila e a pilha, primeiro preciso
editar o ficheiro de interface elemento.h, e definir o nmero de elementos das estruturas
de dados da fila e da pilha, com um valor suficiente para armazenar o nmero de caracteres
da palavra e definir tambm o tipo dos elementos da fila e da pilha como sendo do tipo
char. Depois os mdulos so compilados, com a opo c, para ficarem concretizados
para uma fila e uma pilha de caracteres. Finalmente, o programa compilado, indicando no
comando de compilao os ficheiros objectos dos mdulos fila_est.o e pilha_est.o.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "fila_est.h"
/* ficheiro de interface do mdulo da fila */
#include "pilha_est.h" /* ficheiro de interface do mdulo da pilha */
int main (void)
{
char palavra[81],
cfila,
cpilha;
int nc, c;

/* palavra lida do teclado */


/* carcter armazenado na fila */
/* carcter armazenado na pilha */

printf ("Palavra -> "); scanf ("%80s", palavra);


nc = strlen (palavra);
for (c = 0; c < nc; c++)
{
Fifo_In (palavra[c]); Stack_Push (palavra[c]);
}
for (c = 0; c < nc; c++)
{
Fifo_Out (&cfila); Stack_Pop (&cpilha);
if (cfila != cpilha)
{
printf ("A palavra no uma capicua\n"); return EXIT_SUCCESS;
}
}
printf ("A palavra uma capicua\n");
return EXIT_SUCCESS;
}

Figura 7.35 - Programa que verifica se uma palavra uma capicua.

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

26

7.5 Implementaes abstractas


Uma alternativa para poder reutilizar um mdulo de memria sem a necessidade de o
recompilar sempre que o utilizador precisa de mudar o tipo dos elementos da memria,
consiste na criao de um mdulo abstracto. Um mdulo abstracto pode ser concretizado
durante a execuo do programa, permitindo assim a sua reutilizao para diferentes tipos
de dados. Para que um mdulo de memria seja reutilizvel sem limitaes, ele deve
tambm ter uma implementao dinmica para que a sua capacidade de armazenamento
seja ilimitada. Por outro lado, para implementar certos algoritmos, por vezes existe a
necessidade de utilizar mais do que uma memria do mesmo tipo, pelo que, o mdulo de
memria tambm deve ter a capacidade de mltipla instanciao. Portanto, um mdulo de
memria abstracto, com uma implementao dinmica e com capacidade de mltipla
instanciao um mdulo muito verstil e com uma reutilizao praticamente ilimitada.
Com a linguagem C possvel criar um mdulo de memria usando ponteiros de tipo
void, uma vez que ele pode ser colocado a apontar para qualquer tipo de dados. Portanto,
estamos perante um ponteiro genrico que possibilita a construo de um mdulo de
memria, em que o seu elemento de armazenamento pode ser de qualquer tipo de dados.
Aquando da utilizao do mdulo preciso concretiz-lo para o tipo de dados necessrio
aplicao. Esta operao feita atravs de uma funo de criao do mdulo, que
responsvel pela caracterizao do tamanho em bytes do elemento de armazenamento da
memria. Para permitir manipular estruturas de dados, sem conhecimento do seu tipo
concreto, a biblioteca de execuo ANSI string providencia a funo memcpy, que copia
blocos de memria, sem atribuir qualquer interpretao ao contedo dos bytes copiados.
A Figura 7.36 apresenta o ficheiro de interface da fila dinmica, abstracta e com capacidade
de mltipla instanciao, cujo ficheiro de implementao se apresenta na Figura 7.37 e na
Figura 7.38.
Para que o mdulo possa criar e manipular mais do que uma fila, necessrio que exista
uma referncia para cada fila criada de forma a identificar de forma inequvoca a fila onde
se pretende colocar ou retirar elementos. Para isso preciso uma estrutura de suporte que
possa armazenar os elementos que controlam a fila e que so os ponteiros para a cabea e
para a cauda da fila e um indicador do tamanho em nmero de bytes do elemento da fila.
Esta estrutura vai ser criada na memria na altura em que pedido a criao de uma fila e o
seu endereo devolvido de forma a permitir o posterior acesso fila criada, quer para a
colocao e remoo de elementos, quer para a destruio da fila, quando ela deixa de ser
necessria. Pelo que, o ficheiro de interface define o tipo PtFifo, que um ponteiro para
struct fifo, que permite ao utilizador do mdulo manipular as filas criadas, sem no entanto
ter acesso estrutura de dados da fila.
A funo de criao da fila Fifo_Create, cria a estrutura de suporte da fila, coloca os
ponteiros para a cabea e a cauda da fila a apontar para NULL, ou seja, cria uma fila sem
elementos, concretiza o tipo de elementos atravs da especificao do seu tamanho em bytes
e devolve a referncia da estrutura de suporte da fila criada.
Por sua vez, a funo de destruio da fila Fifo_Destroy, liberta toda a memria ocupada
pelos elementos da fila e pela estrutura de suporte da fila e coloca a referncia da fila a
NULL, de maneira a no ser mais possvel aceder fila.

27

CAPTULO 7 : FILAS E PILHAS

A funo de colocao de elementos na fila Fifo_In comea por assegurar que a fila existe
e depois comporta-se tal como na implementao dinmica. Mas, a cpia do elemento para
a fila feita pela funo memcpy indicando o ponteiro para o elemento a colocar na fila, o
ponteiro para a cauda da fila e o nmero de bytes a copiar, que est armazenado no campo
que especifica o tamanho dos elementos da fila. Desta maneira possvel manipular os
elementos da fila sem que se saiba de que tipo eles so.
A funo de remoo de elementos da fila Fifo_Out comea por assegurar que a fila existe
e depois comporta-se tal como na implementao dinmica. Tal como na funo Fifo_In,
a cpia do elemento da fila feita pela funo memcpy.
/****************** Interface da FILA Abstracta ******************/
/* Nome : fila_abs.h */
#ifndef _FILA_ABSTRACTA
#define _FILA_ABSTRACTA
typedef struct fifo *PtFifo;
/* Definio de Constantes */
#define
#define
#define
#define
#define
#define

OK
NULL_PTR
NULL_SIZE
NO_MEM
FIFO_EMPTY
NO_FIFO

0
1
2
3
4
7

/*
/*
/*
/*
/*
/*

operao realizada com sucesso */


ponteiro nulo */
tamanho nulo */
memria esgotada */
fila vazia */
no foi instanciada qualquer fila */

/* Aluso s Funes Exportadas pelo Mdulo */


PtFifo Fifo_Create (unsigned int sz);
/* Concretiza a fila para elementos de sz bytes.
referncia da fila criada ou NULL em caso de erro. */

Devolve

int Fifo_Destroy (PtFifo *fila);


/* Destri a fila referenciada por fila e coloca a referncia a
NULL. Valores de retorno: OK ou NO_FIFO. */
int Fifo_In (PtFifo fila, void *pelemento);
/* Coloca o elemento apontado por pelemento na cauda da fila
referenciada por fila. Valores de retorno: OK, NO_FIFO, NULL_PTR ou
NO_MEM. */
int Fifo_Out (PtFifo fila, void *pelemento);
/* Retira o elemento da cabea da fila referenciada por fila, para o
elemento apontado por pelemento. Valores de retorno: OK, NO_FIFO,
NULL_PTR ou FIFO_EMPTY. */
#endif

Figura 7.36 - Ficheiro de interface da fila abstracta com mltipla instanciao.

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

28

/**************** Implementao da FILA Abstracta ****************/


/* Nome : fila_abs.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "fila_abs.h"

/* Ficheiro de interface do mdulo */

/* Definio da Estrutura de Dados Interna da FILA */


typedef struct no *PtNo;
struct no
{
void *pelemento;
PtNo pseg;
};

/* ponteiro para o elemento */


/* ponteiro para o n seguinte */

struct fifo
{
unsigned int size;/* tamanho em nmero de bytes de cada elemento */
PtNo head;
/* cabea da fila */
PtNo tail;
/* cauda da fila */
};
/* Definio das Funes */
PtFifo Fifo_Create (unsigned int sz)
{
PtFifo fifo;
if (sz == 0) return NULL;
if ((fifo = (PtFifo) malloc (sizeof (struct fifo))) == NULL)
return NULL;
fifo->size = sz;
fifo->head = NULL;
fifo->tail = NULL;
return fifo;
}
int Fifo_Destroy (PtFifo *fila)
{
PtFifo fifo = *fila; PtNo tmp;
if (fifo == NULL) return NO_FIFO;
while ( fifo->head != NULL )
{
tmp = fifo->head;
fifo->head = fifo->head->pseg;
free (tmp->pelemento);
free (tmp);
}
free (fifo);
*fila = NULL;
return OK;
}

Figura 7.37 - Ficheiro de implementao da fila abstracta com mltipla instanciao (1 parte).

29

CAPTULO 7 : FILAS E PILHAS

int Fifo_In (PtFifo fila, void *pelemento)


{
PtFifo fifo = fila; PtNo tmp;
if (fifo == NULL) return NO_FIFO;
if (pelemento == NULL) return NULL_PTR;
if ((tmp = (PtNo) malloc (sizeof (struct no))) == NULL)
return NO_MEM;
if ((tmp->pelemento = (void *) malloc (fifo->size)) == NULL)
{
free (tmp); return NO_MEM;
}
tmp->pseg = NULL;
if (fifo->tail == NULL) fifo->head = tmp;
else fifo->tail->pseg = tmp;
fifo->tail = tmp;
memcpy (fifo->tail->pelemento, pelemento, fifo->size);
return OK;
}
int Fifo_Out (PtFifo fila, void *pelemento)
{
PtFifo fifo = fila; PtNo tmp;
if (fifo == NULL) return NO_FIFO;
if (pelemento == NULL) return NULL_PTR;
if (fifo->head == NULL) return FIFO_EMPTY;
memcpy (pelemento, fifo->head->pelemento, fifo->size);
tmp = fifo->head;
fifo->head = fifo->head->pseg;
if (fifo->head == NULL) fifo->tail = NULL;
free (tmp->pelemento);
free (tmp);
return OK;
}

Figura 7.38 - Ficheiro de implementao da fila abstracta com mltipla instanciao (2 parte).

A Figura 7.39 apresenta o ficheiro de interface da pilha dinmica, abstracta e com


capacidade de mltipla instanciao, cujo ficheiro de implementao se apresentam na
Figura 7.40 e na Figura 7.41.
Para que o mdulo possa criar e manipular mais do que uma pila, necessrio que exista
uma referncia para cada pilha criada de forma a identificar de forma inequvoca a pilha
onde se pretende colocar ou retirar elementos. Para isso preciso uma estrutura de suporte
que possa armazenar os elementos que controlam a pilha e que so o ponteiro para o topo
da pilha e um indicador do tamanho em nmero de bytes do elemento da pilha. Esta
estrutura vai ser criada na memria na altura em que pedido a criao de uma pilha e o
seu endereo devolvido de forma a permitir o posterior acesso pilha criada, quer para a
colocao e remoo de elementos, quer para a destruio da pilha, quando ela deixa de ser
necessria. Pelo que, o ficheiro de interface define o tipo PtStack, que um ponteiro para
struct stack, que permite ao utilizador do mdulo manipular as filas criadas, sem no
entanto ter acesso estrutura de suporte da fila.

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

30

A funo de criao da pilha Stack_Create, cria a estrutura de suporte da pilha, coloca o


ponteiro para o topo da pilha a apontar para NULL, ou seja, cria uma pilha sem elementos,
concretiza o tipo de elementos atravs da especificao do seu tamanho em bytes e devolve
a referncia da estrutura de suporte da pilha criada.
Por sua vez, a funo de destruio da pilha Stack_Destroy, liberta toda a memria
ocupada pelos elementos da pilha e pela estrutura de suporte da pilha e coloca a referncia
da pilha a NULL, de maneira a no ser mais possvel aceder pilha.
A funo de colocao de elementos na pilha Stack_Push comea por assegurar que a
pilha existe e depois comporta-se tal como na implementao dinmica. Mas, a cpia do
elemento para a pilha feita pela funo memcpy indicando o ponteiro para o elemento a
colocar na pilha, o ponteiro para o topo da pilha e o nmero de bytes a copiar, que est
armazenado no campo que especifica o tamanho dos elementos da pilha. Desta maneira
possvel manipular os elementos da pilha sem que se saiba de que tipo eles so.
A funo de remoo de elementos da pilha Stack_Pop comea por assegurar que a pilha
existe e depois comporta-se tal como na implementao dinmica. Tal como na funo
Stack_Push, a cpia do elemento da pilha feita pela funo memcpy.
/****************** Interface da PILHA Abstracta ******************/
/* Nome : pilha_abs.h */
#ifndef _PILHA_ABSTRACTA
#define _PILHA_ABSTRACTA
typedef struct stack *PtStack;
/* Definio de Constantes */
#define
#define
#define
#define
#define
#define

OK
NULL_PTR
NULL_SIZE
NO_MEM
STACK_EMPTY
NO_STACK

0
1
2
3
4
7

/*
/*
/*
/*
/*
/*

operao realizada com sucesso */


ponteiro nulo */
tamanho nulo */
memria esgotada */
pilha vazia */
no foi instanciada qualquer pilha */

/* Aluso s Funes Exportadas pelo Mdulo */


PtStack Stack_Create (unsigned int sz);
/* Concretiza a pilha para elementos de sz bytes.
referncia da pilha criada ou NULL em caso de erro. */

Devolve

int Stack_Destroy (PtStack *pilha);


/* Destri a pilha referenciada por pilha e coloca a referncia a
NULL. Valores de retorno: OK ou NO_STACK. */
int Stack_Push (PtStack pilha, void *pelemento);
/* Coloca o elemento apontado por pelemento no topo da pilha
referenciada por pilha. Valores de retorno: OK, NO_STACK, NULL_PTR
ou NO_MEM. */
int Stack_Pop (PtStack pilha, void *pelemento);
/* Retira o elemento do topo da pilha referenciada por pilha, para o
elemento apontado por pelemento. Valores de retorno: OK, NO_STACK,
NULL_PTR ou STACK_EMPTY. */
#endif

Figura 7.39 - Ficheiro de interface da pilha abstracta com mltipla instanciao.

31

CAPTULO 7 : FILAS E PILHAS

/**************** Implementao da PILHA Abstracta ****************/


/* Nome : pilha_abs.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "pilha_abs.h"

/* Ficheiro de interface do mdulo */

/* Definio da Estrutura de Dados Interna da PILHA */


typedef struct no *PtNo;
struct no
{
void *pelemento;
PtNo pant;
};

/* ponteiro para o elemento */


/* ponteiro para o n anterior */

struct stack
{
unsigned int size;/* tamanho em nmero de bytes de cada elemento */
PtNo top;
/* topo da pilha */
};
/* Definio das Funes */
PtStack Stack_Create (unsigned int sz)
{
PtStack stack;
if (sz == 0) return NULL;
if ((stack = (PtStack) malloc (sizeof (struct stack))) == NULL)
return NULL;
stack->size = sz;
stack->top = NULL;
return stack;
}
int Stack_Destroy (PtStack *pilha)
{
PtStack stack = *pilha; PtNo tmp;
if (stack == NULL) return NO_STACK;
while (stack->top != NULL)
{
tmp = stack->top;
stack->top = stack->top->pant;
free (tmp->pelemento);
free (tmp);
}
free (stack);
*pilha = NULL;
return OK;
}

Figura 7.40 - Ficheiro de implementao da pilha abstracta com mltipla instanciao (1 parte).

PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C

32

int Stack_Push (PtStack pilha, void *pelemento)


{
PtStack stack = pilha; PtNo tmp;
if (stack == NULL) return NO_STACK;
if (pelemento == NULL) return NULL_PTR;
if ((tmp = (PtNo) malloc (sizeof (struct no))) == NULL)
return NO_MEM;
if ((tmp->pelemento = (void *) malloc (stack->size)) == NULL)
{
free (tmp); return NO_MEM;
}
tmp->pant = stack->top;
stack->top = tmp;
memcpy (stack->top->pelemento, pelemento, stack->size);
return OK;
}
int Stack_Pop (PtStack pilha, void *pelemento)
{
PtStack stack = pilha; PtNo tmp;
if (stack == NULL) return NO_STACK;
if (pelemento == NULL) return NULL_PTR;
if (stack->top == NULL) return STACK_EMPTY;
memcpy (pelemento, stack->top->pelemento, stack->size);
tmp = stack->top;
stack->top = stack->top->pant;
free (tmp->pelemento);
free (tmp);
return OK;
}

Figura 7.41 - Ficheiro de implementao da pilha abstracta com mltipla instanciao (2 parte).

A Figura 7.42 apresenta a utilizao da pilha abstracta para a resoluo do problema da


dupla inverso de uma linha de texto. um exemplo meramente acadmico, uma vez que
para inverter uma cadeia de caracteres no necessrio uma pilha, mas serve para mostrar a
utilizao de duas pilhas em simultneo no mesmo programa. Os caracteres so colocados
numa pilha e depois ao serem retirados so escritos no monitor por ordem inversa. Mas, se
forem de novo colocados noutra pilha, depois ao serem retirados da segunda pilha so
escritos no monitor pela ordem inicial. Temos assim uma dupla inverso.
Para podermos usar duas pilhas, primeiro preciso declarar duas variveis de tipo PtStack,
que vo apontar para as pilhas criadas atravs da invocao da funo Stack_Create. Na
invocao desta funo indicado que os elementos da pilha devem ter o tamanho de um
char, pelo que estamos a criar pilhas para caracteres. Para colocar e retirar caracteres numa
pilha obrigatrio passar s funes Stack_Push e Stack_Pop, como parmetro de
entrada, a pilha que se est a processar usando o respectivo ponteiro. Assim que as pilhas
no so mais precisas, elas devem ser destrudas invocando a funo Stack_Destroy para
cada uma das pilhas. Para simplificar o programa, o resultado de sada das operaes de
colocao e remoo de caracteres nas pilhas no testado para conferir se so bem
sucedidas ou no.

33

CAPTULO 7 : FILAS E PILHAS

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "pilha_abs.h" /* ficheiro de interface do mdulo da pilha */
int main (void)
{
char exp[81], cpilha;
int nc, c, st;
PtStack pilha1 = NULL, pilha2 = NULL;
pilha1 = Stack_Create (sizeof (char));
pilha2 = Stack_Create (sizeof (char));
printf ("Texto de entrada -> ");
nc = strlen (exp);

/* Criao da pilha 1 */
/* Criao da pilha 2 */

scanf ("%80s", exp);

for (c = 0; c < nc; c++)


st = Stack_Push (pilha1, &exp[c]); /* Colocar texto na pilha 1 */
printf ("Texto de sada depois de colocado na pilha 1 -> ");
for (c = 0; c < nc; c++)
{
st = Stack_Pop (pilha1, &cpilha); /* Retirar texto da pilha 1 */
printf ("%c", cpilha);
st = Stack_Push (pilha2, &cpilha); /* Colocar texto na pilha 2 */
}
printf ("\n");
printf ("Texto de sada depois de colocado na pilha 2 -> ");
for (c = 0; c < nc; c++)
{
st = Stack_Pop (pilha2, &cpilha); /* Retirar texto da pilha 2 */
printf ("%c", cpilha);
}
printf ("\n");
Stack_Destroy (&pilha1);
Stack_Destroy (&pilha2);

/* Destruio da pilha 1 */
/* Destruio da pilha 2 */

return EXIT_SUCCESS;
}

Figura 7.42 - Exemplo de utilizao da pilha abstracta com mltipla instanciao.

7.6 Leituras recomendadas


x 7 captulo do livro Data Structures, Algorithms and Software Principles in C, de
Thomas A. Standish, da editora Addison-Wesley Publishing Company, 1995.

Você também pode gostar