Embora haja uma discreta relação entre funções e expressões booleanas e as linguagens de programação como C ou Pascal, você deve estar admirado com o tempo dedicado a este tema. Entretanto, a relação entre lógica booleana e sistemas de computador é muito grande. Há uma relação um-para-um entre funções booleanas e circuitos eletrônicos. Engenheiros eletrônicos que desenvolvem CPUs e outros circuitos de computador precisam estar muito familiarizados com tudo isto. Mesmo que você nunca pretenda desenvolver seus próprios circuitos eletrônicos, entender esta relação é importante se você quiser tirar maior proveito de qualquer sistema de computador.
Há uma correspondência de um-para-um entre circuitos elétricos e funções booleanas. Para toda e qualquer função booleana é possível desenvolver um circuito eletrônico e vice-versa. Como as funções booleanas precisam apenas dos operadores booleanos AND, OR e NOT, podemos construir qualquer circuito eletrônico usando exclusivamente essas operações. As funções booleanas AND, OR e NOT correpondem aos seguintes circuitos eletrônicos: às portas lógicas AND e OR e ao inversor (NOT). Veja a Fig.1

![]() Fig.2 - A porta NAND |
![]() Fig.3 - Inversor construído de uma porta NAND |
Um fato interessante é que você precisa apenas de um único tipo de porta lógica para implementar qualquer circuito. Esta porta é a NAND, mostrada na Fig.2 à esquerda.
Para provar que podemos construir qualquer função booleana usando apenas portas NAND, precisamos mostrar como construir um inversor (NOT), a porta AND e a porta OR de um NAND (já que podemos criar qualquer função booleana usando apenas AND, NOT e OR). Construir um inversor é fácil, apenas conecte as duas entradas (veja a Fig.3 à direita).
![]() Fig.4 - Porta AND construída com duas portas NAND |
Uma vez criado o inversor, construir uma porta AND é fácil - apenas inverta a saída de uma porta NAND. Afinal de contas, NOT (NOT (A AND B)) é equivalente a A AND B.
É claro que isto consome duas portas NAND para se construir uma única porta AND, mas ninguém disse que circuitos construídos apenas com portas NAND seriam ótimos, apenas que isto seria possível de ser feito.
A última porta que precisamos sintetizar é a porta OR. Podemos construir facilmente uma porta OR a partir de portas NAND aplicando os teoremas de DeMorgan.
(A or B)' = A' and B' Teorema de DeMorgan. A or B = (A' and B')' Inverta os lados da equação. A or B = A' nand B' Definição da operação NAND.
![]() Fig.5 - Porta OR construída com duas portas NAND e um inversor |
Aplicando essas transformações, obtém-se o circuito da Fig.5. Agora você poderia estar se perguntando porque estamos nos preocupando com isto. Afinal de contas, porque simplesmente não usar as portas AND, OR e inversora? Existem duas razões para isso. Primeiro, portas NAND são geralmente menos caras de se construir do que outras portas. Segundo, é também muito mais fácil construir circuitos integrados complexos usando blocos básicos iguais do que construir um circuito integrado usando as diferentes portas básicas.
A propósito, note que é possível construir qualquer circuito lógico usando apenas portas NOR (NOT (A or B)). A correspondência entre a lógica NAND e NOR é ortogonal à correspondência entre as duas formas canônicas mostradas neste capítulo (soma de mintermos vs. produto de maxtermos). Apesar da lógica NOR ser útil para muitos circuitos, a maioria dos projetos eletrônicos usam a lógica NAND. Veja os exercícios para mais exemplos.
Um circuito combinatório é um sistema contendo operações booleanas básicas (AND, OR e NOT), algumas entradas e um conjunto de saídas. Uma vez que para cada saída corresponde uma função lógica individual, um circuito combinatório geralmente implementa diversas funções booleanas diferentes. É muito importante que você se lembre desse fato - cada saída representa uma função booleana diferente.
A CPU de um computador é composta de vários circuitos combinatórios. Por exemplo, podemos implementar um circuito de soma usando funções booleanas. Suponha que você tenha dois números de um bit, A e B. Você pode produzir a soma de um bit, e o "vai um" (carry) de um bit desta adição, usando as duas funções booleanas a seguir:
S = AB' + A'B Soma de A e B. C = AB "Vai um" da adição de A e B.
Estas duas funções booleanas implementam um meio-somador. Engenheiros elétricos o chamam de meio-somador porque ele soma dois bits, mas não adiciona o "vai um" de uma operação anterior. Um somador completo adiciona três entradas de um bit (dois bits mais um "vai um" da soma anterior) e produz duas saídas: a soma e o novo "vai um". As duas equações lógicas para um somador completo são:
S = A'B'Cin + A'BC'in + AB'C'in + ABCin Cout = AB + ACin + BCin
![]() Fig.6 - Somador de N-Bits usando um meio-somador e somadores completos |
Embora essas equações lógicas produzam apenas um único bit como resultado (ignorando o "vai um"), é fácil construir uma soma de n bits combinando circuitos somadores (veja a Fig.6). Assim, como este exemplo ilustra claramente, podemos usar funções lógicas para implementar operações booleanas e aritméticas.
![]() Fig.7 Display de 7 segmentos |
Um outro circuito combinatório muito comum é o decodificador de sete segmentos. Este é um circuito combinatório que aceita quatro entradas e determina quais dos sete segmentos de um display luminoso de sete segmentos deverá ser ativado (1 lógico) ou desligado (zero lógico). Como um display de sete segmentos contém sete valores de saída (um para cada segmento), existirão sete funções lógicas associadas ao display (do segmento zero até o segmento seis). Veja a identificação dos sete segmentos na Fig.7. A Fig.8 mostra a quais segmentos correspondem a cada um dos dez valores decimais.
![]() Fig.8 - Valores de "0" a "9" para sete segmentos |
As quatro entradas para cada uma das sete funções booleanas são os quatro bits de um número binário no intervalo de 0 a 9. Considere a variável D como o bit mais significativo deste número e a variável A como o bit menos significativo deste número. Cada função lógica produz um segmento ativado (resulta em 1) para uma dada entrada se aquele segmento em particular deve ser iluminado. Por exemplo, o S (segmento quatro) deve estar ativado para os valores binários 0000, 0010, 0110 e 1000 (que correspondem aos decimais 0, 2, 6 e 8). Para cada valor que acende este segmento, temos um mintermo na equação lógica:
S4 = D'C'B'A' + D'C'BA' + D'CBA' + DC'B'A'
S0, como um segundo exemplo, está ativo para os valores zero, dois, três, cinco, seis, sete, oito e nove. Então, a função lógica para S0 é:
S0 = D'C'B'A' + D'C'BA' + D'C'BA + D'CB'A + D'CBA' + D'CBA + DC'B'A' + DC'B'A
Você pode gerar as outras cinco funções lógicas de forma semelhante.
Circuitos combinatórios são a base para muitos componentes de um sistema de computação básico. Podemos construir circuitos para soma, substração, comparação, multiplicação, divisão e muitas outras operações usando a lógica combinatória.
Um dos grandes problemas da lógica combinatória é que ela é destituída de memória. Na teoria, todas as saídas de funções lógicas dependem somente das entradas atuais. Qualquer modificação nos valores de entrada é imediatamente refletida nas saídas. Infelizmente os computadores precisam ter a capacidade de lembrar os resultados de cálculos anteriores. Este é o domínio da lógica sequencial e de clock.
![]() Fig.9 - Flip-flop set/reset |
Uma célula de memória é um circuito eletrônico que lembra um valor de entrada depois que tenha sido removido. A unidade de memória mais básica é o flip-flop set/reset (liga/desliga). Você pode construir um flip-flop SR usando duas portas NAND, como mostrado ao lado.
As entradas S e R normalmente são altas. Se você levar temporariamente a entrada S para zero e depois reverte-la novamente para 1 (inverter a entrada S), isto força a saída Q para 1. Do mesmo modo, se você inverter a entrada R de 1 para zero e novamente para 1, isto seta a saída Q para zero. A saída Q' geralmente é o inverso da saída Q.
Note que, se tanto S quanto R forem 1, então a saída Q depdende de Q. Isto é, seja qual for o valor de Q, a porta NAND do topo continua a dar saída deste valor. Se Q originalmente era 1, então ocorrem duas entradas de 1 no flip-flop inferior (Q e R). Isto produz uma saída de sero (Q'). Portanto, as duas entradas na porta NAND do topo são zero e 1. Isto produz o valor 1 como saída (coincidindo com o valor original de Q).
Se o valor original de Q era zero, então as entradas na porta NAND inferior são Q=0 e R=1. Portanto, a saída desta porta NAND é 1. Neste caso, as entradas na porta NAND do topo são S=1 e Q'= 1. Isto produz uma saída zero, o valor original de Q.
Suponha que Q seja zero, S seja zero e R seja 1. Isto ajusta as duas entradas para o flip-flop do topo para 1 e zero, forçando a saída (Q) de 1. Revertendo S para o estado alto (S=1) não altera em nada a saída. Pode-se obter este mesmo resultado se Q for 1, S for zero e R for 1. Novamente, isto produz um valor de saída 1. Este valor permanece 1, mesmo se S for trocado de zero para 1. Portanto, trocando a entrada S de 1 para zero de novamente para 1 produz 1 na saída (isto é, seta o flip-flop). O mesmo raciocínio se aplica para a entrada R, exceto que ela força a saída Q para zero, ao invés de 1.
Existe uma mancada neste circuito. Ele não funciona apropriadamente se as duas entradas S e R forem zeradas simultaneamente. Isto força ambas as saídas Q e Q' para 1 (o que, logicamente, é inconsistente). A entrada que permanecer zero por mais tempo determina o estado final do flip-flop. Este modo de funcionamento de um flip-flop é chamado de instável.
![]() Fig.10 - Flip-flop para dados |
O único problema com o flip-flop S/R é que se precisa usar entradas separadas para guardar um valor zero ou 1. Uma célula de memória teria mais valor se pudéssemos especificar o valor do dado que deve ser mantido para uma das entradas e pudéssemos fornecer uma entrada de clock para trancar o valor de entrada. Este tipo de flip-flop, o flip-flop D (para dados) usa o circuito visto ao lado.
Considerando que as saídas Q e Q' estejam ajustadas para 0/1 ou 1/0, quando se envia um pulso de clock que vai de 0 para 1 e novamente para zero, ocorre uma cópia da entrada D para a saída Q. Também ocorrerá uma replicação de D' para Q'. Os exercícios no final deste capítulo descreverão esta operação em detalhe portanto, estude este diagrama cuidadosamente.
Apesar de saber que um único bit, com frequência, é importante, na maioria dos sistemas de computador teremos que guardar um grupo de bits. Pode-se guardar uma sequência de bits combinando vários flip-flops D em paralelo. Concatenando flip-flops para guardar um valor de n bits forma um registrador. O esquema eletrônico na figura abaixo mostra como construir um registrador de 8 bits com um conjunto de flip-flops D:

Observe que os oito flip-flops D usam uma linha de clock comum. O diagrama não mostra as saídas Q' nos flip-flops porque, num registrador, raramente são necessários.
![]() Fig.12 - Registrador de deslocamento (shift) |
![]() Fig.13 - Contador |
Flip-flops D são úteis para se construir muitos circuitos sequenciais além e aquém de simples registradores. Por exemplo, são usados para construir registradores de deslocamento (shift) que deslocam os bits uma posição para a esquerda a cada pulso do clock. À esquerda você pode ver um registrador de deslocamento.
Usando flip-flops pode-se até mesmo contruir um contador que conta o número de vezes que o clock alterna de 1 para zero e novamente para 1. O circuito na figura à direita implementa um contador de 4 bits usando flip-flops D.
Apesar de surpreendente, é possível construir uma CPU inteira com circuitos combinatórios e com apenas alguns circuitos sequenciais adicionais.
Uma vez que se tem registradores, contadores e registradores de deslocamento, pode-se construir máquinas de estado. A implementação de um algoritmo no hardware utilizando máquinas de estado está bem além do escopo deste texto. Contudo, um ponto importante, relativo a este sistema eletrônico, precisa ser destacado - qualquer algoritmo que possa ser implementado em software também pode ser implementado diretamente em hardware. Isto sugere que a lógica booleana é a base para a computação em todos os sistemas modernos de computadores. Você pode classificar qualquer programa como uma sequência de equações booleanas.
É claro que é muito mais fácil elaborar uma solução para um problema de programação utilizando linguagens como C, Pascal ou até mesmo a linguagem Assembly, do que elaborar uma solução utilizando equações booleanas. É por isso que não é razoável implementar um programa inteiro utilizando um conjunto de máquinas de estado e outros sistemas de circuitos eletrônicos. Todavia, há ocasiões em que a implementação em hardware é melhor. Uma solução de hardware pode ser uma, duas, três ou mais ordens de grandeza mais rápida do que uma solução em software equivalente. É por isso que, algumas vezes, operações de tempo crítico requerem uma solução em hardware.
Um fato ainda mais interessante é que o oposto da afirmação acima também é verdade. Podemos não só implementar todas as funções de software em hardware, como também podemos implentar todas as funções de hardware em software. Esta é uma revelação importante porque muitas operações que normalmente seriam implementadas em hardware são muito mais baratas de implementar utilizando software num microprocessador. Na realidade, este é o uso primário da linguagem Assembly nos sistemas modernos - para substituir, sem custo, um circuito eletrônico complexo. Frequentemente é possível economizar muitas dezenas ou centenas de dólares de componentes eletrônicos substituindo-os por um simples chip microcomputador de $25. Todos os sistemas embutidos lidam exatamente com este assunto. Sistemas embutidos são sistemas de computadores embutidos em outros produtos. Por exemplo, muitos fornos microondas, aparelhos de TV, video games, CD players e outros dispositivos de consumo contém um ou mais sistemas de computadores completos cujo único propósito é o de substituir um projeto de hardware complexo. Engenheiros utilizam computadores para este propósito porque eles são mais baratos e mais fáceis de desenvolver do que sistemas eletrônicos tradicionais.
Você pode facilmente desenvolver software que lê interruptores (variáveis de entrada) e liga motores, LEDs ou luzes, tranca ou destranca portas, etc. (funções de saída). Para escrever tal software, você precisa entender as funções booleanas e como implementar tais funções em software.
É claro que há outras razões para estudar funções booleanas, mesmo se você nunca pretender escrever software para sistemas embutidos ou escrever software que manipule dispositivos do mundo real. Muitas linguagens de alto nível processam expressões da álgebra booleana (por exemplo, aquelas expressões que controlam uma declaração if ou um laço while). Geralmente é possível melhorar a performance de um código numa linguagem de alto nível aplicando transformações como os teoremas de DeMorgan ou fazendo uma otimização por mapeamento. Portanto, estudar funções booleanas é importante, mesmo que você nunca pretenda desenvolver um circuito eletrônico. Elas podem te ajudar a escrever um código melhor em linguagens de programação tradicionais.
Por exemplo, suponha que você tenha a seguinte expressão em Pascal:
if ((x=y) and (a <> b)) or ((x=y) and (c <= d)) then AlgumaInstr;
Você pode utilizar a lei distributiva para simplificar a expressão:
if ((x=y) and ((a <> b) or (c <= d)) then AlgumaInstr;
Da mesma forma, podemos utilizar o teorema de DeMorgan para reduzir:
while (not((a=b) and (c=d)) do Algo;
para
while (a <> b) or (c <> d) do Algo;
Para uma aplicação específica, pode-se criar uma função lógica que alcance um resultado específico. Suponha, entretanto, que você queira escrever um programa para simular qualquer função booleana possível. Por exemplo, imagine um programa que permita a entrada de uma função booleana arbitrária com quatro variáveis diferentes. Este programa lerá as entradas e produzirá necessariamente os resultados das funções. Uma vez que o número de funções de quatro variáveis únicas é grande (65536, para ser exato), não é prático incluir uma solução específica para cada uma no programa. O que se precisa é de uma função lógica genérica, uma que calcule os resultados para qualquer função arbitrária. Esta seção descreve como escrever tal função.
Uma função booleana genérica de quatro variáveis requer cinco parâmetros - os quatro parâmetros de entrada e um quinto parâmetro que especifica a função que deve ser calculada. Apesar de haver muitas formas de se especificar uma função, optamos por passar o número da função booleana como quinto parâmetro.
À primeira vista você poderia perguntar como podemos calcular uma função utilizando o seu número. Contudo, lembre-se de que os bits que formam o número da função vêm diretamente da tabela verdade da própria função. Então, se extrairmos os bits do número da função, poderemos construir a tabela verdade para a mesma. Além disso, se selecionarmos somente o iésimo bit do número da função, onde i = D*8 + C*4 + B*2 + A, obteremos o resultado da função para os valores especificados para A, B, C e D. O exemplo a seguir, em C e Pascal, mostra como escrever tais funções:
/************************************************************************/
/* */
/* Este programa C demonstra como escrever uma função booleana genérica */
/* que pode calcular qualquer função lógica de quatro variáveis. Com os */
/* operadores de manipulação de bits e com a E/S hexadecimal, esta é */
/* uma tarefa fácil de ser efetuada com a linguagem C. */
/* */
/************************************************************************/
#include
#include
/* função lógica genérica. O parâmetro "Func" contém o número 16 */
/* da função lógica. Na verdade, isto é a função lógica de uma tabela */
/* verdade codificada. Os parâmetros a, b, c e d são as entradas para */
/* a função lógica. Se tratarmos "func" como um array 2x2x2x2 de bits, */
/* esta função particular seleciona o bit "func[d,c,b,a]" da função. */
int
generic(int func, int a, int b, int c, int d)
{
/* Retorna o bit especificado por a, b, c e d */
return (func >> (a + b*2 + c*4 + d*8)) & 1;
}
/* Programa principal para comandar a função lógica genérica escrita em C. */
main()
{
int func, a, b, c, d;
/* Repete o seguinte até o usuário entrar zero. */
do
{
/* Obtém o número da função (tabela verdade) */
printf("Entre o valor da função (hex): ");
scanf("%x", &func);
/* Se o usuário especificar zero como nro da função */
if (func != 0)
{
printf("Entre os valores para d, c, b & a: ");
scanf("%d%d%d%d",
&d, &c, &b, &a);
printf("O resultado é %d\n", generic(func,a,b,c,d));
printf("Func = %x, A=%d, B=%d, C=%d, D=%d\n",
func, a, b, c, d);
}
} while (func !=0);
}
O programa a seguir, em Pascal, é escrito para Standard Pascal. Standard Pascal não fornece quaisquer operações para manipulação de bits, então este programa é grande porque ele precisa manipular os bits utilizando um array de inteiros. Muitos Pascals modernos (especialmente o Turbo Pascal) fornecem operações de bits embutidas ou rotinas em bibliotecas que operam bits. Este programa seria muito mais fácil de escrever utilizando tais características não padronizadas.
program GenericFunc(input,output);
(* Já que o Pascal Padrão não fornece uma forma fácil para manipular *)
(* diretamente bits em um inteiro, simularemos o número da função *)
(* utilizando um array de 16 inteiros. "GFTYPE" é o tipo desse array. *)
type
gftype = array [0..15] of integer;
var
a, b, c, d:integer;
fresult:integer;
func: gftype;
(* Pascal Padrão não tem a capacidade de deslocar um dado inteiro para *)
(* a esquerda ou a direita. Então, simularemos um valor de 16 bits *)
(* utilizando um array de 16 inteiros. Podemos simular o deslocamento *)
(* movendo o dado através do array. *)
(* *)
(* Note que o Turbo Pascal *fornece* os operadores shl e shr. *)
(* Contudo, este código é escrito para funcionar com o Pascal Padrão, *)
(* e não apenas em Turbo Pascal. *)
(* *)
(* ShiftLeft desloca os valores em func para a esquerda e insere o *)
(* valor deslocado no bit de posição zero *)
procedure ShiftLeft(shiftin:integer);
var i:integer;
begin
for i := 15 downto 1 do func[i] := func[i-1];
func[0] := shiftin;
end;
(* ShiftNibble desloca o dado em func para a esquerda quatro posições e *)
(* insere os quatro bits a , b, c & d nas posições vagas *)
procedure ShiftNibble(d,c,b,a:integer);
begin
ShiftLeft(d);
ShiftLeft(c);
ShiftLeft(b);
ShiftLeft(a);
end;
(* ShiftRight desloca o dado em func uma posição à direita. Desloca *)
(* um zero para dentro do bit de mais alta ordem do array. *)
procedure ShiftRight;
var i:integer;
begin
for i := 0 to 14 do func[i] := func[i+1];
func[15] := 0;
end;
(* ToUpper converte um caracter minúsculo para maiúsculo. *)
procedure toupper(var ch:char);
begin
if (ch in ['a'..'z']) then ch := chr(ord(ch) - 32);
end;
(* ReadFunc lê um número de função hexadecimal fornecido pelo usuário *)
(* e põe este valor no array func (bit a bit) *)
function ReadFunc:integer;
var ch:char;
i, val:integer;
begin
write('Entre o número da função (hexadecimal): ');
for i := 0 to 15 do func[i] := 0;
repeat
read(ch);
if not eoln then begin
toupper(ch);
case ch of
'0': ShiftNibble(0,0,0,0);
'1': ShiftNibble(0,0,0,1);
'2': ShiftNibble(0,0,1,0);
'3': ShiftNibble(0,0,1,1);
'4': ShiftNibble(0,1,0,0);
'5': ShiftNibble(0,1,0,1);
'6': ShiftNibble(0,1,1,0);
'7': ShiftNibble(0,1,1,1);
'8': ShiftNibble(1,0,0,0);
'9': ShiftNibble(1,0,0,1);
'A': ShiftNibble(1,0,1,0);
'B': ShiftNibble(1,0,1,1);
'C': ShiftNibble(1,1,0,0);
'D': ShiftNibble(1,1,0,1);
'E': ShiftNibble(1,1,1,0);
'F': ShiftNibble(1,1,1,1);
else write(chr(7),chr(8));
end;
end;
until eoln;
val := 0;
for i := 0 to 15 do val := val + func[i];
ReadFunc := val;
end;
(* Generic - Calcula a função lógica genérica especificada pelo *)
(* número da função "func" nas quatro variáveis *)
(* a, b, c & d. Faz isto retornando o bit *)
(* d*8 + c*4 + b*2 + a da função. *)
function Generic(var func:gftype; a,b,c,d:integer):integer;
begin
Generic := func[a + b*2 + c*4 + d*8];
end;
begin (* main *)
repeat
fresult := ReadFunc;
if (fresult <> 0) then begin
write('Entre os valores para D, C, B, & A (0/1):');
readln(d, c, b, a);
writeln('O resultado é ',Generic(func,a,b,c,d));
end;
until fresult = 0;
end.
O código a seguir demonstra o poder das operações de manipulação de bits. Esta nova versão do código acima utiliza características especiais presentes na linguagem de programação do Turbo Pascal que permitem aos programadores deslocarem para a direita ou para a esquerda e fazerem um AND lógico bit a bit em variáveis inteiras:
program GenericFunc(input,output);
const
hex = ['a'..'f', 'A'..'F'];
decimal = ['0'..'9'];
var
a, b, c, d:integer;
fresult:integer;
func: integer;
(* Aqui está uma segunda versão da função genérica do Pascal que *)
(* utiliza as características do Turbo Pascal para simplificar o *)
(* programa. *)
function ReadFunc:integer;
var ch:char;
i, val:integer;
begin
write('Entre o número da função (hexadecimal): ');
repeat
read(ch);
func := 0;
if not eoln then begin
if (ch in Hex) then
func := (func shl 4) + (ord(ch) and 15) + 9
else if (ch in Decimal) then
func := (func shl 4) + (ord(ch) and 15)
else write(chr(7));
end;
until eoln;
ReadFunc := func;
end;
(* Generic - Calcula a função lógica genérica especificada pelo *)
(* número da função "func" nas quatro variáveis *)
(* a, b, c e d. Faz isto retornando o bit *)
(* d*8 + c*4 + b*2 + a de func. Esta versão deixa o *)
(* trabalho para o operador de deslocamento do Turbo *)
(* Pascal e sua habilidade para fazer operações em *)
(* bits de inteiros. *)
function Generic(func,a,b,c,d:integer):integer;
begin
Generic := (func shr (a + b*2 + c*4 + d*8)) and 1;
end;
begin (* main *)
repeat
fresult := ReadFunc;
if (fresult <> 0) then begin
write('Entre os valores para D, C, B, & A (0/1):');
readln(d, c, b, a);
writeln('O resultado é ',Generic(func,a,b,c,d));
end;
until fresult = 0;
end.
Dê asas à imaginação. Construa circuitos e mais circuitos, colocando os NAND em todas as posições possíveis. Eu sempre tenho a impressão de que estou brincando com pecinhas de Lego :)))
Bem, finalmente o capítulo 2 está encerrado. É hora de laboratório. Pegue seu caderno (o que? você ainda não tem um caderno????) e arregace as mangas. Você vai ver que, com os exercícios do laboratório, o assunto vai ficar muito mais claro.