Declarações e comandos
const, type e var; variáveis que nascem no meio do bloco; if, case, laços, rótulos, with e os operadores.
Declarações#
const
Limit = 3; // constante
Greeting: UString = 'olá'; // constante tipada
Names: array[0..2] of UString = ('um', 'dois', 'três'); // de vetor também
var
Total: Int32 = 100; // variável global com valor inicial
Fresh: default Int32; // "default": a variável nasce zeradaconst aceita constantes simples, tipadas, de vetor e de record (inclusive com parte variante). var aceita valor inicial. threadvar declara uma variável por thread.
program Declarations;
const
Limit = 3; // constante
Greeting: UString = 'olá'; // constante tipada
Names: array[0..2] of UString = ('um', 'dois', 'três'); // constante tipada de vetor
type
TPoint = record
X, Y: Int32;
end record;
TItem = class(TObject)
public
Value: Int32;
end class;
var
Total: Int32 = 100; // global com valor inicial
P: TPoint;
function NewItem(const AValue: Int32): TItem;
begin
Result := TItem.Create;
Result.Value := AValue;
end function;
procedure Demo;
var
Fresh: default Int32; // "default" faz a variável nascer zerada
begin
WriteLn('Fresh = ', Fresh);
end procedure;
begin
WriteLn(Greeting, ' ', Names[Limit - 1], ' ', Total);
Demo;
// with sobre um valor, com apelido: o objeto vive enquanto o bloco o usa
with NewItem(7) as It do
WriteLn('Item.Value = ', It.Value);
P.X := 1;
P.Y := 2;
WriteLn('P = (', P.X, ', ', P.Y, ')');
end.olá três 100
Fresh = 0
Item.Value = 7
P = (1, 2)Variáveis que nascem prontas#
Duas formas deixam de lado o "declare tudo no alto":
A lista entre parênteses depois do tipo constrói o objeto. C: TCounter(10) é a chamada de TCounter.Create(10), com a mesma escolha de sobrecarga. O comando entra no começo do corpo, o único ponto em que "já construído ao entrar" vale por todos os caminhos. Só em variável local, e uma por linha.
var no meio de um bloco. Um begin…end é um escopo: o var escrito no meio dele vale dali até o end, e o mesmo nome pode voltar num bloco irmão com outro tipo.
| Forma | O que faz |
|---|---|
var X: T := V; | tipo escrito, valor inicial |
var X: T(args); | tipo escrito, objeto construído |
var X := V; | o tipo vem do valor (o valor é obrigatório) |
for var I := 1 to N do | a variável do laço vive só no laço |
program InlineVars;
type
TCounter = class(TObject)
public
Start: Int32;
constructor Create(const AStart: Int32);
destructor Destroy; override;
end class;
constructor TCounter.Create(const AStart: Int32);
begin
Start := AStart;
WriteLn(' Create(', AStart, ')');
end constructor;
destructor TCounter.Destroy;
begin
WriteLn(' Destroy(', Start, ')');
inherited Destroy;
end destructor;
procedure Demo;
var
C: TCounter(10); // declarada E construída: TCounter.Create(10)
begin
WriteLn('corpo: ', C.Start);
if C.Start > 5 then
begin
var Total := C.Start * 2; // o tipo vem do valor
var Msg: UString := 'bloco';
WriteLn(Msg, ' ', Total);
end if; // Total e Msg morrem aqui
for var I := 1 to 3 do
Write(I, ' ');
WriteLn;
end procedure;
begin
Demo;
WriteLn('fim');
end. Create(10)
corpo: 10
bloco 20
1 2 3
Destroy(10)
fimO que for gerenciado (objetos, cadeias, interfaces) morre no end, num try…finally que o compilador monta, de modo que um Exit no meio do bloco também solta o que o bloco criou. A destruição acontece na ordem inversa da declaração.
Condicionais#
if N < 2 then
Result := N
else
Result := Fib(N - 1) + Fib(N - 2);if também é uma expressão (o else passa a ser obrigatório), na grafia por extenso ou na com ?:
WriteLn(if I > 5 then 'grande' else 'pequeno');
WriteLn(I > 5 ? 'grande' : 'pequeno');Só o ramo escolhido é avaliado. Os dois ramos têm de terminar no mesmo tipo: número com número dá o mais largo, dois objetos dão o ancestral comum, e nil aceita o tipo do outro lado.
case#
case I of
0..4: WriteLn('baixo');
5, 6, 7: WriteLn('médio');
8..99: WriteLn('alto');
else
WriteLn('fora da faixa');
end case;O seletor pode ser inteiro, enumerado ou caractere. Os rótulos aceitam faixas, listas e expressões constantes (VT_VECTOR or VT_UI1:), desde que o seletor seja inteiro.
Para cadeias há scase (sensível a maiúsculas) e icase (insensível); uscase e uicase são as versões para UTF:
program CaseStrings;
var
Command: UString;
begin
Command := 'Stop';
// scase: o case de cadeia, sensível a maiúsculas
scase Command of
'start': WriteLn('iniciar');
'Stop': WriteLn('parar');
else
WriteLn('comando desconhecido');
end scase;
// icase: o mesmo, sem diferenciar maiúsculas de minúsculas
icase Command of
'START': WriteLn('iniciar');
'STOP': WriteLn('parar (sem diferenciar a caixa)');
else
WriteLn('comando desconhecido');
end icase;
end.parar
parar (sem diferenciar a caixa)Laços#
| Forma | Observação |
|---|---|
for I := A to B do / downto | for var I := … deixa a variável viver só no laço |
for var I in A..B do | sobre uma faixa; a direção vem dos valores (0..1000 sobe, 10000..0 desce) |
while Cond do | testa antes |
repeat … until Cond | testa depois |
for X in Colecao do | sobre vetores, sobre um campo ChainRoot e sobre chain(obj) |
for X downin Colecao do | percorre ao contrário (também vale para vetores) |
Break sai do laço, Continue passa à próxima volta. Para sair de fora de um laço aninhado, sem bandeira nem goto, o laço recebe um rótulo entre colchetes e Break o cita pelo nome (ou pelo índice, contado de dentro para fora; Break pelado é Break(0)):
for [outer] I := 1 to 3 do
for [inner] J := 1 to 3 do
begin
if J = 2 then
Break(outer); // sai dos dois laços
end for;O nome é melhor que o índice, porque não muda de significado quando alguém insere um laço no meio. Um salto fora de alcance é erro de compilação.
program Control;
var
I, J, N: Int32;
Q, R: Int32;
Names: array[0..2] of UString;
Name: UString;
begin
Names[0] := 'Ana'; Names[1] := 'Bia'; Names[2] := 'Caio';
// for, com e sem passo invertido
for I := 1 to 3 do
Write(I, ' ');
for I := 3 downto 1 do
Write(I, ' ');
WriteLn;
// for sobre faixa: a direção vem dos valores
for var K: Int32 in 0..3 do
Write(K, ' ');
WriteLn;
// for-in sobre vetor; downin percorre do fim para o começo
for Name in Names do
Write(Name, ' ');
WriteLn;
for Name downin Names do
Write(Name, ' ');
WriteLn;
// laço com rótulo: Break(nome) sai do laço de fora, sem bandeira nem goto
N := 0;
for [outer] I := 1 to 3 do
for [inner] J := 1 to 3 do
begin
Inc(N);
if J = 2 then
Break(outer);
end for;
WriteLn('passos: ', N);
// repeat..until e while com Continue
I := 0;
repeat
Inc(I);
if Odd(I) then
Continue;
Write(I, ' ');
until I >= 8;
WriteLn;
// quociente e resto numa conta só
Q, R := 17 divmod 5;
WriteLn('17 = 5 * ', Q, ' + ', R);
// if como valor, nas duas grafias
WriteLn(if I > 5 then 'grande' else 'pequeno');
WriteLn(I > 5 ? 'grande' : 'pequeno');
// case aceita faixas e listas
case I of
0..4: WriteLn('baixo');
5, 6, 7: WriteLn('médio');
8..99: WriteLn('alto');
end case;
end.1 2 3 3 2 1
0 1 2 3
Ana Bia Caio
Caio Bia Ana
passos: 2
2 4 6 8
17 = 5 * 3 + 2
grande
grande
altoExit, Result e Halt#
Result é a variável de retorno de uma função. Exit sai da rotina atual; Exit(valor) sai devolvendo um valor (e é a forma mais usada no lugar de atribuir a Result e sair). Halt encerra o programa. Dentro de um try…finally, o finally roda de qualquer jeito.
with#
with NewItem(7) as It do
WriteLn(It.Value);O with aceita um apelido (as It): a referência vai para uma local escondida e o objeto continua vivo enquanto o bloco o usa. Com o apelido, os nomes usados dentro do bloco ficam explícitos, em vez de se misturarem com os de fora.
Quociente e resto numa conta só#
Q, R := 17 divmod 5; // Q = 3, R = 2Operadores#
| Grupo | Operadores |
|---|---|
| Aritméticos | + - * / div mod |
| Bits | and or xor not shl shr (o shr é lógico sobre a largura do tipo) |
| Comparação | = <> < > <= >= |
| Conjuntos e tipos | in not in is as |
| Ponteiros | @ ^ |
| Anuláveis | ?T ?X ?? ?. ! — veja Nulos e exceções |
| Valor | C ? A : B e if C then A else B |
and e or avaliam só o necessário (curto-circuito). A atribuição é :=. Os operadores de um record (Add, Equal, Assign, Increment…) são declarados com class operator; veja Classes.
Rótulos e goto#
label e goto existem, para os poucos casos em que o fluxo estruturado não serve. O rótulo é declarado na rotina onde é usado, prefixa um comando e o desvio fica dentro da rotina; atravessar um try/finally/except não vale. Para sair de laços, prefira o laço com rótulo e Break(nome).
Seções protegidas: sync#
sync(Semaforo[, Prazo]) do … else … é uma região protegida por semáforo, com o else do prazo esgotado. Quando entra por um Exit no meio, o semáforo é solto. Quem diz o que é um semáforo é a RTL.
Prefira o end nomeado#
Todo bloco que se fecha com end pode dizer o que está fechando, repetindo a palavra que o abriu: end class, end record, end interface, end procedure, end function, end constructor, end destructor, end operator, end case, end try, end if, end for, end while, end with e end region. O end sozinho continua valendo, mas o nome é o estilo recomendado, e é assim que o código da RTL, da WCL, dos exemplos e dos demos está escrito.
Duas vantagens. Quem lê um end lá no fim de uma rotina comprida sabe na hora o que ele fecha, sem subir a tela procurando o begin. E o compilador confere: um end case onde está aberto um if é erro, com a mensagem dizendo o que realmente está aberto. Assim, um end esquecido ou sobrando é apontado perto de onde está, e não no fim do arquivo.
program EndNomeado;
// O "end" pode dizer o que está fechando. O compilador confere: "end case" num "if" é erro.
type
TPoint = record
X: Int32;
Y: Int32;
end record;
TCounter = class
private
FValue: Int32;
public
procedure Add(const AAmount: Int32);
function Value: Int32;
end class;
procedure TCounter.Add(const AAmount: Int32);
begin
FValue := FValue + AAmount;
end procedure;
function TCounter.Value: Int32;
begin
Result := FValue;
end function;
procedure Describe(const AKind: Int32);
begin
case AKind of
1: WriteLn('um');
2: WriteLn('dois');
else
WriteLn('outro');
end case;
end procedure;
var
LCounter: TCounter;
LPoint: TPoint;
LIndex: Int32;
begin
LCounter := TCounter.Create;
try
for LIndex := 1 to 3 do
begin
LCounter.Add(LIndex);
if LIndex = 2 then
begin
WriteLn('dois!');
end if;
end for;
WriteLn('soma = ', LCounter.Value);
finally
LCounter.Free;
end try;
LPoint.X := 1;
LPoint.Y := 2;
Describe(LPoint.X);
Describe(LPoint.Y);
Describe(9);
end.dois!
soma = 6
um
dois
outroCada bloco fecha com o seu nome