Um computador atualmente é construído com componentes eletrônicos, e opera sobre grandezas elétricas. Ele deve, portanto, representar números (os objetos que ele manipula) usando essas grandezas. É relativamente complicado construir um circuito que consiga diferenciar, de forma confiável e barata, entre vários valores diferentes de alguma grandeza elétrica, e manter esses valores invariáveis com o passar do tempo. Diferenciar entre dois valores é muito mais simples, então se usa codificação binária para representar números no interior de um computador.
Em codificação binária, um dígito pode ter um de dois valores, normalmente representados como 0
e 1
.
A forma de codificação de números em binário usa a mesma lógica da codificação decimal que estamos acostumados, mas com base dois e não dez.
Em decimal, o dígito mais à direita de uma sequência de dígitos tem peso 1, o que está à esquerda dele tem peso dez vezes maior (o dígito das dezenas), o terceiro dígito tem peso dez vezes maior (o das centenas) e assim por diante.
O valor representado por uma sequência de dígitos corresponde à soma do valor de cada dígito (0 a 9) multiplicado pelo peso da posição onde esse dígito está.
Por exemplo, a sequência 32
representa o valor 3 vezes dez mais 2 vezes um, ou trinta e dois.
Em binário, o dígito mais à direita tem peso 1, o logo à esquerda dele tem peso duas vezes maior (2), o terceiro dígito tem peso duas vezes maior que o segundo (4) e assim por diante.
A sequência 101
representa o valor 1 vezes quatro mais 0 vezes dois mais 1 vezes um, ou cinco.
Em binário, como cada dígito só pode ter 2 valores possíveis, necessitamos de mais dígitos do que em decimal para representar o mesmo valor (aproximadamente 10 dígitos binários são necessários para a mesma capacidade de representação de 3 dígitos decimais). Um dígito em binário é chamado de bit, uma abreviação de binary digit**.
- Qual a representação decimal dos seguintes valores em binário:
1001
,010101
,101010
,1111111
? - Qual a representação binária dos seguintes valores em decimal:
34
,12
,125
?
Quando se vai representar um valor em um computador, deve-se escolher o número de dígitos que serão usados. Quanto mais dígitos se usa, maior a gama de valores que podem ser representados. Em compensação, mais espaço (e mais circuitos) é necessário, aumentando o custo dessa representação. Em uma máquina que tem uma determinada capacidade (uma determinada quantidade de memória, por exemplo), essa escolha vai dizer quantos valores podem ser representados pelo computador, e qual a abrangência possível para cada um deles. Essa escolha é difícil de ser realizada antes de se conhecer o uso que se vai fazer do cumputador, então os processadores são construídos de forma que seja possível trabalhar com diferentes formas de se representar um valor. Com o passar do tempo, se escolheu tamanhos padronizados para a representação de valores inteiros, em múltiplos de 8 bits. A base é 8 bits porque padronizou-se esse tamanho para o armazenamento de um número que representa um caractere -- com 8 bits consegue-se 256 valores diferentes, mais que suficiente para representar o conjunto de caracteres da maior parte das línguas ocidentais.
A maior parte dos processadores atuais permitem operações com valores de 8, 16, 32 ou 64 bits. Geralmente, um desses bits é usado para representar o sinal, então, com 8 bits por exemplo, tem-se 27 números negativos e 27 números não negativos, e pode-se representar valores desde -128 até +127 (tem um a menos nos positivos porque o 0 ocupa um dos valores). Os limites de valores representáveis com cada um dos tamanhos usuais são:
bits | valores representáveis |
---|---|
8 | -128 a 127 |
16 | -32768 a 32767 |
32 | -2147483648 a 2147483647 |
64 | -9223372036854775808 a 9223372036854775807 |
O último número é quase 10 quintilhões. Esses números parecem aleatórios, mas em binário são números bem "redondos".
Em C, essas diferentes representações são chamadas de tipos de dados.
Cada uma dessas representações é um tipo de dados diferente, e recebe um nome na linguagem: char
, short
, int
e long
.
O padrão da linguagem não especifica exatamente a quantidade de bits que deve ser usada para cada tipo de dados, mas a maior parte dos compiladores atuais em computadores tipo PC usa 8 bits para char
, 16 para short
, 32 para int
e 64 para long
.
Para representar valores não inteiros, usa-se uma forma semelhante à notação científica. São dois valores inteiros para representar um número. O primeiro, chamado mantissa, contém os dígitos do número, e o segundo, chamado expoente, contém a posição que separa os dígitos da parte inteira dos da parte fracionária (a posição da vírgula ou do ponto).
Em decimal, o número 3.14159
seria representado nessa notação como 314159 -5
; o número 12000
como 12 3
.
Essa notação é chamada de números em ponto flutuante (ou vírgula flutuante).
Dois tamanhos para representação de números em ponto flutuante em binário foram padronizados, e são usados pelos computadores atuais, um que usa 32 bits (24 para mantissa e 8 para o expoente), e um que usa 64 bits (52 para a mantissa e 12 para o expoente).
Em C, essas duas representações (esses dois tipos de dados) são chamadas de float
e double
.
As gamas aproximadas de valores representáveis por cada uma delas é:
tipo | bits | gama de valores aproximados em decimal |
---|---|---|
float | 32 | entre 6 e 7 dígitos; entre 10-38 e 1038 |
double | 64 | entre 15 e 16 dígitos; entre 10-308 e 10308 |
Para se imprimir valores de tipo int
em decimal, usa-se a conversão %d
no printf. Essa mesma conversão pode ser usada para os tipos char
e short
, mas não para valores do tipo long
, em que se usa %ld
.
Para valores dos tipos float
ou double
usa-se %f
.
Caso se queira controlar o número de casas impressas depois do ponto, pode-se usar o formato %.3f
(por exemplo, para três casas depois da vírgula).
A memória de um computador é usada para se colocar valores que poderão ser manipulados pelo processador. A memória de um computador tem capacidade para armazenar um grande número de valores. Um programa deve especificar, a cada instrução, qual desses valores deve ser manipulado. Um programa deve, portanto, gerenciar cada posição de memória, para não perder nenhum dos valores que está usando, e para não sub-utilizar esse recurso. Esse gerenciamento pode ser um tormento; as linguagens de programação oferecem algumas abstrações para facilitar esse trabalho.
A principal dessas abstrações é o que se chama de variável. Uma variável representa uma região de memória que armazena um valor que o programador julga necessário. O programador não necessita saber onde na memória esse valor está, só precisa garantir que essa região é exclusiva para esse valor, não vai ser usada para outra coisa e é facilmente encontrável quando necessário. Para isso, cada variável recebe um nome, dado pelo programador. Esse nome é usado para diferenciar uma variável da outra, liberando o programador de ter que lembrar a posição na memória onde foi colocado tal valor.
Como visto na seção anterior, tem-se várias opções de codificação para um valor, e essa é a segunda característica importante de uma variável, o tipo de dados que ela vai poder representar, ou o tipo da variável. Para cada valor que o programa precisar armazenar, deve ser criada uma variável para contê-lo. Essa é a forma de se representar a memória em um programa.
Para se poder usar uma variável em um programa em C, ela deve inicialmente ser declarada, em uma sentença chamada declaração de variável (!).
Na declaração de variável, deve-se informar o tipo e o nome da variável, seguido pelo ;
que termina qualquer sentença em C. A sentença abaixo declara (ou cria) uma variável chamada quantidade
, que vai conter um valor do tipo int
:
int quantidade;
O significado dessa sentença é algo como "encontre uma região de memória que não esteja sendo usada, que seja de tamanho suficiente para armazenar um dado do tipo int; marque essa região como usada; de agora em diante, toda vez que o nome quantidade aparecer no programa se está referindo a essa região".
O nome de uma variável segue as mesmas regras dos nomes de funções: o primeiro caractere é uma letra, os demais, se houver, podem ser letras, dígitos ou sublinha.
Uma variável possui um valor, que é o valor armazenado na região de memória reservada para essa variável. Esse valor está sob controle do programa. Na sentença de declaração de uma variável pode-se opcionalmente colocar um valor na variável que está sendo criada; se isso não for feito, o valor que a variável conterá será o que já estiver na região de memória que foi encontrada para ela. Colocar um valor em uma variável tem o nome de atribuição, e a atribuição de um valor na declaração da variável é chamado de inicialização; o valor de uma variável não inicializada é por vezes chamado de lixo. Abaixo está uma declaração de uma variável inteira inicializada com o valor 7:
int xis = 7;
Pode-se declarar mais de uma variável de um mesmo tipo em uma única sentença:
int xis, ips;
long a=3, soma_total;
Além da declaração e da inicialização, duas outra operações são realizáveis com uma variável: alterar o seu valor (atribuir um valor à variável) e obter seu valor.
A atribuição de um valor a uma variável é realizada em uma sentença que contenha uma atribuição, que é o nome da variável, o sinal =
e o valor que se que atribuir.
Por exemplo:
xis = 45;
Uma variável pode receber quantas atribuições o programador julgar necessário (o nome "variável" foi dado por causa disso, o valor dela pode variar). Em um dado instante, o valor de uma variável é o último valor que foi atribuído a ela.
Para obter o valor de uma variável, usa-se o nome da variável onde o seu valor for necessário. Por exemplo, a sentença abaixo imprime o valor da variável xis:
printf("xis vale %d\n", xis);
O valor de uma variável pode ser usado em uma expressão; abaixo o dobro do valor da variável xis é atribuído à variável ips (que deve ter sido previamente declarada):
ips = 2*xis;
No programa abaixo, é calculada a área de um círculo com um raio conhecido:
#include <stdio.h>
int main()
{
float raio = 12.4;
float area;
area = 3.14159 * raio * raio;
printf("A área de um círculo de raio %f é %f\n", raio, area);
return 0;
}
O programa acima contém valores constantes de ponto flutuante.
Essas constantes se distinguem das constantes inteiras por possuírem um ponto decimal (não pode ser vírgula).
Uma constante em ponto flutuante também pode ser escrita na forma mantissa e expoente (em decimal), separando-os pelo caractere e
ou E
. Por exemplo, o valor 3.14159
pode também ser escrito 314159e-4
ou 314.59e-2
ou 0.314159e1.
- Refaça os exercícios da aula anterior, mas desta vez usando variáveis para armazenar os valores (por exemplo, a largura, comprimento, profundidade e volume da piscina).
A entrada de dados em um programa se dá pela transferência de um valor de um dispositivo de entrada para uma variável.
Para se ler um valor do teclado, usa-se uma sentença com o comando scanf
.
Esse comando é semelhante ao comando printf, ele tem entre aspas os pedidos de conversão, e separado por vírgulas os dados.
No caso do printf, os dados contêm valores a serem impressos; no caso do scanf, eles representam locais onde os valores devem ser armazenados.
No printf, usa-se expressões para fornecer valores, no caso do scanf, só se pode usar variáveis, e elas devem ser precedidas pelo caractere &
.
Por exemplo, uma sentença para ler o valor da variável xis
, do tipo int
seria:
scanf("%d", &xis);
Quando essa sentença for executada, o programa irá parar sua execução até que o usuário digite uma linha (terminada por "enter"), então converterá os caracteres digitados segundo o pedido de conversão (%d
no caso), e armazenará o resultado na variável xis
-- essa é outra forma de se atribuir um valor a uma variável.
O scanf só realiza entrada de dados, se quiser imprimir uma mensagem na tela para informar o usuário do que se quer que ele faça, use o printf. Por exemplo,
printf("Digite o valor do raio: ");
scanf("%f", &raio);
Os códigos de conversão do scanf são semelhantes (infelizmente não iguais) aos do printf. Tem um código diferente para cada tipo de dados:
tipo | código |
---|---|
char | %hhd |
short | %hd |
int | %d |
long | %ld |
float | %f |
double | %lf |
Apesar de não ser obrigatório, o printf aceita esses mesmos códigos; para evitar confusão, recomenda-se usar esses códigos para ambos.
- Refaça os exercícios da aula anterior, mas desta vez pedindo para o usuário digitar os valores usados no cálculo (por exemplo, cada nota do aluno).
- Faça um programa que pede a altura e o peso de uma pessoa e imprime o seu índice de massa corporal.
- Faça um programa que pede o consumo de um carro (em km/l), a distância do trajeto (em km) e o valor do combustível (em R$/litro), e informa o custo do combustível necessário para realizar esse trajeto (com 2 casas depois da vírgula, para os centavos).
- Faça um programa que lê dois números inteiros e imprime a soma dos quadrados desses números. O programa deve possuir uma variável onde colocar a soma. O programa deve imprimir o resultado em uma linha como "A soma dos quadrados de 2 e 3 vale 13". Coloque esse programa em um arquivo chamado "e2-fulano.c", anexe-o a um mail com assunto "l1-E2-fulano", envie o mail para "[email protected]", antes do início da aula do dia 27mai. Esse é o E2. Substitua "fulano" por seu login no domínio inf ou seu número de matrícula.