Escolar Documentos
Profissional Documentos
Cultura Documentos
C
C
S. D
edudobay@gmail.com
Instituto de Fsica
Universidade de So Paulo
Sumrio
Prefcio
Introduo
1 Princpios bsicos
1.1 O ncleo de um programa
1.2 Variveis . . . . . . . . .
1.3 Entrada e sada de dados
1.4 Matemtica bsica . . . .
1.5 Boas maneiras em C . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
7
7
9
11
13
16
2 Controle de uxo
2.1 Desvios condicionais: if . . . . . .
2.2 Repetindo operaes: laos (loops)
2.3 Contadores e laos for . . . . . . .
2.4 Condies compostas . . . . . . .
2.5 Repeties encaixadas . . . . . . .
2.6 Variveis booleanas . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
19
19
22
27
31
32
34
3 Funes
3.1 Introduo . . . . . . . . . . . .
3.2 Os parmetros e o valor de sada
3.3 Usando funes . . . . . . . . .
3.4 Trabalhando com vrias funes .
3.5 Escopo de variveis . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
37
37
39
41
42
43
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
45
45
47
50
58
61
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
ii
5
Sumrio
Ponteiros e vetores
5.1 Prolegmenos . . . . . . . . . . . . .
5.2 Ponteiros como parmetros de funes
5.3 Cuidado com os ponteiros! . . . . . .
5.4 Vetores . . . . . . . . . . . . . . . .
5.5 Vetores como argumentos de funes .
5.6 Ponteiros e vetores . . . . . . . . . . .
5.7 Strings . . . . . . . . . . . . . . . . .
5.8 Mais sobre entrada e sada . . . . . . .
Algoritmos
Mais ponteiros
7.1 Matrizes . . . . . . . . . . . .
7.2 Alocao dinmica de memria
7.3 Ponteiros duplos . . . . . . . .
7.4 Ponteiros para funes . . . . .
7.5 Escopo de variveis . . . . . .
7.6 Funes recursivas . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
63
63
66
68
69
72
74
75
79
81
.
.
.
.
.
.
83
83
84
87
88
90
90
Estruturas
8.1 Structs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
8.2 Listas ligadas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
91
91
93
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
Apndices
A Compilao
95
B Tabelas de referncia
97
Referncias Bibliogrcas
99
Prefcio
Introduo
Este captulo introduz alguns conceitos bsicos sobre computao, mas sem entrar na linguagem C.
O que um computador?
A denio mais ampla de computador a de uma mquina que realiza uma srie de operaes lgicas ou matemticas sobre um conjunto de dados e devolve o resultado para o
usurio. O computador que conhecemos no o nico exemplo disso; podemos citar diversos aparelhos eletrnicos com essas caractersticas, de calculadoras a telefones celulares.
Um computador tem um conjunto de instrues que correspondem s operaes que podem ser feitas com ele. Uma seqncia lgica dessas instrues tal qual uma receita de
bolo o que conhecemos como programa.
Nos primeiros computadores, toda a programao era feita diretamente no circuito do
aparelho: no se podia modicar o funcionamento do computador sem uma reconstruo
fsica dele ou de alguma de suas partes. Ainda existem computadores desse tipo o caso
de uma calculadora simples, por exemplo: voc no pode criar programas e introduzir nela;
necessrio mexer diretamente nas placas de circuito dela.
O computador que conhecemos faz parte de um conjunto especial dentro da nossa denio de computador: nele, os programas so armazenados na memria (e no inseridos
diretamente atravs dos circuitos), da mesma maneira que os dados, e podem ser criados
e modicados utilizando o prprio Computador. A encarnao mais comum desse tipo de
funcionamento um modelo conhecido como arquitetura de von Neumann. (A palavra arquitetura usada em geral para descrever o modelo de funcionamento interno de um computador.)
Esse modelo descreve o funcionamento geral do computador em termos da interao
entre trs componentes:
uma unidade de processamento conhecida como CPU (unidade central de processamento, na sigla em ingls) ou processador que responsvel por todas as operaes
matemticas e lgicas. basicamente o crebro do computador;
um espao para armazenamento (temporrio) de dados, que a memria RAM (sigla
em ingls para memria de acesso randmico);
3
Introduo
quaisquer dispositivos de entrada e sada, que recebem e enviam informaes de/para
o usurio. Os exemplos mais imediatos so o monitor (dispositivo de sada), o teclado
e o mouse (dispositivos de entrada). Tambm se encaixam aqui os dispositivos de
armazenamento de dados, como o disco rgido, que tm papel tanto de entrada quanto
de sada.
Memria
Unidade de
processamento
(CPU)
Sada
Monitor
Mdias externas
Entrada
Teclado
Mouse
Mdias externas
Linguagens de programao
J vimos que um programa simplesmente um conjunto de instrues ou comandos que
dizem ao computador como realizar as operaes que ele deve fazer. Mas como vm a ser
essas instrues? O processador do computador s consegue compreender uma linguagem
que chamamos de linguagem de mquina; essa linguagem, inclusive, diferente para cada
arquitetura de processadores. Um programa escrito em linguagem de mquina consiste de
uma srie de bits (os famosos zeros e uns), que, para ns, parecem no ter nenhum signicado.
Uma instruo razoavelmente simples seria escrita assim:
10110000 00101010
No seria nada prtico usar essa linguagem para fazer programas com alto nvel de complexidade; por isso, foram desenvolvidas diversas linguagens de programao, que servem
como intermedirio entre a linguagem humana e a linguagem de mquina.
Uma linguagem de programao deve ser, por um lado, legvel e compreensvel para
um humano, e, por outro lado, sucientemente simples para que possa ser compreendida
Para ser mais preciso, a arquitetura Intel de 32 bits, ou, abreviadamente, IA-32. Mais tarde veremos o que
isso signica.
5
completamente e sem ambigidades por um computador. Geralmente, uma linguagem de
programao composta de:
um conjunto de palavras-chave (geralmente em ingls, como
associadas a certos comandos e recursos da linguagem;
Princpios bsicos
Veja que, aqui, imprimir no tem nada a ver com a sua impressora que joga tinta no papel. Nesse livro,
sempre que usar essa palavra, entenda como uma referncia ao ato de mostrar alguma mensagem na tela.
Esta linha pede ao compilador que disponibilize para ns algumas funes de entrada
e sada de dados, que permitem que voc exiba mensagens na tela e leia dados que o
usurio digitar no teclado. Mais adiante veremos como que essa instruo funciona.
int main()
{ }
Se voc est curioso, esse int signica que o valor que a funo devolve um nmero
inteiro, e os parnteses vazios indicam que a funo no recebe nenhum parmetro. No
se preocupe se isso no zer muito sentido; um pouco mais adiante estudaremos funes
com detalhe, e voc poder entender melhor o que tudo isso signica. Aceitar isso como
uma frmula pronta por enquanto no lhe far nenhum mal.
printf("Ol, maravilhoso mundo da programao!\n");
Aqui printf o nome uma funo da biblioteca padro ( possvel us-la graas ao #include que colocamos l em cima!). Essa funo simplesmente toma a mensagem (uma
sequncia de caracteres) que lhe foi passada e mostra-a na tela.
Nessa linha, passamos como parmetro a essa funo a mensagem que queremos imprimir (usando aspas duplas, veja bem). E o que aquele \n intruso no nal? Ele um
cdigo especial que representa uma quebra de linha indicando que qualquer coisa que
for impressa depois da nossa mensagem deve ir para a linha seguinte. Se omitssemos
o \n, a prxima mensagem que fosse eventualmente impressa sairia grudada nessa; isso
til em vrias situaes, mas no particularmente nessa; em geral melhor terminar a
sada de um programa com uma quebra de linha.
Essa coisa entre aspas chamada de sequncia de caracteres (adivinhe por qu!), e
tambm bastante conhecida pelo seu nome em ingls, string (literalmente, cadeia [de
caracteres]). Para falar a verdade, usarei principalmente a palavra string daqui em diante.
Note que usamos um ponto-e-vrgula aqui. Da mesma maneira que em portugus usamos um ponto nal para encerrar uma frase, em C precisamos usar obrigatoriamente um
ponto-e-vrgula para encerrar um comando.
return 0;
Essa instruo encerra a execuo do programa (por isso, deve ser sempre a ltima da
funo main). Alm disso, o nmero zero serve para indicar ao sistema que o programa terminou com sucesso (nmeros diferentes de zero indicariam um erro); uma
conveno da linguagem. Voc entender melhor como isso funciona quando falarmos
detalhadamente de funes, no captulo 3.
Note novamente o ponto-e-vrgula.
Variveis
1.2 Variveis
Uma das necessidades mais comuns em programao a de guardar dados em algum lugar
da memria. Para isso, a maioria das linguagens de programao usa o conceito de variveis, que so simplesmente pedaos da memria que servem para guardar um certo valor
(um nmero, por exemplo) e que tm um nome. Com isso, voc no precisa se preocupar
em saber em que lugar da memria voc guardar seus dados; todo o trabalho ca para o
compilador.
Em C, h trs tipos primitivos de dados: nmeros inteiros, nmeros de ponto utuante
(um nome pomposo para os nmeros fracionrios, que tem a ver com a maneira como os
computadores trabalham com eles) e caracteres (como a, #, Z, 5 o caractere que
representa o nmero 5, e no o nmero em si). A esses trs tipos de dados correspondem os
quatro tipos primitivos de variveis, listados na tabela 1.1.
Na verdade, eu poderia dizer que so apenas dois esses tipos: os nmeros inteiros e os
de ponto utuante. O que ocorre que os caracteres so representados na memria como se
fossem nmeros (inteiros); por exemplo, quando eu escrevo os caracteres abc em algum lugar
(em ltima instncia, isso ca em algum lugar da memria), o que o computador guarda na
memria so os trs nmeros 97, 98, 99. Isso est relacionado conhecida tabela ASCII,
que nada mais que uma conveno de quais caracteres correspondem a quais nmeros
(consulte o apndice para ver uma tabela ASCII completa). Por causa disso, o tipo de dados
usado para armazenar caracteres tambm podem ser usado para guardar nmeros, embora
isso no seja to comum, pois ele s permite guardar nmeros pequenos.
Esses dois ou trs tipos de dados traduzem-se em quatro tipos primitivos de variveis,
resumidos na tabela 1.1.
Tabela 1.1: Os quatro tipos primitivos de variveis na linguagem C
Tipo
int
float
double
char
Utilidade
Armazena um nmero inteiro
Armazena um nmero de ponto utuante
Como oat, mas fornece maior preciso
Guarda um nico caractere. Com esse tipo tambm podemos guardar sequncias de caracteres (strings), mas isso requer um outro recurso da linguagem
que s veremos mais tarde.
Esses tipos so chamados de primitivos porque eles podem ser modicados e combinados de certas maneiras, de modo a criar estruturas mais complexas de dados (por exemplo,
10
as tais sequncias de caracteres), como veremos mais tarde. Por enquanto, usaremos apenas
os tipos inteiros; trabalharemos com nmeros de ponto utuante a partir do captulo ??.
Cada varivel que quisermos usar deve ser criada com antecedncia; para isso, precisamos dizer o nome e o tipo das variveis. Para isso, usamos um comando no seguinte
formato:
tipo_da_variavel nome_da_variavel;
Esse tipo de comando chama-se declarao de variveis, e faz com que o computador reserve na memria um espao suciente para guardar valores do tipo que voc pediu. Vejamos
alguns exemplos de declaraes:
int dia;
int mes;
int ano;
float preco_unitario;
float preco_total;
Se voc tem vrias variveis do mesmo tipo, voc pode declar-las todas de uma vez
s simplesmente coloque os vrios nomes separados por vrgulas. Poderamos, assim,
condensar o exemplo anterior com apenas duas linhas:
int dia, mes, ano;
float preco_unitario , preco_total;
Mas ateno: nos nomes de variveis voc no pode usar acentos nem nenhum outro
tipo de caractere especial; o C s aceita os caracteres alfabticos de A a Z (minsculos e
maisculos), dgitos de 0 a 9 e o trao inferior (_). H ainda a restrio de que o primeiro
caractere no pode ser um nmero. Alm disso, como a linguagem sensvel diferena
entre maisculas e minsculas, a varivel var diferente da varivel VAR (e portanto ambas
podem existir ao mesmo tempo).
Aps criar variveis, o prximo passo aprender a us-las. Uma das coisas mais simples
que podemos fazer guardar um valor numa varivel: usamos um sinal de igual, escrevendo
sua esquerda o nome da varivel, e direita o valor que queremos guardar:
dia = 22;
mes = 5;
ano = 2008;
11
Na verdade, voc pode atribuir a uma varivel o valor de uma expresso qualquer; por exemplo, expresses aritmticas envolvendo variveis e constantes, como veremos a seguir.
A primeira atribuio de uma varivel costuma ser chamada de inicializao. A inicializao de uma varivel muito importante pois, quando voc declara uma varivel, o
lugar da memria que ela ocupa apenas reservado para seu uso, sem atribuir nenhum valor
padro, como 0 ou 23. Ali h inicialmente um valor aleatrio, que foi deixado por algum
programa que j usou aquele lugar da memria. Por isso,
Voc no pode tentar acessar o valor de uma varivel antes de lhe atribuir um valor.
Muitos dos problemas esquisitos que podem ocorrer em programas esto relacionados a variveis que no foram inicializadas.
Devemos ter em mente que uma varivel apenas um lugar para guardar dados, assim
como uma caixa ou uma gaveta, desprovido de qualquer tcnica de magia ou adivinhao. O
valor de uma varivel s muda quando voc o zer explicitamente. Por exemplo:
int a, b, c;
a = 5;
b = a;
c = 5*a;
a = 7;
Se eu escrever isso, ao nal desse trecho, o valor de a ser 7, mas o valor de b continuar
sendo 5 e o de c continuar sendo 25. As variveis so sempre independentes, no h como
criar vnculos do tipo c sempre vale 5a.
Tambm vale reforar que uma varivel um lugar, de certa maneira, temporrio. Quando
lhe atribumos um valor, qualquer valor que nela houvesse anteriormente completamente
esquecido.
Infelizmente, isso est errado! No entanto, ainda possvel usar a funo printf para
esse propsito; s precisamos mudar um pouco a maneira de us-la. Observe o seguinte
exemplo:
12
#include <stdio.h>
int main()
{
int dia, mes, ano;
dia = 22;
mes = 7;
ano = 2008;
printf("Hoje dia %d/%d/%d.\n", dia, mes, ano);
return 0;
}
Este exemplo mostra como podemos imprimir uma string que contenha o valor de uma
varivel: colocamos o cdigo %d onde queremos que a varivel aparea; a varivel em si
especicada direita da string. Neste exemplo, j colocamos trs variveis de uma vez (com
o cdigo repetido em trs lugares diferentes), e o computador as substitui na ordem em que
elas aparecem aps a mensagem que voc escreveu. H uma ilustrao disso na gura 1.1.
Matemtica bsica
13
e termine com a tecla Enter, interpretar o que o usurio escreveu e salvar em uma ou mais
variveis.
Para cada dado que voc quiser ler, voc deve fornecer funo scanf duas informaes:
o tipo de dado (inteiro, nmero de ponto utuante, etc.) e o lugar onde o dado deve ser
armazenado (uma varivel). Um exemplo simples:
#include <stdio.h>
int main()
{
int idade;
printf("Qual a sua idade? ");
scanf("%d", &idade);
return 0;
}
Veja que aqui apareceu novamente o cdigo %d, que signica o mesmo que na funo
printf, mas funciona ao contrrio: ele l um inteiro do teclado e guarda seu valor na prxima
varivel da lista de parmetros. Voc tambm poderia ler vrios valores e guardar em vrias
variveis num comando s:
#include <stdio.h>
int main()
{
int dia, mes, ano;
printf("Qual a sua data de nascimento (no formato dd mm aaaa)? ");
scanf("%d %d %d", &dia, &mes, &ano);
return 0;
}
Mas aqui ainda tem uma coisa esquisita: tem um E comercial (&) antes do nome de cada
varivel. Pra que isso serve? Ele uma maneira de dizer que estamos nos referindo ao
lugar em que est a varivel (um lugar na memria), e no ao seu valor. Isso necessrio
justamente para que a funo possa colocar o valor lido no lugar que queremos veja que
no estamos atribuindo explicitamente nenhum valor s nossas variveis. (Veremos como
isso funciona no Captulo ??, Ponteiros.) Voc no pode deixar esse E comercial de lado;
se voc o omitir, o valor lido no ir para a varivel que voc pediu, e seu programa poder
terminar com um erro do tipo Falha de segmentao.
14
As operaes matemticas bsicas no so nenhum mistrio em C. A linguagem possui os mesmos operadores matemticos com os quais estamos acostumados (com algumas
adaptaes que foram outrora necessrias), de acordo com a seguinte tabela:
Operador
+
*
/
%
Signicado
adio
subtrao
multiplicao
diviso
resto da diviso inteira
Os operadores em C funcionam da maneira usual para realizar uma operao com dois
nmeros, basta colocar o operador entre eles. Esses nmeros podem ser tanto constantes
numricas quanto variveis (ou qualquer coisa que em C for considerada uma expresso).
Formalmente, os nmeros ou as expresses s quais se aplica um operador so chamados de
operandos.
Podemos tambm montar expresses aritmticas mais complexas, e nelas as operaes
seguem tambm a ordem usual: multiplicaes e divises so realizadas antes de adies e
subtraes, a menos que voc use parnteses para alterar a ordem. Observe que para esse
m voc no pode usar os colchetes e chaves; em compensao, voc pode usar vrios nveis
de parnteses, como em 3 * (5 * (7 - 1)).
O operador - (o sinal de menos) tambm pode ser usado para obter o valor de uma
varivel com o sinal trocado. Ele tambm usado como na matemtica: -num corresponde
ao valor de num com o sinal trocado. Note que esse valor no muda o valor da varivel, apenas
obtm uma cpia dele com o sinal trocado. Para alterar o valor da varivel, necessrio
dizer explicitamente que voc quer guardar o valor com sinal trocado de volta na varivel.
Veja alguns exemplos de uso dos operadores aritmticos, com os resultados anotados ao
lado:
x = 14 / 7;
y = 12 % 7;
/* 2 */
/* 5 (resto da diviso) */
x = 12 + 5 * 7;
x = (12 + 5) * 7;
/* 47 */
/* 119 */
x = ((12 + 5) * 7 + 2) * 3; /* 363 */
fat = 1 * 2 * 3 * 4 * 5;
/* 120 */
x = y + 2*z;
*/
x = -x;
/* troca o sinal de x e
guarda de volta em x */
x = x + 1;
Aqui est includo tambm o uso do operador %, j que o resto tambm resultado de uma operao de diviso.
Matemtica bsica
15
Note (pelos dois ltimos exemplos) que podemos usar a mesma varivel nos dois lados
da operao de atribuio. Isso no tem nenhuma ambigidade para o computador; ele sabe
reconhecer que, no lado direito, voc est fazendo algum clculo que envolve o valor da
varivel, e que o lado esquerdo representa o lugar onde voc quer guardar o resultado desse
clculo. O computador primeiro faz o clculo com o valor atual da varivel, e depois guarda
de volta na varivel.
Voc pode usar expresses aritmticas diretamente na funo printf: os parmetros no
precisam ser nomes de variveis. Por exemplo:
int x;
printf("Digite um nmero inteiro: ");
scanf("%d", &x);
printf("O dobro de %d %d.\n"
"O quadrado de %d %d.\n", x, 2*x, x, x*x);
9
TC .
5
#include <stdio.h>
int main()
{
int celsius , fahrenheit;
printf("Digite a temperatura de hoje: ");
scanf("%d", &celsius);
fahrenheit = 9 * celsius / 5 + 32;
printf("Hoje est fazendo %d graus Fahrenheit!\n", fahrenheit);
return 0;
}
Voc, que conhece a propriedade comutativa da multiplicao, deve imaginar que a conta
principal do programa poderia ser escrita de vrias maneiras:
fahrenheit = 9 * celsius / 5 + 32;
fahrenheit = celsius * 9 / 5 + 32;
fahrenheit = celsius / 5 * 9 + 32;
fahrenheit = 9 / 5 * celsius + 32;
16
Em teoria, isso estaria certo. Agora tente executar o programa com as quatro variaes,
testando algumas temperaturas diferentes. Voc deve reparar que as duas primeiras variaes
do os valores mais prximos do valor correto, enquanto que a terceira d um erro maior e a
quarta d um erro realmente grosseiro. Por que isso ocorre? Para o computador, faz sentido
que uma operao entre inteiros deve dar um resultado inteiro; por isso, quando voc divide
dois inteiros, voc obtm apenas a parte inteira do resultado. Dessa maneira, quando fazemos
no programa a diviso por 5, obtemos a verso arredondada do valor que esperaramos obter,
por exemplo, numa calculadora.
Os resultados foram diferentemente incorretos porque os arredondamentos so realizados em diferentes etapas. Sabendo que o C calcula expresses matemticas sempre da
esquerda para a direita, podemos prever, por exemplo, que, na ltima variao da nossa
conta, o primeiro clculo realizado 9 / 5, que d 1 (com resto 4). Assim, a temperatura
em graus Celsius simplesmente somada com 32, o que d um erro muito grosseiro. Na
terceira conta, o problema na diviso da temperatura por 5 qualquer temperatura que
no seja mltipla de 5 sofrer um arredondamento para baixo, de modo que 29 C seriam
convertidos para 77 F, que na verdade correspondem a 25 C.
Por isso, neste caso, a melhor opo a usar a primeira ou a segunda, j que o arredondamento na diviso feito sobre um nmero maior e ocorre numa das ltimas etapas, de
modo que o erro no se propaga mais para outras contas ou melhor, ele s se propaga
para a adio, na qual no h perigo de haver outros erros, pois a adio de inteiros sempre
exata.
Mais adiante veremos como trabalhar com nmeros de ponto utuante, o que resolver
o problema das divises mas tambm traz alguns outros problemas, como tambm vamos
estudar.
Existe tambm uma outra maneira de fazer comentrios, que foi copiada da linguagem
C++: usam-se duas barras //, mas o comentrio s vale at o nal da linha. Essa segunda
maneira s foi xada no padro mais recente da linguagem (C99); se seu compilador estiver
operando no modo ANSI, ele no gostar desse segundo tipo de comentrio.
/* Comentrio estilo C
* ===================
*
Comea com barra e asterisco ,
*
*/
Boas maneiras em C
17
Tenho algumas palavrinhas a proferir sobre o uso dos comentrios, mas postergarei esse
assunto para o prximo captulo, quando tivermos visto um pouco mais de teoria.
0;}
#include <stdio.h>
int main()
{
printf("Ol, maravilhoso mundo da programao!\n");
return 0;
}
#include <stdio.h>
int
main(
){
printf(
"Ol, maravilhoso mundo da programao!\n"
);
return 0;
}
Mas qual deles mais fcil de ler? O segundo, no ? Embora o estilo especco que
voc ir usar possa variar de acordo com sua preferncia, h alguns princpios gerais que
voc deve procurar seguir ao escrever um programa:
Em ingls, h a expresso whitespace, que indica qualquer sequncia de espaos, tabulaes e quebras de linha.
No conheo nenhuma expresso equivalente em portugus, ento teremos de conviver com essa ambiguidade.
18
Controle de uxo
Com o pouco que aprendemos at agora, s possvel construir programas lineares, ou seja,
que s sabem fazer uma determinada sequncia de operaes, quaisquer que sejam os dados
fornecidos (ou outras condies). Na maioria dos programas, isso no suciente; preciso que o programa saiba fazer decises. A capacidade do computador de fazer decises
conhecida como controle de uxo.
19
20
Signicado
menor que
menor que ou igual a
maior que
maior que ou igual a
igual a (cuidado! so DOIS iguais!)
diferente de
Existe uma abreviao muito til que pode ser usada com a estrutura if (e tambm pode
ser usada com outros tipos de estruturas que veremos a seguir). Quando houver apenas
um comando dentro do bloco delimitado pelas chaves, voc pode omitir as chaves. Assim,
poderamos escrever o trecho acima de forma um pouco mais compacta:
if (a == b)
printf("Os nmeros so iguais!\n");
Reforo que voc s pode fazer essa abreviao quando dentro do bloco houver apenas
um comando. Se voc zer isso quando houver mais de um comando, mesmo que estejam
todos na mesma linha ou com a mesma indentao, todos os comandos a partir do segundo
sero dados como externos ao bloco. Por exemplo, observe o cdigo a seguir:
if (a < 0)
printf("O valor de A no pode ser negativo!\n");
a = 0;
/* indentao enganosa! */
A terceira linha foi indentada de modo que parea fazer parte do if; na verdade ela est
fora dele, e ser executada em qualquer caso, seja a negativo ou no. Esse cdigo equivalente a este outro a seguir; este sim d a correta impresso da organizao do programa.
if (a < b) {
printf("A menor que B!\n");
}
a = b;
Desvios condicionais: if
21
Outro erro comum confundir o operador de comparao de igualdade, ==, com o operador de atribuio, =. O cdigo a seguir gramaticalmente correto, mas no faz o que ele
aparenta fazer:
if (a = b)
printf("A igual a B!\n");
O que est acontecendo aqui? Copiamos o valor de b para a varivel a; o comando de atribuio a = b por si mesmo uma expresso, cujo valor igual ao valor que foi atribudo.
Esse valor , ento, interpretado como se fosse o valor de uma condio cobriremos isso
mais adiante, na seo 2.6; mas veja que, independentemente de quem ele , esse valor condicional no tem a ver com a relao entre a e b, pois ele s depende do valor da varivel b!
Portanto, a concluso de que a igual a b a partir desse if est errada.
Esse um dos vrios erros que podem no ser detectados pelo compilador, pois o cdigo, ainda que semanticamente incorreto, sintaticamente vlido; por causa disso, voc
conseguir compilar e executar seu programa, mas alguma parte dele exibir algum comportamento inesperado. Portanto, muito cuidado ao fazer comparaes de igualdade.
Mas pense bem: sendo o nmero digitado inteiro, ou ele par ou mpar; ento, se j
vericamos que ele no par, no precisamos fazer outra conta para ver que ele mpar.
Esse tipo de padro associar um procedimento ao caso em que a condio satisfeita, e
outro procedimento ao caso em que ela falsa extremamente comum em programao.
Por isso, a cada condio voc pode associar tambm um outro bloco de comandos, a ser
executado caso ela seja falsa. Para isso, voc usa a palavra-chave else (que signica seno),
da seguinte maneira:
if (condio) {
22
23
Agora vamos passar isso de volta para o C. A traduo quase literal, excluindo-se o
cabealho e o rodap do programa:
24
#include <stdio.h>
int main()
{
int num;
num = 1;
while (num <= 10) {
printf("%d\n", num);
num = num + 1;
}
return 0;
}
Esse tipo de cdigo muito importante em programao, e ilustra um padro conhecido como contador uma varivel usada em um lao, cujo valor varia em uma unidade
a cada iterao. Esse padro usado em diversas situaes que envolvam a repetio de
vrias operaes iguais ou bem parecidas, que possam ser diferenciadas apenas pelo valor
do contador.
E
2.1 Faa um programa que l um nmero n do teclado e, em seguida, imprime
todos os nmeros inteiros de 1 a n junto com seus quadrados.
Vamos construir um exemplo mais sosticado: calcular o fatorial de um nmero inteiro
no-negativo fornecido pelo usurio.
Denio. Dado um nmero natural n, denimos como fatorial de n, e denotamos n!,
o produto de todos os nmeros naturais menores que ou iguais a n. Observe que 1! = 1.
Denimos tambm 0! = 1.
Por exemplo, 5! = 1 2 3 4 5 = 120.
Como podemos usar o while nessa tarefa? Suponhamos primeiro que n maior que
1. Nessa situao, devemos fazer vrias multiplicaes, cada vez com fatores diferentes
anal, a multiplicao com n fatores se dene indutivamente a partir da multiplicao
elementar de dois fatores. Se realizarmos as operaes da esquerda pela direita, faremos as
seguintes contas:
12
(1 2) 3
..
.
(1 2 (n 1)) n
Tendo isso em vista, podemos montar um algoritmo no qual, em cada passo, o resultado
do passo anterior (a) multiplicado pelo valor atual de um contador (b), que vai de 2 at n:
a 1
b2
25
repita enquanto b n:
a a * b
bb+1
Ao nal do algoritmo, a varivel a conter o fatorial do nmero n, conforme desejvamos.
Agora pensemos nos casos n = 1 e n = 0. Se n = 1, a condio do lao (2 1) ser, j
de incio, falsa, e o valor inicial de a garantir o resultado correto, 1! = 1. Caso n = 0, a
condio tambm ser falsa de incio, e assim tambm teremos o resultado 0! = 1. Agora
vamos escrever tudo isso em C:
/* Calcula o fatorial de um nmero inteiro no-negativo. */
#include <stdio.h>
int main()
{
int n, a, b;
printf("Digite um nmero: ");
scanf("%d", &n);
a = 1;
b = 2;
while (b <= n) {
a = a * b;
b = b + 1;
}
printf("%d! = %d\n", n, a);
return 0;
}
26
lendo nmeros. Declarando uma varivel num para armazenar os nmeros lidos, podemos
proceder assim:
while (num != 0) {
soma = soma + num;
printf("Digite outro nmero ou 0 para terminar: ");
scanf("%d", &num);
}
Mas preciso prestar ateno a um detalhe: num dever ser lido pela primeira vez antes
desse lao caso contrrio, a varivel no estaria inicializada!. Feitas essas observaes,
nosso programa car com essa cara:
#include <stdio.h>
int main()
{
int num, soma;
soma = 0;
printf("Digite um nmero ou 0 para terminar: ");
scanf("%d", &num);
while (num != 0) {
soma = soma + num;
printf("Digite um nmero ou 0 para terminar: ");
scanf("%d", &num);
}
printf("Soma = %d\n", soma);
return 0;
}
Vamos agora adicionar mais uma tarefa ao nosso problema: encontrar o elemento mximo da sequncia digitada pelo usurio. Para ver como faremos isso, imagine que voc
dispe de papel e caneta, e que algum est ditando para voc a tal sequncia de nmeros.
Como voc realizaria essa tarefa? Voc anota o primeiro nmero e, caso oua posteriormente um nmero maior que ele, anota-o logo abaixo; se ouvir outro nmero ainda maior,
anota-o abaixo do anterior; e assim por diante no necessrio tomar nota de todos os
nmeros. No nal, o ltimo nmero anotado ser o maior nmero da sequncia.
Um computador poderia fazer isso exatamente da mesma maneira. Em vez de anotar os
nmeros, ele os guardaria em variveis; alm disso, ele no precisaria manter as anotaes
anteriores; basta substituir o valor da varivel. Assim, nosso programa completo poderia ser
escrito assim:
#include <stdio.h>
27
int main()
{
int soma, /* somas parciais */
num, /* ltimo nmero lido */
max;
/* candidatos a mximo */
soma = 0;
printf("Digite um nmero ou 0 para terminar: ");
scanf("%d", &num);
max = num;
while (num != 0) {
soma = soma + num;
if (num > max)
max = num;
/* l o prximo nmero */
}
printf("Soma = %d\n", soma);
printf("Mximo = %d\n", max);
return 0;
}
incremento do contador ;
}
Esse tipo de situao uma das mais frequentes aplicaes dos laos; por isso, essa
estrutura comum foi abreviada em outro tipo de lao: a estrutura for. Com ela, a escrita
desses laos ca mais compacta e de mais fcil visualizao:
for (inicializao;
{
comandos;
controle; incremento)
28
O funcionamento da estrutura for pode ser mais detalhado como a seguir (voc pode ver
isso de maneira mais esquemtica na gura 2.2):
1. A varivel do contador inicializada com um valor especicado; isso ocorre apenas
uma vez, antes do incio do lao. A inicializao costuma ser uma instruo do tipo
i = 1.
2. A condio de controle especica que condio a varivel do contador deve satisfazer
para que os comandos sejam executados; em outras palavras, quando essa condio
deixar de valer, o lao ir parar de se repetir. Por exemplo, uma condio do tipo
i <= n diz que o lao ser interrompido assim que o contador i car maior que n. Se
voc quiser que seu contador decresa at um valor mnimo, voc usar uma condio
do tipo i >= n.
3. Sempre que a condio de controle for satisfeita, executada uma iterao do bloco
de comandos fornecido, de cabo a rabo. Mesmo que a condio de controle deixe de
ser satisfeita no meio do bloco, o lao s ser abandonado quando chegarmos ao nal
do bloco. Essa ltima observao vale tanto para o for quanto para o while.
29
4. Ao nal de cada iterao, executa-se a instruo de incremento ela pode ser qualquer instruo que altere o valor do contador, como i = i + 1 ou i = i - 2. Apesar
do nome incremento, voc pode variar o valor do contador da maneira que desejar
diminuindo, por exemplo.
Apesar de termos vinculado nossa discusso do for a uma varivel-contador, o for uma
estrutura bastante exvel. No entanto, isso no signica que voc deve sair usando o for a
torto e a direito e esquecer o while; deve existir uma boa razo para existirem os dois tipos de
estrutura. E essa razo semntica: voc deve usar o for quando seu lao tiver um contador
ou algo com funcionamento semelhante; nos outros casos, voc deve, em geral, ater-se ao
while.
2.3.1 Abreviaes
Atribuies que envolvem o valor original da varivel que est sendo alterada (como a nossa
j conhecida i = i + 1) so muito comuns, j que so usadas o tempo todo nos laos com
contadores; por isso, foram criadas algumas abreviaes bastante teis. Quando representa
um dos cinco operadores aritmticos, podemos abreviar a = a b para a = b. Assim,
temos:
Abreviao
Signicado
var += expr
var -= expr
var *= expr
var /= expr
var %= expr
Existe ainda outro tipo de abreviao qui o mais usado em C que serve para
aumentar ou diminuir em uma unidade o valor de uma varivel. So os operadores ++ e --,
que podem ser usados tanto antes quanto depois do nome das variveis a serem alteradas. Ou
seja, para aumentar o valor de uma varivel, podemos escrever
var++;
30
No contexto atual, o mais comum usar os operadores depois dos nomes das variveis.
Porm, mais tarde veremos que existe uma diferena importante entre as duas possibilidades
de posio.
Dessa maneira, a cara de um lao com contador passa a car parecida com isso (aqui
foi reescrito o lao do ltimo exemplo):
for (b = 2; b <= n; b++)
a *= b;
Exerccios
2.4. Crie um programa que l um nmero natural n do teclado e imprime todos os divisores
desse nmero. Ao nal, imprima tambm a soma dos divisores encontrados.
2.5. Aproveite o programa do exerccio anterior para vericar se um nmero n dado
primo.
2.6. Crie um programa que calcula o fatorial duplo de um nmero natural n (lido do teclado), que denido como a seguir:
{
n(n 2) 4 2, se n par;
n!! =
n(n 2) 3 1, se n mpar.
2.7. Faa um programa que l dois nmeros inteiros a e b, sendo a > 0 e b 0, e calcula
a potncia ab . Veja se seu algoritmo funciona para o caso b = 0; tente elabor-lo de
modo que no seja preciso um teste especial para esse caso.
2.8. Faa um programa que l uma sequncia de nmeros naturais do teclado (terminada
por zero) e imprime a quantidade de nmeros pares e mpares da sequncia. Imprima
tambm o maior e o menor mpar e o maior e o menor par encontrados na sequncia.
2.9. Variao sobre o mesmo tema: Mesmo problema que o anterior; mas agora o tamanho
n da sequncia conhecido previamente. Antes de tudo voc deve ler n, e depois ler
os n nmeros.
2.10. Faa um programa que l do teclado uma sequncia de nmeros inteiros no-nulos (terminada por zero) e os soma e subtrai alternadamente por exemplo, para a sequncia
[5, 7, 1, 4, 9], seu programa dever calcular 5 7 + 1 (4) + 9.
2.11. [Problema] Crie um programa que recebe um nmero n e a seguir l uma sequncia
de n algarismos de 0 a 9; voc dever calcular e imprimir o nmero que corresponde
queles algarismos, na mesma ordem (no vale imprimir dgito por dgito! O objetivo
construir o nmero a partir da sua sequncia de dgitos). Por exemplo, para a sequncia
[5, 4, 2, 0, 7, 0], seu programa dever imprimir o nmero 542070.
Condies compostas
31
2.12. [Idem] Crie um programa que l um nmero natural n e imprime a soma de seus
dgitos (na base decimal).
No entanto, esse mtodo tem duas desvantagens: (1) ele no deixa to evidente o fato de
que queremos que as duas condies sejam satisfeitas ao mesmo tempo; e (2) tivemos mais
trabalho para vericar quando as condies no so satisfeitas foi necessrio usar dois
blocos else, e acabamos usando o mesmo cdigo nos dois.
Quando precisamos de critrios compostos como esse dentro de um lao, a situao
torna-se ainda mais complicada; com o que aprendemos at agora, isso simplesmente impossvel de se implementar mesmo com outros recursos da linguagem, a soluo no
seria nem um pouco prtica ou clara. Por isso, precisamos introduzir dois operadores que
permitem a criao de condies compostas:
&&
||
ou
O operador &&, chamado de E lgico, serve para vericar se duas condies so satisfeitas
simultaneamente. O operador ||, o OU lgico, verica se, dentre duas condies, pelo menos
uma satisfeita. (Os nomes desses operadores sero grafados em maisculas para evitar
confuses no texto.)
32
Note que, coloquialmente, quando usamos a palavra ou para unir duas oraes, geralmente imaginamos que apenas uma delas verdadeira (por exemplo, Independncia ou
morte cada uma das possibilidades exclui o acontecimento da outra). Nesse caso,
dizemos que trata-se de um ou exclusivo. Em outras palavras, esse ou signica que,
dentre as duas sentenas, uma e apenas uma verdadeira.
J em computao, quando dizemos A ou B, geralmente queremos dizer que, das duas
sentenas (condies), pelo menos uma verdadeira. Esse conhecido como ou inclusivo. Daqui para a frente, a menos que indiquemos o contrrio, usaremos sempre o ou
inclusivo.
Com esses operadores, podemos reescrever aquele exemplo assim:
if (x >= 0 && x <= 10)
printf("Ok!\n");
else
printf("O nmero precisa estar entre 0 e 10!\n");
Bem mais claro, no? Veja que tambm podemos inverter as coisas para escrever nosso
exemplo usando um operador OU se um nmero no satisfaz a condio de estar entre 0
e 10, ento necessariamente ele ou menor que zero ou maior que 10:
if (x < 0 || x > 10)
printf("O nmero precisa estar entre 0 e 10!\n");
else
printf("Ok!\n");
Repeties encaixadas
33
Essa uma das implementaes mais inecientes possveis, pois vrios dos candidatos da
lista {2, 3, . . . , n 1} podem ser sumariamente descartados. Por exemplo, nenhum nmero
maior que n
2 pode ser divisor de n, pois isso signicaria que n tambm tem um divisor
entre 1 e 2. Ainda mais, se s precisamos vericar se
n primo (e no achar todos os seus
divisores),
basta
vericar
os
possveis
divisores
d
n/d n tambm ser divisor). Mas, no momento, para o propsito atual, vamos nos
contentar com a maneira ineciente.
Agora tudo o que precisamos fazer usar esse cdigo vrias vezes (usando um lao) para
encontrar N nmeros primos:
int N, /* quantidade de primos a procurar */
i, /* primos j encontrados */
n, /* candidato atual */
divisores; /* conta o nmero de divisores */
for (n = 2, i = 0; i < N; n++) {
divisores = 0;
/* testa todos os candidatos a divisor */
for (k = 2; k < n; k++)
if (n % k == 0)
divisores++;
if (divisores == 0) {
/* no achamos nenhum divisor dentre os possveis
candidatos , ento primo */
printf("%d\n", n);
i++;
}
}
Observe que cada iterao do lao for externo verica se um nmero n primo, e s
aumenta o contador i em caso armativo. Veja tambm que o limite do contador k, no for
34
j++.
No entanto, como a varivel b est num contexto lgico, seu valor ser interpretado como
um valor booleano, ou seja, falso se b == 0 e verdadeiro se b != 0. Assim, nalmente, a
comparao if (a = b) na verdade equivalente a
Nota para o futuro: na verdade, s podem ser interpretados como booleanos os tipos escalares, ou seja, os
tipos numricos (inteiro e ponto utuante), alm dos ponteiros, que s veremos no captulo 5.
Variveis booleanas
a = b;
if (b != 0)
05/02/2011
35
Funes
3.1 Introduo
Em C, uma funo um pedao de cdigo, dentro de um programa maior, que realiza
uma certa tarefa com uma certa independncia do resto do programa. Funes podem ser
executadas vrias vezes, e uma grande vantagem disso a reutilizao de cdigo: em vez
de repetir vrias vezes o cdigo para executar certa tarefa, podemos simplesmente chamar
vrias vezes a funo que executa essa tarefa. Alm de economizar linhas de cdigo, isso
permite que voc mude facilmente o cdigo associado a essa tarefa se no fosse pelas
funes, voc teria de buscar em seu programa por todos os locais em que voc executou
essa tarefa e alterar o cdigo em cada um. Mais ainda, ao organizarmos o cdigo em vrias
funes, podemos focar cada parte do cdigo em uma s tarefa, deixando o programa mais
claro e limpo.
Em C, uma funo deve ter as seguintes caractersticas:
Um nome pela qual ela possa ser chamada. Os nomes possveis seguem as mesmas
restries que os nomes de variveis: devem comear com uma letra ou com um
sublinhado (_), e podem conter qualquer combinao desses e dos algarismos 09.
Lembre-se de que h distino entre maisculas e minsculas.
Valores de entrada ou parmetros so os valores sobre os quais a funo deve operar. Os parmetros das funes (tambm chamados de argumentos) atuam de maneira
anloga s variveis das funes matemticas.
Tambm possvel criar funes sem argumentos por exemplo, se voc quiser
criar uma funo que calcula o valor (aproximado, claro) do nmero , no precisa
de nenhum parmetro (a princpio; voc poderia introduzir parmetros se quisesse
por exemplo, a preciso desejada , mas eles no so necessrios se voc quer
executar a operao de uma maneira pr-determinada).
Um valor de sada, que corresponde ao resultado da funo para o conjunto dos parmetros de entrada fornecidos. Tambm possvel criar funes que no devolvem
nenhum valor de sada. Por exemplo, uma funo que simplesmente exibe uma mensagem na tela no precisa devolver nenhum valor embora a funo tenha um resultado,
ele mostrado na tela, e no devolvido internamente para o programa.
37
38
Captulo 3. Funes
nome_da_funo (parmetros )
contedo da funo;
}
Essa denio deve ser colocada no nvel superior do arquivo, ou seja, no deve estar
dentro de outra funo como o main. Todas as funes dentro de um arquivo devem car no
mesmo nvel, cada uma aps o nal da anterior. Na verdade, enquanto eu no falar de uma
outra coisa (ainda neste captulo), as demais funes devem car antes da funo main.
O tipo do valor de sada pode ser qualquer um dos tipos usados para variveis (por enquanto, voc s conhece o int). No caso em que no h valor de sada, voc deve usar no
lugar do tipo a palavra void (vazio, em ingls). Ela no um tipo de varivel; ela apenas
indica a ausncia de um valor. (Muitos falam do tipo void, mas isso apenas um abuso
de linguagem.)
A denio dos parmetros semelhante declarao de variveis. Cada parmetro deve
ter um nome (seguindo, novamente, as mesmas restries vlidas para os nomes de variveis)
e um tipo. Para especicar esses parmetros, voc deve usar o formato
Uma funo que recebe dois nmeros inteiros, a e b, e devolve o valor da potncia
ab . Novamente, todos os valores envolvidos so do tipo int, e nosso cabealho vem a
ser
39
Voc est criando uma funo que recebe um ms e um ano e imprime na tela o calendrio desse ms. Nesse caso, no h nenhum valor de sada (os dados so enviados
diretamente para a tela, com a funo printf), o que indica que usaremos a palavra
void no lugar do tipo da sada; mas h dois parmetros do tipo int, ento o cabealho
ca assim:
void imprime_calendario(int mes, int ano)
Suponha agora que voc quer fazer uma funo que l um inteiro do teclado (usando a
funo scanf como intermediria). Para isso, voc no precisa de nenhum parmetro
de entrada; voc simplesmente devolver o nmero lido.
int le_inteiro(void)
Agora, o prato principal: como escrever o contedo da funo? Isso o menor dos
mistrios tudo que voc precisaria saber sobre isso j foi feito na funo main, que
uma funo (quase) como outra qualquer. Basta colocar um par de chaves aps o cabealho
e colocar no meio das chaves tudo o que voc souber fazer em C: declarar variveis, fazer
contas, chamar as funes scanf e printf, usar laos e controles, etc.
Antes de poder criar exemplos concretos, precisamos ver um pouco mais de teoria.
40
Captulo 3. Funes
tarefa muito simples: uma vez denidos os parmetros no cabealho da funo, voc pode
acess-los como se fossem variveis normais. Por exemplo, se quisssemos criar uma funo
que recebe dois inteiros e imprime sua soma na tela, poderamos escrever:
void imprime_soma(int a, int b)
{
int soma;
soma = a + b;
printf("%d\n", soma);
}
Veja que nesse caso a funo no tem nenhum resultado a devolver para o programa,
ento usamos a palavra void para o tipo de sada. Lembre-se de que no necessrio criar
uma varivel intermediria para fazer essa conta. Poderamos ter escrito apenas
void imprime_soma(int a, int b)
{
printf("%d\n", a + b);
}
Para devolver o valor de sada, usamos a instruo return seguida do valor de sada
(terminando com um ponto-e-vrgula). O valor pode ser qualquer expresso que seja legtima
de se colocar no lado direito de uma atribuio: o valor de uma varivel, uma constante
numrica, uma expresso aritmtica, etc. Por exemplo:
return 0;
return x*x;
return y + 1;
Vamos ver um exemplo mais concreto. A funo a seguir devolve para o programa a
soma dos dois nmeros recebidos como parmetros:
int soma(int a, int b)
{
return a + b;
}
Usando funes
41
}
printf("%d\n", n);
}
Vamos criar um exemplo mais elaborado: uma funo que calcula a potncia ab , dados
dois inteiros a e b. (Para deixar o cdigo mais claro, chamamos a de base e b de expoente.)
Veja que o cdigo da funo essencialmente o mesmo que havamos criado antes.
int potencia(int base, int expoente)
{
int pot, i;
pot = 1;
for (i = 1; i <= expoente; i++)
pot *= base;
return pot;
}
nome_da_funo ()
Esse tipo de comando uma chamada de funo; ele simplesmente faz com que o
computador pule para a funo chamada, execute-a por inteiro e depois volte para o mesmo
ponto de onde saiu.
Se a funo tiver um valor de sada, provavelmente vamos querer aproveit-lo nesse
caso, basta colocar essa chamada de funo no meio de uma expresso qualquer; por exemplo,
podemos guard-lo numa varivel, coloc-lo no meio de uma expresso aritmtica ou mesmo
mand-lo como parmetro para outra funo. Um exemplo, utilizando duas funes que j
escrevemos acima:
int a, b, c;
a = potencia(2, 3);
b = soma(a, 8);
c = potencia(3, soma(b, a) + b);
Se no houver valor de sada, simplesmente colocamos a chamada de funo com o tradicional ponto-e-vrgula no nal. Por exemplo:
42
Captulo 3. Funes
int a, b;
a = potencia(2, 3);
b = soma(a, 8);
imprime_soma(a, b + potencia(a, b));
Veja que a funo potencia foi colocada antes da main, como j observamos que seria
necessrio. Se voc trocasse a ordem das funes, receberia uma mensagem de erro do
compilador; veremos a seguir por que isso ocorre e uma outra maneira de denir as funes
que, de certa maneira, elimina essa limitao.
Escopo de variveis
43
44
Captulo 3. Funes
printf("funo troca - antes:
temp = a;
a = b;
b = temp;
printf("funo troca - depois: a = %d, b = %d\n", a, b);
}
int main()
{
int x, y;
x = 10;
y = 5;
printf("main - antes:
troca(x, y);
Se voc executar esse exemplo, ver que o valor das variveis x e y dentro da funo
main no se altera. A sada desse programa ser:
main
- antes:
funo troca - antes:
x = 10,
a = 10,
y = 5
b = 5
b = 10
y = 5
Voc deve entender por que isso acontece: quando trocamos as variveis a e b, estamos
nada mais que trocando de lugar as cpias das variveis originais x e y. Quando a funo
troca encerrada, essas cpias so inutilizadas e a funo main volta a ser executada. No
zemos nada explicitamente com as variveis da funo main; no h motivo para que elas
mudem.
Existe, sim, uma maneira de criar uma funo que troque o valor de duas variveis
ela envolve o uso de ponteiros, conforme veremos no Captulo 5.
As variveis locais podem ser declaradas no apenas dentro de funes, mas dentro de
laos ou estruturas de controle (if, while, for, etc.). Isso quer dizer que podemos criar variveis que s existem dentro de um bloco if (por exemplo), e no podem ser acessadas fora
dele, mesmo dentro da mesma funo. O contexto em que uma varivel existe e pode ser
acessada denominado escopo. Ento dizemos que o escopo de uma certa varivel um
certo bloco ou uma certa funo.
Mesmo podendo declarar variveis dentro de qualquer bloco (bloco como se costuma
designar genericamente qualquer estrutura de comandos delimitada por { }, seja uma funo
ou uma estrutura de controle), continua valendo a restrio de que as declaraes de variveis
devem vir no comeo do bloco correspondente, no podendo haver nenhum outro tipo de
comando antes dessas declaraes.
46
an bn + an1 bn1 + + a1 b + a0 =
ak bk
k=0
Para que a representao de um nmero seja nica, necessrio que os valores de todos os
seus algarismos sejam menores do que b, ou seja, no mximo b1. Caso contrrio, o nmero
b2 , por exemplo, poderia ser representado pelas duas sequncias (b, 0) e (1, 0, 0). Os valores
dos dgitos tambm no podem ser negativos seno surgiria mais uma ambiguidade na
representao.
importante nesse ponto no confundir os valores dos dgitos com as suas representaes (que costumam ser os algarismos arbicos sempre que possvel). Aqui, os aj simbolizam os valores dos dgitos (como objetos matemticos abstratos), independentemente de
sua representao. Quando a base no ultrapassa 10, os valores confundem-se com suas representaes (usamos os algarismos de 0 a b 1 para representar os valores de 0 a b 1).
Quando a base maior que 10, precisamos de outra conveno para a base hexadecimal,
por exemplo, os dgitos com valores de 10 a 15 so representados pelas seis primeiras letras
do alfabeto, enquanto os dgitos at 9 continuam sendo representados da maneira usual.
A frmula acima nos permite, dados os valores dos dgitos de um nmero, achar o valor
do nmero. Podemos tambm querer realizar o processo inverso: dado um nmero, achar
47
os valores dos seus dgitos na representao em base b. Olhando para a frmula acima,
podemos ver que, ao dividir (com resto) o valor do nmero por b, obtemos como resto o
valor de a0 ; o quociente o novo nmero
an bn1 + an1 bn2 + + a1
E, ao dividi-lo por b, obteremos como resto o prximo dgito, a1 (que pode muito bem ser
zero). No difcil concluir que, se realizarmos n + 1 divises por b, os restos sero, na
ordem, os dgitos
(a0 , a1 , a2 , . . . , an1 , an ).
Ou seja, dividindo repetidamente por b, obteremos os dgitos da direita para a esquerda.
Note que, na ltima diviso, obtemos an como resto e zero como quociente.
Para ilustrar esse algoritmo, vamos fazer uma funo que recebe um nmero inteiro e
imprime seus dgitos na representao binria.
void imprime_binario(int numero)
{
while (numero > 0) {
printf("%d", numero % 2);
numero /= 2;
}
printf("\n");
}
Notemos, primeiro, que essa funo fornece os dgitos na ordem inversa (conforme foi
observado acima); por exemplo, o nmero 12 = (1100)2 seria impresso como 0011.
Veja tambm que, em vez de xar o nmero de divises, estabelecemos como critrio
de parada o anulamento do quociente. Com isso, no precisamos saber de antemo quantos
dgitos binrios tem o nmero; vimos que o quociente ser zero quando s sobrar o dgito
mais signicativo.
48
armazenamento (eles se chamam registradores) tm esse mesmo tamanho. Quando queremos usar inteiros de tamanhos diferentes, o processador simplesmente trabalha com pedaos
desses tais registradores ou com vrios registradores juntos.
Agora voltemos um pouco ao C. J conhecemos os dois tipos bsicos de inteiros: int e,
embora no o tenhamos usado ainda, char (como vimos no comeo do primeiro captulo, os
caracteres so armazenados como se fossem inteiros). O tipo char tem um tamanho nico:
um byte ou seja, 8 bits. O tipo int geralmente tem um tamanho padro de 32 bits, mas
ele tambm possui alguns subtipos com tamanhos diferentes. So eles:
short int,
long int,
um inteiro longo, que costuma ter 32 bits (no h nada de errado aqui; o
tipo int realmente costuma ser igual ao long).
que geralmente tem 64 bits. (Somente no padro C99.)
Nota: No padro da linguagem C, os tamanhos desses tipos de dados so denidos de uma maneira
mais formal (e mais vaga), devido grande diferena entre os vrios tipos de sistemas em que o C
pode ser usado. Eu os deni de uma maneira mais prtica, segundo os tamanhos que esses tipos
tm na maioria dos computadores pessoais de hoje em dia; uma denio mais correta pode ser
encontrada na especicao da linguagem.
Todos esses subtipos podem (e costumam) ser abreviados, tirando deles a palavra int.
Assim, short int costuma ser escrito como simplesmente short (e assim por diante).
Quo grandes so esses tamanhos? Que valores cabem num inteiro de 32 ou 16 bits
(ou de qualquer outro nmero n)? Para isso, podemos pensar no menor e no maior nmero
que pode ser representado com n bits. O resultado anlogo ao caso decimal: se temos k
algarismos, o maior nmero que podemos representar uma sequncia de k algarismos 9,
que igual a 10k 1. Da mesma maneira, com n bits, o maior nmero representvel (em
binrio) 2n 1, que corresponde a uma sequncia de n algarismos 1.
Assim, com 32 bits, por exemplo, podemos representar todos os nmeros de 0 a 232 1,
que vale 4 294 967 295. Na tabela a seguir voc pode ver as capacidades dos tamanhos de
inteiros mais comuns.
Tabela 4.1: Os tamanhos mais comuns de inteiros nos microcomputadores atuais, e os maiores nmeros armazenveis com tais tamanhos.
Tipo
Bits
Bytes
char
8
16
32
64
1
2
4
8
short
int, long
long long
Maior nmero
255
65 535
4 294 967 295
18 446 744 073 709 551 615
Voc tambm pode chegar a esses nmeros pensando em termos das nossas somas
ak bk , com todos os
ak = b 1. Lembre da frmula de soma de uma progresso geomtrica e verique que, de fato, o resultado o
mesmo.
49
escrever o sinal de menos para os nmeros negativos. Nos computadores, a codicao mais
comum de nmeros negativos tem como princpio reservar um dos bits do nmero para o sinal
assim, um nmero de 32 bits ca com 31 bits para guardar o mdulo do nmero e 1 bit para
guardar o sinal. Geralmente usado o bit mais signicativo (o que estiver mais esquerda na
nossa representao usual), e ele vale 1 para nmeros negativos ou 0 para nmeros positivos.
Sabendo isso, talvez voc poderia imaginar que, por exemplo, se o nmero 14 armazenado
em 8 bits como 0000 1110, o nmero 14 seria armazenado como 1000 1110. No bem
assim.
O que acontece que essa representao no muito eciente quando o computador
vai fazer clculos. Existe uma outra representao, conhecida como representao do complemento de 2, que permite que nmeros negativos sejam operados exatamente da mesma
maneira que os nmeros positivos, isto , sem que o computador precise vericar se um
nmero negativo para saber como fazer a conta.
Complemento de 2 no o melhor nome (seria mais adequado complemento de 2n );
mas o mtodo consiste em guardar cada nmero negativo k como se fosse o nmero positivo
2n k, sendo n o nmero de bits reservado para o inteiro. Por exemplo, ao trabalhar com
8 bits, o nmero 14 seria guardado como se fosse 256 14 = 242 (em binrio, isso seria
1111 0010).
Agora, se olharmos para o nmero 128, veremos que ele o seu prprio complementar
quando usamos n = 8 bits: 256 128 = 128. Mas lembre-se que a representao binria
de 128 1000 0000 como o bit de sinal 1, faz muito mais sentido convencionar que
essa ser a representao do nmero negativo 128. Ento, nessa representao, o 128 no
existe (precisaramos usar mais que 8 bits); os nmeros positivos param no 127. Os nmeros
negativos vo at o 128.
Generalizando essas concluses, num espao de n bits possvel guardar, com sinal,
todos os nmeros inteiros de 2n1 at 2n1 1. (Veja como isso equivalente ao nosso
intervalo de 128 a 127 para a representao de 8 bits.)
Um algoritmo prtico para calcular o complemento de 2 de um nmero, j na representao binria, o seguinte: preencha com zeros esquerda para completar a quantidade de
bits que est sendo usada; ento inverta todos os bits da representao (trocar os 0 por 1 e
vice-versa) e some 1. Lembre-se de inverter tambm os zeros esquerda que voc incluiu.
Em C possvel armazenar tanto nmeros negativos quanto positivos, como j mencionamos no comeo. Mais ainda, ao declarar uma varivel voc pode escolher se quer guardar
nmeros negativos e positivos (com sinal) ou s positivos (sem sinal) como acabamos de
ver, isso faz diferena no intervalo de nmeros que pode ser guardado. Assim, uma varivel
de 8 bits pode suportar nmeros de 0 a 255 ou nmeros de 128 a 127. Para indicar isso,
voc pode modicar os tipos numricos inteiros (char e int) com mais dois adjetivos: signed (com sinal) e unsigned (sem sinal). Quando voc no diz nada, o computador assume
que voc quer guardar os nmeros com sinal (signed).
Se voc quiser vericar por que os dois mtodos so equivalentes, pense na soma de um nmero com o que
tem todos os seus bits invertidos.
50
Regio
(8 bits)
(16 bits)
int, long (32 bits)
long long (64 bits)
char
short
128 a 127
32 768 a 32 767
2 147 483 648 a 2 147 483 647
9 223 372 036 854 775 808 a 9 223 372 036 854 775 807
Para escrever nmeros fracionrios em C, usamos o ponto (.) para separar a parte inteira
da fracionria por exemplo, 3.14159 ou -0.001. Podemos tambm usar uma espcie de
notao cientca para trabalhar com nmeros muito grandes ou muito pequenos escrevemos o valor principal do nmero da maneira que acabei de descrever, e usamos a letra e ou
E para indicar a potncia de 10 pela qual o nmero deve ser multiplicado. Alguns exemplos:
(
)
(
)
(
)
3.14159e-7 3,14159 107
1.234E+26 1,234 1026
4.56e5 4,56 105
Veja que no faz diferena se o caractere E maisculo ou minsculo, nem se colocamos o
sinal de + nos expoentes positivos.
To importante quanto saber escrever esses nmeros saber imprimi-los na tela ou llos do teclado. Usando a funo printf, usamos o cdigo %f (da mesma maneira que o nosso
conhecido %d), tanto para o double quanto para o oat, como no seguinte exemplo:
#include <stdio.h>
int main()
{
double x = 5.0;
float
int
y = 0.1;
z = 27;
Nmeros reais
51
Para ler nmeros reais do teclado, precisamos dizer se a varivel onde vamos guardar
do tipo double ou oat. No primeiro caso, usamos o cdigo %lf; no segundo, apenas %f
(como no printf)
#include <stdio.h>
int main()
{
double x, z;
float y;
printf("Digite dois nmeros de ponto flutuante: ");
scanf("%lf %f", &x, &y);
z = x + y;
printf("%f\n", z);
return 0;
}
Veja que podemos fazer contas entre nmeros de ponto utuante da mesma maneira que
fazamos com os inteiros; podemos at misturar doubles com oats. Mais adiante faremos
algumas observaes sobre isso.
6.000000e+00
5.000000E-02 */
Mas o printf tambm tem uma opo muito inteligente, que a %g ela escolhe o melhor
formato entre %f e %e de acordo com a magnitude do nmero. Nmeros inteiros no muito
grandes so impressos sem casas decimais (e sem ponto); nmeros muito grandes ou muito
pequenos so impressos com notao cientca. Tambm podemos usar um G maisculo se
quisermos que o E da notao cientca saia maisculo. Por exemplo:
printf("%g %g %G\n", 6.0, 0.00005, 4000000.0);
/* resultado: 6 5e-05 4E+06 */
Outra coisa que podemos mudar o nmero de casas decimais exibidas aps o ponto. O
padro para o formato %f de 6 casas; isso pode ser demais em vrios casos por exemplo,
52
se voc for imprimir preos de produtos. Para que o nmero de casas decimais exibidas seja
n, trocamos o cdigo %f por
%.nf
Para imprimirmos um nmero com 2 casas, por exemplo, podemos escrever assim:
printf("Preo = %.2f\n", 26.5);
/* resultado:
26.50 */
26.58
27 */
4.3.2 A representao
Falamos um pouco acima que tanto o oat quanto o double so tipos de ponto utuante.
Isso diz respeito representao dos nmeros reais na memria do computador trata-se
de uma espcie de notao cientca. Nessa representao, um nmero tem duas partes: a
mantissa, que so os algarismos signicativos, e o expoente, que indica a posio da vrgula
decimal em relao aos algarismos signicativos.
Pensando na base decimal, um nmero como 0,000395 teria a representao 3,95104 ,
cuja mantissa 3,95 e cujo expoente 4 (indica que a vrgula deve ser deslocada 4 dgitos
para a esquerda). Na notao cientca, estabelecemos que a mantissa deve ser maior que
ou igual a 1, mas no pode ter mais de um dgito esquerda da vrgula por exemplo, a
mantissa pode ser 1,00 ou 9,99, mas no 15,07 ou 0,03. Assim, estamos de fato dividindo
a informao do nmero em duas partes: o expoente d a ordem de grandeza do nmero, e
a mantissa d o valor real dentro dessa ordem de grandeza.
Nem todo nmero tem uma representao decimal nita em outras palavras, nem
todo nmero tem uma mantissa nita. Por exemplo, a frao 5/3 pode ser expandida innitamente como 1,6666 . . .; se quisermos express-la com um nmero nito de dgitos (como
necessrio em um computador), devemos parar em algum dgito e arredondar conforme
necessrio por exemplo, 1,6667, se precisarmos trabalhar com 5 dgitos.
Assim, para colocar um nmero nessa representao, necessrio primeiro normalizar
a mantissa para que que entre 1 (inclusive) e 10, e depois arredond-la para um nmero de
dgitos pr-estabelecido.
A representao usual de ponto utuante no computador praticamente igual a essa;
antes de ver como ela realmente funciona, vamos ver como funciona a representao dos
nmeros fracionrios na base binria.
O que a representao decimal de um nmero? Considere um nmero que pode ser
escrito como an an1 a1 a0 ,b1 b2 (veja que a parte inteira, esquerda da vrgula,
tem um nmero nito de dgitos; a parte fracionria, direita da vrgula, pode ter innitos
dgitos). J vimos como funciona a representao da parte inteira; falta analisarmos a parte
fracionria.
Vejamos primeiro o caso de um nmero cuja parte fracionria nita e tem m dgitos
no vamos nos importar com a parte inteira; suponha que zero. Esse nmero pode ser
escrito como 0,b1 b2 bm . Ento, se multiplicarmos esse nmero por 10m , ele voltar
Nmeros reais
53
bk 10k
k=1
No difcil estender essas contas para os casos em que a parte fracionria no nita
(ou em que a parte inteira no zero), mas seria muito tedioso reproduzi-las aqui; deix-lasei como exerccio se voc quiser pensar um pouco, e direi apenas que aquela representao
(possivelmente innita) an an1 a1 a0 ,b1 b2 corresponde ao nmero
n
ak 10 +
k=0
bk 10k
k=1
Como no caso dos inteiros, claro que todos os dgitos ak e bk s podem assumir os valores {0, 1, . . . , 9}, para que a representao de cada nmero seja nica. (Na verdade isso
no suciente para que a representao seja nica; precisamos, para isso, exigir que haja
um nmero innito de dgitos diferentes de 9, evitando assim as representaes do tipo
0,999999 . . . 1.)
Veja que essa representao uma extenso da representao dos inteiros; poderamos
trocar os bs por as com ndices negativos (bk = ak ) e estender a somatria para os ndices
negativos:
n
ak 10k
k=
Com isso, a transio para a base binria est muito bem encaminhada. O que mudar na
base binria que os dgitos so multiplicados por potncias de 2 (o dgito ak multiplicado
por 2k ), e s podem assumir os valores 0 e 1.
Por exemplo, o nmero 3/8 = 1/8 + 1/4 = 22 + 23 poder ser representado como
(0,011)2 . E o nmero 1/10, que aparentemente muito simples? Na base 2, ele no tem
uma representao nita! fcil ver o porqu: se ele tivesse representao nita, bastaria
multiplicar por uma potncia de 2 para torn-lo inteiro; sabemos que isso no verdade
(nenhuma potncia de 2 divisvel por 10!).
Como descobrir a representao binria de 1/10? Vou falar sobre dois jeitos de fazer
isso. O primeiro envolve alguns truquezinhos algbricos. Vamos escrever
1
1 1
1 3
3
1
3
1
= =
= 4
= 24
10
2 5
2 15
2 2 1
2
1 24
Agora a ltima frao a soma da srie geomtrica de razo 24 . Podemos ento escrever
(
)
1
= 3 25 1 + 24 + 28 +
10
(
)
= (1 + 2) 25 1 + 24 + 28 +
(
) (
)
= 24 + 25 1 + 24 + 28 +
(
) (
)
1
= 24 + 28 + 212 + + 25 + 29 + 213 +
10
54
)
a
1
a (
a
= n
= n 1 + 2n + 22n +
n
1
2 12
2
Nmeros reais
55
.
sinal
31
63
.
expoente
30
62
23
52
22
51
.
mantissa
0
0
Figura 4.1: Esboo da estrutura de representao de ponto utuante. Em cada parte foram indicados
os nmeros dos bits utilizados nas representaes de preciso simples e dupla, respectivamente.
Eu ainda no havia falado do sinal: a representao de ponto utuante usa um bit para o
sinal, assim como na representao de inteiros. Mas, ao contrrio desta, em ponto utuante
no se usa nada parecido com o complemento de 2; a nica diferena entre as representaes de dois nmeros de mesma magnitude e sinais opostos o bit de sinal.
O expoente (relacionado a uma potncia de 2, no de 10!) representado (quase) como
um nmero inteiro comum, e por isso h uma limitao nos valores que ele pode assumir.
Na preciso simples, so reservados 8 bits para o expoente, o que permite at 256 valores
possveis no so exatamente de 128 a 127; na verdade, os dois extremos so reservados
para situaes especiais; os expoentes representveis so de 127 at 126. Na preciso dupla,
usam-se 11 bits, e a gama de expoentes muito maior: de 1023 at 1022.
Sobre a mantissa no resta muito a falar; os bits da mantissa so armazenados sequencialmente, como na representao binria que construmos acima. Em preciso simples, so
reservados 23 bits para a mantissa. O leitor atento notar que eu falei em 24 dgitos alguns pargrafos atrs. De fato, a representao binria s reserva 23 dgitos. Mas, como a
mantissa est sempre entre 1 e 2, esquerda da vrgula temos sempre um algarismo 1 sozinho; ento, convenciona-se que esse algarismo no ser escrito (o que nos deixa com 1 bit
a mais!), e assim temos uma mantissa de 24 dgitos em um espao de apenas 23 bits. Na
preciso dupla, so reservados 52 bits (que na verdade representam 53).
Na verdade, h vrios outros detalhes sobre a representao de ponto utuante; poderia
gastar mais algumas pginas com essa descrio, mas no vem ao caso. Meu objetivo era dar
uma idia geral do funcionamento dessa representao.
At agora, s explorei a parte terica dessa representao. Precisamos conhecer tambm
as caractersticas prticas da representao de ponto utuante. Por exemplo, em termos
da representao decimal, como se traduzem os limites de representao do expoente e da
mantissa? No vou fazer contas aqui, vou apenas mostrar os resultados prticos. Descreverei
primeiro que resultados so esses:
Devido aos limites de expoentes, existe um nmero mximo e um nmero mnimo que
podem ser representados com uma dada preciso. O nmero mximo corresponde ao
maior expoente possvel com a maior mantissa possvel; o nmero mnimo, analogamente, corresponde ao menor expoente possvel com a menor mantissa possvel. Na
tabela, so apresentadas as faixas aproximadas.
Os mesmos limites se aplicam para os mdulos dos nmeros negativos, j que nmeros negativos e positivos so tratados de maneira simtrica na representao de ponto
utuante.
Como vrios nmeros precisam ser arredondados para serem representados, a representao tem um certo nmero de algarismos signicativos de preciso quantas
casas decimais esto corretas na representao de um nmero. Como os nmeros no
so representados em base 10, a quantidade de casas corretas pode variar de nmero
para nmero; os valores apresentados na tabela seguir correspondem quantidade mnima.
56
Expoente
8 bits
11 bits
15 bits
Mantissa
Intervalo
Preciso
23 bits
52 bits
64 bits
10
10
10324 10308
104950 104932
6 decimais
15 decimais
19 decimais
45
38
O tipo long double ainda no tinha sido mencionado. Ele um tipo que, em geral, tem
preciso maior que o double (embora certos compiladores no sigam essa norma), mas seu
tamanho no to padronizado como os outros dois tipos (preciso simples e dupla). Alm
de 80 bits, possvel encontrar tamanhos de 96 ou 128 bits.
De maneira similar ao double, necessrio utilizar o cdigo %Lf em vez de apenas %f
quando queremos ler com scanf uma varivel long double. Nesse caso tambm necessrio
utilizar esse mesmo cdigo para o printf (em contraste com o double que continua usando
o cdigo %f).
Nmeros reais
57
58
Para sua frustrao, voc obteria o nmero 0.000000 como resultado. Claro, se voc fez
uma diviso entre inteiros, o resultado foi a diviso inteira entre eles; ao guardar o resultado
numa varivel de ponto utuante, apenas o resultado convertido quem decide que tipo
de operao ser realizada so os operandos em si.
Para fazer o que queramos, pelo menos um dos operandos dever ser de ponto utuante.
Ou seja, devemos escolher uma das alternativas:
x = 1.0 / 7;
x = 1 / 7.0;
x = 1.0 / 7.0;
Mas e se quisssemos calcular a diviso entre dois nmeros que no conhecemos a priori?
Devemos usar a
(novo_tipo) varivel_ou_valor
Por exemplo, para calcular a razo (fracionria) entre dois inteiros fornecidos pelo usurio, poderamos converter um deles para o tipo oat e realizar a diviso:
int a, b;
float x;
/* (...) leitura dos nmeros */
x = (float)a / b;
printf("%f\n", x);
Note, porm, que o operador de converso de tipos s atua sobre a varivel que vem
imediatamente depois dele por exemplo, se quisssemos converter uma expresso inteira,
precisaramos de parnteses:
x = (float)(a / b) / c;
Nesse caso, seria realizada a diviso inteira entre a e b, e depois seu quociente seria dividido
por c, numa diviso racional.
Funes matemticas
59
Signicado
Funes trigonomtricas: seno, cosseno e tangente. Os ngulos
so sempre expressos em radianos.
Funes trigonomtricas
inversas.
asin e atan devolvem um n[
]
gulo no intervalo 2 , 2 ; acos devolve um ngulo no intervalo
[0, ].
Funes hiperblicas (seno, cosseno e tangente)
Raiz quadrada (square root)
Funo exponencial (ex )
Logaritmo natural, base e (ln)
Logaritmo na base 10
Mdulo (valor absoluto) de um nmero. Use abs para inteiros e
fabs para nmeros de ponto utuante.
Potenciao: xy (x e y podem ser nmeros de ponto utuante)
xk
x2 x3 x4
=1+x+
+
+
+
k!
2
3!
4!
k=0
claro que, em um computador, impossvel calcular todos os termos dessa srie para
chegar ao resultado; aps um certo ponto, devido convergncia das sries, o valor de cada
termo muito pequeno, de modo que, frente preciso do computador, som-los no faz
mais diferena. Dessa maneira, devemos somar um nmero nito N de termos dessa srie.
Mas como escolher N? Seguem alguns dos critrios possveis:
Escolher um N xo. Esse critrio no muito bom, porque a preciso at o N-simo
termo costuma depender do argumento x; em vrios casos, para valores grandes de
x necessrio somar um nmero maior de parcelas, enquanto, para x bem pequeno,
poucas parcelas j do uma aproximao muito boa.
Limitar a magnitude das parcelas a serem somadas interromper a soma quando o
mdulo da parcela for menor que um dado (por exemplo, = 109 ). Esse critrio
melhor que o anterior, mas no leva em conta a magnitude do resultado; por exemplo,
se o valor da funo for da ordem de 109 , uma parcela de 109 realmente no faz
60
O tipo char
61
#include <stdio.h>
#define PRECO_MUSSARELA
15.00
%5.2f\n"
%5.2f\n"
%5.2f\n",
62
Tabela 4.5: Algumas das sequncias utilizadas para representar caracteres especiais.
Sequncia
ASCII
\t
\n
10
\r
13
\"
34
39
92
\'
\\
\nnn
\xnn
Signicado
Tabulao. Avana o cursor para posies pr-denidas ao longo
da linha; usualmente, essas posies so denidas de 8 em 8 caracteres a partir da primeira posio da linha.
Quebra de linha. Esse caractere comumente conhecido pela sigla
NL, new line, ou por LF, line feed.
Retorno de carro (CR, carriage return): retorna o cursor para o
incio da margem esquerda. (Ver observao adiante.)
Aspa dupla.
Aspa simples.
Barra invertida.
Permite especicar qualquer caractere pelo seu cdigo em base
octal (de 000 a 377). Cada n um dgito de 0 a 7.
Idem, em base hexadecimal (de 00 a FF). Cada n um dgito de
0 a 9 ou uma letra de A a F (maiscula ou minscula).
int opcao;
opcao = getchar();
putchar('a');
importante notar que, tanto com a funo scanf quanto com a getchar, os caracteres
digitados no so recebidos pelo programa em tempo real. Cada vez que voc digita um
caractere, ele armazenado temporariamente numa regio da memria chamada buer de
teclado, e s aps o nal da linha que o contedo do buer liberado para o nosso programa,
atravs dessas funes.
Ponteiros e vetores
5.1 Prolegmenos
Num computador, cada varivel guardada em uma certa posio da memria. Essas posies de memria so numeradas, de modo que cada uma tem um endereo numrico
como se cada uma fosse uma casa em uma rua.
Em C, um ponteiro (tambm chamado de apontador) uma varivel que guarda uma
referncia a outra varivel seu valor o endereo de uma outra varivel. Se o valor de um
ponteiro p o endereo de uma varivel X, ento dizemos que p aponta para X. Veja uma
ilustrao disso na gura 5.1. Se um ponteiro aponta para uma varivel, voc pode acessar
essa varivel (ler ou alterar seu valor) atravs do ponteiro logo veremos como.
Isso pode parecer apenas uma maneira de complicar as coisas, mas na realidade tem
diversas utilidades, das quais citamos algumas:
Quando precisamos transmitir uma grande quantidade de dados a outra parte do programa, podemos passar apenas um ponteiro para esses dados em vez de fazer uma
cpia dos dados e transmitir a cpia. Isso economiza tempo o processo de duplicar
os dados gasta tempo de processamento e, obviamente, espao na memria.
Uma funo em C s pode devolver um valor com a instruo return. No entanto,
se uma funo recebe como parmetros ponteiros para outras variveis, voc poder
gravar valores nessas variveis, e com isso uma funo pode gerar vrios valores de
sada.
63
64
Essa instruo apenas declara um ponteiro, sem apont-lo para nenhuma varivel.
Se quisermos declarar vrios ponteiros com uma nica instruo, devemos colocar o
asterisco em cada um deles. Se voc escrever o asterisco apenas no primeiro nome, apenas a
primeira varivel ser um ponteiro!
int *p1, *p2, *p3;
double *p4, p5, p6;
Para fazer um ponteiro apontar para uma varivel, devemos atribuir-lhe como valor o
endereo de outra varivel, e no um nmero comum. Para isso, usamos o operador &
(E comercial), que fornece o endereo de uma varivel aqui ele ser conhecido como o
operador endereo-de. Se temos uma varivel var, seu endereo representado por &var.
Por exemplo:
int n;
int *p;
p = &n;
Aqui criamos uma varivel inteira chamada n, e em seguida criamos um ponteiro p, que
apontado para a varivel n.
Prolegmenos
65
var
*p
&var
*p = 5;
printf("n = %d\n", n);
/* imprime 5
*/
n = 10;
printf("*p = %d\n", *p);
/* imprime 10 */
Vemos, ento, que acessar um ponteiro para uma varivel , de certa forma, equivalente
a acessar a varivel apontada. Podemos tambm mudar a varivel para a qual um ponteiro
aponta, e a partir da as operaes com o ponteiro s afetaro a varivel nova:
int a, b, *p;
p = &a;
*p = 5;
printf("a = %d\n", a);
/* imprime 5
*/
p = &b;
*p = 10;
printf("a = %d\n", a);
printf("b = %d\n", b);
/* ainda imprime 5
/* imprime 10 */
*/
66
Esse cdigo teria o efeito de obter os valores apontados por p1 e p2, multiplic-los e guardar
o resultado em c, como poderamos esperar.
Isso ocorre porque os operadores de desreferenciao so sempre interpretados antes
dos de multiplicao. Aps a interpretao das expresses *p1 e *p2, o que sobra so os dois
valores apontados pelos ponteiros, com um asterisco entre eles; isso s pode signicar uma
multiplicao se o asterisco do meio fosse atuar como operador de desreferenciao, a
expresso caria invlida, alm de ele estar agindo sobre algo que j no seria um ponteiro!
Apesar do cdigo mostrado acima ser vlido e inambguo (para um computador), recomendvel que voc use parnteses quando tiver de fazer esse tipo de coisa isso deixa o
cdigo bem mais legvel:
c = (*p1) * (*p2);
Para chamar essa funo, usamos o operador & para passar o endereo de uma varivel.
Veja que voc no pode passar um nmero (uma constante) diretamente para essa funo,
pois ele no tem um endereo! Outro detalhe que deve ser levado em conta que a varivel
apontada deve ser inicializada antes de chamarmos a funo, j que a funo se baseia no
valor que havia na varivel.
int main()
{
int num;
num = 10;
dobra_variavel(&num);
printf("%d\n", num);
return 0;
/* 20 */
67
Com esse exemplo em mente, no devemos ter diculdades para montar uma funo que
de fato troca o valor de duas variveis (essa funo, na prtica, no incrivelmente til, mas
boa para ilustrar esse conceito):
void troca(int *a, int *b)
{
int temp = *a;
*a = *b;
*b = temp;
}
Naturalmente, tambm ser necessrio alterar a chamada funo troca, j que agora
precisamos passar os endereos das variveis e no mais os seus valores. Para cham-la,
escreveremos algo como o seguinte:
int m, n;
m = 10;
n = 20;
troca(&m, &n);
Em computao, comum usar os nomes chamada por valor e chamada por referncia;
eles indicam se a funo chamada recebe apenas uma cpia do valor da varivel, ou se recebe
uma referncia (um ponteiro) para a varivel. Em C, as chamadas de funes so intrinsecamente por valor, mas podemos usar ponteiros para obter o comportamento das chamadas
por referncia.
68
// erro terrvel!!
O que ocorreu aqui que voc criou um ponteiro mas no deniu para onde ele aponta.
Seu valor inicial ser aleatrio, e portanto, ao tentar atribuir um valor varivel apontada
pelo ponteiro, voc estar acessando uma posio aleatria da memria. Repito: no faa
isso!
Geralmente, quando voc tentar acessar ponteiros invlidos, o programa ir dar um erro
durante a execuo e fechar. No Linux, voc ver algo do tipo Segmentation fault ou Falha
de segmentao (isso quer dizer que voc acessou um segmento de memria invlido; tem a
ver com a organizao interna da memria). No Windows, voc dever ver uma das famosas
e genricas mensagens Este programa executou uma operao ilegal e ser fechado.
Vetores
69
5.4 Vetores
Vetores so uma maneira prtica de guardar um grande conjunto de variveis relacionadas
por exemplo, sequncias de nmeros. Em C, um vetor uma srie de variveis indexadas
que podem ser acessadas por meio de um ndice inteiro, por exemplo vetor[4]. H
uma pequena restrio: um vetor s guarda variveis do mesmo tipo ou seja, voc pode
fazer um vetor de inteiros e um vetor de nmeros de ponto utuante, mas no um vetor que
contenha ambos.
H vrios termos diferentes para se referir aos vetores. muito comum encontrar array, que o
termo original em ingls; tambm vemos matrizes, termo que prero reservar apenas aos vetores
multidimensionais, como veremos mais tarde. O termo lista tambm possvel, mas pouco usado
em C (ele pode ser usado em outro contexto semelhante, sendo mais comum em outras linguagens).
V[n 1]
.
V[1]
.
.
V[0]
tipo_de_dados nome_vetor[tamanho];
O compilador entende essa frase como: reserve na memria um espao para tamanho variveis do tipo tipo_de_dados, e chame esse espao de nome_vetor. Veja dois exemplos desse
tipo de declarao:
int sequencia[40];
double notas[100];
70
Podemos acessar os elementos de um vetor, em geral, tratando-os como se fossem variveis normais. O operador de endereo tambm pode ser usado com os elementos individuais
do vetor. Por exemplo,
int lista[3];
scanf("%d", &lista[0]);
lista[1] = 37;
lista[2] = lista[1] - 3*lista[0];
printf("%d %d %d\n", lista[0], lista[1], lista[2]);
Vetores
71
int main()
{
int lista[100];
int i, n;
printf("Digite o nmero de elementos (no mximo 100): ");
scanf("%d", &n);
if (n > 100) {
printf("n no pode ser maior que 100! S lerei os "
"100 primeiros nmeros.\n");
n = 100;
}
printf("Digite a seguir os %d elementos:\n", n);
/* leitura dos elementos */
for (i = 0; i < n; i++)
scanf("%d", &lista[i]);
/* impresso dos elementos , na ordem inversa */
for (i = n-1; i >= 0; i--)
printf("%d ", lista[i]);
printf("\n");
return 0;
}
Um aspecto que ainda no reforamos foi a validao da entrada do usurio. Voc pode
pedir encarecidamente que o usurio digite um nmero at 100, mas nada garante que o
usurio no ser desonesto ou distrado e digite um nmero fora dos limites pedidos. Nessas
horas, voc no deve conar no usurio, e deve vericar se o valor digitado vlido, para
evitar que aconteam coisas ms em seu programa nesse caso, se o usurio digitasse um
nmero maior que 100 e no zssemos essa vericao, acabaramos acessando posies
invlidas do vetor (acima do ndice 99).
72
Felizmente, o C permite que voc inicialize os valores de um vetor junto com a declarao
(assim como de uma varivel escalar comum). Isso feito da seguinte maneira:
Note que isso s possvel na hora da declarao. No possvel, fora desse contexto,
denir de uma s vez todos os elementos do vetor, como a seguir ( necessrio atribuir
elemento por elemento):
/* isto est errado! */
lista = {7, 42, 0, -1, 3, 110, 57, -43, -11};
Tambm possvel declarar um vetor de um certo tamanho mas s inicializar parte dele
(desde que seja a parte do comeo). Por exemplo, suponha que queremos espao para uma
sequncia de 100 inteiros, mas que vamos comear inicializando apenas os 10 primeiros.
Nesse caso, escrevemos explicitamente o tamanho do vetor, mas s especicamos os elementos desejados:
int lista[100] = {9, 35, -17, 9, -4, 29, -2, 10, -1, 47};
73
detalhe: no devemos fornecer o tamanho do vetor os colchetes devem ser deixados sozinhos, sem nada no meio. A funo a seguir recebe um vetor de inteiros como parmetro
e imprime seu primeiro elemento:
void imprime_primeiro(int v[])
{
printf("%d\n", v[0]);
}
Para mandar um vetor como parmetro de uma funo, voc simplesmente deve escrever
o nome dele, sem colchetes ou qualquer outro smbolo:
int vetor[] = {1, 2, 3, 4};
imprime_primeiro(vetor);
/* 1 */
Agora, se a funo no impe nenhum tamanho para o vetor, como vamos descobrir qual
o tamanho do vetor que a funo recebeu? A resposta que a funo no tem como descobrir
isso sozinha; em geral, voc quem deve cuidar disso. Por exemplo, voc pode passar para
a funo dois parmetros: o vetor e o tamanho do vetor. Uma ilustrao disso uma funo
que imprime todos os elementos de um vetor:
void imprime_vetor(int v[], int n)
{
int i;
for (i = 0; i < n; i++)
printf("%d\n", v[i]);
}
Da mesma maneira, poderamos fazer uma rotina que l do teclado uma srie de nmeros
inteiros e armazena todos eles num vetor. Uma implementao simples seria como a seguir:
void le_vetor(int v[], int n)
{
int i;
for (i = 0; i < n; i++)
scanf("%d", &v[i]);
}
Se o tamanho do vetor for explicitamente fornecido, o compilador no deve reclamar, mas o programa no
ser forado de maneira alguma a respeitar esse tamanho.
74
p++;
Essas operaes com ponteiros so conhecidas como aritmtica de ponteiros. Veja que
elas apenas alteram o local de destino dos ponteiros; elas no mexem com os valores apontados. Note tambm que no existem, nem fariam sentido, as operaes de multiplicao e
diviso de ponteiros.
Agora como usamos isso para acessar o i-simo elemento do vetor v? A expresso v + i
(ou p + i neste exemplo acima) certamente um ponteiro para ele, pelo que acabamos de
ver; ento podemos usar *(v+i) para acess-lo. Como isso usado com muita frequncia,
existe uma abreviao sinttica, que nada mais que v[i]. Assim, ao acessar elementos
de vetores, estamos o tempo todo utilizando a aritmtica de ponteiros!
Isso nos d uma nova maneira de caminhar pelos vetores: criar um ponteiro que aponta
inicialmente para o comeo do vetor e aument-lo de uma unidade num lao for, por exemplo:
int v[10];
int *p;
for (p = v; /* condio */; p++) {
Strings
75
/* o elemento atual do vetor pode ser acessado
* simplesmente pela expresso *p. */
5.7 Strings
Uma das principais utilidades do tipo char usar vetores para formar sequncias de caracteres, conhecidas em computao como strings (esse termo tornou-se to difundido que
no costuma ser traduzido; possveis tradues so sequncias ou cadeias de caracteres).
Lembre-se de que caracteres nada mais so que inteiros com um signicado especial, codicados atravs de uma tabela (em geral, a tabela ASCII e suas extenses). Em C, uma string
simplesmente um vetor de caracteres, com uma conveno especial: como o valor zero no
utilizado por nenhum caractere, ele utilizado para marcar o nal de uma string, ou seja, ele
o terminador da string. O caractere de cdigo zero comumente chamado de caractere
nulo, e representado pela sequncia especial '\0' ou simplesmente pela constante inteira
0.
Sabendo disso, podemos declarar uma string da mesma maneira como declaramos vetores:
char string[] = {'O', 'l', '', '\0'};
Certamente no nada prtico escrever assim! Felizmente essa notao pode ser bastante abreviada: em vez de escrever explicitamente um vetor de caracteres com um caractere
nulo, podemos deixar mais clara a cara de string, colocando os caracteres desejados entre
aspas duplas, sem separao entre eles, e o resto (inclusive o caractere nulo) car por conta
do compilador. (Veja que j usamos essa sintaxe o tempo todo quando usamos as funes
scanf e printf!) O cdigo acima poderia ser reescrito como a seguir, e as duas formas so
absolutamente equivalentes:
char string[] = "Ol";
Uma das grandes vantagens de usar o terminador '\0' (explicita ou implicitamente) que
voc no precisa se preocupar com o tamanho das strings: ao caminharmos pelos caracteres
de uma string, no precisamos registrar a posio em que estamos para saber quando parar;
s parar quando encontrarmos o caractere nulo. Com isso, em vez de percorrer os caracteres
de uma string usando um ndice de um vetor, podemos usar apenas um ponteiro. Veja um
uso disso neste exemplo, que imprime os caracteres da string indicada, um por linha:
char string[] = "Bom dia!";
char *p;
for (p = string; *p != 0; p++)
printf("Caractere: '%c'\n", *p);
76
int n = 0;
char string[] = "Bom dia!";
char *p;
for (p = string; *p != 0; p++)
n++;
printf("A string tem comprimento %d\n", n);
Isso equivalente a copiar individualmente cada caractere da nossa string pr-fabricada para
a varivel de destino:
char mensagem[20];
mensagem[0] = 'B';
mensagem[1] = 'o';
mensagem[2] = 'm';
mensagem[3] = ' ';
mensagem[4] = 'd';
mensagem[5] = 'i';
mensagem[6] = 'a';
Strings
77
mensagem[7] = '!';
mensagem[8] = 0;
Note que o terminador tambm copiado! Assim, na string de destino, devemos garantir
que o espao seja suciente para guardar todos os caracteres da string original mais um (o
terminador). Se o destino tem tamanho 20, podemos copiar no mximo uma string de 19
caracteres.
Observe tambm que no utilizamos o operador de endereo com a nossa varivel tipo
string, pois ela um vetor e, como j vimos, vetores so naturalmente passados por referncia
em parmetros de funes.
Essa funo tambm pode, naturalmente, ser usada para copiar o contedo de uma string
para outra quando ambas esto contidas em vetores j declarados:
char origem[20] = "Boa noite!";
char destino[20];
strcpy(destino , origem);
Como sempre, devemos ter cuidado para no exceder o tamanho do vetor original. Se
tentssemos copiar uma mensagem muito longa para o vetor, nosso programa iria tentar
gravar dados fora da rea reservada para o vetor, o que j vimos que no uma boa ideia.
Por isso, existe tambm uma outra funo, strncpy, que copia o contedo de uma string para
outra sem exceder um certo nmero mximo de caracteres (especicado por um parmetro
da funo):
O resultado deste cdigo ser copiar a sequncia Copiando uma mensage, com exatamente 20
caracteres, para a varivel mensagem. O problema aqui que, como a string de origem era
mais longa do que o destino poderia suportar, no foi inserido nenhum terminador. Nesse
caso, o que podemos fazer inserir o terminador manualmente, e pedir para copiar no mximo 19 caracteres:
char mensagem[20];
strncpy(mensagem , "Copiando uma mensagem muito longa", 19);
mensagem[19] = 0;
Caso a origem tenha tamanho menor do que o tamanho mximo especicado, o terminador
ser copiado automaticamente.
J vimos, na seo anterior, como possvel encontrar o tamanho de uma string. Como
esse cdigo muito usado, existe uma funo da biblioteca padro que faz exatamente a
mesma coisa: strlen (string length). Seu uso bem simples:
char mensagem[50] = "Uma mensagem muito longa";
printf("A mensagem tem comprimento %d.\n", strlen(mensagem));
78
Note que a funo devolve o comprimento da string (24), e no o comprimento do vetor que
foi usado para guard-la (que 50).
Outra tarefa muito comum descobrir se duas strings so iguais. Como strings so vetores, que so ponteiros, uma comparao do tipo s1 == s2 compara os endereos em que
esto guardadas as strings. Obviamente essa comparao s verdadeira quando comparamos uma string com ela mesma; nosso objetivo comparar duas strings distintas que, no
entanto, possam apresentar o mesmo contedo.
Para isso, devemos comparar as strings elemento a elemento; convenientemente h uma
funo que j incorpora esse trabalho, strcmp (string compare). Dadas duas strings s1 e s2 ,
strcmp(s1, s2) devolve o valor zero caso elas sejam iguais, e um valor diferente de zero caso
sejam diferentes. Esse valor tambm serve para determinar a ordem lexicogrca (a ordem
do dicionrio) das duas strings: um nmero negativo indica que s1 < s2 , e um nmero
positivo indica que s1 > s2 . Por exemplo, dadas s1 = verde e s2 = vermelho, temos
s1 < s2 pois as duas palavras coincidem nas 3 primeiras letras, mas, na 4 letra, d vem
antes de m.
A ordem lexicogrca estabelecida de acordo com a tabela ASCII por exemplo,
nmeros vm antes de letras maisculas, que vm antes das minsculas, de modo que verde
diferente de Verde (por exemplo, Vermelho < verde < vermelho). Se quisermos fazer
uma comparao seguindo outra ordem, precisaremos construir nossa prpria funo, o que
seremos capazes de fazer depois de estudar, no Captulo 6, o funcionamento interno da funo
strcmp.
Para ler uma string, possvel tambm usar o cdigo %s na funo scanf; no entanto, h
alguns cuidados que devem ser tomados, como explicaremos adiante. agora o momento
de introduzir uma nova funo: fgets. A novidade dessa funo que ela tambm pode ser
usada para ler uma string de um arquivo, e portanto precisamos de um parmetro especial
para dizer que o arquivo de que vamos ler a entrada do usurio pelo teclado esse
parmetro stdin, que a abreviao em ingls para entrada padro (standard input), que
o termo usado para denotar o canal de comunicao entre o programa e o terminal.
Antes de dizer como se escreve isso em C, vamos observar que, para ler uma string,
precisamos de um vetor grande o bastante para guardar os caracteres. Como no sabemos
a priori quantos caracteres sero digitados, a estratgia que seguimos em geral a seguinte:
comeamos reservando um espao inicial (temporrio) para os caracteres, e lemos os dados
Se voc se perguntou por que cargas dgua precisvamos usar esse termo tcnico em vez de simplesmente
dizer teclado, adianto que a entrada padro pode ser redirecionada para pegar dados, por exemplo, de um arquivo
em vez do teclado, e portanto uma estrutura exvel que permite diversos tipos de entrada. Veremos um pouco
sobre isso mais adiante.
79
disponveis at encher esse espao. Se no houver dados para preencher todo o espao, terminamos nosso trabalho; se ainda sobrarem dados para ler, precisamos transferir os dados
lidos para outro lugar, para poder continuar lendo o restante dos dados, repetindo o procedimento conforme for necessrio. O nome que costuma ser dado a esse espao temporrio
buer, que tem exatamente esse sentido um espao temporrio para armazenar dados
provenientes de algum dispositivo (que o nosso caso, com o teclado), ou destinados a algum
dispositivo.
A funo fgets precisa, alm do parmetro especial stdin, de duas informaes: quantos
caracteres (no mximo) ler, e onde guard-los. Isso feito da seguinte maneira:
fgets(destino, n, stdin);
Um comentrio importante faz-se necessrio: o parmetro n na verdade indica o nmero
mximo de caracteres da string menos um, pois a funo fgets sempre adiciona o terminador
\0 aps o ltimo caractere lido: o tamanho n refere-se ao comprimento da string quando se
inclui o terminador. Nesse sentido preciso prestar ateno diferena em relao funo
strncpy. Assim, se temos um vetor de tamanho 20, podemos chamar a funo fgets com o
parmetro n = 20, e no mximo 19 caracteres sero lidos e armazenados.
Essa funo ir ler uma linha da entrada padro at o mximo de n 1 caracteres. Se a
linha tiver menos do que isso, a leitura acabar no nal da linha; se a linha tiver mais do que
isso, a leitura acabar no (n 1)-simo caractere.
possvel ler strings usando a funo scanf de duas maneiras diferentes, ambas um pouco
diferentes do funcionamento de fgets. Para evitar problemas de acesso a lugares errados na
memria, sempre necessrio especicar o nmero mximo n de caracteres que podero ser
armazenados (Nos itens abaixo, no meio dos cdigos, entenda um n como esse nmero, e
no como uma letra n a ser digitada literalmente.)
Com o cdigo %nc, sero lidos n caracteres da entrada padro, incluindo espaos e
quebras de linha. O terminador \0 no includo.
Usando o cdigo %ns, so lidos no mximo n caracteres da entrada padro, parando
assim que encontrar algum espao em branco que no ser armazenado na string.
Um terminador includo automaticamente ao nal da string, mas o tamanho n no o
leva em conta. Ou seja, o vetor deve ter espao para n + 1 caracteres.
necessrio prestar ateno diferena de comportamento das diferentes funes em
relao incluso do terminador da string. Sempre que estiver em dvida, consulte o manual
da biblioteca padro ou, melhor ainda, faa um programa simples para testar!
Espao em branco a denominao genrica para os caracteres que representam algum tipo de espaamento,
seja horizontal ou vertical a saber, o espao comum (ASCII 32), a tabulao horizontal \t (ASCII 9) e quebras
de linha (\n, ASCII 10, ou o retorno de carro \r, ASCII 13), entre outros de uso mais raro.
80
Descrio
origem)
origem, n)
strncpy(dest ,
strlen(str)
strcmp(s1, s2)
fgets(dest , n, stdin)
Algoritmos
81
Mais ponteiros
7.1 Matrizes
Os vetores que vimos at agora eram usados para guardar variveis escalares; vamos explorar
agora outra possibilidade: usar um vetor para guardar um conjunto de vetores. Por exemplo,
se temos 3 vetores de 5 inteiros, podemos criar um vetor que contm esses 3 vetores, e
podemos acessar os inteiros usando dois ndices: primeiro o ndice que identica cada um
dos trs vetores, depois o ndice que identica cada inteiro dentro de cada vetor. Podemos
interpretar isso como uma matriz: o primeiro ndice indica a linha em que um elemento est,
e o segundo indica a posio (coluna) desse elemento dentro da linha correspondente.
Em suma, nessa representao, cada linha de uma matriz um vetor de n nmeros, e
a matriz um vetor de m vetores-linha, formando assim uma matriz m n (m linhas, n
colunas). A seguir v-se uma ilustrao dessa representao, na qual as barras mais claras
representam os vetores-linha, que esto contidos na caixa mais escura, que corresponde
matriz:
.
a0,0
.
a0,1
.
a1,0
...
.
.
am1,0
.
a1,1
.
am1,1
.
a0,n1
.
a1,n1
.
am1,n1
Poderamos tambm inverter nessa representao o papel das linhas e colunas isto ,
o ndice principal seria o da coluna e o secundrio seria uma posio (linha) dentro dessa
coluna. Preferimos manter a linha como ndice principal para cooperar com a conveno
dos matemticos, mas no h nenhuma razo computacional que nos obrigue a escolher
uma dessas interpretaes em detrimento da outra. A estrutura computacional subjacente s
determina que h uma hierarquia de ndices, que podemos interpretar como quisermos.
Para declarar uma varivel do tipo matriz, usamos a seguinte sintaxe, muito semelhante
sintaxe de vetores:
tipo_de_dados nome_matriz[linhas][colunas];
83
84
/* ok */
/* ok */
/* invlido!
85
lidos, pedir um pedao de memria no qual caibam n inteiros (ou variveis de qualquer
outro tipo). O bom disso que, como a poro de memria alocada contgua (ou seja, sem
buracos no meio), ela pode ser usada como se fosse um vetor comum.
No entanto, na maioria das vezes, queremos alocar espao para um certo nmero de
dados ou de elementos de um vetor. Resta ento saber o nmero de bytes que cada dado (ou
elemento) ocupa. Para isso, usaremos o operador sizeof, que diz quantos bytes ocupa uma
varivel de um certo tipo. Veja como ele usado a partir deste exemplo:
printf("O tamanho de um char %d bytes\n", sizeof(char));
printf("O tamanho de um int %d bytes\n", sizeof(int));
printf("O tamanho de um double %d bytes\n", sizeof(double));
Voc sempre deve usar o operador sizeof em vez de assumir, por exemplo, que o tamanho de um inteiro de 4 bytes. Isso poderia causar grandes problemas quando o programa
for transportado para um sistema em que o tamanho diferente, alm de no deixar claro o
que signica aquele 4 no meio do cdigo.
Dito isso, temos todo o material necessrio para realizar a alocao dinmica de um
vetor. Notemos que, por exemplo, se um inteiro ocupa 4 bytes, ento um vetor de n inteiros
ocupar 4n bytes, podemos escrever a rotina de alocao desta maneira:
int
*v_int;
double *v_double;
char
*v_char;
v_int
= malloc(n * sizeof(int));
v_double = malloc(n * sizeof(double));
v_char
= malloc(n * sizeof(char));
Note que o endereo devolvido pela funo malloc deve ser armazenado num ponteiro do
tipo apropriado; ou seja, o espao alocado para inteiros deve ser armazenado num ponteiro
int *, e assim por diante.
Se a memria tiver sido alocada com sucesso, poderemos acessar esses ponteiros como
vetores normais:
v_int[0] = -5;
v_int[1] = 4;
scanf("%d", &v_int[2]);
v_double[5] = (v_int[0] + v_int[1]) * 1.0/v_int[2];
Quando voc terminar de usar um bloco de memria alocado dinamicamente, voc deve
sempre liber-lo usando a funo free com o ponteiro correspondente como argumento,
como em free(ponteiro). Ao liberar um pedao de memria, o sistema operacional retoma
a guarda dele, permitindo que ele seja posteriormente usado por outros programas.
86
Da surge uma possvel fonte de erros: se voc tentar acessar um ponteiro depois que a
memria tiver sido liberada, essa memria poder estar sendo usada por outro programa, e
portanto no nada legal mexer nesse pedao de memria! Ponteiros que apontam para um
pedao de memria que foi desalocado so chamados, em ingls, de dangling pointers (literalmente, ponteiros pendentes), que vou preferir traduzir como ponteiros desapropriados.
Assim, ao liberar um bloco de memria, o ponteiro que apontava pra ele torna-se invlido, e voc no deve tentar us-lo novamente (a menos que reaponte o ponteiro para outro
bloco de memria que voc esteja permitido a usar). Uma soluo que usada com certa
frequncia transformar o ponteiro em um ponteiro nulo aps a liberao da memria
um ponteiro nulo simplesmente um ponteiro que aponta para o endereo zero (a expresso NULL simplesmente um apelido para o nmero zero que no causa problemas quando
usada como endereo), que no usado para nenhuma rea vlida da memria. Um exemplo
disso o seguinte:
free(ponteiro);
ponteiro = NULL;
O mrito dessa soluo est no fato de ser muito mais fcil vericar se um ponteiro
nulo do que vericar se ele aponta para um lugar imprprio tanto dentro do seu programa
quanto pelo sistema operacional. Quando o programa tenta acessar um ponteiro nulo, o
sistema detecta a tentativa e encerra a execuo do programa; acessos a ponteiros desapropriados nem sempre podem ser detectados.
Sendo essa uma tarefa comum, que ser executada vrias vezes no programa, til escrever uma funo que cuida desse trabalho repetitivo:
void *mallocX(size_t tamanho)
{
void *ponteiro;
ponteiro = malloc(tamanho);
if (ponteiro == NULL)
{
printf("Socorro! No foi possvel alocar memria!\n");
Tanto a ideia quanto o nome da funo foram inspirados nas notas do Prof. Paulo Feolo [1].
Ponteiros duplos
87
exit(EXIT_FAILURE);
}
}
A funo exit que foi aqui usada serve para terminar a execuo do programa imediatamente, como se tivesse terminado a funo main. O argumento EXIT_FAILURE (uma constante denida pelo sistema) indica ao sistema operacional que houve algum erro na execuo do programa. O efeito dessa instruo equivalente a voltar funo main e escrever
return EXIT_FAILURE;. No entanto, usar a funo exit mais prtico pois assim no necessrio nos preocupar com a parte do programa em que estvamos.
88
A outra maneira de criar uma matriz dinamicamente permite que ela seja acessada com
a mesma linguagem que as matrizes estticas (matriz[i][j]), porm aumentando a carga
computacional tanto do acesso aos elementos quanto da alocao da matriz. Para matrizes
bidimensionais (m, n), ela consiste em alocar m vetores de n elementos, e alocar um vetor
de m ponteiros no qual sero colocados os m vetores. importante notar que os elementos
desse ltimo vetor so ponteiros para o tipo de dados que queremos guardar, o que gera uma
pequena diferena no clculo do tamanho de cada elemento:
void exemplo_matriz(int m, int n)
{
/* aloca o vetor que guardar as m linhas */
int **matriz = mallocX(m * sizeof(int*));
int i, j;
/* aloca cada linha com n elementos */
for (i = 0; i < m; i++)
matriz[i] = mallocX(n * sizeof(int));
/* preenche cada elemento com a posio linearizada
correspondente */
for (i = 0; i < m; i++)
for (j = 0; j < n; j++)
matriz[i][j] = j + i*n;
}
89
de criar em C uma funo genrica calcula_derivada que zesse isso, dada uma funo f e
um ponto x0, bastando escrever um cdigo que fosse, esquematicamente, como a seguir:
calcula_derivada(f, x0);
Os parnteses em torno de *ponteiro so absolutamente necessrios; se no os usssemos, teramos (verique) uma declarao de uma funo que devolve um ponteiro algo
totalmente diferente!
Se a funo tiver parmetros, voc deve colocar seus tipos dentro do segundo par de
parnteses. Note bem: voc s deve colocar os tipos dos parmetros no coloque os
nomes. Por exemplo, se tivssemos uma funo que devolve um inteiro e recebe um inteiro
e um nmero real, um ponteiro para ela seria declarado assim:
int (*ponteiro)(int, float);
Obviamente, como voc j deve saber, um ponteiro no serve para nada se ele no aponta
para algo que conhecemos. Pois bem, para que um ponteiro aponte para uma funo, procedemos da mesma maneira que para os ponteiros para variveis:
ponteiro = &funcao;
Muitos compiladores (o GCC inclusive) permitem que voc omita o E comercial ao criar um ponteiro para uma funo. No entanto, recomendado que voc o escreva explicitamente para garantir
a mxima portabilidade do seu cdigo.
Talvez voc tenha achado estranho usarmos o endereo de uma funo. Mas, exatamente
como ocorre com as variveis, as funes so guardadas em algum lugar da memria quando
o programa carregado, de modo que elas tambm tm um endereo.
90
Estruturas
8.1 Structs
Em cada parte de um programa geralmente h vrias variveis associadas realizao de
uma tarefa especca. Por causa disso, conveniente ter um modo de agrupar um conjunto
de variveis relacionadas. Conhecemos anteriormente os vetores, que so agrupamentos de
uma srie de variveis do mesmo tipo, cada uma identicada por um nmero.
Se, por outro lado, quisermos um tipo de agrupamento que englobe variveis de tipos
diferentes, ou no qual cada varivel possa ser identicada por um nome especco, usamos
um tipo de estrutura chamado de registro (mais conhecido por seu nome em ingls, struct,
uma abreviao de structure, estrutura).
Esse recurso da linguagem C permite que o usurio dena seus prprios tipos de dados
a partir dos tipos primitivos da linguagem. Esse tipo de estrutura um exemplo de tipo de
dados composto o segundo a ser apresentado aqui, os vetores tendo sido o primeiro.
Um struct em C funciona de maneira similar a um registro (uma linha) em um banco de
dados: ele contm um conjunto de variveis, que tm tipos xados e so identicadas por
nomes (como as variveis comuns). Por exemplo, um registro que representa um produto
numa compra pode conter as seguintes variveis:
char *descricao;
int quantidade;
double preco_unitario;
double desconto;
double preco_total;
91
92
Captulo 8. Estruturas
int quantidade;
double preco_unitario;
double desconto;
double preco_total;
} produto;
Podemos usar esse tipo de comando para declarar vrios registros ao mesmo tempo,
colocando vrios nomes de varivel em vez de apenas um (separando por vrgulas). Contudo,
essa forma de declarao no permite reutilizar o mesmo tipo de registro em outro comando
posterior.
Para resolver essa inconvenincia, basta dar um nome ao tipo de registro: iniciamos sua
declarao por struct nome (substituindo o nome escolhido, que segue as mesmas regras de
sempre para nomes de coisas em C), em vez de apenas struct assim, declaramos um tipo
de registro, no apenas um registro. Da em diante, s digitar struct nome para se referir
ao tipo j declarado.
Tambm possvel declarar o tipo sem declarar nenhuma varivel desse tipo, bastando
para isso no colocar nenhum nome de varivel; necessrio manter o ponto-e-vrgula, no
entanto. Com essa possibilidade, interessante separar em comandos diferentes a declarao
do tipo (que ca na parte exterior do programa) e as declaraes de variveis desse tipo (que
cam no lugar certo para cada varivel), como no exemplo a seguir:
/* Declarao do tipo info_produto */
struct info_produto {
char *descricao;
int quantidade;
double preco_unitario;
double desconto;
double preco_total;
};
/* Declarao de duas variveis desse tipo */
struct info_produto produtoA , produtoB;
Listas ligadas
93
Resumo
Declarao de um tipo de registro
struct nome_do_tipo {
/* declaraes dos membros */
};
Declarao de um registro
struct nome_do_tipo nome_da_variavel;
Compilao
95
Tabelas de referncia
97
[1]
[2]
99