Pragiva — Tecnologia na prática

Arrays em AdvPL: índice em 1, aDel que não encolhe e cópia por referência

Como criar, crescer, procurar, ordenar e remover itens de um array em AdvPL, por que aDel precisa de aSize e por que atribuir um array não copia nada.

Desenvolvimento Tutorial 4 min de leitura

Assista o vídeo

Resposta rápida

Primeiro índice
1, não 0. aLista[0] gera erro de execução.
Tamanho
Len(aLista), sempre recalculado — o array cresce e encolhe em tempo de execução.
Remover
aDel seguido de aSize. Só aDel deixa um Nil no fim.
Procurar
aScan devolve a posição, ou 0 quando não acha.
Copiar
aClone. Atribuir com := só cria outro nome para o mesmo array.
Nesta página8 seções

Array é o tipo que mais aparece em customização de Protheus: aHeader e aCols de uma grade, o cabeçalho e os itens de um ExecAuto, o PARAMIXB de um ponto de entrada. Este artigo cobre as operações do dia a dia e as três armadilhas que mais geram chamado. Os outros tipos estão em tipos de dados em AdvPL.

Criar e crescer

Há duas formas de nascer: vazio, com {}, ou com tamanho fixo, com Array(n). Nesse segundo caso, cada posição começa com Nil.

ADVPL / TLPP
Local aFiliais := {}          // vazio, Len() == 0
Local aMeses   := Array(12)   // 12 posições, todas Nil
Local nX       := 0

aAdd(aFiliais, "0101")        // Len() == 1
aAdd(aFiliais, "0102")        // Len() == 2

For nX := 1 To Len(aFiliais)
    ConOut("Filial " + cValToChar(nX) + ": " + aFiliais[nX])
Next nX

Um array aceita elementos de tipos diferentes na mesma lista — caractere, número, data, outro array. Isso é liberdade e também fonte de erro: nada garante que aLista[3] é o que você imagina. O laço For e suas variações estão em condições e laços.

Inserir no meio: aSize e depois aIns

aAdd só acrescenta no fim. Para abrir espaço numa posição específica, o caminho é aumentar o array em uma posição e depois deslocar:

ADVPL / TLPP
Local aFila := {"B", "C", "D"}

aSize(aFila, Len(aFila) + 1)  // {"B","C","D",Nil}
aIns(aFila, 1)                // {Nil,"B","C","D"}
aFila[1] := "A"               // {"A","B","C","D"}

aIns não aumenta nada: ele empurra os elementos para baixo e o último cai fora. Sem o aSize antes, você perde o "D" sem nenhum aviso.

Remover: aDel não encolhe

É o espelho do caso anterior e a armadilha mais comum. aDel apaga a posição, puxa os de baixo para cima e deixa Nil na última posição. O tamanho continua o mesmo.

ADVPL / TLPP
Local aFila := {"A", "B", "C", "D"}

aDel(aFila, 2)                // {"A","C","D",Nil}  -> Len() ainda é 4
aSize(aFila, Len(aFila) - 1)  // {"A","C","D"}      -> Len() agora é 3

Esquecer o aSize não dá erro ali. Dá erro depois, num For qualquer que concatena aFila[nX] com texto e encontra o Nil no fim. O aSize corta sempre as últimas posições — funciona justamente porque o aDel já levou o Nil para lá.

Atribuir aFila[2] := Nil também não remove: só esvazia o conteúdo.

Procurar: aScan com valor e com bloco

Em array simples, passe o valor procurado:

ADVPL / TLPP
nPos := aScan(aFiliais, "0102")   // 2, ou 0 se não existir

Em array de arrays, o valor não serve — é preciso dizer onde comparar. Para isso existe o bloco de código, que recebe cada elemento e devolve .T. quando achou. O próprio padrão faz assim: GdFieldPos, a função que devolve a coluna de um campo na grade e está declarada é um aScan com bloco sobre o aHeader, comparando a segunda posição de cada linha com o nome do campo.

ADVPL / TLPP
Local aPrecos := {{"PA001", 12.50}, {"PA002", 8.90}, {"PA003", 30.00}}
Local nPos    := 0

nPos := aScan(aPrecos, {|aItem| aItem[1] == "PA002"})

If nPos > 0
    ConOut("Preço: " + cValToChar(aPrecos[nPos, 2]))
EndIf

Teste o retorno sempre. aPrecos[0, 2] com nPos zerado é erro de execução em produção.

Ordenar: aSort com bloco

aSort ordena o próprio array. Em array simples, basta passá-lo. Em array de arrays, o bloco recebe dois elementos e devolve .T. quando o primeiro deve vir antes:

ADVPL / TLPP
// do maior preço para o menor
aSort(aPrecos,,, {|x, y| x[2] > y[2]})

As duas vírgulas vazias são as posições inicial e a quantidade, que ficam no padrão (o array inteiro). O mesmo formato aparece no próprio padrão, por exemplo na função Ctb280Proc(), ordenando um aCols pela primeira coluna.

Copiar: array é referência

Esta é a que mais custa, porque o código parece certo:

ADVPL / TLPP
Local aOriginal := {"A", "B"}
Local aCopia    := aOriginal     // NÃO copia

aCopia[1] := "Z"
ConOut(aOriginal[1])             // "Z"

A variável guarda uma referência ao array, não o conteúdo. aCopia := aOriginal cria um segundo nome para o mesmo array. O mesmo vale quando você passa um array como parâmetro: a função chamada altera o array de quem chamou. É exatamente o risco com aCols em ponto de entrada, discutido em PARAMIXB.

Para ter uma cópia independente, inclusive dos subarrays:

ADVPL / TLPP
aCopia := aClone(aOriginal)

Multidimensional: o formato do ExecAuto

Array de arrays é como o Protheus representa tabela em memória. O aCols tem uma linha por item e uma coluna por campo; o aHeader descreve essas colunas. O ExecAuto usa outro formato: uma lista de trios campo, valor e Nil, um por campo que você quer preencher. A função A036GrvNF() monta o cabeçalho de um pedido de venda exatamente assim, campo a campo, sem precisar descrever a estrutura inteira da tabela antes.

ADVPL / TLPP
Local aCab    := {}
Local aItens  := {}
Local aLinha  := {}

aAdd(aCab, {"C5_TIPO", "N", Nil})
aAdd(aCab, {"C5_CLIENTE", cCliente, Nil})
aAdd(aCab, {"C5_LOJACLI", cLoja, Nil})
aAdd(aCab, {"C5_CONDPAG", cCondPag, Nil})

aLinha := {}
aAdd(aLinha, {"C6_ITEM", "01", Nil})
aAdd(aLinha, {"C6_PRODUTO", cProduto, Nil})
aAdd(aLinha, {"C6_QTDVEN", 10, Nil})
aAdd(aLinha, {"C6_PRCVEN", 12.50, Nil})
aAdd(aItens, aLinha)

Repare no aLinha := {} antes de cada item. Sem ele, e por causa da referência, todos os itens de aItens apontariam para o mesmo array — e o pedido sairia com a última linha repetida. Recriar o array a cada volta (ou adicionar um aClone(aLinha)) resolve.

Para ler um valor desse formato, volta o aScan com bloco:

ADVPL / TLPP
nPos := aScan(aCab, {|x| AllTrim(x[1]) == "C5_CLIENTE"})

Quando o ExecAuto recusa o array, o caminho de diagnóstico está em MsExecAuto: como descobrir por que a rotina falhou, e a escolha entre ele e a gravação direta, em RecLock ou ExecAuto.

Em resumo

OperaçãoFunçãoCuidado
Criar{} ou Array(n)Array(n) começa com Nil
AcrescentaraAddsó no fim
Inserir no meioaSize + aInssem aSize, perde o último
RemoveraDel + aSizesem aSize, sobra Nil
ProcuraraScantestar > 0
OrdenaraSortbloco devolve .T. se x vem antes
CopiaraClone:= não copia

O próximo passo da série são os operadores, incluindo as comparações que você acabou de usar dentro dos blocos.

Referências

  1. TDNtdn.totvs.com

    TDN — AScan
  2. TDNtdn.totvs.com

    TDN — ASort
  3. TDNtdn.totvs.com

    TDN — ADel
  4. TDNtdn.totvs.com

    TDN — AClone

Continue por aqui