Execução condicional; Funções; Ciclos

Operadores relacionais

Exemplos

int i, j;
i = 2;
j = 4;
printf("%d\n", i > j);     // imprime 0
printf("%d\n", i < j);     // imprime 1
printf("%d\n", i*i == j);  // imprime 1
printf("%d\n", i*j >= 10); // imprime 0

Precedência e associatividade

Atenção

A expressão

   i < j < k 

é válida mas não testa se j está entre i e k:

A expressão correta usa a conjunção de duas condições:

   i < j && j < k   

Comparações de igualdade

int i, j;
i = 2;
j = 3;
printf("%d\n", i == j);   // imprime 0
printf("%d\n", i+1 == j); // imprime 1
printf("%d\n", i != j);   // imprime 1

Atenção

Não confundir atribuições com comparações:

i = j

modifica o lado esquerdo; resultado é o valor atribuido

i == j

compara o lados esquerdo e o direito; resultado é 0 ou 1

Instrução if

if ( line_num == MAX ) line_num = 0;
// continuação do programa
...

Instrução if

Exemplo

Podemos colocar numa só linha:

if (line_num == MAX) { line_num = 0; page_num ++; } 


Mas fica mais claro se partimos em várias linhas:

if (line_num == MAX)
{
   line_num = 0;
   page_num ++;
} 

Alternativa else

Exemplo:

if (i > j) max = i; else max = j;

Alternativa else

if (i > j)
   if (i > k)
      max = i;
   else
      max = k;
else
   if (j > k)
      max = j;
   else
      max = k;

Alternativa else

Podemos também acrescentar chavetas para auxiliar a compreensão:

if (i > j) {
   if (i > k)
      max = i;
   else
      max = k;
} else {
   if (j > k)
      max = j;
   else
      max = k;
}

Alternativa else

Há quem prefira colocar sempre chavetas:

if (i > j) {
   if (i > k) {
      max = i;
   } else {
      max = k;
   }
} else {
   if (j > k) {
      max = j;
   } else {
      max = k;
   }
}

Vantagens

Ao colocarmos sempre chavetas:

if em “cascata”

Para testar condições em sequência escrevemos múltiplas instruções if em “cascata”.

Exemplo:

if (n < 0)
   printf("n negativo\n");
else
   if (n == 0)
      printf("n zero\n");
   else
      printf("n positivo\n");

if em “cascata”

Apesar do segundo if estar dentro do primeiro, é preferível alinhar os else em vez de indentar:

if (n < 0)
   printf("n negativo\n");
else if (n == 0)
   printf("n zero\n");
else
   printf("n positivo\n");

if em “cascata”

Desta forma evitamos a indentação exagerada quando temos muitas condições.

if (expressão)
  instrução
else if(expressão)
  instrução
...
else if(expressão)
  instrução
else
  instrução

Exemplo: calcular comissões

Montante Comissão
Até €2500 €30 + 1.7%
€2500-€6250 €56 + 0.66%
€6250-€20K €76 + 0.34%
€20K-€50K €100 + 0.22%
€50K-€500K €155 + 0.11%
Acima de €500K €255 + 0.09%

Exemplo: calcular comissões

Introduza o valor: EUR 30000
Comissão: EUR 166.00

/* corretor.c
   Calcular a comissão de uma transação
*/
#include <stdio.h>

int main(void)
{
  float valor, comissao;

  printf("Introduza o valor: EUR ");
  scanf("%f", &valor);

  if(valor < 2500.0)
    comissao = 30.0 + 0.017 * valor;
  else if(valor < 6250.0)
    comissao = 56.0 + 0.0066 * valor;

(continua no slide seguinte)


(continuação do slide anterior)

  else if(valor < 20e3)
    comissao = 76.0 + 0.0034 * valor;
  else if(valor < 50e3)
    comissao = 100.0 + 0.0022 * valor;
  else if(valor < 500e3)
    comissao = 155.0 + 0.0011 * valor;
  else
    comissao = 255.0 + 0.0009 * valor;

  if (comissao < 39.0)
    comissao = 39.0;

  printf("Comissão: EUR %.2f\n", comissao);
}

Operadores lógicos

Podemos construir condições complexas partindo de outras mais simples usando operadores lógicos.

conjunção (\(\land\)) &&
disjunção (\(\lor\)) ||
negação (\(\neg\)) !

Exemplos:

i>=0 && i<10 
i==j || i+j==0
!(i==0)

Operadores lógicos

Operadores lógicos

Resultados dos operadores lógicos

!\(expr\)

resultado 1 se \(expr\) tem valor 0

\(expr_1\) && \(expr_2\)

resultado 1 se \(expr_1\) e \(expr_2\) são ambos diferentes de 0

\(expr_1\) || \(expr_2\)

resultado 1 se \(expr_1\) é diferente de 0 ou \(expr_2\) é diferente de 0 (ou ambos são diferentes de 0)

Em todos os outros casos: o resultado é 0.

Ordem de avaliação

Ordem de avaliação — exemplo

   (i != 0) && (j/i > 0)
  1. Primeiro é avaliada a condição i != 0
  2. Se i for diferente de 0, então é avalia j/i > 0
  3. Se i for 0 então a conjução será sempre falsa e não avaliamos j/i > 0 (evitando a divisão por zero)

Precedência e associatividade

Exemplos:

i < j && k < m

é equivalente a (i < j) && (k < m)

i < j && j < k && k < l

é equivalente a ((i < j) && (j < k)) && (k < l)

!i == 0

não é equivalente a i != 0

Cuidados com if

Um erro comum é trocar == (igualdade) por = (atribuição)

if (i == 0) ...

testa se i é igual a 0

if (i = 0) ...

atribui 0 a i e depois testa se o resultado é diferente de 0 (o que é sempre falso)

Recomendação: o gcc avisa possíveis erros deste tipo compilando com a opção -Wall

Cuidados como o else

Ao colocarmos if dentro de outro, temos de ter o cuidado de “casar” corretamente os else:

if (y != 0)
   if (x != 0)
      result = x / y;
else
   printf("erro: y igual a 0\n");

Cuidados com o else

Uma versão corretamente indentada seria assim:

if (y != 0)
   if (x != 0)
      result = x / y;
   else
      printf("erro: y igual a 0\n");

Cuidados com o else

Para associar o else ao if exterior temos de delimitar o if interior usando chavetas:

if (y != 0) {
   if (x != 0)
      result = x / y;
} else
   printf("erro: y igual a 0\n");

Recomendação: para evitar estes problemas use sempre chavetas num if que contém outro if

Funções

Objetivo

Exemplo

Uma função que calcula a média aritmética de dois valores:

float media(float a, float b)
{ 
   float m = (a + b) / 2.0;
   return m;
}

Corpo da função

O corpo da função está delimitado entre chavetas:

{
   float m = (a + b) / 2.0;
   return m;
}

Invocação da função

Um expressão: identificador(arg1, arg2, …)


Fluxo de execução

    z = media(x*0.5, y+1);
    printf("%f\n", media(x*0.5,y+1));

Exemplo: calcular médias

Vamos escrever um programa que lê três números e calcula as médias dois a dois.

Introduza 3 números: 3.5 9.6 10.2
Médias
3.5 e 9.6: 6.55
9.6 e 10.2: 9.9
3.5 e 10.2: 6.85

#include <stdio.h>

float media(float a, float b) {
  float m = (a + b)/2;
  return m;
}

int main(void) {
  float x, y, z;
  printf("Introduza 3 números: ");
  scanf("%f %f %f", &x, &y, &z);
  printf("Médias\n");
  printf("%f e %f: %f\n", x, y, media(x,y));
  printf("%f e %f: %f\n", y, z, media(y,z));
  printf("%f e %f: %f\n", x, z, media(x,z));
  return 0;
}

Declarações e definições

    float media(float, float);

Exemplo

float media(float, float); // protótipo

int main(void) {
 ...
 printf(..., x, y, media(x,y));   // uso
 ...
 return 0;
 }

float media(float a, float b) {  // definição
   m = (a + b)/2.0;
   return m;
}

Funções sem valor de retorno

Exemplo

void print_time(int n)
{
  printf("T minus %d and counting\n", n);
}

int main(void) {
    print_time(3);
    print_time(2);
    print_time(1);
    return 0;
}

Instrução return

  return 0;
  return n;
  return (x + y) / 2.0;

Instrução return

Podemos usar return para terminar a execução da função a meio do corpo.

int max(int a, int b)
{
   if(a >= b)
       return a;  // termina imediatamente
           
   // se a execução chegar a este ponto,
   // então a < b; logo o máximo é b
   return b;
}

Instrução return

void print_time(int n)
{
    if (n < 0)
        return;   // terminar imediatemente
    printf("T minus %d and counting\n", n);
}

Quando usar múltiplos return

Passagem de argumentos

Exemplo 1

// máximo de 2 valores
// NB: modifica o primeiro argumento
int max(int a, int b) {
    if (b > a)
        a = b;
    return a;
}

int main(void) {
   int x = 1, y = 2;
   printf("%d\n", max(x,y)); // imprime 2
   printf("%d %d\n", x, y); // imprime 1, 2
   return 0;
}

Exemplo 2

// Tenta trocar os valores de a, b;
// não funciona porque a,b são temporários
void trocar(int a, int b) {
   int t;
   t = a;
   a = b;
   b = t;
}

int main(void) {
   int x = 1, y= 2;
   trocar(x, y);  
   printf("%d %d\n", x, y); // imprime 1, 2
   return 0;
}

Decomposição funcional

Exemplo: imprimir algarismos

Escrever um programa que imprime os algarismos das dezenas e unidades de um inteiro até 99.

Exemplos de execução:

12
um dois 

78
sete oito 

7
zero sete

Sub-problema

Dado o valor de um algarismo de 0–9, imprimir o texto correspondente em português:

0 "zero"
1 "um"
2 "dois"
3 "três"
4 "quatro"
5 "cinco"
6 "seis"
7 "sete"
8 "oito"
9 "nove"

Função auxiliar

Vamos definir uma função auxiliar:

void imprime_algarismo(int a);

A definição da função é longa mas simples: uma sequência de condições if em cascata.

Função auxiliar

void imprime_algarismo(int a) {
  if (a == 0)
    printf("zero");
  else if (a == 1)
    printf("um");
  else if (a == 2)
    printf("dois");
  else if (a == 3)
    printf("três");
  else if (a == 4)
    printf("quatro");

(continua no slide seguinte)


(continuação do slide anterior)

  else if (a == 5)
    printf("cinco");
  else if (a == 6)
    printf("seis");
  else if (a == 7)
    printf("sete");
  else if (a == 8)
    printf("oito");
  else if(a == 9)
    printf("nove");
  else 
    printf("algarismo inválido!");
}

Solução do problema original

Função principal

int main(void) {
  int n, d, u;
  scanf("%d", &n);
  if (!(n>=0 && n<=99)) {
     printf("número inválido\n");
     return -1;  // erro
  }
  u = n % 10;  // unidades (0..9)
  d = n / 10;  // dezenas (0..9)
  imprime_algarismo(d); printf(" ");
  imprime_algarismo(u); printf("\n");
  return 0;      // OK
}

Observações

Conclusão

A decomposição em funções permitiu:

Ciclos

Instruções de ciclos

while

é usado para ciclos em que a expressão é testada antes de executar o corpo do ciclo

do...while

é usado para ciclos em que a expressão é testada depois de executar o corpo

for

é uma forma conveniente para ciclos com uma variável de controlo

Instrução while

while ( expressão ) instrução


Execução:

  1. Primeiro avalia a expressão:
  2. Se for zero, o ciclo termina imediatamente;
    se for diferente de zero, executa instrução e repete 1.

i = 1;
while (i < 10)   /* expressão de controlo */
   i = i * 2;   /* corpo do ciclo */
i = 1;
i < 10? 1 (verdade)
i = i * 2 = 2
i < 10? 1 (verdade)
i = i * 2 = 4
i < 10? 1 (verdade)
i = i * 2 = 8
i < 10? 1 (verdade)
i = i * 2 = 16
i < 10? 0 (falso)

No final: i = 16.

Instrução while

O corpo pode ser um bloco de instruções em vez de uma só:

i = 1;
while (i < 10) {
   printf("%d\n", i);
   i = i * 2;
}

Podemos colocar chavetas mesmo com uma só instrução:

i = 1;
while (i < 10) {
  i = i * 2;
}

Terminação

   while (1) {
      ...   /* ciclo infinito */
   }

Exemplo 1

Limite superior: 5
1        1
2        4
3        9
4       16
5       25

/*
  quadrados.c
  Imprimir uma tabela de quadrados
*/
#include <stdio.h>

int main(void) {
   int i, n;    

   printf("Limite superior: ");
   scanf("%d", &n);
   i = 1;
   while (i <= n) {
      printf("%d\t%d\n", i, i*i);
      i ++;
   }
   return 0;
}

Exemplo 2


/*
  somar.c
  Somar uma sequência de números
*/
#include <stdio.h>

int main(void) {
   int n, soma = 0;
   printf("Introduza valores; 0 termina.\n");
   scanf("%d", &n);      // primeiro valor
   while (n != 0) {  // enquanto não terminou
        soma += n;       // acumular
        scanf("%d", &n); // ler próximo valor
   }
   printf("A soma é: %d\n", soma);
   return 0;
}

Instrução do...while

do instrução while ( expressão );


Execução:

  1. Primeiro executa a instrução
  2. Depois avalia a expressão
  3. Se for zero, o ciclo termina;
    se for diferente de zero, repete o passo 1.

Exemplo

Vamos re-escrever o programa para somar números usando um ciclo do.


/*
  somar2.c
  Somar uma sequência de números (alternativa)
*/
#include <stdio.h>

int main(void) {
   int n, soma = 0;
   printf("Introduza valores; 0 termina.\n");
   do {
       scanf("%d", &n);  // próximo valor
       soma += n;        // acumular
   } while (n != 0);  // enquanto não terminou
   printf("A soma é: %d\n", soma);
   return 0;
}

Observações

Exemplo 3

Calcular o número de algarismos de um inteiro positivo:

Introduza um inteiro positivo: 5633
4 algarismo(s)

/* algarismos.c
   Contar algarismos de um inteiro positivo
*/
#include <stdio.h>

int main(void) {
  int digits = 0, n;
  printf("Inteiro positivo: ");
  scanf("%d", &n);
  do {
     n /= 10;    // quociente divisão por 10
     digits ++;  // mais um algarismo
  } while (n > 0);
  printf("%d algarismo(s)\n", digits);
  return 0;
}

Instrução for

for ( expr1; expr2; expr3 ) instrução

Exemplo:

for (i = 0; i < 10; i++)
  printf("%d\n", i);

Instrução for

Instrução for

Exemplo:

for (i = 0; i < 10; i++)
  printf("%d\n", i);

é equivalente a

i = 0;
while (i < 10) {
  printf("%d\n", i);
  i++;
}

Instrução for

A forma geral

for (expr1; expr2; expr3) instrução

é equivalente a:

expr1;
while ( expr2 ) {
   instrução
   expr3;
}

Esta tradução pode ajudar a compreender alguns detalhes.

Instrução for

Exemplo: qual o efeito de usar ++i em vez de i++ no ciclo seguinte?

for(i = 0; i < 10; ++i)
   printf("%d\n", i);

Traduzindo para while:

i = 0;
while (i < 10) {
   printf("%d\n", i);
   ++i;  // alternativa: i++;
}

Usar ++i ou i++ é equivalente — contamos de 0 a 9.

Formas comuns de for

Repetir n vezes:

// contagem ascendente de 0 até n-1 
for(i = 0; i < n; i++) ...

// contagem ascendente de 1 até n
for(i = 1; i <= n; i++) ...

// contagem descendente de n-1 até 0
for(i = n-1; i >= 0; i--) ...

// contagem descendente de n até 1
for(i = n; i > 0; i--) ...

Erros comuns no for

Omitir expressões no for

Podemos omitir uma ou mais expressões no ciclo for.

Omitir a inicialização:

int i = 10;
for (; i > 0; i--)
   printf("%d\n", i)

Omitir a atualização:

int i;
for (i = 10; i > 0;)
   printf("%d\n", i--)

Omitir expressões no for

Omitir ambas inicialização e atualização:

int i = 10;
for (; i > 0;)
   printf("%d\n", i--)

Isto é equivalente a um ciclo while:

int i = 10;
while (i > 0)
   printf("%d\n", i--)

Omitir condição do for

// usando ciclo while
while (1) {
   ...
}

// usando ciclo for
for (;;) {
   ... 
}

Ciclos for em C99

Ciclos for em C99

for(int i = 0; i < n; i++) {
   printf("%d\n", i); // OK: i é válido
}
printf("%d\n", i); // ERRO: i fora de âmbito
printf("%d\n", n); // OK: n é válido

Instrução break

Instrução break

Exemplo: procurar o primeiro divisor próprio de um número \(n\) (isto é, um divisor entre 2 e \(n-1\)):

int i, n;
... /* obter valor de `n' */
for (i = 2; i < n; i++) {
   if (n%i == 0) break;
}

Este ciclo pode terminar de duas formas:

Instrução break

No final do ciclo podemos determinar a causa de terminação com um teste simples:

int i, n;
... /* obter valor de `n' */
for (i = 2; i < n; i++) {
   if (n%i == 0) break;
}
if (i < n)
   printf("Encontrou divisor: %d\n", i);
else
   printf("Não tem divisores próprios\n");

Instrução break

for(;;) {
  scanf("%d", &n);
  if (n == 0)   // se for zero
     break;     // terminar o ciclo
  ... // se não: processar o valor 
}

Instrução continue

Exemplo

Ler e acumular 10 inteiros não-negativos.

int i = 0, n, soma = 0;
while (i < 10) {
  scanf("%d", &n);
  if (n < 0)
    continue;
  soma += n;
  i ++;
  /* continue salta para aqui */
}

Exemplo

int i = 0, n, soma = 0;
while (i < 10) {
  scanf("%d", &n);
  if (n >= 0) {
     soma += n;
     i ++;
  }
}

Instrução vazia

Uma instrução pode ser vazia, isto é, apenas um ponto-e-vírgula sem mais símbolos:

i = 0; ; j = 1;  // segunda instrução vazia

Uma instrução vazia não faz nada; é útil apenas para escrever um ciclo cujo corpo é vazio.

Instrução vazia

Considere o ciclo para procurar divisores:

for (i = 2; i < n; i++) {
   if (n%i == 0) break;
}

Podemos juntar as duas condições e retirar o break; o corpo fica vazio:

for (i = 2; i < n && n%i != 0; i++)
   ; /* instrução vazia */ 

Para evitar confusão, devemos escrever a instrução vazia numa linha separada.