Pragiva — Tecnologia na prática

Operadores em AdvPL: atribuição, comparação, lógica e precedência

Operadores em AdvPL: a diferença entre = e == em texto, o curto-circuito do .And. e do .Or., o Default para parâmetro opcional e a ordem de precedência.

Desenvolvimento Tutorial 4 min de leitura

Assista o vídeo

Resposta rápida

Atribuição
use :=; o = sozinho também atribui, mas é ambíguo.
Texto
compare com ==. O = compara só até o tamanho do lado direito enquanto SET EXACT estiver desligado.
Diferente
!=, <> e # são a mesma coisa, e seguem a regra do =, não a do ==.
Lógica
.And. e .Or. param de avaliar assim que o resultado está decidido.
Parâmetro opcional
Default cVar := valor preenche só o que chegou Nil.
Nesta página6 seções

Operador parece o assunto mais tranquilo da linguagem, e por isso aparece em tanto bug de produção. Os tipos envolvidos estão em Tipos de dados em AdvPL.

Atribuição e incremento

OperadorEfeitoEquivale a
:=atribui—
+=soma e atribuinX := nX + n
-=subtrai e atribuinX := nX - n
*=multiplica e atribuinX := nX * n
/=divide e atribuinX := nX / n
++ / --soma ou subtrai 1nX := nX + 1

O = também atribui quando está sozinho numa linha, mas dentro de uma expressão ele compara; := tira a dúvida. O += funciona com texto:

ADVPL / TLPP
Local cLog   := ""
Local nLidos := 0

nLidos++
cLog += "Produto processado: " + cValToChar(nLidos) + CRLF

Aritméticos e concatenação

OperadorEfeitoExemploResultado
+soma7 + 310
-subtração7 - 34
*multiplicação7 * 321
/divisão7 / 23.5
** ou ^potência2 ** 101024
%resto da divisão7 % 21

O % resolve “a cada mil registros”: If nLidos % 1000 == 0.

Em texto, + concatena como está. O - também concatena, mas leva os espaços do fim do operando da esquerda para o fim do resultado: "PGV " - "001" dá "PGV001 ". Na prática, AllTrim() explícito lê melhor.

Comparação: onde mora o bug

OperadorTeste
==exatamente igual
=igual (em texto, depende de SET EXACT)
!=, <>, #diferente — três grafias para o mesmo operador
<, <=menor, menor ou igual
>, >=maior, maior ou igual
$o texto da esquerda está contido no da direita

Com número, = e == dão o mesmo resultado. Com texto, não. O = herda a regra do Clipper: com SET EXACT desligado, que é o padrão, ele compara caractere a caractere só até acabar o texto da direita. O comando existe na base do compilador — em stdwin.ch, traduzido para Set( _SET_EXACT,... ).

ADVPL / TLPP
Local cCod := "PGV001"
Local lOk := .F.

lOk := (cCod = "PGV") //.T. -- só 3 caracteres comparados
lOk := ("PGV" = cCod) //.F. -- agora o lado direito é o mais longo
lOk := (cCod = "") //.T. -- nada à direita para comparar
lOk := (cCod == "PGV") //.F. -- comparação exata

A terceira linha é a que mais custa: If cFiltro = "" parece testar se o filtro está vazio e é verdadeira para qualquer conteúdo. Para vazio, use Empty().

Como o = depende de uma configuração que qualquer rotina pode ter alterado, o seguro é == em todo texto.

O == tem a sua armadilha: considera os espaços. Campo chega com o tamanho do dicionário, e tem 15 posições, então SB1->B1_COD == "PGV001" é falso com o produto certo posicionado. Compare com AllTrim(SB1->B1_COD) ou complete o literal com PadR("PGV001", Len(SB1->B1_COD)).

Confira no seu ambienteSQL
SELECT X3_CAMPO, X3_TIPO, X3_TAMANHO
FROM SX3010
WHERE X3_CAMPO = 'B1_COD'
  AND D_E_L_E_T_ = ' '

O $ diferencia maiúsculas de minúsculas e é a forma curta de testar uma lista: If SB1->B1_TIPO $ "PA|PI". O separador evita casar o fim de um código com o começo do outro.

Lógicos e curto-circuito

OperadorResultado .T. quando
.And.os dois lados são verdadeiros
.Or.pelo menos um lado é verdadeiro
.Not. ou !o operando é falso

A avaliação vai da esquerda para a direita e para quando o resultado está decidido: num .And. com o primeiro lado falso, o segundo não roda; num .Or. com o primeiro verdadeiro, também não. O próprio padrão conta com isso para economizar avaliação cara: uma guarda de tipo barata evita gastar uma chamada mais pesada quando o dado já reprovou no primeiro teste. Em AjRetParam(), uma das condições é ValType(aRet) == "A" .AND. Len(aRet) == Len(aParamBox): se aRet não fosse array, o Len() do lado direito nem chegaria a ser avaliado.

A consequência prática é a ordem dos testes:

ADVPL / TLPP
// Guarda de tipo primeiro, acesso depois
If ValType(aItens) == "A" .And. Len(aItens) > 0 .And. aItens[1] > 0
 //...
EndIf

// Teste barato primeiro, consulta cara depois
If lIntegra .Or. U_PGV002(cPedido)
 //...
EndIf

O inverso também vale: função com efeito colateral à direita de um .And. pode não executar. Se precisa rodar sempre, chame antes e guarde o retorno. A guarda de tamanho em array está em Arrays em AdvPL.

Default: valor para parâmetro opcional

Parâmetro que não foi passado chega Nil. O Default resolve isso em uma linha, logo depois das declarações Local:

ADVPL / TLPP
User Function PGV001(cFilPed, nDias, lMostra)

    Local aArea := GetArea()

    Default cFilPed := xFilial("SC5")
    Default nDias   := 30
    Default lMostra := !IsBlind()

 //...

    RestArea(aArea)

Return Nil

Não é operador da linguagem, é comando do pré-processador: em protheus.ch, Default x := v vira x := If( x == nil, v, x ). Logo, só age sobre Nil — string vazia, zero e .F. passam intactos —, e o fonte precisa incluir protheus.ch. Sobre Local, veja Escopo de variáveis em AdvPL.

Precedência

Quando não há parênteses, a ordem de avaliação é:

  1. incremento e decremento prefixados (++nX);
  2. operadores de texto;
  3. aritméticos — potência, depois *, / e %, depois + e -;
  4. relacionais (==, <, $…);
  5. lógicos — .Not., depois .And., depois .Or.;
  6. atribuição;
  7. incremento e decremento pós-fixados (nX++).

O que morde é o 5: .And. vem antes de .Or., então lA .Or. lB .And. lC é lido como lA .Or. (lB .And. lC). Se a regra era “(A ou B) e C”, o código aceita registro que não devia. Parêntese não custa nada.

Próximo passo da série: Condições e laços em AdvPL.


Se uma regra de negócio sua depende de uma comparação que ninguém sabe explicar, fale com a gente.

Referências

  1. TDNtdn.totvs.com

    TDN — AdvPL (espaço TEC)

Continue por aqui