Vamos por partes. Qualquer laço pode ser reescrito usando um if e um comando goto, de fato, isso é implicitamente feito pelo compilador. Portanto, o que vou explicar é válido para TODAS as estruturas de controle de fluxo.
Por exemplo, se quiséssemos re-escrever um while teríamos algo como:
label: if(x) {
// código a ser executado...
goto label;
}
Deixo como exercício pensar como reescrever as demais estruturas (for e do-while). No entanto, é sempre bom salientar: não torne o uso do goto uma prática habitual! Quando mal utilizado, tende a gerar código espaguete. Ele tem seus usos, este não é um deles.
Agora que reduzimos o problema ao if, vamos entender como o C verifica se algo é verdadeiro ou falso. Teste o código abaixo:
#include <stdio.h>
#define OP_FUNCTOR(NAME, OP) int NAME(int a, int b) { return a OP b; }
OP_FUNCTOR(maior,>)
OP_FUNCTOR(maiorOuIgual,>=)
OP_FUNCTOR(menor,<)
OP_FUNCTOR(menorOuIgual,<=)
OP_FUNCTOR(igual,==)
OP_FUNCTOR(difente,!=)
int print(const char* const toString, int (* const apply)(int, int)) {
static const int x = 1, y = 1;
int z = apply(x, y);
return printf("%10s : %d %2s %d -> z = %d\n",
z ? "VERDADEIRO" : "FALSO",
x,
toString,
y,
z);
}
int main(int argc, char *argv[]) {
const struct {
const char* const toString;
int (* const apply)(int, int);
} op[] = {
{ ">", maior},
{">=", maiorOuIgual},
{"==", igual},
{"!=", difente},
{"<=", menorOuIgual},
{ "<", menor}
};
for(int i = 0; i < 6; i++)
print(op[i].toString, op[i].apply);
return 0;
}
Em C não existem tipos booleanos, toda comparação retorna um inteiro. Sempre que for verdadeira, retorna 1, de outra forma, 0. Porém, qualquer valor diferente de 0 é verdadeiro. Apenas para dar um exemplo mais abrangente, a definição da macro NULL é a seguinte:
#define NULL ((void*) 0);
Portanto, com base no que eu disse até agora, qual o trecho de código será executado?
void *x = NULL;
if(x) {
// Código A...
} else {
// Código B...
}
Se escolheu a opção dentro do bloco else, acertou! Agora, o que acontece se fazemos o seguinte?
if(x = funcao()) {
// Código A...
} else {
// Código B...
}
Uma atribuição retorna o valor lógico do rvalue. O código acima funciona devido ao side-effect. O código é confuso, essa prática é, no melhor dos casos, de gosto duvidoso, pois torna o código menos legível devido a dependências obscuras. No pior dos casos, porém, revela um erro de sintaxe. Qual a intenção aqui? Atribuir o valor de retorno de funcao a x, ou verificar se x é igual a esse valor? Por este motivo, o compilador emite um warning! O compilador solicitará que a expressão seja envelopada em parênteses. O código ficará assim:
if((x = funcao())) {
// Código A...
} else {
// Código B...
}
Pronto! Agora ficou claro para o compilador que queremos atualizar o valor de x, e então testar esse novo valor. Vamos apenas adicionar a seguinte comparação, que, ainda que desnecessária, torna o código mais legível:
if((x = funcao()) != NULL) {
// Código A...
} else {
// Código B...
}
Nossa intenção é clara, tanto para o compilador quanto para os que forem ler o código. Adicionamos a informação de que x é um ponteiro, pois está sendo comparado com NULL, e tornamos a semântica dos blocos if e else mais rica, pois sabemos exatamente qual é o fluxo principal, e qual o de exceção.
Em nosso caso concreto:
while((c = c/t) != 0)
Estamos atualizando o valor de c pelo resultado da divisão de c por t, e verificando se esse resultado é diferente de 0. Poderíamos ainda usar a versão simplificada da expressão:
while((c /= t) != 0)
Como c e t são variáveis inteiras, contanto que o divisor seja diferente de 1, é garantido que o laço termina.
Ex.: a divisão inteira 1 / 2 resulta em 0.
Espero ter ajudado, qualquer dúvida remanescente, não hesite em perguntar.