Lógica de Programação

Operações com Variáveis e Condicionais

Professor: Gabriel Soares Baptista

Operações com variáveis

Nesta aula, exploraremos as diversas operações que podem ser realizadas com variáveis na linguagem C.

Objetivos de Aprendizado
  • Definir o valor contido em uma variável.
  • Realizar operações matemáticas e de comparação.
  • Realizar operações lógicas e em nível de bits (bitwise).
  • Conhecer as operações simplificadas.
  • Saber a ordem de precedência em que as operações são processadas.

O operador de atribuição "="

Uma das operações mais fundamentais na programação é a atribuição. Sua função é armazenar um determinado valor em uma variável.

nome_da_variável = expressão;
  • Uma expressão pode ser:
    • Valores diretos.
    • Variáveis ou constantes.
    • Chamadas de funções ou cálculos matemáticos.
    • Condição: O resultado final deve ser do mesmo tipo de dado da variável destino.

Regra de Direção

O fluxo de dados da atribuição ocorre sempre da direita para a esquerda. O computador calcula a expressão à direita do "=" e armazena o resultado na variável à esquerda.

Regra de Direção

O computador avalia primeiro o que está à direita do sinal de "=" e atribui o resultado à variável da esquerda. Tentar realizar o inverso resultará em erro de compilação.

// Correto: recebe o resultado da soma
x = y + 5;

// Correto: recebe o valor constante
x = 5;
// ERRADO: não se atribui a uma expressão
y + 5 = x; 

// ERRADO: não se atribui a uma constante
5 = x;

Múltiplas Atribuições e Conversão de Tipos

A linguagem C suporta múltiplas atribuições em uma única linha, onde o valor é copiado em cadeia (da direita para a esquerda):

x = y = z = 5;

Conversão e Perda de Informação

A linguagem permite a atribuição entre tipos básicos distintos, realizando conversão automática.

Atenção à Conversão de Tipos

Ao atribuir um número real (float) a uma variável inteira (int), apenas a parte inteira será preservada, descartando-se toda a precisão decimal (truncamento).

Operadores Aritméticos

Os operadores atuam sobre valores numéricos, resultando sempre em um novo valor numérico.

OperadorSignificadoExemplo
+Adição de dois valoresz = x + y
-Subtração de dois valoresz = x - y
*Multiplicação de dois valoresz = x * y
/Quociente (divisão) de dois valoresz = x / y
%Resto de uma divisão inteiraz = x % y

Precedência, Parênteses e Operadores Unários

Precedência

As operações de multiplicação (*), divisão (/) e resto (%) são executadas sempre antes da adição (+) e da subtração (-).

  • Utilize parênteses para forçar a execução prioritária de um cálculo.
    • z = x * y + 10; (Multiplica, depois soma)
    • z = x * (y + 10); (Soma, depois multiplica)

Operadores Unários

Os sinais de + e - podem atuar sobre um único valor para inversão de sinal (ex: x = -y;).

Divisão e o Operador de Resto

  • Se ambos os números na divisão forem inteiros, o compilador retorna apenas a parte inteira, descartando casas decimais.
  • Para obter um resultado real, ao menos um dos números deve ser tratado como ponto flutuante (ex: 5 / 4.0).
Regra do Operador de Resto

O operador de resto da divisão (%) é exclusivo para tipos de dados inteiros, como int e char. Tentar utilizá-lo com números reais (float ou double) causará um erro.

Operadores Relacionais

Utilizados para verificar a relação de magnitude e a igualdade entre valores. Eles retornam um valor lógico (booleano): UM (1) para verdadeiro ou ZERO (0) para falso.

OperadorSignificadoExemplo
>Maior do quex > 5
>=Maior ou igual ax >= 10
<Menor do quex < 5
<=Menor ou igual ax <= 10
==Igual ax == 0
!=Diferente dex != 0

Erros Comuns de Sintaxe

Erros Comuns de Sintaxe

Não existem os operadores "=<", "=>" ou "<>". Os símbolos devem seguir a ordem estrita: <= e >=. Utiliza-se obrigatoriamente != para representar "diferente de".

O maior erro: Atribuição vs Comparação

Não confunda o operador de atribuição = com o operador de teste de igualdade ==.

// Errado para testar igualdade: atribui o valor 4 a x
if(x = 4) 

// Correto: testa se x é igual a 4
if(x == 4)

Operadores Lógicos

Permitem combinar múltiplas expressões relacionais em uma única estrutura (ex: $0 < x < 10$). Retornam 1 (verdadeiro) ou 0 (falso).

OperadorSignificadoComportamento
&&Operador E (AND)Verdadeiro apenas se todas as condições forem verdadeiras.
||Operador OU (OR)Verdadeiro caso qualquer uma das condições for verdadeira.
!Operador NEGAÇÃO (NOT)Inverte o estado lógico (verdadeiro vira falso, falso vira verdadeiro).

Tabela-Verdade

Resumo das combinações possíveis para os operadores lógicos, onde a e b representam os resultados de expressões relacionais (0 para falso, 1 para verdadeiro).

ab!a!ba && ba || b
001100
011001
100101
110011

Questões de fixação - 1

1. O operador de atribuição (=) é fundamental na linguagem C. Analise o trecho de código abaixo e identifique qual das instruções gera um erro de compilação, justificando sua resposta com base na "Regra de Direção" apresentada no texto.

int main() {
    int a = 10;
    int b = 20;
    int c;

    c = a + b;      // Instrução I
    a = 5;          // Instrução II
    a + b = c;      // Instrução III
    c = 15;         // Instrução IV
    
    return 0;
}

Questões de fixação - 2

2. A divisão em C pode se comportar de maneira diferente dependendo dos tipos de dados envolvidos. Considere o código a seguir:

#include <stdio.h>

int main() {
    float x, y;
    x = 7 / 2;
    y = (float) 7 / 2;
    
    printf("x = %f\n", x);
    printf("y = %f\n", y);
    return 0;
}

Sem executar o código, determine qual será a saída exata impressa para x e y. Explique por que o valor de x pode não ser o esperado (3.5), mencionando o conceito de divisão inteira e perda de precisão.

Questões de fixação - 3

3. Analise a expressão abaixo considerando a tabela de precedência de operadores: int x = 10 + 5 * 2 < 25 && 4 != 5;

Qual será o valor final armazenado em x (0 ou 1)? Descreva o passo a passo da resolução que o computador faria (ex: qual conta é feita primeiro, qual comparação vem depois, etc.).

Operadores bit a bit (bitwise)

O computador representa números em formato binário (0s e 1s). Esses operadores manipulam cada um desses bits individualmente, essenciais para programação em baixo nível.

Restrição de Tipos

Estes operadores só podem ser aplicados aos tipos char, int e long. Não funcionam com tipos de ponto flutuante (float e double).

OperadorSignificadoExemplo
~Complemento (inversão)~x
&E (AND) bit a bitx & 167
|OU (OR) bit a bitx | 167
^OU Exclusivo (XOR)x ^ 167
<<Deslocamento à esquerdax << 2
>>Deslocamento à direitax >> 2

Operador de Complemento e Lógicos Bit a Bit

  • Complemento (~): Inverte todos os bits (0 vira 1, 1 vira 0).
  • E (&): Resulta 1 apenas se os dois bits comparados forem 1.
  • OU (|): Resulta 1 se ao menos um dos dois bits for 1.
  • OU Exclusivo (^): Resulta 1 apenas se os bits forem diferentes.

Demonstração E (&):

  • 00101100 (44)
  • 10100111 (167)
  • 00100100 (Resultado: 36)

Demonstração XOR (^):

  • 00101100 (44)
  • 10100111 (167)
  • 10001011 (Resultado: 139)

Operadores de Deslocamento (<< e >>)

Movem o conjunto de bits para a esquerda ou direita por N posições.

  • x << N: Desloca à esquerda. Multiplica o número por $2^N$.
  • x >> N: Desloca à direita. Realiza divisão inteira por $2^N$.

Exemplo (x = 44 ou 00101100):

  • x << 2: 10110000 (176)
  • x >> 2: 00001011 (11)

Operadores de atribuição simplificada

Tornam o código mais conciso ao combinar uma operação com a atribuição do resultado à mesma variável.

OperadorSignificadoExemploEquivalente a
+=Soma e atribuix += yx = x + y
-=Subtrai e atribuix -= yx = x - y
*=Multiplica e atribuix *= yx = x * y
/=Divide e atribui quocientex /= yx = x / y
%=Divide e atribui restox %= yx = x % y

(Também aplicáveis aos operadores bitwise: &=, |=, ^=, <<=, >>=)

Precedência na Atribuição Simplificada

Atenção à Precedência

Ao usar atribuição simplificada, o compilador avalia toda a expressão à direita do operador antes de realizar a operação combinada (como se estivesse entre parênteses).

Escrita Convencional:

int x = 10, y = 20;

// x = 10 - 5 + 20 = 25
x = x - 5 + y; 

Atribuição Simplificada:

int x = 10, y = 20;

// x = 10 - (5 + 20) = -15
x -= 5 + y; 

Operadores de pré e pós-incremento/decremento

Somam ou subtraem exatamente uma unidade de uma variável.

  • ++x (pré-incremento) / --x (pré-decremento): Altera a variável antes de seu valor ser utilizado na expressão.
  • x++ (pós-incremento) / x-- (pós-decremento): Utiliza o valor atual na expressão e somente após o uso altera a variável.
Regra de Ouro

Pense no posicionamento do operador como uma fila de prioridade: se o sinal vier antes da variável, a conta é feita primeiro; se vier depois, a conta fica para o final.

Modeladores de tipos (casts)

Permitem a conversão explícita de dados, forçando o resultado de uma expressão a assumir o tipo especificado.

(nome_do_tipo) expressão;
float x, y, f = 65.5;

// x = 6.550000
x = f / 10.0;

// y = 6.000000 (descarta decimais)
y = (int) (f / 10.0);
Utilidade do Cast

Garante que operações entre tipos diferentes resultem no comportamento esperado, evitando ambiguidades e controlando a perda de precisão conscientemente.

O operador vírgula (,) e Precedência Geral

Papéis do operador vírgula:

  1. Pontuação: Separar argumentos ou variáveis (int a, b;).
  2. Execução sequencial: Processa uma lista de expressões da esquerda para a direita. O valor final é o da última expressão. x = (y = 2, y + 3); (x recebe 5).

Precedência de Operadores

A linguagem adota uma hierarquia estrita para decidir a ordem de execução.

  • Mais alta: Parênteses (), Array [], Estruturas ->, Incrementos Pós/Pré.
  • Intermediária: Aritmética * / % depois + -, Relacionais, Lógicos.
  • Mais baixa: Atribuições =, +=, etc., e por último a vírgula ,.

Transição: Definindo uma Condição

As operações que vimos geram valores que orientam o fluxo do programa. Uma condição é qualquer expressão (matemática, relacional, lógica) que resulte em verdadeiro ou falso.

Lógica Binária do Compilador:

  • Falso: O computador atribui o valor zero.
  • Verdadeiro: O computador atribui qualquer valor diferente de zero.

Equivalências Práticas:

  • (num != 0) equivale a (num) -> Verdadeiro se não for nulo.
  • (num == 0) equivale a (!num) -> Falso se for zero.

Comando IF

Utilizado para escolher caminhos ou executar instruções baseadas em um teste lógico. Se a condição for verdadeira, o bloco é executado. Se falsa, é ignorado.

FlowchartDecisãoif (num > 10)PrintOutprintf("Maior que 10");Decisão->PrintOutSIMFimDecisão->FimNÃOPrintOut->Fim
if(num > 10) {
    printf("O numero e maior do que 10\n");
}
Atenção ao Ponto e Vírgula

Nunca utilize o ponto e vírgula (;) logo após a condição if(condicao);. Isso encerra o comando prematuramente.

Comando ELSE

Atua como complemento direto do if. Só será processado se a condição do if for FALSA. Representa o "caso contrário" exato.

FlowchartDecisãoif(num == 10)PrintSimprintf ("Igual");Decisão->PrintSimSIMPrintNaoprintf ("Diferente");Decisão->PrintNaoNÃOFimPrintSim->FimPrintNao->Fim
if(num == 10){
    printf("O numero e igual a 10.\n");
} else {
    printf("O numero e diferente de 10.\n");
}
  • Sem condição: O else não recebe (e não aceita) uma condição própria.
  • Independência: Os blocos if e else são independentes; apenas um é executado por vez.

Questões de fixação - 1

1. Sobre os operadores de pré-incremento (++x) e pós-incremento (x++), analise a saída do seguinte programa e explique o comportamento da variável res em cada caso:

int main() {
    int x = 5, res;
    
    // Caso 1
    res = x++;
    // Qual o valor de 'res' e 'x' aqui?
    
    x = 5; // Resetando x
    
    // Caso 2
    res = ++x;
    // Qual o valor de 'res' e 'x' aqui?
    
    return 0;
}

Questões de fixação - 2

2. Realize manualmente as operações bitwise (bit a bit) abaixo, considerando A = 12 (Binário: 00001100) e B = 10 (Binário: 00001010). Apresente o resultado em decimal.

  • a) A & B (AND)
  • b) A | B (OR)
  • c) A ^ B (XOR)
  • d) A >> 2 (Deslocamento à direita)

Questões de fixação - 3

3. Escreva um programa completo em C que solicite ao usuário dois números inteiros. O programa deve:

  1. Calcular a divisão do primeiro pelo segundo.
  2. Utilizar um modelador de tipo (cast) para garantir que o resultado da divisão inclua as casas decimais, mesmo que os números digitados sejam inteiros.
  3. Imprimir o resultado final.

Questões de fixação - 4

4. Utilize os operadores de atribuição simplificada para reescrever e programar a seguinte lógica de forma mais concisa. Considere que a variável total inicia com o valor 100.

  1. Some 50 ao total.
  2. Divida o total por 2.
  3. Obtenha o resto da divisão do total por 5.
  4. Imprima o valor final.

Questões de fixação - 5

5. Escreva um programa que leia um número inteiro fornecido pelo usuário. O programa deve verificar se o número é par ou ímpar e imprimir uma mensagem correspondente.

  • Entrada: Um número inteiro (ex: 7).
  • Saída Esperada: "O numero 7 eh impar."

Aninhamento de IF

Um if aninhado é a utilização de um comando if dentro do bloco de outro if (ou else). Permite lidar com múltiplos caminhos e refinar decisões.

FlowchartDecisao1num == 10PrintIgualprintf ("Igual");Decisao1->PrintIgualSIMDecisao2num > 10Decisao1->Decisao2NÃOPrintMaiorprintf ("Maior");Decisao2->PrintMaiorSIMPrintMenorprintf ("Menor");Decisao2->PrintMenorNÃO

Aninhamento de IF

if(num == 10){
    printf("Igual a 10.\n");
} else {
    if(num > 10)
        printf("Maior que 10.\n");
    else
        printf("Menor que 10.\n");
}
Associação do ELSE

Um else sempre se associa ao if mais próximo anterior a ele dentro do mesmo escopo. Use chaves {} para forçar associações específicas.

Operador Ternário (?)

Simplificação prática da estrutura if-else em uma única linha. Ideal para atribuições condicionais compactas.

expressão condicional ? expressão1 : expressão2;

Estrutura Tradicional (IF/ELSE):

if (x > y) {
    z = x;
} else {
    z = y;
}

Operador Ternário:

z = (x > y) ? x : y;

Comando SWITCH

Comando de seleção múltipla. Verifica se uma variável (int ou char) é igual a valores constantes preestabelecidos, sendo mais elegante que múltiplos ifs aninhados.

switch (variável) {
    case valor1:
        // comandos;
        break;
    case valor2:
        // comandos;
        break;
    default:
        // comandos de escape;
}
  • Restrições: Não testa expressões complexas (ex: x > 10), apenas igualdade exata de valores constantes.
  • default: Cláusula opcional executada se nenhuma correspondência for encontrada.
  • Chaves no case: Obrigatórias apenas se a primeira instrução for a declaração de uma variável.

O comando BREAK no switch

O break interrompe a execução e sai do bloco switch. Se for omitido, ocorre o "efeito cascata": o programa continuará executando todos os comandos dos cases seguintes, ignorando seus valores.

FlowchartIgual1Igual?Case1case 1: printf("A");Igual1->Case1SIMCase2case 2: printf("B");Case1->Case2Sem break!Case3case 3: printf("C");Case2->Case3Sem break!

O comando BREAK no switch

// Se op = 1, imprimirá "ABC"
switch(op) {
    case 1: printf("A");
    case 2: printf("B");
    case 3: printf("C");
}

A ausência do break pode ser uma falha lógica grave ou uma técnica intencional (para estados que compartilham a mesma saída).

Questões de fixação - 1

1. Crie um programa que verifique se um número é "Válido". Um número é válido se ele atender a todas as seguintes regras simultaneamente (utilize operadores lógicos e relacionais):

  • Ser maior que 0.
  • Ser menor ou igual a 100.
  • Ser um número par.

O programa deve imprimir 1 se o número for válido e 0 caso contrário.

Questões de fixação - 2

2. O comando switch é uma alternativa elegante ao uso de múltiplos if-else encadeados, mas possui limitações. Assinale a alternativa que descreve corretamente uma restrição do switch em C:

  • A) Pode avaliar expressões lógicas complexas, como case (x > 10 && x < 20):.
  • B) Só aceita variáveis do tipo float ou double como parâmetro de controle.
  • C) Exige o uso obrigatório de chaves { } em cada case, mesmo que não haja declaração de variáveis.
  • D) Só pode testar igualdade de valores constantes do tipo int ou char.

Questões de fixação - 3

3. O operador ternário é uma ferramenta poderosa para simplificação de código. Reescreva o trecho de código abaixo utilizando uma única linha com o operador ternário:

// Código Original
if (nota >= 60) {
    printf("Aprovado");
} else {
    printf("Reprovado");
}

Questões de fixação - 4

4. Crie um programa que receba três números inteiros como entrada e imprima o maior deles. Utilize estruturas if-else aninhadas para realizar as comparações.

  • Entrada: Três números inteiros (ex: 10, 5, 20).
  • Saída Esperada: "O maior numero eh: 20".

Questões de fixação - 5

5. Desenvolva uma calculadora básica utilizando o comando switch. O programa deve ler dois números reais e um operador aritmético (+, -, *, /).

  • Entrada: Dois números e um caractere (ex: 5.0, 2.0, '/').
  • Saída Esperada: O resultado da operação (ex: "Resultado: 2.50").
  • Requisito: Trate a divisão por zero, exibindo uma mensagem de erro caso o divisor seja 0.

Questões de fixação - 6

6. Escreva um programa que leia um ano (ex: 2024) e determine se ele é bissexto ou não. As regras para um ano ser bissexto são:

  1. Ser divisível por 4.
  2. Não ser divisível por 100, a menos que seja também divisível por 400.
  • Entrada: Um ano (inteiro).
  • Saída Esperada: "Bissexto" ou "Nao Bissexto".

Questões de fixação - 7

7. Analise o comportamento do break no comando switch. Dado o código abaixo, qual será a saída exata se o usuário digitar o valor 2? Explique por que a saída ocorre dessa forma.

int main() {
    int op;
    scanf("%d", &op);
    
    switch(op) {
        case 1: printf("A");
        case 2: printf("B");
        case 3: printf("C");
        default: printf("D");
    }
    return 0;
}

Conclusão e Próximos Passos

Resumo

  • Compreendemos como o C trata as atribuições, precedências e perdas de conversão.
  • Dominamos as operações matemáticas, relacionais, lógicas e a manipulação bruta de bits (bitwise).
  • Traduzimos lógica booleana em controle de fluxo através de estruturas de seleção (if, else, switch, ternário).

Próximo Capítulo: Estruturas de Repetição

  • Vamos além da execução única.
  • Criação de loops (laços) utilizando while, do-while e for.
  • Como processar grandes volumes de dados através de iterações controladas.