Fundamentos da linguagem C & Expressões

Programas

#include <stdio.h>

int main(void)
{
  printf("To C or not to C, ");
  printf("that is the question.\n");
  return 0;
}

Execução

Para ser executado, um programa em C tem primeiro de ser traduzido para código-máquina.

A tradução é efetuada por programa compilador.

Nestas aulas: vamos usar o GCC (GNU Compiler Collection).


Fases de tradução

Pré-processamento

o pré-processador interpreta diretivas (linhas que começam por #)

Compilação

o compilador traduz o código C para código-máquina

Ligação

o ligador (linker) combina o código-máquina gerado com as bibliotecas necessárias


Em sistemas GNU/Linux: o pré-processador, compilador e ligador são executados em sequência pelo comando gcc.

Compilar, ligar e executar

Invocamos o compilador usando o interpretador de comandos Linux (shell):

$  gcc -o hello hello.c 

Produz um ficheiro hello que podemos executar:

$ ./hello
To C or not to C, that is the question.

Estrutura de programas simples

directivas

int main(void)
{ 
    instruções
}

Directivas

   #include <stdio.h>

Funções

Função main

   int main(void)
   {
      ....
      return 0;
   }

Instruções

  printf("To C or not to C, ");
  printf("that is the question.\n");
  return 0;
   To C or not to C, that is the question.

Instruções (cont.)

  printf(
     "To C or not to C, "
  );
  printf("To C or "); printf("not to C, ");

Comentários

   /* Isto é um comentário */

   /*
     Ficheiro: hello.c
     Programa: Imprime uma mensagem de exemplo
   */

Comentários (cont.)

Atenção: esquecer de fechar um comentário pode fazer com que o compilador ignore parte do programa.

   printf("To be ");    /* comentário aberto
   printf("or not to be; ");   /* fechado */
   printf("that is the question.\n");

Comentários (cont.)

   // Isto é um comentário

Variáveis e atribuições

Tipos

Tipos (cont.)

Declarações

As variáveis têm de ser declaradas antes do uso:

   int altura;
   float raio;

Podemos declarar múltiplas variáveis do mesmo tipo duma só vez:

   int altura, largura, profundidade;
   float raio, massa;

Declarações

Até C89: todas as declarações têm de ocorrer antes das instruções.

int main(void) {
   /* declarações de variáveis */
   int altura, largura;
   float raio;

   /* seguem-se instruções */
   ...
}

Em C99: declarações e instruções podem ser misturadas (desde que a declaração ocorra antes do uso).

Atribuição

Podemos definir ou modificar o valor de uma variável usando uma atribuição.

   int altura;   // declaração
   altura = 8;   // atribuição

Atribuição (cont.)

No lado direito duma atribuição podemos usar expressões e.g.
constantes, variáveis e operações.

   int altura, largura, area;
   altura = 8;
   largura = 3;
   area = altura * largura;
       // area é 24

Exemplo

Um programa para calcular o volume \(V\) de uma caixa retangular.

Exemplo:

 

\[ V = 11 \text{cm} \times 5 \text{cm} \times 6 \text{cm} = 330 \text{cm}^3 \]

volume.c

#include <stdio.h>

int main(void) {
   int l, w, h, v;   // dimensões e volume

   l = 11;    // comprimento
   w = 5;     // largura
   h = 6;     // altura
   v = l*w*h; // cálculo do volume

   printf("LxWxH: %d*%d*%d (cm)\n", l,w,h);
   printf("Volume: %d (cm^3)\n", v);
   return 0;
}

Imprimir valores

Podemos usar a função de bibloteca printf para imprimir valores das variáveis. Exemplo:

   int alt;
   alt = 6;
   printf("Altura: %d cm\n", alt);

Imprime o texto:

   Altura: 6 cm

%d é um campo que é substituido pelo valor duma variável inteira em decimal.

Imprimir valores (cont.)

Para valores float usamos o campo %f.

   float custo;
   custo = 123.45;
   printf("Custo: EUR %f\n", custo);

Resultado:

   Custo: EUR 123.449997

Imprimir valores (cont.)

Para forçar formatação com \(n\) casas decimais usamos %\(.n\)f:

Exemplo:

   printf("Custo: EUR %.2f\n", custo);

Resultado:

   Custo: EUR 123.45

Imprimir valores (cont.)

Podemos formatar vários valores num só printf:

   printf("Altura: %d cm; Custo: EUR %.2f\n",
          alt, custo);

Atenção:

Ler valores

   int n;
   scanf("%d", &n);

Ler valores (cont.)

Para ler um float não necessitamos de especificar casas decimais.

   float x;
   scanf("%f", &x);

Funciona com ou sem casas decimais na entrada; exemplos:

   123
   123.4
   123.4567

Exemplo revisto

Vamos alterar o programa de exemplo anterior para ler as dimensões da caixa.

volume.c revisto

#include <stdio.h>

int main(void) {
   int l, w, h, v;   // dimensões e volume

   printf("L=?"); scanf("%d", &l);
   printf("W=?"); scanf("%d", &w);
   printf("H=?"); scanf("%d", &h);
   v = l*w*h; // cálculo do volume

   printf("LxWxH: %d*%d*%d (cm)\n", l,w,h);
   printf("Volume: %d (cm^3)\n", v);
   return 0;
}

Inicializações

   int x, y;
   y = x + 1;  // variável x não-inicializada

Inicializações (cont.)

   int alt = 8;
   int alt = 8, larg = 5, comp = 11;
   int alt, larg, comp = 11;
     // só inicializa uma variável (comp)

Identificadores

Exemplos válidos:

   times10   get_Next_Char   _done

Exemplos inválidos:

   10times   get-Next-Char   máximo

Idetificadores (cont.)

   get_next_char
   get_next_Char
   get_Next_Char

são identificadores diferentes (seria confuso usá-los num mesmo programa…).

Palavras reservadas

Não podemos usar as seguintes palavras reservadas como identificadores.

auto      enum      restrict   unsigned
break     extern    return     void
case      float     short      volatile
char      for       signed     while
const     goto      sizeof     _Bool
continue  if        static     _Complex
default   inline    struct     _Imaginary
do        int       switch
double    long      typedef
else      register  union

Definir constantes

#define INCHES_PER_METER   39.3701
   /* factor de conversão:
      polegadas por cada metro */

Pre-processamento

O pré-processador substitui as macros textualmente.

Exemplo:

   inches = meters * INCHES_PER_METER;

após processamento fica:

   inches = meters * 39.3701;

Exemplo

Um programa para calcular a área de uma circunferência.

A área de uma círcunferência de raio \(r\) é \[ A = \pi r^2 \] (onde \(\pi\) é a constante \(3.14159\ldots\)).

area.c

#include <stdio.h>

#define PI 3.14159 

int main(void) {
   float raio, area;
   printf("Raio da circunferência?");
   scanf("%f", &raio);
   area = PI * raio * raio;
   printf("Área: %f\n", area);
   return 0;
}

Estrutura de programas

Um programa em C é uma sequência de símbolos (“tokens”):

Estrutura de programas

Exemplo: a instrução

   printf("Área: %f\n", area);

contém sete símbolos:

printf identificador
( pontuação
"Área: %f\n" cadeia de carateres
, pontuação
area identificador
) pontuação
; pontuação

Layout de programas

#include <stdio.h>
#define PI 3.14159 
int main(void) {float raio,area;printf(
"Raio da circunferência?");scanf("%f",&raio);
area=PI*raio*raio;printf("Área: %f\n",area);
return 0;}

Layout de programas

Devemos usar espaços, tabulações e mudança de linha para aumentar a legibilidade do programa:

Salientar a sintaxe

Exemplo

#include <stdio.h>

#define PI 3.14159

int main(void) {
   float raio, area;

   printf("Raio da circunferência?");
   scanf("%f", &raio); 
   area = PI * raio * raio;
        // calcular a área
   printf("Área: %f\n", area);
   return 0;
}

Expressões

Operadores aritméticos

adição a+b
subtração a-b
multiplicação a*b
divisão a/b
resto da divisão a%b
menos unário -a
mais unário +a

Operadores aritméticos unários

    i = +1;
    j = -i;

Operadores aritméticos binários

Divisão inteira

Cuidados com / e %

Precedência de operadores

Precedência de operadores

  1. Primeiro é avaliados + e - unários
  2. Depois *, /, %
  3. Por fim + e - binários

Exemplos

i + j * k equivalente a i + (j*k)
-i * -j equivalente a (-i) * (-j)
+ i + j/k equivalente a (+i) + (j/k)

Associatividade de operadores

Exemplos

i - j - k equivale a (i - j) - k
i * j / k equivale a (i * j) / k
- + j equivale a -(+j)

Atribuição simples

    i = 5;            /* valor de i: 5  */
    j = i;            /* valor de j: 5  */
    k = 10 * i + j;   /* valor de k: 55 */

Atribuição simples (cont.)

Se a variável e expressão não forem do mesmo tipo dá-se uma conversão implícita de tipos.

   int i;
   float f;
    
   i = 72.99;   // valor de i: 72 
   f = 136;     // valor de f: 136.0 

Atribuições são expressões

Exemplo:

int i, j, k;
i = 1;
k = 1 + (j = i);
   // j: 1, k: 2

Cuidado: usar atribuições no meio de expressões pode dificultar a compreensão de programas

Atribuições em sequência

Podemos atribuir um mesmo valor a várias variáveis:

i = j = k = 0;

Como a atribuição associa à direita, isto é equivalente a:

i = (j = (k = 0));

Valores esquerdos (Lvalues)

i = 12;    // OK: i é um lvalue
12 = i;    // erro: 12 não é um lvalue
1+j = 12;  // erro: 1+j não é um lvalue

Atribuição composta

É frequente atribuir a uma variável um novo valor que depende do seu valor atual.

Exemplo:

i = i + 2;

Nestes casos podemos usar uma atribuição composta:

i += 2;

Operadores de atribuição composta

\(v\) += \(e\)

adicionar \(e\) de \(v\), guardando o resultado em \(v\)

\(v\) -= \(e\)

subtrair \(e\) a \(v\), guardando o resultado em \(v\)

\(v\) *= \(e\)

multiplicar \(e\) por \(v\), guardando o resultado em \(v\)

\(v\) /= \(e\)

dividir \(v\) por \(e\), guardando o resultado em \(v\)

\(v\) %= \(e\)

calcular o resto de divisão de \(v\) por \(e\), guardando o resultado em \(v\)

Incremento e decremento

É frequente somar ou subtrair uma variável inteira de uma unidade.

i = i + 1;
j = j - 1;

Também aqui podemos usar uma atribuição composta:

i += 1;
j -= 1;

Em alternativa, podemos usar operadores de incremento ou decremento.

Incremento e decremento (cont.)

++i;        // equivalente a i = i + 1
--j;        // equivalente a j = j - 1

Pré- e pós-incremento

++i modifica a variável i (incrementa de uma unidade) e dá o valor resultante.

i = 1;
printf("%d\n", ++i);  // imprime 2
printf("%d\n", i);    // imprime 2

i++ dá o valor atual de i e depois modifica a variável i (incrementa de 1 unidade).

i = 1;
printf("%d\n", i++);  // imprime 1
printf("%d\n", i);    // imprime 2

Pré- e pós-incremento

Pré- e pós-decremento

O operador de decremento comporta-se de forma análoga ao de incremento:

i = 1;
printf("%d\n", --i);  // imprime 0
printf("%d\n", i);    // imprime 0
i = 1;
printf("%d\n", i--);  // imprime 1
printf("%d\n", i);    // imprime 0

Incremento e decremento

Pode ser difícil seguir o efeito de múltiplos ++ ou -- em várias variáveis numa mesma expressão:

i = 1;
j = 2;
k = ++i + j++;
 // i: 2, j: 3, k: 4
i = 1;
j = 2;
k = i++ + j++;
 // i: 2, j: 3, k: 3

Recomendação: evite expressões com mútiplos incrementos.

Ordem de avaliação

a = 5;
c = (b = a + 2) - (a = 1);

Qual é o valor final de c?

Logo: o comportamento deste programa depende da ordem de avaliação das subexpressões.

Ordem de avaliação

Outro exemplo

i = 2;
j = i * i++;

O resultado final de j depende da ordem de avaliação da multiplicação (4 ou 6).

Comportamento indefinido

       gcc -Wall -o programa programa.c

Comportamento indefinido

Re-escrever o primeiro exemplo para evitar comportamento indefinido:

a = 5;
b = a + 2;
a = 1;
c = b - a;

Desta forma o resultado final de c é sempre 6.