Você está na página 1de 140

Linguagem C - Notas de Aula

Profa . Carmem Hara e Prof. Wagner Zola


Reviso: Prof. Armando Luiz N. Delgado

Maio 2008

Parte I

Programao Bsica em C
Estas notas de aula apresentam os conceitos bsicos da Linguagem C e se prope a abordar apenas o que
importante para a compreenso bsica de programas de computadores.

Programas C

Um programa C consiste de uma ou mais partes chamadas funes. Um programa em C consiste de pelo
menos uma funo chamada main. Esta funo marca o ponto de incio de execuo do programa.
Programas C tem a seguinte estrutura geral:
#include <stdio.h>
definio de constantes
funes
int main()
{
declarao de variveis
....
sentenas
....
}

1.1

Sentenas: simples e compostas

Cada instruo em C chamada de sentena. Sentenas simples so terminadas com um ponto e vrgula.
Usando chaves, podemos agrupar sentenas em blocos, chamados de sentenas compostas.
Exemplos de sentenas incluem:
Simples:
x = 3;
Composta:
{
i = 3;
printf("%d\n", i);
i = i + 1;
}
O corpo da funo main() um exemplo de sentena composta.

1.2

Variveis em C

Uma varivel uma informao qe voc pode usar dentro de um programa C . Esta informao est associada
com um lugar especfico da memria (isso feito pelo compilador). O nome da varivel e o endereo da
memria onde a informao est armazenada esto associados. O nome e o endereo no mudam. Mas, o
valor da informao pode mudar (o valor do que est dentro da caixa pode mudar, embora o tipo seja sempre
o mesmo). Cada varivel tem um tipo associado. Alguns tipos de variveis que discutiremos incluem int,
char e float.
Cada varivel usa uma determinada quantidade de armazenamento em memria. A maneira como sabemos quantos bytes so utilizados pelo tipo da varivel. Variveis do mesmo tipo utilizam o mesmo nmero
de bytes, no interessando qual o valor que a varivel armazena.
2

Um dos tipos utilizados para armazanar nmeros o int. Ele usado para armazenar nmeros inteiros.
Outro tipo o char, usado para armazenar caracteres. Um caracter um smbolo (uma letra do alfabeto,
um dgito, um smbolo de pontuao, etc). Um char armazenado em 1 byte de memria. Cada caracter
associado com um valor entre 0 e 255. O compilador C faz a traduo para voc, portanto voc no precisa
saber estes nmeros. Em C , um caracter representado entre apstrofes (). Por exemplo, C, a, 5, $.
Note que 5 um caracter, e no o inteiro 5.

A figura acima mostra como um int e um char so armazenados na memria.


Outro tipo existente o float, usado para armazenar nmeros reais (nmeros com o ponto decimal). Este
nmeros so armazenados em duas partes: a mantissa e o expoente. Eles so armazenados de uma maneira
que se assemelha a notao exponencial. Por exemplo, o nmero 6.023 1023 escrito como 6.023e23.
Neste caso, a mantissa 6.023 e o expoente 23.
Estes nmeros so armazenados de uma forma padro, tal que a mantissa tem apenas um dgito para
a esquerda do ponto decimal. Desta forma, 3634.1 escrito como 3.6341e3, e 0.0000341 escrito 3.41e5. Note tambm que a preciso limitada pela mantissa. Somente os 6 dgitos mais significativos so
armazenados. Em Dev-C++ um float ocupa 4 bytes de memria. H muitos outros tipos (short, long,
double), que sero descritos no futuro.

1.3

Definio de Varivel em C

Se voc usa variveis no programa, voc deve defini-las. Isto envolve especificar o tipo da varivel e o seu
nome. As regras para formar nomes de variveis em C so:
qualquer sequncia de letras, digitos, e _, MAS DEVE COMEAR com uma letra ou com _.
Por exemplo, hora_inicio, tempo, var1 so nomes de variveis vlidos, enquanto 3horas, total$ e
azul-claro no so nomes vlidos;
Maisculas 6= Minsculas;
No so permitidos nomes ou palavras reservadas da linguagem.
sempre uma boa idia ter certas regras (para voc mesmo) para nomear variveis para tornar o programa mais legvel:
D nomes significativos as variveis (mas no muito longos);
Use nomes de variveis do tipo i, j, k somente para variveis tipo contadores;
Pode-se usar letras maisculas ou _ para juntar palavras. Por exemplo, horaInicio ou hora_inicio.
Use o que voc preferir, mas SEJA CONSISTENTE em sua escolha.
3

auto
default
float
main
static
void

break
do
for
register
struct
volatile

case
double
goto
return
switch
while

char
else
if
short
typedef

const
enum
int
signed
union

continue
extern
long
sizeof
unsigned

Tabela 1: Palavras Reservadas da Linguagem C


Os tipos bsicos de dados existentes em C so:
Tipo de Dado

Bits

Faixa de Valores

char
int
float
double

8
16
32
64

-128 a 127
-32768 a 32767
7 dgitos significativos
15 dgitos significativos

Abaixo est um exemplo de um programa com diversas definies de variveis:


int main()
{
int pera;
char qualidade;
float peso;
pera = 3;
qualidade = A;
peso = 0.653;
...
}
Quando variveis so definidas, elas no possuem valores ainda. Ns damos valores s variveis usando
o operador de atribuio (=). Variveis tambm podem ser inicializadas para conter valores quando so
definidas. Usando esta forma, o program acima ficaria:
int main()
{
int pera = 3;
char qualidade = A;
float peso = 0.653;
...
}
Para resumir: quando um programa executado, uma varivel associada com:
um tipo: diz quantos bytes a varivel ocupa, e como ela deve ser interpretada.
um nome: um identificador.
um endereo: o endereo do byte menos significativo do local da memria associado a varivel.
4

um valor: o contedo real dos bytes associados com a varivel; o valor da varivel depende do tipo
da varivel; a definio da varivel no d valor a varivel; o valor dado pelo operador de atribuio,
ou usando a funo scanf(). Ns veremos mais tarde que a funo scanf() atribui a uma varivel um
valor digitado no teclado.
Em C , nomes de variveis devem ser declarados antes de serem usados. Se no for declarado, ocorrer
um erro de compilao.
Devem ser dados valores s variveis antes que sejam utilizadas. Se voc tentar utilizar a varivel
antes de especificar o seu valor, voc obter lixo (o que quer que esteja armazenado no endereo da
varivel na memria quando o programa comea sua execuo), culminando com falha na execuo
do programa.

1.4

Constantes

Em C , alm de variveis, ns podemos usar tambm nmeros ou caracteres cujos valores no mudam. Eles
so chamados de constantes. Constantes no so associados a lugares na memria.
Assim como variveis, constantes tambm tm tipos. Uma constante pode ser do tipo int, char, etc.
Voc nao tem que declarar constantes, e pode utiliz-las diretamente (o compilador reconhece o tipo pela
maneira que so escritos). Por exemplo, 2 do tipo int, e 2.0 do tipo double. Por conveno, todas as
constantes reais so do tipo double.

1.5

Caracteres Constantes

Um constante caracter escrita entre apstrofes, como em A. Todas as letras, nmeros e smbolos que
podem ser impressos so escritos desta forma em C . s vezes precisamos de caracteres que no podem
ser impressos, por exemplo, o caracter de nova linha, que no tem uma tecla especfica no teclado. Neste
caso, usa-se caracteres de escape. Tais caracteres so escritos no somente como um smbolo entre apstrofes, mas como um sequncia de caracteres entre apstrofes. Por exemplo, \n o caracter para nova
linha (uma sequncia que inicia com a barra invertida chamada de sequncia de escape). Se quisermos
representar o caracter de barra invertida, temos que escrever \\. Note que \n o caracter de nova linha
- embora use-se dois smbolos para represent-lo. A barra invertida chamada de escape. Ele diz ao compilador que o n que segue no a letra n, mas que a sequncia completa de caracteres deve ser interpretada
como o caracter de nova linha.
Cada caracter constante tem um valor inteiro igual ao seu valor numrico do seu cdigo ASCII. Por
exemplo, considere a constante A, que tem cdigo ASCII 65, e B que tem cdigo 66. Ns podemos usar
a expresso A + 1. O resultado o valor 66. E se o tipo da expresso resultante for char, ento o resultado
da expresso B.

1.6

Entrada e Sada

Se quisermos que um programa C mostre alguns resultados, ou se quisermos que o programa pea ao usurio que entre com alguma informao, ns podemos usar as funes existentes em C chamadas printf() e
scanf(). Se voc quiser usar estas funes em seu programa, voce deve incluir a seguinte linha no incio do
seu cdigo fonte:
#include <stdio.h>
Isto faz com que o arquivo header chamado stdio.h seja includo no seu arquivo fonte. Este arquivo
contm prottipos das funes print() e scanf(). Ele declara ao compilador o nome das funes e algumas
informaes adicionais necessrias para que as instrues sejam executadas corretamente.

1.7

Exibindo informaes na tela: printf()

printf() pode ser utilizado para imprimir mensagens e valores em uma variedade de formatos. Por enquanto,
printf() melhor descrito atravs de exemplos.
printf(Al todo mundon);
Imprimir Al todo mundo na tela do computador.
Para dizer funo printf exatamente o que fazer, ns devemos especificar o que ser impresso. Ns
devemos dar a funo o que chamamos de argumentos. No exemplo acima, Alo todo mundo um
argumento para a funo printf().
O primeiro argumento do printf() sempre um string (uma srie de caracteres entre aspas (")).
Ns tambm podemos colocar caracteres de escape no string para imprimir caracteres especiais. Por
exemplo, colocando \n no string causa que o restante do string seja impresso na linha seguinte. Outros
caracteres de escape sero apresentados no futuro.
Se quisermos imprimir o valor de expresses variveis, argumentos adicionais so necessrios. Dizemos ao printf() como mostrar valores de expresses usando especificadores de formato. Podemos colocar
%c, %d, %f (ou outros especificadores de formato listados no texto) dentro do primeiro argumento para
especificar o que queremos dar display. Ns ento passamos argumentos adicionais que ser referem aos
especificadores de formato (na ordem em que eles ocorrem). Este argumentos podem ser constantes ou
variveis, ou alguma expresso mais complicada. O que quer que eles sejam, eles devem ser avaliados e os
valores obtidos e impressos de acordo com os especificadores de formato. Considere o seguinte programa:
#include <stdio.h>
#define PRECO 1.99
int main()
{
int pera = 3;
char qualidade = A;
float peso = 2.5;
printf(Existem %d peras de qualidade %c , pera, qualidade);
printf(pesando %f quilos.\n, peso);
printf(O preco por quilo e %f, total e %f\n, PRECO, peso * PRECO);
}
A sada do programa ser:
Existem 3 peras de qualidade A pesando 2.500000 quilos.
O preco por quilo e 1.990000, total e 4.975000
A linha #define PRECO 1.99 no incio do programa define uma macro. Ou seja, definimos que
PRECO um sinnimo para 1.99 e, portanto, toda ocorrncia de PRECO no programa substitudo por
1.99 antes que ele seja compilado.
Ns tambm podemos especificar o tamanho utilizado para impresso da seguinte forma:
%6d inteiro, com pelo tamanho pelo menos 6
%6f ponto flutuante, com tamanho pelo menos 6
%.3f ponto flutuante, com 3 digitos depois do ponto decimal
%6.3fponto flutuante, com tamanho pelo menos 6 e 3 digitos depois do ponto decimal
%6.0fponto flutuante, com pelo menos tamanho 6 e nenhum digito depois do ponto decimal.
6

Note que a especificao de tamanho simplesmente determina o tamanho mnimo. Se o nmero no


couber no tamanho especificado, o nmero completo ser mostrado.
Quando utilizar a funo printf() tenha cuidado para especificar o tipo correto dos argumentos. Se o tipo
do argumento no for correto, o compilador Dev-C++ no acusar erro, e um valor incorreto ser mostrado.
Por exemplo, no programa abaixo que est incorreto:
#include <stdio.h>
int main()
{
printf(Exemplo errado: %d\n, 3.14159);
}
O resultado do programa ser alguma coisa como:
Exemplo errado: -31147

1.8

Lendo informao: scanf()

scanf() pode ser usado para ler valores digitados no teclado. Estes valores so lidos de acordo com especificadores de converso, que so especificados pelo programador como argumentos do scanf().
Considere o seguinte programa:
#include <stdio.h>
int main()
{
int idade;
printf(Entre sua idade: );
scanf(%d, &idade);
printf(Voce tem %d anos\n, idade);
}
Este programa mostrar no monitor: Entre sua idade: e aguardar que um nmero seja digitado e a
tecla ENTER. Depois disso, a varivel idade conter o valor digitado pelo usurio.
Assim como com o printf(), o primeiro argumento o especificador de formato. Os prximos argumentos correspondem a o que est sendo especificado pelo primeiro argumento.
Note o & precedendo a varivel idade. Simplesmente lembre-se que voc geralmente precisar colocar
um & precedendo nomes de variveis em um scanf(). Voc sempre precisar us-lo antes de variveis do
tipo primrio como os discutidos at este momento (int, char, float, e suas verses long e unsigned).
Mais de um valor pode ser lido por um mesmo scanf(). Considere o seguinte exemplo:
#include <stdio.h>
int main()
{
int dia, mes, ano;
printf(Entre com a data do seu aniversario (dd mm aa): );
scanf(%d %d %d, &dia, &mes, &ano);
7

printf(Voce nasceu em %d/%d/%d\n, dia, mes, ano);


}
Este exemplo funciona exatamente como o exemplo anterior. Um nico scanf() l os 3 nmeros quando
estes nmeros so separados por espaos (espaos em branco, tabulao, novas linhas). Ento voc pode
teclar ENTER depois de cada nmero, ou colocar espaos ou tabulaes entre os nmeros. Os espaos so
ignorados pelo scanf(). Os brancos na especificao de formato do scanf(), %d %d %d so simplesmente
para facilitar a leitura do programa, e no tem nada a ver com os espaos ignorados pelo scanf(). Se tivssemos escrito %d%d%d, o scanf() funcionaria da mesma forma. Os espaos em branco simplesmente so
necessrios para saber quando termina um nmero e comea o outro.
Porm se o scanf() estiver lendo caracteres (%c), os espaos no so ignorados, j que espaos so
caracteres vlidos na linguagem. Por exemplo, o cdigo ASCII para espao em branco e 32.

1.9

Algoritmo X Programa
ALGORITMO PERIMETRO_AREA
/* Calcula o permetro e a area de uma circunferencia
de raio R (fornecido pelo usuario) */
/* Definir variaveis */
int Raio;
float Perim, Area, PI;
PI = 3.14159;
/* Obter Raio da circunferencia */
Escreva("Entre com o valor do raio:");
Leia(Raio);
/* Calcular Perimetro do Circulo */
Perim = 2 * PI * Raio;
/* Calcular Area da Circunferencia */
Area = PI * Raio ** 2;
/* Exibir Resultados */
Escreva("O perimetro da circunferencia de raio", Raio, "eh", Perim);
Escreva("e a area eh ",Area);
/* Terminar Programa */
FIM_ALGORITMO PERIMETRO_AREA

Programa em C
/* programa que calcula o permetro e a rea de uma
circunferncia de raio R (fornecido pelo usurio) */
#include <stdio.h> /* inclui diretivas de
entrada-sada */
#include <math.h> /* inclui diretivas das funes matemticas */
8

#define

PI

3.14159

int main()
{
/* Definir variaveis */
int Raio;
float Perim, Area;
/* Obter Raio da circunferencia */
printf("Entre com o valor do raio: ");
scanf("%d", &Raio);
/* Calcular Perimetro do Circulo */
Perim = 2 * PI * Raio;
/* Calcular Area da Circunferencia */
Area = PI * pow(Raio, 2);
/* Exibir Resultados */
printf("O perimetro da circunferencia de raio %d eh %.2f
printf("e a area eh %.2f", Area);
}

\n", Raio, Peri

Operaes Aritmticas e Expresses.


Operaes Relacionais.

2.1

Operaes Aritmticas

Em C , ns podemos executar operaes aritmticas usando variveis e constantes. Algumas operaes mais
comuns so:
+ adio
- subtrao
* multiplicao
/ diviso
% resto (mdulo)
Estas operaes podem ser usadas como mostram os exemplos abaixo, assumindo que as variveis necessrias j esto declaradas:
celsius = (fahrenheit - 32) * 5.0 / 9.0;
forca =

massa * aceleracao;

i = i + 1;
2.1.1

Precedncia de Operadores

Em C , assim como em lgebra, h uma ordem de precedncia de operadores.


Assim, em (2 + x)(3x2 + 1), expresses em parntesis so avaliadas primeiro, seguidos por exponenciao, multiplicao, diviso, adio e subtrao.
Da mesma forma, em C , expresses entre parntesis so executadas primeiro, seguidas de *, / and %
(que tem todos a mesma precedncia), seguido de + and - (ambos com a mesma precedncia).
Quando operaes adjacentes tm a mesma precedncia, elas so associadas da esquerda para a direita.
Assim, a * b / c * d % e o mesmo que ((((a * b) / c) * d) % e).
2.1.2

A Operao de Resto (%)

Esta operao usada quando queremos encontrar o resto da diviso de dois inteiros. Por exemplo, 22
dividido por 5 4, com resto 2 (4 5 + 2 = 22).
Em C , a expresso 22 % 5 ter valor 2.
Note que % s pode ser utilizados entre dois inteiros. Usando ele com um operando do tipo float
causa um erro de compilao (como em 22.3 % 5).
2.1.3

Expresses e Variveis

Expresses aritmticas podem ser usadas na maior parte dos lugares em que uma varivel pode ser usada.
O exemplo seguinte vlido:
int raio = 3 * 5 + 1;
printf("circunferencia = %f\n", 2 * 3.14 * raio);
10

Exemplos de lugares onde uma expresso aritmtica NO pode ser usada incluem:
int yucky + 2 = 5;
scanf("%d", &(oops * 5))
Este exemplo ilegal e causar erro de compilao.

2.2

Operadores Relacionais
Em C , h operadores que podem ser usados para comparar expresses: os operadores relacionais.
H seis operadores relacionais em C :

< menor que


> maior que
<= menor ou igual que ()
>= maior ou igual que ()
== igual a
!= no igual a (6=)
Os resultados deste operadores 0 (correspondendo a falso), ou 1 (correspondendo a verdadeiro). Valores como esses so chamados valores booleanos. Algumas linguagens de programao como Pascal tem
um tipo de varivel distinto para valores booleanos. Este no o caso do C , onde valores booleanos so
armazenados como variveis numricas tais como o int.
Considere o seguinte programa:
int main()
{
int idade;
idade = 17;
printf("Pode tirar carteira de motorista? %d\n", idade >= 18);
idade = 35;
printf("Pode tirar carteira de motorista? %d\n", idade >= 18);
}
A sada deste programa ser:
Pode tirar carteira de motorista? 0
Pode tirar carteira de motorista? 1
Na primeira linha, idade 17. Logo, 17 >= 18 falso, que 0.
Depois disso, idade 35. Logo, 35 >= 18 verdadeiro, que 1.
Note tambm que o operador de igualdade escrito com sinais de igual duplo, ==, no =. Tenha
cuidado com esta diferena, j que colocar = no lugar de == no um erro sinttico (no gera erro de
compilao), e no significa o que voc espera.

11

2.2.1

Precedncia dos operadores relacionais

Operadores aritmticos tem precedncia maior que os operadores relacionais. Por exemplo, a expresso
3 + 5 < 6 * 2 o mesmo que (3 + 5) < (6 * 2).
Se por alguma razo voc quer que o resultado do uma operao relacional em uma expresso aritmtica,
necessrio usar parntesis. Por exemplo, a expresso score + (score == 0) ser sempre igual ao
valor de score, exceto quando o valor de score seja 0. Neste caso, o valor da expresso 1 (porque
(score == 0) igual a 1).
Uma observao sobre valores booleanos embora voc possa assumir que o valor de uma operao
relacional 0 ou 1 em C , qualquer valor diferente de zero considerado verdadeiro. Falaremos sobre
isso mais tarde durante o curso.

2.3

Reviso de Expresses:

O que impresso pelos dois programas abaixo?


#include <stdio.h>
int main() {
int score = 5;
printf(%d,
printf(%d,
printf(%f,
printf(%c,
printf(%d,

5 + 10 * 5 % 6);
10 / 4);
10.0 / 4.0);
A + 1);
score + (score == 0));

==>
==>
==>
==>
==>

7
2
2.5
B
5

}
#include <stdio.h>
int main() {
int n1, n2, n3;
printf(Entre com um numero inteiro: );
scanf(%d, &n1);
n1 += n1 * 10;
n2 = n1 / 5;
n3 = n2 % 5 * 7;
n2 *= n3-- % 4;
printf(%d %d %d, n2, n3, n2 != n3 + 21);
}
Como a seguinte expresso completamente parentizada ?
a * b / c + 30 >= 45 + d * 3 ++e == 10

2.4

Exemplo de programas

Exemplo 1: escreva um programa que leia um nmero inteiro e imprima 0 se o nmero for par e 1 se o
nmero for mpar.
12

#include <stdio.h>
int main() {
int numero;
printf(Entre com um numero inteiro: );
scanf(%d, &numero);
printf(\nPar? %d\n, numero % 2 );
}
Exemplo 2: escreva um programa que leia 3 nmeros inteiros e calcule a soma, mdia, e produto.
#include <stdio.h>
int main() {
int n1, n2, n3;
int soma;
printf( "Entre com 3 numeros inteiros: ");
scanf( "%d %d %d",&n1, &n2, &n3);
soma = n1 + n2 + n3;
printf( "Soma = %d\n", soma );
printf( "Media = %8.2f\n", soma / 3.0 );
printf( "Produto = %d\n", n1 * n2 * n3 );
}

2.5

Precedncia e associatividade de operadores

Operador

Associatividade

()
++
*
+
<
==
=

esquerda para direita


direita para esquerda
esquerda para direita
esquerda para direita
esquerda para direita
esquerda para direita
direita para esquerda

-- & (unrios)
/ %
<= > >=
!=
+= -= *= /=

%=

13

Expresses como valores

Em C , todas as expresses so avaliadas. O resultado da avaliao um valor e pode ser usado em quaisquer
lugares.

3.1

Expresses aritmticas, relacionais e lgicas

Como voc j sabe, expresses usando operadores aritmticos, relacionais e lgicos1 so avaliados. O valor
resultante um nmero. Para os operadores relacionais e lgicos, este nmero pode ser 0 (que significa
falso) ou 1 (que significa verdadeiro). Por exemplo:
3 + 5 * 4 % (2 + 8)
tem valor 3;
3 < 5
tem valor 1;
x + 1
tem valor igual ao valor
da varivel x mais um;
(x < 1) || (x > 4)
tem valor 1 quando o valor da varivel x fora do
intervalo [1,4], e 0 quando
x est dentro do intervalo.

3.2

Expresses envolvendo o operador de atribuio (=)

O formato do operador de atribuio :


lvalue = expressao

(1)

Um lvalue (do ingls left-hand-side value - valor a esquerda) um valor que se refere a um endereo
na memria do computador. At agora, o nico lvalue vlido visto no curso o nome de uma varivel. A
maneira que a atribuio funciona a seguinte: a expresso do lado direito avaliada, e o valor copiado
para o endereo da memria associada ao lvalue. O tipo do objeto do lvalue determina como o valor da
expressao armazenada na memria.
Expresses de atribuio, assim como expresses, tm valor. O valor de uma expresso de atribuio
dado pelo valor da expresso do lado direito do =. Por exemplo:
x = 3
tem valor 3;
x = y+1
tem o valor da expresso
y+1.
Como consequncia do fato que atribuies serem expresses que so associadas da direita para esquerda, podemos escrever sentenas como:
i = j = k = 0;
Que, usando parnteses, equivalente a i = (j = (k = 0)). Ou seja, primeiro o valor 0 atribudo a k, o valor de k = 0 (que zero) atribudo a j e o valor de j = (k = 0) (que tambm zero)
atribudo a i.
Uma caracterstica muito peculiar de C que expresses de atribuio podem ser usados em qualquer
lugar que um valor pode ser usado. Porm voc deve saber que us-lo dentro de outros comandos produz
um efeito colateral que alterar o valor da varivel na memria. Portanto, a execuo de:
int quadrado, n = 2;
printf("Quadrado de %d eh menor que 50? %d \n", n, (quadrado = n * n) < 50);
1

Operadores lgicos && e || sero vistos na prxima aula.

14

causa no apenas que o valor 4 seja impresso, como a avaliao da expresso relacional dentro do printf()
faz com que o nmero 4 seja copiado para o endereo de memria associado com a varivel quadrado.
Note que necessrio usar parnteses em quadrado = n * n j que = tem menor precedncia que o
operador relacional <.
Agora compare o exemplo anterior com o prximo, no qual o valor 4 impresso, mas sem nenhum
efeito colateral:
int quadrado, n = 2;
printf("Quadrado de %d eh menor que 50? %d \n", n, n * n < 50);
Note que agora no h necessidade de parnteses para a expresso n * n porque * tem maior precedncia que o operador relacional <.
3.2.1

Operadores de atribuio aritmtica

Como foi discutido em classe, estes comandos de atribuio funcionam de forma similar que o comando de
atribuio. O lado esquerdo da expresso deve ser um lvalue. O valor da expresso de atribuio aritmtica
igual ao valor da sentena de atribuio correspondente. Por exemplo:
x += 3
igual a x = x + 3 e tem valor x + 3
x *= y + 1 igual a x = x * (y + 1) e tem valor x * (y + 1)

15

Ordem sequencial de execuo de sentenas


o comando condicional: if and if - else

A execuo de um programa C comea com a funo main(). Em todos os exemplos que vimos at este
momento, sentenas so executadas sequencialmente. A ordem sequencial de execuo de senteas pode
ser alterada se certas condies forem satisfeitas durante a execuo do programa. Isto chamado desvio
condicional.
Todas as linguagens de programao oferecem comandos para o desvio condicional. O mais simples a
sentea if. Em C , ele tem o formato:
if (expressao)
sentenca
Quando uma sentena if encontrada em um programa,
1. O teste na expressao em parnteses avaliada.
2. Se o valor da expresso de teste for DIFERENTE de zero, a sentena que segue a expresso de teste
executada.

Figura 1: O comando if
Considere o seguinte exemplo que converte uma frao digitada pelo usurio (numerador e denominador) em decimal e imprime o resultado:
#include <stdio.h>
main(void)
{
int a, b;
printf("Entre com uma fracao (numerador and denominador): ");
scanf("%d %d", &a, &b);
printf("A fracao em decimal eh %f\n", 1.0 * a / b);
}

No exemplo acima, escrevemos 1.0 * a / b, j que a e b so do tipo int, e portanto a / b uma


diviso de inteiros e a parte fracional do resultado seria truncado, o que certamente no o que desejamos.
16

Voce v algo errado neste programa ? Uma coisa a ser notada que se o usurio digitar um denominador
igual a 0, ns teremos um erro de execuo, j que o programa tentaria executar uma diviso por zero. O
que necessrio fazer testar se o denominador igual a zero e dividir s no caso dele for diferente de zero.
Poderamos reescrever o programa acima da seguinte forma:
Exemplo 1:
#include <stdio.h>
main(void)
{
int a, b;
printf("Entre com uma fracao (numerador e denominador): ");
scanf("%d %d", &a, &b);
if (b != 0)
printf("A fracao em decimal eh %f\n", 1.0 * a / b);
}

Exemplo 2:
o segundo.

Programa que l dois nmeros e ordena o par caso o primeiro nmero digitado for maior que

#include <stdio.h>
main() {
int num1, num2, aux;
printf("Entre com dois numeros inteiros: ");
scanf("%d %d", &num1, &num2);
if (num1 > num2) {
aux = num1;
num1 = num2;
num2 = aux;
printf("Trocou \n");
}
printf("Os numeros ordenados: %d %d\n", num1, num2);
}
O programa do Exemplo 1 acima ficaria ainda melhor se ao invs de no fazer nada no caso do denominador ser zero, imprimirmos uma mensagem de erro ao usurio, explicando o que h de errado.
A sentena em C que permite fazermos isso o if - else. O formato do if-else :
if (expressao)
sentenca1
else
sentenca2
17

Figura 2: O comando if-else


Primeiro, a expressao (que usualmente chamamos de condio) avaliada. Caso a condio seja verdadeira (o que equivalente a dizer que o valor diferente de zero), entao a sentenca1 executada. Caso
contrrio, a sentenca2 executada.
Note que uma sentena pode ser simples ou composta. Se voc quiser agrupar diversas sentenas para
serem executadas, voc pode coloc-las entre chaves ({ e }).
Por hora, vamos continuar com nosso exemplo simples e torn-lo mais explicativo:
Exemplo 3:
#include <stdio.h>
main(void)
{
int a, b;
printf("Entre com uma fracao (numerador and denominador): ");
scanf("%d %d", &a, &b);
if (b != 0)
printf("A fracao decimal e %f\n", 1.0 * a / b);
else
printf("Erro: denominador zero!\n");
}
Exemplo 4: Considere agora o exemplo j visto que pede que um usurio entre com um nmero e verifique
se o nmero par. Porm agora, queremos que o programa imprima o numero e par ou o numero e
impar.
#include <stdio.h>
main(void)
{
int num;
18

/* obtem um numero do usuario */


printf("Entre com um inteiro: ");
scanf("%d", &num);
/* imprime uma mensagem dizendo se o numero e par ou impar */
if (num % 2 == 0)
printf("O numero e par.\n");
else
printf("O numero e impar.\n");
}

4.1

Um erro comum

muito frequente utilizar o operador relacional == em expresses condicionais da sentena if. Por exemplo:
int

saldo = 2000;

if (saldo == 1)
printf("Voce esta quebrado! \n");
else
printf("Seu saldo e %d\n", saldo);
Como a sentena saldo = 2000 inicializa o valor da varivel saldo com 2000, a expresso
saldo == 1 tem valor 0. Portanto, a sentea que segue o else ser executada, e a mensagem
Seu saldo e 2000

ser impressa.
Agora, suponha que, devido a um erro, voc tenha colocado = ao invs de ==:
int saldo = 2000;
if (saldo = 1)
printf("Voce esta quebrado! \n");
else
printf("Seu saldo e %d\n", saldo);
Agora, a expresso saldo = 1 tem valor 1. Portanto, a sentena que segue o if ser executada, e
a mensagem
Voce esta quebrado!
ser impressa. Alm disso, a atribuio causar um efeito colateral, e alterar o valor de saldo para 1.
Tal uso do operador de atribuio no ilegal, e no ser detectado pelo compilador como erro. Portanto,
tome cuidado com o uso de atribuio no lugar de igualdade. Tal erro muito comum, e no fcil de achar.
Como regra geral, NO utilize atribuies dentro de outras sentenas.

19

Aninhando senteas if e if-else

Como era de se esperar, possvel colocar uma sentena condicional dentro de outra. Por exemplo, se
quisermos imprimir uma mensagem apropriada caso um nmero seja positivo ou negativo e par ou mpar,
ns poderamos escrever o seguinte:
#include <stdio.h>
main(void)
{
int num;
/* Obtem um numero do usuario */
printf("Entre com um inteiro: ");
scanf("%d", &num);
/* Imprime uma mensagem dizendo se o numero e positivo ou
negativo, positivo ou negativo. */
if (num >= 0) {
if (num % 2 == 0)
printf("O numero e par e positivo\n");
else
printf("O numero e impar e positivo\n");
}
else {
if (num % 2 == 0)
printf("O numero e par e negativo\n");
else
printf("O numero e impar e negativo\n");
}
}

5.1

A ambigidade do else

O aninhamento de sentenas if-else sem usar chaves ({ e }) para delimitar o bloco de senteas a ser
executado pode trazer efeitos indesejados.
H uma regra simples para determinar qual if est associado a qual else.
Regra de associao: Um else est associado com a ltima ocorrncia do if sem else.
O exemplo seguinte est errado porque associa o else ao if "incorreto":
#include <stdio.h>
main(void)
{
int num;
/* Obtem um numero do usuario */
printf("Entre com o numero de peras: ");
scanf("%d", &num);
20

/* Imprime uma mensagem dizendo se o numero de peras e 0 ou 1


(*** isto esta errado !! ***) */
if (num != 0)
if (num == 1)
printf("Voce tem uma pera.\n");
else
printf("Voce nao tem nenhuma pera.\n");
}
Neste exemplo, o if tem o seguinte significado, segundo a regra de associao:
#include <stdio.h>
main(void)
{
int num;
/* Obtem um numero do usuario */
printf("Entre com o numero de peras: ");
scanf("%d", &num);
/* Como a sentenca if e vista pelo compilador */
if (num != 0)
if (num == 1)
printf("Voce tem uma pera.\n");
else
printf("Voce nao tem nenhuma pera.\n");
}
Para evitar este problema, chaves ({ e }) devem ser usadas para tirar a ambiguidade. O exemplo abaixo
mostra como as chaves podem ser inseridas para corrigir o programa acima.
#include <stdio.h>
main(void)
{
int num;
/* Obtem um numero do usuario */
printf("Entre com o numero de peras: ");
scanf("%d", &num);
/* Como corrigir o problema (este programa funciona) */
if (num != 0) {
if (num == 1)
printf("Voce tem uma pera.\n");
} else
printf("Voce nao tem nenhuma pera.\n");
}
Exerccio 1: Faa um programa que leia 3 nmeros e imprima o maior.
21

Operadores Lgicos

Todos os programas at agora consideraram if com condies de teste simples. Alguns exemplos de testes
simples: b != 0, contador <= 5. Estas expresses testam uma condio. Portanto, quando mais de
uma condio precisa ser testada, precisamos usar sentenas if e if-else aninhadas.
A linguagem C , assim como a maioria das linguagens de programao de alto nvel suportam operadores lgicos que podem ser usados para criar operaes lgicas mais complexas, combinando condies
simples. O valor de uma expresso lgica ou VERDADEIRO ou FALSO. Lembre que no h constantes
lgicas VERDADEIRO e FALSO em C ; em expresses lgicas 0 interpretado como FALSO, e qualquer
valor diferente de zero interpretado como VERDADEIRO.
Os operadores lgicos so
! NO lgico, operao de negao (operador unrio)
&& E lgico, conjuno (operador binrio)
|| OU lgico, disjuno (operador binrio).
Por exemplo, se quisermos testar se um nmero num positivo e par, e imprimir uma mensagem como
no exemplo anterior, podemos escrever:
if (num >= 0)
if (num % 2 == 0)
printf("Numero par nao negativo.\n");
Com os operadores lgicos isso pode ser simplificado:
if ((num>=0) && (num%2 == 0))
printf("Numero par nao negativo.\n");
A operao de negao, !, pode ser usado da seguinte forma:
!expresso lgica: O valor a negao lgica da expresso dada. Por exemplo:
!0
1
!1
0
Ns podemos usar o operador de negao lgica e escrever o exemplo acima como:
if (num>0 && !(num%2))
printf("Numero par nao negativo.\n");
Os dois operadores binrios operam sobre duas expresses lgicas e tem o valor 1 (verdadeiro) or 0
(falso). Os exemplos abaixo mostram o seu uso:
a==0 && b==0 (verdadeiro se ambos a == 0 e b == 0, portanto se a e b so 0)
a==0 || b==0 (verdadeiro se pelo menos uma das variveis a or b for 0)
Uma expresso usando && verdadeira somente se ambos os operadores forem verdadeiros (no
zero).
Uma expresso usando || falsa somente se ambos os operadores forem falsos (zero).
Verifique na Tabela 2 o resultado do uso de operadores lgicos:
A precedncia do operador de negao lgica a mais alta (no mesmo nvel que o - unrio). A
precedncia dos operadores lgicos binrios menor que a dos operadores relacionais, e mais alta que a
operao de atribuio. O && tem precedncia mais alta que o ||, e ambos associam da esquerda para a
direita (como os operadores aritmticos).
22

expr1
expr2
expr1 && expr2 expr1 || expr2
verdadeiro verdadeiro
verdadeiro
verdadeiro
verdadeiro
f also
f also
verdadeiro
f also
verdadeiro
f also
verdadeiro
f also
f also
f also
f also
Tabela 2: Resultado de uso de Operadores Lgicos
Como a precedncia dos operadores lgicos menor que a dos operadores relacionais, no necessrio
usar parnteses em expresses como:
x >= 3 && x <= 50
x == 1 || x == 2 || x == 3
A Tabela 3 mostra o quadro completo de precedncia de operadores aritmticos, relacionais e lgicos.
Operador
()
!
*
+
<
==
&&
||
=

Associatividade

- ++
/ %
<= >
!=

+=

-=

--

(cast) & (unrios)

>=

*=

/=

%=

esquerda para direita


direita para esquerda
esquerda para direita
esquerda para direita
esquerda para direita
esquerda para direita
esquerda para direita
esquerda para direita
direita para esquerda

Tabela 3: Precedncia e associatividade de operadores


No prximo exemplo, o programa verifica se as trs variveis lado1, lado2, e lado3, podem ser
lados de um tringulo reto. Ns usamos o fato que os trs valores devem ser positivos, e que o quadrado de
um dos lados deve ser igual a soma dos quadrados dos outros lados (Teorema de Pitgoras) para determinar
se o tringulo reto.

23

#include <stdio.h>
main() {
int lado1, lado2, lado3;
int s1, s2, s3;
printf(Entre com o tamanho dos lados do triangulo: );
scanf(%d %d %d, &lado1, &lado2, &lado3);
/*
s1
s2
s3

calcula o quadrado dos lados */


= lado1*lado1;
= lado2*lado2;
= lado3*lado3;

/* testa a condicao para um triangulo reto */


if ( lado1>0 && lado2>0 && lado3 > 0 ) {
if (s1==s2+s3 || s2==s1+s2 || s2==s1+s3) ) {
printf("Triangulo reto!\n");
}
else {
printf("Nao pode ser um triangulo!\n");
}
}
Na utilizao de expresses lgicas, as seguintes identidades so teis. Elas so chamadas de Lei de
DeMorgan:
!(x && y) equivalente a !x || !y
e
!(x || y) equivalente a !x && !y

Exemplos

7.1

IF - ELSE

Assuma as seguintes declaraoes de variveis:


int x = 4;
int y = 8;
O que impresso pelos seguintes programas ?
1.

if (y = 8)
if (x = 5)
printf( "a " );
else
printf( "b " );
printf( "c " );
printf( "d\n" );
==> a c d
24

2. mude = para ==
==> b c d
3. altere o programa acima para produzir a seguinte saida:
Assuma x = 5 e y = 8
(a) a
(b) a d
Assuma x = 5 e y = 7
(a) b c d

7.2

Operadores lgicos

O que impresso pelas seguintes sentenas?


1. Assuma x = 5 e y = 8.
if (x == 5 && y == 8)
printf( "a\n" );
else
printf( "b\n" );

==> a

2. Assuma x = 4 e y = 8.
if (x == 5 || y == 8)
printf( "a\n" );
else
printf( "b\n" );

if !(x == 5 || y == 8)
printf( "a\n" );
else
printf( "b\n" );

if !(x == 5 && y == 8)
printf( "a\n" );
else
printf( "b\n" );
3. Precedncia: !

==> a

/* equiv. (x != 5 && y != 8) */

==> b

/* equiv. (x != 5 || y != 8) */

==> a

> && > ||

if (x == 5 ||

y == 8 && z == 10)

equiv.
if (x == 5 || (y == 8 && z == 10))
25

A construo else-if

Embora ela no seja um tipo diferente de sentena, a seguinte construo bastante comum para programar
decises entre diversas alternativas:
if (expressao1 )
sentenca1
else if (expressao2 )
sentenca2
else if (expressao3 )
sentenca3
..
.
else if (expressaon1 )
sentencan1
else
sentencan
As expresses lgicas so avaliadas em ordem, comeando com a expressao1 . Se uma das expresses for verdadeira, a sentena associada ser executada. Se nenhuma for verdadeira, ento a sentena,
sentencan , do ltimo else ser executada como opo default. Se a opo default no for necessria,
ento a parte
else
sentencan
pode ser removida.

Exemplo 9: O seguinte exemplo mostra um else-if de trs opes. O programa l dois nmeros e diz
se eles so iguais ou se o primeiro nmero menor ou maior que o segundo.
#include <stdio.h>
main(void)
{
int num1, num2;
/* obtem 2 numeros do usuario */
printf("Entre um numero: ");
scanf("%d", &num1);
26

printf("Entre com um outro numero: ");


scanf("%d", &num2);
/* mostra a mensagem de comparacao */
if (num1 == num2)
printf("Os numeros sao iguais\n");
else if (num1 < num2)
printf("O primeiro numero e menor\n");
else
printf("O primeiro numero e maior\n");
}
No programa acima, se (num1 == num2) for verdadeiro, ento os nmeros so iguais. Seno,
verificado se (num1 < num2). Se esta condio for verdadeira, ento o primeiro nmero menor. Se
isso no for verdadeiro, ento a nica opo restante que o primeiro nmero maior.
Exemplo 10: Este programa l um nmero, um operador e um segundo nmero e realiza a operao
correspondente entre os operandos dados.
#include <stdio.h>
main(void)
{
float num1, num2;
char op;
/* obtem uma expressao do usuario */
printf("Entre com numero operador numero\n");
scanf("%f %c %f", &num1, &op, &num2);
/* mostra o resultado da operacao */
if (op == +)
printf(" = %.2f", num1 + num2);
else if (op == -)
printf(" = %.2f", num1 - num2);
else if (op == /)
printf(" = %.2f", num1 / num2);
else if (op == *)
printf(" = %.2f", num1 * num2);
else
printf(" Operador invalido.");
printf("\n");
}
Exemplos da execuo deste programa:
Entre com numero operador numero:
5 * 3.5
= 17.50
Entre com numero operador numero:
27

10 + 0
= 10.00
Entre com numero operador numero:
10 x 5.0
Operador invalido.

28

A sentena switch

A sentena switch outra maneira de fazer decises mltiplas. Ele pode ser usado para testar se uma dada
expresso igual a um valor constante e, dependendo do valor, tomar determinadas aes.
O formato da sentena switch :
switch (expressao) {
case expressao-constante 1:
sentencas 1
case expressao-constante 2:
sentencas 2
..
.
default:
sentencas n
}

A sentena switch primeiro avalia a expresso. Se o valor da expresso for igual a uma das expresses
constantes, as sentenas que seguem o case so executados. Se o valor da expresso no for igual a
nenhuma das constantes, as sentenas que seguem default so executadas.
As sentenas que seguem o case so simplesmente uma lista de sentenas. Esta lista pode conter mais
de uma sentena e no necessrio coloc-las entre chaves ({ e }). A lista de sentenas tambm pode ser
vazia, isto , voc pode no colocar nenhuma sentena seguindo o case.
Tambm no obrigatrio colocar o default. S o use quando for necessrio.
Note no diagrama acima que TODAS as sentenas que seguem a constante com o valor igual ao da
expresso sero executados. Para que se execute APENAS as sentenas que seguem o case que seja igual
ao valor da expresso precisamos usar a sentena break, que veremos em seguida.

10

A sentena break

O break faz com que todas as sentenas que o seguem dentro da mesma sentena switch sejam ignorados. Ou seja, colocando a sentena break no final de uma sentena case faz com que as sentenas que
seguem os cases subsequentes no sejam executadas. Em geral, este o comportamento desejado quando
se usa o switch, e cases sem o break no final so de pouca utilidade. Portanto, o uso de sentenas
case sem o break devem ser evitados e quando utilizados devem ser comentados ao lado com algo como
/* continua proxima sentenca - sem break */.
Com a sentena break o diagrama de fluxo fica:
29

Note a similaridade com o diagrama da sentena else-if e a diferena com o diagrama da sentena
switch acima.
O prximo programa tem a mesma funo de calculadora do programa anterior, porm utilizando a
sentena switch.
Exemplo 11:
#include <stdio.h>
main(void)
{
float num1, num2;
char op;
printf("Entre com numero operador numero:\n");
scanf("%f %c %f", &num1, &op, &num2);
switch (op) {
case +:
printf(" = %.2f",
break;
case -:
printf(" = %.2f",
break;
case *:
printf(" = %.2f",
break;
case /:
printf(" = %.2f",
break;
default:
printf(" Operador
break;
}
printf("\n");

num1 + num2);

num1 - num2);

num1 * num2);

num1 / num2);

invalido.");

30

}
Como mencionado anteriormente, possvel no colocar nenhuma sentena seguindo um case. Isso
til quando diversas sentenas case (diversas constantes) tm a mesma ao.
Por exemplo, podemos modificar o programa acima para aceitar x e X para multiplicao e \ para
diviso. O programa fica ento:
#include <stdio.h>
main(void)
{
float num1, num2;
char op;
printf("Entre com numero operador numero:\n");
scanf("%f %c %f", &num1, &op, &num2);
switch (op) {
case +:
printf(" = %.2f",
break;
case -:
printf(" = %.2f",
break;
case *:
case x:
case X:
printf(" = %.2f",
break;
case /:
case \\:
printf(" = %.2f",
break;
default:
printf(" Operador
break;
}
printf("\n");

num1 + num2);

num1 - num2);

num1 * num2);

num1 / num2);

invalido.");

}
Exerccio 2: Ler mes e ano e imprimir o numero de dias do mes no ano digitado.

31

11

Funes

11.1

Funes: o que so e por que us-las

Quando queremos resolver um problema, em geral tentamos dividi-lo em subproblemas mais simples e relativamente independentes, e resolvemos os problemas mais simples um a um. A linguagem C dispe de
construes (abstraes) que auxiliam o projeto de programas de maneira top-down. Uma funo cria uma
maneira conveniente de encapsular alguns detalhes de processamento, ou seja, como algum resultado obtido. Quando esta computao necessria, a funo chamada, ou invocada. Desta forma, quando uma
funo chamada o usurio no precisa se preocupar como a computao realizada. importante saber
o que a funo faz (qual o resultado da execuo de uma funo) e tambm como se usa a funo. Criando
funes, um programa C pode ser estruturado em partes relativamente independentes que correspondem as
subdivises do problema.
Voc j viu algumas funes: printf(), scanf(), getchar(), sqrt(). Elas so funes
de uma biblioteca padro (do C ). Voc no sabe como elas foram escritas, mas j viu como utiliz-las. Ou
seja, voc sabe o nome das funes e quais informaes especficas voc deve fornecer a elas (valores que
devem ser passados para as funes) para que a funo produza os resultados esperados.
Quando nos referirmos a uma funo neste texto usaremos a maneira frequentemente utilizada que o
nome da funo seguido de ().
Tomemos como exemplo o programa abaixo, que calcula o produto de 2 nmeros inteiros positivos
apenas se ambos forem primos:

Observe que o cdigo que verifica se um nmero primo teve que ser reproduzido dentro do programa
por duas vezes (para testar se os nmeros fornecidos pelo usurio eram primos).
Um dos benefcios mais bvios de usar funes que podemos evitar repetio de cdigo. Em outras
palavras, se voc quiser executar uma operao mais de uma vez, voc pode simplesmente escrever a funo
uma vez e utiliz-la diversas vezes ao invs de escrever o mesmo cdigo vrias vezes. Outro benefcio que
se voc desejar alterar ou corrigir alguma coisa mais tarde, mais fcil alterar em um nico lugar.
O exemplo acima poderia ser simplificado pela criao de uma funo chamada ehPrimo, que dado
um nmero n, d como resultado 1 se este nmero primo, ou 0 (zero) se o nmero no primo:

O exemplo pode ser ento alterado e simplificado com o uso da funo ehPrimo():

Como pode ser observado, sejam quais forem os 2 nmeros fornecidos, no precisa escrever um cdigo
similar ao mostrado na funo ehPrimo acima para cada nmero.Basta chamar a funo ehPrimo(),
passar os valores necessrios para verificar a primalidade de cada nmero, e utilizar os resultados.
Evitar repetio de cdigo a razo histrica que funes foram inventadas (tambm chamado de procedimento ou subrotinas em outras linguagens de programao). A maior motivao para utilizar funes nas
linguagens contemporneas a reduo da complexidade do programa e melhoria da modularidade do programa. Dividindo o programa em funes, muito mais fcil projetar, entender e modificar um programa.
Por exemplo, obter a entrada do programa, realizar as computaes necessrias e apresentar o resultado ao
usurio pode ser implementado como diferentes funes chamadas por main() nesta ordem.
Funes podem ser escritas independentemente uma da outra. Isto significa que, em geral, variveis
usadas dentro de funes no so compartilhadas pelas outras funes. Assim sendo, o comportamento da
funo previsvel. Se no for assim, duas funes completamente no relacionadas podem alterar os dados
uma da outra. Se as variveis so locais a uma funo, programas grandes passam a ser mais fceis de serem
escritos. A comunicao entre funes passa a ser controlada elas se comunicam somente atravs pelos
valores passados as funes e os valores retornados.
32

11.2

Definindo funes

Um programa C consiste de uma ou mais definies de funes (e variveis). H sempre uma funo
chamada main. Outras funes tambm podem ser definidas. Cada uma pode ser definida separadamente,
mas nenhuma funo pode ser definida dentro de outra funo. Abaixo, mostramos um exemplo simples
de um programa que consiste de duas funes: main() e alo(). Quando executado, este programa
imprimir a mensage Alo! trs vezes.
#include <stdio.h>
/* declaracao (prottipo) da funcao alo() */
void alo(void);
/* definicao da funcao main() */
main()
{
int i;
i = 1;
while (i <= 3)
{
alo();
i += 1;
}
}
/* definicao da funcao alo() */
void alo(void)
{
printf("Alo!\n");
}
Todas as funes devem ser declaradas antes de serem usadas. As funes da biblioteca padro, tais
como printf(), scanf() e getchar(), so pr-definidas, mas memsmo assim devem ser declaradas
(deve ser anunciado ao compilador que elas existem). por isso que inclumos a linha #include <stdio.h>
no incio do cdigo fonte.
O formato geral da definio de uma funo
tipo-do-resultado nome-da funo (lista-de-argumentos)
{
declaraes e sentenas
}
A primeira linha da definio o cabealho da funo. Ela tm trs partes principais: o nome da
funo, o tipo do resultado (que um valor) que a funo computa e retorna, e entre parnteses uma lista
de parmetros (tambm chamado de argumentos formais). Se a funo no retorna nenhum valor, o tipo
chamado de void, e esta palavra escrita no cabealho na frente do nome da funo. Se a funo no tiver
argumentos formais, a palavra void escrita no lugar da lista de argumentos formais entre os parnteses.
Para simplificar a exposio, falaremos sobre o tipo do retorno e os argumentos formais mais tarde. Eles
servem para permitir que as funes troquem informaes entre si.
33

11.3

Funes simples

Para comear, vamos utilizar funes na seguinte forma:


void nome-da-funo(void)
{
declaraes e senteas (corpo da funo)
}
O primeiro void significa que esta funo no tem tipo de retorno (no retorna um valor), e o segundo
significa que a funo no tem argumentos (ela no precisa de nenhuma informao externa para ser executada). Isso no significa que a funo no faz nada. Ela pode realizar alguma ao, como imprimir uma
mensagem. O exemplo abaixo mostra um programa que usa uma funo como essa:
void alo(void);
main()
{
alo();
}
void alo(void)
{
printf("Alo.\n");
}
Neste exemplo, o programa consiste de duas funes, main() e alo(). A ordem em que as funes
so definidas no importante, desde que prottipos de funes so usadas. A linha
void alo(void);
no topo do programa um prottipo de funo para a funo alo(). Um prottipo usado para declarar
uma funo. Um prottipo passa ao compilador informaes sobre uma funo que definida dentro de
um programa em algum lugar. Prottipos so sempre colocados prximo ao incio do programa, antes do
comeo da definio de funes.
A funo alo() imprime a mensagem Alo. quando chamada. A sentena printf() o corpo
da funo. Dentro da funo main() h uma chamada a funo alo(). A funo chamada pelo seu
nome seguido de () (j que a funo alo no tem argumentos, nenhuma expresso escrita dentro dos
parnteses). A funo alo() no retorna um valor, ela chamada simplesmente para realizar uma ao
(imprimir a mensagem). A chamada de funo uma sentena vlida em C , portanto deve ser terminada
por ponto e vrgula (;).
alo();
Outra coisa que voc deve ter notado que main() tambm uma funo. A funo main() no difere
em nada das demais funes, com a exceo de que contm o programa principal. Alm disso, no
necessrio declarar o prottipo da funo main().
11.3.1

Argumentos

Nosso prximo exemplo pede que o usurio digite suas iniciais, e ento chama a funo cumprimenta()
para imprimir a mensagem Ola junto com as iniciais digitadas. Estas iniciais (seus valores) so passadas
para a funo cumprimenta(). A funo cumprimenta() definida de forma que ela imprimir a
mensagem incluindo quaisquer iniciais passadas.
34

#include <stdio.h>
void cumprimenta(char, char);
int main()
{
char primeiro, segundo;
printf("Entre com duas iniciais (sem separacao): ");
primeiro = getchar();
segundo = getchar();
cumprimenta(primeiro, segundo);
}
void cumprimenta(char inic1, char inic2)
{
printf("Ola, %c%c!\n",inic1,inic2);
}
A funo main() chama a funo cumprimenta(); main() passa para cumprimenta() os
valores dos dois caracteres para serem impressos. Veja um exemplo de execuo do programa:
Entre com duas iniciais (sem separacao): YK
Alo, YK!
Note que h uma correspondncia entre o nmero e tipo dos valores que main() passa (estes so chamados
de parmetros reais ou argumentos reais) e os argumentos listados no cabealho da funo cumprimenta().

11.4

Funes que retornam um valor

Funes que no retornam nenhum valor (como alo(), main()) possuem tipo void.
Alm de executarem aes (como imprimir) uma funo tambm pode retornar um valor para o programa que o chamou. Uma funo que retorna um valor tem no cabealho o nome do tipo do resultado. O
valor retornado pode ser de qualquer tipo, incluindo int, float e char ( claro que uma vez definida,
a funo s de um tipo especfico). Uma funo que retorna um tipo diferente de void executa alguns
clculos, e retorna o resultado (que um nico valor) para quem a chamou. A funo chamadora pode ento
usar o resultado. Para retornar um valor para a funo chamadora, a funo usa a sentena return.
O formato da sentena return a seguinte:
return expresso;
A expresso avaliada e o seu valor convertido ao tipo de retorno da funo (o tipo da funo dado
no cabealho da funo antes do nome da funo).
Considere o seguinte exemplo. O programa consiste de duas funes: main() e quadrado. O
programa pede que o usurio digite trs nmeros e verifica se eles podem ser os lados de um tringulo reto.
/* programa que verifica se 3 numeros podem ser os lados de um
* triangulo reto.
*/
#include <stdio.h>
int quadrado(int);
35

int main()
{
int s1, s2, s3;
printf("Entre tres inteiros: ");
scanf("%d %d %d", &s1, &s2, &s3);
if ( s1 > 0 && s2 > 0 && s3 > 0 &&
(quadrado(s1) + quadrado(s2) == quadrado(s3) ||
quadrado(s2) + quadrado(s3) == quadrado(s1) ||
quadrado(s3) + quadrado(s1) == quadrado(s2)) )
printf(" %d %d %d podem formar um triangulo reto\n", s1, s2, s3);
else
printf(" %d %d %d nao podem formar um triangulo reto\n",s1, s2, s3);
}
/* funcao que calcula o quadrado de um numero */
int quadrado(int n)
{
return n * n;
}
Note que quando chamamos a funo quadrado() passamos o valor no qual desejamos executar o
clculo, e tambm usamos o valor retornado pela funo em expresses. O valor de quadrado(s1) o
valor que a funo quadrado() retorna quando chamado com o valor do argumento sendo igual ao valor
da varivel s1.
Os valores retornados pelas chamadas de funes podem ser usados em todos os lugares valores podem
ser usados. Por exemplo,
y = quadrado(3);
Aqui quadrado(3) tem o valor 9, portanto 9 pode ser atribudo a varivel y;
x = quadrado(3) + quadrado(4);
atribuir 25 a varivel x, e
area = quadrado(tamanho);
atribuir a varivel area o valor da varivel tamanho elevado ao quadrado.
O prximo exemplo tem uma funo chamada cinco:
int cinco(void);
main()
{
printf("cinco = %d\n", cinco() );
}
int cinco(void)
{
return 5;
}
A sada do programa ser
cinco = 5
36

porque o valor de cinco() dentro da sentena printf() 5. Olhando na sentena return, 5 a


expresso retornada para o chamador.
Outro exemplo:
int obtem_valor(void);
main()
{
int a, b;
a = obtem_valor();
b = obtem_valor();
printf("soma = %d\n", a + b);
}
int obtem_valor(void)
{
int valor;
printf("Entre um valor: ");
scanf("%d", &valor);
return valor;
}
Este programa obtm dois inteiros do usurio e mostra a sua soma. Ele usa a funo obtem valor()
que mostra uma mensagem e obtm o valor do usurio.
Um exemplo de sada deste programa :
Entre um valor: 15
Entre um valor: 4
soma = 19

11.5

Mais sobre o return

Quando uma funo return executada, a funo imediatamente acaba mesmo que haja cdigo na
funo aps a sentena return. A execuo do programa continua aps o ponto no qual a chamada de
funo foi feita. Sentenas return podem ocorrer em qualquer lugar na funo no somente no final.
Tambm vlido ter mais de um return dentro de uma funo. A nica limitao que return retorna
um nico valor.
O seguinte exemplo mostra uma funo (uma verso para int da funo obtem valor) que pede
para usurio um valor e se o usurio digitar um valor negativo, imprime uma mensagem e retorna um valor
positivo.
int obtem_valor_positivo(void)
{
int valor;
printf("Entre um valor: ");
scanf("%d", &valor);
37

if (valor >= 0)
return valor;
printf("Tornando o valor positivo...\n");
return -valor;
}
Em uma funo void, return; (s com ;) pode ser usado para sair de uma funo. O exemplo
seguinte, pede instrues ao usurio. Se o usurio reponder nao, a funo termina. Do contrrio, ele
imprime as instrues e depois termina.
void instrucoes(void)
{
int ch;
printf("Voce quer instrucos? (s/n): ");
ch = getchar();
/* Termina se resposta for n */
if (ch == n || ch == N)
return;
/* Mostra instrucoes */
printf("As regras do jogo sao . . . ");
.
.
.
return;
}
O return final (antes de fechar as chaves do corpo da funo) na funo opcional. Se omitido, a
funo atingir o final da funo e retornar automaticamente. Note que o return opcional somente para
funes void.

11.6

Mais sobre Argumentos

A comunicao entre uma funo e o chamador pode ser nas duas direes. Argumentos podem ser usados
pelo chamador para passar dados para a funo. A lista de argumentos definida pelo cabealho da funo
entre parnteses.. Para cada argumento voc precisa especificar o tipo do argumento e o nome do argumento.
Se houver mais de um argumento, eles so separados por vrgula. Funes que no possuem argumentos
tem void como lista de argumento. No corpo da funo os argumentos (tambm chamados de argumentos
formais ou parmetros formais) so tratados como variveis. erro defini-los dentro do corpo da funo
porque eles j esto definidos no cabealho. Antes da execuo da funo os valores passados pelo chamador
so atribudos aos argumentos da funo.
Considere o seguinte programa com a funo abs() que calcula o valor absoluto de um nmero.
int abs(int);
main()
{
int n;
38

printf("Entre um numero: ");


scanf("%d", &n);
printf("Valor absoluto de

%d e %d", n, abs(n));

}
/* Definicao da funcao abs */
int abs(int x)
{
if (x < 0)
x = -x;
return x;
}
A funo abs() tem um argumento do tipo int, e seu nome x. Dentro da funo, x usado como
uma varivel x.
Uma vez que abs() tem um nico argumento, quando ela chamada, h sempre um valor dentro do
parnteses, como em abs(n). O valor de n passado para a funo abs(), e antes da execuo da funo,
o valor de n atribudo a x.
Aqui est um exemplo de uma funo que converte uma temperatura de Farenheit para Celsius:
float fahr_para_cels(float f)
{
return 5.0 / 9.0 * (f - 32.0);
}
Como voc pode ver, esta funo tem somente um argumento do tipo float. Um exemplo de chamada
desta funo poderia ser:
fervura = fahr_para_cels(212.0);
O resultado da funo fahr para cels(212.0) atribudo a fervura. Portanto, depois da
execuo desta sentena, o valor de fervura (que do tipo float) ser 100.0.
O exemplo seguinte possui mais de um argumento:
float area(float largura, float altura)
{
return largura * altura;
}
Esta funo possui dois argumentos do tipo float. Para chamar uma funo com mais de um argumento,
os argumentos devem ser separados por vrgula. A ordem em que os argumentos so passados deve ser na
mesma em que so definidos. Neste exemplo, o primeiro valor passado ser a largura e o segundo a altura.
Um exemplo de chamada seria
tamanho = area(14.0, 21.5);
Depois desta sentena, o valor de tamanho (que do tipo float) ser 301.0.
Quando passar os argumentos, importante ter certeza de pass-los na ordem correta e que eles so do
tipo correto. Se isso no for observado, pode ocorrer erro ou aviso de compilao, ou resultados incorretos
podem ser gerados.
Uma ltima observao. Os argumentos que so passados pelo chamador podem ser expresses em geral
e no somente constantes e varivies. Quando a funo chamada durante a execuo do programa, estas
expresses so avaliadas, e o valor resultante passado para a funo chamada.
39

11.7

Chamada por valor

Considere novamente a funo quadrado(). Se esta funo chamada de main() como

p = quadrado(x);

somente o valor (no o endereo) de x passado para quadrado. Por exemplo, se a varivel tem valor
5, para a funo quadrado(), quadrado(x) ou quadrado(5) so o mesmo. De qualquer forma,
quadrado() receber somente o valor 5. quadrado() no sabe se na chamada da funo o 5 era uma
constante inteira, o valor de uma varivel do tipon int, ou alguma expresso como 625/25 - 4 * 5.
Quando quadrado() chamado, no interessa qual a expresso entre parnteses, ela ser avaliada e o
valor passado para quadrado().
Esta maneira de passar argumentos chamada de chamada por valor. Argumentos em C so passados
por valor. Portanto, a funo chamada no pode alterar o valor da varivel passada pelo chamador como
argumento, porque ela no sabe em que endereo de memria o valor da varivel est armazenado.

11.8

Variveis locais

Como voc provavelmente j reparou em alguns exemplos, possvel definir variveis dentro de funes, da
mesma forma que temos definido variveis dentro da funo main(). A declarao de variveis feita no
incio da funo.
Estas variveis so restritas a funo dentro da qual elas so definidas. S esta funo pode enxergar
suas prprias variveis. Por exemplo:
void obtem_int(void);
main()
{
obtem_int();
/* **** Isto esta errado **** */
printf("Voce digitou %d\n", x);
}
void obtem_int(void)
{
int x;
printf("Entre um valor: ");
scanf("%d", &x);
printf("Obrigado!\n");
}
A funo main() usou um nome x, mas x no definido dentro de main; ele uma varivel local a
get int(), no a main(). Este programa gera erro de compilao.
Note que possvel ter duas funes que usam variveis locais com o mesmo nome. Cada uma delas
restrita a funo que a define e no h conflito. Analise o seguinte programa (ele est correto):
int obtem_novo_int(void);
40

main()
{
int x;
x = obtem_novo_int();
/* ****Isto nao esta errado !! **** */
printf("Voce digitou %d\n", x);
}
int obtem_novo_int(void)
{
int x;
printf("Entre um valor: ");
scanf("%d", &x);
printf("Obrigado!\n");
return x;
}
A funo obtem novo int() usa uma varivel local chamada x para armazenar o valor digitado e
retorna como resultado o valor de x. main() usa outra varivel local, tambm chamada de x para receber
o resultado retornado por obtem novo int(). Cada funo tem sua prpria varivel x.

11.9

Prottipos

Os prottipos servem para dar ao compilador informaes sobre as funes. Isso para que voc possa chamar
funes antes que o compilador tenha a definio (completa) das funes. O prottipo de uma funo
idntico ao cabealho da funo, mas o nome dos argumentos podem ser omitidos e ele terminado com
uma vrgula. Prottipos declaram uma funo ao invs de defini-las. O formato dos prottipos :
tipo-de-retorno nome-da-funo(lista-dos-tipos-dos-argumentos);
Definindo prottipos, voc no precisa se preocupar com a ordem em que define as funes dentro do
programa. A principal vantagem de definir prottipos que erros de chamada de funes (como chamar
uma funo com o nmero incorreto de argumentos, ou com argumentos de tipo errado) so detectados pelo
compilador. Sem prottipos, o compilador s saberia que h erro depois de encontrar a definio da funo.
Em verses antigas do compilador C , programas com tais erros compilariam sem erros, o que tornava a
depurao de erros mais difcil.
Abaixo, mostramos duas funes e seus prottipos:
float volume(float, float, float);
float dinheiro(int, int, int, int);

float volume(float comprimento, float largura, float altura)


{
return comprimento * largura * altura;
}
41

float dinheiro(int c25, int c10, int c5, int c1)


{
return c25 * 0.25 + c10 * 0.10 +
c5 * 0.05 + c1 * 0.01;
}

11.10

Documentao de funes

Voc deve documentar as funes que escreve. Na documentao voc deve especificar as seguintes informaes:
Ao o que a funo faz
Entrada descrio dos argumentos passados para a funo
Sada descrio do valor retornado pela funo
Suposies o que voc assume ser verdade para que a funo funcione apropriadamente
Algoritmo como o problema resolvido (mtodo)
Estas informaes devem ser colocadas como comentrio antes da definio da funo.

11.11

Comentrios

Voc pode colocar comentrios no seu programa para documentar o que est fazendo. O compilador ignora
completamente o que quer esteja dentro de um comentrio.
Comentrios em C comeam com um /* e terminam com um */. Alguns exemplos:
/* Este e um comentario sem graca */
/* Este e
um comentario
que usa
diversas linhas
*/
/* Este e
* um comentario
* de diversas linhas
* mais bonito
*/
Note que no podemos aninhar comentrios dentro de comentrios. Um comentrio termina no primeiro
/
que
encontrar. O comentrio abaixo ilegal:
*
/* Este e um comentario /* illegal */ ilegal */

42

Regras para comentrio


sempre uma boa idia colocar comentrios em seu programa das coisas que no so claras. Isto vai
ajudar quando mais tarde voc olhar o programa que escreveu j h algum tempo ou vai ajudar a entender
programas escritos por outra pessoa.
Um exemplo de comentrio til:
/* converte temperatura de farenheit para celsius */
celsius = (fahrenheit - 32) * 5.0 / 9.0;
O comentrio deve ser escrito em portugus e no em C . No exemplo abaixo
/* usando scanf, obter valor de idade e multiplicar por 365 para
* obter dias */
scanf("%d", &idade);
dias = idade * 365;
o comentrio basicamente uma transcrio do cdigo do programa. Em seu lugar, um comentrio
como
/* obtem idade e transforma em numero de dias */
seria mais informativo neste ponto. Ou seja, voc deve comentar o cdigo, e no codificar o comentrio.
Voc tambm deve evitar comentrios inteis. Por exemplo:
/* Incrementa i */
i++;
No h necessidade de comentrios j que i++ j auto explicativo.
E abaixo est um exemplo de como voc deve comentar uma funo.
/* funcao instrucoes()
mostra instrucoes do programa
* acao:
nenhuma
* entrada:
saida:
nenhuma
*
* suposicoes: nenhuma
imprime as instrucoes
* algoritmo:
/
*
void instrucoes(void)
{
/* mostra instrucoes */
printf("O processo de purificacao do Uranio-235 e . . .
.
.
}

43

");

12

O pr-processador

O pr-processador um programa que faz alguns processamentos simples antes do compilador. Ele executado automaticamente todas as vezes que seu programa compilado, e os comandos a serem executados
so dados atravs de diretivas do pr-processador.
Estas diretivas so colocadas em linhas que contm somente a diretiva (elas no so cdigo da linguagem
C , portanto as regras para elas so um pouco diferentes). As linhas que comeam com um # so comandos
para o pr-processador. A linha inteira reservada para este comando (nenhum cdigo C pode aparecer
nesta linha e comandos do pr-processador no podem estar separados em diversas linhas).

12.1

A diretiva #define

Uma diretiva que usada frequentemente o #define. Esta diretiva usada para fazer substituio
de macros. Por enquanto, mostraremos uma utilizao simples do #define, que simplestemente uma
substituio no texto.
O uso mais frequente desta diretiva dar nomes simblicos a uma constante (voc j viu outra maneira
de definir contantes que colocar a palvavra const antes da definio de uma varivel). Por exemplo,
seria conveniente usar PI em seus programas ao invs de digitar 3.1415926535 toda hora. Como outro
exemplo, se voc quiser escrever um programa sobre estudantes de uma turma de 81 alunos, voc poderia
definir NUM_ALUNOS como 81. Assim, se o nmero de alunos mudar, voc no precisaria modificar todo
o seu programa onde o nmero de alunos (81) utilizado, mas simplesmente alterar a diretiva #define.
Estas duas diretivas so definidas da seguinte forma:
#define PI
3.1415926535
#define NUM_ALUNOS 81
Por conveno, nomes introduzidos por um #define so geralmente em letra maiscula (e variveis
so em letra minscula, ou uma mistura de letras minsculas e maisculas). Assim, quando voc v um nome
em um programa, voc sabe se o nome refere-se a uma varivel ou um nome definido por um #define.
Considere o seguinte programa exemplo que usa PI:
#define PI

3.14159265

int main()
{
double raio;
printf("Entre com o raio: ");
scanf("%f", &raio);
printf("Circunferencia = %f\n", 2.0 * PI * raio);
}
Lembre-se que o nome PI no um nome de varivel. Ele um nome que o pr-processador substituir
pelo texto especificado pelo #define (mais ou menos da mesma forma que o comando pesquisa-e-substitui
do editor de texto). O compilador nunca v ou sabe sobre PI. O compilador v o seguinte printf() do
programa acima depois do pr-processador ser executado:
printf("Circunferencia = %f\n", 2.0 * 3.14159265 * raio);

12.2

A diretiva #include

Agora imagine que estamos escrevendo uma biblioteca geomtrica: um conjunto de funes para calcular
a rea de cilindros, cones, esferas. Se diferentes pessoal esto escrevendo cada uma das funes, eles
44

provavelmente colocaro suas funes em diferentes arquivos. Mas todas as funes usam o numero , e
algumas outras constantes podem ser necessrias tambm. Ao invs de colocar o #define no incio de
cada arquivo, um nico arquivo geom.h pode ser criado. Este arquivo conter a linha
#define PI
3.14159265
Assim, se todos os arquivos de funes geomtricas puderem enxergar geom.h, eles compartilharo as
mesmas definies. para isso que usamos a diretiva #include, para incluir em seu programa, informaes que esto em outro arquivo. Estas diretivas geralmente esto no incio do programa fonte, antes da
definio de funes e varveis. Por exemplo, a diretiva
#include "geom.h"
colocada nos arquivos fontes que contm as funes geomtricas far com que todos eles usem o nome
simblico PI ao invs de 3.14159265. O fato do nome do arquivo estar em aspas significa que o arquivo geom.h est no mesmo diretrio que os arquivos fontes (ao invs do diretrio onde se encontram as
bibliotecas padro de C ).
A diretiva
#include <stdio.h>
colocada no incio do programa fonte para incluir informaes (como prottipos de funes) que so
necessrios quando printf() e scanf() so chamados dentro do programa. O arquivo entre < > est
em algum diretrio padro conhecido pelo pr-processador. Este arquivo stdio.h comum a todas as
implementaes da linguagem C e contm infomaes necessrias para executar operaes de entrada e
sada da entrada e sada padro (teclado e monitor).
A extenso .h vem do ingls header file. Apesar de no ser obrigatrio que arquivos includos tenham
a extenso .h, geralmente esta a conveno utilizada.

12.3

Comentrios

De um modo geral, o pr-processador dos compiladores existentes remove todos os comentrios do arquivo
fonte antes do programa ser compilado. Portanto, o compilador nunca v realmente os comentrios.

13

Mais sobre funes

A nfase aqui ser em como funes funcionam. O que acontece quando uma funo chamada ? A que
varivel um nome est se referenciando?
O tratamento em tempo de execuo de um nome de varivel em C simples: um nome de varivel ou
uma varivel local (a funo) ou uma varivel global (definida fora de qualquer funo).
Em C , todas as funes tem que ser definidas. Para cada funo deve ser definido um prottipo. O
prottipo escrito fora de qualquer funo. Desta forma, nomes de funes so visveis para todas as outras
funes que podem ento invoc-las. A funo main() especial: onde a execuo do programa comea,
e o prottipo de main() pode ser omitido.
Uma definio de funo consiste de quatro partes:
1. o nome da funo;
2. a lista de parmetros formais (argumentos) com seus nomes e tipos. Se no houver argumentos, a
palavra void escrita entre os parnteses.
3. o tipo do resultado que a funo retorna atravs da sentena return ou void se a funo no retorna
nenhum valor. Lembre-se que somente um valor pode ser retornado por uma sentena return.
4. o corpo da funo, que uma sentena composta (comea e termina com chaves ({ }) contendo
definio de variveis e outras sentenas. Em C , no se pode definir uma funo dentro de outra.

45

Para funes com argumentos: uma funo chamada dando o seu nome e uma lista de argumentos
(expresses que so avaliadas e cujos valores so atribudos para os correspondentes parmetros formais da
funo).
Por exemplo, suponha que triang area() e circ area() sejam funes que calculam a rea de
tringulos e crculos, respectivamente. Seus prottipos so:
float triang_area(float , float);
float circ_area(float);
Estas funes podem chamadas de dentro de outras funes. Os argumentos reais com os quais elas so
chamadas podem ser expresses constantes, or variveis locais, ou qualquer expresso cuja avaliao resulte
em valores do tipo float (inteiros so convertidos para float da mesma forma que ocorre com atribuio
de inteiros para variveis do tipo float). Alguns exemplos de chamadas:
float

area2, area3, area4, area5, base, altura, raio;

printf("area do triangulo = ", triang_area(0.03, 1.25));


base = 0.03;
altura = 1.25;
area2 = triang_area(base, altura);
area3 = triang_area(1.6, altura);
area4 = triang_area( 0.03 + base, 2 * altura);
raio = base + altura;
area5 = triang_area(raio, circ_area(raio));
A ltima sentena do exemplo acima atribui a varivel area5 a rea de um tringulo cuja base igual
ao valor da varivel raio e a altura igual a area de um crculo de raio igual ao valor da varivel raio.
Quando um programa executado, somente uma nica funo tem o controle em determinado momento.
Falaremos mais sobre o que acontece quando uma funo chamada mais tarde nestas notas de aula.
Variveis Locais Variveis que so definidas dentro de uma funo so variveis locais desta funo.
Parmetros formais de uma funo so variveis locais da funo. Variveis locais so privativas a
funo na qual so definidas. Somente esta funo pode enxerg-las (ela conhece o endereo das variveis e
pode usar e modificar o seu contedo). Nenhuma outra funo pode acessar variveis locais de outra funo
sem permisso (uma funo pode acessar variveis locais de outra se esta passar o endereo da varivel
local como argumento este assunto ser tratado em notas de aula futuras). O fato de cada funo manter
variveis locais escondidas do resto do mundo torna mais fcil a tarefa de escrever programas estruturados
e modulares. Quando voc est escrevendo uma funo, voc pode dar as suas variveis locais o nome que
quiser. Voc tambm no precisa se preocupar se outra pessoa escrevendo outra funo ter acesso ou altera
variveis locais a sua funo.
Variveis locais que so definidas dentro da funo devem ser inicializadas com algum valor antes
de serem usadas. Caso contrrio, o seu valor indefinido.
J que parmetros formais (argumentos) so variveis locais da funo, eles podem ser usados no corpo
da funo. Eles no devem ser definidos dentro da funo (sua definio j est no cabealho da funo). Os
parmetros formais no precisam ser inicializados. Seus valores so fornecidos pelo chamador da funo
atravs dos argumentos reais.
Considere o seguinte exemplo:
/*****************************************************************
* Um programa que calcula a area de triangulos e circulos.
* A base, altura e raio sao fornecidos pelo usuario.
46

* A saida do programa e a area do triangulo e circulo.


*****************************************************************/
#include <stdio.h>
#define PI 3.1415
/*******************
prototipos
*******************/
float triang_area(float, float);
float circ_area(float);
/*******************
definicao de funcoes
*******************/
main(void)
{
/* definicao das variaveis locais */
float
base, altura, raio;
/* dialogo de entrada
printf("\nEntre com a
scanf("%f %f", &base,
printf("\nEntre com o
scanf("%f", &raio);

*/
base e altura do triangulo: ");
&altura);
raio do circulo: ");

/* chama as funcoes e imprime o resultado */


printf("Area do triagulo com base e altura %f e %f = %f\n",
base, altura, triang_area(base, altura));
printf("Area do circulo com raio %f = %f\n", raio, circ_area(raio));
}
/*****************************************************************
* funcao: triang_area
* calcula a area de um triangulo dada a base e altura
* Entrada: base e altura do triangulo
* Saida: area do triangulo
*****************************************************************/
float triang_area(float base, float alt)
{
return 0.5*base*alt;
}
/*****************************************************************
* funcao: circ_area
* calcula a area de um circulo dado o raio
* Entrada: raio do circulo
* Saida: area do circulo
47

*****************************************************************/
float circ_area(float r)
{
return PI*r*r;
}

Este programa C consiste de trs funes, main(), triang_area(), e circ_area(). main()


tem variveis locais chamadas base, altura e raio; triang_area() tem como variveis locai seus
parmetros formais, base e alt; circ_area() tem como varivel local seu parmetro formal r.
Em geral, uma varivel local s existe durante a execuo da funo na qual ela est definida. Portanto,
variveis locais existem desde o momento que a funo chamada at o momento em que a funo
completada. Tais variveis so chamadas de automatic. Em C , uma varivel pode ser definida como
sendo static. Neste caso, uma varivel local no visvel de fora do corpo da funo, mas ela no
destruda no final da funo como variveis automticas so. Cada vez que a funo chamada, o valor das
variveis static o valor final da varivel da chamada anterior.
Variveis Globais
At este momento, todas as variveis que vimos so definidas dentro de funes (no corpo da funo
ou como parmetros formais). possvel tambm definir variveis fora das funes. Tais variveis so
chamadas de variveis globais ou externas. O formato da definio de variveis globais o mesmo da
definio de variveis locais. A nica diferena onde a varivel definida: variveis globais so definidas
fora de qualquer funo. Ao contrrio das variveis locais, variveis globais podem ser vistas por todas as
funes definidas aps a definio das variveis globais.
Ns temos usado declaraes globais este tempo todo por exemplo, as declaraes de prottipos de
funes. Elas so declaradas fora de qualquer funo e podem ser vistas por qualquer funo que esto aps
sua declarao.
No exemplo seguinte, uma varivel saldo que atualizada por trs funes diferentes definida como
uma varivel global. As trs funes que a atualizam no chamam uma a outra.
/*****************************************************************
* Caixa eletronico simples
* o saldo e o valor a ser alterado e entrado pelo usuario
* a saida do programa e o saldo atualizado, incluindo juros
*****************************************************************/
#include <stdio.h>
#define JUROS 0.07
/*******************
prototipos
*******************/
void credito(float);
void debito(float);
void juros(void);
/*******************
globais
*******************/
float saldo; /* saldo atual;
48

* Alterada em: credito(), debito(), juros(), main()


* Lida em:
*/

/***********************
definicao de funcoes
***********************/
main(void)
{
float valor;

/* valor a ser

depositado/retirado

printf("Entre com o saldo atual: ");


scanf("%f",&saldo);
printf("Deposito: ");
scanf("%f", &valor);
credito(valor);
printf("Retirada: ");
scanf("%f", &valor);
debito(valor);
juros();
printf("Juros 7%%.\n");
printf("Saldo = : %.2f\n ", saldo);
}
/*****************************************************************
* Deposita um valor; atualiza a variavel global saldo
* Entrada: valor a ser depositado
* Saida: nenhum
*****************************************************************/
void credito(float val)
{
saldo += val;
}
/*****************************************************************
* Debita um valor; atualiza a variavel global saldo
* Entrada: valor a ser debitado
* Saida: nenhum
*****************************************************************/
void debito(float val)
{
saldo -= val;
}
/*****************************************************************
* Acumula juros; atualiza a variavel global saldo; juros: RATE
* Entrada: nenhuma
* Saida: nenhuma
49

*/

*****************************************************************/
void juros(void)
{
saldo += (saldo * JUROS);
}

Um exemplo de execuo do programa:

Entre com o saldo atual: 1000


Deposito: 200
Retirada: 80
Juros 7%.
Saldo = 1198.40
Variveis globais devem ser usadas SOMENTE quando muitas funes usam muito as mesmas variveis. No entanto, o uso de variveis globais perigoso (e no recomendado) porque a modularidade do
programa pode ser afetada. Uma varivel global pode ser alterada de dentro de uma funo, e esta alterao
pode influir no resultado de uma outra funo, tornando-a incorreta (em um exemplo dado posteriormente
nestas notas, duas chamadas a funo soma_y() com o mesmo argumento (zero) produz resultados diferentes, 100 e 300).
Quando variveis globais so utilizadas, deve ser dado a elas nomes descritivos e um breve comentrio
qual a finalidade da varivel e quais funes a acessam.
Neste curso, voc utilizar variveis globais SOMENTE QUANDO FOR DADO PERMISSO PARA
FAZ-LO. Caso contrrio, no permitido utiliz-las (ou seja, sero descontados pontos).
Escopo de Variveis
Como j discutimos anteriormente, uma varivel uma abstrao de dados que ns usamos em um
programa. A varivel representa um endereo de memria onde os valores so armazenados. Durante a
execuo do programa, valores diferentes poder ser armazenados neste endereo. Quando uma varivel
definida, o nome da varivel atrelada a um endereo especfico na memria. At este momento, j
discutimos o que o nome de uma varivel, seu endereo, tipo e valor. Outra caracterstica que apresentaresmo agora o escopo. O escopo de uma varivel refere-se a parte do programa onde podemos utilizar a
varivel. Em outras, palavras, uma varivel visvel dentro do seu escopo.
O escopo de uma varivel local a funo na qual ela definida. Os parmetros formais de uma funo
tambm so tratados como variveis locais.
O escopo de uma varivel global a poro do programa depois da definio da varivel global (a partir
do ponto onde ela definida at o final do programa).
Se o nome de uma varivel global idntico a uma varivel local de uma funo, ento dentro desta
funo em particular, o nome refere-se a varivel local. (Embora tais conflitos devem ser evitados para
evitar confuso).
Por exemplo, considere o seguinte programa:
int valor = 3;

/* definicao da variavel global */

int main()
{
/* definicao local de valor */
int valor = 4;
printf("%d\n", valor);
}
50

A sada do programa acima ser 4 j que valor refere-se a definio local.


Considere outro exemplo:
#include <stdio.h>
int soma_y(int);
int soma_yy(int);
int y = 100;
/* variavel global
main(void)
{
int z = 0;

*/

/* variavel local */

printf("%d\n", soma_y(z));
printf("%d\n", soma_yy(z));
printf("%d\n", soma_y(z));
}
int soma_y(int x)
{
return x + y;
/* x e variavel local, y e global */
}
int soma_yy(int x)
{
y = 300;
/* y e variavel global */
return x + y;
/* x e variavel local */
}
Vamos seguir a execuo deste programa. Primeiro, a varivel global y criada e inicializada com 100.
Ento, a execuo da funo main() comeca: alocado espao na memria para a varivel local z. Esta
varivel inicializada com 0. Considere a primeira sentena printf():
printf("%d\n", soma_y(z));
Esta uma chamada para a funo da biblioteca padro printf(). Os parmetros reais desta chamada
so o string "%d\n" e a expresso soma_y(z). A ltima expresso a chamada da funo soma_y().
O valor desta expresso o resultado retornado por soma_y(). Qual o resultado? A funo soma_y
chamada com o parmetro real z. Como z = 0, este o valor que ser passado para a funo soma_y;
o 0 copiado para o parmetro formal x da funo soma_y(). Portanto, durante a excuo da primeira
chamada a funo soma_y(), o valor da expresso x + y ser 0 + 100, que 100. Portanto, o valor
da primeira chamada soma_ y(z) 100, e este nmero ser impresso com o primeiro printf() em
main(). Agora considere a segunda sentena:
printf("%d\n", soma_yy(z));
Quando a funo soma_yy(z) chamada, o valor de z (a varivel local z) ainda 0, portanto
novamente 0 copiado para o parmetro formal int x da funo soma_yy. Quando a execuo de
soma_yy() comea, ela primeiro troca o valor da varivel global y para 300 e ento retorna o valor de x
+ y, que neste caso 0 + 300. Portanto, o valor desta chamada a soma_yy(z) 300, e este nmero
ser impresso pelo segundo printf() em main().
Por ltimo, considere a terceira sentena:
51

printf("%d\n", soma_y(z));
Quando a funo soma_y(z) chamada, o valor de z ainda 0, portanto, 0 copiada para o parmetro
formal int x da funo soma_y(). Quando soma_ y() executada pela segunda vez, a varivel
global y foi modificada para 300, portanto o valor de x + y 0 + 300. Portanto, o valor da chamada
soma_yy(z) 300, e este nmero ser impresso pelo terceiro printf() em main().
Portanto, a sada da execuo deste programa ser
100
300
300
Neste exemplo, o escopo da varivel global y o programa todo.
O escopo da varivel local z, definida dentro de maio o corpo da funo main. O escopo do parmetro
formal x da funo soma_y o corpo de soma_y. O escopo do parmetro formal x da funo soma_yy
o corpo de soma_yy.

13.1

Outro exemplo

Aqui apresentamos um exemplo de uma funo mais complicada. Esta funo calcula a raiz quadrada
inteira de um nmero (o maior inteiro menor ou igual a raiz quadrada do nmero).
Este programa usa o algoritmo divide e calcula mdia (uma aplicao do mtodo de Newton). Ele
executa o seguinte:

Dado x, achar x computando sucessivamente


(

an =

se n = 0

x
+an1
an1

Os valores de an convergem para

caso contrrio

para todo n  N

x a medida que n cresce.

Para achar a raiz quadrada inteira, este algoritmo repetido at que


a2n x < (an + 1)2
Por exemplo, para achar a raiz quadrada inteira de 42 (usando diviso inteira que trunca a parte fracional
do nmero)
a0 = 1, a1 = (42/1 + 1)/2 = 21, a2 = (42/21 + 21)/2 = 11, a3 = (42/11 + 11)/2 = 7, a4 =
(42/7 + 7)/2 = 6.
Uma vez que a24 = 62 = 36 42 < (a4 + 1)2 = 72 = 49, o processo termina e a resposta 6.
(No necessrio voc entender por que este algoritmo funciona portanto no se preocupe se no
conseguir entend-lo)

52

int raizInteira(int);

/* prototipo */

/**************************************************************
* function: raizInteira(x)
dado x, retorna a raiz quadrada inteira de x
* acao:
inteiro positivo x
* in:
out:
raiz quadrada inteira de x
*
* suposicoes: x >= 0
metodo de dividr e calcular media: comecando com
* algoritmo:
um palpite de 1, o proximo palpite e calculado como
*
(x/palpite_ant + palpite_ant)/2. Isso e repetido
*
ate que palpite^2 <= x < (palpite+1)^2
*
***************************************************************/
int raizInteira(int x)
{
int palpite = 1;
/* Continue ate que o palpite esteja correto */
while (!(x >= palpite*palpite && x < (palpite+1)*(palpite+1))) {
/* Calcula proximo palpite */
palpite = (x/palpite + palpite) / 2;
}
return palpite;
}

Note que usando a lei de DeMorgan, podemos re-escrever a expresso teste do while em uma forma
equivalente:
x < palpite * palpite || x >= (palpite + 1) * (palpite + 1)
Deve estar claro neste ponto a diferenca entre ao e algoritmo. Uma pessoa que quer usar esta funo
precisa saber somente a ao, no o algoritmo. tambm importante especificar os dados que so esperados
pela funo e retornados por ela para outras pessoas poderem us-la. As suposies devem esclarecer as
restries da funo sobre quando a funo pode falhar ou produzir resultados errados. Neste caso, um
nmero negativo produziria um erro, j que nmeros negativos no possuem raiz quadrada.
No h necessidade de ir em muitos detalhes em qualquer parte da documentao da funo. Embora ela
deva conter informao suficiente para que algum (que no possa ver o cdigo) saber utiliz-la. Detalhes
sobre implementao e detalhes menores sobre o algoritmo devem ser colocados como comentrios no
prprio cdigo.

53

14

Estruturas de Repetio

A linguagem C possui comandos para repetir uma sequncia de instrues. Estas estruturas de repetio,
tambm conhecidas como laos (do ingls loops). A principal construo que veremos o while2

14.1

O comando de repetio while

O comando de repetio while tem duas partes: a expresso de teste e o corpo da repetio. O formato do
while :
while (expresso teste)
corpo da repetio
A expresso teste inicialmente avaliada para verificar se o lao deve terminar. Caso a expresso seja
verdadeira (isto , diferente de 0 (zero)), o corpo da repetio executado. Depois desta execuo, o
processo repetido a partir da expresso teste. O corpo do lao, por sua vez, pode ser uma sentena simples
ou composta.

O exemplo abaixo mostra o uso do comando de repetio while:


int contador = 0;
while( contador < 5 )
{
printf( "contador = %d\n", contador);
contador += 1;
}
printf("ACABOU !!!!\n");
Sada:
contador = 0
contador = 1
contador = 2
contador = 3
contador = 4
ACABOU !!!!
2

Existem outras estruturas de repetio: for (seo 15.1) e do ... while (seo 15.3).

54

Neste exemplo, a expresso de teste contador < 5, e o corpo do lao a sentena printf().
Se examinarmos cuidadosamente este exemplo, veremos que a varivel contador inicializada com
0 (zero) quando definida. Depois disso, a expresso de teste verificada e, como 0 < 5 verdadeiro,
o corpo da repetio executado. Assim, o programa imprime contador = 0, e incrementa contador de
um (atravs do ps-decremento indicado no argumento de printf()). Em seguida, a expresso de teste
verificada novamente e todo o processo se repete at que contador seja 4 e contador = 4 seja impresso.
Depois disso, contador incrementado para 5 e o teste executado. Mas desta vez, 5 < 5 falso, ento
o lao no continua. A execuo do programa continua na sentena que segue o lao (no caso, imprimir a
frase ACABOU !!!).
Aps a execuo do while, a varivel contador tem valor 5.
No exemplo acima, h uma sentena simples no corpo da repetio. Quando este for definido por uma
sentena composta (bloco), no se deve esquecer de usar as chaves ({ e }) para delimitar o bloco da sentena
composta.
O exemplo seguinte mostra um uso mais apropriado do comando while: Em situaes onde o nmero
de repeties no conhecido antes do inico do comando while:
Exemplo 1: Este programa pede nmeros ao usurio at que a soma de todos os nmeros digitados for
pelo menos 20.
#include <stdio.h>
main( ){
int total = 0, num;
while( total < 20 ) {
printf( "Total = %d\n", total );
printf( "Entre com um numero: " );
scanf( "%d", &num );
total += num;
}
printf( "Final total = %d\n", total );
}
Exemplo de sada:
Total
Entre
Total
Entre
Total
Entre
Final

= 0
com um numero: 3
= 3
com um numero: 8
= 11
com um numero: 15
total = 26

Inicialmente, dado o valor 0 varivel total, e o teste verdadeiro (0 < 20). Em cada iterao, o total
impresso e o usurio digita um nmero que somado a total. Quanto total for maior ou igual a 20, o teste
do while torna-se falso, e a repetio termina.
55

14.2

Estilo de formatao para estruturas de repetio

A regra principal ser consistente. Assim, seu programa ser mais legvel.
14.2.1

Colocao das chaves

H trs estilos comuns de colocar as chaves:


while (expressao)
{
sentenca;
}
while (expressao)
{
sentenca;
}
while (expressao) {
sentenca;
}
APENAS UM DESTES ESTILOS deve ser consistentemente usado para as sentenas for, while e do ...
while. Use o estilo com o qual voc se sentir mais confortvel.

14.2.2

Necessidade ou no das chaves

Foi mencionado anteriormente que as chaves ({ e }) podem ser omitidas quando o corpo da repetio contiver
apenar uma sentena. Por exemplo:
while( i < 5 )
i += 1;
Embora as chaves possam ser omitidas, h uma nica razo para coloc-las sempre:
while( i < 5 ) {
i += 1;
}
Quando voc adicionar algo ao programa, voc poder adicionar uma sentena para um lao com apenas
uma sentena. Se voc fizer isso, vital que voc tambm adicione chaves. Se voc no fizer isso, a segunda
sentena do lao no ser considerada como parte do lao. Por exemplo:
while( i < 5 )
i += 1;
j += 1;
na verdade o mesmo que:
while( i < 5 )
i += 1;
j += 1;
56

enquanto a inteno era na realidade:


while( i < 5 ) {
i += 1;
j += 1;
}
14.2.3

Uso de espao em branco

A outra questo de formato se deve ser colocado um espao em branco depois do while e antes do abre
parnteses ((). Por exemplo:
while (i<5)
ou
while (i<5)
ou
while( i < 5 )
Isto tambm uma escolha pessoal. Porm seja consistente em sua escolha !
14.2.4

Laos aninhados

possvel colocar um lao dentro de outro (lao aninhado).


Exemplo 2:
#include <stdio.h>
int main( ){
int linha, coluna;
linha = 1;
while (linha < 5)
{
coluna = 1;
while (coluna < 5)
{
printf( "%3d", linha * coluna );
coluna += 1;
}
linha += 1;
}
printf( "\n" );
}
Sada:

57

1
2
3
4

2 3 4
4 6 8
6 9 12
8 12 16

No exemplo acima, para cada iterao do lao externo, o lao interno imprime uma linha com nmeros
e depois pula de linha.
Exemplo 3: Este exemplo parecido com o anterior, exceto que o printf() colocado dentro do lao
interno. Como era de se esperar uma nova linha impressa aps cada valor ao invs de ser depois de 4
valores.
#include <stdio.h>
int main( ){
int linha, coluna;
linha = 1;
while (linha < 5)
{
coluna = 1;
while (coluna < 5)
{
printf( "%3d", linha * coluna );
printf( "\n" );
coluna += 1;
}
linha += 1;
}
}
Sada:
1
2
3
4
2
4
6
8
3
6
9
12
4
8
12
16
58

Exemplo 4:
#include <stdio.h>
int main( ){
int linha, coluna;
printf("\n");
linha = 1;
while (linha < 8)
{
printf( "\t" );
coluna = 1;
while (coluna < linha)
{
printf( "*" );
coluna += 1;
}
printf( "\n" );
linha += 1;
}
}
Sada:

*
**
***
****
*****
******
*******
********

59

15

Outras Estruturas de Repetio

A linguagem C possui outras estruturas de repetio alm do while (seo 14.1). So elas o for e o
do...while.

15.1

A Estrutura de Repetio for

Considere o lao while abaixo:


int i;
i = 0; /* valor inicial da varivel de controle da repetio */
while (i <= 10) /* Testa varivel de controle para saber se haver
repetio ou no do corpo do "while" */
{
....
....
i += 1; /* expresso de incremento da varivel de controle da
repetio */
}
Este lao pode ser expresso de forma diferente utilizando a estrutura de repetio for. A estrutura acima
pode ser expressa de forma equivlente com um for da seguinte forma:
int i;
for (i = 0; i <= 10; i += 1)
{
....
....
}

Como pode-se observar, h 4 partes no lao for: inicializao, expresso de teste, expresso de incremento e o corpo do lao. O formato do lao for :
for (inicializao; expresso de teste; incremento){
corpo da repetio
}
A inicializao executada uma nica vez no incio do lao. A expresso teste ento avaliada para
verificar se o lao deve terminar. Caso a expresso seja verdadeira (isto , diferente de Zero), o corpo da
repetio executado. Depois desta execuo, a expresso de incremento executada e o processo repetido
a partir da expresso teste. O corpo da repetio, por sua vez, pode ser uma sentena simples ou composta.

60

Veja abaixo um exemplo simples do lao for:


int contador;
for( contador = 0; contador < 5; contador += 1 )
printf( "contador = %d\n", contador );
printf{"ACABOU !!!!\n");
Sada do programa:
contador = 0
contador = 1
contador = 2
contador = 3
contador = 4
ACABOU !!!!
Se voc examinar cuidadosamente este exemplo, poder ver precisamente o que est acontecendo. Primeiro, a inicializao executada, que a sentena contador = 0. Isso modifica o valor da varivel contador para 0. Ento, o teste executado. como 0 < 5 verdadeiro, o lao continua. Assim, o corpo da
repetio executado, imprimindo a primeira linha da sada, contador = 0. Depois disso, o incremento
executado, que a sentena contador++, que altera o valor da varivel contador para 1.
Esta a 1a iterao do lao. Ento, o teste executado novamente (como 1 < 5 verdadeiro, o lao
continua), o corpo da repetio mostra contador = 1, e contador incrementado novamente.
Este processo continua at que contador seja 4 e contador = 4 seja impresso. Depois disso, contador
incrementado para 5 e o teste executado. Mas desta vez, 5 < 5 falso, ento o lao no continua. A
execuo do programa continua na sentena que segue o lao (no caso, imprimir a frase ACABOU !!!).
Aps a execuo do lao, a varivel contador tem valor 5.
Ao invs de usar o teste contador < 5, voc poderia tambm ter usado a expresso contador <= 4. O
resultado seria o mesmo. Use a expresso que voc preferir. Outra expresso que tambm poderia ter sido
usada contador != 5. Porm esta expresso torna o programa menos legvel (no to evidente que o valor
61

de contador est sendo incrementado at atingir o valor 5). Alm disso, isso poderia causar problemas se
mudssemos a inicializao para um valor maior que 5. Por exemplo, se a inicializao for contador = 25
e a expresso teste for contador != 5 o lao nunca terminaria, pois o contador comea com 25 e a cada
iterao o valor incrementado, o que nunca tornaria o teste falso.
Tambm poderamos ao invs de usar contador += 1 como a expresso de incremento, usar ++contador,
contador++ e contador = contador + 1. O resultado seria o mesmo (neste caso, o uso de ps- e princremento no faz diferena).
Se voc quisesse incrementos de dois, voc poderia escrever contador += 2 (ou contador = contador + 2).
15.1.1

Diversas sentenas dentro de um lao

Como no comando while, o corpo da repetio pode ser definido por uma sentena simples ou composta.
No caso de uma sentena composta (bloco), no se deve esquecer de usar as chaves ({ e }) para delimitar o
bloco da sentena composta.
Em um for tambm podemos ter mais de uma expresso de inicializao ou incremento. Nestes caso,
elas devem ser separadas por vrgula (,) o que ilustrado no exemplo abaixo:
Exemplo 1:
#include <stdio.h>
#include <stdio.h>
main( void ){
int contador, total;
for( contador = 0, total = 0; contador < 10; contador += 1 ) {
total += contador;
printf( "contador = %d, total = %d\n", contador, total );
}
}
Sada:
contador
contador
contador
contador
contador
contador
contador
contador
contador
contador

=
=
=
=
=
=
=
=
=
=

0,
1,
2,
3,
4,
5,
6,
7,
8,
9,

total
total
total
total
total
total
total
total
total
total

=
=
=
=
=
=
=
=
=
=

0
1
3
6
10
15
21
28
36
45

No exemplo acima, contador = 0, total = 0 a inicializao, contador < 10 a expresso teste, e


contador += 1 a expresso de incremento.
Exemplo 2: Um programa que imprime todos os nmeros entre 30 e 5 (nesta ordem) divisveis por 3, e
no final imprime sua soma.
62

#include <stdio.h>
main( ){
int i, soma;
soma = 0;
for( i = 30; i >= 5; i -= 1 ){
if( (i % 3) == 0 ){
printf( "\t%2d\n", i );
soma += i;
}
}
printf( "\t soma = %d\n", soma );
}
Sada do programa:
30
27
24
21
18
15
12
9
6
soma = 162

15.2

Usando while e for

Embora qualquer lao possa ser escrito usando while ou for, a escolha baseada principalmente no estilo.
Por exemplo, se o lao precisa de uma inicializao e um incremento, ento o for geralmente usado. No
caso em que o nmero de repeties no pr-determinado em geral usa-se o while.
Como o comando for:
for( inicializacao; teste; incremento )
sentenca;

equivalente a:
inicializacao;
while( teste ) {
sentenca;
incremento;
};
voc pode escolher o que preferir, a princpio.

63

15.3

A Estrutura de Repetio do...while

H outro comando de repetio em linguagem C . O do...while bastante parecido com while, com a
diferena que a expresso de teste avaliada DEPOIS que o corpo da repetio executado.
O formato do do...while :
do
corpo da repetio
while (expresso teste)

O exemplo abaixo usa o do...while:


int contador = 0;
do {
printf( "contador = %d\n", contador );
contador += 1;
} while( contador < 5 );
printf("ACABOU !!!!\n");
A execuo deste programa idntico ao primeiro exemplo mostrado para o comando while, com a
expresso de teste mudada para o final.
Sada:
contador = 0
contador = 1
contador = 2
contador = 3
contador = 4
ACABOU !!!!
O do...while usado quando o corpo da repetio deve ser executado pelo menos uma vez. Um exemplo
comum disto o processamento da entrada de um programa.
Exemplo 3: Neste exemplo, o teste do lao baseado no valor digitado pelo usurio. O lao deve ser
executado pelo uma vez antes que o teste sobre o valor seja executado.
#include <stdio>
main( ){
64

int num;
printf( "Entre com um numero par:\n" );
do{
scanf( "%d", &num );
} while( num % 2 != 0 );
printf( "Obrigado.\n" );
}
Exemplo de execuo:
Entre com um numero par:
3
1
5
4
Obrigado.
Neste caso, o valor da varivel num digitado pelo usurio. Depois disso, o teste executado para
verificar se o nmero par (o teste num % 2 != 0 falso se num par j que o resto da diviso de
qualquer nmero par por 2 zero).
possvel escrever o programa acima usando while:
#include <stdio>
main( ){
int num;

/* Atribui um numero impar a

num */

printf( "Entre com um numero par:\n" );


num = 1;
while( num % 2 != 0 ){
scanf( "%d", &num );
}
printf( "Obrigado.\n" );
}
O problema com este programa que a varivel num deve ser inicializada com um valor que torne
o teste do lao verdadeiro. Neste exemplo, simples encontrar tal valor. Para uma expresso teste mais
complicada, isso pode no ser to fcil.

65

16

Ativao de funo

Uma funo comea sua execuo assim que for chamada. Cada execuo da funo chamada de ativao
da funo. Como j mencionamos em notas de aula anteriores, variveis locais so locais a ativao da
funo: cada ativao possui suas prprias variveis locais. No comeo da ativao, memria alocada para
as variveis locais e no final da execuo, elas so dealocadas.
Definies de funes em C no podem ser aninhadas, mas ativaes de funo podem: uma funo,
digamos A, pode chamar uma outra funo, digamos B (dizemos que A chama B). Nos referimos a A como
o chamador e B como a funo chamada.
O que acontece quando uma funo chama outra (quando A chama B)? Um registro especial, chamado
registro de ativao criado. A informao neste registro necessria para a ativao da funo chamada e
para a reativao do chamador depois que a execuo da funo chamada termina.
1. C usa chamada-por-valor, ou seja, o chamador avalia as expresses que so os parmetros reais e passa
seus valores para a funo chamada.
2. A informao necessria para reiniciar a execuo da funo chamadora guardada em um registro
de ativao. Tal informao inclui o endereo da instruo do chamador que ser executada depois
que a funo chamada termine.
3. A funo chamada aloca espao na memria para suas variveis locais.
4. O corpo da funo chamada executado.
5. O valor retornado para a funo chamadora atravs de um return colocado em um lugar especial
para que a funo chamadora possa encontr-lo. O controle retorna a funo chamadora.
O fluxo de controle atravs de ativao de funes da forma ltimo-que-entra-primeiro-que-sai. Se A
chama B e B chama C: A ativado primeiro, ento B ativado (um registro de ativao para A chama B
criado e armazenado, A temporariamente suspenso), ento C ativado (um registro de ativao de B
chama C criado e armazenado, A e B so suspensos); C o ltimo a iniciar execuo, mas o primeiro a
terminar (ltimo-a-entrar-primeiro-a-sair). Depois que C termina, B reativado. O registro de ativao B
chama C foi criado por ltimo, mas o primeiro a ser destrudo (no momento que o controle retornada para
B). Depois que B termina, A reativado. O registro de ativao correspondente a A chama B destrudo
no momento em que o controle retorna para A.

17

Mais sobre funes: Quando return no suficiente

Considere o programa abaixo que pede ao usurio dois inteiros, armazena-os em duas variveis, troca seus
valores, e os imprime.
#include <stdio.h>
main(void)
{
int a, b, temp;
printf("Entre dois numeros: ");
scanf("%d %d", &a, &b);
printf("Voce entrou %d e %d\n", a, b);
66

/* Troca a com b */
temp = a;
a = b;
b = temp;
printf("Trocados, eles sao %d e %d\n", a, b);
}

Aqui est um exemplo de execuo do programa:


Entre dois numeros: 3 5
Voce entrou 3 e 5
Trocados, eles sao 5 e 3
O seguinte trecho do programa executa a troca de valores das variveis a e b:
temp = a;
a = b;
b = temp;
possvel escrever uma funo que executa esta operao de troca? Considere a tentativa abaixo de escrever
esta funo:
#include <stdio.h>
void troca(int, int);
void troca(int x, int y)
{
int temp;
temp = x;
x = y;
y = temp;
}
main(void)
{
int a, b;
printf("Entre dois numeros: ");
scanf("%d %d", &a, &b);
printf("Voce entrou com %d e %d\n", a, b);
/* Troca a e b */
troca(a, b);
printf("Trocados, eles sao %d e %d\n", a, b);
}
67

Se voc executar este programa, ver que ele no funciona:


Entre dois numeros: 3 5
Voce entrou 3 e 5
Trocados, eles sao 3 e 5
Como voc j viu nas notas anteriores, em C os argumentos so passados por valor. Uma vez que
somente os valores das variveis so passados, no possvel para a funo troca() alterar os valores
de a e b porque troca() no sabe onde na memria estas variveis esto armazenadas. Alm disso,
troca() no poderia ser escrito usando a sentena return porque s podemos retornar um valor (no
dois) atravs da sentena return.

17.1

Usando ponteiros

A soluo para o problema acima ao invs de passar os valores de a e b, passar o endereo das variveis
a e b. Desta forma, troca() saberia em que endereo de memria escrever, portanto poderia alterar os
valores de a e b.
Lembre-se que em C a cada varivel est associado: (i) um nome; (ii) um tipo; (iii) um valor; e (iv) um
endereo. Assuma que existam as seguintes definies de variveis.
int i = 5;
char c = G;
Na memria, eles podem estar armazenados da forma abaixo:

A varivel inteira i est armazenada no endereo 1342. Ela usa dois bytes de memria (quando um
objeto usa mais de um byte, seu endereo onde ele comea neste caso, 1342 e no 1343). A varivel do
tipo char c est armazenada no endereo 1346 e usa um byte de memria. O compilador que controla
do local de armazenamento destas variveis em memria.

17.2

O operador de endereo (&)

Ns podemos usar o operador de endereo para determinar o endereo de uma objeto na memria. Este
operador s pode ser usado com lvalues (objetos que podem estar no lado esquerdo de uma atribuio,
como no caso de variveis) porque lvalues tem um endereo alocado na memria.
Por exemplo, no exemplo acima, poderamos usar o operador de endreo como nas expresses abaixo:
&i tem valor 1342
&c tem valor 1346
68

17.3

Tipo ponteiro

Em C , uma varivel que contm um endereo de memria uma varivel do tipo ponteiro. Um valor, que
um endereo (como &a) um valor de ponteiro. Quando um ponteiro (a varivel) contm um determinado
endreo, dizemos que ele aponta para o endereo de memria (ou se este endereo de memria for associado
a uma varivel, dizemos que ele aponta para esta varivel).
H um tipo distinto de ponteiro para cada tipo bsico C (como int, char e float). verdade que
todos os endereos tem o mesmo tamanho (em Dev-C++ so 5 bytes), mas ns tambm precisamos saber
algo sobre o que armazenado no endereo de memria apontado (quantos bytes ocupa e como os bytes
devem ser interpretados). Por exemplo, um tipo ponteiro usado para apontar para inteiros chamado
ponteiro para int e isso denotado por um *. Variveis do tipo ponteiro para int so usados para armazenar
endereos de memria que contem valores do tipo int.
Por exemplo, dadas as definies de i e c acima, ns podemos definir duas novas variveis pi e pc,
ambos do tipo ponteiro.
int *pi;
char *pc;
Nesta definio no inicializamos as variveis com nenhum valor. Podemos inicializ-las com:
pi = &i;
pc = &c;
Depois destas atribuies, o valor de pi seria 1342, e o valor de pc seria 1346.
Note que nesta definio da varivel int *pi, pi o nome da varivel e int * o tipo de pi
(ponteiro para int).

17.4

O operador de dereferncia: *

Quando um ponteiro aponta para um endereo de memria, a operao para acessar o contedo do endereo
apontado chamado de dereferncia. O operador unrio * usado para fazer a dereferncia. Note que este
uso do smbolo * no tem nada a ver com o smbolo de multiplicao. Usando os exemplos anteriores, *pi
o objeto apontado por pi.
*pi tem valor 5
*pc tem valor G
Como um pointer dereferenciado (tais como *pi ou *pc) refere-se a um objeto na memria, ele pode
ser usado no s como valor, mas tambm como um lvalue. Isto significa que um pointer dereferenciado
pode ser usado no lado esquerdo de uma atribuio. Veja alguns exemplos:
printf("Valor= %d, Char = %c\n", *pi, *pc);
*pi = *pi + 5;
*pc = H;
*pi no lado esquerdo do = refere-se ao endereo de memria para o qual pi aponta. *pi no lado direito
do = refere-se ao valor armazenado no endereo apontado por pi. A sentena *pi = *pi + 5; faz com
que o valor armazenado no endereo apontado por pi seja incrementado de 5. Note que o valor de *pi
muda, no o valor de pi.
Neste exemplo, os valores das variveis i e c poderiam ter sido alterados sem a utilizao de ponteiros
da seguinte forma:
printf("Valor = %d, Char = %c\n", i, c);
i = i + 5;
c = H;
Os exemplos acima ilustram como uma varivel pode ser acessada diretamente (atravs do seu nome)
ou indiretamente (atravs de um ponteiro apontando para o endereo da varivel).
69

17.5

Ponteiros como argumentos de funes

Nos exemplos acima, pode parecer que ponteiros no so teis, j que tudo que fizemos pode ser feito sem
usar ponteiros. Agora, considere novamente o exemplo da funo troca(). Quando a e b so passados
como argumentos para troca(), na verdade, somente seus valores so passados. A funo no podia
alterar os valores de a e b porque ela no conhece os endereos de a e b. Mas se ponteiros para a e b
forem passados como argumentos ao invs de a e b, a funo troca() seria capaz de alterar seus valores;
ela saberia ento em que endereo de memria escrever. Na verdade, a funo no sabe que os endereos
de memria so associados com a e b, mas ela pode modificar o contedo destes endereos. Portanto,
passando um ponteiro para uma varivel (ao invs da varivel), habilitamos a funo a alterar o contedo
destas variveis da funo chamadora.
Uma vez que endereos de variveis so do tipo ponteiro, a lista de parmetros formais da funo deve
refletir isso. A definio da funo troca() deveria ser alterada, e a lista de parmetros formais deve
ter argumentos no do tipo int, mas ponteiros para int, ou seja, int *. Quando chamamos a funo
troca(), ns no passamos como parmetros reais a e b, que so do tipo int, mas &a e &b, que so
do tipo int *. Dentro da funo troca() dever haver mudanas tambm. Uma vez que agora os
parmetros formais so ponteiros, o operador de dereferncia, *, deve ser usado para acessar os objetos.
Assim, a funo troca() capaz de alterar os valores de a e b remotamente.
O programa abaixo a verso correta do problema enunciado para a funo troca():
#include <stdio.h>
void troca(int *, int *);
/* function troca(px, py)
troca os valores inteiros apontados por px e py
* acao:
entrada:
apontadores
px e py
*
saida:
valor
de
px
e *py trocados
*
*
* suposicoes: px e py sao apontadores validos
primeiro guarda o primeiro valor em um temporario e troca
* algoritmo:
*/
void troca(int *px, int *py)
{
int temp;
temp = *px;
*px = *py;
*py = temp;
}
main(void)
{
int a, b;
printf("Entre dois numeros: ");
scanf("%d %d", &a, &b);
printf("Voce entrou com %d e %d\n", a, b);
/* Troca a e b -- passa enderecos */
troca(&a, &b);
70

printf("Trocados, eles sao %d e %d\n", a, b);


}

A sada deste programa :


Entre dois numeros: 3 5
Voce entrou com 3 e 5
Trocados, eles sao 5 e 3
Basicamente, se a funo precisa alterar o valor de uma varivels da funo chamadora, ento passamos
o endereo da varivel como parmetro real, e escrevemos a funo de acordo, ou seja, com um ponteiro
como parmetro formal.

17.6

Precedncia de operadores

A precedncia dos operadores * e & alta, a mesma que outros operadores unrios. A tabela 4 apresenta a
precedncia de todos os operadores vistos at agora.
Operador

Associatividade

()
!
*
+
<
==
&&
||
=

esquerda para direita


direita para esquerda
esquerda para direita
esquerda para direita
esquerda para direita
esquerda para direita
esquerda para direita
esquerda para direita
direita para esquerda

- ++
/ %
<= >
!=

+=

-=

--

* & (cast) (unrios)

>=

*=

/=

%=

Tabela 4: Precedncia e associatividade de operadores

71

18

Arrays

Considere o seguinte programa. Este programa pede ao usurio notas de 4 estudantes, calcula a mdia e
imprime as notas e a mdia.
int main()
{
int nota0, nota1, nota2, nota3;
int media;
printf("Entre a nota
scanf("%d", &nota0);
printf("Entre a nota
scanf("%d", &nota1);
printf("Entre a nota
scanf("%d", &nota2);
printf("Entre a nota
scanf("%d", &nota3);

do estudante 0: ");
do estudante 1: ");
do estudante 2: ");
do estudante 3: ");

media = (nota0 + nota1 + nota2 + nota3) / 4;


printf("Notas: %d %d %d %d\n", nota0, nota1, nota2, nota3);
printf("Media: %d\n", media);
}
Este programa bem simples, mas ele tem um problema. O que acontece se o nmero de estudantes
aumentar ? O programa ficaria muito maior (e feio !!). Imagine o mesmo programa se existissem 100
estudantes.
O que precisamos uma abstrao de dados para agrupar dados relacionados. Este o objetivo de arrays
em C .
Um array uma coleo de um ou mais objetos, do mesmo tipo, armazenados em endereos adjacentes
de memria. Cada objeto chamado de elemento do array. Da mesma forma que para variveis simples,
damos um nome ao array. O tamanho do array o seu nmero de elementos. Cada elemento do array
numerado, usando um inteiro chamado de ndice. Em C , a numerao comea com 0 e aumenta de um em
um. Assim, o ltimo ndice igual ao nmero de elementos do array menos um.
Por exemplo, podemos definir um array nota de tamanho 100 para armazenar as notas dos cem estudantes:
int nota[100];
Quando o compilador encontra esta definio, ele aloca 200 bytes consecutivos de memria (dois bytes
referente a cada int para cada nota). Cada nota pode ser acessada dando o nome do array e o ndice entre
colchetes: como nota[0] (para a primeira nota), nota[1] para a segunda nota, e assim por diantes, at
a ltima nota, nota[99].

18.1

Definindo arrays e acessando seus elementos

A definio de arrays muito parecida com a definio de variveis. A nica diferena que em array
necessrio especificar seu tamanho (quantos elementos ele tem).
Os colchetes [ e ] so usados na definio do tamanho, como mostra os exemplos a seguir:
int total[5];
float tamanho[42];
72

O primeiro exemplo um array de 5 inteiros (o tipo int) com o nome total. Como a numerao de
arrays comea com 0, os elementos da array so numerados 0, 1, 2, 3 e 4.
O segundo exemplo um array de 42 elementos do tipo float com ndices de 0 a 41.
Cada elemento do array total do tipo inteiro e pode ser usado do mesmo jeito que qualquer varivel
inteira. Para nos referirmos a um elemento do array, usamos colchetes tambm ([ e ]). O valor dentro
dos colchetes pode ser qualquer expresso do tipo inteiro. Quando um array definido, armazenamento
suficiente (bytes contnuos na memria) so alocados para conter todos os elementos do array.
O nome do array representa um endereo de memria3 constante que aponta para o incio do espao de
armazenamento (o primeiro byte do bloco de bytes). O array, como um todo, no tem um valor, mas cada
elemento individual tem um valor.
Note na tabela de precedncia abaixo que [ ] tem precedncia maior que todos os demais operadores.
Operador
()
!
*
+
<
==
&&
||
=
,

Associatividade

[]

->

- ++
/ %
<= >
!=

+=

-=

.
--

\&

>=

*=

/=

%=

esquerda para direita


direita para esquerda
esquerda para direita
esquerda para direita
esquerda para direita
esquerda para direita
esquerda para direita
esquerda para direita
direita para esquerda
esquerda para direita

Verifique se voc entende as sentenas do programa abaixo.


int i, x, sala, total[5];
float area;
float tamanho[42];
x = total[3];
i = 4;
total[i] = total[i-1] + total[i-2];
total[4]++;
tamanho[17] = 2.71828;
sala = 3;
area = tamanho[sala] * tamanho[sala];
scanf("%f", &tamanho[41]);
Agora, podermos reescrever o programa que calcula a mdia de uma classe de 4 alunos:
int main()
3

Este valor de endereo de memria conhecido em C como ponteiro. Este conceito ser visto com detalhes nos prximos
captulos.

73

{
int indice, nota[4], total = 0;
for (indice = 0; indice < 4; indice++) {
printf("Entre a nota do estudante %d: ", indice);
scanf("%d", &nota[indice]);
}
printf("Notas: ");
for (indice = 0; indice < 4; indice++) {
printf("%d ", nota[indice]);
total += nota[indice];
}
printf("\nMedia: %d\n", total/4 );
}
Exemplo de Sada:
Entre a nota do estudante
Entre a nota do estudante
Entre a nota do estudante
Entre a nota do estudante
Notas: 93 85 74 100
Media: 88

0:
1:
2:
3:

93
85
74
100

O programa consideravelmente mais curto. Note que um & ainda precede o elemento do array passado
para o scanf(). No necessrio usar parnteses porque [] tem maior precedncia que &.
O nico problema que ainda no fcil modificar o programa para cem alunos porque 4 est em vrios
pontos do programa. Ns podemos usar o #define para manter o tamanho do array como uma constante
simblica ao invs de utilizar uma constante numrica.
#define ESTUDANTES 4
int main()
{
int nota[ESTUDANTES], indice, total = 0;
indice = 0;
while (indice < ESTUDANTES) {
printf("Entre a nota do estudante %d: ",indice);
scanf("%d", &nota[indice]);
indice += 1; /* mesma coisa que "indice = indice + 1" */
}
printf("Notas: ");
indice = 0;
while (indice < ESTUDANTES) {
printf("%d ", nota[indice]);
total = total + nota[indice];
indice += 1; /* mesma coisa que "indice = indice + 1" */
}
printf("\nMedia: %d\n", total / ESTUDANTES );
}
74

18.2

Inicializao de arrays

Os arrays podem ser inicializados quando so definidos. Se o array no for inicializado, ento ele contem
valores indefinidos (tambm conhecidos como lixo).
Para inicializar um array, um valor para cada elemento deve ser especificado. Estes valores devem estar
entre chaves ({ e }) e so separados por vrgula (,). Alguns exemplos:
int valor[4] = { 1, 42, -13, 273 };
/* o tamanho do array pode ser omitido */
int peso[] = { 153, 135, 170 };
No primeiro exemplo, valor um array de 4 inteiros onde valor[0] e 1, valor[1] e 42,
valor[2] e -13, e valor[3] e 273.
Note que no segundo exemplo, o tamanho do array foi omitido. Neste caso, o compilador calcula o
tamanho como sendo o nmero de elementos listados. Quando um array definido, se ele no for inicializado, o tamanho do array deve ser especificado. Se o array for inicializado, o tamanho pode ser omitido. O
segundo exemplo acima equivalente a
int peso[3] = { 153, 135, 170 };
Se o tamanho no for omitido, o nmero de elementos presentes no deve exceder o tamanho. Se
exceder, o compilador gerar uma mensagem de erro. Se houver menos elementos na lista de inicializao,
ento os elementos dados so usados para inicializar os primeiros elementos do array. Qualquer elemento
no inicializado conter lixo.
Note que este tipo de inicializao s vlido no contexto onde o array definido. Uma sentena como a
seguinte produzir um erro do compilador, uma vez que arrays s podem ser inicializados quando definidos.
int erro[5];
erro = { 2, 4, 6, 8, 10 };

/* ISTO ESTA ERRADO */

H mais uma restrio na inicializao de um array. Os valores devem ser todos constantes nenhuma
varivel ou expresso permitida. O seguinte trecho de programa produz um erro porque um dos valores de
inicializao uma varivel:
int x = 21;
int yy[3] = { 1, 2, x };

18.3

/* ISTO ESTA ERRADO */

Verificao de Limite

Quando um array definido, alocado espao em memria para conter todos os elementos do array (no
mais). O tamanho do array dado explicitamente escrevendo o tamanho, ou implicitamente, inicializando o
array. Embora arrays tenham tamanhos especficos, possvel que um programa tente acessar endereos de
memria de elementos fictcios, ou seja, endereos de memria que no pertencem ao array. Isto acontece
quando usamos um ndice que no esteja entre 0 e n-1 para um array de tamanho n. O compilador no
gera nenhum aviso quando isto acontece. Quando executamos um acesso fora dos limites do array, o
resultado pode ser desastroso. Isto siginifica que o programa pode no fazer nada, cancelar a execuo,
travar o computador, entrar em um loop infinito, etc.
Se voc executar uma atribuio a um elemento do array fora do seu limite, voc estar escrevendo
em um endereo de memria que pode conter algo importante, destruindo-o. Em geral, erros como estes
so difceis de encontrar, j que o programa pode at executar, s que faz algo estranho. Se voc estiver
usando o Dev-C++ , voc poder ver uma mensagem como Esta aplicao violou a integridade do sistema
75

devido a execuo de uma instruo invlida e ser cancelada.. No entre em pnico !! Voc ter que
reinicializar o seu computador e examinar o seu programa cuidadosamente para achar acessos a array fora
do seu limite. claro que ao reinicializar o seu computador, voc perder todo o seu trabalho se no tiver
salvado antes. MORAL: depois que seu programa compilar com sucesso, salve o seu programa em disco
antes de execut-lo.
Por exemplo, considere o seguinte programa:
int main()
{
int ops[10], i;
/* Acesso fora dos limites quando i = 10 */
i = 0;
while (i <= 10)
{
ops[i] = 0;
i += 1;
}
}
Este programa conta de 0 a 10, inicializando cada elemento do array com 0. O problema ocorre quando
i tem o valor 10. Neste ponto, o programa coloca 0 em ops[10]. Isto pode produzir resultados indefinidos
(e desastrosos) embora o compilador no gere nenhum erro.

18.4

Arrays como argumentos

Para passar um array como argumento (com todos os seus elementos) passamos o nome do array. Considere
o exemplo abaixo.

Aqui est um exemplo de execuo deste programa


Entre um inteiro:
Entre um inteiro:
Entre um inteiro:
Entre um inteiro:
Entre um inteiro:
O maior e 85

73
85
42
-103
15

Em main() a chamada para array max() tem valor como seu argumento, que copiado para o
parmetro formal a, que um array de inteiros. Note que o tamanho no foi especificado, somente o nome
do array, a. Porm tambm correto incluir o tamanho (isto uma questo de estilo escolha o que voc
preferir):
int array_max(int a[TAMANHO])
{
...
}
A incluso do tamanho de um array unidimensional na definio da funo somente por razes de
legibilidade (para arrays multi-dimensionais, todas as dimenses exceto a primeira deve ser especificada).
76

At este ponto, parece que no h diferena entre passar uma varivel simples e um array como argumento para uma funo. Mas h uma diferena fundamental: QUANDO PASSAMOS O ARRAY COMO
ARGUMENTO, ALTERAES NO ARRAY FEITAS DENTRO DA FUNO ALTERAM O CONTEDO DO ARRAY PASSADO COMO PARMETRO REAL.
Note que isto no acontece quando uma varivel simples passada como argumento. Considere o
exemplo seguinte:

A sada deste programa :


x=10
v[0]=60 v[1]=70 v[2]=80
O valor da varivel x do programa principal no se altera porque como j vimos nas notas de aula 7,
quando a funo troca chamada, o valor do argumento real x avaliado, que 10, este valor copiado
para o parmetro formal a da funo troca e a funo ento executada. O parmetro a da funo
tratada como varivel local, portanto quando atribumos 20 a a, estamos atribuindo 20 a uma varivel
local. Terminada a funo, a execuo retorna ao programa principal, que imprime o valor de x, que no foi
alterado, ou seja, imprime x=10.
Quando a funo troca_vet chamada, o array v passado como argumento e copiado para o
parmetro formal vet. A funo ento executada, e os elementos do array so alterados para 60, 70,
80. Como mencionado anteriormente, quando passamos um array como parmetro, as alteraes feitas no
array dentro da funo alteram o array passado como parmetro. Portanto, quando a funo termina e a
execuo continua no programa principal com a impresso dos valores dos elementos de v, ser impresso
60, 70, 80, os novos valores alterados de dentro da funo troca_vet.
Vamos entender por que quando passamos s o nome do array como argumento as alteraes afetam
o array passado como parmetro real. Como j mencionamos anteriormente, quando um array definido,
como v no programa principal acima, alocado espao suficiente na memria para conter todos os elementos
do array. Na ilustrao abaixo, so alocados 6 bytes de memria a partir do endereo 1342 para conter
o array. O array como um todo no tem um valor, mas cada elemento do array tem (neste caso, foram
inicializados com 30, 40, 50). O nome do array, na verdade, contm o endereo onde comea o array,
neste caso, o endereo 1342.
Portanto, quando passamos o nome do array como argumento para uma funo estamos na realidade
passando como argumento o endereo de memria onde comea o array. No exemplo anterior, 1342
passado como argumento para o parmetro formal vet da funo troca_vet. Portanto, da mesma forma
que no caso da varivel simples, o valor de v, que o endereo 1342, copiado para o parmetro vet
de troca_vet. Ento, quando a funo troca_vet executada, vet um array de elementos do tipo
int que comea no endereo 1342. Quando atribumos o valor 60 a vet[0], estamos atribuindo 60 ao
primeiro elemento do array que comea no endereo 1342. Como este o mesmo endereo onde comea
o array v do programa principal, quando a funo troca_vet termina, o array v enxergar o valor dos
elementos do array que comea no endereo 1342, que foram alterados pela funo.
Quando passamos variveis simples como argumento para uma funo estamos passando somente o
valor da varivel, portanto, de dentro da funo no possvel saber qual o endereo da varivel para poder
alter-la.
Lembre-se que o endereo s passado para a funo quando passamos o array COMO UM TODO
(ou seja, o nome do array, sem ser indexado por um elemento). Se passarmos como argumento apenas um
elemento do array, o comportamento o mesmo que se passssemos uma varivel simples. Ou seja, o nome
do array indexado por um valor entre colchetes refere-se ao valor do elemento do array, enquanto o nome
do array sozinho refere-se ao endereo onde comea o array. Assim, no programa abaixo:

77

A sada do programa :
v[0]=30
v[0]=60 v[1]=70 v[2]=80
Outro exemplo: a funo inicializaArray abaixo inicializa todos os elementos do array valor
com um valor passado como argumento pelo programa principal.

Como as alteraes feitas por inicializaArray so vistas do programa principal, depois da funo
inicializaArray ser executada, no programa principal todos os elementos do array valor tero o
valor 42.

18.5

Exemplo: pesquisa linear de um array

Pesquisar (procurar) em um array um determinado valor (chamado de chave) um problema muito comum
em programao. Ele tem diversas aplicaes. Por exemplo, podemos pesquisar um array de notas para verificar se algum aluno tirou 100 na prova. H diversos algoritmos de pesquisa: cada um com suas vantagens
e desvantagens. Nestas notas de aula, discutiremos um algoritmo simples, chamado de pesquisa linear. A
pesquisa feita usando uma repetio e examinando cada elemento do array a cada repetio e comparando
o elemento com a chave que buscamos. A pesquisa termina quando um elemento do array que casa com
a chave encontrada, ou quando o array todo percorrido e a chave procurada no encontrada.
18.5.1

O Problema

Escreva uma funo pesquisa linear que tem como argumento de entrada: um array de inteiros a
ser pesquisado, o tamanho do array, e uma chave (um valor inteiro) a ser procurado. A funo retorna um
inteiro: o ndice do elemento do array (se a chave for achada) ou -1 caso contrrio.
1. Prottipo:
int pesquisa_linear(int [], int, int);
2. Definio:

78

18.6

Exemplo: somar os elementos de dois arrays

18.6.1

O Problema

Escrever uma funo que some dois arrays de floats, do mesmo tamanho. Dar o resultado em um terceiro
array.
1. Prottipo:
void soma_array( float [], float [], float [], int );
2. Definio de soma array():

18.7

Exemplo: Ordenao de um vetor - Verso 1

Um outro programa muito popular com arrays orden-lo de acordo com algum critrio. Por exemplo,
um array de inteiros pode ser ordenado em ordem crescente ou decrescente. O apresentado a seguir um
algortmo bsico e nem um pouco eficiente, denominado Select sort.
Ele usa o fato simples de comparar cada elemento de um array com o restante deste. Quando se acha o
menor, ocorre uma troca de valores entre o elemento sob anlise e o outro elemento do array que o menor.
Por exemplo, se comearmos com um array: 9 5 2 7 3 8 1 4 6, (o primeiro elemento 9 e o
ltimo elemento 6) isto o que acontece com os elementos do array depois de cada passagem sobre ele (e
consequente troca de valores):
passagem
~~~~
0 -->

conteudo do array depois da passagem


~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
9 5 2 7 3 8 1 4 6

1 -->

2 -->

3 -->

4 -->

5 -->

6 -->

7 -->

8 -->

Note que mesmo que se comessemos com um array ordenado de 9 elementos, ainda assim o algoritmo
dado faz 8 passagens sobre o array.
18.7.1

Prottipo da funo e definio

1. Prottipo
void ordena(int [], int);
79

2. Definicao

18.8

Exemplo: Ordenao de um vetor - Verso 2

O algoritmo abaixo ligeiramente melhor que o anterior e chamado Bubble sort. Ele bastante simples,
porm ainda no muito eficiente.
Basicamente, o algoritmo funciona da seguinte forma:
na primeira passagem sobre o array: comeando do ltimo elemento do array at o segundo elemento,
compare o valor de cada elemento com o valor do elemento anterior a ele. Se os elementos comparados
estiverem fora de ordem, trocar os seus valores. Depois que esta primeira passada terminar, o que
acontece que o menor elemento do array torna-se o primeiro elemento do array.
na segunda passagem pelo array: comeando com o ltimo elemento do array at o terceiro elemento,
compare o valor de cada elemento com o valor do elemento anterior a ele. Se os dois elementos
comparados estiverem fora de ordem, trocar os seus valores. Depois que esta passagem sobre o array
terminar, o segundo menor elemento do array ser o segundo elemento do array.
repetir a passagem sobre o array de maneira similar at que a ltima passagem ser simplesmente uma
comparao dos valores do ltimo elemento com o elemento anterior.
Por exemplo, se comearmos com um array: 9 8 7 6 5 4 3 2 1, (o primeiro elemento 9 e o
ltimo elemento 1) isto o que acontece com os elementos do array depois de cada passagem sobre ele (e
troca de valores adjacentes):
passagem
~~~~
1 -->

conteudo do array depois da passagem


~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1 9 8 7 6 5 4 3 2

2 -->

3 -->

4 -->

5 -->

6 -->

7 -->

8 -->

Note que, tambm aqui, mesmo que se comessemos com um array ordenado de 9 elementos, ainda
assim o algoritmo dado faz 8 passagens sobre o array.
Isto pode ser melhorado da seguinte forma: Antes de comear cada passagem, inicializamos uma varivel ordenado com 1. Se durante a passagem uma troca de valores ocorrer, trocamos o valor da varivel
para 0. Assim, se depois da passagem, o valor da varivel continuar sendo 1, isso significa que nenhuma
troca ocorreu e que o array est ordenado.

80

18.8.1

Algoritmo Bubble Sort otimizado

Enquanto o array nao estiver ordenado


1. inicializar ordenado com 1
2. comparar pares adjacentes do array
troque seus valores se estiver fora de ordem
ordenado = 0.
18.8.2

Prottipo da funo e definio

1. Prottipo
void ordena(int [], int);
2. Definicao

18.9

Comentrios Finais

Neste curso, um dos nicos lugares que veremos o nome do array sem estar indexado quando passamos
o array (como um todo) para uma funo. Para outras finalidades, veremos sempre o array indexado. Por
exemplo, o seguinte trecho de programa est errado:
int main(void){
int arr1[4] = {10, 20, 30, 40};
int arr2[4];
arr2 = arr1;

/* ERRADO: NO copia arr1 em arr2 */


/* tem que copiar elemento por elemento */

if( arr1 == arr2 ) /* ERRADO: NO podemos comparar arrays inteiros */


printf(X);
/* tem que comparar elemento por elemento */
}

81

19

Arrays Multidimensionais

Nas notas de aula anteriores, apresentamos arrays unidimensionais. Em C , possvel tambm definir arrays
com 2 ou mais dimenses. Eles so arrays de arrays. Um array de duas dimenses podem ser imaginado
como uma matriz (ou uma tabela).
Como voc deve ter imaginado, para definir e acessar arrays de dimenses maiores, usamos colchetes
adicionais ([ e ]). Por exemplo:
int tabela[3][5];
Define um array bidimensional chamado tabela que uma matriz 3 por 5 de valores do tipo int (15
valores no total). Os ndices da primeira dimenso vo de 0 a 2, e os ndices da segunda dimenso vo de 0
a 4.
Abaixo apresentamos um programa que imprime os elementos de um array bidimensional.
#include <stdio.h>
#define ALTURA 5
#define LARGURA 5
int main()
{
int x;
/* numero da coluna */
int y;
/* numero da linha */
int matriz [ALTURA] [LARGURA]; /* array 2-D [num_lins, num_cols] */

/* preenche a matriz com zeros


y = 0;
while(y < ALTURA)
{
x = 0;
while(x < LARGURA)
{
matriz[y][x] = 0;
x+=1;
}
y+=1;
}

*/

/* Imprime a matriz com zeros e a coordenada escolhida com 1 */


printf("\nEntre coordenadas na forma y,x (2,4).\n");
printf("Use valores negativos para sair do programa.\n");
printf("Coordenadas: ");
scanf("%d,%d", &y, &x);
while (x >= 0 && y >= 0)
{
matriz[y][x] = 1;
/* coloca 1 no elemento escolhido */
82

y = 0;
while (y < ALTURA)
/* imprime o array todo */
{
x = 0;
while (x < LARGURA)
{
printf("%d ", matriz[y][x] );
x += 1;
}
printf("\n\n");
y += 1;
}
printf("\n");
printf("Coordenadas: ");
scanf("%d,%d", &y, &x);
}
}
Neste exemplo, matriz um array bidimensional. Ela tem nmero de elementos igual a ALTURAxLARGURA,
sendo cada elemento do tipo int.
O exemplo abaixo preenche os elementos de um array bidimensional com os valores que representam a
taboada e imprime a matriz.
ATENO: a partir daqui os exemplos usam a estrutura de controle for. Veja a explicao sobre esta
estrutura (uma variao do while()) na Seo 15.1.
/* Exemplo de array 2-D - taboada */
#include <stdio.h>
#define LIN 10
#define COL 10
int main()
{
int x;
int y;
int tabela[LIN] [COL];

/* numero da coluna */
/* numero da linha */
/* tabela de taboada */

/* preenche a tabela */
for(y=0; y < LIN; y+=1)
for(x=0; x < COL; x+=1)
tabela[y][x] = y*x;
printf("\n

Tabela de Multiplicacao\n");

/* Imprime o numero das colunas */


printf("%6d", 0);
83

for (x=1; x < COL; x+=1)


printf("%3d", x);
printf("\n");
/* Imprime uma linha horizontal */
printf("
");
for (x=0; x < 3*COL; x+=1)
printf("-");
printf("\n");
/* Imprime as linhas da tablea.
Cada linha a precedida pelo indice de linha e uma barra vertical */
for (y=0; y < LIN; y+=1) {
printf("%2d|", y);
for(x=0; x < COL; x+=1)
printf("%3d", tabela[y][x]);
printf("\n");
}
}
A sada do programa :
Tabela de Multiplicacao
0 1 2 3 4 5 6 7 8 9
-----------------------------0| 0 0 0 0 0 0 0 0 0 0
1| 0 1 2 3 4 5 6 7 8 9
2| 0 2 4 6 8 10 12 14 16 18
3| 0 3 6 9 12 15 18 21 24 27
4| 0 4 8 12 16 20 24 28 32 36
5| 0 5 10 15 20 25 30 35 40 45
6| 0 6 12 18 24 30 36 42 48 54
7| 0 7 14 21 28 35 42 49 56 63
8| 0 8 16 24 32 40 48 56 64 72
9| 0 9 18 27 36 45 54 63 72 81

19.1

Inicializao

Arrays multidimensionais podem ser inicializados usando listas aninhadas de elementos entre chaves. Por
exemplo, um array bidimensional tabela com trs linhas e duas colunas pode ser inicializado da seguinte
forma:
double tabela[3][2] = { {1.0, 0.0},
{-7.8, 1.3},
{6.5, 0.0}
};

/* linha 0 */
/* linha 1 */
/* linha 2 */

Quando o array inicializado, o tamanho da primeira dimenso pode ser omitido. A definio de array
abaixo equivalente a dada anteriormente.
84

double tabela[][2] = { {1.0, 0.0},


{-7.8, 1.3},
{6.5, 0.0}
};

19.2

/* linha 0 */
/* linha 1 */
/* linha 2 */

Arrays Multidimensionais arrays de arrays

O formato da definio de um array de dimenso k, onde o nmero de elementos em cada dimenso


n0 , n1 , . . . , nk1 , respectivamente, :
nome_tipo nome_array[n0 ][n1 ]...[nk1 ];
Isto define um array chamado nome_array consistindo de um total de n0 n1 . . . nk1 elementos,
sendo cada elemento do tipo nome_tipo.
Arrays multidimensionais so armazenados de forma que o ltimo subscrito varia mais rapidamente.
Por exemplo, os elementos do array
int tabela[2][3];
so armazenados (em endereos consecutivos de memria) como

tabela[0][0], tabela[0][1], tabela[0][2], tabela[1][0], tabela[1][1], tabela[1][2


Um array de dimenso k, onde o nmero de elementos em cada dimenso n0 , n1 , . . . , . . . , nk1 , respectivamente, pode ser imaginado como um array de dimenso n0 cujos elementos so arrays de dimenso
k 1.
Por exemplo, o array bidimensional tabela, com 20 elementos do tipo int
int tabela[4][5] = { {13,
{20,
{31,
{40,

15,
22,
33,
42,

17,
24,
35,
44,

19,
26,
37,
46,

21},
28},
39},
48} };

pode ser imaginado como um array unidimensional de 4 elementos do tipo int[], ou seja, arrays de int;
cada um dos 4 elementos um array de 5 elementos do tipo int:
tabela[0]
tabela[1]
tabela[2]
tabela[3]

19.3

--->
--->
--->
--->

{13,
{20,
{31,
{40,

15,
22,
33,
42,

17,
24,
35,
44,

19,
26,
37,
46,

21}
28}
39}
48}

Arrays Multidimensionais como argumento para funes

Quando o parmetro formal de uma funo um array multidimensional (um array com dimenso maior que
um), todas as dimenses deste array, exceto a primeira, precisa ser explicitamente especificada no cabealho
e prottipo da funo.
tipo_do_resultado nome_da_f uncao ( nome_do_tipo nome_do_array[ ][n1 ]...[nk1 ], ...... )
Quando uma funo com um parmetro formal do tipo array chamada, na chamada da funo somente
o nome do array passado como parmetro real. O tipo (e portanto a dimenso) do array passado como
parmetro real deve ser consistente com o tipo (e portanto a dimenso) do array que o parmetro formal.
O programa abaixo mostra o exemplo da tabela de multiplicao escrita usando funes.
85

/* Exemplo de array 2-D - tabela de multiplicacao */


#include <stdio.h>
#define LIN 10
#define COL 10
void inicializa_arr (int arr[][COL], int);
void imprime_arr (int arr[][COL], int);
int main()
{
int tabela[LIN] [COL];
inicializa_arr(tabela, LIN);
printf("\n

Tabela de Multiplicacao\n");

imprime_arr(tabela, LIN);
}
/* Inicializa o array com a tabela de multiplicacao */
void inicializa_arr (int arr[][COL], int nLIN)
{
int x;
/* numero da coluna */
int y;
/* numero da linha */
/* preenche o array */
for (y=0; y < nlin; y+=1)
for(x=0; x < COL; x+=1)
arr[y][x] = y*x;
}
/* imprime um array LIN x COL */
void imprime_arr(int arr[][COL], int nlin)
{
int x;
/* numero da coluna */
int y;
/* numero da linha */
/* imprime o numero das colunas */
printf("%6d", 0);
for (x=1; x < COL; x+=1)
printf("%3d", x);
printf("\n");

/* imprime uma linha horizontal */


86

printf("
");
for (x=0; x < 3*COL; x+=1)
printf("_");
printf("\n");
/* imprime as linhas do array. cada linha e precedida
pelo numero da linha e uma barra vertical */
for (y=0; y < nlin; y+=1) {
printf("%2d|", y);
for(x=0; x < COL; x+=1)
printf("%3d", arr[y][x]);
printf("\n");
}
}
Outro exemplo com funoes de manipulao de arrays bidimensionais:
/* funcoes com argumentos tipo array 2-D */
#include <stdio.h>
#define ALTURA 5
#define LARGURA 5
void
void
void
void
void
void

seleciona_elem
(int [][LARGURA], int);
pontos
(int [][LARGURA], int);
imprime_matriz (int [][LARGURA], int);
marca_triang (int [][LARGURA], int);
flip
(int [][LARGURA], int);
espera_entrada(void);

/**** DEFINICAO DE FUNCOES ********/


/* funcao que preenche uma matriz nlin X LARGURA com pontos */
void pontos( int matriz[][LARGURA], int nlin)
{
int x,y;
for(y=0; y<nlin; y+=1)
for(x=0; x<LARGURA; x+=1)
matriz[y][x] = .;
}
/* funcao que preenche os elementos selecionados da matriz com um
* quadrado e imprime a matriz
*/
void seleciona_elem(int matriz[][LARGURA], int nlin)
{
int x, y;
87

printf("\nEntre com as coordenadas na forma y,x (2,4).\n");


printf("Use numeros negativos para terminar.\n");
while (1)
{
printf("Coordenadas: ");
scanf("%d,%d", &y, &x);
if (x >= 0 && y >= 0)
{
matriz[y][x]=\xB1;
imprime_matriz(matriz, nlin);
}
else
break;
}

/* preenche o elemento com quadrado */


/* imprime a matriz */

}
/* funcao que marca todos os elementos abaixo da diagonal principal de
* um array nlin X LARGURA com quadrados
*/
void marca_triang(int matriz[][LARGURA], int nlin)
{
int x, y;
printf("Triangulo\n");
pontos(matriz, nlin);
for (y = 0; y < nlin; y+=1)
for (x = 0; x <= y; x+=1)
matriz[y][x] = \xB1;
}
/* funcao que imprime um array 2-D nlin X LARGURA */
void imprime_matriz(int matriz[][LARGURA], int nlin)
{
int x,y;
for(y=0; y<nlin; y+=1)
{
for(x=0; x<LARGURA; x+=1)
printf("%c ", matriz[y][x] );
printf("\n\n");
}
printf("\n");
}
/* funcao que flipa um array ao longo da diagonal principal */
void flip(int matriz[][LARGURA], int nlin)
{
int x, y;
int temp;
88

printf("Flipado ao longo da diagonal principal.\n");


for (y = 0; y < nlin; y+=1)
for (x = 0; x <= y; x+=1){
temp = matriz[y][x];
matriz[y][x] = matriz[x][y];
matriz[x][y] = temp;
}
}
/* funcao que espera ate que uma tecla seja digitada */
void espera_entrada( void ) {
getchar(); }
/********** MAIN ************/
/* alguns exemplos de chamadas de funcoes com argumentos array 2-D */
int main()
{
int matriz [ALTURA] [LARGURA];
pontos(matriz, ALTURA);
seleciona_elem(matriz, ALTURA);
espera_entrada();
flip(matriz, ALTURA);
imprime_matriz(matriz,ALTURA);
espera_entrada();
marca_triang( matriz, ALTURA);
imprime_matriz( matriz, ALTURA);
espera_entrada();
flip( matriz, ALTURA);
imprime_matriz(matriz, ALTURA);
espera_entrada();
}

89

Parte II

Tpicos Avanados
As sees seguintes apresentam temas mais avanados da linguagem C , no abordadas em sala de aula.
Elas podem ser estudadas pelo aluno como atividade complementar, pois apresentam mecanismos bastantes
teis em programas mais complexos (tratamento de arquivos e textos, manipulao dinmica de memria,
etc.).

90

20

Operadores e Expresses Especiais

20.1

Operao de Atribuio Aritmtica

freqente em programas C expresses do tipo:


tudo

= tudo + parte;

tamanho = tamanho * 2.5;


x = x * (y + 1);
j = j - 1;
C fornece operadores adicionais que podem ser usados para tornar estes tipos de atribuies mais curtos.
H um operador de atribuio para cada operao aritmtica listada anteriormente:
+= operao de atribuio de adio
-= operao de atribuio de subtrao
*= operao de atribuio de multiplicao
/= operao de atribuio de diviso
%= operao de atribuio de resto
Cada uma dessas operaes podem ser usadas para tornar as expresses anteriores mais curtas:
tudo += parte;
tamanho *= 2.5;
x *= y + 1;
j -= 1;

20.2

Operadores de Incremento e Decremento

H alguns operadores em C que so equivalentes as seguintes expresses (que so bastante comuns em


programas):
k = k + 1;
j = j - 1;
Estes operadores adicionais, que so ++ and --, podem ser usados para encurtar as operaes acima:
k++;
j--;
Estes operadores tambm podem ser colocados depois do nome da varivel:
91

++k;
--j;
O fato do operador de incremento ser colocado antes ou depois da varivel no altera o efeito da operao
o valor da varivel incrementada ou decrementada de um. A diferena entre os dois casos QUANDO
a varivel incrementada. Na expresso k++, o valor de k primeiro usado e ento incrementada isto
chamado ps-incremento. Na expresso ++k, k incrementado primeiro, e ento o valor (o novo valor) de
k usado isso chamado pr-incremento.
A diferena ilustrada nos seguintes exemplos:
int main()
{
int k = 5;
printf("k = %d\n", k);
printf("k = %d\n", k++);
printf("k = %d\n", k);
}
O programa acima (que usa ps-incremento) imprimir o seguinte:
k = 5
k = 5
k = 6
A segunda linha impressa com o valor de k 5 porque o valor de k++ era 5, e k 6 depois da impresso.
Para o programa:
int main()
{
int k = 5;
printf("k = %d\n", k);
printf("k = %d\n", ++k);
printf("k = %d\n", k);
}
O programa, que usa pr-incremento, ter a seguinte sada:
k = 5
k = 6
k = 6
A segunda linha impressa 6 porque o valor de ++k 6.
Os operadores de atribuio no podem ser usados com expresses aritmticas. Por exemplo, as expresses
(ack + 2)++;
(nope + 3) += 5;
resultaro em erros de compilao.
Finalmente, quando usar o operador de incremento em um printf(), tome cuidado para no fazer o
seguinte:
92

printf("%d %d\n", ++uhoh, uhoh * 2);


Embora isso seja perfeitamente legal em C , os resultados no so garantidados que sejam consistentes.
A razo para isso que no h garantia que os argumentos do printf() sejam avaliados em uma determinada ordem. O resultado do printf() ser diferente dependendo se ++uhoh avaliado primeiro ou
depois de uhoh * 2.
A soluo para este problema escrever o seguinte:
++uhoh;
printf("%d
20.2.1

%d\n", uhoh, uhoh * 2);

Expresses como Valor com Operadores de incremento e decremento

J que incremento e decremento so formas de atribuio, o operando deve ser um lvalue. O valor de
uma expresso de incremento ou decremento depende se o operador usado na notao PR ou PS fixada
(x++, ++x, x--, --x). Se for pr-fixada, o valor da expresso o novo valor aps o incremento ou
decremento. Se for ps-fixada, o valor da expresso o valor antigo (antes do incremento ou decremento).
Por exemplo no caso de incremento, a expresso:
x++ tem o valor de x
++x tem o valor de x + 1
Note que no importando a notao usada, o valor de x (o contedo do endereo de memria associada
a x) ser x + 1. A diferena est no valor das expresses x++ e ++x, no no valor de x (em ambos os
casos o valor de x ser incrementada de um).
20.2.2

Ambiguidade em certas expresses

s vezes, problemas podem acontecer devido o fato que C no especifica a ordem de avaliao dos operadores em uma operao binria. Em outras palavras, em expresses como a + b ou a < b, no h
maneira de saber se o valor de a ser avaliado antes ou depois de b (pense em a e b como sendo qualquer
expresso, no somente variveis.) Qual deles ser avaliado primeiro particular de cada compilador, e
diferentes compiladores em mquinas diferentes podem ter resultados diferentes. Portanto, se a avaliao
de um dos operadores pode alterar o valor do outro, o resultado pode ser diferente dependendo da ordem
de avaliao. Portanto, em expresses do tipo x + x++, o valor pode diferir dependendo do compilador
utilizado. Isto porque no sabemos quando exatamente o incremento de x ocorre. Outros maus exemplos:
y = x + x-- e x = x++. De forma geral, para evitar este problema, no utilize senteas como estas.

93

21

Mais sobre tipos: converso implcita e explcita

Expresses no tem somente um valor, mas tambm tem um tipo associado.


Se ambos os operandos de uma operao aritmtica binria so do mesmo tipo, o resultado ter o mesmo
tipo. Por exemplo:
3 + 5
8, e o tipo int
3.5 + 2.25 5.75, e o tipo double
O nico comportamento no bvio a da diviso de inteiros:
30 / 5 6
31 / 5 6
29 / 5 5
3 / 5 0
Lembre-se de evitar escrever algo como 1 / 2 * x significando 21 x. Voc sempre obter o valor 0
porque 1 / 2 * x (1 / 2) * x que 0 * x que 0. Para obter o resultado desejado, voc poderia
escrever 1.0 / 2.0 * x.

21.1

Converso de tipos

Valores podem ser convertidos de um tipo para outro implicitamente, da forma j comentada anteriormente.
Em expresses envolvendo operadores binrios com operandos de tipos diferentes, os valores dos operandos so convertidos para o mesmo tipo antes da operao ser executada: tipos mais simples so promovidos para tipos mais complexos. Portanto, o resultado da avaliao de uma expresso com operandos de
tipos diferentes ser o tipo do operando mais complexo. Os tipos em C so (do mais simples para o mais
complexo):
char < int < long < float < double
O sinal de < significa que o tipo da esquerda promovido para o tipo da direita, e o resultado ser do
tipo mais a direita. Por exemplo:
3.5 + 1 4.5
4 * 2.5 10.0
Esta regra estende-se para expresses envolvendo mltiplos operadores, mas voc deve se lembrar que
a precedncia e associatividade dos operadores pode influenciar no resultado. Vejamos o exemplo abaixo:
int main()
{
int a, b;
printf("Entre uma fracao (numerador e denominador): ")
scanf("%d %d", &a, &b);
printf("A fracao em decimal e

%f\n", 1.0 * a / b);

}
94

Multiplicando por 1.0 assegura que o resultado da multiplicao de 1.0 por a ser do tipo real, e
portanto, a regra de converso automtica evitar que o resultado da diviso seja truncado. Note que se tivssemos primeiro feito a diviso a/b e depois multiplicado por 1.0, embora o tipo da expresso a/b*1.0
seja do tipo double, o valor da expresso seria diferente do valor de 1.0 * a/b. Por que ?
Em atribuies, o valor da expresso do lado direito convertido para o tipo da varivel do lado esquerdo
da atribuio. Isto pode causar promoo ou rebaixamento de tipo. O rebaixamento pode causar perda
de preciso ou mesmo resultar em valores errados.
Em operaes de atribuio, atribuir um int em um float causar a converso apropriada, e atribuir
um float em um int causar truncamento. Por exemplo:
float a = 3;
equivalente a a = 3.0
int a = 3.1415; equivalente a a = 3 (truncado)
Basicamente, se o valor da expresso do lado direito da atribuio de um tipo que no cabe no tamanho
do tipo da varivel do lado esquerdo, resultados errados e no esperados podem ocorrer.

21.2

Modificadores de tipos

Os tipos de dados bsicos em C podem estar acompanhados por modificadores na declarao de variveis.
Tais modificadores so: long, short, signed e unsigned. Os dois primeiros tm impacto no tamanho (nmero de bits) usados para representar um valor e os dois ltimos indicam se o tipo ser usado para representar
valores negativos e positivos (signed) ou sem este modificador) ou apenas positivos (unsigned).
A Tabela 5 mostra uma lista completa de todos os tipos de dados em C , com e sem modificadores:

Modificador

Tamanho em bits

char
unsigned char
int
unsigned int
short int
unsigned short int
long int
unsigned long int
float
double
long double

8
8
16
16
16
16
32
32
32
64
80

Faixa de valores
-127 a 127
0 a 255
-32767 a 32767
0 a 65535
-32767 a 32767
0 a 65535
-2147483647 a 2147483647
0 a 4294967295
Mantissa de 6 dgitos
Mantissa de 10 dgitos
Mantissa de 10 dgitos

Tabela 5: Modificadores de Tipos de Dados

21.3

Cast de tipos

C tem um operador para alterar o tipo de um valor explicitamente. Este operador chamado de cast.
Executando um cast de tipos, o valor da expresso forado a ser de um tipo particular, no importando a
regra de converso de tipos.
O formato do cast de tipos :
(nome-do-tipo)expressao
95

O parnteses NO opcional na expresso acima.


Podemos usar o cast de tipos da seguinte forma:
int fahr = 5;
float cels;
printf("Valor = %f\n", (float)fahr);
cels = (float)5 / 9 * (fahr - 32);
printf("celsius = %d\n", (int)cels);
Agora que conhecemos o operador de cast de tipo podemos reescrever o programa que faz a converso
de frao para decimal.
int main()
{
int a, b;
printf("Entre com uma fracao (numerador e denominador): ")
scanf("%d %d", &a, &b);
printf("A fracao em decimal e

%f\n", (float) a / b);

}
O cast de tipo tem a maior precedncia possvel, portanto podemos fazer o cast de a ou de b para ser do
tipo float, e no h necessidade de parnteses extra. No exemplo acima, o cast causa o valor da varivel
a ser convertido para float, mas no causa mudana no tipo da varivel a. O tipo das variveis definido
uma vez na declarao e no pode ser alterado.

96

22

Tipo Enumerado

Em muitos programas, variveis do tipo int so utilizadas no por suas propriedades numricas, mas para
representar uma escolha dentre um pequeno nmero de alternativas. Por exemplo:
int sexo;
int cor;

/* masculino = 1
feminino = 2 */
/* vermelho = 1
amarelo = 2
verde = 3
*/

A utilizao de cdigos para representar os valores que uma varivel poder assumir, certamente compromete a clareza da estrutura de dados do programa, tornando sua lgica obscura e inconsistente. Por
exemplo:
cor = 3;
if( sexo == 2 ) ...
cor = cor + sexo;
for( cor = 1; cor < 10; cor ++ )...
Um tipo enumerado permite definir uma lista de valores que uma varivel deste tipo poder assumir. A
definio de um tipo enumerado feita da seguinte forma:
enum N ome_do_tipo { valor1 , valor2 , . . ., valorn };
Exemplos de definio de tipos enumerados:
enum TpCores {VERMELHO, AMARELO, VERDE};
enum TpDias {SEG, TER, QUA, QUI, SEX, SAB, DOM};
enum TpSexos {MASCULINO, FEMININO};
Variveis destes tipos so definidas da seguinte forma:
enum TpCores var1, var2;
enum TpDias var3;
Agora, possvel dar valores a estas variveis, por exemplo:
var1 = AMARELO;
var3 = QUI;
um erro usar valores no definidos na declarao do tipo. A expresso var2 = AZUL; causa erro de
compilao.
Internamente, o compilador trata variveis enumeradas como inteiros. Cada valor na lista de valores
possveis corresponde a um inteiro, comeando com 0 (zero). Portanto, no exemplo enum TpCores,
VERMELHO armazenado como 0, AMARELO armazenado como 1, e VERDE armazenado como 2.
Utilizao de tipos enumerados
Variveis de tipos enumerados so geralmente usados para clarificar a operao do programa. Considere
o seguinte trecho de programa que codifica dias da semana como inteiros (sendo sabado = 5 e domingo
= 6) para verificar se o dia do pagamento cai no final de semana e altera a dia para a segunda-feira seguinte.

97

#include <stdio.h>
/* prototipo da funcao que dada a data, retorna o dia da semana.
seg=0, ter=1, qua=2, qui=3, sex=4, sab=5, dom=6 */
int diaDaSemana( int dia, int mes, int ano );
int main(){
int diaPgto, mesPgto, anoPgto;
int diaSem;
printf("Entre com a data de pagamento (dd mm aa): ");
scanf("%d %d %d", &diaPgto, &mesPgto, &anoPgto);
diaSem = diaDaSemana( diaPgto, mesPgto, anoPgto );
if( diaSem == 5 )
diaPgto = diaPgto + 2;
else if( diaSem == 6 )
diaPgto++;
printf("Data do pagamento: %d/%d/%d\n", diaPgto, mesPgto,
anoPgto);
}
Este programa ficaria mais legvel se ao invs de codificar os dias da semana como inteiros e colocar a
codificao como comentrio, utilizar tipos enumerados. O programa ficaria ento
#include <stdio.h>
enum TpSemana {SEG, TER, QUA, QUI, SEX, SAB, DOM};
/* prototipo da funcao que dada a data, retorna o dia da semana */
enum TpSemana diaDaSemana( int dia, int mes, int ano );
int main(){
int diaPgto, mesPgto, anoPgto;
int diaSem;
printf("Entre com a data de pagamento (dd mm aa): ");
scanf("%d %d %d", &diaPgto, &mesPgto, &anoPgto);
diaSem = diaDaSemana( diaPgto, mesPgto, anoPgto );
if( diaSem == SAB )
diaPgto = diaPgto + 2;
else if( diaSem == DOM )
diaPgto++;
printf("Data do pagamento: %d/%d/%d\n", diaPgto, mesPgto, anoPgto);
}
Note que a funo diaDaSemana agora retorna apenas um dos valores da lista SEG, TER, QUA,
QUI, SEX, SAB, DOM e portanto, no programa principal ao invs de testar se o diaSem == 5 podemos escrever diaSem == SAB, o que torna o programa muito mais legvel.

98

23

Entrada e Sada Padro

A forma com que um programa em C se comunica com o mundo externo atravs de entrada e sada de
dados: o usurio fornece dados via teclado e o programa imprime mensagens na tela. Todos os programas
vistos at agora lem suas entradas do teclado e produzem suas sadas na tela.
Em C toda entrada e sada feita com fluxos (streams) de caracteres organizados em linhas. Cada
linha consiste de zero ou mais caracteres e termina com o caracter de final de linha. Pode haver at 254
caracteres em uma linha (incluindo o caracter de final de linha). Quando um programa inicia, o sistema
operacional automaticamente define quem a entrada padro (geralmente o teclado) e quem a sada
padro (geralmente a tela).
As facilidades de entrada e sada no fazem parte da linguagem C . O que existe uma biblioteca padro
de funes para manipular a transferncia de dados entre programa e os dispositivos (devices) de sada
e entrada padro. Algumas destas funes so: scanf(), printf(), getchar(), puts(),
gets(). Estas funes so declaradas no arquivo <stdio.h>. Existem funes teis para converso
e teste de caracteres declaradas no arquivo <ctype.h>.
As funes de entrada e sada operam sobre streams (fluxos) de caracteres. Toda vez que uma funo se
entrada chamada (por exemplo, getchar(), scanf()) ela verifica pela prxima entrada disponvel
na entrada padro (por exemplo, texto digitado no teclado). Cada vez que uma funo de sada chamada,
ela entrega o dado para a sada padro (por exemplo, a tela).
As funes para leitura da entrada padro e para escrita na sada padro que tm sido usadas at agora
so:
1. Entrada e sada de caracteres:
int getchar( void );
int putchar( int );
2. Entrada e sada de strings:
char *gets(char *);
int puts( char *);
3. Entrada e sada formatada:
int scanf(char *format, arg1, arg2, ... );
int printf(char *format, arg1, arg2, ... );

23.1

Comandos de entrada e sada: getchar() e putchar()

Vamos discutir algumas funes de entrada de dados (diferente do scanf()). A entrada de texto considerada
como um fluxo de caratecteres. Um fluxo texto uma sequncia de caracteres dividida em linhas; cada linha
consiste de zero ou mais caracteres seguido do caractere de nova linha (\n). Como programador, voc no
quer se preocupar em como as linhas so representadas fora do programa. Quem faz isso por voc so
funes de uma biblioteca padro.
Suponha que voc queira ler um nico caractere, mas no quer usar o scanf(). Isso pode ser feito usando
a funo getchar(). A funo putchar() aceita um argumento de entrada, cujo valor ser impresso como
caracter:
#include <stdio.h>
99

int main()
{
char ch;
printf("Digite algum caracter: ");
ch = getchar();
printf("\n A tecla pressionada eh %c.\n", ch);
}
O Resultado deste programa na tela :
Digite algum caracter: A
A tecla pressionada eh A.
Outro exemplo:
#include <stdio.h>
int main()
{
char ch;
printf("Digite outro caracter: ");
ch = getchar();
putchar(ch);
}
O Resultado deste programa na tela :
Digite outro caracter: B
B

23.2
23.2.1

Consideraes sobre Leitura de Dados pelo Teclado


Lendo o teclado usando getchar()

getchar() uma funo da biblioteca padro stdio. Cada vez que chamada, esta funo l um caractere
teclado; getchar comea a ler depois que a tecla RETURN digitada no final de uma sequncia de
caracteres (dizemos que a entrada para a funo getchar() est no fluxo de entrada). A funo getchar()
retorna um valor, o caractere lido (mais precisamente, o cdigo inteiro ASCII correspondente ao caractere).
Vejamos o que acontece quando um programa trivial executado.
#include <stdio.h>
main(){
100

int ch;
ch = getchar();
}
getchar() obtm sua entrada do teclado. Portanto, quando o programa acima executado, o programa
espera que o usurio digite alguma coisa. Cada caractere digitado mostrado no monitor. O usurio pode
digitar diversos caracteres na mesma linha, inclusive backspace para corrigir caracteres j digitados. No
momento que ele teclar RETURN , o primeiro caractere da sequncia digitada o resultado da funo
getchar(). Portanto, na instruo do programa acima o caractere (ou melhor, o seu cdigo ASCII) atribudo
a varivel ch. Note que o usurio pode ter digitado diversos caracteres antes de teclar RETURN , mas a
funo getchar() s comear a ler o que foi digitado depois que for teclado RETURN . Alm disso, com
uma chamada da funo getchar() s o primeiro caractere da sequncia digitada lida.
Voc deve saber que o caractere de nova linha, \n, que tem o cdigo ASCII 10, automaticamente
adicionado na sequncia de caracteres de entrada quando o RETURN teclado. Isso no tem importncia
quando a funo getchar() chamada uma nica vez, mas isto pode causar problemas quando ele usado
dentro de um lao.
No iniccio de qualquer programa que usa getchar(), voc deve incluir
#include <stdio.h>
Esta diretiva do pr-processador diz ao compilador para incluir informaes sobre getchar() e EOF
(mais sobre EOF mais tarde.).
Considere o seguinte programa:
#include <stdio.h>
int main(){
int ch;
printf( "Entre com uma letra: " );
ch = getchar();
if( ch < A || ch > z )
printf( "Voce nao teclou uma letra!" );
else
printf( "Voce teclou %c, e seu codigo ASCII eh %d.\n", ch, ch );
}
Um exemplo da execuo do programa:
Entre com uma letra: A
Voce teclou A, e seu codigo ASCII eh 65.
No exemplo de execuo acima o usurio teclou A e depois RETURN .
Outro exemplo de execuo do programa:
Entre com uma letra: AbcD
Voce teclou A, e seu codigo

ASCII eh 65.

Neste caso o usurio digitou quatro caracteres e depois teclou RETURN . Embora quatro caracteres
tenham sido digitados, somente uma chamada a funo getchar() foi feita pelo programa, portanto s um
caractere foi lido. O valor atribudo ao argumento da funo o cdigo ASCII do primeiro caractere lido.
O tipo do resultado da funo getchar() int e no char. O valor retornado pela funo o cdigo
ASCII do caractere lido.
101

23.2.2

Marcando o final da entrada

Frequentemente quando voc est digitando a entrada para o programa, voc quer dizer ao programa que
voc terminou de digitar o que queria. Em ambiente Unix, digitando ^D (segure a tecla de Ctrl e pressione
D) voc diz ao programa que terminou a entrada do programa. Em ambiente MS-Windows, voc faz isto
digitando ^Z (segure a tecla de Ctrl e pressione Z).
Isto envia uma indicao para a funo getchar(). Quando isso ocorre, o valor de ch depois de executar
ch = getchar(); ser um valor especial do tipo inteiro chamado EOF (que significa end of file final do
arquivo).
Considere o seguinte programa exemplo que conta o nmero de caracteres digitados (incluindo o caractere de prxima linha):
#include <stdio.h>
int main()
{
int total = 0, ch;
/* Le o proximo caractere em ch e para quando encontrar final do arquivo */
while( (ch = getchar()) != EOF ) {
total++;
}
printf( "\n%d caracteres digitados\n", total );
}

S para esclarecer: voc deve teclar RETURN depois de entrar com o comando ^D (ou ^Z no MSWindows).
23.2.3

Para evitar problemas com a entrada...

(Observao: nesta seo, espaos em branco so relevantes e so mostrados como t)


Quando voc executa um programa, cada caractere que voc digita lido e considerado como parte do
fluxo de entrada. Por exemplo, quando voc usa getchar(), voc deve teclar RETURN no final. Como
mencionado anteriormente, o primeiro caractere digitado lido pelo getchar(). Mas, o caractere de nova
linha continua no fluxo de entrada (porque voc teclou RETURN ).
De qualquer forma, se voc executar um getchar() depois de um scanf() ou de um getchar() voc ler
o caractere de nova linha deixado no fluxo de entrada.
Da mesma forma, quando voc usa scanf() para ler informaes, ele somente l o que necessrio. Se
voce usar scanf() para ler um nmero inteiro e digitar 42tt (seguido de RETURN ), o scanf() l 42,
mas deixa tt (e o caractere de nova linha do RETURN ) no fluxo de entrada.
Outro caso problemtico quando o scanf() usado num lao. Se voc digitar um valor do tipo
errado, o scanf() ler o valor errado e a execuo do lao continuar na sentena aps o scanf(). Na
prxima iterao do lao o scanf() vai tentar ler novamente, mas o lixo deixado da iterao anterior ainda
estar l, e portanto a chamada corrente do scanf() tambm no dar certo. Este comportamento resultar
num lao infinito (um lao que nunca termina), ou terminar e ter um resultado errado.
H uma maneira simples de resolver este problema; toda vez que voc usar getchar() (para ler um
caracter s) ou scanf(), voc deve ler todo o lixo restante at o caractere de nova linha. Colocando as
seguinte linhas aps chamadas a getchar() ou scanf() o problema eliminado:
102

/* Pula o restante da linha */


while( getchar() != \n );
Note que isso no necessrio aps todas as chamadas a getchar() ou scanf(). S depois daquelas
chamadas que precedem getchar() (ou scanf()), especialmente em um lao.
A funo scanf() na realidade retorna um inteiro que o nmero de itens (valores) lidos com sucesso.
Voc pode verificar se o scanf() funcionou testando se o valor retornado igual ao nmero de especificadores de formato no primeiro argumento da funo.
int main(){
int total = 0, num;
while( total < 20 ){
printf( "Total = %d\n", total );
printf( "Entre com um numero: " );
if( scanf("%d", &num) < 1 )
/* Ignora o resto da linha */
while( getchar() != \n );
else
total += num;
}
printf( "Final total = %d\n", total );
}

103

24

Arquivos

O armazenamento de dados em variveis e arrays temporrio. Arquivos so usados para armazenamento


permanente de grandes quantidades de dados (e programas) em dispositivos de armazenamento secundrio,
como discos.
s vezes no suficeinte para um programa usar somente a entrada e sada padro. H casos em que
um programa deve acessar arquivos. Por exemplo, se ns guardamos uma base de dados com endereos de
pessoas em um arquivo, e queremos escrever um programa que permita ao usurio interativamente buscar,
imprimir e mudar dados nesta base, este programa deve ser capaz de ler dados do arquivo e tambm gravar
dados no mesmo arquivo.
No restante desta seo discutiremos como arquivos de texto so manipulados em C . Como ser visto,
tudo ocorre de maneira anloga ao que acontece com entrada e sada padro.

24.1

Acessando um arquivo: FILE *, fopen(), fclose()

C visualiza cada arquivo simplesmente como um stream seqencial de bytes. Da mesma forma que uma
string em C termina com o caracter nulo, \0, cada arquivo em C termina com um marcador de final de
arquivo (end-of-file), EOF.
As regras para acessar um arquivo so simples. Antes que um arquivo seja lido ou gravado, ele aberto.
Um arquivo aberto em um modo que descreve como o arquivo ser usado (por exemplo, para leitura,
gravao ou ambos). Um arquivo aberto pode ser processado por funes da biblioteca padro em C . Estas
funes so similares s funes de biblioteca que lem e escrevem de/para entrada/sada padro. Quando
um arquivo no mais necessrio ele deve ser fechado. Ao final da execuo de um programa todos os
arquivos abertos so automaticamente fechados. Existe um nmero mximo de arquivos que podem ser
simultaneamente abertos de forma que voc deve tentar fechar arquivos quando voc no precisa mais deles.
Quando um arquivo est aberto, um stream associado ao arquivo. Este stream fornece um canal
de comunicao entre um arquivo e o programa. Trs arquivos e seus respectivos streams so abertos
automaticamente quando um programa inicia sua execuo: a entrada padro, a sada padro e a sada
padro de erros.
A funo da biblioteca padro fopen() usada para abrir um arquivo. fopen() toma dois argumentos
do tipo string: o primeiro argumento o nome do arquivo (por exemplo data.txt), o segundo argumento a
indicao do modo no qual o arquivo deve ser aberto. fopen() negocia com o sistema operacional e retorna
um ponteiro para um tipo estrutura especial FILE. Este ponteiro chamado file pointer, e aponta para uma
estrutura que contm informaes de sistema sobre o arquivo. O tipo FILE predefinido em <stdio.h>. O
file pointer usado pelas funes de biblioteca que processam o arquivo aberto, e "representa"o arquivo do
momento em que aberto at o momento em que fechado. A estrutura FILE referida como file control
block (FCB). Cada arquivo possui um FCB correspondente no disco. Quando um arquivo aberto sua FCB
copiada para a memria e um ponteiro definido para l. O processamento do arquivo usa o ponteiro para o
FCB para manipular arquivos, de forma que o tipo do ponteiro FILE *. A sada padro, a entrada padro
e a sada padro de erros so manipulados usando ponteiros pr-definidos chamados stdout, stdin e
stderr respectivamente.
O usurio no necessita saber detalhes de como a transferncia de dados entre programa e arquivo
feita. As nicas sentenas necessrias no programa so a definio de uma varivel do tipo FILE * (um
file pointer) e uma atribuio de um valor para aquela varivel por fopen(). As sentenas abaixo do um
exemplo de como abrir o arquivo data.txt para leitura.
FILE *fp;
fp = fopen("data.txt", "r");
O prottipo da funo fopen() :
FILE *fopen(char *name, char *mode);
104

fopen() recebe dois argumentos: o primeiro uma string que um nome de um arquivo a ser aberto, e
o segundo uma string que representa o modo de abertura do arquivo: r indica que o arquivo ser aberto
apenas para leitura, w, para escrita apenas. Se o arquivo no existe e aberto para escrita, fopen() cria
o arquivo. Se um arquivo j existente aberto para escrita, o seu contedo descartado. H outros modos,
incluindo anexao a um arquivo, leitura e escrita simultnea; para mais detalhes, veja a documentao da
funo nos livros-texto ou no manual on-line.
Se um arquivo aberto com sucesso, o endereo da estrutura FILE retornado por fopen(). Se a
tentativa de abertura resulta em erro, fopen() retorna um ponteiro nulo, NULL. Alguns dos error possveis
so: abrir um arquivo que no existe para leitura, abrir um arquivo para escrita quando no h mais espao
disponvel em disco, ou abrir um arquivo para qualquer operao sendo que as permisses de acesso do
arquivo no o permitem.
recomendvel que voc teste o valor de retorno de fopen() para verificar se houve erro de abertura. O
trecho de programa abaixo ilustra como faz-lo:
#include <stdio.h>
....
FILE *fp;
char fnome[13];
char fmodo[3];
printf("Entre um nome de arquivo para abrir:");
scanf("%s", fnome);
printf("Entre o modo de abertura do arquivo:");
scanf("%s", fmodo);
fp = fopen( fnome, fmodo );
if (fp == NULL)
{
printf("Erro na abertura de %s no modo %s\n", fnome, fmodo);
return ;
}
else
printf("Arquivo %s aberto com sucesso no modo %s\n", fnome, fmodo);
...
No exemplo acima se o arquivo no puder ser aberto com sucesso, uma mensagem apropriada exibida
na sada padro e o programa termina. Caso contrrio uma mensagem indicando o sucesso na abertura do
arquivo exibida e o programa continua sua execuo.
Cada arquivo aberto possui seu prprio file pointer. Por exemplo, se um programa vai manipular dois
arquivos diferentes arq1 and arq2 simultaneamente (um para leitura e outro para escrita), dois file pointers
devem ser usados:
FILE *fp1, *fp2;
fp1 = fopen("arq1", "r");
fp2 = fopen("arq2", "w");
Os valores de file pointer (FILE *) so chamados streams. Eles estabelecem conexo entre o programa
e o arquivo aberto. A partir do momento de abertura, o nome do arquivo irrelevante para o programa. Todas
as funes que operam sobre o arquivo usam o file pointer associado.
105

Terminada a manipulao do arquivo o programa deve fechar o arquivo. A funo padro fclose()
usada com este propsito. Ela quebra a conexo entre o file pointer e o arquivo. Esta funo toma como
argumento o file pointer que representa o arquivo a ser fechado. file to be closed. O prottipo de fclose() :
int fclose(FILE *);
fclose() retorna 0, se h sucesso ou EOF em caso contrrio. Abaixo um exemplo de uso de fclose():
fclose(pf1);
fclose(pf2);

24.2

Processando arquivos de texto

Arquivos podem guardar duas categorias bsicas de dados: texto (caracteres no universo ASCII) ou binrio
(como dados armazenados em memria ou dados que representam uma imagem JPEG).
Depois que um arquivo de texto aberto, existem 3 formas diferentes de ler ou escrever sequencialmente
os dados: (i) um caracter por vez, usando as funes da biblioteca padro fgetc() e fputc(); (ii) uma linha
(string) por vez, usando fgets() e fputs(); e (iii) em um formato especfico, usando fscanf() e fprintf().
Arquivos binrios podem ser lidos como registros de dados estruturados. Alm disso, uma vez que
todos os registros tem o mesmo tamanho, os dados podem ser acessados de forma no-sequencial (acesso
aleatrio). As funes usadas para isto so fwrite() e fread().
Outras funes de entrada e sada de mais baixo nvel que podem ser usadas so as funes read() e
write(). Estas funes no sero usadas no momento e geralmente somente programadores experientes as
usam.
24.2.1

Entrada e sada de caracteres

As funes fgetc() e putc() so similares a getchar() e putchar(). Elas operam sobre um arquivo aberto
cujo file pointer passado como argumento.
Os prottipos de fgetc() e fputc() are
int fgetc(FILE *fp);
int fputc(char ch, FILE *fp);
getc() returns the next character read from the file represented by the stream fp, or EOF if error or end of
file occurs. putc() writes the character ch in the file represented by the stream fp. It returns the character
written or EOF.
Abaixo segue um exemplo de programa que l um arquivo caracter a caracter e imprime o que foi lido
na sada padro (a tela do computador):
/************************************************************************
* L um caracter por vez de um arquivo e
* o imprime na sada padro
************************************************************************/
#include <stdio.h> /* para funes padro de E/S */
main()
{
FILE *fp;
char fnome[13];
int ch;
/* dialogo com usurio */
106

printf("Entre um nome de arquivo: ");


scanf("%s", fnome);
fp = fopen( fnome, "r" ); /* abre arquivo*/
if (fp == NULL)
{
printf("Erro ao abrir %s\n", fnome);
return;
}
else
{
printf("Arquivo aberto com sucesso.\n");
/* L o arquivo caracter a caracter e imprime em stdout (sada padro) */
while( (ch=fgetc(fp)) != EOF )
printf("%c", ch);
fclose(fp); /* fecha arquivo */
}
}
24.2.2

Entrada e sada de strings

As funes fgets() and fputs() so similares a gets() e puts(). Elas operam sobre um arquivo aberto cujo
file pointer passado como argumento.
Os prottipos de fgets() e fputs() so:
char *fgets(char *str, int n, FILE *fp);
int fputs(char *str, FILE *fp);
A funo fgets() l do arquivo conectado ao stream fp no mximo (n - 1) caracteres para o array str,
parando a leitura se o caracter \n (uma mudana de linha) encontrado; O caracter \n includo no
array e ao elemento do array seguinte atribudo \0 (final de string). A funo fgets() retorna str ou
o ponteiro nulo, NULL, se um erro ou final de arquivo ocorre.
A funo fputs() escreve no arquivo conectado ao stream fp a string str, retornando um nmero
no-negativo, ou EOF em caso de erro.
No exemplo a seguir usada a funo fgets() (e no gets(). Voc pode dizer por qu?) para salvar
em um arquivo um texto digitado atravs da entrada padro (stdin). Para sinalizar pelo teclado que voc
terminou de entrar o texto, deve-se teclar ^D (^Z Turbo C ) e ento a tecla ENTER.
/************************************************************************
* Escreve texto digitado em stdin em um arquivo
***********************************************************************/
#include <stdio.h> /* funes padro de E/S */
main()
{
FILE *fp;
char fnome[13];
char linha[81];
/* dialogo com usuario */
107

printf("Entre um nome de arquivo: ");


scanf("%s", fnome);
fp = fopen( fnome, "w" ); /* abre arquivo. Contedo anterior perdido.*/
if (fp == NULL)
{
printf("Erro ao abrir %s\n", fnome);
return;
}
else
{
printf("Arquivo aberto com sucesso");
/* l linha do teclado, armazena em uma string,
* salva string em arquivo */
while( fgets(linha, 80, stdin ) != NULL)
fputs(linha, fp);
fclose(fp); /* fecha arquivo */
}
}
24.2.3

Entrada e sada formatada: fscanf(), fprintf()

Para entrada e sada formatada as funes padro fscanf() e fprintf() podem ser usadas. Elas so idnticas
s funes scanf() e printf(), exceto que elas tm um argumento adicional (o primeiro em sua lista de
argumentos) que o stream conectado ao arquivo a ser lido ou escrito. Informalmente, seus prottipos
podem ser escritos como:
int fscanf( FILE *fp, char *format, arg1, arg2, ... );
int fprintf(FILE *fp, char *format, arg1, arg2, ... );
A funo fscanf() l do arquivo representado pelo stream fp sob controle de um string de formato format.
O string de formato geralmente contm converses (como %d, %s, %f) que dirigem a interpretao da
entrada. Os valores convertidos so atribudos para os argumentos subsequentes, cada qual devendo ser
um ponteiro. A funo fscanf() retorna quando o string de formato foi totalmente interpretado. O valor
retornado por fscanf() EOF se o final do arquivo foi atingido ou um erro ocorre, caso contrrio retorna a
quantidade de itens convertidos e atribudos.
A funo fprintf() escreve no arquivo conectado ao stream fp sob controle de um string de formato
format. O string de format contm dois tipos de objetos: caracteres ordinrios que so copiados do jeito
que so, e especificadores de converso que causam a converso e impresso dos argumentos seguintes de
fprintf(). O valor de retorno o nmero de caracteres escritos, ou negativo em caso de ocorrncia de erros.
Abaixo segue um exemplo simples de base de dados. Os dados so armazenados permanentemente em
um arquivo, agentes.txt. A base de dados contm registros de agentes secretos famosos. Para cada agente
um apelido e um nmero de cdigo so armazenados. Uma vez que ser usada e/s formatada, deve-se
conhecer o formato no qual os dados esto armazenados no arquivo da base de dados. Este formato : os
dados de diferentes agentes esto em linhas separadas; para cada agente em uma linha, tem-se primeiro o
apelido e ento o cdigo numrico, separados por espao.
O programa orientado a menu abaixo lista todos os registros e adiciona novos itens.
/****************************************************************
* programa com menu para operar uma base de dados de no mximo 50
108

* agentes secretos; a base de dados guardada permanentemente em


* um arquivo em disco.
****************************************************************/
#include <stdio.h>
#define FNOME "agentes.txt"
#define NUM 50
#define NOMELEN 30

/* funes padro de E/S */


/* nome do arquivo de dados */
/* numero de registros na base de dados */
/* tamanho de um nome */

/*** declara estrutura de dados ***/


struct pessoal
{
char nome [NOMELEN];
/* nome codigo(sem espaos em branco) */
int agnum ;
/* numero codigo */
};
/***
int
int
void
void

prototipos ***/
cargadb(struct pessoal []);
novonome(struct pessoal [], int);
listatudo(struct pessoal [], int);
salvadb(struct pessoal [], int);

109

/****** MAIN *********/


main()
{
struct pessoal agentes[50];
int n;
char ch;

/* array de 50 estruturas
*/
/* indice para o ultimo registro ativo */

/*** carrega a base de dados em agentes[], n o tamanho da base de dados


n = loaddb(agentes);
/* seleciona uma opo do menu e processa os dados em memria */
do {
printf("\nDigite e para entrar novo agente,");
printf("\n
l para listar todos os agentes,");
printf("\n
q para terminar: ");
ch = getchar();
switch (ch)
{
case e:
n = novonome(agentes, n);
/* adiciona um novo agente no indice n
break;
case l:
/* lista todos os registros */
listatudo(agentes, n);
break;
case q:
/* salva todos os registros */
salvadb(agentes, n);
break;
default:
/* Engano do usuario */
printf("\nEntre somente as opes listadas.\n");
}
while (fgetc(stdin) != \n) ;
} while (ch != q);
}

Uma amostra de uma execuo do programa segue abaixo. Inicialmente o contedo do arquivo agentes.txt
:
Klara 89
Edward 888
ZipZap 109

110

Uma amostra de execuo:


Digite e para entrar novo agente,
l para listar todos os agentes,
q para terminar: l
Klara 89
Edward 888
ZipZap 109
Digite e para entrar novo agente,
l para listar todos os agentes,
q para terminar: e
Digite nome e cdigo: TipTop 999
Digite e para entrar novo agente,
l para listar todos os agentes,
q para terminar: l
Klara 89
Edward 888
ZipZap 109
TipTop 999
Digite e para entrar novo agente,
l para listar todos os agentes,
q para terminar: q
Salvar? (s para salvar)
y
Salvando...Feito
A seguir, apresenta-se a implementao das quatro funes cargadb(), novonome(), listatudo(), and salvadb().
/***********************************************************************
* l a base de dados do arquivo (at EOF) no array em memria
* ENTRADA: um array de do tipo struct pessoal
* RETORNO: nmero de elementos lidos
* SUPOSIES: o tamanho da base de dados deve ter no mximo 50 registros
***********************************************************************/
int cargadb(struct pessoal pessoa[])
{
int i = 0;
FILE *fp;
/* define ptr to FILE
*/
fp = fopen(FNOME, "r");
while ( fscanf(fp, "%s %d", pessoa[i].nome, &pessoa[i].agnum) != EOF )
i++;
fclose(fp);
return i;
}

111

/*************************************************************************
* adiciona novo elemento ao indice n no array pessoa[],
* o valo da estrutura obtido da entrada padro
* ENTRADA: array pessoa[] -- to store the structure value
n -- indice do elemento, incrementado a
*
cada novo elemento
*
*************************************************************************/
int novonome(struct pessoal pessoa[], int n)
{
if (n < NUM)
{
printf("Digite nome e cdigo: ");
scanf("%s %d", pessoa[n].nome, &pessoa[n].agnum);
n++;
}
else
printf("No h mais espao\n");
return n;
}
/*************************************************************************
* imprime a base de dados na tela
* ENTRADA: array pessoa[] a imprimir
n nmero de registros para imprimir
*
*************************************************************************/
void listatudo(struct pessoal pessoa[], int n)
{
int j;
for (j = 0; j < n; j++)
{
printf("%s %d\n", pessoa[j].nome, pessoa[j].agnum);
}
}
/**************************************************************************
* Pergunta ao usurio se quer salvar a base de dados. Se a resposta SIM (s)
* abre o arquivo para escrita e grava o array no arquivo
* ENTRADA: array pessoa[] a ser salvo
n nmero de registros a ser salvo
*
**************************************************************************/
void salvadb(struct pessoal pessoa[], int n)
{
int i;
FILE *fp;
while (fgetc(stdin) != \n) ;
printf("Salvar? (s para salvar)\n");
if ( getchar() == s)
{
112

fp = fopen(FNOME, "w");
printf("Salvando...");
for (i = 0; i < n; i++)
{
fprintf(fp, "%s %d\n", pessoa[i].nome, pessoa[i].agnum);
}
fclose(fp);
printf("Feito.\n");
}
else
printf("Alteraes no foram salvas.\n");
}

113

25

Array de Caracteres

Nas notas de aula anteriores, enfatizamos arrays de nmeros. Em geral, podemos ter arrays com elementos
de qualquer um dos tipos vistos at agora (incluindo arrays visto nas notas de aula 9). Nesta seo,
apresentaremos arrays com elementos do tipo char.
Abaixo, apresentamos um exemplo de programa que define e inicializa um array de caracteres, e depois
imprime o array em ordem reversa.
#include <stdio.h>
int main(void)
{
char arr1[] = {c,i,2,0,8};
int i;
for (i = 4; i >= 0; -= 1i)
printf("%c", arr1[i]);
}
Arrays de caracteres so usados para armazenar texto, mas muito inconveniente se tivermos que colocar
cada caractere entre apstrofes. A alternativa dada pela linguagem C
char arr2[] = "ci208" ;
Neste caso, ci208 um string de caracteres ou uma constante do tipo string. Ns j usamos strings
antes, com as funes printf() e scanf() (constantes do tipo string esto sempre entre aspas - "):
printf("Entre com a nota para o estudante 2: ");
scanf("%d", &gr2);

26

Strings

Strings so arrays de caracteres (arrays com elementos do tipo char) que DEVEM terminar com \0 (o
caracter NULL). Se voc usa o nome NULL em seu programa, ento necessria a definio #define NULL \0.
No exemplo acima, embora no tenhamos escrito explicitamente o caracter NULL, o compilador automaticamente o colocou como o ltimo elemento do array arr2[]. Portanto, o tamanho de arr2[]
6: 5 para os caracteres que digitamos (ci208) e 1 para o caractere NULL que o compilador introduziu
automaticamente. As definies abaixo so equivalentes.
char arr2[] =

{c,i, , 2,0,8,\0};

char arr2[] =

{c,i, , 2,0,8, NULL};

O caractere NULL marca o fim de um string. Outros exemplos:


/* a maneira tediosa */
char name1[] = { j,o,s,e, ,s,i,l,v,a,\0 };
/* e a maneira facil */
char name2[] = "jose silva";

114

Embora o primeiro exemplo seja um string, o segundo exemplo mostra como strings so geralmente
escritos (como constantes). Note que se voc usar aspas quando escreve uma constante, voc no precisa
colocar \0, porque o compilador faz isso para voc.
Quando voc for criar um array de caracteres de um tamanho especfico, lembre-se de adicionar 1 para
o tamanho mximo de caracteres esperado para armazenar o caractere NULL. Por exemplo, para armazenar
o string programar e divertido, voc precisa de um array de tamanho 22 (21 caracteres + 1 para
o NULL).

26.1

Imprimindo strings com puts() e printf()

Strings podem ser impressos usando printf() com o especificador de formato %s. Por exemplo:
int main()
{
char mensagem[] = "tchau";
printf("ola\n%s\n", mensagem);
}
A sada deste programa :
ola
tchau
A funo puts() simplesmente imprime um string e depois pula de linha. Nenhuma opo de formatao pode ser definida. A funo puts() somente pega um string como argumento e o imprime. O
programa abaixo tem a mesma sada que o programa anterior.
int main()
{
char mensagem[] = "tchau";
puts("ola");
puts(mensagem);
}

26.2

Lendo strings do teclado com gets() e scanf()

A funo gets() l uma linha de texto digitado no teclado e a armazena em um string. Veja o exemplo
abaixo:
#include <stdio.h>
int main(void)
{
char nome[100];
printf("Entre seu nome: ");
gets(nome);
printf("Oi, %s.\n", nome);
}
115

Exemplo de execuo
Entre seu nome: Jose Silva
Oi, Jose Silva.
Passando um nome de array para a funo gets(), como ilustrado no programa acima, coloca a linha
inteira digitada pelo usurio no array nome (tudo at que seja teclado enter). Note que se o usurio digitar
caracteres demais (neste caso, mais de 99 caracteres), isso causar um erro de acesso fora dos limites (que
pode ser PERIGOSO !!)
A funo scanf() pode ser usada de maneira similar. A nica diferena que o scanf() l somente
a primeira palavra (tudo at que de digite um separador um espao em branco, tabulao, ou enter). Alm
disso, como estamos passando um array como argumento para o scanf(), O & QUE GERALMENTE
PRECEDE O ARGUMENTO NO DEVE ESTAR PRESENTE.
#include <stdio.h>
int main()
{
char nome[100];
printf("Entre seu nome: ");
scanf("%s", nome);
printf("Oi, %s.\n", nome);
}
Exemplo de execuo
Entre seu nome: Jose Silva
Oi, Jose.
Note que somente o primeiro nome lido pelo scanf() porque a funo pra no primeiro espao em
branco que encontra (enquanto gets() pra quando encontra um enter).

26.3

Array de Strings

Em notas de aula anteriores, vimos alguns exemplos de arrays de arrays (matrizes ou tabelas). Como strings
so tambm arrays, podemos definir arrays de strings. O programa abaixo inicializa um array de strings
com nomes e os imprime.
#include <stdio.h>
#define NUM_NOMES 5
#define TAM
20

/* define a quantidade de nomes no array */


/* define o tamanho maximo do nome */

int main()
{
char nomes[NUM_NOMES][TAM] = {"Jose Silva",
"Maria Silva",
"Antonio dos Santos",
"Pedro dos Santos",
"Joao da Silva"};
int i;
116

for(i = 0; i < 5; i += 1)
printf("%s\n", nomes[i]);
}

A sada deste programa :


Jose Silva
Maria Silva
Antonio dos Santos
Pedro dos Santos
Joao da Silva

26.4

Funes de String

H funes para manipulao de string j definidas na biblioteca padro C chamada string.h. Todas as
funes que apresentaremos nesta seo so parte desta biblioteca. Portanto, se seu programa utilizar uma
destas funes voc deve incluir a linha #include <string.h> no incio do seu programa.
O objetivo desta seo mostrar como estas funes poderiam ser implementadas como exemplos de
programas de manipulao de strings.
26.4.1

A funo strlen()

A funo strlen() tem como argumento um string. Ela retorna um inteiro que o comprimento do
string (o nmero de caracteres do string, no contando o caractere NULL). Por exemplo, o comprimento do
string alo 3.
#include <stdio.h>
#include <string.h>
int main(void)
{
char nome[100];
int comprimento;
printf("Entre seu nome: ");
gets(nome);
comprimento = strlen(nome);
printf("Seu nome tem

%d caracteres.\n", comprimento);

}
Um exemplo de execuo:
Entre seu nome: Dostoevsky
Seu nome tem 10 caracteres.
Abaixo, mostramos como a funo strlen() poderia ser implementada.
int strlen( char str[] )
{
int comprimento = 0;
117

while ( str[comprimento] != NULL )


++comprimento;
return comprimento;
}
26.4.2

A funo strcmp()

A funo strcmp() usada para comparar dois strings. Lembre que no podemos usar ==, como em
str1 == str2, para comparar dois strings, uma vez que strings so arrays. Strings devem ser comparados caractere por caractere. A funo strcmp() tem como argumento dois strings e retorna um inteiro.
Strings so ordenados de forma similar a maneira como palavras so ordenadas em um dicionrio. Ordenamos palavras em um dicionrio alfabeticamente, e ordenamos strings respeitando a ordem dos caracteres
no conjunto de caracteres da mquina. A ordenao abaixo vlida em qualquer computador:
0 < 1 < ... < 8 < 9
A < B < ... < Y < Z
a < b < ... < y < z
A ordem relativa do trs conjuntos (dgitos, letras maisculas e letras minsculas) depende do computador
utilizado.
Se s1 e s2 so strings, o resultado da chamada de funo strcmp(s1, s2) :
se s1 =s s2, strcmp() retorna 0
se s1 <s s2, strcmp() retorna um nmero negativo (< 0)
se s1 >s s2, strcmp() retorna um inteiro positivo (> 0)
(onde =s , <s e >s so =, < e > para strings)
s1 <s s2 significa s1 vem antes de s2 no dicionrio. Exemplos: tudo menor que xadrez,
calor menor que calorao, frio menor que quente, e claro o string vazio , NULL,
menor que qualquer string.
Considere o exemplo abaixo que usa strcmp():
#include <stdio.h>
#include <string.h>
int main(void)
{
char palavra1[100], palavra2[100];
int resultado;
printf("entre com uma palavra: ");
gets(palavra1);
printf("entre outra palavra: ");
gets(palavra2);
resultado = strcmp(palavra1, palavra2);
if (resultado == 0)
printf("igual\n");
else if (resultado > 0)
printf("o primeiro e maior\n");
118

else
printf("o segundo e maior\n");
}
Aqui est um exemplo de como a funo strcmp() poderia ser implementada.
int strcmp( char s1[], char s2[] )
{
int i = 0;
while (1)
{
if (s1[i] == NULL && s2[i] == NULL)
return 0;
else if (s1[i] == NULL)
return -1;
else if (s2[i] == NULL)
return 1;
else if (s1[i] < s2[i])
return -1;
else if (s1[i] > s2[i])
return 1;
else
++i;
}
}
Na biblioteca padro, a funo strcmp() faz distino entre letras maisculas e minsculas. Se voc
no quer que a funo faa esta distino, voc pode modificar o seu string para ter apenas letras minsculas
(ou maisculas) antes de pass-lo como argumento para a funo strcmp(). Para fazer isso, voc pode
usar a funo da biblioteca padro tolower(), que tem como argumento um caractere. Se o caractere
passado uma letra maiscula, ele retorna esta letra minscula; caso contrrio, retorna o mesmo caractere.
Por exemplo: tolower(A) a, tolower(1) 1, tolower(a) a.
26.4.3

A funo strcpy()

A funo strcpy() usada para copiar o contedo de um string para outro. Ela tem dois argumentos:
strcpy(s1, s2) copia o contedo do string s2 para o string s1. A funo strcpy() que apresentamos abaixo no retorna um valor. Seu prottipo
void strcmp(char [], char []);
O exemplo abaixo mostra a utilizao do strcpy().
#include <stdio.h>
#include <string.h>
int main(void)
{
char pal[100], palCopia[100];
printf("entre com uma palavra: ");
119

gets(pal);
strcpy(palCopia, pal);
printf("entre outra palavra: ");
gets(pal);
printf("voce entrou primeiro: %s\n", palCopia);
}
Embora este programa pudesse ter sido escrito sem usar strcpy(), o objetivo mostrar que se pode
usar strcpy() para fazer atribuio de strings.
A funo strcpy() poderia ter sido implementada da seguinte forma:
void strcpy( char s1[], char s2[] )
{
int i = 0;
while ( s2[i] != NULL )
{
s1[i] = s2[i];
++i;
}
s1[i] = s2[i];
}

120

27

Estruturas

A estrutura de dados array usada para conter dados do mesmo tipo junto. Dados de tipos diferentes
tambm podem ser agregados em tipos chamados de estruturas ou registros (tipo struct em linguagem
C). Primeiro, o tipo estrutura declarado (precisamos especificar que tipos de variveis sero combinados
na estrutura), e ento variveis deste novo tipo podem ser definidas (de maneira similar que usamos para
definir variveis do tipo int ou char).

27.1

Declarao de Estruturas

Uma declarao de estrutura declara um tipo struct. Cada tipo struct recebe um nome (ou tag). Referese quele tipo pelo nome precedido pela palavra struct. Cada unidade de dados na estrutura chamada
membro e possui um nome de membro. Os membros de uma estrutura podem ser de qualquer tipo. Declaraes de estrutura no so definies. No alocada memria, simplesmente introduzida um novo tipo de
estrutura.
Geralmente declaraes de estruturas so globais. Elas so colocadas prximas ao topo do arquivo com
o cdigo fonte do programa, assim elas so visveis por todas as funes (embora isto dependa de como a
estrutura est sendo usada).
A forma padro de declarao de uma estrutura :
struct nome-estrutura {
declarao dos membros
} definio de variveis (optional);
Abaixo se apresenta um exemplo de um tipo estrutura que contm um membro do tipo int e um outro
membro do tipo char.
struct facil {
int num;
char ch;
};
Esta declaracao cria um novo tipo chamado struct facil que contm um inteiro chamado num e
um caracter chamado ch.

27.2

Definio de variveis de um tipo estrutura declarado

Como acontece com qualquer outro tipo de dados, variveis de tipos de estruturas so definidas fornecendo
o nome do tipo e o nome da varivel. Considere a definio abaixo relativa a uma varivel com o nome
fac1 que do tipo struct facil:
struct facil fac1;
Tal definio est associada com a alocao de memria: memria suficiente ser alocada para guardar
um int e um char (nesta ordem). Como qualquer outra varivel, fac1 tem um nome, um tipo, e um
endereo associados.
Variveis de estruturas possuem tambm valores, e como outras variveis locais, se elas no tem atribudas um valor especfico, seu valor indefinido.
possvel definir variveis durente a declarao do tipo estrutura:
struct facil {
int num;
char ch;
} fac1;
121

Note-se que sem conflito, nomes de membros (tais como num e ch) podem ser usados como nomes de
outras variveis independentes (fora do tipo estrutura definido) or como nomes de membros em outros tipos
estrutura. No entanto, deve-se evitar situaes que criem confuso.

27.3

Acesso a membros de uma estrutura: ponto (.), o operador membro de estrutura

Dada uma varivel de estrutura, um membro especfico referenciado usando o nome da varivel seguida de
. (ponto) e pelo nome do membro da estrutura. Assim, as seguintes referncias a membros de uma estrutura
so vlidas:
fac1.num se refere ao membro com nome num na estrutura fac1;
fac1.ch se refere ao membro com nome ch na estrutura fac1.
Membros de estrutura (como fac1.num) so variveis, e podem ser usadas como valores (no lado
direito de uma atribuio, em expresses como argumentos para funes), ou como lvalues (no lado esquerdo
de atribuies, com operadores de incremento/decremento ou com o operador de endereo (&)). O exemplo
abaixo mostra alguns exemplos do uso de membros de estrutura:
fac1.ch = G;
fac1.num = 42;
fac1.num++;
if (fac1.ch == H) {
printf("%d\n", fac1.num);
}
Tentar acessar um nome de membro que no existe causa um erro de compilao.

27.4

Operadores usados com variveis de estrutura: valores e lvalues

Uma varivel de estrutura pode ser tratada como um objeto simples no todo, com um valor especfico
associado a ela (a estrutura fac1 tem um valor que agrega valores de todos os seus membros). Note a
diferena com arrays: se arr[] um array de tamanho 2 definedo como int arr[2] = {0,1};, o
nome arr2 no se refere ao valor coletivo de todos os elementos do array. Na verdade, arr2 um ponteiro
constante e se refere ao endereo de memria onde o array se inicia. Alm disso arr2 no um lvalue e
no pode ser mudado. Variveis de estrutura so diferentes. Elas podem ser usadas como valores e lvalues,
mas com certas limitaes.
Os nicos usos vlidos de uma varivel de estrutura so dos dois lados de um operador de atribuio (=), como operando do operador de endereo & (obtendo o endereo da estrutura), e referenciando
seus membros.
De todas as variaes de atribuio (incluindo o incremento e decremento) atribuio de estruturas pode
ser usada APENAS com =. O uso de outros operadores de atribuio ou de incremento causar um erro de
compilao A atribuio de um valor de estrutura para outro copia todos os membros de uma estrutura para
outra. Mesmo que um dos membros seja um array ou outra estrutura, ela copiada integralmente. As duas
estruturas envolvidas na atribuio devem ser do mesto tipo struct. Considere o seguinte exemplo:
struct facil {
int num;
char ch;
};
main()
{
122

struct facil fac1, fac2;


fac1.num = 3;
fac1.ch = C;
/* Atribuindo fac1 a fac2 */
fac2 = fac1;
}
Lembre-se que este tipo de atribuio ilegal com arrays. Tentar fazer isto com dois arrays causa um
erro de compilao (uma vez que nomes de arrays so ponteiros constantes).
int a[5], b[5];
/* Est errado -- No ir compilar */
a = b;

27.5

Inicializao de estruturas

Variveis de estruturas no-inicializadas contm valores indefinidos em cada um de seus membros. Como
em outras variveis, variveis de estruturas podem ser inicializadas ao serem declaradas. Esta inicializao
anloga ao que feito no caso de arrays. O exemplo abaixo ilustra a inicializao de estruturas:
struct facil {
int num;
char ch;
};
main()
{
struct facil fac1 = { 3, C }, fac2;
fac2 = fac1;
}
Uma lista de valores separados por vrgula fica entre chaves ({ and }). Os valores de inicializao devem
estar na mesma ordem dos membros na declarao da estrutura.

27.6

Estruturas como argumentos de funo e valores de retorno

Como qualquer outro valor do tipo int ou float, valores de estruturas podem ser passados como argumentos para funes, e podem ser retornados de funes. O exemplo abaixo ilustra tal prorpiedade:
#define LEN 50
struct endereco {
char rua[LEN];
char cidade_estado_cep[LEN];
};
struct endereco obtem_endereco(void);
void imprime_endereco(struct endereco);
123

struct endereco obtem_endereco(void)


{
struct endereco ender;
printf("\t Entre rua: ");
gets(ender.rua);
printf("\t Entre cidade/estado/cep: ");
gets(ender.cidade_estado_cep);
return ender;
}
void imprime_endereco(struct endereco ender)
{
printf("\t %s\n", ender.rua);
printf("\t %s\n", ender.cidade_estado_cep);
}
main()
{
struct endereco residencia;
printf("Entre seu endereco residencial:\n");
residencia = obtem_endereco();
printf("\nSeu endereco eh:\n");
imprime_endereco(residencia);
}
No exemplo acima, a estrutura struct endereco contm dois arrays de tamanho 50. Dentro da
funo obtem_endereco(), a varivel ender declarada como sendo do tipo struct endereco.
Aps usar gets() para o fornecimento da informao, o valor de ender retornado para main(), de
onde a funo obtem_endereco() foi chamada. Este valor ento passado para a funo imprime_endereco(),
onde o valor de cada membro da estrutura exibido na tela.
Este programa pode ser comparado ao programa abaixo, que usa valores do tipo int no lugar de valores
do tipo struct endereco (claro que a informao lida e exibida um simples valor numrico, e no
um nome de rua, etc.):
int obtem_int(void);
void imprime_int(int);
int obtem_int(void)
{
int i;
printf("Entre valor: ");
scanf("%d", &i);
return i;
}
124

void imprime_int(int i)
{
printf("%d\n", i);
}
main()
{
int valor;
valor = obtem_int();
printf("\nSeu valor:\n");
imprime_int(valor);
}

27.7

Arrays de estruturas

Arrays de estruturas so como arrays de qualquer outro tipo. Eles so referenciados e definidos da mesma
forma. O exemplo abaixo anlogo ao exemplo de endereo apresentado anteriormente, exceto que uma
quantidade de NUM endereos armazenada ao invs de apenas um.
#define LEN 50
#define NUM 10
struct endereco {
char rua[LEN];
char cidade_estado_cep[LEN];
};
void obtem_endereco(struct endereco [], int);
void imprime_endereco(struct endereco);
void obtem_endereco(struct endereco aender [], int index)
{
printf("Entre rua: ");
gets(aender[index].rua);
printf("Entre cidade/estado/cep: ");
gets(aender[index].cidade_estado_cep);
}
void imprime_endereco(struct endereco ender)
{
printf("%s\n", ender.rua);
printf("%s\n", ender.cidade_estado_cep);
}
main()
{
struct endereco residencias[NUM];
int i;
125

for (i = 0; i < NUM; i++) {


printf("Entre o endereco da pessoa %d:\n", i);
obtem_endereco(residencias,i);
}
for (i = 0; i < NUM; i++) {
printf("endereco da pessoa %d:\n", i);
imprime_endereco(residencias[i]);
}
}
Neste programa, o array residencias passado para obtem_endereco(), juntamente com o indice onde deve ser guardado o novo endereo. Depois, cada elemento do array passado para imprime_endereco()
um por vez.
Observe-se ainda na funo obtem_endereco() como os membros de cada elemento do array podem ser acessados. elements da estrutura em can be accessed as well. Por exemplo, para acessar a rua do
elemento residencias[0] usa-se:
printf("%s\n", residencias[0].rua);
printf("%s\n", residencias[0].cidade_estado_cep);

27.8

Estruturas aninhadas

Como definido anteriormente, membros de estruturas podem ser de qualquer tipo. Isto inclui outras estruturas. Abaixo define-se duas estruturas, a segunda tendo membros que so tambm estruturas:
#define LEN 50
struct endereco {
char rua[LEN];
char cidade_estado_cep[LEN];
};
struct student {
char id[10];
int idade;
struct endereco casa;
struct endereco escola;
};
struct student pessoa;
Dadas estas definies, pode-se potencialmente acessar os seguintes campos de pessoa, uma varivel
do tipo struct student:
pessoa.id
pessoa.casa.rua
pessoa.casa.cidade_estado_cep
pessoa.escola.rua
pessoa.escola.cidade_estado_cep
Note o uso repetido de . quando se acessa membros dentro de membros.
126

28

Ponteiros

Em linguagem C a cada varivel est associado: (i) um nome; (ii) um tipo; (iii) um valor; e (iv) um endereo.
Considere as seguintes definies de variveis.
int i = 5;
char c = G;
Na memria, eles podem estar armazenados da forma abaixo:

A varivel inteira i est armazenada no endereo 1342. Ela usa dois bytes de memria (quando um
objeto usa mais de um byte, seu endereo onde ele comea neste caso, 1342 e no 1343). A varivel do
tipo char c est armazenada no endereo 1346 e usa um byte de memria. O compilador que controla
do local de armazenamento destas variveis em memria.

28.1

O operador de endereo (&)

Ns podemos usar o operador de endereo para determinar o endereo de uma objeto na memria. Este
operador s pode ser usado com lvalues (objetos que podem estar no lado esquerdo de uma atribuio,
como no caso de variveis) porque lvalues tem um endereo alocado na memria.
Por exemplo, no exemplo acima, poderamos usar o operador de endreo como nas expresses abaixo:
&i tem valor 1342
&c tem valor 1346

28.2

Tipo ponteiro

Em C, uma varivel que contm um endereo de memria uma varivel do tipo ponteiro. Um valor, que
um endereo (como &a) um valor de ponteiro. Quando um ponteiro (a varivel) contm um determinado
endereo, dizemos que ele aponta para o endereo de memria. Alm disso, se o valor deste ponteiro o
endereo de uma outra varivel qualquer, dizemos que tal ponteiro aponta para esta outra varivel.
H um tipo distinto de ponteiro para cada tipo bsico C (como int, char e float). verdade que todos os endereos tem o mesmo tamanho4 , mas ns tambm precisamos saber algo sobre o que armazenado
no endereo de memria apontado (quantos bytes ocupa e como os bytes devem ser interpretados).
Assim, a declarao de um tipo ponteiro em C feita da seguinte forma:
tipo *nome_var;
4

O operador sizeof() pode ser usado para determinar o tamanho de um ponteiro. Por exemplo, sizeof (char *)

127

Esta declarao indica que est sendo definido um ponteiro para tipo chamado nome_var. Por exemplo,
um tipo ponteiro usado para apontar para inteiros chamado ponteiro para int e isso denotado por um
int *. Variveis do tipo ponteiro para int so usadas para armazenar endereos de memria que contem
valores do tipo int.
Dadas as definies de i e c acima, ns podemos definir duas novas variveis pi e pc, ambos do tipo
ponteiro.
int *pi;
char *pc;
Nesta definio as variveis no foram inicializadas com nenhum valor. Podemos inicializ-las com:
pi = &i;
pc = &c;
Depois destas atribuies, o valor de pi seria 1342, e o valor de pc seria 1346.
Note que nesta definio da varivel int *pi, pi o nome da varivel e int * o tipo de pi
(ponteiro para int).

28.3

O operador de dereferncia: *

Quando um ponteiro aponta para um endereo de memria, a operao para acessar o contedo do endereo
apontado chamado de dereferncia. O operador unrio * usado para fazer a dereferncia. Note que este
uso do smbolo * no tem relao com o smbolo de multiplicao. Usando os exemplos anteriores, *pi
o objeto apontado por pi (no caso, o valor de um inteiro).
*pi tem valor 5
*pc tem valor G
Como um ponteiro dereferenciado (tais como *pi ou *pc) refere-se a um objeto na memria, ele pode
ser usado no s como valor, mas tambm como um lvalue. Isto significa que um ponteiro dereferenciado
pode ser usado no lado esquerdo de uma atribuio. Veja alguns exemplos:
printf("Valor= %d, Char = %c\n", *pi, *pc);
*pi = *pi + 5;
*pc = H;
*pi no lado esquerdo do = refere-se ao endereo de memria para o qual pi aponta. *pi no lado direito
do = refere-se ao valor armazenado no endereo apontado por pi. A sentena *pi = *pi + 5; faz com
que o valor armazenado no endereo apontado por pi seja incrementado de 5. Note que o valor de *pi
muda, no o valor de pi.
Neste exemplo, os valores das variveis i e c poderiam ter sido alterados sem a utilizao de ponteiros
da seguinte forma:
printf("Valor = %d, Char = %c\n", i, c);
i = i + 5;
c = H;
Os exemplos acima ilustram como uma varivel pode ser acessada diretamente (atravs do seu nome)
ou indiretamente (atravs de um ponteiro apontando para o endereo da varivel).

28.4

Atribuies envolvendo ponteiros

Um ponteiro pode ter atribudo a si um valor que seja o endereo de memria onde est armazenado um valor
do mesmo tipo do ponteiro. Isto ocorre quando se usa o operador de endereo visto acima, ou quando se usa
o valor de um outro ponteiro que aponte para um objeto do mesmo tipo do primeiro ponteiro. Observe-se o
exemplo abaixo:
128

int *p1, *p2, x;


float *p3;
p1 = &x;
p2 = p1;
p3 = p1;

/* Correto */
/* Correto */
/* Incorreto. Compilador acusa "Warning". */

No exemplo acima, a linguagem C admite a atribuio de um ponteiro para outro de outro tipo (p3 = p1;),
mas a compilao acusa uma mensagem de aviso. Posteriormente sero vistas situaes em que a atribuio
de ponteiros de tipos diferentes devem ocorrer e como devem ser manipuladas em C.

28.5

Aritmtica de ponteiros

Apenas as operaes de adio e subtrao (e operadores C associados) so permitidos com ponteiros.


Assim, possvel adicionar ou subtrair valores inteiros de ponteiros.
Operaes de soma, subtrao e comparao entre ponteiros tambm so vlidas, desde que os ponteiros
envolvidos apontem para o mesmo tipo de dados. Ainda assim, o resultado somente ter algum sentido
prtico se os ponteiros apontarem tambm para o mesmo objeto.
Alguns exemplos:
int num[20], *pnum, diff;
char str[30], *pstr, *pn, char nome[20];
pn = nome;
pstr = str;
pnum = num;
pnum += 3;
*pnum = 10;

/* pnum = &num[3] */
/* equivale a num[3] = 10 */

pstr++;

/* pstr = &str[1] */

diff = pstr - pnum;

/* INCORRETO. Os ponteiros apontam para


* tipos diferentes
*/
/* CORRETO, mas o valor no tem
* necessriamente o sentido de "numero
* de bytes entre pn e pstr".
*/

diff = pstr - pn;

pn = str;
pstr = &str[30];
diff = pstr - pn;

/* CONCEITUALMENTE CORRETO. diff == 30 */

Um ltimo ponto a respeito de operaes sobre ponteiros: Adicionar um ponteiro a outro no produz
nenhum resultado prtico ou vlido.

28.6

Ponteiros e Arrays

Em C, o nome de uma varivel que foi declarada como array representa um ponteiro que aponta para o
incio do espao de armazenamento do array, isto , o endereo de memria do primeiro byte associado ao
primeiro elemento do array:
129

char nome[20],
*pstr;
int val[10],
*ptr;

pstr = nome;
ptr = val;

/* Equivalente a pstr = &nome[0] */


/* Equivalente a ptr = &val[0] */

pstr = nome + 4;
ptr = val + 5;

/* Equivalente a pstr = &nome[4] */


/* Equivalente a ptr = &val[5] */

pstr = nome++;

/* ATENCAO: INCORRETO !!! */


/* "nome" NO UM PONTEIRO */

Se um ponteiro aponta para um array, pode-se usar indistintamente as formas abaixo para acessar os
elementos do array:
int val[10],
x,
*ptr;
ptr = val;
*(ptr + 3) = 7;
ptr[3] = 10;

ptr += 4;
ptr[3] = 20;

28.7

/* Equivalente a ptr = &val[0] */


/* val[3] = 7 */
/* val[3] = 10 */
/* Equivalente a *(ptr + 3) = 10 */

/* ATENCAO:

val[7] = 20 */

Ponteiros e Estruturas

Como em qualquer outro tipo, ponteiros para estruturas podem ser definidos. Considere o exemplo abaixo:
/* declara uma estrutura */
struct facil {
int num;
char ch;
};
main()
{
/* definioes de variaveis */
struct facil fac,
/* uma variavel do tipo "struct facil" */
/* um ponteiro para "struct facil" */
*pfac;
pfac = &fac;

(*pfac).num = 32; /* o membro "num" da "struct facil" apontada por "pfac"


(*pfac).ch = A; /* o membro "char" da "struct facil" apontada por "pfac
}
130

Como se espera, quando se usa um ponteiro para um tipo struct, o ponteiro deve ter assinalado a
si um valor ANTES de ser dereferenciado. A ordem pela qual um membro pode ser acessado atravs do
ponteiro, the pointer : primeiro o ponteiro dereferenciado, e ento o operador de membro de estrutura e
o nome do membro so usados para acessar um membro em particular da estrutura apontada pelo ponteiro.
Uma vez que o operador . tem precedncia mais alta que o operador * (veja Tabela 6), os parenteses so
necessrios.
28.7.1

Acesso a membros de estrutura via ponteiro: O operador ->

Uma notao do tipo (*pfac).ch confusa, de forma que a linguagem C define um operador adicional
(->) para acessar membros de estruturas atravs de ponteiros. O operador -> formalmente usado como
o operador ., exceto que ao invs do nome da varivel de estrutura, um ponteiro para o tipo struct
usado esquerda do operador ->. No exemplo acima, as duas ltimas linhas de cdigo podem portanto ser
reescritas como:
pfac->num = 32;
pfac->ch = A;

/* o mesmo que (*pfac).num = 32;


/* o mesmo que (*pfac).ch = A;

*/
*/

Basicamente, use o operador . se voc tem uma varivel de tipo struct, e o operador -> caso voc
tenha um ponteiro para um tipo struct.

28.8

Ponteiros como argumentos de funes

Nos exemplos acima, pode parecer que ponteiros no so teis, j que tudo que fizemos pode ser feito sem
usar ponteiros. Agora, considere o exemplo da funo troca() abaixo, que deve trocar os valores entre
seus argumentos:
#include <stdio.h>
void troca(int, int);
void troca(int x, int y)
{
int temp;
temp = x;
x = y;
y = temp;
}
main(void)
{
int a, b;
printf("Entre dois numeros: ");
scanf("%d %d", &a, &b);
printf("Voce entrou com %d e %d\n", a, b);
131

/* Troca a e b */
troca(a, b);
printf("Trocados, eles sao %d e %d\n", a, b);
}

Quando a e b so passados como argumentos para troca(), na verdade, somente seus valores so
passados. A funo no pode alterar os valores de a e b porque ela no conhece os endereos de a e b.
Mas se ponteiros para a e b forem passados como argumentos ao invs de a e b, a funo troca() seria
capaz de alterar seus valores; ela saberia ento em que endereo de memria escrever. Na verdade, a funo
no sabe que os endereos de memria so associados com a e b, mas ela pode modificar o contedo destes
endereos. Portanto, passando um ponteiro para uma varivel (ao invs do valor da varivel), habilitamos a
funo a alterar o contedo destas variveis na funo chamadora.
Uma vez que endereos de variveis so do tipo ponteiro, a lista de parmetros formais da funo deve
refletir isso. A definio da funo troca() deveria ser alterada, e a lista de parmetros formais deve
ter argumentos no do tipo int, mas ponteiros para int, ou seja, int *. Quando chamamos a funo
troca(), ns no passamos como parmetros reais a e b, que so do tipo int, mas &a e &b, que so
do tipo int *. Dentro da funo troca() dever haver mudanas tambm. Uma vez que agora os
parmetros formais so ponteiros, o operador de dereferncia, *, deve ser usado para acessar os objetos.
Assim, a funo troca() capaz de alterar os valores de a e b remotamente.
O programa abaixo a verso correta do problema enunciado para a funo troca():
#include <stdio.h>
void troca(int *, int *);
/* function troca(px, py)
troca os valores inteiros apontados por px e py
* acao:
entrada:
apontadores
px e py
*
valor de *px e *py trocados
* saida:
suposicoes:
px e py sao apontadores validos
*
primeiro guarda o primeiro valor em um temporario e troca
* algoritmo:
*/
void troca(int *px, int *py)
{
int temp;
temp = *px;
*px = *py;
*py = temp;
}
main(void)
{
int a, b;
printf("Entre dois numeros: ");
scanf("%d %d", &a, &b);
printf("Voce entrou com %d e %d\n", a, b);
132

/* Troca a e b -- passa enderecos */


troca(&a, &b);
printf("Trocados, eles sao %d e %d\n", a, b);
}

A sada deste programa :


Entre dois numeros: 3 5
Voce entrou com 3 e 5
Trocados, eles sao 5 e 3
Basicamente, se a funo precisa alterar o valor de uma varivel na funo chamadora, ento passamos
o endereo da varivel como parmetro real, e escrevemos a funo de acordo, ou seja, com um ponteiro
como parmetro formal.
28.8.1

Arrays como argumentos de funes

Quando um array passado como argumento para uma funo, somente o ponteiro para a primeira posio
do array passada e no o contedo de todo o array. Arrays so portanto passados por referncia e no por
valor.
Ao se definir um array como o argumento formal de uma funo em C, duas formas podem ser usadas.
Elas podem ser vistas abaixo nas definies das funes func_1() e func_2().
func_1 (char vet [], int ivet[])
{
vet[3] = A;
vet++;
ivet += 3;
}
func_2 (char *vet, int *ivet)
{
vet[4] = B;
vet++;
ivet += 3;
}
main()
{
char ender[20];
char vals[20];
func_1(ender, vals);
func_2(ender, vals);
}
Observe no exemplo acima que a passagem dos arrays ao se chamar as funes func_1() e func_2()
feita da mesma forma: Usa-se o NOME das variveis declaradas como arrays. Note tambm o uso dos
argumentos formais nas funes: vet e ivet podem ser usadas como ponteiros ou como nomes de arrays
(com a notao indexada por []).
133

28.8.2

Ponteiros para estruturas como argumentos de funes

Quando estruturas so passadas como argumentos para funes o valor de todo o objeto agregado passado
literalmente. Alm disso, se este valor alterado na funo, ele deve ser retornado (via return), o que
implica em copiar de volta toda a estrutura. Isto pode ser bastante ineficiente no caso de uma estrutura
grande (com muitos membros, com membros de tamanho grande como arrays, etc.). Assim, em alguns
casos melhor passar ponteiros para estruturas. Repare a diferena com arrays passados como argumentos
para funes vista na seo anterior.
O programa abaixo um exemplo do uso de passagem de ponteiros de estruturas para funes:
#define LEN 50
struct endereco {
char rua[LEN];
char cidade_estado_cep[LEN];
};
void obtem_endereco(struct endereco *);
void imprime_endereco(struct endereco);
void obtem_endereco(struct endereco *pender)
{
printf("Entre rua: ");
gets(pender->rua);
printf("Entre cidade/estado/cep: ");
gets(pender->cidade_estado_cep);
}
void imprime_endereco(struct endereco ender)
{
printf("%s\n", ender.rua);
printf("%s\n", ender.cidade_estado_cep);
}
main()
{
struct endereco residencia;
printf("Entre seu endereco residencial:\n");
obtem_endereco(&residencia);
printf("\nSeu endereco:\n");
imprime_endereco(residencia);
}
Neste caso, main() passa para a funo obtem_endereco() um ponteiro para a variavel residencia.
obtem_endereco() pode ento alterar o valor de residencia remotamente. Este valor, em main(),
ento passado para imprime_endereco(). Note-se que no necessrio passar um ponteiro para a
estrutura se seu valor no ser mudado (como o caso da funo imprime_endereco()).
De um modo geral, melhor passar ponteiros para estruturas ao invs de passar e retornar valores
de estruturas. Embora as duas abordagens sejam equivalentes e funcionem, o programa ir apenas passar
134

ponteiros ao invs de toda uma estrutura que pode ser particularmente grande, implicando em um tempo
final de processamento maior.

28.9

Precedncia de operadores

A precedncia dos operadores * e & alta, a mesma que outros operadores unrios. A tabela 6 apresenta a
precedncia de todos os operadores vistos at agora.
Operador

Associatividade

()
!
*
+
<
==
&&
||
=
,

esquerda para direita


direita para esquerda
esquerda para direita
esquerda para direita
esquerda para direita
esquerda para direita
esquerda para direita
esquerda para direita
direita para esquerda
esquerda para direita

[] -> .
- ++ -- * & (cast) (unrios)
/ %
<= > >=
!=

+=

-=

*=

/=

%=

Tabela 6: Precedncia e associatividade de operadores

135

29

Strings e Ponteiros

Vimos anteriormente que strings em C nada mais so que arrays de elementos do tipo char cujo ltimo
elemento o caracter \0 (nulo). At agora a declarao e inicializao de um string tem sido como abaixo:
char nome[] = "Um string de caracteres";
char str[20] = "Um outro string de caracteres";
No exemplo acima, cada conjunto de caracteres limitados por um par de aspas denominado de uma
constante string. Como toda expresso em C , uma constante string tem um valor e um tipo associado.
O compilador C automaticamente aloca espao no programa para armazenar este string. Esta constante
na verdade est definindo um array que est armazenado em um endereo de memria mas que no tm um
nome de varivel associado a ele. O valor produzido para esta constante string um ponteiro para este array
annimo. E o tipo da constante um ponteiro para char.
Assim, na maior parte dos casos de programas em C , mais conveniente representar um string atravs
de um ponteiro para char. O valor inicial deste ponteiro deve ser sempre o endereo do primeiro caracter
do string ou uma constante string. O exemplo acima pode ser reescrito como abaixo, que deve ser a forma
preferida para se declarar e inicializar strings em programas em linguagem C :
char *nome = "Um string de caracteres";
char *str = "Um outro string de caracteres";

30 Arrays de ponteiros
Da mesma forma que se pode ter arrays de tipos bsicos (e.g. int, char) e de estruturas, pode-se tambm
definir arrays de ponteiros:
char *frases[60];
frases[0] = "Um string de caracteres";
frases[3] = "Um outro string de caracteres";
frases[10] = "Mais uma frase muito mais comprida que as outras acima";
frases[20] = "Agora uma frasesita pequena";
frases[30] = "Chega de frases";
puts(frases[3]);
No exemplo acima cada elemento do array frase um ponteiro para char. Em cada uma das atribuies, cada elemento do array recebe como valor um ponteiro para um string (veja seo anterior sobre
constantes string).

31

Argumentos de linha de comando: a funo main

Uma aplicao de arrays de ponteiros surge de imediato quando voc deseja acessar os argumentos digitados
para seu programa na linha de comando.
Para isto, a funo main manipula dois argumentos quando o programa inicia a execuo:
main (int argc, char *argv[])
{
.....
}
136

O primeiro argumento, denominado argc por conveno, representa o nmero de argumentos digitados
na linha de comando, incluindo o nome do programa na linha de comando, que definido como o primeiro
argumento. Desta forma o valor de argc sempre maior ou igual a 1 (um).
Em UNIX, um argumento considerado como uma sequncia de caracteres at a primeira ocorrncia do
caracter de espaamento, que pode ser um espao em branco, tab ou o caracter de mudana de linha5 .
O segundo argumento de main o array de ponteiros para caracteres chamado argv, por conveno.
O primeiro ponteiro em argv, argv[0], aponta para o nome do programa em execuo. Os elementos
sucessivos do array, argv[1], argv[2], ..., argv[argc - 1], contm ponteiros para os argumentos
digitados na linha de comando aps o nome do programa.
Como um exemplo, considere a execuo do programa chamado nroff. Se a linha de comando abaixo
digitada:
nroff -mm -TXR memo1
ento o valor de argc ser 4 (o nome do programa mais os trs argumentos que se seguem), e o
argumento argv ser um array de ponteiros para caracteres. O primeiro elemento deste array aponta para
o string "nroff", o segundo para -mm", o terceiro para -TXR" e finalmente o quarto elemento aponta para
o string "memo1". Isto pode ser melhor visualizado na Figura 3.

Figura 3: Argumentos na linha de comando

32

Alocao dinmica de memria

Quando voc declara um array em um programa C , voc deve informar quantos elementos devem ser
reservados para este array. Se voc conhece este nmero a priori, tudo est bem.
No entanto, o tamanho de um array pode ser desconhecido por uma srie de fatores. Por exemplo, se
voc deseja ler todas as linhas de um arquivo e armazen-los em um array de seu programa, o tamanho
de memria necessrio depender do tamanho do arquivo. E se este tamanho variar muito de um arquivo
para outro, voc ter que reservar espao suficiente para acomodar o maior tamanho de arquivo possvel, o
que um desperdcio se seu programa vai lidar com muitos arquivos de tamanho reduzido e apenas alguns
arquivos com tamanho grande.
Definir um tamanho mximo para o suas estruturas de dados aumenta o tamanho de seu programa. Em
um ambiente multitarefa como UNIXeste seu programa estar competingo por espao livre de memria.
Se seu programa aloca espao desnecessariamente, isto quer dizer que menos processos podero ocupar
5

Aspas e apstrofes podem agrupar palavras como um argumento nico, mesmo que tenham espaamento.

137

a memria para serem executados. Portanto, durante a gerncia do processo de sua tarefa pelo sistema
operacional, a trasnferncia do programa do disco para a memria e vice-versa (processo conhecido como
swapping), ir demorar mais.
Para permitir que o espao para estruturas de dados de um programa possam ser alocados durante a
execuo do mesmo que a linguagem C define funes de biblioteca chamadas funes de alocao
dinmica de memria.
Estas funes so:
#include <stdlib.h>
void
void
void
void

*malloc (int tamanho)


*calloc (int numero_elementos, int tamanho_elemento)
*realloc (void *area_alocada, int novo_tamanho)
free (void *area_alocada)

As funes malloc() e calloc() so usadas para alocar espao para seus dados uma vez que voc
determinou quanto espao voc precisa. E se sua estimativa se revela muito grande ou muito pequena, voc
pode mudar o tamanho deste espao alocado com a funo realloc. Finalmente, uma vez que o programa
terminou de usar o espao alocado, a funo free pode ser usada para liberar o espao previamente alocado.
Observe-se a diretiva #include que necessria para o uso das funes de alocao.

32.1

malloc e calloc

As funes malloc e calloc alocam espao de memria. malloc recebe como argumento o nmero de
bytes a ser alocado. calloc recebe como primeiro argumento o nmero de elementos a aser alocado. O
segundo argumento indica o tamanho em bytes de cada elemento. calloc garante que o espao alocado
inicializado com zeros, enquanto que malloc no.
Estas duas funes retornam um ponteiro para a nova rea alocada. O tipo do ponteiro por conveno
char *. Caso o ponteiro seja usado para apontar para outro tipo qualquer, um cast no valor retornado deve
ser usado. Observe o exemplo abaixo:
#include <stdlib.h>
char buf[30],
*memchar;
int *memint, i;
....
memchar = malloc (sizeof(buf));
if (memchar == NULL) {
printf ("Erro em alocacao de memoria\n");
exit (1);
}
else {
memcpy (memchar, buf, sizeof(buf)); /* copia o contedo de
* buf para memchar
*/
}
memint = (int *) calloc (1, sizeof(int));
138

if (memint == NULL) {
printf ("Erro em alocacao de inteiros\n");
exit (1);
}
else {
i = 0;
do {
scanf("%d", memint + i);
if (memint[i]) {
memint = (int *) realloc (memint, (i + 2) * sizeof(int));
if (memint == NULL)
break;
}
} while (memint[i++]);
}
Oberve no exemplo acima o uso de cast na chamada de calloc. Como o ponteiro retornado ser usado
para apontar para inteiros, o retorno da funo deve ser convertido de acordo com o cast.
Quando no h sucesso na alocao de memria, as funes malloc e calloc retornam um ponteiro
nulo, representado pela constante NULL. Assim, toda vez que um programa usa estas funes, deve-se
testar o valor retornado para verificar se houve sucesso na alocao. o que acontece no exemplo acima ao
se testar os valores de memchar e memint imediatamente aps a chamada das funes de alocao.

32.2

realloc

A funo realloc usada para redimensionar um espao alocado previamente com malloc ou calloc.
Seus argumentos so um ponteiro para o INCIO de uma rea previamente alocada, e o novo tamanho, que
pode ser maior ou menor que o tamanho original.
realloc retorna um ponteiro para a nova rea alocada. Este ponteiro pode ser igual ao ponteiro
original se o novo tamanho for menor que o tamanho original, e diferente do ponteiro original se o novo
tamanho for maior que o tamanho original. Neste ltimo caso, realloc copia os dados da rea original
para a nova rea.
O ltimo bloco else no exemplo da seo anterior um exemplo de uso da funo realloc. Neste
bloco, usa-se a funorealloc para aumentar de 1 (um) o tamanho do array dinmico representado por
memint.

139

Referncias
[1] Allen I. Holub. Enough Rope to Shoot Yourself in the Foot: rules for C and C++ programming.
McGraw-Hill, 1995.
[2] Brian W. Kernighan and Dennis M. Ritchie. A Linguagem de Programao C. Editora Campus, 1986.
[3] Victorine Viviane Mizrahi. Treinamento em Linguagem C. Prentice-Hall, Inc., 2a edition, 2008.
[4] H. Schildt. C Completo e Total. Makron Books, 3a edition, 1997.

140