Operadores
por Callahan WhitmoreEntenda 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ê.
/devolvefloatse a divisão não é exata; useintdiv()para o quociente inteiro e%para o resto.- O ponto
.concatena textos;+com strings não numéricas lançaTypeErrorno PHP 8. +=,-=,*=,.=e??=atualizam a variável a partir do valor atual;$x++vale o valor antigo e++$x, o novo.$a <=> $bdevolve-1,0ou1; inverter os operandos inverte a ordem, e?:encadeia um critério de desempate.**vem antes de*e/, que vêm antes de+e-;andeorficam abaixo de=, por isso prefira&&e||.
Do conceito à prática
3 exemplos para ver a ideia funcionando.
01Aritméticos na reposição do estoque
<?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
<?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
<?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.
- Usar
intdiv()para as horas e%para os minutos restantes. - Calcular a média com
/e arredondar comround()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.
<?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.
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
- A constante
META_MINUTOSguarda a meta do clube, para que o número 600 não fique repetido no código. - O operador
+soma os minutos dos três membros; como todos sãoint, o total também éint(313). intdiv($total, 60)devolve quantas horas inteiras cabem no total (5), descartando a parte fracionária.$total % 60devolve o resto dessa mesma divisão (13), ou seja, os minutos que não completam uma hora.$total / 3não é exata e produz umfloat(104.333...);round(..., 1)deixa uma casa decimal.- O operador
-calcula quanto falta para a meta, e as quatro linhas são impressas comPHP_EOLno 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 / 60dá5.2166..., e a linha sai como5.2166666666667 h 13 min. Useintdiv()para o quociente inteiro. - Calcular os minutos com subtração errada:
$total - $horasdá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 / 3divide 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 para104e perde a casa decimal pedida. Useround($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.
- 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.
<?php
const PONTOS_POR_MOEDA = 10;
$pontos = 0;
$vidas = 3;
$eventos = "Eventos:";
// Aplique os quatro eventos em ordem e imprima o resultado.
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
- A constante
PONTOS_POR_MOEDAdá nome ao valor de cada moeda, e as três variáveis guardam o estado inicial da fase. $pontos += 5 * PONTOS_POR_MOEDA;calcula primeiro a multiplicação (50) e depois soma ao placar, porque*tem precedência sobre a atribuição.- Cada
$eventos .= ...acrescenta um trecho ao final do texto, sem precisar repetir$eventos = $eventos . .... $vidas--tira uma vida, e$pontos -= 30desconta a penalidade do dano: o placar vai de 300 para 270.$pontos *= 2dobra o valor acumulado até aquele momento, por isso a estrela vale mais quando vem no fim.- 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 *= 2antes do dano dá(300 * 2) - 30 = 570. Com atribuições compostas, a ordem das linhas muda o resultado. - Usar
+=com texto:$eventos += ", chefe";lançaTypeErrorno 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.
- Criar
compararAlunos(float $mediaA, int $faltasA, float $mediaB, int $faltasB): intcomdeclare(strict_types=1), devolvendo -1, 0 ou 1. - Resolver a comparação em um único
returncom<=>para os dois critérios e?:para o desempate, semif. - 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.
<?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;
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
declare(strict_types=1)garante que as médias cheguem comofloate as faltas comoint, sem conversões silenciosas.$mediaB <=> $mediaAcompara as médias com os operandos invertidos: assim, quando A tem a média maior, o resultado é-1e A vem antes.- O
?:avalia o lado esquerdo e, se ele for0(falso), devolve o lado direito; é assim que o desempate só entra quando as médias são iguais. $faltasA <=> $faltasBfica na ordem normal, porque menos faltas é melhor e deve vir primeiro.- Como
<=>tem precedência maior que?:, as duas comparações são feitas antes do?:, sem precisar de parênteses. - Em
descrever(), omatchtraduz -1, 0 e 1 em texto; como só existem esses três valores, nenhumdefaulté necessário. - 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 <=> $mediaBcoloca 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 <=> $faltasBnunca chega ao desempate, porque0não énull. O?:testa se o valor é falso, e o??só se énull. - Somar os critérios:
($mediaB <=> $mediaA) + ($faltasA <=> $faltasB)dá0para 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 umfloatcomo0.6, e com o retornointestrict_typesisso geraTypeError. O<=>sempre devolve umint.
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)