1 1 A Máquina Analítica de Charles Babage

Transcrição

1 1 A Máquina Analítica de Charles Babage
NOTAS DE AULA DE INTRODUÇÃO À PROGRAMAÇÃO
PROFESSOR: MATEUS CONRAD B. DA COSTA1
1
2
3
4
5
6
7
1
A Máquina Analítica de Charles Babage ....................................................................... 3
1.1
Estrutura Lógica ..................................................................................................... 3
Máquinas Programáveis ................................................................................................. 4
Programa (Software)....................................................................................................... 4
3.1
Solução de Problemas............................................................................................. 5
3.1.1
Solução descritiva do problema...................................................................... 7
3.1.2
Algoritmos ...................................................................................................... 8
3.1.3
Refinamentos Sucessivos ............................................................................... 9
3.1.4
Desvios Condicionais ................................................................................... 10
3.1.5
Comando de repetição .................................................................................. 11
3.1.6
Algoritmo Completo..................................................................................... 11
3.1.7
Comando de atribuição................................................................................. 12
3.1.8
Exercícios de Fixação {Níveis de Conhecimento e Compreensão}............ 13
3.2
Variáveis............................................................................................................... 14
3.2.1
Memória ....................................................................................................... 14
3.2.2
Variáveis são apelidos para endereços ......................................................... 15
3.2.3
Nomes de variáveis (identificadores) ........................................................... 15
3.2.4
Tipos de uma variável .................................................................................. 16
3.2.5
Declaração de Variáveis ............................................................................... 17
3.2.6
Exemplos ...................................................................................................... 17
3.2.7
Expressões Aritméticas ................................................................................ 20
3.2.8
Expressões Lógicas ...................................................................................... 21
3.2.9
Exemplo........................................................................................................ 23
3.2.10
Exercícios de Fixação................................................................................... 25
Construção de Programas em Linguagem C ................................................................ 28
4.1
Estratégias para resolução de Problemas usando C.............................................. 28
4.2
Introdução a Linguagem C ................................................................................... 29
4.3
Guia Rápido.......................................................................................................... 29
4.4
Exemplos Introdutórios ........................................................................................ 33
4.5
Exercícios de Fixação........................................................................................... 40
Comandos de Repetição ............................................................................................... 41
5.1
Motivação ............................................................................................................. 41
5.2
Estrutura básica de um laço .................................................................................. 41
5.3
Sintaxe de comandos de repetição........................................................................ 42
5.3.1
Comando Enquanto ..................................................................................... 42
5.3.2
Exemplos ...................................................................................................... 43
5.3.3
Exercícios ..................................................................................................... 47
5.3.4
Comando Faça .. .Enquanto.......................................................................... 48
5.3.5
Exercícios ..................................................................................................... 48
Vetores e Matrizes ........................................................................................................ 49
6.1
Vetores.................................................................................................................. 49
Registros - Structs........................................................................................................ 56
Obs: Esta cópia não é revisada, podendo conter erros de ortografia.
1
7.1
Definição .............................................................................................................. 56
7.2
Declaração de registros......................................................................................... 56
7.3
Manipulando Registros......................................................................................... 57
7.4
Vetores de Registros............................................................................................. 58
7.5
Exercícios ............................................................................................................. 59
7.6
Registro em C ....................................................................................................... 59
8
Arquivos em C.............................................................................................................. 60
8.1
Definições............................................................................................................. 61
8.2
Tipos de arquivos ................................................................................................. 61
8.3
Ferramentas para manipulação de arquivos.......................................................... 62
8.3.1
Estrutura de controle FILE – ........................................................................ 62
8.3.2
Abrindo um arquivo ..................................................................................... 62
8.3.3
Escrevendo e lendo em um arquivo texto.................................................... 64
8.4
Outras Funções de manipulação de arquivos ....................................................... 67
8.5
Lendo e escrevendo em Arquivos Binários - fread() e fwrite()........................... 68
8.5.1
Operador que determina o tamanho de tipos de dados - sizeof() ............... 68
8.5.2
Função fwrite() ............................................................................................. 69
8.5.3
Função fread()............................................................................................... 69
8.5.4
Exemplo1 - Escrevendo número inteiros em um arquivo ........................... 70
8.5.5
Operador de endereço &............................................................................... 70
8.5.6
Exemplo 2 – Programa que lê um arquivo binário de números inteiros e
imprime na tela ............................................................................................................. 71
8.5.7
Exemplo 3 - Escrevendo um vetor de inteiros em um arquivo binário ...... 71
8.5.8
Nomes de vetores são ponteiros ................................................................... 72
9
Apontadores.................................................................................................................. 72
9.1
Apontadores e Endereços ..................................................................................... 73
9.2
Declaração de Apontadores .................................................................................. 74
9.3
Qual o valor inicial de um apontador? ................................................................. 78
9.4
Apontadores e Vetores (Arranjos)........................................................................ 78
9.5
O nome do vetor é um apontador constante ......................................................... 80
9.6
Exercícios ............................................................................................................. 82
9.6.1
Mais exemplos.............................................................................................. 89
10
Construção de Funções............................................................................................. 92
10.1 Chamada de Função ............................................................................................. 92
10.2 Fluxo de Execução ............................................................................................... 93
10.3 Passagem de parâmetros....................................................................................... 93
10.4 Tipo de retorno da função..................................................................................... 93
10.5 Construção de Funções......................................................................................... 93
2
1
A Máquina Analítica de Charles Babage
1.1
Estrutura Lógica
Armazena
mento
Seção de
Entrada
Engenho
Seção
De
Saída
Seção de Entrada - Dispositivo que interpreta Cartões perfurados contendo valores e
instruções, e conduzia os valores para o armazenamento e instruções para o engenho.
Valores – Informações numéricas a serem trabalhadas (Dados)
Instruções – “A forma” como os valores devem ser trabalhados.
Exemplo:
Valores:
- valor1 = 7
- Valor2 = 3.14
Instruções:
- Elevar valor1 ao quadrado
- Guardar o resultado da operação anterior em valor3
- Multiplicar valor2 por valor3
- Enviar o resultado da operação para a seção de saída
Armazenamento – Dispositivo capaz de guardar valores vindos da seção de entrada e do
engenho. Os valores podem ser recuperados (buscados) pelo engenho através de um
sistema de numeração das células de armazenamento (sistema de endereços)
3
Engenho – A partir de instruções vindas da seção de entrada, (isto é, seguindo o que é
determinado pelas instruções) realiza os seguintes tipos de operações:
- busca de valores no armazenamento
- realiza operações aritméticas sobre os valores
- envia valores para o armazenamento
- envia valores para a seção de saída
Seção de saída - Produz cartões perfurados representando os valores enviados pelo
engenho em símbolos que podem ser interpretados pelo operador
2
Máquinas Programáveis
Essa estrutura da máquina analítica tornava a mesma programável.
Definição:
“Uma máquina programável é aquela que pode alterar seu comportamento
mediante uma seqüência de instruções novas (software) sem a necessidade de
alterar a sua estrutura física (Hardware).”
Exemplos de máquinas não programáveis
- Linha de produção tradicional
- Um aparelho liqüidificador
- Calculadoras (não programáveis)
Exemplos de máquinas programáveis
- Computador
- Robôs
- Calculadoras programáveis
3
Programa (Software)
Programa e software são sinônimos. Um definição de programa é dada a seguir:
Programa
“Um programa é um conjunto de instruções em seqüência (ordem
cronológica para a sua execução) que pode ser interpretada e executada por uma
máquina programável de forma não ambígua.”
Nível Abstração
“Abstrair significa deixar de lado certas partes de um todo em função de
prestar atenção a apenas alguns detalhes desse todo. É focar seu ponto de
atenção em um detalhe que, por hora, é o que interessa... é concentrar-se naquilo
que, por hora, interessa.”
Essa idéia de abstração é usada em computação. Níveis de abstração altos
indicam uma proximidade com os mecanismos humanos de resolver problemas,
deixando-se de lado características físicas de computadores. Níveis de abstração
4
baixos indicam a preocupação com a implementação da solução em uma forma
executável em um computador.
Linguagens de Programação
Os elementos (códigos, símbolos, nomes, etc.) usados para se escrever um
programa formam uma linguagem de programação2.
Existem linguagens que não podem ser entendidas pelo computador e são mais
próximas do homem. Essas linguagens possuem um alto nível de abstração.
São exemplos dessas linguagens: Basic, Pascal, Modula, C, C++, Java, Python,
etc.
A linguagem entendida pelo computador é de difícil compreensão para o homem e
possui um baixo nível de abstração.
Criaram-se então mecanismos para que os programadores escrevam programas em
linguagens de alto nível que possam ser traduzidos em programas de baixo nível.
Esses programas são chamados de Tradutores e Interpretadores.
Um tradutor tem a função de, a partir de um programa escrito em uma
determinada linguagem, traduzi-lo completamente para uma outra, similarmente à
tradução de um texto de um idioma para outro.
Um interpretador funciona como um intérprete, traduzindo o programa instrução
por instrução e passando essa instrução interpretada individualmente para o seu
receptor. No caso, o computador.
Tradutores que transformam programas escritos em linguagem de alto nível para
programas em linguagem de baixo nível executáveis em um computador
(linguagem de máquina) são chamados de Compiladores. Ou seja, um
compilador é um tipo especial de tradutor.
3.1
Solução de Problemas
Programas são criados para se resolver um determinado problema. Dada uma
máquina e um problema, a solução desse problema é dada por um programa. No
entanto, a construção de um programa passa por várias etapas até estar em
linguagem de máquina
A figura a seguir mostra os vários níveis de abstração de um programa desde a
proposição do problema.
2
Na realidade, esse elementos formam a gramática da linguagem e o conjunto de todos os programas que
podem ser escritos com essa gramática formam a linguagem. Por motivos de simplificação consideramos o
conjunto dos elementos citados como sendo a linguagem.
5
SER HUMANO
PROBLEMA
homem
SOLUÇÃO
DESCRITIVA
homem
ALGORITMO
homem
homem
PROGRAMA FONTE EM
LINGUAGEM DE ALTO NÍVEL
Compilador/
Interpretador
PROGRAMA EM LINGUAGEM
DE MONTAGEM OU PSEUDO
CÓDIGO
Montador (Assembler)
PROGRAMA EM LINGUAGEM
DE MÁQUINA (EXECUTÁVEL)
Execução do programa
COMPUTADOR
6
3.1.1 Solução descritiva do problema
A construção de programas se inicia com a definição do problema. Esta, por sua vez,
parte do estabelecimento de um resultado a ser alcançado a partir de um determinado
conjunto de dados de entrada (domínio dos dados). Matematicamente um programa pode
ser considerado uma função (conjunto de instruções) que atua sobre um conjunto de
variáveis (dados).
F(variáveis de entrada) = Programa
As implicações do conceito matemático de função podem ajudar ao programador a não
cometer erros.
Vejamos por exemplo o problema do cálculo do fatorial de um número. Nesse problema, o
programador terá como variável de entrada um único número inteiro e deverá formular um
algoritmo, ou seja, uma seqüência ordenada de instruções, para obter o fatorial desse
número. Além disso, o programador deverá conhecer as peculiaridades do cálculo de
fatorial para definir restrições quanto ao domínio da função fatorial.
Sabemos que o fatorial só existe para números inteiros positivos incluindo o zero.
Portanto, o programa para o calculo do fatorial não poderá aceitar como dados de entrada
números inteiros negativos e tampouco números fracionários.
As restrições de entrada de dados devem ser precisamente estabelecidas pois a corretude de
algoritmo tem por base um determinado conjunto de dados de entrada e, situações
imprevistas para esses dados podem levar o algoritmo a uma condição de erro.
Não só o domínio dos dados mas a observação de todas as características que determinam
a natureza do problema é de suma importância para o programador. O primeiro passo do
programador é entender o problema em seus mínimos detalhes.
Voltando ao problema do fatorial, para construção da solução, o programador deve
obviamente conhecer a função fatorial. O fatorial de um número x é dado pelo produto de
todos os números inteiros que vão de x até 1, para valores de x maiores que 0 e é igual a 1
para x igual a zero. Se o programador não conhecer essa informação, torna-se impossível o
mesmo construir um algoritmo que forneça o resultado desejado.
Assim, com base nas restrições de entrada e nas características do problema, podemos
montar uma primeira solução do mesmo, a solução descritiva do problema:
Para calcular o fatorial de um número devemos obter como entrada de dados
apenas números inteiros positivos incluindo o zero. Em posse do número que se deseja
calcular o fatorial verificamos duas alternativas:
Se o número for maior que 0, o fatorial é dado pelo produto de todos os números
inteiros menores ou iguais ao número e maiores que zero.
Se o número for igual a 0, então o fatorial é igual a 1
Nem sempre a solução descritiva de um problema está pronta na matemática como no
caso do fatorial. Na maioria das vezes a solução descritiva do problema resulta de
7
elaborações de longas seqüências lógicas encadeadas, obtendo-se assim vários resultados
intermediários até que se chegue no resultado final esperado.
3.1.2 Algoritmos
A solução descritiva de um problema possui um alto nível de abstração e é baseada em
mecanismos humanos de resolução de problemas. Quando Apresentamos na solução do
problema do fatorial a sentença:
Se o número for maior que 0, o fatorial é dado pelo produto de todos os números
inteiros menores ou iguais ao número e maiores que zero,
estamos usando um mecanismo humano para resolver o problema. Ora, sabemos
como fazer um produto de um conjunto de números:
Multiplicamos o primeiro pelo segundo e o resultado disso pelo terceiro e assim por
diante até terminarmos com os números.
Resta-nos saber ser conseguiremos transpor para uma linguagem de programação a
solução proposta na sentença.
Iniciar a tradução de uma solução descritiva de um problema para um programa em
uma linguagem de computador pode não ser uma abordagem eficiente. Lembre-se: Existe
um nível de abstração adequado para cada momento. Além disso, alguns mecanismos da
solução descritiva podem mesmo nem ter uma forma de implementação adequada em uma
linguagem de programação. Por esses motivos, o correto é a construção de um algoritmo
que detalhe a solução descritiva em termos de construções lógicas similares as que serão
encontradas nas linguagens de programação em que se pretende implementar a solução.
Construindo um algoritmo, o programador deverá atingir os seguintes objetivos:
1. Validar solução encontrada como possível de ser implementada;
2. encontrar uma solução computacional eficiente para o problema;
3. impor um nível de detalhamento da solução tal que no trabalho de transposição
posterior para um código na linguagem de programação alvo, o programador
não se tenha nenhuma necessidade de alterar ou aumentar a solução do ponto de
vista lógico, preocupando-se nesse momento com os detalhes de implementação
(nível de abstração mais baixo).
8
Como exemplo, tomemos novamente o problema do fatorial. Um possível algoritmo para a
solução dada seria o seguinte:
Algoritmo Fatorial
1. obter o número que se deseja calcular o
armazenar na variável número
2. Se número for maior ou igual a 0 então
3. Calcular e informar o fatorial
Senão
4. informar que o fatorial não existe
Fim Se
Fim Alg.
fatorial
e
3.1.3 Refinamentos Sucessivos
Os itens numerados no algoritmo acima são instruções que ainda encontram-se em um
nível de abstração alto e podem ser detalhadas. A técnica de refinamentos sucessivos é
amplamente utilizada e visa o aumento gradativo do nível de detalhamento de um
algoritmo. Os programadores utilizam essa técnica para evitarem erros e encontrarem o
caminho mais curto para a solução.
Nessa técnica, o detalhamento de cada sentença numerada chama-se refinamento.
Vejamos, por exemplo, o refinamento 3 do algoritmo fatorial:
Ref. 3 { Calcular e informar o fatorial }
Se o número = 0 então
1.informar que o fatorial é igual a 1
Senão
2.calcular
o
fatorial
para
valores
maiores que 0
Fim Se
Fim Ref. 3
Para tornar os refinamentos claros de serem lidos, coloca-se a palavra Ref. seguida do
número do refinamento e o título do refinamento entre chaves. Em seguida o refinamento
passa a ser construído. No final coloca-se a indicação do fim do refinamento.
Os refinamentos podem continuar sucessivamente até que o algoritmos esteja detalhado no
nível que possa ser fielmente traduzido para a linguagem de programação.
No refinamento 3 teremos ainda dois refinamentos para serem escritos com comandos da
linguagem algorítmica utilizada:
Ref. 3.1 {informar que o fatorial é 1}
Imprima (“ O fatorial é ”, 1)
Fim ref. 3.1
9
Ref. 3.2 { Calcular o fatorial para valores maiores que 0 }
Fatorial recebe 1
Repita enquanto número > 0
Fatorial recebe Fatorial * Numero
Número recebe Número - 1
Fim Repita
Imprima(“O fatorial é ”, Fatorial)
Fim Ref. 3.2
Restam ainda os refinamentos 1,2 e 4. O refinamento 1 trata-se de uma entrada de dados.
Qualquer dado que for introduzido em um algoritmo deverá ser feito através da “leitura” de
uma variável. O comando algorítmico usado para a leitura da variável será Leia. Desse
modo teremos:
Ref. 1 { obter o número que se deseja calcular o fatorial
e armazenar na variável número}
Leia(número)
Fim Ref. 1.
O refinamento 4 é uma operação de saída. Para realizarmos qualquer operação de saída de
dados, usaremos o comando Imprima, que já foi usado no ref. 3.
Ref. 4 { informar que o fatorial não existe}
Imprima(“O fatorial de números negativos não existe”);
Fim Ref. 4
3.1.4 Desvios Condicionais
O refinamento 2 é um comando condicional se .... então .... senão .... fim se. Este comando
indica que as instruções posteriores ao então e anteriores ao senão só serão executadas
se as condições colocadas após o se forem verdadeiras. Caso contrário essas
instruções serão ignoradas e ocorrerá um salto para a instrução imediatamente
posterior ao senão. Nesse caso as instruções após o senão e antes do fim se é que serão
executadas.
No ref. 2 as condições podem ser detalhadas da seguinte forma:
Ref. 2 {Se
número for
maior ou igual a 0
então}
Se (número >=0) então
Fim Ref. 2
10
O comando formado pelas partes se ... então .... senão ... fim se é chamado de desvio
condicional composto pois possui a cláusula senão. O desvio condicional simples é
formado apenas pela construção:
Se .... então .... fim se.
No desvio condicional simples, se a condição após o Se for verdadeira, as instruções após o
então serão executadas. Caso contrário, ocorrerá um desvio (salto) para o primeiro
comando.
3.1.5 Comando de repetição
Para calcular o fatorial de números maiores que zero, foi lançado mão de uma construção
algorítmica chamada de comando de repetição. Esse comando é composto da forma
Repita enquanto <Condição>
Instrução 1 .
...
...
Instrução k
Fim repita
Nesse comando, as instruções após a condição e antes do fim repita serão executadas
ordenadamente. Quando a última instrução for executada (instrução k) haverá um retorno
até o comando repita, onde novamente a condição é testada. Se a condição continuar
verdadeira, o bloco de instruções será novamente executado. Quando a condição for para
um estado de Falsa, haverá um desvio para o comando imediatamente após o Fim repita.
Outras aplicações e derivações do comando de repetição serão estudadas mais adiante em
maiores detalhes. Nessa introdução nos ateremos a soluções de problemas que não
necessitam de comandos de repetição.
3.1.6 Algoritmo Completo
Concluídos todos os refinamentos, basta agora que os mesmos sejam reunidos em um
único código para termos um algoritmo completo da solução do problema. Esse
procedimento deve ser automático e não envolve nenhum raciocínio. Basta substituir os
títulos dos refinamentos no algoritmo principal pelos próprios refinamentos. O Algoritmo
completo e pronto para ser traduzido para uma linguagem de programação é dado a seguir.
Para ficar claro foram inseridos comentários no algoritmos. São os textos inseridos após os
// que estão indicando o refinamento correspondente dos comandos seguintes. Comentários
não tem função operacional alguma em algoritmos e programas. Sua única finalidade é
tornar claro o algoritmo. Segue então o algoritmo:
11
Algoritmo Fatorial
Leia(número) // ref. 1
Se (número >=0) então // ref. 2
Se número = 0 então // refinamento 3
Imprima (“ O fatorial é ”, 1) // ref. 3.1
Senão
// ref. 3.2
Fatorial recebe 1
Repita enquanto número > 0
Fatorial recebe Fatorial * Numero
Número recebe Número - 1
Fim Repita
Imprima(“O fatorial é ”, Fatorial)
Fim Se
Senão
Imprima(“O fatorial de números negativos não existe”);
Fim Se
Fim Algoritmo
3.1.7 Comando de atribuição
O comando Fatorial recebe 1 Está descrito claramente e indica que será armazenado na
variável Fatorial o valor 1. Em linguagens de programação comando assim são chamados
de comando de atribuição e representados por um sinal de atribuição. Nos algoritmos
iremos usar o sinal ← para representar uma atribuição.
Assim, o comando anterior fica mais simples de ser escrito, da seguinte forma:
Fatorial ← 1
O comando Fatorial recebe Fatorial * Numero, por sua vez, indica que o valor contido em
fatorial anteriormente deve ser multiplicado pelo valor contido em número e posteriormente
armazenado na variável fatorial, (apagando o valor que existia anteriormente em Fatorial).
Com o comando de atribuição ficaria da seguinte forma:
Fatorial ← Fatorial * Número
Analogamente, o comando Número recebe Número - 1 ficaria:
Número ← Número -1
Resumindo, Sempre que quisermos escrever que um variável recebe um valor usaremos o
sinal ← no lugar da palavra recebe. O algoritmo completo ficaria então:
Algoritmo Fatorial
Leia(número) // ref. 1
12
Se (número >=0) então // ref. 2
Se número = 0 então // refinamento 3
Imprima (“ O fatorial é ”, 1) // ref. 3.1
Senão
// ref. 3.2
Fatorial ←1
Repita enquanto número > 0
Fatorial ←Fatorial * Numero
Número ←Número - 1
Fim Repita
Imprima(“O fatorial é ”, Fatorial)
Fim Se
Senão
Imprima(“O fatorial de números negativos não existe”);
Fim Se
Fim Algoritmo
Chegamos ao final dessa seção. Na seção seguinte veremos detalhes relacionados a
variáveis. No capítulo seguinte estudaremos com mais detalhes as construções envolvendo
desvios condicionais e expressões lógicas. É fundamental a resolução dos exercícios
propostos para a continuação dos estudos.
3.1.8 Exercícios de Fixação {Níveis de Conhecimento e Compreensão}
1. Cite os quatro componentes da máquina analítica de Babage e suas respectivas funções.
2. Esta máquina poderia funcionar sem o armazenamento? Por que?
3. Como Funcionava o armazenamento? Como o engenho conseguia obter valores lá
armazenados?
4. Podemos afirmar que a máquina analítica era programável? Por que?
5. Explique a relação entre máquina programável, hardware e software.
6. Defina Programa. Enfatize três características que um programa deve possuir.
7. O que é linguagem de programação?
8. O que são tradutores e interpretadores?
9. O que caracteriza um compilador?
10. Geralmente, quando construímos um programa, partimos de uma solução mais abstrata.
Por que?
11. Quais aspectos de um problema devemos considerar para conseguirmos uma solução
descritiva do mesmo?
Quais objetivos devem ser alcançados na construção de um algoritmo?
12. Construa soluções descritivas para os problemas a seguir:
i.
Dados como entrada os valores de horas, minutos e segundos, informar o total
de segundos equivalente ao montante de horas, minutos e segundos informados.
ii.
Calcular a raiz quadrada de um número.
iii.
Dados como entrada três números, verificar se os mesmos podem ser os lados de
um triângulo
iv.
Dados três números de entrada (x, y e z), informar qual deles é o maior e qual
deles é o menor.
13
v.
Dados os coeficientes a, b e c de uma equação do segundo grau, verificar se as
raízes existem e em caso afirmativo, calcular e informar as mesmas.
vi.
Dados quatro números de entrada (x, y, z e w), informar qual deles é o maior e
qual deles é o segundo maior.
13. Construir algoritmos utilizando a técnica de refinamentos sucessivos para os itens ii, iv,
v e vi do exercício 12.
3.2
Variáveis
3.2.1 Memória
Assim como na máquina analítica de Babage, em computadores digitais a programação só
se torna possível pela fato destes terem em sua estrutura dispositivos de armazenamento
de dados.
A armazenagem de dados é fundamental em um programa pois é ela que permite “guardar”
os resultados de um passo de programação (instrução) para serem utilizados nos passos
seguintes (próximas instruções). Ou seja, é por meio das variáveis que se registram as
operações realizadas pelas instruções.
O esquema de armazenamento de computadores modernos é chamado memória. A
memória de um computador pode conter milhões ou bilhões de células numeradas onde
cada célula pode armazenar um valor (dado) individualmente. O número de cada célula é
chamado de endereço de memória da célula. Por maior que esta memória seja, cada
célula de armazenamento terá um endereço exclusivo, único. É através desses endereços
que os dados podem ser recuperados na memória e levados para o processador.
Em uma célula de memória, não pode existir mais de um valor ao mesmo tempo, embora
este valor possa ser alterado no tempo dada a execução de um programa. Por exemplo, uma
célula pode, no inicio de um programa estar valendo 0 e através da execução de uma
instrução de programa vir a valer 5.
O esquema a seguir é um modelo simplificado da memória de um computador. Os números
binários da coluna endereço são os endereços de cada célula e os números binários na
coluna Célula são os conteúdos das posições de memória identificados pelos respectivos
endereços. Por exemplo, o conteúdo do endereço de memória 1 (1 em binário, 001 na
representação da figura) é 128 (binário 1000 0000). Já o conteúdo do endereço de memória
5 (binário 101) é 2 ( 10 em binário, 0000 0010 na figura).
Endereço
000
001
010
011
100
101
110
111
Célula
00101001
10000000
11001000
10101010
11000000
00000010
10101010
01010101
Representação da memória de um computador
digital
14
3.2.2 Variáveis são apelidos para endereços
Os endereços são seqüências de 0 e 1 e geralmente estão submetidos a esquemas de
endereçamento complexos. Por esse e outros motivos, o uso de números para informar
endereço de memória em um programa em linguagem de alto nível seria uma tarefa
trabalhosa e inconveniente para o programador. Felizmente, há uma maneira mais simples
de lidar com esses endereços de memória chamando-os de variáveis.
Uma variável nada mais é do que uma abstração, um apelido para um endereço de
memória. Tal abstração é possível porque quando estamos construindo um programa não
necessitamos saber onde exatamente os dados serão armazenados. Necessitamos apenas
que sejam armazenados em lugares determináveis. Dessa forma, quando quisermos
armazenar um valor em um determinado lugar, basta denominar esse lugar com um nome
único e, sempre que se quiser realizar uma operação envolvendo o valor armazenado,
utilizamos o nome dado. Este nome é chamado de identificador da variável ou
simplesmente nome da variável. O nome da variável é o seu atributo mais importante.
No algoritmo para o cálculo do fatorial, foram utilizadas duas variáveis: A variável
Número e a variável Fatorial. Ambas as variáveis se destinam, no algoritmo, a
armazenarem valores numéricos. Poderíamos entretanto, termos variáveis destinadas a
armazenarem outros tipos de dados como por exemplo palavras e valores lógicos
(verdadeiro e falso). O tipo da variável é o outro atributo que por hora irá nos importar.
Uma variável portanto possui dois atributos fundamentais que iremos ver em detalhes:
- nome
- tipo
3.2.3 Nomes de variáveis (identificadores)
Nomes de variáveis em algoritmos devem obedecer a regras rigorosas quanto a sua
formação, semelhantes as regras impostas em linguagens de programação. Essas regras são
chamadas de regras para formação de identificadores e serão descritas a seguir:
1. Um nome de variável deve ser único no módulo do algoritmo onde for declarada.
Inicialmente consideraremos apenas que um nome de variável deve identificar
unicamente uma variável. Ou seja, NUNCA daremos um mesmo nome para duas
variáveis em um mesmo bloco de declaração.
2. Um nome de variável pode ser formado por uma letra de (A .. Z) e de (a .. z), sendo
que em linguagem algorítmica não faremos distinção entre maiúsculas e minúsculas
(embora na linguagem C maiúsculas e minúsculas sejam distinguidas).
São exemplos de nomes válidos de variáveis: X, y h M Z Q W e k.
3. Um nomes de variável pode ser formado por palavras ou seqüências de caracteres que
obedeçam as seguintes regras:
- O primeiro caracter deve obrigatoriamente ser uma letra;
- Os demais caracteres podem ser números, letras ou o símbolo _
(underscore).
São exemplos de variáveis: Numero, Nota, Teste, x1,X2, Yw1, HK3, Nota_maxima
15
4. Uma variável nunca poderá receber como identificador, um nome de comando ou tipo
de variável da linguagem definida. Ex: se, senão, então, repita, etc.
5. Um nome de variável deve ser formado exclusivamente obedecendo as regras acima.
Vale lembrar que não podem existir nomes de variáveis com espaços em branco, por
exemplo: valor máximo. Ao invés disso, utilize o símbolo _ para unir as duas partes
do nome: valor_máximo. Qualquer outro símbolo não é admitido em nomes de
variáveis. Isso inclui os símbolos:
( ) { } [ ] ‘ “ : ; > < , . / ? | \ - + = * & ^ % $ # @ ! ~ `.
6. Por último, devemos lembrar que em algoritmos admitiremos nomes de variáveis com
acentos, embora em quase todas as linguagens de programação não se admitem acentos
( A linguagem Logo permite acentos em variáveis).
3.2.4 Tipos de uma variável
Assim como em nossas vidas classificamos as coisas como pertencentes a determinada
classe ( eletrodomésticos, veículos, roupas, cães, pessoas, etc.) as variáveis também
deverão possuir uma classificação. Essa classificação diz respeito primordialmente à
natureza dos dados que desejamos armazenar nas variáveis. Em algoritmos, definiremos
três tipos de variáveis que determinam os três tipos básicos logicamente diferenciáveis em
linguagens de programação. São eles os tipos:
- Numérico
- Literal
- Lógico
Tipo Numérico – Quando definimos uma variável do tipo numérico, é por que nela
desejamos armazenar valores numéricos, ou seja, qualquer valor pertencente ao conjunto
dos números Reais, fracionários, inteiros, positivos ou negativos, evidentemente. Assim se
X e Y forem variáveis numéricas podemos realizar as seguintes atribuições são válidas:
X ← 20
Y←3.14
Y ← (X*X)*Y
Tipo Literal - O Tipo literal se reserva a armazenar valores que são seqüências de
caracteres. Uma seqüência de caracteres é formada por mais de um caracter (letras, número
ou símbolo) inserido entre “aspas” . Variáveis do tipo literal são destinadas a guardar
informação alfanumérica. Por exemplo, se as variáveis Nome e Turma forem literais. Os
comandos a seguir são válidos.
Nome ← “Adriana da Silva”
Turma ← “2° período”
Tipo Lógico – Verdadeiro ou falso, sim ou não, aberto ou fechado zero ou um, branco ou
preto, cheio ou vazio, etc. Muitas são as circunstâncias em que temos apenas duas
alternativas possíveis para um dado. Nesses casos, o tipo de variável que usaremos para
armazenar o dado é o tipo lógico, que só poderá armazenar dois valores: Verdadeiro e
Falso.
São atribuições válidas para variáveis lógicas:
Teste ← Verdadeira
16
Resultado ← (10<4) Neste caso, a variável receberá o valor resultante da
expressão lógica. No caso Falso)
3.2.5 Declaração de Variáveis
A maior parte das linguagens de programação exige que o programador defina
explicitamente as variáveis utilizadas no programa. Esse definição das variáveis se chama
declaração de variáveis e tem os seguintes objetivos:
- Definir o identificador da variável
- Definir o tipo da variável
- Definir o escopo da variável
O escopo de uma variável é a sua área de abrangência dentro de um programa. Falar em
escopo só faz sentido quando falamos de modularização e definição de funções. Portanto,
essa discussão será feita em etapas seguintes.
Sintaxe da declaração
A declaração de variáveis em nossos algoritmos será feita através do comando Declare
que define o início da declaração de variáveis.
Para declararmos uma variável devemos definir um identificador, de acordo com as regras
estabelecidas e o tipo da seguinte forma:
declare
X: numérico
Variáveis do mesmo tipo podem ser declaradas em seqüência, separadas por vírgulas:
declare
X,nota, media: numérico
Variáveis de tipos diferente são definidas em seqüências independentes:
declare
X,nota, media: numérico
teste: lógico
Nome,curso: literal;
3.2.6 Exemplos
Como exemplo vamos construir o algoritmo para o problema iii do exercício de fixação 12
da seção anterior.
Dados três números verificar e informar se os mesmos podem ser os lados de um triângulo.
17
Passo 1- Solução descritiva
Para solucionar esse problema precisamos saber que em um triângulo, um lado nunca é
maior ou igual a soma dos outros. Sabendo disso basta obter os valores e comparar cada
valor com a soma dos outros verificando se a condição do problema é atendida.
Passo2 – Algoritmo
No algoritmo iremos inserir um refinamento inicial chamado declarar variáveis. Esse
refinamento sempre será o último a ser feito pois só com o algoritmo finalizado é que
sabemos com certeza quais as variáveis que serão usadas.
Algoritmo triângulo
1. Declarar variáveis
2. Obter os três valores (a,b e c) candidatos a lados do triângulo
3. Verificar se os mesmos podem formar um triângulo
4. Informar o resultado
Fim algoritmo
Ref. 2 { Obter os três valores (a,b,c), candidatos a lados de um triângulo}
Leia (a,b,c)
Fim Ref. 2
Ref. 3 { Verificar se os mesmos podem formar um triângulo}
Se a < b+c então
Se b< a+c então
Se c < b+a então
E_TRIANGULO ← verdadeiro
Senão
E_TRIANGULO ← falso
Fim se
Senão
E_TRIANGULO ← falso
Fim se
Senão
E_TRIANGULO ← falso
Fim se
Fim ref. 3
Ref. 4 {informar o resultado}
Se E_TRIANGULO= verdadeiro então
Imprima(“Os valores fornecidos formam um triângulo”)
Senão
Imprima(“Os valores fornecidos não formam um triângulo”)
18
Fim se
Fim ref. 4
Nesse refinamento, usamos a variável lógica E_TRIANGULO para fazer o teste de
verificação. Como a variável é lógica,, ou seja já possui um valor verdadeiro ou falso, o
teste pode ser feito sem a comparação com o valor verdadeiro. Da seguinte forma:
Ref. 4 {informar o resultado}
Se E_TRIANGULO então
Imprima(“Os valores fornecidos formam um triângulo”)
Senão
Imprima(“Os valores fornecidos não formam um triângulo”)
Fim se
Fim ref. 4
Ref. 1 {declarar variáveis}
Declare a,b,c:numérico
E_TRIANGULO: lógico
Fim ref. 1
O algoritmo completo fica:
Algoritmo triangulo
Declare a,b,c:numérico
E_TRIANGULO: lógico
Leia (a,b,c)
Se a < b+c então
Se b< a+c então
Se c < b+a então
E_TRIANGULO
←
verdadeiro
Senão
E_TRIANGULO ← falso
Fim se
Senão
E_TRIANGULO ← falso
Fim se
Senão
E_TRIANGULO ← falso
Fim se
Se E_TRIANGULO então
Imprima(“Os
valores
fornecidos
formam
um
triângulo”)
Senão
Imprima(“Os
valores
fornecidos não formam um
triângulo”)
Fim se
Fim algoritmo
19
Uma outra alternativa para o algoritmo do triângulo seria inicializar a variável
E_TRIANGULO com o valor FALSO, antes das verificações. Neste caso os testes
precisariam apenas alterar o valor da variável E_TRIANGULO quando os valores puderem
formar um triângulo. Caso contrário, a variável já estaria com o valor FALSO. O algoritmo
ficaria assim:
O algoritmo completo ficaria assim:
Algoritmo triangulo
Declare a,b,c:numérico
E_TRIANGULO: lógico
Leia (a,b,c)
E_TRIANGULO ← FALSO {inicialização da variável}
Se a < b+c então
Se b< a+c então
Se c < b+a então
E_TRIANGULO ← verdadeiro
Fim se
Fim se
Fim se
Se E_TRIANGULO então
Imprima(“Os valores fornecidos formam um triângulo”)
Senão
Imprima(“Os valores fornecidos não formam um triângulo”)
Fim se
Fim algoritmo
3.2.7 Expressões Aritméticas
Expressões aritméticas podem ser formadas por operadores aritméticas (*, /, + e -) e
valores numéricos. Os valores numéricos podem ser valores absolutos e variáveis
numéricas. Da mesma forma que na matemática, existe uma ordem de precedência entre os
operadores. Ou seja, em uma expressão os operadores ‘*’ e ‘/’ são executados primeiro,
seguindo-se os operadores + e -.
Para mudar essa regra é necessário usar os parênteses.
Ex:
2 +3*3 = 11
(2+3)*3 = 15
(( 2+3)*4)+1
Veja que podemos usar parênteses dentro de parênteses
20
3.2.8 Expressões Lógicas
Assim como podemos formar expressões aritméticas como valores numéricos e operadores
aritméticos (+, - , *, /), podemos formar expressões lógicas com valores lógicos e
operadores lógicos. As expressões lógicas são usadas em programas em qualquer comando
que exija um teste de uma condição, como no caso do
Se condição então... senão .... Fim se.
A condição sempre será uma expressão lógica. Outros comandos que utilizam testes de
condição, como o repita enquanto condição ... fim repita, também exigem que a condição
seja uma expressão lógica.
Os operadores lógicos são os três operadores básicos definidos na álgebra booleana:
-
Operador OU
Operador E
Operador Não
Os operadores OU e E são chamados operadores binários, pois envolvem dois operandos. O
operador Não é unário, envolvendo apenas um operando.
Tabelas verdade de operadores
Cada operador lógico possui a sua tabela verdade, que representa todo seu universo de
possibilidades dados os possíveis valores dos operandos. A tabela verdade define a lógica
de funcionamento dos operadores.
Tabela verdade do operador OU
Sejam A e B valores lógicos. Temos os possíveis valores para A OU B
A
B
A OU B
FALSO
FALSO
VERDADEIRO
VERDADEIRO
FALSO
VERDADEIRO
FALSO
VERDADEIRO
FALSO
VERDADEIRO
VERDADEIRO
VERDADEIRO
Tabela verdade do operador E.
Sejam A e B valores lógicos. Temos os possíveis valores para A E B
A
B
AEB
FALSO
FALSO
VERDADEIRO
VERDADEIRO
FALSO
VERDADEIRO
FALSO
VERDADEIRO
FALSO
FALSO
FALSO
VERDADEIRO
Tabela verdade do operador E.
Sejam A um valor lógico. Temos os possíveis valores para NÂO( A )
21
A
NÂO(A)
FALSO
VERDADEIRO
VERDADEIRO
FALSO
Os valores lógicos podem ser variáveis lógicas, valores lógicos absolutos
(VERDADEIRO ou FALSO) e expressões cujo resultado seja um valor lógico. Essas
expressões são formadas a partir dos operadores matemáticos de comparação:
> maior que
< menor que
= igual a
<> diferente de
>= maior ou igual a
<= menor ou igual a
Ex: idade > 20
Nome = “Maria”
Media >= (nota1+nota2)/2
As expressões lógicas podem ser compostas de combinações dessas expressões unidas por
operadores lógicos.
Ex. (idade > 20) E (nome = “Maria” ) E NÂO(Media >= (nota1+nota2)/2)
Regras de precedência
Como pode ser visto, podemos formar expressões envolvendo operadores aritméticos, de
comparação e lógicos. Portanto é necessário definirmos regras de precedência para os
possíveis operadores. As regras de precedência definem que executa primeiro.
Operador
Primeiro lugar
Segundo lugar
Terceiro lugar
Quarto lugar
Precedência
Operador lógico Não
Operadores aritméticos *,/ e operador lógico E
Operadores aritméticos + -, operador lógico OU
Operadores de comparação
Portanto para escrevermos expressões lógicas corretas é necessário utilizar parênteses para
mudarmos a prioridade da execução. Por exemplo, sejam X<Y Variáveis numéricas e
TESTE uma variável lógica:
Errada
NÃO x>y+1
NÂO(Y + 2=7 E X >3)
X + Y < 10 OU X<10 E TESTE
Possibilidade correta
NÃO (x>y+1)
NÂO((Y + 2 = 7 ) E (X >3))
((X + Y < 10) OU (X<10) ) E TESTE
22
3.2.9 Exemplo
Construir um algoritmo para o problema abaixo:
Dados três valores (a,b,c) verificar se os mesmos podem formar um triângulo. Em caso
afirmativo, verificar se o triângulo será equilátero, isósceles ou escaleno.
Passo 1- Solução descritiva
No exemplo anterior a solução descritiva foi:
Para solucionar esse problema precisamos saber que em um triângulo, um lado nunca é
maior ou igual a soma dos outros. Sabendo disso basta obter os valores e comparar cada
valor com a soma dos outros verificando se a condição do problema é atendida.
Neste, além disso temos que determinar o tipo do triângulo. Sabemos que um triângulo é:
- Equilátero se os seus lados são todos iguais,
- Escaleno se todos os lados são diferentes
- Isósceles se pelo menos dois de seus lados são iguais
Portanto, depois de verificada a condição de existência do triângulo, devemos comparar
seus lados verificando qual das condições acima é verdadeira, determinando assim o tipo do
triângulo.
Passo2 – Algoritmo
Algoritmo triângulo
1. Declarar variáveis
2. Obter os três valores (a,b e c) candidatos a lados do triângulo
3. Verificar se os mesmos podem formar um triângulo
4. Verificar o tipo do triângulo
5. Informar o resultado
Fim algoritmo
Ref. 2 { Obter os três valores (a,b,c), candidatos a lados de um triângulo}
Leia (a,b,c)
Fim Ref. 2
Ref. 3 { Verificar se os mesmos podem formar um triângulo}
E_TRIANGULO←falso
Se a < b+c então
Se b< a+c então
Se c < b+a então
E_TRIANGULO ← verdadeiro
Fim se
Fim se
Fim se
Fim ref. 3
23
Ref. 4 {verificar o tipo do triângulo}
Se E_TRIANGULO então
Se ((a = b) E (a=c)) então
Tipo ← “Equilátero”
Senão
Se ((a <> b) E (a<>c) E (b <> c)) então
Tipo ← “Escaleno”
Senão
Tipo ← “Isósceles”
Fim se
Fim Se
Fim Se
Fim Ref. 4
Ref. 5 {informar o resultado}
Se E_TRIANGULO= verdadeiro então
Imprima(“Os valores fornecidos formam um triângulo”)
Imprima(“O triângulo é ”, tipo)
Senão
Imprima(“Os valores fornecidos não formam um triângulo”)
Fim se
Fim ref. 5
Ref. 1 {declarar variáveis}
Declare a,b,c:numérico
E_TRIANGULO: lógico
Tipo: literal
Fim ref. 1
Algoritmo Completo
Algoritmo triângulo
Declare a,b,c: numérico
E_TRIANGULO: lógico
Tipo: literal
Leia (a,b,c)
E_TRIANGULO←falso
Se a < b+c então
Se b< a+c então
Se c < b+a então
E_TRIANGULO ← verdadeiro
Fim se
Fim se
24
Fim se
Se E_TRIANGULO então
Se ((a = b) E (a=c)) então
Tipo ← “Equilátero”
Senão
Se ((a <> b) E (a<>c) E (b <> c)) então
Tipo ← “Escaleno”
Senão
Tipo ← “Isósceles”
Fim se
Fim Se
Fim Se
Se E_TRIANGULO então
Imprima(“Os valores fornecidos formam um triângulo”)
Imprima(“O triângulo é ”, tipo)
Senão
Imprima(“Os valores fornecidos não formam um triângulo”)
Fim se
Fim algoritmo
3.2.10 Exercícios de Fixação
Para os problemas abaixo construa algoritmos usando a técnica de refinamentos sucessivos
e depois monte o algoritmo completo. Se possível tente encontrar mais de uma solução para
cada problema.
1. Ler 4 números verificar se existem dois números iguais entre os quatro.
2. Ler 3 números (A,B,C) trocar os valores colocando o conteúdo de B em A, de C em B e
de A em C.
3. Ler 4 números, calcular a média aritmética dos mesmos e imprimir dentre os 4 , os
números acima da média calculada.
4. Ler 3 números (A,B,C) e calcular o valor da raiz quadrada do produto entre a média de
A e B e o quadrado de C.
5. Ler um número complexo na forma retangular e converter para forma polar.
6. Ler 4 nomes e imprimir os mesmos em ordem alfabética
7. Ler o valor de um empréstimo e imprimir o valor corrigido com juros de 7%.
8. Ler quatro dígitos binários (números que poderão ser 0 ou 1) e imprimir o valor
decimal correspondente ao número binários formado pelos 4 dígitos.
9. Ler um número inteiro de 4 dígitos e escrever a sua representação de acordo com o
exemplo abaixo:
Para o número 2341 deverá ser impresso 2 milhares, 3 centenas, 4 dezenas e 1
unidade.
10. Ler um valor total em segundos e transformar em horas, minutos e segundos.
Ex. 11030 seg. = 3h 3min 50 seg.
11. Em uma central de PABX de uma empresa, os ramais são formados por números de 3
dígitos, sendo que quando o dígito das centenas é igual a:
25
-
7 : o PABX irá transferir para o atendimento automático.
8: o PABX irá transferir para o PABX de uma Filial da empresa
9: o PABX irá estabelecer a conexão direta com o ramal
Quando um número iniciando em 9 é discado, Se o dígito das dezenas for:
- 1 : o PABX irá transferir para a central da Diretoria
- 2 : o PABX irá transferir para a central de Marketing
- 3: o PABX irá transferir para a central de Atendimento ao Cliente
- 4: o PABX irá transferir para a central da Produção
Construa um algoritmo que simule o funcionamento desse sistema de PABX. O Algoritmo
deverá ler um número inteiro de três dígitos e imprimir mensagens informando destino da
ligação correspondente ao número discado, conforme descrito acima. Por exemplo, quando
a ligação for para o atendimento automático, imprimir a mensagem: LIGAÇÃO PARA O
ATENDIMENTO AUTOMÁTICO. Quando a ligação for para a Produção, imprimir a
mensagem: ENCAMINHADO PARA A CENTRAL DE PRODUÇÃO.
12. Construa um algoritmo que leia 4 números e imprima os 4 em ordem decrescente.
13. Construa um algoritmo que leia 3 números inteiros, descubra e informe se a soma do
menor com o maior é um número par.
14. Construa um algoritmo que leia 2 números e um literal que deverá ser um dos símbolos
das operações aritméticas: “+” , “-“, “*” ou “/” e:
- Verifique se o símbolo lido está entre os símbolos possíveis. Em caso afirmativo
informar a operação que será realizada e o resultado. Caso contrário imprimir a
mensagem: “Erro. Operação inválida”
Exemplos:
a) Valores lidos 3 4 e “+”
Saída do algoritmo: Operação: Soma
Resultado: 7
b) Valores lidos 18 9 e “s”
Saída do algoritmo: Erro Operação inválida
15. Em um jogo, uma bola principal é atirada do ponto de saída e para a uma distância de X
metros da saída. Três competidores lançam suas bolas a exatamente A, B e C metros
da saída na mesma direção da bola principal. Ganhará a partida o competidor cuja a
bola estiver mais próxima da bola principal. Em caso de empate e as bolas empatadas
estiverem a distâncias diferentes da saída, ganha a bola que estiver mais distante da
saída. Veja o exemplo abaixo:
C= 8 metros
X=10 metros
Bola principal
A=12 metros
B=14 metros
Saída
Ganhador: Competidor A
26
Nesse caso, ganhou o competidor A, embora as distâncias de A e C para a bola principal
sejam iguais.
Construa um algoritmo que leia as distâncias X,A,B E C , determine e informe o vencedor.
16. Construa um algoritmo que leia o nome e a cidade de 3 pessoas e imprima o nome e a
cidade em ordem alfabética tomando por base a cidade.
Ex. Valores lidos:
Nome1 = Maria
Cidade1 = Vitória
Nome2 = Fernanda
Cidade2 = Aracruz
Nome3 = Pedro da Silva
Cidade3 = São Paulo
Saída:
Fernanda
Aracruz
Pedro da Silva
São Paulo
Maria
Vitória
17. Sabendo que o dia 1 de Janeiro foi um Segunda feira e que os dias da semana se
repetem de 7 em sete dias, podemos encontrar o dia da semana de qualquer outro dia de
janeiro. Como podemos calcular? Usando o operador resto (%). Construa então um
algoritmo que leia um dia do mês de janeiro de 2001 e informe qual o dia da semana.
18. A empresa de telefonia TELECRUZ possui o seguinte sistema de tarifação com
relação aos horários de início das ligações:
- De 0h00 até as 5h00: 0,1 o segundo
- De 5h00 até as 8h00: 0,2 o segundo
- Demais horários : 0,8 o segundo
Construa um algoritmo que leia o total de minutos de uma ligação, a sua hora de início
e determine o valor total da ligação.
27
4
Construção de Programas em Linguagem C
Este capítulo tem os seguintes objetivos:
- fazer uma introdução a linguagem de Programação C cobrindo as estruturas de
programação já estudadas.
- Desenvolver maiores habilidades com relação a estratégias para resolução de
Problemas através de programas computacionais.
A construção de algoritmos vista no capítulo anterior, dependeu exclusivamente do uso dos
seguintes elementos s e estruturas:
-
Estrutura do algoritmo
Valores numéricos, literais e lógicos
Operadores aritméticos
Operadores de comparação
Operadores lógicos
Declaração de Variáveis numéricas, literais e lógicas
Comando de atribuição
Comando de entrada de dados
Comando de saídas de dados
Comando Condicional Simples
Comando Condicional composto
Expressões aritméticas
Expressões lógicas
Assim como outras linguagens de programação, a linguagem C possui estruturas e
elementos equivalentes aos já estudados em algoritmos, de modo que a implementação de
programas nessa linguagem pode ser feita através da transposição dos elementos e
estruturas já conhecidas para as formas da linguagem obedecendo a sua simbologia,
sintaxe e semântica.
4.1
Estratégias para resolução de Problemas usando C
Em se tratando de programação, a expressão “Se você não souber, jamais irá aprender.” é
uma verdade absoluta. É muito comum ouvirmos queixas do tipo “nunca conseguirei
escrever um programa”, ou “só sendo louco ou gênio para descobrir a solução”. Estas
expressões geralmente são ditas por estudantes que desconhecem o fato de que cada
elemento da linguagem usada para programar (comandos, funções) não existe sozinho, mas
somente combinados a outros elementos.
Desta forma a orientação maior para a compreensão geral do programa, deve vir antes da
análise detalhada de cada comando ou função.
De fato, para construirmos nossos primeiros algoritmos, necessitamos, até o momento, de
alguns comandos fundamentais e outros elementos que devem ser de conhecimento do
estudante. Mas este, não pode cometer o erro de tentar enxergar os algoritmos de forma
fragmentada, pois os elementos de um programa só fazem sentido se vistos como um todo.
28
Falar em estratégias de resolução de problemas sem formalismos matemáticos não é uma
tarefa simples e não há nada além do uso da lógica, do raciocínio abstrato e de estratégias
como dividir para conquistar e abordagem top-down para facilitar o nosso trabalho.
Acredita-se que a facilidade para resolução pode vir da experiência em resolver problemas
e do desenvolvimento do senso crítico com relação aos mesmos. Sendo assim, nossa
abordagem nesse capítulo será
a apresentação dessas estratégias através do
desenvolvimento de exemplos e a vinculação do raciocínio lógico e abstrato com o dia-adia do estudante.
Todos os elementos utilizados até o momento foram construídos em linguagem algorítmica.
De agora em diante nossos exemplos serão construídos em linguagem algorítmica e
posteriormente transformados para C. Para tanto, na sessão 2 serão introduzidos os
elementos da linguagem necessários.
4.2
Introdução a Linguagem C
C foi Desenvolvida nos laboratórios Bell na década de 70, a partir da Linguagem B, por
Brian Kernighan e Dennis M. Ritchie.
Com a linguagem C podemos construir programas organizados e concisos (como o Pascal),
ocupando pouco espaço de memória e com alta velocidade de execução (como o
Assembler).
Todavia, devido a grande flexibilidade da linguagem, também poderemos escrever
programas desorganizados e difíceis de serem compreendidos.
Uma análise superficial dos programas escritos em C e Clipper, nos permite perceber que a
linguagem C supera em muito em dificuldade o programa análogo em Clipper. Ora, então
porque não desenvolvermos programas somente em Clipper?
Há inúmeras razões para a escolha da linguagem C como a predileta para os
desenvolvedores “profissionais”. Algumas delas estão descritas em suas características:
- Portabilidade entre máquinas e sistemas operacionais.
- Dados compostos em forma estruturada.
- Programas Estruturados.
- Total interação com o Sistema Operacional.
- Código compacto e rápido, quando comparado ao código de outras linguagem de
complexidade análoga.
Como nosso objetivo é construir programas, não nos ateremos em comparações, fatos
históricos e detalhes não importantes no momento.
4.3 Guia Rápido
A seguir temos uma tabela que apresenta de forma sucinta
construções algorítmicas utilizadas até o momento.
a tradução para C das
29
Estrutura
#include <stdio.h>
#include <conio.h>
#include <stdlib.h>
#include <math.h>
#define max 100
Linguagem C - Guia Rápido de Consulta - Primeira parte Algoritmo
Algoritmo XXXX
.....
Fim Algoritmo
void main()
{
....}
Funcionamento/Descrição
Todo programa em C tem como corpo principal a função main(). Os
comando devem ser colocados entre as chaves { ... }
A diretiva #include permite a inclusão de funções das bibliotecas
referenciadas. No caso stdio.h e conio.h.
Em nossos programas iremos sempre inserir essas bibliotecas e
poderemos necessitar de outras.
A diretiva #define permite a definição de constantes ou macros.
Declaração de variáveis. As listas de identificadores são os nomes das
Declare
tipo_1
lista
de lista de identificadores 1: variáveis que serão declaradas. Cada lista será do tipo especificado
anteriormente
identificadores 1;
tipo1;
tipo_2
lista
de lista de identificadores 2: Ex: int x,y,z;
int é o tipo inteiro
identificadores 2;
tipo2;
...
...
tipo_n
lista
de lista de identificadores n:
tipo n;
identificadores n;
Inicio... fim
Delimitam respectivamente o início e o fim de um bloco de comandos.
{ ...
}
Variável = Expressão;
Comando de Atribuição. O valor calculado na expressão é armazenado
Variável ←Expressão;
na variável. A variável e a expressão devem ser do mesmo tipo.
if (expressão lógica) Se expressão lógica então
Estrutura de decisão simples: O comando1 só será executado caso o
Comando1;
Comando1
valor da expressão lógica seja verdadeiro. O ";" só vem depois do
comando1. Os parêntesis não podem ser esquecidos.
if (expressão lógica) {
Se expressão lógica então
Estrutura de decisão simples. Nesse caso, todo o bloco de comandos
Comando1;
Comando1
delimitado pelo { ... } só será executado se a expressão lógica for
Comando2;
Comando2
verdadeira.
...
...
Comando n;
Comando n
}
Fim se
if (expressão lógica)
Se expressão lógica então
Estrutura de decisão composta. Nesse caso o comando1 será executado
Comando1;
Comando1
se a expressão lógica for verdadeira e o comando 2 só será executado se
a expressão lógica for falsa. Repare que o último comando antes do else
else
Senão
Comando2;
Comando2;
não possui ";"
if (expressão lógica)
{
Comando1;
Comando2;
...
Comando n;
}
else
Comando n+1;
Se expressão lógica então
Comando1;
Comando2;
...
Comando n;
Fim se
Senão
Comando n+1;
-
-
Int
Float
Char
Estrutura condicional composta. Se o valor da expressão lógica for
verdadeiro, o bloco de comandos entre o { e } será executado. Caso a
expressão lógica seja falsa, apenas o comando n+1 será executado.
Inteiro
São os tipos de dados simples. Variáveis do tipo:
- int: podem armazenar valores inteiros com ou sem sinal.
Real
Literal (caracter ou - float: podem armazenar valores reais, com parte inteira e parte
fracionária, separadas por um "."
cadeia de caracteres)
- char: pode armazenar um único caracter ou uma cadeia de
caracteres existentes na tabela ASCII.
30
pow(x,y), sqrt(x)
xy e raiz quadrada de (x)
printf(“string
de Imprima(..)
formatação”, “valores”)
scanf(“String
de Leia(..)
formatação”, “endereço
das
variáveis
de
armazenamento”)
getch()
Função usada para obter
apenas um caracter da
entrada
Não
!
E
&&
OU
||
=
==
<>
!=
<, >, >= e <=
<, >, >= e <=
/* .... */ ou
// ....
As cadeias de caracteres devem vir sempre representadas entre
apóstrofes. Ex. 'CASA', 'maria', ' X11', 'TEMPO', etc.
Para se declarar uma vairiável char para conter uma cadeia de caracter,
devemos determinar o tamanho da mesma. Ex:
char palavra[20]; declara uma variável literal que pode conter até 20
caracteres.
Calcula respectivamente x elevado y e raiz quadrada de x.
Devemos incluir a biblioteca math.h : #include<math.h>
função usada para saída de dados ( impressão)
Função usada para entrada de dados (leitura)
Muitas vezes usamos essa função para que o programa pare e possamos
ver o resultado na tela
Operador lógico de negação.
Conectivo lógico E
Conectivo lógico OU
Operador de comparação igual
Operador de comparação diferente (Não igual)
Respectivamente: menor que, maior que, maior ou igual que, menor ou
igual que
Comentários:
Quando se que colocar um comentário dentro de um programa em se
usam-se os delimitadores /* no início do comentário e */ no final do
comentário.
Uma outra alternativa é comentar uma linha usando as barras duplas: //
Ex:
/* Primeiro programa em C */
Ou
// Primeiro programa em C
%
+.*,-,/
while (expressão lógica) {
Comando1;
Comando2;
...
Comando n;
}
%
+.*,-,/
enquanto (expressão lógica)
Comando1;
Comando2
...
fim enquanto
do{
Comando1;
Comando2;
...
Comando n;
}
while
(expressão
lógica);
Faça
Comando1;
Comando2
...
enquanto (expressão lógica)
Operador Resto
Operadores aritméticos
Comando para implementar um laço de repetição. Enquanto o valor da
expressão lógica for verdadeiro o conjunto de comandos entre as chaves
será repetido sucessivamente. C1, C2..Cn C1C2 .. Cn ...
Observação: é importante que quando a execução atingir o while as
variáveis utilizadas na expressão lógica tenha valores válidos. Em geral,
a lista de comandos dentro do bloco deve garantir que em algum
momento a expressão lógica fique falsa para que o programa não entre
em loop infinito.
Comando para implementar um laço de repetição. Enquanto o valor da
expressão lógica for verdadeiro o conjunto de comandos entre as chaves
será repetido sucessivamente. C1, C2..Cn C1C2 .. Cn ... Neste caso os
comando do bloco são executados pelo menos uma vez. Somente após o
final da execução quando o while é executado é que a expressão lógica
é avaliada.
31
for(C1; C2; C2){
Comando1;
Comando2;
...
Comando n;
para i Å x ate y faça
Comando1;
Comando2;
...
Comando n;
Fim para
}
break
continue
Switch
(variável
avaliada) {
case v1: Comando 1;
Comando 2;
...
Comando k;
break;
case v2: Comando k+1
Comando k+2
..
Comando m;
break;
case v3: Comando m+1;
Comando m+2
..
Comando n;
break;
...
default: ...
}
Caso(variável avaliada)
valor 1: Comando 1;
Comando 2;
...
Comando k;
valor 2: Comando k+1
Comando k+2
..
Comando m;
Valor 3: Comando m+1;
Comando m+2
..
Comando n;
...
Fim caso
O Comando for é um comando de repetição que contém dentro dos
parentes todas os comandos para que seja iniciado e interrompido. Ou
seja, o for não depende dos comando de dentro do bloco para ser
interrompido. O for contem 3 partes separadas por ponto e vírgula
conforme descrito:
C1: é um comando executado uma única vez, antes do laço ser iniciado.
Geralmente é usado para inicializar uma variável de controle.
C2: é a expressão lógica avaliada para continuar ou interromper o laço.
Se C2 é verdadeira, o a repetição continua. Se C2 é falsa a repetição é
interrompida. Ou seja, o for termina quando C2 é falsa.
C3: é um comando executado toda vez que uma repetição é iniciada.
Com este comando, a variável utilizada na expressão lógica pode ser
atualizada.
Um comando break permite a saída antecipada de um laço while, do, for
e de um comando swtich. Um comando break faz com que um laço
(while,do, for) ou um swtich mais interno seja terminado
antecipadamente.
Inicia a próxima interação do laço mais interno.
Comando de seleção ou chaveamento. O Switch permite que,
dependendo do valor de uma variável, um bloco específico de
comandos sejam executados. A variável a ser avalida aparece entre os
parentes depois da palavra chave switch. Esta variável deve ser um
escalar. Ou seja, uma variável que guarde valores contáveis, como um
inteiro ou um caractere (um único caracter). Strings ou números reais
não podem ser usados. Na implementação do switch em C quando o
valor contido na variável avaliada é encontrado junto a um case, todos
os comandos existentes após este case até o final do switch serão
executados. Por isso, sempre que se queira que apenas o bloco
específico do case seja executado, deve-se colocar um break antes do
próximo case.
Os comandos da opção default são executados caso nenhum case seja
satisfeito.
A função scanf
scanf()
Uma das mais importantes e poderosas instruções, servirá basicamente para promover
leitura de dados via teclado.
Sua forma geral será: scanf(“string de controle”, lista de argumentos);
Posteriormente ao vermos sua sintaxe completa, abordaremos os recursos mais poderosos
da <string de controle>, no momento bastará saber que:
%c - leitura de caracter (char)
%d - leitura de números inteiros (int)
%f - leitura de números reais (float)
32
%s - leitura de cadeia de caracteres (char)
A lista de argumentos deve conter exatamente o mesmo número de argumentos quantos
forem os códigos de formatação na <string de controle>. Se este não for o caso, diversos
problemas poderão ocorrer - incluindo até mesmo a queda do sistema.
A função printf()
printf()
printf() servirá basicamente para a apresentação de dados no monitor.
Sua forma geral será: printf(“string de controle”, lista de argumentos);
Necessariamente você precisará ter tantos argumentos quantos forem os comandos de
formatação na “string de controle”. Se isto não ocorrer, a tela poderá exibir sujeira ou não
exibirá qualquer dado.
Os caracteres a serem utilizados pelo printf() em sua <string de controle>, no momento
serão os mesmos de scanf().
4.4
Exemplos Introdutórios
Exemplo 1 – Imprimindo uma constante literal
Como nosso primeiro exemplo, iremos construir um programa para imprimir a mensagem
Olá Pessoal. Seguem algoritmo e o programa equivalente:
O programa em C equivalente será:
Algoritmo ola
Imprima(“Olá pessoal”)
#include
#include
#inlcude
#include
<stdio.h>
<conio.h>
<stdlib.h>
<math.h>
Fim Algoritmo
void main()
{
printf(“Olá Pessoal”);
}
Para se tornar uma linguagem portável, C possui poucas instruções próprias, sendo que
nenhuma operação de entrada e saída (que dependem do Sistema Operacional) é feita
através de comandos próprios da linguagem, mas através de funções.
As bibliotecas de programação são necessárias para permitir o acesso funções. Nesse
primeiro exemplo temos que usar a função printf() que está na biblioteca stdio. Assim foi
preciso adicionar a diretiva
#include <stdio.h>
que inclui o arquivo header (cabeçalho) da biblioteca stdio no nosso programa.
33
Asa outras inclusões que foram feitas não são absolutamente necessárias nesse programa.
No entanto, para iniciantes, incluir sempre as bibliotecas stdio, conio, stdlib e math é uma
boa prática.
Todo e qualquer programa em C possui a função main(). Esta função é onde o
processamento irá iniciar. Dentro da função, ou seja, entre as chaves ({ e } ), poderemos
declarar variáveis e inserir os comandos que necessitamos para resolver o problema em
questão. O abre chaves, indica o início da função main(). O fecha chaves indica o final da
função main() (nesse exemplo!)
O nome void antes de main(), indica que essa função não deverá retornar nenhum valor.
Alguns aspectos da linguagem, como esse, serão melhor entendidos mais a frente, no
decorrer do aprendizado e não cabem no momento.
Como pode ser observado, a função printf() é a substituta do comando Imprima(). A
constante literal Olá pessoal!, está entre “ ”, e é assim que tem de ser sempre que queremos
imprimir literais.
Palavras reservadas e Regras básicas de Sintaxe
C diferencia letras maiúsculas de minúsculas. Todos os comandos e funções e palavras
reservadas de C devem ser escritos com letras minúsculas. As palavras reservadas são o
conjunto de palavras utilizadas pela linguagem e que não podem ser usadas para outros
fins, como nome de variáveis, por exemplo.
Quadro de palavras reservadas do C Padrão.
auto
break
case
char
continue
default
do
Double
Else
Entry
Extern
Float
For
Goto
If
Int
Long
Register
Return
sizeof
Short
static
struct
switch
typedef
union
unsigned
while
Um outro detalhe de C é que todo fim de comando deve ser marcado com um ponto-evírgula (;). Para o estudante não cometer erros com relação a esta regra, o mesmo deve
entender o que significa fim de comando.
Em um algoritmo por exemplo o comando
(a) X←10
termina após o 10.
Já o comando
(b)
Se (X>Y) então
(c)
X←5
(d)
Fim se
Não termina após o então pois a ação do comando se irá agir sobre o próximo comando.
Dessa forma o comando em C para o exemplo (a), leva o ;
34
X=10;
Já o comando (b) não leva o ponto e vírgula pois só terminará após o comando que está sob
a condição do if
if (X>Y)
X=5;
O término do if é no ponto e vírgula.
Exemplo 2 – Imprimindo números
Seja o seguinte algoritmo:
O programa em C equivalente será:
Algoritmo inteiros
Imprima (10)
Imprima (10.75)
Imprima(10/3)
Fim Algoritmo
#include
#include
#inlcude
#include
<stdio.h>
<conio.h>
<stdlib.h>
<math.h>
void main()
{
printf(“%d\n”,10);
printf(“%f\n”,10.75);
printf(“%f”,10.0/3.0);
}
Conforme citado, a função printf necessita da string de controle para determinar o tipo e a
posição dos argumentos a serem impressos.
Assim, para imprimir um inteiro usamos o %d na string de controle. Para imprimir um
número real, usamos o %f. O \n usado na string de controle para pular uma linha. Se não
tivesse sido colocado, os números iriam ser impressos todos ligados.
Por motivos de espaço, a partir de agora iremos omitir as diretivas #include nos programas,
mas lembre-se que elas são quase sempre necessárias.
Exemplo 3 – Declarando variáveis
Algoritmo variaveis
Declare x,yz,
S,r,t: numérico
A,c: literal
x←10
y←5
z←x+y
r←5.5
t←10
S←(r+t)/2
A←”z é maior que S”
c←”!”
Imprima(“Valor de S: ”,S)
Imprima(“Valor de z: ”,z)
Imprima(A,c)
Fim algoritmo
A saída desse algoritmo será exatamente:
Valor de S: 7.75
Valor de z: 15
z é maior que S!
O programa equivalente em C ficará:
void main(){
int x,y,z,t;
float r,S;
char A[20],c;
x=10;
y=5;
z=x+y;
35
r=5.5;
t=10;
S=(r+t)/2.0;
strcpy(A, ”z é maior que S”);
c=’!’;
printf(“Valor de S: %f\n”,S)
printf(“Valor de z: %d\n ”,z)
printf(“%s%c”,A,c)
}
Detalhes importantes no Exemplo 3
- O tipo numérico pode ser de dois tipos: Inteiros ou reais. No caso de inteiro, a variável
deve ser declarada como int. No caso de reais, a variável deve ser declarada como float. O
tipo da variável vai depender do uso que o programador pretende dar a ela. No exemplo, as
variáveis x, y, z e t foram usadas para armazenarem valores inteiros. Sendo assim foi usado
o tipo int para declara-las. Já as variáveis r e S foram usadas para armazenar números
contendo uma parte decimal, reais e não inteiros. Assim o tipo das variáveis deve ser float.
-
Os tipos int e float podem ser misturados em expressões aritméticas, sendo que o
resultado da expressão será um valor do tipo float.
- O separador de casas decimais em C é o ponto (.)
- A variável S declarada em maiúsculo, deve ser sempre referenciada no programa em
maiúsculo.
- A variável A, foi declarada como char de 20 posições. Dessa forma essa variável pode
conter valores literais (palavras, por exemplo) de até 20 caracteres.
- O número de posições é definido em função do uso que se dará para a variável. Se
uma variável char for usada para armazenar palavras cujo tamanho varia entre 10 e 150
letras, a mesma deve ser declarada com tamanho 150.:
char Nomes[150];
- A variável c irá armazenar apenas um caracter.
- A atribuição de valores a variáveis do tipo char com apenas 1 caracter é feita
utilizando-se as aspas simples para especificar o valor:
c=’a’;
No caso de cadeias de caracteres, como a variável A do ex. 3, a atribuição de valores
deve ser feita através do uso de uma função chamada strcpy que deve Ter dois argumentos:
a cadeia origem e a cadeia destino.
Suponha o exemplo: strcpy(s1,s2), nesse caso, s1 é a cadeia destino e s2 a cadeia
origem. Isso significa que o conteúdo de s2 (origem) será copiado para a cadeia s2
(destino).
No ex. 3, a cadeia ”z é maior que S” foi copiada para a “cadeia” variável A:
strcpy(A,”z é maior que S”);
Observe que o contrário (copiar A para ”z é maior que S”) não seria possível, pois só
podemos ter como cadeia destino, uma variável e nunca uma constante.
- Na função printf, para imprimir um float, devemos usar %f, para imprimir uma cadeia de
caracteres devemos usar o %s e para imprimir um caracter usamos o %c. Já o \n ca cadeia
de controle é usado para saltar uma linha.
36
-
Quando realizamos operações aritméticas envolvendo apenas operandos do tipo inteiro,
o resultado da operação será também um tipo inteiro. Por exemplo, na chamada da
função: printf(“%f”,10/3), a expressão 10/3 resultará na divisão inteira entre 10 e 3 cujo
resultado é 3. Para obtermos a divisão real entre os números 10 e três temos que usar
pelo menos 1 operando real. Assim a função printf poderá ser escrita da seguinte forma:
printf(“%f”, 10.0/3) ou printf(“%f”, 10/3.0) ou printf(“%f”, 10.0/3.0), ou seja, basta que
tenhamos pelo menos 1 operando real para que o resultado da expressão seja um float.
Exemplo 4 – Definido constantes, Lendo variáveis, construindo expressões lógicas e
testando condições.
Nesse exemplo vamos verificar mais alguns pontos importantes da linguagem C. Para isso
utilizaremos o exemplo triângulo da sessão 3.2.6. O algoritmo é o seguinte:
Algoritmo triangulo
Declare a,b,c:numérico
E_TRIANGULO: lógico
Leia (a,b,c)
E_triangulo←falso
Se a < b+c então
Se b< a+c então
Se c < b+a então
E_TRIANGULO ← verdadeiro
Se E_TRIANGULO então
Imprima(“Os valores fornecidos formam um triângulo”)
Senão
Imprima(“Os valores fornecidos não formam um triângulo”)
Fim se
Fim algoritmo
O programa em C equivalente será:
//Definição
das
constantes
#define mensagem1
"Os valores
fornecidos formam um triangulo\n"
#define mensagem2
"Os valores
fornecidos
não
formam
um
triangulo\n"
#define TRUE 1
#define FALSE 0
void main(){
int a,b,c;
int eh_triangulo;
printf("Entre com os lados
triangulo:");
scanf("%d%d%d",&a,&b,&c);
eh_triangulo=FALSE;
if ( a<b+c)
if (b<a+c)
if (c<a+b)
eh_triangulo=TRUE;
if (eh_triangulo)
printf(mensagem1);
else
printf(mensagem2);
getch();
}
do
Definindo Constantes em C
37
Em C podemos usar a diretiva #define para definir constantes e macros. Uma constante
nada mais é que um apelido para um valor. A sintaxe é a seguinte:
#define nome_da_constante
valor
Antes do programa ser compilado, todas as constantes são substituídas na íntegra pelos
seus valores. Constantes são usadas para facilitar a programação. Suponha por exemplo
que em um programa qualquer seja usado um valor fixo em centenas de expressões. Ou
seja, um mesmo valor é repetido centenas de vezes no programa. Caso esse valor sofra
alguma alteração, o programador vai ter de alterar o programa em centenas de linhas
diferentes. Se, ao invés disso o programador tivesse definido uma constantes para o referido
valor, bastaria que ele alterasse o valor na definição da constante, reduzindo assim o
esforços de manutenção do programa.
A diretiva define pode definir constantes de qualquer tipo.
Valores lógicos em C
C não possui um tipo lógico especial. Em C, o valor 0 é tido como falso em um teste
lógico e os demais valores são tidos como verdadeiro. Por exemplo, Se a é um valor inteiro,
posso realizar teste da seguinte maneira:
if (a)
printf(“A
é
diferente
zero”);
else
printf(“A vale zero”);
de
ou,
if (!a)
printf(“A vale
else
printf(“A
é
zero”);
zero”);
diferente
de
Para facilitar a escrita e leitura de programas, Podemos definir constantes que representem
os valores lógicos: TRUE para o 1 e FALSE para o 0. Essas definições funcionam
perfeitamente, pois:
− Quando uma expressão lógica é avaliada como Falsa, essa retorna o valor 0.
− Uma expressão avaliada como verdadeira, retorna o valor 1.
− Finalmente, quando um valor Falso (0) é negado, este é convertido para 1, e quando
um valor Verdadeiro é negado, este é convertido para 0.
Lendo Variáveis
Conforme visto no exemplo, a função scanf foi usada para leitura de variáveis em C. Essa
função pertence a biblioteca padrão stdio (standard input output library) e,
semelhantemente a função printf, possui uma string de controle para determinar o número e
os tipos dos parâmetros que serão lidos. Assim, para cada variável lida, devemos ter 1 “%”
seguido da letra que determina o tipo da variável na string de controle.
Além disso, em C variáveis simples, devem vir antecedidas do operador de endereço &.
Isso ocorre porque a função scanf necessita do endereço da variável para armazenar o valor
lido dentro dela. Já variáveis compostas, como cadeias de caracteres (char A[10], por
exemplo), não necessitam do operador & para serem lidas. Isso ocorre porque o seu nome
já é um endereço de memória.
38
Expressões Lógicas
Expressões lógicas em C podem ser construídas de modo semelhante as expressões em
algoritmos. Conforme visto anteriormente os operadores lógicos em C são:
&& - Operador E
|| - Operador OU
! - Operador NÃO
Os operadores de comparação em C são os seguintes:
== (dois iguais) - Igual
<
- Menor que
>
- Maior que
<=
- Menor ou igual
>=
- Maior ou igual
!=
- Diferente
Condicionais
A sintaxe do if
seguinte:
simples em C é a
if (condição)
Comando;
Ou
if (condição) {
Seqüência de comandos
}
Ou
if (condição) {
Seqüência de comandos
}
else {
Seqüência de Comandos
}
Ou
O uso de parênteses fechando a
condição por completo é sempre
necessário.
if (condição)
Comando;
else {
Seqüência de Comandos
}
A sintaxe do if
seguinte:
Ou
composto em C é a
if (condição)
Comando;
else
Comando;
if (condição) {
Seqüência de comandos
}
else
Comando;
Um outro detalhe é a necessidade do { } quando o if vai definir a execução de uma
seqüência de comandos. Quando apenas uma comando é submetido ao if, não há
necessidade do {}.
No Exemplo temos:
if(a<b+c) //1
if (b<a+c) //2
if (c<a+b) //3
39
eh_triangulo=TRUE;
O terceiro if está submetido ao segundo, que por sua vez está submetido a condição do
primeiro. Portanto, o comando he_triangulo=TRUE; só será executado caso as três
condições sejam verdadeiras. Essa estrutura pode também ser construída utilizando o
conectivo lógico &&:
if ( (a<b+c) && (b<a+c) && (c<a+b) )
eh_triangulo=TRUE;
4.5
Exercícios de Fixação
1. Construa os programas em C equivalentes a todos os problemas da sessão 3.2.10
2. Sabendo-se que primeiro de janeiro de 2001 foi uma Segunda-feira, Construa um
programa que leia uma data qualquer de 2001 (Mês e Dia), determine e imprima o dia
da semana equivalente.
3. Faça um programa leia valores de temperatura em Celsius e transforme para Fahrenheit
e vice-versa. O programa deve consultar o usuário sobre qual a opção de conversão
desejada. Nota. C = (5/9)(F-32).
4. Construa um programa que leia um binário de 8 dígitos e imprima o seu equivalente em
Decimais
5. Construa um programa que leia um número de 4 dígitos hexadecimais e imprima seu
equivalente em binário, octal e decimal.
6. Construa um programa que leia um número de no máximo 4 dígitos expresso em
algarismos romanos e imprima o seu equivalente em números arábicos (sistema
decimal).
40
5
Comandos de Repetição
Até o momento nossos programas e algoritmos caracterizam-se por serem uma seqüência
de instrução que sempre avançam para o fim do programa. Nessa sessão iremos introduzir
construções que permitem a construção de “laços de execução” utilizando comandos de
repetição, que como o próprio nome sugere, nos permitirá realizar tarefas repetitivas com
mais facilidade.
5.1
Motivação
Inúmeros são os casos em que necessitamos de inserir laços em programas. Na verdade, é
muito difícil encontrarmos problemas cuja solução não dependa de um laço no programa.
Isso se deve ao fato que o computador e usada para automatizar processos e automatizar
processos em muitos casos significa realizar tarefas repetitivas, geralmente implementadas
em programas através de comandos de repetição ou laços.
5.2
Estrutura básica de um laço
Um laço em um programa, assim como um condicional é uma estrutura que determina
fluxos de controle em programas.
A figura a seguir representa um laço genérico:
Início do laço
Comando 1;
Comando 2;
Comando 3;
Comando 4;
...
Comando n
Fim do Laço
Quando a execução do programa atinge os comandos do interior do laço, esta procede
normalmente até chegar no último comando (comando n, no exemplo). Quando chega no
último comando, o programa pode voltar para o comando1 e repetir a seqüência de
comandos novamente.
41
Quando um programa entra em um laço e permanece neste indefinidamente, sempre
repetindo a mesma seqüência de comandos dizemos que o mesmo entrou em um Looping
infinito.
Exemplo de aplicação:
Suponha por exemplo que queiramos calcular a soma dos números pares menores ou iguais
a 10 e maiores que 0.
A solução do problema deve partir de uma situação inicial, processar os dados de forma a
obter os valores necessários para obtermos a soma,
Situação inicial:
Numero inicial = 10
1. Processamento: Verificar se o número é par acumular o valor em uma variável
2. Obter o próximo número
3. verificar se o número é maior 0. Se for Voltar ao passo 1
5. Apresentar o conteúdo acumulado
Exercícios
Construa soluções semelhantes a anterior para encontrar:
- A soma dos número maiores que 3 e menores 31 que são divisíveis por 3
- A soma do 100 primeiros números inteiro maiores que 0 que são divisíveis por 8
Para que um laço não entre em um looping infinito, este deve Ter uma condição de
parada. Muitos erros em programa ocorrem pelo fato do programador não Ter definido
uma condição de parada correta. A condição de parada é uma expressão lógica associada ao
comando de repetição.
5.3
Sintaxe de comandos de repetição
5.3.1 Comando Enquanto
Este comando Possui a seguinte sintaxe:
Enquanto (expressão lógica)
Comando1
Comando2
Comando3
...
Comando n
Fim enquanto
Funcionamento
Quando um programa atinge o comando enquanto, ele verifica a validade da expressão
lógica. Se for válida (verdadeira) os comandos de dentro do enquanto passam a ser
executados. Quando o programa atinge o Fim Enquanto, ele volta ao enquanto e testa
42
novamente a validade da expressão. Esse processo se repete até que a expressão lógica
torne-se falsa.
Expressão Lógica – As expressões lógicas do comando enquanto devem obedecer as
condições de construção de expressões lógicas já vistas no capítulo anterior.
A seguir veremos alguns exemplos de algoritmos utilizando o comando Enquanto:
5.3.2 Exemplos
Exemplo 1 Construir um algoritmo que imprime os números inteiros de 1 a 10
Esse exercício pede para que seja impressa a série de números que inicia em 1 e termina
em 10. A alternativa viável é usar um comando de repetição para gerar essa série. A media
que os números são gerados, eles devem ser impressos. Ou seja a geração dos números e a
impressão ficam dentro do comando de repteição. Usaremos uma variável numérica para
gerarmos a série:
Algoritmo exemplo1
Declare i:numérico
i←1
enquanto (i<=10) // repete enquanto i<=10
imprima(i) // imprime os números
i←i+1
// gera o próximo número
fim enquanto
fim algoritmo
Exemplo 2 - Imprimir a soma dos números pares de 1 a 10
Nesse exemplo, temos que: Gerar os números de 1 a 10 e, dentro do repita, acumular os
alores gerados em outra variável de modo a obter a soma final. A impressão da soma tem
de ser feita fora do comando de repetição:
Algoritmo exemplo2
Declare i,soma:numérico
soma←0 //inicializo o valor da soma com 0
i←1 //inicializo i com 1
enquanto (i<=10) // repete enquanto i<=10
soma←soma+i // acumula os valores gerados na
// variável soma
i←i+1
// gera o próximo número
fim enquanto
imprima(soma)
fim algoritmo
Exemplo 3 - Algoritmo que imprime os divisores de 100 maiores que 0
Esse exemplo pede para que seja impresso a série de números que são divisores de 100,
iniciando de 1.
Solução: Utilizando um comando de repetição podemos gerar todos os número de 1 a 100 e
verificar se esses números são divisores de 100. Para verificar se são divisores de 100 basta
verificar se o resto da divisão entre 100 e o número gerado é 0:
Algoritmo exemplo3
Declare i: numérico
i←1 //inicializo i com 1
43
enquanto (i<=100) // repete enquanto i<=10
se resto(100,i)=0 então
imprima(i)
fim se
i←i+1
// gera o próximo número
fim enquanto
fim algoritmo
Exemplo 4 - Algoritmo que lê 10 números e imprime o quadrado desses números
Qualquer comando pode ser inserido dentro de uma repetição, inclusive comandos de
leitura. Nesse exemplo, quero ler 10 números e imprimir o quadrado dos mesmos e só.
Sendo assim, não necessitamos de guardar os números em variáveis disitintas, podendo
usar apenas uma. Assim lemos um valor e imprimimos o quadrado, depois lemos outro e
imprimimos o quadrado, assim por diante até completarmos 10 números. Para que o
programa pare de ler quando completarmos 10 números, devemos Ter um contador auxiliar
(i):
Algoritmo exemplo4
Declare i,n: numérico
i←1 //inicializo i com 1
enquanto (i<=10) // repete enquanto i<=10
leia(n) // lê os números
imprima(n2) // imprime o quadrado de n
i←i+1
// incrementa o contador
fim enquanto
fim algoritmo
Exemplo 5- Algoritmo que lê 10 pares de números e imprime o maior.
Nesse caso temos que ler 10 pares de números e imprimir apenas o maior de cada par:
Algoritmo exemplo5
Declare i,a,b: numérico
i←1 //inicializo i com 1
enquanto (i<=10) // repete enquanto i<=10
leia(a,b) // lê os números
se (a >b) então
imprima(a) // se a for maior imprime a
senão
imprima(b) // se b for maior imprime b
fim se
i←i+1
// incrementa o valor de i
fim enquanto
fim algoritmo
Exemplo 6- Escrever um algoritmo que leia a nota de 30 alunos de uma turma, calcule e
imprima a média da turma.
Para calcular a média, basta lermos as 30 notas e acumularmos em uma variável. Depois,
dividimos o total acumulado por 30:
Algoritmo exemplo6
Declare i,nota,soma,media: numérico
44
i←1
//inicializo i com 1
soma←0 // inicializo a soma com zero
enquanto (i<=30) // repete enquanto i<=30
leia(nota) // lê as notas
soma←soma+nota // acumula os valores das notas
i←i+1
// incrementa o valor de i
fim enquanto
media←soma/30
imprima(media)
fim algoritmo
Note que não é possível ler e acumular em uma única variável. Precisamos ler em uma
variável (nota) e acumular o valor em outra (soma)
Exemplo 7 - Calcular e imprimir os 30 primeiros termos da série: 1 3 7 15 31..
Para gerar elementos da série temos que descobrir primeiramente a lei de formação da
série. Ou seja, como os termos da série são produzidos matematicamente, em função do
termo inicial
Neste caso temos que o termo inicial é 1 e os demais são gerados multiplicando o termo
anterior por 2 e somando mai 1:
Termo = (termo_anterior* 2 )+1
Assim teremos:
Algoritmo exemplo7
Declare i,termo: numérico
i←1 // uso um contador auxiliar i começando com 1
termo←1 // termo recebe o valor inicial da série
enquanto (i<=30) // repete enquanto i<=30
imprime(termo) // imprimo os termos
termo←2*termo +1 // atualizo o valor do termo
// incrementa o valor de i
i←i+1
fim enquanto
fim algoritmo
Exemplo 8 - Calcular e imprimir os 50 primeiros termos da série:
S = 1/1 - 2/3 +3/7 - 4/15 + 5/31 - ...
Quando temos uma série com numerador e denominador nos termos é interessante repartir
o termo em duas variáveis, uma para o numerador e outra para o denominador. Nesse
exemplo, temos ainda a inversão de sinais entre os termos consecutivos. Para inverter, uma
das possibilidades é usarmos uma variável sinal, que nos termos impares será positiva (+1)
e nos termos pares será negativa (-1).
Assim, teremos:
Algoritmo exemplo8
Declare a,b,termo,sinal: numérico
termo←1 // termo recebe o valor inicial da série
a←1
b←1
sinal←1
enquanto (a<=30) // utilizo o numerador na condição do enquanto
45
imprime(termo) // imprimo os termos
a←a+1 // atualizo o valor do numerador
b←(2*b)+1 // atualizo o valor do denominador
sinal←sinal*(-1) // inverto o sinal
termo←a/b*sinal // calculo o próximo termo
fim enquanto
fim algoritmo
Exemplo 9 – calcular e imprimir os 20 primeiros termos da série:
1 2 3 5 9 17 33 65 ....
Para gerarmos essa série, temos que observar que:
- o primeiro termo é 1
- os demais termos são sempre 2 elevado a uma potência adicionado de 1. Temos então
que:
1º termo: 1
2º termo: 2 - 20 +1
3º termo: 3 - 21 +1
4º termo: 5 - 22 +1
5º termo: 9 - 23 +1
.....
20º termo: 262145 - 218 +1
Sendo assim, se usarmos uma variável para contar os 20 termos e iniciarmos a variável com
0:
Algoritmo exemplo9
Declare termo,i: numérico
termo←1 // termo recebe o valor inicial da série
i←0
enquanto (i<20) // o i será incrementado até 20 quando a repetição
//termina
imprime(termo) // imprimo o termo
termo←2i+1 // Gero o próximo termo
i←i+1 // incremento o i só depois de gerar o próximo termo
fim enquanto
fim algoritmo
Exemplo 10 - Calcular o fatorial de todos os números de 1 a 10
Para calcular o fatorial de um número lido temos os seguinte algoritmo:
Algoritmo fatorial
declare n, i, fat: numerico
leia(n);
se (n<0)
imprima ("Não existe fatorial de número negativo");
senão
i←n;
fat←1;
enquanto (i>0)
fat←fat*i
46
i=i-1
fim enquanto
imprima(fat)
fim algoritmo
A parte sombreada do algoritmo e a que realmente faz o cálculo do fatorial e o imprime.
No exemplo estamos querendo realizar esse processo para uma série de números de 1 a 10.
Sendo assim basta construirmos um comando de repetição para gerarmos a série e dentro da
repetição iremos calcular o fatorial da forma apresentada:
Algoritmo exemplo10
Algoritmo fatorial
declare n, i, fat: numerico
n←1
enquanto (n<=10)
i←n;
fat←1;
enquanto (i>0)
fat←fat*i
i=i-1
fim enquanto
imprima(fat)
n←n+1
fim enquanto
fim algoritmo
Note que as variáveis i e fat devem ser reiniciadas sempre que a fatorial do próximo
número da série for ser calculado. Ou seja, essas inicializações tem de ficar no laço mais
externo. Esse exercício mostra como podemos utilizar um laço dentro de outro. A mesma
abordagem deve ser usada para a resolução do problema do número de Euler dado a seguir.
5.3.3 Exercícios
1. Escrever um algoritmo que leia um número e verifique se ele é um número perfeito ou
não. Um número perfeito é aquele cuja soma de seus divisores, exceto ele mesmo, é igual a
ele.
Ex: 6 é um número perfeito. Pois 6 = 1 + 2 + 3
2. O número de euler, e (e=2.7182818), pode ser calculado através da série abaixo:
e = 1 + 1/(1!) + 1/(2!) +1/(3!) + ...
Construa um algoritmo que calcule o número de Euler
A somatória deverá parar quando o termo a ser adicionado for menor que 0.00001
3. Ler trinta notas e imprimir a maior e a menor
4. Calcular e imprimir os 50 primeiros termos da série de Fibonacci
5. Construir um algoritmo para encontrar o Máximo divisor comum entre dois números
6. Imprimir os 20 primeiros termos da série 1 3 5 11 29 83 ...
7. Imprimir a soma dos 15 primeiros termos da série 1 3 6 18 66 258 ...
8. Calcular e imprimir o seguinte somatório:
S = 1/225 - 2/196 + 4/169 - 8/144 + ...+ 16384/1
9.
Calcular e imprimir o seguinte somatório:
S = 37*38/1 + 36*37/2 + 35*36/3 + ... + 1*2/37
47
5.3.4
Comando Faça .. .Enquanto
Sintaxe
Faça
Comando1
Comando2
Comando3
...
Comando n
Enquanto (expressão lógica)
Nessa estrutura a expressão lógica é testada apenas no final do comando. Isso significa que
os comandos dentro da estrutura de repetição serão executado sempre, pelo menos uma vez.
5.3.5 Exercícios
1. Refazer os exercícios e exemplos dados para o comando enquanto utilizando o
comando faça.
2. Estudem toda a matéria , sem entender a teoria é impossível o aprendizado pleno.
Prestem atenção aos detalhes. Concentrem-se e abstraiam:
“Nada mais prático que uma boa teoria”
48
6
6.1
Vetores e Matrizes
Vetores
Seja a declaração string:
char s[20];
Esta declaração define uma cadeia de caracteres de 20 posições. Cada posição pode
armazenar um elemento de seu tipo básico, no caso o char. Um string pode também ser
chamado de um Vetor de caracteres:
Um vetor é um conjunto de variáveis de um mesmo tipo básico agrupadas. Cada variável
do conjunto pode ser referenciada, manipulada ou lida de maneira isolada.
Para termos acesso a cada variável isoladamente vetores mantém índices de cada posição
em separado.
Uma string é um vetor tratado de maneira especial em linguagens de programação porque o
seu uso para manipulação de dados alfanuméricos é muito corriqueiro.
No entanto, podemos ter vetores de qualquer tipo básico ou não.
Vetor numéricos
Declaração:
Algoritmo
Declare Nome_vetor: vetor[Tamanho] numerico;
Exemplo: Declare Vet: vetor[10] numérico;
Esta declaração define um vetor de nome Vet com 10 posições
Representação gráfica:
Vet
0
1
2
3
4
5
6
7
8
9
Cada posições de um vetor numérico pode guardar um valor numérico.
49
Assim se fizermos
vet[0]←5
vet[1]←10
vet[2]←15
vet[3]←20
vet[4]←25
vet[5]←30
vet[6]←35
vet[7]←40
vet[8]←45
vet[9]←50
O vetor ficará com os conteúdos:
0 5
1 10
2 15
3 20
4 25
5 30
6 35
7 40
8 45
9 50
A grande vantagem de usarmos vetores está na possibilidade de usarmos variáveis como
índices:
O resultado acima pode ser obtido da seguinte maneira:
Declare i: numérico;
i←0 ;
enquanto (i<10)
vet[i]←5 * (i+1);
i←i+1
fim enquanto
Usando o comando para, o programa fica ainda menor:
Para i←0 até 9 faça
vet[i]←5 * (i+1);
Leitura e impressão de vetores usando índices
Exemplo: ler 10 números e imprimir na ordem que foi lida e na ordem inversa:
50
Algoritmo le_vetor;
Declare vet: vetor[10] numérico;
i: numérico
Para i←0 até 9 faça
Leia(vet[i])
Fim para
Para i←0 até 9 faça
Imprima(vet[i])
Fim para
Para i←0 até 9 faça
Imprima(vet[9-i])
Fim para
Exercícios:
1. Construa um algoritmo que leia 20 números, Calcule a média e imprima os números
que ficaram acima da média calculada
2. Construa um algoritmo que leia 50 números e imprima separadamente os número pares
e os números ímpares lidos. Neste exercício, use um vetor para ler os números, outro
para guardar os números pares e outro para guardar os números ímpares.
3. Construa um algoritmo que leia 50 números e armazene-os em um vetor em ordem
crescente. Imprima o vetor lido.
4. Construa um algoritmo que leia 50 números. Estes números poderão ser 0 e 1s. Informe
o tamanho da maior sequência de 0 e da maior sequência de 1’s que existir no vetor.
5. Construa um algoritmo que leia dois vetores X e Y de 30 posições. Os vetores x e y
representam um conjunto de posições de uma figura da janela de texto
51
Exemplo1
Construa um algoritmo que leia 10 números e armazene-os em um vetor em ordem
crescente. Imprima o vetor lido.
Solução: A medida que os números são lidos, manter o vetor ordenado:
Seja a seguinte simulação:
Vetor V- inicial
0
1
2
3
4
5
6
7
8
9
Valores lidos (aux)
I
0
1
2
3
4
5
6
7
8
9
Aux
3
2
5
0
4
7
3
8
9
7
Simulação:
I= 0 aux =3
V[i]=aux
0
3
1
2
3
4
5
6
7
8
9
1
3
2
3
4
5
6
7
8
9
I=1
aux=2
V[0]=au
0
2
52
V[1]=v[0]
Algoritmo
1. Declarar variáveis
2. Ler e Ordenar o vetor em ordem crescente
3. Imprimir o vetor ordenado
Fim Algoritmo
Ref. 2 {ler e ordenar o vetor}
Para i ← 0 até 9 faça
leia (aux)
Se ( i = 0 ) então
1. insiro o 1° valor lido na primeira posição do vetor
Senão
2. Pesquiso o vetor a partir da posição 0 até i para tentar encontrar um valor ja
inserido maior que aux
3. Com o índice da posição onde o valor é maior que aux, (j) percorro o
vetor da última posição
inserida (i-1) até j deslocando os elementos já
inseridos uma posição adiantes no vetor
4. Insiro o valor de aux na posição que ficará disponível no vetor
Fim se
Fim para
Fim ref. 2
Ref. 2.1 { insiro o 1° valor lido na primeira posição do vetor}
V[i]=aux
Fim Ref. 2.1
Ref. 2.2 {Pesquiso o vetor a partir da posição 0 até i para tentar encontrar um valor ja
inserido maior que aux
j←0
Enquanto (( j<i) E (v[j]< aux))
j←j+1
fim Enquanto
Fim ref. 2.2
Ref 2.3 { Com o índice da posição onde o valor é maior que aux, (j) percorro
o vetor da última posição inserida (i-1) até j deslocando os elementos já
inseridos uma posição adiantes no vetor}
k ← i-1
Enquanto (k>=j)
53
V[k+1]←v[k]
k←k-1
Fim Enquanto
Fim Ref. 2.3
Ref. 2.4 { Insiro o valor de aux na posição que ficará disponível no vetor}
V[j]←aux;
Fim ref. 2.4
Ref. 3 {imprmir o vetor ordenado}
Para i←0 até 9
Imprima(v[i]
Fim para
Ref. 1 {declarar variaveis}
Declare v: vetor[10] numércio
Declare i,j,k,aux: numerico
Exemplo 2 - Construa um algoritmo que leia um vetor de 20 números. Estes números
poderão ser 0 e 1s. Informe o tamanho da maior sequência de 0 e da maior sequência de 1’s
que existir no vetor.
Solução: Ler o vetor , identificar e contar o tamanho das seqüências de 1 e 0. Guardar
sempre o tamanho das maiores seqüências de 1’s e 0’s identificadas.
Algoritmo
1. Declaração de Variaveis
2. Leitura e inicialização de variáveis
3. Identificar e contar as seqüências de 1 e 0 e guardar a maior sequencia até o termino do
vetor
4. Imprimir resultados
Ref. 2
Para i←0 até 19 faça
Leia(v[i])
Fim para
Maior1←0
Maior0←0
54
Fim ref 2
Ref 3.
I=0;
Enquanto(i<20)
C0←0
Enquanto(( v[i]=0) e (i<20)
C0←c0+1
i←i+1
fim enquanto
se (c0> maior0) então
maior0←c0
fim se
c1←0
Enquanto(( v[i]=1) e (i<20)
C1←c0+1
i←i+1
fim enquanto
se (c1> maior1) então
maior1←c1
fim se
Fim enquanto
Fim ref 3
Ref. 4 {imprmir}
Imprima(“ a maior seqüência de 0’s é:”, maior0)
Imprima(“ a maior seqüência de 1’s é:”, maior1)
Fim ref 4
55
7
7.1
Registros - Structs
Definição
Assim como um vetor ou uma matriz, um registro agrupa um conjunto de variáveis sob
único nome. No entanto, um registro é uma estrutura de dados heterogênea ao passo que
um vetor ou matriz é uma estrutura de dados homogênea. Qual a diferença então? No caso
dos vetores ou matrizes, o tipo de dados básico das variáveis agrupadas é o mesmo. Ou
seja, quando declaramos uma vetor de 100 inteiros, todas as 100 variáveis serão do tipo
inteiro. Um vetor pode ser visualmente representado por uma longa lista indexada:
0
1
2
3
4
5
6
7
...
Já um registro se assemelha visualmente a uma ficha de cadastro onde cada campo de
preenchimento possui um nome especifico. Por exemplo, suponha que uma escola mantém
um arquivo de fichas de alunos contendo os seguintes dados: Nome, Endereço, Telefone,
Identidade, CPF e data de Nascimento.
Registro Aluno
Nome
Endereço
Telefone
Identidade
CPF
Data de Nascimento
Um registro então é uma forma de estruturar dados de acordo com o esquema já utilizado
em arquivos de registros tradicionais.
Um registro é chamado de estrutura de dados heterogênea porque as variáveis que o
compõe podem ser de diferentes tipos básicos.
7.2
Declaração de registros
Um registro possui um:
- Identificador que é seu nome e obedece as leis de formação de
nomes de variáveis
56
-
Campos que são os elementos ou variáveis que o compõe. No
exemplo acima o nome do registro é Aluno e este possui os
seguintes campos:
- Nome
- Endereço
- Telefone
- Identidade
- CPF
- Data de Nascimento
Os nomes dos campos deve obedecer as leis de formação de nomes de
variáveis, não podendo Ter acentos nem espaços em branco não caracteres
especiais nem iniciar com números.
Sintaxe da Declaração
Declare Identificador_registro: registro
< Seqüência de identificadores de campo 1>: Tipo1
< Seqüência de identificadores de campo 2>: Tipo2
....
fim do registro
Para declarar o exemplo acima, teremos:
Declare Aluno: Registro
Nome, endereco: literal
Telefone,identidade: numerico
CPF,datanascimento: literal
Fim do registro
7.3
Manipulando Registros
O acesso aos campos de um registro deve ser feito individualmente. Ou seja para lermos,
escrevermos, e utilizarmos os campos de um registro, temos uma forma de acessar cada
campo individualmente.
Cada campo do registro e acessado individualmente através do nome do registro e o nome
do campo separados por um ponto “.”. Assim, suponha que queiramos ler o registro Aluno:
Leia (Aluno.nome)
Leia (Aluno.endereco)
Leia (Aluno.telefone)
Leia (Aluno.identidade)
Leia (Aluno.CPF)
Leia (Aluno.datanascimento)
Ou em um único comando leia:
Leia (Aluno.nome, Aluno.endereco,
aluno.datanascimento)
Aluno.telefone,
Aluno.identidade,
Aluno.CPF,
57
Atribuições de valores, escritas e outras operações são feitas da mesma maneira:
Aluno.nome ← “Daniela”
Aluno.datanascimento ← “20/03/1970”
Aluno.CPF←127654844
Atribuições de estruturas de mesmo tipo
Algumas linguagens de programação, dentre elas o C permite a atribuição de registros de
mesma estrutura diretamente, sem precisar atribuir campo a campo. Exemplo
Declare circulo1,circulo2: registro
X,Y,Raio: numérico
Fim registro
circulo1.X←10
circulo1.Y←5
circulo1.Raio←30
circulo2←circulo1
7.4
Vetores de Registros
O uso mais comum de registros e associado a vetores. O objetivo é termos uma lista de
registros de forma que possamos guardar vários registros de um tipo simultaneamente.
Suponha no caso de uma escola. Obviamente esta possuirá mais de um aluno e um único
registro não seria suficiente para armazenar as informações de todos os alunos ao mesmo
tempo.
No entanto, se definirmos um vetor cujo tipo básico é um registro teremos então um
conjunto de N registros de alunos, onde N é o tamanho do vetor.
Declaração de Vetores de registros:
Tomemos como exemplo a declaração do vetor Turma:
Declare Turma: Vetor [100] de Registro
Nome, endereco: literal
Telefone,identidade: numerico
CPF,datanascimento: literal
Fim do registro
/* Com essa declaração, teremos um conjunto de 100 registros contendo cada registro os
campos nome, endereco, telefone identidade, CPF e datanascimento.
Para ler os dados da turma teriamos o seguinte código: */
Declare i: numérico
Imprima(“Digite o numero de alunos:”)
Leia (N)
58
Se N <=100 então
Para i← 0 até N-1 faça
Leia (Turma[i].nome, Turma[i].endereco,
Turma[i].datanascimento)
Fim para
Turma[i].telefone,
Turma[i].identidade,
Turma[i].CPF,
Senão
Escreva(“ O número de alunos deve ser menor que 100”)
Fim se
7.5
Exercícios
1. Declarar o registro que represente os dados informativos de uma banda de rock. O
registro deve conter os dados: Nome, número de integrantes, data de criação, estilo
musical, número de Cds gravados.
2. Declarar um vetor de 200 registros que representem os dados informativos de fazendas.
O registro deve conter os dados: Nome da Fazenda, endereço, Principal atividade,
Tamanho, nome do Proprietário, CPF do proprietário.
3. Construir um algoritmo que leia um conjunto de fazendas de e armazene no vetor de
registro declarado no exercício 2 Posteriormente ler um nome de uma fazenda e
verificar se este nome está na lista de fazendas lida anteriormente. Em caso afirmativo,
imprimir os demais dados da fazenda.
7.6
Registro em C
Um registro em C é chamado de struct (estrutura)
Sua declaração é feita da seguinte forma:
struct {
Tipo1
Tipo2
< Seqüência de identificadores de campo 1>
< Seqüência de identificadores de campo 2>
.....
} Nome_da_struct
Suponho o registro aluno declarado anteriormente. Em C teríamos a seguinte declaração.
Struct Aluno {
char nome[50],endereco[100];
long int Telefone,identidade;
char CPF[20],datanascimento[20];
} Al;
Para declararmos um vetor ou matriz de structs em C, podemos usar o nome da estrutura predefinida.
Assim, para declarar o vetor turma teríamos:
struct Al turma[100];
59
A referência aos nomes dos campos e feita usando o ponto “.”:
int register i;
strcpy(turma[0].nome, “Maria de Lourdes”);
for (i=0;i<100;i++)
scanf(“%s %d”,turma[i].endereco,&turma[i].telefone);
Notas: -
Modificador de tipo register – Aloca a variável em um registrador
de memória tornando seu acesso mais eficiente.
Modificador de tipo
long – aumenta a capacidade de
representação do tipo int para um número de 4 bytes.
O uso do operador de endereço continua sendo necessário quando
se trata de variáveis diferentes de cadeias de caracteres
Trabalho 1 – Construir um programa que implemente uma agenda telefônica contendo as
seguintes operações
Tipo: Individual
Data de entrega: 16/08/01 Apresentação: 16 e 17/08
Cadastro de pessoas
Pesquisa por nome
Eliminação
Atualização
A agenda deve guardar os seguintes dados:
Nome, telefone fixo, telefone celular, e_mail, endereço
Os dados devem ser guardados em um vetor de registros. Dica, Cada registro deve conter
um campo que indique se o mesmo está ou não ocupado. De modo que quando um registro
é eliminado a posição dele no vetor possa ser novamente ocupada.
Exercicios:
1. Declare um novo tipo de registro chamado Conta, que deve Ter os campos: código,
codigo do Correntista, número, saldo.
2. Declare um novo tipo de registro chamado correntista, contendo os campos Codigo,
Nome, CPF, Telefone, e_mail.
3. Declare as variáveis Contas e Correntistas. Contas será um vetor de 500 posições do
tipo Conta. Correntistas será um vetor de 500 posições do tipo Correntista.
8
Arquivos em C
O objetivo deste capítulo e permitir a construção de programas que armazenem dados de
forma permanente em dispositivos de armazenamento secundário, tais como disquetes e
discos rígidos.
60
8.1
Definições
Sistema de arquivos em C Æ é o conjunto de funções da linguagem que podem ler,
escrever e manipular dados em dispositivos periféricos.
Stream e Arquivos Æ Stream é uma abstração do dispositivo periférico. O dispositivo real
é o arquivo. Em C o termo Stream identifica logicamente um dispositivo periférico
qualquer que permita a leitura e/ou gravação de dados.
No entanto neste texto usaremos o termo arquivo para nos referirmos aos arquivos
gravados em memória secundária
Arquivos de dados armazenados em memória Secundária Æ permitem o armazenamento
permanentemente dos dados.
A linguagem C provê um conjunto completo de funções para manipulação de arquivos em disco.
8.2 Tipos de arquivos
Existem dois tipos básicos de arquivos, nos quais se baseiam programas que manipulam
arquivos. Em C esses tipos de arquivos são chamados de arquivos texto e arquivos
binários.
Arquivo texto - É um arquivo cujo conteúdo é baseado em uma seqüência de caracteres
que formam linhas determinadas por um caracter de nova linha ( “ \ n ”). Dentro destes
arquivos podem ser gravados apenas dados em forma de texto. Ou seja, não poderemos
gravar, por exemplo, um valor numérico nestes arquivos, a não ser que este valor seja
transformado em seqüências de caracteres. Esta forma de organização de arquivos e a mais
simples.
Funções em C
Funções são subprogramas que quando
chamados
Arquivo texto
Arquivos binários – Arquivos binários tem o seu conteúdo baseado em uma estrutura ou
dado que respeita um determinado tipo de dado. Este tipo de dado pode ser um tipo simples
(int, float, char) ou um tipo estruturado como registro (struct). Portanto esses arquivos são
construídos como uma seqüência de bytes respeitando uma determinada estrutura. Esess
arquivos podem ser visualizados como um tabela onde cada linha possui um determinado
conjuntos de campos (colunas). Cada linha dessa tabela possuirá também um número que
identificará sua posição no arquivo. Por essas características, tais arquivos tem a vantagem
de permitir o acesso aleatório a posições.
Nome
Telefone idade
Maria
45545
21
João
87878
30
Paula
78867
22
José
565675
20
...
...
...
Visualização abstrata de um arquivo
binário organizado a partir de uma
struct contendo os campos nome
61
8.3
Ferramentas para manipulação de arquivos
8.3.1 Estrutura de controle FILE –
A linguagem C utiliza esta estrutura para criarmos um ponteiro para o arquivo.
Este ponteiro irá conter o endereço da estrutura FILE criada que manterá várias
informações sobre o arquivo, como o seu nome, posição atual, etc. Estas informações são
usadas pelas funções do sistema de arquivos em C.
Para ler ou escrever arquivos, qualquer programa precisa usar ponteiros de arquivos.
Declaração de um ponteiro de arquivo
FILE *fp;
FILE – é o nome da estrutura (tipo de dado)
O * define que a variável fp é uma variável do tipo ponteiro para FILE.
8.3.2 Abrindo um arquivo
abrir um arquivo significa criar uma associação entre o arquivo físico (na memória
secundária) e o programa, através do ponteiro de arquivo.
Para tanto usa-se a função fopen cujo protótipo é mostrado a seguir. O protótipo de uma
função indica como a função foi definida, qual o tipo de dado que ela retorna e quais os
parâmetros da função. Os parâmetros são indicados entre os parênteses da função. Por
exemplo no lugar do parâmetro nomearq da função fopen, deve ser informado o nome do
arquivo a ser aberto, através de uma variável do tipo ponteiro para char. Ainda não falamos
de ponteiros para char. No entanto você já está acustumado a usá-los. Trata-se de uma
string, de um variável do tipo cadeia de caracteres (ex. char str[30]) ou de uma constante
do tipo string (ex. “arquivox.txt”).
O valor de retorno de uma função deve ser atribuído a uma variável do mesmo tipo da
função. No caso, fopen() é definida como um FILE *, ou seja, um ponteiro para FILE.
62
Assim sendo, quando fopen é chamada deve ser feita com a atribuição do seu valor de
retorna a uma variável do tipo FILE *.
Ex: FILE *arq;
arq = fopen(“arquivox.txt”, “w);
Usar o comando de atribuição entre uma variável e uma função indica que o valor de
retorno da função está sendo atribuído a variável.
Parâmetro indicando o nome físico do arquivo
FILE *fopen ( const char * nomearq, const char *modo)
Significa que
fopen retorna
um ponteiro
de arquivo
parêmtro do tipo string que determina o modo
como o arquivo será aberto
nomearq é um ponteiro (nome de string) para uma cadeia de caracteres que indica um
nome válido de um arquivo, podendo indicar o seu caminho.
modo é um ponteiro para uma cadeia de caracteres que indica o modo como o arquivo será
aberto.
Esta string é construída utilizando-se os caracteres da tabela abaixo:
MODO
r
w
a
rb
wb
ab
r+
w+
a+
r+b
w+b
a+b
SIGNIFICADO
Abre um arquivo texto para leitura
Cria um arquivo texto para escrita ou sobrescreve caso o arquivo
exista
Anexa a um arquivo texto: abre para escrita no final do arquivo texto
ou caia um novo arquivo caso o mesmo não exista
Abre um arquivo binário para leitura
Cria um arquivo binário para escrita
Abre um arquivo binário para escrita no final do arquivo ou cria um
novo caso o mesmo não exista
Abre um arquivo texto para leitura e escrita
Cria um arquivo texto p/ leitura e escrita ou sobrescreve caso o
arquivo já exista
Abre um arquivo e cria um novo para leitura e escrita no final do
arquivo
Abre um arquivo binário para leitura/escrita
Cria um arquivo binário para leitura/escrita
Abre um arquivo binário para leitura e escrita no final do mesmo
63
Exemplo: Suponha o arquivo teste.txt. O programa abaixo pode ser usado para abrí-lo para
escrita no modo texto
main ( ) {
FILE *fp;
fp = fopen (“teste.txt”, “w”)
}
Embora esteja correto, normalmente o código anterior é escrito da seguinte maneira:
main ( ) {
FILE *fp;
if ((fp = fopen (“teste.txt”, “w”) = = null) {
printf (“o arquivo não pode ser aberto \ n”);
return 0;
}
}
NULL é um valor nulo – inválido. Desta forma o programa irá saber se o arquivo foi
corretamente aberto, detectando qualquer tipo de erro que possa ocorrer. Para evitar erros,
sempre devemos conferir o sucesso da chamada de fopen.
Obs. Sempre que abrirmos um arquivo para escrita, se o arquivo existir o conteúdo do
mesmo será apagado.
Fechando um arquivo
todo arquivo aberto deve ser fechado quando não mais necessário, evitando perda de dados
e outros problemas.
Para tanto a função fclose ( ) deve ser usada. Seu protótipo é o seguinte:
Ponteiro do arquivo
int close ( FILE *fp )
fclose Retorna EOF se houver algum erro e 0 caso tenha sucesso..
8.3.3 Escrevendo e lendo em um arquivo texto
Escrevendo caracteres
Função: putc ( ou fputc ( ) ).
64
Protótipo:
int putc (int ch, FILE*fp);
Esta função pode ser usada para escrevermos caracteres em arquivos que foram previamente abertos por
fopen ().
fp é um ponteiro de arquivo retornado por fopen.
ch é definido como int (2 bytes) mas apenas o byte menos significativo é levado em conta.
Se putc ( ) falha ela devolve o caracter de fim de arquivo EOF
Lendo caracteres
Função getc ( ) (ou fgetc())
Protótipo:
int getc (FILE,*fp);
Lê um caracter de um arquivo previamente aberto por fopen() usando o ponteiro fp. Da
mesma forma que putc, getc devolve um inteiro, mas apenas o byte menos significativo é
usado.
Quando chega ao final do arquivo getc ( ) devolve o caracter EOF.
Ex. No código seguinte um arquivo é lido até o seu final
do {
ch = getc (fp);
}while (ch!=EOF);
Quando ocorre um erro, getc também retorna EOF.
Lendo e escrevendo strings
Funções: fputs e fgets
As funções: fputs () e fgets() – efetuam operações de leitura e escrita de e para arquivos de
forma semelhante a getc e putc. Contudo, elas lêem e escrevem strings, ao invés de
caracteres.
Protótipos:
int fputs ( const char * str, FILE *fp);
char * fgets ( char *str, int lenght, FILE *fp);
fgets retorna um ponteiro para STR
65
fputs escreve a string str no arquivo especificado. Devolve EOF se um erro ocorre.
fgets lê uma string do arquivo especificado até que um caracter de nova linha seja
encontrado ou que length-1 caracteres tenham sido lidos, se o caracter de nova linha for
lido ele será inserido na string. A string resultante será terminada por um ‘\0’. Retorna
NULL se ocorre um erro ou fim de arquivo.
Exemplo1: Lê a primeira linha do arquivo “agenda.cpp”
int main(int argc, char **argv)
{ FILE *fp;
char buffer[100];
strcpy(buffer,"");
if ((fp=fopen("agenda.cpp","r"))==NULL) {
puts("erro ao abrir o arquivo");
return 0;
}
fgets(buffer,100,fp);
puts(buffer);
getch();
return 0;
}
8.3.3.1.1
Exemplo 2 – Escrevendo strings em um arquivo
int main(int argc, char **argv)
{ FILE *fp;
char buffer[100];
strcpy(buffer,"");
if ((fp=fopen("agenda.txt","a"))==NULL) {
puts("erro ao abrir o arquivo");
return 0;
}
do {
printf ("Digite uma string (enter para sair:\n");
gets(buffer);
// gets não guarda o ‘\n‘. temos que usar o strcat para
// colocar o ‘\n‘ no final da string se quisermos saltar uma linha no
// arquivo.
strcat (buffer,"\n");
// função de concatenação de strings
fputs (buffer, fp);
}while (*buffer!='\n');
fclose(fp);
}
exemplo 3 – Lendo um arquivo e apresentando na tela com fgets()
int main(int argc, char **argv)
{ FILE *fp;
66
char buffer[100];
strcpy(buffer,"");
if ((fp=fopen("agenda.cpp","r"))==NULL) {
puts("erro ao abrir o arquivo");
return 0;
}
fgets(buffer,100,fp);
while (fgets(buffer,100,fp)!=NULL) {
printf("%s",buffer);
}
fclose(fp);
getch();
}
Exercícios:
1. Construir um programa que leia uma string e escreva essa string em um arquivo
chamado ex1.txt caracter a caracter usando a função putc.
2. Construir um programa que leia o arquivo ex1.txt e guarde os caracteres lidos em uma
string. Posteriormente imprima a string na tela.
3. Construir um programa que leia palavras do teclado para um vetor de 20 strings.
Posteriormente, grave as strings guardadas no vetor no arquivo ex2.txt usando a função
fputs().
4. Construir um programa que leia o arquivo ex2.txt. usando a função fgets e apresente as
strings lidas na tela.
5. Criar um programa que permita a cópia de arquivos texto. O programa deve pedir o
nome do arquivo que se quer copiar e o nome do arquivo que será a cópia.
6. Criar um programa que conte o número de palavras de um arquiv. Dica: Uma palavra
pode ser terminada por um espaço (‘ ‘), um caracter de tabulação (‘\t’) ou um caracter
de nova linha (‘\n’).
7. Construir um programa que leia uma palavra do teclado e conte o número de
ocorrências da palavra lida em um arquivo texto.
8. Criar um programa que leia um arquivo e crie uma cópia “criptografada” do arquivo
lido somando-se o valor 1 a cada caracter lido no arquivo original. Criar um programa
para decriptografar o arquivo subtraindo 1 de cada caracter lido. Sugestão: Não
criptografe o caracter de nova linha (‘\n’).
9. Construa um programa que conte o número de ocorrências de uma seqüência de
caracteres dentro de um arquivo texto. A seqüência de caracteres deve ser lida do
teclado e não deve conter nem espaços, nem tabs, nem caracteres de nova linha. Neste
caso a sequência pode estar dentro de um outra seqüência. Por exemplo, se procurarmos
a seqüência “ana” em um arquivo que contém o texto:
“Mariana é uma garota de Americana muito bacana”.
Neste caso, a seqüência ana ocorre três vezes:
Mariana é uma garota de Americana muito bacana.
8.4
Outras Funções de manipulação de arquivos
feof() – Determina a ocorrência de fim de arquivo quando este é aberto em modo binário.
Protótipo:
67
int feof(FILE *fp)
Comentário: No caso de arquivos binários, é possível que um dos dados lidos seja o valor
EOF. Portanto, não é confiável utilizar este caracter para determinar o fim de arquivo.
Assim, devemos usar a função feof() no lugar do teste de fim de arquivo.
rewind() – Reposiciona o indicador de posição do arquivo em seu início.
Protótipo:
void rewind (FILE * fp)
A medida que avançamos na leitura ou escrita de um arquivo, o seu indicador de posição
avança junto. Assim, se quisermos voltar ao início do arquivo sem fechá-lo, devemos usar a
função rewind().
ferror() – determina se uma operação com um arquivo produziu um erro.
Protótipo:
int ferror(FILE *fp);
Ferror informa retornando verdadeiro se ocorreu um erro na última operação realizada no
arquivo. Ou seja, ferreor deve ser testada logo após as operações que se deseja verificar.
remove() - apaga um arquivo
Protótipo:
int remove(const char *nomearq)
Esta função apaga o arquivo indicado com pelo nome. Esta função deve ser usada
cuidadosamente. É sempre bom utilizar questionar o usuário se este deseja realmente
apagar o arquivo em questão.
8.5
Lendo e escrevendo em Arquivos Binários - fread() e fwrite()
Até agora, usamos o sistema de arquivos de C para lermos arquivos texto. No entanto, C
também permite que criemos arquivos do tipo binário. Um arquivo binário é uma forma de
organização que permite o armazenamento sequencial de estruturas maiores que um byte
(char).
Finalidade – a finaldade deste tipo de organização é podermos Ter em armazenamento
secundário uma forma de organização mais eficiente. Um aplicação imediata é no progama
da agenda. Neste caso, os dados são armazenados em um vetor de registros. Da mesma
forma, podemos armazenar os registros em um arquivo binário usando fread e fwrite.
8.5.1 Operador que determina o tamanho de tipos de dados - sizeof()
O operador sizeof será bastante utilizado na manipulação de arquivos binários. Isso
porque, para usarmos fread ou fwrite precisamos determinar o número de bytes que
68
desejamos ler ou gravar no arquivo binário. Como saber exatamente o tamanho de uma
variável? Simples: basta usar sizeof tomando como parâmetro o seu tipo.
Por exemplo, o programa abaixo imprime os tamanho em bytes dos tipos char, int, float,
double e long double:
int main()
int a,b,c,d,e;
a=sizeof(char);
b=sizeof(int);
c=sizeof(float);
d=sizeof(double);
e=sizeof(long double);
printf (" char: %d \n int: %d \n float: %d \n double: %d \n
long double: %d \n",a,b,c,d,e);
getch();
}
sizeof pode ser usado para determinar o tamanho de structs.
Exercício: Construa um struct para conter os dados de uma pessoa: Nome, telefone,
endereco, e-mail, data de nascimento e CPF. Use sizeof para imprimir o tamanho da struct
que você declarou.
8.5.2 Função fwrite()
Protótipo:
Size_t fwrite(const void *buffer, size_t numbytes, size_t count, FILE
*fp);
Parâmetros:
- buffer – ponteiro para uma região da memória que contém os
dados que serão escritos no arquivo
- numbytes – determina o número de bytes de cada item
(elemento individual) a ser escrito no arquivo.
- Count determina o número de itens a serem escritos no
arquivo.
Os bytes serão pegos na memória a partir do endereço
especificado por buffer e escritos no arquivo.
- fp é um ponteiro para um arquivo previamente aberto por
fopen no modo binário.
8.5.3 Função fread()
Protótipo:
Size_t fread(void *buffer,size_t numbytes, size_t count, FILE *fp);
Parâmetros:
- buffer – ponteiro para uma região da memória que
receber os dados que serão lidos do arquivo
irá
69
-
numbytes – determina o número de bytes de cada item
(elemento individual) a ser lido do arquivo.
- Count determina o número de itens a serem lidos do
arquivo.
Os bytes serão lidos do arquivo e armazenados na memória a
partir do endereço especificado por buffer.
- fp é um ponteiro para um arquivo previamente aberto por
fopen no modo binário.
8.5.4 Exemplo1 - Escrevendo número inteiros em um arquivo
// Programa que lê n números inteiros do teclado e armazena os números em
um arquivo binário
int main()
{
FILE *fp;
int numero,n,i;
char nomearq[15];
// Le o nome do arquivo
printf("Digite o nome do arquivo:");
scanf("%s",nomearq);
//abre o arquivo para escrita em modo binário
fp=fopen(nomearq,"wb");
if (fp==NULL){
puts("erro");
getch();
return 0;
}
printf("Digite o número de elementos
scanf("%d",&n);
a serem lidos:");
for(i=0;i<n;i++){
printf("Digite o elemento %d:",i+1);
scanf("%d",&numero); // Le um número
// escreve o numero no arquivo
fwrite(&numero,sizeof(int),1,fp);
}
if(fclose(fp)!=0)
printf("Erro ao fechar o arquivo");
}
8.5.5 Operador de endereço &
Neste exemplo n números inteiros são lidos do teclado para a memória na variável numero,
e posteriormente gravados no arquivo. Observe que na função fwrite o parâmetro buffer e
substituído por &numero. O operador & usado é chamado de operador de endereço. Ele
70
é usado porque, conforme visto anteriormente, buffer deve ser um ponteiro (endereço) para
a região da memória onde se encontra o dado a ser gravado por fwrite. Ou seja, a função do
& junto a uma variável é obter o endereço desta variável. Este é o mesmo motivo pelo qual
utilizamos o operador & nas variáveis a serem lidas por scanf.
8.5.6 Exemplo 2 – Programa que lê um arquivo binário de números inteiros e
imprime na tela
int main()
{
FILE *fp;
int numero,n,i;
char nomearq[15];
// Le o nome do arquivo
printf("Digite o nome do arquivo:");
scanf("%s",nomearq);
//abre o arquivo para leitura em modo binário
fp=fopen(nomearq,"rb");
if (fp==NULL){
puts("erro");
getch();
return 0;
}
fread(&numero,sizeof(int),1,fp); // tenta ler o primeiro elemento do
//arquivo
while(!feof(fp)){ // feof testa se não é fim de arquivo
printf("%d\n",numero); // imprime o número
fread(&numero,sizeof(int),1,fp);
// le o próximo número
// escreve o numero no arquivo
}
getch();
if(fclose(fp)!=0)
printf("Erro ao fechar o arquivo");
}
Neste exemplo, o procedimento de leitura do arquivo é semelhante ao de leitura de uma
arquivo texto. Lê-se primeiro elemento fora do while. No entanto, ao invés do teste com o
caracter EOF, a função feof() deve ser utilizada.
8.5.7 Exemplo 3 - Escrevendo um vetor de inteiros em um arquivo binário
int main()
{
FILE *fp;
int i, n, vetint[100];
char nomearq[15];
// Le o nome do arquivo
printf("Digite o nome do arquivo:");
71
scanf("%s",nomearq);
//abre o arquivo para escrita em modo binário
fp=fopen(nomearq,"wb");
if (fp==NULL){
puts("erro");
getch();
return 0;
}
// le o numero de elementos a ser lido para o vetor
printf("Digite o número de elementos do vetor:");
scanf("%d",&n);
// le os números preenchendo o vetor
for (i=0;i<n;i++) {
printf("Digite o elemento %d",i+1);
scanf("%d",&vetint[i]);
}
// escreve o conteudo do vetor de uma única vez
fwrite(vetint,sizeof(int),n,fp);
//fecha o arquivo
if(fclose(fp)!=0)
printf("Erro ao fechar o arquivo");
}
Neste exemplo, o vetor foi previamente lido elemento a elemento do teclado.
Posteriormente o vetor foi escrito de uma única vez através de fwrite. Veja que o
parâmetro count de fwrite foi substituído por n que determina o número de elementos
lidos para o vetor. Isso significa que o número de bytes a ser gravado no arquivo
nesse caso será igual a sizeof(int)*count a partir do endereço vetint.
8.5.8 Nomes de vetores são ponteiros
Observe que o no último exemplo, o parâmetro buffer de fwrite foi substituído apenas
por vetint e não por &vetint. Isso ocorre porque na realidade, um nome de vetor em C
já é um endereço de memória. Ou seja, o nome de um vetor possui um endereço que
aponta para a região da memória onde se encontram as posições daquele vetor. Mais
especificamente, um nome de vetor possui o endereço (aponta) de memória da
primeira posição do vetor. Por esse motivo, o operador & é desnecessário quando
usamos vetores.
Por esse mesmo motivo, na função scanf, quando vamos ler uma string (vetor de
char), não precisamos usar o operador de endereço.
9
Apontadores
Defnição: Um apontador ou ponteiro é uma uma variável que pode conter o endereço de
outra variável.
Sua utilização é vasta em programas por dois motivos:
72
1. Algumas computações só são possíveis com a utilização de ponteiros
2. A utilização de ponteiros em geral simplifica a programação e aumenta a
eficiência do código executável, tornando-o mais rápido.
9.1 Apontadores e Endereços
Deve ficar claro que o apontador não é um endereço de memória. É uma variável que pode
conter um endereço.
Por exemplo Seja x uma variável inteira e px um ponteiro.
Como sabemos, o operador & fornece o endereço de uma variável. Assim a atribuição
abaixo é válida:
px=&x
Observação: O operador & só pode ser aplicado sobre variáveis e elementos de arranjos.
Não é possível por exemplo obter o endereço de expressões como &(x+1).
Suponha a seguinte sequencia de código:
x=7;
px= &x;
A figura abaixo mostra esquematicamente a situação em uma memória hipotética de 256
posições endereçadas de 0 (00000000) a 255 (11111111). Suponha que a variável x tenha
sido alocada no endereço 3 da memória e px na posição 5 da memória. Assim, após a
execução das linhas acima teremos a seguinte situação:
Memória
Endereço
0000000
0
0000000
1
0000001
0
0000001 0 0
1
0000010
0
0000010 0 0
1
0000011
0
0000011
1
....
Dado
0
0
0
1
1 1
0
0
0
0
1 1
73
O operador * trata seu operando como um endereço permitindo o acesso a esse endereço
para manipulação do conteúdo desse enreço. Por exemplo, a atribuição
px=&x
*px= 6;
coloca o valor 6 no endereço apontado por px, ou seja em x.
Seja y uma variável inteira. Então a seqüência:
x=20;
px=&x;
y= *px;
Surte o mesmo efeito de
x=20;
y=x;
Quando temos uma variável do tipo apontador (px) com um endereço para uma outra
variável (x), dizemos que px aponta para x.
9.2
Declaração de Apontadores
Assim como qualquer outra variável, um apontador necessita ser declarado:
int x,y;
int *px;
Como sabemos, int x,y declaram x e y como duas variáveis inteiras.
A declaração int *px é a declaração do ponteiro px. Esta declaração é uma menemônico que
representa a seguinte idéia:
Como sabemos uma variável do tipo int x, indica que x é um inteiro. O mesmo raciocínio
pode ser aplicado com relação a *px. Ou seja *px é um inteiro também. Este raciocínio
deve ser sempre empregado identificarmos e entenderemos claramente a declaração de
variáveis e ponteiros.
É importante perceber que a sintaxe da declaração das variáveis tem forma similar a sintaxe
das expressões onde a variável pode aparecer.
Ou fator importante é que um apontador em geral restringe-se a apontar para um único tipo
de objeto, explicitado na declaração.
A declaração do apontador px do tipo inteiro significa que a forma *px pode ser utilizada
em qualquer situação em que uma variável iteira pode poderia. Por exemplo, a sequência
abaixo:
Int *px, x , y;
x=20;
px=&x;
y=*px +10;
74
Faz com que y receba o conteúdo de x somado com 10.
Outras expressões válidas são:
px=&x;
Atribui o valor 10 no endereço apontado por px:
*px=10;
Incrementa o o conteúdo de px em uma unidade:
*px+=1;
ou
(*px)++;
Na última construção, o uso dos parênteses é impressindível. Sem eles, estaríamos
incrementando o ponteiro e não o seu conteúdo, dado que operadores unários ( * e ++ por
exemplo) são avaliados da direita para a esquerda.
Não podemos esquecer que apontadores são variáveis. Assim sendo serão também válidas
espressões envolvendo variáveis apontadores. Por exemplo, se py é uma variável do tipo
ponteiro, então a expressão
py=px;
copia o conteúdo de px (não o conteúdo do endereço apontado por px) para dentro de py
fazendo com que py passe a apontar para o mesmo local para onde px aponta.
Por exemplo, na seqüência abaixo, o conteúdo de x e atribuído indiretamente a y através
dos ponteiros:
int x, y, *px, *py;
x=10;
px=&x;
py=px;
y=*py;
Note que py aponta para x e não para y.
Vejamos esquematicamente. Suponha que os nomes representam os endereços das
variáveis na memória.
x=10;
X
y
px
py
10
px=&x;
75
X
y
10
px
py
x
py=px;
X
y
10
px
x
py
x
y=*py;
X
10
y
10
px
x
py
x
Exercícios
Analise as seguintes sequências de código. Verifique se estão corretas ou incorretas. Caso
estejam incorretas, apontem os prováveis erros. Para tanto, considere a declaração das
variáveis abaixo:
int a,b, *c, *d;
a) c=a;
b) c= &b;
d=c;
*d=10;
c) c=*a;
d=10+ *c;
d) d=c;
c=&a;
a=10;
b=*d;
printf(“%d”,b);
e) c=&a;
scanf(“%d”,&c);
d=c;
b=*d;
f) b=10;
c=&b;
printf(“%d, c);
76
g) *c=20;
h) b=2;
c=&b;
*c+=2;
i) b=1;
c=*b;
*c=a+ *c;
j) a=1;
c=&a;
for(b=0;b<10;b++)
*c+=1;
k) c=&b;
b=10;
printf(“%d”,*c);
l) scanf(“%d”, c);
m) a=100;
c=&a;
while (a>0){
*c=*c-1;
printf(“%d\n”,a);
}
n) *c=10;
d=c;
*d=*d +10;
printf(“%d”,*d);
77
9.3
Qual o valor inicial de um apontador?
Observe o seguinte programa:
Main() {
int *px, int x;
*pt=10;
x=*pt;
}
O que há de errado neste programa?
Basta construir o esquema de quadros e observar:
*pt = 10;
X
Px
???????
Logo na primeira instrução percebemos que não há coerência na atribuição. Isso porque: se
px não aponta para ninguém, como poderemos atribuir um conteúdo para dentro deste
ninguém?
Devemos Ter em mente que inicialmente em um apontador não há endereço nenhum.
Se há um endereço inicial em um ponteiro, este aponta para uma posição desconhecida da
memória. Isso significa que se usarmos um ponteiro sem a sua prévia inicialização
tornamos a manipulação desse ponteiro uma provável fonte de erros graves no programa.
Situações como essas podem facilmente derrubar o programa ou mesmo o sistema
operacional .
Assim, para manipularmos apontadores, estes devem apontar sempre para um endereço de
memória previamente alocada pelo programa (através da declaração de variáveis ou
dinamicamente como veremos no mais adiante). Caso isso não aconteça estabelece-se uma
situação a usar o conteúdo de uma variável sem antes atribuir qualquer valor a esta. Por
exemplo:
int x,y;
y= x+10;
9.4
Apontadores e Vetores (Arranjos)
Na sessão anterior, para incrementarmos o conteúdo de um ponteiro em uma unidade
usando o operador ++ tivemos que usar os parênteses:
78
(*px)++;
A pergunta agora é: qual o efeito da instrução
*px++;
A resposta desta pergunta pode nos ajudar a compreender a relação entre apontadores e
vetores. Mais adiante a pergunta será respondida.
Na linguagem C, a relação entre apontadores e vetores é muito próxima. Qualquer
operação realizada com índices de vetores pode ser realizada através de apontadores. Em
geral a solução com apontadores será mais rápida.
A definição int a[10] define um vetor de tamanho 10. Ou seja, um bloco de 10 objetos
consecutivos na memória chamados respectivamente de a[0], a[1], a[2], a[3],
a[4], a[5], a[6], a[7], a[8] e a[9].
A notação a[i] refere-se ao i-ésimo elemento do vetor a partir de seu início. Considere p
um apontador para inteiro e a um vetor de 10 inteiros definidos como segue:
int *p;
int a[10];
Nesse caso,
p=&a[0];
fará com que p receba o endereço da primeira posição do vetor a. Ou seja, p irá apontar
para a posição 0 do vetor a.
Pela definição da linguagem, se p aponta para um posição de um vetor, p+1 apontará para a
próxima posição do vetor e p-1 apontará para a posição anterior do vetor.
Genericamente, se p aponta para uma posição do vetor, p- i apontará para a i-ésima posição
anterior a p e p+i irá apontar para a i-ésima posição posterior.
Sendo assim, A seqüência
p=&a[0];
p=p+9;
*p=20;
irá armazenar o valor 20 na posição a[9] do vetor.
Após a atribuição
p=&a[0];
Teriamos as seguintes correspondências:
Acesso ao
conteúdo
a[0] p
*p
a[1] p+1 *(p+1)
79
a[2]
a[3]
a[4]
a[5]
a[6]
a[7]
a[8]
a[9]
p+2
p+3
p+4
p+5
p+6
p+7
p+8
p+9
*(p+2)
*(p+3)
*(p+4)
*(p+5)
*(p+6)
*(p+7)
*(p+8)
*(p+9)
9.5
O nome do vetor é um apontador constante
Quando um vetor é criado, seu nome é convertido para um apontador para a primeira
posição do vetor.
Assim se temos a declaração
int v[10];
*v fará referência a primeira posição do vetor
*(v+i) fará referência a i-ésima posição do vetor
São então válidas expressões com o nome do vetor sendo um apontador. Por exemplo,
O fragmento abaixo,
for(i=0; i<10 i++)
v[i]=0;
Pode ser escrito assim:
for(i=0; i<10 i++)
*(v+i)=0;
Note que o comando *(v+i) =0; NÃO ALTERA O VALOR DO APONTADOR v. Não
poderia ser diferente pois, o apontador v, por ser nome de vetor, é um apontador constante.
Ou seja, o valor de v não pode ser alterado. De fato, não faria sentido alterar o valor de um
endereço de um vetor.
Dessa forma que tentem alterar o valor de um apontador constante não são válidos:
Por exemplo,
v++;
v=v+5;
v--;
v=v+i;
são comandos não aplicáveis, quando v é um vetor.
80
Todos os aspectos discutidos aqui se aplicam a vetores de outros tipos de dados. Vejamos
um exemplo com vetores de caracteres:
char str[30];
int i;
// inicialização da string str com \0
*str=’\0’;
// coloco o carcater a ná posição 10 da string
*(str+10)=’a’;
// coloco o caracter ‘b’ em 29 posições da string e o \0 na última posição
for (i=0; i<29;i++) {
*(str+i)=’b’;
}
*(str+i)=’\0’;
// leio 29 caracteres e guardo na string, colocando o \0 na última posição:
for(i=0;i<29;i++) {
*(str+i)=getchar();
}
*(str+i)=’\0’;
81
9.6
Exercícios
Verificar a corretude e determinar o que será feito nos fragmentos de código abaixo.
Leve em consideração as seguintes declarações:
int v[5], *pi,*pa, x,i;
EXEMPLO
pi=v;
*pi=30;
*pi+1=50;
Está incorreto pois na linha 3 a referência *pi+1=50 não faz sentido pois
uma referência a uma variável ou posição de vetor é necessária do lado
esquerdo da expressão. Uma possivel correção seria:
pi=v;
*pi=30;
*(pi+1)=50;
De forma que a posição v1 seria coupada pelo valor 30:
Esquema:
Situação inicial:
v
pi
v0
v0
v1
V2
v3
v4
v0
v1
V2
v3
v4
v0
30
v1
V2
v3
v4
v1
50
V2
v3
v4
pi=v;
v
pi
v0 v0
*pi=30;
v
pi
v0 v0
*(pi+1)=50;
v
pi
v0 v0
v0
30
*pa+=&pi;
a) pi=v;
*pi++=200;
pi=pi+2;
*pi=100;
c) *v=10;
v++;
*v=20;
b) v[0]=10;
v[4]=30;
pi=&v[4];
pa=&v[0];
d) scanf(“%d”,v);
if (v[0]>10){
pi=v+1;
*pi=2*(*v);
}
82
else {
*(pi+1)=v[0];
}
83
e) scanf(“%d”,v+1);
v++;
x=*v;
if (x <0) {
pa=&v;
for (i=0;i<5;i++)
*pa+1=i;
}
else {
pa=&v[4];
*pa= x+5;
}
f) pi=*v;
for(i=0;i<5;i++)
scanf(“%d”,&(pi+i));
if (*pi > *(pi+1)) {
x=pi;
*pi=*(pi+1);
*(pi+1)=x;
}
g) v[0]=70;
v[1]=40;
pi=v;
pa=v+1;
if (*pi>*pa){
pi=pa
pa=v;
printf(“%d %d”, *pi,*pa);
}
h) pi=&v[4];
v=pi-1;
*(p1-1)=10;
printf(“%d”,*v);
i) pi=&x;
pa=v;
scanf(“%d”,&x);
*pa=*pi;
*pa+1=*pi-1;
*pa+2=*pi-2;
*pa+3=*pi-3;
Considere agora as seguintes declarações:
char *ps, *pr, s1[10],s2[10];
int i;
84
j) strcpy(s1,”Aracruz”);
ps=s1;
ps++;
printf(“%c”,*ps);
l) strcpy(s1,”Aracruz”);
ps=s1;
while(*pr=*ps)!=’\0’);
Solução de problemas Usando apontadores - Exemplos Comentados
Considere o seguinte problema:
Um programa tem um espaço para armazenamento de palavras de tamanho = 100
caracteres, ou seja um vetor de 100 caracteres. As palavras armazenadas no vetor devem ser
terminadas com um \0. Construa um programa que armazene o máximo de palavras nesse
vetor.
A palavras serão lidas com a função scanf. Assim, o programa deve fornecer um apontador
para a primeira posição livre do vetor.
/*exemplo 1 : Usando um string para guardar várias palavras
O objetivo desse programa é utilizar uma única string para guardar
várias palavras.
O tamanho total da string é 20
Cada palavra ocupa o espaço correspondente ao número de caracteres + 1 do
\0
As palavras são lidas e guardadas a partir do início da string.
A leitura será feita usando um apontador para a primeira posição livre da
string
Algoritmo
1. Declaração de variáveis
2. Inicialização de Variáveis
Faça
Imprima("Escolha uma das opções abaixo:");
Imprima("Ler uma palavra - 1");
Imprima("Imprimir as palavras armazenadas - 2");
Imprima("Procurar uma palavra- 3");
Imprima("Sair - 0:");
leia(op);
se op=1 então
3. ler uma palavra
senão
se op=2 então
4. Imprimir as palavras armazenadas
senão
se op=3 então
5. Procurar uma palavra
fim se
fim se
fim se
enquanto (op<> 0)
85
Fim algoritmo
86
Implementação
dos refinamentos
Ref. 1 {declaração de variáveis}
char buffer[20], *p, *paux;
int op;
Ref. 2 { inicialização de variáveis}
p=buffer;
fim ref.
Ref. 3 { ler uma palavra }
Quando uma palavra poderá ser inserida no vetor?
Quando houver espaço
Como testar se ha espaço? Comparando os ponteiros p e buffer+19
O ponteiro p ser menor que o endereço da última posição da string
significa que há espaco ainda no buffer.
Então podemos testar:
se (p< buffer+19)
le a palavra
senão
imprima("Não há mais espaço para outras palavras");
fim se
Além disso, podemos imprimir o número de espaços vagos no vetor.
Quando subtraimos um ponteiro de outro relacionado a um mesmo vetor,
obtemos o número de espaços que existem entre eles
Assim, se pegamos o endereço da ultima posição (buffer+19) e
subtraímos de p (primeira posição livre) teremos:
buffer+19-p
reescrevendo o código acima teremos:
se (p< buffer+19)
imprima("O número de espaços vagos é , buffer+19-p)
le a palavra
senão
imprima("Não há mais espaço para outras palavras");
fim se
O ponteiro p deve ser alterado para que ele fique sempre apontando para a
próxima posição livre do vetor.
O endereço da próxima posição livre é dado por
p+tamanho(palavra lida)+1
Dessa forma, p deve ser atualizado por p+tamanho(palavra lida)
reescrevendo o código acima teremos:
se (p< buffer+19)
imprima("O número de espaços vagos é , buffer+19-p)
le a palavra
p<--p+ tamanho(palavra lida) +1
senão
87
imprima("Não há mais espaço para outras palavras");
fim se
Ref. 4{ Imprimir as palavras armazenadas}
Usando um
ponteiro auxiliar (paux) podemos percorrer o vetor
imprimindo as palavras.
Inicialmente paux aponta para a primeira posição:
paux<-- buffer
Posteriormente, o ponteiro paux deve se atualizado de forma que
aponte sempre para o início das palavras
Quando paux > = p não háverá mais palavras para serem impressas,
pois p aponta sempre para o primeiro lugar vazio de buffer
Assim podemos fazer:
paux<--buffer
Faça
se (p>paux) então
imprima(paux);
paux=paux+tamanho(paux)+1;
}
}enquanto(p>paux);
A implementação completa e dada a seguir. Como execício, verifiqueo
funcionamento da implementação da opção de pesquisa de palavras.
*/
int main(int argc, char **argv)
{ char *p,*paux, buffer[20],str[20];
int op;
p=buffer;
do{
printf("Ler uma palavra: 1\n");
printf("Imprimir as palavras: 2\n");
printf("Pesquisar uma palavra: 3\n");
printf("Sair: 0\n");
scanf("%d",&op);
if( op==1) {
if (p<buffer+19){
printf("O espaco maximo para a palavra e: %d\n Digite a palavra:" ,
buffer+19-p);
scanf("%s",p);
p = p+strlen(p)+1;
} else {
printf("Nao ha espaco para mais palavras\n");
}
}
else
if(op==2){
paux=buffer;
do{
if(p>paux) {
printf("%s\n",paux);
paux=paux+strlen(paux)+1;
}
88
}while(p>paux);
}
else
if(op==3) {
printf("Digite a palavra a ser pesquisada: ");
scanf("%s",str);
paux=buffer;
do{
if(p>paux) {
if(!strcmp(paux,str)){
printf("Palavra encontrada: %s\n",paux);
break;
}
else {
paux=paux+strlen(paux)+1;
}
}
}while(p>paux);
if (p<=paux)
printf("A palavra não existe no buffer:\n");
}
}while(op!=0);
return 0;
}
9.6.1 Mais exemplos
Copia de cadeias.
// Versão com indices
void main()
{
char s1[30],s2[30];
int i;
// versão com indices
scanf("%s",s1);
i=0;
while(( s2[i]=s1[i])!='\0')
i++;
printf("%s",s2);
getch();
}
// versão com apontadores
void main()
{
char s1[30],s2[30], *p1,*p2;
scanf("%s",s1);
p1=s1;
p2=s2;
while(( *p2=*p1)!='\0'){
p1++;
p2++;
89
}
printf("%s",s2);
getch();
}
// versão com apontadores atribuindo e incrementando
// dentro da condição do while
void main()
{
char s1[30],s2[30], *p1,*p2;
scanf("%s",s1);
p1=s1;
p2=s2;
while(( *p2++=*p1++)!='\0')
;
printf("%s",s2);
getch();
}
Notemos que o \0 é falso para C então a comparação (*p2++=*p1++)!='\0' é redundante.
Assim o código em sua versão final pode ser escrito da seguinte maneira (mínima):
// versão com apontadores atribuindo e incrementando dentro da
// condição do while
void main()
{
char s1[30],s2[30], *p1,*p2;
scanf("%s",s1);
p1=s1;
p2=s2;
while(*p2++=*p1++)
;
printf("%s",s2);
getch();
}
Comparação de strings
// versão com índices
void main()
{
char s1[30],s2[30], *p1,*p2;
int i,retorno;
scanf("%s",s1);
scanf("%s",s2);
i=0;
retorno=-1;
while(s1[i]==s2[i])
if (s1[i++]=='\0') {
90
retorno=0;
break;
}
if (retorno!=0)
retorno = s1[i]-s2[i];
if (retorno == 0 )
printf("São iguais");
else
if (retorno<0)
printf(" %s menor que %s", s1,s2);
else
printf(" %s maior que %s", s1,s2);
getch();
}
// versão com apontadores
void main()
{
char s1[30],s2[30], *p1,*p2;
int retorno;
scanf("%s",s1);
scanf("%s",s2);
p1=s1;
p2=s2;
retorno=-1;
for(; *p1==*p2;p1++,p2++)
if (*p1=='\0'){
retorno=0;
break;
}
if (retorno!=0)
retorno = *p1-*p2;
if (retorno == 0 )
printf("São iguais");
else
if (retorno<0)
printf(" %s menor que %s", s1,s2);
else
printf(" %s maior que %s", s1,s2);
getch();
}
91
10 Construção de Funções
Até o momento, os programas que temos construído são compostos de um bloco único de
declarações de variáveis e comandos: a função main. Como no programa abaixo:
int main()
{ int x,y,z;
scanf("%d%d",&x,&y);
z=x+y;
printf("%d",z);
getch();
return 0;
}
A função main (principal) é a função central de qualquer programa em C. Todo programa
em C tem de possuir uma função main e começa a execução a partir desta.
Assim, no programa acima, a primeira instrução a ser executada é
scanf("%d%d",&x,&y);
Note que scanf não é um comando da linguagem C, mas sim uma função da biblioteca
stdio (Standard Input Output – biblioteca padrão de entrada e saída). Da mesma forma
printf é uma função de stdio e getch é um função da biblioteca conio (console input
output- biblioteca de entrada e saída de console ). Assim como estas, existem dezenas de
outras funções úteis na biblioteca C.
10.1 Chamada de Função
O que acontece exatamente quando scanf é executada?
Quando o computador encontra a função scanf("%d%d",&x,&y); dizemos que houve uma
chamada da função scanf.
Uma chamada de função faz com que o computador execute um desvio na execução
direcionando-se para o código da função chamada. Uma representação do que acontece
pode ser visualizada na figura abaixo:
Desvio para a
função
e
transferência
de dados
int main() {
....
scanf("%d%d",&x,&y);
z=x+y;
...
}
int scanf(const char *format[, address, ...])
{
/* implementação de scanf */
Retorno da função
scanf para main na
execução
do
comando
return
transferência
do
valor de retorno
return ...;
}
92
10.2 Fluxo de Execução
É importante notar que no estilo de programação seqüencial que estamos considerando,
quando ocorre a chamada de uma função, a função que chamou fica interrompida até o
término da função chamada . No exemplo do início da seção, o comando
z = x+y;
só executará após o término da execução da função scanf.
10.3 Passagem de parâmetros
Na hora da chamada da função ocorre também a transferência de dados da função que
chamou (main) para a função chamada (scanf). Essa transferência de dados é chamada
de passagem de parâmetros.
Através da passagem de parâmetros, funções conseguem executar suas tarefas sobre dados
especificados na hora da chamada. Tomemos como exemplo a função scanf. Através de
seus parâmetros ela consegue identificar para que endereços de memória ela deve ler os
dados: (scanf("%d%d",&x,&y);. Assim, conseguimos usar scanf para ler dados para
qualquer endereço de variável definida no programa, bastando alterar os parâmetros na hora
da chamada da função.
10.4 Tipo de retorno da função
Quando uma função termina, esta pode transferir um valor para a função que a chamou
através do comando return. Este valor deve é determinado na implementação da função e
deve ser de um tipo definido este tipo é definido na declaração da função e é denominado o
tipo de retorno da função. No caso programa anterior, main é do tipo int por causa da sua
declaração:
int main ( ) {
... }
O valor de retorno de uma função pode ser capturado pela função chamadora através de um
comando de atribuição. Por exemplo:
Ch=getch();
O comando acima chama a função getch cuja finalidade é ler um caracter do teclado.
Quando a mesma é executada, o caracter lido é atribuído a variável Ch.
Um outra forma de utilizar o valor de retorno é em alguma comparação. Por exemplo:
if (getch() == ‘s’)
Neste caso o valor de retorno foi usado apenas para o teste e não pode mais ser recuperado
10.5 Construção de Funções
Se main é uma função e scanf também, podemos deduzir que scanf ou qualquer outra
função deve possuir características construtivas semelhante a main. Ou seja scanf, printf e
getch, por serem funções devem ser construída em um esquema parecido com a função
main.
Daí, concluímos que para construirmos funções novas seguiremos esquemas
93
similares a implementação de main, tendo um nome, um tipo de retorno, parâmetros,
variáveis e uma seqüência de comandos.
Na realidade, funções podem ser vistas como sub-programas: são construídas com as
mesmas características de um programa, tem um finalidade definida, são executadas
seqüencialmente, iniciam e terminam.
Como exemplo, vamos verificar a implementação da função copia (similar a strcpy) e sua
utilização
int main()
{
char s1[30],s2[30];
scanf("%s",s1);
copia(s2,s1);
printf("%s",s2);
getch();
}
int copia( char *s, char *t){
while(*s++=*t++)
;
return 1;
}
Da mesma forma que main, copia também possui um tipo e um nome (int copia). A função
main, nesse caso não possui parâmetros, por isso os parênteses vazios (main()). copia
possui dois parâmetros: dois apontadores para char (char *s, char *t).
Na função main são declaradas variáveis (char s1[30],s2[30];). copia não possui nenhuma
variável interna mas poderia ter.
copia tem a função de copiar o conteúdo de uma string para a outra. Os ponteiros para as
strings que ela deve manipular devem ser passadas para ela através dos parâmetros na hora
da chamada da função: (copia(s2,s1);) na função main. Na passagem de parâmetros em C
os dados transferidos para a função são como “cópias” dos originais. Ou seja, quando s1 e
s2 são colocados na lista de parâmetros de copia, o que é transferido é o valor de dentro de
s1 e s2, nesse caso os endereços iniciais dessas duas strings.
Em posse dos endereços das duas strings, a função copia pode manipular as variáveis cujos
endereços foram transferidos.
Discutiremos a passagem de parâmetros mais adiante.
Um programa em C pode possuir várias funções e não só a função main. Na verdade, a
maioria dos programas em C é formado por um grande conjunto de pequenas funções e não
por funções grandes. Isso porque quando repartimos o nosso programa em pequenas
funções tornamos o código mais compacto, legível e mais fácil de se depurar. Todas essas
são vantagens da utilização de funções mas podemos dizer que a maior vantagem é
reutilização de funções, ou seja, desde que uma função esteja implementada, podemos
utilizá-la quantas vezes quisermos somente fazendo uma chamada da mesma.
94
A divisão de um programa em funções acompanha a definição do algoritmo e da construção
de refinamentos. Em geral uma função é a implementação de um refinamento.
Vejamos o exemplo a seguir:
Construir um programa que realize as 4 operações aritméticas entre dois números:
Algoritmo operacoes
Declare a,b,c:numérico
op: literal
Leia(a,b);
Leia(op)
Se (op=’+’) então
1 Soma a e b e guarda em c
Fim se
Se op=’-‘ então
2 Subtrair b de a e guarda em c
Fim se
Se op=’*’ então
3 Multiplica a e b e guarda em c
Fim se
se (op=’/’ ) então
4 divide a por b e guarda em c
fim se
imprime c
fim do algoritmo
Cada um dos refinamentos pode ser construído em uma função. A implementação ficaria
assim:
float soma(float x,float y){
float r;
r=(x+y);
return r;
}
float subtrai(float x,float y){
return (x-y);
}
float multiplica(float x,float y){
return (x*y);
}
float divide(float x,float y){
return (x/y);
}
int main(int argc, char **argv)
{ float a,b,c;
char op;
printf("Digite os números:");
scanf("%f%f",&a,&b);
95
printf("Digite a operação:");
op=getch();
if (op=='+')
c = soma(a,b);
if (op=='-')
c = subtrai(a,b);
if (op=='*')
c = multiplica(a,b);
if (op=='/')
c = divide(a,b);
printf("%f", c);
getch();
}
O programador poderia pensar em construir uma função genérica para as operações. Nesse
caso ele deverá informar para a função (através dos parâmetros) qual a operação que deve
ser feita:
float opera(float x,float y, char opcao){
switch(opcao){
case '+' : return(x+y);
case '-' : return (x-y);
case '*' : return (x*y);
case '/': return (x/y);
default: return 0.0;
}
}
int main(int argc, char **argv)
{ float a,b,c;
char op;
printf("Digite os números:");
scanf("%f%f",&a,&b);
printf("Digite a operação:");
op=getch();
c= opera(a,b,op);
printf("%f", c);
getch();
}
Exercícios
1. determine para as funções de biblioteca da linguagem C abaixo em qual biblioteca se
encontram, qual a sua finalidade, quais são os seus parâmetros e seus tipos, e qual o
tipo de retorno da função:
- strcmp
- atoi
- sqrt
- atof
- fread
- ceil
- fgets
- floor
- getc
- strcat
- getchar
- pow
96
-
sin
cos
fcvt
-
itoa
tan
2. Quais parâmetros seriam necessários e qual o valor de retorno para a implementação
das seguintes funções:
a) Função que verifica se um número é menor que 10
b) Função que verifica se um caractere está entre os dígitos ‘0’ e ‘9’
c) Função que conta quantas vogais existem em uma string
d) Função que calcula a média de um vetor contendo n números
e) Função que calcula o fatorial de um número
3. Construa uma função maior que identifique e retorne o maior entre 3 números. Use a
função em um programa que leia 3 números e imprima o maior dentre os números
lidos.
4. Construa um programa que leia um número e verifique se o número é par ou impar. A
função deve retornar 1 se o número for para e 0 se for impar. Use a função para
construir um programa que leia 10 números e imprima apenas os números pares
5. Construa uma função que calcule o fatorial de um número. Use a função para calcular a
soma dos 100 primeiros termos da série: 1/1! +1/2! +1/3!...
6. 4. Construa uma função para calcular a média final dos alunos. A média é dada a partir
de 3 notas: n1,n2,n3, de acordo com as seguinte fórmula:
7. n1*0.3+n2*0.4+n3*0.3
Use a função acima em um programa que leia 20 conjuntos de 3 notas e calcule a média
de cada conjunto.
97