Você está na página 1de 59

Universidade Luterana do Brasil 

ULBRA­ Gravataí
Curso de Ciência da Computação

Linguagem de Programação C
Apostila com conceitos básicos

Linguagem C

Elgio Schlemer
Maio de 2009
Linguagem de Programação C ­ Elgio Schlemer ULBRA ­ 2009

1 Introdução
A linguagem "C" foi criada nos laboratórios Bell por Brian W. Kernighan e Dennis Ritchie em 
1972.   Esta   linguagem,   teve   suas   idéias   iniciais   originadas   da   linguagem   BCPL   (Basic   Combined  
Programming Language), desenvolvida por Martin Richards. Esta influência do BCPL se deu através 
de outra linguagem, chamada "B" e criada por Thompson em 1970 para o primeiro sistema operacional 
UNIX no PDP­11.

A partir de sua criação a linguagem "C" sofreu uma longa evolução sendo que uma de suas 
primeiras utilizações foi a de reescrever o sistema operacional UNIX (1973) que estava escrito em 
linguagem assembly do PDP­11. Por este motivo é que se tem associado a linguagem ao S.O. UNIX, 
visto   que   o   UNIX   é   composto,   quase   na   sua   totalidade,   de   programas   escritos   em   "C"   (Sistema 
Operacional, utilitários, compiladores, ...). Entretanto isto não implica que o "C" seja uma linguagem 
"amarrada" a um sistema operacional ou máquina.

Devido à evolução do "C", que seguia apenas o padrão descrito por Kernighan e Ritchie, tornou­
se   necessária   uma   padronização   mais   rígida   para   a   linguagem,   permitindo   a   portabilidade   dos 
softwares escritos nesta linguagem. Isto foi feito pelo ANSI (American National Standard Institute), 
criando assim o padrão C ANSI.

1.1 Características da Linguagem
O "C" é uma linguagem de propósitos gerais e que tem como características principais:
• Permite a geração de um código bem otimizado e compacto (quase tão otimizado quanto o 
assembly);
• Grande portabilidade de programas (a maioria das máquinas existentes no mercado suportam 
a linguagem "C");
• É uma linguagem de nível "relativamente baixo", mas com recursos de alto nível;
• Apresenta facilidade de manipulação direta do hardware da máquina;
• Grande "liberdade" para o programador.

Cap 1: Introdução página 2
Linguagem de Programação C ­ Elgio Schlemer ULBRA ­ 2009

2 Dados e Operadores do C
O C possui, como toda linguagem, um conjunto básico de dados, os operadores e um esqueleto 
básico de programa.

2.1 Tipos de Dados Básicos

Em C, os tipos básicos de dados são:
• char:   considerado   um   inteiro,   sendo   representado   por   apenas   1   byte.   Apesar   do   nome 
sugerir que deva ser usado para armazenar caractere, porque uma letra justamente ocupa 
um byte, nada impede que uma variável char seja usada para propósitos gerais.
• se considerar sinal, em complemento de dois: de ­128 a 127
• se não considerar sinal: de 0 a 255
• int: um valor inteiro, podendo ser 4 bytes em arquiteturas de 32 bits (antigamente era 2 
bytes, como no borland C para DOS)
• se considerar sinal, em complemento de dois: de ­2147483648 a 2147483647
• se não considerar sinal: de 0 a 4294967295
• float: um valor real de precisão simples. Ocupa 4 bytes em uma arquitetura 32 bits.
• double: um valor real de precisão dupla. Ocupa 8 bytes.
• "ponteiros": usados para indicar o endereço de alguma variável ou área de dados
Ao contrário do que ocorre nas maioria das linguagens de programação, C não possui um tipo 
para cadeias de caracteres (strings). Para utilizar strings em C é necessário declarar uma variável como 
sendo um vetor de caracteres. Desta forma para armazenar um nome de 30 caracteres usa­se o seguinte 
trecho de programa:

char nome[31];  /* Além dos 30 caracteres, deve­se reservar espaço 
                   para o final de string: 0, '\0' ou NULL 
*/

2.2 Definição de Variáveis

Para declarar uma variável ou um conjunto de variáveis, basta especificar o tipo e a seguir a lista 
de variáveis, como por exemplo:

int val;
char a,b;
double d1,d2,x,y;

Como   a  linguagem  C distingue  letras  maiúsculas  de minúsculas,  os  nomes  de variáveis   são 
sensíveis ao contexto. Variáveis podem ser declaradas fora do escopo das funções (neste caso são 
consideradas globais) ou no início de um bloco (neste caso) são locais e seus escopos estão restritos 
aos blocos em que foram declaradas).

Cap 2: Dados e Operadores do C página 3
Linguagem de Programação C ­ Elgio Schlemer ULBRA ­ 2009

A declaração de variáveis locais deve obrigatoriamente ser a primeira parte de um bloco, ou seja, 
deve vir logo após um caracter de "abre chaves", '{'; e não deve ser intercalada com instruções ou 
comandos.

{ /*  A declaração de variáveis é a primeira coisa */
  int v; /*  que deve ser feita dentro de um bloco        */
  ...

Quanto aos nomes ou identificadores usados na declaração de variáveis, deve­se considerar as 
seguintes regras:
• nomes de variáveis começam com uma letra ('A'..'Z', 'a'..'z') ou pelo underscore ('_');
• após podem ser seguidos dígitos, letras e underscores. No caso do TC podemos ter até 32 
caracteres definindo um identificador;
• evite o uso do '_' (underscore) no primeiro caracter do identificador de uma variável, pois este 
tipo de identificadores é de uso do sistema;
• são diferenciados os caracteres minúsculos dos maiúsculos no nome de qualquer variável.

2.3 Definição de Constantes

Constantes   no   C   podem   ser   definidas   com   a   palavra   reservada  #define,   com   a   seguinte 
sintaxe:

#define <nome_da_constante> valor

Da mesma forma como nas outras linguagems, uma constante não faz parte do código, ou seja, 
não gera código. Na verdade uma constante é como se fosse um comando “Substituir” existente em 
quase todos os editores de texto. 

Exemplos:

#define PI 3.14
#define ARQUIVO “lixo.dat”
Na definição de uma constante não há o “;” no final. Se for colocado, este fará parte do valor 
associado à constante.  Veja exemplos a seguir:
/* Código digitado */ /*   Código   que   será   compilado, 
depois   do   pré   processador   do   C 
#define PI 3.14 resolver as constantes */
#define SOMA 100+120;
 ... ...
a = PI; a = 3.14;

x = SOMA + 4; x = 100+120; + 4;

/* Erro na linha acima!!!!! */
Pelo exemplo fica claro como o C trata uma constante. Como foi colocado um “;” no final da 
constante SOMA, esta passa a ser parte do valor da mesma, causando um erro na penúltima linha do 
código, erro que será difícil descobrir, pois, a primeira vista, esta linha está perfeita (o compilador C 

Cap 2: Dados e Operadores do C página 4
Linguagem de Programação C ­ Elgio Schlemer ULBRA ­ 2009

informará o erro existente na penúltima linha e não na definição de constantes). Muito cuidado com o 
uso de constantes no C (assim como em outras linguagens).

2.4 Operadores Aritméticos

Operam sobre números e expressões, resultando valores numéricos. 
= atribuição
+ soma
­ subtração
* multiplicação
/ divisão
% módulo da divisão (resto da divisão inteira)
­ sinal negativo (operador unário)
Alguns Exemplos:

int x, vet[10], z;
char a;
...

x = 20;
z = 30;
vet[5] = x + y;
a = x % 2; /* O C permite atribuição para tipos diferentes !! */

2.5 Operadores Relacionais e lógicos

Operam sobre expressões, resultando valores lógicos de TRUE ou FALSE.
> maior que
>= maior ou igual que
< menor que
<= menor ou igual que
== igual a
!= não igual a (diferente)
Cuidado! não existem os operadores relacionais: "=<", "=>" e "<>". Não confunda a atribuição 
("=") com a comparação ("=="). Os operadores lógicos são os seguintes:
&& operação AND
|| operação OR
! operador de negação NOT (operador unário)
Estes operadores lógicos são usados quando se deseja que mais de uma ou apenas uma condição 
seja VERDADEIRA. Não confundir com operadores binários AND e OR, que mexem diretamente 
com os bits dos dados (visto adiante).

Estes operadores possuem o conceito de “short­circuit”, ou seja, uma forma inteligente de testar 
expressões. O C testa apenas o que é necessário e se já tem condições de avaliar toda a expressão ele 
interrompe o teste. 

Cap 2: Dados e Operadores do C página 5
Linguagem de Programação C ­ Elgio Schlemer ULBRA ­ 2009

Exemplos de "short circuit":

(a == b) && (b == c) /* Se a != b não avalia o resto da expressão */
(a == b) || (b == c) /* Se a == b não avalia o resto da expressão */

O C também permite algumas liberdades no uso destes operadores. Na maioria das linguagens, 
estes operadores são de uso exclusivo para comandos de seleção ou controle de laços. Os operadores 
relacionais e lógicos são tipicamente utilizados em comandos de seleção (Se­então­senão) e de laços 
(enquanto­faça), mas o C trata cada comando relacional como uma expressão que pode ser usada 
inclusive em atribuições.  Os exemplos “estranhos” mostrados a seguir são perfeitamente aceitos pelo 
C e possuem uma lógica (estudado mais adiante):

int x=20, y=0, z=100;
y = x > 100;
y = ((x > 0)&&(z > x));

2.6 Operadores para manipulação de Bits

A manipulação é feita em todos os bits da variável a qual não pode ser do tipo float ou double. 
Estes operadores executam a tabela verdade para todos os bits envolvidos na operação.
& bit and
| bit or
^ bit xor ­ exclusive or
<< shift left
>> shift right
~ bit not (complemento)
Para entender exatamente o que estes operadores fazem, deve­se conhecer como os números são 
representados em binário, bem como o tamanho de cada variável. Considere variáveis do tipo char, que 
possuem 8 bits (1 byte) para os exemplos a seguir:

Cap 2: Dados e Operadores do C página 6
Linguagem de Programação C ­ Elgio Schlemer ULBRA ­ 2009

char a,b,c;

b = 21; /* em binário: 0001 0101 */
c = 9; /* em binário: 0000 1001 */

a = b & c; /* Esta­se fazendo: 0001 0101 (21)


AND 0000 1001 (9)
= 0000 0001 (1)
   a receberá 1 
*/

a = b | c; /* Esta­se fazendo: 0001 0101 (21)


OR 0000 1001 (9)
= 0001 1101 (29)
   a receberá 29 
*/

a = b ^ c; /* Esta­se fazendo: 0001 0101 (21)


XOR 0000 1001 (9)
= 0001 1100 (28)
   a receberá 28 
*/

a = c << 1; /* Os bits de c são rotacionados uma vez para a 
  esquerda, perdendo­se o bit mais significativo 
  (ultimo da esquerda) e colocando­se 0 no bit 
  menos significativo (mais a direita):

0000 1001 (9)
<<1 = 0001 0010 (18)
   a receberá 18 
*/

a = ~c; /* todos os bits de c são invertidos:
0000 1001 (9)
1111 0110 (246 ou ­10)
   a receberá 246 (se não for considerado sinal!!) 
   se for usado sinal, a receberá ­10 
*/

Se as variáveis fossem do tipo inteira, com 4 bytes (32 bits) no caso de um compilador 32 bits 
como os compiladores para Linux e Windows, deve­se levar em conta que a operação será feita sobre 
TODOS os trinta e dois bits.

2.7 Operadores de Assinalamento , Pré e Pós­incremento

Alguns operadores (+, -, *, /, %, >>, <<, &, ^ , |) podem ser combinados com a 


atribuição, quando um dos operandos é a própria variável que recebe a atribuição. Estes operadores 
diminuem o código digitado, mas causam algumas confusões quanto a semântica. 

Cap 2: Dados e Operadores do C página 7
Linguagem de Programação C ­ Elgio Schlemer ULBRA ­ 2009

Exemplos:

i +=2; /* É equivalente a: i = i + 2 */
i ­=3; /* Equivale a: i = i­3; */

O C também possui os chamados operadores de pré/prós incremento ou decremento. Quando 
operado com variáveis inteiras, pode ser interpretado como soma 1 ou diminui 1. Cuidado porém, 
porque eles tem utilidade bem distinta em apontadores! 

Exemplos:

i = i + 1 ­> i = ++i  ­> ou simplesmente ++i;


i = i ­ 1 ­> i = ­­i  ­> ou simplesmente ­­i;
z = a; a = a + 1 ­> z = a++
z = a; a = a ­ 1 ­> z = a­­
a = a + 1; z = a ­> z = ++a
a = a ­ 1; z = a ­> z = ­­a

Existe   uma   grande   diferença   entre   colocar   o   operador   antes   ou   depois   da   variável. 
Conceitualmente, executar a++ não é o mesmo que executar ++a. No exemplo anterior, esta diferença 
fica bem clara nas últimas quatro linhas.

2.8 Operadores de Endereço

Usados com ponteiros (pointers), para acesso a endereços de memória.
& Obtém o endereço de uma variável. Exemplos: 
  int var, x;
  x = &var; /* x recebe o endereço de var. atenção: 
estão omitidos detalhes. Na prática,
x não pode receber endereços, por ser 
  do tipo int */
* conteúdo do endereço especificado. Exemplo: 
var = *x;

2.9 Função sizeof()

Trata­se de uma função embutida da linguagem que fornece o tamanho em bytes de um tipo, 
variável ou estrutura. O valor retornado por sizeof() é do tipo  size_t. Este tipo está definido em 
diversos arquivos de cabeçalho, como, por exemplo, stdio.h, e é compatível com o tipo inteiro. Sua 
sintaxe é: sizeof (objeto). Exemplos:

double d;       /* variavel do tipo double 8 bytes*/
int x;

x = sizeof(int) + sizeof(d);  /* x recebera 12 (4+8) */

Cap 2: Dados e Operadores do C página 8
Linguagem de Programação C ­ Elgio Schlemer ULBRA ­ 2009

2.10 Tabela de Operadores do "C" (Resumo)
Op Função Exemplo "C"
­ menos unário a = ­b
+ mais unário a = +b
! negação lógica (TRUE ou FALSE) ! x
~ inverte todos os bits a = ~b
& endereço de variável a = &b
* referência a ponteiro a = *ptr
sizeof tamanho de variáveis a = sizeof(b)
++ pós ou pré incremento ++a ou a++
­­ pós ou pré decremento ­­a ou a­­
* multiplicação a = b * c
/ divisão a = b / c
% resto da divisão (módulo) a = b % c
+ soma a = b + c
­ subtração a = b ­ c
>> shift right a = b >> n
<< shift left a = b << n
> maior que a > b
>= maior ou igual que a >= b
< menor que a < b
<= menor ou igual que a <= b
== igual a (TRUE ou FALSE) a == b
!= diferente de (TRUE ou FALSE) a != b
& AND bit a bit (tabela verdade) a = b & c
| OR bit a bit (tabela verdade) a = b | c
^ XOR bit a bit (tabela verdade) a = b ^ c
&& AND lógico (TRUE ou FALSE) flag1 && flag2
|| OR lógico (TRUE ou FALSE) flag1 || flag2
= atribuição a = b

Cap 2: Dados e Operadores do C página 9
Linguagem de Programação C ­ Elgio Schlemer ULBRA ­ 2009

2.11 Exercício: Considerando o trecho de programa a seguir, sem o uso do computador:

char a, b, c, d, e, r;

a = 23;
b = 37;
c = ­5;
d = 0;
e = 9;

Qual o valor de r após as expressões abaixo? 

a) r = a | b;
b) r = c | d;
c) r = a & b;
d) r = a ­ c;
e) r = (a >> e) | b;
f) r = (a + c) << (3 / 2);
g) r = c / e * 2;
h) r = ( (a^b) * (c|d) + 1 + e ) << 3;
i) r = ~c;
j) r = (a > b) == d; /* Desafio. O que será que ocorre? */

Cap 2: Dados e Operadores do C página 10
Linguagem de Programação C ­ Elgio Schlemer ULBRA ­ 2009

3 Estrutura dos programas C
Os   comandos   de   "C"   devem   ser   escritos   em   minúsculas   e   devem   ser   seguidos   de   um   ";", 
podendo haver mais de um comando na mesma linha, desde que separados por ponto e vírgula (não 
abuse disto pois torna o código difícil de compreender).

Os blocos são definidos de uma "{" até a outra "}". Dentro de cada bloco pode­se definir um 
conjunto   de  comandos   como  se fossem  um  só,  sendo que  no  C pode­se  declarar   novas  variáveis 
(variáveis locais ao bloco) independentemente da posição deste bloco no programa. 

Formato:

{    ← início de bloco
[ <definição_de_variáveis_locais> ]

<comandos>

}    ← fim de bloco

3.1 Funções

São   subrotinas   (conjuntos   de   comando)   que   podem   ser   chamados   de   diferentes   partes   do 
programa quando necessário. Retornam um valor ao final de sua execução e podem receber parâmetros 
de entrada para a sua execução. Para executar uma função basta referenciar o seu nome. A definição de 
funções NÃO pode ser "aninhada", ou seja, não podem haver funções dentro de funções! 

Exemplo:

/* definição de uma função */
int soma(int x, int y)
{
int total;

total = x + y;
return(total);
}

/* chamada da função soma */

n = soma(j,k);
n = soma (100, 200); 

Pelo exemplo fica claro a NECESSIDADE de um retorno pelo uso do comando return. No C o 
return causa o IMEDIATO término da função, mesmo que existam instruções após o return.

Cap 3: Estrutura dos programas C página 11
Linguagem de Programação C ­ Elgio Schlemer ULBRA ­ 2009

3.2 Esqueleto de um programa em C

Todo o programa em C deve conter a função  main(). Esta função é responsável pelo início da 
execução. No C, só existem funções e não existe o conceito de procedimentos, ou seja, todas devem 
retornar algo a quem a chamou, mesmo que o retorno seja do tipo void (sem valor). Os comentários no 
C são feitos através do par “/*” e “*/”, sendo um “/*” usado para abrir um comentário e um “*/” para 
encerrá­lo. Um bloco de comandos é delimitado por chaves (“{“ e “}”). Exemplo:

/* No início do programa, declara­se as bibliotecas usadas */
#include <stdio.h>

/* Declaração de variáveis globais */

/* Declaração de funções do programador, se for o caso */

/* declaração da função principal. Sempre necessária */
int main ()
{
/* variaveis locais ao main, se existirem */
// Isto também é comentário até o final da linha
/* comandos ... */

return(valor); // função main também retorna valor
}

Existe,   como   mencionado   anteriormente,   uma   função   especial   nos   programas   chamada   de 
main. Esta função é chamada sempre que um programa começa a ser executado. Quando o programa 
for   executado   pelo   sistema   Operacional,   ele   iniciará   a   execução   sempre   pela   função   main,   que 
obrigatoriamente tem que estar presente em qualquer programa "C" (Exceto módulos lincados).

Considerando o exemplo da função soma, citada anteriormente, o próximo exemplo mostra um 
programa completo, com cada parte em sua determinada posição.

Cap 3: Estrutura dos programas C página 12
Linguagem de Programação C ­ Elgio Schlemer ULBRA ­ 2009

/* Bibliotecas: nenhuma é necessária para este exemplo */

/* Variaveis globais: bom ter o hábito de não usá­las :­D */

/* Funções: apenas uma, nossa soma: */
int soma(int x, int y)
{
int total;

total = x + y;
return (total);
}

/* Função principal */
int main()
{
int j, k, n;

j = 40;
k = 80;

n = soma(j,k);
n = soma (100, 200); 
n = soma(100, 200) + soma(j,k) ­3;
}

3.3 Funções de E/S básicas

O C não possui nenhum comando interno para E/S, necessitando do uso de uma biblioteca para 
isso. Basicamente as funções mais comuns de entrada e saída estão na biblioteca stdio.h, que deve ser 
lincada ao programa através do comando #include. Exemplo:
#include <stdio.h>
Uma biblioteca declarada entre “<” e “>”, indica que o compilador deverá procurá­la no diretório 
padrão das bibliotecas. Se quisermos inserir uma existente no diretório atual, ou em algum outro, o 
mesmo é feito da seguinte forma:
#include “mylib.h”

Eis algumas funções importantes da biblioteca stdio.h do C:

Função printf(): Envia os dados para a saída padrão (stdout). Sintaxe:
printf("format_string", arg1, arg2,...);
Onde:

format_string:   formado   por   caracteres   ordinários   mais   especificações   de   formato 


(especif_format). Os caracteres ordinários são copiados diretamente na saída padrão. Especificações de 
formato são comandos enviados para o printf que indicam se um dado a ser impresso é inteiro, char, 
double, string, etc.

Cap 3: Estrutura dos programas C página 13
Linguagem de Programação C ­ Elgio Schlemer ULBRA ­ 2009

Exemplos de formatos:
d ­ o argumento é dado na forma decimal
i ­  o argumento é dado na forma inteiro
o ­ o argumento é apresentado em octal (inteiro)
x ­ o argumento é dado na forma inteiro hexadecimal
u ­ o argumento é considerado decimal inteiro sem sinal
c ­ o argumento é um caracter único
s ­ o argumento é uma cadeia de caracteres
e ­ o argumento é dado como float em notação científica (com expoente)
f ­ o argumento é dado como float em notação decimal comum (sem expoente)
g ­ usar "e" ou "f", conforme a necessidade
p ­ o argumento é um ponteiro
% ­ reproduz o próprio símbolo "%"
O uso do printf não é nem um pouco trivial.  Exemplo:

printf ("Resultado: %7.2f", num);

Se a variável num for, por exemplo, 20.2, esta linha irá imprimir na tela o texto:  

Resultado:   20.20

No format string tem­se "Resultado: %7.2f". O printf irá interpretar isto como:

1. Imprima a frase “Resultado: ”

2. Espere por um argumento do tipo float (f) e imprima ele reservando 7 casas numéricas, 
sendo que destas haverá um ponto e duas casas após a vírgula (ou seja, sobram 4 casas antes 
do ponto, ficando 4 + ponto + 2 = 7

Como o printf está esperando por um parâmetro, o float, é passado ainda a variável num para 
satisfazer este parâmetro. Haveria impressão de sujeira se nenhum valor fosse passado. 

Exemplos de erros do printf (mas que compilam!!!)

printf("Idade = %i Salario = %f", idade); ?
    /* idade satisfaz o parâmetro %d, 
       mas faltou uma variável para o %f. Imprimirá LIXO 
    */

Existem, no C, comandos especiais para o uso em E/S, como por exemplo, para gerar uma 
quebra de linha na tela. 

Cap 3: Estrutura dos programas C página 14
Linguagem de Programação C ­ Elgio Schlemer ULBRA ­ 2009

A tabela a seguir traz alguns  destes comandos:
\n nova linha
\b backspace
\t tabulação horizontal
\v tabulação vertical
\\ Imprime uma contra barra
\' imprime aspas simples
\" imprime aspas duplas
%% para imprimir o próprio símbolo %
Exemplos:
printf(“Ex1: Aqui:\nhouve uma \tquebra de linha\n”);
printf(“Ex2: Valor de r = \t %i\n”, 200);
printf(“Ex3: %04i + %04i = %04i\n”, 3, 4, soma(3,4));
Sairá na  tela:

Ex1: Aqui
Houve uma  quebra de linha
Ex2: Valor de r =  200
Ex3: 0003 + 0004 = 0007
Função  scanf(): entrada formatada através da entrada padrão. Atua de forma análoga à saída 
formatada (printf), mas com sentido inverso. Serve para ler dados. Sintaxe:
[nro_itens_lidos =] scanf ("format_string", &arg1,&arg2,...);
Onde argumento deve ser o endereço da variável, exceto para arrays. Exemplo: 
scanf (“%d”, &num);
O scanf irá esperar pela digitação de um número decimal inteiro com sinal (%d), armazendo 
este número  na variável  num. O scanf() precisa do endereço  da variável onde será armazenado  o 
número. Por isso usa­se o sinal “&” antes do nome da variável. Outro exemplo (programa completo, 
combinando printf com scanf e a função soma citada anteriormente):

Cap 3: Estrutura dos programas C página 15
Linguagem de Programação C ­ Elgio Schlemer ULBRA ­ 2009

#include <stdio.h>

int soma(int x, int y)
{
// nova maneira de implementar esta função: 
return(x + y);
}

int main()
{
int j, k, n;

printf("Digite valor para k:\n");
scanf("%i", &k);

printf("Digite valor para j:\n");
scanf("%i", &j);

printf("Voce digitou k = %i\tj = %i\n", k, j);

printf("A soma de %i com %i eh %i\n", k, j, soma(k,j));
}

Para o scanf existe uma diferença significativa entre ler um inteiro com %i e ler com %d. Se usar 
o %d o valor inteiro será lido apenas como um decimal, ou seja, deve­se digitar 200, 5000, etc. Porém, 
ao usar o %i o scanf aceita outras formas de entrada de dados que não somente a decimal. 

Se estiver lendo com %i e ao digitar os dados o usuário preceder sua entrada com 0x, o scanf 
entenderá que trata­se de um número em hexa e o lerá corretamente (Ex: 0x10  ele leu na verdade 16 
em decimal). Se digitar 0x10 com %d nada será lido. Se uma entrada, usando o %i, for precedida com 
0 (ex: 010) o scanf entenderá que o valor está em octal (digitar 010 e ler com %i resultará no decimal 
8. Mas digitar 010 e ler com %d resultará na leitura do decimal 10).

3.4 if/else

Execução condicional de comandos. A instrução if causa a execução de uma ou de um conjunto 
de instruções, dependendo do valor resultante de uma expressão avaliada. 

Sintaxe:

if (expressão)
     comando1;
[ else          ]
[    comando2; ]

Se a expressão for verdadeira,  executa o comando1, caso contrário  executa o comando2.  Se 


comando1 ou comando2 tiverem mais de uma instrução deve­se criar blocos usando { e }.

Cap 3: Estrutura dos programas C página 16
Linguagem de Programação C ­ Elgio Schlemer ULBRA ­ 2009

Exemplo:

if (n != 0) {
x = a*b/n;
} else {   
n = x*x;
x = a*b/n;
}

As instruções de execução condicional dependem da avaliação de uma expressão condicional. 
Esta expressão é composta por várias operações, que são efetuadas através dos operadores lógicos e 
relacionais (ou mesmo atribuições e operações matemáticas), devolvendo um valor booleano TRUE ou 
FALSE. O C não possui o tipo boolean e não atribui conceito abstrato para VERDADEIRO e FALSO. 
Para o C V ou F é representado por um valor inteiro, com o seguinte significado: 

0  => FALSO (FALSE),
Não Zero  => VERDADEIRO (TRUE).

Ou seja, para o C, o valor 0 é considerado FALSO, enquanto que todos os demais valores (­1, 1, 
2,   1000,   ­1000,   etc)   são   considerados   VERDADEIROS.   Quando   o   C   precisa   CALCULAR   uma 
expressão, atribuindo o conceito de V ou F, ele usará SEMPRE o valor 1 para Verdadeiro e o valor 0 
para FALSO. 

Assim fica fácil entender o que ocorre nestes exemplos:

int x, y, r;

x = 20; y = 10;

r = x > y; /* Sim, o x é maior que y. Então esta sentença é  considerada como 
    VERDADEIRA. O C irá avaliar ela como tendo resultado 1 (VERDADEIRO).
     r recebe 1, portanto 
*/

if (x) {  /* x vale 20. 20 para o C é conceito de VERDADEIRO. r receberá o valor 0 */
r = 0;
}

r = x + ((x>y) && (y > 0)); 

/* Temos uma sentença AND que será calculada como sendo VERDADEIRA (1) 
ou FALSA (0).  Como x é maior que y (V, 1) e y é maior que 0 (V), 
a sentença (x>y) && (y > 0) é  VERDADEIRA, valendo 1.  x + 1 = 21. 
r receberá 21 porque o if foi VERDADEIRO
*/

Cap 3: Estrutura dos programas C página 17
Linguagem de Programação C ­ Elgio Schlemer ULBRA ­ 2009

Devido à cláusula else ser opcional, pode existir uma ambigüidade no uso de if­else aninhado. O 
compilador resolve isto associando o else ao último if sem else. Por exemplo:

if (n>0)
   if (a>b)
      z=a;
   else
      z=b;

Se a>b, então z=a, quando n>0. Se a<=b, então z=b, quando n>0. Se n<=0, o valor de z não 
será alterado nesta porção de código.

Entretanto, se tivermos:

if (n>0)  {
   if (a>b)
      z=a;
} else
   z=b;

Se a>b, então z=a, quando n>0. Se n<=0, então z=b. Se n>0 e a<=b, o valor de z não será 
alterado nesta porção de código.

Exercícios

1) Escrever um trecho de código que peça ao usuário digitar dois números. Faça uma função que 
receba estes números como parâmetro e retorne o maior deles. Imprima na tela os dois números 
digitados, a soma deles, o maior e o menor deles.

2) Escrever uma função que recebe dois inteiros (a e b) e um código inteiro (cod) e retorne: a+b, se 
cod >=0; e |a­b|, se cod <0. Observação: |a­b| = abs(a­b).

3.5 while

Enquanto a condição descrita pela expressão for satisfeita (ou seja, verdadeira), o comando será 
repetido. O comando somente será executado se a expressão condicional for verdadeira. A avaliação 
da expressão é realizada da mesma forma que no comando if. 

Sintaxe:

while (expressão)
      comando;

Exemplo: contagem de bits setados na variável n.

Cap 3: Estrutura dos programas C página 18
Linguagem de Programação C ­ Elgio Schlemer ULBRA ­ 2009

bits = 0;
while (n != 0)  {  // enquanto n nao for zero
      if (n & 1)   // and bit a bit. Isto podera ser 0 ou 1 (F ou V)
         bits++;   // Se V, o bit menos sig era 1
      n = n >> 1;  // Rotaciona os bits
}

3.6 for

O comando for serve para a execução de um número fixo de vezes (ou não), enquanto uma 
variável percorre uma determinada faixa de valores. Esta variável é chamada de variável de índice. 

Sintaxe:

for (inicio; condição; modificador)
     comando;

Onde tem­se “início” como sendo uma expressão que irá gerar o valor inicial da variável de 
índice utilizada pelo comando. A "condição" irá indicar uma condição para o prosseguimento do laço 
(enquanto tal condição for verdadeira irá repetir o laço). E finalmente "modific" será o comando dado 
a cada execução do laço, sendo este realizado ao final de um laço do for, modificando o valor da 
variável de índice, antes de um novo teste da condição. Cada um destes elementos (início, condição e 
comando) pode ainda estar dividido em séries de comandos, como nas expressões do if.

Exemplo: números ímpares de 3 a 21, com for:

for (i = 3; i <= 21; i = i+2){
    printf("%i\n", i);
}

Para   criarmos   um  laço   infinito   (sem   fim)   podemos   fazer   um   comando   da   seguinte   forma: 
for(;;) ­ comando sem expressões de controle.

O comando for é equivalente a seguinte estrutura:

início;
while (condição)  {
      comando;
      modific;
}

3.6.1 Exercícios

1) Fazer uma função que receba um número (do teclado) e retorne o fatorial deste número, usando 
for. (Lembre que 0!=1 e 1!=1). Deve pedir para o usuário repetir a entrada se o número que ele 
digitou for negativo.

2) Faça um programa completo que leia do teclado 10 números inteiros, armazene­os em um vetor e 
depois imprima a) A soma de todos os elementos e b) O maior e o menor elemento

Cap 3: Estrutura dos programas C página 19
Linguagem de Programação C ­ Elgio Schlemer ULBRA ­ 2009

3.7 do/while

O comando do/while é o inverso do comando while, ou seja, o teste é executado ao final deste 
ao invés de ser no início. Sintaxe:

do
  comando;
while (expressão);

O comando será executado e depois será testada a condição dada pela expressão, e caso esta 
seja verdadeira (TRUE) será novamente executado o comando.

3.8 break e continue

break causa a saída no meio de um laço (para comandos: for, while, do/while, switch). Provoca a 
antecipação   do   fim   do   laço.   É   muito   usado   com   o   comando   case   como   será   visto   mais   adiante. 
continue serve para a re­execução do laço, a partir do teste. Irá causar a reinicialização do laço (não 
funciona com o switch). Um exemplo prático do seu uso pode ser visto no exemplo a seguir:

while ( x <= valmax)  {
      printf ("Entre com valor: ");
      scanf  ("%d",&val);
      if (val < 0)
         continue;
      ...
}

3.9 switch/case

Faz uma associação de valores com comandos a executar. Conforme o valor dado, executa um 
certo número de instruções. Serve como uma estrutura mais sofisticada que os if's encadeados. Sintaxe:

switch (variável)  {
case <valor1> : <comando1>;
case <valor2> : <comando2>;
<comando3>;
...
[ default : comando4; ]
}

No comando switch não é possível definir intervalos para os quais o comando será executado, 
temos que definir os valores textualmente, um a um. Os valores utilizados no case devem ser do tipo 
inteiro ou char.

O uso do comando break é muito importante quando associado ao switch, pois caso este não 
seja usado a cada case, será feita a execução de todos os comandos até encontrar o fim do switch, 
sendo executado inclusive o default, se houver. Por isso a estrutura usada em um comando case é:

Cap 3: Estrutura dos programas C página 20
Linguagem de Programação C ­ Elgio Schlemer ULBRA ­ 2009

       case 'A': x++;
                 break;
       case 'b':
       case 'B': y++;
                 break;
       ...

3.10 Expressões Condicionais (comando ternário)

As expressões condicionais se apresentam da seguinte forma:

expr1 ? expr2 : expr3

Esta expressão é equivalente a:

se expr1 Onde: expr1 ­> Condição de teste
   então expr2 expr2/expr3 ­> Valor retornado
   senão expr3

Portanto expr2 será o valor calculado se expr1 for verdadeiro e expr3 será o valor calculado se 
expr1 for falso.

Exemplo:
b = ((x == y)?x:y);

Outro Exemplo (nova maneira da função maior):

Cap 3: Estrutura dos programas C página 21
Linguagem de Programação C ­ Elgio Schlemer ULBRA ­ 2009

int maiorConvencional (int x, int y)
{
/* Como um programador Pascal faria :­D */
int maior;

if (x > y)
maior = x;
else 
maior = y;
return (maior);
}

int maiorMelhorado (int x, int y)
{
if (x > y)
return(x);  // Se x eh maior, retorna x e ENCERRA

return (y); /* Desnecessario o else, pois so vai executar esta
  linha se nao caiu no if (caso em que a funcao
  ENCERRARIA */
}

int novoMaior (int x, int y)
{
return (x>y?x:y); /* vai retornar o x ou o y, dependendo da
     condicao do operador ternario
*/
}

3.11 Exercícios

1) Escrever   um   trecho   de   código,   usando   atribuição   condicional,   que   teste   o   conteúdo   de   duas 
variáveis e atribua o maior valor a uma terceira variável.

3) O que faz o trecho de código a seguir?

  int a,b,c;
  ...
  c = (a>=0)?(b>=0):(b<0);

Cap 3: Estrutura dos programas C página 22
Linguagem de Programação C ­ Elgio Schlemer ULBRA ­ 2009

4 Passagem de Parâmetros
Todas as funções do C podem conter parâmetros, inclusive a função main. A forma como o C 
passa parâmetros de uma função para outra é através da pilha e o devido conhecimento desta técnica 
ajuda a compreender melhor as limitações do C em relação a outras linguagens.

4.1 Parâmetros de Funções

A passagem de parâmetros em "C" é feita por valor (também chamada de por cópia). Não existe 
no C a passagem por referência, muito embora isto seja facilmente confundido com a simulação de 
passagem por referência, que já existe, por definição, em arrays. 

O   compilador   faz   uma   cópia   dos   valores   passados   como   parâmetros,   colocando­os   na   pilha 
(stack) do sistema. Os valores dos dados não são alterados ao se retornar de uma rotina, mesmo que 
seu conteúdo tenha sido modificado no interior da função, a alteração só afeta a cópia do dado. (exceto 
para vetores, cuja exceção será estudada quando o conceito de ponteiros for visto. Isto inclui strings).

Como já foi visto, devemos na definição de uma função, definir também os seus parâmetros. 
Um caso  especial na definição de argumentos  de uma função serão os arrays, onde não é  feita a 
definição do tamanho do array, apenas de seu nome, que deve ser declarado da seguinte forma:

função ( char argumento[] );

Passagem   de   parâmetros   por   referência   não   existe,   mas   pode   ser   simulada   com   o   uso   de 
ponteiros ­ visto adiante, exemplo do scanf().

Retorno padrão de parâmetros em funções:
• É feito através do comando return(var)
• Funções retornam int por default
Retorno de outros tipos de dados além de inteiros  e caracteres, sendo necessário declarar  as 
funções que não retornam valores do tipo int.

Cap 4: Passagem de Parâmetros página 23
Linguagem de Programação C ­ Elgio Schlemer ULBRA ­ 2009

Exemplo de retorno de um double:

double dobro (double x)
{
return(x * 2);
}

main()
{
double y;
y = dobro(3.14);
}

Pode­se   declarar   no   inicio   do   código   apenas   a   função,   com   seus   parâmetros   e   deixar   a 
implementação   para   o   fim   do   código.   Isto   se   chamado   "prototype".   Exemplos   de   funções   com 
prototypes e suas chamadas:

#include <stdio.h>

int soma(int, int); /* não se coloca nomes aqui, apenas o tipo */
double media(int, int);

int main()
{
int x,y,z;
double i;

x = 20; y = 30;

z = soma(x,y);
i = media(x,y);
}

int soma(int a, int a)
{
return(a+b);
}

double media(int a,int b)
{
return( (a+b) / 2.0);
}

Cap 4: Passagem de Parâmetros página 24
Linguagem de Programação C ­ Elgio Schlemer ULBRA ­ 2009

4.2 Parâmetros da função "main"

O módulo principal do programa (main) pode receber dois parâmetros externos opcionais, são 
eles: argc e argv. Sintaxe utilizada:

int main (int argc,char *argv[])

Onde tem­se:

argc ­ número de apontadores;
argv ­ array de apontadores para cadeias de caracteres.
Tem­se em argc um valor maior que zero caso o programa executável tenha sido chamado com 
parâmetros. O apontador argv[0] nos dá o nome pelo qual o programa foi chamado. O exemplo abaixo 
mostra a associação dos parâmetros:

C:\> prog_c param1 b:param2 xyz

argc=4 e argv[0] = ponteiro para "prog_c"
argv[1] = ponteiro para "param1"
argv[2] = ponteiro para "b:param2"
argv[3] = ponteiro para "xyz"

4.2.1 Exercício

1. Faça um programa que leia um conjunto de argumentos da linha de comando e imprima cada 
um deles em uma linha separada, informando, ao final, o número total de argumentos sem considerar 
nome do programa em si.

2. Modifique seu programa para calcular fatorial, de forma que ele calcule números passados por 
parâmetros.

Cap 4: Passagem de Parâmetros página 25
Linguagem de Programação C ­ Elgio Schlemer ULBRA ­ 2009

5 Classificação e modificadores de variáveis
Já estudamos como declarar uma variável e os tipos básicos do C. As variáveis podem ser, 
como já visto nos exemplos, de dois tipos, quanto ao seu escopo:

Externas: são variáveis globais declaradas fora de qualquer função ou bloco, podendo ser referidas em 
outras partes do programa de forma explícita ou implícita. As variáveis externas globais podem ser 
acessadas de qualquer função do programa.

Internas: são definidas dentro de funções, ou blocos e podem ser do tipo estáticas, automáticas ou 
registradores. Estas variáveis serão locais ao bloco ou função em que forem definidas, ou seja, só são 
acessíveis dentro do bloco ou da função em que estão declaradas.

Além do escopo onde as variáveis são válidas, eles podem ser divididas de outra forma, quanto 
a sua validade, ou seja, por quanto tempo elas estarão ativas na memória. Neste caso, elas se dividem 
em outros dois tipos:

Automáticas:   são   as   variáveis   comuns   internas   de   funções.   Elas   existem   durante   a   execução   da 
referida função, sendo "apagadas" após o término desta e no caso de uma nova chamada da função ela 
não possui   mais  o seu antigo valor. São variáveis  dinâmicas  e são alocadas  na pilha do  sistema 
(STACK), as variáveis internas e os parâmetros passados para as funções são deste tipo.

Estáticas: podem ser internas ou externas as funções, mas estas mesmo após o fim da execução, ao 
contrário das automáticas, continuam em memória. Permitem também a não interferência em outras 
funções, ou seja, podem haver outras variáveis declaradas com o mesmo nome destas, pois elas serão 
invisíveis sendo vistas apenas na função onde foram declaradas. São declaradas da seguinte forma:
static <tipo_dado> <nove_var>;
As variáveis estáticas são armazenadas na área de dados do sistema que é fixa.Toda a variável 
global é, por natureza, também estática e as variáveis internas são automáticas a menos que se defina o 
contrário com static. Exemplo da declaração de uma variável estática.

int calcula_salário (...){
static cont;  
cont++;

/* cont ira contar o numero de vezes que esta funcao foi chamada */

printf ("Estah funcao foi chamda %d vezes\n", cont);
return(1);
}

Registradores:   são   variáveis   muito   usadas   e   que   se   possível   serão   alocadas   em   um   dos 
registradores   do processador. Usa­se como variável  do tipo int, char ou apontadores. Otimizam   o 
acesso a uma variável, mas deve m ser usadas com muita cautela, pois ao designar­mos um registrador 
para ficar preso com o valor de uma variável, limitamos os recursos disponíveis ao compilador, e ele 
terá um a menos, fazendo mais trocas entre memória e registradores, tornando o código um pouco 

Cap 5: Classificação e modificadores de variáveis página 26
Linguagem de Programação C ­ Elgio Schlemer ULBRA ­ 2009

maior.  Deve ser usado  somente para variáveis que são muito acessadas. São declaradas da seguinte 


forma:
register <tipo_dado> <nome_var>;

5.1 Modificadores.

Além dos tipos básicos do C, existem alguns modificadores, que alteram a interpretação da 
variável. São eles:

• unsigned: indica que a variável deve ser tratada sem o sinal. Por exemplo, se usarmos este 
modificador para o tipo char, temos que ela poderá assumir um valor entre 0 a 255 e não 
­127 a 127, caso usemos o sinal.

• signed: indica para usar o sinal. No caso de um int, temos que a variável pode ir de ­32768 a 
32768 (considerando inteiro de 16 bits).

• short: usado para inteiros e indica um inteiro curto.

• long: usado para inteiros, indica um inteiro longo. Tanto o short como o long, não fazem 
sentido em algumas arquiteturas. Nos compiladores LINUX, por exemplo, tanto o int como 
o   short   como   o   long   utilizam   4   bytes   para   representar,   pois   são   implementados   nos 
registradores de 32 bits da arquitetura intel. Mas no sistema DOS, que é de 16 bits, um 
inteiro  e   um  short  tem   apenas  16  bits,   enquanto   que  um  long   tem   32.  Atualmente   em 
arquiteturas de 64 bits um inteiro é de 32 bits enquanto que um long int é de 64 bits (caso 
Linux 64 bits). Pode­se verificar o tamanho para determinada arquitetura através do uso de 
sizeof().

Exemplos de modificadores:

unsigned char a;  /* um char (1 byte) sem sinal*/

unsigned long int fatorial; /* um inteiro longo sem sinal*/

unsigned long fatorial2; /* como short e long são modificadores do
                            int pode­se suprimir o tipo da variável */

Tanto no scanf como no printf, ao imprimir um long o tipo deve ser precedido de l. Para ler um 
long int, deve­se usar o %li. Interessante que para ler um double ou imprimir um double, o mesmo é 
entendido no scanf/printf como um long float, devendo ser manipulado com “%lf”.

5.2 Inicialização

A princípio todas as variáveis são inicializadas de acordo com as seguintes regras:

• externas e estáticas: inicializadas com "0";

• automáticas e registradores: contém lixo (isto é, não são inicializadas).

Cap 5: Classificação e modificadores de variáveis página 27
Linguagem de Programação C ­ Elgio Schlemer ULBRA ­ 2009

Apesar das variáveis externas e estáticas serem inicializadas com zero, para evitar problemas, é 
conveniente inicializar sempre as variáveis. 

5.3 Conversão de Tipos

O "C" segue algumas regras para a conversão de tipos de dados, conversão esta que é feita para 
possibilitar ao compilador de realizar as operações requeridas em expressões com tipos compatíveis 
entre si. As regras de conversão de tipos são as seguintes:

• Todos os valores não­inteiros ou não­double são convertidos como mostrado na tabela a 
seguir. Após isto os dois valores a serem operandos serão do tipo int (incluindo  long e 
unsigned) ou do tipo double.

• Se um dos operadores é do tipo double, o outro operando também será convertido para 
double.

• Mas se um dos operandos for do tipo unsigned long, então o outro será convertido para 
unsigned long.

• Por   outro   lado,   se   um   dos   operandos   for   do   tipo   long,   então   o   outro   operando   será 
convertido para long.

• Se um dos operandos for do tipo unsigned, então o outro operando será convertido para 
unsigned.

Tabela de conversão: (TC)
TIPO CONVERTIDO PARA MÉTODO USADO
char int extensão de sinal
unsigned char int zera o byte mais significativo
signed char int extensão de sinal
short int se unsigned, então unsigned int
float double preenche mantissa com 0's

5.4 Conversão de Tipos Explícita

O programador pode (e as vezes deve) especificar de forma explícita o tipo para o qual o dado 
deve ser convertido. Basta especificar o tipo entre parenteses, antes da variável ou expressão. Esta 
prática se chama cast.

Cap 5: Classificação e modificadores de variáveis página 28
Linguagem de Programação C ­ Elgio Schlemer ULBRA ­ 2009

Exemplo:

int x;
double y;
y=(double)x + 0.5;

Importante:  por não fazer uma verificação  severa de tipos  de dados, o C permite   que  se 


escreva expressões usando os mais variados tipos de dados. Outras linguagens, como o pascal, não 
permitem este tipo de coisa, pois facilmente o programador pode ser induzido ao erro. Veja e pense 
sobre o exemplo a seguir:

#include <stdio.h>

int main()
{
        int a=200;

        a=(a*0.7)+a; 
/* pergunta: qual deveria ser o valor de a? */
        printf("%i\n",a);
/* pergunta: qual eh o valor de a agora? Porque? */
}

5.5 Exercícios

1) Faça um programa que imprima o tamanho das seguintes estruturas de dados: 

char, int, long int, short int, float e double.

Diga se existe diferenças entre o compilador do DOS e do LINUX (ou Windows).

2) Considere cada uma das atribuições abaixo e determine qual o resultado atribuído:

int i = 3;
int j = 2;
double a = 3.0;
double k;

k = i/j;
k = (double)(i/j);
k = (double) i/j;
k = (int) a/2;

Cap 5: Classificação e modificadores de variáveis página 29
Linguagem de Programação C ­ Elgio Schlemer ULBRA ­ 2009

6 Matrizes e strings
Atenção!   Como   mencionado   anteriormente,   o   C   não   possui   o   tipo   de   dado   string   de   forma 
implícita, como no Pascal, por exemplo. Para manipular este tipo de dado, o próprio programador deve 
controlar a estrutura de dados. Para uma string, por exemplo, cria­se uma "cadeia de caracteres", ou, 
como também pode ser chamado, um vetor de caracteres. Todas as linguagens que implementam este 
tipo de dado, precisam ter uma forma de controlar onde esta cadeia termina, ou seja, onde é o último 
elemento   da   minha   string.   O   Pascal   faz   isso   de  forma   totalmente   implícita   e   transparente   para   o 
programador, guardando na primeira posição do vetor de caracteres o tamanho deste. Por isso que o 
tamanho de uma string no pascal está limitada a 255 caracteres.

Como o C não tem esta transparência e um vetor de caracteres é apenas uma mera seqüência de 
bytes, sem sentido algum, é o programador quem deve fazer este controle de forma explícita.

Nada impede que   ele o faça da mesma forma que o Pascal. Alias, a instrução  gets()  do C 


trabalha com uma cadeia de caracteres cujo primeiro elemento tem o tamanho máximo, o segundo tem 
o tamanho atual e somente a partir da terceira posição é que se tem o texto lido.

Mas existem várias funções no C para trabalhar com o conceito de strings e estas já respeitam um 
determinado   padrão   e,   a   menos   que   o   programador   esteja   disposto   a   fazer   suas   próprias   rotinas, 
desprezando   as  existentes   na  biblioteca  stdio.h  e  strings.h,  recomenda­se  que   se  faça   uso 
deste. O que deve ficar bem  esclarecido  é que o fato do C possuir em suas bibliotecas funções que 
lidam com strings, não significa que exista este tipo de dado na linguagem.

Então,   devidamente   entendido   desta   forma,   para   a   maioria   das   funções   do   C   uma   cadeia   de 
caracteres (strings) sempre termina com o valor decimal 0, isto é, o zero mesmo, e não o caractere '0'. 
Isto é possível porque uma string deve conter apenas letras e números (caracteres imprimíveis) e o zero 
não faz parte deste conjunto. Se quisermos representar o zero no meio de um texto, devemos usar o 
caractere de scape '\' (exemplo: "teste\0"). Ao declarar uma cadeia de caracteres para se usar como uma 
string, deve sempre ter­se o cuidado de reservar uma posição a mais: aquela que indicará o final da 
string. 

Exemplo:

char texto[10];  /* suporta até 9 caracteres, mas o '\0'*/
/* atribuição: */
texto[0]='A';
texto[1]='B';
texto[2]=0;   /* ou texto[2] = '\0';
/* para atribuir um texto, deve­se usar uma função específica */

Um exemplo de uma função que   trabalha com este conceito é o já visto printf("%s",


texto);

Se   o   programador   esquecer   de   colocar   o   '\0'     no   fim,   estas   funções   irão   se   perder,   pois 
interpretarão como final de string o primeiro '\0' que encontrar, imprimindo talvez coisas indesejáveis 
até lá!!! CUIDADO!!!

Cap 6: Matrizes e strings página 30
Linguagem de Programação C ­ Elgio Schlemer ULBRA ­ 2009

A declaração de uma matriz é feita de forma semelhante:

int tabela[4][5];
/* matriz de inteiros 4x5 */
tabela[0][0]=200;
tabela[0][1]=3000;
... 

Importante ressaltar que matrizes e vetores são na verdade a mesma estrutura, pois a memória 
do micro é um vetor. Pode­se, caso o programador deseje, manipular uma matriz como se fosse um 
vetor, desde que se saiba o cálculo  que o C utiliza para calcular a posição [x][y] no vetor.  Estes 
detalhes são exaustivamente estudados em Estruturas de dados. 

6.1 Funções de manipulação de strings

Na biblioteca padrão stdio.h já existe funções, como o printf e o scanf, que trabalham com 
este conceito de strings e as mesmas até já foram empregadas em exemplos. Existem outras, mas 
iremos ver duas mais importantes:

• sprintf():   lembra do printf e de sua sintaxe? O que o printf faz? Envia o resultado de uma 
impressão para a saída padrão, tipicamente a tela. Bem, o sprintf() é exatemante igual ao printf(), 
fazendo as mesmas coisas e com a mesma forma de uso. Entretanto, o resultado da impressão não 
vai para a saída padrão, mas sim para um vetor de caracteres passado por parâmetro. Esta função, 
então, tem apenas um parâmetro a mais: o nome da cadeia de caracteres. Temos aqui a função que 
precisamos para atribuir textos as nossas strings. 

Exemplos:

char texto[100];
sprintf(texto, "Isto eh um teste");
sprintf(texto, "O valor de a eh %d", a);

Como visto, o uso é igual ao printf. Observe que esta função já coloca o '\0' no final, livrando o 
programador desta preocupação.

• sscanf(): a função scanf lê da entrada padrão, certo? Bem, esta é semelhante, só que busca os 
dados de uma string e não do teclado. Na verdade esta função é pouco empregada.

Existe no C uma biblioteca específica para trabalhar com strings.

Funçõs da biblioteca string.h:

• strcmp(): compara o conteúdo entre duas strings passadas por parâmetro, retornando 0 se 
elas forem iguais e diferente de zero se diferentes: strcmp(texto, texto2);

• strlen():   retorna   um   inteiro   que   expressa   o   tamanho   atual   da   string   (ou   seja,   quantos 
caracteres tem até o '\0').

Cap 6: Matrizes e strings página 31
Linguagem de Programação C ­ Elgio Schlemer ULBRA ­ 2009

• strcpy(): copia o conteúdo de uma string para outra. Pode ser feito com o sprinf().

Exemplo:

strcpy(destino, destino);
/* pode ser simulado com:*/
sprintf(destino, "%s", origem);

Evidente que destino deve suportar origem, pois se o programador não cuidou deste detalhe, já 
sabemos que o C não irá cuidar também.

Cuidado com os erros típicos de manipulação de strings. A função sizeof() não retornará o 
tamanho, no sentido do número de caracteres de uma string.  Ela serve para retornar o tamanho do tipo 
e string não é um tipo de dado do C. Da mesma forma, já que string não é um tipo básico do C, não é 
possível   fazer   comparações   e   atribuições   de   strings,   a   menos   que   se   use   as   funções   existentes 
(strcmp e srtcpy).

6.2 Exercícios:

1) Implemente você mesmo uma função que faça a mesma coisa que a strlen.

2) Utilize a função strcmp com vários exemplos. Observe que ela retorna 0 se as duas strings 
forem iguais, mas o que retorna se elas forem diferentes? Implemente uma função que faça a mesma 
coisa.

Cap 6: Matrizes e strings página 32
Linguagem de Programação C ­ Elgio Schlemer ULBRA ­ 2009

7 Estruturas
As estruturas são conjuntos (agrupamentos) de dados, que definem um novo tipo de dado mais 
complexo, formado por tipos de dados mais simples. 

struct nome  {
  tipo_var nome_var;
  tipo_var nome_var;
  ...
};
OU

Exemplo:

struct data  {
  int dia,mes,ano,dia_ano;
  char nome_mes[10];
};
struct data today;

Referência a um elemento da estrutura:

nome_estrut.elemento ==> data.dia = 23;

Características das estruturas:

• Não podem ser inicializadas na declaração ou copiadas através de atribuição;

• Podem ser acessadas com o auxílio de ponteiros. Permitem a criação de listas encadeadas.

Também podem ser declarados vetores de estruturas, da mesma forma que um vetor de inteiros 
ou caracteres. A forma de acesso é semelhante:

struct data st[10]; /*um vetor da estrutura de 10 elementos */

st[0].dia=2;
sprintf(st[0].nome_mês, "Janeiro");
...

7.1 Exercício

1) Faça um programa que define um tipo para alunos com os seguintes campos: nome (20 caracteres), 
código (10 caracteres), notas de 3 provas e média final. O programa deverá ler da entrada padrão 
os dados de 10 alunos, calcular a média individual de cada aluno e a média geral dos 10 alunos.

2) Modifique o exercício anterior, mas permita que o usuário forneça antes o número total de alunos 
que deseja digitar.

Cap 7: Estruturas página 33
Linguagem de Programação C ­ Elgio Schlemer ULBRA ­ 2009

8 Apontadores
Antes de vermos o que são apontadores e como podem ser utilizados no C, vamos fazer uma 
rápida revisão sobre o conceito de variáveis. Uma variável é mostrada nas disciplinas de algoritmos 
como se fosse uma caixa com um nome e esta caixa possui um valor qualquer.
teste cont texto
209 34 “exemplo”
Bem, esta é uma analogia fácil de entender quando lidamos com algoritmos, mas agora devemos 
entender como as varáveis são realmente implementadas pelas linguagens.

Uma variável é nada mais que um nome (label) que referencia (aponta) uma posição de memória. 
A memória pode ser considerada como um array de bytes, sendo que cada palavra tem um endereço 
único. Considerando que teste seja do tipo int, cont do tipo char e texto uma string de 10 posições, 
podemos   admitir   que   os   mesmos   poderiam   estar   dispostos   da   seguinte   forma   em   um   trecho   de 
memória hipotético que começa no endereço 200:

Endereço Valor Nome


200 209 teste
201 000
202 34 cont
203 ‘e’ texto
204 ‘x’
205 ‘e’
206 ‘m’
207 ‘p’
208 ‘l’
209 ‘o’
210 0
211 ?
212 ?
213 ... xx

Tem­se, então, que um label, ou seja, um nome de variável aponta para o início de uma área de 
memória reservada para si, cujo tamanho depende do tipo. Ou seja, para o tipo int, são reservados 2 
bytes (em algumas arquiteturas são 4 ou mesmo 8 bytes), para o tipo char apenas um e para a string 
texto, são reservados tantos bytes quantos forem necessários para satisfazer o tamanho requerido. No 
momento o texto de exemplo não utiliza todos os 10 bytes, sendo que a próxima variável declarada 
(xx) ocupará a posição de memória 213 mesmo assim. No desenho também está representado o uso do 
0 como terminador de uma cadeia de caracteres no C.

Anteriormente falamos dos operadores “&” e “*” para manipulação de endereços. Agora podemos 
entender   melhor   seu   funcionamento.   Quando   referenciamos   a   variável   pelo   nome,   o   que   nos   é 
retornado   será   simplesmente   o   valor   contido   na   memória   referenciada   pela   variável,   ou   seja,   ao 
referenciarmos a variável teste, nos será retornado o valor 209, ao referenciarmos a variável cont, nos 
será retornado o valor 34. 

Quando, entretanto, referenciar­se a variável teste com a seguinte sintaxe: 

Cap 8: Apontadores página 34
Linguagem de Programação C ­ Elgio Schlemer ULBRA ­ 2009

&teste

Esta­se agora pedindo o endereço ocupado pela variável, ou seja, receberemos o valor 200 
(posição da memória onde a mesma começa). De forma análoga, se usarmos a notação:

*teste

será retornado o valor contido na memória cujo endereço está na posição de memória apontada 
por teste! Complicado? Vejamos no exemplo: 

A   posição   de   memória   apontada   pela   variável   teste   contem   o   valor   209.   Quando   usamos 
*teste, queremos o valor da posição 209, ou seja, nos será retornado  ‘o’!! Este é o conceito de 
apontadores:   variáveis   cujo   valor   é   o   endereço   de   uma   outra   variável   ou   uma   área   de   memória 
reservada para tal. Estes exemplos são hipotéticos, pois o C não permite usar o operador “*" para 
variáveis que não sejam do tipo ponteiro.

Os   apontadores   ou  pointers  são   tipos   de   dados   que   tem   por   função   "apontar"   (referenciar) 
variáveis através de seus endereços físicos (posição da variável na memória), permitindo uma maior 
flexibilidade em nossos programas como: acesso indireto por ponteiros, simulação de passagem de 
parâmetros   por   referência,   alocação   dinâmica   de   memória,   criação   de   listas   encadeadas   e   outras 
estruturas de dados mais complexas.

São declarados da seguinte forma:

int x,y; /* Declaração de dois inteiros */
int *px; /* Declaração de um ponteiro para inteiros */
double vard, *pd; /* um double e um ponteiro para double */

Os ponteiros são definidos em função do tipo de variável ao qual ele será ligado, são do tipo 
"aponta para um determinado tipo de dado". 

Como se usa um apontador:

pt  = &x; /* pt recebe o endereço de x, aponta para x   */
y   = *pt; /* y recebe o valor apontado por pt           */

*pt = 12; /* O endereço dado por pt recebe o valor 12   */
/* Se pt = &x então *pt = 12 é igual a x = 12 */

É impossível apontar para registradores e constantes definidas através do comando #define do 
processador de macros. Um uso muito prático de ponteiros é com os arrays, pois este tipo de dado tem 
características que se comportam de maneira muito parecida a eles. Na realidade, um array pode ser 
visto, na maioria das vezes, como sendo um apontador para uma posição onde se encontram alocados 
os dados. Por isso temos:

Cap 8: Apontadores página 35
Linguagem de Programação C ­ Elgio Schlemer ULBRA ­ 2009

char car,a[10],*ptr;
ptr = a; /* ptr aponta para o endereço de a[0]  */
ptr = &(a[0]); /* igual ao exemplo anterior a = &a[0] */
car = *(ptr); /* car recebe o conteúdo de a[0]       */

int var[5],*pint;
pint = var; /* pint aponta para var[0] */
pint = (var+2); /* pint aponta para var[2] */

Como já pode ser visto, a indexação dos arrays é feita na mesma maneira em que se trata com 
ponteiros. Então, incrementar um ponteiro significa somar ao endereço atual tantas unidades quanto 
for o tamanho do tipo de dado apontado, assim como o endereço de um elemento de um array pode ser 
obtido apenas somando­se tantas unidades quanto for o tamanho do elemento do array. 

Portanto podemos fazer somas e incrementos com ponteiros operando­os como se fossem meros 
endereços. Sendo que no caso de incrementos, o acréscimo será feito de acordo com tipo de dado ao 
qual o ponteiro atua (soma tantos bytes quanto for o tamanho do tipo).

Como foi visto, apontadores acessam diretamente a memória do micro, por isso constituem uma 
ferramenta poderosa mas ao mesmo tempo perigosa, pois um descuido qualquer pode causar sérios 
danos. Sempre que formos usar um ponteiro ele já deverá ter sido inicializado, ou seja, já deve ter sido 
atribuído algum endereço a ele.

Arrays, algumas informações extras ...

• A diferença entre um array e um ponteiro é que quando definimos um array, uma área de 
memória   é   reservada   para   ele   e   quando   definimos   um   apontador,   não   há   alocação   de 
memória. Exemplo:

char *string ­> reserva área somente para o pointer (2/4 bytes)
char string[10] ­> reserva área para 10 caracteres (10 bytes)

• Arrays são passados como parâmetros para funções como sendo um ponteiro para o início do 
array. 

• Como conseqüência do modo de alocação de arrays de mais de uma dimensão, quando for 
passado como parâmetro o array, temos que indicar as outras dimensões, exceto a principal. 
Isto se dá pelo fato de que é passado apenas o endereço inicial do array, que é tratado como 
um   vetor   linear.   Sem   a   indicação   das   outras   dimensões   não   conseguimos   distinguir   os 
elementos de uma ou de outra dimensão. Exemplo:

Cap 8: Apontadores página 36
Linguagem de Programação C ­ Elgio Schlemer ULBRA ­ 2009

função (a)
int a[][10]
      |  |
opcional obrigatório

8.1 Apontadores: usando para passagem de parâmetros por referência

Como visto anteriormente, o C só possui passagem de parâmetros por valor, através da pilha do 
sistema  e  não possui passagem por referência.  Esta regra continua sendo verdadeira, mas  pode­se 
simular a passagem por referência através de ponteiros.

Exemplo:

int quadrado ( int *a)

  *a=(*a)*(*a);
  return(1);
}

main ()
{
 int a=8;
 quadrado(&a);
 printf("A resposta foi %d\n", a);

Observe que a passagem  continua sendo por valor  porque o que foi passado não é o valor 


inteiro, mas sim o seu endereço, e este endereço não pode ser mudado dentro da função. Veja pelo 
exemplo a seguir:

Cap 8: Apontadores página 37
Linguagem de Programação C ­ Elgio Schlemer ULBRA ­ 2009

int x=34; /* variáveis globais */

int quadrado (int *a)

  *a=(*a)*(*a); 
  a=&x;
  printf("A tem o valor %d\n", *a);
  return(1);
}

main ()
{
 int a=8;
 quadrado(&a);
 printf("A resposta foi %d\n", a);

No exemplo acima, o endereço da variável "a", passada por parâmetro, é trocado e a impressão 
dentro da função gerará o valor 34 (valor de x), mas quando a função retornar, o valor do endereço de 
'a' (passado por parâmetro) é restaurado, portanto continua sendo por valor, mas o valor absoluto de 
"a" foi alterado.

Outra observação importante é que a manipulação de valores dentro de uma função deverá ser 
feita sempre com o operador "*". Outro exemplo:

Cap 8: Apontadores página 38
Linguagem de Programação C ­ Elgio Schlemer ULBRA ­ 2009

int quadrado ( int *a)

  *a=(*a)*(*a);
  a=&x;
  printf("A tem o valor %d\n", *a);
  return(1);
}

main ()
{
 int a=8, *b;
 b=&a;
 quadrado(b);
 printf("A resposta foi %d\n", a);
 printf("A resposta foi %d\n, *b);
}

No exemplo acima, criou­se uma variável do tipo apontador para inteiros e fez­se com que ela 
recebesse o endereço de a. A variável b já contem o endereço de uma área de memória e portando a 
passagem de parâmetro é feita simplesmente pelo seu nome. Por fim, imprimir o valor de a ou o valor 
da posição apontada por b resultará no mesmo valor, pois b aponta para a.

Portanto, sempre que desejar passar algum valor por referência, deve­se passar o endereço deste 
valor, se desejar­se passar o próprio endereço por referência, deve­se passar o endereço do endereço :­
D. Mais adiante serão vistos outras utilidades para ponteiros.

8.2 Exercícios

1) Implemente a função my_strcmp(), semelhante a strcmp() mas apenas retornando 0 se forem 
iguais e diferente de zero se não.

2) Faça uma função para somar dois vetores. A função deve receber 3 vetores (os 2 vetores a 
serem somados e o vetor que receberá a soma) e o tamanho dos vetores e deve retornar a média dos 
valores somados (int).

3) Faça uma função que copie X caracteres para uma outra cadeia de caracteres. A função deverá 
retornar o número de caracteres efetivamente copiados (pode ser <que x que a string origem terminar 
antes). Chamada:

char a[50], b[50], tam;
/* atribua um texto qualquer a a */

tam=copia(b,a,10);  
/* copia no maximo 10 caracteres de a para b. 
Se a string de a tiver só 5, copia apenas os cinco */

Cap 8: Apontadores página 39
Linguagem de Programação C ­ Elgio Schlemer ULBRA ­ 2009

8.3 Alocação Dinâmica de ponteiros

Um apontador pode referenciar uma área de memória já alocada, como por exemplo, um inteiro, 
char ou mesmo um vetor declarado de forma estática. Entretanto, pode­se reservar uma nova área de 
memória e fazê­lo apontar para lá. Considere um exemplo em que tenhamos que guardar os inteiros 
digitados pelo usuário para depois imprimi­los todos. Precisamos de um vetor de inteiros para isso, 
certo? De quantos elementos?

Talvez soubéssemos de antemão que o usuário irá digitar apenas 20 números, então, podemos 
fazer a seguinte declaração: 

int vetor[20];

Mas se o usuário quiser digitar mais que 20, não será possível ao passo que se ele digitar, 
digamos, apenas cinco, estamos desperdiçando espaço (15). E se tivermos uma forma de alocar espaço 
quando tivermos certeza qual o tamanho dele? É possível com o uso de apontadores.

#include <stdio.h>
int main()
{
int *vet;
int tamanho,i;

printf("Quantos elementos vc vai digitar? ");
scanf("%d", &tamanho);
vet = (int *)malloc (tamanho * sizeof(int));
if (vet==NULL) {
printf("Erro na reserva de memória\n");
return(0);
}
/* usa­se vet como vet[0], vet[1], ... vet[tamanho­1] */
}

Vetores estáticos e dinâmicos, como já dito, são praticamente o mesmo para o C, e podem ser 
manipulados de forma semelhante. Vamos analisar por partes o exemplo acima. 

Ao criarmos o apontador vet, ele terá algum endereço de memória e podería­mos usá­lo. Mas, 
este espaço em memória pode estar sendo usado por outro, pois não o reservamos para nosso uso 
exclusivo   (que é o que fazemos  com  int vet[20]). Poderia  ser desastrosa a utilização   nestes 
termos.

Reservar um espaço em memória de uso exclusivo nosso é o que tenta fazer o comando malloc. 
Digo tenta, porque ele pode não conseguir, por exemplo, caso não haja memória suficiente. O malloc 
só reserva espaço em bytes e retorna um apontador para este endereço do tipo void. Por isso que para o 
compilador aceitar, devemos usar um  cast  no início para o tipo que estamos usando (no caso, int). 
Como a reserva é em bytes, devemos reservar a quantidade de bytes que reflita a nossa necessidade. 
No exemplo, temos uma necessidade de tamanho elementos, sendo que cada elemento é do tipo int, o 
espaço que precisamos é de tamanho*sizeof(int).

Cap 8: Apontadores página 40
Linguagem de Programação C ­ Elgio Schlemer ULBRA ­ 2009

O malloc retorna o endereço reservado se conseguiu ou '\0', (NULL) se falhou. É extremamente 
recomendável testar seu retorno antes de usar.

Não é  correto  afirmar que o malloc  cria variáveis  dinâmicas,  pois ele  apenas  reerva  espaço 


deixando   sobre a  inteira responsabilidade do programador  o gerenciamento  desta  área   (e  você 
esperava que fosse diferente? :­D). Logo, se o programador fizer um mal uso, azar o dele!!! Podemos 
considerar um péssimo uso o exemplo a seguir:

#include <stdio.h>
int main()
{
int *vet;
int tamanho=20,i;
vet = (int *)malloc (tamanho * sizeof(int));
if (!vet) {
printf("Erro na reserva de memória\n"); return(0);
}
vet = &i;
}

Na linha em destaque, vet recebe o endereço da variável i. Então, o que aconteceu com a área 
anteriormente   reservada?   Continua   reservada   para   nós,   mas   já   não   temos   como   usá­la   pois   não 
sabemos onde está. 

Para liberar­mos um espaço anteriormente alocado, usamos o comando free():

free(vet);

Não precisamos, neste caso, indicar o tamanho de memória liberada, pois isso o C controla.

8.4 Exercícios:

Faça um programa que crie dois vetores dinâmicos de mesmo tamanho. O tamanho deve ser 
passado por parâmetro na execução do programa. O usuário, ao executar o programa, deve digitar 
todos os elementos de um dos vetores. Depois que todos os elementos foram digitados, os elementos 
do   segundo   vetor   deve   ser   calculado   da   seguinte   forma vet2[0]=tamanho-vet1[0],
vet2[n]=tamanho-vet1[n]. Finalmente imprima ambos os vetores.

Cap 8: Apontadores página 41
Linguagem de Programação C ­ Elgio Schlemer ULBRA ­ 2009

9 Estruturas x Apontadores
A utilização de ponteiros em conjunto com estruturas é muito útil e indispensável em alguns 
casos. Considere o exemplo:

struct aluno {
char nome[40];
char idade;
char endereco[100];
int nmat;
};

Digamos que temos o problema de cadastrar os alunos de uma instituicao e mante­los TODOS 
na memória. Como faríamos?  A idéia mais simples seria criar um vetor desta estrutura:

int main()
{
struct aluno a1[200];
int i;
for (i=0;i<200;i++){
scanf("%s", a1[i].nome);
scanf("%d", &a1[i].idade);
scanf("%s", a1[i].endereco);
scanf("%d", &a1[i].nmat);
}
}

O exemplo acima ira ler nome, idade, endereço e número de matricula de 200 alunos, sendo 
que cada aluno terá uma entrada no vetor da estrutura. 

Da mesma forma, podem­se definir apontadores para estruturas e usa­los de forma semelhante ao 
que já vimos com inteiros:

struct data *st; /*um apontador para uma estrutura */
struct data aux;

aux.dia=2;
sprintf(aux.nome_mês, "Janeiro");
st=&aux;
printf("%s", st­>nome);

st=(struct data *)malloc (10 * sizeof(struct data));
...

Quando acessamos um membro de nossa estrutura e a variavel é um apontador para a estrutura, 
devemos acessá­lo da seguinte forma:

(*st).mes=2;  /* indica que eh um apontador e o membro mês */

Ou, de forma resumida, através do "­>".

Cap 9: Estruturas x Apontadores página 42
Linguagem de Programação C ­ Elgio Schlemer ULBRA ­ 2009

A reserva de um espaço para construir uma estrutura de tamanho dinâmico também se dá de 
forma análoga aos demais tipos de dados. A utilização de ponteiros e estruturas de forma dinâmica 
constitui umas das coisas mais poderosas não apenas no C, mas em todas as linguagens e será estudado 
em um capítulo a parte.

Como sabemos, todo o vetor é na verdade um apontador, de forma que a manipulação dos dados, 
para o mesmo exemplo acima, poderia ser assim:

int main()
{
struct aluno a1[200];
struct aluno *aux;
int i;
aux=a1;
for (i=0;i<200;i++){
scanf("%s", aux­>nome);
scanf("%d", &aux­>idade);
scanf("%s", aux­>endereco);
scanf("%d", &aux­>nmat);
aux++;
}
}

Veja que quando fazemos aux++ estamos incrementando o endereço de aux, de forma análoga 
ao que fazíamos com inteiros. Lembre­se que isto não equivale a somar 1 ao valor do endereço, mas 
sim a pular para a próxima posição de memória onde encontra­se o próximo elemento do meu vetor. 
No caso de char, o próximo elemento encontra­se na posição de memória subsequente, ou seja, +1, no 
caso de inteiros, no Dev++ (32 bits), o próximo elemento está a 4 bytes do atual, ou seja, +4. Ou seja, 
incrementar uma posição de memória de um vetor, equivale a somar o tamanho do tipo de dado ao seu 
endereço. Qualquer operação de soma ou subtração sobre ponteiros será interpretada desta forma.

E se não soubermos quantos alunos teremos que cadastrar? Se a quantidade de alunos será 
fornecida pelo usuário? Podemos usar malloc() para isso da mesma forma que usávamos para vetor de 
inteiros:

Cap 9: Estruturas x Apontadores página 43
Linguagem de Programação C ­ Elgio Schlemer ULBRA ­ 2009

int main()
{
struct aluno *a1;
struct aluno *aux;
int i, naluno;

printf ("Entre com a quantidade de alunos:");
scanf("%d", &naluno);
printf ("\nTentando alocar %d espacos na memoria...\n", naluno);
a1=(struct aluno *)malloc(naluno*sizeof(struct aluno));
if (a1==NULL){
printf ("\nERRO!!! Malloc nao conseguiu alocar espaco.");
return (­1);
}
aux=a1;
for (i=0;i<naluno;i++){
scanf("%s", aux­>nome);
scanf("%d", &aux­>idade);
scanf("%s", aux­>endereco);
scanf("%d", &aux­>nmat);
aux++;
}
}

Após   alocar   espaço   em   memória,   a   manipulação   dos   campos   é   feita   da   mesma   forma!! 
Também poderia ser feita:

scanf("%s", aux­>nome);    /* forma do exemplo */
scanf("%s", (*aux).nome);  /*outra forma de enderecar */
scanf("%s", a1[i].nome);   /* como se fosse um vetor*/

E se nem mesmo o usuário souber quantos alunos deverá cadastrar? Ou se, no dia seguinte, ele 
deseja manter estes mesmos 200 na memória e cadastrar mais 20? Veja que a facilidade de se fazer 
referência   aos  elementos   do  nosso  vetor   está  no  fato   deles  estarem   em  posições   subsequentes   na 
memória, ou seja, na memória depois de aux, vem o elemento aux++. Se alocarmos mais 20 posições 
para o novo cadastro, nada nos garante que estes 20 estarão na memória logo após os 200 já existentes. 
Além do mais, precisaríamos de uma outra variável apontadora, ou então:

Cap 9: Estruturas x Apontadores página 44
Linguagem de Programação C ­ Elgio Schlemer ULBRA ­ 2009

/*precisa alocar mais 20 aos 200 já existentes*/

aux=a1;  /*guardando a posição de memória da minha estrutura*/

a1=(struct aluno *)malloc(220*sizeof(struct aluno));
/*realocando 220 posiçoes*/

if (a1==NULL) {/*consição de erro*/}

/* Copiando TODOS os valores já alocados para a nova area*/
for (i=0;i<200;i++){
sprintf(a1[i].nome, "%s", aux[i].nome);
a1[i].idade=aux[i].idade;
a1[i].nmat=aux[i].nmat;
sprintf(a1[i].endereco, "%s", aux[i].endereco);
}

free(aux); /*liberando espaco na área antiga*/

Isso   não   é   prático,   ao   mesmo   tempo   que   não   há   forma   mais   simples   de   se   fazer   com   as 
estruturas   empregadas.  Veja   que  o  principal   problema   é  que  precisamos  de   espaços   contínuos   na 
memória. Ou será que não?

9.1 Lista encadeada

Para   resolver   este   problema,   faz­se   uso   de   listas   encadeadas.   Os   vários   tipos   de   listas 
encadeadas   existentes   são   estudados   com   profundidade   em   Estruturas   de   Dados.   Veremos   a  mais 
simples.

Uma lista encadeada é quando o próprio elemento de uma estrutura possui um campo com o 
endereço   da   outra.   Quando   temos   uma   alocação   contínua   na   memória,   não   precisamos   desta 
informação,   pois   sabemos   que   ela   será   a   posição   subsequente   na   memória,   isto   é,   basta   pegar   o 
próximo elemento. Na lista encadeada, o elemento anterior possui um campo contento o endereço do 
próximo elemento. Exemplo:

struct aluno {
char nome[40];
char idade;
char endereco[100];
int nmat;
struct aluno *proximo;
};

O   membro  proximo  da   estrutura   acima,   receberá   o   endereço   do   próximo   elemento.   Desta 


forma, podemos alocar espaço na memória a medida que formos necessitando:

Cap 9: Estruturas x Apontadores página 45
Linguagem de Programação C ­ Elgio Schlemer ULBRA ­ 2009

int main()
{
struct aluno *a1;
struct aluno *aux;
int i;

a1=(struct aluno *)malloc(sizeof(struct aluno));
if (a1==NULL) {
/*ERRO*/
}
a1­>proximo=NULL;
aux=a1;

for (i=0;;i++){
scanf("%s", aux­>nome);
scanf("%d", &aux­>idade);
scanf("%s", aux­>endereco);
scanf("%d", &aux­>nmat);
printf("Deseja cadastrar outro (S/N)");
if (getchar()=='S'){
aux­>proximo=(struct aluno *)malloc(sizeof(struct aluno));
if (aux­>proximo==NULL) {/*ERRO*/}
aux=aux­>proximo;
}
else break;
}
}

A manipulação de listas encadeadas não é algo trivial e requer muito cuidado para que alguma 
área   alocada   não   seja   perdida.   Se   não   controlarmos   com   cuidado   o   campo  proximo  sempre 
atualizando­o   com   o   endereço   do   próximo   elemento,   podemos   ir   para   posições   de   memória 
inconsistentes!

No exemplo, se quisermos agora imprimir o nome de todos os alunos e liberar a memória:

/* Ao sair do laco, i contera o numero de alunos cadastrados.
Mas veja que nem ao menos precisamos desta informação*/

for (aux=a1;aux!=NULL;aux=aux­>proximo){
printf ("%s", aux­>nome);
}

/* Tudo foi impresso. Para limpar...*/
for (aux=a1­>proximo;aux!=NULL;){
free(a1);
a1=aux;
aux=a1­>proximo;
}
free(a1);

Observe que agora não podemos mais fazer aux++ pois isso faria com que ele recebesse o que 
deveria ser o próximo elemento da estrutura, o que pode não ser verdade.

Cap 9: Estruturas x Apontadores página 46
Linguagem de Programação C ­ Elgio Schlemer ULBRA ­ 2009

10 Manipulação de Arquivos
Este     capítulo   apresenta   um   conjunto   de   funções   em   C  que   permite   a   leitura   e   escrita   de 
arquivos de forma rápida e padronizada, sem contudo esgotar o assunto. Existem outras funções na 
própria biblioteca padrão que podem ser utilizadas para manipulação de arquivos. Mais detalhes sobre 
estas funções podem ser encontrados em livros especializados ou na documentação que acompanha o 
próprio compilador.

Declaração: definição de pointers para arquivos. Sintaxe:

FILE  *fopen(), *fp;
 |       |       |
 |       |       apontador para arquivo
 |      função que retorna apontador para arquivo
tipo de dado definido em stdio.h

A declaração de apontadores e funções de manipulação de arquivo deve ser feita juntamente 
com a declaração de variáveis.

Exemplo:

FILE *arq1, *arq2;
/* Declaracao de duas variaveis apontadoras para arquivo */

Antes de usar os arquivos, é necessário abri­lo e após usá­lo deve­se fechá­lo.

10.1 Função fopen()

As regras para leitura de arquivos em C são simples. Antes de acessar, para leitura ou escrita, 
um arquivo, o mesmo deve ser aberto pela função fopen() da biblioteca padrão. A função fopen() 
recebe o nome do arquivo e o modo de acesso ao arquivo (leitura, escrita, atualização etc.), e retorna 
um apontador de arquivo. Este apontador de arquivo corresponde ao endereço de uma estrutura que 
contém   informações   sobre   o   arquivo,   tais   como:   localização   de   um  buffer,   a   posição   do   caracter 
corrente no buffer, se o arquivo está sendo lido ou gravado etc. A estrutura recebe o nome FILE e está 
definida no arquivo stdio.h.

O nome do arquivo e o modo são cadeias de caracteres variáveis ou constantes. A cadeia de 
caracteres correspondente ao modo indica como o arquivo vai ser acessado. Entre os modos permitidos 
estão: leitura ("r"), escrita ("w") ou adição ("a"). Para iniciar o acesso a um arquivo para leitura, pode­
se, por exemplo, utilizar o seguinte trecho de código:

Cap 10: Manipulação de Arquivos página 47
Linguagem de Programação C ­ Elgio Schlemer ULBRA ­ 2009

#include <stdio.h>

int main()
{
  /* Ponteiro para o arquivo; FILE definido em stdio.h */
  FILE *pont_arq;

  /* Abre o arquivo para leitura. */
  pont_arq = fopen ( "NOME.ARQ", "r" );
  ...

Observe que, se um arquivo é aberto para escrita, o seu conteúdo antigo é descartado. E que, se 
um arquivo é aberto para escrita ou adição, e ele não existe, um arquivo novo será criado. Por outro 
lado, se um arquivo for aberto para leitura, ele obrigatoriamente deverá existir, caso contrário um erro 
será retornado pela função fopen().

A função fopen() retornará NULL (definido em stdio.h) sempre que o arquivo não puder ser 
aberto no modo especificado. É conveniente, portanto, testar o valor retornado pela função fopen() 
para garantir o correto funcionamento do programa. O seguinte trecho de código pode ser acrescentado 
ao arquivo.

  ...
  if ( pont_arq == NULL)
     /* Se pont_arq for igual a NULL, não conseguiu */
     /* abrir o arquivo. */
     printf ("ERRO: arquivo não pode ser aberto!\n");
  else
     ...

Modos:
"r" ­ leitura (open)
"w" ­ escrita (rewrite)
"a" ­ adição (append)
"r+" ­ para atualizar um arquivo (read e write)
"w+" ­ para criar um arquivo para atualização
"a+" ­ adição, em arquivo para leitura e escrita
Existem certos tipos de apontadores de arquivos padrão que são constantes:
stdin ­ entrada padrão (normalmente teclado)
stdout ­ saída padrão (normalmente vídeo)
stderr ­ saída padrão de mensagens de erro (vídeo)

10.2 Funções fgetc() e fputc()

Depois que o arquivo foi aberto com sucesso, será possível gravar ou escrever no arquivo. 
Existem várias possibilidades para leitura ou escrita do conteúdo do arquivo, mas as mais simples são 
as funções fgetc() e fputc(). A primeira lê um caracter do arquivo e a segunda escreve um caracter no 
arquivo.

Cap 10: Manipulação de Arquivos página 48
Linguagem de Programação C ­ Elgio Schlemer ULBRA ­ 2009

A função fgetc() recebe o ponteiro para o arquivo (retornado por fopen()) e retorna o próximo 
caracter do arquivo. Quando o final de arquivo é encontrado a constante EOF (também definida em 
stdio.h)   é   retornada.  Por  exemplo,  para   atribuir  à  variável   car  o  próximo  caracter   de  um   arquivo 
anteriormente aberto, pode­se utilizar a seguinte seqüência:

  /* Lê um caracter do arquivo. */
  car = fgetc ( pont_arq ); 

A função inversa de fgetc() é fputc(), que escreve um caracter na próxima posição do arquivo. 
fputc() recebe o caracter a ser escrito e o ponteiro para o arquivo. Por exemplo:

  /* Escreve um caracter no arquivo. */
  fputc ( car, pont_arq ); 

coloca o caracter car no arquivo aberto anteriormente e cujo ponteiro é pont_arq.

10.3 Função fclose()

Após a abertura com sucesso do arquivo e sua conseqüente utilização é necessário fechar o 
arquivo, ou seja, informar ao sistema operacional que ele não será mais necessário.

A função fclose() recebe o ponteiro para um arquivo aberto por fopen() e fecha o arquivo. Para 
fechar o arquivo utilizado nas seqüências de código anteriores, poderíamos utilizar, por exemplo:

  /* Fecha o arquivo pont_arq, aberto com fopen(). */
  fclose ( pont_arq );

10.4 Um Exemplo

As funções descritas nas seções anteriores podem ser utilizadas para criar programas como o 
comando TYPE do DOS ou cat do UNIX. O programa a seguir ilustra a impressão de um conjunto de 
arquivos cujos nomes foram fornecidos a partir da linha de comando.

Cap 10: Manipulação de Arquivos página 49
Linguagem de Programação C ­ Elgio Schlemer ULBRA ­ 2009

/* Programa: cat.c */

#include <stdio.h>

/* Função principal. */
/* argc = núm. nomes fornecidos na linha de comando, */
/* argv[] = nomes */

int main ( int argc, char *argv[] )
{                                    
  FILE *arq; /* Ponteiro para o arquivo. */
  int  car;  /* Recebe caracteres lidos do arquivo. */
  int  i;    /* Contador de nomes de arquivos fornecidos. */

  /* Para todos os nomes fornecidos na linha de comando, */
  /* menos o nome do executável ... */

  for ( i = 1; i < argc; ++i )  {
      /* ... tenta abrir o arquivo com o nome fornecido. */
      arq = fopen ( argv[i], "r" );

      /* Se conseguiu entao ... */
      if ( arq != NULL )  {
         /* ... inicia a impressão do arquivo. */
         /* Enquanto fgetc não retornou EOF ... */
         while ( ( car = fgetc ( arq ) ) != EOF )
               /* ... imprime car na saída padrão. */ 
               fputc ( car, stdout );

         /* Fecha o arquivo aberto. */
         fclose ( arq );
      }
      else  /* Se nao conseguiu abrir o arquivo... */
         /* ... imprime uma mensagem de erro. */
         printf ( "ERRO: Impossível abrir arquivo %s\n", argv[i] );

  }  /* Fim do for(;;). */
}  /* Fim do main(). */

10.5 Usando fscanf e fprintf

Para entrada e saída formatada em arquivos, as funções fscanf() e fprintf() podem ser usadas. 
Elas são idênticas a scanf() e printf(), exceto que o primeiro argumento é um apontador de arquivo que 
especifica   o   arquivo   a   ser   lido   ou   gravado.   O   formato   é   o   segundo   argumento   e   os   parâmetros 
aparecem em seguida.

Por exemplo, o salvamento de um vetor de registros formados por um inteiro, um real e uma 
cadeia de caracteres poderia ser feita em um arquivo usando o código a seguir:

Cap 10: Manipulação de Arquivos página 50
Linguagem de Programação C ­ Elgio Schlemer ULBRA ­ 2009

  ...
  struct regsitro{
    int    inteiro;
    double real;
    char   cadeia[20];
  };    /* Tipo criado para definição do registro. */

  struct registro vet_reg[20];    /* Vetor de 20 registros. */
  int i;                   /* Contador de registros. */
  FILE *arq;               /* Ponteiro para o arquivo aberto. */

  /* Abre o arquivo para escrita. */
  arq = fopen ( "ARQUIVO.TXT", "w" );

  /* Se conseguiu abrir o arquivo para escrita, então ... */
  if ( arq != NULL )  {
     /* ... inicia salvamento dos registros. */
     /* Para cada um dos 20 registros ... */
     for ( i = 0; i < 20; ++i )  {
         /* ... imprime os seus 3 campos separados por espaço, */
         /* em uma linha do arquivo. */
         fprintf ( arq, "%d %lf %s\n", vet_reg[i].inteiro,
                                       vet_reg[i].real,
                                       vet_reg[i].cadeia );
     }  /* Fim for(;;). */

     /* Fecha o arquivo. */
     fclose ( arq );
 
 }  /* Fim if(). */

  else  /* Se não conseguiu abrir o arquivo... */
     /* ... imprime uma mensagem de erro. */
     printf ( "ERRO: Impossível abrir o arquivo para escrita.\n" );
  ...

Da   mesma   forma   que   a   escrita   ou   criação,   a   recuperação   poderia   ser   feita   com   a   função 
correspondente de leitura, ou seja, fscanf(). Deve­se, no entanto, tomar os mesmos cuidados tomados 
com a função scanf(). Não há, por exemplo, como ler cadeias de caracteres com espaços em branco, 
uma vez que, para scanf() ou fscanf(), o caracter de espaço ou final de linha é o separador de entradas. 
Isto significa que a leitura de uma cadeia de caracteres será interrompida tão logo um espaço ou um 
caracter de nova linha seja encontrado. A próxima entrada será lida pela próxima função scanf() ou 
fscanf().

Considerando que as cadeias de caracteres do exemplo anterior não apresentam espaços em 
branco, a sua recuperação poderia ser feita com o seguinte trecho de programa.

Cap 10: Manipulação de Arquivos página 51
Linguagem de Programação C ­ Elgio Schlemer ULBRA ­ 2009

  ...
 struct registro {
    int    inteiro;
    double real;
    char   cadeia[20];
  };  
           /* Tipo criado para definição do registro. */

  struct registro vet_reg[20];   /* Vetor de 20 registros. */
  int i;                  /* Contador de registros. */
  FILE *arq;              /* Ponteiro para o arquivo aberto. */
  double auxval;          /* Variável auxiliar p/ leitura de doubles. 
*/

  /* Abre o arquivo para leitura. */
  arq = fopen ( "ARQUIVO.TXT", "r" );

  /* Se conseguiu abrir o arquivo para leitura, então ... */
  if ( arq != NULL )  {
     /* ... inicia recuperação dos registros. */

     /* Para cada um dos 20 registros ... */
     for ( i = 0; i < 20; ++i )  {
         /* ... lê os seus 3 campos do arquivo. */
         fscanf ( arq, "%d %lf %s", &(vet_reg[i].inteiro),
                                    &auxval,
                                    vet_reg[i].cadeia );
         vet_reg[i].real = auxval;
     }  /* Fim for(;;). */

     /* Fecha o arquivo. */
     fclose ( arq );
  }  /* Fim if(). */

  else  /* Se não conseguiu abrir o arquivo... */
     /* ... imprime uma mensagem de erro. */
     printf ( "ERRO: Impossível abrir o arquivo para leitura.\n" );
  ...

10.6 Entrada e Saída de Linhas

Em algumas situações, como a leitura de cadeias de caracteres com espaços, a função fscanf é 
limitada. Nestas situações é possível utilizar a função fgets() da biblioteca padrão. Esta função lê uma 
linha inteira do arquivo e a coloca em um vetor de caracteres. A função fgets() recebe o vetor de 
caracteres onde a linha será depositada, o tamanho máximo de caracteres que deverá ser lido da linha e 
o ponteiro para o arquivo do qual os caracteres serão lidos. Desta forma, no trecho de programa a 
seguir, a função fgets() lê um máximo de MAXLINHA­1 caracteres (incluindo o caracter de nova­
linha) e os coloca no vetor linha.

Cap 10: Manipulação de Arquivos página 52
Linguagem de Programação C ­ Elgio Schlemer ULBRA ­ 2009

#define MAXLINHA 256
...
char linha [ MAXLINHA ];
FILE *arq;
...
fgets ( linha, MAXLINHA, arq );

A linha lida será automaticamente terminada por '\0'. A medida que as linhas forem sendo lidas, 
fgets() retornará o próprio vetor linha. No final do arquivo, fgets() retornará NULL.

Caso seja necessário ler cadeias de caracteres com espaços em branco, a função fgets() poderia 
ser utilizada. A seguir o programa deveria localizar as diversas entradas sobre a própria cadeia de 
caracteres lida.

A função análoga para escrever linhas em um arquivo é fputs(), usada da seguinte forma:

fputs ( linha, arq );

10.7 Função feof()

Esta função retorna 0 se o final do arquivo foi alcançado.

10.8 Exercícios

1) Faça  um  comando   chamado   “copiarq”,  em   C, que  copia  um  arquivo  especificado   sobre   outro 
arquivo. Sintaxe:

copiarq arquivo1 arquivo2

Cap 10: Manipulação de Arquivos página 53
Linguagem de Programação C ­ Elgio Schlemer ULBRA ­ 2009

11 Definição de Novos Tipos de Dados
Sintaxe:

typedef <descrição do tipo> <novo tipo>

Exemplos:

typedef long inteiro;
typedef enum {seg,ter,qua,qui,sex,sab,dom} dias;
inteiro var_int;
dias    hoje;

Cap 11: Definição de Novos Tipos de Dados página 54
Linguagem de Programação C ­ Elgio Schlemer ULBRA ­ 2009

12 MACROS DO PRÉ­PROCESSADOR
As macros são instruções analisadas por um pré­processador (CPP ­ C pré­processador) que 
realiza as operações definidas. As principais macros existentes são:

#include #define #if #ifdef #endif


#line #undef #else #ifndef #elif

Nas seções a seguir são apresentadas a utilização, sintaxe e exemplos destas macros.

12.1 #define

Força   a  substituição  dos  nomes  dados  pelo   texto   de  reposição,  dentro   do  código  fonte   do 
programa.   Há também  a macro  substituição  com  parâmetros, na qual os argumentos  são trocados 
posicionalmente (de acordo com sua posição) e por correspondência. Esta operação é realizada antes 
do início da compilação, sendo transparente ao compilador. Sintaxe:

#define <nome> <texto_de_reposição> ou
#define <nome> ( argumento, argumento, ...) <texto_de_reposição>

As definições podem ser bem extensas podendo continuar em outras linhas com o auxílio de 
uma "\" no final da linha.

Exemplos:

#define MAXVAL 999
#define then
#define begin {
#define end   }
#define max(a,b) ((a>b)? a:b )

Fonte antes do CPP Fonte após o CPP
if (num == valor) if (num == valor)
then begin {
i++; i++;
x = MAXVAL;' x = 999;
end }
else x = max (num,valor); else x = ((num<valor)? num:valor);
Pode­se,  portanto,  declarar  um  trecho   de código  completo   através   de  um  #define,  como   o 
exemplo   do   max(a,b)   anterior.   Antes   usava­se   o   #defeine   apenas   como   uma   forma   de   declarar 
constantes, mas seu uso é muito mais amplo do que isso. Pode ser usado quase como uma função 

Cap 12: MACROS DO PRÉ­PROCESSADOR página 55
Linguagem de Programação C ­ Elgio Schlemer ULBRA ­ 2009

(entretanto, não retorna valor). Qual seria a vantagem de se declarar um bloco inteiro de código através 
de um #define? Consideremos o seguinte exemplo:

#define tela(a) \
{ \
clrscr();  \
printf("Apenas um teste\n"); \
printf("Digite um inteiro: "); \
scanf("%d", &a); \
}

int main()
{
int valor;
tela(valor);
printf("Voce digitou %d\n", valor);
return(1);
}

Um define compreende tudo que existe até o final da linha atual. Para facilitar a visualização, é 
interessante   que   ele   se   extenda   por   várias   linhas,   quando   o   usamos   desta   forma.   Para   "dizer"   ao 
compilador   que   nosso   define   continua   na   linha   seguinte,   devemos   terminar   a   linha   atual   com   '\'. 
Evidentemente, a última linha de nosso comando não terá este caracter.

Mas, voltemos a nossa pergunta inicial: qual seria a vantagem e desvantagem de se usar um 
define neste caso, e não uma função? 

Lembre­se que, de fato, NÃO SERÁ este o código que efetivamente será compilado, mas sim o 
seguinte:

Cap 12: MACROS DO PRÉ­PROCESSADOR página 56
Linguagem de Programação C ­ Elgio Schlemer ULBRA ­ 2009

int main()
{
int valor;

{
clrscr(); 
printf("Apenas um teste\n");
printf("Digite um inteiro: ");
scanf("%d", &valor);
}

printf("Voce digitou %d\n", valor);
return(1);
}

Isto   é,   onde   houver   o   nome   tela,   este   será   substituído  antes   de   compilar,   pelo   nossos 
comandos,   fazendo   devidamente   a   substituição   de   a   pelo   nome   passado   nos   parênteses.   Qual   a 
vantagem? Evidente: dispensa o custo que teríamos em uma chamada de função, logo é bem mais 
rápido que uma função!! E qual seria a desvantagem? Aumento no tamanho do código. Se usarmos 
tela umas 10 vezes, sendo ela uma função ocupará apenas uma vez espaço em memória pelo seu 
código. Sendo ela um define, ocupará 10 vezes mais.

Esta técnica é conhecida como MACROS e alguns compiladores C possui um modificador na 
função chamdo inline, que faz algo parecido.

12.2 #undef

Desfaz a definição, faz com que o pré­processador "esqueça" a definição feita anteriormente. 
Sintaxe:

#undef <identificador>

Exemplo:

#undef MAXVAL

Como não é possível redefinir uma constante ou macro, é necessário desfazê­la antes.

12.3 #include

Causa a substituição da linha pelo conteúdo do arquivo informado. Inclui um trecho de um 
programa contido no arquivo especificado, a partir da linha onde foi dado o include. Sintaxe:

Cap 12: MACROS DO PRÉ­PROCESSADOR página 57
Linguagem de Programação C ­ Elgio Schlemer ULBRA ­ 2009

#include "nome_do_arq" /* ou */
#include <nome_do_arq> /* são aceitas tanto as "" como <> */

Observação: no TC as "" indicam que o arquivo include se encontra no diretório corrente e o 
<> indica que o arquivo deve ser procurado no diretório definido como diretório de arquivos include.

Exemplos:

#include "arquivo" /* Inclui o texto do "arquivo" no texto fonte */
#include <stdio.h> /* Inclui a biblioteca de rotinas stdio.h */

Já   usamos   muito   o   include   para   adicionar   bibliotecas   em   nossos   códigos,   mas   agora   que 
entendemos seu significado, podemos perceber que podemos usá­lo para adicionar qualquer coisa em 
nosso código, inclusive funções já definidas em algum .c que criamos anteriormente (cuidado para não 
existir mais de um main: o seu e o do código inserido. Lembre­se que o include é como um Copy And 
Paste).

12.4 Compilação Condicional

Há um conjunto de instruções que são definidas para permitir uma compilação condicional do 
programa fonte. Certas funções podem ser ou não compiladas conforme estas diretivas.

#if <expr_constante>    // Se != 0 então gera o código que segue
#ifdef <identificador>  // Se ident foi definido gera código
#ifndef <identificador> // Se ident não foi def. gera cód.
#else                   // Funciona em com o #if. (caso oposto)
#elif                   // Associação de um else com if
#endif                  // Final do trecho de código condicional

Exemplo:

#define versao 2.3
#if versao >= 2.1 //Inclui o arquivo "ext_21" caso a versao
#include <ext_21.c> // seja maior ou igual a 2.1
#endif

Estas   funções   são   vastamente   usadas   no   C   por   programadores   experientes,   principalmente 


quando escreve­se um código que deve compilar com sucesso em várias arquiteturas (DOS e Linux, 
por exemplo) e existem pequenas diferenças entre elas. Pr exemplo, a biblioteca stdlib.h não existe no 
Linux (o clrscr(), gotoxy() estão nesta biblioteca). Como resolver? Uma solução seria esta:

Cap 12: MACROS DO PRÉ­PROCESSADOR página 58
Linguagem de Programação C ­ Elgio Schlemer ULBRA ­ 2009

#ifdef LINUX
#include "my_stdlib.h"
#else
#include <stdlib.h> 
#endif

E   em   my_stdlib.h   programa­se   as   funções   inexistentes   no   Linux.   Para   compilar   no   DOS, 


procederia­se normalmente. Mas para compilar no Linux, usaria­se o gcc com a seguinte sintaxe:

gcc -DLINUX nome.c -o nome

Pense   nas   inúmeras   possibilidades   que   isto   proporciona!!   Exemplos   bastante   comuns   em 
códigos abertos, onde o usuário tem a possibilidade de compilar e/ou modificar o código, são no uso 
da língua usada nas mensagens de erro:

#ifdef PT
#include "portugues.h"
#endif
#ifdef EN
#include "english.h"
#endif

12.5 Exercício

1) Escrever sob forma de macro as seguintes atribuições:
a) menor valor de dois números;
b) o quadrado de um número;
c) o dobro de um número;
d) retornar 1 se o número for par e zero em caso contrário;
e) retornar 1 se o número for impar e zero em caso contrário;
f) retornar 1 se os dois valores tiverem o mesmo sinal ou zero em caso contrário (considerar zero 
positivo);
g) retornar 1 se o segundo número for divisor do primeiro e zero em caso contrário.

Cap 12: MACROS DO PRÉ­PROCESSADOR página 59