Username: Password:

.Variáveis e Valores

Para ilustrar o papel dos valores e das variáveis, consideramos uma nova versão do programa de calculo do índice de massa corporal introduzido na secção sobre [the label «progimcv1» is unknown in document hp NIL.]:

1 #include <stdio.h>
2
3 int main (void) {
4   double peso = 75.0;
5   double altura = 1.78;
6   printf("Indice de Massa Corporal: %f\n", peso / (altura * altura));
7   return 0;
8 }
9
A diferença com a primeira versão é o uso de duas variáveis «peso» e «altura». Uma variável numa linguagem de programação tem um papel semelhante à noção de variável em matemática. Serve para associar um nome a um valor. No nosso programa o valor 1.78 é associado a uma variável com nome «altura» enquanto o valor 75.0 é associado à variável de nome «peso». Como o nome indica, o valor associado à uma variável pode variar, o seja, ser alterado durante a execução do programa.

Para associar um valor a uma variável usa-se, na linguagem C (e em muitas outras linguagens), o símbolo igual («=»). Por exemplo para associar o valor 3 à variável x basta escrever: x = 3;. Esta operação é chamada uma atribuição.

Note que numa atribuição, o valor aparece a direita do símbolo igual enquanto o nome da variável aparece do lado esquerdo ex: x = 5.

Se um programa tiver duas atribuições para a mesma variável, o segundo valor vem substituir o primeiro (uma variável não pode conter simultaneamente vários valores) :

1 #include <stdio.h>
2
3 int main (void) {
4    int x = 3;
5    printf("x = %d\n", x);
6    x = 5;
7    printf("x = %d\n", x);
8    return 0;
9 }

Quando executado, este programa imprimirá duas linhas :

3
5
As linhas do programa são executadas sequencialmente. A variável x é inicializada com o valor três (linha 4), o valor é impresso no ecrã (linha 5), o valor de x é alterado (linha 6) finalmente o novo valor é impresso no ecrã.
A seguir a uma atribuição o valor anterior da variável é destruido.

Os valores que usamos até agora são todos valores numéricos decimais. Existem outros tipos de valores, por exemplo os valores númericos inteiros (56, 13442, etc...) ou os caracteres. Conforme as linguagens de programação, o leque de tipos de valores disponíveis varia. Geralmente existe pelo menos dois tipos numéricos para representar valores decimais e valores inteiros e um tipo caracter para escrever mensagens no ecrã. Os tipos assim pre-definidos são as vezes chamados tipos de base ou tipos básicos.

Na linguagem C, as variáveis podem armazenar valores de um só tipo durante a execução do programa. É necessário decidir, para cada variável qual o tipo de valores poderá conter. O tipo não poderá ser alterado durante a execução do programa. A associação de um tipo a uma variável é feito atravez de uma «declaração». Uma declaração, na sua forma mas simples é um tipo seguido de um nome de variável:

float peso;
define uma variável peso de tipo float (número decimal). Para definir uma variável x de tipo inteiro podemos escrever:
int x;
É possível declarar várias variáveis numa mesma linha:
int x, y, z;
É boa prática inicializar as variáveis logo na declaração de forma a assegurar que têm um valor inicial correcto. Para declarar e inicilizar ao mesmo tempo basta fazer como nas linhas 4 e 5 do programa anterior:
double peso = 75.0;
double altura = 1.78;

Neste exemplo, o nome de cada variável foi escolhido de maneira a corresponder ao seu valor. A variável «peso» contém o valor do peso e a variável «altura» o valor da altura. Embora isto pode parecer óbvio e natural, quando uma pessoa tem de nomear dezenas de variáveis, rapidamente fica sem imaginação e começa a usar nomes sem sentido (por exemplo: a, b, c, xpto, aux1, aux2 etc... É essencial resistir a esta tentação !

A escolha dos nomes das variáveis representa um factor importante para legibilidade do programa.

Para a imensa mioria das linguagens de programação, os nomes de variáveis são sequências de caracteres (palavras) que devem començar por uma letra, seguida de uma sequência de letras ou dígitos. As linguagens de programação são em geral sensíveis à diferença entre maiúsculas e minusculas (case-sensitive). Isto significa que as palavras peso e Peso designam duas variáveis diferentes. Se por exemplo escrever em C:

int main (void) {
  float peso = 75.0;
  float altura = 1.78;
  printf("Indice de Massa Corporal: %f\n"
         Peso / (Altura * Altura));
  return 0;
}

O compilador assinalará um erro porque na linha do printf desconhece as variáveis «Peso» e «Altura» dado que na declaração foram chamadas «peso» e «altura». As linguagens de programação que diferenciam as letras maiúsculas e minusculas são ditas «case-sensitive».

Resumo:

  • A variáveis servem para atribuir um nome a um valor.
  • O nome das variáveis devem ser escolhidos cuidadosamente de forma a representar o valor da variável ou o seu papel no algoritmo.
  • Os nomes são compostos por uma letra seguida de letras ou dígitos.
  • As variáveis devem ser declaradas antes de ser usadas.
  • Em C a declaração consiste em associar um tipo a uma variável e, opcionalmente um valor inicial.

Valores inteiros

Os valores inteiros são números inteiros representados por sequências de dígitos. Por exemplo 1 23 29374 0. Podem ser valores negativos: -1 -45 -12500. Esses valores podem ser armazenados em variáveis de tipo inteiro (int em C).

Isto parece muito natural mas será que 127839481987439187 representa um valor inteiro em C ? Será que podemos representar qualquer valor inteiro em C ?
Nada melhor do que fazer uma pequena experiência. O programa seguinte deve escrever no ecrã o valor de 1000000000 vezes 4 ou seja 4000000000 :

1 #include <stdio.h>
2
3 int main (void) {
4   int k;
5   k = 1000000000; 
6   printf("1000000000 x 4 = %d\n", k * 4);
7   return 0;
8 }
9
mas quando executamos o programa aparece:
> ./testint1
1000000000 x 4 = -294967296

Apesar de ter sido compilado e executado sem receber mensagens de erro, o programa dá um resultado errado. Este exemplo constituí uma lição importante para quem deseja programar em C. Um programa aparentemente correcto pode dar um resultado errado ! Este exemplo mostra que, mais do que no caso de outras linguagem de programação, é perigoso programar em C sem perceber como funciona a linguagem e como os dados estão representados em memória.

Sem entrar demasiado nos pormenores, neste caso o problema é que o resultado ultrapassa o valor máximo que a representação usada para os inteiros permite. De facto, existe na linguagem C valores limites para os valores inteiros. Para complica ainda a situação, esses limites dependem do computador usado. O resultado (errado) obtido na máquina usada podia estar correcto noutra e valores mais pequenos podem estar na origem de erros em máquinas com menos recursos. O problema não é tanto existir um limite na representação dos valores inteiros (este limite é conhecido pelo programador que pode tomar precauções) mas antes é o facto de não haver nenhum aviso quando um programa tenta calcular um valor demasiado grande.

Este peculiar comportamento encontra-se em outras linguagens de programação. Por exemplo, o programa equivalente em Java dá o mesmo erro:


> javac testint1.java
> java testint1
1000000000 x 4 =-294967296

O famoso site twitter.com identificava as mensagens com um valor inteiro. Foi a origem de um bug. No dia 12 de junho de 2009 ás 23:52 o valor máximo (2,147,483,647) foi atingido causando uma quebra de serviço e problemas em cadeia referidos como o Twitpocalypse.

O programador deve portanto estar atento aos problemas ligados à aritmética inteira. No entanto existe um tipo de inteiro (chamado long) que permite armazenar valores no intervalo [-9223372036854775808, +9223372036854775807] o que deve ser suficiente para a grande maioria das aplicações.

Valores reais

Existem várias maneiras de representar valores decimais, por exemplo 0,35 pode ser resresentado por 0.35, 3.5e-1, 35e-2, 0.035e1. Os números escritos desta forma serão representados em memória por valores com precisão dupla (double). Para especificar valores que têm uma precisão simples (float) basta acrescentar a letra f (ou F) no fim : 0.35f, 3.5e-1f, 35e-2F, 0.035e1F. Esses valores podem ser armazenados em variáveis de tipo float ou double (veja os pormenores nesta secção). Tal como para o caso dos inteiros existem algumas subtilezas na utilização dos valores decimais em programas devido a sua representação em memória. A primeira limitação é que não é possível representar um valor real qualquer em C. É por isso que a palavra «real» não é usada para designar os tipos associados aos valores decimais !

Existe uma solução perfeita para a representação de números tal como 1/3. Basta usar um número racional (quociente). Um racional consiste em representar um valor decimal pelo quociente de dois números inteiros. 0 valor 14.15 pode ser representado por 1415/100 ou 283/20. O Common Lisp é uma linguagem de programação que incluí o tipo rational como tipo de base. Contudo esta representação não é encontrada frequentemente em linguagens de programação porque não permite resolver todos os problemas de representação dos valores decimais. Existem números iracionais ! Por exemplo Pi é um número que não pode ser representado sob forma de um quociente. Em suma, com a grande maioria das linguagens de programação estamos condenados a lidar com aproximações de valores reais.

Então qual é o problema com a representação dos valores reais ? Para além do problema da gama de valores representáveis (valores máximo e mínimo) há o problema da precisão. Se dividir 1 por 3 na sua calculadora o resultado será algo próximo de 0.3333333333. Este valor é apenas uma aproximação do valor 1/3. A representação decimal de 1/3 é complicada porque involve uma sequência inifinita de 3 a sequir ao ponto decimal.

O valor representado em memória será sempre uma aproximação. Felizmente isto não é problema desde que estamos cientes deste facto. Como veremos mais à frente, em certas ocasiões teremos que ter cuidado, por exemplo quando comparar dois valores de tipo float. Dois valores que consideramos iguais podem ser diferentes para o programa, simplesmente porque têm uma precisão diferente. Sempre que o programador necessita de mais precisão, tem ao seu dispor o tipo double.

Diferenças entre float e double

Existe portanto em C dois tipos básicos para representar valores decimais : float e double. Quais são as diferenças e quando devemos usar um ou outro ?

O nome float deriva do método usado para representar os valores decimais em memória (o método da virgula flutuante). A palavra double deriva do facto que o tipo permite representar valores decimais com uma precisão muito maior mas ocupa o dobro da memória (comparado com um valor de tipo float). O programador deve portanto escolher entre o tipo float e double com base no problema que está a resolver. Se o problema implica o armazenamento de uma quantidade muito elevada de valores decimais e que uma grande precisão não é necessária então pode usar o tipo float. Caso seja preciso para resolver o problema efectuar cáculos com uma grande precisão, poderá usar o tipo double.

A linguagem C foi inventada no início dos anos setenta. Nessa altura os computadores eram muito menos potentes do os que usamos hoje. Os recursos disponíveis (tanto em termos de memória como em termos de velocidade de computação) para o programador eram muito reduzidos. Por essa razão a linguagem devia permitir uma optimização dos recursos disponíveis. Neste contexto, o espaço ocupado em memória não era o único factor de limitação, o tempo de cálculo era importante. O tempo levado para o processador para multiplicar dois valores de tipo double era mais ou menos o dobro do tempo necessário para multiplicar dois valores de tipo float. Com os progressos da tecnologia, hoje é dificil dizer que a computação com valores de tipo float é mais rápida do que com tipos double.

Caracteres

Os caracteres constituem o terceiro tipo de dado disponivel em C é o tipo char. Este tipo básico é usado para representar caracteres. A declaração seguinte define uma variável car e inicializa-a com o caractere z:

char car = 'z';
Como pode-se ver neste exemplo, os valores de tipo caracter aparecem entre plicas.

Como todos os dados os caracteres estão representados internamente por valores númericos. O esquema usado para representar as letras com números é conhecido como código ASCII. Por exemplo, o caracter 'A' é representado pelo número 65. Não é útil conhecer os valores atribuidos a cada caracter. Contudo é bom conhecer algumas propriedades do código ASCII. Por exemplo, os caracteres que correspondem às letras de 'a' até 'z' e de 'A' até 'Z' bem como os dígitos de '0' até '9' são representados por números inteiros consecutivos. O código da letra 'G' é igual ao código de 'F' mais um. Esta propriedade será útil para ajudar a resolver alguns problemas mas também constitui um perigo e uma fonte de erro caso o programador não seja atento.

Conversões automáticas

O que é que acontece se atribuirmos um valor de tipo inteiro a uma variável de tipo float ? de tipo double ?
Em linguagem C, quando numa atribuição o tipo do valor é diferente do tipo da variável, o valor é automaticamente convertido. Por exemplo se atribuirmos um valor inteiro a uma variável de tipo float :

float a;
= 5;
o valor efectivamente copiado para a variável é 5.0. Se atribuirmos um valor de tipo float a uma variável de tipo double, o valor é convertido para double. No caso oposto (atribuição de um valor double a uma variável declarada float), a conversão provoca uma perca de precisão. Veja esta sequência de instruções :
double pi1 = 3.14159265358979323846264338327950288;
float pi2 = pi1;
double pi3 = pi2

  1. À pi1, variável de tipo double é atribuido um valor de tipo double,
  2. à variável pi2, de tipo float é atribuido o valor de pi1. O valor é convertido para uma precisão simples, há perca de precisão,
  3. finalmente à pi3 declarada double é atribuido o valor de pi2, de tipo float. Este valor é convertido para a precisão dupla mas não há um aumento do número de casas decimais significativas, a informação perdida na conversão anterior não pode ser recuperada. Os valores de pi1 e pi3 são diferentes. A perca de precisão significa que o valor armezanado em pi3 é mais afastado do valor real de pi do que o valor que está em pi1.

Uma conversão com perca de precisão acontece em mais casos. Por exemplo após execução das instruções :

double z = 5.99999;
int a = z;
O valor de a é : 5 (a conversão para inteiro remove a parte decimal do valor, não há arredondamento).

Resumindo :

  1. quando uma atribuição é executada, e o tipo do valor à direita do símbolo de atribuição (=) é diferente do tipo da variável à esquerda uma conversão do valor é feita automaticamente.
  2. esta conversão automática pode provocar uma perca de precisão no valor atribuido.
Quando uma atribuição implica uma perca de precisão, o compilador não produz nenhum aviso, é da responsibilidade do programador controlar que não há problema.

Para saber mais

Para saber mais sobre os valores numéricos em C, consulte a secção Valores em C (nível 2).