Você está na página 1de 15

3 Estruturas de controlo

3.1 Instruções simples e estruturadas 3.3 Instruções repetitivas


3.2 Instruções de selecção 3.3.1 Instrução while
3.2.1 Instrução if-else 3.3.3 Instrução do-
while
3.2.2 Expressões lógicas
3.3.2 Incremento, decremento e atribuição composta
3.2.3 Utilização do if-else
3.3.4 Instrução for
3.2.4 Instrução switch 3.4
Estruturas embutidas

© Copyright J.P. Marques de Sá - 2000

3.1 Instruções simples e estruturadas

instrução composta
instruções de selecção
instruções repetitivas

3.2 Instruções de selecção

3.2.1 Instrução if-else

A instrução if-else (se-então-senão) permite uma selecção de duas alternativas disjuntas (selecção
dicotómica) de execução das instruções A e B (B pode não existir: instrução vazia), de acordo com o
valor lógico de uma expressão (condição) C, conforme ilustra o diagrama de fluxo seguinte.

Em C a instrução se-então-senão ("if-else statement") tem a seguinte sintaxe:

if (condição)
instrução1;
[else
instrução2;]

A condição (que se escreve sempre rodeada de parêntesis curvos) é


uma expressão lógica de cuja avaliação resulta um valor verdadeiro
ou falso.
A parte else pode ou não existir.
É aconselhável marginar a escrita das instruções da forma indicada
a fim de aumentar a clareza do programa.

Exemplo 3.1
Determinar se um inteiro lido é par ou ímpar.

#include <stdio.h>
main()
{
int n;

printf("Escreva um número inteiro\n");


scanf("%d", &n);

if (n % 2 == 0) /* condição */
printf("O número é par\n");
else
printf("O número é ímpar\n");
}

Notar a condição (entre parêntesis) n % 2 == 0. Trata-se de uma expressão lógica (ou Booleana)
expressa através do operador de igualdade == (não confundir com a atribuição de valor, =). A
avaliação de uma expressão lógica resulta num valor verdadeiro ou falso.

3.2.2 Expressões lógicas

As expressões lógicas constroiem-se em C com:


• Operadores relacionais
• Operadores lógicos
Operadores relacionais:

Operador
Significado
relacional
< menor que
<= menor ou igual que
> maior
>= maior ou igual que
== igual a
!= diferente de

Exemplos (assume-se int a = 10, b = -3):


Expressão Resultado
a >= b verdadeiro
(a + 2*b) ==
falso
0
a+b != 0 verdadeiro

Operadores lógicos:

Operador lógico Significado


&& E
|| OU
! NÃO

Exemplos (assume-se int a = 10, b = -3):

Expressão Resultado
a != 0 && b < 0 verdadeiro
a !=0 && b = 0 falso
!(a >= 0) falso
a >=0 || ! (b != 0) verdadeiro

Em C não existe um tipo Booleano de dados. Qualquer valor numérico diferente de zero é
interpretado como verdadeiro; zero é interpretado como falso.

Exemplo 3.2
Interpretar o seguinte programa.

#include <stdio.h>
main()
{
int x = 2;
if (x)
printf("Isto é escrito\n");
else
printf("Isto não é escrito");
}

Exemplo 3.3
Determinar se um inteiro lido é positivo e par.

#include <stdio.h>
main()
{
int x;
printf("Escreva um número inteiro:");
scanf("%d",&x);

if (x >= 0 && x % 2 == 0)
printf("O número é positivo e par\n");
else
printf("O número é negativo ou ímpar\n");
}

Regras de precedência:

Operador
!
< <= > >=
== !=
&&
||

Em caso de dúvida usar parêntesis.

3.2.3 Utilização do if-else

Exemplo 3.4

#include <stdio.h>
#include <math.h>
main()
{
/* calcula maior raiz de ax^2 + bx + c = 0, a>=0 */
float a, b, c;
double discr;

printf("Escreva os valores de a, b e c:");


/* supõe-se a>=0 */
scanf("%f %f %f",&a, &b, &c);
/* entradas separadas por espaços, tabs ou linhas */

discr = b*b - 4*a*c ;

if (discr < 0)
printf("Raízes complexas !\n")
else
printf("Maior raiz = %E\n", (-b+sqrt(discr))/(2*a) );
}

Notas:
• A inclusão da header file math.h permite a utilização de várias funções matemáticas tais
como: sin, cos, atan, exp, log, fabs (valor absoluto de real), sqrt.
• Por forma a incluir o código destas funções é necessário efectuar uma "linkagem" com a
respectiva biblioteca. Isto é feito com a opção -lm; assim, se o programa for guardado em
pol2.c a compilação será feita com:
gcc pol2.c -lm -o pol2.out
• Podem utilizar-se outras funções úteis (tal como abs, valor absoluto de um inteiro) existentes
na biblioteca standard <stdlib.h>, sem necessitar de qualquer menção especial no código
ou na compilação. Outras implementações de C exigem a directiva #include
<stdlib.h>.
• Podemos inspeccionar a forma de utilizar uma função usando o comando man do Linux.
Assim, p. ex., man sqrt informar-nos-á de que a função sqrt tem um argumento de tipo
double e fornece um valor de tipo double.
Na descrição sintáctica de if-else (ou de qualquer outra instrução de controlo) podemos sempre,
onde se menciona instrução, usar uma instrução composta em vez de uma instrução simples.

Uma instrução composta, ou bloco, é um conjunto de instruções


delimitado por {} que é encarado como se de uma única instrução se
tratasse.

Exemplo 3.5

#include <stdio.h>
#include <math.h>
main()
{
/* Calcula raizes de ax^2 + bx + c = 0 */
float a, b, c;
double discr, x1, x2;
printf("Escreva os valores de a, b e c:");
scanf("%f%f%f",&a, &b, &c);

discr = b*b - 4*a*c ;

if (discr < 0)
printf("Raizes complexas !\n")
else
{ /* bloco de instruções */
discr = sqrt(discr);
x1 = (-b + discr) / (2*a);
x2 = (-b - discr) / (2*a);
printf("Raizes : %E , %E\n", x1, x2);
}
}

A utilização de instruções estruturadas em sucessivas menções de instrução conduz ao chamado


embutimento ou encaixe de estruturas.
Se no embutimento de if-else's o número de else's for inferior ao número de if's considera-se
que cada else se refere ao precedente if:

...
int a, b , c;
if (a > b)
if (b > c)
printf("a > b > c\n");
else
printf("a > b; c >= b\n");

Desejando, podemos usar chavetas para impôr a ordem de avaliação:

...
if (a > b)
{
if (b>c)
printf("a > b > c\n");
}
else
printf("b >= a\n");

3.2.4 Instrução switch

A instrução switch (comutador) permite lidar de forma eficiente com selecções não dicotómicas.
Consiste numa enumeração de instruções alternativas, conforme o valor de uma expressão:

switch (expressão)
{
case constante1: instruções1;
case constante2: instruções2;
...
case constanteN: instruçõesN;
[default: instruções;]
}
• A expressão pode ser do tipo char, int ou long.
• Depois de avaliada remeterá para o caso de valor constante correspondente (do mesmo tipo)
sendo assim executada a respectiva instrução (simples ou bloco) bem como as que se seguem
até terminar a chaveta final do switch.
• Para sair do switch usa-se a instrução break.
• A parte opcional default toma conta dos valores não enumerados em case.
Exemplo 3.6

#include <stdio.h>
main()
{
/* Escreve o resultado c, de a operado com b;
aceita apenas os operadores + e * (ou x) */
float a, b, c;
char op;

printf("Escreva 2 nºs reais:"); scanf("%f%f", &a, &b);


printf("Escolha um operador (+, *, x): ");
scanf("%c", &op);

switch (op)
{
case '+' : c = a + b; printf("c = %f\n", c); break;
case '*' :
case 'x' : c = a * b; printf("c = %f\n", c); break;
default: printf("Operador inválido !\n");
}
}

A selecção de alternativas deste exemplo corresponde ao seguinte diagrama de fluxo:

A instrução switch pode implementar-se à custa de múltiplas selecções dicotómicas embora, em


geral, com menor eficiência.

© Copyright J.P. Marques de Sá - 2000

3.3 Instruções repetitivas

• while
• do-while
• for
3.3.1 Instrução while

A instrução while (enquanto-faça) implementa um laço condicional, no qual a repetição de uma


instrução depende de uma condição.

while (condição_C)
instrução_A;
Exemplo 3.7
Escrever os primeiros dez números naturais.

Algoritmo Implementação em C
#include <stdio.h>
main()
{
inicializar n=1;
int n = 1;
while (n <=10)
enquanto n for <=10 fazer:
{
• escrever o valor de n; printf("%d\n", n);
• incrementar n de 1; n = n + 1;
}
}

Exemplo 3.8
Escrever um programa que leia números inteiros a partir do teclado e acumule a some dos
números positivos. O programa termina se o número lido for zero e nessa altura escreve a soma.

Algoritmo Implementação em C
#include <stdio.h>
inicializar soma corrente com main()
0; {
ler primeiro número, n; int n, soma = 0;
scanf("%d", &n);
while (n != 0)
enquanto n for ! = 0 fazer:
{
• Se n > 0 acumule à if (n > 0)
soma; soma = soma + n;
• ler próximo número, n; scanf("%d", &n);
}
escrever o valor da soma. printf("%d\n", soma);
}

Exemplo 3.9
Escrever um programa que determine e escreva no ecrã quantas vezes um determinado número
lido é divisível por 2.

Algoritmo Implementação em C
inicializar n_vezes com 0; #include <stdio.h>
ler o número n; main()
{
int n, n_vezes = 0;
enquanto n for divisível por
printf("Escreva o número:");
2 fazer:
scanf("%d", &n);
• incrementar while (n % 2 == 0 && n != 0)
n_vezes; {
• actualizar n para o n_vezes = n_vezes +1:
seu n = n / 2;
quociente de div. por }
2; printf("%d\n", n_vezes);
}
escrever o valor de
n_vezes.

Exemplo 3.10
Escrever um programa que determine o máximo de uma sequência de valores inteiros lidos do
teclado, terminada com zero, assinalando a posição em que esse máximo ocorreu.

Algoritmo Implementação em C
#include <stdio.h>
main()
{
int n, max, pos_corr = 1,
inicalizar pos_corr=1 e pos_max = 1;
pos_max=1;
printf("Escreva o 1.o
ler o primeiro número n; número:");
scanf("%d", &n);
inicializar max=n;
max = n;
enquanto n for !=0 fazer:
while (n != 0)
• ler próximo número,
{
n;
printf("Escreva novo
• incrementar pos_corr número:");
de 1; scanf("%d", &n);
• se n > max então: pos_corr = pos_corr + 1;
actualizar max=n; if (n > max)
actualizar {
pos_max=pos_corr max = n;
pos_max = pos_corr;
escrever o valor de max, e }
pos_max. }
printf("O máximo é %d e
ocorreu na posição %d\n", max,
pos_max);
}

Notar que este algoritmo usa extensivamente a ideia de actualização de valor, já referida aquando da
instrução if-else e que tem uma aplicação muito frequente em algoritmia. Podemos por analogia
considerar max uma marca de um depósito de água que se move para cima sempre que a água no
depósito sobe.

Exemplo 3.11
Escrever um programa que determine o factorial, n!, de um número inteiro n >=0. Por
definição n!=n.(n-1)! e 0!=1!=1.

Algoritmo Implementação em C
inicializar fact com 1; #include <stdio.h>
ler o número n; main()
inicializar termo corrente {
com n; int n, termo;
long fact = 1;
enquanto termo for > 1 printf("Escreva o número:");
fazer: scanf("%d", &n);
• fact = fact *
termo; termo = n;
• termo = termo -1; while (termo > 1)
escrever o valor de fact. {
fact = fact * termo:
termo = termo - 1;
}
printf("factorial=%d\n", fact);
}

Notar que, neste exemplo, para n=0, 1 o programa calcula correctamente (por definição de factorial)
fact=1. Importa declarar fact como long (melhor ainda unsigned long) dado que a função fact
cresce rapidamente.

Notar nos anteriores exemplos:

Há situações em que a priori não se conhece quantas vezes o laço irá


executar (exemplos 3.7 e 3.11). Noutras sabe-se quantas vezes irá
executar (exemplos 3.8, 3.9 e 3.10)
Podemos ter embutimento de estruturas a um nível aribitrariamente
grande de embutimento. No exemplo 3.10 temos um if-else
embutido dentro de um laço while.
Frequentemente, em laços, é necessário incrementar ou decrementar
de uma unidade o valor de certas variáveis (ver exemplos). Para tal
finalidade o C dispõe de operadores especiais.

3.3.2 Incremento, decremento e atribuição composta

É frequente, em laços, usar-se incrementos (x=x+1) ou decrementos (x=x-1) tal como nos exemplos
anteriores. A linguagem C dispõe de operadores de incremento (++) e decremento (--) para este
efeito.

Exemplo 3.12
Exemplo Significado
x++; (ou
x = x + 1;
++x;)
x--; (ou --
x = x - 1;
x;)
y = x; x = x +
y = x++;
1;
y = ++x; x = x + 1; y = x;

Atribuição composta:
Sempre que temos uma atribuição do tipo:
var = var op (expressão)

podemos substituir esta atribuição pela chamada atribuição composta:

var op = expressão

Exemplo 3.13

Atribuição composta Significado


soma = soma + n /* Exemplo
soma += n
3.8 */
y -= 2*x+3 y = y - (2*x+3)
k /= j +1 k = k/(j+1)
r %= 2 r = r % 2

3.3.3 Instrução do-while

do
instrução_A;
while (condição_C);

Assim, a instrução é sempre executada pelo menos uma vez.

Exemplo 3.14
Implementação alternativa do Exemplo 3.11:

Algoritmo Implementação em C
#include <stdio.h>
main()
{
int n, termo;
inicializar fact com 1; long fact = 1;
printf("Escreva o número:");
ler o número n;
scanf("%d", &n);
inicializar termo corrente
com n;
termo = n;
do
enquanto termo for > 1 fazer:
{
• fact = fact * termo; fact *= termo;
• termo = termo -1; termo--;
escrever o valor de fact. }
while (termo > 1);
printf("factorial=%d\n",
fact);
}
A diferença face à solução anterior é que agora dá o resultado errado fact=0 para n=0, consequência
da execução de (pelo menos uma vez) da 1ª instrução do laço.

Podemos forçar o fim de um laço usando a instrução break (já referida na instrução switch).
Assim, resolveríamos o problema anterior inserindo a seguinte instrução em do-while:

...
termo = n;
do
{
if (termo == 0) break;
fact *= termo:
termo--;
}
while (termo > 1);

3.3.4 Instrução for

for
(inicializações; condição_C; pós-instrução)
instrução_A;

• Inicialmente é executado (uma única vez) o código de inicializações, que corresponde


normalmente a inicializar variáveis controladoras do laço com determinados valores.
• A condição C é avaliada. Se for falsa, o laço é interrompido.
• Enquanto a condição C for verdadeira é executada a instrução A (pode ser um bloco) e a
seguir a pós-instrução que normalmente corresponde a actualizar as variáveis controladoras
do laço.
Exemplo 3.15
Implementar o Exemplo 3.7 com um laço for.

Algoritmo Implementação em C
#include <stdio.h>
inicializar n=1;
main()
{
enquanto n for <=10 fazer:
int n;
• escrever o valor de for (n=1; n <= 10; n++)
n; printf("%d\n", n);
• incrementar n de 1; }

Um laço for é, portanto, sempre redutível a um laço while:


inicializações; /* inicialização de variáveis */
while (condição_C);
{
instrução_A;
pós-instrução; /* actualização de variáveis (ou outras
instruções)*/
}

Exemplo 3.16
Escrever um programa que gere uma tabela de 10 quadrados e cubos dos naturais, a partir e
com incrementos de respectivamente de 2 e 3.

#include <stdio.h>
main()
{
int i, x, y;
for (i=1, x=2, y=3; i <= 10; i++, x += 2, y += 3)
printf("Quad. de %d=%d, Cubo de %d=%d\n", x, x*x, y, y*y*y);
}

Exemplo 3.17
Escrever um programa que gere uma tabela das primeiras 20 potências de 2.

Algoritmo Implementação em C
inicializar n=1; (contador do #include <stdio.h>
laço) main()
inicializar var. corrente pot2 {
com 1 (= 2^0); int n;
float pot2 = 1.0;
enquanto n for <=20 fazer: for (n=1; n <= 20; n++)
• actualizar pot2 {
(multiplicando por 2 o pot2 *= 2.0;
seu valor anterior); printf("2^%d = %f\n", n,
• Escrever o valor de pot2);
pot2; }
• Incrementar n de 1; }

© Copyright J.P. Marques de Sá - 2000

3.4 Estruturas embutidas

Conforme já referido em 3.2.3 podemos embutir quaisquer estruturas a um nível arbitrariamente


grande desde que qualquer estrutura interior fique totalmente embutida na que a precede.

O uso de marginação (e.g. com TABs) é fundamental para a clareza do programa:


for (...)
{
...
if (...)
{
...
while (...)
{
switch (...)
{
...
}
...
}
}
...
}

Exemplo 3.20
Escrever um programa que simule sucessivas vezes a experiência de atirar um dado ao ar,
usando a função standard (<stdlib>) de geração de números aleatórios rand(). A simulação
deve parar quando saírem dois seis consecutivos. Nesse momento devem ser escritas no ecrã quantas
subsequências contínuas de valores <=3 foram encontradas.

A função rand() fornece sempre um valor inteiro longo, pseudo-aleatório entre 0 e um certo valor
constante (RAND_MAX). A fim de obter um valor inteiro x em [1, k], teremos que usar: x = 1 +
rand() % k. Para obter um valor real em [0,1] usar (float) rand()/RAND_MAX.

Consideremos uma sequência possível de valores:

A fim de detectar as subsequências precisamos de uma variável auxiliar que indique quando estamos
dentro de uma subsequência. Designemos essa variável lógica por seq (0= falso; 1 (ou qualquer valor
diferente de 0)= verdadeiro). A variável nseq contará o número de subsequências detectadas. A
condição de paragem usa também uma variável lógica stop.

Algoritmo:
O aluno deverá notar que a determinação de uma solução algorítmica segue habitualmente uma
abordagem estruturada top-down, que parte das estruturas mais exteriores procurando especificar as
mais interiores.
Perante o enunciado anterior, teremos, seguindo do "exterior" para o "interior":

1 - Inicializar seq, nseq e stop com zero.


2 - Obter o 1º número aleatório, fp ("face precedente").
3 - Se fp<=3 inicia-se uma subsequência, logo seq=1, nseq=1.
4 - Enquanto não for detectada a condição de paragem, fazer:
a) Obter próximo número, fa ("face actual").
b) Se estamos dentro de uma subsequência e fa>3 então esta termina: seq=0.
c) Se não estamos dentro de uma subsequência e fa<=3, então inicia-se uma nova subsequência:
seq=1 e incrementa-se o contador nseq.
d) Se ambos fp e fa são iguais a 6 activa-se a condição de paragem: stop=1.
e) O valor precedente fp é actualizado para o valor actual fa.

Implementação em C:

#include <stdio.h>
main()
{
int fp, fa; /* fp: face precedente; fa: face actual */
int seq=0, nseq=0; /* seq: subsequência "ON";
nseq: nº de subseq. */
int stop=0; /* condição de paragem */

fp = 1 + rand()%6; printf("%d, ", fp);


if (fp<=3) {
seq=1; nseq++;
}

do {
fa=1+rand()%6; printf("%d, ", fa);
if (seq && fa > 3) seq=0;
if (!seq && fa <= 3) {
seq=1; nseq++;
}
if (fp==6 && fa==6) stop=1;
fp=fa;
}
while (!stop);
printf("\nNº de subsequências: %d\n", nseq);
}

Nota: Frequentemente é necessário gerar números aleatórios num certo intervalo, e.g. [0,1]. Efectua-
se isto como ilustra o seguinte programa:

#include <stdio.h>
#include <stdlib.h> /* contém a definição de RAND_MAX */

main()
{
int i, float x;
for (i=0; i<20; i++) {
x=rand();
printf("%f\n", x/RAND_MAX);
}
}

Você também pode gostar