Desenvolvimento

Associatividade e precedência de operadores

Entender associatividade e precedência dos operadores da linguagem de programação que você utiliza, pode te poupar de muita dor de cabeça.

há 3 anos 9 meses


Você sabia que a TreinaWeb é a mais completa escola para desenvolvedores do mercado?

O que você encontrará aqui na TreinaWeb?

Conheça os nossos cursos

Associatividade e precedência de operadores é um assunto um pouco marginalizado no estudo de linguagens de programação. Em algum momento todo desenvolvedor vai se deparar com um problema relacionado a isso.

Por exemplo, a expressão 8 * 2 - 1 pode ser vista como (8 * 2) - 1 ou 8 * (2 - 1) e essa diferença muda a forma dela ser calculada e, consequentemente, o seu resultado. Essa ambiguidade foi resolvida lá nos primórdios pelos matemáticos através de regras de precedência e associatividade. Essas mesmas regras são aplicadas nos projetos das linguagens de programação. Quando um projetista de linguagem vai criar a especificação dela, ele precisa definir de antemão todas essas regras.

Precedência: um operador possui maior precedência que outro se ele precisar ser analisado antes em todas as expressões sem parênteses que envolverem apenas os dois operadores. Na matemática, na expressão 8 * 2 - 1, a multiplicação é sempre avaliada antes da subtração, ou seja, ela possui maior precedência. Portanto, a expressão equivalente seria: (8 * 2) - 1.

Já a associatividade especifica se os operadores de igual precedência devem ser avaliados da esquerda para a direita ou da direita para a esquerda. De volta à matemática, o operador “menos” possui associatividade à esquerda, portanto, 10 - 9 - 8 é equivalente a (10 - 9) - 8.

Na matemática

Precedência na matemática, da maior pra menor:

    1. Parênteses;
    1. Expoentes;
    1. Multiplicações e divisões; (da esquerda para a direita);
    1. Somas e subtrações. (da esquerda para a direita);

Grande parte das linguagens de programação seguem (sempre que possível) as convenções matemáticas comuns para associatividade e precedência.

Na maioria das linguagens de programação os operadores de atribuição são definidos com associatividade à direita. Ou seja, a = b = c é o equivalente a a = (b = c), que alimenta as variáveis a e b com o valor armazenado em c. As linguagens de programação possuem em suas documentações uma tabela dos operadores e suas precedências, da maior pra menor, bem como a associatividade desses operadores. Portanto, a regra aqui é avaliar essa tabela na linguagem que você utiliza.

Forçando precedência

Assim como na matemática, as linguagens permitem que usemos parênteses para forçar uma maior precedência. Essa expressão aqui:

x = 8 + 9 * 2; // 26

Em qualquer linguagem, vai resultar em 26, pois a multiplicação possui maior precedência que a subtração. No entanto, se utilizarmos parênteses entre a operação de soma, ela passa ter precedência maior que a multiplicação e, consequentemente, o resultado muda:

x = (8 + 9) * 2; // 34

Sem uma definição clara de precedência as expressões com múltiplos operadores se tornariam ambíguas. Um exemplo disso:

x = 4/2*1+3; // 5

Que resultado você espera ter? Pra quem está lendo essa expressão, não é tão óbvio como calculá-la. Por isso forçar precedência é uma boa prática, traz mais legibilidade e coerência para sua operação.

O resultado da expressão acima é diferente do resultado que força precedência:

x = (4/2)*(1+3); // 8

Precedência é sobre agrupamento

Uma observação muito importante a ser feita é que precedência não determina a ordem de avaliação, precedência apenas determina como a operação vai ser agrupada para depois ser avaliada. Ou seja, precedência especifica que a expressão:

f1() + f2() * f3()

Será agrupada como:

f1() + (f2() * f3())

Ou seja, precedência não fala que a função f2() vai ser avaliada primeiro que f1(). A ordem de avaliação padrão da maioria das linguagens de programação é da esquerda para a direita, mas as regras de associatividade podem alterar isso no meio do caminho.

Então, a ordem de avaliação das funções acima seria primeiro a f1(), depois a f2() e por fim a f3(), da esquerda para a direita. A função da precedência foi apenas agrupar as duas expressões (f2() * f3()).

Abaixo exemplos de como diferentes expressões são agrupadas no processo de parsing de uma linguagem de programação (na construção da árvore sintática abstrata):

-a * b                      => ((-a) * b)
!-a                         => (!(-a))
a + b + c                   => ((a + b) + c)
a * b * c                   => ((a * b) * c)
a * b / c                   => ((a * b) / c)
a + b / c                   => (a + (b / c))
a + b * c + d / e - f       => (((a + (b * c)) + (d / e)) - f)
3 + 4 * 5 == 3 * 1 + 4 * 5  => ((3 + (4 * 5)) == ((3 * 1) + (4 * 5)))

Por exemplo, a expressão:

1 + 2 + 3;

No parsing da linguagem, depois de se aplicar as regras de precedência, ela é agrupada dessa forma:

((1 + 2) + 3)

A representação da árvore abstrata dessa expressão seria algo como:

Representação abstrata de precedência

Ou seja, mais à esquerda temos uma expressão que um lado do seu nó é o literal 1 e o outro lado o 2. E essa expressão está à esquerda da expressão que tem o nó do literal 3.

Tabela de precedência e associatividade

Na documentação da sua linguagem de programação, você deverá encontrar uma tabela parecida com essa abaixo, onde os operadores são ordenados pelos de maior precedência e uma coluna especificando a associatividade:

Nome Operador(es) Associatividade
Unário ! direita
Aritmética * / % esquerda
Aritmética - + esquerda
Comparação < > <= >= esquerda
Comparação == != esquerda
Atribuição = += -= *= direita

Nessa especificação temos claro que a operação aritmética de multiplicação * tem precedência maior que a subtração - (por estar numa ordem superior na tabela). Da mesma forma, os operadores ariméticos possuem maior precedência que os operadores de atribuição.

O operador unário ! de negação tem associatividade à direita, ou seja, quer dizer que primeiro a expressão da direita será avaliada antes de fazer a negação. Da mesma forma os operadores de atribuição vão resolver as espressões à direita antes de fazer a atribuição.

Apesar de as linguagens de programação seguirem regras parecidas, não há como generalizar, há sempre algumas diferenças, portanto, não deixe de consultar a documentação da sua linguagem.

Até a próxima!

Autor(a) do artigo

Kennedy Tedesco
Kennedy Tedesco

Head de desenvolvimento. Vasta experiência em desenvolvimento Web com foco em PHP. Graduado em Sistemas de Informação. Pós-graduando em Arquitetura de Software Distribuído pela PUC Minas. Zend Certified Engineer (ZCE) e Coffee Addicted Person (CAP). @KennedyTedesco

Todos os artigos

Artigos relacionados Ver todos