Linguagem de Programação1
Linguagem de Programação1
Linguagem de Programação1
PROGRAMAÇÃO
autor
REGINALDO APARECIDO GOTARDO
1ª edição
SESES
rio de janeiro 2015
Conselho editorial regiane burger; roberto paes; gladis linhares.
Revisão linguística joice karoline vasconcelos dos santos, aline zaramelo simões e
amanda duarte aguiar
Todos os direitos reservados. Nenhuma parte desta obra pode ser reproduzida ou transmitida
por quaisquer meios (eletrônico ou mecânico, incluindo fotocópia e gravação) ou arquivada em
qualquer sistema ou banco de dados sem permissão escrita da Editora. Copyright seses, 2015.
isbn: 978-85-5548-155-0
cdd 005.133
Prefácio 7
Bons estudos!
7
1
Conceitos
Fundamentais sobre
Programação
Recentemente um filme chamado “O Jogo da Imitação” (The Imitation Game,
2014) chamou a atenção do público, particularmente a minha, pois mostra
um pouco da história do matemático Alan Turing, considerado um dos “pais”
da ciência computação. Não é um documentário, é uma ficção e eu não vou
contar a história, claro! (rs). Mas, o que posso dizer é que, na minha humilde
opinião, mostra o quanto a computação pode ser importante, o quanto a “pro-
gramação de artefatos computacionais” pode ser relevante para a sociedade.
E, claro, mostra Nerds assumindo o papel de heróis! (o máximo, com certeza).
Neste capítulo, vou usar um pouco da lógica do Alan Turing para começarmos
nossa discussão à respeito de programação de computadores!
OBJETIVOS
Neste capítulo, os objetivos principais são:
10 • capítulo 1
1.1 Conceitos Fundamentais e Máquinas de
Turing
Dados Dados
de entrada de saída
Processamento
Este modelo de único propósito atua como uma caixa preta: o que há nele,
normalmente, não é visto por quem o usa. Além disto, este modelo é usado,
como o próprio nome diz, para resolver um único problema.
Hoje em dia você verá adiante que a noção de caixa preta pode atuar so-
bre modelos de propósito geral ou específico. Mas, voltaremos nesta parte do
assunto.
Vamos a um exemplo de computador de propósito único. Na figura 1.2 você
pode observar uma máquina chamada Hello. Nesta máquina você coloca uma
nota de dois reais (R$ 2,00) e aperta um botão verde. Após isto cai uma lati-
nha de refrigerante para você. Se você apertar o botão vermelho seu dinheiro é
devolvido.
• 11
capítulo 1
Hello
Dim Dim aqui!
Botão verde
Reac
Botão vermelho
12 • capítulo 1
Dados Dados
de entrada de saída
Processamento
Programa
Vamos agora usar exemplos diferentes. Veja na figura 1.4 que ao trocar o
conjunto de instruções (o programa) o computador devolve resultados diferen-
tes para o mesmo conjunto de entrada.
Somar Subtrair
• 13
capítulo 1
Já na figura 1.5, veja que dados de entrada diferentes produzem resultados
diferentes para o mesmo programa. A ideia do programa, seu conjunto de ins-
truções, se mantém (que é a soma dos dois números) e varia apenas pela varia-
ção nos dados de entrada.
Somar Somar
CONEXÃO
O trabalho original do Alan Turing pode ser lido aqui:
http://www.cs.virginia.edu/~robins/Turing_Paper_1936.pdf (em9/7/2015).
Dentro desse contexto inicial, também recomenda-se o filme "O Jogo da Imitação" (The
Imitation Game - 2014), sobre a vida de Alan Turing.
14 • capítulo 1
No entanto, não usaremos uma Máquina de Turing e não criaremos progra-
mas para Máquinas de Turing.
Mas, por que não?
ULA
Entrada /
Memória
Saída
UC
Dados Dados
de entrada de saída
Como pode ser visto na figura 1.6, os computadores construídos com base
no modelo de von Neumann possuem quatro componentes principais:
• 15
capítulo 1
num grupo de comandos básicos, blocos de construção, e gerencia como se dá
a execução destes comandos com as demais unidades.
• Memória: é o local onde os programas e os dados são armazenados du-
rante a execução do programa, ou seja, o processamento dos dados.
• Unidade de Entrada e Saída: esta unidade é responsável por receber da-
dos externos ao computador e enviar de volta os resultados obtidos.
1. Leia um número
2. Leia outro número
3. Multiplique o primeiro pelo segundo
4. Mostre o resultado
16 • capítulo 1
Para escrevermos nossos programas então precisaremos aprender uma lin-
guagem de programação que siga conceitos que entendemos algoritmicamen-
te. Vamos entender então o que há nesse processo de escrita de programas.
capítulo 1 • 17
#include <stdio.h>
#include <stdlib.h>
18 • capítulo 1
É mais fácil de entender o código da figura 1.8, não? Mesmo não sabendo
qual linguagem há lá é possível ter uma ideia do propósito dele. Veja pela figura
1.9 que o programa objeto, traduzido, funciona como nossos exemplos ante-
riores para computadores de propósito geral. Ele é inserido na máquina que
transformará dados de entrada em dados de saída com base nas instruções do
programa traduzido.
Programa Programa
Compilador
fonte objeto
Dados Dados
Máquina
de saída de entrada
O uso dos compiladores para a criação de programas insere para nós, desen-
volvedores, dois conceitos de tempo:
• Tempo de Compilação
• Tempo de Execução
• 19
capítulo 1
tempo de execução, pois o usuário final, dificilmente terá recursos para lidar
com estes problemas.
Resumindo então:
“Programas são a representação de algoritmos que foram escritos em al-
guma linguagem de programação e compilados para uma linguagem alvo que
permite ser executada num dispositivo computacional”
Para termos um algoritmo é preciso:
CONEXÃO
Você pode baixa-lo em (acesso em março de 2015):
http://www.bloodshed.net/dev/devcpp.html
Nós estamos usando a versão 5.0 beta (4.9.9.2) com Mingw/GCC 3.4.2 (que é
a versão do compilador). Os meus testes foram feitos num ambiente com Win-
dows 8.1 64bits.
20 • capítulo 1
Como você já notou, nossos programas serão escritos usando Linguagem C.
Em breve falaremos mais dela e teremos uma apresentação mais formal!
CONEXÃO
Você pode usar também os códigos num compilador online (acesso em março de 2015):
http://www.tutorialspoint.com/compile_cpp_online.php
Este compilador online usa o GCC v4.8.3:
https://gcc.gnu.org/onlinedocs/gcc-4.1.2/gcc/
Para criar um novo projeto no DEV você precisa clicar em arquivo -> novo ->
projeto. Escolha um projeto do tipo Console Application (a entrada e saída de
nosso projeto será via terminal de acesso – console). Escolha também um “Pro-
jeto C”. Dê um nome ao seu projeto e pronto! Podemos começar!
capítulo 1 • 21
Assim que você salva o projeto a primeira tela que se abre é um arquivo
main.c. Este arquivo, normalmente, já conta com uma estrutura base de um
programa em C. Uma estrutura mínima, como pode ser visto na figura 1.11.
Código 1.1
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[]) {
printf("Hello C World\n");
return 0;
}
Agora podemos realizar nosso primeiro teste clicando no Menu Executar, depois “Com-
pilar & Executar”. Se for pedido, salve o arquivo main.c na mesma pasta do seu projeto.
Você verá a tela a seguir, na figura 1.12.
22 • capítulo 1
Figura 1.12 – Resultado do nosso primeiro programa.
Caso o processo seja rápido e esta tela não apareça para você altere seu có-
digo para o seguinte:
Esta alteração que fizemos na linha 5 do código foi para os casos em que o
compilador não coloca um mecanismo de parada (pause) automático para o
usuário. O getchar() serve para leitura de caracteres, mas, por hora, para aguar-
dar uma digitação do usuário para seguir.
Quando você precisar adicionar arquivos ao seu projeto (criar uma bibliote-
ca.h) você fará como indicado na figura 1.13.
capítulo 1 • 23
Figura 1.13 – Inserindo novos arquivos ao projeto.
Para isto, você clicará com o botão direito do mouse no projeto e adicionará
novos arquivos. Usaremos este recurso mais adiante.
Agora vamos ver como usar o compilador online. Veja na figura 1.14 que
ele possui já integrado um console na parte de baixo onde podemos digitar
comandos.
24 • capítulo 1
Minha sugestão é que você feche o arquivo e o exclua, clicando nele com o
botão direito. Depois crie novamente outro arquivo, mas, desta vez, o renomeie
para main.c (você também pode apenas renomear o arquivo atual para main.c
se desejar).
Escreva o código 1 alterado no corpo do seu projeto.
Após isto, clique na região do console (a região abaixo do editor) e digite o
seguinte comando:
Feito isto, caso não ocorram erros, você digitará o próximo comando (caso
ocorram erros a dica é: não copie e cole o código, digite-o novamente, pois es-
tamos usando sistemas diferentes para compilação. Os testes no DEV estão no
Windows, os testes online são em Linux!):
./main
Esclarecendo então, nossos testes no DEV são no Windows 8.1, como men-
cionei. O compilador online simula um ambiente Linux. Logo, os comandos
que digitamos são válidos se você usar um Sistema Operacional Linux.
capítulo 1 • 25
1.5 A Linguagem C
A Linguagem C foi criada por Dennis M. Ritchie e Ken Thompson no laborató-
rio Bell em 1972. Ela foi baseada na linguagem B, de Thompson. A definição da
linguagem C está no livro The C programming language, escrito por Brian W.
Kerninghan e Dennis M. Ritchie.
Rapidamente a linguagem C se tornou uma das linguagens mais populares
entre os programadores, pois é poderosa, portátil, flexível e foi desenhada para
ser de propósito geral, estruturada, imperativa, procedural e padronizada.
CONEXÃO
Há várias implementações para a Linguagem C e há um padrão ANSI para ela:
• http://www.le.ac.uk/users/rjm1/cotter/page_47.htm
• http://en.wikipedia.org/wiki/ANSI_C
Código 1.1
Algoritmo
Ler um valor para a variável A
Ler um valor para a variável B
Efetuar a adição das variáveis A e B, atribuindo o
resultado a variável X
Apresentar o valor da variável X após a operação de
adição
26 • capítulo 1
A tradução deste algoritmo para linguagem C fica assim:
Código 1.2
#include<stdio.h>
main()
{
int x;
int a;
int b;
scanf("%d", &a);
scanf("%d", &b);
x = a + b;
printf("%d\n",x);
}
Código 1.3
[inclusão de bibliotecas]
[declaração de dados globais]
[declaração dos protótipos de funções]
void main (void)
{
[corpo do programa principal]
}
• 27
capítulo 1
Código 1.4
#include <stdio.h>
int main(void)
{
printf(“Programar em C eh facil.\n");
return 0;
}
28 • capítulo 1
Para usar estes tipos básicos em C é preciso declarar as variáveis antes de
usá-las.
Código 1.5
#include <stdio.h>
int main(void)
{
int soma;
soma = 500 + 15;
printf(“A soma de 500 e 15 eh %d\n", soma);
}
Veja pelo código 1.X que, na linha 4, há a declaração da variável “soma” ca-
paz de armazenar um valor inteiro. Na linha 5 temos uma operação de atribui-
ção na qual soma recebe o resultado de outra operação: a adição entre os valo-
res 500 e 15.
Por fim, o printf é usado para apresentar o resultado na saída padrão com
uma formatação de saída para número inteiro. Ele processa o texto inserido,
substituindo o %d pelo valor da variável soma. Para que a substituição seja pro-
cessada corretamente é preciso indicar que soma é do tipo inteiro. Logo, %d é
chamado de máscara de formatação de saída.
Temos também os seguintes caracteres de formatação que podem ser usados:
• %c – Caractere
• %d - Decimal com sinal
• %i - Inteiro com sinal
• %f - Números reais
• %o – Octal
• %x – Hexadecimal
• %s – String - conjunto de caracteres
• %s – Cadeia de caracteres
• %e – precisão dupla
• %i – inteiro decimal com sinal
• %E – Notação científica (E maiúsculo)
• %p – ponteiro (endereço)
capítulo 1 • 29
• %n – ponteiro (inteiro)
• %% - imprime o caractere %
• Media_dos_Valores
• Nota1
• Nome_do_usuario
Todo bom programa em C deve ser bem documentado. Isto é uma boa polí-
tica de desenvolvimento e manutenção.
30 • capítulo 1
Código 1.6
/* Este programa calcula a soma de dois
numeros inteiros */
#include <stdio.h>
int main(void)
{
int soma; // Variável que guarda a soma
soma = 500 + 15; // Soma dos valores
printf(“A soma de 500 e 15 eh %d\n", soma);
return 0;
}
Veja no código 1.6 que os comentários podem ser feitos usando /* e */ para
múltiplas linhas e // para comentário de linha única a partir das barras.
No código 1.7 temos agora o uso dos diversos tipos de variáveis.
Código 1.7
#include <stdio.h>
int main(void) {
int soma;
loat dinheiro;
char letra;
double pi;
soma = 10; // Atribuir um inteiro
dinheiro = 2.21; // Atribuir um loat
letra = ´A´; // Atribuir um caracter
pi = 31415E-4; // Eis o pi!
printf(“Aqui esta um inteiro (%d) e um ”, soma);
printf(“número de ponto lutuante (%f)\n”,
dinheiro);
printf(“Eis a letra ´%c´ e o valor de pi: %e\n”, letra,
pi);
return 0;
}
• 31
capítulo 1
Nas linhas 3 a 7 declaramos 4 tipos de variáveis diferentes. Você pode ver pe-
los códigos nas linhas 11 a 14 que usamos máscaras diferentes para as variáveis.
A seguir, veja na tabela o conjunto de palavras reservadas da linguagem C:
-2.147.483.648 a
4 int
2.147.483.647
-2.147.483.648 a
4 signed int
2.147.483.647
32 • capítulo 1
TAMANHO EM TIPO ESCALA
BYTES
-2.147.483.648 a
4 long int
2.147.483.647
-2.147.483.648 a
4 signed long int
2.147.483.647
Padrão:
tipo_de_dado lista_de_variáveis;
Ex:
o int idade, valor;
capítulo 1 • 33
Declaração com inicialização da variável:
Código 1.8
int main ()
{
int valor;
}
Já o código a seguir mostra uma variável que poderá ser acessada em qual-
quer região do programa, pois foi declarada fora do escopo da função. Esta va-
riável é chamada de variável global.
Código 1.9
#include <stdio.h>
int numero;
int main ()
{
numero = 20;
putchar (numero);
}
34 • capítulo 1
• Real (float,double): 23.56 - número com componente fracionário;
• Strings (char vet[14]): "Estácio" - caracteres envolvidos por aspas duplas.
• \n - nova linha;
• \a - beep sonoro;
• \0 - fim da cadeia de caractere.
.c .h .obj .exe
Compilação Link-edição
+
Bib. da linhagem
capítulo 1 • 35
1.8 Operadores
Um dos principais operadores na linguagem C é o operador de atribuição “=”. É
com ele que você “atualiza” o valor de uma variável na memória.
Código 1.10
#include <stdio.h>
void main ()
{
int Inteiro = 15;
char primeiroCaracter = ‘d’
loat mesada = 210.48;
}
A seguir vamos listar e agrupar por tipo os principais operadores que usare-
mos neste material.
OPERADOR DESCRIÇÃO
* Multiplicação
/ Divisão
% Resto
+ Adição
- Subtração
36 • capítulo 1
Por exemplo, o código a seguir usa o operador de resto (%) para calcular qual
a sobra da divisão de dois inteiros (10 e 3). O resultado do código será 1.
Código 1.11
#include <stdio.h>
int main ()
{
int var1 = 10;
int var2 = 3;
int var3 = var1 % var2;
OPERADOR DESCRIÇÃO
== Igual
!= Diferente
capítulo 1 • 37
Por exemplo, veja no código 1.12, na linha 5, que “perguntamos” se 10 é
maior que 5, usando o operador >. O resultado será 1.
Código 1.12
#include <stdio.h>
#include <stdlib.h>
++ (incremento de 1)
-- (decremento de 1)
38 • capítulo 1
O formato de utilizá-los é o seguinte:
(expressão_lógica) && (expressão_lógica)
(expressão_lógica) || (expressão_lógica)
!(expressão_lógica)
Por exemplo:
• 39
capítulo 1
1.8.5 Operador condicional (ternário)
x = (var==0) ? 2 : 3;
if( var == 0 )
x = 2;
else
x = 3;
40 • capítulo 1
1.8.7 Operador sizeof
Como já foi visto anteriormente, cada tipo de dado possui um tamanho diferen-
te em bytes, ou seja, podem ocupar mais ou menos espaço na memória.. Desta
forma, o operador sizeof retorna o número de bytes ocupados pelo operando,
que pode ser uma variável ou um tipo genérico de dado.
Por exemplo:
capítulo 1 • 41
No código 1.14 podemos ver o uso da variável global que será constante (não
poderá ser alterada) na linha 2. A lógica do programa envolve calcular a área de
um círculo. Veja na linha 7 que usamos o printf apenas com “série de controle”.
Na linha 9 usamos o printf também com lista de argumentos, no caso o raio. O
valor de raio será substituído na impressão no local onde consta %5.2f. Assim,
raio terá uma máscara para número de ponto flutuante. O valor 5.2 indica que
o número deverá indicar até 5 dígitos sendo 2 depois do ponto decimal (parte
fracionária do número).
Código 1.14
#include <stdio.h>
const loat pi = 3.1415;
int main ()
{
loat area, raio = 3;
area = pi * raio * raio;
printf(“\nCalculo -> area = pi*raio*raio”);
42 • capítulo 1
Para a leitura dos dados do usuário é possível usar um conjunto vasto de
funções. Uma delas é o scanf que tem a seguinte estrutura:
scanf("série_de_controle",lista_de_endereços_dos_argumentos)
No scanf os dados são lidos de formata fomatada, ou seja, já com suas res-
pectivas representações (tipo de dados). Os formatos usados na série de con-
trole da função scanf são semelhantes aos formatos usados pela função printf.
Temos o getchar() que espera até que seja digitado um caractere no teclado.
Então ele mostra o caractere na tela e sai do processo de entrada de dados.
Temos getch() que efetua a leitura de um caractere do teclado sem ecoar o
caractere na tela e o gets() que efetua a leitura de um string de caracteres digita-
do por meio do teclado até que seja digitado <ENTER>. É importante citar que
o caractere da tecla <ENTER> não faz parte do string e no seu lugar é colocado o
caractere de fim de cadeia ('\0').
Para escrita na saída padrão há ainda o putchar() que escreve um caractere a
partir da posição corrente do cursor na tela. E o puts() que escreve um string na
tela seguido por uma nova linha ('\n'). O puts somente opera com argumentos
string (cadeias de caracteres).
Código 1.15
#include <stdio.h>
int main ()
{
char nome [10];
int anonascimento, idadeatual;
printf ("\nQual e o seu nome: ");
gets(nome);
printf("Em que ano voce nasceu: ");
scanf("%d", &anonascimento);
idadeatual = 2003 - ano nascimento;
printf("%s, voce tem em %d anos", nome, idadeatual);
getchar();
}
capítulo 1 • 43
Veja por exemplo no código 1.15 que declaramos uma variável caractere na
linha 4. Por hora, não entraremos no detalhe dos [20], pois veremos isto adian-
te em vetores. Para leitura do nome usamos, na linha 7, o gets, que retornará o
conteúdo lido do usuário. Para leitura do ano de nascimento usamos o scanf.
O & (e comercial) é muito importante, pois indica que o valor a ser lido será
colocado num endereço de memória. Em linguagem C é comum indicar o en-
dereço da memória e não apenas a variável em si. Falaremos mais disto e tam-
bém sobre conceitos de passagem de parâmetro por valor e por referência.
Após o usuário clicar em algo a aplicação finalizará (devido ao uso do
getchar()).
Agora, vejamos como fica a memória (ou uma parte dela) enquanto o usuá-
rio interage com a aplicação.
Imagine que cada “caixinha” ou “casinha” na memória forneça o espaço de
1 byte, conforme esquematizado na figura 1.17A. Repare que a numeração da
direita simboliza o que seriam os endereços de memória, avançando de 1 em 1
byte. Sendo assim, a variável nome precisará de 10 bytes de espaço organizados
por índices numerados de 0 a 9 conforme os números da esquerda (cada po-
sição pode receber um caractere diferente compondo a variável nome. O tipo
char ocupa 1 byte para cada caractere. As variáveis ano nascimento e idade atu-
al ocuparão 4 bytes cada uma, pois este é o tamanho do tipo inteiro em C (int).
0 12
1 13
2 14
3 15
4 16
Nome[10]
5 17
6 18
7 19
8 20
9 21
22
23
Anonasamento
24
25
26
27
Idade atual
28
29
44 • capítulo 1
Após a execução da linha 7 do programa 1.15, se o usuário digitar MARIA en-
tão o conteúdo da memória ficará como a figura 1.17B mostra. Perceba que, por
mais que o nome tenha no máximo 10 valores de char, MARIA ocupa 6 posições
(5 de conteúdo mais o \0 no final que marca o término na string). Vale ressaltar
aqui, que na verdade os valores armazenados não são as letras, mas código nu-
mérico referente a cada uma delas.
0 M 12
1 A 13
2 R 14
3 I 15
4 A 16
Nome[10]
5 17
6 18
7 19
8 20
9 21
22
23
Anonasamento
24
25
26
27
Idade atual
28
29
capítulo 1 • 45
0 M 12
1 A 13
2 R 14
3 I 15
4 A 16
Nome[10]
5 17
6 18
7 19
8 20
9 21
22
23
Anonasamento 1990
24
25
26
27
Idade atual
28
29
REFLEXÃO
Neste capítulo nós vimos conceitos fundamentais sobre programação de computadores.
Aprendemos a importância de algoritmos, linguagens de programação e vimos também qual
será nossa linguagem alvo e nossos ambientes de teste. Conhecemos um pouco da lingua-
gem C, seus operadores e um pouco acerca do processo de compilação. Na próxima unidade
avançaremos no estudo da linguagem C e veremos opções de comandos para escrever
programas mais complexos.
LEITURA
Para complementar seu aprendizado computação, programação e linguagem C sugiro os
seguintes links em inglês:
• http://en.wikiversity.org/wiki/Introduction_to_Computers Acesso em março de 2015.
• http://www.cprogramming.com/ Acesso em março de 2015.
Também sugiro a leitura de livros em português como:
46 • capítulo 1
• KERNIGHAN, B. W.; RITCHIE, D. M. C: a linguagem de programação, padrão ANSI. Rio de
Janeiro: Campus, 1995. 289p.
E também sugiro os seguintes links em português
• http://pt.wikipedia.org/wiki/C_%28linguagem_de_programa%C3%A7%C3%A3o%29
Acesso em março de 2015.
• https://www.inf.pucrs.br/~pinho/LaproI/IntroC/IntroC.htm Acesso em março de 2015.
REFERÊNCIAS BIBLIOGRÁFICAS
KERNIGHAN, B. W.; RITCHIE, D. M. C: a linguagem de programação, padrão ANSI. Rio de Janeiro:
Campus, 1995. 289p.
KELLEY, A.; POHL, I. A Book on C: Programming in C. 4. ed. Boston: Addison Wesley, 1997. 726p.
ZIVIANI, N. Projetos de algoritmos: com implementações em Pascal e C. São Paulo: Pioneira
Thomson, 2002. 267p.
ARAÚJO, J. Dominando a Linguagem C. 1. ed. Ciência Moderna, 2004, 146p.
CORMEN, T. H.; LEISERSON, C. E.; RIVEST, R. L.; STEIN, C. Algoritmos: teoria e prática. Rio de
Janeiro: Campus, 2002. 916p.
FORBELLONE, A. L. Lógica de Programação. 2. ed. São Paulo: Makron Books, 2000. 195p.
KNUTH, D. E. The Art of Computer Programming, Fundamental Algorithms. 3. ed. Boston:
Addison Wesley, 1997. 672p. (vol 1)
SEBESTA, R. W. Conceitos de Linguagens de Programação. 4. ed. Porto Alegre: Bookman, 2000.
624p.
capítulo 1 • 47
48 • capítulo 1
2
Controle de Fluxo,
Tomada de Decisão
e Funções
Neste capítulo veremos uma série de comandos que nos permitem programar
a resolução dos mais variados tipos de problemas. Tomar decisões, por exem-
plo, é algo que fazemos rotineiramente. Com programas de computador não
é diferente. Veremos estruturas básicas que, juntas, nos permite criar gran-
des e complexas soluções.
OBJETIVOS
Neste capítulo, os objetivos principais são:
• Controle de Fluxo
• Tomada de decisão
• Laços
• Seleção
• Funções
• Pilha de execução
• Ponteiros de variáveis
• Passagem por valor e por referência
50 • capítulo 2
2.1 Controle de Fluxo
Existem vários tipos de comandos na linguagem C que controlam o fluxo de
suas instruções. O controle de fluxo serve para que o computador consiga fazer
as instruções parcialmente, através de avaliações de condições. Na resolução
de problemas é muito comum em que uma parte de código seja executada ape-
nas se uma condição for verdadeira ou falsa. Também é comum que a aplica-
ção passe repetidas vezes em um trecho de código, evitando a cópia do mesmo
código em vários trechos da aplicação (o que seria trabalhoso demais). Alguns
grupos de comandos também são:
• Você quer saber se certa variável tem valor numérico maior que outra
variável;
• Você quer saber se um conteúdo é formado por letras;
• Você quer saber se um número é ou não divisível por outro;
capítulo 2 • 51
• Você quer decidir em qual rua virar dependendo da distância a ser
percorrida.
Com exceção do último exemplo, são todos casos de blocos básicos de cons-
trução de tomada de decisão. Para resolver o problema no último exemplo você,
com certeza, usará blocos básicos em sua programação.
São estes blocos básicos que aprenderemos aqui!
No uso da tomada de decisão você deverá escrever o código de tal forma que
forneça a “habilidade necessária” para seu programa (ou algoritmo) tomar a
decisão de qual rumo seguir de acordo com as condições.
Na da linguagem C, os comandos mais comuns utilizados na programação
de tomada de decisão são do tipo if (se), while (enquanto), switch-case (escolha,
caso), for(para), do-while(faça enquanto).
2.2.1 O Comando If
Código 2.1
if (condição)
{
Sequencia de comandos;
}
52 • capítulo 2
No código 2.2, temos um exemplo de um programa que consegue ler um
número inteiro digitado pelo usuário e informar se o número é maior do que 5:
Código 2.2
#include <stdio.h>
#include <stdlib.h>
int main()
{
int num;
if(num > 5)
printf("O Numero eh maior do que 5\n");
system("pause");
return 0;
}
Agora imagine um programa que pega sua idade e lhe informa se você é
maior de idade, e logo depois também te deixa informado que você pode adqui-
rir uma carteira de habilitação. Veja o código 2.3.
Código 2.3
#include <stdio.h>
#include <stdlib.h>
int main()
{
int idade;
system("pause");
return 0;
}
capítulo 2 • 53
Nos exemplos acima, percebe-se o uso do comando if, usando condições
simples para exemplificar o uso de tomada de decisão. Neste outro programa,
no código 2.4, o usuário digita números que o programa avaliará. A avaliação
feita informa se o primeiro número é maior do que o segundo número.
Código 2.4
#include <stdio.h>
#include <stdlib.h>
int main()
{
int num1;
int num2;
system("pause");
return 0;
}
54 • capítulo 2
2.2.2 O Comando If-Else
O comando if else é uma estrutura de decisão que decide qual dentre duas se-
quências de comandos será executada. O comando else pode ser entendido
como sendo um complemento do comando if, auxiliando o comando na tarefa
de escolher dentre os vários caminhos a ser seguido dentro do programa. Pode-
mos pensar no comando else como a negação do comando if, ou seu inverso.
Mas, devemos tomar bastante cuidado com comandos if-else compostos, que
veremos mais adiante. A sintaxe do comando if-else é:
Código 2.5
if(condição)
{
Sequencia de comandos;
}
else
{
Sequencia de comandos;
}
capítulo 2 • 55
Código 2.6
#include <stdio.h>
#include <stdlib.h>
int main()
{
int num;
if(num % 2 == 0)
printf("O numero: %d, eh par.\n", num);
else
printf("O numero: %d, eh impar.\n", num);
system("pause");
return 0;
}
Veja que a coleta dos dados é feita na linha 8. O usuário digitará o número a
ser checado. Depois, na linha 10 é feito cálculo do resto da divisão do número
por 2. Sabemos que todo número par tem resto zero quando dividido por 2 e
todo número ímpar tem resto 1. É esta avaliação que também é feita na linha
10.
Isto é importante! Na mesma linha 10 são realizadas 2 instruções: o cálculo
do resto da divisão do número por 2; a avaliação lógica deste resto, checando se
o mesmo é igual à 0.
Como tanto no if quanto no else há apenas um comando, não usamos o de-
limitador de bloco ({ e } ).
Agora, vamos a outro exemplo: pense que você está jogando um jogo eletrô-
nico e um inimigo aproxima-se de você e te acerta um golpe; como seria o tra-
tamento dessa remoção de sua vida pelo valor do golpe do inimigo? Podemos
ver este exemplo em um simples código abaixo, utilizando tomada de decisão.
56 • capítulo 2
Código 2.7
#include <stdio.h>
#include <stdlib.h>
int main()
{
int vida;
int dano;
if(vida > 0)
printf("O jogador levou: %d, e icou com %d
de vida.\n", dano, vida);
else
printf("O jogador morreu com o dano: %d",
dano);
system("pause");
return 0;
}
No código acima podemos notar que existe uma condição para informar a
quantidade de vida que ainda resta no jogador (linhas 8 e 9).
A partir da linha 17 avaliamos se a vida do jogador for maior que zero. Se
for verdadeira esta afirmação o programa irá informar ao usuário a quantidade
do dano que foi dado ao jogador e também a vida que lhe resta, senão ele irá
informar que o jogador morreu e a quantidade de dano que fez aquele jogador
morrer.
capítulo 2 • 57
Agora o usuário pode informar um número para o programa, e assim, ele
informa para o usuário se o número informado é maior, menor ou igual a zero.
Dentro do código será mostrado o uso de várias condições em um mesmo pro-
grama. No código podemos ver três condições para verificar se irá ou não execu-
tar a sequência de comandos em cada condição.
Código 2.8
#include <stdio.h>
#include <stdlib.h>
int main()
{
int num;
if(num == 0)
printf("O numero é 0.\n");
else if(num > 0)
printf("O numero %d é maior que 0.\n",
num);
else
printf("O numero %d é menor que 0.\n",
num);
system("pause");
return 0;
}
58 • capítulo 2
Isto ocorre no nosso exemplo, pois a avaliação do número tem três respos-
tas possíveis. Logo, o simples uso do comando if-else não resolveria nosso pro-
blema. É preciso “quebrar” uma estrutura lógica binária avaliando primeiro se
o número é zero, caso não seja então OU ele é maior do que zero OU ele é menor
do que zero.
system("pause");
return 0;
{
capítulo 2 • 59
2.2.4 Laço ou Loop
O loop while funciona da seguinte forma: ele testa uma condição; caso a condi-
ção seja verdadeira, o código dentro da estrutura de repetição é executado e o
teste é repetido ao final da execução. Quando a condição for falsa, a execução
do código continua logo após a estrutura de repetição, sua sintaxe é definida
assim:
Código 2.10
while(condição)
{
Sequencia de comandos;
}
//Instrução logo após estrutura de repetição
Código 2.11
#include <stdio.h>
#include <stdlib.h>
int main()
{
int num1;
int num2;
60 • capítulo 2
scanf("%d", &num1);
system("pause");
return 0;
}
Usando while, você também pode fazer loops infinitos, usando uma condi-
ção que é sempre verdadeira, como "1 == 1" ou simplesmente "1".
Código 2.12
#include <stdio.h>
#include <stdlib.h>
int main()
{
int num1;
while(1)
{
printf("\nDigite um numero: ");
scanf("%d", &num1);
printf("Seu numero é: %d", num1);
}
system("pause");
return 0;
}
capítulo 2 • 61
2.2.6 O Loop do While
O loop "do ... while" é exatamente igual ao "while" exceto por uma observação:
a condição é testada depois do bloco, o que significa que o bloco é executado
pelo menos uma vez. A estrutura do ... while executa o bloco, testa a condição e,
se esta for verdadeira, volta para o bloco de código. Sua sintaxe é:
Código 2.13
do
{
Sequencia de comandos;
} while(condição);
Código 2.14
#include <stdio.h>
#include <stdlib.h>
int main ()
{
int i;
do {
printf ("Escolha uma das opções pelo número:\n\n");
printf ("\t(1) Esquerdo\n");
printf ("\t(2) Pequeno\n");
printf ("\t(3) Baixo\n\n");
scanf("%d", &i);
} while (i < 1 || i > 3);
62 • capítulo 2
if(i == 1)
printf ("Você escolheu Esquerdo, o
oposto é Direito.\n");
else if(i == 2)
printf ("Você escolheu Pequeno, o oposto
é Grande.\n");
else
printf ("Você escolheu Baixo, o oposto é
alto.\n");
i = 0;
system("pause");
return 0;
}
O loop for permite que alguma inicialização seja feita antes do loop e que um
incremento (ou alguma outra ação) seja feita após cada execução sem incluir o
código dentro do bloco. Pode se dizer também que é uma estrutura de repetição
que repete a execução de uma dada sequência de comandos uma certa quanti-
dade de vezes, que pode ser determinada pelo próprio programa. Sua sintaxe
deve ser escrita assim:
Código 2.15
for( inicialização; condição; incrementação )
{
Sequencia de comandos/ instruções;
}
capítulo 2 • 63
Código 2.16
Inicialização;
while(condição)
{
Sequência de comandos/ instruções;
Incremento;
}
Código 2.17
#include <stdio.h>
#include <stdlib.h>
int main()
{
int voltas;
int i;
system("pause");
return 0;
}
64 • capítulo 2
Imagine agora que você precise de algum jeito fazer uma soma dentro de
cada volta dada pelo brinquedo, seja somar força, velocidade ou qualquer outra
coisa que você pretenda utilizar. No código abaixo o programa sempre soma
o número escolhido a um total de voltas, e no final mostra o total somado em
todas as voltas.
Código 2.18
#include <stdio.h>
#include <stdlib.h>
int main()
{
int voltas;
int num;
int i;
int soma = 0;
system("pause");
return 0;
}
capítulo 2 • 65
2.2.8 Comandos de Seleção Múltipla Switch/Case
Código 2.19
switch (expressão)
{
case valor1:
Instruções;
break;
case valor2:
Instruções;
break;
...
default:
Instruções;
}
66 • capítulo 2
Código 2.20
#include <stdio.h>
#include <stdlib.h>
int main()
{
int opcao;
printf ("[1] Cadastrar cliente\n"
"[2] Procurar cliente\n"
"[3] Inserir pedido\n"
"[0] Sair\n\n"
"Digite sua escolha: ");
scanf ("%d", &opcao);
switch (opcao) {
case 1:
printf("Cadastrar Cliente...");
break;
case 2:
printf(" Procurar Cliente...");
break;
case 3:
printf(" Inserir Pedido...");
break;
case 0:
return 0;
default:
printf ("Opção inválida!\n");
}
}
capítulo 2 • 67
O exemplo anterior do comando switch poderia facilmente ser escrito com
a utilização dos comando if..else..if, como podemos ver no código abaixo.
Código 2.21
#include <stdio.h>
#include <stdlib.h>
int main()
{
int opcao;
printf ("[1] Cadastrar cliente\n"
"[2] Procurar cliente\n"
"[3] Inserir pedido\n"
"[0] Sair\n\n"
"Digite sua escolha: ");
scanf ("%d", &opcao);
if(opcao == 1 )
printf("Cadastrar Cliente...");
else if(opcao == 2)
printf(" Procurar Cliente...");
else if(opcao == 3)
printf(" Inserir Pedido...");
else if(opcao == 0)
return 0;
else
printf ("Opção inválida!\n");
2.2.9 Funções
68 • capítulo 2
Ao crescer o que ocorre naturalmente é o aumento de número de linhas do pro-
grama e a existência de trechos repetíveis, ou seja, trechos do código que fazem
a mesma coisa. É como escrever um texto: às vezes dizemos a mesma coisa, de
forma diferente, várias vezes. Para isto, programar requer o uso de funções.
Um programa em C pode e deve ser escrito como um conjunto de funções
que são executadas a partir da execução de uma função denominada main().
Uma função pode ter diferentes tipos de declarações de variáveis, instruções,
ativações de funções próprias do sistema que está rodando a aplicação. O ob-
jetivo de se utilizar funções é realizar alguma “sub-tarefa” específica da tarefa
que o programa pretende realizar. Podemos dizer que uma função é um trecho
de programa que faz tarefas especificas e pode ser chamado de qualquer parte
do programa quantas vezes desejarmos. As funções devem realizar uma única
tarefa (favorece a reutilização) e devem ser pequenas (caber numa tela).
O fato de executar uma tarefa facilita a sua denominação. A dificuldade de
se nomear uma função normalmente está associada ao fato dela não estar bem
definida.
Utilizar funções pequenas facilita muito a etapa de testes e manutenção de
programas.
O uso de funções permite a reutilização de códigos e auxilia também na ma-
nutenção, pois o código fica melhor estruturado.
A ideia principal da função é permitir que o programador possa agrupar vá-
rias operações em um só escopo que pode ser chamado através do seu nome. A
função deve ser definida da seguinte forma:
Código 2.22
[Tipo de retorno da função] [nome da função] (1º parâmetro, 2º
parâmetro,...)
{
//código
}
Uma função pode necessitar de dados para realizar uma ação desejada.
Esses dados são chamados parâmetros da função. A função também pode re-
tornar um valor é chamado de retorno da função.
capítulo 2 • 69
Há algumas regras em C a serem seguidas para o uso da função, para o
nome da função e dos parâmetros. Para nome dos parâmetros valem as mes-
mas regras que foram dadas aos nomes de variáveis. A definição da função deve
ser codificada antes da função main e o código tem a obrigação de estar dentro
de chaves para que funcione.
A compilação da função deve acontecer antes dela ser chamada pela primei-
ra vez. Isso garante que, quando a sua chamada for compilada, o compilador já
a conheça, aceitando o código como correto.
O código da função deve estar entre chaves que delimitam seu escopo.
Em C utiliza-se a instrução return para poder retornar algum valor. Para isto é
preciso especificar o tipo de retorno, que pode ser char, int, float dentre outros.
Um exemplo simples de uma função que retorna um char.
char clara()
{
return `C`;
}
Dentro de uma função também podemos retornar um tipo void, que na ver-
dade significa que não há retorno.
2.2.11 Parâmetros
70 • capítulo 2
local desta função.
Portanto, podemos entender que os parâmetros são declarações de variá-
veis locais da função (somente existem durante a sua execução) e que recebem
os valores de entrada que serão processados pela função. O mecnismo de co-
municação permite que uma função retorne apenas um valor, fruto do proces-
samento dos dados de entrada.
Da mesma forma que uma variável, uma função pode ser declarada. Isso
faz com que não seja obrigatório posicionar o código da função antes das suas
chamadas. Caso a função tenha sido declarada, seu código pode aparecer em
qualquer posição.
Para declarar a existência de uma função no programa e a presença de parâ-
metros, usamos uma lista de parâmetros entre parênteses, separados por vírgu-
las. Cada declaração de parâmetro é feita de maneira semelhante à declaração
de variáveis: a forma geral é <tipo nome>. Isto é chamado de prototipação de
funções.
A prototipação é uma forma de “avisar” ao compilador sobre a existência da
função, seus parâmetros e tipo de retorno.
Alguns exemplos da declaração:
void função(void);
Agora vamos a um exemplo: vamos criar uma função que recebe dois núme-
ros inteiros como parâmetros (linha 4) e retorna a soma deles (linha 6). É uma
função bem simples e veja que não usamos protótipo. Nós fizemos a declaração
e escrita dela já antes da função main.
Nas linhas 9 a 25 nós utilizamos a função através de um exemplo de inserção
dos dados pelo usuário e retorno do valor calculado da soma.
capítulo 2 • 71
Código 2.22
#include <stdio.h>
#include <stdlib.h>
int main()
{
int a;
int b;
int total;
return 0;
}
Podemos usar funções sem parâmetros, como vimos. Podemos criar fun-
ções de mensagens para interagir com o usuário. Pense que quando o usuário
entra no programa, recebe boas vindas e, quando ele acaba de usar o programa,
recebe uma mensagem de despedida. Fica interativo, não?
Código 2.23
#include <stdio.h>
#include <stdlib.h>
72 • capítulo 2
void inicio()
{
printf("Bem Vindo ao Programa\n");
}
void inal()
{
printf("\nFechando programa. Obrigado por usar");
}
int main()
{
int a;
int b;
int total;
inicio();
inal();
return 0;
}
capítulo 2 • 73
2.2.12 Ponteiros
num 98342
soma 98346
nome 98350
74 • capítulo 2
int *p;
int **p;
Código 2.24
#include <stdio.h>
#include <stdlib.h>
int main()
{
int a;
int b;
int c;
int *p;
int *q;
p = &a;
q = &b;
c = *p + *q;
return 0;
}
Logo a seguir, utilizaremos o mesmo código, só que mostrando como fica-
ria você utilizar um ponteiro do ponteiro para fazer a troca.
capítulo 2 • 75
Código 2.25
#include <stdio.h>
#include <stdlib.h>
int main()
{
int a;
int b;
int c;
int *p;
int *q;
int **r;
p = &a;
q = &b;
r = &q;
c = **r + *q;
return 0;
}
Podemos ver nos exemplos que o ponteiro é uma variável capaz de armaze-
nar um endereço de outra variável e também pode “apontar” para outro pon-
teiro que “aponta” para uma variável qualquer. Na prática, apontar implica em
armazenar valores de memória.
Dados dois ponteiros p e q podemos igualá-los fazendo p=q. Repare que esta-
mos fazendo com que p aponte para o mesmo lugar que q, ou seja, é um co-
mando de atribuição normal, só que tanto o conteúdo de p, quanto q, trata-se
76 • capítulo 2
de endereço de memória. Logo, p receberá o conteúdo de q que é um endereço
de memória e passará a “apontar” para o local que q aponta.
Se quisermos que a variável apontada por p tenha o mesmo conteúdo da
variável apontada por q devemos fazer:
*p = *q
Código 2.26
#include <stdio.h>
#include <stdlib.h>
int main()
{
int a;
int *p;
a = 10;
p = &a;
return 0;
}
capítulo 2 • 77
Na figura 2.1 pode ser visto o valor da variável a na linha 1. Na linha 2 vemos
o endereço da variável a. Agora, observe bem o valor (o conteúdo) da variável p
(que é um ponteiro). Veja que seu conteúdo, na linha 3 é exatamente o mesmo
do endereço da variável a.
p++
p--
78 • capítulo 2
Código 2.27
#include <stdio.h>
#include <stdlib.h>
int main()
{
int a;
int b;
int *p;
a = 10;
b = 20;
p = &b;
return 0;
}
capítulo 2 • 79
Figura 2.2 – Execução do Código 2.27.
Por que usamos o “por acaso”? Você deve tomar muito cuidado com o uso
de ponteiros, pois perceba na figura que os valores de memória foram decres-
cendo ao invés de crescer. Isto se deve pelo funcionamento da alocação de me-
mória pelo sistema operacional. Não se pode apenas usar uma operação p++ e
esperar que tudo ocorra bem. Deve-se tomar muito cuidado e saber exatamente
qual alteração você quer fazer no seu ponteiro.
Mas, e se quiséssemos incrementar o conteúdo apontado por um ponteiro?
Estamos falando de operações com ponteiros e não de operações com o
conteúdo das variáveis para as quais eles apontam. Por exemplo, para incre-
mentar o conteúdo da variável apontada pelo ponteiro p, faz-se:
(*p)++;
Quando se usa uma função para fazer a troca de duas variáveis, é necessário
passar variáveis de ponteiros nos parâmetros. No exemplo abaixo tem-se uma
função de troca de variáveis que, por falta dos ponteiros é impossível acessar
seu endereço de memória e alterá-las dentro da função.
80 • capítulo 2
void troca(int p, int q)
{
int aux;
aux = p;
p = q;
q = aux;
}
Vejamos o código 2.28. Nós criamos uma função que calcula o fatorial de
um número, a função fat. Ele recebe um número inteiro n e devolve outro nú-
mero inteiro. O fatorial de um número é na forma fat(n) = n x n-1 x n-2 x ... x 1.
A chamada para a função fat ocorre na linha 8. Veja nas linhas 13 a 20 que
ela se trata de um loop multiplicador de n por n-1 até que n não seja 0 (consulte
o conceito da função fatorial se precisar, ok?).
capítulo 2 • 81
Código 2.28
#include <stdio.h>
#include <stdlib.h>
int fat(int);
82 • capítulo 2
Início do programa: Declaração das Chamada da função:
pilha vazia variáveis: n, r cópia do parâmetro
n 5
r - r -
n 5 n 5
Declaração da Retorno da
Final do laço
variável local: f função: desempilha
f 1 f 120
n 5 n 0
r - r - r 120
n 5 n 5 n 5
capítulo 2 • 83
Código 2.29
#include <stdio.h>
#include <stdlib.h>
int main()
{
int a;
int b;
troca(&a, &b);
return 0;
}
Agora veja no código 2.30 que é o mesmo exemplo de 2.29, mas mostramos
os valores de endereço de memória das variáveis. Observe que cada variável
mantém seus valores de endereço, mas seus conteúdos foram alterados.
84 • capítulo 2
Código 2.30
#include <stdio.h>
#include <stdlib.h>
int main()
{
int a;
int b;
troca(&a, &b);
return 0;
}
capítulo 2 • 85
As técnicas de programação definem que, numa chamada de função, os pa-
râmetros podem ser passados por valor ou por referência.
A passagem por valor apenas informa um valor, não permitindo, assim, que
a função altere o valor fornecido na variável de origem.
Já uma passagem por referência, permite que a função altere o valor arma-
zenado na variável da função que realizou a chamada. Portanto, quando se faz
uso de uma passagem de parâmetro por referência, estamos permitindo que a
função determine o valor de uma variável da função que realizou a chamada.
Este recurso permite, na verdade, que a função determine quantos valores fo-
rem necessários, embora ela sóssa retornar um valor por definição.
A linguagem C só permite a passagem de parâmetros por valor. No entanto,
se utilizarmos ponteiros, podemos simular a passagem por referência.
Se os valores forem de tipos comuns (int, float etc.), só recebemos o valor
sem sabermos onde eles estão originalmente armazenados, não sendo possível
alterar seu local de origem.
Já se o o valor passado for um endereço de memória, podemos recebe-lo em
uma variável do tipo ponteiro e alterar seu valor na origem.
Quando passamos parâmetros para funções que são ponteiros usamos o
nome de passagem de parâmetro por referência. Quando usamos variáveis que
não são ponteiros chamamos de passagem de parâmetros por valor.
86 • capítulo 2
2.2.15 Reflexões: Aplicações com Passagem de Valores Numéricos
e Endereços de Memória
Código 2.31
#include <stdio.h>
#include <stdlib.h>
void saudacaoAplicacao(void);
void saidaAplicacao(void);
void menu(void);
void pularLinha(int);
void troca(int*, int*);
void main(void)
{
int num1 = 200;
int num2 = 100;
int run = 1;
int tipoCena = 0;
capítulo 2 • 87
saudacaoAplicacao();
system("pause");
while(run != 0)
{
if(tipoCena == 0)
{
menu();
scanf("%d", &tipoCena);
}
else if(tipoCena == 1)
{
system("cls");
printf("Insira o primeiro Numero
inteiro: ");
scanf("%d", &num1);
printf("Insira o segundo Numero
inteiro: ");
scanf("%d", &num2);
printf("Inseriu os numeros com
sucesso...\n");
system("pause");
tipoCena = 0;
}
else if(tipoCena == 2)
{
system("cls");
printf("A soma dos numeros %d e %d e
igual a: %d\n", num1, num2, calculadora(1, &num1, &num2));
system("pause");
tipoCena = 0;
}
else if(tipoCena == 3)
{
system("cls");
printf("A subtração dos numeros %d e %d e
88 • capítulo 2
igual a: %d\n", num1, num2, calculadora(2,
&num1, &num2));
system("pause");
tipoCena = 0;
}
else if(tipoCena == 4)
{
system("cls");
printf("A Multiplicação dos numeros
%d e %d e igual a: %d\n", num1, num2, calculadora(3,
&num1, &num2));
system("pause");
tipoCena = 0;
}
else if(tipoCena == 5)
{
system("cls");
printf("\nO Primeiro numero e igual
%d", num1);
printf("\n... e corresponde a este
endereço de memoria: %d", &num1);
pularLinha(1);
printf("\nO Segundo numero e igual
%d", num2);
printf("\n... e corresponde a este
endereço de memoria: %d", &num2);
pularLinha(2);
troca(&num1, &num2);
capítulo 2 • 89
printf("\n... e corresponde a este
endereço de memoria: %d", &num1);
pularLinha(1);
printf("\nO Segundo numero e igual
%d", num2);
printf("\n... e corresponde a este
endereço de memoria: %d", &num2);
pularLinha(1);
troca(&num1, &num2);
system("pause");
tipoCena = 0;
}
else if(tipoCena == 6)
{
system("cls");
printf("O primeiro numero %d
multiplicado por ele mesmo é: %d \n", num1,
multiplicaPorEleMesmo(num1));
printf("O segundo numero %d
multiplicado por ele mesmo é: %d \n", num2,
multiplicaPorEleMesmo(num2));
system("pause");
tipoCena = 0;
}
else if(tipoCena == 9)
{
run = 0;
}
}
saidaAplicacao();
system("pause");
}
90 • capítulo 2
int calculadora(int selecao, int *salario1, int
*salario2)
{
switch(selecao)
{
case 1:
return ((*salario1) + (*salario2));
break;
case 2:
return ((*salario1) - (*salario2));
break;
case 3:
return ((*salario1) * (*salario2));
break;
}
}
void saudacaoAplicacao()
{
printf("Bem-vindo ao programa em C");
pularLinha(1);
}
void saidaAplicacao()
{
printf("Saindo do programa em C ....");
pularLinha(1);
}
void menu()
{
system("cls");
printf("[1] Inserir Numeros.");
pularLinha(1);
printf("[2] Somar.");
pularLinha(1);
capítulo 2 • 91
printf("[3] Subtrair.");
pularLinha(1);
printf("[4] Multiplicar.");
pularLinha(1);
printf("[5] Trocar Numeros usando Parametros de
ponteiro.");
pularLinha(1);
printf("[6] Multiplicar os numeros por eles
mesmos.");
pularLinha(1);
printf("[9] Sair.");
pularLinha(1);
}
92 • capítulo 2
LEITURA
Para complementar seu aprendizado computação, programação e linguagem C sugiro os
seguintes links em inglês:
REFERÊNCIAS BIBLIOGRÁFICAS
KERNIGHAN, B. W.; RITCHIE, D. M. C: a linguagem de programação, padrão ANSI. Rio de Janeiro:
Campus, 1995. 289p.
KELLEY, A.; POHL, I. A Book on C: Programming in C. 4. ed. Boston: Addison Wesley, 1997. 726p.
ZIVIANI, N. Projetos de algoritmos: com implementações em Pascal e C. São Paulo: Pioneira
Thomson, 2002. 267p.
ARAÚJO, J. Dominando a Linguagem C. 1. ed. Ciência Moderna, 2004, 146p.
CORMEN, T. H.; LEISERSON, C. E.; RIVEST, R. L.; STEIN, C. Algoritmos: teoria e prática. Rio de
Janeiro: Campus, 2002. 916p.
FORBELLONE, A. L. Lógica de Programação. 2. ed. São Paulo: Makron Books, 2000. 195p.
KNUTH, D. E. The Art of Computer Programming, Fundamental Algorithms. 3. ed. Boston:
Addison Wesley, 1997. 672p. (vol 1)
SEBESTA, R. W. Conceitos de Linguagens de Programação. 4. ed. Porto Alegre: Bookman, 2000.
624p.
capítulo 2 • 93
94 • capítulo 2
3
Vetores e Cadeias
de Caracteres
Neste capítulo estudaremos o conceito de vetores e cadeias de caracteres. Ve-
tores são estruturas simples, mas extremamente úteis em programação de
computadores. Como veremos, poderemos realizar leituras de variáveis do
mesmo tipo (chamadas variáveis homogêneas) usando laços e simplificando
nossas tarefas.
Vetores também são a base para outro tipo em Linguagem C: cadeias de carac-
teres (ou strings). Trata-se de um tipo útil para armazenamento de palavras e
textos, pois, como vimos, o tipo char armazena um único caractere.
OBJETIVOS
Neste capítulo, os objetivos principais são aprender sobre:
• Vetores
• Declaração
• Uso e manipulação
• Vetores e funções
• Cadeias de caracteres
• Caracteres, tabela ASCII e caracteres de controle
• Cadeia de caracteres (strings)
• Leitura de caracteres e de cadeia de caracteres
• Funções pré-definidas de manipulação de strings (comprimento, cópia e concatenação)
• Parâmetros da função main()
96 • capítulo 3
3.1 Vetores e Cadeias de Caracteres
Vetor, também chamado array ou arranjo, é uma maneira de armazenar vários
dados numa mesma variável através do uso de índices numéricos. Os vetores
são chamados também de tipos homogêneos, pois devem sempre conter dados
do mesmo tipo de variável.
A declaração dos vetores é de maneira semelhante na declaração de variá-
veis normais. A diferença é que depois do nome da variável deve ser informada
a quantidade de elementos do vetor.
Eles são usados para armazenar grandes quantidades de informação em
um tipo de variável e para facilitar a manipulação destes dados. Imagine que
você precise criar um sistema para uma escola onde você vai armazenar "n" no-
tas, para "n" Alunos, imagine quantas variáveis você teria que declarar! É quase
impossível prever a quantidade exata para esses registros. Mas, em linguagens
de programação temos o auxílio dos vetores. Sua sintaxe é:
int V[5];
capítulo 3 • 97
posição 2, 25 na posição 3 e 58 na posição 4. Abaixo outra pequena anotação do
uso de vetor.
Código 3.1
vetor[0] = 3;
int x = vetor[2];
int y = vetor[5]; //ERRO EM TEMPO DE EXECUÇÃO!!!
Código 3.2
#include <stdio.h>
#include <stdlib.h>
int main()
{
int V[5] = {1,3,5,25,58};
int a = V[0];
int b = V[2];
int c = V[5]; //ERRO ?
98 • capítulo 3
Declaramos um vetor com 5 elementos na linha 6. Inicializamos as variáveis
a, b e c nas linhas 7 a 9. Agora, o que ocorrerá nas linhas 11 a 13? Execute o có-
digo e veja o valor resultante da linha 13. É um valor fora dos limites do vetor.
Lembre-se, V[5] é a sexta posição do vetor, pois ele começa a contar a partir de 0!
Em C podemos omitir o número de elementos na inicialização de um vetor.
O tamanho do vetor será o número de valores inicializados. Por exemplo, as
duas notações abaixo são equivalentes:
#deine MAX 10
int main()
{
int vetor[MAX];
int i;
capítulo 3 • 99
Perceba nas linhas 11 a 14 e 17 a 20, do código 3.3, que usamos laços do tipo
for variando o índice i do vetor de acordo com a variação do próprio laço. Esta é
a forma mais comum de inserirmos diversas informações num vetor, pois agru-
pamos as instruções no laço e código fica mais enxuto!
Neste outro exemplo 3.4 pense num programa onde o usuário entra com
certa quantidade de números e o programa vai mostrar ao usuário qual foi o
maior número digitado.
Código 3.4
#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
int main(void)
{
int vetor[5];
int x, i;
while (i < 5)
/* laço de comparação dos elementos */
{
if (vetor[i] > x)
{
x = vetor[i];
}
i++;
100 • capítulo 3
}
return 0;
}
Podemos ver que o primeiro for é usado para inserir os números pelo usu-
ário. Em seguida atribuímos à variável x o primeiro número do vetor. No while
começamos a comparar cada valor dentro do vetor com o x para verificar qual é
o maior número dentre eles. Caso o número do vetor seja maior que o número
x, passaremos a informação do vetor para a variável x. Ao final do while, o pro-
grama informará qual número dentro do vetor é o maior.
Vamos agora fazer um programa, que escolhe 10 números aleatórios, e o
usuário pode inserir um número para poder verificar se existe aquele núme-
ro dentro do vetor, usaremos dois cabeçalhos para poder usar alguns métodos
que serão comentados dentro do código.
Código 3.5
#include <stdio.h>
#include <conio.h>
#include <stdlib.h> // para usar rand ou srand
#include <time.h> //necessário p/ função time()
#deine MAX 10
int main()
{
int vetor[MAX], num;
int i;
capítulo 3 • 101
* será diferente.
*/
srand(time(NULL));
102 • capítulo 3
3.3 Vetores e Funções
Na passagem de vetores como parâmetros de funções estamos sempre passan-
do um ponteiro para o primeiro elemento do vetor, ou seja ele contém o ende-
reço de seu 1º elemento. Isto implica dizer que a passagem de vetores como
parâmetros de funções é sempre por referência. Por exemplo:
Código 3.6
#deine MAX 100
int main() {
double a, A[MAX];
//algum código aqui
a = f(A);
//algum código aqui
}
capítulo 3 • 103
double prodEscalar (double V1[MAX], double V2[MAX], int N);
A função recebe os vetores V1 e V2, e um inteiro N. Veja que cada vetor pos-
sui MAX elementos cada. O produto escalar é calculado somando-se todas as
multiplicações de cada um dos elementos de um vetor pelo outro na mesma
posição.
Código 3.7
double prodEscalar (double V1[MAX], double V2[MAX], int N) {
int i;
double res = 0;
for (i=0; i<N; i++)
res = res + V1[i] * V2[i];
return res;
}
Acreditamos que esse seja o protótipo mais "claro", pois ele define que V1 e
V2 são vetores de tamanho máximo. Porém, há outro protótipo possível:
Código 3.8
#include <stdio.h>
#include <stdlib.h>
104 • capítulo 3
int main (void) {
int n, i;
double vetA[MAX], vetB[MAX];
double prod;
• 105
capítulo 3
Vamos agora fazer uma função para retornar um vetor com a soma de dois
outros vetores. A função agora deverá receber 2 vetores de entrada e devolver 1
vetor como resultado. Sendo assim, temos o seguinte protótipo:
void somaVetorial ( int A[MAX], int B[MAX], int Res[MAX], int N);
Código 3.9
void somaVetorial ( int A[MAX], int B[MAX], int Res[MAX], int N) {
int i;
for(i=0; i<N; i++)
Res[i] = A[i] + B[i];
}
Código 3.10
#include <stdio.h>
#include <stdlib.h>
#deine MAX 10
106 • capítulo 3
printf("Valores do vetor A\n");
for (i=0; i<n; i++)
scanf("%d",&vetA[i]);
printf("\n");
return 0;
}
unsigned char u;
char c;
CONEXÃO
Você pode ler mais sobre a tabela ASCII aqui:
• http://pt.wikipedia.org/wiki/ASCII
SI-
BIN OCT DEC HEX SINAL BIN OCT DEC HEX SINAL BIN OCT DEX HEX
NAL
0010 0000 040 32 20 (espaco) 0100 0000 100 64 40 @ 0110 0000 140 96 60 `
0010 0001 041 33 21 ! 0100 0001 101 65 41 A 0110 0001 141 97 61 a
0010 0010 042 34 22 " 0100 0010 102 66 42 B 0110 0010 142 98 62 b
0010 0011 043 35 23 # 0100 0011 103 67 42 C 0110 0011 143 99 63 c
0010 0100 044 36 24 $ 0100 0100 104 68 44 D 0110 0100 144 100 64 d
0010 0101 045 37 25 % 0100 0101 105 69 45 E 0110 0101 145 101 65 e
0010 0110 046 38 26 & 0100 0110 106 70 46 F 0110 0110 146 102 66 f
0010 0111 047 39 27 ' 0100 0111 107 71 47 G 0110 0111 147 103 67 g
0010 1000 050 40 28 ( 0100 1000 110 72 48 H 0110 1000 150 104 68 h
0010 1001 051 41 29 ) 0100 1001 111 73 49 I 0110 1001 151 105 69 i
0010 1010 052 42 2A * 0100 1010 112 74 4A J 0110 1010 152 106 6A j
108 • capítulo 3
Você também pode executar um código em C que imprima sua própria ta-
bela ASCII. Veja o código 3.11. Neste caso, são só os caracteres imprimíveis, por
isto i começa em 32, na linha 07. Se você usar outras máscaras de formatação
poderá ver os valores em hexa ou octal.
Código 3.11
#include <stdio.h>
int main(void)
{
int i;
return 0;
}
saudacao[9] = “?”;
Código 3.12
char *s;
s = malloc( 10 * sizeof (char));
s[0] = 'A';
s[1] = 'B';
s[2] = 'C';
s[3] = '\0';
s[4] = 'D';
printf(“%s”, saudacao);
scanf(“%s”, saudacao);
110 • capítulo 3
Código 3.13
#include <stdio.h>
int main()
{
char saudacao[100];
printf("Escreva um nome:");
scanf("%s", saudacao);
printf("\nOla %s, como vai?", saudacao);
return 0;
}
printf(“%c”, &texto);
scanf(“%c”, texto);
Código 3.14
#include <stdio.h>
int main()
{
char texto;
printf("Digite uma letra: ");
scanf("%c", &texto);
return 0;
}
• 111
capítulo 3
Experimento digitar mais de uma letra na leitura e veja o que acontece. Na
figura abaixo nós mostramos!
Código 3.15
int quantasVogais( char *s) {
int numVogais, i;
char *vogais;
vogais = "aeiouAEIOU";
numVogais = 0;
112 • capítulo 3
Agora, no código 3.16, criamos um exemplo para testar nossa função. Insira
o código da sua função depois do código main.
Código 3.16
#include <stdio.h>
#include <stdlib.h>
int main()
{
char entrada[100];
printf("Palavra para teste: ");
scanf("%s", entrada);
return 0;
}
char buffer[50];
gets(buffer);
Você declara uma variável que será a string alvo, no caso um vetor de char. E
passa o nome da variável para a função gets().
Para verificar o tamanho de uma string (tamanho será a quantidade de valo-
res úteis/válidos na string, ou seja, tudo antes do ‘\0’) usamos a função strlen().
O nome da função é a abreviação de string length e devolve o comprimento da
string passada como parâmetro.
Vamos a um exemplo! Veja no código 3.17 um programa que lê um email do
usuário e informa o tamanho deste email em caracteres.
Código 3.17
#include<stdio.h>
#include<string.h>
int main(void)
{
char email[100];
int tamanho;
tam = strlen(email);
return 0;
}
114 • capítulo 3
É possível tratar a entrada do usuário de forma que o tamanho do email di-
gitado seja válido.
Código 3.18
#include<stdio.h>
#include<string.h>
#deine MAX 20
int main(void)
{
char email[100];
int tamanho = 0;
do
{
printf("Informe um email: ");
gets(email);
int main(void){
char nome[30], sobre[30], aux[30];
printf("Qual o seu nome? ");
gets(nome);
printf("Qual o seu sobrenome? ");
gets(sobrenome);
Veja na linha 13 que usamos a variável aux, declarada na linha 6, para rece-
ber temporariamente o valor de nome. Se não fizéssemos isto perderíamos este
valor, pois seria sobrescrito pelo sobrenome na linha 14.
Para comparar duas strings em C usamos a função strcmp (abreviatura de
string compare) que faz uma comparação lexicográfica. Ela devolve um número
negativo se a primeira string for lexicograficamente menor que a segunda, de-
volve 0 se as duas strings são iguais e devolve um número positivo se a primeira
string for lexicograficamente maior que a segunda.
Código 3.20
#include<stdio.h>
#include<string.h>
int main(void){
char palavra1[30], palavra2[30];
int comp;
116 • capítulo 3
printf("Qual a primeira palavra? ");
gets(palavra1);
printf("Qual a segunda palavra? ");
gets(palavra2);
//compara as palavras
comp = strcmp(palavra1, palavra2);
if(comp == 0)
printf(“\nAs palavras são iguais”);
if(comp > 0)
printf(“\nPalavra 1 eh maior que Palavra 2”);
if(comp < 0)
printf(“\nPalavra 1 eh menor que Palavra 2”);
return 0;
}
Código 3.21
#include<stdio.h>
#include<string.h>
int main(void) {
char nome[30], sobrenome[30];
// receber as variaveis
printf("Informe o nome: ");
capítulo 3 • 117
gets(nome);
printf("Informe o sobrenome: ");
gets(sobrenome);
Pelo código 3.21, na linha 14, vemos que a variável nome receberá a variável
sobrenome. Agora, atente-se ao resultado da execução na figura 3.3. Veja que o
nome ficou justaposto ao sobrenome. É preciso adicionar um espaço entre os
dois, certo?
No código 3.22 fiz um pequeno ajusteque você pode conferir na linha 14.
Faça também no seu código, compile-o e execute-o novamente.
Código 3.22
#include<stdio.h>
#include<string.h>
int main(void) {
char nome[30], sobrenome[30];
118 • capítulo 3
// receber as variaveis
printf("Informe o nome: ");
gets(nome);
printf("Informe o sobrenome: ");
gets(sobrenome);
Agora, veja na figura 3.4 que nosso exemplo deu certo! Ou seja, agora nome
e sobrenome não estão justapostos, mas sim com um espaço entre eles.
capítulo 3 • 119
Código 3.23
void impressao (char* s)
{
int i;
for (i=0; s[i] != '\0'; i++)
printf(“%c”, s[i]);
printf(“\n”);
}
Agora vejamos uma função que retorna o tamanho de uma string, percor-
rendo-a até encontrar o elemento ‘\0’e contando os caracteres válidos antes
disto.
Código 3.24
int tamanho (char* s)
{
int i;
int n = 0;
/* contador */
for (i=0; s[i] != '\0'; i++)
n++;
return n;
}
Código 3.25
void copia (char* dest, char* orig)
{
int i;
for (i=0; orig[i] != '\0'; i++)
dest[i] = orig[i];
/* limitação da cadeia copiada */
dest[i] = '\0';
}
120 • capítulo 3
A função “concatenacao” copia os elementos de uma cadeia de origem
(orig) para o final da cadeia de destino (dest) colocando-os de forma justaposta.
Código 3.26
void concatenacao (char* dest, char* orig)
{
int i = 0;
int j;
i = 0;
while (dest[i] != '\0')
i++;
dest[i] = '\0';
}
Veja na função do código 3.26 que usamos dois índices para concatenar as
cadeias: i e j. O índice i é usado na cadeia de destino, começando em zero. O
índice j é usado na cadeia de origem. Nas linhas 7 a 8 o while é usado para en-
contrar o final da cadeia de destino (afinal, a concatenação será a partir dali).
Nas linhas 10 a 14 o for é usado para copiar cada caractere da cadeia de ori-
gem na cadeia de destino. Depois, colocamos o marcado de final de cadeia na
linha 16 para fechar a cadeia de destino.
Código 3.10
#include <stdio.h>
#include <stdlib.h>
122 • capítulo 3
Só que, desta vez, vamos usar o compilador online (você também pode usar
o Dev, desde que vá na pasta bin e coloca lá seu arquivo .exe depois de compilar
seu programa. Ou, configure o caminho da pasta bin do seu Dev na variável de
ambiente path). Veja o link do compilador online no capítulo 1.
Após digitar seu programa, compile-o com o comando abaixo:
./hello
• 123
capítulo 3
Veja que ele trata o nome do programa como primeiro parâmetro. Faltou
passar o segundo parâmetro que é o nome da pessoa! Agora tente assim:
./hello reginaldo
./hello Reginaldo 18
Ele nos dá uma saudação e também fala sobre nossa idade! Por fim, tente:
124 • capítulo 3
Figura 3.9 – O quarto teste com mais de dois parâmetros.
REFLEXÃO
Nesse capítulo vimos as mais variadas formas de utilização de vetores, do uso de cadeias
de caracteres, a passagem de vetores como parâmetros de funções e conhecemos a tabela
ASCII.
Também vimos como receber argumentos em linha de comando, algo que pode ser muito
legal para escrever programas que conversem com comandos!!
Convido você a pesquisar sobre o que é um shell, como são criados e tentar melhorar nosso
programa hello. Topa o desafio?
LEITURA
Para complementar seu aprendizado computação, programação e linguagem C sugiro os
seguintes links em inglês:
• http://www.cprogramming.com/tutorial/c/lesson8.html Acesso em março de 2015. Um
detalhe importante aqui é que ainda não falamos de matrizes. Vetores são estruturas unidi-
mensionais. Veremos sobre matrizes no capítulo seguinte.
• http://www.cprogramming.com/tutorial/c/lesson9.html Acesso em março de 2015.
Também sugiro a leitura de livros em português como:
• KERNIGHAN, B. W.; RITCHIE, D. M. C: a linguagem de programação, padrão ANSI. Rio de
Janeiro: Campus, 1995. 289p.
E também sugiro os seguintes links em português
• http://www2.ic.uff.br/~hcgl/tutorial.html Acesso em março de 2015.
capítulo 3 • 125
REFERÊNCIAS BIBLIOGRÁFICAS
KERNIGHAN, B. W.; RITCHIE, D. M. C: a linguagem de programação, padrão ANSI. Rio de Janeiro:
Campus, 1995. 289p.
KELLEY, A.; POHL, I. A Book on C: Programming in C. 4. ed. Boston: Addison Wesley, 1997. 726p.
ZIVIANI, N. Projetos de algoritmos: com implementações em Pascal e C. São Paulo: Pioneira
Thomson, 2002. 267p.
ARAÚJO, J. Dominando a Linguagem C. 1. ed. Ciência Moderna, 2004, 146p.
CORMEN, T. H.; LEISERSON, C. E.; RIVEST, R. L.; STEIN, C. Algoritmos: teoria e prática. Rio de
Janeiro: Campus, 2002. 916p.
FORBELLONE, A. L. Lógica de Programação. 2. ed. São Paulo: Makron Books, 2000. 195p.
KNUTH, D. E. The Art of Computer Programming, Fundamental Algorithms. 3. ed. Boston: Addison
Wesley, 1997. 672p. (vol 1)
SEBESTA, R. W. Conceitos de Linguagens de Programação. 4. ed. Porto Alegre: Bookman, 2000.
624p.
126 • capítulo 3
4
Tipos Estruturados
e Matrizes
Estruturas são usadas para agrupamento de dados de diferentes tipos. Imagi-
ne, por exemplo, a necessidade de um tipo particular de dado para armazenar
informações sobre alunos, como nome, número de matrícula, disciplinas que
está cursando com as respectivas notas etc. Sim, você poderia usar variáveis
de tipo primitivo ou até mesmo vetores. Mas as variáveis são limitadas ao pró-
prio tipo e os vetores só podem possuir um único tipo de dado para todas as
variáveis.
Você já conhece os tipos de dados mais simples e usados em linguagem C:
char, int, double e void. Agora, vamos usar estes e outros tipos para criar tipos
complexos, definidos conforme nossa necessidade de aplicação.
Também veremos a criação de matrizes, estruturas homogêneas com mais de
uma dimensão (veremos o que o termo dimensão significa) e tipos enumera-
dos, pré-definidos.
OBJETIVOS
Neste capítulo, os objetivos principais são aprender sobre:
128 • capítulo 4
4.1 Estruturas Heterogêneas
No estudo sobre vetores nós vimos que eles são usados para armazenamento de
dados do mesmo tipo, também chamados de estruturas homogêneas. Ao usar
um índice do vetor estamos acessando uma variável (um espaço de memória)
do mesmo tipo declarado deste vetor.
Na linguagem C nós podemos definir novos tipos de dados, através do agru-
pamento de outros tipos. Isto é chamado de estrutura heterogênea ou simples-
mente estrutura. É comum alguns autores usarem também o nome de registros.
Para este tipo de definição usaremos um operador especial chamado struct.
Este operador serve para agrupar outros tipos de dados numa definição de novo
tipo.
Na definição de estruturas podemos usar tipos primitivos, ou seja tipos já
previstos pela linguagem de programação, ou tipos compostos, aqueles criados
pelas definições do programador.
Os elementos internos a uma estrutura são chamados de membros e serão
acessados por operador próprio. Assim, a estrutura é vista como um tipo com-
posto e poderemos ter variáveis neste formato.
A forma padrão de declaração de uma estrutura é:
Código 4.1
struct nome_estrutura
{
//declaração dos membros
} deinição de variáveis (opcional);
Código 4.2
struct exemplo
{
int num;
char letra;
};
Código 4.3
#include<stdio.h>
struct exemplo
{
int num;
char letra;
};
int main(void) {
struct exemplo var1;
return 0;
}
Veja no código 4.3 que a declaração é feita antes do código da função main().
Na função main a variável var1 é do tipo da estrutura definida, como visto na
linha 10.
Em linguagem C normalmente as declarações de estruturas são globais.
Elas são implementadas próximas ao topo do arquivo com o código fonte do
programa, assim elas são visíveis por todas as funções.
Na declaração do código 4.3, feita entre as linhas 3 e 7, é criado um novo
tipo de estrutura com o nome de exemplo. Esta estrutura contém um número
inteiro chamado num e um caractere chamado letra.
Quando declaramos variáveis de tipos primitivos é preciso informar o nome
do tipo e o nome da variável. Isso também acontece com estruturas, mas há a
necessidade do uso da palavra reservada struct na frente do nome da estrutura
(observe a linha 10 do código 4.3):
130 • capítulo 4
struct exemplo var1;
Código 4.4
struct exemplo
{
int num;
char letra;
} var1;
// ou ...
struct exemplo
{
int num;
char letra;
} var1, var2, var3 ,..., varN;
Código 4.5
#include<stdio.h>
int main(void) {
struct exemplolocal
{
int num;
char letra;
} var2;
return 0;
}
capítulo 4 • 131
Nas linhas 4 a 8 declaramos uma estrutura chamada exemplolocal que con-
tém um número inteiror e um caractere apenas. Na linha 10 declaramos a vari-
ável var1 que é do tipo struct exemplolocal. A variável var2 também é do mesmo
tipo e foi declarada logo após a definição da estrutura.
Porém, neste caso, o escopo da estrutura declarada está restrito à função
main(). Se você tentar usar uma variável deste tipo em outra função receberá
um erro de compilação.
Digite o código 4.6 e depois tente compila-lo.
Código 4.5
#include<stdio.h>
void funcao(void);
int main(void) {
struct exemplolocal
{
int num;
char letra;
} var2;
return 0;
}
void funcao(void) {
132 • capítulo 4
Figura 4.1 – Estrutura com declaração local.
Para que a struct exemplolocal possa ser usada na sua funcao() é necessário
declará-la antes da função main().
int main()
{
exe.n = 3;
exe.c = 'M';
exe.n++;
exe.c = 'N';
return 0;
}
134 • capítulo 4
Um ponto importante a destacar é que o operador ponto (.) tem precedência
sobre o operador de incremento unário (++). Do contrário teríamos um erro de
compilação.
Caso tente acessar um nome de membro que não foi declarado dentro da-
quela estrutura, causara um erro de compilação também. Substitua a linha 15
por esta:
exe.x = 10;
Código 4.7
#include <stdio.h>
struct data
{
int dia;
char mes;
int ano;
};
d1.dia = 3;
d1.mes = 'D';
d1.ano = 2015;
return 0;
}
No código 4.7 criamos uma estrutura chamada struct data que contém va-
riáveis para representar dia, mês e ano. Ao executar o código 4.7 vemos o resul-
tado da figura 4.4.
136 • capítulo 4
Código 4.8
#include <stdio.h>
#include <string.h>
struct data
{
int dia;
char mes[12];
int ano;
};
int main()
{
struct data d1, d2;
d1.dia = 3;
strcpy(d1.mes, "dezembro");
//d1.mes = "dezembro"; //erro de compilacao
d1.ano = 2015;
capítulo 4 • 137
int a[5], b[5];
Código 4.9
#include <stdio.h>
#include <string.h>
struct data
{
int dia;
char mes[12];
int ano;
};
138 • capítulo 4
int main()
{
struct data d1 = {10, "agosto", 2016};
struct data d2 = {11, "setembro", 2015};
return 0;
}
Podemos ver que uma lista de valores separados por vírgula fica entre cha-
ves ({ e }) para definir os valores ao declarar uma variável. Devemos saber que
os valores de inicialização devem estar na mesma ordem dos membros na de-
claração da estrutura. Na figura 4.6 podemos ver o resultado da execução do
código 4.9.
Assim como qualquer outro tipo primitivo na linguagem C (int, float), valores
de estruturas podem ser passados como argumentos para funções e podem ser
retornados de funções.
No código 4.9 criamos uma função que fará a impressão de uma estrutura
endereço criada para armazenar 4 variáveis vetores de char (strings). Também
capítulo 4 • 139
criamos uma função fará a coleta dos dados do usuário e devolverá uma estru-
tura endereço pronta.
Como mostra a figura 4.7, ao executarmos o código 4.9 poderemos fazer a
leitura e impressão dos valores através das funções criadas para manipulação
de estruturas.
Código 4.9
#include <stdio.h>
#deine TAMANHO 50
struct endereco
{
char rua[TAMANHO];
char cidade[TAMANHO];
char estado[TAMANHO];
char cep[TAMANHO];
};
int main()
{
140 • capítulo 4
struct endereco end1;
return 0;
}
printf("\nRua: ");
gets(_endereco.rua);
printf("\nCidade: ");
gets(_endereco.cidade);
printf("\nEstado: ");
gets(_endereco.estado);
printf("\nCEP: ");
gets(_endereco.cep);
return _endereco;
}
• 141
capítulo 4
No código 4.9, a estrutura “endereco” tem 4 arrays com tamanho máximo
50. Na função é declarada uma variável do tipo endereço para podermos pegar
a entrada de dados do usuário (linha 30). Usamos a função gets() para leitura
das informações.
Após o preenchimento dos dados retornamos a variável criada com os valo-
res escolhidos pelo usuário (linha 41). Esta variável será copiada na variável da
linha 20 na função main(). Logo após o retorno da variável, nós a passamos para
a função impressao(), onde o valor de cada membro da estrutura será exibido
na tela.
Estruturas podem ser passadas por valor ou por referência. No exemplo que
vimos, a função impressão() recebe uma variável estrutura e a função leitura()
retorna uma variável estrutura pelo retorno da pilha de execução.
No entanto, seria possível passar a estrutura para a função leitura() por refe-
rência e não necessitar do parâmetro de retorno. Antes de fazermos esta altera-
ção veremos como lidar com estruturas e ponteiros.
(*p).n = 12.0;
142 • capítulo 4
No código 4.10 o acesso à variável “n”, interna à estrutura exemplo, é feito
com o operador “*”, assim como vimos anteriormente. A declaração de pontei-
ros para estrutura, como visto na linha 7, é feita também de maneira parecida
ao que já vimos, adicionando-se o “*” na frente da variável.
Um ponto muito importante, que vou grifar para você é o seguinte:
Para acessar um elemento de uma estrutura por um ponteiro este ponteiro
deve antes ter recebido a devida referência à estrutura. Não basta apenas decla-
rar um ponteiro do tipo estrutura, pois ele não conterá uma região de memória
para armazenar uma estrutura, mas sim para armazenar o endereço de memó-
ria de uma estrutura.
Vejamos o código 4.11.
Código 4.11
#include <stdio.h>
struct exemplo
{
int n;
char c;
};
int main()
{
struct exemplo e1;
struct exemplo *pE1;
pE1 = &e1;
(*pE1).n = 3;
(*pE1).c = 'M';
return 0;
}
capítulo 4 • 143
Na linha 14 o ponteiro pE1 recebe o endereço da variável estrutura e1. Assim,
é possível acessar seus elementos nas linhas 16 e 17.
O resultado na figura 4.8 mostra que o programa executou como esperávamos.
144 • capítulo 4
O operador especial é composto por um traço seguido de um sinal de maior
(->), formando uma seta. Agora que sabemos como acessar de modo mais fá-
cil um ponteiro para uma estrutura vamos reescrever as atribuições do código
4.11.
Código 4.12
#include <stdio.h>
struct exemplo
{
int n;
char c;
};
int main()
{
struct exemplo e1;
struct exemplo *pE1;
pE1 = &e1;
pE1->n = 3;
pE1->c = 'M';
return 0;
}
Veja as diferenças nas linhas 16, 17, 19 e 20. Ficou mais simples, não?
Vou usar a figura 4.10 para uma anotação importante:
- O operador seta (->) é para ponteiros de estruturas e não para estruturas.
Para estruturas continua valendo o operador ponto (.) que vimos, ok?
capítulo 4 • 145
Figura 4.10 – Operador . versus operador ->.
struct aluno
{
int codigo;
char nome[MAX_CHAR];
loat notas[MAX_NOTAS];
};
146 • capítulo 4
void lerAluno(struct aluno [] , int);
void imprimirAluno(struct aluno);
CONEXÃO
Para mais detalhes sobre isto consulte:
• http://www.cprogressivo.net/2012/12/Buffer--o-que-e-como-limpar-e-as-funcoes-
fflush-e-fpurge.html Acesso em março de 2015.
capítulo 4 • 147
printf("\nNota[%d]: ", i);
scanf("%f", &_alunos[index].notas[i]);
flush(stdin);
}
}
Veja na função de leitura, linhas 5, 8 e 12, que a leitura usa a posição passada
pelo índice (index). O acesso ao vetor é feito da mesma forma como em vetores
com tipos primitivos. Só devem ser tomados os devidos cuidados no acesso às
variáveis internas da estrutura. Por exemplo, veja que na linha 5, temos:
scanf("%d", &_alunos[index].codigo);
Assim, será acesso o endereço (&) da variável código que está dentro da estru-
tura aluno que está na posição apontada por index no vetor de alunos (_alunos).
Como pode ser visto na figura 4.11, a estrutura aluno é o elemento homogê-
neo do vetor. Então, é preciso cuidado ao acessar suas variáveis internas.
148 • capítulo 4
_alunos
Aluno
0 1 MAX_VETOR-1
Código Nome[ ]
Notas[ ]
Por fim, veja no código 4.13B que, nas linhas 9 a 14, tornamos a entrada de
notas dinâmicas, ou seja, dependem de MAX_NOTAS. Se mudarmos a constan-
te alteramos facilmente o funcionamento do programa.
Já a função imprimirAlunos() é bem simples. Recebe apenas uma variável
por valor do tipo struct aluno e imprime os dados na tela.
Código 4.13C
int main()
{
struct aluno lista[MAX_VETOR];
int i;
printf("<<<<< LEITURA >>>>>\n");
for (i = 0; i < MAX_VETOR; i++)
{
printf("\nAluno[%d] - dados:\n", i);
lerAluno(lista, i);
imprimirAluno(lista[i]);
}
return 0;
}
capítulo 4 • 149
O código 4.13C usa as funções que declaramos. Se quiser testar com mais
valores de alunos basta mudar o valor de MAX_VETOR.
Minha sugestão é que você escreva todos estes códigos e os teste num pro-
grama novinho!
Código 4.14
#deine TAMANHO 50
struct endereco
{
char rua[TAMANHO];
char cidade[TAMANHO];
char estado[TAMANHO];
char cep[TAMANHO];
};
struct funcionario
{
char id[10];
int idade;
struct endereco casa;
struct endereco empresa;
};
150 • capítulo 4
O acesso às estruturas aninhadas segue as mesmas regras vistas. Por exem-
plo, o acesso das variáveis de um funcionário será feito da seguinte forma:
pessoa.id
pessoa.casa.rua
pessoa.empresa.rua
int main(void)
{
printf("Digite o numero do mes: ");
scanf("%d",&meses);
switch(meses)
{
case Janeiro:
printf("%d - Janeiro",meses);
break;
case Fevereiro:
printf("%d - Fevereiro",meses);
break;
case Marco:
printf("%d - Marco",meses);
break;
case Abril:
printf("%d - Abril",meses);
break;
case Maio:
printf("%d - Maio",meses);
break;
152 • capítulo 4
case Junho:
printf("%d - Junho",meses);
break;
case Julho:
printf("%d - Julho",meses);
break;
case Agosto:
printf("%d - Agosto",meses);
break;
case Setembro:
printf("%d - Setembro",meses);
break;
case Outubro:
printf("%d - Outubro",meses);
break;
case Novembro:
printf("%d - Novembro",meses);
break;
case Dezembro:
printf("%d - Dezembro",meses);
break;
default:
printf("<Valor INVALIDO!!!>\n");
printf("Valor deve ser entre 1 e 12\n\n");
}
return 0;
}
• 153
capítulo 4
4.8 Matrizes
Nós já estudamos o conceito de vetores e tipos homogêneos. Um vetor de 10
variáveis inteiras, por exemplo, é declarado da seguinte forma:
int v[10];
tipo nome[LINHAS][COLUNAS];
Por exemplo:
int mat[3][4];
154 • capítulo 4
loat Media[3][3]; //declaração de matriz 3x3
Media[0][1] = 5; //atribuição simples
scanf(“%f”, &Media[0][0]); //leitura de um elemento
printf(“%f”, Media[1][2]); //impressão de elemento
Uma forma simples, prática e intuitiva para ler ou imprimir todos os ele-
mentos de uma matriz é usar dois laços aninhados. Veja o código 4.16.
Código 4.16
for ( i=0; i<2; i++ ){
for ( j=0; j<2; j++ ){
printf ("%d", matriz[ i ][ j ]);
}
}
Código 4.17
#include<stdio.h>
#deine L 2
#deine C 2
getch();
return(0);
}
CONEXÃO
Mais informações sobre traço de uma matriz (acesso em março de 2015):
https://pt.wikipedia.org/wiki/Tra%C3%A7o_(%C3%A1lgebra_linear)
Para isto, antes, precisamos entender como uma matriz é passada para uma
função.
Para podermos operar valores recebidos em funções como matrizes do tipo
pessoas[2][1]
156 • capítulo 4
precisamos passar para uma função o tamanho da segunda dimensão da
matriz. Assim, nossa função terá o protótipo:
O valor LIMITE será uma constante definida na diretiva #define para deli-
mitar o tamanho das matrizes quadradas.
Nossa função traco() definida no código 4.18A receberá a matriz quadrada
mat[][] e o tamanho n que indica os limites de mat. A implementação da fun-
ção, basicamente, percorre todo seu espaço de memória somando os elemen-
tos que tiverem o mesmo índice de linha e coluna (linha 19), ou seja, elementos
da diagonal principal.
Código 4.18A
#include<stdio.h>
#deine LIMITE 10
int main(void) {
...
return 0;
}
capítulo 4 • 157
Depois, basta retornar o valor somado na variável soma (linha 23).
Código 4.18B
void imprime(int matriz[][LIMITE], int n) {
int i, j;
for ( i=0; i<n; i++ ){
printf("\n");
for ( j=0; j<n; j++ )
{
printf ("\t%d", matriz[i][j]);
}
}
}
Código 4.18C
...
int main(void) {
int n;
int i, j;
int soma = 0;
int mat[LIMITE][LIMITE];
do {
printf("N: ");
scanf("%d", &n);
}
while((n <= 0) || (n > LIMITE));
printf("\nValores da Matriz\n");
for(i = 0; i < n; i++){
for(j = 0; j < n; j++){
printf("\nMat[%d][%d]: ", i, j);
158 • capítulo 4
scanf("%d", &mat[i][j]);
}
}
imprime(mat, n);
printf("\nTraco da Matriz = %d", traco(mat, n));
return 0;
}
Por fim, para deixarmos o código ainda mais elegante e enxuto sugiro a cria-
ção de uma função para leitura dos dados da matriz e alteração do código exis-
tente na função main().
Código 4.18D
...
void leitura(int mat[][LIMITE], int n);
...
int main(void) {
...
//O for das linhas 15 a 20 no código 4.18C
//foi troca apenas pela linha abaixo
leitura(mat, n);
imprime(mat, n);
printf("\nTraco da Matriz = %d", traco(mat, n));
return 0;
}
capítulo 4 • 159
Vamos a outro exemplo de aplicação de matrizes. Criaremos uma função
que calcula e devolve a Matriz Transposta Mt de uma Matriz M. Matriz transpos-
ta é obtida pela troca de linhas e colunas de uma dada matriz.
Por exemplo, imagine a matriz abaixo:
[a1, b1]
[a2, b2]
[a3, b3]
160 • capítulo 4
No código 4.19A declaramos os protótipos (linhas 4 a 7) das funções que
serão importantes para nós e temos também o conteúdo do código main(), res-
ponsável pela lógica principal do programa. Atente-se ao protótipo da função
transposta(). Veja que esta função recebe duas matrizes. Nós devolveremos a
variável Mtransp (linha 4) como sendo a matriz transposta da variável matriz.
Código 4.19A
#include<stdio.h>
#deine MAX 10
int main(void) {
int l = 0, c = 0;
int i, j;
int mat[MAX][MAX], matTrans[MAX][MAX];
do {
printf("Tamanho Linhas: ");
scanf("%d", &l);
} while((l <= 0) || (l > MAX ));
printf("\n");
do {
printf("Tamanho Colunas: ");
scanf("%d", &c);
} while((c <= 0) || (c > MAX ));
printf("\n");
leitura(mat, l, c);
printf("\n<<< MATRIZ ORIGINAL >>>\n");
imprime(mat, l, c);
//obtendo M_transposta
return 0;
}
Mtransp[j][i] = matriz[i][j];
Código 4.19B
...
void transposta(int matriz[][MAX], int Mtransp[][MAX], int l, int
c) {
int i, j;
for(i = 0; i < l; i++){
printf("\n");
for(j = 0; j < c; j++){
Mtransp[j][i] = matriz[i][j];
}
}
}
Observe no código 4.19C que fizemos alguns ajustes nas nossas funções
imprime e leitura, pois agora as matrizes não são necessariamente quadradas.
162 • capítulo 4
Código 4.19C
void imprime(int matriz[][MAX], int l, int c) {
int i, j;
for ( i=0; i<l; i++ ){
printf("\n");
for ( j=0; j<c; j++ )
{
printf ("\t%d", matriz[i][j]);
}
}
printf("\n");
}
Assim, é preciso passar para as funções os limites de linha e coluna nas va-
riáveis l e c.
REFLEXÃO
Nesse capítulo nós vimos o conceito de estruturas heterogêneas que nos permite criar tipos
complexos de dados nas nossas aplicações. Aprendemos como cria-las, passa-la para fun-
ções como parâmetros, acessar seus dados por valor e por referência.
Também vimos como criar vetores dessas estruturas.
Aprenderemos no capítulo 5 como persistir dados, uma importante forma de tornar mais
robustas e interessantes nossas aplicações.
Os conceitos de matrizes nos permitiram utilizar dados em mais de uma dimensão (mesmo
que logicamente e não fisicamente).
LEITURA
Para complementar seu aprendizado computação, programação e linguagem C sugiro os
seguintes links em inglês:
• MIZRAHI V. V.: Treinamento em Linguagem C. São Paulo: Pearson Prentice Hall, 2008.
REFERÊNCIAS BIBLIOGRÁFICAS
KERNIGHAN, B. W.; RITCHIE, D. M. C: a linguagem de programação, padrão ANSI. Rio de Janeiro:
Campus, 1995. 289p.
KELLEY, A.; POHL, I. A Book on C: Programming in C. 4. ed. Boston: Addison Wesley, 1997. 726p.
ZIVIANI, N. Projetos de algoritmos: com implementações em Pascal e C. São Paulo: Pioneira Thomson,
2002. 267p.
ARAÚJO, J. Dominando a Linguagem C. 1. ed. Ciência Moderna, 2004, 146p.
CORMEN, T. H.; LEISERSON, C. E.; RIVEST, R. L.; STEIN, C. Algoritmos: teoria e prática. Rio de
Janeiro: Campus, 2002. 916p.
164 • capítulo 4
FORBELLONE, A. L. Lógica de Programação. 2. ed. São Paulo: Makron Books, 2000. 195p.
KNUTH, D. E. The Art of Computer Programming, Fundamental Algorithms. 3. ed. Boston: Addison
Wesley, 1997. 672p. (vol 1)
SEBESTA, R. W. Conceitos de Linguagens de Programação. 4. ed. Porto Alegre: Bookman, 2000.
624p.
capítulo 4 • 165
166 • capítulo 4
5
Manipulação de
Arquivos, Biblioteca
de Funções e
Definições de Tipos
Neste capítulo nós veremos como manter os dados de nossos programas após
encerrá-los, após desligar o computador ou numa queda de energia. Afinal,
não dá para contarmos apenas com os programas executando em memória
principal.
Também veremos como podemos criar tipos com uma instrução especial.
Com isto você ganhará um novo recurso para aprimorar seu aprendizado do
capítulo 4.
OBJETIVOS
Neste capítulo, os objetivos principais são:
• Persistência de dados.
• Manipulação de arquivos.
• Modo texto e modo binário.
• O uso de typedef.
• Bibliotecas de funções.
• Aplicações diversas.
168 • capítulo 5
5.1 Persistindo Dados com Arquivos
Variáveis, vetores, matrizes e estruturas são todos regiões da memória RAM.
Quando um computador ou dispositivo é desligado ou os programas são fina-
lizados por algum motivo, perdemos as informações, pois a memória RAM é
volátil.
Uma forma de não perdemos as informações é usar os chamados arquivos.
Arquivos, em linguagem C, são mais amplos do que a definição de dados em
disco, ou memória secundária. A palavra arquivo é usada para indicar streams
ou fluxos de bytes, assim, podemos nos referir a arquivos em disco, teclado,
vídeo, impressora e portas de comunicação como sendo arquivos.
Em linguagem C é possível trabalhar com o acesso a arquivos em alto-ní-
vel ou em baixo-nível. As funções para manipulação de arquivos em alto-nível
são bufferizadas, ou seja, mantém uma região de memória com os dados antes
de enviá-los para o destino. Já as funções de baixo nível não são bufferizadas.
Nosso escopo compreende apenas o acesso de alto nível, mas, se tiver interes-
se em conhecer o acesso de baixo nível consulte as leituras recomendadas e
bibliografias.
Arquivos são de grande importância e uma das suas principais vantagens
é a facilidade para utilizar os dados armazenados em qualquer momento, em
vários programas diferentes. Importante saber que quando um computador é
desligado, não perdemos os arquivos salvo na memória secundaria, o disco.
Outra vantagem dos arquivos é a maior capacidade para manipulação de
dados do que vetores e matrizes oferecem.
Quando falamos em utilização e manipulação de arquivos estamos falando
de criação e abertura do arquivo, gravação e leitura de dados no arquivo e fecha-
mento do arquivo.
O acesso a um arquivo começa pela verificação de sua existência no disco.
Um arquivo pode ser acessado de forma sequencial ou aleatória:
Acesso Sequencial:
Acesso Aleatório:
170 • capítulo 5
Figura 5.1 – Trecho de stdio.h.
Você pode verificar que o tipo FILE é uma estrutura contendo informações
descritivas do arquivo em questão.
Em Linguagem C nós trabalharemos com arquivos bufferizados através de
Streams. Este formato nos fornecerá um bom nível de abstração, torna o aces-
so a arquivos independente do dispositivo real. As streams podem ser textuais
ou binárias. Como cada dispositivo (disco, pen drives, cds, etc) é manipulado
de forma diferente no sistema operacional, nós faremos o acesso aos arquivos
pelas streams:
Stream Texto:
capítulo 5 • 171
Stream Binária
172 • capítulo 5
• Funções para leitura e escrita formatada: fprintf() e fscanf()
• Funções para leitura e escrita de blocos de bytes: fwrite() e fread()
Após declarar a variável que vai referenciar o arquivo, é possível usar a fun-
ção fopen() para abrí-lo. A sintaxe para utilização do fopen():
• “r” – opção para abrir um arquivo em modo texto para leitura. O arquivo
deve existir antes de ser aberto.
• “w” – opção para abrir um arquivo em modo texto para gravação. Neste
caso, se o arquivo não existir, será criado, se já existir, o conteúdo anterior será
destruído. Muito cuidado com esta opção!
• “a” – opção para abrir um arquivo em modo texto para gravação na qual os
dados serão adicionados no fim do arquivo, se ele já existir, ou será criado um
novo arquivo, no caso do arquivo ainda não existir.
• “rb” – opção para abrir um arquivo em modo binário para leitura, pareci-
do com o modo “r”, só que trata de um arquivo em binário.
• “wb” – opção para criar um arquivo em modo binário para escrita, como
no modo “w” anterior, só que com arquivo binário.
capítulo 5 • 173
• “ab” – opção para acrescentar dados binários no fim do arquivo, como no
modo “a” anterior, só que o arquivo é binário.
• “r+” – opção para abrir um arquivo em modo texto para leitura e gravação.
Se o arquivo existir seu conteúdo anterior será destruído, se não, um novo ar-
quivo será criado.
• “a+” – opção para abrir um arquivo em modo texto para gravação e leitura.
Se o arquivo existir os dados serão adicionados no final, senão um novo arquivo
será criado.
• “r+b” – opção para abrir um arquivo binário para leitura e escrita. O mes-
mo que "r+" acima, só que com arquivo binário.
• “w+b” – opção para criar um arquivo em modo binário para leitura e escri-
ta. O mesmo que "w+" acima, só que o arquivo é binário.
• “a+b” – opção para acrescentar dados ou criar um arquivo em modo biná-
rio para leitura e escrita. O mesmo que "a+" acima, só que o arquivo é binário.
fclose(nome_variavel);
Código 5.1
#include <stdio.h>
int main()
{
FILE *arquivo;
int idade = 23;
if (arquivo==NULL)
printf ("Erro na abertura do arquivo.");
else
{
174 • capítulo 5
printf("Arquivo aberto com sucesso.");
fprintf(arquivo, "%d", idade);
fprintf(arquivo, "\nJose tem %d anos.",
idade);
}
fclose(arquivo);
return 0;
}
Note a grande semelhança no uso do printf(). São quase idênticas. Sua úni-
ca diferença é que você tem que informar no primeiro parâmetro o nome da
variável que armazena o arquivo FILE, onde os dados vão ser gravados em um
arquivo.
Podemos perceber, abrindo o documento “Arquivo.txt” que se encontra no
diretório onde está salva a sua aplicação, que o conteúdo passado a fprintf()
está salvo agora em um arquivo do tipo texto e este dado pode ser recuperado
mesmo depois que a aplicação ou o sistema for desligado.
capítulo 5 • 175
Figura 5.2 – Os dados de um arquivo gravado.
Código 5.2
#include <stdio.h>
#include <stdlib.h>
int main()
{
FILE *arquivo;
char ch;
arquivo = fopen("Arquivo.txt", "r+t");
176 • capítulo 5
if (arquivo==NULL) {
printf ("Erro na abertura do arquivo.\n");
exit(EXIT_FAILURE);
}
else
{
ch = fgetc(arquivo);
while(ch != EOF)
{
printf("%c", ch);
ch = fgetc(arquivo);
}
}
fclose(arquivo);
return 0;
}
• 177
capítulo 5
Como estudo, experimente excluir o arquivo criado (Arquivo.txt) e tente exe-
cutar seu programa do código 5.2 novamente. Veja o resultado esperado:
Isto ocorrerá, pois você está tentando abrir seu arquivo apenas para leitura
e ele não existe!
Agora vamos criar um programa que terá uma estrutura simples de um
cliente. Vamos registrar 3 clientes, depois salvaremos a informação em disco.
Antes de tudo devemos criar a estrutura do cliente, o código abaixo mostra
como vai ser a estrutura que vamos utilizar em nosso programa:
Código 5.3
#deine MAX 3
struct Cliente
{
char nome[NOME];
int codigo;
int idade;
};
Após declarar uma estrutura “Cliente” simples, que contém o nome, códi-
go e idade, nosso programa deve conter uma função onde vamos poder inserir
uma estrutura Cliente no arquivo texto. A implementação para esta função está
no código 5.4:
178 • capítulo 5
Código 5.4
void gravarCliente(struct Cliente *c) {
FILE *arquivo;
if(arquivo == NULL)
arquivo = fopen("Cliente.txt", "r+t");
if(arquivo == NULL) {
printf ("Erro na abertura do arquivo
Cliente.");
exit(EXIT_FAILURE);
}
else
{
printf("Nome do Cliente:");
scanf("%s", &c->nome);
printf("Codigo do Cliente:");
scanf("%d", &c->codigo);
printf("Idade do Cliente:");
scanf("%d", &c->idade);
capítulo 5 • 179
Na linha 4 tentamos abrir o arquivo para gravação adicionando os dados no
final (parâmetro a). Se o arquivo ainda não existir usamos a linha 6 para criar
um novo arquivo.
Dentro da função foi criada uma variável FILE quer será nossa referência
para o arquivo. Depois de ter criado a variável vamos tentar ler ou criar um ar-
quivo do tipo Cliente, se a criação ou leitura do arquivo for executada com su-
cesso, vamos começar a pegar informação digitada pelo usuário e a estrutura
que for preenchida completa vamos salvar dentro do arquivo “Cliente” usan-
do a função fprintf(), como podemos ver dentro da implementação da função
gravarCliente().
Após implementar a função para gravar uma estrutura “Cliente” dentro de
um arquivo texto, vamos fazer uma função com o objetivo de imprimir toda in-
formação que está salva dentro do arquivo texto, recuperando, assim, sua infor-
mação e estrutura. O código para implementar está função ficará assim:
Código 5.5
void lerClientes(struct Cliente *c) {
FILE *arquivo;
char ch;
if(arquivo == NULL) {
printf("Erro na abertura do arquivo
Cliente.\n");
printf("Nao existe nenhum arquivo Cliente
ainda\n");
}
else
{
ch = fscanf(arquivo,"%s %d %d ", &c->nome,
&c->codigo, &c->idade);
int index = 0;
while(ch != EOF)
{
index++;
180 • capítulo 5
printf("A ordem no arquivo desse cliente
eh: %d", index);
printf("\nO Nome do Cliente eh: %s", c-
>nome);
printf("\nO Codigo do Cliente eh: %d", c-
>codigo);
printf("\nA Idade do Cliente eh: %d", c-
>idade);
printf("\n\n");
ch = fscanf(arquivo,"%s %d %d ", &c-
>nome, &c->codigo, &c->idade);
}
fclose(arquivo);
}
}
Como o próprio nome já diz, está função tem como objetivo ler um arquivo
texto com o nome “Cliente”. Como é uma função somente de leitura e não ire-
mos escrever nada dentro do arquivo, vamos abrir o arquivo utilizando o modo
“r”, o qual indica que este arquivo pode somente ser lido. Depois que conse-
guimos abrir o arquivo o próximo passo será fazer a leitura das informações,
e para este processo utilizaremos a função fscanf(), no qual tem objetivo de
pegar as informações do arquivo de acordo com as informações passadas por
parâmetro.
Vamos entender como funciona o fscanf(), para podermos entender melhor
o código:
Como podemos ver, teremos que passar por parâmetro o ponteiro do ar-
quivo o qual vai ser lido e um char, que seria a string formatada, mas como
essa string funciona? É simples! Quando é necessário ler um arquivo é preciso
de uma string formatada para que a informação possa ser lida corretamente.
Suponha que exista um arquivo com os dados abaixo:
a b c
d f g
h i j
capítulo 5 • 181
Como usar o fscanf para ler estas informações? Para poder ler um arquivo
é preciso primeiramente saber qual o formato do arquivo. Neste exemplo é um
formato simples, são 3 caracteres separados por um espaço e no ultimo uma
quebra de linha, para passar este tipo de informação para o fscanf(), seria desta
maneira “%c %c %c\n”.
Para que possamos pegar a primeira linha do arquivo exemplo ficaria desta
maneira:
char a, b, c;
// abrir arquivo …..
// usar função fscanf()
fscanf(nome_arquivo, “%c %c %c\n”, &a, &b, &c);
Código 5.6
#include <stdio.h>
#include <stdlib.h>
#deine MAX 3
#deine NOME 100
struct Cliente
{
char nome[NOME];
int codigo;
int idade;
};
182 • capítulo 5
void gravarCliente(struct Cliente *c);
void lerClientes(struct Cliente *c);
int main() {
}
} while(index_menu!=0);
}
• 183
capítulo 5
Figura 5.5 – Execução do código 5.6.
184 • capítulo 5
Figura 5.6 – Conteúdo do Arquivo Clientes.txt.
Onde:
capítulo 5 • 185
• n_de_elementos: número de elementos que serão escritos.
• arq_binario: ponteiro para o arquivo de gravação.
• elemento : Ponteiro para um bloco do elemento onde vai ser escrito a in-
formação buscada no arquivo.
• tamanho_elemento: tamanho em bytes de cada elemento que vai ser
escrito.
• n_de_elementos: número de elementos que serão escritos.
• arq_binario: ponteiro para o arquivo de leitura.
Código 5.7A
void gravarCliente(void) {
FILE *arquivo;
struct Cliente c;
arquivo = fopen("ClienteBinario.txt", "ab");
186 • capítulo 5
if(arquivo == NULL)
arquivo = fopen("ClienteBinario.txt", "w+b");
if(arquivo == NULL) {
printf ("Erro na abertura do arquivo
Cliente.");
exit(EXIT_FAILURE);
}
else
{
printf("Nome do Cliente:");
gets (c.nome);
printf("Codigo do Cliente:");
scanf("%d", &c.codigo);
flush(stdin);
printf("Idade do Cliente:");
scanf("%d", &c.idade);
flush(stdin);
fwrite(&c, sizeof(struct Cliente), 1,
arquivo);
}
}
capítulo 5 • 187
extensão textual, o seu conteúdo é binário (o .txt é apenas um indicar, o que vale
é o conteúdo do arquivo), como você pode ver na figura 5.7.
Código 5.7B
void lerClientes(void) {
FILE *arquivo;
struct Cliente c;
int index;
arquivo = fopen("ClienteBinario.txt", "rb");
if(arquivo == NULL) {
printf("Erro na abertura do arquivo
Cliente.\n");
printf("Nao existe nenhum arquivo Cliente
ainda\n");
188 • capítulo 5
}
else
{
index = 0;
while(fread(&c, sizeof(struct Cliente)
,1,arquivo))
{
index++;
printf("A ordem no arquivo desse cliente
eh: %d", index);
printf("\nO Nome do Cliente eh: %s",
c.nome);
printf("\nO Codigo do Cliente eh: %d",
c.codigo);
printf("\nA Idade do Cliente eh: %d",
c.idade);
printf("\n\n");
}
fclose(arquivo);
}
}
Código 5.7C
#include <stdio.h>
#include <stdlib.h>
#deine MAX 3
#deine NOME 100
struct Cliente
{
char nome[NOME];
capítulo 5 • 189
int codigo;
int idade;
};
void gravarCliente(void);
void lerClientes(void);
int main() {
int index_menu;
do
{
printf("Qual opção voce deseja executar\n");
printf("[0] - Sair\n");
printf("[1] - Salvar Clientes no Arquivo
Texto\n");
printf("[2] - Imprimir Clientes do Arquivo
Texto \n");
scanf("%d", &index_menu);
flush(stdin);
switch(index_menu)
{
case 0:
{
index_menu = 0;
break;
}
case 1:
{
gravarCliente();
break;
}
case 2:
{
lerClientes();
break;
190 • capítulo 5
}
default:
{
printf("Escolha Errada");
break;
}
}
} while(index_menu!=0);
}
/* Protótipo */
void rewind(FILE *fp);
/* Protótipo */
int ferror(FILE *fp);
/* Protótipo */
int remove(const char *ilename);
/* Protótipo */
int flush(FILE *fp);
/* Em Linux use a função abaixo */
__fpurge(stdin);
/* Protótipo */
int fseek(FILE *fp, long numbytes, int origin);
Onde:
192 • capítulo 5
5.6 Definindo Tipos com Typedef
Nós aprendemos no capítulo 4 sobre a criação e utilização de estruturas hete-
rogêneas. No entanto, toda utilização de estrutura necessitava da palavra reser-
vada struct.
Ao usar typedef podemos evitar o uso de struct e, ao mesmo tempo, definir
um novo tipo abreviado de uma estrutura. Funciona com variáveis simples, ve-
tores, etc, também!
Sua forma geral é:
Por exemplo:
Veja o código 5.8 no qual usamos typedef com estruturas mais complexas.
Código 5.8
...
struct end {
char rua[40];
int num;
char bairro[30];
char cidade[20];
};
typedef struct end endereco;
endereco e1;
strcpy(e1.rua, "Tiradentes");
strcpy(e1.bairro, "Jardim dos Inconidentes");
capítulo 5 • 193
strcpy(e1.cidade, "Brasilia");
e1.num = 100;
A partir da linha 8 a estrutura end passa a poder ser referenciada como ape-
nas endereco.
É possível ainda usar typedef de outras duas formas com estruturas:
Inserir na própria declaração:
typedef struct {
char rua[40];
int num;
char bairro[30];
char cidade[20];
} Endereco;
194 • capítulo 5
manutenções. Por isto, podemos colocá-las em um arquivo separado, organi-
zando-os por quais funções serão armazenadas em quais arquivos. É interes-
sante que todas as funções que estão conceitualmente relacionadas fiquem no
mesmo arquivo.
Ao fazer isto estamos criando uma biblioteca de funções!
Tecnicamente uma biblioteca é diferente de um arquivo de funções compi-
lado separadamente, pois quando as rotinas em uma biblioteca são linkedita-
das com o restante do programa apenas as funções que o programa realmente
usa são carregadas e linkeditadas. Em um arquivo compilado separadamente,
todas as funções são carregadas e linkeditadas com o programa.
Os compiladores C vem com biblioteca C padrão de funções para realizar
as tarefas mais comuns (como vimos ao longo dos capítulos). O padrão ANSI C
especifica um conjunto mínimo que estará contido no compilador.
Quando fazemos referência a uma função da biblioteca padrão usamos a
seguinte diretiva:
#include <arquivo_cabeçalho>
Para criarmos nossa biblioteca vamos criar os arquivos bib.h, bib.c e o ar-
quivo main. Basta adicionar estes arquivos no seu projeto no dev-c. Se estiver
usando o compilar online, basta criar os arquivos separadamente. Depois mos-
trarei o comando de compilação.
O código 5.9 mostra o arquivo bib.h e seu conteúdo. Veja que ele contém
apenas as definições das funções, seus protótipos.
capítulo 5 • 195
Por segurança, nós usamos as diretivas ifndef e endif para que o conteúdo
ali seja tratado como uma macro que só será compilada e inserida se ainda não
foi feito por nenhuma outra biblioteca.
Já o arquivo bib.c, no código 5.10 contém as implementações das funções
da biblioteca. Veja que ele precisa “incluir” a bib.h em suas diretivas. Outro de-
talhe é o uso de aspas (“) ao invés de “<” e “>”. Isto se dá, pois a biblioteca defi-
nida está em nossa pasta de trabalho. Os sinais de menor e maior são usados
para as bibliotecas na pasta padrão de seu compilador.
Por sim, o código 5.11 apresenta nossos testes, mais uma vez incluindo bi-
b.h entre aspas.
196 • capítulo 5
{
printf("Area ret: %.2f \n", retangulo(12, 3.3f));
printf("Area tri: %.2f \n", triangulo(10, 4.3f));
printf("Area quadrado: %.2f \n", quadrado(14));
printf("Area circulo: %.2f \n", circulo(8.4f));
return 0;
}
Veja que, pela figura 5.8, os 3 arquivos fazem parte do seu projeto agora.
capítulo 5 • 197
Figura 5.9 – Compilando bibliotecas em linha de comando.
Cria o arquivo main.exe (que não precisa desta extensão no Linux) adicio-
nando nossa biblioteca criada e compilada no programa main.
Se você estiver usando a IDE Dev-cpp basta compilar e executar o seu
programa.
REFLEXÃO
Neste capítulo expandimos nossos recursos para programação em linguagem C. Vimos
como persistir dados, manipulando arquivos em modo texto ou binário, aprendemos um pou-
198 • capítulo 5
co sobre a criação de bibliotecas de funções e sobre diretivas para criação delas.
Minha sugestão agora é que você use todo o conhecimento aprendizado e aplique nos exem-
plos anteriores do livro. Tente criar aplicações persistentes em exemplos anteriores, salvando
o estado das suas execuções.
Bom trabalho!
LEITURA
Para complementar seu aprendizado computação, programação e linguagem C sugiro os
seguintes links em inglês:
• MIZRAHI V. V.: Treinamento em Linguagem C. São Paulo: Pearson Prentice Hall, 2008.
REFERÊNCIAS BIBLIOGRÁFICAS
KERNIGHAN, B. W.; RITCHIE, D. M. C: a linguagem de programação, padrão ANSI. Rio de Janeiro:
Campus, 1995. 289p.
KELLEY, A.; POHL, I. A Book on C: Programming in C. 4. ed. Boston: Addison Wesley, 1997. 726p.
ZIVIANI, N. Projetos de algoritmos: com implementações em Pascal e C. São Paulo: Pioneira
Thomson, 2002. 267p.
ARAÚJO, J. Dominando a Linguagem C. 1. ed. Ciência Moderna, 2004, 146p.
capítulo 5 • 199
CORMEN, T. H.; LEISERSON, C. E.; RIVEST, R. L.; STEIN, C. Algoritmos: teoria e prática. Rio de
Janeiro: Campus, 2002. 916p.
FORBELLONE, A. L. Lógica de Programação. 2. ed. São Paulo: Makron Books, 2000. 195p.
KNUTH, D. E. The Art of Computer Programming, Fundamental Algorithms. 3. ed. Boston:
Addison Wesley, 1997. 672p. (vol 1)
SEBESTA, R. W. Conceitos de Linguagens de Programação. 4. ed. Porto Alegre: Bookman, 2000.
624p.
200 • capítulo 5