2. Operandos e Operadores

2.1 Introdução

Na base de qualquer programa estão os operandos e operadores, pois é através destes, que se constroem as expressões que acabarão por formar um programa.

As variáveis e as constantes constituem os operandos que representam os dados, enquanto os operadores determinam o que lhes fazer.

Os operandos classificam-se através de três atributos:

- nome
- tipo
- classe de armazenamento

As próximas secções tratam exactamente destes atributos, fundamentais para um correcto entendimento da linguagem C.

2.2 Nomes

Um nome ou identificador, é uma sequência de letras ou dígitos que começa obrigatoriamente por uma letra. O sinal _ (underscore) conta como uma letra, e é normalmente usado para simular um espaço num nome.

Nomes com letras maiúsculas são diferentes de nomes com letras minúsculas. Assim sendo, A e a representarão dois nomes distintos.

Normalmente na linguagem C, usam-se letras minúsculas para representar as variáveis, e letras maiúsculas para representar as constantes.

Esta utilização de maiúsculas e minúsculas não é uma regra da linguagem, mas ao ser seguida por quem programa facilita a leitura dos programas.

As palavras reservadas int,if,else, etc. da linguagem C, não são permitidas como nomes.

Essas palavras são reservadas ao compilador para utilização da linguagem. Não podem ser definidas classes, variáveis ou funções com esses nomes. A lista seguinte é um pouco arbitrária, visto que algumas palavras reservadas são específicas a um tipo de compilador.

Palavras Reservadas :

auto break case catch char class const continue default delete do double else enum extern float for friend goto if int long mutable newo perator private protected public register return short signed size of static structs witch template this throw type def union unsigned virtual void volatile while

2.3 Tipos de Dados

Todas as variáveis são de um determinado tipo de dado que é especificado na definição da variável.

Ao definir-se uma variável, seja qual for o tipo, o compilador reserva o espaço na memória necessário a essa variável.

Em C existem quatro tipos de dados básicos, que são:

- char - caracter : 1 byte
- int - inteiro; normalmente uma palavra de computador : 2 ou 4 bytes
- float - número de virgula flutuante :normalmente 4 bytes
- double - não inferior à precisão do float; normalmente o dobro da precisão do float : 8 bytes.

Para além dos tipos de dados existem qualificadores que lhes podem ser aplicados, e que são :

- short, long, signed, unsigned.

O qualificador short aplica-se a inteiros e tem como objectivo reduzi-los para metade do seu tamanho (ex: numa máquina em que os inteiros (int) sejam de 4 bytes, o short int será de 2 bytes).

O long aplicado a inteiros tem a função inversa do short. O qualificador long também se aplica ao tipo double, mas com efeitos que dependem da sua implementação no compilador.

Os qualificadores signed e unsigned são aplicados ao tipo char ou a qualquer inteiro, e indicam se o número tem ou não sinal. No caso dos signed o seu bit mais significativo está reservado ao sinal. Números unsigned são sempre superiores ou iguais a zero, enquanto os signed poderão ser negativos.

Exemplificando, para o caso do char, uma variável signed char tomaria valores entre -128 e 127, enquanto que se fosse unsigned char, tomaria valores entre 0 e 255. Isto significa que uma variável char pode conter qualquer caracter ASCII, mais exactamente os respectivos códigos, pois estes variam de 0 a 127 ( Existem extensões a este código onde se incluem caracteres de desenho).

Todas as variáveis devem ser declaradas antes de serem utilizadas. Uma declaração especifica um tipo, e contém uma ou mais variáveis desse tipo.

Exemplos disso são :

    int ano,mes,dia;

que declara três variáveis do tipo inteiro cujos nomes são ano, mes e dia.

    unsigned char tamanho;

declara uma variável do tipo char sem sinal, e cujo nome é tamanho. Para este caso o compilador alocaria espaço para um caracter (1 byte).

As variáveis podem ser inicializadas no momento da sua declaração:

int x=-691;
double media=0;

Existe em C um operador para a determinação do espaço em memória ocupado por um determinado tipo de dado, e cujo nome é sizeof. O operador sizeof devolve um valor inteiro que é o número de bytes correspondente ao tamanho desse objecto. Por exemplo para determinar o espaço ocupado por um inteiro usa-se :

sizeof (int);

Exercicio : Escreva um programa que diga qual o espaço reservado em memória para os seguintes dados: char;short int; long int; float e double.

Resolução :

    #include <stdio.h>
    #include <conio.h>

    main(){
       clrscr();
       printf (Tamanho dos Short Int =%d\n,sizeof (short int));
       printf (Tamanho dos Long Int =%d\n,sizeof (long int));
       printf (Tamanho dos Caracteres =%d\n,sizeof (char));
       printf (Tamanho dos Float =%d\n,sizeof (float));
       printf (Tamanho dos Double =%d\n,sizeof (double));
    }

Saída:

    Tamanho dos Short Int =2
    Tamanho dos Long Int =4
    Tamanho dos Caracteres =1
    Tamanho dos Float =4
    Tamanho dos Double =8

2.4 Classes de Armazenamento

As classes de armazenamento de um identificador definem o modo como o compilador lhes reserva espaço.

A classe de armazenamento determina para uma variável o seu tempo de vida, e o seu âmbito (escopo) , isto é :

- tempo de vida : Tempo de vida da variável no programa.
- escopo : Porção de programa onde a variável é reconhecida.

Os objectos em C caem em duas classes gerais dependendo onde são definidas.

- locais - objectos definidos dentro de um bloco, normalmente numa função.
- globais - objectos definidos no nível mais exterior, fora da função.

As declarações/definições das variáveis incluido a sua classe de armazenamento têm a forma geral:

classe_armazenamento tipo_dado nome_variável ;

2.4.1 Tipos de Classes de Armazenamento

São quatro as classes de armazenamento de objectos em C:

- auto: Classe de armazenamento interna para variáveis locais que existem e são reconhecidas somente ao longo da duração da função. Todas as variáveis locais são implicitamente auto.
- static: O seu significado varia consoante o objecto é definido dentro ou fora de uma função. Uma variável static quando é inicializada na sua definição só é inicializada na primeira vez que a função é chamada. Nas chamadas seguintes vai apresentar o valor da sua última utilização. O interesse desta classe surge quando por exemplo se pretende que o valor de uma variável de uma função não seja destruído por uma nova chamada a essa função. O tempo de vida destas variáveis é o da duração do programa.
- extern:Para variáveis globais a todo o programa. Nesta classe caem por exemplo as variáveis definidas externamente num ficheiro e que se pretende utilizar noutro ficheiro do mesmo programa.
- register: O mesmo que auto, excepto que o compilador, se possível, escolhe locais de memória de acesso mais rápido (registos). É normalmente utilizada para variáveis de ciclo. Só é aplicável a variáveis do tipo int ou char.

2.5 Constantes

As constantes são, como o seu nome indica, valores fixos que não podem ser alterados durante o programa e, têm associado a cada uma delas um determinado tipo de dado.

Os tipos de constantes inteiras são:

- Decimal
- Octal
- Hexadecimal

Para identificar o tipo de dado como unsigned usa-se o sufixo u ou U, enquanto para significar long usa-se o sufixo l ou L, e o sufixo ul ou UL para unsigned long. O tipo int é assumido por defeito, desde que a constante seja inteira e portanto não necessita de sufixo.

Um ponto decimal numa string (cadeia) de dígitos indica que o objecto é do tipo double.

O expoente pode ser denotado usando e ou E.

Uma constante de vírgula flutuante com o sufixo l ou L é considerada do tipo long double. Para significar float usa-se o sufixo f ou F.

Os caracteres são denotados através de plicas, por exemplo, 'q' representa o caracter q.

Os caracteres têm uma representação interna numérica.

No código ASCII o caracter q é representado pelo inteiro (em octal ) 0161. Então, c=0161; é equivalente a c='q';

c=49; é equivalente a c='1';

Vários caracteres especiais podem ser representados como se segue :

\n ->newline
\t ->tab
\b ->backspace
\\ ->backslash
\r ->carriage return

Qualquer caracter pode ser especificado pelo seu equivalente numérico usando a notação \ddd onde ddd é um inteiro octal,ou por \xddd, onde ddd é um inteiro em hexadecimal.

As sequências de escape para caracteres de não impressão são também válidas dentro de strings de caracteres.

Por exemplo:

  main()
  {
    printf (%s,OLA\n);
  }

imprime no ecra a palavra OLA e em seguida há uma mudança de linha provocada pelo caracter \n.

Uma constante também pode ser uma string, ou melhor, um array (vector) de caracteres em que o último elemento é o caracter nulo (i.e, caracter cujo código ASCII é 0.

Por Exemplo para a constante ARRAY (string de caracteres), a sua representação interna seria o conjunto de caracteres, 'A' 'R' 'R' 'A' 'Y' '\0'.

2.6 Operadores.

Os operadores em C podem dividir-se em três classes :

- Aritméticos.
- Relacionais e Lógicos.
- Bit a Bit (binários).

Consoante os operadores envolvam um,dois ou três operandos, classificam-se em unários, binários, ternários.

2.6.1 Operadores Aritméticos

Os operadores aritméticos são :

+ | Adição
- | Subtracção
* | Multiplicação
/ | Divisão
% | Módulo (resto da divisão de inteiros)

O operador % não pode ser aplicado a números de virgula flutuante. Na divisão de inteiros a parte fraccionária é sempre truncada. Por exemplo de 5/2 resultaria 2.

Na Linguagem C qualquer operador aritmético pode ser combinado com a atribuição, o que significa que :

  x+=2 <=> x=x+2
  a/=b+c <=> a=a/(b+c)

2.6.2 Operadores Incremento e Decremento.

Os operadores incremento e decremento, ++ e -- respectivamente, podem ser usados tanto na forma prefixa como na forma sufixa. O operador ++ soma uma unidade à variável, enquanto o operdor -- subtrai, como se pode ver nos casos que se seguem:

- x++; <=> x=x+1;
- y=x++; <=> y=x; x=x+1;
- y=++x <=> x=x+1; y=x;

Como se constata, na forma prefixa acontece primeiro o incremento e só depois a atribuição, enquanto na sufixa sucede o contrário.

2.6.3 Operadores relacionais e Operadores lógicos

Estes operadores permitem a comparação de expressões.

No caso de a relação ser verdadeira, resulta o valor 1 (verdade), no caso de a relação ser falsa resulta o valor 0.

Relacionais:

x<y | menor que
x<=y | menor ou igual
x>y | maior que
x>=y | maior ou igual
x==y | comparar igualdade
x!=y | comparar diferença

Lógicos:

&& | and (e)
|| | or (ou)
! | not (negação)

2.6.4 Operador Condicional

O operador condicional é um operador ternário usado em C. O valor da expressão:

expr0 ? expr1:expr2

é o valor de expr1 se expr0 for verdadeira ou o valor de expr2 se expr0 for falsa.

Por exemplo, a instrução

x=a>b?a:b;

é exactamente equivalente à combinação das seguintes instruções :

if (a>b)
x=a;
else
x=b;

2.6.5 Operadores Bit a Bit

Os operadores bit a bit (binários) permitem, como o nome implica, operações ao nível dos bits. São seis os operadores bit a bit, os quais só poderão ser aplicados a expressões do tipo inteiro:

~ | complementa bits (inverte o estado dos bits)
<< | desloca para a esquerda os bits
>> | desloca para a direita os bits
& | conjunção
^ | ou exclusivo
| | disjunção

As tabelas de verdade para E,OU,OU exclusivo são respectivamente :

& | 0 | 1
0 | 0 | 0
1 | 0 | 1
| | 0 | 1
0 | 0 | 1
1 | 1 | 1
^ | 0 | 1
0 | 0 | 1
1 | 1 | 0

Exemplos :

Supôr duas variáveis x e y do tipo char, cuja representação binária é :

  x=10111011
  y=10101010

Os resultados de expressões envolvendo várias operações são (as operações são executadas sobre os valores originais de x e y):

  ~x = 01000100
  x<<2 = 11101100
  y>>2 = 01010100
  x&y = 10101010
  x|y = 10111011
  x^y = 00010001

Outros exemplos de carácter mais prático:

a=b&037 -> Atribuir a a os 5 bits de menor ordem de b.
a|=0200 ->O 8.º bit de a é posto a 1.
a&=017 -> limpa todos os bits excepto os 4 finais.
a^=077 -> reversa os últimos 6 bits
a<<=2 -> a é multiplicado por 4.
a~=b ->a fica complementar de b
a=b&1 ->a será 1 se o primeiro bit de b estiver a 1.

Atenção : Ter sempre cuidade em não confundir os operadores lógicos && e || com os operadores binários & e |.

2.6.6 Operadores de Ponteiros

Operadores de ponteiro são importantes em C. Permitem a passagem de vectores, strings e estruturas para funções, além de permitir que essas mesmas funções alterem o conteúdo dos argumentos chamados. Os dois operadores de ponteiros são & e *.(Esses operadores usam os mesmos símbolos da multiplicação e do E bit a bit, que são completamente des-relacionados dos operadores de ponteiros).

O operador & retorna o endereço da variável a que precede.

O operador * leva o valor da variável à que precede e usa este valor como endereço da informação na memória.

O estudo deste tipo de operadores devido à sua dimensão e complexidade, foi deslocado para capítulo próprio, merecendo neste espaço apenas um lugar de referência.

2.7 Precedências

O conhecimento da ordem de precedência dos vários operadores é fundamental para que se possam construir expressões correctamente.

A tabela seguinte lista as precedências por ordem decrescente bem como o sentido de avaliação (associatividade). Ter em atenção que alguns dos operadores indicados na tabela ainda não foram discutidos.

Operadores | Associatividade
() [] -> . | da esquerda para a direita
! ^ ++ -- + - * & (tipo) sizeof | da direita para a esquerda
* / % | da esquerda para a direita
+ - | da esquerda para a direita
<< >> | da esquerda para a direita
< <= > >= | da esquerda para a direita
== != | da esquerda para a direita
& | da esquerda para a direita
^ | da esquerda para a direita
| | da esquerda para a direita
&& | da esquerda para a direita
|| | da esquerda para a direita
?: | da direita para a esquerda
= += -= *= /= %= &= ^= |= <<= >>= | da direita para a esquerda
, | da esquerda para a direita

2.8 Conversão de Tipos

Por vezes surge a necessidade de trabalhar operandos cujos tipos de dados diferem uns dos outros.

Nestas situações há que proceder à conversão de tipos, isto é a conversão desses diferentes tipos num tipo comum, de forma a possibilitar a resolução das expressões. As conversões caem em três classes : automática, requerida pela expressão e forçada.

Automática

Feita automaticamente pelo compilador sempre que a variável seja do tipo char, short ou float, não existindo possibilidade de perda de informação. As converssões automáticas são feitas do seguinte modo :

char => int
short => int
float => double

Requerida pela expressão

Quando há mistura de tipos, sendo necessária para a resolução da expressão.

Exemplo:

  int x;
  double y; /* conversão num double seguida de */
  x=x+y; /* uma conversão num inteiro */

A conversão para inteiro faz com que o resultado seja truncado.

Forçada (cast)

Este tipo de conversão pode ser requerido pelo utilizador sempre que este o considere necessário, e faz-se, precedendo a variável pelo novo tipo entre parêntises. Ter em atenção que a conversão para tipos menores pode truncar a variável.

Forma Geral:

(tipo de dado) expressão;

Exemplo : A seguinte função devolve o resto da divisão de duas variáveis de virgula flutuante.

double mod(double a,double b)
{
double temp;
temp = (int) (a/b);
temp *= b;
return (a-temp);
}

Neste exemplo o cast tornou-se necessário para que o resultado da divisão fosse um inteiro, perdendo-se a parte fraccionária.

2.9 Typedef

Se um tipo de dado é precedido pela palavra typedef então o identificador na declaração é entendido pelo compilador como um novo nome para esse tipo de dado.

Exemplo :Programa que calcula a área de um rectângulo . O resultado (área) é guardado numa variável do tipo AREA, definido usando-se typedef. Assim em todas as declarações de variáveis que representem áreas, poder-se-ia usar AREA em vez de float.

    /* programa area.c */
    #include <stdio.h>
    typedef float AREA;
    main()
    {

    >   AREA x;
    >   float lado1, lado2
    >   printf (\nIntroduza os valores dos lados: );
    >   scanf (%f%f,&lado1,&lado2); /* scanf lê dados do teclado com o tipo indicado entre aspas.*/ /*Neste caso 2 floats %f%f */
    >   x=lado1*lado2;
    >   printf (A área é igual a %f,x); /* %f indica um float para escrita */

    }

Ter em atenção que em última instância a variável x é um float, como se prova pela formatação (com %f) utilizada no printf().

A utilização de typedef é mais frequente com tipos de dados compostos, como se poderá ver no capítulo dedicado a esses tipos de dados.

No exemplo anterior foi utilizada a função scanf() com dois ou mais argumentos. O primeiro argumento, entre aspas (string), indica os tipos de dados a ler para as variáveis que ocupam os argumentos seguintes.

Back | Up | Forward

To front page | Pages by Nuno Nunes. |