Escolar Documentos
Profissional Documentos
Cultura Documentos
E S. D
edudobay@gmail.com
Instituto de Fsica
Universidade de So Paulo
Prefcio 1
Introduo 3
1 Princpios bsicos 7
1.1 O ncleo de um programa . . . . . . . . . . . . . . . . . . . . . . . . . . 7
1.2 Variveis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
1.3 Entrada e sada de dados . . . . . . . . . . . . . . . . . . . . . . . . . . 11
1.4 Matemtica bsica . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
1.5 Boas maneiras em C . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
2 Controle de uxo 19
2.1 Desvios condicionais: if . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
2.2 Repetindo operaes: laos (loops) . . . . . . . . . . . . . . . . . . . . . 22
2.3 Contadores e laos for . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
2.4 Condies compostas . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
2.5 Repeties encaixadas . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
2.6 Variveis booleanas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
3 Funes 37
3.1 Introduo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
3.2 Os parmetros e o valor de sada . . . . . . . . . . . . . . . . . . . . . . 39
3.3 Usando funes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
3.4 Trabalhando com vrias funes . . . . . . . . . . . . . . . . . . . . . . . 42
3.5 Escopo de variveis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
i
ii Sumrio
5 Ponteiros e vetores 63
5.1 Prolegmenos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63
5.2 Ponteiros como parmetros de funes . . . . . . . . . . . . . . . . . . . 66
5.3 Cuidado com os ponteiros! . . . . . . . . . . . . . . . . . . . . . . . . . 68
5.4 Vetores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69
5.5 Vetores como argumentos de funes . . . . . . . . . . . . . . . . . . . . 72
5.6 Ponteiros e vetores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
5.7 Strings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
5.8 Mais sobre entrada e sada . . . . . . . . . . . . . . . . . . . . . . . . . . 79
6 Algoritmos 81
7 Mais ponteiros 83
7.1 Matrizes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
7.2 Alocao dinmica de memria . . . . . . . . . . . . . . . . . . . . . . . 84
7.3 Ponteiros duplos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87
7.4 Ponteiros para funes . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88
7.5 Escopo de variveis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90
7.6 Funes recursivas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90
8 Estruturas 91
8.1 Structs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91
8.2 Listas ligadas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93
Apndices
A Compilao 95
B Tabelas de referncia 97
Referncias Bibliogrcas 99
Prefcio
1
Introduo
Este captulo introduz alguns conceitos bsicos sobre computao, mas sem entrar na lin-
guagem C.
O que um computador?
A denio mais ampla de computador a de uma mquina que realiza uma srie de ope-
raes 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 diver-
sos aparelhos eletrnicos com essas caractersticas, de calculadoras a telefones celulares.
Um computador tem um conjunto de instrues que correspondem s operaes que po-
dem 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 de-
nio 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 ar-
quitetura usada em geral para descrever o modelo de funcionamento interno de um com-
putador.)
Esse modelo descreve o funcionamento geral do computador em termos da interao
entre trs componentes:
3
4 Introduo
Memria
Monitor
Sada
Unidade de Mdias externas
processamento
(CPU) Teclado
Entrada 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 comple-
xidade; 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
#include <stdio.h>
int main()
{
printf("Ol, maravilhoso mundo da programao!\n");
return 0;
}
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.
7
8 Captulo 1. Princpios bsicos
#include <stdio.h>
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()
aqui que denimos nossa funo main. As chaves { } servem para delimitar o seu
contedo.
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.
Aqui printf o nome uma funo da biblioteca padro ( possvel us-la graas ao #in-
clude 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 impri-
mir (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 usa-
mos 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 pro-
grama 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 9
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 vari-
veis, 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.
Tipo Utilidade
int Armazena um nmero inteiro
float Armazena um nmero de ponto utuante
double Como oat, mas fornece maior preciso
char Guarda um nico caractere. Com esse tipo tambm podemos guardar sequn-
cias 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 combi-
nados de certas maneiras, de modo a criar estruturas mais complexas de dados (por exemplo,
10 Captulo 1. Princpios bsicos
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, pre-
cisamos 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 re-
serve 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:
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;
int a, b;
a = 5;
b = a;
Entrada e sada de dados 11
Na verdade, voc pode atribuir a uma varivel o valor de uma expresso qualquer; por exem-
plo, expresses aritmticas envolvendo variveis e constantes, como veremos a seguir.
A primeira atribuio de uma varivel costuma ser chamada de inicializao. A ini-
cializao 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 va-
riveis 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.
int num;
num = 17;
printf(num);
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 Captulo 1. Princpios bsicos
#include <stdio.h>
int main()
{
int dia, mes, ano;
dia = 22;
mes = 7;
ano = 2008;
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.
No menos importante que a sada a entrada de dados, ou seja, o ato de ler informaes
fornecidas pelo usurio. Novamente, uma das maneiras mais simples de fazer isso ler dados
que o usurio digita pelo teclado. Para isso, usamos uma funo parente da printf, a funo
scanf. O que ela faz passar o controle para o teclado, esperar que o usurio digite algo
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;
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;
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.
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; /* 2 */
y = 12 % 7; /* 5 (resto da diviso) */
x = 12 + 5 * 7; /* 47 */
x = (12 + 5) * 7; /* 119 */
x = ((12 + 5) * 7 + 2) * 3; /* 363 */
fat = 1 * 2 * 3 * 4 * 5; /* 120 */
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);
#include <stdio.h>
int main()
{
int celsius , 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:
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 reali-
zados 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 arredon-
damento 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 ,
* termina com asterisco e barra.
*/
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.
#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;
}
#include <stdio.h>
int main(
){
printf(
);
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 Captulo 1. Princpios bsicos
Escreva uma instruo (um comando) por linha. Quando for necessrio, divida a ins-
truo em mais de uma linha para melhorar a legibilidade (voc pode quebrar a linha
em qualquer lugar onde poderia colocar um espao). Em algumas situaes tambm
pode ser aceitvel juntar duas instrues em uma linha s, mas no abuse disso.
Sempre que voc tiver um bloco de comandos (algo delimitado por chaves), o contedo
do bloco deve estar mais afastado da margem que o exterior do bloco. isso que
chamamos de indentao; ela torna muito mais fcil a visualizao da estrutura do
cdigo e de onde os blocos se encaixam. Voc tambm deve deixar com o mesmo
recuo as linhas que pertencem ao mesmo bloco, seno acaba aparecendo uma falsa
noo de hierarquia (ou uma verdadeira noo de baguna).
Controle de uxo
2
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; pre-
ciso que o programa saiba fazer decises. A capacidade do computador de fazer decises
conhecida como controle de uxo.
if (condio)
{
/* comandos aqui */
}
19
20 Captulo 2. Controle de uxo
Operador Signicado
< menor que
<= menor que ou igual a
> maior que
>= maior que ou igual a
== igual a (cuidado! so DOIS iguais!)
!= diferente de
if (a == b)
{
printf("Os nmeros so iguais!\n");
}
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 equiva-
lente 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 ope-
rador 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 atri-
buio 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 con-
dicional 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 c-
digo, ainda que semanticamente incorreto, sintaticamente vlido; por causa disso, voc
conseguir compilar e executar seu programa, mas alguma parte dele exibir algum compor-
tamento inesperado. Portanto, muito cuidado ao fazer comparaes de igualdade.
#include <stdio.h>
int main()
{
int num;
printf("Digite um nmero: ");
scanf("%d", &num);
if (num % 2 == 0)
printf("O nmero par.\n");
if (num % 2 != 0)
printf("O nmero mpar.\n");
return 0;
}
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 Captulo 2. Controle de uxo
#include <stdio.h>
int main()
{
int num;
printf("Digite um nmero: ");
scanf("%d", &num);
if (num % 2 == 0)
printf("O nmero par.\n");
else
printf("O nmero mpar.\n");
return 0;
}
while (condio)
{
/* comandos aqui */
}
Repetindo operaes: laos (loops) 23
Vamos criar um exemplo bem bsico: um programa que imprime na tela, em ordem
crescente, os nmeros inteiros de 1 a 10. Como fazer isso usando while?
Uma idia usar uma varivel que tem o valor inicial 1, e aumentar esse valor de 1 em
1 at que chegue a 10 (aqui entra o while!), imprimindo o valor atual antes de realizar cada
incremento. Ficou confuso? Vamos ver um esquema disso, usando no lugar do C um esboo
em uma linguagem de programao simplicada e genrica:
num 1
repita enquanto num 10:
imprima num
num num + 1
Para ter certeza de que cou tudo claro, vamos simular a execuo desse pseudocdigo.
Veja que inicialmente a varivel num vale 1. Como 1 10, entramos no bloco do repita;
assim, imprimimos o nmero 1 e a varivel passa a valer 2. Voltamos para a avaliao
da condio e obtemos novamente um resultado positivo, 2 10; o programa imprime o
nmero 2 e a varivel recebe o valor 3. Isso continua at que o valor de num seja 9; esse
nmero impresso e o valor 10 colocado na varivel. A ocorre a ltima iterao do lao:
imprimimos o nmero 10, e num passa a valer 11. Com isso, a condio do lao deixa de
ser verdadeira, pois ela ser avaliada como 11 10.
Agora vamos passar isso de volta para o C. A traduo quase literal, excluindo-se o
cabealho e o rodap do programa:
24 Captulo 2. Controle de uxo
#include <stdio.h>
int main()
{
int num;
num = 1;
while (num <= 10) {
printf("%d\n", num);
num = num + 1;
}
return 0;
}
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
Repetindo operaes: laos (loops) 25
repita enquanto b n:
a a * b
bb+1
int main()
{
int n, a, b;
a = 1;
b = 2;
while (b <= n) {
a = a * b;
b = b + 1;
}
Vamos agora ver outro tipo de programa que vale a pena ser ressaltado. Buscaremos
resolver o seguinte problema: ler do teclado uma sequncia de nmeros, cujo tamanho no
inicialmente conhecido, mas que sabemos ser terminada por um zero (ou seja, os demais
nmeros da sequncia so todos no-nulos), e somar os nmeros lidos. Que tipo de algoritmo
podemos usar para atacar esse problema?
Obviamente, se no sabemos a priori quantos elementos a sequncia tem, no podemos
armazenar todos os elementos para depois somar todos; precisamos acumular as somas par-
ciais a cada nmero lido. Utilizaremos para isso uma varivel chamada soma, que dever ser
inicializada com o valor zero, e qual se somar cada nmero lido.
A idia central ler nmeros do teclado indenidamente at que o nmero lido seja
zero. Sempre que o nmero for diferente de zero, devemos inclu-lo na soma e continuar
26 Captulo 2. Controle de uxo
lendo nmeros. Declarando uma varivel num para armazenar os nmeros lidos, podemos
proceder assim:
while (num != 0) {
soma = soma + 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;
while (num != 0) {
soma = soma + num;
Vamos agora adicionar mais uma tarefa ao nosso problema: encontrar o elemento m-
ximo 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 posterior-
mente 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>
Contadores e laos for 27
int main()
{
int soma, /* somas parciais */
num, /* ltimo nmero lido */
max; /* candidatos a mximo */
soma = 0;
while (num != 0) {
soma = soma + num;
if (num > max) /* para procurar o mximo */
max = num;
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:
int main()
{
int num;
for (num = 1; num <= 10; num = num + 1)
printf("%d\n", num);
return 0;
}
int main()
{
int n, a, b;
printf("Digite um nmero: ");
scanf("%d", &n);
a = 1;
for (b = 2; b <= n; b = b + 1)
a = a * b;
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.
4. Ao nal de cada iterao, executa-se a instruo de incremento ela pode ser qual-
quer 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 = var + expr
var -= expr var = var - expr
var *= expr var = var * expr
var /= expr var = var / expr
var %= expr var = 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 Captulo 2. Controle de uxo
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 te-
clado), 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 (ter-
minada 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).
if (x >= 0) {
if (x <= 10)
printf("Ok!\n");
else
printf("O nmero precisa estar entre 0 e 10!\n");
}
else
printf("O nmero precisa estar entre 0 e 10!\n");
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 im-
possvel 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:
&& e || 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 Captulo 2. Controle de uxo
Note que, coloquialmente, quando usamos a palavra ou para unir duas oraes, geral-
mente 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 inclu-
sivo. Daqui para a frente, a menos que indiquemos o contrrio, usaremos sempre o ou
inclusivo.
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:
divisores = 0;
if (divisores == 0) {
/* no achamos nenhum divisor dentre os possveis
candidatos , ento primo */
}
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 (pois, se d for realmente divisor,
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 */
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 Captulo 2. Controle de uxo
A partir desse conhecimento, podemos analisar o que acontece em algumas das situaes
capciosas que mencionamos anteriormente. Primeiro temos as comparaes compostas do
tipo a < b < c; quando escrevemos em C exatamente dessa maneira, o que ocorre que a ex-
presso avaliada da esquerda para a direita, dois operandos por vez. Assim, primeiramente
avaliada a expresso a < b, cujo resultado poder ser 0 ou 1 chamemo-no genericamente
de R. Ento avaliada a expresso R < c, que nada tem a ver com a relao entre b e c.
Olhemos agora para as comparaes errneas que usam o operador de atribuio em vez
do operador de comparao de igualdade, como if (a = b). O valor de uma expresso como
a = b corresponde ao valor que foi atribudo, ou seja, b (ou a aps a atribuio). Assim, o
cdigo if (a = b) equivalente a
a = b;
if (b)
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 35
a = b;
if (b != 0)
R 05/02/2011
Funes
3
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.
37
38 Captulo 3. Funes
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 en-
quanto, 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 calcula a soma dos divisores de um nmero inteiro n. Como entrada,
teremos obviamente o nmero n, que ser uma varivel do tipo int. Como sada,
teremos outro valor do tipo int, que corresponder soma dos divisores de n. Com
isso, o cabealho ca
int soma_divisores(int n)
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
Os parmetros e o valor de sada 39
Voc est criando uma funo que recebe um ms e um ano e imprime na tela o ca-
lendrio 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.
x y
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:
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
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:
void imprime_numero(int n)
{
if (n < 0) {
printf("No quero imprimir nmeros negativos!\n");
return;
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.
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);
int a, b;
a = potencia(2, 3);
b = soma(a, 8);
imprime_soma(a, b + potencia(a, b));
#include <stdio.h>
pot = 1;
for (i = 1; i <= expoente; i++)
pot *= base;
return pot;
}
int main()
{
int base, expoente;
return 0;
}
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.
int main()
{
int x, y;
x = 10;
y = 5;
printf("main - antes: x = %d, y = %d\n", x, y);
troca(x, y);
printf("main - depois: x = %d, y = %d\n", x, y);
return 0;
}
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:
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 va-
riveis 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.
Mais sobre nmeros
4
4.1 Bases de numerao
Ao criar uma mquina que faz clculos, nada mais crucial que a escolha da maneira de re-
presentao dos nmeros ou do formato em que eles sero armazenados. Para ns, o sistema
decimal parece ser o mais natural contamos com os 10 dedos das mos (uma possvel
explicao para o uso da base 10 e no de outra); usamos as potncias de 10 (dez, cem, mil)
como referncia ao falar os nmeros por extenso.
No entanto, os componentes de um computador funcionam com base em correntes el-
tricas, e com eles muito mais simples implementar um sistema de numerao binria (ou
base 2), no qual existem apenas dois dgitos (0 e 1), correspondendo, por exemplo, a um
nvel baixo ou nulo (0) e a um nvel alto (1) de corrente ou voltagem.
Se tomarmos um instante para compreender como a nossa usual base 10 funciona, ser
mais fcil entender como a base 2 funciona. Tomemos um nmero; por exemplo, 41 502.
Ele pode ser tambm escrito como
Veja, ento, que cada algarismo funciona como um multiplicador de uma potncia de
10. Os expoentes so contados a partir da direita, comeando pelo zero. Assim, o primeiro
algarismo (a partir da direita) multiplicado por 100 , o segundo por 101 , e assim por diante.
Perceba a regrinha implcita do sistema: o valor de cada algarismo sempre menor que a
base (o maior algarismo, 9, menor que a base, 10) e maior ou igual a zero. Isso necessrio
para que cada nmero tenha uma nica representao nessa base.
Para lembrar como so as regras para se contar, pense em um contador analgico (por
exemplo, o odmetro dos carros mais antigos, ou os medidores de consumo de gua e energia
eltrica nas casas), daqueles que mostram cada algarismo em uma rodinha. Cada rodinha
marca um algarismo de 0 a 9 e corresponde a uma ordem de grandeza (1, 10, 100, ).
Sempre que uma das rodinhas d uma volta completa (passando do 9 para o 0), a rodinha
sua esquerda aumenta seu dgito. Se esse dgito j era 9, o mesmo processo repetido
sucessivamente para as outras rodinhas da esquerda.
45
46 Captulo 4. Mais sobre nmeros
Sabendo isso, podemos, por analogia, proceder base 2. As regras so, pois, as seguintes:
Como cada algarismo deve ter um valor menor que o da base e maior e igual a zero, s
podemos ter os algarismos 0 e 1, como voc provavelmente j sabia. Por isso, contar
em binrio bastante simples pense num contador binrio, no qual cada rodinha
s tem duas posies: 0 e 1. Quando uma rodinha passa do 1 para o 0, a prxima
rodinha (a da esquerda) se movimenta: se estiver no 0, passa para o 1; se estiver no 1,
passa para o 0, propagando o movimento para a esquerda.
Os nmeros de um a dez escrevem-se assim em binrio:
(1101101)2 = 1 26 + 1 25 + 0 24 + 1 23 + 1 22 + 0 21 + 1 20 = (109)10
Veja que usamos a notao ( )2 ou ( )10 para especicar a base em que estamos
escrevendo. (Quando no houver indicao, car entendido que o nmero est na
base decimal.)
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 representa-
es (que costumam ser os algarismos arbicos sempre que possvel). Aqui, os aj simbo-
lizam os valores dos dgitos (como objetos matemticos abstratos), independentemente de
sua representao. Quando a base no ultrapassa 10, os valores confundem-se com suas re-
presentaes (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
O armazenamento dos dados 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
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.
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.
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 n-
meros armazenveis com tais tamanhos.
Tipo Bits Bytes Maior nmero
char 8 1 255
short 16 2 65 535
int, long 32 4 4 294 967 295
long long 64 8 18 446 744 073 709 551 615
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 com-
plemento 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 represen-
tao 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 mencio-
namos 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: sig-
ned (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 Captulo 4. Mais sobre nmeros
Tabela 4.2: As faixas de nmeros armazenveis nos tipos inteiros com sinal.
Tipo Regio
char (8 bits) 128 a 127
short (16 bits) 32 768 a 32 767
int, long (32 bits) 2 147 483 648 a 2 147 483 647
long long (64 bits) 9 223 372 036 854 775 808 a 9 223 372 036 854 775 807
double x, y;
float z, w;
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 escreve-
mos 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
#include <stdio.h>
int main()
{
double x = 5.0;
float y = 0.1;
int z = 27;
return 0;
}
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;
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.
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:
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 Captulo 4. Mais sobre nmeros
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:
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 inni-
tamente 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
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-las-
ei 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
k
ak 10 + bk 10k
k=0 k=1
Como no caso dos inteiros, claro que todos os dgitos ak e bk s podem assumir os va-
lores {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 Captulo 4. Mais sobre nmeros
a a 1 a ( )
= n n
= n 1 + 2n + 22n +
2n 1 2 12 2
Como a representao de a tem no mximo n dgitos, e a srie geomtrica direita uma
dzima de perodo n, a representao da nossa frao ser uma dzima cujo perodo a
representao de a (com zeros esquerda para completar os n dgitos se necessrio).
O outro mtodo de achar a representao decimal de uma frao voc j conhece:
o algoritmo da diviso. Para aplic-lo ao caso das fraes, basta escrever o denominador
e o numerador em binrio e fazer o processo de diviso, lembrando que as regras do jogo
mudam um pouco por exemplo, voc s pode multiplicar o divisor por 0 ou 1. No vou
ensinar os detalhes aqui no o propsito deste livro. Mas basicamente este o algoritmo
usado pelo computador para converter nmeros fracionrios para a representao binria;
assim que ele pode controlar quantos dgitos sero mantidos na representao basta parar
o algoritmo na casa desejada.
Fizemos uma longa digresso sobre representao binria de nmeros. Para que isso ser-
viu? Agora poderemos entender melhor como funciona a representao de ponto utuante.
Como dissemos acima, na representao de ponto utuante um nmero tem duas partes: a
mantissa e o expoente. Para a mantissa M devemos impor uma condio de normalizao
como no caso da notao cientca decimal: devemos ter 1 M < 2 para que esquerda
da vrgula haja um e apenas um dgito (no-nulo). Assim, uma frao como 21/4 seria
normalizada da seguinte maneira:
21 21 (10101)2
= 4= 22 = (1,0101)2 22
4 16 24
Devemos considerar que a mantissa deve ser representada com um tamanho xo (e nito)
de dgitos. No caso da preciso simples do oat, esse nmero costuma ser de 24 dgitos.
Assim, muitas fraes precisam ser arredondadas tanto as que tm representaes innitas
quanto as que tm representaes nitas porm muito longas. Por exemplo, a frao 1/10
(representao innita) seria arredondada para
Agora vamos chegar mais perto da representao que realmente usada pelo computador.
Um nmero de ponto utuante representado com a seguinte estrutura:
Nmeros reais 55
. . . .
sinal expoente mantissa
31 30 23 22 0
63 62 52 51 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 representa-
es 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 sequenci-
almente, 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 al-
guns 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 sozi-
nho; 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.
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, analoga-
mente, 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 nme-
ros negativos e positivos so tratados de maneira simtrica na representao de ponto
utuante.
Como vrios nmeros precisam ser arredondados para serem representados, a repre-
sentao 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 m-
nima.
56 Captulo 4. Mais sobre nmeros
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).
ax2 + bx + c = 0,
#include <stdio.h>
int main()
{
float a, b, c, delta;
printf("D os coeficientes da equao ax^2 + bx + c = 0: ");
scanf("%f %f %f", &a, &b, &c);
Nmeros reais 57
if (delta > 0)
printf("A equao tem duas razes reais distintas.\n");
else if (delta == 0)
printf("A equao tem uma raiz real dupla.\n");
else
printf("A equao no tem razes reais.\n");
return 0;
}
float x;
x = 1/7;
printf("1/7 = %f\n", x);
58 Captulo 4. Mais sobre nmeros
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 usu-
rio, 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.
Funo Signicado
sin, cos, tan Funes trigonomtricas: seno, cosseno e tangente. Os ngulos
so sempre expressos em radianos.
asin, acos, atan Funes trigonomtricas
[ inversas.
] asin e atan devolvem um n-
gulo no intervalo 2 , 2 ; acos devolve um ngulo no intervalo
[0, ].
sinh, cosh, tanh Funes hiperblicas (seno, cosseno e tangente)
sqrt Raiz quadrada (square root)
exp Funo exponencial (ex )
log Logaritmo natural, base e (ln)
log10 Logaritmo na base 10
abs, fabs Mdulo (valor absoluto) de um nmero. Use abs para inteiros e
fabs para nmeros de ponto utuante.
pow(x, y) Potenciao: xy (x e y podem ser nmeros de ponto utuante)
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:
diferena; se o valor da funo for da ordem de 106 , a mesma parcela de 109 ainda
signicativa.
Podemos modicar um pouco esse critrio levando isso em conta: em vez de limitar
o tamanho de cada parcela, limitaremos o tamanho da parcela dividido pelo valor
acumulado at ento. Se dividirmos pela soma do passo anterior, que no inclui essa
parcela, obtemos a quantidade conhecida como variao relativa se Sn indica a
soma das primeiras n parcelas, a (n + 1)-sima parcela corresponde a Sn+1 Sn , e
ento o critrio de limitao na variao relativa traduz-se matematicamente em
Sn+1 Sn
< ,
Sn
parando no passo n + 1 se essa condio for satisfeita. Esse critrio bastante usado
em diversos mtodos numricos.
#include <stdio.h>
void imprime_cardapio()
{
printf (
"Mussarela %5.2f\n"
"Margherita %5.2f\n"
"Portuguesa %5.2f\n",
PRECO_MUSSARELA , PRECO_MARGHERITA , PRECO_PORTUGUESA);
}
char opcao;
opcao = 'b';
Tabela 4.5: Algumas das sequncias utilizadas para representar caracteres especiais.
Sequncia ASCII Signicado
\t 9 Tabulao. Avana o cursor para posies pr-denidas ao longo
da linha; usualmente, essas posies so denidas de 8 em 8 ca-
racteres a partir da primeira posio da linha.
\n 10 Quebra de linha. Esse caractere comumente conhecido pela sigla
NL, new line, ou por LF, line feed.
\r 13 Retorno de carro (CR, carriage return): retorna o cursor para o
incio da margem esquerda. (Ver observao adiante.)
\" 34 Aspa dupla.
\' 39 Aspa simples.
\\ 92 Barra invertida.
\nnn Permite especicar qualquer caractere pelo seu cdigo em base
octal (de 000 a 377). Cada n um dgito de 0 a 7.
\xnn 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).
char opcao;
scanf("%c", &opcao);
printf("Voc escolheu a opo (%c)!\n", opcao);
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
5.1 Prolegmenos
Num computador, cada varivel guardada em uma certa posio da memria. Essas posi-
es 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 pro-
grama, 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.
63
64 Captulo 5. Ponteiros e vetores
O conceito de ponteiros pode ser estendido para funes possvel passar um pon-
teiro para uma funo como parmetro. Por exemplo, podemos criar uma funo
chamada acha_raiz com um algoritmo numrico que acha razes de uma funo ma-
temtica f; a funo f poderia ser passada (na forma de ponteiro) como parmetro da
funo acha_raiz.
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; /* ok, os trs so ponteiros */
double *p4, p5, p6; /* problema! s p4 ser um ponteiro */
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.
&var
var p
*p
Figura 5.2: Ilustrao da relao entre ponteiros e variveis.
Por exemplo,
*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); /* ainda imprime 5 */
printf("b = %d\n", b); /* imprime 10 */
c = *p1 * *p2;
66 Captulo 5. Ponteiros e vetores
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), re-
comendvel 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); /* 20 */
return 0;
Ponteiros como parmetros de funes 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):
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 intrinse-
camente por valor, mas podemos usar ponteiros para obter o comportamento das chamadas
por referncia.
else if (delta == 0) {
*raiz1 = -b/(2*a);
return 1;
}
else {
*raiz1 = (-b - sqrt(delta)) / (2*a);
*raiz2 = (-b + sqrt(delta)) / (2*a);
return 2;
}
}
Voc no inicializou o ponteiro (isto , no atribuiu nenhum valor a ele), de modo que
no se tem idia do lugar da memria para o qual ele aponta.
Voc apontou o ponteiro para um endereo arbitrrio, que no pertence ao seu pro-
grama. Por exemplo, se voc tentar atribuir o valor 300 ao seu ponteiro, ele apontar
para a posio 300 da memria do seu computador no temos idia do que pode
haver nesse lugar.
Seu ponteiro caiu num lugar invlido da memria, apesar de ter sido inicializado
corretamente. Isso ocorre mais frequentemente quando voc est variando atravs de
um lao (for, while) a posio apontada pelo ponteiro (veremos isso mais adiante) e
no pra na hora certa alguma hora, seu ponteiro acaba apontando para um lugar
que no deveria.
int *p;
*p = 5; // 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[0] V[1] V[n 1]
.
Para usar um vetor, precisamos primeiro declar-lo, como era feito para qualquer varivel
normal. A declarao de um vetor feita da seguinte maneira:
tipo_de_dados nome_vetor[tamanho];
O compilador entende essa frase como: reserve na memria um espao para tamanho va-
riveis 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];
Isso no apenas uma extravagncia do C; muitas linguagens funcionam dessa maneira, que a mais natural
para um computador logo mais veremos por qu.
70 Captulo 5. Ponteiros e vetores
confunda com a declarao! Um tal vetor seria declarado com uma instruo do tipo
int V[5], mas o elemento V[5] no existiria!
realmente necessrio tomar bastante cuidado com a numerao dos elementos. Se
voc tentar acessar um elemento invlido, como V[5] ou V[100] (para este caso com
5 elementos), o compilador no o avisar disso, e erros estranhos comearo a ocorrer
no seu programa o mesmo tipo de erro que pode ocorrer com os ponteiros.
O tamanho deve ser um valor constante (no pode depender de valores de variveis).
Ou seja, voc no pode perguntar ao usurio o tamanho desejado, guardar numa va-
rivel n e depois declarar um vetor do tipo int V[n]. Em outras palavras, o tamanho
do vetor deve ser um valor que possa ser estabelecido na hora da compilao do pro-
grama.
Por isso, quando for necessrio ler uma quantidade de dados que s estipulada na
execuo do programa, a princpio teremos de estabelecer um teto no nmero de dados
a serem lidos, usando um vetor de tamanho xo. Caso o teto no seja atingido, algumas
entradas do vetor caro sem ser utilizadas.
possvel, sim, criar vetores cujo tamanho s conhecido a posteriori; no padro
C99 possvel fazer declaraes do tipo int V[n] com algumas restries. H outro
recurso, de certa maneira mais exvel, que permite criar vetores cujo tamanho s
conhecido na execuo do programa: a alocao dinmica de memria, que ser vista
no Captulo 7.
Alm de constante, o tamanho dos vetores imutvel, ou seja, se eu declarei um vetor
de 5 entradas, eu no posso aument-lo para que caibam 10 entradas. Se eu quero que
caibam 10 entradas, eu preciso reservar espao para 10 entradas logo no comeo.
(Novamente, a alocao dinmica de memria salva a ptria nesse aspecto; no captulo
7, voc ver o que possvel fazer quanto a isso.)
int lista[3];
scanf("%d", &lista[0]);
lista[1] = 37;
lista[2] = lista[1] - 3*lista[0];
#include <stdio.h>
Vetores 71
int main()
{
int lista[100];
int i, 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).
int lista[100];
lista[0] = 9;
lista[1] = 35;
.
.
.
lista[99] = -1;
72 Captulo 5. Ponteiros e vetores
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:
int lista[] = {9, 35, -17, 9, -4, 29, -2, 10, -1};
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):
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 ele-
mentos desejados:
int lista[100] = {9, 35, -17, 9, -4, 29, -2, 10, -1, 47};
funcao({2, 3, 4, 5});
detalhe: no devemos fornecer o tamanho do vetor os colchetes devem ser deixados so-
zinhos, sem nada no meio. A funo a seguir recebe um vetor de inteiros como parmetro
e imprime seu primeiro elemento:
Para mandar um vetor como parmetro de uma funo, voc simplesmente deve escrever
o nome dele, sem colchetes ou qualquer outro smbolo:
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:
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:
int vetor[20];
le_vetor(vetor, 20);
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 Captulo 5. Ponteiros e vetores
int v[10];
int *p;
p = v; /* faz o ponteiro apontar para o comeo do vetor */
/* (equivale a: p = &v[0]) */
p++; /* aponta o ponteiro para o segundo elemento */
p = p+5; /* faz mais um salto e aponta para o 7 elemento */
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 apon-
tados. 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;
5.7 Strings
Uma das principais utilidades do tipo char usar vetores para formar sequncias de ca-
racteres, 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, codi-
cados 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 veto-
res:
char string[] = {'O', 'l', '', '\0'};
Certamente no nada prtico escrever assim! Felizmente essa notao pode ser bas-
tante 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;
int n = 0;
char string[] = "Bom dia!";
char *p;
char mensagem[20];
strcpy(mensagem , "Bom dia!");
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:
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):
char mensagem[20];
strncpy(mensagem , "Copiando uma mensagem muito longa", 20);
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 m-
ximo 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:
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 ve-
tores, que so ponteiros, uma comparao do tipo s1 == s2 compara os endereos em que
esto guardadas as strings. Obviamente essa comparao s verdadeira quando compa-
ramos 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.
Mais sobre entrada e sada 79
disponveis at encher esse espao. Se no houver dados para preencher todo o espao, ter-
minamos 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 proce-
dimento 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 Captulo 5. Ponteiros e vetores
Funo Descrio
strcpy(dest , origem) Copia todo o contedo de origem para dest.
strncpy(dest , origem, n) Copia no mximo n caracteres de origem para dest.
strlen(str) Devolve o tamanho da string str.
strcmp(s1, s2) Compara as strings s1 e s2, e devolve 0 caso elas
sejam iguais.
fgets(dest , n, stdin) L uma linha da entrada padro, com no mximo
n 1 caracteres, armazenando em dest.
Algoritmos
6
81
Mais ponteiros
7
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 . .
a0,n1
.
a1,0 .
a1,1 . .
a1,n1
.
...
.
.
am1,0 .
am1,1 . .
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 Captulo 7. Mais ponteiros
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.
ponteiro = malloc(num_bytes);
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:
Voc sempre deve usar o operador sizeof em vez de assumir, por exemplo, que o tama-
nho 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 Captulo 7. Mais ponteiros
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 (lite-
ralmente, ponteiros pendentes), que vou preferir traduzir como ponteiros desapropriados.
Assim, ao liberar um bloco de memria, o ponteiro que apontava pra ele torna-se inv-
lido, 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 expres-
so 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 desapro-
priados nem sempre podem ser detectados.
ponteiro = malloc(tamanho);
if (ponteiro == NULL)
{
printf("Socorro! No foi possvel alocar memria!\n");
/* executar alguma ao para sair deste imbrglio */
}
Sendo essa uma tarefa comum, que ser executada vrias vezes no programa, til es-
crever uma funo que cuida desse trabalho repetitivo:
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 imedia-
tamente, como se tivesse terminado a funo main. O argumento EXIT_FAILURE (uma cons-
tante denida pelo sistema) indica ao sistema operacional que houve algum erro na execu-
o 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 ne-
cessrio nos preocupar com a parte do programa em que estvamos.
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;
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);
tipo (*ponteiro)();
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, pro-
cedemos 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 pon-
teiro 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.
Novamente, tudo isso necessrio porque o compilador precisa saber que parmetros a funo est esperando,
para poder fazer as manipulaes corretas dos dados na memria.
90 Captulo 7. Mais ponteiros
char *descricao;
int quantidade;
double preco_unitario;
double desconto;
double preco_total;
struct {
char *descricao;
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:
calcular_preco_total(produtoA.preco_unitario , produtoA.-
quantidade , produtoA.desconto , &produtoA.preco_total);
Resumo
Declarao de um tipo de registro
struct nome_do_tipo {
/* declaraes dos membros */
};
Declarao de um registro
variavel.nome_do_membro
95
Tabelas de referncia
B
97
Referncias Bibliogr cas
99