Documenti di Didattica
Documenti di Professioni
Documenti di Cultura
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 PDP11.
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 PDP11. 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 usase o seguinte
trecho de programa:
char nome[31]; /* Além dos 30 caracteres, devese 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, devese 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 “shortcircuit”, 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 (Seentãosenão) e de laços
(enquantofaç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, devese 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 = c << 1; /* Os bits de c são rotacionados uma vez para a
esquerda, perdendose o bit mais significativo
(ultimo da esquerda) e colocandose 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, devese levar em conta que a operação será feita sobre
TODOS os trinta e dois bits.
2.7 Operadores de Assinalamento , Pré e Pósincremento
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 = i3; */
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:
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()
Tratase 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 podese definir um
conjunto de comandos como se fossem um só, sendo que no C podese 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, declarase 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:
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 temse "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 usase 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, devese 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 tratase 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; ]
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 ifelse 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 |ab|, se cod <0. Observação: |ab| = abs(ab).
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 temse “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, armazeneos 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 reexecuçã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, colocandoos 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);
}
Podese 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 temse:
argc número de apontadores;
argv array de apontadores para cadeias de caracteres.
Temse 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. Usase 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 designarmos 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
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). Podese 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 podese 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, devese 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ãointeiros ou nãodouble 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;
#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:
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, criase 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.
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, recomendase 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 terse 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, devese usar uma função específica */
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. Podese, 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:
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:
Temse, 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, referenciarse a variável teste com a seguinte sintaxe:
Cap 8: Apontadores página 34
Linguagem de Programação C Elgio Schlemer ULBRA 2009
&teste
Estase 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 somandose tantas unidades quanto for o tamanho do elemento do array.
Portanto podemos fazer somas e incrementos com ponteiros operandoos 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 podese
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);
}
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, criouse uma variável do tipo apontador para inteiros e fezse 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, devese passar o endereço deste
valor, se desejarse passar o próprio endereço por referência, devese 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, podese 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 imprimilos 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);
}
/* usase vet como vet[0], vet[1], ... vet[tamanho1] */
}
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íamos 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.
#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 liberarmos 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 mantelos 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, podemse definir apontadores para estruturas e usalos 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. Lembrese 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 encontrase o próximo elemento do meu vetor.
No caso de char, o próximo elemento encontrase 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, fazse 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;
};
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
atualizandoo 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 abrilo e após usálo devese 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, podese 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(). Devese, 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 MAXLINHA1 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:
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);
Podese, portanto, declarar um trecho de código completo através de um #define, como o
exemplo do max(a,b) anterior. Antes usavase 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?
Lembrese 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. Lembrese 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
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
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