Sua dose diária de programação

Conceito, 3 exemplos e 3 exercícios: uma pequena conquista de cada vez.

✦   Lição

Operadores

Operadores são os símbolos que combinam valores em expressões. Os aritméticos fazem as contas: +, -, *, /, % (resto da divisão) e ** (potência). Em PHP, / devolve float quando a divisão não é exata e int quando é; para o quociente inteiro existe a função intdiv(). Textos se juntam com o ponto ., o operador de concatenação, e não com +.

PHP Fundamentos Iniciante 3 exemplos + 3 exercícios
42 lições publicadas 10 linguagens novas lições todo dia útil
PHP / Fundamentos

Operadores

por Callahan Whitmore
Iniciante

Entenda o conceito

Operadores são os símbolos que combinam valores em expressões. Os aritméticos fazem as contas: +, -, *, /, % (resto da divisão) e ** (potência). Em PHP, / devolve float quando a divisão não é exata e int quando é; para o quociente inteiro existe a função intdiv(). Textos se juntam com o ponto ., o operador de concatenação, e não com +.

Os operadores de atribuição composta atualizam uma variável a partir do valor que ela já tem: $total += 10 é o mesmo que $total = $total + 10, e há versões para todos os aritméticos, para texto (.=) e para valores ausentes (??=). Os operadores ++ e -- somam ou subtraem 1; na forma $x++ a expressão vale o número antigo, e na forma ++$x, o novo.

O operador <=>, chamado de spaceship, compara dois valores e devolve -1, 0 ou 1, o que o torna a base de qualquer ordenação. Quando vários operadores aparecem juntos, vale a precedência: ** vem antes de * e /, que vêm antes de + e -. Na dúvida, use parênteses; eles deixam a intenção clara para quem lê.

↳ Pontos-chave
  • / devolve float se a divisão não é exata; use intdiv() para o quociente inteiro e % para o resto.
  • O ponto . concatena textos; + com strings não numéricas lança TypeError no PHP 8.
  • +=, -=, *=, .= e ??= atualizam a variável a partir do valor atual; $x++ vale o valor antigo e ++$x, o novo.
  • $a <=> $b devolve -1, 0 ou 1; inverter os operandos inverte a ordem, e ?: encadeia um critério de desempate.
  • ** vem antes de * e /, que vêm antes de + e -; and e or ficam abaixo de =, por isso prefira && e ||.

Do conceito à prática

3 exemplos para ver a ideia funcionando.

01Aritméticos na reposição do estoque

estoque.php
<?php

// Reposição do estoque: canetas chegam soltas e são guardadas em caixas.
$canetas = 157;
$porCaixa = 12;
$precoCaixa = 30.0;

// + - * / são os operadores aritméticos de sempre.
$comReserva = $canetas + 20;
echo "Com reserva: $comReserva canetas" . PHP_EOL;

// / devolve float se a divisão não é exata e int se ela é exata.
echo "157 / 12 = " . $canetas / $porCaixa . PHP_EOL;
echo "24 / 12 = " . 24 / $porCaixa . PHP_EOL;

// intdiv dá o quociente inteiro; % dá o resto da divisão.
$caixasCheias = intdiv($canetas, $porCaixa);
$soltas = $canetas % $porCaixa;
echo "Caixas cheias: $caixasCheias, canetas soltas: $soltas" . PHP_EOL;

// ** é a potência: 3 andares de prateleiras com 3 x 3 caixas cada.
$capacidade = 3 ** 3;
echo "Capacidade do depósito: $capacidade caixas" . PHP_EOL;

// O ponto (.) concatena textos; números viram texto ao concatenar.
$valor = $caixasCheias * $precoCaixa;
echo "Valor do estoque: R$ " . number_format($valor, 2, ",", ".") . PHP_EOL;

// O sinal de % segue o do primeiro operando.
echo "-7 % 3 = " . -7 % 3 . PHP_EOL;

As 157 canetas da loja precisam ir para caixas de 12. A divisão comum 157 / 12 dá 13.083..., um float, enquanto 24 / 12 dá 2, um int: o tipo do resultado de / depende de a divisão ser exata. Para a pergunta "quantas caixas cheias e quantas sobram", o par certo é intdiv(), que devolve 13, e %, que devolve o resto 1.

O ** calcula a potência (3 ** 3 é 27) e o ponto junta textos e números na mesma linha. A última linha mostra um detalhe do %: o sinal do resultado segue o do primeiro operando, então -7 % 3 vale -1, e não 2 como na matemática.

02Atribuição composta e incremento no caixa da loja

caixa.php
<?php

// Caixa da loja: o total vai sendo atualizado item a item.
$total = 0.0;
$itens = 0;
$recibo = "Recibo:";

// += soma ao valor atual: é o mesmo que $total = $total + 45.0.
$total += 45.0;
$itens++;                 // ++ soma 1 à variável
$recibo .= " camiseta";   // .= acrescenta texto ao final

$total += 2 * 12.5;
$itens += 2;
$recibo .= ", 2 meias";

// -= e *= funcionam do mesmo jeito: desconto de R$ 10 e depois 5% off.
$total -= 10;
$total *= 0.95;

echo $recibo . PHP_EOL;
echo "Itens: $itens" . PHP_EOL;
echo "Total: R$ " . number_format($total, 2, ",", ".") . PHP_EOL;

// Pós-incremento devolve o valor antigo; pré-incremento, o novo.
$senha = 7;
echo "Atendendo senha " . $senha++ . PHP_EOL; // mostra 7, depois vira 8
echo "Próxima senha " . ++$senha . PHP_EOL;   // vira 9, depois mostra 9

// ??= só atribui se a variável for null ou não existir.
$cupom = null;
$cupom ??= "SEM-CUPOM";
$cupom ??= "OUTRO";       // não muda: $cupom já tem valor
echo "Cupom: $cupom" . PHP_EOL;

O total da compra cresce com += a cada item, o contador sobe com ++ e o recibo ganha texto com .=. Depois, -= aplica o desconto de R$ 10 e *= 0.95 tira mais 5%: (45 + 25 - 10) * 0.95 dá R$ 57,00. Cada linha lê o valor atual da variável, opera e guarda o resultado nela mesma.

As senhas de atendimento mostram a diferença entre pós e pré-incremento: $senha++ entrega 7 para o echo e só depois passa a valer 8; ++$senha primeiro sobe para 9 e então entrega o 9. Por fim, ??= preenche o cupom só porque ele era null; a segunda tentativa não muda nada.

03Precedência e spaceship na biblioteca

biblioteca.php
<?php

// Precedência: * e / são calculados antes de + e -.
$taxaFixa = 2;
$diasDeAtraso = 5;
$multaDiaria = 0.5;
echo "taxa + dias * multa = " . ($taxaFixa + $diasDeAtraso * $multaDiaria) . PHP_EOL;
echo "(taxa + dias) * multa = " . (($taxaFixa + $diasDeAtraso) * $multaDiaria) . PHP_EOL;

// ** é avaliado da direita para a esquerda e antes do sinal de menos.
var_dump(2 ** 3 ** 2);   // 2 ** 9
var_dump(-2 ** 2);       // -(2 ** 2)

// <=> (spaceship) devolve -1, 0 ou 1: menor, igual ou maior.
var_dump(3 <=> 7);
var_dump(7 <=> 7);
var_dump(9 <=> 7);

// Com strings, compara em ordem alfabética (pelos bytes).
var_dump("Iracema" <=> "Dom Casmurro");

// Um uso comum: transformar o resultado em texto.
$exemplares = 2;
$reservas = 4;
$situacao = match ($exemplares <=> $reservas) {
    -1 => "fila de espera",
    0 => "todos reservados",
    1 => "exemplares livres",
};
echo "Iracema: $situacao" . PHP_EOL;

// "and" e "or" têm precedência menor que "=": prefira && e ||.
$disponivel = true and false;    // atribui true, depois avalia o and
var_dump($disponivel);
$disponivel = true && false;
var_dump($disponivel);

A multa do empréstimo mostra a precedência: sem parênteses internos, 2 + 5 * 0.5 calcula a multiplicação primeiro e dá 4.5; com (2 + 5) * 0.5, a soma vem antes e o resultado é 3.5. O ** tem regras próprias: é avaliado da direita para a esquerda (2 ** 3 ** 2 é 2 ** 9) e antes do sinal de menos, por isso -2 ** 2 vale -4.

O <=> devolve -1, 0 ou 1, também com strings, comparando em ordem alfabética pelos bytes. Com um match, esse número vira texto: 2 exemplares para 4 reservas dá -1, fila de espera. O último trecho mostra a armadilha do and: ele tem precedência menor que =, então $disponivel = true and false guarda true. Com &&, o resultado é o esperado.


Agora é com você

Tente resolver no rascunho antes de abrir a resposta. Os códigos não são executados nesta página.

01Minutos do clube de leituraFácil

Três membros de um clube de leitura registraram os minutos lidos na semana. Calcule o total, converta-o em horas e minutos, mostre a média por membro com uma casa decimal e quanto falta para a meta de 600 minutos.

↳ Requisitos
  • Usar intdiv() para as horas e % para os minutos restantes.
  • Calcular a média com / e arredondar com round() para uma casa decimal.
  • Guardar a meta em uma constante e calcular o que falta com -, sem escrever 313 ou 287 no código.
  • Imprimir exatamente as quatro linhas da saída esperada.
código inicial
<?php

const META_MINUTOS = 600;

$minutosAna = 95;
$minutosBeto = 140;
$minutosCaio = 78;

// 1. Some os minutos.
// 2. Converta o total em horas e minutos.
// 3. Calcule a média e quanto falta para a meta.
saída esperada
Total lido: 313 minutos
Equivale a: 5 h 13 min
Média por membro: 104.3 minutos
Meta de 600 minutos: faltam 287
Ver dica

Pense em quantos grupos completos de 60 cabem no total e no que sobra depois de tirar esses grupos: são duas operações diferentes.

Ver resposta
<?php

const META_MINUTOS = 600;

// Minutos lidos por cada membro do clube de leitura na semana.
$minutosAna = 95;
$minutosBeto = 140;
$minutosCaio = 78;

// + soma os três tempos.
$total = $minutosAna + $minutosBeto + $minutosCaio;

// intdiv dá as horas inteiras; % dá os minutos que sobram.
$horas = intdiv($total, 60);
$minutos = $total % 60;

// / gera float; round deixa uma casa decimal.
$media = round($total / 3, 1);

// - calcula quanto falta para a meta.
$faltam = META_MINUTOS - $total;

echo "Total lido: $total minutos" . PHP_EOL;
echo "Equivale a: $horas h $minutos min" . PHP_EOL;
echo "Média por membro: $media minutos" . PHP_EOL;
echo "Meta de " . META_MINUTOS . " minutos: faltam $faltam" . PHP_EOL;

Passo a passo

  1. A constante META_MINUTOS guarda a meta do clube, para que o número 600 não fique repetido no código.
  2. O operador + soma os minutos dos três membros; como todos são int, o total também é int (313).
  3. intdiv($total, 60) devolve quantas horas inteiras cabem no total (5), descartando a parte fracionária.
  4. $total % 60 devolve o resto dessa mesma divisão (13), ou seja, os minutos que não completam uma hora.
  5. $total / 3 não é exata e produz um float (104.333...); round(..., 1) deixa uma casa decimal.
  6. O operador - calcula quanto falta para a meta, e as quatro linhas são impressas com PHP_EOL no fim.

Por que funciona

Converter minutos em horas é uma divisão inteira: o quociente diz quantos grupos de 60 cabem no total, e o resto diz o que sobrou. intdiv e % são as duas metades dessa conta e sempre obedecem à relação $horas * 60 + $minutos === $total.

Já a média precisa das casas decimais, por isso usa /, que devolve float quando a divisão não é exata. Cada operador foi escolhido pelo tipo de resultado que a linha da saída exige.

Erros comuns

  • Usar / para as horas: $total / 60 dá 5.2166..., e a linha sai como 5.2166666666667 h 13 min. Use intdiv() para o quociente inteiro.
  • Calcular os minutos com subtração errada: $total - $horas dá 308, porque subtrai horas de minutos. O resto certo é $total % 60 (ou $total - $horas * 60).
  • Esquecer os parênteses na média: $minutosAna + $minutosBeto + $minutosCaio / 3 divide só o último valor, porque / vem antes de +. Some primeiro e depois divida.
  • Fazer o cast para int na média: (int) ($total / 3) corta para 104 e perde a casa decimal pedida. Use round($total / 3, 1).

Outra forma de resolver

$horas = ($total - $total % 60) / 60; também chega a 5, tirando o resto antes de dividir. Funciona, mas o resultado pode vir como float em outras contas e a intenção fica menos clara; intdiv() é a forma direta quando se quer só o quociente inteiro.

Saída esperada

Total lido: 313 minutos
Equivale a: 5 h 13 min
Média por membro: 104.3 minutos
Meta de 600 minutos: faltam 287

02Placar de uma fase do jogoMédio

Um jogador começa a fase com 0 pontos e 3 vidas. Aplique os eventos em ordem (5 moedas de 10 pontos, chefe derrotado por 250 pontos, dano que tira uma vida e 30 pontos, estrela que dobra os pontos) e mostre o registro dos eventos, os pontos e as vidas.

↳ Requisitos
  • Atualizar os pontos só com +=, -= e *=, um evento por vez, na ordem do enunciado.
  • Tirar a vida com -- e montar o registro dos eventos com .=.
  • Não escrever o resultado 540 em nenhum lugar do código.
  • Imprimir exatamente as três linhas da saída esperada.
código inicial
<?php

const PONTOS_POR_MOEDA = 10;

$pontos = 0;
$vidas = 3;
$eventos = "Eventos:";

// Aplique os quatro eventos em ordem e imprima o resultado.
saída esperada
Eventos: moedas, chefe, dano, estrela
Pontos: 540
Vidas: 2
Ver dica

A estrela dobra o que já foi acumulado, então o momento em que ela é aplicada muda o placar final.

Ver resposta
<?php

const PONTOS_POR_MOEDA = 10;

// Estado inicial da fase.
$pontos = 0;
$vidas = 3;
$eventos = "Eventos:";

// Coletou 5 moedas.
$pontos += 5 * PONTOS_POR_MOEDA;
$eventos .= " moedas";

// Derrotou o chefe da fase.
$pontos += 250;
$eventos .= ", chefe";

// Levou dano: perde uma vida e 30 pontos.
$vidas--;
$pontos -= 30;
$eventos .= ", dano";

// A estrela dobra os pontos acumulados até aqui.
$pontos *= 2;
$eventos .= ", estrela";

echo $eventos . PHP_EOL;
echo "Pontos: $pontos" . PHP_EOL;
echo "Vidas: $vidas" . PHP_EOL;

Passo a passo

  1. A constante PONTOS_POR_MOEDA dá nome ao valor de cada moeda, e as três variáveis guardam o estado inicial da fase.
  2. $pontos += 5 * PONTOS_POR_MOEDA; calcula primeiro a multiplicação (50) e depois soma ao placar, porque * tem precedência sobre a atribuição.
  3. Cada $eventos .= ... acrescenta um trecho ao final do texto, sem precisar repetir $eventos = $eventos . ....
  4. $vidas-- tira uma vida, e $pontos -= 30 desconta a penalidade do dano: o placar vai de 300 para 270.
  5. $pontos *= 2 dobra o valor acumulado até aquele momento, por isso a estrela vale mais quando vem no fim.
  6. As três linhas mostram o registro, os pontos (540) e as vidas (2), todas terminando com PHP_EOL.

Por que funciona

Os operadores de atribuição composta leem o valor atual da variável, aplicam a operação e guardam o resultado nela mesma. Assim o placar evolui evento por evento, e a ordem das linhas é a ordem do jogo: (50 + 250 - 30) * 2 dá 540.

O -- é um atalho para $vidas -= 1, e o .= faz com texto o que o += faz com números. Nenhum valor final aparece escrito no código: todos nascem das operações.

Erros comuns

  • Inverter a ordem dos eventos: aplicar $pontos *= 2 antes do dano dá (300 * 2) - 30 = 570. Com atribuições compostas, a ordem das linhas muda o resultado.
  • Usar += com texto: $eventos += ", chefe"; lança TypeError no PHP 8, porque + não soma strings não numéricas. Para texto, use .=.
  • Trocar .= por =: $eventos = ", dano"; apaga tudo o que já estava registrado. O ponto antes do sinal de igual é o que acrescenta.
  • Escrever $pontos =+ 250;: isso é $pontos = +250, que substitui o placar por 250. O operador certo é +=, com o sinal antes do igual.

Outra forma de resolver

Daria para escrever cada passo por extenso, como $pontos = $pontos + 250; e $vidas = $vidas - 1;. O resultado é o mesmo, e essa forma pode ajudar quem está começando a ler; no dia a dia, as formas compostas são preferíveis por serem mais curtas e evitarem repetir o nome da variável.

Saída esperada

Eventos: moedas, chefe, dano, estrela
Pontos: 540
Vidas: 2

03Quem vem antes no boletimDesafio

Escreva uma função que compare dois alunos para a classificação da turma: quem tem a maior média vem antes e, se as médias empatarem, vem antes quem tem menos faltas. Mostre o número devolvido e o que ele significa para quatro pares de alunos.

↳ Requisitos
  • Criar compararAlunos(float $mediaA, int $faltasA, float $mediaB, int $faltasB): int com declare(strict_types=1), devolvendo -1, 0 ou 1.
  • Resolver a comparação em um único return com <=> para os dois critérios e ?: para o desempate, sem if.
  • Traduzir o resultado em texto com match, em uma segunda função que monta cada linha.
  • Reproduzir exatamente as quatro linhas da saída esperada.
código inicial
<?php

declare(strict_types=1);

// Crie compararAlunos() e descrever().

echo descrever("Ana", 8.5, 2, "Bruno", 7.9, 0) . PHP_EOL;
echo descrever("Carla", 9.0, 3, "Davi", 9.0, 1) . PHP_EOL;
echo descrever("Elisa", 6.5, 4, "Fábio", 6.5, 4) . PHP_EOL;
echo descrever("Gabi", 7.0, 0, "Hugo", 7.5, 5) . PHP_EOL;
saída esperada
Ana x Bruno: -1 (Ana vem antes)
Carla x Davi: 1 (Davi vem antes)
Elisa x Fábio: 0 (empate)
Gabi x Hugo: 1 (Hugo vem antes)
Ver dica

O ?: devolve o lado esquerdo quando ele é verdadeiro e o direito quando é falso; lembre que 0 conta como falso.

Ver resposta
<?php

declare(strict_types=1);

// Devolve -1 se A vem antes, 1 se B vem antes e 0 se empatam.
function compararAlunos(float $mediaA, int $faltasA, float $mediaB, int $faltasB): int
{
    // Maior média primeiro: B <=> A inverte a ordem crescente.
    // ?: só usa o desempate quando as médias empatam (resultado 0).
    return $mediaB <=> $mediaA ?: $faltasA <=> $faltasB;
}

// Monta a linha com o resultado da comparação e o texto dele.
function descrever(string $nomeA, float $mediaA, int $faltasA, string $nomeB, float $mediaB, int $faltasB): string
{
    $resultado = compararAlunos($mediaA, $faltasA, $mediaB, $faltasB);
    $texto = match ($resultado) {
        -1 => "$nomeA vem antes",
        1 => "$nomeB vem antes",
        0 => "empate",
    };
    return "$nomeA x $nomeB: $resultado ($texto)";
}

echo descrever("Ana", 8.5, 2, "Bruno", 7.9, 0) . PHP_EOL;
echo descrever("Carla", 9.0, 3, "Davi", 9.0, 1) . PHP_EOL;
echo descrever("Elisa", 6.5, 4, "Fábio", 6.5, 4) . PHP_EOL;
echo descrever("Gabi", 7.0, 0, "Hugo", 7.5, 5) . PHP_EOL;

Passo a passo

  1. declare(strict_types=1) garante que as médias cheguem como float e as faltas como int, sem conversões silenciosas.
  2. $mediaB <=> $mediaA compara as médias com os operandos invertidos: assim, quando A tem a média maior, o resultado é -1 e A vem antes.
  3. O ?: avalia o lado esquerdo e, se ele for 0 (falso), devolve o lado direito; é assim que o desempate só entra quando as médias são iguais.
  4. $faltasA <=> $faltasB fica na ordem normal, porque menos faltas é melhor e deve vir primeiro.
  5. Como <=> tem precedência maior que ?:, as duas comparações são feitas antes do ?:, sem precisar de parênteses.
  6. Em descrever(), o match traduz -1, 0 e 1 em texto; como só existem esses três valores, nenhum default é necessário.
  7. Cada linha mostra o número devolvido por compararAlunos() e o texto correspondente.

Por que funciona

O <=> resume uma comparação inteira em um número: negativo, zero ou positivo. Inverter os operandos inverte o sentido da ordem, e isso basta para colocar a maior média primeiro. Carla e Davi empatam na média, o <=> devolve 0, e o ?: passa a decisão para as faltas, que favorecem Davi.

Encadear comparações com ?: é o padrão para ordenar por mais de um critério: o primeiro critério decide sempre que pode, e o seguinte só é consultado em caso de empate. A mesma função serve depois como comparador de ordenação, porque devolve exatamente -1, 0 ou 1.

Erros comuns

  • Manter a ordem natural nas médias: $mediaA <=> $mediaB coloca a menor média primeiro, e Bruno passaria a vir antes de Ana. Para ordem decrescente, inverta os operandos.
  • Usar ?? no lugar de ?:: $mediaB <=> $mediaA ?? $faltasA <=> $faltasB nunca chega ao desempate, porque 0 não é null. O ?: testa se o valor é falso, e o ?? só se é null.
  • Somar os critérios: ($mediaB <=> $mediaA) + ($faltasA <=> $faltasB) dá 0 para Gabi e Hugo, um empate falso, porque um critério anula o outro. O desempate precisa ser hierárquico, não somado.
  • Comparar com subtração: return $mediaB - $mediaA; devolve um float como 0.6, e com o retorno int e strict_types isso gera TypeError. O <=> sempre devolve um int.

Outra forma de resolver

Com if, a mesma regra fica assim: if ($mediaA !== $mediaB) { return $mediaB <=> $mediaA; } return $faltasA <=> $faltasB;. Essa forma é preferível quando há três ou mais critérios ou quando cada um precisa de uma explicação no código; para dois critérios, o encadeamento com ?: cabe em uma linha e é fácil de ler.

Saída esperada

Ana x Bruno: -1 (Ana vem antes)
Carla x Davi: 1 (Davi vem antes)
Elisa x Fábio: 0 (empate)
Gabi x Hugo: 1 (Hugo vem antes)
← Anterior na trilha de PHPDecisões com condicionais

Últimas lições

Ver o arquivo completo →