Anda di halaman 1dari 78

1.

Programação Imperativa

Aula 20
2 Sumário
Ficheiros de texto

3 Ficheiros de texto
Um ficheiro de texto não é mais do que uma sequência de caracteres
que ficam armazenados no disco do computador. Essa sequência de
caracteres é terminada com um caracter especial denominado fim-de-
ficheiro.

Um ficheiro de texto pode ser criado com um editor de texto. É


precisamente isso que fazem nas aulas práticas quando escrevem os
vossos programas. No entanto, nada impede que escrevam outras
coisas. Com um editor de texto, podem escrever cartas, poesias,
relatórios de trabalhos, e claro que também podem escrever
programas em C.

Em C, podemos dar instruções ao programa para ler e escrever em


ficheiros de texto, em vez de ler do teclado e escrever no ecrã. Isto
tem a vantagem de não se ter de estar sempre a introduzir os dados.

4 Leitura e escrita em ficheiros de texto


Para um programa ler e escrever em ficheiros de texto, podemos
utilizar as funções fscanf e fprintf. Estas funções são praticamente
iguais ao scanf e printf que já conheçem. A única diferença é que as
funções têm um argumento adicional que indica o ficheiro em que se
pretende ler ou escrever. Exemplo:
fscanf( f, "%d", &n );

A variável f é uma variável que tem de ser associada previamente a


um ficheiro, e que se declara como sendo do tipo FILE *.
O exemplo que se segue é um programa que lê dois números de um
ficheiro chamado "dados.txt" e escreve o quadrado desses números
num ficheiro novo chamado "resultados.txt"

Antes de se começar a ler ou escrever num ficheiro, deve ser


chamada a função fopen para abrir o ficheiro. O fopen tem 2
argumentos, o 1º é o nome do ficheiro e o 2º indica se o ficheiro vai
ser aberto para leitura ou escrita ("r" ou "w" respectivamente. Estes
nomes vêm do inglês, r-read, w-write).

Depois de abrir o ficheiro deve-se testar se a operação foi bem


sucedida. Para tal, compara-se o valor de f com a constante NULL que
está definida em stdio.h. Caso haja um erro, podemos escrever no
ecrã uma mensagem de erro e terminar o programa.

Se o ficheiro foi aberto com sucesso, poderemos ler e escrever. A


leitura e escrita são feitas sequencialmente do inicio para o fim do
ficheiro. No final, devemos fechar os ficheiros (anteriormente abertos
com a função fopen) com a função fclose. Aqui vai o programa:
#include <stdio.h>
#include <stdlib.h>

main()
{
FILE *in, *out;
int a, b;

/* abre o ficheiro dados.txt para leitura e associa-o a in */


in = fopen( "dados.txt", "r" );
if( in == NULL )
{
printf("ERRO: não consigo abrir o ficheiro dados.txt\n");
exit(1);
}

/* abre o ficheiro resultados.txt para escrita e associa-o a out


*/
out = fopen( "resultados.txt", "w" );
if( out == NULL )
{
printf("ERRO: não consigo abrir o ficheiro
resultados.txt\n");
exit(1);
}

/* leitura e escrita */
fscanf( in, "%d", &a );
fscanf( in, "%d", &b );
fprintf( out, "Este ficheiro foi criado pelo programa em C.\n"
);
fprintf( out, "%d\n", a*a );
fprintf( out, "%d\n", b*b );
/* fecha os ficheiros */
fclose( in );
fclose( out );
}

O scanf e o printf (e as variantes fscanf e fprintf) fazem input/output


formatado. Isto permite-lhes ler e escrever variáveis dos tipos base.
Em C, também existem funções específicas que permitem ler um
caracter e escrever um caracter. Essas funções chamam-
se getchar e putchar.
char c;

c = getchar(); /* equivalente a scanf("%c", &c) */


putchar( 'z' ); /* equivalente a printf("%c", 'z')
ou simplesmente printf("z") */

O getchar lê um caracter do teclado e o putchar escreve um caracter


para o ecrã. Existem variantes que permitem ler/escrever um caracter
de/para um ficheiro. Os nomes são fgetc e fputc.
char c;

c = fgetc( f ); /* equivalente a fscanf(f, "%c", &c) */


fputc( 'z' ); /* equivalente a fprintf(f, "%c", 'z')
ou simplesmente fprintf(f, "z") */

O getchar e putchar podem ser escritos como sendo casos particulares


do fgetc e fputc.
getchar() é equivalente a fgetc(stdin)
putchar(c) é equivalente a fputc(stdout,c)

stdin (standard input) é interpretado como sendo um ficheiro especial,


o teclado. stdout (standard output) também é interpretado como sendo
um ficheiro especial, o ecrã.

5 Leitura caracter a caracter


O programa que vimos acima não faz qualquer tipo de validação. Por
exemplo, se o ficheiro de entrada tiver apenas um número, o
programa irá funcionar mal. Ao tentar fazer o segundo fscanf, o
programa já vai estar a aceder a coisas que estão depois do fim-de-
ficheiro. Isso é equivalente a termos um array de dimensão 10 e
tentarmos aceder à posição 12!

A solução para este problema é verificar se já chegamos ao fim do


ficheiro imediatamente antes de tentarmos ler qualquer coisa.
Em stdio.h existe uma constante chamada EOF (end of file) que
significado o fim-de-ficheiro.

De seguida apresenta-se um programa que converte um ficheiro de


texto para maiúsculas. A ideia é abrir o ficheiro de entrada e ir lendo
os caracteres um após outro até chegar ao fim do ficheiro. Cada
caracter lido é convertido para maiúsculas e escrito para o ficheiro de
saída. Aqui vai o código,
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>

main()
{
FILE *in, *out;
char c;

/* abre o ficheiro pequenas.txt para leitura e associa-o a in */


in = fopen("pequenas.txt", "r" );
if( in == NULL )
{
printf("ERRO: não consigo abrir o ficheiro pequenas.txt\n");
exit(1);
}

/* abre o ficheiro grandes.txt para escrita e associa-o a out */


out = fopen( "grandes.txt", "w" );
if( out == NULL )
{
printf("ERRO: não consigo abrir o ficheiro grandes.txt\n");
exit(1);
}

c = fgetc( in );
while( c != EOF )
{
fputc( toupper(c), out );
c = fgetc( in );
}

fclose( in );
fclose( out );
}

O ciclo while faz o trabalho todo. A função fgetc lê um caracter de


cada vez. Esse caracter pode ser qualquer coisa, incluindo o caracter
espaço (' '), o caracter mudança-de-linha ('\n') e o caracter fim-de-
ficheiro (EOF).

A função toupper está definida em ctype.h e converte o caracter de


entrada para maiúscula.
Para tornarem o programa mais genérico, podem pedir ao utilizador
para introduzir o nome dos ficheiros de entrada e saída.
Experimentem fazer isso como exercício.

6 Outro exemplo
Vamos supor que temos um ficheiro chamado nomes.txt cujo conteúdo
são nomes de pessoas, um nome por linha. Pretende-se fazer um
programa que leia esses nomes para um array de nomes. Aqui vai
uma tentativa,
#include <stdio.h>
#include <stdlib.h>

/* retorna 1 se 'c' é uma vogal, retorna 0 caso contrário */


int eh_vogal( char c )
{
return ( c == 'a' || c == 'A' ||
c == 'e' || c == 'E' ||
c == 'i' || c == 'I' ||
c == 'o' || c == 'O' ||
c == 'u' || c == 'U'
);
}

/* retorna o número de vogais da string 's' */


int conta_vogais( char *s )
{
int i, vogais;

vogais = 0;
for( i=0; i< strlen(s); i++ )
if( eh_vogal(s[i]) )
vogais++;

return vogais;
}

main()
{
FILE *f;
int i;
int ultimo;
char nomes[100][30];

f = fopen("nomes.txt", "r");
if( f == NULL )
erro("...");

i = 0;
while( fgets(nomes[i], 30, f) != NULL && (i<100) )
{
ultimo = strlen( nomes[i] );
nomes[i][ultimo-1] = '\0'; /* para mandar fora o '\n'
*/
printf("%s %d\n", nomes[i], conta_vogais(nomes[i]) );
i++;
}

fclose( f );
}

Novamente, o sumo do programa está no ciclo while. A função fgets é


a variante do gets para ficheiros. O fgets tem 3 argumentos,

 um array de caracteres onde a linha vai ser guardada.


 um número que indica o número máximo de caracteres que
pode ser lido (incluindo o '\0').
 o ficheiro de onde pretendemos ler.

Se chegarmos ao fim do ficheiro ou acontecer outro erro qualquer,


o fgets retorna NULL. Além de guardar uma linha de texto no array
nome, o fgets também guarda o caracter de fim de linha ('\n') caso
exista. Ou seja, se o ficheiro nomes.txt for,
susana
toze
miguel angelo
maria

ao fazer o primeiro fgets a variável nome[0] vai ficar assim:


0 1 2 3 4 5 6 7
's' 'u' 's' 'a' 'n' 'a' '\n' '\0'

Uma nota acerca do gets e fgets

O fgets é mais seguro que o gets. Aliás, de agora em diante devem


deixar de utilizar o gets porque o gets não faz a validação do número
máximo de caracteres do array. Se pretenderem ler um string do
teclado podem fazer,
char s[80];

fgets( s, 80, stdin );

Isto permite que s guarde 79 caracteres mais o '\0'. Se por acaso o


utilizador escrever mais do que 79, os caracteres adicionais serão
ignorados. Se fizerem o mesmo com a função gets o programa estoira.
2. Arquivos
Os arquivos permitem gravar os dados de um programa de forma permanente em mídia
digital.
Vantagens de utilizar arquivos

 Armazenamento permanente de dados: as informações permanecem disponíveis


mesmo que o programa que as gravou tenha sido encerrado, ou seja, podem ser
consultadas a qualquer momento.
 Grande quantidade dados pode ser armazenada: A quantidade de dados que pode
ser armazenada depende apenas da capacidade disponível da mídia de
armazenamento. Normalmente a capacidade da mídia é muito maior do que a
capacidade disponível na memória RAM.
 Acesso concorrente: Vários programas podem acessar um arquivo de forma
concorrente.

A linguagem C trata os arquivos como uma sequência de bytes. Esta sequência pode ser
manipulada de várias formas e para tanto, existem funções em C para criar, ler e escrever
o conteúdo de arquivos independente de quais sejam os dados armazenados.

6.1.1 Tipos de arquivos


Em C trabalhamos com dois tipos de arquivos:
1) Arquivo texto: Armazena caracteres que podem ser mostrados diretamente na tela ou
modificados por um editor de texto.
Exemplos de arquivos texto: documentos de texto, código fonte C, páginas XHTML.
2) Arquivo binário é uma sequência de bits que obedece regras do programa que o gerou.
Exemplos: Executáveis, documentos do Word, arquivos compactados.

6.1.2 O ponteiro para arquivo


Em C, o arquivo é manipulado através de um ponteiro especial para o arquivo.
A função deste ponteiro é “apontar” a localização de um registro.
Sintaxe:
FILE < *ponteiro >
O tipo FILE está definido na biblioteca stdio.h.
Exemplo de declaração de um ponteiro para arquivo em C:
FILE *pont_arq;

Lembrando que FILE deve ser escrito em letras maiúsculas.

6.1.3 Operações com arquivos do tipo texto


Abertura de arquivos.
Para trabalhar com um arquivo, a primeira operação necessária é abrir este arquivo.
Sintaxe de abertura de arquivo:
< ponteiro > = fopen(“nome do arquivo”,”tipo de abertura”);
A função fopen recebe como parâmetros o nome do arquivo a ser aberto e o tipo de
abertura a ser realizado.
Depois de aberto, realizamos as operações necessárias e fechamos o arquivo.
Para fechar o arquivo usamos a função fclose.
Sintaxe de fechamento de arquivo
fclose< ponteiro >;

Lembrando que o ponteiro deve ser a mesma variável ponteiro associada ao comando de
abertura de arquivo.
Tipos de abertura de arquivos
r: Permissão de abertura somente para leitura. É necessário que o arquivo já esteja
presente no disco.
w: Permissão de abertura para escrita (gravação). Este código cria o arquivo caso ele não
exista, e caso o mesmo exista ele recria o arquivo novamente fazendo com que o
conteúdo seja perdido. Portanto devemos tomar muito cuidado ao usar esse tipo de
abertura.
a: Permissão para abrir um arquivo texto para escrita(gravação), permite acrescentar
novos dados ao final do arquivo. Caso não exista, ele será criado.
Exemplo de criação de arquivos.

1. #include <stdio.h>
2. #include <stdlib.h>
3.
4. int main(void)
5. {
6. // criando a variável ponteiro para o arquivo
7. FILE *pont_arq;
8.
9. //abrindo o arquivo
10. pont_arq = fopen("arquivo.txt", "a");
11.
12. // fechando arquivo
13. fclose(pont_arq);
14.
15. //mensagem para o usuário
16. printf("O arquivo foi criado com sucesso!");
17.
18. system("pause");
19. return(0);
20. }

Tela de execução
Tela de execução do exemplo criação de arquivo
Problemas na abertura de arquivos
Na prática, nem sempre é possível abrir um arquivo. Podem ocorrer algumas situações
que impedem essa abertura, por exemplo:

 Você está tentando abrir um arquivo no modo de leitura, mas o arquivo não existe;
 Você não tem permissão para ler ou gravar no arquivo;
 O arquivo está bloqueado por estar sendo usado por outro programa.

Quando o arquivo não pode ser aberto a função fopen retorna o valor NULL.
É altamente recomendável criar um trecho de código a fim de verificar se a abertura
ocorreu com sucesso ou não.
Exemplo:

if (pont_arq == NULL)
{
printf("ERRO! O arquivo não foi aberto!\n");
}
else
{
printf("O arquivo foi aberto com sucesso!");
}

Gravando dados em arquivos


A função fprintf armazena dados em um arquivo. Seu funcionamento é muito semelhante
ao printf, a diferença principal é a existência de um parâmetro para informar o arquivo
onde os dados serão armazenados.
Sintaxe:
fprintf(nome_do_ponteiro_para_o_arquivo, “%s”,variavel_string)

1. #include <stdio.h>
2. #include <stdlib.h>
3. int main(void)
4. {
5. FILE *pont_arq; // cria variável ponteiro para o arquivo
6. char palavra[20]; // variável do tipo string
7.
8. //abrindo o arquivo com tipo de abertura w
9. pont_arq = fopen("arquivo_palavra.txt", "w");
10.
11. //testando se o arquivo foi realmente criado
12. if(pont_arq == NULL)
13. {
14. printf("Erro na abertura do arquivo!");
15. return 1;
16. }
17.
18. printf("Escreva uma palavra para testar gravacao de arquivo: ");
19. scanf("%s", palavra);
20.
21. //usando fprintf para armazenar a string no arquivo
22. fprintf(pont_arq, "%s", palavra);
23.
24. //usando fclose para fechar o arquivo
25. fclose(pont_arq);
26.
27. printf("Dados gravados com sucesso!");
28.
29. getch();
30. return(0);
31. }

Tela de execução

Tela de execução do exemplo abrindo, gravando e fechando arquivo


Leitura de arquivos
Leitura caracter por caracter – Função getc()
Faz a leitura de um caracter no arquivo.
Sintaxe:
getc(ponteiro_do_arquivo);
Para realizar a leitura de um arquivo inteiro caracter por caracter podemos usar getc
dentro de um laço de repetição.

do
{
//faz a leitura do caracter no arquivo apontado por pont_arq
c = getc(pont_arq);

//exibe o caracter lido na tela


printf("%c" , c);

}while (c != EOF);

A leitura será realizada até que o final do arquivo seja encontrado.


Leitura de strings – Função fgets()
É utilizada para leitura de strings em um arquivo. Realiza a leitura dos caracteres até o
final da linha quando encontra o caracter \n. A leitura é efetuada de tal forma que a string
lida é armazenada em um ponteiro do tipo char. A função pode ser finalizada quando
encontrar o final do arquivo, neste caso retorna o endereço da string lida. Se ocorrer algum
erro na leitura do arquivo, a função retorna NULL.

1. //Leitura de arquivo
2. #include <stdio.h>
3. #include <stdlib.h>
4.
5. int main(void)
6. {
7. FILE *pont_arq;
8. char texto_str[20];
9.
10. //abrindo o arquivo_frase em modo "somente leitura"
11. pont_arq = fopen("arquivo_palavra.txt", "r");
12.
13. //enquanto não for fim de arquivo o looping será executado
14. //e será impresso o texto
15. while(fgets(texto_str, 20, pont_arq) != NULL)
16. printf("%s", texto_str);
17.
18. //fechando o arquivo
19. fclose(pont_arq);
20.
21. getch();
22. return(0);
23. }

Tela de execução
Tela de execução do exemplo leitura de arquivo
Até a próxima!

Trabalhando com arquivos[editar | editar código-fonte]


Já vimos como podemos receber e enviar dados para usuário através do teclado e da tela;
agora veremos também como ler e gravar dados em arquivos, o que é aliás muito
importante ou até essencial em muitas aplicações.
Assim como as funções de entrada/saída padrão (teclado e tela), as funções de
entrada/saída em arquivos estão declaradas no cabeçalho stdio.h que significa "STanDard
Input-Output". Aliás, as funções para manipulação de arquivos são muito semelhantes às
usadas para entrada/saída padrão. Como já dissemos na seção sobre a entrada e saída
padrões, a manipulação de arquivos também se dá por meio de fluxos (streams).
Na manipulação de um arquivo, há basicamente três etapas que precisam ser realizadas:

1. abrir o arquivo;
2. ler e/ou gravar os dados desejados;
3. fechar o arquivo.
Em C, todas as operações realizadas com arquivos envolvem seu identificador de fluxo,
que é uma variável do tipo FILE * (sobre o qual não cabe agora falar). Para declarar um
identificador de fluxo, faça como se fosse uma variável normal:

FILE *fp; // não se esqueça do asterisco!

Abrindo e fechando um arquivo[editar | editar código-fonte]


Não surpreendentemente, a primeira coisa que se deve fazer para manipular um arquivo é
abri-lo. Para isso, usamos a função fopen(). Sua sintaxe é:
FILE *fopen (char *nome_do_arquivo, char *modo_de_acesso);

 O nome do arquivo deve ser uma string ou com o caminho completo (por
exemplo, /usr/share/appname/app.conf ou C:\Documentos\nomes.txt) ou o
caminho em relação ao diretório atual (nomes.txt, ../app.conf) do arquivo que se
deseja abrir ou criar.

 O modo de acesso é uma string que contém uma seqüência de caracteres que dizem
se o arquivo será aberto para gravação ou leitura. Depois de aberto o arquivo, você só
poderá executar os tipos de ação previstos pelo modo de acesso: não poderá ler de
um arquivo que foi aberto somente para escrita, por exemplo. Os modos de acesso
estão descritos na tabela a seguir.

Modo Significado

Abre o arquivo somente para leitura. O arquivo deve existir. (O r vem do


r
inglês read, ler)
r+ Abre o arquivo para leitura e escrita. O arquivo deve existir.

Abre o arquivo somente para escrita no início do arquivo. Apagará o conteúdo


w do arquivo se ele já existir, criará um arquivo novo se não existir. (O w vem do
inglês write, escrever)

w+ Abre o arquivo para escrita e leitura, apagando o conteúdo pré-existente.

Abre o arquivo para escrita no final do arquivo. Não apaga o conteúdo pré-
a
existente. (O a vem do inglês append, adicionar, apender)

a+ Abre o arquivo para escrita no final do arquivo e leitura.

Em ambientes DOS/Windows, ao ler arquivos binários (por exemplo, programas


executáveis ou certos tipos de arquivos de dados), deve-se adicionar o caractere "b" ao
final da string de modo (por exemplo, "wb" ou "r+b") para que o arquivo seja lido/gravado
corretamente.
Isso é necessário porque no modo texto (o padrão quando não é adicionado o b) ocorrem
algumas traduções de caracteres (por exemplo, a terminação de linha "\r\n" é substituída
apenas por "\n" na leitura) que poderiam afetar a leitura/gravação dos arquivos binários
(indevidamente inserindo ou suprimindo caracteres).

 O valor de retorno da função fopen() é muito importante! Ele é o identificador do fluxo


que você abriu e é só com ele que você conseguirá ler e escrever no arquivo aberto.

 Se houver um erro na abertura/criação do arquivo, a função retornará o valor NULL . O


erro geralmente acontece por duas razões:
o O arquivo não existe, caso tenha sido requisitado para leitura.
o O usuário atual não tem permissão para abrir o arquivo com o modo de acesso
pedido. Por exemplo, o arquivo é somente-leitura, ou está bloqueado para
gravação por outro programa, ou pertence a outro usuário e não tem permissão
para ser lido por outros.
Ao terminar de usar um arquivo, você deve fechá-lo. Isso é feito pela função fclose():
int fclose (FILE *fluxo);

 O único argumento é o identificador do fluxo (retornado por fopen). O valor de retorno


indica o sucesso da operação com o valor zero.
 Fechar um arquivo faz com que qualquer caractere que tenha permanecido no "buffer"
associado ao fluxo de saída seja gravado. Mas, o que é este "buffer"? Quando você
envia caracteres para serem gravados em um arquivo, estes caracteres são
armazenados temporariamente em uma área de memória (o "buffer") em vez de serem
escritos em disco imediatamente. Quando o "buffer" estiver cheio, seu conteúdo é
escrito no disco de uma vez. A razão para se fazer isto tem a ver com a eficiência nas
leituras e gravações de arquivos. Se, para cada caractere que fôssemos gravar,
tivéssemos que posicionar a cabeça de gravação em um ponto específico do disco,
apenas para gravar aquele caractere, as gravações seriam muito lentas. Assim estas
gravações só serão efetuadas quando houver um volume razoável de informações a
serem gravadas ou quando o arquivo for fechado.
 A função exit() fecha todos os arquivos que um programa tiver aberto.
 A função fflush() força a gravação de todos os caracteres que estão no buffer para o
arquivo.
6.3.1 Exemplo[editar | editar código-fonte]
Um pequeno exemplo apenas para ilustrar a abertura e fechamento de arquivos:

#include <stdio.h>

int main()
{
FILE *fp;
fp = fopen ("README", "w");
if (fp == NULL) {
printf ("Houve um erro ao abrir o arquivo.\n");
return 1;
}
printf ("Arquivo README criado com sucesso.\n");
fclose (fp);
return 0;
}

6.3.2 Arquivos pré-definidos[editar | editar código-fonte]


Na biblioteca padrão do C, existem alguns fluxos pré-definidos que não precisam (nem
devem) ser abertos nem fechados:

 stdin: dispositivo de entrada padrão (geralmente o teclado)


 stdout: dispositivo de saída padrão (geralmente o vídeo)
 stderr: dispositivo de saída de erro padrão (geralmente o vídeo)
 stdaux: dispositivo de saída auxiliar (em muitos sistemas, associado à porta serial)
 stdprn: dispositivo de impressão padrão (em muitos sistemas, associado à porta
paralela)

Escrevendo em arquivos[editar | editar código-fonte]


Para escrever em arquivos, há quatro funções, das quais três são análogas às usadas
para saída padrão:

Saída padrão Arquivos Explicação

putchar fputc Imprime apenas um caractere.

puts fputs Imprime uma string diretamente, sem nenhuma formatação.

printf fprintf Imprime uma string formatada.


N/A fwrite Grava dados binários para um arquivo.

A seguir apresentamos os protótipos dessas funções:


void fputc (int caractere, FILE *fluxo);
void fputs (char *string, FILE *fluxo);
void fprintf (FILE *fluxo, char *formatação, ...);
int fwrite (void *dados, int tamanho_do_elemento, int num_elementos, FILE
*fluxo);

 Sintaxe quase igual à de printf(); só é necessário adicionar o identificador de fluxo no


início.
6.4.1 fwrite[editar | editar código-fonte]

 Esta função envolve os conceitos de ponteiro e vetor, que só serão abordados mais
tarde.
A função fwrite() funciona como a sua companheira fread(), porém escreve no arquivo.
Seu protótipo é:

unsigned fwrite(void *buffer,int numero_de_bytes,int count,FILE


*fp);

A função retorna o número de itens escritos. Este valor será igual a count a menos que
ocorra algum erro. O exemplo abaixo ilustra o uso de fwrite e fread para gravar e
posteriormente ler uma variável float em um arquivo binário.

#include <stdio.h>
#include <stdlib.h>

int main()
{
FILE *pf;
float pi = 3.1415;
float pilido;
if((pf = fopen("arquivo.bin", "wb")) == NULL) /* Abre arquivo
binário para escrita */
{
printf("Erro na abertura do arquivo");
exit(1);
}
if(fwrite(&pi, sizeof(float), 1,pf) != 1) /* Escreve a
variável pi */
printf("Erro na escrita do arquivo");
fclose(pf); /* Fecha o
arquivo */
if((pf = fopen("arquivo.bin", "rb")) == NULL) /* Abre o arquivo
novamente para leitura */
{
printf("Erro na abertura do arquivo");
exit(1);
}
if(fread(&pilido, sizeof(float), 1,pf) != 1) /* Le em pilido o
valor da variável armazenada anteriormente */
printf("Erro na leitura do arquivo");
printf("\nO valor de PI, lido do arquivo e': %f", pilido);
fclose(pf);
return 0;
}

Nota-se o uso do operador sizeof, que retorna o tamanho em bytes da variável ou do tipo
de dados.

6.4.2 fputc[editar | editar código-fonte]


A função fputc é a primeira função de escrita de arquivo que veremos. Seu protótipo é:

int fputc (int ch, FILE *fp);

Escreve um caractere no arquivo.O programa a seguir lê uma string do teclado e escreve-


a, caractere por caractere em um arquivo em disco (o arquivo arquivo.txt, que será aberto
no diretório corrente).

#include <stdio.h>
#include <stdlib.h>

int main()
{
FILE *fp;
char string[100];
int i;
fp = fopen("arquivo.txt","w"); /* Arquivo ASCII, para
escrita */
if(!fp)
{
printf( "Erro na abertura do arquivo");
exit(0);
}
printf("Entre com a string a ser gravada no arquivo:");
gets(string);
for(i=0; string[i]; i++) fputc(string[i], fp); /* Grava a string,
caractere a caractere */
fclose(fp);
return 0;
}

Depois de executar este programa, verifique o conteúdo do arquivo arquivo.txt (você pode
usar qualquer editor de textos). Você verá que a string que você digitou está armazenada
nele.

Lendo de arquivos[editar | editar código-fonte]


Novamente, há quatro funções, das quais três se assemelham às usadas para a saída
padrão:
Saída padrão Arquivos Explicação

getchar fgetc Recebe apenas um caractere.

gets fgets Lê uma string (geralmente uma linha inteira).

scanf fscanf Recebe uma string formatada.

N/A fread Lê dados binários de um arquivo.

int fgetc (FILE *fluxo);


void fgets (char *string, int tamanho, FILE *fluxo);
void fscanf (FILE *fluxo, char *formatação, ...);
int fread (void *dados, int tamanho_do_elemento, int num_elementos, FILE
*fluxo);

Este módulo tem a seguinte tarefa pendente: criar exemplos de uso das funções

6.5.1 fgetc[editar | editar código-fonte]

 Está função requer como parâmetro o indicador de fluxo do arquivo, retorna um


caractere do arquivo ou EOF, caso ocorra um erro ou o final do arquivo seja atingido,
podendo ser verificado respectivamente por ferror e feof.
Exemplo:

#include <stdio.h>
#include <stdlib.h>

int main()
{
FILE *fl;
int c;

if((fl = fopen("caminho/do/arquivo", "r")) == NULL)


{
perror("Erro: fopen");
exit(EXIT_FAILURE);
}

while((c = fgetc(fl)) != EOF)


printf("Caractere lido: %c\n", c);

if((c == EOF) && (feof(fl) == 0) && (ferror(fl) != 0))


perror("Erro: fgetc");

fclose(fl);
return EXIT_SUCCESS;
}

6.5.2 fgets[editar | editar código-fonte]

 Ao chamar a função fgets(), você deve fornecer o ponteiro para a string onde os dados
lidos devem ser guardados, além do tamanho máximo dos dados a serem lidos (para
que a memória reservada à string não seja ultrapassada).
Para se ler uma string num arquivo podemos usar fgets() cujo protótipo é:

char *fgets (char *str, int tamanho,FILE *fp);

A função recebe 3 argumentos: a string a ser lida, o limite máximo de caracteres a serem
lidos e o ponteiro para FILE, que está associado ao arquivo de onde a string será lida. A
função lê a string até que um caracter de nova linha seja lido ou tamanho-1 caracteres
tenham sido lidos. Se o caracter de nova linha ('\n') for lido, ele fará parte da string, o que
não acontecia com gets.
A função fgets é semelhante à função gets(), porém, além dela poder fazer a leitura a partir
de um arquivo de dados e incluir o caracter de nova linha na string, ela ainda especifica o
tamanho máximo da string de entrada. Como vimos, a função gets não tinha este controle,
o que poderia acarretar erros de "estouro de buffer". Portanto, levando em conta que o
ponteiro fp pode ser substituído por stdin, como vimos acima, uma alternativa ao uso de
gets é usar a seguinte construção:

fgets (str, tamanho, stdin);

6.5.3 fscanf[editar | editar código-fonte]

 Sintaxe quase igual à de scanf(); só é necessário adicionar o identificador de fluxo no


início.
6.5.4 fscanf[editar | editar código-fonte]
A função fscanf() funciona como a função scanf(). A diferença é que fscanf() lê de um
arquivo e não do teclado do computador. Protótipo:

int fscanf (FILE *fp,char *str,...);


#include <stdio.h>
#include <stdlib.h>
int main()
{
FILE *p;
char str[80],c;
printf("\n\n Entre com um nome para o arquivo:\n"); /* Le um
nome para o arquivo a ser aberto: */
gets(str);
if (!(p = fopen(str,"w"))) /*
Caso ocorra algum erro na abertura do arquivo..*/
{ /* o
programa aborta automaticamente */
printf("Erro! Impossivel abrir o arquivo!\n");
exit(1);
}
fprintf(p,"Este e um arquivo chamado:\n%s\n", str);
fclose(p); /* Se nao
houve erro, imprime no arquivo, fecha ...*/
p = fopen(str,"r"); /* abre
novamente para a leitura */
while (!feof(p))
{
fscanf(p,"%c",&c);
printf("%c",c);
}
fclose(p);
return 0;
}

6.5.5 fread[editar | editar código-fonte]

 Essa função envolve os conceitos de ponteiro e vetor, que só serão abordados mais
tarde.
Podemos escrever e ler blocos de dados. Para tanto, temos as funções fread() e fwrite(). O
protótipo de fread() é:

unsigned fread (void *buffer, int numero_de_bytes, int count, FILE


*fp);

O buffer é a região de memória na qual serão armazenados os dados lidos. O número de


bytes é o tamanho da unidade a ser lida. count indica quantas unidades devem ser lidas.
Isto significa que o número total de bytes lidos é:

numero_de_bytes*count

A função retorna o número de unidades efetivamente lidas. Este número pode ser menor
que count quando o fim do arquivo for encontrado ou ocorrer algum erro.
Quando o arquivo for aberto para dados binários, fread pode ler qualquer tipo de dados.

Movendo pelo arquivo[editar | editar código-fonte]

6.6.1 fseek[editar | editar código-fonte]


Para se fazer procuras e acessos randômicos em arquivos usa-se a função fseek(). Esta
move a posição corrente de leitura ou escrita no arquivo de um valor especificado, a partir
de um ponto especificado. Seu protótipo é:

int fseek (FILE *fp, long numbytes, int origem);


O parâmetro origem determina a partir de onde os numbytes de movimentação serão
contados. Os valores possíveis são definidos por macros em stdio.h e são:

Nome Valor Significado


SEEK_SET 0 Início do arquivo
SEEK_CUR 1 Ponto corrente no arquivo
SEEK_END 2 Fim do arquivo

Tendo-se definido a partir de onde irá se contar, numbytes determina quantos bytes de
deslocamento serão dados na posição atual.

6.6.2 rewind[editar | editar código-fonte]


Volta para o começo do arquivo de um fluxo

6.6.3 feof[editar | editar código-fonte]


EOF ("End of file") indica o fim de um arquivo. Às vezes, é necessário verificar se um
arquivo chegou ao fim. Para isto podemos usar a função feof(). Ela retorna não-zero se o
arquivo chegou ao EOF, caso contrário retorna zero. Seu protótipo é:

int feof (FILE *fp);

Outra forma de se verificar se o final do arquivo foi atingido é comparar o caractere lido por
getc com EOF. O programa a seguir abre um arquivo já existente e o lê, caracter por
caracter, até que o final do arquivo seja atingido. Os caracteres lidos são apresentados na
tela:

#include <stdio.h>
#include <stdlib.h>

int main()
{
FILE *fp;
char c;
fp = fopen("arquivo.txt","r"); /* Arquivo ASCII, para leitura */
if(!fp)
{
printf( "Erro na abertura do arquivo");
exit(0);
}
while((c = getc(fp) ) != EOF) /* Enquanto não chegar ao final
do arquivo */
printf("%c", c); /* imprime o caracter lido */
fclose(fp);
return 0;
}

Verifique o exemplo.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main()
{
FILE *p;
char c, str[30], frase[80] = "Este e um arquivo chamado: ";
int i;
printf("\n\n Entre com um nome para o arquivo:\n");
gets(str); /* Le um nome para o
arquivo a ser aberto: */
if (!(p = fopen(str,"w"))) /* Caso ocorra algum erro na
abertura do arquivo..*/
{
printf("Erro! Impossivel abrir o arquivo!\n");
exit(1); /* o programa aborta automaticamente */
}
strcat(frase, str);
for (i=0; frase[i]; i++)
putc(frase[i],p);
fclose(p); /* Se nao houve erro,imprime no
arquivo e o fecha ...*/
p = fopen(str,"r"); /* Abre novamente para leitura
*/
c = getc(p); /* Le o primeiro caracter */
while (!feof(p)) /* Enquanto não se chegar no
final do arquivo */
{
printf("%c",c); /* Imprime o caracter na tela */
c = getc(p); /* Le um novo caracter no arquivo */
}
fclose(p); /* Fecha o arquivo */
}

Outras funções[editar | editar código-fonte]

Função Explicação

remove Remove um arquivo especificado

6.7.1 ferror e perror[editar | editar código-fonte]


Protótipo de ferror:

int ferror (FILE *fp);

A função retorna zero, se nenhum erro ocorreu e um número diferente de zero se algum
erro ocorreu durante o acesso ao arquivo. se torna muito útil quando queremos verificar se
cada acesso a um arquivo teve sucesso, de modo que consigamos garantir a integridade
dos nossos dados. Na maioria dos casos, se um arquivo pode ser aberto, ele pode ser lido
ou gravado.
Porém, existem situações em que isto não ocorre. Por exemplo, pode acabar o espaço em
disco enquanto gravamos, ou o disco pode estar com problemas e não conseguimos ler,
etc. Uma função que pode ser usada em conjunto com ferror() é a função perror() (print
error), cujo argumento é uma string que normalmente indica em que parte do programa o
problema ocorreu.

#include <stdio.h>
#include <stdlib.h>

int main()
{
FILE *pf;
char string[100];
if((pf = fopen("arquivo.txt","w")) ==NULL)
{
printf("\nNao consigo abrir o arquivo ! ");
exit(1);
}
do
{
printf("\nDigite uma nova string. Para terminar, digite <enter>:
");
gets(string);
fputs(string, pf);
putc('\n', pf);
if(ferror(pf))
{
perror("Erro na gravacao");
fclose(pf);
exit(1);
}
}while (strlen(string) > 0);
fclose(pf);
}

Vai a este link

https://www.codingame.com/playgrounds/24988/programacao-c/exercicios-arquivos

3. Exercícios de Programação II e
respectivas resoluções

Observações

Note-se que:

1. As soluções apresentadas não são as únicas, e possivelmente também


não as "melhores".
2. Estas soluções por vezes incluem algumas características do C que não
tinham sido ainda apresentadas nas aulas teóricas correspondentes.
No entanto, uma vez que as soluções são apresentadas como
elemento de estudo, julgámos ser vantajoso apresentar código C tão
completo quanto possível.
3. Quando as soluções são semelhantes entre si, as versões vão sendo
sucessivamente menos comentadas.
4. Ao longo do texto usam-se sempre as expressões "abrir ficheiro",
"fechar ficheiro", etc., que devem, em rigor, ser entendidas como
"abrir canal de acesso ao ficheiro", etc.
5. Note que as funções scanf() e fscanf() devolvem o número de
argumentos lidos.

Exercício 1

6.9.1 Alínea a)

Mostrar no terminal o conteúdo de um ficheiro de texto.

Resolução

O programa deve, ao ser executado, mostrar no ecrã o conteúdo dum ficheiro


de texto já existente algures no disco.

 Algoritmo: recordar as cópias da primária...


 Algoritmo passo-a-passo:
1. Abrir o ficheiro de entrada.
2. Ler um caractere e mostrá-lo enquanto não se atingir o fim do
ficheiro.
3. Fechar o ficheiro de entrada.
 Linguagem C: ex1a.c
 Linguagem C (alternativa usando versão "sofisticada" da
função main()): ex1a_alt.c
 Notas:
1. Note-se a utilização das macros (definições de pré-processador,
com #define.} EXIT_FAILURE e EXIT_SUCCESS (definidas
em stdlib.h). Estes valores devem ser utilizados como
argumentos na chamada da função exit() para se assinalar a
ocorrência de erros ou a não ocorrência de erros,
respectivamente. A instrução return x na função main(), e
apenas nesta, tem o mesmo significado que chamar exit(x).
2. Note-se também que, na primeira versão do programa, se
utiliza a função fgets() e não gets() para leitura do nome do
ficheiro do teclado. Tal deve-se a que apenas por intermédio
de fgets() é possível limitar o número de caracteres lidos aos
que cabem na cadeia de caracteres de destino. A
macro FILENAME_MAX (definida em stdio.h) tem como valor o
máximo número de caracteres possíveis de utilizar no nome de
um ficheiro (directórios incluídos) na máquina e sistema
operativo em que o programa for compilado. Assim garante-se
o correcto funcionamento do programa em qualquer ambiente
(repare-se a necessidade de espaço na matriz nome para o
terminador da cadeia).
3. Note-se ainda que na versão alternativa do código se utilizam os
parâmetros da função main, tal como especificados na norma
ANSI-C, para aceder aos argumentos passados ao programa na
linha de comando do sistema operativo (e.g., MS-DOS ou UNIX).
Os parâmetros são argc e argv. O primeiro contém o número de
argumentos passados ao programa contando com o próprio
nome do programa. O segundo consiste numa matriz (array) de
ponteiros para cadeias de caracteres, cada uma contendo seu
argumento (sendo o primeiro o nome do programa). Assim, se o
programa executável se chamar meutype o comando:
4. meutype texto.txt

mostra no ecrã o conteúdo do ficheiro texto.txt.

5. Finalmente, aquando da escrita no ecrã, apenas se testa a


condição de erro no final da escrita e por intermédio da
função ferror(); esta prática justifica-se muitas vezes, pois
permite simultaneamente simplificar o código e torná-lo mais
eficiente.

6.9.2 Alínea b)

Armazenar em ficheiro um texto lido do terminal.

Resolução

Entende-se por "texto" o que quer que seja escrito no teclado pelo utilizador
do programa até que seja premida a combinação de teclas equivalente a um
fim de ficheiro, que no MS-DOS é ctrl-z e em Unix é ctrl-d.

 Algoritmo: idem...
 Algoritmo passo-a-passo:
1. Abrir o ficheiro de saída.
2. Ler um caractere do teclado e guardá-lo no ficheiro de saída
enquanto não se atingir o fim das entradas de dados pelo
utilizador.
3. Fechar o ficheiro de saída.
 Linguagem C: ex1b.c
 Notas: Repare na semelhança entre as soluções dos três primeiros
exercícios (1.a), 1.b) e 1.c)).

6.9.3 Alínea c)

Fazer uma cópia dum ficheiro de texto.

Resolução

 Algoritmo: idem...
 Algoritmo passo-a-passo:
1. Abrir o ficheiro de entrada.
2. Abrir o ficheiro de saída.
3. Ler um caractere do ficheiro de entrada, enquanto não se
atingir o fim do ficheiro, e guardá-lo no ficheiro de saída.
4. Fechar o ficheiro de entrada.
5. Fechar o ficheiro de saída.
 Linguagem C: ex1c.c

6.9.4 Alínea d)

Concatenar dois ficheiros de texto.

Resolução

Neste caso optou-se por escrever o resultado da concatenação no ecrã, mas as


alterações necessárias para que o resultado da concatenação seja escrito num
terceiro ficheiro são triviais.

 Algoritmo: Repetir algoritmo do exercício 1.a) duas vezes.


 Algoritmo passo-a-passo:
1. Abrir o ficheiro de entrada 1.
2. Ler um caractere do ficheiro de entrada 1, enquanto não se
atingir o fim do ficheiro, e escrevê-lo no ecrã.
3. Fechar o ficheiro de entrada 1.
4. Abrir o ficheiro de entrada 2.
5. Ler um caractere do ficheiro de entrada 2, enquanto não se
atingir o fim do ficheiro, e escrevê-lo no ecrã.
6. Fechar o ficheiro de entrada 2.
 Linguagem C: ex1d.c
 Linguagem C (alternativa usando um ciclo for): ex1d_alt.c
 Notas:
1. A utilização do ciclo for na solução alternativa permite ao
programa concatenar um qualquer número de ficheiros, e não
apenas dois, como na versão original.
2. Note-se que, apesar de se abrirem (e fecharem) potencialmente
muitos ficheiros, utiliza-se apenas uma variável do tipo FILE *.

6.9.5 Alínea e)

Comparar dois ficheiros de texto linha a linha e mostrar a primeira linha onde
eles diferem.

Resolução

 Algoritmo: Ler a par linhas de ambos os ficheiros enquanto nenhum


deles terminar. Se alguma vez as linhas forem diferentes, assinalar a
diferença, mostrar ambas as linhas, e sair. Caso um ficheiro termine
antes do outro indicar esta ocorrência e sair.
 Algoritmo passo-a-passo:
1. Abrir o ficheiro de entrada 1.
2. Abrir o ficheiro de entrada 2.
3. Repetir indefinidamente:
1. Ler uma linha de cada um dos ficheiros.
2. Se alguma das leituras falhar, verificar se falharam ambas
ou apenas uma da leituras. Caso tenha sido apenas uma,
assinalar qual dos ficheiros é mais curto. Em qualquer dos
casos abortar a repetição.
3. Comparar as duas linhas, se forem diferentes assinalar a
diferença e abortar a repetição.
4. Fechar o ficheiro de entrada 1.
5. Fechar o ficheiro de entrada 2.
 Linguagem C: ex1e.c
 Notas:
1. A construção
2. while(fgets(s1, n, entrada1) != NULL &&
3. fgets(s2, n, entrada2) != NULL)

tem a desvantagem de, quando a primeira condição do E falha, a


segunda não chegar a ser calculada, ou seja, o
segundo fgets() não chega a ser chamado (lembre-se que os
operadores E (&&) e OU (||) são especiais, pois o operando
esquerdo é calculado em primeiro lugar e, se determinar o
resultado da expressão lógica, o operando direito não chega a ser
calculado!). Isto leva a que, se ambos os ficheiros forem iguais, o
ciclo termina com condição de fim-de-ficheiro no ficheiro
correspondente a entrada1 mas não no entrada2 ! Isso tornaria
mais dificil avaliar se os dois seriam de facto iguais ou não.

4. Note-se também na definição de variáveis dentro de um bloco


de instruções (variáveis eof1 e eof2).

6.9.6 Alínea f)

Contar os números de caracteres, palavras e linhas de um ficheiro de texto.

Resolução

São consideradas palavras sequências contíguas de caracteres que não


contenham qualquer caractere "branco" (por exemplo ' ', tabulador '\t' ou
fim-de-linha '\n').

 Algoritmo: Inicializar contadores de caracteres, palavras e linhas e


zero. Ler caractere a caractere o ficheiro de entrada. Incrementar o
contador de linhas de cada vez que o caractere lido for um fim-de-
linha. Se o último caractere do ficheiro não for um fim-de-linha, o
número de linhas é o número de fins-de-linha adicionado de um. Caso
contrário o número de linhas será igual ao número de fins-de-linha. O
contador de caracteres incrementa-se sempre, independentemente
do caractere lido. O contador de palavras é incrementado sempre que
o ultimo caractere lido for um "branco" e o penúltimo um "não-
branco". O número de palavras é igual ao valor do contador de
palavras, excepto se o último caractere do ficheiro for "não-branco",
caso em que deverá ser adicionado de 1.
 Algoritmo passo-a-passo:
1. Abrir o ficheiro de entrada.
2. Inicializar contadores de caracteres, palavras e fins-de-linha a
zero.
3. Inicializar penúltimo caractere com um fim-de-linha (evita-se
assim contar palavras ou linhas a mais).
4. Enquanto se conseguir ler um caractere da entrada repetir:
1. Incrementar o número de caracteres.
2. Se o último caractere for "branco" e o penúltimo for
"não-branco" incrementar o número de palavras.
3. Se o último caractere for um fim-de-linha incrementar o
número de linhas.
4. Atribuir o valor de último caractere a penúltimo
caractere.
5. Se o penúltimo caractere for um "não-branco", incrementar o
número de palavras.
6. Se o penúltimo caractere não for um fim-de-linha, incrementar
o número de linhas.
7. Imprimir valores obtidos.
8. Fechar o ficheiro de entrada.
 Linguagem C: ex1f.c
 Notas:
1. A identificação de caracteres brancos pode ser feita por
comparação explícita com todos os caracteres brancos ou
mediante a função ANSI-C isspace(), cuja declaração se
encontra no ficheiro de cabeçalho ctype.h.
2. Utilizam-se contadores unsigned long int para assegurar que a
sua gama não seja facil de ultrapassar ao se operar sobre
ficheiros de texto normais.

6.9.7 Alínea g)

Mostrar as linhas de um ficheiro de texto onde ocorre uma dada sequência de


caracteres.

Resolução

 Algoritmo: Ler o ficheiro linha e linha. Procurar a sequência de


caracteres na linha (ver próximo item). Se estiver presente mostrar a
linha.
 Algoritmo de procura: Para cada caractere da linha verificar se é o
início da sequência de caracteres: para tal percorrem-se
simultaneamente a linha (começando no caractere de início corrente)
e a sequência de caracteres a encontrar (começando no início). Se se
encontrar alguma diferença entre os caracteres, aborta-se a
comparação e passa-se ao próximo caractere de início. Se durante a
procura for atingido o final da sequência a procurar, isso significa que
a procura teve sucesso, e devolve-se o ponteiro para o caractere de
início da linha. Se o que foi atingido foi o final da linha, então não vale
a pena procurar mais e devolve-se um ponteiro nulo.
 Algoritmo passo-a-passo - Procura duma cadeia s2 (a sequência a
procurar) dentro duma cadeia s1 (e.g., a linha onde procurar),
devolvendo-se a primeira posição de s1 onde se encontra a
sequência s2:
1. Para cada posição de s1 enquanto não for atingido o caractere
nulo (que marca o final da cadeia) repetir (note-se que se pode
utilizar o próprio ponteiro s1 para percorrer a cadeia, pois não
temos qualquer interesse em poder "voltar a trás"):
1. Percorrer simultaneamente ambas as cadeias (com
ponteiros p1 e p2, em que p1 se começa não no início da
cadeia original s1 mas no local apontado
por s1 correntemente, e em que p2 começa no início
de s2) até que se atinjam posições em que as cadeias são
diferentes ou até que se atinja o final duma das
cadeias. 1
2. Se do ciclo anterior resultou p2 apontando o caractere
nulo, isso significa que não se encontraram diferenças até
que o final de s2 foi atingido. Ou seja, s2 está contida na
cadeia s1 original. Neste caso devolve-se s1.
3. Esta linha atinge-se apenas se s2 não terminou.
Se p1 resultar apontando o caractere nulo, então a
cadeia s2 é mais longa do que a parte que falta testar da
cadeia original s1, e portanto pode-se abortar a procura
imediatamente devolvendo NULL.
2. Se esta linha for atingida, significa que a cadeia original s1 é
vazia (só neste caso se atinge esta última linha do algoritmo: em
caso de dúvida experimente-se o algoritmo manualmente para
várias cadeias de entrada). Então, apenas se s2 estiver também
vazia se pode considerar a pesquisa com sucesso. Nesse caso
devolve-se s1, caso contrário devolve-se NULL.
 Linguagem C: ex1g.c
 Notas: Para procurar uma cadeia de caracteres dentro de outra
poderia ter-se usado a função ANSI-C char *strstr(const char s1[],
const char s2[]), que procura a primeira ocorrência de s2 dentro
de s1 devolvendo um ponteiro para a sua localização
em s1 ou NULL caso s2 não esteja presente em s1. A
função procura() funciona duma forma semelhante.

6.9.8 Alínea h)

Gerar um ficheiro de texto contendo uma pauta de resultados de avaliações


lida do terminal. O ficheiro conterá um registo de aluno por linha. Cada
registo é composto pelos seguintes campos (separados por um caractere
espaço): número de aluno (4 posições), nome (25 posições), notas do teste e
do trabalho (2 posições cada).

Resolução

A concretização deste programa é trivial, requerendo apenas algum


conhecimento das especificações de conversão da função printf().

 Linguagem C: ex1h.c
 Notas:
1. Não sendo especificada qualquer condição de paragem no
enunciado, arbitrou-se parar se for premida a combinação de
teclas que assinala o fim do ficheiro, ou sempre que um valor
inválido for introduzido.
2. São de notar as particularidades das especificações de
conversão da função printf() utilizadas:
 %4d -- imprime um inteiro de modo a ocupar pelo menos
quatro caracteres, acrescentando espaços à esquerda se
necessário, isto é, justificando o número à direita.
 %-*.*s -- a construção n.m, em que n e m são números
indica que a cadeia de caracteres depois de impressa
deve ocupar pelo menos n caracteres e que não devem
ser impressos mais do que m caracteres da cadeia;
sempre que m ou n forem substituídos por * (que é o
caso) o valor correspondente será lido do próximo
argumento de printf() (no caso a macro NOME_MAX); o
caractere - indica que a cadeia de caracteres deverá ser
justificada à esquerda, e não `a direita.
3. Note-se ainda que a leitura do nome se faz por intermédio
de fgets(), tendo-se no entanto o cuidado de eliminar todos os
caracteres espúrios introduzidos pelo utilizador (até ao fim-de-
linha) de modo a evitar a sua interpretação errónea aquando da
leitura da nota do teste. Observe-se o tamanho da matriz nome,
com espaço para os 25 caracteres do nome e para o
terminador '\0'.
4. A utilização de macros e do caractere * na especificação de
conversão do nome (na chamada à função printf()) permitem
mudar o tamanho máximo dos nomes alterando uma única
linha do programa!

6.9.9 Alínea i)
Mostrar no terminal o conteúdo de ficheiros de texto gerados pelo programa
da alínea anterior.

Resolução

A concretização deste programa é trivial, requerendo apenas algum


conhecimento das especificações de conversão da função fscanf().

 Linguagem C: ex1i.c
 Notas: São de notar as particularidades das especificações de
conversão da função fscanf() utilizadas. Em particular, a especificação
de conversão %nc lê os próximos n caracteres da entrada para uma
cadeia de caracteres. Note ainda que, neste caso, a conversão está
"mascarada" em "%d %" NOMECAD "c %d %d": acontece que qualquer
compilador ANSI-C tem obrigação de concatenar numa só qualquer
sequência de cadeias de caracteres constantes, resultando pois "%d
%25c %d %d" conforme desejado. A vantagem da utilização da
macro NOMECAD está em concentrar em duas linhas a informação a
alterar para mudar o tamanho máximo dos nomes a utilizar (a
utilização do caractere * em fscanf() tem um significado totalmente
diferente do que possuia para printf(), daí a necessidade deste
"truque").

6.9.10 Alínea j)

Ler um conjunto de valores para uma matriz bidimensional de inteiros com


dimensão 4x4. Escrever o conteúdo da matriz de duas formas diferentes:
ficheiro de texto e ficheiro binário. Comparar o tamanho dos ficheiros obtidos.

Resolução

 Linguagem C: ex1j.c
 Notas: Como se pode verificar, é perfeitamente possível escrever num
ficheiro binário uma matriz bidimensional toda duma só vez.

6.9.11 Alínea k)

Ler para matrizes os ficheiros do exercício 1.j) e afixar no ecrã o seu


conteúdo.

Resolução

 Linguagem C: ex1k.c
Exercício 2

Faça um programa para gerir uma base de dados associada aos utentes de um
parque de estacionamento universitário. Admite-se um máximo de 50 utentes
armazenados sob a forma de uma matriz. Cada utente é descrito pelos
seguintes campos:

 Nome (máximo de 40 caracteres).


 Categoria, com valores possíveis de 0, 1 ou 2.
 Código: um número positivo. Um valor negativo indica que a posição
da matriz está livre.

O programa deve permitir as seguintes operações, a cada uma das quais


corresponde um função:

1. Inserção de novos utentes: a função deve ter como único argumento a


matriz de utentes. Os dados do utente são introduzidos pelo teclado.
O utente será colocado na primeira posição livre da matriz.
2. Remoção de utentes: a função tem como único argumento a matriz de
utentes. Deve perguntar o nome do utente a remover e retirá-lo da
matriz (marcando a posição como livre), caso ele exista.
3. Armazenamento da base de dados num ficheiro de texto: a função
tem como argumentos a matriz de utentes e o nome do ficheiro. Não
se esqueça que o ficheiro de texto deve ser legível pela função do
pr'oximo item.
4. Leitura da base de dados de um ficheiro de texto: a função tem como
argumentos a matriz de utentes e o nome do ficheiro. Deve ler o
ficheiro e guardar os utentes na matriz. A leitura termina quando o
número máximo de utentes for atingido ou quando o fim do ficheiro
for atingido. Todos os utentes existentes anteriormente na matriz
serão eliminados. Não esquecer que as posições que não forem
ocupadas por utentes lidos do ficheiro devem ser marcadas como
livres.
5. Mostrar no ecrã o conteúdo das posições ocupadas da matriz: a
função tem como único argumento a matriz de utentes.

A estrutura do programa principal, que faz uso das funções acima, fica ao
critério do aluno, e deverá ser preparada antes das funções. Este assunto, tal
como o resto do enunciado, deve ser pensado com antecedência.

6.10.1 Resolução
 Linguagem C: ex2.c
 Notas:
1. Não se controlam repetições de nomes e/ou códigos (fica como
exercício adicional...).
2. Alterou-se o enunciado no que diz respeito às funções de leitura
e escrita de ficheiros: têm apenas um argumento, pois
perguntam elas próprias o nome dos ficheiros respectivos.
3. Note-se a utilização da variável alterado em main(), para
garantir que o utilizador do programa não deita a perder uma
sessão de manipulação duma base de dados.
4. Notem-se também os valores devolvidos pelas funções.
5. Finalmente, note-se que a função mataLinha() se destina a
descartar todos os caracteres até ao próximo fim-de-linha,
tendo um efeito semelhante à chamada fflush(stdin) quando
se usa o Turbo-C. Esta utilização de fflush() com um canal
aberto para leitura (no caso stdin) tem resultados indefinidos,
de acordo com a norma ANSI-C, pelo que torna os programas
não "transportáveis", ou seja, com comportamento variável
consoante o compilador e/ou sistema operativo utilizado.

Exercício 3

Faça um programa que leia um ficheiro de utentes de um parque de


estacionamento, contendo, em cada linha, o código, a categoria e o nome de
cada utente, por esta ordem. Estes dados devem ser carregados numa matriz
de estruturas. O programa deve escrever num ficheiro a base de dados de
utentes ordenada por nome ou por código. A execução do programa é feita
com passagem de três argumentos (usando argc e argv de main()):
programa entrada saida campo

em que campo indica o campo pelo qual a ordenação é feita, podendo tomar os
valores nome ou codigo.

Durante a ordenação a matriz de utentes permanece inalterada, para o que se


deve utilizar uma matriz adicional de ponteiros para a estrutura de utente
(inspire-se no exemplo 13P13 da página 246 do livro). Exemplo 1:
ordena entrada.txt saida.txt codigo

Este comando lê o ficheiro entrada.txt, carrega-o para a matriz de utentes,


ordena-a pelo código (indirectamente, não esquecer), e escreve o resultado no
ficheiro saida.txt.
Exemplo 2:
ordena entrada.txt saida.txt nome

Este comando lê o ficheiro entrada.txt, carrega-o para a matriz de utentes,


ordena-a pelo nome do utente, e escreve o resultado no ficheiro saida.txt.

6.11.1 Resolução

 Linguagem C: ex3.c
 Notas:
1. Admite-se que os ficheiros têm o mesmo formato utilizado no
exercício 2, isto é, uma linha por cada campo. Este formato
simplifica a leitura. Repare-se na utilização dum espaço em
branco no formato passado à função scanf(): "%d%d ". Este
espaço serve para que a função scanf() elimine os espaços em
branco, incluindo fins-de-linha, depois de ler a categoria e o
código, de modo a estes não afectarem a leitura do próximo
nome.
2. Repare-se que o algoritmo de ordenação utilizado é o de
"ordenação por permutação" (em inglês "bubble sort"),
diferente, e mais eficiente, que o utilizado no livro. Deve-se ter
em conta que este algoritmo, embora interessante, é muito
ineficiente (número de comparações proporcional, em média, a
NxN, sendo N o número de elementos a ordenar), não sendo
por isso recomendável a sua utilização (entre os algoritmos
mais eficientes encontra-se a "ordenação rápida" - em inglês
"quick sort" - cujo número de comparações é proporcional, em
média, a Nxlog(N)).
3. Note-se que, na função ordenaUtentes(), existem dois
algoritmos de ordenação idênticos, um ordenando por código e
outro por nome. Tal deve-se a que a utilização de um teste
adicional (ao campo pelo qual ordenar) dentro dos ciclos de
ordenação contribuiria fortemente para tornar a ordenação
mais lenta. Muito embora neste caso esta preocupação seja
exagerada, em programas exigindo a ordenação de um número
de elementos muito grande estas medidas simples podem levar
facilmente a um aumento considerável da velocidade de
execução.
4. Note-se finalmente na flexibilidade do programa face ao
número de argumentos. Admitindo que o programa se
chama ordena:
ordena
lê os dados do teclado e escreve-os, ordenados pelo nome, no ecrã.
ordena entrada

lê os dados do ficheiro entrada e escreve-os, ordenados pelo nome, no


ecrã.
ordena entrada saida

lê os dados do ficheiro entrada e escreve-os, ordenados pelo nome, no


ficheiro saida.
ordena entrada saida campo

lê os dados do ficheiro entrada e escreve-os, ordenados pelo nome ou


pelo código (consoante o valor de campo), no ficheiro saida.

Exercício 4

Listas e recursividade:

6.12.1 Alínea a)

Refaça o programa do exercício 2 de modo a usar o conceito de lista


simplesmente ligada. O programa deve, portanto, permitir:

1. leitura de ficheiro
2. escrita de ficheiro
3. inserção de utentes
4. remoção de utentes
5. listagem dos utentes

6.12.2 Alínea b)

Escreva duas funções que gerem a sucessão de Fibonacci até um determinado


um determinado valor de n: F(0) = F(1) = 1 e, para n > 1, F(n) = F(n-1)
+ F(n-2). A primeira função deverá ser recursiva e a segunda iterativa.
Compare a velocidade das duas funções. Veja o que sucede quando o valor
de n é muito grande.

Resolução

 Linguagem C: ex4b.c
 Notas: A "moral" a retirar deste exercício é que, ainda que muitas
vezes as funções recursivas correspondam à concretização mais
evidente de um determinado algoritmo, geralmente também é
verdade que conduzem a realizações muito ineficientes, como pode
ser verificado correndo o programa apresentado como solução do
exercício.

6.12.3 Alínea c)

Refaça o programa da alínea a) de modo a que as listas usadas sejam


totalmente genéricas, não dependendo, portanto, dos dados a guardar. A
estrutura de elemento da lista deverá conter um ponteiro generico ( void *)
para os dados a guardar. Deve também conter um campo que guardará uma
chave de acesso aos dados (use uma chave inteira). As funções associadas às
listas genéricas poderão, por exemplo, incluir:
Lista *Lcria()

cria lista vazia.


void *Linsere(Lista *l, void *dados, int chave)

insere dados na lista, devolvendo-o ou devolvendo NULL em caso de


erro.
void *Lremove(Lista *l, int chave)

remove da lista os dados indexados por chave, devolvendo-os ou


devolvendo NULL caso não existam ou em caso de erro.
void *Lbusca(Lista *l, int chave)

devolve os dados indexados chave, ou devolve NULL caso não existam


ou em caso de erro.
void Ldestroi(Lista *l, int tudo)

desafecta toda a memória da lista; se tudo for verdadeira, liberta


também a memória afectada aos dados.
void *LbuscaFunc(Lista *l, PFI função, void *oque)

procura dados contendo oque, usando uma função passada como


argumento (a função devolve verdadeiro quando encontrou os dados).
Assume-se que:
typedef int (*PFI)(void *dados, void *oque);

por exemplo:
int buscaNome(void *dadosgen, void *nomegen)
{
Utente *dados = dadosgen;
char *nome = nomegen;

return strcmp(dados->nome, nome) == 0;


}

chamando-se a função, por exemplo, da seguinte forma


dados = LbuscaFunc(lista, buscaNome, "Zacarias Zagalo");

Notas: não esquecer, nas alíneas a) e c), que a memória afectada deve ser
desafectada quando deixa de ser necessária.

Exercício 5

6.13.1 Introdução

Sumário

 Declaração vs. Definição


 Categoria, âmbito, classe ou permanência e validade de objectos e
nomes em C
o Categoria de objectos
o Âmbito dos nomes
o Classe ou permanência das variáveis
o Validade de funções e variáveis

Apresentam-se de seguida algumas notas sobre a linguagem C que não


dispensam a leitura da bibliografia da cadeira, nomedamente do livro "The C
Programming Language" (segunda edição), B. Kernighan e D. Ritchie,
Prentice Hall, 1988.

Repare-se que a nomenclatura usada nestas notas não corresponde


exactamente à usada, por exemplo, no livro referido acima. Os nomes aqui
usados (e.g., local vs. global, interno vs. externo) foram escolhidos de modo a
fazerem algum sentido em conjunto com os qualificadores do ANSI-C
(e.g., extern).

Declaração vs. definição

A linguagem C diferencia os conceitos de declaração e de definição. Uma


declaração informa o compilador da existência de algo (uma declaração de x é
entendida pelo compilador como "existe x"). Uma definição, para além de
declarar a existência, diz ao compilador em que é que consiste o objecto
definido (que instruções compõem uma função, que campos compõem uma
estrutura) e reserva memória para o objecto (no caso de variáveis).

Por declaração no sentido lato entende-se normalmente qualquer declaração,


com ou sem definição. Por declaração no sentido estrito entende-se uma
declaração sem definição. Uma definição é sempre também uma declaração.

O exemplo mais claro desta distinção encontra-se nas funções:


int func(int a);

que é equivalente a
int func(int);

é uma declaração no sentido estrito, pois não se especificam os pormenores de


concretização da função, i.e., não se define a função. Já
int func(int a)
{
return a/2;
}

é uma definição (e consequentemente também uma declaração) da


função func().

No que diz respeito às variáveis, a distinção entre declaração e definição exige


o recurso aos conceitos da próxima secção, nomeadamente aos conceitos
de categoria e validade . Por exemplo, considerem-se os seguintes ficheiros:
/* Ficheiro a.c */
int i = 10;

e
/* Ficheiro b.c */
extern int i;

No ficheiro a.c define-se (e consequentemente declara-se) a variável (global e


externa) i enquanto no ficheiro b.c se declara (no sentido estrito) uma
variável i, que se supõe estar definida noutro ficheiro (neste caso seria
provavelmente o ficheiro a.c...).

A mesma distinção entre declaração e definição se pode estabelecer para


estruturas:
struct a;

é uma declaração, no sentido estrito, duma estrutura, enquanto que


struct a
{
int b;
...
}

é uma definição duma estrutura.

Note-se que este último facto torna possível esconder totalmente, do utilizador
de um determinado módulo de funções, os seus pormenores de realização.
Suponha-se, por exemplo, um módulo de processamento de listas. Os
respectivos ficheiros listas.h (ficheiro de cabeçalho, do inglês header file)
e listas.c poderiam ser:
/* listas.h */
/*
* Declaracao (sentido estrito) da estrutura struct ListaStr e
* definicao do tipo Lista!
*/
typedef struct ListaStr Lista;
/* Declaracoes, sentido estrito, de funcoes de interface... */
Lista *Lcria(void);
...

e
/* listas.c */
/*
* A inclusao do ficheiro de cabecalho .h no ficheiro .c do
* proprio modulo permite:
* 1. Ter acesso `a definicao do tipo Lista.
* 2. Garantir coerencia entre as declaracoes das funcoes no
* ficheiro .h e a sua definicao no ficheiro .c.
*/
#include "listas.h"
/* Declaracao da estrutura ElementoStr e definicao do
tipo Elemento: */
typedef struct ElementoStr Elemento;
/* Definicao da estrutura ElementoStr: */
struct ElementoStr
{
void *dados;
Elemento *seguinte;
};
/* Definicao da estrutura struct ListaStr (declarada em
listas.h): */
struct ListaStr
{
unsigned long numero;
Elemento *inicio;
}
/* Definicao das funcoes: */
Lista *Lcria(void)
{
...
}
...
Categoria, âmbito, classe ou permanência e validade de objectos e nomes
em C

Categoria de objectos

Em C existem duas categorias de objectos: objectos locais e objectos globais.


São objectos locais aqueles que se definem dentro duma função. Os objectos
definidos fora de qualquer função são objectos globais. 2

As funções, não se podendo nunca definir dentro de outras funções, são


sempre globais. Quanto às variáveis, são locais as variáveis definidas dentro
de funções e todos os parâmetros das funções. Por exemplo:
/* A funcao f() e' global: */
int f(int i) /* O parametro i e' local. */
{
int n; /* A variavel n e' local. */
n = i*i;
for(; n > 0; n--)
{
int aux = n; /* A variavel aux e' local. */
i += aux;
}
return n;
}
int n; /* A variavel n e' global. */
Âmbito dos nomes

Outro conceito importante diz respeito ao âmbito (ou skope em inglês) dos
nomes declarados, i.e., em que zonas dum ficheiro em C são visíveis esses
nomes. As regras são bastante simples:

1. Os nomes declarados externamente às funções são visíveis desde o


ponto de declaração até ao final do ficheiro.
2. Os nomes dos parâmetros das funções são válidos ao longo de toda a
função.
3. Os nomes declarados dentro das funções são válidos desde a
declaração até ao final do bloco em que foram declarados.
4. Nomes declarados dentro de funções ocultam os mesmos nomes
(desde que pertencentes ao mesmo espaço de nomenclatura)
declarados fora das funções.
5. Nomes declarados dentro de um bloco ocultam os mesmos nomes
(desde que pertencentes ao mesmo espaço de nomenclatura)
declarados em blocos que os envolvam.

Observe-se:
1 int a;
2 int f3(int);
3 int f1(int b)
4 {
5 int c;
6 int f2(int);
7 for(c = 0; b > 0; b--)
8 {
9 static int d = 1;
10 c += f2(d);
11 }
12 return c;
13 }
14 int e;
15 int f2(int f)
16 {
17 return f3(f);
18 }
19 int f3(int g)
20 {
21 return 1;
22 }

Neste exemplo têm-se os seguintes nomes:

 a - visível da linha 1 à 22.


 f3 - visível da linha 2 à 22.
 f1 - visível da linha 3 à 22.
 b - visível ao longo de toda função f1() (linhas 3 a 13).
 c - visível da linha 5 à 13.
 f2 - visível da linha 6 à 13 (dentro da função f1()) e da linha 15 à 22.
 d - visível da linha 9 à 11.
 e - visível da linha 14 à 22.

Classe ou permanência das variáveis

As variáveis em C têm diferentes tipos de permanência na memória consoante


os qualificadores usados na sua definição e consoante sejam globais ou locais.
As variáveis podem pertencer a uma de duas possíveis classes de permanência
(ou storage class em inglês):

Automáticas
São variáveis locais (i.e., definidas dentro duma função) que têm
existência (i.e., um espaço de memória afectado) apenas durante a
execução da função em que foram definidas. O seu valor, como é
óbvio, perde-se entre chamadas sucessivas à função.
Estáticas
São variáveis que têm existência desde o início até ao fim do
programa.
As variáveis globais são sempre estáticas. As variáveis locais são automáticas
por omissão e são estáticas caso a sua definição seja precedida do
qualificador static.

As variáveis automáticas, quando não forem explicitamente inicializadas


durante a sua definição, tomam valores indefinidos até à primeira atribuição
que lhes seja feita.

As variáveis estáticas, quando não forem explicitamente inicializadas durante


a definição, são inicializadas implicitamente com valores nulos (zeros). A
inicialização (explícita ou implícita) de variáveis estáticas é feita apenas no
início do programa.

Assim, no exemplo acima, apenas as variáveis a, d e e são estáticas.

Validade de funções e variáveis

De modo a facilitar a escrita de programas consistindo de diferentes módulos,


existe na linguagem C o conceito de validade das funções e variáveis. As
validades (ou linkage, em inglês) são:

Interna
Para variáveis e funções acessíveis apenas dentro do ficheiro onde são
definidas.
Externa
Para variáveis e funções acessíveis em todo o programa (qualquer que
seja o ficheiro).

As variáveis locais (ver definição acima) têm sempre validade apenas interna.
As variáveis ou funções globais têm, por omissão, validade externa. No
entanto, a sua validade pode ser interna desde que a sua definição seja
precedida do qualificador static. 3

Para poder utilizar um objecto externo definido noutro ficheiro é necessário


declará-lo com o qualificador extern. Este qualificador é obrigatório no caso
das variáveis e opcional no caso das funções.

Considerem-se, por exemplo, os ficheiros: 4


/* Ficheiro a.c */
#include "a.h"
/* Definicao de variaveis globais: */
static int globalInterna = 2;
int globalExterna = 1;
/* Definicao de funcoes: */
static int funcaoInterna(int a)
{
return globalExterna * globalInterna * a * a;
}
int funcaoExterna(int a)
{
return funcaoInterna(a)-1;
}
/* Ficheiro a.h */
/* Declaracao de variaveis globais: */
extern int globalExterna;
/* Declaracao de funcoes: */
int funcaoExterna(int);

e
/* Ficheiro b.c */
#include <stdio.h>
#include <stdlib.h>
#include "a.h"
int main(void)
{
globalExterna = -1;
printf("funcaoExterna(3) = %d\n", funcaoExterna(3));
return EXIT_SUCCESS;
}

Neste programa existem duas funções (para além de main()). A


primeira, funcaoInterna() apenas pode ser utilizada dentro do ficheiro a.c,
enquanto a segunda, funcaoExterna(), pode ser utilizada ao longo de todo o
programa.

Relativamente às duas variáveis globais passa-se o mesmo: a


primeira, globalInterna, só pode ser usada dentro de a.c enquanto a
segunda, globalExterna, pode ser usada em todo o programa.

Como é óbvio, a utilização dos objectos externos fora do ficheiro onde são
definidos implica a sua declaração, o que é conseguido, neste caso, por
intermédio da inclusão do ficheiro de cabeçalho a.h. Note-se que se utilizou o
qualificador extern apenas para a declaração das variáveis, tendo-se omitido,
por desnecessário, esse qualificador na declaração das funções.

Finalmente, é de frisar neste ponto que, em geral, a utilização de variáveis


globais é desaconselhável, por reduzir consideravelmente a legibilidade dos
programas ao permitir que as funções afectem mais do que os seus
argumentos. Os exemplos apresentados destinam-se apenas a demonstrar
conceitos, e não devem ser tomados como exemplos de boa programação.

6.13.2 Alínea a)
Altere o programa produzido no exercício 4.b) de modo a que o número de
chamadas da versão recursiva da função possa ser contabilizado. Para isso,
utilize uma variável local à função (que terá de ser, obviamente, estática).
Essa variável deve ser anulada sempre que se chamar a função com o
argumento -1. O número de chamadas à função deverá ser devolvido pela
função sempre que for chamada com o argumento -2.

Resolução

 Linguagem C: ex5a.c
 Notas: repare que se utiliza
 printf("F(%d) = %lu\n", n, fibonacciRec(n));
 printf("chamadas = %lu\n", fibonacciRec(-2));

e não
printf("F(%d) = %lu\nchamadas = %lu\n", n,
fibonacciRec(n), fibonacciRec(-2));

pois o ANSI-C não garante a ordem de cálculo dos argumentos duma


função, e portanto fibonacciRec(-2) poderia ser calculada em primeiro
lugar!

6.13.3 Alínea b)

Envolva todo o código relativo à contabilização de chamadas introduzido na


alínea anterior em directivas do pré-processador de modo a ser compilado
apenas se estiver definida a macro CONTABILIZA.

Recorra às seguintes directivas do pré-processador:

#define nome oque (em que oque é opcional)


define a macro nome, que é expandida para oque
#ifdef nome

o código entre esta directiva e a próxima directiva #else ou #endif é


compilado apenas se a macro nome estiver definida.

Resolução

 Linguagem C: ex5b.c

6.13.4 Alínea c)
Coloque as funções de cálculo da sucessão de Fibonacci num módulo
separado, de nome sucessao.c, com o respectivo ficheiro de
cabeçalho sucessao.h. Utilize nomes e tipos apropriados para as funções e
seus parâmetros, por exemplo unsigned long SUCfibonacci(int n) e unsigned
long SUCfibonacciRec(int n).

Resolução

 Linguagem C: ex5cde.c, sucessao.c e sucessao.h


 Notas:
1. Repare que o ficheiro de cabeçalho sucessao.h é incluido tanto
em ex5cde.c como em sucessao.c.
2. Note ainda que a macro CONTABILIZA se define (ou não) no
ficheiro de cabeçalho.

6.13.5 Alínea d)

Tente calcular analiticamente o número de chamadas da


função SUCfibonacciRec() quando lhe é passado o argumento n. Verifique que
o número de chamadas pode ser expresso duma forma recursiva muito
semelhante à da própria sucessão de Fibonacci! Escreva uma nova
função, unsigned long SUCnfibbonaci(int n) (na sua forma iterativa!) que
devolva o número de chamadas à função SUCfibbonaciRec() quando lhe é
passado o argumento n.

Verifique que o valor calculado está correcto comparando-o com o valor


contabilizado pela própria função SUCfibonacciRec().

Resolução

A dedução é simples. Seja a definição recursiva da sucessão: F(0) = F(1) = 1


e, para n > 1, F(n) = F(n-1) + F(n-2). Seja N(n) o número de chamadas à
função de Fibonacci recursiva. Claramente N(0)=N(1)=1. Por outro lado, para
calcular F(n) é necessário chamar a função uma vez, que por sua vez
chamará F(n-1) e F(n-2). Então, a função N() define-se recursivamente, tal
como F()! Em particular, N(0) = N(1) = 1 e, para n > 1, N(n) = 1 + N(n-1)
+ N(n-2).

 Linguagem C: ex5cde.c, sucessao.c e sucessao.h

6.13.6 Alínea e)

Um problema que surge frequentemente quando se trabalha com módulos é o


da inclusão múltipla do mesmo ficheiro de cabeçalho. Por exemplo, considere
o módulo de listas genéricas que foi programado no exercício 4.c), e admita
que o ficheiro de cabeçalho respectivo se chama listas.h. Suponha que o (os)
tipos definidos no ficheiro listas.h é (são) necessário(s) para a declaração
das funções no ficheiro de cabeçalho de outro módulo, por
exemplo utentes.h:
/* utentes.h */
#include "listas.h"
...

Se o módulo principal (aquele que contém a função main()) necessitar


simultâneamente dos módulos listas e utentes, conterá, provavelmente, as
directivas:
#include "listas.h"
#include "utentes.h"

o que resultará na inclusão do ficheiro listas.h duas vezes. Esta dupla


inclusão acabará por gerar erros de compilação, porque o mesmo tipo não
pode ser definido duas vezes (com typedef). Mas, mesmo que não ocorra
qualquer erro de compilação, a dupla inclusão de um ficheiro de cabeçalho é
inútil e desperdiça tempo de compilação.

Tente evitar a dupla inclusão, tal como descrita atrás, do ficheiro sucessao.h,
recorrendo para isso às seguintes directivas do pré-processador:

#define nome oque (em que oque é opcional)


define a macro nome, que é expandida para oque
#ifndef nome

o código entre esta directiva e a próxima directiva #else ou #endif é


compilado apenas se a macro nome não estiver definida.

Resolução

 Linguagem C: ex5cde.c, sucessao.c e sucessao.h


 Notas:
1. Repare que para evitar os problemas da multipla inclusão se
acrescentaram directivas do pré-processador apenas no ficheiro
de cabeçalho!
2. Note o comentário em
3. #endif /* _SUCESSAO_H_ */

uma vez que o ANSI-C não permite argumentos nas


directivas #endif e #else.
4. Repare ainda no nome escolhido para a macro definida, que: 1.
evita colisões com outras macros já existentes (utilização de
sublinhados), e 2. está relacionada com o nome do proprio
ficheiro.

6.13.7 Alínea f)

Escreva um ficheiro de cabeçalho uteis.h, não correspondendo a qualquer


módulo, que defina as seguintes macros úteis:
ABS(x)

Calcula o valor absoluto de x.


ABSDIF(x, y)

Calcula o valor absoluto da diferença entre x e y.


ABSMAIOR(x, y)

Devolve verdadeiro se |x| > y (com y positivo).


MAX(x, y)

Devolve o maior dos argumentos.


MIN(x, y)

Devolve o menor dos argumentos.


LIM(x, y, z)

Devolve o valor de y limitado ao intervalo [x z].


SINAL(x)

Devolve o sinal de x (-1 para negativo, 0 para nulo e 1 para positivo).


TROCA(x, y, aux)

Troca a variável x com y usando a variável auxiliar aux.

Comente claramente os possíveis efeitos secundários das macros,


nomedamente indicando o número de vezes que cada argumento da macro é
utilizado no texto de expansão da mesma.

Explique porque é que não se devem, em geral, chamar macros com


argumentos envolvendo expressões complexas. Por exemplo:
x = ABS(sin(cos(x*x/y)));

Não se esqueça de usar parênteses generosamente para evitar os problemas de


precedências, tão frequentes quando se utilizam macros!
Resolução

 Linguagem C: uteis.h

Repare que a chamada


x = ABS(sin(cos(x*x/y)));

é traduzida pelo compilador para


x = ((sin(cos(x*x/y))) >= 0 ? (sin(cos(x*x/y))) :
-(sin(cos(x*x/y))));

o que implica que o cálculo completo é efectuado duas vezes, o que é


extremamente ineficiente. Por outro lado
x = ABS(i++);

é traduzida pelo compilador para


x = ((i++) >= 0 ? (i++) : -(i++));

que não produzirá os resultados esperados nem em i nem em x!

Exercício 6

Refaça o programa do exercício 2 de modo a utilizar um ficheiro de registos


binário para guardar a base de dados, em vez duma matriz. Assim, deixa de
haver limitação quanto ao número de utentes. Não são permitidas repetições
de nomes ou códigos.

Utilize as funções:

fopen() com modo "r+b" ou "w+b"


para criar um abrir ou criar um ficheiro no modo de actualização.
ftell()

para obter a posição de leitura/escrita corrente.


fseek()

para colocar a posição de leitura/escrita num local determinado.


fread()

para efectuar leituras de registos.


fwrite()

para efectuar escritas de registos.


feof()

devolve verdadeiro se e só se se tiver já tentado ler para além do fim


do ficheiro (isto é, se o indicador de fim-de-ficheiro estiver activo).

Valores negativos no código dos utentes continuam a indicar que a posição,


neste caso no ficheiro de registos, está livre.

Não esquecer que, nesta versão do programa, a base de dados é guardada,


entre chamadas ao programa, no próprio ficheiro de registos binário (e não
num ficheiro de texto como no exercício 2). 5

Note-se que é muito importante pensar nas funções a realizar antes de as


começar a programar em C. Em particular, a utilização de um diagrama para
representar o estado do ficheiro de registos e a evolução da posição de
leitura/escrita revelar-se-á muito útil.

Finalmente, lembre-se de que a norma ANSI-C obriga a que entre passagens


de leitura para escrita e vice versa sejam intercaladas chamadas
a fflush(), fseek(), fsetpos() ou frewind(). Entre operações de leitura e
escrita (neste sentido, de leitura para escrita), não é necessário intercalar estas
funções se as funções de leitura terminaram colocando o indicador de fim-de-
ficheiro no valor verdadeiro.

6.14.1 Resolução

 Linguagem C: ex6.c, leituras.c e leituras.h

Fica como exercício expurgar o ficheiro de registo de todos os registos livres


antes de sair do programa.

Exercício 7

6.15.1 Alínea a)

Realize um módulo de pilhas genéricas (o último a entrar é o primeiro a sair).


Faça também o respectivo programa de teste. Dever-se-ão utilizar matrizes
para guardar os ponteiros genéricos (void *) para os dados do utilizador do
módulo. Assim, sendo, existe um número máximo de elementos na pilha. O
número máximo de elementos deverá poder ser especificado pelo utilizador
aquando da criação duma nova pilha (i.e., deverá usar memória dinâmica para
a matriz, que será afectada pela função calloc()).

Apresentam-se sugestões para as funções de interface do módulo (PM significa


"pilhas com matrizes"):
PilhaM *PMcria(size_t n)

cria uma nova pilha cuja dimensão (número máximo de elementos


guardados) é n.
void PMdestroi(PilhaM *p)

desafecta toda a memória associada à pilha p.


PilhaM *PMesvazia(PilhaM *p)

esvazia a pilha p.
size_t PMquantos(PilhaM *p)

devolve o número de elementos guardados na pilha p.


int PMcheia(PilhaM *p)

devolve verdadeiro se a pilha p estiver cheia.


PilhaM *PMpoe(PilhaM *p, void *d)

insere os dados d na pilha p; devolve zero se a pilha estiver cheia, caso


contrário devolve o ponteiro para a pilha.
void *PMtira(PilhaM *p)

retira o elemento que se encontra há menos tempo na pilha p e


devolve os seus dados; devolve zero se a pilha estiver vazia.

Resolução

 Linguagem C: pilhasm.c e pilhasm.h.

6.15.2 Alínea b)

Semelhante à alínea a), mas agora serão filas (o primeiro a entrar é o primeiro
a sair), e não pilhas.

As funções de exemplo são semelhantes, substituindo-se PM por FM.

Note que a função void *FMtira(FilaM *f) retira o elemento que se encontra
há mais tempo na fila f!
Repare que neste caso poderá usar os indíces de modo a que "dêem a volta",
evitando-se assim a renormalização da matriz sempre que se retira um
elemento.

Finalmente, note que a condição de "fila cheia" se pode confundir com a


condição de "fila vazia"! Pode evitar o problema reservando n+1 posições na
matriz e nunca deixando introduzir mais do que n elementos.

Resolução

 Linguagem C: filasm.c e filasm.h.

6.15.3 Alínea c)

Semelhante à alínea a), mas agora utilizando o módulo de listas genéricas,


desenvolvido no exercício 4.c), e não matrizes. Exemplo de funções de
interface do módulo (P significa "pilhas"):
Pilha *Pcria(void)

cria uma nova pilha.


void Pdestroi(Pilha *p)

desafecta toda a memória associada à pilha p.


Pilha *Pesvazia(Pilha *p)

esvazia a pilha p.
unsigned long Pquantos(Pilha *p)

devolve o número de elementos guardados na pilha p.


Pilha *Ppoe(Pilha *p, void *d)

insere os dados d na pilha p; devolve o ponteiro para a pilha.


void *Ptira(Pilha *p)

devolve os dados que se encontram há menos tempo na pilha p;


devolve zero se a pilha estiver vazia.

Repare que neste caso não se especifica qualquer dimensão máxima para a
pilha aquando da sua criação. Note-se ainda que não existe, como é óbvio,
qualquer função para verificar se a pilha está cheia.

Note ainda que poderá necessitar de fazer algumas alterações ão módulo de


listas genéricas, pois poderá faltar uma função que "retire o elemento inicial".

Resolução
 Linguagem C: pilhas.c e pilhas.h.

6.15.4 Alínea d)

Semelhante à alínea c), mas agora serão filas, e não pilhas.

As funções de exemplo são semelhantes, substituindo-se P por F.

Note que a função void *Ftira(Fila *f) retira o elemento que se encontra
há mais tempo na fila f!

Note ainda que poderá necessitar de fazer algumas alterações ão módulo de


listas genéricas, pois poderá faltar uma função que "retire o elemento final".

Resolução

 Linguagem C: filas.c e filas.h.

Exercício 8

Faça um programa que permita realizar as seguintes operações elementares


com valores double:

 Atribuição de valores a variáveis. Se a variável indicada não existir


deverá ser criada.
 Soma, subtracção, multiplicação e divisão de variáveis. Se alguma das
variáveis não existir deverá ser assinalado um erro.
 Atribuição do último valor calculado a uma variável.

As variáveis, que consistirão em pares (nome,valor) (i.e., estruturas), devem


ser guardadas numa lista (use o módulo de listas genéricas desenvolvido no
exercício 4.c)).

Note que as variáveis a que este enunciado se refere não são variáveis da
linguagem C, mas sim objectos guardados pelo programa e definidos pelo seu
utilizador.

6.16.1 Resolução

 Linguagem C: ex8.c, leituras.c e leituras.h..

http://home.iscte-iul.pt/~mms/courses/oop/1995-1996/aulas-praticas/exercicios.html
https://www.kelvinsantiago.com.br/exercicios-resolvidos-em-linguagem-c-lista-d/

Estarei disponibilizando abaixo 30+ exercícios resolvidos utilizando


a linguagem C, espero que estes contribua nos seus estudos.

Linguagem C

C é uma linguagem de programação compilada de propósito geral,


estruturada, imperativa, procedural, padronizada pela ISO, criada
em 1972, por Dennis Ritchie, no AT&T Bell Labs, para desenvolver
o sistema operacional Unix (que foi originalmente escrito em
Assembly).

Fonte: Wikipedia

Exercício 1

Faça um algorítimo que receba valores inteiros de uma matriz 5×2


e preencha um vetor inteiro de
tamanho 10. Imprima o vetor preenchido.

1 /*

2 * Autor: Kelvin Santiago

3 * Criado: 19/07/2013

4 */

6 #include<math.h>

7 #include<stdio.h>

8 #include<string.h>

10 int main(){

11

12 int vetor[9], i;
13

14 for (i = 0; i < 10; i++){

15

16 scanf("%d",&vetor[i]);

17 printf("%d\n",vetor[i]);

18 }

19 return 0;

20 }

Exercício 2

Fazer um algoritmo que:


Leia números de matrículas de alunos e armazene-os em um vetor
até o vetor ser preenchido por 10 matrículas. Esses números são
distintos, ou seja, o vetor não armazenará valores repetidos.

1 /*

2 * Autor: Kelvin Santiago

3 * Criado: 23/07/2013

4 */

6 #include<math.h>

7 #include<stdio.h>

8 #include<string.h>

10 int main(){

11

12 int vetor[10], numero, cont, posicao = 0 ;

13

14 while (posicao < 10){

15

16 scanf("%d",&numero);
17

18 if (posicao == 0){

19 vetor[posicao] = numero;

20 printf("%d\n",vetor[posicao]);

21 posicao++;

22 }

23

24 else{

25

26 for(cont = 0; (cont < posicao)&&(vetor[cont]!= numero); cont++);

27

28 if (cont >= posicao){

29 vetor[posicao] = numero;

30 printf("%d\n",vetor[posicao]);

31 posicao++;

32 }

33 }

34 }

35 return 0;

36 }

Exercício 3

Fazer um algoritmo que:


Preencha um vetor com X números inteiros, em que o último
número lido seja 999 (o último número não fará parte do vetor).
E imprima o vetor na ordem inversa.

1 /*

2 * Autor: Kelvin Santiago

3 * Criado: 23/07/2013

4 */
5

6 #include<math.h>

7 #include<stdio.h>

8 #include<string.h>

10 int main(void){

11

12 int contador = 0, numero, vetor[contador];

13

14 scanf("%d",&numero);

15

16 while (numero != 999){

17

18 if (contador == 0){

19 vetor[contador] = numero;

20 contador++;

21 }

22

23 else{

24 vetor[contador] = numero;

25 contador++;

26 }

27

28 scanf("%d",&numero);

29 }

30

31 if (numero == 999){

32 contador--;

33 }

34

35 while ( contador >= 0 ){

36

37 printf("%d\n",vetor[contador]);

38 contador--;

39 }
40 return 0;

41 }

Exercício 4

Faça um programa que receba os valores de uma matriz de ordem


3 e imprimia sua diagonal principal de trás para frente.

1 /*

2 * Autor: Kelvin Santiago

3 * Criado: 23/07/2013

4 */

6 #include<math.h>

7 #include<stdio.h>

8 #include<string.h>

10 int main(void){

11

12 int matriz[3][3], linha = 0, coluna = 0;

13

14 for(linha = 0; linha < 3; linha++){

15

16 for (coluna = 0; coluna < 3; coluna++){

17

18 scanf("%d",&matriz[linha][coluna]);

19 }

20

21 }

22

23 for (linha = 2; linha >= 0; linha--){

24
25 for(coluna = 2; coluna >=0; coluna--){

26 if ( linha == coluna){

27 printf("%d\n",matriz[linha][coluna]);

28 }

29 }

30 }

31

32 return 0;

33 }

Exercício 5

Fazer um algoritmo que:


Leia um vetor contendo 10 números, que correspondem a
matrículas de alunos. Ler 3 matrículas e imprima uma mensagem
informando se eles estão ou não presentes no vetor.

1 /*

2 * Autor: Kelvin Santiago

3 * Criado: 23/07/2013

4 */

6 #include<math.h>

7 #include<stdio.h>

8 #include<string.h>

10 int main(void){

11

12 int vetor[10],contagem, verificanumero, contagemverifica = 0, contemcerto = 0;

13

14 for(contagem = 0; contagem < 10; contagem++){

15
16 scanf("%d",&vetor[contagem]);

17

18 }

19

20 while (contagemverifica < 3){

21

22 scanf("%d",&verificanumero);

23

24 for(contagem = 0; contagem < 10; contagem++){

25

26 if (verificanumero == vetor[contagem]){

27 printf("A matricula %d esta presente no sistema\n",verificanumero);

28 contemcerto++;

29 }

30

31 }

32 if(contemcerto == 0){

33 printf("A matricula %d nao esta presente no sistema\n",verificanumero);

34 }

35

36 contagemverifica++;

37 contemcerto = 0;

38 }

39

40 return 0;

41 }

Exercício 6

Fazer um algoritmo que:


Preencha 3 vetores, o primeiro com a nota da primeira prova, o
segundo com a nota da segunda prova e o terceiro com a média
das 2 primeiras notas, e imprima o resultado “APROVADO”
para aqueles que obtiverem uma média igual ou acima de 6, e
“REPROVADO” para quem obtiverem uma média abaixo de 6.
OBS.: Saia do laço quando a primeira nota for igual a -1.
1 /*

2 * Autor: Kelvin Santiago

3 * Criado: 23/07/2013

4 */

6 #include<math.h>

7 #include<stdio.h>

8 #include<string.h>

10 int main(void){

11

12 int contador = 0;

13 float vetor[3],nota = 0 ;

14

15 scanf("%f",&nota);

16

17 while ( nota != -1){

18

19 vetor[contador] = nota;

20 contador++;

21

22 if (contador == 2){

23

24 vetor[contador] = (vetor[0] + vetor[1]) / 2;

25 printf("%.2f\n",vetor[contador]);

26

27 if(vetor[contador] >= 6){

28 printf("APROVADO\n");

29 }

30 else{

31 printf("REPROVADO\n");
32 }

33 contador = 0;

34 }

35 scanf("%f",&nota);

36

37 }

38 return 0;

39 }

Exercício 7

Preencha e imprima um vetor dos 20 primeiros números primos


começando com o número 5000.

1 * Autor: Kelvin Santiago

2 * Criado: 24 / 07 / 2013

3 *

4 */

6 #include <stdio.h>

7 #include <math.h>

8 #include <string.h>

10 int main(void)

11 {

12

13 int contador_vetor = 0, vetor[20], numeroprimo = 0, calculo = 0,numero, contador_verificar;

14

15 for (numero = 5000; contador_vetor < 20 ; numero++){

16

17 for (contador_verificar = 1; contador_verificar <= numero; contador_verificar++ ){

18
19 calculo = (numero % contador_verificar);

20

21 if (calculo == 0){

22 numeroprimo++;

23 }

24 }

25 if (numeroprimo == 2){

26 vetor[contador_vetor] = numero;

27 printf("%d\n",vetor[contador_vetor]);

28 contador_vetor++;

29

30 }

31 numeroprimo = 0;

32 }

33

34 return 0;

35 }

Exercício 8

Fazer um algorítimo que leia os valores de um vetor inteiro de


tamanho 10, e imprima o valor da soma dos números ímpares
presentes neste vetor.

1 * Autor: Kelvin Santiago

2 * Criado: 25 / 07 / 2013

3 *

4 */

6 #include <stdio.h>

7 #include <math.h>

8 #include <string.h>
9

10 int main(void)

11 {

12 int vetor[10], somaimpares = 0, contador;

13

14 for(contador = 0; contador < 10; contador++){

15

16 scanf("%d",&vetor[contador]);

17

18 if (vetor[contador] % 2 != 0){

19 somaimpares += vetor[contador];

20 }

21 }

22

23 printf("%d\n",somaimpares);

24 return 0;

25 }

103. Fazer um algoritmo que leia os valores de duas matrizes 3×3


e imprima a mutiplicação das duas matrizes.

1 * Autor: Kelvin Santiago

2 * Criado: 25 / 07 / 2013

3 *

4 */

6 #include <stdio.h>

7 #include <math.h>

8 #include <string.h>

10 int main(void)

11 {
12 int matriz1[3][3], matriz2[3][3], matrizmult[3][3], linha, coluna, i, j, k;

13

14 // Fazendo Scan da PRIMEIRA matriz :DD

15 for(linha = 0; linha < 3; linha++){

16 for( coluna = 0; coluna < 3; coluna++){

17 scanf("%d",&matriz1[linha][coluna]);

18 }

19 }

20

21 // Fazendo Scan da SEGUNDA matriz :DD

22 for(linha = 0; linha < 3; linha++){

23 for( coluna = 0; coluna < 3; coluna++){

24 scanf("%d",&matriz2[linha][coluna]);

25 }

26 }

27

28 // Zerando matriz da multiplicacao

29

30 for(i = 0; i < 3; i++){

31 for(j = 0; j < 3; j++){

32 matrizmult[i][j]= 0;

33 }

34 }

35

36 // Fazendo Multiplicacao de matrizes :/

37 for( i = 0; i < 3; i++){

38 for( j = 0; j < 3; j++){

39 for( k = 0; k < 3; k++) {

40 matrizmult[i][j] += matriz1[i][k] * matriz2[k][j];

41 }

42 }

43 }

44

45 // Imprimindo matriz mult

46 for(linha = 0; linha < 3; linha++){


47 for( coluna = 0; coluna < 3; coluna++){

48 if( coluna == 2){

49 printf("%d\n",matrizmult[linha][coluna]);

50 }

51 else{

52 printf("%d ",matrizmult[linha][coluna]);

53 }

54 }

55 }

56 return 0;

57 }

Exercício 9

Faça um algorítimo que leia um vetor de 5 posições, e preencha


um segundo vetor, sendo que cada posição do segundo vetor
receberá o valor do primeiro vetor na mesma posição
multiplicado pelo maior valor dentro do primeiro vetor.

1 * Autor: Kelvin Santiago

2 * Criado: 25 / 07 / 2013

3 *

4 */

6 #include <stdio.h>

7 #include <math.h>

8 #include <string.h>

10 int main(void)

11 {

12 int vetor1[5], vetor2[5], contador, maiornumero = 0;

13
14 // Lendo valores do primeiro vetor e verificando maior numero :DD

15 for(contador = 0; contador < 5; contador++){

16 scanf("%d",&vetor1[contador]);

17

18 if (maiornumero == 0){

19 maiornumero = vetor1[contador];

20 }

21 else if( vetor1[contador] > maiornumero){

22 maiornumero = vetor1[contador];

23 }

24 }

25

26 // Preenchendo vetor 2 multiplicando vetor 1 pelo maior numero.

27 for( contador = 0; contador < 5; contador++){

28 vetor2[contador] = vetor1[contador] * maiornumero;

29 printf("%d\n",vetor2[contador]);

30 }

31

32 return 0;

33 }

Exercício 10

Dado dois números inteiros X e Y, preencha um vetor em que é


armazenado os primeiro X números múltiplos de Y.

1 * Autor: Kelvin Santiago

2 * Criado: 25 / 07 / 2013

3 *

4 */

6 #include <stdio.h>
7 #include <math.h>

8 #include <string.h>

10 int main(void)

11 {

12 int vetorA[5] = {0}, vetorB[8] = {0}, contador, contadorb, igualdades = 0;

13

14 for(contador = 0; contador < 5; contador++){

15

16 scanf("%d",&vetorA[contador]);

17 }

18

19 for(contador = 0; contador < 8; contador++){

20

21 scanf("%d",&vetorB[contador]);

22 }

23

24 for(contador = 0; contador < 5; contador++){

25 for(contadorb = 0; contadorb < 8; contadorb++){

26

27 if (vetorA[contador] == vetorB[contadorb]){

28

29 if (igualdades == vetorA[contador]){

30

31 }

32

33 else{

34 igualdades = vetorA[contador];

35 printf("%d\n",vetorA[contador]);

36 }

37 }

38 }

39 }

40

41 return 0;
42 }

Exercício 11

Dados dois vetores, A (5 elementos) e B (8 elementos), faça um


programa que imprima todos os elementos comuns aos dois
vetores.

1 * Autor: Kelvin Santiago

2 * Criado: 25 / 07 / 2013

3 *

4 */

6 #include <stdio.h>

7 #include <math.h>

8 #include <string.h>

10 int main(void)

11 {

12 int vetorA[5] = {0}, vetorB[8] = {0}, contador, contadorb, igualdades = 0;

13

14 for(contador = 0; contador < 5; contador++){

15

16 scanf("%d",&vetorA[contador]);

17 }

18

19 for(contador = 0; contador < 8; contador++){

20

21 scanf("%d",&vetorB[contador]);

22 }

23

24 for(contador = 0; contador < 5; contador++){


25 for(contadorb = 0; contadorb < 8; contadorb++){

26

27 if (vetorA[contador] == vetorB[contadorb]){

28

29 if (igualdades == vetorA[contador]){

30

31 }

32

33 else{

34 igualdades = vetorA[contador];

35 printf("%d\n",vetorA[contador]);

36 }

37 }

38 }

39 }

40

41 return 0;

42 }

Exercício 12

Fazer um algorítimo que seja lido um número inteiro X e preencha


um vetor com os divisores de X, começando do X até o número 1.
Imprimir o vetor em ordem inversa (de trás para frente).

1 * Autor: Kelvin Santiago

2 * Criado: 25 / 07 / 2013

3 *

4 */

6 #include <stdio.h>

7 #include <math.h>
8 #include <string.h>

10 int main(void)

11 {

12 int numero, divisores, numerodivisores = 0, vetor[numerodivisores], contador = 0;

13

14 scanf("%d",&numero);

15 for (divisores = 1; divisores <= numero; divisores++){

16

17 if (numero % divisores == 0){

18 vetor[contador] = divisores;

19 contador++;

20 numerodivisores++;

21 }

22 }

23

24 for(contador =0; contador < numerodivisores; contador++){

25 printf("%d\n",vetor[contador]);

26 }

27

28 return 0;

29 }

Exercício 13

Fazer um algoritmo que seja lido uma palavra e imprima a


quantidade de letras que compões a palavra.

1 #include<stdio.h>

2 #include<string.h>

4 int main()
5 {

6 char palavra[50];

7 int i;

9 scanf("%s",palavra);

10

11 for(i = 0; i <= palavra[i]; i++){}

12

13 printf("%d\n",i);

14 return 0;

15 }

Exercício 14

Fazer um algorítimo que seja lido uma palavra e imprima a


quantidade de vogais que compõe a palavra.

1 * Autor: Kelvin Santiago

2 * Criado: 25 / 07 / 2013

3 *

4 */

6 #include <stdio.h>

7 #include <math.h>

8 #include <string.h>

10 int main(void)

11 {

12 char palavra[20];

13 int contador, numVogais = 0;

14

15 scanf("%s",palavra);
16

17 for(contador=0; contador < palavra[contador]; contador++){

18

19 if (palavra[contador]== 'A' || palavra[contador]== 'E' || palavra[contador]== 'I' || palavra[contador]== 'O' ||


palavra[contador]== 'U'){
20
numVogais++;
21
}
22
}
23

24
printf("%d\n",numVogais);
25

26
return 0;
27
}

_____________________________________________________________________________
_______

https://www.ime.usp.br/~macmulti/exercicios/vetores/

4. Exercícios com Vetores

1. Dada uma seqüência de n números, imprimi-la na ordem inversa à da


leitura.

Solução em C

2. Deseja-se publicar o número de acertos de cada aluno em uma prova em


forma de testes. A prova consta de 30 questões, cada uma com cinco
alternativas identificadas por A, B, C, D e E. Para isso são dados:

o cartão gabarito;
o número de alunos da turma;
o cartão de respostas para cada aluno, contendo o seu número e suas
respostas.

Solução em C
Solução em Pascal

3. Tentando descobrir se um dado era viciado, um dono de cassino


honesto (ha! ha! ha! ha!) o lançou n vezes. Dados os n resultados dos
lançamentos, determinar o número de ocorrências de cada face.

4. Dados dois vetores x e y, ambos com n elementos, determinar o


produto escalar (1) desses vetores.

Solução em C
Solução em Pascal

5. Faça um programa para resolver o seguinte problema:


São dadas as coordenadas reais x e y de um ponto, um número
natural n, e as coordenadas reais de n pontos (1 < n < 100). Deseja-se
calcular e imprimir sem repetição os raios das circunferências centradas
no ponto (x,y) que passam por pelo menos um dos n pontos dados.

Exemplo : (x,y) = (1.0, 1.0) ; n = 5


pontos : (-1.0, 1.2) , (1.5, 2.0) , (0.0, -2.0) , (0.0, 0.5) , (4.0, 2.0)
Nesse caso há três circunferências de raios: 1.12, 2.01 e 3.162.
Observações:

o Distância entre os pontos (a,b) e (c,d) é


o Dois pontos estão na mesma circunferência se estão à
mesma distância do centro.

6. (COMP 89) Dados dois strings (um contendo uma frase e outro
contendo uma palavra), determine o número de vezes que a palavra
ocorre na frase.

Exemplo:
Para a palavra ANA e a frase :
ANA E MARIANA GOSTAM DE BANANA (2)
Temos que a palavra ocorre 4 vezes na frase.

Solução em Pascal

7. (MAT 88) Dada uma seqüência de n números reais, determinar os


números que compõem a seqüência e o número de vezes que cada um
deles ocorre na mesma.

Exemplo: n = 8
Seqüência: -1.7, 3.0, 0.0, 1.5, 0.0, -1.7, 2.3, -1,7
Saída: -1.7 ocorre 3 vezes
3.0 ocorre 1 vez
0.0 ocorre 2 vezes
1.5 ocorre 1 vez
2.3 ocorre 1 vez

Solução em C
Solução em Pascal

8. Dados dois números naturais m e n e duas seqüências ordenadas


com m e n números inteiros, obter uma única seqüência ordenada
contendo todos os elementos das seqüências originais sem repetição.

Sugestão: Imagine uma situação real, por exemplo, dois fichários de


uma biblioteca.

Solução em Pascal

9. Dadas duas seqüências com n números inteiros entre 0 e 9,


interpretadas como dois números inteiros de n algarismos, calcular a
seqüência de números que representa a soma dos dois inteiros.

Exemplo: n = 8,
1ª seqüência 8 2 4 3 4 2 5 1

2ª seqüência + 3 3 7 5 2 3 3 7
1 1 6 1 8 6 5 8 8

Solução em Pascal

10. Calcule o valor do polinômio p(x)=a0+a1x+...+anxn em k pontos


distintos. São dados os valores de n (grau do polinômio), de a0, a1, ...,
an (coeficientes reais do polinômio), de k e dos pontos x1, x2, ..., xk.

11. Dado o polinômio p(x)=a0+a1x+...+anxn, isto é, os valores de n e


de a0, a1, ..., an , determine os coeficientes reais da primeira derivada
de p(x).

12. Dado um polinômio p(x)=a0+a1x+...+anxn, calcular o


polinômio q(x) tal que p(x)= (x- ).q(x) + p( ), para m valores
distintos de (Usar o método de Briot-Ruffini) (3).

13. Dados dois polinômios


reais p(x)=a0+a1x+...+anxn e q(x)=b0+b1x+...+bmxm determinar o
produto desses polinômios.

14. (POLI 82) Chama-se seqüência de Farey relativa a n, a seqüência


das frações racionais irredutíveis, dispostas em ordem crescente, com
denominadores positivos e não maiores que n.

Exemplo: Se n=5, os termos da seqüência de Farey, tais


que 0 < < 1 são:

Para gerarmos os termos de uma seqüência de Farey tais


que 0 < < 1, podemos usar o seguinte processo. Começamos com as
frações

,
e entre cada duas frações consecutivas

,
introduzimos a fração:

e assim sucessivamente enquanto j + m < n. Quando não for mais


possível introduzir novas frações teremos gerado todos os termos da
seqüência de Farey relativa a n, tais que 0 < < 1.

Usando o processo descrito, determine os termos , 0 < < 1, da


seqüência de Farey relativa a n, n inteiro positivo.

Sugestão: Gere os numeradores e os denominadores em dois vetores.

15. Em uma classe há n alunos, cada um dos quais realizou k provas


com pesos distintos. Dados n , k, os pesos das k provas e as notas de
cada aluno, calcular a média ponderada das provas para cada aluno e a
média aritmética da classe em cada uma das provas.

16. (QUIM 84) Dada uma seqüência x1, x2, ..., xk de números inteiros,
verifique se existem dois segmentos consecutivos iguais nesta
seqüência, isto é, se existem i e m tais que:

xi, xi+1,..., xi+m-1 = xi+m, xi+m+1,..., xi+2m-1

Imprima, caso existam, os valores de i e m.

Exemplo: Na seqüência 7, 9, 5, 4, 5, 4, 8, 6 existem i=3 e m=2.

Solução em C
Solução em Pascal

17. Dada uma seqüência de n números inteiros, determinar um


segmento de soma máxima.

Exemplo: Na seqüência 5, 2, -2, -7, 3, 14, 10, -3, 9, -6, 4, 1 , a soma do


segmento é 33.

18. (POLI 88) Simule a execução do programa abaixo destacando a


sua saída:
#include <stdio.h
int main()
{
int n, inic, fim, i, aux, para, a[100];

printf("Digite n: ");
scanf("%d", &n);
printf("n = %d\n", n);
printf("Digite uma sequencia de %d numeros.\n", n);
for (i = 0; i < n; i++) {
scanf("%d", &a[i]);
printf("%d ", a[i]);
}
printf("\n");
inic = 0;
fim = n - 1;
aux = a[inic];
while (inic < fim) {
para = 0;
while ((inic < fim) && !para) {
if (a[fim] <= aux)
para = 1;
else
fim = fim - 1;
}
if (para) {
a[inic] = a[fim];
inic = inic + 1;
para = 0;
while ((inic < fim) && !para) {
if (a[inic] <= aux)
inic = inic + 1;
else
para = 1;
}
if (para) {
a[fim] = a[inic];
fim = fim - 1;
}
}
for (i = 0; i < n; i++)
printf("%d ", a[i]);
printf("\n");
}
a[inic] = aux;
for (i = 0; i < n; i++)
printf("%d ", a[i]);
printf("\n");
return 0;
}
Dados:
7
10 3 6 12 13 7 15

Resultado da Simulação

Anda mungkin juga menyukai