Anda di halaman 1dari 85

Impressão de ● Sistema binário, hexadecimal e

caracteres por acesso


octal
● Operadores sobre os bits
Conversão entre sistemas
direto à memória

● Campos bit

Conteúdos
Sistemas de Numeração

No nosso dia-a-dia, utilizamos o Sistema de Numeração Decimal.

Sabem porque tem o nome de decimal?

Pois bem, é porque usa um conjunto de dez algarismos diferentes, a saber: 0, 1, 2, 3, 4, 5,


6, 7, 8 e 9. O n.º 10, por exemplo, é a combinação do algarismo 0 com o algarismo 1.
Sistemas de Numeração

Tudo o que se passa dentro do computador é armazenado utilizando números (quer seja
dados, texto, sons ou imagens)

Tudo começou com a invenção do bit. Ora, o bit é apenas uma forma de representar dois
estados: verdadeiro e falso ou ligado ou desligado.

Com um bit podemos representar dois estados (verdadeiro ou ligado, representado por 1) e
(falso ou desligado, representado por 0).
Sistemas de Numeração

Um bit serve perfeitamente para indicar qual o sexo de um determinado indivíduo, pois
(pelo menos até agora) apenas existem o sexo masculino e o feminino:
1 - Masculino
0 - Feminino
Se tentarmos representar o estado civil de uma pessoa um bit já não chega, pois os
possíveis estados são:
00 - Solteiro
01 - Casado
10 - Divorciado
11 - Viúvo
Sistemas de Numeração
Assim, para se representar conjuntos maiores de valores a solução é sempre a mesma: ir
juntando bits até que possamos representar o conjunto pretendido.

Quando é que se pára de juntar bits?

A solução encontrada foi definir uma unidade. Essa unidade vai passar a ter 8 bits e passa a
se chamar byte.

Como se representam então os números dentro de um byte?

0 - 0000 0000 3 - 0000 0011 6 - 0000 0110


1 - 0000 0001 4 - 0000 0100 7 - 0000 0111
2 - 0000 0010 5 - 0000 0101 8 - 0000 1000
9 - 0000 1001
Sistemas de Numeração

O sistema decimal utiliza os valores


de 0 a 9 (dez dígitos), o binário
utiliza os valores 0 e 1.
Sistemas de Numeração
Observem, que se representarmos os bits da direita para a esquerda, começamos na
posição índice zero,

facilmente obtemos os valores que se apresentam na coluna da direita:

Ficamos assim sabendo que o bit que se apresenta na posição índice n estiver com 1, o
número que ele representa é o 2n
Sistemas de Numeração
Que número é 01102 na base 10?

0110(2) = 0*23 + 1*22 + 1*21 + 0*20

0110(2) = 4 + 2 = 6(10)
Sistemas de Numeração
Facilmente se compreende agora que se um byte é constituído por 8 bits, o menor valor
nele representado será o 0

e o maior será

1111 1111(2) = 1*27 + 1*26 + 1*25 + 1*24 + 1*23 + 1*22 + 1*21 + 1*20

1111 1111(2) = 128 + 64 + 32 + 16 + 8 + 4 +2 +1 = 255(10)

Assim, o maior número representado em um byte será o número 255, existindo assim 256
números passíveis de serem representados.
Sistemas de Numeração
A base que se adapta menos à computação é a base decimal, por isso, e porque não
podemos apenas trabalhar na base 2, pois é complicado representar valores numéricos bit a
bit, foram desenvolvidas bases de trabalho diferentes.

Assim, surgiram as bases Octal (oito símbolos) e Hexadecimal (16 símbolos)


Sistemas de Numeração
Base Octal

A base octal é formatada pelos símbolos 0 .. 7.


Sistemas de Numeração
Tomemos o exemplo do número 29(10).

Segundo a tabela, ele é equivalente ao número 35(8)

Vamos conferir:

35(8) = 3*81 + 5*80

35(8) = 24 + 5 =29(10)
Sistemas de Numeração
Base Hexadecimal

A base hexadecimal é formatada por 16 símbolos. Como a base de trabalho (base 10) é
composta por apenas 10 símbolos (0..9), houve necessidade de adicionar os símbolos A(10),
B(11), C(12), D(13), E(14) e F(15).
Sistemas de Numeração
Tomemos o exemplo do número 29(10).

Segundo a tabela, ele é equivalente ao número 1D(16)

Vamos conferir:

1D(16) = 1*161 + D*160

1D(16) = 16 + 13*1 =29(10)


Sistemas de Numeração
Como podemos escrever em C um número em Octal?

Bastará colocar esse valor precedido de um zero.


Sistemas de Numeração
Como podemos escrever em C um número em Hexadecimal?

Bastará colocar esse valor precedido de 0x.


Sistemas de Numeração
Converter Decimal para Binário

Como representamos o número 181 em binário?

Vamos começar por representar o número 181 como um conjunto de bits.

Começa-se sempre da esquerda para a direita.

Temos então de representar o número com uma sequência de 0 e 1.


Sistemas de Numeração
Neste momento temos representado o número 128, por isso falta-nos (181-128=53)

O valor do próximo bit é > 53, logo esse bit não vai ser utilizado, ficando com 0.

Como 53 > 32, vamos ficar com o bit em 1 e sobram ainda (53-32=21)

Como 21 > 16, vamos ficar com o bit em 1 e sobram ainda (21-16=5)
Sistemas de Numeração
Facilmente se compreende que para conseguir construir o número 5 a partir dos bits
teremos de fazer 4+1, isto é, selecionar os bits associados a esses dois números.

181(10) = 10110101(2)
Sistemas de Numeração
Converter Binário para Octal

É extremamente simples, a base Octal é constituída pelos seguintes 8 símbolos, bastando


três bits para representar qualquer dos números da base:

Assim, para representar o número anterior:

10110101(2) = (10)(110)(101) = 265(8)


Sistemas de Numeração
Converter Binário para Hexadecimal

É extremamente simples, faz-se como no procedimento anterior. A base hexadecimal é


constituída pelos 16 símbolos (0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F), sendo neste caso necessário
quatro bits.

Assim, para representar o número anterior:

10110101(2) = (1011)(0101) = B5(16)


Sistemas de Numeração
Converter Binário para Decimal

Já sabemos, basta multiplicar cada um dos bits pelo valor da base elevada a cada uma das
potências.

Exemplo: Converter 10110 para decimal

10110(2) = 1*24 + 0*23 + 1*22 + 1*21 + 0*20

10110(2) = 16+0+4+2+0 = 22(10)


Sistemas de Numeração
Converter Octal para Decimal

Já sabemos, basta multiplicar cada um dos bits pela base 8 elevada a cada uma das
potências.

Exemplo: Converter 067 para decimal

067(8) = 0*82 + 6*81 + 7*80

067(8) = 48+7 = 55(10)


Sistemas de Numeração
Converter Hexadecimal para Decimal

Já sabemos, basta multiplicar cada um dos bits pela base 16 elevada a cada uma das
potências.

Exemplo: Converter 0x1A5 para decimal

1A5(16) = 1*162 + A*161 + 5*160

1A5(16) = 256+10*16+5 = 421(10)


Sistemas de Numeração
Operações Bit a Bit

Agora que já sabemos comos os números são representados internamente, podemos utilizar
algumas características de um nível da linguagem mais baixo que permitem a manipulação
de dados ao nível do bit.

Operadores de manipulação de Bits


Sistemas de Numeração
Exemplos:

& (apenas quando existem bits com 1 em ambas as posições é que o resultado é 1, se não é
sempre 0)

| (basta que exista um bit com 1 para que o bit resultado seja 1. Para ser 0 os dois bits
terão de ser simultaneamente iguais a 0)
Sistemas de Numeração
Exemplos:

^ (o resultado deste operador é 1 se os bits têm valores distintos. Se forem iguais o


resultado é 0 bit a bit)
Sistemas de Numeração
Campos Bits

Na definição de uma estrutura podemos definir campos formados apenas por um ou alguns
Bits, permitindo assim poupar espaço.

Se, por exemplo, ao armazenar o valor “SIM” ou “NÃO”, “MASCULINO” ou “FEMININO” será
usado 1 byte para cada um dos caracteres armazenados. Para este caso apenas precisamos
de 1 bit, pois existem apenas 2 estados (1 ou 0).

Vamos definir uma estrutura com os seguintes campos:

● nome
● idade
● sexo (0: Mulher, 1: Homem)
● Estado civil (0: Solteiro, 1: Casado, 2: Viúvo, 3: Divorciado)
Sistemas de Numeração
struct Pessoa {
char nome[100];
int idade;
unsigned sexo : 1; /* 1 bit */
unsigned est_civil : 2; /* 2 bits */
};

Nota: Você pode usar o unsigned para se certificar que essa variável inteira nunca irá
receber um valor negativo, como para armazenar dados de memória, idade, etc...
Sistemas de Numeração

Nota: Os campos armazenados em


bits podem ser particularmente
úteis se forem utilizados para
representar os dados de perguntas
em que o conjunto de respostas é
normalmente limitado e constituído
por conjuntos predefinidos de
valores.
● Níveis de leitura e escrita em
ficheiros
● Abertura e fecho de ficheiros
● Condições de erro

Ficheiros
● Buffers
● Outras funções de manipulação de
ficheiros

Conteúdos
Ficheiros

Até agora usaram programas que pediam dados ao utilizador e o programa é que
manipulava-os. Uma vez terminado o programa, todos os dados introduzidos ou resultados
eram perdidos.

Neste módulo vamos aprender a manipular ficheiros.


Ficheiros

Operações básicas sobre ficheiros

● Para processar um ficheiro, a primeira operação a ser realizada é ligar uma variável a
esse ficheiro, dar um nome ao ficheiro que pretendemos criar (abertura de ficheiro).

● Desta forma evitamos estar sempre a escrever o nome do ficheiro sempre que
precisamos de o referenciar.

● Depois de criado o ficheiro, podemos realizar todas as operações que pretendemos


sobre esse ficheiro - ler, escrever, posicionarmo-nos ao longo deste, etc…

● Depois de processado o ficheiro, quando já não precisarmos dele, devemos retirar a


ligação a essa variável (fechar o ficheiro).
Ficheiros

Abertura de um ficheiros

Para podermos utilizar um ficheiro, temos que declarar uma variável do tipo FILE (ou, mais
propriamente, um ponteiro para o tipo FILE)
FILE *ficheiro;

A abertura de um ficheiro é realizada utilizando a função fopen, e encontra-se no ficheiro


stdio.h, pois trata-se de uma operação padrão (normal) de entrada/saída

A sintaxe é:

FILE * fopen(const char *filename, const char *mode)


Ficheiros

A sintaxe é:

FILE * fopen(const char *filename, const char *mode)

A função recebe, assim, dois parâmetros

filename - string contendo o nome do ficheiro (ex: DADOS.DAT)

mode - string contendo o modo de abertura do ficheiro


Ficheiros

Nome de um ficheiros

Para associar a nossa variável ponteiro para um ficheiro que será lido no computador, é
necessário que seja fornecido a localização desse ficheiro, afinal o C não adivinha isso.
Isto é feito através de uma string passada para a função fopen(), e esta string deve conter o
endereço completo do ficheiro e isso inclui o nome do ficheiro.
Vamos supor que trabalhamos com um ficheiro chamado “arquivo.txt”.
Se queremos usar este ficheiro e ele está na mesma pasta do executável, precisamos
apenas de fornecer o nome completo (com extensão) do ficheiro. Por ex: arquivo.txt
Ficheiros

Modos de abertura
“r” - read - abertura do ficheiro para leitura
FILE *arquivo = fopen("arquivo.txt", "r");

“w” - write - abertura do ficheiro para escrita


FILE *arquivo = fopen("arquivo.txt", "W")
Atenção: cria um novo ficheiro, mas se o ficheiro existir apaga o existente e substitui-o pelo
novo.

“a” - append - abertura do ficheiro para acrescentar


FILE *arquivo = fopen("arquivo.txt", "a")
Atenção: se não existir ele cria um novo ficheiro, se existir, coloca-se no final do ficheiro de
forma a introduzir novos dados de forma sequencial.
Ficheiros

Para além destes 3 modos básicos existe ainda a possibilidade de abrir um ficheiro de forma
a permitir simultaneamente operações de leitura e escrita colocando um sinal de + após o
modo.
“r+” - abertura do ficheiro para leitura e escrita.
FILE *arquivo = fopen("arquivo.txt", "r+")
Se o ficheiro não existir é criado. Se já existir, os novos dados serão colocados por cima dos
dados existentes (apaga os dados anteriores).

“w+” - abertura do ficheiro para leitura e escrita.


FILE *arquivo = fopen("arquivo.txt", "w+")
Se o ficheiro não existir é criado. Se já existir, é apagado e criado um novo ficheiro com o
mesmo nome.
Ficheiros

“a+” - abertura do ficheiro para leitura e escrita.


FILE *arquivo = fopen("arquivo.txt", "a+")
Se o ficheiro não existir é criado. Se o ficheiro já existir, os novos dados serão colocados a
partir do final do ficheiro.

“rb” - abertura do ficheiro para leitura, mas em modo binário.


FILE *arquivo = fopen("arquivo.txt", "rb")

“wb” - abertura do ficheiro para leitura e escrita, mas em modo binário.


FILE *arquivo = fopen("arquivo.txt", "wb")
Se o ficheiro não existir é criado. Se já existir, é apagado e criado um novo ficheiro com o
mesmo nome.
Ficheiros

“ab” - abertura do ficheiro para leitura e escrita, mas em modo binário.


FILE *arquivo = fopen("arquivo.txt", "ab")
Se o ficheiro não existir é criado. Se o ficheiro já existir, os novos dados serão colocados a
partir do final do ficheiro.
Ficheiros

Fechar um ficheiro

Fechar um ficheiro, retira a ligação entre a nossa variável e o ficheiro existente no disco.
Antes do ficheiro ser fechado são gravados, fisicamente, todos os dados que possam ainda
existir em buffers associados ao ficheiro.

A sintaxe utilizada é:
int fclose(FILE *arq)

Esta função devolve 0 em caso de sucesso ou a constante EOF em caso de erro.

No caso de estarmos a trabalhar com vários ficheiro, podemos fechá-los todos através da
função:
int fcloseall()
Ficheiros

Erros em abertura de ficheiros

Quando abrimos um ficheiro, o ponteiro que criamos do tipo FILE armazenará o endereço de
um ficheiro.

Porém, nem sempre esta tarefa é possível, gerando um erro. Quando este erro ocorre o
ponteiro irá apontar para NULL, sendo essa prática muito importante para o tratamento de
erros.

Este erro pode ocorrer por vários motivos. O mais óbvio é abrir um ficheiro que não existe.

if(arquivo == NULL)
printf("Nao foi possivel abrir o arquivo!");
Ficheiros

Escrever em ficheiros - as funções fputc(), fprintf() e fputs()

Ficheiros padrão - stdin, stdout e stderr

Antes de iniciarmos a escrita nos ficheiros, é necessário explicar alguns detalhes sobre como
os ficheiros são vistos em programação C.

A troca de informações num sistema pode se dar de várias maneiras. Pode ser através da
interação teclado-programa-monitor, onde o utilizador fornece os dados via teclado, o
programa processa essa informação, e exibe algo na ecrã.

Outra maneira de trocar informações é através da impressora ou de um scanner. Ou seja,


podemos receber informações através de um dispositivo externo (como um leitor de códigos
de barras), bem como podemos mandar dados para uma impressora.

Nota: existem várias maneiras de se trocar informações, e constantemente estão a surgir novas. Por exemplo, as tecnologias
WiFi e Bluetooth. Visando facilitar, foram padronizadas como sendo ficheiros.
Ficheiros
Mesmo que apenas tenhamos a interação teclado-programa-monitor, existem alguns
ficheiros abertos, e os mais importantes são:
● stdin - é o ficheiro de entrada padrão. É o teclado.
● stdout - é o ficheiro de saída padrão. É a monitor do computador, através do
terminal de comando.
● stderr - ficheiro padrão de erro, onde podemos direcionar mensagens de erro
para outro local sem ser o da saída padrão, como para um log (espécie de registro
de ações)

Além destes, outros ficheiros como o stdaux (dispositivo auxiliar, geralmente a porta COM1)
e o stdprn(impressora padrão), são abertos automaticamente.
Ficheiros
fputc() - Como escrever um caractere em um ficheiro

Vamos então aprender a criar e escrever em um ficheiro externo no nosso computador.

Para fazer isso vamos usar a função fputc(), que recebe dois dados: o caractere e o FILE*,
que terá as informações do local onde iremos escrever o dito caractere:

int fputc(int char, FILE *arq);

Esta função retorna EOF caso não tenha conseguido escrever no ficheiro, ou retorna um
inteiro que representa o caractere, caso tenha ocorrido.
Ficheiros
Escreva um programa que peça um caractere e guarde esta entrada num ficheiro chamado
"char.txt", localizado na mesma pasta do programa executável.
Ficheiros
Vamos inicialmente definir nosso endereço através de
uma string (char url[]), que é simplesmente
"char.txt", bem como o caractere ch para armazenar o
caractere que vamos escrever.

Como queremos salvar o char num ficheiro, criamos


um ponteiro arq do tipo FILE. Em seguida, solicitamos
ao utilizador para digitar um caractere, que
capturamos através da função getchar() e salvamos
em ch.

Agora vamos abrir um ficheiro para escrever, e isso é


feito através da função fopen(), que vai receber a
string com a localização do ficheiro e o modo de
abertura. Como queremos escrever, o modo é o "w".
Ficheiros

Testamos se a fopen não retorna NULL para arq. Se


não retornar, escrevemos o caractere no ficheiro.
Isto é feito pela função fputc, que é diferente da
putchar que necessita só do caractere, ela também
necessita saber onde vai ser a saída - monitor.

Essa saída é indicada pelo vetor arq. E pronto, o


caractere é guardado no ficheiro "char.txt".
Mas antes de finalizar o programa, devemos fechar o
ficheiro, usando o fclose e passando o arq como
argumento.
Ficheiros
Crie um programa semelhante ao anterior, mas em vez de substituir o caractere, adicione
um caractere ao fim do ficheiro.
Ficheiros
Para escrever um texto em sequência, temos que
alterar apenas uma coisa: o modo de abertura, de
"w" para "a", assim sempre que escrevermos algo,
será inserido ao final, anexando, em vez de
substituir.

Vamos criar um looping do while para pedir


caracteres ao utilizador, e este só termina se
digitarmos enter (caractere '\n').

Guarda os dados que


possam existir em
memória do buffer.

Como vamos alterar várias vezes o


ficheiro, abrimo-lo antes do
looping e fechamos depois.
Ficheiros
fprintf() - Escrever texto (strings) em ficheiros

Embora interessante e importante, a escrita de caracteres em ficheiros, é um pouco


limitada.

Para isso, existe a função fprintf, que nos permite escrever strings inteiras em ficheiros.

A sua sintaxe simplificada é:

int fprintf(FILE *arq, char string[])

Ou seja, recebe o local onde deve direcionar a saída (para um ficheiro, apontado pelo
ponteiro arq do tipo FILE), e a string que devemos adicionar ao ficheiro. Esta função retorna
EOF em caso de erro.
Ficheiros
Escreva um programa em C que peça 3 notas de um aluno (Matemática, Física e
Química), e salve esses dados num ficheiro chamado "notas.txt", que deve ter, ao final, a
média das três disciplinas.
Ficheiros Vamos definir a nosso url como "notas.txt" e criar
duas variáveis do tipo float, a "nota" (que vai
armazenar a nota de cada disciplina) e a "media"
(que vai calcular a média das 3 disciplinas).

A cada vez que pedimos uma nota,


adicionamos uma linha de informação ao
nosso ficheiro, e no final escrevemos a
média.
Ficheiros
fgetc() - Ler caracteres em ficheiros

Quando estamos a ler informações do teclado, usávamos as funções getchar, scanf e gets.
Agora vamos conhecer as funções correspondentes, mas para ler em ficheiros.

A única diferença entre leitura e escrita, é a posição do ficheiro que vamos ler.
Quando estamos a escrever, se usarmos o modo de abertura "w", escrevemos no início do
ficheiro. Se usarmos o "a", escrevemos ao final do ficheiro.

Para a leitura, vamos usar funções que iniciam a leitura sempre no início do ficheiro.
Quando usamos a função fgetc, por exemplo, ele lê o primeiro caractere e automaticamente
já se posiciona no próximo. Ao ler o próximo, o C já se prepara para ler o próximo caractere
do ficheiro, e assim faz sempre isso até encontrar a constante EOF.
Ficheiros

A sintaxe da função fgetc é:


int fgetc(FILE *arq)

Esta função retorna um inteiro que representa o caractere, e EOF, que vale -1, caso aponte
para o fim do arquivo. E como os caracteres são representados por inteiros que vão de 0 até
255, um caractere no ficheiro nunca terá o valor -1, somente entre 0 e 255.
Ficheiros
Escreva um programa em C que leia de um ficheiro (poema.txt) e liste no ecrã, caractere
por caractere.

Notas: Crie o ficheiro poema.txt e copie um pequeno poema que encontre na internet
Ficheiros

Declaramos uma variável de nome "ch" do


tipo char, que vai receber caractere por
caractere do ficheiro.

Como queremos ler, vamos


usar o método de abertura
"r".

E isso deve ser feito enquanto o caractere


apontado no ficheiro não for EOF.
Ficheiros
Aproveitando o ficheiro criado anteriormente, escreva um programa em C que conte o
número de linhas do ficheiro.

Nota:

O que caracteriza uma linha?


O caractere new line (“\n”).
Ficheiros

Para contar quantas linhas tem um


ficheiro de texto, basta percorrermos
todos os caracteres do ficheiro em busca
dos "\n", que representa final de uma
linha.
Pré-processador ● Directiva #define
C/C++ ●

Macros e funções comparação
Directiva #include

e directivas
● Directivas #undef, #if, #ifdef,
#ifndef, #else, #endif e #error

Conteúdos
Pré-processador C/C++ e directivas

O que é que as macros (e o Pré-processador) vieram resolver.

Problema: Implemente a função Mult, que devolve o produto de dois valores numéricos.

int Mult(int a, int b){


return a*b;
}

Será que podemos multiplicar dois floats utilizando esta função?


A resposta é não. Porque esta função foi feita para multiplicar dois inteiros.

Como podemos resolver então este problema?


Pré-processador C/C++ e directivas

Vamos criar outra função.

float Mult(float 1, float b){


return a*b;
}

Mas um novo problema surge.


Não podemos ter no mesmo programa duas funções com o mesmo nome. Vamos então
alterar o nome nas funções.

float iMult(float 1, float b){ float fMult(float 1, float b){


return a*b; return a*b;
} }
Pré-processador C/C++ e directivas

E se quisermos multiplicar dois longs, ou um long e um int, ou um float e um double? E para


a soma, subtração, divisão, etc...?

Estamos diante de um problema complicado, e uma linguagem não pode emperrar neste
tipo de problemas.

Ora, quando escrevemos


x = Mult(12, 2.34);
O que queremos fazer é
x = 12 * 2.34;

Para resolvermos estes problemas utilizamos as macros.


Pré-processador C/C++ e directivas

Macros

As macros são porções de código que são substituídas, pelo Pré-processador, antes mesmo
do compilador passar pelo código.

Embora sem saberem, já devem ter utilizado macros ao longo da aprendizagem da


linguagem.

#define MAX 100

Esta linha substitui todas as ocorrências MAX pelo valor 100.


Pré-processador C/C++ e directivas

No entanto, o termo Macro não é normalmente associado à definição de constantes


simbólicas. Em geral o termo macro refere-se a chamadas a “funções” que são substituídas
por outro código pelo Pré-processador.

É assim possível escrever um código que seja substituído por outro, antes do programa ser
compilado, evitando os problemas apresentados anteriormente.
Pré-processador C/C++ e directivas

Suponhamos, então o seguinte programa:


Pré-processador C/C++ e directivas

Ora, na realidade apenas pretendemos fazer:

Podemos fazer exactamente isso escrevendo Mult como


macro e não como função. Como as macros não sabem
C, não conhecem tipos de dados, logo são definidas sem
levar em conta o tipo dos dados irão receber.
Pré-processador C/C++ e directivas

A macro Mult irá ter dois parâmetros, os quais chamaremos x e y.

Mult(x,y)

Ora, pretendemos que o Pré-processador substitua todas as ocorrências de Mult(x,y) por


x*y, adaptando-se x e y aos parâmetros enviados à macro.

#define Mult(x,y) x*y

Nota: na definição de uma macro, a abertura dos parênteses dos parâmetros terá que ficar
imediatamente após o nome da macro.
Pré-processador C/C++ e directivas

Reparem que esta operação só é possível realizar porque o Pré-processador é invocado


antes do compilador.

Vamos então testar a macro.

Resultado:

4*5=20
5*6=30
Pré-processador C/C++ e directivas

Como podemos reparar, o programa funciona perfeitamente, pois o seu código é expandido
em:
Pré-processador C/C++ e directivas

Vamos pegar apenas na primeira linha do programa anterior, mas com uma ligeira
alteração:

Reparem que os valores são os


mesmos, 4 (3+1) e 5 (2+3).
Ao executar o programa obtemos o
seguinte resultado
4*5=8

Estranho, não é?
4*5=8
Pré-processador C/C++ e directivas

Para percebermos o que se está a passar, vamos fazer a expansão do printf:

printf("%d*%d=%d\n", 3+1, 2+3, Mult(3+1,2+3));

#define Mult(x,y) x*y

printf("%d*%d=%d\n", 3+1, 2+3, 3+1*2+3);

Como a multiplicação tem precedência sobre a soma, o valor é 3+(1*2)+3 = 8

O que falta?
Faltam os parênteses.
Pré-processador C/C++ e directivas

E onde são eles colocados?


Na própria macro, para que todas as expressões sejam realizadas sem ambiguidades,
resultando assim a macro:

#define Mult(x,y) (x)*(y)

O código expandido será:

printf("%d*%d=%d\n", 3+1, 2+3,(3+1)*(2+3));


Pré-processador C/C++ e directivas

Só para confirmar, vamos experimentar uma expressão mais complexa:

1000/Mult(2+3,7+3)
MULT(2+3,7+3) = 5*10 = 50
1000/50 = 20

Vamos testar então:

Resultado:

2000
Pré-processador C/C++ e directivas

Estranho, o resultado da divisão é maior que 1000?

printf("%d\n", 1000/(2+3)*(7+3));

Como sabem a multiplicação e a divisão têm o mesmo nível de precedência, pelo que
são realizadas pela ordem em que se encontram:

1000/5 = 200
200*10 = 2000

Ora, note-se que ainda falta um outro nível de parênteses, que deverá ser colocado em
volta de toda a expressão em que a macro será expandida:

#define Mult(x,y) ((x)*(y))


Pré-processador C/C++ e directivas

Regras para a colocação de parênteses

1. em volta de cada um dos parâmetros existentes na expansão da macro


2. em volta de cada expressão
3. em volta de toda macro
Pré-processador C/C++ e directivas

Exercício:

Implemente a macro Min, que devolve o menor de dois valores.

A macro irá ter dois parâmetros e terá que devolver um dos valores. Como se pretende
devolver um resultado, vamos utilizar o operador ternário.

#define Min(a,b) a<b ? a:b

Se a<b, então o resultado de toda a macro é a, se não é b.

Vamos então colocar o primeiro nível de parênteses em volta de cada parâmetros.

#define Min(a,b) (a)<(b) ? (a):(b)


Pré-processador C/C++ e directivas

O segundo nível de parênteses é colocado em volta das expressões.

#define Min(a,b) ((a)<(b)) ? (a):(b)

O último nível é colocado em volta da macro completa.

#define Min(a,b) (((a)<(b)) ? (a):(b))

E eis a forma como se escrevem macros sem qualquer tipo de problema.


Pré-processador C/C++ e directivas

#include

O objetivo da diretiva #include é indicar ao pré-processador para substituir a linha que se


encontra pelo conteúdo do ficheiro que é colocado logo após.

/* Inclui aqui o ficheiro stdio.h para não ter que


* escrever todos aqueles cabeçalhos das funções printf, scanf, … , não
* ter que definir o tipo FILE, etc…
*/

#include <stdio.h>
Pré-processador C/C++ e directivas

Como devem saber, ao instalar o pacote que contém a linguagem C é automaticamente


criado um conjunto de diretórios

No exemplo, existe um diretório chamado Lang (languages) onde estão colocadas todas as
linguagens de programação existentes num determinado computador.
Neste exemplo, a linguagem C encontra-se no diretório Tc (Turbo C), que contém
subdiretórios no seu interior, dos quais se destacam: Bin, Include, Lib
Pré-processador C/C++ e directivas

Bin: onde existem todos os executáveis (compilador, pré-processador, utilitários, etc...)

Include: local onde existem os ficheiro com extensão .h (stdio.h, string.h, etc...)

Lib: local onde estão as bibliotecas com o código para criar o executável final da aplicação.
Neste diretório encontram-se os ficheiros .lib que contêm o código executável das funções
printf, scanf, atoi, etc…

Quando fazemos #include <stdio.h> estamos a dizer ao pré-processador para ir ao seu


diretório (Include), buscar o ficheiro stdio.h e colocá-lo nessa posição do ficheiro.

O ficheiro stdio.h é um ficheiro de texto que contém os cabeçalhos das funções que
utilizamos para a entrada/saída.
Pré-processador C/C++ e directivas

Compilação condicional
#if … #endif

O pré-processador pode retirar algum código da compilação, compilar partes de código na


situação x e compilar outras partes na situação y através do seguinte conjunto de diretivas:

#if condição_1 #if COMPUTADOR == 1


#include “pc.h”
instruções_1 #elif COMPUTADOR == 2
#elif condição_2 #include “mac.h”
#elif COMPUTADOR == 3
instruções_2 #include “unix.h”
#else
#else condição_n
#error Tipo de computador inválido ou não definido
instruções_n #endif
#endif
Pré-processador C/C++ e directivas

Compilação condicional
#ifdef #ifndef #undef

A diretiva #ifdef verifica se um determinado símbolo está definido.


A diretiva #ifndef verifica se um determinado símbolo não está definido.
A diretiva #undef permite retirar a definição de um símbolo.
Pré-processador C/C++ e directivas

Vamos analisar o seguinte código

No exemplo vamos definir um símbolo DEBUG. Não lhe vamos atribuir nenhum valor.
Se o símbolo estiver definido, é compilado.
Pré-processador C/C++ e directivas

Vamos alterar o programa anterior

No exemplo o DEBUG foi inicialmente definido,


em seguida foi eliminada a sua definição e se o
símbolo não estiver definido é mostrado a
respectiva mensagem “Debug: desativado”