Tipos
Inteiros e reais que dizem a largura, a família monetária, as quatro cadeias, enumerados, conjuntos, vetores e Variant.
O nome diz a largura#
Em Fenner o nome do tipo carrega o tamanho. Os nomes herdados do Delphi (Integer, Cardinal, Extended…) não existem, e o compilador aponta o erro:
Erro: tipo desconhecido ou não suportado: "Integer"| Família | Tipos |
|---|---|
| Inteiros com sinal | Int8 Int16 Int32 Int64 Int128 Int256 Int512 Int1024 Int2048 Int4096 |
| Inteiros sem sinal | UInt8 UInt16 UInt32 UInt64 UInt128 UInt256 UInt512 UInt1024 UInt2048 UInt4096 |
| Reais | Float16 Float32 Float64 Float128 |
| Monetários | Currency8 Currency16 Currency32 Currency64 Currency128 Currency256 Currency512 Currency1024 Currency2048 Currency4096 |
| Lógicos | Bool8 Bool16 Bool32 Bool64 |
| Caracteres | AChar (1 byte), WChar (UTF-16, 2 bytes) |
| Ponteiros | Pointer (o mesmo que ^void), ^T tipado |
| Inteiros do tamanho do ponteiro | NativeInt, NativeUInt |
program Types;
// Os nomes dos tipos dizem a largura: nada de Integer, Cardinal ou Extended.
const
Million = 1_000_000; // o "_" só separa dígitos
Mask = 0b1010_1010; // binário
Permissions = 0o755; // octal
Color = 0xFF8800; // hexadecimal ($FF8800, do Pascal, também vale)
var
Small: Int8;
Wide: UInt64;
Huge: Int256;
Price: Currency64;
Ratio: Float64;
begin
Small := 127;
Wide := 18_446_744_073_709_551_615;
WriteLn('Int8: ', Small, ' (', SizeOf(Int8), ' byte)');
WriteLn('UInt64: ', Wide, ' (', SizeOf(UInt64), ' bytes)');
// inteiros largos: a aritmética é nativa, sem biblioteca de números grandes
Huge := 1_000_000_000_000_000_000;
Huge := Huge * Huge * Huge;
WriteLn('10^54 = ', Huge, ' (', SizeOf(Int256), ' bytes)');
WriteLn('10^54 div 10^18 = ', Huge div 1_000_000_000_000_000_000);
// moeda: ponto fixo; o literal não passa por Float64
Price := 19.99;
WriteLn('19.99 * 3 = ', Price * 3);
Ratio := 1 / 3;
WriteLn('1 / 3 = ', Ratio);
WriteLn(Million, ' ', Mask, ' ', Permissions, ' ', Color);
end.Int8: 127 (1 byte)
UInt64: 18446744073709551615 (8 bytes)
10^54 = 1000000000000000000000000000000000000000000000000000000 (32 bytes)
10^54 div 10^18 = 1000000000000000000000000000000000000
19.99 * 3 = 59.9700
1 / 3 = 0.333333
1000000 170 493 16746496Os inteiros largos#
De Int256 a Int4096 (e os UInt correspondentes) a aritmética é nativa: somar, multiplicar, dividir, comparar e converter saem como instrução do LLVM, não como chamada de rotina. Não há biblioteca de números grandes por trás: é o mesmo add/sdiv dos tipos pequenos, só que mais largo. A única peça escrita à mão é a impressão, porque printf não formata mais que 64 bits.
A promoção de tipos numa conta é aritmética, não uma escada de if: o resultado tem a maior das duas larguras, e um operando sem sinal dessa largura sobe um degrau, porque só assim todo valor dele continua representável. Por isso UInt64 + Int32 dá Int128, e UInt128 + Int32 dá Int256.
Float16 e Float32 guardam estreito e calculam em Float64.
A família monetária#
Ponto fixo com sinal. A largura está no nome; as casas decimais não seguem a largura. Os dois primeiros são os que já existiam, e de 256 para cima é bits/8:
| Tipo | Casas | Tipo | Casas |
|---|---|---|---|
Currency8 | 1 | Currency256 | 32 |
Currency16 | 2 | Currency512 | 64 |
Currency32 | 3 | Currency1024 | 128 |
Currency64 | 4 | Currency2048 | 256 |
Currency128 | 12 | Currency4096 | 512 |
Soma e subtração são inteiras. Produto e divisão passam pela largura dobrada: o produto de dois valores escalados tem a escala ao quadrado, e tirar uma delas é dividir. O arredondamento é meio para longe do zero, em toda a família.
Larguras misturadas reescalam. Somar Currency32 (3 casas) com Currency256 (32) multiplica o estreito por 10²⁹; a conta acontece na largura maior, e só então o resultado estreita. Moeda de 128 bits ou mais ganha do Float64 numa conta; de 64 bits ou menos perde (a regra do Delphi).
As quatro cadeias de texto#
| Tipo | Codificação | Observação |
|---|---|---|
LString | bytes, sem codificação declarada | só converte por cast explícito |
U8String | UTF-8 | conversão implícita entre as codificações conhecidas |
UString | UTF-16 | conversão implícita entre as codificações conhecidas |
WString | UTF-16 (um BSTR de verdade) | para COM; alocada pelo OleAut32 |
Todas compartilham o cabeçalho [contagem de referências][tamanho], o mesmo do vetor dinâmico. A indexação é base 1 (S[1] é o primeiro caractere). Há ainda a cadeia curta SString[N], com o tamanho no byte 0.
program Strings;
const
Query =
'''
select * from clientes
where nome like '%silva%'
''';
var
U: UString; // UTF-16
U8: U8String; // UTF-8
L: LString; // bytes, sem codificação declarada
begin
U := 'Olá, mundo';
U8 := U; // conversão implícita entre codificações conhecidas
WriteLn('UString: ', Length(U), ' caracteres');
WriteLn('U8String: ', Length(U8), ' bytes');
L := 'abc';
WriteLn(L + '-' + LString(U8)); // LString só converte com cast explícito
WriteLn(Copy(U, 1, 3), ' | ', Pos('mundo', U));
U := U + '!' + #13#10 + 'segunda linha';
WriteLn(U);
// cadeia de várias linhas: o recuo do fecho sai de cada linha
WriteLn('[', Query, ']');
end.UString: 10 caracteres
U8String: 11 bytes
abc-Olá, mundo
Olá | 6
Olá, mundo!
segunda linha
[select * from clientes
where nome like '%silva%']Como são quatro cadeias, as conversões dizem qual você quer: IntToUStr, IntToLStr, IntToU8Str, UStrToInt32… Não existe IntToStr.
Delete, Insert, Pos e Copy são formas da linguagem; as dezenas de funções auxiliares moram na RTL. Uma constante tipada de cadeia (const S: UString = '…';) vale para LString, U8String e UString: o literal já sai como bloco estático, e a constante é só o endereço dele.
Enumerados, conjuntos e faixas#
type
TColor = (Red, Green, Blue);
TColors = set of TColor;
TByteRange = 0..255; // vira o MENOR inteiro que contém a faixa (UInt8)program EnumsSets;
type
TColor = (Red, Green, Blue);
TColors = set of TColor;
TStatus = (
Stopped = 0,
Running = 5,
Paused = 7
);
var
C: TColor;
S: TColors;
St: TStatus;
begin
// o escopo do enum é obrigatório: TColor.Green
C := TColor.Green;
S := [TColor.Red, TColor.Green];
WriteLn(C in S);
WriteLn(TColor.Blue in S);
WriteLn(TColor.Blue not in S);
Include(S, TColor.Blue);
Exclude(S, TColor.Red);
WriteLn(TColor.Red in S);
// no rótulo do case, o seletor já fixa o tipo: Green: vale sem o escopo
case C of
Red: WriteLn('vermelho');
Green: WriteLn('verde');
Blue: WriteLn('azul');
end case;
St := TStatus.Running;
WriteLn(Ord(St)); // valores explícitos, em qualquer ordem
WriteLn(Ord(TStatus.Paused));
end.TRUE
FALSE
TRUE
FALSE
verde
5
7- O escopo do enumerado é obrigatório (
TColor.Green), exceto no rótulo de umcasecujo seletor já fixa o tipo, ondeGreen:vale. - Valores explícitos podem ir em qualquer ordem, e um membro pode ser negativo: isso faz o enumerado ser guardado com sinal, e a comparação acerta sozinha. A largura é o maior entre o que menor..maior exige e o
{$config minenumsize N}, e é daí que vêm os 4 bytes que a API do Windows espera. - Um
set ofé um bitset de até 64 elementos; um conjunto de enumerado com membro negativo é recusado.in,not in,IncludeeExcludevalem. - Um tipo faixa não muda o binário: acrescenta verificação em compilação para valores constantes. A verificação em execução é opcional (
{$config rangechecks on}).
Vetores#
program Arrays;
type
TByteRange = 0..255; // vira UInt8: a faixa é checada em compilação
TMatrix = array[1..2, 1..3] of Int32;
function Sum(const AValues: array of Int32): Int32; // vetor aberto
var
I: Int32;
begin
Result := 0;
for I := 0 to High(AValues) do
Inc(Result, AValues[I]);
end function;
var
Fixed: array[0..4] of Int32; // estático
Names: array of UString; // dinâmico (contagem de referências)
Matrix: TMatrix;
Short: SString[10]; // cadeia curta: o tamanho vai no byte 0
R: TByteRange;
I, J: Int32;
begin
for I := 0 to 4 do
Fixed[I] := I * I;
WriteLn(Length(Fixed), ' elementos, de ', Low(Fixed), ' a ', High(Fixed));
SetLength(Names, 2);
Names[0] := 'um';
Names[1] := 'dois';
SetLength(Names, 3); // cresce preservando o conteúdo
WriteLn(Length(Names), ' nomes; o segundo é ', Names[1]);
for I := 1 to 2 do
for J := 1 to 3 do
Matrix[I, J] := I * 10 + J;
WriteLn('Matrix[2, 3] = ', Matrix[2, 3]);
Short := 'cadeia curta demais';
WriteLn(Short, ' (', Length(Short), ')');
R := 200;
WriteLn(R, ' cabe em ', SizeOf(TByteRange), ' byte');
WriteLn(Sum([1, 2, 3, 4])); // literal de vetor passado a um vetor aberto
end.5 elementos, de 0 a 4
3 nomes; o segundo é dois
Matrix[2, 3] = 23
cadeia cur (10)
200 cabe em 1 byte
10| Forma | O que é |
|---|---|
array[0..9] of Int32 | estático; aceita vários índices (array[1..2, 1..3]) |
array of Int32 | dinâmico, com contagem de referências atômica |
array[0..?] of UInt8 = (1, 2, 3) | o ? conta a lista de valores |
const A: array of Int32 | vetor aberto, como parâmetro; aceita um literal [1, 2, 3] |
array of const | lista de valores de tipos variados (a base do Format) |
downin percorre um vetor do fim para o começo (veja Comandos).
Variant#
Variant e OleVariant não são tipos embutidos no compilador: são records gerenciados da RTL. O que eles fazem sai de class operator: Initialize/Finalize/Assign cuidam do tempo de vida, Implicit converte nos dois sentidos, e Add…GreaterThan dão a aritmética. A diferença entre os dois é a de sempre: o OleVariant só carrega o que o Automation entende e guarda o texto como BSTR, para ir direto a uma API COM.
program Variants;
var
V: Variant;
begin
V := 10;
V := V * 3; // aritmética: o Variant é um record gerenciado da RTL
WriteLn(UString(V));
V := 'agora é texto'; // o mesmo Variant, outro conteúdo
WriteLn(UString(V));
end.30
agora é textoPonteiros#
^T é um ponteiro tipado, Pointer o sem tipo. @X é o endereço, P^ o conteúdo, New/Dispose alocam e liberam um tipo, GetMem/FreeMem blocos. Um ponteiro para um tipo declarado depois (PNode = ^TNode) é aceito. A aritmética de ponteiro existe, e a sobreposição absolute e as regiões {$code unsafe} são o caminho para o que o código seguro recusa.
program Pointers;
type
PNode = ^TNode; // ponteiro para um tipo declarado depois
TNode = record
Value: Int32;
Next: PNode;
end record;
var
Head, Node: PNode;
I, Sum: Int32;
X: Int32;
PX: ^Int32;
begin
X := 41;
PX := @X;
PX^ := PX^ + 1;
WriteLn('X via ponteiro: ', X);
// lista ligada: insere na frente
Head := nil;
for I := 1 to 5 do
begin
New(Node);
Node^.Value := I * 10;
Node^.Next := Head;
Head := Node;
end for;
Sum := 0;
Node := Head;
while Node <> nil do
begin
Sum := Sum + Node^.Value;
Node := Node^.Next;
end while;
WriteLn('soma da lista: ', Sum);
while Head <> nil do
begin
Node := Head^.Next;
Dispose(Head);
Head := Node;
end while;
end.X via ponteiro: 42
soma da lista: 150