sábado, julho 09, 2011

Aula 4 - Estruturas de Controle de Fluxo


As estruturas de controle de fluxo são fundamentais para qualquer linguagem de programação. Sem elas só haveria uma maneira do programa ser executado: de cima para baixo comando por comando. Não haveria condições, repetições ou saltos. A linguagem C possui diversos comandos de controle de fluxo. É possível resolver todos os problemas sem utilizar todas elas, mas devemos nos lembrar que a elegância e facilidade de entendimento de um programa dependem do uso correto das estruturas no local certo.

O Comando if

Já introduzimos o comando if. Sua forma geral é:
if (condição) declaração;
A expressão, na condição, será avaliada. Se ela for zero, a declaração não será executada. Se a condição for diferente de zero a declaração será executada. Aqui reapresentamos o exemplo de um uso do comando if :
#include
int main ()
{
      int num;
      printf ("Digite um numero: ");
      scanf ("%d",&num);
      if (num>10)
            printf ("\n\nO numero e maior que 10");
      if (num==10)
        {
            printf ("\n\nVoce acertou!\n");
            printf ("O numero e igual a 10.");
        }
      if (num<10)
            printf ("\n\nO numero e menor que 10");
      return(0);
}

O else
Podemos pensar no comando else como sendo um complemento do comando if. O comando if completo tem a seguinte forma geral:

if (condição) declaração_1;
else declaração_2;


A expressão da condição será avaliada. Se ela for diferente de zero a declaração 1 será executada. Se for zero a declaração 2 será executada. É importante nunca esquecer que, quando usamos a estrutura if-else, estamos garantindo que uma das duas declarações será executada. Nunca serão executadas as duas ou nenhuma delas. Abaixo está um exemplo do uso do if-else que deve funcionar como o programa da seção anterior.
#include
int main ()
{
               int num;
               printf ("Digite um numero: ");
               scanf ("%d",&num);
               if (num==10)
               {
                               printf ("\n\nVoce acertou!\n");
                               printf ("O numero e igual a 10.\n");
               }
               else 
               {
                               printf ("\n\nVoce errou!\n");
                               printf ("O numero e diferente de 10.\n");
               }
               return(0);
}

O if-else-if
A estrutura if-else-if é apenas uma extensão da estrutura if-else. Sua forma geral pode ser escrita como sendo:
if (condição_1) declaração_1;
else if (condição_2) declaração_2;
else if (condição_3) declaração_3;
.
.
.
else if (condição_n) declaração_n;
else declaração_default;
A estrutura acima funciona da seguinte maneira: o programa começa a testar as condições começando pela 1 e continua a testar até que ele ache uma expressão cujo resultado dê diferente de zero. Neste caso ele executa a declaração correspondente. Só uma declaração será executada, ou seja, só será executada a declaração equivalente à primeira condição que der diferente de zero. A última declaração (default) é a que será executada no caso de todas as condições darem zero e é opcional.


Um exemplo da estrutura acima:
#include 
int main ()
{
      int num;
      printf ("Digite um numero: ");
      scanf ("%d",&num);
      if (num>10)
            printf ("\n\nO numero e maior que 10");
      else if (num==10)
        {
            printf ("\n\nVoce acertou!\n");
            printf ("O numero e igual a 10.");
        }
      else if (num<10) 
             printf ("\n\nO numero e menor que 10");
      return(0);
}


A expressão condicional
Quando o compilador avalia uma condição, ele quer um valor de retorno para poder tomar a decisão. Mas esta expressão não necessita ser uma expressão no sentido convencional. Uma variável sozinha pode ser uma "expressão" e esta retorna o seu próprio valor. Isto quer dizer que teremos as seguintes expressões:
 
                int num;
                if (num!=0) ....
                if (num==0) ....
            for (i = 0; string[i] != '\0'; i++)
equivalem a  
                int num;
                if (num) ....
                if (!num) ....
            for (i = 0; string[i]; i++)
Isto quer dizer que podemos simplificar algumas expressões simples.


ifs aninhados
O if aninhado é simplesmente um if dentro da declaração de um outro if externo. O único cuidado que devemos ter é o de saber exatamente a qual if um determinado else está ligado.

Vejamos um exemplo:  
#include 
int main ()
{
      int num;
      printf ("Digite um numero: ");
      scanf ("%d",&num);
      if (num==10)
        {
            printf ("\n\nVoce acertou!\n");
            printf ("O numero e igual a 10.\n");
        }
      else
        {
            if (num>10)
                {
                  printf ("O numero e maior que 10.");
                }
            else
                {
                  printf ("O numero e menor que 10.");
                }
        }
      return(0);
}

O Operador ?

Uma expressão como:
if (a>0)
        b=-150;
else
        b=150;
pode ser simplificada usando-se o operador ? da seguinte maneira:
b=a>0?-150:150;
De uma maneira geral expressões do tipo:
if (condição)
expressão_1;
else
expressão_2;
podem ser substituídas por:
condição?expressão_1:expressão_2;
O operador ? é limitado (não atende a uma gama muito grande de casos) mas pode ser usado para simplificar expressões complicadas. Uma aplicação interessante é a do contador circular.


Veja o exemplo:
#include 
int main()
{
     int index = 0, contador;
     char letras[5] = "Joao";
     for (contador=0; contador < 1000; contador++)
     { 
      printf("\n%c",letras[index]);
      (index==3) ? index=0: ++index; 
     }
} 
O nome Joao é escrito na tela verticalmente até a variável contador determinar o término do programa. Enquanto isto a variável index assume os valores 0, 1, 2, 3, , 0, 1, ... progressivamente.
AUTO AVALIAÇÃO
Veja como você está:
Altere o último exemplo para que ele escreva cada letra 5 vezes seguidas. Para isto, use um 'if' para testar se o contador é divisível por cinco (utilize o operador %) e só então realizar a atualização em index.

O Comando switch

O comando if-else e o comando switch são os dois comandos de tomada de decisão. Sem dúvida alguma o mais importante dos dois é o if, mas o comando switch tem aplicações valiosas. Mais uma vez vale lembrar que devemos usar o comando certo no local certo. Isto assegura um código limpo e de fácil entendimento. O comando switch é próprio para se testar uma variável em relação a diversos valores pré-estabelecidos. Sua forma geral é:
switch (variável)
{
case constante_1:
declaração_1;
break;
case constante_2:
declaração_2;
break;
.
.
.
case constante_n:
declaração_n;
break;
default
declaração_default;
}

Podemos fazer uma analogia entre o switch e a estrutura if-else-if apresentada anteriormente. A diferença fundamental é que a estrutura switch não aceita expressões. Aceita apenas constantes. O switch testa a variável e executa a declaração cujo case corresponda ao valor atual da variável. A declaração default é opcional e será executada apenas se a variável, que está sendo testada, não for igual a nenhuma das constantes.
O comando break, faz com que o switch seja interrompido assim que uma das declarações seja executada. Mas ele não é essencial ao comando switch. Se após a execução da declaração não houver um break, o programa continuará executando. Isto pode ser útil em algumas situações, mas eu recomendo cuidado. Veremos agora um exemplo do comando switch:  
#include 
int main ()
{
      int num;
      printf ("Digite um numero: ");
      scanf ("%d",&num);
      switch (num)
        {
            case 9:
                  printf ("\n\nO numero e igual a 9.\n");
            break;
            case 10:
                  printf ("\n\nO numero e igual a 10.\n");
            break;
            case 11:
                  printf ("\n\nO numero e igual a 11.\n");
            break;
            default:
                  printf ("\n\nO numero nao e nem 9 nem 10 nem 11.\n");
        }
      return(0);
}
AUTO AVALIAÇÃO
Veja como você está.
Escreva um programa que pede para o usuário entrar um número correspondente a um dia da semana e que então apresente na tela o nome do dia. utilizando o comando switch.




O Comando for

for é a primeira de uma série de três estruturas para se trabalhar com loops de repetição. As outras são  whiledo. As três compõem a segunda família de comandos de controle de fluxo. Podemos pensar nesta família como sendo a das estruturas de repetição controlada.
            Como já foi dito, o loop for é usado para repetir um comando, ou bloco de comandos, diversas vezes, de maneira que se possa ter um bom controle sobre o loop. Sua forma geral é:
for (inicialização;condição;incremento) declaração;
O melhor modo de se entender o loop for é ver como ele funciona "por dentro". O loop for é equivalente a se fazer o seguinte:  
inicialização;
if (condição)
{

declaração;
incremento;
"Volte para o comando if"
}  
Podemos ver, então, que o for executa a inicialização incondicionalmente e testa a condição. Se a condição for falsa ele não faz mais nada. Se a condição for verdadeira ele executa a declaração, faz o incremento e volta a testar a condição. Ele fica repetindo estas operações até que a condição seja falsa. Um ponto importante é que podemos omitir qualquer um dos elementos do for, isto é, se não quisermos uma inicialização poderemos omiti-la. Abaixo vemos um programa que coloca os primeiros 100 números inteiros na tela:  
#include 
int main ()
{
    int count;
    for (count=1; count<=100; count++) printf ("%d ",count);
    return(0);
}
Note que, no exemplo acima, há uma diferença em relação ao exemplo anterior. O incremento da variável count é feito usando o operador de incremento que nós agora já conhecemos. Esta é a forma usual de se fazer o incremento (ou decremento) em um loop for.
O for na linguagem C é  bastante flexível. Temos acesso à inicialização, à condição e ao incremento. Qualquer uma destas partes do for pode ser uma expressão qualquer do C, desde que ela seja válida. Isto nos permite fazer o que quisermos com o comando. As três formas do for abaixo são válidas:
for ( count = 1; count < 100 ; count++) { ... }
for (count = 1; count < NUMERO_DE_ELEMENTOS ; count++) { ... }
for (count = 1; count < BusqueNumeroDeElementos() ; count+=2) { ... }
etc ...
Preste atenção ao último exemplo: o incremento está sendo feito de dois em dois. Além disto, no teste está sendo utilizada uma função (BusqueNumeroDeElementos() ) que retorna um valor que está sendo comparado com count.

O loop infinito

O loop infinito tem a forma
for (inicialização; ;incremento) declaração;
Este loop chama-se loop infinito porque será executado para sempre (não existindo a condição, ela será sempre considerada verdadeira), a não ser que ele seja interrompido. Para interromper um loop como este usamos o comando break. O comando break vai quebrar o loop infinito e o programa continuará sua execução normalmente.
Como exemplo vamos ver um programa que faz a leitura de uma tecla e sua impressão na tela, até que o usuario aperte uma tecla sinalizadora de final (um FLAG). O nosso FLAG será a letra 'X'. Repare que tivemos que usar dois scanf() dentro do for. Um busca o caractere que foi digitado e o outro busca o outro caracter digitado na seqüência, que é o caractere correspondente ao . 
#include 
int main ()
{
   int Count;
   char ch;
   printf(" Digite uma letra -  ");
   for (Count=1;;Count++)
   {
     scanf("%c", &ch);
     if (ch == 'X') break;
     printf("\nLetra: %c \n",ch);
     scanf("%c", &ch);
   }
   return(0);
}


O loop sem conteúdo
Loop sem conteúdo é aquele no qual se omite a declaração. Sua forma geral é (atenção ao ponto e vírgula!):
for (inicialização;condição;incremento);




Uma das aplicações desta estrutura é gerar tempos de espera.
O programa
#include 
int main ()
{
      long int i;
      printf("\a");                /* Imprime o caracter de alerta (um beep) */
      for (i=0; i<10000000; i++);  /* Espera 10.000.000 de iteracoes */
      printf("\a");                /* Imprime outro caracter de alerta */
      return(0);
}
faz isto.

AUTO AVALIAÇÃO
Veja como você está.
Faça um programa que inverta uma string: leia a string com gets e armazene-a invertida em outra string. Use o comando for para varrer a string até o seu final.
                                              

O Comando while

O comando while tem a seguinte forma geral:
while (condição) declaração;
Assim como fizemos para o comando for, vamos tentar mostrar como o while funciona fazendo uma analogia. Então o while seria equivalente a:
 if (condição)
{

declaração;
"Volte para o comando if"
}
Podemos ver que a estrutura while testa uma condição. Se esta for verdadeira a declaração é executada e faz-se o teste novamente, e assim por diante. Assim como no caso do for, podemos fazer um loop infinito. Para tanto basta colocar uma expressão eternamente verdadeira na condição. Pode-se também omitir a declaração e fazer um loop sem conteúdo. Vamos ver um exemplo do uso do while.   O programa abaixo é executado enquanto i for menor que 100. Veja que ele seria implementado mais naturalmente com um for ...

#include 
int main ()
{
      int i = 0;
      while ( i < 100)  
      {
            printf(" %d", i);
            i++;
            }
      return(0);
}
O programa abaixo espera o usuário digitar a tecla 'q' e só depois finaliza:  
#include 
int main ()
{
      char Ch;
      Ch='\0';
      while (Ch!='q') 
       {
            scanf("%c", &Ch);
       }
      return(0);

AUTO AVALIAÇÃO
Veja como você está:
Refaça o programa da página anterior. Use o comando while para fechar o loop.

O Comando do-while

A terceira estrutura de repetição que veremos é o do-while de forma geral:  
do
{
declaração;
} while (condição);
Mesmo que a declaração seja apenas um comando é uma boa prática deixar as chaves. O ponto-e- vírgula final é obrigatório. Vamos, como anteriormente, ver o funcionamento da estrutura do-while "por dentro":
declaração;
if (condição) "Volta para a declaração"
Vemos pela análise do bloco acima que a estrutura do-while executa a declaração, testa a condição e, se esta for verdadeira, volta para a declaração. A grande novidade no comando do-while é que ele, ao contrário do for e do while, garante que a declaração será executada pelo menos uma vez.
Um dos usos da extrutura do-while é em menus, nos quais você quer garantir que o valor digitado pelo usuário seja válido, conforme apresentado abaixo:  
#include 
int main ()
{
      int  i;
      do
        {
            printf ("\n\nEscolha a fruta pelo numero:\n\n");
            printf ("\t(1)...Mamao\n");
            printf ("\t(2)...Abacaxi\n");
            printf ("\t(3)...Laranja\n\n");
            scanf("%d", &i); 
        } while ((i<1)||(i>3));
 
      switch (i)
        {
            case 1:
                  printf ("\t\tVoce escolheu Mamao.\n");
            break;
            case 2:
                  printf ("\t\tVoce escolheu Abacaxi.\n");
            break;
            case 3:
                  printf ("\t\tVoce escolheu Laranja.\n");
            break;
        }
      return(0);
}


O Comando break

Nós já vimos dois usos para o comando break: interrompendo os comandos switch e for. Na verdade, estes são os dois usos do comando break: ele pode quebrar a execução de um comando (como no caso do switch) ou interromper a execução de qualquer loop (como no caso do for, do while ou do do while). O break faz com que a execução do programa continue na primeira linha seguinte ao loop ou  bloco que está sendo interrompido.


Observe que um break causará uma saída somente do laço mais interno. Por exemplo:
for(t=0; t<100; ++t)
{
      count=1;
      for(;;)
      {
            printf("%d", count);
            count++;
            if(count==10) break;
      }
}
O código acima imprimirá os números de 1 a 10 cem vezes na tela. Toda vez que o break é encontrado, o controle é devolvido para o laço for externo.
Outra observaçao é o fato que um break usado dentro de uma declaraçao switch afetará somente os dados relacionados com o switch e nao qualquer outro laço em que o switch estiver.

O Comando continue
O comando continue pode ser visto como sendo o oposto do break. Ele só funciona dentro de um loop. Quando o comando continue é encontrado, o loop pula para a próxima iteração, sem o abandono do loop, ao contrário do que acontecia no comando break.  O programa abaixo exemplifica o uso do continue:
#include 
int main()
{
        int opcao;
        while (opcao != 5)
        {
               printf("\n\n Escolha uma opcao entre 1 e 5: ");
               scanf("%d", &opcao);
               if ((opcao > 5)||(opcao <1)) continue;  /* Opcao 
invalida: volta ao inicio do loop */
               switch (opcao)
               
                       case 1: 
                               printf("\n --> Primeira opcao..");
                       break; 
                       case 2: 
                               printf("\n --> Segunda opcao..");
                       break; 
                       case 3: 
                               printf("\n --> Terceira opcao..");
                       break; 
                       case 4: 
                               printf("\n --> Quarta opcao..");
                       break; 
                       case 5: 
                               printf("\n --> Abandonando..");
                       break; 
               
       }
return(0);
}
O programa acima ilustra uma aplicação simples para o continue. Ele recebe uma opção do usuario. Se esta opção for inválida, o continue faz com que o fluxo seja desviado de volta ao início do loop. Caso a opção escolhida seja válida o programa segue normalmente.

O Comando goto

Vamos mencionar o goto apenas para que você saiba que ele existe. O goto é o último comando de controle de fluxo. Ele pertence a uma classe à parte: a dos comandos de salto incondicional. O goto realiza um salto para um local especificado. Este local é determinado por um rótulo. Um rótulo, na linguagem C, é uma marca no programa. Você dá o nome que quiser a esta marca. Podemos tentar escrever uma forma geral:  
nome_do_rótulo:
....

goto nome_do_rótulo;
....
Devemos declarar o nome do rótulo na posição para a qual vamos dar o salto seguido de :. O goto pode saltar para um rótulo que esteja mais à frente ou para trás no programa. Uma observação importante é que o rótulo e o goto devem estar dentro da mesma função. Como exemplo do uso do goto vamos reescrever o equivalente ao comando for apresentado na seção equivalente ao mesmo:  
inicialização;
início_do_loop:
if (condição)
{
declaração;
incremento;
goto início_do_loop;
}  
O comando goto deve ser utilizado com parcimônia, pois o abuso no seu uso tende a tornar o código confuso. O goto não é um comando necessário, podendo sempre ser substituído por outras estruturas de controle. Recomendamos que o goto nunca seja usado.
Existem algumas situações muito específicas onde o comando goto pode tornar um código mais fácil de se entender se ele for bem empregado. Um caso em que ele pode ser útil é quando temos vários loops e ifs aninhados e se queira, por algum motivo, sair destes loops e ifs todos de uma vez. Neste caso um goto resolve o problema mais elegantemente que vários breaks, sem contar que os breaks exigiriam muito mais testes. Ou seja, neste caso o goto é mais elegante e mais rápido.

O exemplo da página anterior pode ser reescrito usando-se o goto:
#include 
int main()
{
      int opcao;
      while (opcao != 5)
            {
            REFAZ: printf("\n\n Escolha uma opcao entre 1 e 5: ");
                  scanf("%d", &opcao);
                  if ((opcao > 5)||(opcao <1)) goto REFAZ;  /* Opcao invalida:
volta ao rotulo REFAZ */
                  switch (opcao)
            
                  case 1: 
                               printf("\n --> Primeira opcao..");
                  break; 
                  case 2: 
                               printf("\n --> Segunda opcao..");
                  break; 
                  case 3: 
                               printf("\n --> Terceira opcao..");
                  break; 
                  case 4: 
                               printf("\n --> Quarta opcao..");
                  break; 
                  case 5: 
                               printf("\n --> Abandonando..");
                  break; 
            
            }
      return(0);
}

AUTO AVALIAÇÃO
Escreva um programa que peça três inteiros, correspondentes a dia , mês e ano. Peça os números até conseguir valores que estejam na faixa correta (dias entre 1 e 31, mês entre 1 e 12 e ano entre 1900 e 2100). Verifique se o mês e o número de dias batem (incluindo verificação de anos bissextos). Se estiver tudo certo imprima o número que aquele dia corresponde no ano. Comente seu programa.
PS: Um ano é bissexto se for divisível por 4 e não for divisível por 100, exceto para os anos divisíveis por 400, que também são bissextos.