sabato 4 aprile 2015

Corso Lua - puntata 5 - Operazioni logiche e stringhe


Operatori logici

Gli operatori logici 'and' 'or' e 'not' danno luogo ad alcune espressioni idiomatiche di Lua.
Cominciamo con 'or': è un operatore logico binario. Se il primo operando è vero lo restituisce altrimenti restituisce il secondo.

Ricordatevi che in Lua un'espressione è vera se è il valore 'true' oppure se è un valore non nil, quindi per esempio nel seguente codice 'a' vale sempre 123.

local a = 123 or "mai assegnato"
print(a)


L'operatore 'and' restituisce il primo operando se esso è falso altrimenti restituisce il secondo operando.

Con and e or combinati otteniamo l'operatore ternario del C++ che è "a ? b:c" in Lua: Ecco l'espressione in un esempio di codice: se 'a' è vero restituisce 'b' altrimenti 'c':

local val = (a and b) or c

Poiché 'and' ha priorità maggiore rispetto a 'or' nell'espressione precedente possiamo omettere le parentesi per un codice più idiomatico:

local val = a and b or c


Il massimo tra due numeri è un'espressione condizionale:

local x = 45.69
local y = 564.3

local max
if x > y then
    max = x
else
    max = y
end

ma con gli operatori logici è tutto più Lua...
local x = 45.69
local y = 564.3

local max = (x > y) and x or y

L'operatore logico 'not' restituisce true se l'operando è nil oppure se è false --- valore booleano --- e viceversa restituisce false se l'operando non è nil oppure è true. Alcuni esempi:
print(not 5)       --> 'false'
print(not not 5)   --> 'true'
print(not true)    --> 'false'
print(not false)   --> 'true'
print(not nil)     --> 'true'

L'operatore di negazione può essere usato per controllare se una variabile è valida oppure no. Per esempio possiamo controllare se in una tabella esiste un campo 'prezzo':
local t = {} -- una tabella vuota

if not t.prezzo then -- t.prezzo è nil
    print("assente")
else
    print("presente")
end

t.prezzo = 12.00
if not t.prezzo then
    print("assente")
else
    print("presente")
end

Le stringhe

In Lua le stringhe rappresentano uno dei tipi di base del linguaggio.
Per rappresentare valori stringa letterali ci sono tre diversi delimitatori:
  • doppi apici: carattere ";
  • apice semplice: carattere ';
  • doppie parentesi quadre: delimitatori [[ e ]] con o senza =.

In una stringa delimitata da doppi apici possiamo inserire liberamente apici semplici e viceversa, e caratteri non stampabili come il ritorno a capo (\n) e la tabulazione (\t), tramite il carattere di escape backslash che quindi va inserito come doppio backslash:
local s1 = "doppi 'apici'"
local s2 = 'apici semplici e non "doppi"'
local s3 = "prima riga\nseconda riga"
local s4 = "una \\macro"
local s5 = "\""
assert( s5 == '"')

print(s1)
print(s2)
print(s3)
print(s4)
print(s5)

In Lua non esiste il tipo carattere quindi gli Autori del linguaggio hanno pensato di utilizzare i delimitatori normalmente destinati a rappresentare i char per consentire all'utente di creare stringhe contenenti i delimitatori doppi apici e viceversa, senza utilizzare l'escaping. Infatti i caratteri \" e \' rappresentano comunque i caratteri apici corrispondenti, come si vede nella variabile s5 del codice precedente.

Il terzo tipo di delimitatore per le stringhe è una coppia si parentesi quadre e ha la proprietà di ammettere il ritorno a capo. Si possono così introdurre nel sorgente interi brani di testo nel quale i caratteri di escaping non saranno interpretati.
local long_text = [[
Questo è un testo multiriga
dove i caratteri di escape non contano
come \n o \" o \' o \\.

Inoltre, se il testo come in questo caso
comincia con un ritorno a capo allora questo
carattere \n sarà ignorato.
]]

print(long_text)

Se per caso nel testo fossero presenti i delimitatori di chiusura è possibile inserire un numero qualsiasi di caratteri '=' tra le parentesi quadre, purché il numero sia lo stesso per i delimitatori di apertura e chiusura, esempio:
local long_text = [=[
Questo è il codice Lua da stampare:

local tab = {10, 20, 30}
local idx = {3, 2, 1}
print(tab[idx[1]]) -- notare doppia parentesi quadra chiusa

local long_text = [[ -- questo non potremo farlo...
    Testo lungo...
]]

Tutto chiaro?
In Lua le stringhe letterali nel codice
possono essere proprio letterali
senza caratteri di escape e senza
preoccupazioni sulla presenza di gruppi
di delimitazione di chiusura...
]=]

print(long_text)

Commenti multiriga

Questi delimitatori variabili con numero qualsiasi di segni '=' li troviamo nei commenti multiriga di Lua. Abbiamo incontrato fino a ora i commenti di riga che si introducono nel codice con un doppio trattino '--'.

I commenti multiriga sono comodi quando si vuol escludere dall'esecuzione un intero blocco di righe: iniziano con i doppi trattini seguiti da un delimitatore di stringa multiriga e terminano con la corrispondente chiusura:
-- questo è un commento di riga

--[[
questo è un commento
multiriga
]]

--[=[
e anche questo è un commento
multiriga
]=]

Normalmente in Lua i commenti multiriga vengono chiusi premettendo i doppi trattini anche al gruppo delimitatore di chiusura. Questo è solo un trucco per riattivare rapidamente il codice eventualmente contenuto nel commento, basta uno spazio per far diventare il commento multiriga :
--[[ righe di codice non attive

local tab = {}
--]]

-- [[ notare lo spazio dopo i doppi trattini
-- questo codice invece viene eseguito

local tab = {}
--]] -- e questo diventa una normale riga di commento

L'editor SciTE che vi consiglio per scrivere il codice Lua, è in grado di colorare correttamente il testo dei commenti di riga e di quelli multiriga.

Concatenare stringhe e immutabilità

In Lua l'operatore '..' concatena due stringhe, in questo modo:
local s1 = "Hello" .. " " .. "world"
local s2 = s1 .. " Ok"
s2 = s2 .. "."

print(s1 .. "!")
print(s2)

Il concetto importante riguardo alle stringhe è se queste siano o no immutabili. Se non lo sono la concatenazione di stringhe non comporta la creazione di una nuova stringa risultato dell'operazione. In Lua, come in molti altri linguaggi, le stringhe sono invece immutabili.
Ciò significa che una volta create le stringhe non possono essere modificate e nel codice precedente, l'operazione di concatenare il carattere punto in coda alla stringa 's2', genera una nuova stringa che è assegnata alla stessa variabile.

Per poche operazioni di concatenazione ciò non è un problema, ma in alcuni casi invece si. Consideriamo il seguente codice apparentemente innocuo:
local s = ""

for i = 1, 100 do
    s = s .. "**"
end

print(#s) -- l'operatore length funziona anche per le stringhe!

Ma cosa succede in dettaglio? Perché questo codice non è efficiente?
Ad ogni concatenazione viene creata una nuova stringa. La prima volta vengono copiati due byte per dare la stringa "**". La seconda iterazione la memoria copiata sarà di 4 byte, e alla terza di 6 byte, eccetera.
Ad ogni iterazione la memoria copiata cresce di due byte con il risultato che per produrre una stringa di 200 asterischi (200 byte) avremo copiato in totale la memoria equivalente a 10100 byte!

In Java e negli altri linguaggi con stringhe immutabili normalmente si corre ai ripari mettendo a disposizione una struttura dati o una funzione che risolve il problema, per esempio un tipo StringBuffer. In Lua la soluzione è una funzione della libreria table che, anticipando rispetto alle nostre chiaccherate è table.concat():
local t = {}
for i = 1, 100 do
    t[#t + 1] = "**"
end

print(#table.concat(t))

Per la cronaca, nel caso specifico avremo dovuto usare la funzione string.rep() ma table.concat() è più generale.

Esercizi


1 - Prevedere il risultato delle seguenti espressioni Lua:
local a = 1 or 2
local b = 1 and 2
local c = "text" or 45

local d = not 12 or "ok"
local e = not nil or "ok"

2 - Usando gli operatori logici di Lua codificare l'espressione che restituisce la stringa "grande", "uguale" o "piccolo" a seconda dei valori numerici contenuti in due variabili.

3 - Come fare in Lua per creare una stringa letterale contenente sia il carattere apice semplice che doppio?

4 - Quale sarà il risultato dell'esecuzione del seguente codice?

local s = "'"..'"'.."ok"..[["']]
print(s)

5 - Creare la stringa "\/".

6 - Scrivere un programma che a partire dalla stringa "*" crei e stampi la stringa di 64 asterischi senza utilizzare l'operatore di concatenazione o la funzione string.rep().

7 - Scrivere un programma che a partire dalla stringa "*" crei e stampi la stringa di 64 asterischi usando l'operatore di concatenazione il minimo indispensabile di volte.

Riassunto della puntata

Oggi abbiamo introdotto gli operatori logici, le modalità di inserimento delle stringhe e la loro concatenazione. Una puntata un po' noiosa se vogliamo, ma questi dettagli devono pur essere conosciuti per programmare.

D'altra parte non possiamo --- specie per le stringhe --- studiare esempi significativi fino a che non avremmo introdotto la libreria standard di Lua che a sua volta non possiamo introdurre fino a che non avremmo parlato delle funzioni.

E allora facciamolo, la prossima volta parleremo delle funzioni in Lua.
Alla prossima.

Nessun commento:

Posta un commento