Você está na página 1de 300

Machine Translated by Google

Machine Translated by Google

Marco Cantu

Manual de objetos Pascal


Delphi 11 Alexandria Edição

O guia completo para a linguagem de


programação Object Pascal para Delphi 11 Alexandria

Edição original: Piacenza (Itália), julho de


2015 Delphi 10.4 Edição: Piacenza (Itália), março de
2021 Delphi 11 Alexandria Edição: Piacenza (Itália), novembro de 2021
Machine Translated by Google

Autor: Marco Cantù


Editora: Marco Cantù
Editores: Peter WA Wood (primeira edição), Andreas Toth (segunda edição)
Designer da capa: Fabrizio Schiavi (www.fsd.it)

Copyright 1995-2021 Marco Cantù, Piacenza, Itália. Direitos mundiais reservados.

O autor criou código de exemplo nesta publicação expressamente para uso gratuito de seus leitores.
O código-fonte deste livro é freeware protegido por direitos autorais, distribuído por meio de um projeto GitHub listado
no livro e no site do livro. Os direitos autorais impedem que você republice o código em mídia impressa ou eletrônica
sem permissão. Os leitores recebem permissão limitada para usar este código em seus aplicativos, desde que o código
em si não seja distribuído, vendido ou explorado comercialmente como um produto independente.

Além da exceção acima relativa ao código-fonte, nenhuma parte desta publicação pode ser armazenada em um
sistema de recuperação, transmitida ou reproduzida de qualquer forma, seja no original ou em um idioma traduzido,
incluindo, entre outros, fotocópia, fotografia, magnético ou outro registro, sem o acordo prévio e permissão por
escrito do editor.

Delphi é uma marca registrada da Embarcadero Technologies (uma divisão da Idera, Inc.). Outras marcas registradas
são dos respectivos proprietários, conforme mencionado no texto. Embora o autor e a editora tenham feito
seus melhores esforços para preparar este livro, eles não fazem nenhuma representação ou garantia de qualquer
tipo com relação à integridade ou precisão do conteúdo aqui contido e não aceitam nenhuma responsabilidade de
qualquer tipo, incluindo, mas não limitado a, desempenho, comercialização, adequação a qualquer propósito
específico. representam, ou quaisquer perdas ou danos de qualquer tipo causados ou supostamente causados direta
ou indiretamente por este livro.

Manual do Object Pascal: Delphi 11 Alexandria Edition


Esta versão somente em PDF foi publicada em novembro de 2021

A última edição impressa do Delphi 10.4 Sydney é ISBN-13: 9798554519963

A edição eletrônica deste livro foi licenciada para a Embarcadero Technologies Inc. Também é vendida diretamente
pelo autor. Não distribua a versão PDF deste livro sem permissão do autor. A edição impressa é publicada pela
Kindle Direct Publishing e vendida em diversos pontos de venda online.

Mais informações em http://www.marcocantu.com/objectpascal


Machine Translated by Google

começar - 3

começar

À minha família, Raffaella, Benny e Jacopo,


com todo o meu amor e um grande obrigado por tudo que você faz
levar minha vida à frente das minhas expectativas

Poder e simplicidade, expressividade e legibilidade, ótimos para o aprendizado e para o desenvolvimento


profissional, essas são algumas das características do Object Pascal atual, uma linguagem com
uma longa história, um presente vivo e um futuro brilhante pela frente.

Object Pascal é uma linguagem multifacetada. Ele combina o poder da programação orientada a
objetos (OOP), suporte avançado para programação genérica e construções dinâmicas, como atributos,
mas sem remover o suporte para estilos mais tradicionais de programação processual. Uma
ferramenta para todos os negócios, com compiladores e ferramentas de desenvolvimento que abraçam a
era móvel. Uma língua pronta para o futuro, mas com raízes sólidas no passado.

Para que serve a linguagem Object Pascal? Para escrever aplicativos de desktop para cliente-servidor,
para módulos massivos de servidores web para middleware, para automação de escritório, para aplicativos
para os mais recentes telefones e tablets, para sistemas de automação industrial, para redes
telefônicas virtuais na Internet... Isso não é para que a linguagem poderia ser usada, mas para que ela
é usada atualmente hoje, no mundo real.

O núcleo da moderna linguagem Object Pascal vem de sua definição em 1995, um ano fantástico para
linguagens de programação, visto que este também foi o ano em que Java e JavaScript foram
inventados. Embora a raiz da linguagem remonte ao seu ancestral Pascal, sua evolução não parou em
1995, com melhorias básicas continuando até hoje,

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

4 - começar

com os compiladores desktop e móveis criados pela Embarcadero Technologies para uso em seus ambientes
de desenvolvimento Delphi e RAD Studio.

Um livro sobre a linguagem atual


Dada a mudança do papel da linguagem, sua extensão ao longo dos anos e o fato de ela estar atraindo novos
desenvolvedores, achei importante escrever um livro que oferecesse uma cobertura completa da linguagem
Object Pascal como ela é hoje. O objetivo é oferecer um manual para
tudo, um manual de linguagem para novos desenvolvedores, para desenvolvedores vindos de outras
linguagens semelhantes, mas também para veteranos de diferentes dialetos Pascal que desejam aprender
mais sobre as mudanças recentes de linguagem.

Os recém-chegados certamente precisam de alguns dos fundamentos, mas como as mudanças foram
generalizadas, mesmo os mais antigos encontrarão algo novo em vários capítulos do livro.

Além de um pequeno apêndice cobrindo a breve história da linguagem Object Pascal, este livro foi escrito
para cobrir a linguagem como ela é hoje. Uma grande parte dos principais recursos do
a linguagem não mudou significativamente desde as primeiras versões do Delphi, a primeira implementação
do moderno Object Pascal, em 1995.

Como sugerirei ao longo do livro, a linguagem esteve longe de estar estagnada durante todos esses anos; ela
evoluiu em um ritmo bastante rápido.

Em outros livros que escrevi no passado, segui uma abordagem mais cronológica , abordando primeiro o
Pascal clássico, seguido de extensões mais ou menos à medida que apareciam ao longo do tempo. Neste
livro, porém, a ideia é usar uma abordagem mais lógica , progredindo nos tópicos e abordando como a
linguagem é hoje e como melhor utilizá-la, em vez de como ela evoluiu ao longo do tempo.

Por exemplo, os tipos de dados nativos que datam da linguagem Pascal original têm capacidades
semelhantes a métodos (graças aos auxiliares de tipo intrínsecos) introduzidos recentemente. Então, no
Capítulo 2, apresentarei como usar esse recurso; embora só muito mais tarde você descobrirá como criar
essas extensões de tipo personalizadas.

Em outras palavras, este livro aborda a linguagem Object Pascal como ela é hoje, ensinando-a desde o
início, com apenas uma perspectiva histórica muito limitada. Mesmo que você já tenha usado o idioma no
passado, você pode querer folhear todo o texto em busca de recursos mais recentes, e não se concentrar
apenas nos capítulos finais.

Aprender fazendo
A ideia do livro é explicar os conceitos centrais e apresentar imediatamente pequenos exemplos que os
leitores são incentivados a tentar executar, experimentar e ampliar para compreender os conceitos e
assimilá-los melhor. O livro não é um manual de referência

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

começar - 5

ual explicando o que a linguagem deveria fazer em teoria e listando todos os possíveis casos extremos.
Ao tentar ser preciso, o foco está mais no ensino do idioma, oferecendo um guia prático passo a passo.
Os exemplos são geralmente muito simples, porque o objetivo é
para que eles se concentrem em um recurso de cada vez.

Todo o código-fonte está disponível em um repositório de código online no GitHub. Você pode baixá-lo
como um único arquivo, clonar o repositório ou simplesmente navegar online e baixar apenas o código
para projetos específicos. Se você clonar o repositório, detectará e atualizará facilmente seu código se e
quando eu publicar quaisquer alterações ou exemplos adicionais. A localização no GitHub é a mesma
utilizada para a última edição impressa no Delphi 10.4 Sydney:

https://github.com/MarcoDelphiBooks/ObjectPascalHandbook104

Para compilar e testar o código de exemplo, você precisará de uma versão recente do Delphi (pelo
menos 10.4 para executar tudo, mas a maioria dos exemplos funcionará nas versões 10.xe 11).

Se você não possui uma licença Delphi, há uma versão de teste disponível que você pode usar,
geralmente permitindo 30 dias de uso gratuito do compilador e do IDE. Há também uma Delphi
Community Edition gratuita (atualmente atualizada para a versão 10.3) que pode ser usada gratuitamente
por qualquer pessoa sem ou com ganhos limitados com desenvolvimento de software.

Agradecimentos
Como qualquer livro, este volume deve muito a muitas pessoas, muitas para listar uma por uma. A pessoa
que compartilhou a maior parte do trabalho da primeira edição deste livro foi meu editor, Peter Wood,
que acompanhou minha agenda em constante mudança e foi capaz de melhorar meu inglês
técnico, ajudando a tornar este livro o que ele é.

Após a primeira edição, um leitor e desenvolvedor, Andreas Toth, me enviou um extenso feedback da
edição anterior. Mais tarde, ele ingressou como editor da segunda edição e fez uma revisão abrangente
do conteúdo, abrangendo gramática inglesa, consistência do livro e estilo de codificação Object Pascal.
Esta nova edição deve muito a ele.

Esta nova edição também foi revisada por alguns outros especialistas em Delphi (a maioria deles entre
os MVPs da Embarcadero), incluindo, em particular, François Piette, que me enviou mais de uma centena
de correções e sugestões incorporadas no texto final.

Dada a minha posição atual como gerente de produto na Embarcadero Technologies, devo muito a todos
os meus colegas de trabalho e aos membros da equipe de P&D, pois, durante meu tempo na empresa,
meu conhecimento do produto e de sua tecnologia aumentou ainda mais graças a o
conhecimento que obtive em inúmeras conversas, reuniões e conversas por e-mail.

Dado o quão difícil é garantir que todos sejam mencionados, não vou tentar, mas apenas escolherei
três pessoas que, por sua função, tiveram uma contribuição direta na primeira edição deste livro:

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

6 - começar

David I, de Relações com Desenvolvedores, John Thomas (JT), que chefiava o Gerenciamento de
Produtos da RAD, e o Arquiteto da RAD, Allen Bauer.

Mais recentemente, tenho trabalhado extensivamente com os outros dois gerentes de produto do RAD
Studio (Sarina DuPont e David Millington), com nosso atual evangelista Jim McKeeth e com o atual
grupo de excelentes arquitetos de P&D.

Outras pessoas fora da Embarcadero continuaram sendo contatos importantes e, às vezes, oferecendo
contribuições diretas, desde muitos especialistas italianos em Delphi até os inúmeros clientes,
parceiros técnicos e de vendas da Embarcadero, membros da comunidade Delphi, MVPs e até
mesmo desenvolvedores que usam outras linguagens e ferramentas que encontro com frequência.

Se há uma pessoa neste grupo com quem passei muito tempo antes de ingressar na Embar-cadero,
essa pessoa é Cary Jensen, com quem organizei algumas rodadas de Delphi Developer Days
na Europa e nos EUA.

E, por fim, um grande obrigado à minha família por acompanhar minha agenda de viagens, noites de
reuniões, além de alguns livros extras nos finais de semana. Obrigado mais uma vez Lella, Benny e
Jacopo.

Sobre mim, o autor


Passei a maior parte dos últimos 25 anos escrevendo, ensinando e prestando consultoria em
desenvolvimento de software com a linguagem Object Pascal. Escrevi a série de best-sellers Mastering
Delphi e posteriormente publiquei por conta própria vários manuais sobre a ferramenta de
desenvolvimento (sobre as diferentes versões do Delphi 2007 ao Delphi XE).

Já falei em um grande número de conferências de programação na maioria dos continentes e ensinei


para milhares de desenvolvedores em conferências, eventos de desenvolvedores Delphi, aulas
organizadas por empresas, webinars online e conferências CodeRage.

Tendo trabalhado como consultor independente e treinador por muitos anos, em 2013 minha carreira
sofreu uma mudança repentina: aceitei o cargo de Delphi e agora gerente de produto RAD Studio
na Embarcadero Technologies, empresa que constrói e vende essas ferramentas de desenvolvimento.

Para evitar incomodá-lo ainda mais, acrescentarei apenas que atualmente moro na Itália, viajo para a
Califórnia (um pouco menos recentemente), tenho uma esposa adorável e dois filhos maravilhosos e
gosto de voltar à programação tanto quanto posso. .

Espero que você goste de ler este livro, tanto quanto eu gostei de escrever esta nova edição. Para mais
informações, utilize os seguintes sites e canais de mídia social:

https://www.marcocantu.com/objectpascalhandbook
https://blog.marcocantu.com
https://twitter.com/marcocantu

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

índice - 7

índice

começar................................................. .................................................. ........................3 Um livro


sobre a linguagem atual.................. .................................................. ...................................4
Aprenda fazendo............ .................................................. .................................................. ..............4
Agradecimentos.......................... .................................................. ....................................5
Sobre mim, o autor....... .................................................. .................................................. ..6

Índice............................................... .................................................. ........7

Parte I: Fundações............................................... .................................................. ....17 Resumo da


Parte I......................................... .................................................. .................................. 18

01: Codificação em Pascal............................................. .................................................. ....19 Vamos começar com o


código......................................... .................................................. ............................ 19 Uma primeira aplicação de
console.................. .................................................. ...................................20 Uma primeira aplicação
visual....... .................................................. .................................................. 21 Sintaxe e estilo de
codificação.................................... .................................................. ...................24
Comentários............................ .................................................. .................................................. .. 24 comentários e
documento XML........................................... .................................................. ..............26 Identificadores
Simbólicos......................... .................................................. .................................27 Espaço em
branco............... .................................................. .................................................. ............... 29
Recuo................................. .................................................. ............................................. 30 Destaque de
sintaxe. .................................................. .................................................. ...............32 Palavras-chave do
idioma................................. .................................................. .......................................33 A Estrutura de um
Programa..... .................................................. .................................................. .....37 Nomes de Unidades e
Programas........................................ .................................................. .........38 Unidades e
Escopo.......................... .................................................. ...........................................42 O arquivo do
programa... .................................................. .................................................. .................43 Diretivas do
compilador......................... .................................................. ...........................................44

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

8 - índice

Define Condicionais.................................................. .................................................. ..................45


Versões do compilador.......................... .................................................. ........................................45
Incluir arquivos....... .................................................. .................................................. ....................47

02: Variáveis e tipos de dados.......................................... ........................................49 Variáveis e


Atribuições...... .................................................. .................................................. ....50 Valores
Literais........................................... .................................................. ...................................51 Declarações de
Atribuição............. .................................................. ................................................52 Atribuições e
Conversão................................................. .................................................. ..53 Inicializando Variáveis
Globais.......................................... .................................................. .........53 Inicializando Variáveis
Locais.................................... .................................................. ..................54 Variáveis
embutidas.......................... .................................................. ........................................... 54
Constantes.... .................................................. .................................................. ............................56 Tempo de vida
e visibilidade das variáveis.................. .................................................. ...........................58 Tipos de
dados..................... .................................................. .................................................. ............... 59 Tipos Ordinais e
Numéricos.............................. .................................................. .......................59
Booleano......................... .................................................. .................................................. .......... 64
caracteres......................................... .................................................. ...........................................65 Tipos de Ponto
Flutuante... .................................................. .................................................. ............67 Tipos de dados simples
definidos pelo usuário...................... .................................................. ....................70 Tipos Nomeados vs. Tipos Sem
Nome........................ .................................................. ..............................70 Aliases de
tipo................. .................................................. .................................................. ............ 71 Tipos de
subfaixas................................... .................................................. .................................... 72 Tipos
Enumerados......... .................................................. .................................................. ......... 73 Tipos de
conjunto.................................... .................................................. ............................................. 75 Expressões e
Operadores. .................................................. .................................................. .........76 Usando
Operadores...................................... .................................................. ...................................76 Operadores e
Precedência............ .................................................. ...........................................78 Data e
hora.. .................................................. .................................................. ............................80 Auxiliar de data e
hora.................. .................................................. .................................................. .82 Fundição de tipo e conversões
de tipo......................................... .................................................. ...83

03: Declarações de Linguagem............................................. ............................................. 85 Declarações Simples e


Compostas .................................................. ................................................86 O Se
Declaração................................................. .................................................. ...........................87 Declarações de
Casos................... .................................................. .................................................. ........88 O ciclo
For...................................... .................................................. ........................................... 90 O loop for-
in .................................................. .................................................. .......................93 Declarações While e
Repeat...................... .................................................. ...................................94 Exemplos de
Loops.......... .................................................. .................................................. ......95 Quebrando o Fluxo com Interromper e
Continuar................................. ...........................................97

04: Procedimentos e Funções............................................. ....................................100 Procedimentos e


Funções.......... .................................................. .................................................. 100 Declarações
futuras.................................................. .................................................. ............. 103 Uma função
recursiva......................... .................................................. ............................ 104 O que é um
método?........ .................................................. .................................................. .105 Parâmetros e valores de
retorno.......................................... .................................................. ........ 106 Sair com um
resultado.......................... .................................................. .............................. 107 Parâmetros de
referência.................. .................................................. ............................................. 108

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

índice - 9

Parâmetros Constantes.................................................. .................................................. ............... 110 Sobrecarga de


funções......................... .................................................. .............................. 111 Sobrecarga e chamadas
ambíguas............... .................................................. ...........................113 Parâmetros
padrão.................. .................................................. ............................................... 114
Inlining.. .................................................. .................................................. .................................... 116 Recursos Avançados de
Funções....... .................................................. ...........................................119 Convenções de chamada de Object
Pascal.. .................................................. ........................................119 Tipos
processuais....... .................................................. .................................................. ........... 120 Declarações de Funções
Externas......................... .................................................. .........122

05: Matrizes e Registros............................................. ................................................125 Dados de matriz


Tipos.................................................. .................................................. ......................... 125 Matrizes
Estáticas...................... .................................................. .................................................. .... 126 Tamanho e limites
da matriz.......................................... .................................................. ............. 127 Matrizes estáticas
multidimensionais......................... .................................................. .............128 Matrizes
Dinâmicas.......................... .................................................. .................................... 129 Parâmetros de matriz
aberta......... .................................................. .................................................. 133 Tipos de dados de
registro......................................... .................................................. .......................... 137 Usando matrizes de
registros............ .................................................. ....................................... 139 Registros de
variantes........ .................................................. .................................................. ............ 140 Alinhamentos de
Campos................................... .................................................. ................................ 140 E quanto à instrução
With?......... .................................................. ................................142 Registros com
Métodos............ .................................................. .................................................. .. 144 Eu: A magia por trás dos
registros........................................ .................................................. ....146 Inicializando
registros......................................... .................................................. ...................... 147 Registros e
Construtores........................ .................................................. .............................. 147 Operadores ganham novo
terreno.............. .................................................. .................................... 148 Operadores e registro gerenciado
personalizado........ .................................................. ........................153
Variantes........................ .................................................. .................................................. ............... 157 Variantes Não
Têm Tipo.............................. .................................................. .............................. 157 Variantes em
profundidade.................. .................................................. .................................................. .. 159 variantes são
lentas.......................................... .................................................. ........................ 159 E quanto aos
ponteiros?.................... .................................................. ............................................. 161 Tipos de arquivo, Qualquer
um?................................................ .................................................. ...................... 164

06: Tudo sobre Strings............................................. .................................................. .165 Unicode: um


alfabeto para o mundo inteiro........................................ ...........................................166 caracteres do
passado: de ASCII para codificações ISO................................................... ..........166 pontos de
código Unicode e grafemas......................... .................................................. ...167 De pontos de
código a bytes (UTF).................................... .................................................. .....168 A marca de
ordem de bytes........................................ .................................................. ...................... 170
Olhando para Unicode........................ .................................................. ...........................................
171 O tipo de personagem revisitado... .................................................. .................................................. ..........
173 Operações Unicode com a unidade de caracteres.................................. ........................................174
Literais de Caracteres Unicode...... .................................................. ................................................
176 E quanto a Caracteres de 1 Byte?......................................... .................................................. ..........
177 O tipo de dados String................................... .................................................. .................................
178 Passando Strings como Parâmetros............ .................................................. ....................................
181 O uso dos modos de contagem de caracteres [] e string.. .................................................. ...........182
Concatenando Strings.......................... .................................................. ........................... 184

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

10 - índice

As operações auxiliares de strings........................................ .................................................. ...


186 Mais funções RTL de strings......................................... .................................................. ..........
189 Formatando Strings.......................... .................................................. ............................ 189
A Estrutura Interna das Strings............... .................................................. ............................192
Observando Strings na Memória............... .................................................. .................................
193 Strings e Codificação............. .................................................. .................................................. ....
195 Outros tipos de strings......................................... .................................................. .......................
197 O tipo UCS4String............ .................................................. ........................................ 198
Tipos de strings mais antigos...... .................................................. .................................................. .......... 198

Parte II: POO em Object Pascal......................................... ........................................200 Resumo da


Parte II..... .................................................. .................................................. ................201

07: Objetos............................................... .................................................. .............202 Apresentando Classes e


Objetos......................... .................................................. .................202 A definição de uma
classe........................... .................................................. ..............................203 Classes em Outras Linguagens
OOP.............. .................................................. ............................204 Os Métodos de
Classe................. .................................................. ...................................................205 Criando um
Objeto.................................................. .................................................. ...................206 O modelo de referência de
objeto.......................... .................................................. ..............................207 Descarte de
objetos.................. .................................................. ................................................208 O que é
"Nada"?.............................................. .................................................. ...........................209 Registros vs. Classes na
Memória.................. .................................................. ..............................210 Privado, Protegido e
Público............ .................................................. ...........................................210 Um exemplo de dados
privados.. .................................................. .................................................. 212 Encapsulamento e
Formulários............................................. .................................................. .........214 O
autoidentificador.................................... .................................................. ....................................216 Criando Componentes
Dinamicamente............ .................................................. ..............................217
Construtores.................. .................................................. .................................................. .............219 Gerenciando
dados de classes locais com construtores e destruidores.......................... ..................221 Métodos e construtores
sobrecarregados........................... .................................................. ......222 A classe TDate
completa........................................ .................................................. ...............224 Tipos aninhados e constantes
aninhadas...................... .................................................. ...............227

08: Herança............................................... .................................................. ......230 Herdando de tipos


existentes........................................ .................................................. .............230 Uma Classe Base
Comum......................... .................................................. ...................................233 Campos
Protegidos e Encapsulamento........... .................................................. .................................234
Usando o “Hack protegido”.......... .................................................. ...........................................235
Da Herança ao Polimorfismo... .................................................. ....................................236 Herança
e compatibilidade de tipo.... .................................................. ...................................237 Ligação
Tardia e Polimorfismo.......... .................................................. ...................................239
Substituindo, redefinindo e reintroduzindo métodos........ .................................................. ....241
Herança e Construtores......................................... .................................................. .......243
Métodos Virtuais versus Métodos Dinâmicos...................................... .................................................. .....244
Métodos e classes de abstração........................................ .................................................. ........245
Métodos Abstratos........................................ .................................................. ............................245
Classes Seladas e Métodos Finais............... .................................................. ...........................247
Operadores TypeCast Seguros................... .................................................. ...........................................248
Herança de forma visual... .................................................. .................................................. .........250

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

índice - 11

Herdando de um Formulário Base............................................. .................................................. .....251

09: Tratamento de exceções......................................... ...........................................255 Blocos Try-


Except. .................................................. .................................................. .....................256 A
hierarquia de exceções......................... .................................................. ............................258
Levantando Exceções.......... .................................................. ................................................260
Exceções e a pilha................................................ .................................................. ........261 O
último bloco...................................... .................................................. ...................................262
Restaurar o cursor com um bloco final....... .................................................. ...........................264
Restaurar o cursor com um registro gerenciado................ .................................................. ...........264
Exceções no mundo real......................... .................................................. ............265 Tratamento
de Exceções Globais............. .................................................. ..............................266 Exceções
e Construtores............... .................................................. ........................................267 Recursos
Avançados de Exceções..... .................................................. ...........................................269
Exceções aninhadas e o mecanismo InnerException .................................................. .......270
Interceptando uma exceção......................................... .................................................. ...............273

10: Propriedades e Eventos............................................. ...........................................275 Definindo


Propriedades.... .................................................. .................................................. ................276 Propriedades
comparadas a outras linguagens de programação...................... ............................277 Propriedades Implementam
Encapsulamento......... .................................................. ...................278 Conclusão de código para
propriedades.......................... .................................................. ...................279 Adicionando propriedades a
formulários......................... .................................................. .........................280 Adicionando propriedades à classe
TDate.......... .................................................. ..................282 Usando propriedades de
matriz.......................... .................................................. ..............................284 Configurando propriedades por
referência............ .................................................. ..............................285 O especificador de acesso
publicado............ .................................................. ...................................286 Propriedades de tempo de
design....... .................................................. .................................................. .287 Publicados e
Formulários............................................. .................................................. .................288 RTTI
Automático......................... .................................................. ........................................289 Programação Orientada
a Eventos..... .................................................. .................................................. .290 Ponteiros de
método................................................... .................................................. ........................291 O Conceito de
Delegação..................... .................................................. ...................................293 Eventos são
propriedades............. .................................................. ...................................................295 Adicionando um Evento
à Classe TDate......................................... .............................................296 Criando um TDate
Componente................................................. .................................................. .....298 Implementando suporte de
enumeração em uma classe.................................... ...................................301 15 dicas sobre como misturar RAD e
OOP.... .................................................. ..............................303 Dica 1: Um Formulário é uma
Classe.. .................................................. .................................................. ......304 Dica 2: Nomeie os
Componentes...................................... .................................................. .................304 Dica 3: Nomear
eventos......................... .................................................. ....................................304 Dica 4: Use métodos de
formulário...... .................................................. ...................................................305 Dica 5: Adicione construtores
de formulários......................................... .................................................. ....305 Dica 6: Evite Variáveis
Globais........................................ .................................................. ..........306 Dica 7: Nunca use uma variável de
instância em sua implementação.......................... ....................306 Dica 8: Raramente use uma variável de
formulário.................... .................................................. .................306 Dica 9: Remova a variável global
Form1........................ .................................................. .....306 Dica 10: Adicionar propriedades do
formulário...................................... .................................................. ............307 Dica 11: Exponha as propriedades
do componente......................... .................................................. .....307 Dica 12: Use as propriedades do array
quando necessário.................................... ...........................................307

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

12 - índice

Dica 13: Iniciando Operações em Propriedades......................................... ....................................308


Dica 14: Ocultar componentes....... .................................................. ................................................308
Dica 15: Use um assistente de formulário OOP......................................... ...................................................309
Conclusão das dicas.................................................. .................................................. ......................309

11: Interfaces............................................... .................................................. ..........310 Usando


interfaces.......................... .................................................. ..............................311 Declarando uma
Interface....... .................................................. .................................................. ....312
Implementando a Interface......................................... .................................................. ..........313
Interfaces e contagem de referências......................... .................................................. ......314
Erros nas Referências de Mixagem......................................... .................................................. ............316
Referências de interface fraca e insegura......................... .................................................. ..317
Técnicas Avançadas de Interface.......................................... .................................................. .......319
Propriedades da Interface........................................ .................................................. ........................320
Delegação de Interface........................ .................................................. ....................................321
Aliases de múltiplas interfaces e métodos... .................................................. ............................323
Polimorfismo de Interface................... .................................................. ...................................324
Extraindo objetos de referências de interface...... .................................................. .................325
Implementando um padrão de adaptador com interfaces............ ................................................327

12: Manipulando Classes............................................. ...........................................330 Métodos de Classe


e Dados de Classe. .................................................. .................................................. ..330 Dados de
Classe ............................................. .................................................. ................................... 331
Métodos de classe virtual e o parâmetro self oculto...... .................................................. ...332
Métodos estáticos de classe......................................... .................................................. ....................332
Propriedades da classe......................... .................................................. ...........................................334
Uma classe com um contador de instâncias .................................................. ...........................................335
Construtores (e Destruidores) de Classe .................................................. .........................................336
Construtores de classe no RTL... .................................................. .............................................338
Implementando o padrão Singleton .................................................. ....................................338
Referências de classe......... .................................................. .................................................. ................339
Referências de classe na RTL.......................... .................................................. .......................340
Criando componentes usando referências de classe.................... .................................................. ..341
Auxiliares de classe e registro......................................... .................................................. .................343
Auxiliares de classe......................... .................................................. ...........................................344
Auxiliares de Classe e Herança. .................................................. ................................................347
Adicionando controle Enumeração com um auxiliar de classe............................................. ......................347
Auxiliares de Registro para Tipos Intrínsecos...................... .................................................. ..................350
auxiliares para aliases de tipo......................... .................................................. ..............................352

13: Objetos e Memória............................................. ................................................354 Dados Globais,


a pilha e o heap......................................... ................................................355 O Memória
Mundial.................................................. .................................................. ................355 A
Pilha.............................. .................................................. .................................................. 356 A
pilha................................................. .................................................. ................................... 357
O Modelo de Referência de Objeto............ .................................................. .............................................357
Passando objetos como parâmetros .................................................. ................................................358
Gerenciamento de memória Pontas................................................. .................................................. .........359
Destruindo objetos que você cria...................... .................................................. ...........360
Destruindo objetos apenas uma vez......................... .................................................. ..............361

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

índice - 13

Gerenciamento de memória e interfaces................................................... .............................................


363 Mais sobre referências fracas .................................................. .................................................. ....364
O Atributo Inseguro.......................................... .................................................. .....................367
Rastreando e verificando a memória............ .................................................. ...........................368 Status
da memória..................... .................................................. .................................................. .368
RápidoMM4......................................... .................................................. ...................................369
Rastreamento de vazamentos e outras configurações
globais......... .................................................. ....................369 Estouros de buffer no FastMM4
completo......................... .................................................. .............371 Gerenciamento de memória
em plataformas diferentes do Windows.......................... .........................373 Acompanhamento de
alocações por classe................... .................................................. ............................374 Escrevendo
Aplicações Robustas.................. .................................................. ...................................374
Construtores, Destruidores e Exceções..... .................................................. ....................... 375 blocos
finais aninhados............ .................................................. ..............................376 Verificação dinâmica
de tipo....... .................................................. .................................................. .377 Este ponteiro é uma referên

Parte III: Recursos Avançados............................................. ........................................381 Capítulos


da Parte III..... .................................................. .................................................. ...............382

14: Genéricos................................................. .................................................. ............383 Pares genéricos


de valores-chave......................... .................................................. ..............................384 Inferência de
tipos de variáveis inline e genéricos............. .................................................. ..........387 Regras de
tipo em genéricos................................... .................................................. .......................387 Genéricos
em Object Pascal...................... .................................................. ....................................388 Regras de
compatibilidade de tipos genéricos....... .................................................. ...................................389
Métodos genéricos para classes padrão........... .................................................. .......................390
Instanciação de tipo genérico........................ .................................................. ..............................392
Funções de tipo genérico.................. .................................................. ...........................................394
Construtores de classes para classes genéricas.. .................................................. ................................396
Restrições genéricas............... .................................................. .................................................. ....398
Restrições de classe......................................... .................................................. .........................398
Restrições de Classe Específicas............. .................................................. ....................................400
Restrições de interface.............. .................................................. ................................................400Interface
Referências vs. Restrições de Interface Genérica.......................................... ...............403 Restrição
do construtor padrão......................... .................................................. ...............404 Resumo de
restrições e combinação delas......................... ............................................. 405 Contêineres
genéricos predefinidos .................................................. .................................................. .406 Usando
TList<T>....................................... .................................................. ............................407 Classificando
uma TList<T>............... .................................................. .................................................. 408
Classificando com um método anônimo.......................................... ...........................................410
Contêineres de Objetos... .................................................. .................................................. ...............411
Usando um dicionário genérico.............................. .................................................. .......................
412 Dicionários vs. Listas de Strings............. .................................................. ................................415
Interfaces genéricas.................. .................................................. .................................................. .......417
Interfaces genéricas predefinidas............................ .................................................. .........419
Ponteiros Inteligentes em Object Pascal......................... .................................................. ...............420
Usando registros para ponteiros inteligentes.......................... .................................................. .............420
Implementando um Ponteiro Inteligente com um Registro Gerenciado
Genérico........................... .................421 Implementando um Ponteiro Inteligente com um Registro
Genérico e uma Interface..................... ...........423 Adicionando conversão implícita......................... .............

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

14 - índice

Comparando soluções de ponteiro inteligente......................................... ...........................................426


Tipos de retorno covariantes com genéricos.. .................................................. ....................................426
Sobre animais, cães e gatos.... .................................................. ...........................................427 Um
método com resultado genérico. .................................................. ...........................................428
Retornando um objeto derivado de uma classe diferente................................................. ........................429

15: Métodos Anônimos............................................. ...........................................430 Sintaxe e


Semântica de Métodos Anônimos .................................................. ...........................431 Uma
variável de método anônimo.................. .................................................. ...........................431
Um parâmetro de método anônimo................... .................................................. .....................432
Usando Variáveis Locais............. .................................................. ....................................433
Estendendo a vida útil das variáveis locais.. .................................................. ...........................433
Métodos anônimos nos bastidores................. .................................................. ...................435 O
parêntese (potencialmente) ausente........................ .................................................. ............435
Implementação de métodos anônimos......................... ................................................437 Pronto-
Tipos de referência a serem usados............................................. ..................................................
.437 Métodos Anônimos no Mundo Real...................................... .............................................439
Manipuladores de eventos anônimos. .................................................. ..................................................
439 Métodos Anônimos de Cronometragem............................................. .................................................. ....441
Sincronização de Threads......................................... .................................................. .............442
AJAX em Object Pascal......................... .................................................. ............................444

16: Reflexão e Atributos............................................. ....................................449 RTTI


Estendido......... .................................................. .................................................. ..................450 Um
primeiro exemplo.......................... .................................................. ...........................................450
Informações geradas pelo compilador.... .................................................. ..............................451
Ligação de tipo fraco e forte... .................................................. ....................................453 A
Unidade RTTI..... .................................................. .................................................. .......................454
As Classes RTTI na Unidade Rtti................... .................................................. ........................456
Gerenciamento de vida útil de objetos RTTI e o registro TRttiContext................. .........................457
Exibindo informações da classe..................... .................................................. ............................459
RTTI para Pacotes.................. .................................................. ................................................460
O Estrutura do valor ................................................ .................................................. ...................461
Lendo uma propriedade com TValue......................... .................................................. ....................463
Invocando Métodos......................... .................................................. ........................................464
Usando atributos....... .................................................. .................................................. ..................465
O que é um Atributo?......................... .................................................. ....................................465
Classes de Atributos e Declarações de Atributos........ .................................................. ................466
Atributos de navegação......................... .................................................. .................................468
Interceptores de Métodos Virtuais.............. .................................................. ...........................................470
Estudos de Caso RTTI... .................................................. .................................................. ...................473
Atributos para ID e Descrição......................... .................................................. ..................474
Fluxo de XML.......................... .................................................. ...........................................478
Outras bibliotecas baseadas em RTTI.. .................................................. ....................................................485

17: TObject e a Unidade do Sistema......................................... ....................................487 A classe


TObject.......... .................................................. .................................................. ............488
Construção e Destruição.......................... .................................................. ..............488
Conhecendo um objeto......................... .................................................. ........................489 Mais
métodos da classe TObject.................... .................................................. ....................490

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

índice - 15

Métodos Virtuais do TObject................................................... .................................................. .......492


Resumo da Classe TObject......................................... .................................................. ...................495
Unicode e nomes de classe.......................... .................................................. ............................496 A
Unidade do Sistema.................. .................................................. .................................................. .......497
Tipos de Sistema Selecionados......................................... .................................................. ............498
Interfaces na unidade do sistema............ .................................................. ...........................499
Rotinas de sistema selecionadas.................. .................................................. ...................................499
Atributos RTTI predefinidos........... .................................................. ...........................................500

18: Outras Classes Básicas de RTL.......................................... ...........................................501 A Unidade


de Aulas.... .................................................. .................................................. .....................502 As Aulas
na Unidade de Aulas...................... .................................................. ........................502 A classe
TPersistent............ .................................................. .......................................503 A classe
TComponent....... .................................................. .................................................. ..504 Acesso
moderno a arquivos.......................................... .................................................. ...........................507 A
Unidade de Utilitários de Entrada/Saída................. .................................................. ............................507
Apresentando Fluxos............ .................................................. ...........................................509 Usando
leitores e gravadores. .................................................. .................................................. ..510
Construindo Strings e Listas de Strings........................................ .................................................. .......513
A classe TStringBuilder......................................... .................................................. ..................513
Usando listas de strings.......................... .................................................. ........................................514
A biblioteca de tempo de execução é bastante grande. .................................................. ........................................

No encerramento.................................................. .................................................. ..............518

fim................................................. .................................................. .......................519 Resumo do


Apêndice........................ .................................................. ................................................519

A: A Evolução do Objeto Pascal......................................... ...................................520 Pascal de


Wirth.............. .................................................. .................................................. ...............521Turbo
Pascal................................. .................................................. .................................................. 521 Os
primórdios do Object Pascal do Delphi......................................... ............................................522 Objeto
Pascal do CodeGear para Embarcadero................................................. ...........................523 Tornando-
se móvel.................. .................................................. .................................................. .......... 524 O
Período Delphi 10.x................................. .................................................. ................................524 A
versão do Delphi 11.............. .................................................. .................................................. ....525

B: Glossário.................................................. .................................................. .............526


A.................................... .................................................. .................................................. .............. 526
B.......................... .................................................. .................................................. ................ 527
C................................. .................................................. .................................................. ..................
527D.............................. .................................................. .................................................. ...................
529 E............................ .................................................. .................................................. .....................
529F.......................... .................................................. .................................................. .......................
530G.......................... .................................................. .................................................. ........................
530H........................ .................................................. .................................................. ........................ 531
I......................... .................................................. .................................................. ............................ 531
M.................. .................................................. .................................................. ............................ 532
O............ .................................................. .................................................. .............................. 532
p.................. .................................................. .................................................. ................................ 533

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

16 - índice

R................................................ .................................................. .................................................. 534


S................................................. .................................................. .................................................. .. 535
U................................................. .................................................. .................................................. ...535
V................................................. .................................................. .................................................. ....
536W............................................ .................................................. .................................................. .... 536

C: Índice.................................................. .................................................. ..................537

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

parte i: fundações - 17

parte I: fundações

Object Pascal é uma linguagem extremamente poderosa baseada em fundamentos básicos que
suportam estruturas de programas limpas e tipos de dados extensíveis. Essas bases são
parcialmente derivadas da linguagem Pascal tradicional, mas mesmo os recursos básicos da
linguagem tiveram muitas extensões desde os primeiros dias.
Nesta primeira parte do livro, você aprenderá sobre a sintaxe da linguagem, o estilo de codificação,
a estrutura dos programas, o uso de variáveis e tipos de dados, as instruções fundamentais da
linguagem (como condições e loops), o uso de procedimentos e funções e construtores de tipos
principais, como matrizes, registros e strings.
Na segunda e terceira parte do livro, exploraremos os fundamentos dos recursos mais
avançados, desde classes até tipos genéricos.
Aprender um idioma é como construir uma casa, e você precisa começar em terreno sólido e com
boas bases, ou tudo o mais acima pode estar brilhando... mas instável.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

18 - parte i: fundamentos

Resumo da Parte I
Capítulo 1: Codificação em Pascal

Capítulo 2: Variáveis e tipos de dados

Capítulo 3: Declarações de Linguagem

Capítulo 4: Procedimentos e Funções

Capítulo 5: Matrizes e Registros

Capítulo 6: Tudo sobre Strings

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

01: codificação em pascal - 19

01: codificação em
Pascal

Este capítulo começa com alguns dos blocos de construção de uma aplicação Object Pascal,
abordando formas padrão de escrever código e comentários relacionados, introduzindo palavras-
chave e a estrutura de um programa. Começarei escrevendo algumas aplicações simples,
tentando explicar o que elas fazem e, assim, introduzindo alguns outros conceitos-chave
abordados com mais detalhes nos próximos capítulos.

Vamos começar com o código

Este capítulo cobre os fundamentos da linguagem, mas levarei alguns capítulos para
guiá-lo através dos detalhes de um aplicativo funcional completo. Então, por enquanto, vamos dar
uma olhada em dois programas simples (diferentes em sua estrutura) sem entrar em muitos
detalhes. Aqui eu só quero mostrar a estrutura dos programas que usarei para construir exemplos
que explicam construções específicas de linguagem antes de poder cobrir todos os vários elementos.
Dado que desejo que você possa começar a colocar em prática as informações contidas no livro o
mais rápido possível, seria uma boa ideia examinar os exemplos desde o início.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

20 - 01: codificação em pascal

Object Pascal foi projetado para trabalhar lado a lado com seu Ambiente de Desenvolvimento Integrado,
ou IDE, para abreviar. É através desta combinação poderosa que o Object Pascal pode igualar a
facilidade de velocidade de desenvolvimento de linguagens amigáveis ao programador e ao mesmo tempo
igualar a velocidade de execução de linguagens amigáveis à máquina.

O IDE permite projetar interfaces de usuário, ajudá-lo a escrever código, executar seus programas e muito,
muito mais. Usarei o IDE ao longo deste livro ao apresentar a você a linguagem Object Pascal.

Um primeiro aplicativo de console


Como ponto de partida, vou mostrar o código de uma aplicação simples de console Hello, World mostrando
alguns dos elementos estruturais de um programa Object Pascal. Um aplicativo de console é um programa
sem interface gráfica de usuário, que exibe texto e aceita entrada de teclado e geralmente é executado a
partir de um console do sistema operacional ou prompt de comando. Os aplicativos de console geralmente
fazem pouco sentido em plataformas móveis, mas ainda são usados no Windows (onde a Microsoft
recentemente investiu esforços no cmd.exe
melhorias, PowerShell e acesso ao terminal) e são bastante populares no Linux.

Não vou explicar o que significam os diferentes elementos do código abaixo, pois esse é o propósito dos
primeiros capítulos do livro, mas aqui está o código do exemplo Hel-loConsole :

programa HelloConsole;

{$APPTYPE CONSOLE}

era
StrMessage: string;

começar
StrMensagem := 'Olá Mundo' ;
Writeln(StrMessage);
// Espere até o Digitar tecla é pressionada
Leia;
fim.

note Conforme explicado na introdução, o código-fonte completo de todos os exemplos abordados no livro está
disponível em um repositório online no GitHub. Consulte a introdução do livro para obter mais detalhes sobre
como obter esses exemplos. No texto me refiro ao nome do projeto (neste caso HelloConsole), que
também é o nome da pasta que contém os diversos arquivos do exemplo. As pastas do projeto são
agrupadas por capítulo, então você encontrará este primeiro exemplo em 01/HelloConsole.

Você pode ver o nome do programa na primeira linha após uma declaração específica, uma diretiva do
compilador (prefixada pelo símbolo $ e entre chaves), uma declaração de variável
(uma string com um determinado nome) e três linhas de código mais um comentário dentro do texto principal

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

01: codificação em pascal - 21

bloco início-fim . Essas três linhas de código copiam um valor na string, chamam uma função do
sistema para escrever essa linha de texto no console e chamam outra função do sistema para ler
uma linha de entrada do usuário (ou, neste caso, esperar até que o usuário pressione o botão Tecla Enter). Como
veremos, você pode definir suas próprias funções, mas Object Pascal vem com centenas de
os predefinidos.

Novamente, aprenderemos sobre todos esses elementos em breve, pois esta seção inicial serve apenas
para lhe dar uma ideia de como é um programa Pascal pequeno, mas completo. É claro que você pode
abrir e executar este aplicativo, que produzirá resultados como o seguinte (a versão real do Windows
é exibida na Figura 1.1).

Olá Mundo

Figura 1.1:
A saída do
exemplo HelloConsole,
em execução no Windows

Uma primeira aplicação visual


Um aplicativo moderno, porém, raramente se parece com esse programa de console antigo, mas
geralmente é feito de elementos visuais (chamados de controles) exibidos em janelas (chamados de
formulários). Na maioria dos casos neste livro, construirei exemplos visuais (mesmo que na maioria dos
casos eles se reduzam à exibição de texto simples) usando a biblioteca FireMonkey, também conhecida
como FMX.

note No Delphi, os controles visuais vêm em dois tipos: VCL (Visual Component Library for Windows) e FireMonkey (uma
biblioteca para vários dispositivos para todas as plataformas suportadas, desktop e mobile). De qualquer forma,
deve ser bastante simples adaptar as demonstrações à biblioteca VCL específica do Windows.

Para entender a estrutura exata de uma aplicação visual, você terá que ler boa parte deste livro, pois
um formulário é um objeto de uma determinada classe e possui métodos, manipuladores de eventos
e propriedades... todos recursos que vai demorar um pouco para passar. Mas para poder
Para criar esses aplicativos, você não precisa ser um especialista, pois tudo o que você precisa fazer é
usar um comando de menu para criar um novo aplicativo desktop ou móvel. O que farei na parte inicial
do livro é basear os exemplos na plataforma FireMonkey e simplesmente usar o contexto dos formulários
e das operações de clique em botões. Para começar, você pode criar um formulário de qualquer tipo
(desktop ou celular, eu geralmente escolheria um aplicativo “em branco” para vários dispositivos, pois
ele também será executado no Windows) e colocar um controle de botão nele, com um controle de texto
multilinha (ou Memo) abaixo dele para exibir a saída.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

22 - 01: codificação em pascal

A Figura 1.2 mostra como seu formulário de aplicação ficará para uma aplicação móvel no IDE Delphi,
depois de selecionar uma visualização no estilo Android (veja a caixa de combinação acima da
superfície de design) e adicionar um único controle, um botão.

Figura 1.2:
Um aplicativo
móvel simples com um único
botão, usado pelo
exemplo HelloVisual

O que você precisa fazer para criar um aplicativo semelhante é adicionar um botão a um formulário vazio.
Agora, para adicionar o código real, que é a única coisa que nos interessa por enquanto, dobre
clique no botão, você verá o seguinte esqueleto de código (ou algo muito semelhante):

procedimento TForm1.Button1Click (Remetente: TObject)


começar

fim;

Mesmo que você não saiba o que é um método de uma classe (que é o que Button1Click é), você
pode digitar algo nesse fragmento de código (ou seja, dentro do início e do fim
palavras-chave) e esse código será executado quando você pressionar o botão.

Nosso primeiro programa “visual” possui código correspondente ao do primeiro aplicativo de console,
apenas em um contexto diferente e chamando uma função de biblioteca diferente, ou seja, ShowMessage
função global usada para exibir alguma string em uma caixa de mensagem. Este é o código que você pode
encontrar no exemplo HelloVisual e você pode tentar reconstruí-lo do zero com bastante facilidade:

procedimento TForm1.Button1Click (Remetente: TObject)


era
StrMessage: string;
começar
StrMensagem := 'Olá Mundo' ;
ShowMessage(StrMessage);
fim;

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

01: codificação em pascal - 23

Observe como você precisa colocar a declaração da variável StrMessage antes da


instrução inicial e o código real depois dela. Novamente, não se preocupe se as coisas não
ficarem claras, tudo será explicado no devido tempo e detalhadamente.

note Você pode encontrar o código-fonte deste exemplo em uma pasta no contêiner 01 do capítulo. Nisso
No entanto, neste caso, existe um nome de arquivo de projeto como o exemplo, mas também um arquivo de unidade
secundária com a palavra “Formulário” adicionada após o nome do projeto. Esse é o padrão que vou seguir no livro. A
estrutura de um projeto é abordada no final deste capítulo.

Na Figura 1.3 você pode ver a saída deste programa simples, rodando em Windows com o
modo FMX MobilePreview habilitado (você também pode executar este exemplo em
Android, iOS e macOS, mas isso requer algumas configurações extras no IDE).

note O FireMonkey Mobile Preview faz com que um aplicativo do Windows se pareça um pouco com um aplicativo móvel. Eu tenho
habilite este modo na maioria dos exemplos deste livro. Isso é feito adicionando uma declaração de usos para o
Unidade MobilePreview no código-fonte do projeto.

Agora que temos uma maneira de escrever e testar um programa Delphi, vamos voltar à
estaca zero, abordando todos os detalhes dos primeiros blocos de construção de uma
aplicação, como prometi no início deste capítulo. A primeira coisa que você precisa saber é
como ler um programa, como os vários elementos são escritos e qual é a estrutura da
aplicação que acabamos de construir (que possui um arquivo PAS e um arquivo DPR).

Figura 1.3:
Um aplicativo móvel
simples com um único
botão, usado pela
demonstração HelloVisual

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

24 - 01: codificação em pascal

Sintaxe e estilo de codificação


Antes de passarmos ao assunto de escrever instruções reais da linguagem Object Pascal, é importante
destacar alguns elementos do estilo de codificação Object Pascal. A questão que estou abordando aqui é
esta: Além das regras de sintaxe (que ainda não examinamos), como você deve escrever código? Não
há uma resposta única para essa pergunta, pois o gosto pessoal pode ditar estilos diferentes. No entanto,
existem alguns princípios que você precisa
saber sobre comentários, letras maiúsculas, espaços e o que há muitos anos era chamado de beautiful-
printing (bonito para nós, seres humanos, não para o computador), termo hoje considerado obsoleto.

Em geral, o objetivo de qualquer estilo de codificação é a clareza. As decisões de estilo e formatação que você
make são uma forma abreviada, indicando a finalidade de um determinado trecho de código. Uma
ferramenta essencial para clareza é a consistência – seja qual for o estilo que você escolher, certifique-se
de segui-lo durante todo o projeto e entre projetos.

dica O IDE (Ambiente de Desenvolvimento Integrado) tem suporte para formatação automática de código (no nível de unidade ou
projeto): Você pode pedir ao editor para reformatar seu código com as teclas Ctrl+D, seguindo um conjunto de regras que
você pode alterar ajustando cerca de 40 elementos de formatação diferentes (encontrados entre as opções do IDE) e
até mesmo compartilhando essas configurações com outros desenvolvedores de sua equipe para tornar a formatação
consistente. A formatação automática, entretanto, não oferece suporte a alguns dos recursos de idioma mais recentes.

Comentários
Embora o código seja muitas vezes autoexplicativo, é relevante adicionar uma quantidade significativa de
comentários no código-fonte de um programa para explicar melhor aos outros (e a você mesmo quando você
olhar seu código por muito tempo no futuro) por que o código foi escrito de uma determinada maneira e
quais podem ter sido as suposições.

No Pascal tradicional, os comentários só podiam ser colocados entre colchetes ou a combinação parênteses-
asterisco/estrela. Versões modernas da linguagem também aceitam o C/C++
comentário de linha única, barra dupla, que se estende até o final da linha e não requer nenhum símbolo
para indicar o final:

// Isso é a Comente acima para fim o do linha

{ Isto é um
comentário de várias linhas }

(* Esse é
outro comentário de várias linhas *)

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

01: codificação em pascal - 25

A primeira forma de comentário é de longe a mais comum, mas originalmente não fazia parte do Pascal.
Ele foi emprestado do C/C++, que também usa a sintaxe /* comment */ para comentários multilinhas,
junto com C#, Objective-C, Java e JavaScript.

A segunda forma é mais comumente usada do que a terceira, que era frequentemente preferida na Europa
porque muitos teclados europeus não possuem o símbolo de chaveta (ou dificultam o uso, por meio de
uma combinação de múltiplas teclas). Em outras palavras, a sintaxe mais antiga está um pouco fora de
moda.

Comentários até o final da linha são muito úteis para comentários curtos e para comentar uma única
linha de código. Eles são de longe a forma mais comum de comentários na moderna linguagem Object
Pascal.

dica No editor IDE, você pode comentar ou descomentar a linha atual (ou um grupo de linhas selecionadas) com um
pressionamento direto de tecla. A combinação de teclas Ctrl+/ é usada para um layout de teclado dos EUA,
mas é diferente em outros layouts de teclado (dependendo de onde a tecla física / está localizada). A
combinação de teclas real pode ser vista no menu pop-up do editor.

Ter três formas diferentes de comentários pode ser útil para marcar comentários aninhados. Se você
quiser comentar várias linhas de código-fonte para desativá-las, e essas linhas contiverem alguns
comentários reais, você não poderá usar o mesmo identificador de comentário:

código... {Comentário aninhado, criando problemas}


código...
}

O código acima resulta em um erro do compilador, pois a primeira chave fechada indica o final de toda a
seção comentada. Com um segundo identificador de comentário, você pode escrever o seguinte código,
que está correto:

{
código...
// Este comentário é um OK
código...
}

Uma alternativa é comentar um grupo de linhas conforme explicado acima, pois isso adicionará um
segundo // comentário à linha comentada, que você pode remover facilmente descomentando o mesmo
bloco (preservando o comentário original).

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

26 - 01: codificação em pascal

note Se a chave aberta ou a estrela entre parênteses for seguida pelo cifrão ($), não é mais um comentário, mas se torna
uma diretiva do compilador, como vimos na primeira demonstração na linha {$APPTYPE CON- ÚNICO}. As
diretivas do compilador instruem o compilador a fazer algo especial e são explicadas brevemente no final deste
capítulo.
Na verdade, as diretivas do compilador ainda são comentários. Por exemplo, {$X+ Este é um comentário} é legal. É tanto uma diretiva válida
quanto um comentário, embora a maioria dos programadores sensatos provavelmente tenderá a separar diretivas e comentários.

Comentários e documento XML


Existe uma versão especial de comentários, comum também a outras linguagens de programação,
que é tratado de uma maneira particular pelo compilador. Esses comentários especiais geram documentação adicional
disponível diretamente no IDE Help Insight e em arquivos XML gerados pelo compilador.

note No Delphi IDE, o Help Insight exibe automaticamente informações sobre um símbolo (incluindo seu tipo e
onde foi definido). Com os comentários do XML Doc você pode aumentar essas informações com
detalhes específicos escritos no próprio código-fonte.

XML Doc é habilitado usando /// comentários ou {! comentários. Dentro desses comentários você pode usar texto geral ou
(melhor) tags XML específicas para indicar informações sobre o
símbolo comentado, seus parâmetros e valor de retorno e muito mais. Este é um caso muito simples de texto de formato
livre:

público
/// Isso é a método personalizado, claro
procedimento CustomMethod;

As informações serão adicionadas à saída XML gerada pelo compilador se você ativar
Geração de documento XML, conforme segue:

<nome do procedimento="CustomMethod" visibilidade="public">


<notas de desenvolvimento>

Este é um método personalizado, é claro


</devnotes>
</procedure>

A mesma informação é exibida no IDE quando você passa o mouse sobre o símbolo, conforme mostrado na Figura 1.4.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

01: codificação em pascal - 27

Figura 1.4:
Help Insight no Delphi IDE
exibe informações do
documento XML escritas em ///

comentários

Se você fornecer uma seção de resumo no comentário, seguindo as diretrizes recomendadas, ela aparecerá igualmente
na janela Help Insight:

público
/// <resumo>Este é a método personalizado, claro</summary>
procedimento CustomMethod;

A vantagem é que existem muitas outras tags XML que você pode usar para parâmetros, valores de retorno e
informações mais detalhadas. As tags disponíveis estão listadas em:

http://docwiki.embarcadero.com/RADStudio/en/XML_Documentation_Comments

Identificadores Simbólicos
Um programa é feito de muitos símbolos diferentes que você pode introduzir para nomear vários elementos (tipos de
dados, variáveis, funções, objetos, classes e assim por diante). Embora você possa usar quase qualquer identificador
que desejar, existem algumas regras que você deve seguir:

· Os identificadores não podem incluir espaços (já que os espaços separam os identificadores de outras línguas).
elementos de medição)

· Os identificadores podem usar letras e números, incluindo as letras de todo o alfabeto Unicode; então você pode nomear
símbolos em seu próprio idioma, se quiser (algo não recomendado, pois algumas das ferramentas que fazem parte
do IDE podem não oferecer o mesmo suporte)

· Fora dos símbolos ASCII tradicionais, os identificadores podem usar apenas o símbolo de sublinhado
(_); todos os outros símbolos ASCII além de letras e números não são permitidos. Símbolos ilegais em
identificadores incluem símbolos de correspondência (+, -, *, /, =), todos os parênteses e

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

28 - 01: codificação em pascal

colchetes, pontuação, caracteres especiais (incluindo @, #, $, %, ^, &, \, |). O que você pode usar,
porém, são símbolos Unicode, como ÿÿ ou ÿ.

· Os identificadores devem começar com uma letra ou sublinhado, não é permitido começar com um
número (ou seja, você pode usar números, mas não como primeiro símbolo). Aqui, com números,
nos referimos aos números ASCII, de 0 a 9, enquanto outras representações de números em Unicode
são permitidas.

A seguir estão exemplos de identificadores clássicos, listados no aplicativo IdentifiersTest :

Meu valor
Valor1
Meu_Valor
_Valor
Val123
_123

Estes são exemplos de identificadores Unicode legais (onde o último é um pouco extremo):

Cantù (letra com acento latino)


ÿ(Saldo de caixa em chinês simplificado)
Imagem (foto em japonês)
ÿ (símbolo Unicode do Sol)

Estes são alguns exemplos de identificadores inválidos :

123
1Valor
Meu valor
Meu valor
Meu%Valor

dica Caso você queira verificar um identificador válido em tempo de execução (algo raramente necessário, a menos que você esteja
escrevendo uma ferramenta para ajudar outros desenvolvedores), existe uma função na biblioteca de tempo de execução que você
pode usar, chamada IsValidIdent.

Insensibilidade a maiúsculas e minúsculas e uso de letras maiúsculas

Ao contrário de muitas outras linguagens, incluindo todas aquelas baseadas na sintaxe C (como C++,
Java, C# e JavaScript), o compilador Object Pascal ignora o caso, ou capitalização, dos identificadores.
Portanto, os identificadores Meunome, MeuNome, meunome, meuNome e MEUNOME são todos
exatamente iguais. Na minha opinião pessoal, a insensibilidade a maiúsculas e minúsculas é definitivamente
uma característica positiva, pois erros de sintaxe e outros erros sutis podem ser causados por erros incorretos.
letras maiúsculas em idiomas que diferenciam maiúsculas de minúsculas.

Se você considerar o fato de que pode usar Unicode para identificadores, entretanto, as coisas ficam um
pouco mais complicadas, pois a versão maiúscula de uma letra é tratada como o mesmo elemento.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

01: codificação em pascal - 29

mento, enquanto uma versão acentuada da mesma letra é tratada como um símbolo separado. Em outras palavras:

Canto: Inteiro;
Canto: Inteiro; // Erro: identificador duplicado
música: Inteiro; // Correto: identificador diferente

avisar Há apenas uma exceção à regra de insensibilidade a maiúsculas e minúsculas da linguagem: o procedimento Register de
no um pacote de componentes deve começar com R maiúsculo , devido a um problema de compatibilidade com C++. É
claro que, quando você se refere a identificadores exportados por outras linguagens (como uma função nativa do sistema
operacional), talvez seja necessário usar letras maiúsculas adequadas.

Existem algumas desvantagens sutis, no entanto. Primeiro, você deve estar ciente de que esses identificadores
são realmente iguais, portanto, evite usá-los como elementos diferentes.
Segundo, você deve tentar ser consistente no uso de letras maiúsculas, para melhorar a legibilidade do código.

Um uso consistente de maiúsculas e minúsculas não é imposto pelo compilador, mas é um bom hábito adquirir.
Uma abordagem comum é colocar em maiúscula apenas a primeira letra de cada identificador. Quando um
identificador é composto por várias palavras consecutivas (não é possível inserir um espaço em um
identificador), cada primeira letra de uma palavra deve ser maiúscula:

MeuLongIdentifier
MyVeryLongAndAlmostStupidIdentifier

Isto é muitas vezes chamado de “invólucro de Pascal”, para contrastá-lo com o chamado “invólucro de camelo” de
Java e outras linguagens baseadas na sintaxe C, que coloca palavras internas em maiúscula com uma letra inicial
minúscula, como em

meuLongIdentifier

Na verdade, é cada vez mais comum ver código Object Pascal em que variáveis locais usam camel-casing
(inicial minúscula), enquanto elementos de classe, parâmetros e outros elementos mais globais usam Pascal-
casing. De qualquer forma, no livro source code snip-pets tentei usar Pascal-casing consistentemente para todos
os símbolos.

Espaço em branco

Outros elementos completamente ignorados pelo compilador são os espaços, novas linhas e tabulações que você
adiciona ao código-fonte. Todos esses elementos são conhecidos coletivamente como espaços em branco.
O espaço em branco é usado apenas para melhorar a legibilidade do código; isso não afeta a compilação de forma
alguma.

Ao contrário do BASIC tradicional, o Object Pascal permite escrever uma instrução em várias linhas de código,
dividindo uma instrução longa em duas ou mais linhas. A desvantagem de permitir instruções em mais de
uma linha é que você precisa se lembrar de adicionar um

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

30 - 01: codificação em pascal

ponto e vírgula para indicar o final de uma declaração, ou mais precisamente, para separar uma declaração
da seguinte. A única restrição na divisão de instruções de programação em linhas diferentes é que uma
string literal não pode abranger várias linhas.

Embora estranhas, todas as linhas a seguir representam a mesma declaração compilada:

UMA := B + 10;

R:=
B
+
10;

A
:=
// Este é um comentário intermediário
B + 10;

Novamente, não existem regras fixas sobre o uso de espaços e instruções de múltiplas linhas, apenas
algumas regras práticas:

ÿ O editor possui uma linha vertical que você pode colocar após cerca de 80 caracteres. Se você usar esta
linha e tentar evitar ultrapassar esse limite, seu código-fonte ficará melhor e você não precisará rolar
horizontalmente para lê-lo em um computador com tela menor. A ideia original por trás dos 80 caracteres
era deixar o código mais bonito quando impresso, algo não tão comum hoje em dia.

ÿ Quando uma função ou procedimento possui vários parâmetros complexos, é prática comum
É aconselhável colocar os parâmetros em linhas diferentes.

ÿ Você pode deixar uma linha completamente branca (em branco) antes de um comentário ou dividir um trecho
longo de código em porções menores. Mesmo essa ideia simples pode melhorar a legibilidade do código,
da mesma forma que você usa parágrafos e espaço em um livro impresso.

ÿ Use espaços para separar os parâmetros de uma chamada de função e talvez até um espaço antes do
parêntese aberto inicial. Também gosto de manter os operandos de uma expressão separados, embora
isso seja uma questão de preferência.

Recuo
A última sugestão sobre o uso de espaços em branco refere-se ao estilo e recuo típico da linguagem Pascal.

note As regras de recuo estão sujeitas ao gosto pessoal e não quero entrar em uma batalha de tabulações versus espaços .
Aqui estou apenas indicando que é o estilo de formatação “mais comum” ou “padrão” no mundo Object Pascal,
que é o estilo usado no código fonte das bibliotecas Delphi. Historicamente, no mundo Pascal, esse conjunto de
regras era indicado como bonito, um termo hoje bastante incomum.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

01: codificação em pascal - 31

Esta regra é simples: cada vez que você precisar escrever uma instrução composta, recue dois espaços
(não uma tabulação, como um programador C geralmente faria) à direita da instrução atual. Uma instrução
composta dentro de outra instrução composta é recuada com quatro espaços e assim por diante:

se então
declaração;

se então
começar
declaração1;
declaração2;
fim;

se então
começar
se então
declaração1;
declaração2;
fim;

Novamente, os programadores têm diferentes interpretações desta regra geral. Alguns programadores
recuam as instruções de início e fim no nível do código interno, outros programadores colocam o
início no final da linha da instrução anterior (no estilo C). Isto é principalmente uma questão de gosto
pessoal.

Um formato de recuo semelhante é frequentemente usado para listas de variáveis ou tipos de dados após
as palavras-chave type e var :

tipo
Letras = ( 'A' , 'B' , 'C' );
OutroTipo = ...

era
Nome: sequência;
Eu: Inteiro;

note No código acima você deve estar se perguntando por que dois tipos diferentes, string e Integer, são escritos com
maiúsculas e minúsculas diferentes para a letra inicial. Como afirmou o Object Pascal Styles Guide original, “tipos
como Integer são apenas identificadores e aparecem com uma primeira letra maiúscula; strings, entretanto, são
declaradas com a palavra reservada string, que deve estar toda em minúsculas.”

No passado, também era comum usar um recuo do separador baseado em coluna, ao declarar tipos e
variáveis personalizados. Nesse caso, o código acima ficaria assim:

tipo
Cartas =( 'A' , 'B' , 'C' );
OutroTipo = ...

era

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

32 - 01: codificação em pascal

Nome: string;
EU
: Inteiro;

O recuo também é comumente usado para instruções que continuam a partir da linha anterior ou para os
parâmetros de uma função (se você não colocar cada parâmetro em uma linha separada):

'Esse é a mensagem' ,
MessageDlg(mtInformações, [mbOK], 0);

Realce de sintaxe
Para facilitar a leitura e gravação de código Object Pascal, o editor IDE possui um recurso chamado
realce de sintaxe. Dependendo do significado no idioma das palavras que você
tipo, eles são exibidos usando diferentes cores e estilos de fonte. Por padrão, as palavras-chave estão em
negrito, as strings e os comentários estão em cores (e geralmente em itálico) e assim por diante.

Palavras reservadas, comentários e strings são provavelmente os três elementos que mais se beneficiam
desse recurso. Você pode ver rapidamente uma palavra-chave com erro ortográfico, uma string não terminada
corretamente e o comprimento de um comentário de várias linhas.

Você pode personalizar facilmente as configurações de realce de sintaxe usando a página Cores do Editor
da caixa de diálogo Opções do IDE. Se você é a única pessoa que usa seu computador para consultar o
código-fonte do Object Pascal, escolha as cores de sua preferência. Se você trabalha em estreita
colaboração com outros programadores, todos devem concordar com um esquema de cores padrão.
Muitas vezes descobri que trabalhar em um computador com uma coloração de sintaxe diferente daquela
que normalmente uso era realmente confuso.

Insight de erro e insights de código


O editor IDE possui muitos outros recursos para ajudá-lo a escrever o código correto. O mais óbvio é o Error
Insight, que coloca um rabisco vermelho sob os elementos do código-fonte que ele não entende, da mesma
forma que um processador de texto marca erros ortográficos.

note Às vezes você precisa compilar seu programa pela primeira vez para evitar indicações de Error Insight para código
perfeitamente legítimo. Além disso, salvar um arquivo como um formulário pode forçar a inclusão das unidades
adequadas necessárias para os componentes atuais, resolvendo indicações incorretas do Error Insight. Esses
problemas foram amplamente resolvidos pelo novo Code Insight baseado em LSP (baseado em protocolo de servidor
de linguagem), introduzido pela primeira vez no Delphi 10.4.

Outros recursos, como o Code Completion, ajudam você a escrever código, fornecendo uma lista de
símbolos legítimos no local onde você está escrevendo. Quando uma função ou método tiver
parâmetros, você os verá listados enquanto digita. E você também pode passar o mouse sobre um símbolo
para ver sua definição. No entanto, esses são recursos específicos do editor que não quero aprofundar, pois
quero permanecer focado na linguagem e não discutir a edição do IDE.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

01: codificação em pascal - 33

tor em detalhes (mesmo que seja de longe a ferramenta mais comum usada para escrever código Object
Pascal).

Palavras-chave de idioma
Palavras-chave são todos os identificadores reservados pela linguagem. Estes são símbolos que têm um
significado e uma função predefinidos e você não pode usá-los em um contexto diferente. Formalmente,
há uma distinção entre palavras reservadas e diretivas: palavras reservadas não podem ser usadas como
identificadores, enquanto as diretivas têm um significado especial, mas podem ser usadas
também em um contexto diferente (embora seja recomendado que você não faça isso). Na prática, você não
deve usar nenhuma palavra-chave como identificador.

Se você escrever algum código como o seguinte (onde propriedade é de fato uma palavra-chave):

era
propriedade: string

você verá uma mensagem de erro como:

E2029 Identificador esperado, mas 'PROPERTY' encontrado

Em geral, quando você utiliza indevidamente uma palavra-chave, você receberá diferentes mensagens de
erro dependendo da situação, pois o compilador reconhece a palavra-chave, mas fica confuso com sua
posição no código ou com os elementos seguintes.

Aqui não quero mostrar uma lista completa de palavras-chave, pois algumas delas são bastante obscuras e
raramente usadas, mas apenas listar algumas, agrupando-as por sua função. Levarei vários capítulos para
explorar todas essas palavras-chave e outras que estou pulando nesta lista.
Você pode encontrar a referência oficial em:

http://docwiki.embarcadero.com/RADStudio/en/
Fundamental_Syntactic_Elements_(Delphi)#Reserved_Words

note Observe que algumas palavras-chave podem ser usadas em contextos diferentes, e aqui geralmente estou me referindo
apenas ao contexto mais comum (embora algumas palavras-chave sejam listadas duas vezes). Uma das razões é que
ao longo dos anos a equipe do compilador quis evitar a introdução de novas palavras-chave, pois isso poderia
quebrar os aplicativos existentes, então eles reciclaram algumas das existentes.

Então vamos começar nossa exploração de palavras-chave com algumas que você já viu no código fonte do
exemplo inicial e que são utilizadas para definir a estrutura de um projeto de aplicação:

programa Indica o nome de um projeto de aplicativo

biblioteca Indica o nome de um projeto de biblioteca

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

34 - 01: codificação em pascal

pacote Indica o nome de um projeto de biblioteca de pacotes

unidade Indica o nome de uma unidade, um arquivo de código-fonte

usa Refere-se a outras unidades nas quais o código depende

interface A parte de uma unidade com declarações

implementação A parte de uma unidade com o código real

inicialização Código executado quando um programa é iniciado


finalização Código executado no encerramento do programa

começar O início de um bloco de código

fim O fim de um bloco de código

Outro conjunto de palavras-chave refere-se à declaração de diferentes tipos de dados básicos e variáveis de tais
tipos de dados:

tipo Introduz um bloco de declarações de tipo


era Introduz um bloco de declarações de variáveis

const Introduz um bloco de declarações constantes

definir Define um tipo de dados de conjunto de energia

corda Define uma variável de string ou um tipo de string personalizado

variedade Define um tipo de array

registro Define um tipo de registro

inteiro Define uma variável inteira

real, simples, duplo Definir variáveis de ponto flutuante

arquivo Define um arquivo

note Existem muitos outros tipos de dados definidos em Object Pascal que abordarei mais tarde.

Um terceiro grupo inclui palavras-chave usadas para instruções básicas da linguagem, como condições e loops,
incluindo também funções e procedimentos:

se Introduz uma declaração condicional

então Separa a condição do código a ser executado


outro Indica possível código alternativo

caso Introduz uma instrução condicional com múltiplas opções

de Separa a condição das opções

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

01: codificação em pascal - 35

para Introduz um ciclo repetitivo de correções


para
Indica o valor superior final do ciclo for
até Indica o valor inferior final do ciclo for
em Indica a coleção a ser iterada em um ciclo
enquanto Introduz um ciclo repetitivo condicional
fazer
Separa a condição do ciclo do código
repita Introduz um ciclo repetitivo com uma condição final
até Indica a condição final do ciclo
com Indica uma estrutura de dados para trabalhar

função Uma subrotina ou grupo de instruções que retornam um


resultado

procedimento Uma sub-rotina ou grupo de instruções que não retorna resultado

em linha Solicita ao compilador que substitua uma chamada de função


pelo código real da função, para uma execução mais rápida

sobrecarga Permite a reutilização do nome de uma função ou procedimento

Muitas outras palavras-chave estão relacionadas com classes e objetos:

aula Indica um tipo de classe


objeto Usado para indicar um tipo de classe mais antigo (agora
obsoleto)
abstrato Uma classe que não está totalmente definida

selado Uma classe da qual outras classes não podem herdar

interface Indica um tipo de interface (listado também no primeiro grupo)

construtor Um método de inicialização de objeto ou classe


destruidor Um método de limpeza de objeto ou classe
virtual Um método virtual

sobrepor A versão modificada de um método virtual

herdado Refere-se a um método da classe base

privado Parte de uma aula ou registro não acessível externamente

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

36 - 01: codificação em pascal

protegido Parte de uma aula com acesso limitado do lado de fora

público Parte de uma aula ou registro totalmente acessível a partir do


fora

Publicados Parte de uma aula disponibilizada especificamente para


Usuários

estrito Uma limitação mais forte para seções privadas e protegidas

propriedade Um símbolo mapeado para um valor ou método


ler O mapeador para obter o valor de uma propriedade
escrever O mapeador para definir o valor de uma propriedade
nada
O valor de um objeto zero (usado também para outras
entidades)

Um grupo menor de palavras-chave é usado para tratamento de exceções (consulte o Capítulo 11):

tentar O início de um bloco de tratamento de exceções


finalmente Introduz código a ser executado independentemente de
uma exceção
exceto Apresenta código a ser executado em caso de exceção

elevação Usado para disparar uma exceção

Outro grupo de palavras-chave é usado para operadores e é abordado na seção


“Expressões e Operadores” mais adiante neste capítulo (além de alguns operadores avançados
abordados apenas em capítulos posteriores):

e em divisão
contra
como ou shl
não é shr livre

Finalmente, aqui está uma lista parcial de outras palavras-chave menos usadas, incluindo algumas antigas
que você realmente deve evitar usar. Novamente, aqui está apenas uma rápida indicação de onde eles
são usados, como muitos ou abordados posteriormente no livro:

padrão Indica o valor padrão de uma propriedade


dinâmico Um método virtual com uma implementação diferente
exportar Palavra-chave herdada usada para exportação, substituída pela palavra-
chave abaixo

exportações Em um projeto DLL, lista as funções a serem exportadas

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

01: codificação em pascal - 37

externo Refere-se a uma função DLL externa à qual você deseja


vincular

arquivo Usado para o tipo de arquivo legado , raramente usado atualmente.


avançar Indica uma declaração de função direta

Vá para Salta para um rótulo em um local específico do código. Isto


é altamente recomendado evitar goto.
índice Usado para propriedades indexadas e (raramente) ao
importar ou exportar funções
rótulo Define um lebal para o qual uma instrução goto salta. É
altamente recomendável evitar goto.

mensagem Uma palavra-chave alternativa para funções virtuais, associadas


às mensagens da plataforma
nome Usado para mapear funções externas
nenhum padrão Indica que as propriedades não têm valor padrão
sobre
Usado para disparar uma exceção
fora Uma alternativa para var, indicando um parâmetro passado
por referência, mas não inicializado

embalado Altera o layout da memória de um registro ou dado


estrutura

reintroduzir Permite reutilizar o nome de uma função virtual

requer Em um pacote, indica pacotes dependentes

Observe que a lista de palavras-chave da linguagem Object Pascal teve muito poucas adições nos últimos
anos, pois qualquer palavra-chave adicional implica potencialmente na introdução de erros de compilação de
programas existentes que podem usar uma das novas palavras-chave como símbolo. A maioria das adições
recentes à linguagem não requer novas palavras-chave, como genéricos e métodos anônimos.

A Estrutura de um Programa
Quase nunca você escreve todo o código em um único arquivo, embora esse tenha sido o caso do primeiro
aplicativo de console simples que mostrei anteriormente neste capítulo. Assim que você criar um
visual, você obtém pelo menos um arquivo de código-fonte secundário ao lado do arquivo do projeto.
Este arquivo secundário é chamado unit e é indicado pela extensão PAS (para Pascal

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

38 - 01: codificação em pascal

unidade de origem), enquanto o arquivo do projeto principal usa a extensão DPR (para arquivo Delphi PRoject).
Ambos os arquivos contêm código-fonte Object Pascal.

Object Pascal faz uso extensivo de unidades ou módulos de programa. As unidades, na verdade, podem ser usadas
para fornecer modularidade e encapsulamento mesmo sem o uso de objetos. As unidades atuam como namespaces.
Uma aplicação Object Pascal geralmente é composta de diversas unidades, incluindo unidades que hospedam
formulários e módulos de dados. Na verdade, quando você adiciona um novo formulário a um projeto, o IDE
adiciona uma nova unidade, que define o código do novo formulário.

As unidades não necessitam definir formulários; eles podem simplesmente definir e disponibilizar uma coleção de
rotinas ou um ou mais tipos de dados (incluindo classes). Se você adicionar uma nova unidade em branco a um
projeto, ela conterá apenas as palavras-chave usadas para delimitar as seções em que uma unidade está dividida:

unidade Unidade1;

interface

implementação

fim.

A estrutura de uma unidade simples mostrada acima inclui os seguintes elementos:

· Primeiro, uma unidade tem um nome exclusivo correspondente ao nome do arquivo (ou seja, o arquivo de amostra
unidade acima deve ser armazenada no arquivo Unit1.pas).

· Segundo, a unidade possui uma seção de interface que declara o que é visível para outras unidades.

· Terceiro, a unidade possui uma seção de implementação com detalhes de implementação, o código real e
possivelmente outras declarações locais, não visíveis fora da unidade.

Nomes de unidades e programas


Como mencionei, o nome de uma unidade deve corresponder ao nome do arquivo dessa unidade. O mesmo se
aplica a um programa. Para renomear uma unidade, você deve usar a opção Renomear no gerenciador de projetos e
os dois serão mantidos sincronizados (usar a operação Salvar como do IDE mantém o nome da unidade e o nome
do arquivo sincronizados, mas também mantém o arquivo antigo no disco). Claro, você também pode alterar o
nome do arquivo no nível do sistema de arquivos, mas se você não alterar também a declaração no início da
unidade, verá um erro quando a unidade for compilada (ou mesmo quando for carregado no IDE). Este é um exemplo
de mensagem de erro que você receberá se alterar a declaração de uma unidade sem atualizar também o nome do
arquivo:

[Erro DCC] E1038 O identificador da unidade 'Unit3' não corresponde ao nome do arquivo

A implicação do nome da unidade correspondente ao nome do arquivo é que o nome de uma unidade ou programa
deve ser um identificador Pascal válido, bem como um nome de arquivo válido. Por exemplo, o nome não pode
conter espaço nem caracteres especiais além do sublinhado (_), pois

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

01: codificação em pascal - 39

abordado anteriormente neste capítulo na seção sobre identificadores. Dadas as unidades e programas que
devem ser nomeados usando um identificador Object Pascal, eles resultam automaticamente em nomes de
arquivos válidos, então você não deve se preocupar com isso. A exceção, é claro, seria o uso de símbolos
Unicode que não são nomes de arquivo válidos no nível do sistema de arquivos.

Nomes de unidades pontilhadas

Há uma extensão das regras básicas para identificadores de unidade: um nome de unidade pode usar uma
notação pontilhada. Portanto, todos os itens a seguir são nomes de unidades válidos:

unidade 1
meuprojeto.unit1
minhaempresa.meuprojeto.unit1

Seguindo as regras gerais, essas unidades precisam ser salvas em arquivos com o mesmo nome pontilhado (ou
seja, uma unidade chamada MeuProjeto.Unit1 deve ser armazenada no arquivo MeuProjeto.U-nit1.pas).

A razão para esta extensão é que os nomes das unidades devem ser exclusivos e, com cada vez mais unidades
sendo fornecidas pela Embarcadero e por fornecedores terceirizados, isso se tornou cada vez mais complexo.
Todas as unidades RTL e várias outras unidades fornecidas como parte das bibliotecas de produtos agora
seguem a regra de nome de unidade pontilhada, com pré-fixos específicos denotando a área, como:

· Sistema para RTL principal

· Dados para acesso a banco de dados e similares

· FMX para a plataforma FireMonkey, a arquitetura multidispositivo de fonte única para


desktop e celular

· VCL para Biblioteca de Componentes Visuais para Windows

note Observação Geralmente, você se referiria a um nome de unidade pontilhado, incluindo unidades de biblioteca, com o nome completo.
Também é possível usar apenas a última parte do nome em uma referência (permitindo compatibilidade retroativa com código
mais antigo) configurando uma regra correspondente nas opções do projeto. Esta configuração é chamada de “Escopo da unidade
nomes” e é uma lista separada por ponto e vírgula. Observe, entretanto, que o uso desse recurso retarda a compilação em
comparação ao uso de nomes de unidades totalmente qualificados.

Mais sobre a estrutura de uma unidade

Além das seções de interface e implementação, uma unidade pode ter uma seção de inicialização opcional
com algum código de inicialização, para ser executado quando o programa for carregado pela primeira vez na
memória. Se houver uma seção de inicialização , você também poderá ter uma seção de finalização , para ser
executada no encerramento do programa.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

40 - 01: codificação em pascal

note Você também pode adicionar código de inicialização em um construtor de classe, um recurso de linguagem recente
abordado no Capítulo 12. O uso de construtores de classe ajuda o vinculador a remover código
desnecessário, e é por isso que é recomendado usar construtores e destruidores de classe, em vez do seções de
inicialização e finalização. Como observação histórica, o compilador ainda oferece suporte ao uso da palavra-
chave start no lugar da palavra-chave de inicialização . Um uso semelhante de start ainda é padrão no código-fonte do projeto.

Em outras palavras, a estrutura geral de uma unidade, com todas as suas seções possíveis e alguns
elementos amostrais, é semelhante a seguinte:

unidadeNomedaUnidade;

interface

// Outras unidades nós referir para em o seção de interface


usa
UnidadeA, UnidadeB, UnidadeC;

// Definições de tipo exportadas


tipo
NovoTipo = Definição de Tipo;

// Constantes exportadas
const
Zero = 0;

// Variáveis globais
era
Total: Inteiro;

Lista de funções exportadas // e procedimentos


procedimento MyProc;

implementação

// Outras unidades referidas em o implementação


usa
UnidadeD, UnidadeE;

// Tipos de unidades locais


tipo
NovoTipo2 = Definição de Tipo;

// Constantes de unidade local


const
Um = 1;

// Variável de unidade local


era
Total Parcial: Inteiro;

para Código
// qualquer local e tudo funções e procedimentos exportados
procedimento MyProc;
começar
// ... Código para procedimento MyProc

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

01: codificação em pascal - 41

fim;

inicialização
// Código de inicialização opcional

finalização
// Código de limpeza opcional

fim.

O objetivo da parte de interface de uma unidade é detalhar o que a unidade contém e pode ser utilizado
pelo programa principal e por outras unidades que farão uso da unidade.
A parte de implementação, por outro lado, contém as porcas e parafusos da unidade que ficam
ocultos aos observadores externos. É assim que Object Pascal pode fornecer o chamado
encapsulamento mesmo sem usar classes e objetos.

Como você pode ver, a interface de uma unidade pode declarar vários elementos diferentes,
incluindo procedimentos, funções, variáveis globais e tipos de dados. Os tipos de dados são geralmente
os mais usados. O IDE coloca automaticamente um novo tipo de dados de classe em uma unidade
sempre que você cria um formulário visual. Conter definições de formulário certamente não é o único
uso para unidades no Object Pascal. Você pode ter unidades somente de código, com funções e procedimentos
(de forma tradicional) e com aulas que não se referem a formas ou outros elementos visuais
comentários.

Dentro de uma interface ou seção de implementação, as declarações de tipos, variáveis, constantes e


similares podem ser escritas em qualquer ordem e podem ser repetidas múltiplas vezes.
Você pode ter algumas constantes, alguns tipos, depois mais constantes, outras variáveis e outra
seção de tipo. A única regra é que, para se referir a um símbolo, ele precisa ser declarado antes de ser
referenciado, e é por isso que muitas vezes você precisa ter várias seções.

A cláusula de uso
A cláusula uses no início da seção de interface indica quais outras unidades nós
precisa acessar na parte de interface da unidade. Isso inclui as unidades que definem os tipos de
dados aos quais nos referimos na definição dos tipos de dados desta unidade, como os componentes
usados dentro de um formulário que estamos definindo.

A segunda cláusula uses , no início da seção de implementação, indica unidades adicionais que
precisamos acessar apenas no código de implementação. Quando você precisar consultar
para outras unidades do código das rotinas e métodos, você deve adicionar elementos nesta segunda
cláusula de uso em vez da primeira, pois isso reduz dependências e diminui o tempo de compilação.
Todas as unidades às quais você se refere devem estar presentes no diretório do projeto ou em um
diretório do caminho de pesquisa.

dica Você pode definir o caminho de pesquisa para um projeto nas opções do projeto. O sistema também considera unidades em
o caminho da Biblioteca, que é uma configuração global do IDE.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

42 - 01: codificação em pascal

Os programadores C++ devem estar cientes de que a declaração de usos não corresponde a uma diretiva de
inclusão. O efeito de uma instrução uses é importar apenas a parte da interface pré-compilada das
unidades listadas. A parte de implementação da unidade é considerada somente quando essa unidade é
compilada. As unidades às quais você se refere podem estar no formato de código-fonte (PAS) ou no formato
compilado (DCU).

Embora raramente usado, Object Pascal também possui uma diretiva de compilador $INCLUDE que
funciona de forma semelhante às inclusões C/C++. No entanto, em vez de incluir o código-fonte, esses arquivos
especiais de inclusão são mais frequentemente usados por algumas bibliotecas para compartilhar diretivas
de compilador ou outras configurações entre múltiplas unidades e geralmente possuem a extensão de arquivo INC.
Esta diretiva é abordada logo no final deste capítulo.

avisar Observe que as unidades compiladas em Object Pascal são compatíveis apenas se forem construídas com a mesma
no versão do compilador e das bibliotecas do sistema. Uma unidade compilada em uma versão mais antiga do produto
geralmente não é compatível com uma versão mais recente do compilador. No entanto, as atualizações que fazem
parte da mesma versão mantêm a compatibilidade. Em outras palavras, uma unidade construída na versão 10.3.1 é compatível
com todas as versões 10.3.x, mas não com as versões 10.2 ou 10.4, por exemplo. Com a alteração do esquema
de versionamento na versão Delphi 11, qualquer atualização 11.x permanecerá compatível com a versão 11, enquanto
a versão 12 quebrará a compatibilidade.

Unidades e Escopo
No Object Pascal as unidades são a chave para o encapsulamento e a visibilidade; e, nesse sentido, são
provavelmente ainda mais importantes do que as palavras-chave privadas e públicas de uma classe. O
escopo de um identificador (como uma variável, procedimento, função ou tipo de dados)
é a parte do código na qual o identificador está acessível ou visível.

A regra básica é que um identificador é significativo apenas dentro do seu escopo – isto é, apenas dentro
da unidade, função ou procedimento em que é declarado. Você não pode usar um identificador fora do seu
escopo.

note Até recentemente, Object Pascal não tinha o conceito de escopo limitado a um bloco de código genérico
que pode incluir uma declaração. A partir do Delphi 10.3 você pode declarar variáveis inline dentro
de um bloco inicial-fim para limitar o escopo da variável ao bloco específico, como acontece em C ou C++.
Mais detalhes na seção “Vida útil e visibilidade das variáveis” do Capítulo 2.

Em geral, um identificador só fica visível depois de definido. Existem técnicas na linguagem que permitem
declarar um identificador antes de sua definição completa, mas a regra geral ainda se aplica se considerarmos
tanto as definições quanto as declarações.

Dado que não faz muito sentido escrever um programa inteiro em um único arquivo, como a regra acima muda
quando você usa múltiplas unidades? Resumindo, ao referir-se a outra unidade com uma declaração de
uso, os identificadores na seção de interface dessa unidade tornam-se visíveis para a nova unidade.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

01: codificação em pascal - 43

Invertendo isso, se você declarar um identificador (tipo, função, classe, variável e assim por diante) na parte da
interface de uma unidade, ele se tornará visível para qualquer outro módulo referente a essa unidade. Se você
declarar um identificador na parte de implementação de uma unidade, ele só poderá ser usado nessa unidade (e
geralmente é chamado de identificador local).

Usando unidades como namespaces


Vimos que a instrução uses é a técnica padrão para acessar identificadores declarados no escopo de outra
unidade. Nesse ponto você pode acessar as definições do
unidade. Mas pode acontecer que duas unidades às quais você se refere declarem o mesmo identificador; aquilo é,
você pode ter duas classes ou duas rotinas com o mesmo nome.

Neste caso você pode simplesmente usar o nome da unidade para prefixar o nome do tipo ou rotina definida na
unidade. Por exemplo, você pode consultar o procedimento ComputeTotal definido na unidade Calc fornecida
como Calc.ComputeTotal. Isso não é necessário com frequência, pois é altamente desaconselhável usar o
mesmo identificador para dois elementos diferentes do mesmo programa, se puder evitá-lo.

No entanto, se você pesquisar no sistema ou em bibliotecas de terceiros, encontrará funções e classes com o
mesmo nome. Um bom exemplo são os controles visuais de diferentes estruturas de interface de usuário.
Quando você vê uma referência a TForm ou TControl, isso pode significar classes diferentes dependendo das
unidades reais às quais você se refere.

Se o mesmo identificador for exposto por duas unidades em sua instrução de uso, aquela da última unidade usada
substituirá o símbolo e será aquela que o compilador usará. Em outras palavras, os símbolos definidos na
última unidade da lista vencem. Se você simplesmente não puder evitar tal cenário, é recomendável prefixar o
símbolo com o nome da unidade, para evitar que seu código dependa da ordem em que as unidades estão
listadas.

note Os desenvolvedores Delphi aproveitam a capacidade de ter duas classes com o mesmo nome, com uma
técnica chamada classes interpostas, uma técnica que será explicada mais adiante neste livro.

O arquivo do programa
Como vimos, uma aplicação Delphi consiste em dois tipos de arquivos de código-fonte: uma ou mais unidades e
um, e apenas um, arquivo de programa (salvo em um arquivo DPR). As unidades podem ser consideradas
arquivos secundários, que são referenciados pela parte principal da aplicação, o programa. Em teoria, isso é
verdade. Na prática, o arquivo do programa geralmente é um arquivo gerado automaticamente com uma função
limitada. Basta iniciar o programa, geralmente criando e executando o formulário principal, no caso de
uma aplicação visual. O código do arquivo do programa pode ser editado manualmente, mas também é modificado
automaticamente usando algumas das Opções de Projeto do IDE (como aquelas relacionadas ao objeto da
aplicação e aos formulários).

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

44 - 01: codificação em pascal

A estrutura do arquivo do programa é geralmente muito mais simples que a estrutura das
unidades. Aqui está o código-fonte de um arquivo de programa de exemplo (com algumas unidades
padrão opcionais omitidas) que é criado automaticamente pelo IDE para você:

programa Projeto1;

usa
FMX.Formulários,
Unidade1 em 'Unidade1.PAS' {Formulário1} ;

começar
Aplicativo.Inicializar;
Application.CreateForm(TForm1, Form1);
Aplicativo.Executar;
fim.

Como você pode ver, há simplesmente uma seção de usos e o código principal do aplicativo,
delimitado pelas palavras-chave inicial e final. A declaração de usos do programa é particularmente
importante porque é usada para gerenciar a compilação e vinculação da aplicação.

dica A lista de unidades no arquivo do programa corresponde à lista de unidades que fazem parte do projeto no IDE
Project Manager. Quando você adiciona uma unidade a um projeto no IDE, a unidade é automaticamente
adicionada à lista na origem do arquivo do programa. O oposto acontece se você removê-lo do projeto.
Em qualquer caso, se você editar o código fonte do arquivo do programa, a lista de unidades no Gerenciador
de Projetos será atualizada adequadamente.

Diretivas do compilador
Outro elemento especial da estrutura do programa (além do seu código real) são as diretivas do
compilador, como mencionado anteriormente. Estas são instruções especiais para o compilador
escritas no seguinte formato:

{$X+}

Algumas diretivas do compilador possuem um único caractere, como acima, com um símbolo de
mais ou menos indicando se a diretiva está ativada ou desativada. A maioria das diretivas também
possui uma versão mais longa e legível, e utiliza ON e OFF para marcar se estão ativas ou não.
Algumas diretivas têm apenas um formato descritivo mais longo.
As diretivas do compilador não geram código compilado diretamente, mas afetam como o compilador
gera o código após a diretiva ser encontrada. Em muitos casos, usar uma diretiva de compilador
é uma alternativa à alteração de uma das configurações do compilador nas Opções de projeto do
IDE, mas há cenários em que você deseja aplicar uma configuração específica do compilador apenas
a uma unidade ou a um fragmento de código.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

01: codificação em pascal - 45

Abordarei diretivas específicas do compilador quando relevantes na discussão de um recurso da linguagem


que elas podem afetar. Nesta seção quero apenas mencionar algumas diretivas relacionadas ao fluxo de
código do programa: definições condicionais e inclusões.

Define condicionais
As definições condicionais instruem o compilador a incluir uma parte do código-fonte ou ignorá-lo. Existem
dois tipos de definições condicionais no Delphi, o tradicional $IFDEF e $IFNDEF e o mais novo e flexível
$IF.

Essas definições condicionais podem depender de símbolos definidos ou, para a versão $IF , de valores
constantes. Os símbolos definidos podem ser predefinidos pelo sistema (como os símbolos do compilador
e da plataforma), podem ser definidos em uma opção específica do projeto ou podem ser introduzidos no
código com outra diretiva do compilador, $DEFINE.
O tradicional $IFDEF e $IFNDEF têm este formato:

{$TESTE IFDEF}
// Isso é indo para ser compilado se TESTE é definiram

{$ENDIF} {$IFNDEF TESTE}


// Esse é não indo para ser compilado se for TESTE não definiram
{$ENDIF}

Você também pode ter duas alternativas, usando uma diretiva $ELSE para separá-las.

Como mencionado, a diretiva $IF mais recente é mais flexível, permitindo o uso de expressões
constantes para a condição, como funções de comparação que podem se referir a qualquer valor constante
no código (por exemplo, verificar se a versão do compilador é superior a um determinado valor) . A diretiva
$IF é finalizada por uma diretiva $IFEND :

{$IF (ProgramVersão 2.0) }


... > // Este código é executado se a condição for {$ELSE} verdadeiro

... // Este código executa {$IFEND} se condição é falso

Você também pode usar a diretiva $ELSEIF se tiver múltiplas condições.

Versões do compilador
Cada versão do compilador Delphi possui uma definição específica que você pode usar para verificar se está
compilar em uma versão específica do produto. Isso pode ser necessário se você estiver usando um
recurso introduzido posteriormente, mas quiser ter certeza de que o código ainda será compilado para
versões mais antigas.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

46 - 01: codificação em pascal

Se você precisar de código específico para algumas das versões recentes do Delphi, você pode basear
suas instruções $IFDEF nas seguintes definições:

Delfos 2007 VER180

Delphi XE VER220

Delphi XE2 VER230

Delphi XE4 VER250

Delphi XE5 VER260

Delphi XE6 VER270

Delphi XE7 VER280

DelphiXE8 VER290

Delfos 2007 VER180

Delphi XE VER220

Delphi XE2 VER230

Delphi XE4 VER250

Delphi XE5 VER260

Delphi XE6 VER270

Delphi XE7 VER280

DelphiXE8 VER290

Delphi 10 Seattle VER300

Delphi 10.1 Berlim VER310

Delphi 10.2 Tóquio VER320

Delphi 10.3 Rio VER330

Delphi 10.4 Sydney VER340

Delfos 11 Alexandria VER350

Os dígitos decimais desses números de versão indicam a versão real do compilador (por exemplo,
26 no Delphi XE5). A sequência numérica não é específica do Delphi, mas remonta ao primeiro
compilador Turbo Pascal publicado pela Borland (consulte o Apêndice A para obter mais informações).

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

01: codificação em pascal - 47

Você também pode usar a constante de controle de versão interna em instruções $IF , com a vantagem
de poder usar um operador de comparação (>=) em vez de uma correspondência para uma versão específica.
A constante de versionamento é chamada CompilerVersion e no Delphi XE5 é atribuído o valor de
ponto flutuante 26.0, conforme usado no exemplo a seguir:

{$SE Versão do compilador // >= 26)}


Compilar código para em Delfos XE5 ou mais tarde
{$IFEND}

Da mesma forma, você pode usar definições de sistema para as diferentes plataformas para as quais você pode
compilar, caso precise que algum código seja específico da plataforma (geralmente uma exceção no Object
Pas-cal, não é uma prática comum):

janelas MSWINDOWS

Mac OS MAC OS

iOS iOS

Android ANDRÓIDE

Linux LINUX

Abaixo está um trecho de código do projeto HelloPlatform com os testes baseados na plataforma definida
acima:

{$IFDEF iOS}
Mostrar mensagem( 'Correndo sobre iOS' );

{$ENDIF} {$IFDEF ANDROID}


Mostrar mensagem( 'Correndo Android ligado );
{$ENDIF}

Incluir arquivos

A outra diretiva que quero abordar aqui é a diretiva $INCLUDE , já mencionada ao discutir a declaração de
usos. Esta diretiva permite consultar e incluir um fragmento de código-fonte em uma determinada posição de
um arquivo de código-fonte. Às vezes isto é utilizado para poder incluir o mesmo fragmento em unidades
diferentes, nos casos em que o fragmento de código define diretivas do compilador e outros elementos
utilizados diretamente pelo compilador.

Quando você usa uma unidade, ela é compilada apenas uma vez. Quando você inclui um arquivo, esse
código é compilado dentro de cada uma das unidades às quais ele é adicionado (é por isso que você
geralmente deve evitar ter qualquer novo identificador declarado em um arquivo de inclusão, se o mesmo
arquivo de inclusão estiver incorporado em unidades diferentes do mesmo projeto. Então, como você usa um arquivo de incl

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

48 - 01: codificação em pascal

um bom exemplo é um conjunto de diretivas de compilador que você deseja ativar na maioria de suas unidades, ou
algumas definições extras especiais.

Grandes bibliotecas costumam usar arquivos include para esse fim, um exemplo seria a biblioteca FireDAC,
uma biblioteca de banco de dados que agora faz parte das bibliotecas do sistema. Outro exemplo, apresentado pelas
unidades RTL do sistema, é o uso de inclusões individuais para cada plataforma, com um $IFDEF usado para incluir
condicionalmente apenas uma delas.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

02: variáveis e tipos de dados - 49

02: variáveis e
tipos de dados

Object Pascal é conhecido como linguagem fortemente tipada . Variáveis em Object Pascal
são declarados como sendo de um tipo de dados (ou tipo de dados definido pelo usuário). O tipo de uma
variável determina os valores que uma variável pode conter e as operações que podem ser executadas nela.
isto. Isso permite que o compilador identifique erros em seu código e gere programas mais rápidos para você.

É por isso que o conceito de tipo é mais forte em Pascal do que em linguagens como C ou C++.
Linguagens posteriores baseadas na mesma sintaxe, mas que quebram a compatibilidade com C, como C#
e Java, desviaram-se de C e adotaram a forte noção de tipo de dados de Pascal. Em C, por exemplo, os
tipos de dados aritméticos são quase intercambiáveis. Por outro lado, as versões originais do BASIC
não tinham um conceito semelhante e, em muitas das linguagens de script atuais (o JavaScript é um
exemplo óbvio), a noção de tipo de dados é muito diferente.

note Na verdade, existem alguns truques para contornar a segurança de tipo, como usar tipos de registro variantes. O uso destes
truques são fortemente desencorajados e não são muito usados hoje.

Como mencionei, todas as linguagens dinâmicas, do JavaScript em diante, não têm a mesma noção de tipo
de dados, ou (pelo menos) têm uma ideia muito vaga de tipos. Em algumas dessas linguagens, o tipo é
inferido pelo valor atribuído a uma variável, e o tipo da variável pode mudar com o tempo. O que é
importante ressaltar é que os tipos de dados são fundamentais

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

50 - 02: variáveis e tipos de dados

elemento para impor a correção de um aplicativo grande em tempo de compilação, em vez de depender de
verificações em tempo de execução. Os tipos de dados exigem mais ordem e estrutura, e algum
planejamento do código que você vai escrever – o que claramente tem vantagens e desvantagens.

note Escusado será dizer que prefiro linguagens fortemente tipadas, mas de qualquer forma meu objetivo neste livro é explicar
como a linguagem funciona, mais do que defender por que a considero uma linguagem de programação tão boa.

Variáveis e Atribuições
Como outras linguagens fortemente tipadas, Object Pascal exige que todas as variáveis sejam declaradas
antes de serem usadas. Cada vez que você declara uma variável, você deve especificar um tipo de dados.
Aqui estão algumas declarações de variáveis:

era
Valor: Inteiro;
EstáCorreto: Booleano;
A, B: Caráter;

A palavra-chave var pode ser usada em vários lugares de um programa, como no início de
uma função ou procedimento, para declarar variáveis locais para aquela parte do código, ou dentro de uma
unidade para declarar variáveis globais.

note Como veremos daqui a pouco, versões recentes do Delphi adicionam a capacidade de declarar uma variável “inline”,
que é misturada em instruções de programação. Este é um afastamento significativo do Pascal clássico.

Após a palavra-chave var , vem uma lista de nomes de variáveis, seguida por dois pontos e o nome
do tipo de dados. Você pode escrever mais de um nome de variável em uma única linha, como A e B
na última instrução do trecho de código anterior (um estilo de codificação que é menos comum hoje,
comparado à divisão da declaração em duas linhas separadas: usar linhas separadas ajuda na legibilidade,
comparação de versões e fusão).

Depois de definir uma variável de um determinado tipo, você só poderá executar nela as operações suportadas
por seu tipo de dados. Por exemplo, você pode usar a variável booleana em um teste e a variável inteira em
uma expressão numérica. Você não pode misturar variáveis booleanas e inteiros sem chamar uma função de
conversão específica, por exemplo, ou misturar qualquer tipo de dados incompatível (mesmo que a
representação interna possa ser fisicamente compatível, como é o caso de variáveis booleanas e inteiras).

A atribuição mais simples é a de um valor real e específico. No exemplo acima, você pode querer que a
variável Value contenha o valor numérico 10. Mas como expressar valores literais? Embora este conceito
possa ser óbvio, vale a pena examiná-lo com alguns
detalhe.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

02: variáveis e tipos de dados - 51

Valores Literais
Um valor literal é um valor que você digita diretamente no código-fonte do programa. Se precisar de
um número com valor vinte, você pode simplesmente escrever:

20

Existe também uma representação alternativa do mesmo valor numérico, baseada no valor
hexadecimal, ou seja:

US$ 14

A partir do Delphi 11, você também pode expressar o mesmo número como um valor decimal binário,
como em:

%10100;

Para um valor muito grande, também a partir do Delphi 11, você pode usar um sublinhado como separador
de dígitos para tornar o valor constante mais legível. O separador é ignorado pelo compilador. Por
exemplo, você pode escrever o valor 20 milhões como:

20_000_000

Estes serão valores literais para um número inteiro (ou um dos vários números inteiros
tipos disponíveis). Se você deseja o mesmo valor numérico, mas para um valor literal de ponto flutuante,
geralmente adiciona um zero decimal depois dele:

20,0

Os valores literais não se limitam a números. Você também pode ter caracteres e strings.
Ambos usam aspas simples (onde muitas outras linguagens de programação usarão aspas duplas
para ambos, ou aspas simples para caracteres e aspas duplas para strings):

// Personagens literais
'K'
#55

// Cadeia literal
'Marco'

Como você pode ver acima, você também pode indicar caracteres pelo valor numérico correspondente
(originalmente o número ASCII, agora o valor do ponto de código Unicode), prefixando o número com o
símbolo #, como em #32 para um caractere de espaço. Isso é útil principalmente para caracteres de
controle sem representação textual no código-fonte, como backspace ou tabulação.

Caso você precise colocar uma aspa dentro de uma string, você terá que escapá-la com outra aspa
(dobrando-a). Portanto, se eu quiser que meu nome e sobrenome sejam escritos com uma aspa
final em vez de acento, posso escrever:

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

52 - 02: variáveis e tipos de dados

'Meu nome é Marco Cantu''

As duas aspas representam uma aspa dentro da string, enquanto a terceira aspa consecutiva marca
o final da string. Observe também que uma string literal deve ser escrita em uma única linha, mas
você pode concatenar vários literais de string usando o sinal +. Se você quiser ter a nova linha ou
quebra de linha dentro da string, não a escreva em duas linhas, mas concatene os dois elementos
com a constante do sistema sLineBreak (que é específica da plataforma), como em:

'Marco' + sLineBreak + "Cantoria"

Declarações de Atribuição
As atribuições no Object Pascal usam o operador igual a dois pontos (:=), uma notação estranha
para programadores que estão acostumados com outras linguagens. O operador = , que é usado
para atribuições em muitas outras linguagens, no Object Pascal é usado para testar a igualdade.

história O operador := vem de um antecessor do Pascal, Algol, uma linguagem que poucos desenvolvedores de hoje possuem
ouvi falar (até mesmo usado). A maioria das linguagens atuais evita a notação := e favorece a notação de atribuição =.

Ao usar símbolos diferentes para uma atribuição e um teste de igualdade, o compilador Pascal, assim
como o compilador C, pode traduzir o código-fonte mais rapidamente, porque não precisa examinar
o contexto em que o operador é usado para determinar seu significado. O uso de diferentes
operadores também tornam o código mais fácil de ser lido pelas pessoas. Comparado a Pascal, C
e derivados sintáticos (como Java, C#, JavaScript) use = para atribuição e == para teste de igualdade.

note Para ser mais completo, devo mencionar que o JavaScript também tem um operador === (executando um
teste estrito de igualdade de tipo e valor), mas isso é algo que deixa até a maioria dos programadores de JavaScript
confusos.

Os dois elementos de uma atribuição são frequentemente chamados de lvalue e rvalue, para valor
esquerdo (a variável ou local de memória à qual você está atribuindo) e valor direito, o valor das
expressões que estão sendo atribuídas. Embora o rvalue possa ser uma expressão, o lvalue deve
referir-se (direta ou indiretamente) a um local de memória que você pode modificar. Existem, no entanto,
alguns tipos de dados que possuem comportamentos de atribuição específicos que abordarei no devido tempo.

A outra regra é que o tipo de lvalue e de rvalue deve corresponder, ou deve haver uma conversão
automática entre os dois, conforme explicado na próxima seção.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

02: variáveis e tipos de dados - 53

Atribuições e Conversão
Usando atribuições simples, podemos escrever o seguinte código (que você pode encontrar entre
muitos outros trechos nesta seção do projeto VariablesTest ):

Valor := 10;
Valor := Valor + 10;
Está Correto := Verdadeiro;

Dadas as declarações de variáveis anteriores, estas três atribuições estão corretas. A próxima afirmação,
em vez disso, não está correta, pois as duas variáveis têm tipos de dados diferentes:

Valor := EstáCorreto; // Erro

Conforme você digita esse código, o editor Delphi mostra imediatamente um rabisco vermelho
indicando o erro, com uma descrição adequada. Se você tentar compilá-lo, o compilador emitirá o
mesmo erro com uma descrição como:

[Erro dcc32]: E2010 Tipos incompatíveis: 'Inteiro' e 'Booleano'

O compilador informa que há algo errado em seu código, ou seja, dois tipos de dados incompatíveis.
Claro, muitas vezes é possível converter o valor de uma variável de um tipo para outro. Em alguns casos,
a conversão é automática, por exemplo se você atribuir um valor inteiro a uma variável de ponto flutuante
(mas não o contrário, é claro). Normalmente você precisa chamar uma função específica do
sistema que altera a representação interna dos dados.

Inicializando Variáveis Globais


Para variáveis globais, você pode atribuir um valor inicial ao declarar a variável, usando a notação de
atribuição constante abordada abaixo (=) em vez do operador de atribuição
(:=). Por exemplo, você pode escrever:

era
Valor: Inteiro = 10;
Correto: Booleano = Verdadeiro;

Esta técnica de inicialização funciona apenas para variáveis globais, que são inicializadas com seus
valores padrão de qualquer maneira (como zero para um número).

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

54 - 02: variáveis e tipos de dados

Inicializando Variáveis Locais


As variáveis declaradas no início de um procedimento ou função, em vez disso, não são inicializadas com um valor
padrão e não possuem uma sintaxe de atribuição. Para essas variáveis, muitas vezes vale a pena adicionar um
código de inicialização explícito no início do código:

era
Valor: Inteiro;
começar
Valor := 0; // Inicializar

Novamente, se você não inicializar uma variável local, mas usá-la como está, a variável terá um valor totalmente
aleatório (dependendo dos bytes que estavam presentes naquele local de memória). Em vários cenários, o
compilador irá avisá-lo sobre o erro potencial, mas nem sempre.

Em outras palavras, se você escrever:

era
Valor: Inteiro;
começar
ShowMessage(Value.ToString); // O valor é indefinido

O compilador avisará que Value não foi inicializado e, quando você executar o programa,
você descobrirá que a saída será um valor totalmente aleatório exibindo quaisquer bytes que estejam no local de
memória da variável Value interpretada como um número inteiro.

Variáveis embutidas
Versões recentes do Delphi (começando com 10.3 Rio) possuem um conceito adicional, que altera a abordagem
de declaração de variáveis usada desde o início do Pascal e do Turbo Pascal.
compiladores: declarações de variáveis embutidas.

A nova sintaxe de declaração de variável inline permite declarar a variável diretamente em um bloco de código
(permitindo também vários símbolos como nas declarações de variáveis tradicionais):

procedimento Teste;
começar
var I, J: Inteiro;
Eu:= 22;
J := eu + 20;
ShowMessage(J.ToString);
fim;

Embora, superficialmente, essa mudança não pareça grande coisa, há vários efeitos colaterais dessa mudança
relacionados à inicialização, inferência de tipo e tempo de vida da variável. Vou abordar isso nas próximas seções
deste capítulo.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

02: variáveis e tipos de dados - 55

Inicializando Variáveis Inline


A primeira mudança significativa em comparação com o antigo modelo de declaração é que a
declaração inline e a inicialização de uma variável podem ser feitas em uma única instrução.
Isso torna as coisas mais legíveis em comparação com a inicialização de diversas variáveis no início
de uma função, conforme indicado na seção anterior:

procedimento Teste;
começar
var I: Inteiro := 22;
ShowMessage(I.ToString);
fim;

A principal vantagem aqui é que se o valor de uma variável estiver disponível apenas
posteriormente no bloco de código, em vez de definir um valor inicial (como 0 ou nulo) e
posteriormente atribuir o valor real, você pode atrasar a declaração da variável até o ponto onde
você pode calcular um valor inicial adequado, como K abaixo:

procedimento Teste1;
começar
var I: Inteiro := 22;
var J: Inteiro := 22 + I;
var K: Inteiro := I + J;
ShowMessage(K.ToString);
fim;

Em outras palavras, enquanto no passado todas as variáveis locais eram visíveis em todo o
bloco de código, agora uma variável inline é visível apenas a partir da posição de sua declaração e
até o final do bloco de código. Em outras palavras, uma variável como K não pode ser usada nas 2
primeiras linhas do bloco de código, antes de receber um valor adequado.

Inferência de tipo para variáveis embutidas


Outro benefício significativo das variáveis inline é que o compilador agora pode, em diversas
circunstâncias, inferir o tipo de uma variável inline observando o tipo da expressão ou valor
atribuído a ela. Este é um exemplo muito simples:

procedimento Teste;
começar
onde eu := 22;
ShowMessage(I.ToString);
fim;

O tipo da expressão rvalue (ou seja, o que vem depois de :=) é analisado para determinar o tipo da
variável. Ao atribuir uma constante string, a variável seria do tipo string, mas a mesma análise
acontece se você atribuir o resultado de uma função ou de uma expressão complexa.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

56 - 02: variáveis e tipos de dados

Observe que a variável ainda é fortemente do tipo, pois o compilador determina o tipo de dados e o
atribui em tempo de compilação e não pode ser modificado atribuindo um novo valor. A inferência de
tipo é apenas uma conveniência para digitar menos código (algo relevante para tipos de dados
complexos, como tipos genéricos complexos), mas não altera a natureza estática e fortemente
tipada da linguagem nem causa lentidão no tempo de execução.

note Ao inferir o tipo, alguns dos tipos de dados são “expandidos” para um tipo maior, como no caso acima,
onde o valor numérico 22 (um ShortInt) é expandido para Integer. Como regra geral, se o tipo de
expressão à direita for um tipo inteiro e menor que 32 bits, a variável será declarada como um número
inteiro de 32 bits. Claro, você pode usar um tipo explícito se quiser um tipo numérico específico e menor.

Constantes
Object Pascal também permite a declaração de constantes. Isso permite que você dê nomes
significativos a valores que não mudam durante a execução do programa (e possivelmente
reduzindo o tamanho ao não duplicar valores constantes em seu código compilado).

Para declarar uma constante você não precisa especificar um tipo de dados, mas apenas atribuir
um valor inicial. O compilador examinará o valor e inferirá automaticamente o tipo de dados adequado.
Aqui estão alguns exemplos de declarações (também do exemplo VariablesTest ):

const
Mil = 1_000;
Pi = 3,14;
AutorNome = 'Marco Cantú' ;

O compilador determina o tipo de dados constante com base em seu valor. No exemplo acima, a
constante Mil é considerada do tipo SmallInt, o menor número inteiro
tipo que pode segurá-lo. Se desejar, você pode instruir o compilador a usar um tipo específico
simplesmente incluindo o tipo como parte da declaração, como em:

const
Mil: Inteiro = 1_000;

avisar As suposições em um programa geralmente são ruins e o compilador pode mudar com o tempo e não corresponder
no mais às suposições do desenvolvedor. Se você tiver uma maneira de expressar o código de forma mais clara e
sem suposições, faça-o!

Quando você declara uma constante, o compilador pode escolher se deseja atribuir um local de memória à constante e
salvar seu valor lá ou duplicar o valor real cada vez que a constante for usada. Esta segunda abordagem faz sentido
especialmente para constantes simples.

Depois de declarar uma constante, você poderá usá-la quase como qualquer outra variável, mas
não poderá atribuir um novo valor a ela. Se você tentar, receberá um erro do compilador.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

02: variáveis e tipos de dados - 57

note Curiosamente, o Object Pascal permite que você altere o valor de uma constante digitada em tempo de execução,
como se fosse uma variável, mas apenas se você ativar a diretiva do compilador $J ou usar o Assignable correspondente
opção do compilador de constantes digitadas . Este comportamento opcional está incluído para compatibilidade
retroativa de código que foi escrito com um compilador antigo. Claramente este não é um estilo de codificação
sugerido, e eu o abordei nesta nota mais como uma anedota histórica sobre tais técnicas de programação.

Constantes embutidas
Como vimos anteriormente para variáveis, agora você também pode incorporar uma declaração de
valor constante. Isso pode ser aplicado a constantes digitadas ou não digitadas, caso em que o tipo
é inferido (um recurso que está disponível para constantes há muito tempo). Segue um exemplo simples:

começar
// Algum código
const M: Inteiro = (L + H) div 2; // Identificador com especificador de tipo
// Algum código
const M = (L + H) div 2; // Identificador sem especificador de tipo

Observe que, embora uma declaração de constante regular permita atribuir apenas um valor constante,
para uma declaração de constante embutida você pode usar qualquer expressão.

Constantes de string de recursos


Embora este seja um tópico um pouco mais avançado, ao definir uma constante de string, em vez
de escrever uma declaração de constante padrão, você pode usar uma diretiva específica,
resourcestring, que indica ao compilador e ao vinculador para tratar a string como um
Recurso do Windows (ou uma estrutura de dados equivalente em plataformas não Windows Objeto
Pascal suporta):

const
AutorNome = 'Marco' ;

cadeia de recursos
StrNomeAutor = 'Marco' ;

começar
ShowMessage(StrAuthorname);

Em ambos os casos você está definindo uma constante; isto é, um valor que você não altera durante a
execução do programa. A diferença está apenas na implementação interna. Uma constante de
string definida com a diretiva resourcestring é armazenada nos recursos do programa, em uma tabela
de strings, e essa tabela pode, potencialmente, ser editada por um editor de recursos, como uma
ferramenta de localização (uma ferramenta usada para traduzir strings para outra linguagem).

As vantagens do uso de recursos são o manuseio de memória mais eficiente realizado pelo Windows,
uma implementação Delphi correspondente para outras plataformas e uma melhor maneira de
localizar um programa sem ter que modificar seu código-fonte. Como regra de

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

58 - 02: variáveis e tipos de dados

thumb, você deve usar resourcestring para qualquer texto mostrado aos usuários e que possa precisar de
tradução, e constantes internas para todas as outras strings internas do programa, como um nome de
arquivo de configuração fixo.

dica O editor IDE possui uma refatoração automática que você pode usar para substituir uma constante de string em seu
código por uma declaração de string de recursos correspondente . Coloque o cursor de edição dentro de uma string
literal e pressione Ctrl+Shift+L para ativar esta refatoração.

Vida útil e visibilidade das variáveis


Dependendo de como você define uma variável, ela usará diferentes locais de memória e permanecerá
disponível por um período de tempo diferente (algo geralmente chamado de tempo de vida da variável )
e estará disponível em diferentes partes do seu código (um recurso referido pelo visibilidade do
prazo ).

Agora, não podemos ter uma descrição completa de todas as opções neste início do livro, mas certamente
podemos considerar os casos mais relevantes:

• Variáveis globais: Se você declarar uma variável (ou qualquer outro identificador) na parte de
interface da unidade, seu escopo se estende a qualquer outra unidade que utilize aquela que a
declara. A memória para esta variável é alocada assim que o programa é iniciado e existe até
que ele termine. Você pode atribuir um valor padrão a ele ou usar a seção de inicialização da
unidade caso o valor inicial seja calculado em um
maneira mais complexa.

• Variáveis ocultas globais: se você declarar uma variável na parte de implementação de uma
unidade, não poderá usá-la fora dessa unidade, mas poderá usá-la em qualquer bloco de código
e procedimento definido dentro da unidade, a partir da posição da declaração. . Tal variável
utiliza memória global e tem o mesmo tempo de vida que o primeiro grupo; a única diferença está
na sua visibilidade. A inicialização é
o mesmo que o das variáveis globais.

• Variáveis locais: Se você declarar uma variável dentro do bloco que define uma função,
procedimento ou método, você não pode usar esta variável fora desse bloco de código.
O escopo do identificador abrange toda a função ou método, incluindo rotinas aninhadas (a
menos que a rotina aninhada seja declarada acima destas e/ou um identificador com o mesmo
nome na rotina aninhada oculte a definição externa).
A memória para esta variável é alocada na pilha quando o programa executa a rotina que a
define. Assim que a rotina termina, a memória da pilha é automaticamente liberada.

• Variáveis inline locais: se você declarou uma variável inline dentro do bloco
definindo uma função, procedimento ou método, a restrição adicional em comparação com uma
variável local tradicional é que a visibilidade começa na linha em que a variável é declarada e
se estende até o final da função ou método.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

02: variáveis e tipos de dados - 59

• Variáveis inline com escopo de bloco: se você declarar uma variável inline dentro de um
bloco de código (ou seja, uma instrução de início-fim aninhada ou um bloco try-finalmente ), a
visibilidade da variável será limitada a esse bloco aninhado. Isso corresponde ao que acontece
na maioria das outras linguagens de programação, mas foi introduzido no Object Pascal apenas
com a recente sintaxe de declaração de variável inline. Observe que uma variável inline
declarada em um bloco não pode usar o mesmo identificador que uma variável declarada no
bloco de código pai.

note Para variáveis embutidas com escopo de bloco, não apenas a visibilidade, mas também o tempo de vida da variável é
limitado ao bloco. Um tipo de dados gerenciado (como uma interface, string ou registro gerenciado) será descartado
no final do subbloco, e não no final do procedimento ou método. Isto também se aplica a variáveis temporárias
criadas para armazenar resultados de expressões.

Quaisquer declarações na parte de interface de uma unidade são acessíveis a partir de qualquer parte
do programa que inclua a unidade em sua cláusula de uso . Variáveis de classes de formulário são
declaradas da mesma maneira, para que você possa fazer referência a um formulário (e seus campos
públicos, métodos, propriedades e componentes) a partir do código de qualquer outro
formulário. Claro, é uma má prática de programação declarar tudo como global. Além dos problemas
óbvios de consumo de memória, o uso de variáveis globais torna o programa mais difícil de manter e
atualizar. Resumindo, você deve usar o menor número possível de variáveis globais e evitar a tentação
de acessar as variáveis globais que o Delphi cria, como as dos formulários.

Tipos de dados
Em Pascal existem vários tipos de dados predefinidos, que podem ser divididos em três grupos:
tipos ordinais, tipos reais e strings. Discutiremos tipos ordinais e reais nas seções seguintes, enquanto
strings serão abordadas especificamente no Capítulo 6.

O Delphi também inclui um tipo de dados não digitado , chamado variante, e outros tipos “flexíveis”, como
TValue (parte do suporte aprimorado ao RTTI). Alguns desses tipos de dados mais avançados serão
discutidos posteriormente no Capítulo 5.

Tipos ordinais e numéricos


Os tipos ordinais são baseados no conceito de ordem ou sequência. Você não só pode comparar
dois valores para ver qual é o maior, mas você também pode pedir os valores seguintes ou anteriores de
qualquer valor ordinal e calcular os valores mais baixos e mais altos possíveis que o tipo de dados pode
representar.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

60 - 02: variáveis e tipos de dados

Os três tipos ordinais predefinidos mais importantes são Integer, Boolean e Char
(personagem). No entanto, existem outros tipos relacionados que têm o mesmo significado, mas uma
representação interna diferente e suportam uma gama diferente de valores.

A tabela a seguir lista os tipos de dados ordinais usados para representar números:

Tamanho Assinado Não assinado

8 bits ShortInt: -128 a 127 Byte: 0 a 255

SmallInt: -32768 a 32767 Palavra: 0 a 65.535


16 bits
(-32K a 32K) (0 a 64K)

Inteiro: -2.147.483.648 para Cardeal: 0 a 4.294.967.295 (0 a 4


32 bits
2.147.483.647 (-2 GB a +2 GB) GB)

Int64: - UInt64: 0 a
64 bits 9.223.372.036.854.775.808 a 18.446.744.073.709.551.615
9.223.372.036.854.775.807 (se você conseguir ler!)

Como você pode ver, esses tipos correspondem a diferentes representações de números
dependendo da quantidade de bits utilizados para expressar o valor e da presença ou ausência
de um bit de sinal. Os valores com sinal podem ser positivos ou negativos, mas possuem uma faixa
menor de valores (metade do valor sem sinal correspondente), porque um bit a menos está disponível
para armazenar o próprio valor.

O tipo Int64 representa números inteiros com até 18 dígitos. Esse tipo é totalmente suportado por
algumas rotinas de tipo ordinal (como High e Low), rotinas numéricas (como Inc e Dec) e rotinas de
conversão de string (como IntToStr) da biblioteca de tempo de execução.

Tipos inteiros com alias


Se você tiver dificuldade em lembrar a diferença entre ShortInt e Small-
Int (incluindo qual é efetivamente menor), em vez do tipo real, você pode usar
um dos aliases predefinidos declarados na unidade do sistema :

tipo
Int8 = ShortInt;
Int16 = PequenoInt;
Int32 = Inteiro;
UInt8 = Byte;
UInt16 = Palavra;
UInt32 = Cardeal;

Novamente, esses tipos não acrescentam nada de novo, mas são provavelmente mais fáceis de usar,
pois é simples lembrar a implementação real de um Int16 em vez de um SmallInt.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

02: variáveis e tipos de dados - 61

Esses aliases de tipo também são mais fáceis de usar para desenvolvedores vindos de C e de outras
linguagens que usam nomes de tipos semelhantes.

Tipo inteiro, 64 bits, NativeInt e LargeInt


Nas versões de 64 bits do Object Pascal, você pode se surpreender ao saber que o tipo Integer ainda é de 32
bits. Isso ocorre porque este é o tipo mais eficiente para processamento numérico no nível da CPU.

É o tipo Pointer (mais sobre ponteiros posteriormente) e outros tipos de referência relacionados que são de 64
bits. Se você precisar de um tipo numérico que se adapte ao tamanho do ponteiro e ao nativo
Plataforma de CPU, você pode usar os dois tipos especiais de alias NativeInt e NativeUInt .
Eles têm o mesmo tamanho de um ponteiro na plataforma específica (ou seja, 32 bits em plataformas de 32 bits
e 64 bits em plataformas de 64 bits).

Um cenário um pouco diferente acontece para o tipo LargeInt , que geralmente é usado para mapear funções de
API da plataforma nativa. São 32 bits em plataformas de 32 bits e no Windows de 32 bits, enquanto são 64 bits
na plataforma ARM de 64 bits. É melhor ficar longe disso
a menos que você precise dele especificamente para código nativo de uma forma que se adapte ao sistema
operacional subjacente.

Auxiliar de tipo inteiro


Embora os tipos Integer sejam tratados separadamente dos objetos na linguagem Object Pascal, é possível
operar em variáveis (e valores constantes) desses tipos com operações que você aplica usando “notação de
ponto”. Esta é a notação geralmente usada para aplicar métodos a objetos.

note Tecnicamente, essas operações em tipos de dados nativos são definidas usando “auxiliares de registro intrínseco”. Aula
e auxiliares de registro são abordados no Capítulo 12. Resumindo, você pode personalizar as operações aplicáveis a
tipos de dados principais. Os desenvolvedores especialistas notarão que as operações de tipo são definidas como métodos estáticos de
classe no auxiliar de registro intrínseco correspondente.

Você pode ver alguns exemplos no código a seguir extraído da demonstração Inte-gersTest :

era
N: Inteiro;
começar
N:= 10;
Mostrar(N.ToString);

// Mostrar a constante
Mostrar(33.ToString);

// Digite operação, mostre os bytes necessários para loja o tipo


Mostrar(Integer.Size.ToString);

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

62 - 02: variáveis e tipos de dados

note A função Show , usada neste trecho de código, é um procedimento simples usado para exibir alguma saída de string
em um controle de memorando para evitar ter que fechar várias caixas de diálogo ShowMessage . Uma vantagem de
essa abordagem facilita copiar a saída e colar no texto (como fiz abaixo).
Você verá essa abordagem sendo usada na maioria das demonstrações deste livro.

A saída do programa é a seguinte

10
33
4

Dado que estas operações são muito importantes (mais do que outras que fazem parte da biblioteca de tempo
de execução), vale a pena listá-las aqui:

ToString Converta o número em uma string, usando o formato decimal


ToBoolean
Converter para tipo booleano
ToHexString Converte para uma string, usando formato hexadecimal
ToSingle Converter para tipo de dados de ponto flutuante único
ToDoubleConverte para tipo de dados de ponto flutuante duplo

ToExtended Converter para tipo de dados de ponto flutuante estendido

A primeira e a terceira operações convertem o número em uma string, usando uma operação decimal ou
hexadecimal. A segunda é uma conversão para booleano, enquanto as três últimas são conversões para
tipos de ponto flutuante descritos posteriormente.

Existem outras operações que você pode aplicar ao tipo Inteiro (e à maioria das outras operações numéricas).
tipos), como:

Tamanho O número de bytes necessários para armazenar uma variável deste tipo
Analisar Converta uma string no valor numérico que ela representa, gerando uma exceção
se a string não representa um número
TryParse Tente converter a string em um número

Rotinas de tipo ordinal padrão


Além das operações definidas pelos auxiliares do tipo Integer, e listadas acima, existem diversas funções padrão e “clássicas”
que você pode aplicar a qualquer tipo ordinal (não apenas aos numéricos). Um exemplo clássico é solicitar informações
sobre o próprio tipo, usando as funções SizeOf, High e Low. O resultado da função do sistema SizeOf (que você pode aplicar
a qualquer tipo de dados da linguagem) é um número inteiro que indica o número de bytes necessários para representar valores
de um determinado tipo (assim como a função auxiliar Size mostrada acima)

As rotinas do sistema que funcionam em tipos ordinais são mostradas na tabela a seguir:

dezembro Diminuir a variável passada como parâmetro, em um ou no valor do segundo parâmetro


opcional

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

02: variáveis e tipos de dados - 63

Inc. Incrementar a variável passada como parâmetro, por um ou pelo valor especificado

Chance Retorna True se o argumento for um número ímpar. Para testar números pares,
você deve usar uma expressão not (não ímpar)
antes Retorna o valor antes do argumento, ou seja, o antecessor, na ordem determinada
pelo tipo de dado
Sucesso Retorne o valor após o argumento, ou seja, o sucessor
Palavra Retorna um número que indica a ordem do argumento dentro do conjunto de valores
do tipo de dados (usado para tipos ordinais não numéricos)
Baixo Retorna o valor mais baixo no intervalo do tipo ordinal
Alto Retorne o valor mais alto no intervalo do tipo de dados ordinal

nota Os programadores C e C++ devem observar que as duas versões do procedimento Inc , com um ou dois parâmetros,
correspondem aos operadores ++ e += (o mesmo vale para o procedimento Dec que corresponde aos operadores
-- e -= operadores). O compilador Object Pascal otimiza essas operações de incremento e decremento, de
forma semelhante ao que fazem os compiladores C e C++. No entanto, diferentemente do C/C++, o Delphi oferece
apenas versões pré-incremento e pré-decremento, não pós-incremento e pós-decremento. Outra diferença é
que Inc e Dec do Delphi não retornam valores.

Observe que algumas dessas rotinas são avaliadas automaticamente pelo compilador e substituídas
por seus valores. Por exemplo, se você chamar High(X) onde X é definido como um Integer, o
compilador substituirá a expressão pelo valor mais alto possível do tipo de dados Integer . No
exemplo IntegersTest adicionei um evento com algumas destas funções do tipo ordinal:

era
N: UInt16;
começar
N := Baixo(UInt16);
Inc(N);
Mostrar(N.ToString);
Inc(N, 10);
Mostrar(N.ToString);
se ímpar(N) então
'
Mostrar(N.ToString + é estranho' );

Esta é a saída que você deve ver:

1
11
11 é estranho

Você pode alterar o tipo de dados de UInt16 para Inteiro ou outros tipos ordinais para ver como a saída
muda.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

64 - 02: variáveis e tipos de dados

Operações fora do alcance


Uma variável como N acima possui apenas um intervalo limitado de valores válidos. Se o valor atribuído
a ele for negativo ou muito grande, isso resultará em um erro. Na verdade, existem três tipos
diferentes de erros que você pode encontrar em operações fora do intervalo.

O primeiro tipo de erro é um erro do compilador, que ocorre se você atribuir um valor constante (ou uma
expressão constante) fora do intervalo. Por exemplo, se você adicionar ao código acima:

N := 100 + Alto(N);

o compilador emitirá o erro:

[Erro dcc32] E1012 A expressão constante viola os limites do subintervalo

O segundo cenário ocorre quando o compilador não consegue antecipar a condição de erro, pois
depende do fluxo do programa. Suponha que escrevemos (no mesmo trecho de código):

Inc(N, Alto(N));
Mostrar(N.ToString);

O compilador não disparará um erro porque há uma chamada de função e o compilador não sabe seu
efeito antecipadamente (e o erro também dependeria do valor inicial de N). Neste caso existem duas
possibilidades. Por padrão, se você compilar e executar esta aplicação, você terminará com um
valor completamente ilógico na variável (neste caso a operação resultará na subtração de 1!). Este é o
pior cenário possível, pois você não recebe nenhum erro, mas seu programa não está correto.

O que você pode fazer (e é altamente recomendável fazer) é ativar uma opção do compilador chamada
“Verificação de overflow” ({$Q+} ou {$OVERFLOWCHECKS ON}), que irá proteger contra uma operação
de overflow semelhante e gerar um erro, neste caso específico “overflow de número inteiro”.

boleano
Os valores lógicos True e False são representados usando o tipo Boolean. Este também é o tipo de
condição em declarações condicionais, como veremos no próximo capítulo. O
O tipo booleano só pode ter um dos dois valores possíveis, True e False.

avisar Para compatibilidade com a automação COM e OLE da Microsoft, os tipos de dados ByteBool, WordBool e
no LongBool representam o valor True com -1, enquanto o valor False ainda é 0. Novamente, você geralmente
deve ignorar esses tipos e evitar toda manipulação booleana de baixo nível e mapeamento numérico, a
menos que seja absolutamente necessário.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

02: variáveis e tipos de dados - 65

Ao contrário da linguagem C e de algumas de suas linguagens derivadas, Boolean é um tipo enumerado


em Object Pascal. Não há conversão direta para o valor numérico que representa um
Variável booleana , e você não deve tentar abusar de conversões diretas de tipo tentando converter
um booleano em um valor numérico. É verdade, entretanto, que os auxiliares do tipo booleano
incluem as funções ToInteger e ToString. Abordarei os tipos enumerados posteriormente neste capítulo.

Observe que usar ToString retorna a string com o valor numérico da variável booleana. Como
alternativa você pode usar a função global BoolToStr , definindo o segundo parâmetro como True, para
indicar o uso de strings booleanas ('True' e 'False') para o
saída. (Veja a seção “Operações de Tipo de Char” abaixo para ver um exemplo.)

Personagens

Variáveis de caracteres são definidas usando o tipo Char. Ao contrário das versões mais antigas, a
linguagem hoje usa o tipo Char para representar caracteres Unicode de byte duplo (também
representados pelo tipo WideChar ).

note O compilador Delphi ainda oferece a distinção entre AnsiChar para caracteres ANSI de um byte e
WideChar para Unicode, com o tipo Char definido como alias deste último. A recomendação é focar
em WideChar e usar o tipo de dados Byte para elementos de byte único. Porém, é verdade que a
partir do Delphi 10.4 o tipo AnsiChar foi disponibilizado em todos os compiladores e plataformas
para melhor compatibilidade com o código existente.

Para uma introdução aos caracteres em Unicode, incluindo a definição de um ponto de código e de
pares substitutos (entre outros tópicos avançados), você pode ler o Capítulo 6. Nesta seção, focarei
apenas nos conceitos básicos do tipo Char.

Como mencionei anteriormente, embora abranjam valores literais, os caracteres constantes podem ser
representados com sua notação simbólica, como em 'k', ou com uma notação numérica, como em #78.
Este último também pode ser expresso usando a função do sistema Chr , como em Chr(78). A
conversão oposta pode ser feita com a função Ord . Geralmente é melhor usar a notação simbólica ao
indicar letras, dígitos ou símbolos.

Ao se referir a caracteres especiais, como os caracteres de controle abaixo do número 32, você
geralmente usará a notação numérica. A lista a seguir inclui alguns dos caracteres especiais
mais comumente usados:

#8 retroceder
#9 aba
#10 nova linha
#13 retorno de carro
#27 escapar

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

66 - 02: variáveis e tipos de dados

Operações do tipo Char


Assim como outros tipos ordinais, o tipo Char possui diversas operações predefinidas que você pode aplicar
a variáveis desse tipo usando a notação de ponto. Novamente, essas operações são definidas com
um auxiliar de registro intrínseco.

No entanto, o cenário de uso é bem diferente. Primeiro, para usar esse recurso, você precisa habilitá -lo
consultando a unidade de caractere em uma instrução de uso . Em segundo lugar, em vez de algumas
funções de conversão, o auxiliar para o tipo Char inclui algumas dúzias de operações específicas de código
Uni, incluindo testes como IsLetter, IsNumber e IsPunctuation, e conversões como ToUpper e ToLower. Aqui
está um exemplo retirado do exemplo CharsTest :

usa
Personagem;
...
era
Ch: Caráter;
começar
Ch := ; 'a'
Show(BoolToStr(Ch.IsLetter, True));
Mostrar(Ch.ToUpper);

A saída deste código é:

Verdadeiro

note A operação ToUpper do auxiliar do tipo Char é totalmente habilitada para Unicode. Isso significa que se você passar uma letra
acentuada como ù o resultado será Ù. Algumas das funções RTL tradicionais não são tão inteligentes e funcionam
apenas para caracteres ASCII simples. Eles não foram modificados até agora porque as operações Unicode
correspondentes são significativamente mais lentas.

Char como um tipo ordinal


O tipo de dados Char é bastante grande, mas ainda é um tipo ordinal, então você pode usar as funções Inc
e Dec nele (para chegar ao caractere seguinte ou anterior ou avançar por um determinado número de
elementos, como vimos em consulte a seção “Rotinas de tipos ordinais padrão”) e escreva loops for
com um contador Char (mais sobre loops for no próximo capítulo).

Aqui está um fragmento simples usado para exibir alguns caracteres, obtidos aumentando o valor a partir de
um ponto inicial:

era
Ch: Caráter;
Str1: sequência;
começar
Ch:= 'a' ;

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

02: variáveis e tipos de dados - 67

Mostrar(Ch);
Inc(Cap, 100);
Mostrar(Ch);

Str1 := '';
para Ch := #32 a #1024 faça
Str1 := Str1 + Ch;
Mostrar(Str1)

O loop for do exemplo CharsTest adiciona muito texto à string, tornando a saída bastante longa. Começa
com as seguintes linhas de texto:

a
Oh

!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abc
defghijklmnopqrstuvwxyz{|}~
// A mais algumas linhas omitidas...

Convertendo com Chr


Vimos que existe uma função Ord que retorna o valor numérico (mais especificamente um ponto de código
Unicode) de um caractere. Há também uma função oposta que você pode usar para obter o caractere
correspondente a um ponto de código, que é a função especial Chr .

Caracteres de 32 bits
Embora o tipo Char padrão agora esteja mapeado para WideChar, vale a pena notar que o Delphi
também define um tipo de caractere de 4 bytes, UCS4Char, para o Universal Character Set 4-
representação de bytes. É definido na unidade do sistema como:

tipo
UCS4Char = digite LongWord;

Esta definição de tipo e a correspondente para UCS4String (definida como um array de UCS4Char) são
pouco utilizadas, mas fazem parte do tempo de execução da linguagem e são utilizadas em alguns dos
as funções da unidade de personagem .

Tipos de ponto flutuante


Embora números inteiros de vários tipos possam ser representados com um conjunto ordinal de valores,
os números de ponto flutuante não são ordinais (eles têm o conceito de ordem, mas não o conceito de
sequência de elementos) e são representados por algum valor aproximado, com algum erro em sua
representação.

Os números de ponto flutuante vêm em vários formatos, dependendo do número de bytes usados para
representá-los e da qualidade da aproximação. Aqui está uma lista de tipos de dados de ponto flutuante
em Object Pascal:

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

68 - 02: variáveis e tipos de dados

Solteiro O menor tamanho de armazenamento é dado por números únicos , que são implementados
com um valor de 4 bytes. O nome indica um valor de ponto flutuante de precisão única
e o mesmo tipo é indicado com o nome float
em outras línguas.
Dobro Estes são números de ponto flutuante implementados com 8 bytes. O nome indica um valor
de ponto flutuante de precisão dupla e é compartilhado por vários idiomas. A precisão dupla
é o tipo de dados de ponto flutuante mais comumente usado e também é um alias de um
tipo Pascal mais antigo chamado Real.
Estendido São números implementados com 10 bytes no compilador Delphi Win32 original, mas
esse tipo não está disponível em todas as plataformas (em algumas, como Win64, ele
volta para Double, enquanto no macOS é de 16 bytes). Outras linguagens de
programação chamam esse tipo de dados de long double.

Todos esses são tipos de dados de ponto flutuante com precisão diferente, que correspondem ao
Representações de ponto flutuante padrão IEEE e são diretamente suportadas pela CPU
(ou, para ser mais preciso, pela FPU, a unidade de ponto flutuante), para velocidade máxima.

Existem também dois tipos de dados numéricos não ordinais peculiares que você pode usar para representar
números com uma representação precisa, não aproximada:

Comp. Descreve números inteiros muito grandes usando 8 bytes ou 64 bits (que podem conter
números com 18 dígitos decimais). A ideia é representar grandes números sem perda
de precisão, ao contrário dos valores de ponto flutuante correspondentes.

Moeda Indica um valor decimal de ponto fixo com quatro dígitos decimais e a mesma representação
de 64 bits que o tipo Comp . Como o nome indica, o tipo de dados Moeda foi adicionado
para lidar com valores monetários muito precisos, com quatro casas decimais (novamente
sem perda de precisão nos cálculos).

note O Delphi 11 introduz um novo auxiliar de registro para o tipo de dados de moeda. Semelhante aos auxiliares disponíveis para
tipos de ponto flutuante, descritos posteriormente, oferece operações de arredondamento, análise e conversão de strings.

Todos esses tipos de dados não ordinais não possuem os conceitos de Alto, Baixo ou Ord
função. Os tipos reais representam (em teoria) um conjunto infinito de números; os tipos ordinais representam
um conjunto fixo de valores.

Por que os valores de ponto flutuante são diferentes


Deixe-me explicar melhor. Quando você tem o número inteiro 23 você pode determinar qual é o próximo valor.
Os inteiros são finitos (têm um intervalo determinado e uma ordem).
Os números de ponto flutuante são infinitos mesmo dentro de um intervalo pequeno e não possuem sequência.
Afinal, quantos valores existem entre 23,0 e 24,0? E qual número segue 23,46? É 23,47, 23,461 ou 23,4601?
Isso é realmente impossível de saber!

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

02: variáveis e tipos de dados - 69

Por esta razão, embora faça sentido perguntar pela posição ordinal do caracter 'w' no intervalo do tipo de
dados Char , não faz sentido fazer a mesma pergunta sobre 7143.1562 no intervalo de um ponto flutuante tipo
de dados. Embora você possa realmente saber se um número real tem um valor maior que outro, não faz
sentido perguntar quantos números reais existem antes de um determinado número (este é o significado da Ord

função).

Outro conceito chave por trás dos valores de ponto flutuante é que sua implementação não pode representar
todos os números com precisão. Muitas vezes acontece que o resultado de um cálculo que você faria
espera ser um número específico (às vezes um número inteiro), poderia de fato ser um valor aproximado
dele. Considere este código, retirado do exemplo FloatTest :

era
S1: Único;
começar
S1:= 0,5*0,2;
Mostrar(S1.ToString);

Você esperaria que o resultado fosse 0,1, mas na verdade obteria algo como 0,100000001490116.
Isso está próximo do valor esperado, mas não exatamente isso. Escusado será dizer que se você arredondar o
resultado, obterá o valor esperado. Se você usar um Double
variável, a saída será 0,1, como mostra o exemplo FloatTest .

note Agora, não tenho tempo para uma discussão aprofundada sobre matemática de ponto flutuante em computadores,
então estou resumindo esta discussão, mas se você estiver interessado neste tópico da perspectiva da
linguagem Object Pascal, posso recomendar-lhe um excelente artigo do falecido Rudy Velthuis em http://
rvelthuis.de/articles/articles-floats.html.

Auxiliares flutuantes e a unidade matemática


Como você pode ver no trecho de código acima, os tipos de dados de ponto flutuante também possuem
auxiliares de registro que permitem aplicar operações diretamente às variáveis, como se fossem
objetos. Na verdade, a lista de operações para números de ponto flutuante é bastante longa.

Esta é a lista de operações em instâncias para o tipo Single (com algumas operações óbvias por seus nomes,
outras um pouco mais enigmáticas, você pode procurar na documentação):

Expoente Fração Mantissa


Sinal Exp. Fratura
Tipo Especial Acumular Para sequenciar
ÉNan ÉInfinito ÉNegativoInfinity
ÉPositivoInfinito Bytes Palavras

A biblioteca de tempo de execução também possui uma unidade Math que define rotinas matemáticas avançadas,
cobrindo funções trigonométricas (como a função ArcCosh ), finanças (como a função InterestPayment ) e
estatísticas (como o procedimento MeanAndStdDev ).
Existem várias dessas rotinas, algumas das quais me parecem bastante estranhas, como

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

70 - 02: variáveis e tipos de dados

como a função MomentSkewKurtosis (deixarei você descobrir o que é, se ainda não souber).

A unidade System.Math é muito rica em recursos, mas você também encontrará muitas coleções
externas de funções matemáticas para Object Pascal.

Tipos de dados simples definidos pelo usuário


Juntamente com a noção de tipo, uma das grandes ideias introduzidas por Niklaus Wirth no
A linguagem Pascal original era a capacidade de definir novos tipos de dados em um programa (algo
que consideramos natural hoje, mas não era óbvio na época). Você pode definir seus próprios tipos
de dados por meio de definições de tipo, como tipos de subintervalo, tipos de matriz, tipos de
registro, tipos enumerados, tipos de ponteiro e tipos de conjunto. O tipo de dados definido pelo usuário
mais importante é a classe, que faz parte dos recursos orientados a objetos da linguagem,
abordados na segunda parte deste livro.

Se você acha que construtores de tipos são comuns em muitas linguagens de programação, você está
certo, mas Pascal foi a primeira linguagem a introduzir a ideia de forma formal e muito precisa.
Object Pascal ainda possui alguns recursos bastante exclusivos, como a definição de subintervalo,
enumerações e conjuntos, abordados nas seções a seguir. Construtores de tipos de dados mais
complexos (como arrays e registros) são abordados no Capítulo 5.

Tipos nomeados vs. tipos sem nome


Os tipos de dados definidos pelo usuário podem receber um nome para uso posterior ou
aplicados diretamente a uma variável. A convenção em Object Pascal é usar um prefixo da letra T
para denotar qualquer tipo de dados, incluindo classes, mas não limitado a elas. Eu sugiro fortemente
que você siga esta regra, mesmo que possa não parecer natural no início, se você tiver experiência
em Java ou C#.

Ao dar um nome a um tipo, você deve fazê-lo em uma seção “tipo” do seu programa (você pode
adicionar quantos tipos quiser em cada unidade). Abaixo está um exemplo simples de algumas
declarações de tipo:

tipo
// Definição de subfaixa
TUmaiúscula = 'A'..'Z';

// Definição de tipo enumerado


TMyColor = (Vermelho, Amarelo, Verde, Ciano, Azul, Violeta);

// Definir definição
TColorPalette = conjunto de TMyColor;

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

02: variáveis e tipos de dados - 71

Com esses tipos, agora você pode definir algumas variáveis:

era
UpSet: TUpperLetters;
Cor1: TMyColor;

No cenário acima estou usando um tipo nomeado . Como alternativa, a definição do tipo pode
ser usado diretamente para definir uma variável sem um nome de tipo explícito, como no código a seguir:

era
Paleta: conjunto de TMyColor;

Em geral, você deve evitar usar tipos sem nome como no código acima, pois não pode passá-los como
parâmetros para rotinas ou declarar outras variáveis do mesmo tipo.
Dado que a linguagem recorre, em última análise, à equivalência de nomes de tipos em vez da
equivalência de tipos estruturais, ter uma definição única para cada tipo é realmente importante.
Lembre-se também de que as definições de tipo na parte de interface de uma unidade podem ser
vistas no código de qualquer outra unidade por meio de uma instrução usa .

O que significam as definições de tipo acima? Fornecerei algumas descrições para aqueles que não estão
familiarizados com as construções tradicionais do tipo Pascal. Também tentarei sublinhar as diferenças
das mesmas construções em outras linguagens de programação, portanto, de qualquer forma, você
pode estar interessado em ler as seções a seguir.

Aliases de tipo
Como vimos, a linguagem Delphi usa o nome do tipo (não sua definição real) ao verificar a compatibilidade
do tipo. Dois tipos definidos de forma idêntica com nomes diferentes são
dois tipos diferentes.

Isso também é parcialmente verdadeiro quando você define um alias de tipo, que é um novo nome de
tipo baseado em um tipo existente. O que é confuso é que existem duas variações da mesma sintaxe
que produzem efeitos ligeiramente diferentes. Veja este código no exemplo TypeAlias:

tipo
TNewInt = Inteiro;
TNewInt2 = tipo Inteiro;

Ambos os novos tipos permanecem compatíveis com a atribuição do tipo Integer (por meio de
conversão automática); entretanto, o tipo TNewInt2 não será uma correspondência exata, por exemplo,
ele não pode ser passado como parâmetro de referência para uma função que espera o tipo de alias:

procedimento Teste (var N: Inteiro);


começar
fim;
procedimento TForm40.Button1Click(Remetente: TObject);
era

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

72 - 02: variáveis e tipos de dados

Eu: Inteiro;
NI: TNewInt;
IE2: TNewInt2;
começar
Eu:= 10;
NA := eu; // Funciona
EN2 := eu; // Funciona

Teste(eu);
Teste(NI);
Teste(NI2); // Erro

A última linha produz o erro:

E2033 Os tipos de parâmetros var reais e formais devem ser idênticos

Algo semelhante acontece com os auxiliares de tipo, pois o auxiliar do tipo Integer pode ser usado para
TNewInt , mas não para TNewInt2. Isso é abordado especificamente em uma seção posterior, ao
discutir auxiliares de registro.

Tipos de subfaixa
Um tipo de subintervalo define um intervalo de valores dentro do intervalo de outro tipo (daí o nome
subintervalo). Por exemplo, você pode definir um subintervalo do tipo Inteiro , de 1 a
10 ou de 100 a 1000, ou você pode definir um subintervalo do tipo Char apenas com caracteres
maiúsculos em inglês, como em:

tipo
TTen = 1..10;
PARAmais de Cem = 100..1000;
TUpperCase = 'A' .. 'COM' ;

Na definição de um subintervalo, não é necessário especificar o nome do tipo base. Você


só precisa fornecer duas constantes desse tipo. O tipo original deve ser um tipo ordinal e o tipo
resultante será outro tipo ordinal. Depois de definir uma variável como um subintervalo, você poderá
atribuir a ela qualquer valor dentro desse intervalo. Este código é válido:

era
UppLetter: TUpperCase;
começar
UppLetter := 'F' ;

Mas isto não é:

era
UppLetter: TUpperCase;
começar
UppLetter := 'e' ; // Erro em tempo de compilação

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

02: variáveis e tipos de dados - 73

Escrever o código acima resulta em um erro em tempo de compilação, “A expressão constante viola os limites
do subintervalo”. Se você escrever o código a seguir, o compilador o aceitará:

era
UppLetter: TUpperCase;
Carta: Caráter;
começar
Carta := 'e' ;
UppLetter := Carta;

Em tempo de execução, se você tiver habilitado a opção do compilador Verificação de intervalo (na página
Compilador da caixa de diálogo Opções do projeto), receberá uma mensagem de erro de verificação de
intervalo , conforme esperado. Isso é semelhante aos erros de estouro do tipo inteiro que descrevi anteriormente.

Sugiro que você ative esta opção do compilador enquanto estiver desenvolvendo um programa, assim ele
ficará mais robusto e fácil de depurar, pois em caso de erros você receberá uma mensagem explícita e não
um comportamento indeterminado. Eventualmente você pode desabilitar esta opção para a compilação final
do programa, para que ele rode um pouco mais rápido. No entanto, o aumento na velocidade é quase
insignificante, por isso sugiro deixar todas essas verificações de tempo de execução ativadas, mesmo em
um programa de remessa.

Tipos Enumerados
Os tipos enumerados (geralmente chamados de “enums”) constituem outro tipo ordinal definido pelo
usuário. Em vez de indicar um intervalo de um tipo existente, em uma enumeração você lista todos os
valores possíveis para o tipo. Em outras palavras, uma enumeração é uma lista de valores (constantes).
aqui estão alguns exemplos:

tipo
TColors = (Vermelho, Amarelo, Verde, Ciano, Azul, Violeta);
TSuit = (Paus, Ouros, Copas, Espadas);

Cada valor da lista possui uma ordinalidade associada, começando com zero. Ao aplicar a função Ord a um
valor de um tipo enumerado, você obtém esse valor “baseado em zero”. Por exemplo, Ord(Diamante) retorna
1.

Os tipos enumerados podem ter diferentes representações internas. Por padrão, o Delphi utiliza representação
de 8 bits, a menos que haja mais de 256 valores diferentes, caso em que utiliza representação de 16 bits. Há
também representação de 32 bits, que às vezes é útil para compatibilidade com bibliotecas C ou C++.

note Você pode alterar a representação padrão dos tipos enumerados, solicitando uma representação maior,
independentemente do número de elementos na enumeração, usando a diretiva do compilador $Z . Esta é uma
configuração bastante rara.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

74 - 02: variáveis e tipos de dados

Enumeradores com escopo definido

Os valores constantes específicos de um tipo enumerado podem ser considerados para todos os efeitos
como constantes globais, e tem havido casos de conflitos de nomes entre diferentes valores enumerados.
É por isso que a linguagem suporta enumerações com escopo definido, um recurso que você pode ativar
usando uma diretiva específica do compilador, $SCOPEDENUMS, e que exige que você se refira ao valor
enumerado usando o nome do tipo como prefixo:

// Valor enumerado clássico


S1 := Clube;

// Valor enumerado com escopo


S1 := TSuit.Club;

Quando esse recurso foi introduzido, o estilo de codificação padrão permaneceu o comportamento
tradicional, para evitar a quebra do código existente. Os enumeradores com escopo definido, na verdade,
alteram o comportamento das enumerações, tornando obrigatório referir-se a elas com um prefixo de tipo
totalmente qualificado.

Ter um nome absoluto para se referir a valores enumerados elimina o risco de conflito, pode permitir evitar
o uso do prefixo inicial dos valores enumerados como forma de diferenciar com outras enumerações e torna
o código mais legível, mesmo que seja muito mais longo escrever.

Por exemplo, a unidade System.IOUtils define este tipo:

{$SCOPEDENUMS SOBRE}
tipo
TSearchOption = (soTopDirectoryOnly, soAllDirectories);

Isso significa que você não pode se referir ao segundo valor como soAllDirectories, mas deve se referir ao
valor enumerado com seu nome completo:

TSearchOption.soAllDirectories

A biblioteca da plataforma FireMonkey também usa vários enumeradores com escopo definido, exigindo o
tipo como um prefixo para os valores reais, enquanto a biblioteca VCL mais antiga é geralmente baseada
no modelo mais tradicional. O RTL é uma mistura dos dois.

note Os valores enumerados nas bibliotecas Object Pascal geralmente usam duas ou três iniciais do tipo no início do
valor, usando letras minúsculas por convenção, como “so” para Opções de pesquisa no exemplo acima. Ao
usar o tipo como prefixo, isso pode parecer um pouco redundante, mas dada a semelhança da abordagem,
não vejo isso desaparecerá tão cedo.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

02: variáveis e tipos de dados - 75

Definir tipos
Os tipos de conjunto indicam um grupo de valores onde a lista de valores disponíveis é indicada pelo tipo ordinal
no qual o conjunto se baseia. Esses tipos ordinais são geralmente limitados e muitas vezes
representado por uma enumeração ou um subintervalo.

Se tomarmos o subintervalo de 1 a 3, que na notação Pascal é escrito como 1..3, os valores possíveis do
conjunto baseado nele incluem apenas 1, apenas 2, apenas 3, ambos 1 e 2, ambos 1 e 3, 2 e 3, todos os três
valores ou nenhum deles.

Uma variável geralmente contém um dos valores possíveis do intervalo de seu tipo. Uma variável do tipo
conjunto, por outro lado, pode conter nenhum, um, dois, três ou qualquer número de valores possíveis do
intervalo. Pode até incluir todos os valores.

Aqui está um exemplo de conjunto:

tipo
TSuit = (Paus, Ouros, Copas, Espadas);
TSuits = conjunto de TSuits;

Agora posso definir uma variável deste tipo e atribuir a ela alguns valores do tipo original.
Para indicar alguns valores em um conjunto, você escreve uma lista separada por vírgulas, entre colchetes.
O código a seguir mostra a atribuição a uma variável de vários valores, um único valor e um valor vazio:

era
Cartas1, Cartas2, Cartas3: Ternos;

começar
Cartas1:= [Club, Diamond, Heart];
Cartas2 := [Diamante];
Cartões3 := [];

Em Object Pascal, um conjunto geralmente é usado para indicar vários sinalizadores não exclusivos. Por
exemplo, um valor baseado em um tipo definido é o estilo da fonte. Os valores possíveis indicam uma fonte
em negrito, itálico, sublinhado e tachado. É claro que a mesma fonte pode estar em itálico e
negrito, não tem atributos ou tem todos eles. Por esta razão é declarado como um conjunto.

Você pode atribuir valores a este conjunto no código de um programa da seguinte forma:

Estilo de fonte := []; // Não estilo


Font.Style := [fsBold]; Font.Style := // Apenas estilo ousado
[fsBold, fsItalic]; // Dois estilos ativos

Definir operadores
Vimos que os conjuntos são um tipo de dados definido pelo usuário muito específico do Pascal. É por isso que as
operadoras de conjunto valem uma cobertura específica. Eles incluem união (+), diferença (-), interseção
(*), teste de pertinência (in), além de alguns operadores relacionais.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

76 - 02: variáveis e tipos de dados

Para adicionar um elemento a um conjunto, você pode fazer a união do conjunto com outro que tenha
apenas os elementos que você precisa. Aqui está um exemplo relacionado a estilos de fonte:

//Adiciona negrito
Estilo := Estilo + [fsBold];

// Adicione negrito e itálico, mas remova o sublinhado se presente


Estilo:= Estilo + [fsBold, fsItalic] - [fsUnderline];

Como alternativa, você pode usar os procedimentos padrão de inclusão e exclusão , que são
muito mais eficiente (mas não pode ser usado com propriedades de componentes do tipo set):

Incluir(Estilo, fsBold);
Excluir(Estilo, fsItálico);

Expressões e Operadores
Vimos que você pode atribuir a uma variável um valor literal compatível com o tipo, um valor constante ou o valor
de outra variável. Em muitos casos, o que você atribui a uma variável é o resultado de uma expressão,
envolvendo um ou mais valores e um ou mais operadores. As expressões são outro elemento central da linguagem.

Usando Operadores
Não existe uma regra geral para a construção de expressões, pois elas dependem principalmente dos operadores
utilizados, e o Object Pascal possui vários operadores. Existem operadores lógicos, aritméticos, booleanos,
relacionais e de conjunto, além de alguns outros especiais:

// Exemplos de expressões
20 * 5 // Multiplicação
30 + na // Adição
< b -4 // Menos que comparação
c // Valor negativo
= 10 para Teste
// igualdade (como == em C sintaxe)

As expressões são comuns à maioria das linguagens de programação e a maioria dos operadores são iguais. Uma
expressão é qualquer combinação válida de constantes, variáveis, valores literais, operadores e resultados de
funções. As expressões podem ser usadas para determinar o valor a ser atribuído a uma variável, para calcular
o parâmetro de uma função ou procedimento ou para testar uma condição. Cada vez que você executa uma operação
no valor de um identificador, em vez de usar um identificador sozinho, você está usando uma expressão.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

02: variáveis e tipos de dados - 77

note O resultado de uma expressão geralmente é armazenado em uma variável temporária do tipo de dados apropriado.
gerado automaticamente pelo compilador em seu nome. Talvez você queira usar uma variável explícita quando
precisar calcular a mesma expressão mais de uma vez no mesmo fragmento de código. Observe que expressões
complexas podem exigir diversas variáveis temporárias para armazenar resultados intermediários, novamente algo
que o compilador cuida para você e que geralmente você pode ignorar.

Mostrando o resultado de uma expressão


Se você quiser fazer algumas experiências com expressões, nada melhor do que escrever um programa simples.
Como na maioria das demonstrações iniciais deste livro, crie um programa simples baseado em um formulário e use a
função Show personalizada para exibir algo ao usuário. Caso a informação que você deseja mostrar não seja uma
mensagem de string, mas um número ou um valor lógico booleano, você precisa convertê-la, por exemplo, chamando
o IntToStr
ou função BoolToStr .

note Em Object Pascal, os parâmetros passados para uma função ou procedimentos são colocados entre parênteses. Alguns
outras linguagens (notadamente Rebol e, até certo ponto, Ruby) permitem passar parâmetros simplesmente escrevendo-
os após o nome da função ou do procedimento. Voltando ao Object Pascal, as chamadas de funções aninhadas usam
parênteses aninhados, como no código abaixo.

Aqui está um trecho de código de exemplo do exemplo ExpressionsTest (no qual estou usando a sintaxe
clássica IntToStr para maior clareza, já que o parâmetro é uma expressão:

Mostrar(IntToStr(20 * 5));
Mostrar(IntToStr(30 + 222));
Show(BoolToStr(3 < 30, Verdadeiro));
Mostrar(BoolToStr(12 = 10, Verdadeiro));

A saída é que este trecho de código é bastante trivial:


100
252
Verdadeiro

Falso

Forneci esta demonstração como um esqueleto para você experimentar diferentes tipos de expressões
e operadores e ver o resultado correspondente.

note Expressões escritas em Object Pascal são analisadas pelo compilador e geram código assembly. Se quiser alterar uma
dessas expressões, você precisa alterar o código-fonte e recompilar a aplicação. As bibliotecas do sistema, entretanto,
têm suporte para expressões dinâmicas calculadas em tempo de execução, um recurso vinculado à reflexão e
abordado no Capítulo 16.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

78 - 02: variáveis e tipos de dados

Operadores e precedência
As expressões são feitas de operadores aplicados a valores. Como mencionei, a maioria dos operadores
compartilhados entre as diversas linguagens de programação são bastante intuitivos, como os operadores
básicos de correspondência e comparação. Nesta seção destacarei apenas elementos específicos dos
operadores Object Pascal.

Você pode ver uma lista dos operadores da linguagem abaixo, agrupados por precedência e comparados
aos operadores em C#, Java e Objective-C (e a maioria das linguagens baseadas na sintaxe da linguagem
C, de qualquer maneira).

Operadores relacionais e de comparação (precedência mais baixa)

= Teste se é igual (em C isso é ==)


<> Teste se não é igual (em C é ! =)
Teste se é menor que
<
> Teste se é maior que
<= Testar se é menor ou igual ou um subconjunto de um conjunto
>= Testar se é maior ou igual ou um superconjunto de um conjunto
Teste se o item é membro do conjunto
em
é Testar se um objeto é compatível com um determinado tipo (abordado no Capítulo
8) ou implementa uma determinada interface (abordada no Capítulo 11)

Operadores Aditivos

+ Adição aritmética, união de conjuntos, concatenação de strings, adição de deslocamento


de ponteiro

- Subtração aritmética, diferença definida, subtração de deslocamento do ponteiro


ou Booleano ou bit a bit ou (em C é || ou |)

livre
Booleano ou exclusivo bit a bit ou (em C exclusivo bit a bit ou é ^)

Operadores multiplicativos e bit a bit

* Multiplicação aritmética ou interseção de conjuntos


Divisão de ponto flutuante
/
div Divisão inteira (em C também usa /)
contra
Módulo (o resto da divisão inteira) (em C é %)
como
Permite uma conversão com verificação de tipo em tempo de execução (abordada no Capítulo 8)

e Booleano ou bit a bit e (em C é && ou &)


Deslocamento bit a bit para a esquerda (em C é <<)
shl
Shr Deslocamento bit a bit para a direita (em C é >>)

Operadores Unários (Maior Precedência)

@ Endereço de memória de uma variável ou função (retorna um ponteiro, em C é &)

não Booleano ou bit a bit não (em C isso é !)

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

02: variáveis e tipos de dados - 79

Diferentemente de muitas outras linguagens de programação, os operadores lógicos (incluindo

e, ou, e não) têm uma precedência mais alta que os operadores de comparação (incluindo
menor que e maior que). Então, se você escrever:
a<bec<d

o compilador executará a operação and primeiro, geralmente resultando em um erro do compilador de


compatibilidade de tipo na expressão. Se quiser testar ambas as comparações, você deve colocar cada uma
das expressões menor que entre parênteses:

(a <b) e (c <d)

Para operações matemáticas, em vez disso, aplicam-se as regras comuns, com a multiplicação e a divisão
tendo precedência sobre a adição e a subtração. As duas primeiras expressões abaixo são equivalentes, enquanto
a terceira é diferente:

10 + 2 * 5 10 + // O resultado 20
(2 * 5) (10 + 2) * 5 é // O resultado 20
é // O resultado é 60

dica Embora em alguns casos os parênteses não sejam necessários, dado que você pode contar com as regras de precedência
dos operadores de linguagem, é altamente recomendável adicioná-los de qualquer maneira, pois essas regras
variam dependendo da linguagem de programação e é sempre melhor ser mais claro para quem lê ou modificando o
código no futuro.

Alguns dos operadores têm significados diferentes quando usados com diferentes tipos de dados.
Por exemplo, o operador + pode ser usado para adicionar dois números, concatenar duas strings, fazer a união
de dois conjuntos e até adicionar um deslocamento a um ponteiro (se o tipo de ponteiro específico tiver a
matemática de ponteiro habilitada):

10 + 2 + 11
10,3 + 3,4
' '
'Olá' + + 'mundo'

No entanto, você não pode adicionar dois caracteres, como é possível em C.

Um operador incomum é div. No Object Pascal, você pode dividir quaisquer dois números (reais ou inteiros) com
o operador / e invariavelmente obterá um resultado de número real. Se você precisar
para dividir dois números inteiros e desejar um resultado inteiro, use o operador div . Aqui estão dois exemplos
de atribuições (este código ficará mais claro à medida que cobrirmos os tipos de dados no
Próximo Capítulo):

Valor Real:= 123/12;


ValorInteiro:= 123 div 12;

Para garantir que a divisão inteira não tenha resto, você pode usar o operador mod e verificar se o resultado é
zero, como na seguinte expressão booleana:

(x módulo 12) = 0

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

80 - 02: variáveis e tipos de dados

Data e hora
Embora não houvesse um tipo nativo para data e hora nas primeiras versões da linguagem Pascal, o Object
Pascal possui um tipo nativo para data e hora. Ele usa uma representação de ponto flutuante para
lidar com informações de data e hora. Para ser mais específico, o Sistema
unit define um tipo de dados TDateTime específico para esse propósito.

Este é um tipo de ponto flutuante, porque deve ser amplo o suficiente para armazenar anos, meses, dias,
horas, minutos e segundos, com resolução de milissegundos em uma única variável:

· As datas são armazenadas como o número de dias desde 30/12/1899 (com valores negativos indicados
datas anteriores a 1899) na parte inteira do valor TDateTime

· Os tempos são armazenados como frações de um dia na parte decimal do valor

história Caso você esteja se perguntando de onde vem essa data estranha, há uma longa história por trás
está vinculado ao Excel e às representações de datas em aplicativos do Windows. A ideia era considerar o dia
número 1 como o primeiro de janeiro de 1900, de modo que a véspera de Ano Novo de 1899 fosse o dia número 0.
No entanto, o desenvolvedor original dessa representação de data esqueceu devidamente que o ano de 1900
não foi um ano bissexto. ano, e assim os cálculos foram posteriormente ajustados em 1 dia, transformando o
primeiro de janeiro de 1900 no dia número 2.

Conforme mencionado, TDateTime não é um tipo predefinido que o compilador entende, mas é definido na
unidade System como:

tipo
TDateTime = tipo Duplo;

note A unidade System pode ser considerada de alguma forma quase como parte da linguagem principal, já que é sempre incluída
automaticamente em cada compilação, sem uma instrução de uso (na verdade, adicionando a unidade System
unit para uma seção de uso causará um erro de compilação). Tecnicamente, porém, esta unidade é uma parte essencial
da biblioteca de tempo de execução (RTL) e será abordada no Capítulo 17.

Existem também dois tipos relacionados para lidar com as partes de hora e data de um TDateTime
estrutura, definida como TDate e TTime. Esses tipos específicos são apelidos do TDate-Time completo, mas
são tratados por funções do sistema que cortam a parte não utilizada dos dados.

Usar tipos de dados de data e hora é bastante fácil, porque o Delphi inclui diversas funções que operam
nesse tipo. Existem várias funções principais na unidade Sys-tem.SysUtils e muitas funções
específicas na unidade System.DateUtils (que, apesar do nome, inclui também funções para manipulação de
tempo).

Aqui você pode encontrar uma pequena lista de funções de manipulação de data/hora comumente usadas:

Agora Retorna a data e hora atuais em um valor de data/hora.


Data Retorna apenas a data atual.
Tempo Retorna apenas a hora atual.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

02: variáveis e tipos de dados - 81

DateTimeToStr Converte um valor de data e hora em uma string, usando o formato padrão-
ting; para ter mais controle sobre a conversão, use a função
FormatDateTime .
DataToStr Converte a parte da data de um valor de data/hora em uma string.
TimeToStr Converte a parte da hora de um valor de data/hora em uma string.
FormatDateTime Formata uma data e hora usando o formato especificado; você pode especificar quais valores deseja
ver e qual formato usar, fornecendo uma sequência de formato complexa.

StrToDateTime Converte uma string com informações de data e hora em um valor de data/hora, gerando uma
exceção em caso de erro no formato da string. Sua função complementar, StrToDateTimeDef,
retorna o valor padrão em caso de erro, em vez de gerar uma exceção.

Dia da semana Retorna o número correspondente ao dia da semana do valor de data/hora


passado como parâmetro (usando configuração de localidade).
Data de decodificação Recupera os valores de ano, mês e dia de um valor de data.
Tempo de decodificação Recupera as horas, minutos, segundos e milissegundos de um valor de data.

DataCodificação Transforma valores de ano, mês e dia em um valor de data/hora.


Tempo de codificação Transforma valores de hora, minuto, segundo e milissegundo em data/
valor do tempo.

Para mostrar como usar esse tipo de dados e algumas de suas rotinas relacionadas, criei um exemplo
simples, chamado TimeNow. Quando o programa é iniciado, ele calcula e exibe automaticamente a hora
e a data atuais.

era
HoraInicial: TDateTime;
começar
Hora de Início := Agora;
'
Mostrar( 'Tempo é + TimeToStr(StartTime));
'
Mostrar( 'A data é + DateToStr(StartTime));

A primeira instrução é uma chamada à função Now , que retorna a data e hora atuais. Este valor é
armazenado na variável StartTime .

note Quando uma função Object Pascal é chamada sem parâmetros, não há necessidade de digitar o valor vazio
parênteses, ao contrário das linguagens de estilo C.

As próximas duas instruções exibem a parte da hora do valor TDateTime , convertida em uma string,
e a parte da data do mesmo valor. Esta é a saída do programa (que dependerá da configuração local do
seu sistema):

O horário é 18:33:14
A data é 07/10/2020

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

82 - 02: variáveis e tipos de dados

Para compilar este programa você precisa consultar as funções que fazem parte da unidade Sys-
tem.SysUtils (um nome abreviado para “utilitários do sistema”). Além de chamar TimeToStr e
DateToStr você pode usar a função FormatDateTime mais poderosa .

Observe que os valores de hora e data são transformados em strings dependendo das configurações
internacionais do sistema. As informações de formatação de data e hora são lidas no
sistema, dependendo do sistema operacional e da localidade, preenchendo uma estrutura de dados
TFormatSet-tings . Se precisar de formatação personalizada, você pode criar uma estrutura
personalizada desse tipo e passá-la como parâmetro para a maioria das funções de formatação de data e hora.

note O projeto TimeNow também possui um segundo botão que você pode usar para ativar um cronômetro. Este é um componente
que executa um manipulador de eventos automaticamente ao longo do tempo (você especifica o intervalo). Na
demonstração, se você ativar o cronômetro, verá a hora atual adicionada à lista a cada segundo. Uma interface de
usuário mais útil seria atualizar um rótulo com a hora atual a cada segundo, basicamente construindo um relógio.

Auxiliar de data e hora


Para facilitar ainda mais a operação no tipo de dados TDateTime , o Delphi 11 introduziu um auxiliar de
tipo específico, semelhante ao que já vimos para tipos de dados nativos anteriormente neste capítulo. O
auxiliar de registro para TDateTime é chamado TDateTimeHelper e é definido na unidade
System.DateUtils . As operações disponíveis incluem obter a primeira data do mês ou ano, ou converter
para um formato de data Unix, verificar AM/PM, descobrir se está em um ano bissexto, etc. O auxiliar de
registro tem mais de 150 métodos, então ganhou não faz muito sentido deixá-los todos aqui.

O tipo auxiliar TDateTime também introduz uma nova operação NowUTC (a hora atual no fuso horário UTC)
não encontrada no RTL clássico. Este é um trecho de código de exemplo, mostrando também a
concatenação de duas chamadas auxiliares, Tomorrow e ToString:

usa
DataUtils;

procedimento TForm1.Button1Click(Remetente: TObject);


começar
var MinhaData: TDateTime := TDateTime.NowUTC;
MinhaData.Amanhã.ToString;
fim;

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

02: variáveis e tipos de dados - 83

Fundição de tipo e conversões de tipo


Como vimos, você não pode atribuir uma variável de um tipo de dados a outra de tipo diferente. A razão é
que, dependendo da representação real dos dados, você pode acabar com algo sem sentido.

Agora, isso não é verdade para todo e qualquer tipo de dados. Tipos numéricos, por exemplo, sempre podem
ser promovidos com segurança. “Promovido” aqui significa que você sempre pode atribuir com segurança
um valor a um tipo com uma representação maior. Então você pode atribuir uma palavra a um número inteiro e
um número inteiro para um valor Int64. A operação oposta, chamada “rebaixamento”, é permitida pelo
compilador, mas pode emitir um aviso, pois você pode acabar com dados parciais.
Outras conversões automáticas são unilaterais: por exemplo, você pode atribuir um número inteiro a um número
de ponto flutuante, mas a operação oposta é ilegal.

Existem cenários em que você deseja alterar o tipo de valor e a operação faz sentido. Quando você precisar
fazer isso, existem duas opções. Uma delas é realizar uma conversão direta de tipo, que copiará os dados
físicos e poderá resultar em uma conversão adequada ou não dependendo dos tipos e seus valores. Quando
você executa um typecast, você está dizendo ao compilador “Eu sei o que estou fazendo, deixe-me fazer isso”.
Se você usa typecasts, mas não tem certeza do que está fazendo, poderá ter problemas ao perder a rede de
segurança de verificação de tipo do compilador.

Typecasting usa uma notação funcional simples, com o nome do tipo de dados de destino usado como uma
função:

era
Eu: Inteiro;
C: Caráter;
B: Booleano;

começar
Eu := Inteiro( 'X' );
C := Caráter(I);
B := Booleano(I);

Você pode fazer typecast com segurança entre tipos de dados com o mesmo tamanho (que é o mesmo número
de bytes para representar os dados - ao contrário do trecho de código acima!). Geralmente é seguro fazer
typecast entre tipos ordinais, mas você também pode fazer typecast entre tipos de ponteiro (e
também objetos), desde que você saiba o que está fazendo.

A conversão direta de tipo é uma prática de programação perigosa, pois permite acessar um valor como
se representasse outra coisa. Como as representações internas dos tipos de dados geralmente não
correspondem (e podem até mudar dependendo da plataforma de destino), você corre o risco de criar
acidentalmente erros difíceis de rastrear. Por esse motivo, você geralmente deve evitar a conversão de
tipo.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

84 - 02: variáveis e tipos de dados

A outra opção para atribuir uma variável a um tipo diferente é usar uma função de conversão de tipo. Uma
lista de funções que permitem converter entre vários tipos básicos
estão resumidas abaixo (já usei algumas dessas funções nas demonstrações deste capítulo):

Chr Converte um número ordinal em um caractere.


Palavra
Converte um valor do tipo ordinal no número que indica sua ordem.
Redondo Converte um valor do tipo real em um valor do tipo Inteiro, arredondando seu valor
(veja também a nota a seguir).
Porta-malas
Converte um valor do tipo real em um valor do tipo Inteiro, truncando seu valor.

Interno
Retorna a parte inteira do argumento do valor de ponto flutuante.
FloatToDecimal Converte um valor de ponto flutuante em registro incluindo sua representação decimal
(expoente, dígitos, sinal).
FloatToStr Converte um valor de ponto flutuante em sua representação de string usando a
formatação padrão.
StrToFloat Converte uma string em um valor de ponto flutuante.

note A implementação da função Round é baseada na implementação nativa oferecida pela CPU.
Os processadores modernos geralmente adotam o chamado “Arredondamento do Banqueiro”, que arredonda os valores médios (como
5,5 ou 6,5) para cima e para baixo dependendo se seguem um número ímpar ou par. Há
outras funções de arredondamento, como RoundTo, que oferecem mais controle sobre a operação real.

Conforme mencionado anteriormente neste capítulo, algumas dessas funções de conversão também estão
disponíveis como operações diretas no tipo de dados (graças ao mecanismo auxiliar de tipo). Enquanto
existem conversões clássicas como IntToStr, você pode aplicar a operação ToString à maioria dos tipos
numéricos para convertê-los em uma representação de string. Existem muitas conversões que você pode
aplicar diretamente a variáveis usando auxiliares de tipo, e esse deve ser seu estilo de codificação
preferido em relação à conversão de tipo.

Algumas dessas rotinas funcionam nos tipos de dados que discutiremos nas seções a seguir. Observe
que a tabela não inclui rotinas para tipos especiais (como TDateTime ou Variant) ou rotinas
especificamente destinadas à formatação mais do que à conversão, como as poderosas rotinas Format e
FormatFloat .

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

03: declarações de linguagem - 85

03: idioma
declarações

Um conceito forte de tipos de dados foi um dos avanços da linguagem de programação Pascal quando ela
foi inventada. Um programa possui declarações de tipos de dados mais instruções de programação
que operam em variáveis desses tipos de dados.

Na época em que a linguagem Pascal foi inventada, a ideia desses dois pilares (tipos de dados e
instruções de programa) foi esclarecida por Niklaus Wirth em seu excelente livro
“Algoritmos + Estruturas de Dados = Programas”, publicado pela Prentice Hall em fevereiro de 1976
(livro clássico, ainda reimpresso e disponível). Embora este livro anteceda em muitos anos a programação
orientada a objetos, ele pode ser considerado um dos alicerces da programação moderna, baseado
em uma forte noção de tipo de dados e, desta forma, um alicerce dos conceitos que levam a objetos.
linguagens de programação orientadas.

As instruções da linguagem de programação são baseadas em palavras-chave e outros elementos que


permitem indicar ao compilador uma sequência de operações a serem executadas. As instruções são
frequentemente incluídas em procedimentos ou funções, como começaremos a ver com mais detalhes no
próximo capítulo. Por enquanto, focaremos apenas nos tipos básicos de instruções que você pode
escrever para criar um programa. Como vimos no Capítulo 1 (na seção que trata de espaços em branco
e formatação de código), o código real do programa pode ser escrito com bastante liberdade. Também
abordei comentários e alguns outros elementos especiais, mas nunca introduzi completamente alguns
conceitos básicos, como uma declaração de programação.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

86 - 03: declarações de linguagem

Declarações Simples e Compostas


As instruções de programação são geralmente chamadas de instruções. Um bloco de programa
pode ser composto por diversas instruções. Existem dois tipos de declarações, simples e compostas.

Uma instrução é chamada de simples quando não contém nenhuma outra subinstrução. Exemplos de
instruções simples são instruções de atribuição e chamadas de procedimento. Em Object Pascal,
instruções simples são separadas por ponto e vírgula:

X := Y + Z; // Atribuição
Aleatória; // Chamada de procedimento
...

Para definir uma instrução composta , você pode incluir uma ou mais instruções nas palavras-chave
start e end, que atuam como contêineres de múltiplas instruções e têm uma função semelhante, mas
não idêntica, às chaves em linguagens derivadas de C. Uma instrução composta pode aparecer
em qualquer lugar onde uma instrução Object Pascal simples possa aparecer. Aqui está um exemplo:

começar
UMA:= B;
C:=A*2;
fim;

O ponto e vírgula após a última instrução da instrução composta (ou seja, antes do final) não é
obrigatório, como a seguir:

começar
UMA:= B;
C := A * 2
fim;

Ambas as versões estão corretas. A primeira versão tem um ponto e vírgula final inútil (mas
inofensivo). Este ponto e vírgula é, na verdade, uma instrução nula ou vazia; isto é, uma declaração
sem código. Isso é significativamente diferente de muitas outras linguagens de programação (como
aquelas baseadas na sintaxe C), nas quais o ponto e vírgula é um terminador de instrução (não um
separador) e é sempre obrigatório no final de uma instrução.

Observe que, às vezes, uma instrução nula pode ser usada especificamente dentro de loops ou em
outros casos particulares no lugar de uma instrução real, como em:

enquanto condição_com_efeito_lateral faz


; // Nulo ou declaração vazia

Embora esses pontos e vírgulas finais não sirvam para nada, a maioria dos desenvolvedores tende a
usá-los e eu sugiro que você faça o mesmo. Às vezes, depois de escrever algumas linhas, você
você pode querer adicionar mais uma instrução e, se o último ponto e vírgula estiver faltando, lembre-se
de adicioná-lo; então geralmente é melhor adicioná-lo em primeiro lugar. Como veremos direito

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

03: declarações de linguagem - 87

de distância, há uma exceção a esta regra de adicionar pontos e vírgulas extras, e é quando o
O próximo elemento é uma instrução else dentro de uma condição.

A declaração se

Uma instrução condicional é usada para executar uma das instruções que ela contém ou nenhuma delas,
dependendo de um teste (ou condição) específico. Existem dois sabores básicos de
declarações condicionais: declarações if e declarações case .

A instrução if pode ser usada para executar uma instrução somente se uma determinada condição for
atendida (se-então) ou para escolher entre duas alternativas diferentes (se-então-senão). A condição é
definida com uma expressão booleana.

Um exemplo simples de Object Pascal, chamado IfTest, demonstrará como escrever instruções
condicionais. Neste programa usaremos uma caixa de seleção para obter a entrada do usuário, lendo sua
propriedade IsChecked (e armazenando-a em uma variável temporária, embora isso não seja estritamente
necessário, pois você pode verificar diretamente o valor da propriedade na expressão condicional):

era
IsChecked: Booleano;
começar
IsChecked := CheckBox1.IsChecked;
se IsChecked então
Mostrar( 'Caixa de seleção é verificado' );

Se a caixa de seleção estiver marcada, o programa mostrará uma mensagem simples. Caso contrário, nada
acontece. Em comparação, a mesma instrução usando a sintaxe da linguagem C será semelhante a
o seguinte (onde a expressão condicional deve ser colocada entre parênteses):

se (IsChecked)
Mostrar( "A caixa de seleção está marcada" );

Algumas outras linguagens têm a noção de um elemento endif para permitir que você escreva múltiplas
instruções, onde na sintaxe Object Pascal a instrução condicional é uma única instrução por padrão.
Você usa um bloco inicial-fim para executar mais de uma instrução como
parte da mesma condição.

Se quiser fazer operações diferentes dependendo da condição, você pode usar uma instrução if-then-else
(e neste caso usei uma expressão direta para ler o status da caixa de seleção):

// Declaração if-then-else
se CheckBox1.IsChecked então
Show('Caixa de seleção é verificado')
outro
Mostrar( 'Caixa de seleção é não verificado' );

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

88 - 03: declarações de linguagem

Observe que você não pode colocar ponto e vírgula após a primeira instrução e antes do else
palavra-chave ou o compilador emitirá um erro de sintaxe. A razão é que o if-then-else
instrução é uma instrução única, portanto você não pode colocar ponto e vírgula no meio dela.
Uma instrução if pode ser bastante complexa. A condição pode ser transformada em uma série de
condições (usando os operadores booleanos and, or e not ), ou a instrução if pode aninhar uma
segunda instrução if . Além de aninhar instruções if , quando há múltiplas condições distintas, é
comum ter instruções consecutivas if-then-else-if-then. Você pode continuar encadeando quantas
condições else- if desejar.
O terceiro botão do exemplo IfTest demonstra esses cenários, usando o primeiro caractere de uma
caixa de edição (que pode estar faltando, daí o teste externo) como entrada:

era
AChar: Char;
começar
// Vários if aninhados declarações
se Edit1.Text.Length > 0 então
começar
AChar := Edit1.Text.Chars[0];

// Verifica se há caracteres minúsculos e 'a' é (duas condições)


if (AChar >= ) e (AChar <= ); 'Com' ) então

Mostrar( 'Caracteres minúsculo'

// Condições de acompanhamento
se AChar <= Char(47) então
'Caracteres é símbolo )
Mostrar(senão se (AChar >= ) e (AChar <= ) '9' ) então
Mostrar 'Caracteres é a inferior''0'número'
(outro
'Caracteres é não a número ou símbolo inferior' );
Mostrar(fim;

Observe o código com atenção e execute o programa para ver se você o entende (e brinque com
programas semelhantes que você pode escrever para aprender mais). Você pode considerar
mais opções e expressões booleanas e aumentar a complexidade deste pequeno exemplo,
fazendo o teste que desejar.

Declarações de Caso

Se suas instruções if se tornarem muito complexas e forem baseadas em testes para valores
ordinais, considere substituí-las por instruções case . Uma instrução case consiste em uma
expressão usada para selecionar um valor e uma lista de valores possíveis ou um intervalo de valores.
Esses valores são constantes e devem ser únicos e do tipo ordinal. Eventualmente, pode haver
uma instrução else que será executada se nenhum dos valores que você especificou
correspondem ao valor do seletor. Embora não haja uma instrução final específica , um

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

03: declarações de linguagem - 89

case é sempre finalizado por um final (que neste caso não é um terminador de bloco, pois não há um início
correspondente).

note A criação de uma instrução case requer um valor ordinal. Uma instrução case baseada em um valor de string é atualmente
aluguel não permitido. Nesse caso, você precisa usar instruções if aninhadas ou uma estrutura de dados diferente, como
um dicionário (como mostrarei mais adiante no livro, no Capítulo 14).

Aqui está um exemplo (parte do projeto CaseTest ), que utiliza como entrada a parte integrante do número
inserido em um controle NumberBox, um controle de entrada numérica:

era
Número: Inteiro;
ATexto: string;
começar
ANumber := Trunc(NumberBox1.Value);
caso ANúmero de
1: ATexto := 2: 'Um' ;
ATexto := 3: 'Dois' ;
ATexto := fim; 'Três' ;

se AText <> '' então


Mostrar(ATexto);

Outro exemplo é a extensão da instrução if complexa anterior, transformada em uma série de condições
diferentes de um teste de caso :

case AChar of
'+' : AText := 'Sinal de mais';
'-'
: AText := 'Sinal de menos';
'*', '/': AText := 'Multiplicação ou divisão';
'0'..'9': AText := 'Número';
'a'..'z': AText := 'Caractere minúsculo';
'A'..'Z': AText := 'Caracter maiúsculo';
#12032..#12255: AText := 'Kangxi Radical';
outro
AText := 'Outro caractere: ' end; + AChar;

note Como você pode ver no trecho de código anterior, um intervalo de valores é definido com a mesma sintaxe de um
tipo de dados de subintervalo. Vários valores para uma única ramificação, em vez disso, são separados por
vírgula. Para a seção Kangxi Radical usei o valor numérico em vez dos caracteres reais, porque a maioria
das fontes de tamanho fixo usadas pelo editor IDE não exibirá os símbolos corretamente.

É considerada uma boa prática incluir a parte else para sinalizar uma condição indefinida ou inesperada.
Uma instrução case em Object Pascal seleciona um caminho de execução, mas não se posiciona
em um ponto de entrada. Em outras palavras, ele executa a instrução ou bloco após os dois pontos
do valor selecionado e pula para a instrução após o caso.

Isso é muito diferente da linguagem C (e de algumas de suas linguagens derivadas), que tratam
ramificações de uma instrução switch como pontos de entrada e executarão todas as seguintes

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

90 - 03: declarações de linguagem

instruções, a menos que você use especificamente uma solicitação de interrupção (embora este seja
um cenário específico em que Java e C# realmente diferem em sua implementação). A sintaxe da
linguagem C é semelhante a esta:

mudar (aChar) {
case '+': aText = "sinal de mais"; quebrar;
case '-': aText = "sinal de menos"; quebrar;
...
padrão: aText = "desconhecido"; quebrar;
}

O ciclo For
A linguagem Object Pascal possui as instruções repetitivas ou em loop típicas da maioria das
linguagens de programação, incluindo instruções for, while e repeat , além do ciclo for-in (ou for-each)
mais moderno . A maioria desses loops será familiar se você tiver usado outras linguagens de
programação, então irei abordá-los apenas brevemente (indicando as principais diferenças em relação
a outras linguagens).

O loop for em Object Pascal é estritamente baseado em um contador, que pode ser aumentado
ou diminuído cada vez que o loop é executado. Aqui está um exemplo simples de um loop for usado
para adicionar os primeiros dez números (parte da demonstração ForTest ).

era
Total, I: Inteiro;
começar
Total := 0;
para I := 1 a 10 faça
Total := Total + I;
Mostrar(Total.ToString);

Para os curiosos, a saída é 55. Outra maneira de escrever um loop for após a introdução de variáveis
inline é declarar a variável contador de loop dentro da declaração (com uma sintaxe que de alguma
forma se assemelha aos loops for em C e nas linguagens derivadas discutidas mais tarde):

para var I: Inteiro: = 1 a 10 faça


Total := Total + I;

Nesse caso, você também pode aproveitar a inferência de tipo e omitir a especificação de tipo. O
trecho de código completo acima se torna:

era
Total: Inteiro;
começar
Total := 0;
para var I := 1 a 10 faça

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

03: declarações de linguagem - 91

Total := Total + I;
Mostrar(Total.ToString);

Uma vantagem de usar um contador de loop inline é que o escopo dessa variável será limitado
ao loop: usá-lo após a instrução for causará um erro, enquanto em geral
você recebe apenas um aviso ao usar o contador de loop fora do loop.
O loop for em Pascal é menos flexível que em outras linguagens (não é possível especificar um
incremento diferente de um), mas é simples e fácil de entender. Como comparação, este é
o mesmo loop for escrito na sintaxe da linguagem C:

total interno = 0;
for (int i = 1; i <= 10; i++) {
total = total + eu;
}

Nessas linguagens, o incremento é uma expressão que pode especificar qualquer tipo
de sequência, o que pode levar a códigos que muitos considerariam ilegíveis, como o seguinte:

total interno = 0;
for (int i = 10; i > 0; total += i--) {
...
}

No Object Pascal, entretanto, você só pode usar um incremento de etapa única. Se quiser testar
uma condição mais complexa ou fornecer um contador personalizado, você precisará usar uma
instrução while ou repeat , em vez de um loop for .
A única alternativa ao incremento único é o decremento único ou um loop for reverso com a
palavra-chave downto :
era
Total, I: Inteiro;
começar
Total := 0;
para I := 10 até 1 faça
Total := Total + I;

note A contagem reversa é útil, por exemplo, quando você está afetando uma estrutura de dados baseada em lista, você está
percorrendo. Ao excluir alguns elementos, muitas vezes você retrocede, como acontece com um loop de avanço,
você pode afetar a sequência em que está operando (ou seja, se você excluir o terceiro elemento de uma
lista, o quarto elemento se tornará o terceiro: agora você está no terceiro, passe para o próximo (o quarto), mas você
estão realmente operando no que era o quinto elemento, pulando um).

No Object Pascal, o contador de um loop for não precisa ser um número. Pode ser um valor
de qualquer tipo ordinal, como um caractere ou um tipo enumerado. Isso ajuda você a escrever
um código mais legível. Aqui está um exemplo com um loop for baseado no tipo Char :
era
AChar: Char;

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

92 - 03: declarações de linguagem

começar
para AChar := 'a' para 'z' faça
Show(AChar);

Este código (parte do programa ForTest ) mostra todas as letras do alfabeto inglês,
cada um em uma linha separada do controle Memo de saída.

note que já mostrei uma demonstração semelhante, mas baseada em um contador inteiro, como parte do exemplo CharsTest
do Capítulo 2. Nesse caso, porém, os caracteres foram concatenados em uma única string de saída.

Aqui está outro trecho de código que mostra um loop for baseado em uma enumeração personalizada:

tipo
TSuit = (Paus, Ouros, Copas, Espadas);
era
Fato: Fato;
começar
para ASuit := Clube para Spade do
...

Este último loop percorre todos os elementos do tipo de dados. Poderia ser melhor escrito para operar
explicitamente em cada elemento do tipo (tornando-o mais flexível para mudanças no
definição) em vez de indicar especificamente o primeiro e o último elemento, escrevendo:

para ASuit := Baixo (TSuit) para Alto (TSuit) faça

De maneira semelhante, é bastante comum escrever loop for para todos os elementos de uma estrutura de
dados, como uma string. Neste caso você pode usar este código (do projeto ForTest):

era
S: corda;
Eu: Inteiro;
começar
S := 'Olá Mundo' ;
para I := Baixo(S) a Alto(S) faça
Mostrar(S[I]);

Se preferir não ter que indicar o primeiro e o último elemento da estrutura de dados, você pode usar um loop for-
in , um loop for de propósito especial discutido na próxima seção.

note Como o compilador trata a leitura direta dos dados da string usando os operadores [] e determina o
Os limites inferior e superior de uma string continuam sendo um tópico bastante complexo no Object Pascal, mesmo que os
padrões agora sejam os mesmos para todas as plataformas. Isso será abordado no Capítulo 6.

Para estruturas de dados que usam indexação baseada em zero, você deseja fazer um loop do índice zero para
aquele anterior ao tamanho ou comprimento da estrutura de dados. Maneiras comuns de escrever este código
são:

para I := 0 para contar - 1 fazer ...


para I := 0 para Pred(Count) faça ...

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

03: declarações de linguagem - 93

Uma observação final sobre os loops for é o que acontece com o contador de loop após o loop. Em
resumindo, o valor não é especificado e o compilador emitirá um aviso se você tentar usar o
for contador de loop após o término do loop. Uma vantagem de usar uma variável inline para o contador
de loop é que a variável é definida apenas dentro do próprio loop e não estará acessível após sua instrução
final, resultando em um erro do compilador (que fornece proteção mais forte):

começar
var Total := 0;
para var I: Inteiro: = 1 a 10 faça
Inc(Total, I);
Mostrar(Total.ToString);
Mostrar(I.ToString); // Erro do compilador: identificador não declarado 'EU'

O loop for-in
Object Pascal tem uma construção de loop específica para percorrer todos os elementos de uma lista ou
coleção, chamada for-in (um recurso que outras linguagens de programação chamam para cada um). Neste
loop for , o ciclo opera em cada elemento de um array, uma lista, uma string ou algum outro tipo de
contêiner. Ao contrário do C#, o Object Pascal não requer a implementação da interface IEnumer-ator , mas
a implementação interna é um tanto semelhante.

note Você pode encontrar os detalhes técnicos de como suportar o loop for-in em uma classe, adicionando recursos personalizados
apoio à meração, no Capítulo 10.

Vamos começar com um contêiner bem simples, uma string, que pode ser vista como uma coleção de
caracteres. Vimos no final da seção anterior como usar um loop for para operar em todos os elementos
de uma string. O mesmo efeito exato pode ser obtido com o seguinte loop for-in baseado em uma string,
onde a variável Ch recebe cada um dos elementos da string por sua vez:

era
S: corda;
Ch: Caráter;
começar
S := 'Olá Mundo' ;
para Ch em S faça
Mostrar(Ch);

Este trecho também faz parte do exemplo ForTest . A vantagem sobre usar um tradicional
for loop é que você não precisa lembrar qual é o primeiro elemento da string e como extrair a posição do
último. Este loop é mais fácil de escrever e manter e tem eficiência semelhante.

Assim como os loops for tradicionais, também os loops for-in podem se beneficiar do uso de uma
declaração de variável embutida. Podemos reescrever o código acima usando o seguinte código equivalente:

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

94 - 03: declarações de linguagem

era
S: corda;
começar
S := ; 'Olá Mundo'
para var Ch: Char em S do
Mostrar(Ch);

O loop for-in pode ser usado para acessar os elementos das diversas estruturas de dados diferentes:

• Caracteres em uma string (veja o snippet de código anterior)


• Valores ativos em um conjunto

• Itens em uma matriz estática ou dinâmica, incluindo matrizes bidimensionais (abordados


no Capítulo 5)
• Objetos referenciados por classes com suporte a GetEnumerator , incluindo muitos objetos pré-definidos, como
strings em uma lista de strings, elementos de diversas classes de contêiner, componentes
pertencentes a um formulário e muitos outros. Como implementar
isso será discutido no Capítulo 10.

Agora é um pouco difícil neste ponto do livro cobrir esses padrões de uso avançados, então voltarei aos
exemplos desse loop mais adiante no livro.

note O loop for-in em algumas linguagens (por exemplo, JavaScript) tem uma má reputação de ser muito lento para ser
executado. Este não é o caso em Object Pascal, onde leva aproximadamente o mesmo tempo de um loop for
padrão correspondente . Para provar isso, adicionei ao exemplo LoopsTest um código de temporização, que
primeiro cria uma string de 30 milhões de elementos e depois a varre com os dois tipos de loops (fazendo uma
operação muito simples a cada iteração. A diferença na velocidade é de cerca de 10% a favor do loop for clássico (62
milissegundos vs. 68 milissegundos na minha máquina Windows).

Declarações While e Repeat


A ideia por trás dos loops while-do e repeat-until é repetir a execução de
um bloco de código repetidamente até que uma determinada condição seja atendida. A diferença entre estes
dois loops é que a condição é verificada no início ou no final do loop. Em outras palavras, o bloco de
código da instrução de repetição é sempre executado pelo menos uma vez.

note A maioria das outras linguagens de programação possui apenas um tipo de instrução de loop aberto, geralmente
chamada e que se comporta como um loop while. A sintaxe da linguagem C tem as mesmas duas opções da sintaxe
Pascal, com os ciclos while e do-while . Observe, pensei, que eles usam a mesma condição lógica, diferentemente
do loop repetir até que tem uma condição reversa.

Você pode entender facilmente por que o loop de repetição é sempre executado pelo menos uma vez,
observando um exemplo de código simples:

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

03: declarações de linguagem - 95

enquanto (I <= 100) e (J <= 100) fazem


começar
// Usar EU e J. para computar alguma coisa...
eu := eu + 1;
J := J + 1;
fim;

repita
// Usar EU e J. para computar alguma coisa...
eu := eu + 1;
J := J + 1;
até (I > 100) ou (J > 100);

note Você deve ter notado que tanto nas condições while quanto nas condições de repetição coloquei as “subcondições” entre
parênteses. É necessário neste caso, enquanto o compilador irá executar ou antes de realizar as comparações (como
abordei na seção sobre operadores do Capítulo 2).

Se o valor inicial de I ou J for maior que 100, o loop while será completamente ignorado, enquanto as
instruções dentro do loop de repetição serão executadas pelo menos uma vez.

A outra diferença importante entre esses dois loops é que o loop repetir até tem uma condição invertida . Este
loop é executado enquanto a condição não for atendida. Quando o
condição for atendida, o loop termina. Isso é o oposto de um loop while-do , que é executado enquanto a
condição é verdadeira. Por esse motivo tive que reverter a condição do código acima para obter um efeito
semelhante.

nota A “condição reversa” é formalmente conhecida como leis “De Morgan” (descritas, por exemplo,
na Wikipedia em http://en.wikipedia.org/wiki/De_Morgan%27s_laws).

Exemplos de loops
Para explorar mais alguns detalhes dos loops, vejamos um pequeno exemplo prático. O programa
LoopsTest destaca a diferença entre um loop com contador fixo e um loop com contador aberto. O primeiro loop
contador fixo, um loop for , exibe números em
seqüência:

era
Eu: Inteiro;
começar
para I := 1 a 20 faça
'Número ' + IntToStr(I));
Mostrar(fim;

O mesmo também poderia ter sido obtido com um loop while , com incremento interno de um (observe que
você incrementa o valor após ter utilizado o valor atual). Com um

while , no entanto, você pode definir um incremento personalizado, por exemplo, de 2:

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

96 - 03: declarações de linguagem

era
Eu: Inteiro;
começar
Eu:= 1;
enquanto eu <= 20 faço
começar
'Número '
Mostrar( + IntToStr(I));
Inc(I, 2)
fim;
fim;

Este código mostra todos os números ímpares de um a 19.

Esses loops com incrementos fixos são logicamente equivalentes e executam um número
predefinido de vezes. Isso não é sempre o caso. Existem loops cuja execução é mais
indeterminada, dependendo, por exemplo, de condições externas.

note Ao escrever um loop while você deve sempre considerar o caso em que a condição nunca é atendida. Por
exemplo, se você escrever o loop acima, mas esquecer de incrementar o contador do loop, isso resultará
em um loop infinito (que paralisará o programa para sempre, provavelmente consumindo 100% da CPU até
que o usuário ou o sistema operacional elimine o processo ).

Para mostrar um exemplo de um loop menos determinístico, escrevi um loop while ainda baseado
em um contador, mas que é aumentado aleatoriamente. Para conseguir isso, chamei a função
Random com um valor de intervalo de 100. O resultado desta função é um número entre 0 e 99,
escolhido aleatoriamente. A série de números aleatórios controla quantas vezes o loop while é
executado:

era
Eu: Inteiro;
começar
Aleatória;
Eu:= 1;
enquanto eu <500 faço
começar
'Número aleatório: '
Mostrar( + IntToStr(I));
Eu := Eu + Aleatório(100);
fim;
fim;

Se você se lembrar de adicionar uma chamada ao procedimento Randomize , que faz com que o
gerador de números aleatórios inicie em um ponto diferente para cada execução do programa, cada
vez que você executar o programa, os números serão diferentes. A seguir está o resultado de duas
execuções separadas, exibidas lado a lado:

Número aleatório: 1 Número aleatório: 1


Número aleatório: 40 Número aleatório: 47
Número aleatório: 60 Número aleatório: 104
Número aleatório: 89 Número aleatório: 201
Número aleatório: 146 Número aleatório: 223
Número aleatório: 198 Número aleatório: 258

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

03: declarações de linguagem - 97

Número aleatório: 223 Número aleatório: 322


Número aleatório: 251 Número aleatório: 349
Número aleatório: 263 Número aleatório: 444
Número aleatório: 303 Número aleatório: 466
Número aleatório: 349
Número aleatório: 366
Número aleatório: 443
Número aleatório: 489

Observe que não apenas os números gerados são diferentes a cada vez, mas também o número de
itens. Isso ocorre porque o loop while é executado um número aleatório de vezes. Se você executar o
programa várias vezes seguidas, verá que a saída possui um número diferente de linhas.

Quebrando o fluxo com Break and Continue


Apesar das diferenças, cada um dos loops permite executar um bloco de instruções várias vezes, com
base em algumas regras. No entanto, existem cenários em que você pode querer adicionar algum
comportamento adicional. Suponha, por exemplo, que você tenha um loop for onde você procura a
ocorrência de uma determinada letra (esse código faz parte do exemplo FlowTest ):

era
S: corda;
Eu: Inteiro;
Encontrado: Booleano;
começar
S:= 'Olá Mundo' ;
Encontrado := Falso;
para I := Baixo(S) a Alto(S) faça
se S[I] = 'o' então
Encontrado := Verdadeiro;

No final você pode verificar o valor de encontrado para ver se a letra fornecida fazia parte da string. O
problema é que o programa continua repetindo o loop e verificando o caractere fornecido mesmo depois
de encontrar uma ocorrência dele (o que seria um problema com uma string muito longa).

Uma alternativa clássica seria transformar isso em um loop while e verificar ambas as condições (o
contador do loop e o valor de Found):

era
S: corda;
Eu: Inteiro;
Encontrado: Booleano;
começar
S:= 'Olá Mundo' ;
Encontrado := Falso;
Eu := Baixo(S);

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

98 - 03: declarações de linguagem

enquanto não for encontrado e (I <= High(S)) faça


começar
se S[I] = 'o' então
Encontrado := Verdadeiro;
Inc(I);
fim;

Embora esse código seja lógico e legível, há mais código para escrever e, se as condições se tornarem
múltiplas e mais complexas, a combinação de todas as diversas opções tornaria o código muito complicado.

É por isso que a linguagem (ou, para ser mais preciso, seu suporte de tempo de execução) possui
procedimentos de sistema que permitem alterar o fluxo padrão de execução de um loop:

· O procedimento Break interrompe um loop, saltando diretamente para a primeira instrução seguinte,
ignorando qualquer execução adicional

· O procedimento Continue salta para o teste do loop ou incremento do contador, continuando com a
próxima iteração do loop (a menos que a condição não seja mais verdadeira ou o contador tenha
atingido seu valor mais alto)

Usando a operação Break , podemos modificar o loop original para combinar um caractere da seguinte forma:

era
S: corda;
Eu: Inteiro;
Encontrado: Booleano;
começar
S:= 'Olá Mundo' ;
Encontrado := Falso;
para I := Baixo(S) a Alto(S) faça
se S[I] = 'o' então
começar
Encontrado := Verdadeiro;
Quebrar; // o Salta para fora do loop for
fim;

Mais dois procedimentos do sistema, Exit e Halt, permitem retornar imediatamente da função ou procedimento
atual ou encerrar o programa. Abordarei Exit no próximo capítulo, embora basicamente não haja
razão para chamar Halt , pois ele encerra abruptamente o
programa (então não vou discutir isso no livro).

Aí vem Goto? Sem chance!


Na verdade, há mais para quebrar o fluxo do que os quatro procedimentos do sistema acima. O
A linguagem Pascal original contava entre seus recursos a infame instrução goto , que permite anexar um
rótulo a qualquer linha do código-fonte e pular para essa linha de outro local. Diferentemente das
instruções condicionais e de loop, que revelam por que você deseja divergir de um fluxo de código
sequencial, as instruções goto geralmente parecem saltos erráticos e são completamente desencorajadas.
Eu mencionei que eles são sup-

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

03: declarações de linguagem - 99

portado em Object Pascal? Não, não fiz isso, nem vou mostrar um exemplo de código.
Para mim , goto já se foi.

note Há outras declarações de linguagem que não abordei até agora, mas que fazem parte da definição da linguagem.
Uma delas é a instrução with , que está especificamente vinculada a registros, por isso abordarei isso no Capítulo 5.
With é outro recurso de linguagem “debatido”, mas ainda comumente usado.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

100 - 04: procedimentos e funções

04: procedimentos e
funções

Outra ideia importante enfatizada na linguagem Object Pascal (juntamente com recursos semelhantes da
linguagem C) é o conceito de rotina, basicamente uma série de instruções
com um nome exclusivo, que pode ser ativado várias vezes. As rotinas (ou funções) são chamadas pelo
seu nome. Dessa forma, você evita ter que escrever o mesmo código repetidamente e permite usar
uma única versão de algum código em vários lugares do programa. Deste ponto de vista, você pode
pensar nas rotinas como um mecanismo básico de encapsulamento de código.

Procedimentos e Funções

Em Object Pascal, uma rotina pode assumir duas formas: um procedimento e uma função. Em teoria,
um procedimento é uma operação que você pede ao computador para realizar, uma função é um
cálculo que retorna um valor. Essa diferença é enfatizada pelo fato de que uma função possui um
resultado, um valor de retorno ou um tipo, enquanto um procedimento não. A sintaxe da linguagem C
fornece um único mecanismo, funções, e em C um procedimento é uma função com um resultado nulo
(ou nulo).

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

04: procedimentos e funções - 101

Ambos os tipos de rotinas podem ter vários parâmetros de tipos de dados especificados. Como veremos
mais adiante, procedimentos e funções também são a base dos métodos de uma classe, e também
neste caso, a distinção entre as duas formas permanece. Na verdade, diferentemente de C, C++, Java, C#
ou JavaScript, você precisa usar uma dessas duas palavras-chave ao declarar uma rotina ou método.

Na prática, mesmo que existam duas palavras-chave separadas, a diferença entre funções e procedimentos
é muito limitada: você pode chamar uma função para realizar algum trabalho e depois ignorar o resultado (que
pode ser um código de erro opcional ou algo parecido) ou você pode chamar um procedimento que retorna
um resultado em um dos parâmetros (mais sobre parâmetros de referência posteriormente neste capítulo).

Aqui está a definição de um procedimento usando a sintaxe da linguagem Object Pascal, que usa a palavra-
chave de procedimento específica e faz parte do projeto FunctionTest :

procedimento Olá;
começar
'Olá Mundo!' );
Mostrar(fim;

A título de comparação, esta seria a mesma função escrita com a sintaxe da linguagem C, que não possui
palavra-chave, requer parênteses mesmo caso não haja parâmetros e possui um valor de retorno void ou
vazio para indicar nenhum resultado:

vazio Olá()
{
Olá Mundo!" );
Mostrar("};

Na verdade, na sintaxe da linguagem C não há diferença entre um procedimento e uma função. Na sintaxe da
linguagem Pascal, entretanto, é feita uma distinção em que uma função
possui uma palavra-chave específica e deve ter um valor de retorno (ou tipo de retorno).

note Há outra diferença sintática muito específica entre Object Pascal e outras linguagens, que é a
presença de um ponto e vírgula no final da função ou assinatura do procedimento na definição,
antes da palavra-chave de início .

Existem duas maneiras de indicar o resultado da chamada de função, atribuir o valor ao nome da função
ou usar a palavra-chave Result :

// Estilo Pascal clássico


função DoubleOld (Valor: Inteiro): Inteiro;
começar
DuploVelho := Valor * 2;
fim;

// Alternativa moderna
função DoubleIt (Valor: Inteiro): Inteiro;
começar
Resultado:= Valor * 2;

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

102 - 04: procedimentos e funções

fim;

note Diferentemente da sintaxe clássica da linguagem Pascal, o Object Pascal moderno possui na verdade três maneiras de indicar
o resultado de uma função, incluindo o mecanismo Exit discutido neste capítulo na seção “Sair com um Resultado”.

O uso de Resultado em vez do nome da função para atribuir o valor de retorno de uma função é a sintaxe
mais comum e tende a tornar o código mais legível. O uso do nome da função é uma notação Pascal
clássica, raramente usada, mas ainda suportada.

Novamente, em comparação, a mesma função poderia ser escrita com a sintaxe da linguagem C como
esta:

int DoubleIt (valor interno)


{
valor de retorno * 2;
};

note Uma instrução return em linguagens baseadas na sintaxe C indica o resultado da função, mas também encerra a
execução, devolvendo o controle ao chamado. Em Object Pascal, por outro lado, atribuir um valor ao
resultado de uma função não a encerra. É por isso que o resultado é frequentemente usado como uma
variável regular, por exemplo, atribuindo-lhe um valor inicial padrão ou mesmo para modificar seu resultado
em um algoritmo. Ao mesmo tempo, se precisar interromper a execução, você também precisará usar Exit of
alguma outra instrução de controle de fluxo. Tudo isso é abordado com mais detalhes na seção seguinte “Saída com
Resultado”.

Se é assim que essas rotinas podem ser definidas, a sintaxe de chamada é relativamente simples,
conforme você digita o identificador seguido pelos parâmetros entre parênteses. Nos casos em que não
há parâmetros, os parênteses vazios podem ser omitidos (ao contrário das linguagens baseadas na
sintaxe C). Este trecho de código e vários outros seguintes fazem parte
do projeto FunctionsTest deste capítulo:

// Chamar o procedimento
Olá;

// Chamar a função
X := DoubleIt(100);
Y := DoubleIt(X);
Mostrar(Y.ToString);

Isso demonstra o conceito de encapsulamento de código. Quando você liga para o DoubleIt
função, você não precisa conhecer o algoritmo usado para implementá-la. Se, mais tarde, você encontrar
uma maneira melhor de duplicar números, poderá facilmente alterar o código da função, mas o código
de chamada permanecerá inalterado (embora executá-lo possa se tornar
mais rápido).

O mesmo princípio pode ser aplicado ao procedimento Hello : podemos modificar a saída do programa
alterando o código deste procedimento, e o código principal do programa será automaticamente

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

04: procedimentos e funções - 103

alterar automaticamente seu efeito. Aqui está como podemos alterar o código de implementação do
procedimento:

procedimento Olá;
começar
Olá mundo, de novo!' );
Mostrar('fim;

Declarações futuras

Quando for necessário utilizar um identificador (de qualquer tipo), o compilador já deve tê-lo visto, para saber
a que o identificador se refere. Por esse motivo, geralmente você fornece uma definição completa antes de
usar qualquer rotina. Porém, há casos em que isso não é possível.
Se o procedimento A chamar o procedimento B, e o procedimento B chamar o procedimento A, quando
você começar a escrever o código, precisará chamar uma rotina para a qual o compilador ainda não viu uma
definição.

Nestes casos (e em muitos outros) você pode declarar a existência de um procedimento ou função com um
determinado nome e determinados parâmetros, sem fornecer seu código real. Uma maneira de declarar um
procedimento ou funções sem defini-lo é escrever seu nome e parâmetros (referidos como assinatura da
função) seguidos pela palavra-chave forward :

procedimento NovoOlá; avançar;

Posteriormente, o código deverá fornecer uma definição completa do procedimento (que deve estar na
mesma unidade), mas o procedimento agora pode ser chamado antes de ser totalmente definido. Aqui está um
por exemplo, só para você ter uma ideia:

procedimento DoubleHello; avançar;

procedimento NovoOlá;
começar
if 'Você querer a mensagem dupla? ,
MessageDlg(TMsgDlgType.mtConfirmação,
[TMsgDlgBtn.mbSim, TMsgDlgBtn.mbNo], 0) = mrSim então
Olá duplo
outro
'Olá'
ShowMessage(end; );

procedimento DoubleHello;
começar
NovoOlá;
NovoOlá;
fim;

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

104 - 04: procedimentos e funções

note A função MessageDlg chamada no trecho anterior é uma maneira relativamente simples de solicitar uma confirmação do
usuário na estrutura FireMonkey (funções semelhantes também existem na estrutura VCL). Os parâmetros são a
mensagem, o tipo de caixa de diálogo e os botões que você deseja exibir. O resultado é o identificador do botão que foi
selecionado.

Essa abordagem (que também faz parte do exemplo FunctionTest ) permite escrever recursão mútua: DoubleHello
chama Hello, mas Hello também pode chamar DoubleHello . Em outras palavras, se você continuar selecionando
o botão Sim, o programa continuará mostrando a mensagem, e mostrará cada uma duas vezes para cada Sim. No
código recursivo, deve haver uma condição para encerrar a recursão, para evitar uma condição conhecida como
estouro de pilha, provavelmente devido a um loop infinito em termos de chamadas recursivas.

note Chamadas de função usam a parte da pilha da memória do aplicativo para os parâmetros, o valor de retorno,
variáveis locais e muito mais. Se uma função continua chamando a si mesma em um loop infinito, a área de memória para o
pilha (que geralmente tem tamanho fixo e predefinido, determinado pelo vinculador e configurado nas opções do
projeto) terminará por meio de um erro conhecido como estouro de pilha. Escusado será dizer que o popular site de
suporte aos desenvolvedores (www.stackoverflow.com) recebeu o nome desta programação
erro.

Embora uma declaração de procedimento forward não seja muito comum em Object Pascal, existe um caso
semelhante que é muito mais frequente. Quando você declara um procedimento ou função
na seção de interface de uma unidade, ela é automaticamente considerada como uma declaração direta, mesmo
que a palavra-chave forward não esteja presente. Na verdade você não pode escrever o corpo de uma rotina na
seção de interface de uma unidade. Observe que você deve fornecer a implementação real de cada rotina
declarada na mesma unidade.

Uma função recursiva

Dado que mencionei a recursão e dei um exemplo bastante peculiar dela (com dois procedimentos chamando um
ao outro), deixe-me mostrar também um exemplo clássico de uma função recursiva
chamando a si mesmo. Usar a recursão costuma ser uma forma alternativa de codificar um loop.

Para continuar com uma demonstração clássica, suponha que você queira calcular a potência de um número e não
tenha a função adequada (que está disponível na biblioteca de tempo de execução, é claro). Você deve se lembrar
da matemática que 2 elevado a 3 corresponde a multiplicar 2 por si mesmo 3 vezes, ou seja, 2*2*2.

Uma maneira de expressar isso em código seria escrever um loop for que seja executado 3 vezes (ou
o valor do expoente) e multiplica 2 (ou o valor da base) pelo total atual, começando com 1:

função PowerL(Base, Exp: Inteiro): Inteiro;


era
Eu: Inteiro;

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

04: procedimentos e funções - 105

começar
Resultado:= 1;
para I := 1 para Exp fazer
Resultado:= Resultado * Base;
fim;

Uma abordagem alternativa é multiplicar repetidamente a base pela potência do mesmo número, com
um expoente decrescente, até que o expoente seja 0, caso em que o resultado é
invariavelmente 1. Isso pode ser expresso chamando a mesma função repetidamente, de forma
recursiva:

função PowerR (Base, Exp: Inteiro): Inteiro;


era
Eu: Inteiro;
começar
se Exp = 0 então
Resultado:= 1
outro
Resultado:= Base * PowerR(Base, Exp - 1);
fim;

A versão recursiva do programa provavelmente não é mais rápida que a versão baseada no loop for ,
nem é mais legível. No entanto, existem cenários como a análise de estruturas de código (uma estrutura
em árvore, por exemplo) em que não há um número fixo de elementos para processar e, portanto,
escrever um loop é quase impossível, enquanto uma função recursiva se adapta a o papel.

Em geral, porém, o código recursivo é poderoso, mas tende a ser mais complexo. Depois de muitos
anos em que a recursão foi quase esquecida, em comparação com os primeiros dias da programação,
novas linguagens funcionais como Haskell, Erlang e Elixir fazem uso intenso da recursão e estão
levando essa ideia de volta à popularidade.

De qualquer forma, você pode encontrar as duas funções de potência no código no arquivo FunctionTest
exemplo.

note As duas funções de potência da demonstração não tratam do caso de um expoente negativo. A versão recursiva,
nesse caso, fará um loop para sempre (ou mais precisamente, até que o programa atinja uma restrição física).
Além disso, usando números inteiros, é relativamente rápido atingir o tamanho máximo do tipo de dados e estourá-lo. escrevi
essas funções com essas limitações inerentes para tentar manter seu código simples.

O que é um método?

Vimos como você pode escrever uma declaração forward na seção de interface de uma unidade usando
a palavra-chave forward . A declaração de um método dentro de um tipo de classe também é
considerada uma declaração antecipada.

Mas o que exatamente é um método? Um método é um tipo especial de função ou procedimento


relacionado a um dos dois tipos de dados, um registro ou uma classe. No Object Pascal, toda vez que

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

106 - 04: procedimentos e funções

Para lidar com um evento para um componente visual, precisamos definir um método, geralmente um
procedimento, mas o termo método é usado para indicar funções e procedimentos vinculados a uma
classe ou registro.

Aqui está um método vazio adicionado automaticamente ao código-fonte de um formulário (que é de


fato uma classe, como exploraremos mais adiante neste livro):

procedimento TForm1.Button1Click(Remetente: TObject);


começar
// Seu código vai aqui
fim;

Parâmetros e valores de retorno


Ao chamar uma função ou procedimento, você precisa passar o número correto de parâmetros e
certificar-se de que eles correspondem aos tipos esperados. Caso contrário, o compilador emitirá
uma mensagem de erro, semelhante a uma incompatibilidade de tipo quando você atribui a uma variável
um valor do tipo errado. Dada a definição anterior da função DoubleIt , tomando um parâmetro Integer,
se você chamar:

DoubleIt(10.0);

O compilador mostrará o erro:

[Erro dcc32] E2010 Tipos incompatíveis: 'Integer' e 'Extended'

dica O editor ajuda você sugerindo a lista de parâmetros de uma função ou procedimento com uma dica instantânea
assim que você digita seu nome e o parêntese aberto. Esse recurso é chamado de Parâmetros de Código
e faz parte da tecnologia Code Insight (conhecida em outros IDEs como IntelliSense). CodeInsight começando
com Delphi 10.4 é alimentado por um servidor LSP (Language Server Protocol).

Existem cenários em que a conversão limitada de tipos é permitida, de forma semelhante às


atribuições, mas em geral você deve tentar usar parâmetros do tipo específico (isso é obrigatório
para parâmetros de referência, como veremos daqui a pouco).

Ao chamar uma função, você pode passar uma expressão como parâmetro em vez de valor.
A expressão é avaliada e seu resultado atribuído ao parâmetro. Em casos mais simples, basta passar o
nome de uma variável. Neste caso, o valor da variável é copiado para o parâmetro (que geralmente
possui um nome diferente). Eu desencorajo fortemente você a usar o mesmo nome para um parâmetro e
para uma variável passada como o valor desse parâmetro, porque isso pode ser bastante confuso.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

04: procedimentos e funções - 107

avisar Com Delphi, geralmente você não deve confiar na ordem de avaliação dos parâmetros passados para uma
no função, pois isso varia dependendo da convenção de chamada e nos mesmos casos é indefinido,
embora o caso mais comum seja a avaliação da direita para a esquerda. Mais informações sobre isso estão
disponíveis em: http://docwiki.embarcadero.com/RADStudio/en/
Procedimentos_e_Funções_(Delphi)#Calling_Conventions

Finalmente, observe que você pode ter uma função ou procedimento com versões diferentes (um
recurso chamado sobrecarga) e com parâmetros que você pode pular para deixá-los usar um valor predefinido.
valor (um recurso chamado parâmetros padrão). Esses dois recursos principais para funções e
procedimentos são detalhados em seções específicas posteriormente neste capítulo.

Sair com um resultado


Vimos que o retorno de um resultado de uma função usa uma sintaxe bem diferente em comparação
com a família de linguagens C. Não apenas a sintaxe é diferente, mas também o comportamento.
Atribuir um valor ao Resultado (ou ao nome da função) não encerra a função como uma instrução return
faz. Os desenvolvedores do Object Pascal geralmente aproveitam esse recurso, usando o Result como
armazenamento temporário. Por exemplo, em vez de escrever:

função ComputeValue: Inteiro;


era
Valor: Inteiro;
começar
Valor := 0;
enquanto ...
Inc(Valor);
Resultado := Valor;
fim;

Você pode omitir a variável temporária e usar Result . Qualquer que seja o valor Resultado
tem quando a função termina, é o valor retornado pela função:

função ComputeValue: Inteiro;


começar
Resultado:= 0;
enquanto ...
Inc(Resultado);
fim;

Por outro lado existem situações em que você deseja atribuir um valor e sair imediatamente do
procedimento, por exemplo em uma ramificação if específica . Se você precisar atribuir o resultado
da função e interromper a execução atual, você deverá usar duas instruções separadas, atribuir o
Resultado e então usar a palavra-chave Exit .

Se você se lembrar do código do exemplo FlowTest do último capítulo (abordado na seção “Quebrando
o fluxo com Break and Continue”), ele poderia ser reescrito como um

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

108 - 04: procedimentos e funções

função, substituindo a chamada para Break por uma chamada para Exit. Fiz essa alteração no seguinte trecho
de código, parte do exemplo ParamsTest :

função CharInString (S: string; Ch: Char): Boolean;


era
Eu: Inteiro;
começar
Resultado := Falso;
para I := Baixo(S) a Alto(S) faça
se S[I] = Ch então
começar
Resultado := Verdadeiro;
Saída;
fim;
fim;

No Object Pascal você pode substituir as duas instruções do bloco if por uma chamada especial para
Sair passando para ele o valor de retorno da função, de forma semelhante à instrução de retorno da linguagem
C. Portanto, você pode escrever o código acima de uma forma mais compacta (também porque com
uma única instrução você pode evitar o bloco início-fim ):

função CharInString2(S: string; Ch: Char): Boolean;


era
Eu: Inteiro;
começar
Resultado := Falso;
para I := Baixo(S) a Alto(S) faça
se S[I] = Ch então
Sair(Verdadeiro);
fim;

note Exit em Object Pascal é uma função, portanto você deve colocar o valor a ser retornado entre parênteses,
enquanto, em linguagens de estilo C, return é uma palavra-chave do compilador que não requer parênteses.

Parâmetros de referência
No Object Pascal, procedimentos e funções permitem a passagem de parâmetros por valor e por referência.
Passar parâmetros por valor é o padrão: o valor é copiado na pilha e a rotina usa e manipula essa cópia dos
dados, não o valor original (como descrevi anteriormente na seção “Parâmetros de Função e Valores de
Retorno”). Passar um parâmetro por referência significa que seu valor não é copiado na pilha no parâmetro
formal da rotina. Em vez disso, o programa refere-se ao valor original, também no código da rotina. Isso
permite que o procedimento ou função altere o valor real da variável que foi passada como parâmetro. A
passagem de parâmetros por referência é expressa

pela palavra-chave var .

Essa técnica também está disponível na maioria das linguagens de programação, porque evitar uma cópia
geralmente significa que o programa será executado mais rapidamente. Não está presente em C (onde você pode

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

04: procedimentos e funções - 109

basta usar um ponteiro), mas foi introduzido em C++ e outras linguagens baseadas na sintaxe C, onde
você usa o símbolo & (passagem por referência). Aqui está um exemplo de passagem de um
parâmetro por referência usando a palavra-chave var :

procedimento DoubleIt (var Valor: Inteiro);


começar
Valor := Valor * 2;
fim;

Neste caso, o parâmetro é utilizado tanto para passar um valor ao procedimento quanto para retornar um
novo valor ao código chamador. Quando você escreve:

era
X: Inteiro;
começar
X:= 10;
DoubleIt(X);
Mostrar(X.ToString);

o valor da variável X passa a ser 20, porque a função usa uma referência ao local de memória original de
X, afetando seu valor original.

Comparado às regras gerais de passagem de parâmetros, a passagem de valores para parâmetros de


referência está sujeita a regras mais restritivas, visto que o que você está passando não é um valor, mas
uma variável real. Você não pode passar um valor constante como parâmetro de referência, uma
expressão, o resultado de uma função ou uma propriedade. Outra regra é que você não pode passar uma
variável de tipo ligeiramente diferente (exigindo conversão automática). O tipo da variável e o parâmetro
devem corresponder exatamente, ou como diz a mensagem de erro do compilador:

[Erro dcc32] E2033 Os tipos de parâmetros var reais e formais devem ser idênticos

Esta é a mensagem de erro que você receberá se, por exemplo, escrever o seguinte código (que também
faz parte do exemplo ParamsTest , mas comentado):

era
C: Cardeal;
começar
C:= 10;
DuploIt(C);

Passar parâmetros por referência faz sentido para tipos ordinais e para registros (como veremos
veja no próximo capítulo). Esses tipos são frequentemente chamados de tipos de valor porque têm, por
padrão, uma semântica de passagem por valor e de atribuição por valor.

Objetos e strings Object Pascal têm um comportamento ligeiramente diferente que investigaremos com
mais detalhes posteriormente. Variáveis de objeto são referências, portanto você pode modificar os dados
reais de um objeto passado como parâmetro. Esses tipos fazem parte de um grupo diferente,
frequentemente indicado como tipos de referência .

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

110 - 04: procedimentos e funções

Além dos tipos de parâmetro padrão e de referência (var) , Object Pascal também possui um tipo
muito incomum de especificador de parâmetro, out. Um parâmetro out não tem valor inicial e é
usado apenas para retornar um valor. Exceto por não terem um valor inicial, os parâmetros out se
comportam como parâmetros var .

note Os parâmetros out foram introduzidos para suportar o conceito correspondente no Windows' Compo-
modelo de objeto atual (ou COM). Eles podem ser usados para expressar a intenção do desenvolvedor de esperar
valores não inicializados.

Parâmetros Constantes
Como alternativa aos parâmetros de referência, você pode usar um parâmetro const . Como você não
pode atribuir um novo valor a um parâmetro constante dentro da rotina, o compilador pode otimizar a
passagem de parâmetros. O compilador pode escolher uma abordagem semelhante aos parâmetros de
referência (ou uma referência const em termos de C++), mas o comportamento permanecerá semelhante
aos parâmetros de valor, porque o valor original não pode ser modificado pela função.

Na verdade, se você tentar compilar o seguinte código (disponível, mas comentado no


Projeto ParamsTest ), o sistema emitirá um erro:

função DoubleIt (const Valor: Inteiro): Inteiro;


começar
Valor := Valor * 2; // Erro do compilador
Resultado := Valor;
fim;

A mensagem de erro que você verá pode não ser imediatamente intuitiva, pois diz:

[Erro dcc32] E2064 O lado esquerdo não pode ser atribuído a

Parâmetros constantes são bastante comuns para strings, pois neste caso o compilador pode
desabilitar o mecanismo de contagem de referências obtendo uma leve otimização. Este é o motivo
mais comum para usar parâmetros constantes, um recurso que faz sentido limitado para tipos ordinais
e escalares. Parâmetros constantes também não são comumente usados para objetos, porque, em
Object Pascal, quando você passa um objeto como parâmetro constante, é a referência do objeto
que permanece constante, e não o objeto em si. Em outras palavras, o compilador não permitirá
atribuir um novo objeto ao parâmetro, mas permitirá chamar qualquer método do objeto que possa
alterar seus dados.

note Há outra alternativa pouco conhecida para passar um parâmetro const , que é adicionar uma ref
atribua a ele, como em “const [ref]”. Este atributo força o compilador a passar o parâmetro constante
por referência, onde por padrão o compilador escolherá passar um parâmetro constante por valor ou
por referência dependendo do tamanho do parâmetro, com resultado variando dependendo da CPU alvo
e plataforma.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

04: procedimentos e funções - 111

Sobrecarga de função
Às vezes você pode querer ter duas funções muito semelhantes com parâmetros diferentes e uma
implementação diferente. Embora, tradicionalmente, você tenha que criar um nome ligeiramente
diferente para cada função, as linguagens de programação modernas permitem sobrecarregar um
símbolo com múltiplas definições.
A ideia de sobrecarga é simples: o compilador permite definir duas ou mais funções ou procedimentos
usando o mesmo nome, desde que os parâmetros sejam diferentes.
Ao verificar os parâmetros, o compilador pode determinar qual versão da função você está chamando.

Considere esta série de funções extraídas da unidade System.Math da biblioteca de tempo de


execução:

função Min(A,B: Inteiro): Inteiro; sobrecarga;


função Mín(A,B: Int64): Int64; sobrecarga;
função Min(A,B: Único): Único; sobrecarga;
função Min(A,B: Duplo): Duplo; sobrecarga;
função Min(A,B: Estendido): Estendido; sobrecarga;

Quando você chama Min(10, 20), o compilador determina que você está chamando a primeira função
das diversas versões de Min, que recebe dois inteiros e retorna um inteiro.
Existem duas regras básicas de sobrecarga:

• Cada versão de uma função (ou procedimento) sobrecarregada deve ser seguida pelo
palavra-chave sobrecarga (incluindo a primeira versão).
• Entre funções sobrecarregadas, deve haver diferença no número ou em
o tipo dos parâmetros. Os nomes dos parâmetros não são considerados, pois não são
indicados durante a chamada. Além disso, o tipo de retorno não pode ser usado para
distinguir entre duas funções sobrecarregadas.

note Há uma exceção à regra de que você não pode distinguir funções nos valores de retorno e isso se aplica
aos operadores de conversão implícitos e explícitos , abordados no Capítulo 5.

Aqui estão três versões sobrecarregadas de um procedimento ShowMsg que adicionei ao Overload-
Exemplo de teste (um aplicativo demonstrando parâmetros de sobrecarga e padrão):

procedimento ShowMsg(Str: string); sobrecarga;


começar
'
'Mensagem: +Str);
Mostrar(fim;

procedimento ShowMsg(FormatStr: string;


Parâmetros: matriz de const); sobrecarga;
começar
'
Mostrar( 'Mensagem: + Formato(FormatStr, Params));

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

112 - 04: procedimentos e funções

fim;

procedimento ShowMsg(I: Inteiro; Str: string); sobrecarga;


começar
' '
Mostrar(I.ToString + fim; +Str);

As três funções mostram uma caixa de mensagem com uma string, após opcionalmente formatar a string
de diferentes maneiras. Aqui estão as três chamadas dos procedimentos:

Mostrar mensagem( 'Olá' );


Mostrar mensagem( 'Total %d.' = , [100]);
ShowMsg(10, 'MBytes' );

E este é o efeito deles:

Mensagem: Olá
Mensagem: Total = 100.
Mensagem: 10 MBytes

dica A tecnologia Code Parameters do IDE funciona muito bem com procedimentos e funções sobrecarregados.
ções. À medida que você digita o parêntese aberto após o nome da rotina, todas as alternativas disponíveis são listadas.
À medida que você insere os parâmetros, a tecnologia Code Insight usa seu tipo para determinar quais alternativas permanecem válidas.

E se você tentar chamar a função com parâmetros que não correspondem a nenhuma das versões
sobrecarregadas disponíveis? Você receberá uma mensagem de erro, é claro. Suponha que você queira
ligar:

ShowMsg(10.0, 'Olá');

O erro que você verá neste caso é muito específico:

[Erro dcc32] E2250 Não há versão sobrecarregada de 'ShowMsg' que possa ser chamada com esses argumentos

O fato de que cada versão de uma rotina sobrecarregada deve ser marcada adequadamente implica que
você não pode sobrecarregar uma rotina existente da mesma unidade que não esteja marcada com a
palavra-chave sobrecarga .

A mensagem de erro que você recebe ao tentar é:

A declaração anterior de '<nome>' não foi marcada com a diretiva 'overload'.

Você pode, no entanto, criar uma rotina com o mesmo nome daquela que foi declarada em uma unidade
diferente, visto que as unidades atuam como namespaces. Nesse caso, você não está sobrecarregando
uma função com uma nova versão, mas está substituindo a função por uma nova versão, ocultando a
original (que pode ser referenciada usando o prefixo do nome da unidade). Isso é

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

04: procedimentos e funções - 113

por que o compilador não será capaz de escolher uma versão com base nos parâmetros, mas tentará
para corresponder à única versão vista, emitindo um erro se os tipos de parâmetros não corresponderem.

Sobrecarga e chamadas ambíguas


Quando você chama uma função sobrecarregada, o compilador geralmente encontrará uma correspondência
e funcionará corretamente ou emitirá um erro se nenhuma das versões sobrecarregadas tiver os parâmetros
corretos (como acabamos de ver).

Mas há também um terceiro cenário: dado que o compilador pode fazer algumas conversões de tipo para os
parâmetros de uma função, pode haver diferentes conversões possíveis para uma única chamada. Quando o
compilador encontra várias versões de uma função que pode chamar, e não há uma que corresponda
perfeitamente ao tipo (que seria escolhida), ele emite uma mensagem de erro indicando que a chamada
da função é ambígua.

Este não é um cenário comum e tive que construir um exemplo um tanto ilógico para mostrá-lo a vocês, mas
vale a pena considerar o caso (já que acontece ocasionalmente no mundo real).

Suponha que você decida implementar duas funções sobrecarregadas para adicionar inteiros e números de
ponto flutuante:

função Adicionar (N: Inteiro; S: Único): Único; sobrecarga;


começar
Resultado:= N + S;
fim;

função Adicionar (S: Único; N: Inteiro): Único; sobrecarga;


começar
Resultado:= N + S;
fim;

Essas funções estão no exemplo OverloadTest . Agora você pode chamá-los passando os dois parâmetros
em qualquer ordem:

Mostrar(Adicionar(10, 10.0).ToString);
Mostrar(Adicionar(10.0, 10).ToString);

Porém, o fato é que, em geral, uma função pode aceitar um parâmetro de tipo diferente quando há uma
conversão, como aceitar um número inteiro quando a função espera um parâmetro do tipo ponto flutuante.
Então, o que acontece se você ligar:

Mostrar(Adicionar(10, 10).ToString);

O compilador pode chamar a primeira versão da função sobrecarregada, mas também pode chamar a
segunda versão. Não sabendo o que você está pedindo (e não sabendo se chamar uma função ou outra
produzirá o mesmo efeito), irá gerar um erro:

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

114 - 04: procedimentos e funções

[Erro dcc32] E2251 Chamada sobrecarregada ambígua para 'Adicionar'


Método relacionado: função Add(Integer; Single): Single;
Método relacionado: função Add(Single; Integer): Single;

dica No painel de erros do IDE, você verá uma mensagem de erro com a primeira linha acima e um sinal de mais na
lateral que você pode expandir para ver as duas linhas a seguir com os detalhes de quais funções sobrecarregadas
o compilador está considerando ambíguas.

Se este for um cenário do mundo real e você precisar fazer a chamada, você pode adicionar uma chamada
manual de conversões de tipo para resolver o problema e indicar ao compilador qual das versões da função
você deseja chamar:

Mostrar(Adicionar(10, 10.ToSingle).ToString);

Um caso particular de chamadas ambíguas pode acontecer se você usar variantes, um tipo de dados bastante
peculiar que abordarei somente mais adiante neste livro.

Parâmetros padrão

Outra funcionalidade relacionada à sobrecarga é a possibilidade de fornecer um valor padrão para alguns dos
parâmetros de uma função, para que você possa chamar a função com ou sem esses parâmetros. Se o parâmetro
estiver faltando na chamada, ele assumirá o valor padrão.

Deixe-me mostrar um exemplo (ainda parte do exemplo OverloadTest ). Podemos definir o seguinte
encapsulamento da chamada Show , fornecendo dois parâmetros padrão:

procedimento NovaMensagem(Mensagem:
' string; Legenda: string = ); 'Mensagem' ;
Separador: string = início ':

Show(Legenda + Separador + Mensagem);


fim;

Com esta definição, podemos chamar o procedimento de cada uma das seguintes maneiras:

Nova mensagem( 'Algo errado aqui!' );


Nova mensagem( 'Algo errado aqui!' , 'Atenção' );
Nova mensagem( 'Olá' , 'Mensagem' , '--' );

Esta é a saída:

Mensagem: Algo errado aqui!


Atenção: Algo errado aqui!
Mensagem - Olá

Observe que o compilador não gera nenhum código especial para suportar parâmetros padrão; nem cria múltiplas
cópias (sobrecarregadas) das funções ou procedimentos. Os parâmetros ausentes são simplesmente adicionados
pelo compilador ao código de chamada. Há uma restrição importante que afeta o uso de parâmetros padrão: você
não pode “pular” parâmetros.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

04: procedimentos e funções - 115

éteres. Por exemplo, você não pode passar o terceiro parâmetro para a função depois de omitir o
o segundo.

Existem algumas outras regras para a definição e chamadas de funções e procedimentos (e métodos) com
parâmetros padrão:

· Numa chamada só se pode omitir parâmetros a partir do último. Em outras palavras, se você omitir um
parâmetro, também deverá omitir os parâmetros que o seguem.

· Numa definição, os parâmetros com valores padrão devem estar no final dos parâmetros
lista.

· Os valores padrão devem ser constantes. Obviamente, isso limita os tipos que você pode usar com
parâmetros padrão. Por exemplo, um array dinâmico ou um tipo de interface não pode ter um
parâmetro padrão diferente de nulo; os registros não podem ser usados de forma alguma.

· Parâmetros com padrões devem ser passados por valor ou como const. Um parâmetro de referência
(var) não pode ter um valor padrão.

Usar parâmetros padrão e sobrecarga ao mesmo tempo aumenta a probabilidade de você entrar em uma
situação que confunde o compilador, gerando um erro de chamada ambígua , conforme mencionado
na seção anterior. Por exemplo, se eu adicionar a seguinte nova versão do procedimento NewMessage
ao exemplo anterior:

procedimento NovaMensagem(Str: string; I: Inteiro = 0); sobrecarga;


começar
'
Mostrar(Str + ': + IntToStr(I))
fim;

então o compilador não reclamará, pois esta é uma definição legítima. No entanto, se você escrever a
chamada:

Nova mensagem( 'Olá' );

isso é sinalizado pelo compilador como:

[Erro dcc32] E2251 Chamada sobrecarregada ambígua para 'NewMessage'


Método relacionado: procedimento NewMessage(string; string; string);
Método relacionado: procedimento NewMessage(string; Integer);

Observe que esse erro aparece em uma linha de código compilada corretamente antes da nova definição
sobrecarregada. Na prática, não temos como chamar o procedimento NewMessage com um parâmetro
string, pois o compilador não sabe se queremos chamar a versão apenas com o parâmetro string ou
aquela com o parâmetro string e o parâmetro inteiro com um padrão valor. Quando o compilador não
tem certeza, ele para e informa o programador para declarar suas intenções com mais clareza.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

116 - 04: procedimentos e funções

Em linha
Inlining funções e métodos Object Pascal é um recurso de linguagem de baixo nível que pode levar a
otimizações significativas. Geralmente, quando você chama um método, o compilador gera algum código para
permitir que seu programa salte para um novo ponto de execução. Isso implica configurar um quadro de
pilha e realizar mais algumas operações e pode exigir cerca de uma dúzia de instruções de máquina. Entretanto,
o método que você executa pode ser muito curto, possivelmente até mesmo um método de acesso que
simplesmente define ou retorna algum campo privado. Nesse caso,
faz muito sentido copiar o código real para o local da chamada para evitar a configuração do stack frame
e tudo mais. Ao remover essa sobrecarga, seu programa será executado mais rapidamente, principalmente
quando a chamada ocorrer em um loop apertado executado milhares de vezes.

Para algumas funções muito pequenas, o código resultante pode até ser menor, pois o código colado no
local pode ser menor que o código necessário para a chamada da função. No entanto, observe que se uma
função mais longa estiver embutida e essa função for chamada em muitos locais diferentes do seu programa,
você poderá enfrentar inchaço de código, que é um aumento desnecessário no tamanho do arquivo
executável.

No Object Pascal você pode pedir ao compilador para embutir uma função (ou método) com a diretiva
inline , colocada após a declaração da função (ou método). Não é necessário repetir esta directiva na
definição. Tenha sempre em mente que a diretiva inline é apenas uma dica para o compilador, que pode
decidir que a função não é uma boa candidata para inlining e ignorar sua solicitação (sem avisá-lo de forma
alguma). O compilador
também pode incorporar algumas, mas não necessariamente todas, das chamadas da função após analisar
o código de chamada e dependendo do status da diretiva $INLINE no local de chamada. Esta diretiva pode
assumir três valores diferentes (observe que este recurso é independente da opção do compilador de
otimização):

• Com {$INLINE OFF} você pode suprimir o inlining em um programa, em uma parte de um
programa ou para um site de chamada específico, independentemente da presença do inline
diretiva nas funções que estão sendo chamadas.
• Com o valor padrão, {$INLINE ON}, o inlining é ativado para funções marcadas por
a diretiva embutida .
• Com {$INLINE AUTO} o compilador geralmente irá embutir as funções que você marca com a diretiva,
além de embutir automaticamente funções muito curtas. Cuidado porque esta diretiva pode
causar inchaço no código.

Existem muitas funções na Biblioteca Object Pascal Run-Time que foram marcadas como candidatas
in-line. Por exemplo, a função Max da unidade System.Math tem
definições como:

função Max (const A, B: Inteiro): Inteiro; sobrecarga; em linha;

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

04: procedimentos e funções - 117

Para testar o efeito real de incorporar esta função, escrevi o seguinte loop no arquivo
Exemplo de InliningTest :

era
Interruptor: TStopWatch;
I, J: Inteiro;
começar
J:= 0;
Sw := TStopWatch.StartNew;
para I := 0 para LoopCount faça
J := Máx(I, J);
Sw.Parar; '
Mostrar( 'Máx. + J.ToString +
'
[' + Sw.ElapsedMilliseconds.ToString + ']' );
Neste código, o registro TStopWatch da unidade System.Diagnostics , uma estrutura que monitora o
tempo (ou ticks do sistema) decorrido entre as chamadas Start (ou StartNew) e Stop .

O formulário possui dois botões que chamam exatamente o mesmo código, mas um deles tem o inlin-ing
desabilitado no site da chamada. Observe que você precisa compilar com a configuração do Release para
ver qualquer diferença (já que inlining é uma otimização do Release). Com vinte milhões de iterações (o
valor da constante LoopCount ), no meu computador, obtenho os seguintes resultados:

// No Windows (executando em uma VM)


Máximo em 2.000.0000 [17]
Máximo de 2.000.0000 [45]

// No Android (no dispositivo)


Máximo em 2.000.0000 [280]
Máximo de 2.000.0000 [376]
Como podemos interpretar esses dados? No Windows, o inlining mais que dobra a velocidade de
execução, enquanto no Android torna o programa cerca de 35% mais rápido. No entanto, em um
dispositivo o programa roda muito mais devagar (uma ordem de grandeza), então, enquanto no Windows
cortamos 30 milissegundos, no meu dispositivo Android essa otimização economiza cerca de 100
milissegundos.

O mesmo programa faz um segundo teste semelhante com a função Length , uma função mágica do
compilador que foi modificada especificamente para ser incorporada. Novamente, a versão embutida é
significativamente mais rápida no Windows e no Android:

// No Windows (executando em uma VM)


Comprimento embutido 260000013 [11]
Comprimento não embutido 260000013 [40]

// No Android (no dispositivo)


Comprimento embutido 260000013 [401]
Comprimento não embutido 260000013 [474]
Este é o código usado por este segundo loop de teste:

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

118 - 04: procedimentos e funções

era
Interruptor: TStopWatch;
I, J: Inteiro;
Amostra: string;
começar
J:= 0;
Amostra := ; 'sequência de amostra'
Sw := TStopWatch.StartNew;
para I := 0 para LoopCount faça
Inc(J, Comprimento(Amostra));
Sw.Parar;
Mostrar( 'Comprimentonão embutido ' + IntToStr(J) +
'
[' + IntToStr(Sw.ElapsedMilliseconds) + ']' );
fim;

O compilador Object Pascal não define um limite claro para o tamanho de uma função que
pode ser embutido ou uma lista específica de construções (loops for ou while , instruções condicionais) que
impediriam o embutido. No entanto, como incorporar uma função grande oferece poucas vantagens, mas expõe
você ao risco de algumas desvantagens reais (em termos de inchaço de código), você deve evitá-la.

Uma limitação é que o método, ou função, não pode fazer referência a identificadores (como tipos, variáveis
globais ou funções) definidos na seção de implementação da unidade, pois eles não estarão acessíveis no local da
chamada. No entanto, se você estiver chamando uma função local, que também está embutida, o compilador
aceitará sua solicitação para incorporar sua rotina.

Uma desvantagem é que o inlining requer recompilações de unidades mais frequentes, pois, quando você modifica
uma função inline, o código de cada um dos sites de chamada também precisará ser recompilado. Dentro de
uma unidade, você pode escrever o código das funções embutidas antes de chamá-las, mas é melhor colocá-las
no início da seção de implementação.

note O Delphi possui um compilador de passagem única, portanto ele não pode se referir ao código de uma função que ainda não viu.

Dentro de unidades diferentes, você precisa adicionar especificamente outras unidades com funções incorporadas para
suas instruções de uso , mesmo que você não chame esses métodos diretamente. Suponha que sua unidade A
chame uma função embutida definida na unidade B. Se esta função, por sua vez, chamar outra função

embutida na unidade C, sua unidade A também precisará se referir a C. Caso contrário, você verá um aviso do
compilador indicando que a chamada não foi incorporada devido à falta da referência da unidade.
Um efeito relacionado é que as funções nunca são incorporadas quando há referências de unidades circulares

(através de suas seções de implementação).

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

04: procedimentos e funções - 119

Recursos avançados de funções

O que abordei até agora inclui os principais recursos relacionados às funções, mas há
vários recursos avançados que valem a pena explorar. Se você é realmente um novato em termos de
desenvolvimento de software, entretanto, você pode querer pular o resto deste capítulo por enquanto.
e passar para o próximo.

Convenções de chamada Object Pascal


Sempre que seu código chama uma função, os dois lados precisam concordar com a prática real
maneira como os parâmetros são passados do chamador para o receptor, algo chamado convenção de
chamada. Geralmente, uma chamada de função ocorre passando os parâmetros (e esperando
o valor de retorno) através da área de memória da pilha. No entanto, a ordem em que o
Os parâmetros e o valor de retorno são colocados nas alterações da pilha dependendo da linguagem
de programação e da plataforma, com a maioria das linguagens capazes de usar diversas
convenções de chamada diferentes.

Há muito tempo, a versão 32 bits do Delphi introduziu uma nova abordagem para passagem de
parâmetros, conhecida como “fastcall”: sempre que possível, até três parâmetros podem ser passados
nos registradores da CPU, tornando a chamada de função muito mais rápida. Object Pascal usa isso
convenção de chamada rápida por padrão, embora também possa ser solicitada usando a palavra -chave
registrar .

Fastcall é a convenção de chamada padrão e as funções que a utilizam não são compatíveis com
bibliotecas externas, como funções da API do Windows no Win32. As funções da API Win32 devem
ser declaradas usando a convenção de chamada stdcall (chamada padrão) , uma mistura da convenção
de chamada pascal original da API Win16 e a convenção de chamada cdecl da linguagem C. Todas
essas convenções de chamada são suportadas em Object Pascal, mas você raramente usará algo
diferente do padrão, a menos que precise invocar uma biblioteca escrita em uma linguagem diferente,
como uma biblioteca de sistema.

O caso típico em que você precisa se afastar da convenção de chamada rápida padrão é quando
você precisa chamar a API nativa de uma plataforma, o que requer uma convenção de chamada
diferente dependendo do sistema operacional. Mesmo o Win64 usa um modelo diferente do Win32, então
o Object Pascal suporta muitas opções diferentes, não valendo a pena detalhar aqui. Os sistemas
operacionais móveis tendem a expor classes, em vez de funções nativas, embora a questão de respeitar
uma determinada convenção de chamada deva ser levada em consideração mesmo nesses cenários.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

120 - 04: procedimentos e funções

Tipos processuais
Outra característica do Object Pascal é a presença de tipos procedurais. Este é realmente um tópico de
linguagem avançada que apenas alguns programadores usarão. Entretanto, como discutiremos tópicos
relacionados em capítulos posteriores (especificamente, ponteiros de método, uma técnica muito utilizada
pelo ambiente para definir manipuladores de eventos, e métodos anônimos), vale a pena dar uma rápida
olhada neles aqui.

Em Object Pascal (mas não na linguagem Pascal mais tradicional) existe o conceito de um tipo processual
(que é semelhante ao conceito da linguagem C de um ponteiro de função –
(algo que linguagens como C# e Java foram abandonadas porque estão vinculadas a funções e ponteiros
globais). A declaração de um tipo processual indica a lista de parâmetros e, no caso de uma
função, o tipo de retorno. Por exemplo, você pode declarar
um novo tipo processual, com parâmetro Integer passado por referência, com este código:

tipo
TIntProc = procedimento(var Num: Inteiro);

Este tipo processual é compatível com qualquer rotina que tenha exatamente os mesmos parâmetros
(ou a mesma assinatura de função, para usar o jargão C). Aqui está um exemplo de uma rotina
compatível:

procedimento DoubleIt (var Valor: Inteiro);


começar
Valor := Valor * 2;
fim;

Os tipos procedurais podem ser usados para dois propósitos diferentes: você pode declarar variáveis de
um tipo processual ou passar um tipo processual (ou seja, um ponteiro de função) como parâmetro para
outra rotina. Dadas as declarações de tipo e procedimento anteriores, você pode escrever este código:

era
IP: TIntProc;
X: Inteiro;
começar
IP := DoubleIt;
X:= 5;
PI(X);
fim;

Este código tem o mesmo efeito que a seguinte versão mais curta:

era
X: Inteiro;
começar
X:= 5;
DoubleIt(X);
fim;

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

04: procedimentos e funções - 121

A primeira versão é claramente mais complexa, então por que e quando devemos usá-la? Há casos em
que ser capaz de decidir qual função chamar e realmente chamá-la mais tarde
pode ser muito poderoso. É possível construir um exemplo complexo mostrando esta abordagem.
No entanto, prefiro deixar você explorar um exemplo bastante simples, chamado ProcType.

Este exemplo é baseado em dois procedimentos. Um procedimento é usado para dobrar o valor do
parâmetro como o que já mostrei. Um segundo procedimento é usado para triplicar o valor do parâmetro
e, portanto, é denominado TripleIt:

procedimento TripleIt(var Valor: Inteiro);


começar
Valor := Valor * 3;
fim;

Em vez de chamar essas funções diretamente, uma ou outra é salva em uma variável do tipo
processual. A variável é modificada quando o usuário seleciona uma caixa de seleção, e o procedimento
atual é chamado dessa forma genérica quando o usuário clica no botão. O programa utiliza duas
variáveis globais inicializadas (o procedimento a ser chamado e o valor atual), para que esses
valores sejam preservados ao longo do tempo. Este é o código completo, menos as definições dos
procedimentos reais, já mostrados acima:

era
IntProc: TIntProc = DoubleIt;
Valor: Inteiro = 1;

procedimento TForm1.CheckBox1Change(Remetente: TObject);


começar
se CheckBox1.IsChecked então
IntProc := TripleIt
outro
IntProc := DoubleIt;
fim;

procedimento TForm1.Button1Click(Remetente: TObject);


começar
IntProc(Valor);
Mostrar(Value.ToString);
fim;

Quando o usuário altera o status da caixa de seleção, todos os cliques de botão seguintes
chamarão a função ativa. Portanto, se você pressionar o botão duas vezes, alterar a seleção e
pressionar o botão duas vezes novamente, primeiro você dobrará duas vezes e depois triplicará duas
vezes o valor atual, produzindo a seguinte saída:

2
4
12
36

Outro exemplo prático do uso de tipos procedurais é quando você precisa passar uma função para
um sistema operacional como o Windows (onde geralmente são chamados de “call-

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

122 - 04: procedimentos e funções

funções de retorno”). Conforme mencionado no início desta seção, além dos tipos procedurais, os
desenvolvedores de Object Pascal usam ponteiros de método (abordados no Capítulo 10) e métodos
anônimos (abordados no Capítulo 15).

note O mecanismo orientado a objetos mais comum para obter uma chamada de função vinculada tardiamente (que é uma
chamada de função que pode mudar em tempo de execução) é o uso de métodos virtuais. Embora os métodos
virtuais sejam muito comuns no Object Pascal, os tipos procedurais raramente são usados. A base técnica,
porém, é de alguma forma semelhante. Funções virtuais e polimorfismo são abordados no Capítulo 8.

Declarações de funções externas

Outro elemento importante para a programação do sistema é representado pelas declarações externas.
Originalmente usado para vincular código a funções externas que foram escritas em assembly
linguagem, declarações externas tornaram-se comuns na programação do Windows para chamar uma
função de uma DLL (uma biblioteca de vínculo dinâmico). Uma declaração de função externa implica a
capacidade de chamar uma função não totalmente disponível para o compilador ou vinculador, mas requer
a capacidade de carregar uma biblioteca dinâmica externa e invocar uma de suas funções.

note Sempre que você chama uma API para uma determinada plataforma em seu código Object Pascal, você perde a capacidade de
recompilar o aplicativo para qualquer outra plataforma que não a específica. A exceção é se a chamada estiver
cercada por diretivas do compilador $IFDEF específicas da plataforma .

É assim, por exemplo, que você pode invocar funções da API do Windows a partir de uma aplicação Delphi. Se você abrir a
unidade Winapi.Windows você encontrará muitas declarações e definições de funções como:

// Declaração de encaminhamento
função GetUserName(lpBuffer: LPWSTR;
var nSize: DWORD): BOOL; chamada padrão;

de // Declaração externa (em vez de código real)


função GetUserName; advapi32 externo
nome 'GetUserNameW' ;

Você raramente precisa escrever declarações como a que acabamos de ilustrar, pois elas já estão
listadas na unidade Windows e em muitas outras unidades do sistema. A única razão pela qual você
pode precisar escrever esse código de declaração externa é chamar funções de uma DLL personalizada
ou chamar funções do Windows não traduzidas na API da plataforma.
Esta declaração significa que o código da função GetUserName está armazenado na biblioteca
dinâmica advapi32 (advapi32 é uma constante associada ao nome completo da DLL, 'advapi32.dll') com
o nome GetUserNameW, pois esta função API possui tanto um ASCII e uma versão WideString. Dentro
de uma declaração externa, podemos especificar que nosso
function refere-se a uma função de uma DLL que originalmente tinha um nome diferente.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

04: procedimentos e funções - 123

Carregamento atrasado de funções DLL


No sistema operacional Windows, há duas maneiras de invocar uma função API do SDK do Windows (ou
qualquer outra DLL): você pode deixar o carregador de aplicativos resolver todas as referências a funções
externas ou pode escrever um código específico que procura um função e a executa, se disponível.

O código anterior é mais fácil de escrever (como vimos na seção anterior): tudo o que você precisa é da
declaração da função externa. Entretanto, se a biblioteca ou mesmo apenas uma das funções que você
deseja chamar não estiver disponível, seu programa não poderá ser iniciado nas versões do sistema
operacional que não oferecem essa função.

O carregamento dinâmico permite mais flexibilidade, mas implica carregar a biblioteca manualmente, usando
a API GetProcAddress para encontrar a função que deseja chamar e invocá-la após converter o ponteiro para
o tipo adequado. Este tipo de código é bastante complicado e
propenso a erros.

Por isso é bom que o compilador e vinculador Object Pascal tenham suporte específico para um recurso já
disponível no nível do sistema operacional Windows e já utilizado por alguns compiladores C++, o
carregamento atrasado de funções até o momento em que são chamadas.
O objetivo desta declaração não é evitar o carregamento implícito da DLL, que ocorre de qualquer maneira,
mas permitir a ligação atrasada daquela função específica dentro da DLL.

Basicamente, você escreve o código de uma maneira muito semelhante à execução clássica da função DLL,
mas o endereço da função é resolvido na primeira vez que a função é chamada e não no momento do
carregamento. Isso significa que, se a função não estiver disponível, você receberá uma exceção de
tempo de execução, EExternalException. No entanto, geralmente você pode verificar a versão atual do sistema
operacional ou a versão da biblioteca específica que está chamando e decidir
com antecedência, quer você queira fazer a ligação ou não.

note Se você quiser algo mais específico e mais fácil de lidar em nível global do que uma exceção, você pode conectar-
se ao mecanismo de erro para a chamada de carregamento atrasado, conforme explicado por Allen Bauer em
sua postagem no blog: https://blog.therealoracleatdelphi. com/2009/08/excepcional-procrastinação_29.html

Da perspectiva da linguagem Object Pascal, a única diferença está na declaração da função externa. Em
vez de escrever:

função CaixaMensagem;
nome user32 externo 'Caixa de mensagemW' ;

Agora você pode escrever (novamente, a partir de um exemplo real na unidade Windows ):

função GetSystemMetricsForDpi (nIndex: Inteiro; dpi: UINT): Inteiro;


chamada padrão; nome user32 externo 'GetSystemMetricsForDpi' atrasado;

Em tempo de execução, considerando que a API foi adicionada ao Windows 10, versão 1607 pela primeira
vez, talvez você queira escrever um código como o seguinte:

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

124 - 04: procedimentos e funções

if (TOSVersion.Major >= 10) e (TOSVersion.Build >= 14393)


começar
NMetric := GetSystemMetricsForDpi(SM_CXBORDER, 96);

Isso é muito, muito menos código do que você teve que escrever sem atraso no carregamento para poder
executar o mesmo programa em versões mais antigas do Windows.

Outra observação relevante é que você pode usar o mesmo mecanismo ao construir suas próprias DLLs
e chamá-las em Object Pascal, fornecendo um único executável que pode se vincular a múltiplas
versões da mesma DLL, desde que você use o carregamento atrasado para as novas funções.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

05: matrizes e registros - 125

05: matrizes e
registros

Quando apresentei os tipos de dados no Capítulo 2, referi-me ao fato de que no Object Pascal existem
tipos de dados e construtores de tipos integrados. Um exemplo simples de construtor de tipo é o tipo
enumerado, abordado naquele capítulo.

O verdadeiro poder da definição de tipos vem com mecanismos mais avançados, como arrays,
registros e classes. Neste capítulo abordarei os dois primeiros, que em sua essência remontam
à definição inicial de Pascal, mas foram muito alterados ao longo do tempo.
os anos (e se tornaram mais poderosos) que eles mal se assemelham aos seus construtores de tipo
ancestral com o mesmo nome.

No final do capítulo também apresentarei brevemente alguns tipos de dados avançados do Object
Pascal como ponteiros. O verdadeiro poder dos tipos de dados personalizados, entretanto, será
revelado no Capítulo 7, onde começaremos a examinar as classes e a programação orientada a objetos.

Tipos de dados de matriz


Os tipos de array definem listas com elementos de um tipo específico. Estas listas podem ter um
número fixo de elementos (arrays estáticos) ou um número variável de elementos (arrays dinâmicos).

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

126 - 05: matrizes e registros

matrizes). Geralmente você usa um índice entre colchetes para acessar um dos elementos de um array.
Colchetes também são usados para definir o número de valores de uma matriz de tamanho fixo.

A linguagem Object Pascal oferece suporte a diferentes tipos de array, desde arrays estáticos tradicionais
até dinâmicos. O uso de arrays dinâmicos é recomendado, principalmente com as versões móveis do compilador.
Apresentarei primeiro os arrays estáticos e depois focarei nos dinâmicos.

Matrizes estáticas
Os arrays tradicionais da linguagem Pascal são definidos com um tamanho estático ou fixo. Um exemplo está nos
trechos de código a seguir, que definem uma lista de 24 inteiros, apresentando as temperaturas durante as 24
horas do dia:

tipo
TDayTemperatures = array[1..24] de Inteiro;

Nesta definição clássica de array, você pode usar um tipo de subintervalo entre colchetes, definindo na
verdade um novo tipo de subintervalo específico usando duas constantes de um tipo ordinal.
Este subintervalo indica os índices válidos da matriz. Como você especifica o índice superior e o inferior do array,
os índices não precisam ser baseados em zero, como é o caso em C, C++, Java e na maioria das outras
linguagens (embora os arrays baseados em zero também sejam bastante comum em Object Pascal). Observe
também que os índices de array estáticos em Object Pascal podem ser números, mas também outros tipos
ordinais, como caracteres, tipos enumerados e muito mais. No entanto, índices não integrais são bastante
raros.

note Existem linguagens como JavaScript que fazem uso intenso de matrizes associativas. As matrizes Object Pascal
são limitadas a índices ordinais, portanto você não pode usar diretamente uma string como índice. Existem
estruturas de dados prontas para usar nos dicionários de implementação RTL e outras estruturas de dados
semelhantes que fornecem tais recursos. Abordarei-os no capítulo sobre Genéricos, na terceira parte do livro.

Como os índices do array são baseados em subintervalos, o compilador pode verificar seu intervalo. Um
subintervalo constante inválido resulta em um erro em tempo de compilação; e um índice fora do intervalo usado
em tempo de execução resulta em um erro em tempo de execução, mas somente se a opção do compilador
correspondente estiver habilitada.

note Esta é a opção de verificação de intervalo do grupo Erros de tempo de execução da página Compilando da caixa
de diálogo Opções de projeto do IDE. Já mencionei esta opção no Capítulo 2, na seção “Tipos de Subfaixa”.

Usando a definição de array acima, você pode definir o valor de uma variável DayTemp1 do
Digite TDayTemperatures da seguinte maneira (e como fiz no exemplo ArraysTest, do qual os seguintes trechos
de código foram extraídos):

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

05: matrizes e registros - 127

tipo
TDayTemperatures = array[1..24] de Inteiro;

era
DayTemp1: TDayTemperatures;

começar
DayTemp1[1] := 54;
DayTemp1[2] := 52;
...
DayTemp1[24] := 66;

// A seguinte linha causa:


// E1012 A expressão constante viola os limites do subintervalo
// DayTemp1[25] := 67;

Uma maneira padrão de operar em arrays, dada a sua natureza, é usar loops for. Este é um exemplo
de loop usado para exibir todas as temperaturas de um dia:

era
Eu: Inteiro;
começar
para I := 1 a 24 faça
'
Mostrar(I.ToString + ': + DayTemp1[I].ToString);

Embora esse código funcione, codificar os limites do array (1 e 24) está longe de ser o ideal, pois a
definição do array em si pode mudar com o tempo e você pode querer passar a usar um array dinâmico.

Tamanho e limites da matriz


Ao trabalhar com um array, você sempre pode testar seus limites usando as funções padrão Low e High , que
retornam os limites inferior e superior. Usando Baixo e Alto
ao operar em um array é altamente recomendado, especialmente em loops, pois torna o código
independente do intervalo atual do array (que pode ir de zero ao comprimento do array menos um,
pode começar de um e atingir o comprimento do array , ou tenha qualquer outra definição de subfaixa).
Se você alterar posteriormente o intervalo declarado dos índices do array, o código que usa Low e High
funcionará automaticamente, pois seus valores são derivados das definições do array. Se, em vez
disso, você escrever um loop codificando o intervalo de um array, terá que atualizar o código do loop se
o intervalo do array for alterado. Usar Low e High não apenas torna seu código mais fácil de
manter, mas também mais confiável.

note Aliás, não há sobrecarga de tempo de execução para usar Low e High com matrizes estáticas. Eles são
resolvido em tempo de compilação em expressões constantes, não em chamadas de função reais. Essa resolução em tempo
de compilação de expressões e chamadas de função também acontece para muitas outras funções do sistema.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

128 - 05: matrizes e registros

Outra função relevante é Length, que retorna o número de elementos do array. Combinei essas três
funções no código a seguir que calcula e exibe a temperatura média do dia:

era
Eu: Inteiro;
Total: Inteiro;
começar
Total := 0;
para I := Baixo (DayTemp1) para Alto (DayTemp1) faça
Inc(Total, DayTemp1[I]);
Show((Total/Comprimento(DayTemp1)).ToString);

Este código também faz parte do exemplo ArraysTest.

Matrizes estáticas multidimensionais


Uma matriz pode ter mais de uma dimensão, expressando uma matriz ou um cubo em vez de uma lista.
Aqui estão dois exemplos de definições:

tipo
TAllMonthTemps = array[1..24, 1..31] de Inteiro;
TAllYearTemps = array[1..24, 1..31, 1..12] de Inteiro;

Você pode acessar um elemento como este:

era
AllMonth1: TAllMonthTemps;
AllYear1: TAllYearTemps;
começar
TodosMês1[13, 30] := 55; // diaHora, //
TodoAno1[13, 30, 8] := 55; dia, Hora, mês

note Matrizes estáticas ocupam imediatamente muita memória (no caso acima na pilha), que deve ser
evitado. A variável AllYear1 requer 8.928 inteiros, ocupando 4 bytes cada, ou seja, quase 35 KB.
Alocar um bloco tão grande na memória global ou na pilha (como no código de demonstração) é realmente
um erro. Em vez disso, um array dinâmico usa a memória heap e oferece muito mais flexibilidade em termos
de alocação e gerenciamento de memória.

Dado que esses dois tipos de array são construídos nos mesmos tipos principais, é melhor declará-los
usando os tipos de dados anteriores, como no código a seguir:

tipo
TMonthTemps = array[1..31] de TDayTemperatures;
TYearTemps = array[1..12] de TMonthTemps;

Esta declaração inverte a ordem dos índices apresentada acima, mas também permite a atribuição de
blocos inteiros entre variáveis. Vamos ver como você pode atribuir valores individuais:

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

05: matrizes e registros - 129

Mês1[30][14] := 44; // hora


Dia, //
Mês1[30, 13] := 55; hora Dia, //
Ano1[8, 30, 13] := 55; Dia do mês, hora

A importância de usar tipos intermediários reside no fato de que os arrays são compatíveis com o tipo apenas
se eles se referirem exatamente ao mesmo nome de tipo (que é exatamente a mesma definição de
tipo), e não se suas definições de tipo se referirem à mesma implementação.
Esta regra de compatibilidade de tipos é a mesma para todos os tipos no Object Pascal, com apenas
algumas exceções específicas.

Por exemplo, a declaração a seguir copia as temperaturas de um mês para o terceiro mês do ano:

Ano1[3] := Mês1;

Em vez disso, uma declaração semelhante baseada nas definições de array independentes (que não são
compatíveis com o tipo):

TodoAno1[3] := TodoMês1;

causaria o erro:

Erro: Tipos incompatíveis: 'array[1..31] of array[1..12] of Integer' e 'TAllMonthTemps'

Como mencionei, arrays estáticos sofrem problemas de gerenciamento de memória, especificamente quando
você deseja passá-los como parâmetros ou alocar apenas uma parte de um array grande. Além disso, você
não pode redimensioná-los durante a vida útil da variável de matriz. É por isso que é preferível usar arrays
dinâmicos, mesmo que exijam um pouco mais de gerenciamento, como a necessidade de alocar memória.

Matrizes Dinâmicas
No Pascal tradicional, os arrays tinham tamanhos fixos e eram limitados ao número de elementos quando
você declarava o tipo de dados. Object Pascal, entretanto, suporta uma implementação direta e nativa de
arrays dinâmicos.

note “Implementação direta de matrizes dinâmicas” aqui contrasta com o uso de ponteiros e memória dinâmica
alocação para obter um efeito semelhante... com código muito complexo e sujeito a erros. A propósito, arrays
dinâmicos são o único tipo de construção na maioria das linguagens de programação modernas.

Arrays dinâmicos são alocados dinamicamente e as referências são contadas (tornando a passagem de
parâmetros muito mais rápida, pois apenas a referência é passada, e não uma cópia do array completo).
Quando terminar, você pode limpar um array definindo sua variável como nil ou definindo o comprimento
como zero, e como os arrays dinâmicos são contados por referência, o compilador
irá liberar automaticamente a memória para você. Observe que isso é verdade para a memória usada para

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

130 - 05: matrizes e registros

os itens do array: Se o array contém referências a outros locais de memória (como referências de
objetos), você precisa limpar a memória usada por esses objetos antes de liberar o próprio array.

Com um array dinâmico, você declara um tipo de array sem especificar o número de elementos e
então aloca o tamanho do array usando o procedimento SetLength :

era
Array1: array de Inteiros;
começar
// Isso causaria // a erro de verificação de intervalo de tempo de execução
Matriz1[0] 100;:=
SetLength(Array1, 10);
Matriz1[0] := 100; // Isso é OK

Você não pode usar o array antes de atribuir seu comprimento, alocando a memória necessária no
heap. Se você fizer isso, verá um erro de verificação de intervalo (se a opção
correspondente do compilador estiver ativa) ou uma violação de acesso no Windows ou um erro
semelhante de acesso à memória em outra plataforma. A chamada SetLength define todos os valores
como zero. O código de inicialização torna possível começar a ler e escrever valores do array
imediatamente, sem medo de erros de memória (a menos que você viole os limites do array).

Se você precisar alocar memória explicitamente, não precisará liberá-la diretamente. No trecho de
código acima, conforme o código termina e a variável Array1 sai do escopo, o compilador libera
automaticamente sua memória (nesse caso, os dez inteiros que foram alocados). Portanto, embora
você possa atribuir uma variável de matriz dinâmica a nil ou chamar SetLength com 0, isso
geralmente não é necessário (e raramente é feito).
Observe que o procedimento SetLength também pode ser usado para redimensionar um array, sem
perder seu conteúdo atual, se você o estiver aumentando, mas perderia alguns elementos, se o
estivesse diminuindo. Como na chamada inicial de SetLength você indica apenas o número de
elementos do array, o índice de um array dinâmico invariavelmente começa em zero e vai até o número
de elementos menos um. Em outras palavras, os arrays dinâmicos não suportam dois recursos dos
arrays Pascal estáticos clássicos, o limite inferior diferente de zero e os índices não inteiros.
Ao mesmo tempo, eles correspondem mais de perto ao modo como os arrays funcionam na maioria das linguagens
baseado na sintaxe C.

Para consultar o tamanho atual de uma matriz dinâmica, você pode, como acontece com matrizes
estáticas, usar as funções Length, High e Low . Para matrizes dinâmicas, entretanto, Low sempre
retorna 0 e High sempre retorna o comprimento menos um. Isso implica que, para um array vazio,
High retorna -1 (que, pensando bem, é um valor estranho, pois é menor que o retornado por Low).

Assim, por exemplo, no exemplo DynArray , preenchi e extraí as informações de um array dinâmico
usando loops adaptáveis. Esta é a definição de tipo e variável:

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

05: matrizes e registros - 131

tipo
TIntegersArray = array de inteiros;
era
IntArray1: TIntegersArray;

A matriz é alocada e preenchida com valores correspondentes ao índice, usando o seguinte loop:

era
Eu: Inteiro;
começar
SetLength(IntArray1, 20);
para I := Low(IntArray1) to High(IntArray1) faça
IntArray1[I] := I;
fim;

Um segundo botão possui o código para exibir cada valor e calcular a média, semelhante ao do
exemplo anterior, mas contido em um único loop:

era
Eu: Inteiro;
Total: Inteiro;
começar
Total := 0;
para I := Low(IntArray1) to High(IntArray1) faça
começar
Inc(Total, IntArray1[I]); '
Mostrar(I.ToString + ': + IntArray1[I].ToString);
fim; '
'Média: + (Total/Comprimento(IntArray1)).ToString);
Mostrar(fim;

A saída deste código é bastante óbvia (e quase sempre omitida):

0: 0
1: 1
2: 2
3: 3
...
17: 17
18: 18
19: 19
Média: 9,5

Além de Length, SetLength, Low e High, também existem outros procedimentos comuns que
você pode usar em arrays, como a função Copy , que permite copiar uma parte de um array.
(ou tudo isso). Observe que você também pode atribuir um array de uma variável para outra, mas
nesse caso você não está fazendo uma cópia completa, mas sim tendo duas variáveis referentes
ao mesmo array na memória.
O único código um pouco complexo está na parte final do exemplo DynArray , que copia um
array para outro de duas maneiras diferentes:

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

132 - 05: matrizes e registros

· usando a função Copy , que duplica os dados do array em uma nova estrutura de dados
usando uma área de memória separada

· usando o operador de atribuição, que efetivamente cria um alias, ou seja, uma nova variável
capaz de se referir ao mesmo array na memória

Neste ponto, se você modificar um dos elementos dos novos arrays, afetará ou não a versão original
dependendo da forma como fez a cópia. Este é o código completo:

era
IntArray2: TIntegersArray;
IntArray3: TIntegersArray;
começar
// Alias
IntArray2 := IntArray1;

// Cópia separada
IntArray3 := Copiar(IntArray1, Low(IntArray1), Comprimento(IntArray1));

// Modificar itens
IntArray2[1] := 100;
IntArray3[2] := 100;

// Verifique os valores de cada array


-- --
Mostrar(Formato( '[%d] %d %d %d' ,
[1, IntArray1[1], IntArray2[1], IntArray3[1]]));
-- %d -- %d'
Mostrar(Formato( '[%d] %d ,
[2, IntArray1[2], IntArray2[2], IntArray3[2]]));

A saída que você obterá é semelhante a esta:

[1] 100 - 100 - 1


[2] 2 - 2 - 100

As alterações no IntArray2 se propagam para o IntArray1, porque são apenas duas referências
para a mesma matriz física; as alterações no IntArray3 são separadas, porque ele possui uma cópia separada dos
dados.

Operações nativas em arrays dinâmicos


Matrizes dinâmicas têm suporte para atribuição de matrizes constantes e concatenação.

note Essas extensões para arrays dinâmicos foram adicionadas no Delphi XE7.

Na prática, você pode escrever código como o seguinte, que é significativamente simplificado em relação aos
trechos de código anteriores:

era
IN: array de inteiros;

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

05: matrizes e registros - 133

Eu: Inteiro;
começar
DE:= [1, 2, 3]; // Inicialização
DI := DI + DI; // Concatenação
ED := ED + [4, 5]; // Concatenação mista

para eu no fazer
começar
Mostrar(I.ToString);
fim;

Observe o uso de um loop for-in para verificar os elementos da matriz neste código, que faz parte do
exemplo DynArrayConcat . Observe que esses arrays podem ser baseados em qualquer tipo de
dados, desde números inteiros simples, como no código acima, até registros e classes.

Há uma segunda adição que foi feita junto com a atribuição e a concatenação, mas que faz parte da RTL mais do
que da linguagem. Agora é possível usar funções em arrays dinâmicos que eram comuns para strings, como
Insert e Delete.

Isso significa que agora você pode escrever código como o seguinte (parte do mesmo projeto):

era
IN: array de inteiros;
Eu: Inteiro;
começar
DE:= [1, 2, 3, 4, 5, 6];
Inserir([8, 9], DI, 4);
Excluir(DI, 2, 1); // Remover o terceiro item (índice baseado em zero)

Parâmetros de matriz aberta


Existe um cenário muito especial para o uso de arrays, que é passar uma lista flexível de parâmetros
para uma função. Além de passar um array diretamente, há duas convenções de sintaxe especiais
explicadas nesta e na próxima seção. Um exemplo de função que utiliza uma dessas convenções é
a função Format que usei no último trecho de código, que usa uma matriz de valores definida no
local como seu segundo parâmetro.

Ao contrário da linguagem C (e de algumas outras linguagens baseadas na sintaxe C), na linguagem


Pascal tradicional uma função ou procedimento sempre possui um número fixo de parâmetros.
No entanto, em Object Pascal existe uma maneira de passar um número variável de parâmetros
para uma rotina usando um array definido no local, uma técnica conhecida como parâmetros de
array aberto.

note Historicamente, os parâmetros de array aberto são anteriores aos arrays dinâmicos, mas hoje esses dois recursos parecem
tão semelhantes na maneira como funcionam que são quase indistinguíveis atualmente. É por isso que abordei os
parâmetros de array aberto somente depois de discutir os arrays dinâmicos.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

134 - 05: matrizes e registros

A definição básica de um parâmetro de array aberto é a mesma de um parâmetro dinâmico digitado.


tipo de array, prefixado pelo especificador const . Isso significa que você indica o tipo do(s)
parâmetro(s), mas não precisa indicar quantos elementos desse tipo o array terá. Aqui está um
exemplo de tal definição, extraída do OpenArray
exemplo:

função Soma (const A: array de números inteiros): Inteiro;


era
Eu: Inteiro;
começar
Resultado:= 0;
para I := Baixo (A) a Alto (A) faça
Resultado := Resultado + A[I];
fim;

Você pode chamar essa função passando para ela um array de expressões constantes inteiras (que
também pode incluir variáveis como parte das expressões usadas para calcular os valores individuais):

X := Soma([10, Y, 27 * I]);

Dado um array dinâmico de números inteiros, você pode passá-lo diretamente para uma rotina que
requer um parâmetro de array aberto do mesmo tipo base (números inteiros neste caso). Aqui está um
exemplo onde o array completo é passado como parâmetro:

era
Lista: matriz de inteiro;
X, I: Inteiro;
começar
// Inicialize o variedade
SetComprimento(Lista, 10);
para I := Baixo (Lista) para Alto (Lista) faça
Lista[I] := I * 2;
// Obtenha a soma dos elementos da lista usando nossa função
X := Soma(Lista);

Isto é, se você tiver uma matriz dinâmica. Se você tiver um array estático, do tipo base
correspondente, você também pode passá-lo para funções que esperam um parâmetro de array
aberto, ou você pode chamar a função Slice para passar apenas uma parte do array existente
(conforme indicado por seu segundo parâmetro) . O trecho a seguir (também parte do exemplo
OpenArray ) mostra como passar um array estático, ou parte dele, para a função Sum :

era
Lista: array[1..10] de Inteiro;
X, I: Inteiro;
começar
// Inicializa o array
para I := Baixo (Lista) para Alto (Lista) faça
Lista[I] := I * 2;

// Obtenha a soma dos elementos da lista usando nossa função


X := Soma(Lista);

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

05: matrizes e registros - 135

Mostrar(X.ToString);

// Soma porção do variedade


X := Soma(Fatia(Lista, 5));
Mostrar(X.ToString);

Parâmetros de matriz aberta de variante de tipo


Além dos parâmetros de array aberto digitados, a linguagem Object Pascal permite definir arrays abertos
de variante de tipo ou não digitados. Este tipo especial de array possui um número indefinido de elementos,
mas também um tipo de dados indefinido para esses elementos junto com a possibilidade de passar
elementos de diferentes tipos. Esta é uma das áreas da linguagem que não é totalmente segura quanto
ao tipo.

Tecnicamente, você pode definir um parâmetro do tipo array de const para passar um array com um número
indefinido de elementos de diferentes tipos para uma função. Por exemplo, aqui está a definição da função
Format (veremos como usar esta função no Capítulo 6, enquanto abordamos strings, mas já a usei em
algumas demonstrações):

função Formato(formato const: string;


const Args: array de const): string;

O segundo parâmetro é um array aberto, que recebe um número indefinido de valores.


Na verdade, você pode chamar essa função das seguintes maneiras:

N:= 20;
S:= 'Total:' ;
Mostrar(Formato( 'Total: %d' , [N]));
Mostrar(Formato( 'Int: %d, Flutuador: %f' , [N, 12.4]));
Mostrar(Formato( '%s %d' , [S, N * 2]));

Observe que você pode passar um parâmetro como um valor constante, o valor de uma variável ou uma
expressão. Declarar uma função desse tipo é simples, mas como codificá-la?
Como você conhece os tipos de parâmetros? Os valores de um parâmetro de matriz aberta de variante de
tipo são compatíveis com os elementos do tipo TVarRec .

note Não confunda o registro TVarRec com o registro TVarData usado pelo tipo Variant . Estas duas estruturas têm
objectivos diferentes e não são compatíveis. Até mesmo a lista de tipos possíveis é diferente, porque TVarRec
pode conter tipos de dados Object Pascal, enquanto TVarData pode conter tipos de dados OLE do Windows.
As variantes são abordadas posteriormente neste capítulo.

A seguir estão os tipos de dados suportados em um valor de matriz aberta de variante de tipo e pelo
registro TVarRec :

vtInteger vtBoolean vtChar


vtExtended vtString vtPointer
vtPChar vtObject vtClass
vtWideChar vtPWideChar vtAnsiString
vtCurrency vtVariant vtInterface

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

136 - 05: matrizes e registros

vtWideString vtInt64 vtUnicodeString

A estrutura do registro possui um campo com o tipo (VType) e o campo variante que você pode usar
para acessar os dados reais (mais sobre registros em algumas páginas, mesmo que este seja um uso
avançado para essa construção).

Uma abordagem típica é usar uma instrução case para operar nos diferentes tipos de parâmetros que
você pode receber em tal chamada. No exemplo da função SumAll , quero poder
soma valores de diferentes tipos, transformando strings em inteiros, caracteres no valor ordinal
correspondente e Boolean True em 1. O código é certamente bastante avançado (e usa desreferências
de ponteiro), então não se preocupe se não o fizer. compreendê-lo completamente para
agora:

função SumAll (const Args: array de const): Estendido;


era
Eu: Inteiro;
começar
Resultado:= 0;
para I := Baixo (Args) para Alto (Args) faça
case Args[I].VTipo de
vtInteiro:
Resultado := Resultado + Args[I].VInteger;
vtBooleano:
se Args[I].VBoolean então
Resultado := Resultado + 1;
vtExtendido:
Resultado := Resultado + Args[I].VExtended^;
vtWideChar:
Resultado:= Resultado + Ord(Args[I].VWideChar);
vtMoeda:
Resultado:= Resultado + Args[I].VCurrency^;
fim; // Caso
fim;

Adicionei esta função ao exemplo do OpenArray , que a chama da seguinte forma:

era
X: Estendido;
Y: Inteiro;
começar
E := 10;
Y, 'k'
X := SomaTodos([Y * , Verdadeiro, 10,34]);
'
Mostrar(+'SomaTudo:
X.ToString);
fim;

A saída desta chamada adiciona o quadrado de Y, o valor ordinal de K (que é 107), 1 para o valor
booleano e o número estendido, resultando em:

SomaToda: 218,34

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

05: matrizes e registros - 137

Registrar tipos de dados


Enquanto os arrays definem listas de itens idênticos referenciados por um índice numérico, os registros
definem grupos de elementos de diferentes tipos referenciados por nome. Em outras palavras, um
registro é uma lista de itens nomeados, ou campos, cada um com um tipo de dados específico. A definição
de um tipo de registro lista todos esses campos, dando a cada campo um nome usado para se referir a ele.
Enquanto nos primeiros dias de Pascal os registros tinham apenas campos, agora eles também podem ter métodos e
operadores, como veremos neste capítulo.

note Os registros estão disponíveis na maioria das linguagens de programação. Eles são definidos com a palavra-
chave struct na linguagem C, enquanto C++ tem uma definição estendida incluindo métodos, assim como
Object Pascal. Algumas linguagens orientadas a objetos mais “puras” têm apenas a noção de classe, não de
registro ou estrutura, mas C# reintroduziu recentemente o conceito.

Aqui está um pequeno trecho de código (do exemplo RecordsDemo ) com a definição de um tipo de
registro, a declaração de uma variável desse tipo e algumas instruções usando esta variável:

tipo
TMyDate = registro
Ano: Inteiro;
Mês: Byte;
Dia: Byte;
fim;

era
Aniversário: TMyDate;
começar
Aniversário.Ano:= 1997;
ABirthday.Month := 2;
ABirthday.Day := 14; '
Mostrar( 'Nasceu em ano + ABirthday.Year.ToString);

nota O termo registros é às vezes usado de forma um tanto vaga para se referir a dois elementos diferentes da linguagem.
guage: uma definição de tipo de registro e uma variável de tipo de registro (ou instância de registro). Registro é usado
como sinônimo de tipo de registro e instância de registro, diferentemente dos tipos de classe, caso em que a instância é
chamada de objeto.

Há muito mais nesta estrutura de dados em Object Pascal do que uma simples lista de campos, como
ilustrará a parte restante deste capítulo, mas vamos começar com esta estrutura tradicional
abordagem aos registros. A memória para um registro geralmente é alocada na pilha para uma variável
local e na memória global para uma variável global. Isso é destacado por uma chamada para SizeOf, que
retorna o número de bytes exigidos por uma variável ou tipo, como neste
declaração:

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

138 - 05: matrizes e registros

'
Mostrar( 'O tamanho do registro é + SizeOf(ABirthday).ToString);

que no Win32 com configurações padrão do compilador retorna 8 (por que retorna 8 e não 6 – 4 bytes
para o número inteiro e dois para cada campo de byte – é algo que discutirei na seção Alinhamentos
de campos).

Em outras palavras, os registros são tipos de valor. Isto implica que se você atribuir um registro
a outro, estará fazendo uma cópia completa. Se você fizer uma alteração em uma cópia, o registro
original não será afetado. Este trecho de código explica o conceito em termos de código:

era
Aniversário: TMyDate;
ADay: TMyDate;
começar
Aniversário.Ano:= 1997;
ABirthday.Month := 2;
ABirthday.Day := 14;
ADia := Aniversário;
ADia.Ano := 2008;

Show(MyDateToString(ABirthday));
Show(MyDateToString(ADay));

A saída (em formato de data japonês ou internacional) é:

14/02/1997
2008.2.14

A mesma operação de cópia ocorre quando você passa um registro como parâmetro para uma
função, como no MyDateToString que usei acima:

função MinhaDateToString(MinhaData: TMyDate): string;


começar
Resultado:= MinhaData.Ano.ToString + '.' +
MinhaData.Mês.ToString + '.' +
MinhaData.Dia.ToString;
fim;

Cada chamada para esta função envolve uma cópia completa dos dados do registro. Para evitar a
cópia e possivelmente fazer uma alteração no registro original, você deve usar explicitamente um
parâmetro de referência. Isto é destacado pelo seguinte procedimento:

procedimento AumentaAno(var MinhaData: TMyDate);


começar
Inc(MinhaData.Ano);
fim;

era
ADay: TMyDate;
começar
ADia.Ano := 2020;
ADia.Mês := 3;
ADia.Dia := 18;

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

05: matrizes e registros - 139

AumentoAno(ADia);
Show(MyDateToString(ADay));

Este tem o valor do campo Ano , do registro original, aumentado pela chamada do procedimento, a
saída final é um ano depois da entrada:

2021.3.18

Usando matrizes de registros


Como mencionei, arrays representam uma estrutura de dados repetida diversas vezes, enquanto
registram uma única estrutura com elementos diferentes. Dado que esses dois tipos de
construtores são ortogonais, é muito comum usá-los juntos, definindo arrays de registros (embora seja
possível, mas incomum, ver registros de arrays).
O código do array é igual ao de qualquer outro array, com cada elemento do array assumindo o
tamanho do tipo de registro específico. Embora veremos mais tarde como usar coleções mais
sofisticadas ou classes de contêiner (para listas de elementos), há muito que você pode
conseguir em termos de gerenciamento de dados com matrizes de registros.

No exemplo RecordsTest adicionei um array do tipo TMyDate , que pode ser alocado, inicializado e
usado com código como o seguinte:

era
DateList: array de TMyDate;
Eu: Inteiro;
começar
// Alocar elementos da matriz
SetLength(ListaDatas, 5);

// Atribuir valores aleatórios aos elementos da matriz


para I := Low(DatesList) to High(DatesList) faça
começar
ListaDatas[I].Ano := 2000 + Aleatório(50);
ListaDatas[I].Mês := 1 + Aleatório(12);
ListaDatas[I].Dia := 1 + Aleatório(27);
fim;

// Exibem o valores de matriz


para I := Low(DatesList) to High(DatesList) faça
Show(I.ToString + ': ' +
MyDateToString(DatesList[I]));

Dado que o aplicativo usa dados aleatórios, a saída será diferente a cada vez, mas pode ser como
a seguinte que capturei:

0: 8.11.2014
1: 14/09/2005
2: 2037.9.21

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

140 - 05: matrizes e registros

3: 2029.3.12
4: 2012.7.2

note Os registros em uma matriz podem ser inicializados automaticamente no caso de um registro gerenciado, um recurso introduzido
no Delphi 10.4 Sydney que abordarei mais adiante neste capítulo.

Registros de variantes

Desde as primeiras versões da linguagem, os tipos de registro também podem ter uma parte variante; isto
é, vários campos podem ser mapeados para a mesma área de memória, mesmo que tenham tipos de
dados diferentes. (Isso corresponde a uma união na linguagem C.) Alternativamente, você pode usar
esses campos variantes, ou grupos de campos, para acessar o mesmo local de memória dentro de um
registro, mas considerando esses valores de perspectivas diferentes (em termos de tipos de dados).
Os principais usos deste tipo eram armazenar dados semelhantes, mas diferentes, e obter
um efeito semelhante ao da typecasting (algo usado nos primórdios da linguagem, quando a typecasting
direta não era permitida). O uso de tipos de registro variantes foi amplamente substituído por técnicas
orientadas a objetos e outras técnicas modernas, embora algumas bibliotecas de sistema as utilizem
internamente em casos especiais.

O uso de um tipo de registro variante não é seguro para o tipo e não é uma prática de programação
recomendada, especialmente para iniciantes. De qualquer forma, você não precisará enfrentá-los até que
seja realmente um especialista em Object Pascal... e é por isso que decidi evitar mostrar amostras reais e
cobrir esse recurso com mais detalhes. Se você realmente quer uma dica, dê uma
veja o uso do TvarRec que fiz na demonstração da seção “Parâmetros do Type-Variant Open Array”.

Alinhamentos de Campos
Outro tópico avançado relacionado aos registros é a forma como seus campos são alinhados, o que
também ajuda a entender o tamanho real de um registro. Se você examinar as bibliotecas, verá
frequentemente o uso da palavra-chave compactada aplicada aos registros: isso implica que o registro
deve usar a quantidade mínima possível de bytes, mesmo que isso resulte em operações de acesso a
dados mais lentas.

A diferença está tradicionalmente relacionada ao alinhamento de 16 ou 32 bits dos vários campos,


de modo que um byte seguido por um número inteiro pode acabar ocupando 32 bits, mesmo que apenas
8 sejam usados. Isso ocorre porque acessar o seguinte valor inteiro no limite de 32 bits torna a
execução do código mais rápida.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

05: matrizes e registros - 141

note O tamanho e o alinhamento dos campos dependem do tamanho do tipo. Para qualquer tipo com tamanho diferente de potência de 2
(ou 2^N) o tamanho é a próxima potência maior de 2. Assim, por exemplo, o tipo Extended , que usa 10 bytes,
em um registro ocupa 16 bytes (a menos que o registro esteja compactado).

Em geral, o alinhamento de campos é usado por estruturas de dados, como registros, para melhorar
a velocidade de acesso a campos individuais para algumas arquiteturas de CPU. Existem diferentes
parâmetros que você pode aplicar à diretiva do compilador $ALIGN para alterá-la.

Com {$ALIGN 1} o compilador economizará no uso de memória usando todos os bytes possíveis, como quando
você usa o especificador compactado para um registro. No outro extremo, o {$ALIGN
16} usará o maior alinhamento. Outras opções usam alinhamentos 4 e 8.

Por exemplo, se eu voltar ao projeto RecordsTest e adicionar a palavra-chave compactada à definição do


registro:

tipo
TMyDate = registro compactado
Ano: Inteiro;
Mês: Byte;
Dia: Byte;
fim;

a saída para a chamada SizeOf agora retornará 6 em vez de 8.

Como um exemplo mais avançado, que você pode pular se ainda não for um desenvolvedor fluente em Object
Pascal, vamos considerar a seguinte estrutura (disponível no AlignTest
exemplo):

tipo
TMyRecord = registro
C:Bite;
W: Palavra;
B: Booleano;
Eu: Inteiro;
D: Duplo;
fim;

Com {$ALIGN 1} a estrutura ocupa 16 bytes (valor retornado por SizeOf) e os campos estarão nos
seguintes endereços de memória relativa:

C: 0; W: 1; B: 3; Eu: 4; D: 8

note Endereços relativos são calculados alocando o registro e calculando a diferença entre o valor numérico de
um ponteiro para a estrutura e o de um ponteiro para o campo determinado, com uma expressão como:
UIntPtr(@MyRec.w) – UintPtr(@MyRec1) ). O conceito de ponteiros e o endereço do operador (@) serão
abordados posteriormente neste capítulo.

Por outro lado, se você alterar o alinhamento para 4 (o que pode levar a um acesso otimizado aos dados),
o tamanho será de 20 bytes e os endereços relativos:

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

142 - 05: matrizes e registros

C: 0; W: 2; B: 4; Eu: 8; D: 12

Se você for ao extremo e usar {$ALIGN 16}, a estrutura requer 24 bytes e mapeia os campos da seguinte
forma:

C: 0; W: 2; B: 4; Eu: 8; D: 16

E quanto à declaração With?

Outra instrução de linguagem tradicional usada ao trabalhar com registros ou classes é a instrução with .
Esta palavra-chave costumava ser peculiar à sintaxe Pascal, mas foi introduzida posteriormente em
JavaScript e Visual Basic. Esta é uma palavra-chave que pode ser muito útil para escrever menos
código, mas também pode se tornar muito perigosa, pois torna o código muito menos legível.

Você encontrará muito debate em torno da declaração with , e tendo a concordar que ela deve ser usada
com moderação, se for o caso. De qualquer forma, achei importante incluí-lo neste livro de qualquer
maneira (diferentemente das declarações goto ).

note Há algum debate sobre se fará sentido remover as instruções goto da linguagem Object Pascal, e
também foi discutido se deve ser removido with da versão móvel da linguagem. Embora existam
alguns usos legítimos, dados os problemas de escopo que as instruções podem causar, há bons
motivos para descontinuar esses recursos (ou alterá-los para que um nome alternativo seja
necessário, como em C#).

A instrução with nada mais é do que uma abreviação. Quando você precisa consultar um registro
variável de tipo (ou um objeto), em vez de repetir seu nome todas as vezes, você pode usar uma
instrução with . Por exemplo, ao apresentar o tipo de registro, escrevi este código:

era
Aniversário: TMyDate;
começar
Aniversário.Ano := 2008;
ABirthday.Month := 2;
ABirthday.Day := 14;

Usando uma instrução with , eu poderia modificar a parte final deste código, da seguinte forma:

com um aniversário fazer


começar
Ano:= 2008;
Mês := 2;
Dia := 14;
fim;

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

05: matrizes e registros - 143

Essa abordagem pode ser usada em programas Object Pascal para fazer referência a componentes e outras
classes. Quando você trabalha com componentes ou classes em geral, a instrução with permite pular a
gravação de algum código, principalmente para estruturas de dados aninhadas.

Então, por que não estou incentivando o uso da instrução with ? A razão é que isso pode levar a erros sutis
que são muito difíceis de capturar, como mascarar outra variável. Embora alguns desses erros difíceis de
encontrar não sejam fáceis de explicar neste ponto do livro, vamos considerar um cenário moderado, que
ainda pode fazer você coçar a cabeça. Este é um recorde
type e algum código usando-o:

tipo
TMyRecord = registro
MeuNome: string;
MeuValor: Inteiro;
fim;

procedimento TForm1.Button2Click(Remetente: TObject);


era
Registro1: TMyRecord;
começar
com Record1 faça
começar
MeuNome := 'Joe';
MeuValor := 22;
fim;

com Record1 faça


'
Mostrar(Nome + ': + MeuValor.ToString);

Certo? O aplicativo compila e executa, mas sua saída não é o que você esperava (pelo menos à primeira
vista):

Formulário1: 22

A parte da string da saída não é o valor do registro definido anteriormente. O motivo é que a segunda
instrução with usa erroneamente o campo Name , que não é o campo de registro, mas outro campo que está
no escopo (especificamente o nome do objeto de formulário do qual o método Button2Click faz parte).

Se você tivesse escrito:


'
Mostrar (Registro1.Nome + ': + Record1.MyValue.ToString);

o compilador teria mostrado uma mensagem de erro, indicando a estrutura de registro fornecida
não tem um campo Nome .

Em geral, podemos dizer que, como a instrução with introduz novos identificadores no escopo atual,
podemos ocultar os identificadores existentes ou acessar indevidamente outro identificador no mesmo
escopo. Esta é uma boa razão para desencorajar o uso do com
declaração. Ainda mais, você deve evitar usar múltiplas instruções with , como:

com MyRecord1, MyDate1 faz...

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

144 - 05: matrizes e registros

O código seguinte provavelmente seria altamente ilegível, porque para cada campo usado no bloco você
precisaria pensar a qual registro ele se refere.

Registros com Métodos

No Object Pascal os registros são mais poderosos que na linguagem Pascal original ou que
estruturas estão na linguagem C. Os registros, na verdade, podem, assim como as classes (que discutiremos
mais adiante), ter métodos, ou seja, procedimentos e funções, associados a eles. Eles podem até redefinir os
operadores de idioma de maneira personalizada (um recurso chamado sobrecarga de operador), como você
verá na próxima seção.

Um registro com métodos é um pouco semelhante a uma classe, com a diferença mais importante sendo a
maneira como essas duas estruturas gerenciam a memória. Os registros em Object Pascal possuem duas
características fundamentais das linguagens de programação modernas:

· Métodos, que são funções e procedimentos relacionados aos dados do registro


estrutura e tendo acesso direto aos campos de registro. Em outras palavras, os métodos são
função e procedimentos declarados (ou tendo uma declaração direta) dentro da definição do tipo de
registro.

· Encapsulamento, que é a capacidade de ocultar o acesso direto a alguns campos (ou métodos) de uma estrutura
de dados do restante do código. Você pode obter encapsulamento
usando o especificador de acesso privado , enquanto os campos e métodos que você deseja que fiquem
visíveis para o exterior sejam marcados como públicos. O especificador padrão para um registro é público.

Agora que você tem os conceitos básicos sobre registros estendidos, vamos dar uma olhada na definição de um
registro de amostra, retirado do exemplo RecordMethods :

tipo
TMyRecord = registro
privado
NomeF: string;
FValor: Inteiro;
FAlgumChar: Char;
público
procedimento Imprimir;
procedimento SetValue(NewString: string);
procedimento Init(NovoValor: Inteiro);
fim;

Você pode ver que a estrutura de registro está dividida em duas partes, privada e pública. Você pode ter várias
seções, já que as palavras-chave privada e pública podem ser repetidas quantas vezes você quiser, mas uma
divisão clara dessas duas seções certamente ajuda na legibilidade.
Os métodos são listados na definição do registro (como em uma definição de classe) sem sua
código de implementação. Em outras palavras, possui uma declaração direta do método.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

05: matrizes e registros - 145

Como você escreve o código real de um método, ou seja, sua implementação ou definição completa? Quase
da mesma forma que você codificaria uma função ou procedimento global. O
as diferenças estão na maneira como você escreve o nome do método, que é uma combinação do nome
do tipo de registro e do nome do método. Então, dentro da implementação, você pode consultar diretamente
os campos e os demais métodos do registro, sem a necessidade de escrever o nome do registro:

procedimento TMyRecord.SetValue(NewString: string);


começar
FNome := NovaString;
fim;

dica Embora possa parecer tedioso ter que escrever primeiro a definição do método e depois sua declaração
completa, você pode usar a combinação Ctrl+Shift+C no editor IDE para gerar o código automaticamente.
Além disso, você pode usar as teclas Ctrl+Shift+Setas para cima/para baixo para passar de uma declaração de método
para a definição correspondente e vice-versa.

Aqui está o código dos outros métodos deste tipo de registro:

procedimento TMyRecord.Init(NovoValor: Inteiro);


começar
FValor := NovoValor;
FSomeChar := 'A';
fim;

função TMyRecord.ToString: string;


começar
Resultado := FNome +
' +FSomeChar + '
[' ']: + FValue.ToString;
fim;

Aqui está um trecho de exemplo de como você pode usar esse registro:

era
MeuRec: TMyRecord;
começar
MeuRec.Init(10);
'Olá' );
MyRec.SetValue(Show(MyRec.ToString);

Como você deve ter adivinhado, a saída será:

Olá [A]: 10

Agora, e se você quiser usar os campos do código que usa o registro, como no snippet acima:

MeuRec.FValue := 20;

Na verdade, isso compila e funciona, o que pode ser surpreendente, pois declaramos o campo em
a seção privada, para que apenas os métodos de registro possam acessá-la.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

146 - 05: matrizes e registros

A verdade é que no Object Pascal o especificador de acesso privado está habilitado apenas entre unidades
diferentes, então aquela linha não seria legal em uma unidade diferente, mas pode ser usada na unidade
que originalmente definiu o tipo de dados. Como veremos, isso também se aplica às classes.

Eu: a magia por trás dos registros


Suponha que você tenha dois registros, como MyRec1 e MyRec2 do mesmo tipo de registro. Quando você
chama um método e executa seu código, como o método sabe com qual das duas cópias do registro deve
trabalhar? Nos bastidores, quando você define um método, o compilador adiciona um parâmetro oculto a
ele, uma referência ao registro ao qual você aplicou o método.

Em outras palavras, a chamada ao método acima é convertida pelo compilador mais ou menos assim:

// Você escreve
MeuRec.SetValue( 'Olá' );

// O compilador gera
SetValue(@MyRec, 'Olá' );

Neste pseudocódigo, @ é o endereço do operador, usado para obter a localização da memória de uma
instância de registro.

note Novamente, o endereço do operador é brevemente abordado no final deste capítulo na seção avançada
intitulado “E quanto aos ponteiros?”

É assim que o código de chamada é traduzido, mas como a chamada do método real pode consultar e usar esse parâmetro
oculto? Usando implicitamente um identificador especial chamado Self. Portanto, o código do método poderia ser escrito
como:

procedimento TMyRecord.SetValue(NewString: string);


começar
Self.FName := NewString;
fim;

Enquanto esse código é compilado, faz pouco sentido usar Self explicitamente, a menos que você precise se
referir ao registro como um todo, por exemplo, passando o registro como parâmetro para outra função. Isso
acontece com mais frequência com classes, que possuem exatamente os mesmos valores ocultos.
parâmetro para métodos e o mesmo identificador próprio .

Uma situação em que o uso de um parâmetro Self explícito pode tornar o código mais legível (mesmo que
não seja obrigatório) é quando você está manipulando uma segunda estrutura de dados do mesmo tipo, como
no caso de estar testando um valor de outra instância :

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

05: matrizes e registros - 147

função TMyRecord.IsSameName(ARecord: TMyRecord): Boolean;


começar
Resultado:= Self.FName = ARecord.FName;
fim;

note O parâmetro Self “oculto” é chamado assim em C++ e Java, mas é chamado Self em Objective-C (e em Object
Pascal, é claro).

Inicializando Registros
Quando você define uma variável de um tipo de registro (ou uma instância de registro) como uma variável
global, seus campos são inicializados; mas não quando você define um na pilha (como uma variável local de
uma função ou procedimento). Então, se você escrever um código como este (também parte do RecordMethods
exemplo):

era
MeuRec: TMyRecord;
começar
Mostrar(MyRec.ToString);

sua saída será mais ou menos aleatória. Enquanto a string é inicializada como uma string vazia, o
campo de caractere e o campo inteiro do registro terão os dados que estavam no local de memória
determinado (assim como acontece em geral para um caractere ou variável inteira no pilha). Em geral,
você obteria resultados diferentes dependendo da compilação ou execução real, como:

[ÿ]: 1637580

É por isso que é importante inicializar um registro (como a maioria das outras variáveis) antes de usá-lo.
para evitar o risco de leitura de dados ilógicos, que podem até travar o aplicativo. Existem duas abordagens
radicalmente diferentes para lidar com esse cenário. A primeira é o uso de construtores para registros,
conforme abordado na próxima seção. A segunda é o uso
de registros gerenciados, um novo recurso do Delphi 10.4 que abordarei mais adiante neste capítulo.

Registros e Construtores

Vamos começar com construtores regulares. Os registros suportam um tipo especial de métodos chamados
construtores que você pode usar para inicializar os dados do registro. Diferentemente de outros métodos,
os construtores também podem ser aplicados a um tipo de registro para definir uma nova instância (mas eles
ainda pode ser aplicado a uma instância existente). É assim que você pode adicionar um construtor a um registro:

tipo

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

148 - 05: matrizes e registros

TMyNewRecord = registro
público
construtor Criar(NewString: string);
função ToString: string;

O construtor é um método com código:

construtor TMyNewRecord.Create(NewString: string);


começar
Nome := NovaString;
Calor(0);
fim;

Agora você pode inicializar um registro com qualquer um dos dois estilos de codificação a seguir:

era
MyRec, MyRec2: TMyNewRecord;
começar
MyRec := 'Eu mesmo');
TMyNewRecord.Create(MyRec2.Create();
'Eu mesmo'

Observe que os construtores de registros devem ter parâmetros: Se você tentar declarar apenas Create
você receberá a mensagem de erro “Construtores sem parâmetros não permitidos em tipos de registro”.

note Você pode ter vários construtores Create sobrecarregados ou vários construtores com nomes diferentes.
Abordarei isso com mais detalhes ao discutir construtores para classes. Os registros gerenciados, como veremos em
breve, usam uma sintaxe diferente e não introduzem construtores sem parâmetros, mas sim um método de
classe Initialize .

Operadores ganham novo terreno


Outro recurso da linguagem Object Pascal relacionado a registros é a sobrecarga de operadores; isto é, a
capacidade de definir sua própria implementação para operações padrão (adição, multiplicação,
comparação e assim por diante) em seus tipos de dados. A ideia é que você possa implementar um
operador add (um método Add especial ) e então usar o sinal + para chamá-lo. Para definir um operador,
você usa a combinação de palavras-chave do operador de classe .

note Ao reutilizar palavras reservadas existentes, os designers da linguagem conseguiram não causar impacto no
código existente. Isso é algo que eles fizeram recentemente em combinações de palavras-chave como strict
private, class operator e class var.

O termo classe aqui se refere a métodos de classe, um conceito que exploraremos muito mais tarde (no
Capítulo 12). Após a diretiva você escreve o nome do operador, como Add:

tipo
TPointRecord = registro
público

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

05: matrizes e registros - 149

operador de classe Add(A, B: TPointRecord): TPointRecord;

O operador Add é então chamado com o símbolo +, como seria de esperar:

era
A, B, C: TPointRecord;
começar
C:= A + B;

Então, quais são os operadores disponíveis? Basicamente, todo o conjunto de operadores da linguagem,
já que você não pode definir novos operadores de linguagem:

• Operadores de conversão: implícitos e explícitos


• Operadores Unários: Positivo, Negativo, Inc, Dec, LogicalNot, BitwiseNot,
Trunc e redondo

• Operadores de comparação: Equal, NotEqual, GreaterThan, GraterThanOrE-


qual, LessThan e LessThenOrEqual

• Operadores Binários: Adicionar, Subtrair, Multiplicar, Dividir, IntDivide, Módulo,


ShiftLeft, ShiftRight, LogicalAnd, LogicalOr , LogicalXor, BitwiseAnd, Bit-wiseOr e BitwiseXor.

• Operadores de registros gerenciados: inicializar, finalizar, atribuir (consulte a seção a seguir


“Operadores e registros gerenciados personalizados” para obter informações específicas
sobre esses três operadores adicionados no Delphi 10.4)
No código que chama o operador, você não usa esses nomes, mas sim o símbolo correspondente.
Você usa esses nomes especiais apenas na definição, com o operador de classe
prefixo para evitar qualquer conflito de nomenclatura. Por exemplo, você pode ter um registro com um Adicionar
método, bem como um operador Add sem resultar em um conflito de nomenclatura.

Ao definir esses operadores, você especifica os parâmetros e, em seguida, o operador é aplicado


somente quando a “chamada” corresponde exatamente aos parâmetros. Para adicionar dois valores
de tipos diferentes, você terá que especificar duas operações Add diferentes , pois cada operando
pode ser a primeira ou a segunda entrada da expressão. Na verdade, a definição de operadores não
prevê comutatividade automática. Além disso, você deve indicar o tipo com muita precisão, pois as
conversões automáticas de tipo não se aplicam. Muitas vezes isto implica definir múltiplas versões
sobrecarregadas do operador, com diferentes tipos de parâmetros.

Outro fator importante a ser observado é que existem dois operadores especiais que você pode
definir para conversão de dados, Implícito e Explícito. O primeiro é usado para definir uma
conversão implícita de tipo (ou conversão silenciosa), que deve ser perfeita e sem perdas. O segundo,
Explícito, pode ser invocado apenas com um tipo explícito convertido de uma variável de um tipo para
outro tipo determinado. Juntos, esses dois operadores definem as conversões permitidas de e para um
determinado tipo de dados.

Observe que os operadores Implícito e Explícito podem ser sobrecarregados com base no tipo de retorno
da função, o que geralmente não é possível para métodos sobrecarregados. Em

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

150 - 05: matrizes e registros

No caso de uma conversão de tipo, na verdade, o compilador conhece o tipo resultante esperado e
pode descobrir qual é a operação de conversão de tipo a ser aplicada. Como exemplo, escrevi o
exemplo OperatorsOver , que define um registro com alguns operadores:

tipo
TPointRecord = registro privado

X, Y: Inteiro;

procedimento público SetValue(X1, Y1: Inteiro); operador de


classe Add(A, B: TPointRecord): TPointRecord; operador de classe Explícito (A:
TPointRecord): string; operador de classe Implícito (X1: Inteiro): TPointRecord;
fim;

Aqui está a implementação dos métodos de registro:

operador de classe TPointRecord.Add(A, B: TPointRecord): TPointRecord; começar Resultado.X := AX +


BX;
Resultado.Y := AY + BY; fim;

operador de classe TPointRecord.Explicit(A: TPointRecord): string; começar Resultado :=

Formato('(%d:%d)', [AX, AY]); fim;

operador de classe TPointRecord.Implicit(X1: Integer): TPointRecord; começar Resultado.X := X1;

Resultado.Y := 10; fim;

Usar esse registro é bastante simples, pois você pode escrever um código como este:

procedimento TForm1.Button1Click(Remetente: TObject);


era
A, B, C: TPointRecord; comece

A.SetValue(10, 10); B:= 30; C:=


A + B;
Mostrar(string(C));
fim;

A segunda atribuição (B := 30;) é feita usando os operadores implícitos, devido à falta de conversão,
enquanto a chamada Show usa a notação de conversão para ativar uma conversão de tipo explícita.
Considere também que o operador Add não modifica seus parâmetros; em vez disso, ele retorna
um valor totalmente novo.

note O fato de os operadores retornarem novos valores é o que torna mais difícil pensar na sobrecarga do operador para
Aulas. Se o operador criar novos objetos temporários, quem irá descartá-los?

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

05: matrizes e registros - 151

Operadores sobrecarregando nos bastidores


Esta é uma seção curta e bastante avançada, você pode querer pular na primeira leitura

Um fato pouco conhecido é que é tecnicamente possível chamar um operador usando seu nome interno
totalmente qualificado (como &&op_Addition), prefixando-o com duplo &, em vez de usar o símbolo do
operador. Por exemplo, você pode reescrever a soma dos registros da seguinte maneira (veja a demonstração
para obter a listagem completa):

C := TPointRecord.&&op_Addition(A, B);

embora eu possa ver poucos casos marginais em que você possa querer fazer isso. (Todo o propósito de
definir operadores é ser capaz de usar uma notação mais amigável do que um nome de método normal ou aquele
mais feio gerado por chamada direta.)

Implementando Comutatividade
Suponha que você queira implementar a capacidade de adicionar um número inteiro a um de seus
registros. Você pode definir o seguinte operador (que está disponível no código do exemplo OperadoresOver ,
para um tipo de registro ligeiramente diferente):

operador de classe TPointRecord2.Add(A: TPointRecord2;


B: Inteiro): TPointRecord2;
começar
Resultado.X := AX + B;
Resultado.Y := AY + B;
fim;

note A razão pela qual defini este operador para um novo tipo em vez do existente é que o mesmo
A estrutura já define uma conversão implícita de um número inteiro para o tipo de registro, portanto já posso adicionar
números inteiros e registros sem definir um operador específico. Esta questão é explicada melhor na próxima seção.

Agora você pode adicionar legitimamente um valor de ponto flutuante a um registro:

era
R: TPointRecord2;
começar
A.SetValue(10, 20);
UMA := UMA + 10;

No entanto, se você tentar escrever a adição oposta:

UMA:= 30 + UMA;

isso falhará com o erro:

[Erro dcc32] Operador E2015 não aplicável a este tipo de operando

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

152 - 05: matrizes e registros

Na verdade, como mencionei, a comutatividade não é automática para operadores aplicados a


variáveis de tipos diferentes, mas deve ser implementada especificamente repetindo a chamada ou
chamando (como abaixo) a outra versão do operador:

operador de classe TPointRecord2.Add(B: Integer;


R: TPointRecord2): TPointRecord2;
começar
Resultado:= A + B; // Implementar comutatividade
fim;

Promoções implícitas de elenco e tipo


É importante notar que as regras relacionadas à resolução de chamadas envolvendo operadores são
diferentes das regras tradicionais envolvendo métodos. Com promoções automáticas de tipo, há a
chance de que uma única expressão acabe chamando diferentes versões de um operador
sobrecarregado e cause chamadas ambíguas. É por isso que você precisa tomar muito cuidado
ao escrever operadores implícitos .
Considere estas expressões do exemplo anterior:

R:= 50;
C := A + 30;
C:= 50 + 30;
C := 50 + TPointRecord(30);

Eles são todos legais! No primeiro caso, o compilador converte 30 para o tipo de registro adequado,
no segundo a conversão ocorre após a atribuição, e no terceiro a conversão explícita força uma
conversão implícita no primeiro valor, para que a adição que está sendo realizada é o costume
entre os registros. Em outras palavras, o resultado da segunda operação é diferente das outras
duas, conforme destacado na saída – mostrando o X
e valores Y - e na versão expandida destas declarações:

// Saída
(80:20)
(80:10)
(80:20)

// Declarações expandidas
C := A + TPointRecord(30);
// Aquilo é: (50:10) + (30:10)

C := TPointRecord (50 + 30);


// Isso é 80 convertido em (80:10)

C := TPointRecord(50) + TPointRecord(30);
// Aquilo é: (50:10) + (30:10)

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

05: matrizes e registros - 153

Operadores e registro gerenciado personalizado


Há um conjunto especial de operadores que você pode usar para registros na linguagem Delphi para
definir um registro gerenciado personalizado. Antes de chegarmos lá, deixe-me recapitular as regras para registros
inicialização da memória e a diferença entre registros simples e registros gerenciados.

Os registros no Delphi podem ter campos de qualquer tipo de dados. Quando um registro possui campos
simples (não gerenciados), como valores numéricos ou outros valores enumerados, não há muito o que
fazer pelo compilador. Criar e descartar o registro consiste em alocar memória ou liberar área de memória.
(Observe que, por padrão, o Delphi não inicializa registros com zero, mas o faz para arrays e, como
aprenderemos mais tarde, para novas instâncias de objetos.)

Se um registro tiver um campo de um tipo gerenciado pelo compilador (como uma string ou uma interface),
o compilador precisará injetar código extra para gerenciar a inicialização ou finalização. Uma string, por
exemplo, é contada por referência, portanto, quando o registro sai do escopo, a string dentro do registro
precisa ter sua contagem de referência diminuída, o que pode levar à desalocação da memória da string.
Portanto, quando você usa esse registro gerenciado em uma seção do código, o compilador adiciona
automaticamente um bloco try-finally em torno desse código e garante que os dados sejam limpos mesmo no
caso de uma exceção. Este tem sido o caso há muito tempo para registros gerenciados na linguagem Delphi.

A partir da versão 10.4, o tipo de registro Delphi suporta inicialização e finalização personalizadas além
das operações padrão que o compilador executa para registros gerenciados. Você pode declarar um
registro com código de inicialização e finalização personalizado, independentemente do tipo de dados de
seus campos, e pode escrever esse código de inicialização e finalização personalizado. Esses registros
são indicados como “registros gerenciados personalizados”.

Um desenvolvedor pode transformar um registro em um registro gerenciado personalizado adicionando um


dos novos operadores específicos ao tipo de registro, ou mais de um:

• O operador Initialize é invocado após a memória para o registro ter sido alocada, permitindo que você
escreva código para definir um valor inicial para os campos

• O operador Finalize é invocado antes que a memória para o registro seja desalocada
e permite que você faça qualquer limpeza necessária

• A atribuição do operador é invocada quando os dados de um registro são copiados para outro registro
do mesmo tipo, para que você possa copiar as informações de um registro para outro de maneiras
específicas e personalizadas

note Dado que a finalização do registro gerenciado é executada mesmo no caso de uma exceção (com o compilador
gerando automaticamente um bloco try-finally ), eles são frequentemente usados como uma forma alternativa
de proteger a alocação de recursos ou implementar operações de limpeza. Veremos um exemplo desse uso na seção
“Restaurar o Cursor com um Registro Gerenciado” do Capítulo 9.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

154 - 05: matrizes e registros

Registros com Operadores Inicializar e Finalizar


Vamos começar apresentando a inicialização e a finalização usando o trecho de código simples abaixo:

tipo
TMyRecord = registro
Valor: Inteiro; operador
de classe Inicializar(out Dest: TMyRecord); operador de classe
Finalize(var Dest: TMyRecord); fim;

É claro que você precisa escrever o código para os dois métodos de classe que, por exemplo, podem
registrar sua execução ou inicializar o campo Valor do registro . Neste exemplo (parte do projeto de exemplo
ManagedRecords_101), estou inicializando o campo Valor e também registrando uma referência ao local da
memória para ver qual registro está executando cada operação individual:

operador de classe TMyRecord.Initialize(out Dest: TMyRecord); começar Dest.Value :=


10;
Log('Criado' +
IntToHex(Integer(Pointer(@Dest))))); fim;

operador de classe TMyRecord.Finalize(var Dest: TMyRecord); começar

Log('Destroyed' + IntToHex(Integer(Pointer(@Dest))))); fim;

A diferença entre este mecanismo de construção e o que estava anteriormente disponível para registros
é a invocação automática. Se você escrever algo como o código abaixo, poderá invocar o código de
inicialização e de finalização e acabar com um bloco try-finally gerado pelo compilador para sua instância
de registro gerenciado:

procedimento LocalVarTest;
era
Meu1: TMeuRegistro;

começar Log(My1.Value.ToString); fim;

Com este código você obterá um log semelhante a este (os endereços serão diferentes):

Criado 0019F2A8 10

Destruído 0019F2A8

Outro cenário é o uso de variáveis inline, como em:

começar var T: TMyRecord;


Log(T.Value.ToString);

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

05: matrizes e registros - 155

o que resulta na mesma sequência no log.

O operador de atribuição
Em geral, o operador de atribuição (:=) copia de forma plana todos os dados dos campos de registro.
Registros com tipos gerenciados (por exemplo, strings) também são tratados adequadamente pelo
compilador.

Quando você tem campos de dados personalizados e inicialização personalizada, talvez queira alterar o comportamento
padrão. É por isso que para registros gerenciados personalizados você também pode definir um operador de
atribuição. O novo operador é invocado com a sintaxe :=, mas definido como Assign:

operador de classe Assign(var Dest: TMyRecord;


const [ref] Fonte: TMyRecord);

A definição do operador deve seguir regras muito precisas, incluindo ter o primeiro parâmetro como
parâmetro passado por referência (var), e o segundo como parâmetro const passado por referência. Se você
não fizer isso, o compilador emitirá mensagens de erro como a
seguindo:

[Erro dcc32] E2617 O primeiro parâmetro do operador Assign deve ser um parâmetro var do
tipo de contêiner
[dcc32 Hint] H2618 O segundo parâmetro do operador Assign deve ser um parâmetro
const[Ref] ou var do tipo de contêiner

Este é um exemplo de caso invocando o operador Assign :

era
Meu1, Meu2: TMyRecord;
começar
Meu1.Valor := 22;
Meu2 := Meu1;

que produz este log (no qual também adiciono um número de sequência ao registro):

Criado 5 0019F2A0
Criado 6 0019F298
5 copiado para 6
Destruído 6 0019F298
Destruído 5 0019F2A0

Observe que a sequência de destruição é invertida da sequência de construção, sendo o último registro
criado o primeiro destruído.

Passando registros gerenciados como parâmetros


Os registros gerenciados podem funcionar de maneira diferente dos registros regulares também
quando passados como parâmetros ou retornados por uma função. Aqui estão várias rotinas mostrando
os vários cenários:

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

156 - 05: matrizes e registros

procedimento ParByValue(Rec: TMyRecord);


procedimento ParByConstValue(const Rec: TMyRecord);
procedimento ParByRef(var Rec: TMyRecord);
procedimento ParByConstRef(const [ref] Rec: TMyRecord);
função ParReturned: TMyRecord;

Agora, sem passar por cada log um por um (você pode vê-los executando a demonstração ManagedRecords_101),
este é o resumo das informações:

• ParByValue cria um novo registro e chama o operador de atribuição (se disponível


capaz) de copiar os dados, destruindo a cópia temporária ao sair do escopo

• ParByConstValue não faz nenhuma cópia e nenhuma chamada

• ParByRef não faz nenhuma cópia, nenhuma chamada

• ParByConstRef não faz nenhuma cópia, nenhuma chamada

• ParReturned cria um novo registro (via Initialize) e no retorno chama o


Atribua o operador, se a chamada for como my1 := ParReturned, e exclui o registro temporário uma
vez atribuído.

Exceções e registros gerenciados


Quando uma exceção é levantada, os registros em geral são limpos mesmo quando nenhum bloco try-finally explícito
está presente, ao contrário dos objetos. Esta é uma diferença fundamental e fundamental para a real utilidade dos
registros gerenciados.

procedimento ExceptionTest;
começar
foi A: TMRE;
foi B: TMRE;
raise Exception.Create('Mensagem de erro');
fim;

Dentro deste procedimento, existem duas chamadas de construtor e duas chamadas de destruidor. Novamente, esta
é uma diferença fundamental e uma característica fundamental dos registros gerenciados.

Matrizes de registros gerenciados


Se você definir uma matriz estática de registros gerenciados, eles serão inicializados chamando o método
Inicialize o operador na declaração do ponto:

era
A1: array[1..5] de TMyRecord; começar // Inicialização chamada aqui

Log('ArrOfRec');

Eles são todos destruídos quando saem do escopo. Se você definir um array dinâmico de registros gerenciados,
o código de inicialização será chamado com o array dimensionado (com SetLength):

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

05: matrizes e registros - 157

era
A2: matriz de TMyRecord;
começar
Log('ArrOfDyn');
DefinirComprimento(A2, 5); // Inicialização chamada aqui

Variantes
Originalmente introduzido na linguagem para fornecer suporte completo ao OLE e COM do Windows, o Object
Pascal tem o conceito de um tipo de dados nativo de tipo flexível chamado Variant.
Embora o nome seja uma reminiscência de registros variantes (mencionados anteriormente) e a
implementação tenha alguma semelhança com parâmetros de array aberto, é um recurso separado com
uma implementação muito específica (incomum em linguagens fora do mundo de desenvolvimento do
Windows).

Nesta seção não vou realmente me referir ao OLE ou a outros cenários em que esse tipo de dados é usado
(como acesso a campos para conjuntos de dados), quero apenas discutir esse tipo de dados de uma maneira geral.
perspectiva.

Voltarei aos tipos dinâmicos, RTTI e reflexão no Capítulo 16, onde também abordarei um
tipo de dados RTL relacionado (mas com segurança de tipo e muito mais rápido) chamado TValue.

Variantes não têm tipo


Em geral, você pode usar uma variável do tipo variante para armazenar qualquer um dos tipos de dados básicos
e executar diversas operações e conversões de tipo. As conversões automáticas de tipo vão contra a
abordagem geral de segurança de tipo da linguagem Object Pascal e são uma implementação de um
tipo de digitação dinâmica originalmente introduzida por linguagens como Smalltalk e Objective-C, e
recentemente popularizada em linguagens de script, incluindo JavaScript, PHP, Python e Rubi.

Uma variante é verificada por tipo e calculada em tempo de execução. O compilador não avisa sobre possíveis
erros no código, que só podem ser detectados com testes extensivos. No geral, você pode considerar as
partes do código que usam variantes como código interpretado, porque, assim como acontece com o
código interpretado, muitas operações não podem ser resolvidas até o tempo de execução, o que afeta a
velocidade do código.

Agora que eu avisei você contra o uso do tipo Variant , é hora de ver o que você pode fazer com ele.
Basicamente, depois de declarar uma variável variante como a seguinte:

era

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

158 - 05: matrizes e registros

V:Variante;

você pode atribuir valores de vários tipos diferentes a ele:

Em := 10;
V := 'Olá Mundo' ;
V := 45,55;

Depois de obter o valor da variante, você pode copiá-lo para qualquer tipo de dados, compatível ou incompatível.
Se você atribuir um valor a um tipo de dados incompatível, o compilador geralmente não o sinalizará com um
erro, mas executará uma conversão em tempo de execução se isso fizer sentido.
Caso contrário, ocorrerá um erro de tempo de execução. Tecnicamente, uma variante armazena informações
de tipo junto com os dados reais, permitindo uma série de operações de tempo de execução úteis, mas lentas
e inseguras.

Considere o seguinte código (parte do exemplo VariantTest ), que é uma extensão do código acima:

era
V:Variante;
S: corda;
começar
Em := 10;
S:= V;
V := V + S;
Mostrar(V);

Em := 'Olá Mundo' ;
V := V + S;
Mostrar(V);

V:= 45,55;
V := V + S;
Mostrar(V);

Engraçado, não é? Não surpreendentemente, esta é a saída:

20
Olá, Mundo10
55,55

Além de atribuir uma variante contendo uma string à variável S , você pode atribuir a ela uma variante contendo
um inteiro ou um número de ponto flutuante. Pior ainda , você pode usar as variantes para calcular
valores, com a operação V := V + S que é interpretada de diferentes maneiras dependendo dos dados
armazenados na variante. No código acima, essa mesma linha pode adicionar números inteiros, valores de
ponto flutuante ou concatenar strings.

Escrever expressões que envolvam variantes é arriscado, para dizer o mínimo. Se a string contiver um número,
tudo funciona. Caso contrário, uma exceção é levantada. Sem uma razão convincente para fazer isso, você
não deve usar o tipo Variant ; siga os tipos de dados padrão do Object Pascal e a abordagem de verificação
de tipo.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

05: matrizes e registros - 159

Variantes em profundidade
Para aqueles interessados em entender as variantes com mais detalhes, deixe-me adicionar algumas informações
técnicas sobre como as variantes funcionam e como você pode ter mais controle sobre elas.
eles. O RTL inclui um tipo de registro variante, TVarData, que possui o mesmo layout de memória que o tipo Variant .
Você pode usar isso para acessar o tipo real de uma variante. A estrutura TVarData inclui o tipo de Variant, indicado
como VType, alguns campos reservados e o valor real. Observe que existe o conceito de valor nulo, um valor que
você pode atribuir usando NULL (e não nulo).

note Para mais detalhes consulte a definição de TVarData no código fonte RTL, na unidade System. Isso está longe de
ser uma estrutura simples e recomendo que apenas desenvolvedores com alguma experiência analisem os
detalhes de implementação do tipo de variante.

Os valores possíveis do campo VType correspondem aos tipos de dados que você pode usar na automação OLE,
que geralmente são chamados de tipos OLE ou tipos variantes. Aqui está uma lista alfabética completa dos
tipos de variantes disponíveis:

varAny varArray varBoolean varDate ondeByte


varByRef varMoeda varError varDispatch
varDouble varVazio varNull varSingle varInt64
varInteger varLongWord varUInt64 varOleStr
varRecord varShortInt varWord varSmallint
varString varTypeMask varDesconhecido
varUString varVariant

A maioria desses nomes constantes de tipos variantes são fáceis de entender.

Existem também muitas funções para operar em variantes que você pode usar para fazer conversões de tipo
específicas ou para solicitar informações sobre o tipo de uma variante (por exemplo,
a função VarType ). A maioria dessas funções de conversão e atribuição de tipo são chamadas automaticamente
quando você escreve expressões usando variantes. Outras rotinas de suporte a variantes, na verdade, operam em
matrizes variantes, novamente uma estrutura usada quase exclusivamente para integração OLE no Windows.

Variantes são lentas

O código que usa o tipo Variant é lento, não apenas quando você converte tipos de dados, mas até mesmo
quando você simplesmente adiciona dois valores variantes contendo números inteiros. Eles são quase tão lentos
quanto o código interpretado. Para comparar a velocidade de um algoritmo baseado em variantes com a do mesmo
código baseado em inteiros, você pode observar o código executado pelo segundo botão do projeto VariantTest .

Este programa executa um loop, cronometrando sua velocidade e mostrando o status em uma barra de progresso.
Aqui está o primeiro dos dois loops muito semelhantes, baseados em Int64 e variantes:

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

160 - 05: matrizes e registros

const
MaxNo = 10_000_000; // 10 milhões

era
Hora1, Hora2: TDateTime;
N1, N2: Variante;
começar
Hora1 := Agora;
N1 := 0;
N2:= 0;

enquanto N1 < MaxNão faz


começar
Inc (N2, N1);
Inc(N1);
fim;

// Nós deve usar o resultado


Hora2 := Agora;
Mostrar(N2);
'
Mostrar( 'Variantes: + FormatoDataHora(
'
'ss.zzz' , Tempo2-Tempo1) + segundos' );

Vale a pena dar uma olhada no código de tempo, porque é algo que você pode adaptar facilmente a
qualquer tipo de teste de desempenho. Como você pode ver, o programa usa a função Now para obter a
hora atual e a função FormatDateTime para gerar a diferença de horário, mostrando
apenas os segundos (“ss”) e os milissegundos (“zzz”). Neste exemplo, a diferença de velocidade é tão
grande que você a notará mesmo sem um timing preciso. No meu caso, estes são os números que
obtive na minha máquina virtual Windows:

49999995000000 Variantes: 01,169 segundos


49999995000000 Inteiros: 00,026 segundo

Isso é cerca de 50 vezes mais lento para o código baseado em variante na minha máquina virtual! Os
valores reais dependem do dispositivo em que você executa este programa, mas a diferença relativa não
mudará muito. Mesmo no meu telefone Android, obtenho uma proporção semelhante (mas com tempos
muito mais longos no geral):

49999995000000 Variantes: 07,717 segundos


49999995000000 Inteiros: 00,157 segundo

No meu telefone, esse código leva 6 vezes mais tempo que no Windows, mas o fato é que a diferença
líquida é superior a 7 segundos, tornando a implementação baseada em variante visivelmente lenta para
o usuário, enquanto a baseada em Int64 ainda é extremamente rápida (um usuário dificilmente notaria um
décimo de segundo).

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

05: matrizes e registros - 161

E quanto aos ponteiros?

Outro tipo de dados fundamental da linguagem Object Pascal é representado por ponteiros. Algumas
das linguagens orientadas a objetos percorreram um longo caminho para esconder essa construção de
linguagem poderosa, mas perigosa, enquanto Object Pascal permite que um programador a use quando
necessário (o que geralmente não é muito frequente).

Mas o que é um ponteiro e de onde vem seu nome? Diferentemente da maioria dos outros
tipos de dados, um ponteiro não contém um valor real, mas contém uma referência indireta a uma
variável, que por sua vez possui um valor. Uma maneira mais técnica de expressar isso é que um
tipo de ponteiro define uma variável que contém o endereço de memória de outra variável de um
determinado tipo de dados (ou de um tipo indefinido).

note Esta é uma seção avançada do livro, adicionada aqui porque os ponteiros fazem parte do Object Pascal
linguagem e deve fazer parte do conhecimento básico de qualquer desenvolvedor, embora não seja um
tópico básico e se você for novo na linguagem, você pode querer pular esta seção na primeira vez que
ler o livro. Novamente, há uma chance de você ter usado linguagens de programação sem dicas (explícitas),
então esta pequena seção pode ser uma leitura interessante.

A definição de um tipo de ponteiro não é baseada em uma palavra-chave específica, mas usa um
símbolo especial, o sinal de intercalação (^). Por exemplo, você pode definir um tipo que representa um
ponteiro para uma variável do tipo Integer com a seguinte declaração:

tipo
TPointerToInt = ^Integer;

Depois de definir uma variável ponteiro, você pode atribuir a ela o endereço de outra variável do
mesmo tipo, usando o operador: @

era
P: ^Inteiro;
X: Inteiro;
começar
X:= 10;
P := @X;
// Mudar o valor de X usando o ponteiro
P^ := 20;
'
Mostrar( 'X:
' +X.ToString);
Mostrar( 'P ^: + P^.ToString);
'
Mostrar( 'P: + UIntPtr(P).ToHexString(8));

Este código faz parte do exemplo PointersTest . Dado que o ponteiro P se refere à variável X, você
pode usar P^ para se referir ao valor da variável e lê-lo ou alterá-lo. Você também pode exibir o valor
do próprio ponteiro, que é o endereço de memória de X, convertendo o ponteiro para um número usando
o tipo especial UIntPtr (veja a nota abaixo para mais
Informação). Em vez de mostrar o valor numérico simples, o código mostra o hexa-

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

162 - 05: matrizes e registros

representação decimal, que é mais comum para endereços de memória. Esta é a saída (onde o endereço
do ponteiro pode depender da compilação específica):

X: 20
P ^: 20
P: 0018FC18

warning A conversão de um ponteiro para um número inteiro é correta apenas em plataformas de 32 bits quando limitada
a 2 GB. Se você habilitar o uso de um espaço de memória maior, terá que usar o tipo Cardinal. Para plataformas
de 64 bits, uma opção melhor seria usar NativeUInt. Porém, existe um alias desse tipo, voltado especificamente
para ponteiros e chamado UIntPtr, que é a melhor opção para este cenário, pois ao utilizá-lo você indica claramente
suas intenções ao desenvolvedor e também ao compilador.

Deixe-me resumir, para maior clareza. Quando você tem um ponteiro P:

· Ao usar o ponteiro diretamente (com a expressão P) você se refere ao endereço do


local de memória ao qual o ponteiro está se referindo

· Ao desreferenciar o ponteiro (com a expressão P^) você se refere ao conteúdo real


daquele local de memória

Em vez de se referir a um local de memória existente, um ponteiro também pode se referir a um bloco
de memória novo e específico alocado dinamicamente no heap com o procedimento New .
Neste caso, quando você não precisar mais do valor acessado pelo ponteiro, você também
você precisa se livrar da memória alocada dinamicamente, chamando Dispose.

note O gerenciamento de memória em geral e o modo como o heap funciona são abordados no Capítulo 13. Resumindo, o
heap é uma (grande) área de memória na qual você pode alocar e liberar blocos de memória sem nenhuma ordem
específica. Como alternativa a New e Dispose você pode usar GetMem e FreeMem, que exigem que o
desenvolvedor forneça o tamanho da alocação (enquanto o compilador determina o tamanho da alocação
automaticamente no caso de New e Dispose). Nos casos em que o tamanho da alocação não é conhecido
em tempo de compilação, GetMem e FreeMem tornam-se úteis.

Aqui está um trecho de código que aloca memória dinamicamente:

era
P: ^Inteiro;
começar
// Inicialização
Novo p );
// Operações
P^ := 20;
Mostrar(P^.ToString);
// Terminação
Descarte(P);

Se você não descartar a memória após usá-la, seu programa poderá eventualmente consumir toda a
memória disponível e travar. A falha em liberar memória desnecessária é conhecida como vazamento de
memória.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

05: matrizes e registros - 163

avisar Para ser mais seguro, o código acima deveria, na verdade, usar um bloco try-finally , um tópico que decidi não
no apresentar neste ponto do livro, mas abordarei mais tarde no Capítulo 9.

Se um ponteiro não tiver valor, você poderá atribuir o valor nulo a ele. Você pode testar se um ponteiro é nulo
para ver se atualmente se refere a um valor com um teste de igualdade direto ou usando
a função específica atribuída conforme mostrado abaixo.

Este tipo de teste é frequentemente usado, porque a desreferenciação de um ponteiro inválido causa uma violação
de acesso à memória (com efeitos ligeiramente diferentes dependendo do sistema operacional):

era
P: ^Inteiro;
começar
P:= nulo;
Mostrar(P^.ToString);

Você pode ver um exemplo do efeito desse código executando o exemplo PointersTest . O erro que você verá (no
Windows) deve ser semelhante a:

Violação de acesso no endereço 0080B14E no módulo 'PointersTest.exe'. Leitura do endereço 00000000.

Uma das maneiras de tornar o acesso aos dados do ponteiro mais seguro é adicionar uma verificação de
segurança “o ponteiro não é nulo”, como a seguinte:

se P <> nulo então


Mostrar(P^.ToString);

Como mencionei anteriormente, uma forma alternativa, que geralmente é preferível por razões de legibilidade, é usar
a pseudofunção Assigned :

se atribuído(P) então
Writeln(P^.ToString);

note Assigned não é uma função real, porque é “resolvida” pelo compilador que produz o código adequado.
Além disso, pode ser usado sobre uma variável de tipo processual (ou referência de método) sem realmente invocá-la,
mas apenas para verificar se está atribuído.

Object Pascal também define um tipo de dados Pointer , que indica ponteiros não digitados (como
void* na linguagem C). Se você usar um ponteiro não digitado, você deve usar GetMem
em vez de Novo e indicando o número de bytes a alocar, dado que este valor não pode ser
inferido do próprio tipo. O procedimento GetMem é necessário sempre que o tamanho do
variável de memória a ser alocada não está definida.

O fato de ponteiros raramente serem necessários em Object Pascal é uma vantagem interessante
da língua. Ainda assim, ter esse recurso disponível pode ajudar na implementação de algumas funções de baixo
nível extremamente eficientes e na chamada da API de um sistema operacional. Em qualquer caso, compreender
os ponteiros é importante para programação avançada e

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

164 - 05: matrizes e registros

para uma compreensão completa do modelo de objetos do Delphi, que usa ponteiros (geralmente chamados
de referências) nos bastidores.

avisar Quando uma variável contém um ponteiro para uma segunda variável e essa segunda variável sai do escopo ou
no é liberada (se alocada dinamicamente), o ponteiro se referiria apenas ao local da memória indefinido ou contendo
alguns outros dados. Isso pode levar a erros muito difíceis de encontrar.

Tipos de arquivo, alguém?


O último construtor de tipo de dados Object Pascal abordado (brevemente) neste capítulo é o arquivo
tipo. Os tipos de arquivo representam arquivos de disco físico, certamente uma peculiaridade da linguagem
Pa-cal original, visto que muito poucas linguagens de programação antigas ou modernas incluem a noção
de arquivo como um tipo de dados primitivo. A linguagem Object Pascal possui uma palavra-chave file , que
é um especificador de tipo, como array ou registro. Você usa file para definir um novo tipo e então pode
usar o novo tipo de dados para declarar novas variáveis:

tipo
IntFile = arquivo de Inteiros;
era
IntFile1: IntFile;

Também é possível usar a palavra-chave file sem indicar um tipo de dados, para especificar um arquivo
não digitado. Alternativamente, você pode usar o tipo TextFile , definido na unidade System do
a Run Time Library para declarar arquivos de caracteres ASCII (ou, mais corretamente nestes tempos,
arquivos de bytes).

O uso direto de arquivos, embora ainda suportado, é cada vez menos comum atualmente, já que a Run
Time Library inclui muitas classes para gerenciar arquivos binários e de texto em um nível muito mais alto
(incluindo o suporte para arquivos de texto codificados em Unicode, por exemplo).

Os aplicativos Delphi geralmente usam fluxos RTL (TStream e classes derivadas) para lidar com
qualquer operação complexa de leitura e gravação de arquivos. Streams representam arquivos
virtuais, que podem ser mapeados para arquivos físicos, para um bloco de memória, para um soquete ou
qualquer outra série contínua de bytes.

Uma área em que você ainda vê algumas das rotinas de arquivo em uso é quando você escreve aplicativos de
console, onde você pode usar Write, Writeln, Read e funções relacionadas para operar com um tipo especial
de arquivo, a entrada padrão e a saída padrão. (C e C++ têm suporte semelhante para entrada e saída do
console, e muitas outras linguagens oferecem serviços semelhantes).

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

06: tudo sobre cordas - 165

06: tudo sobre


cordas
Cadeias de caracteres são um dos tipos de dados mais comumente usados em qualquer linguagem de
programação. Object Pascal torna o manuseio de strings bastante simples, mas muito rápido
e extremamente poderoso. Mesmo que o básico das cordas seja fácil de entender, e eu tenha usado
cordas nos capítulos anteriores, nos bastidores a situação é um pouco mais complexa do que parece.
pode parecer à primeira vista. A manipulação de texto envolve vários tópicos intimamente
relacionados que vale a pena explorar. Para entender completamente o processamento de strings,
você precisa saber sobre representações Uni-code, entender como as strings são mapeadas para
matrizes de caracteres e aprender sobre algumas das operações de string mais relevantes da Run
Time Library, incluindo salvar e carregar strings de e para arquivos de texto. .

Object Pascal possui diversas opções para manipulação de strings e disponibiliza diferentes
tipos de dados e abordagens. O foco do capítulo será no tipo de dados string padrão, mas também
dedicarei um pouco de tempo aos tipos de string mais antigos, como AnsiString, que você ainda pode
usar em compiladores Delphi. Antes de chegarmos a isso, porém, deixe-me começar do início: a
representação Unicode.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

166 - 06: tudo sobre cordas

Unicode: um alfabeto para todo o mundo


Mundo
O gerenciamento de strings do Object Pascal é centrado no conjunto de caracteres Unicode e, em
particular, em uma de suas representações, chamada UTF-16. Antes de entrarmos nos detalhes
técnicos da implementação, vale a pena dedicar algumas seções para entender completamente
o padrão Unicode.

A ideia por trás do Unicode (que é o que o torna simples e complexo ao mesmo tempo) é que
cada caractere em todos os alfabetos conhecidos do mundo tenha sua própria descrição, uma
representação gráfica e um valor numérico exclusivo chamado ponto de código Unicode .

note O site de referência do consórcio Unicode é http://www.unicode.org, com uma grande quantidade de
documentação. A referência final é o livro “The Unicode Standard”, que pode ser encontrado em
http://www.unicode.org/book/aboutbook.html.

Nem todos os desenvolvedores estão familiarizados com Unicode, e muitos ainda pensam nos
caracteres em termos de representações antigas e limitadas, como ASCII, e em termos de
codificação ISO. Ao ter uma pequena seção cobrindo esses padrões mais antigos, você apreciará
melhor as peculiaridades (e a complexidade) do Unicode.

Personagens do passado: de ASCII a ISO


Codificações
As representações de caracteres começaram com o American Standard Code for Information
Interchange (ASCII), que foi desenvolvido no início dos anos 60 como uma codificação padrão de
caracteres de computador, abrangendo as 26 letras do alfabeto inglês, tanto minúsculas quanto
maiúsculas, os 10 números dígitos, símbolos de pontuação comuns e vários caracteres de
controle (ainda em uso hoje).

ASCII usa um sistema de codificação de 7 bits para representar 128 caracteres diferentes. Apenas os
caracteres entre #32 (espaço) e #126 (til) possuem representação visual, conforme mostra a
Figura 6.1 (extraída de uma aplicação Object Pascal rodando em Windows).

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

06: tudo sobre cordas - 167

Figura 6.1:
Uma tabela com o
conjunto de
caracteres ASCII imprimível

Embora o ASCII tenha sido certamente uma base (com seu conjunto básico de 128 caracteres que são
ainda parte do núcleo do Unicode), logo foi substituído por versões estendidas que usavam o oitavo
bit para adicionar outros 128 caracteres ao conjunto.

Agora, o problema é que, com tantos idiomas ao redor do mundo, não havia uma maneira simples
de descobrir quais outros caracteres incluir no conjunto (às vezes indicados como ASCII-8). Para
resumir a história, o Windows adotou um conjunto diferente de caracteres, chamado de página de
código, com um conjunto de caracteres dependendo da configuração de localidade e da versão
do Windows. Além das páginas de código do Windows, há muitos
outros padrões semelhantes baseados nesta abordagem de paginação , e essas páginas tornaram-
se parte dos padrões ISO internacionais.

A mais relevante foi certamente a norma ISO 8859, que define vários conjuntos regionais . O
conjunto mais utilizado (bem, aquele utilizado na maioria dos países ocidentais para ser um pouco
mais preciso) é o conjunto latino, referenciado como ISO 8859-1.

note Mesmo que seja parcialmente semelhante, a página de código do Windows 1252 não está totalmente em conformidade com o conjunto ISO 8859-1. Ganhar-
dows adiciona caracteres extras como o símbolo €, aspas extras e mais, na área de 128 a 150. Diferentemente de todos os outros
valores do conjunto latino, essas extensões do Windows não estão em conformidade com os pontos de código Unicode.

Pontos de código Unicode e grafemas


Se eu realmente quiser ser preciso, devo incluir mais um conceito além dos pontos de código. Às
vezes, de fato, vários pontos de código podem ser usados para representar um único
grafema (um caractere visual). Geralmente não é uma letra, mas uma combinação de letras ou
letras e símbolos. Por exemplo, se você tiver uma sequência de pontos de código representando
a letra latina a (#$0061) seguida pelo ponto de código representando o acento grave (#$0300),
isso deverá ser exibido como um único caractere acentuado.
Em termos de codificação Object Pascal, se você escrever o seguinte (parte do exemplo CodePoints ),
a mensagem terá um único caractere acentuado, como na Figura 6.2.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

168 - 06: tudo sobre cordas

era
Str: Corda;
começar
Str := #$0061 + #$0300;
MostrarMensagem(Str);

Figura 6.2:
Um único grafema pode
ser o resultado de vários
pontos de código

Neste caso temos dois caracteres, representando dois pontos de código, mas apenas um
grafema (ou elementos visuais). O fato é que, enquanto no alfabeto latino você pode usar um ponto de
código Unicode específico para representar um determinado grafema (a letra a com acento grave
é o ponto de código $00E0), em outros alfabetos combinar pontos de código Unicode é a única
maneira de obter um dado grafema (e a saída correta).

Mesmo que a exibição seja de um caracter acentuado, não há normalização automática


ou transformação do valor (apenas uma exibição adequada), de forma que a string permaneça
internamente diferente daquela com o caractere único à.

note A renderização de grafemas de vários pontos de código pode depender do suporte específico do sistema
operacional e das técnicas de renderização de texto usadas, portanto você descobrirá que, para alguns dos
grafemas, nem todos os sistemas operacionais oferecem a saída correta.

De pontos de código a bytes (UTF)


Enquanto o ASCII utilizava um mapeamento direto e fácil de caracteres para sua representação
numérica, o Unicode utiliza uma abordagem mais complexa. Como mencionei, cada elemento do
alfabeto Unicode tem um ponto de código associado, mas o mapeamento para a representação real
costuma ser mais complicado.

Um dos elementos de confusão por trás do Unicode é que existem várias maneiras de representar
o mesmo ponto de código (ou valor numérico do caractere Unicode) em termos de armazenamento
real, de bytes físicos, na memória ou em um arquivo.
O problema decorre do fato de que a única maneira de representar todos os pontos de código Unicode
de maneira simples e uniforme é usar quatro bytes para cada ponto de código. Isso leva em conta
uma representação de comprimento fixo (cada caractere requer sempre a mesma quantidade de bytes),

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

06: tudo sobre cordas - 169

mas a maioria dos desenvolvedores consideraria isso muito caro em termos de memória e tempo de processamento.

note No Object Pascal, uma representação de pontos de código Unicode de 4 bytes pode ser obtida usando o UCS4Char
tipo de dados.

É por isso que o padrão Unicode define outras representações, geralmente exigindo menos memória, mas nas quais
o número de bytes para cada símbolo é diferente, dependendo dos pontos de código necessários. A ideia é usar uma
representação menor para os elementos mais comuns e uma representação mais longa para aqueles que são
encontrados com pouca frequência.

As diferentes representações físicas dos pontos de código Unicode são chamadas de Unicode Transformation Formats
(ou UTF). Estes são mapeamentos algorítmicos , parte do padrão Uni-code, que mapeiam cada ponto de código (a
representação numérica absoluta de um caractere) para uma sequência única de bytes que representa o caractere
fornecido. Observe que os mapeamentos podem ser usados em ambas as direções, convertendo entre diferentes
representações.

O padrão define três desses formatos, dependendo de quantos bits são usados para representar a parte inicial do
conjunto (os 128 caracteres iniciais): 8, 16 ou 32. É interessante notar que todas as três formas de codificação
precisam no máximo 4 bytes de dados para cada código
apontar.

· UTF-8 transforma caracteres em uma codificação de comprimento variável de 1 a 4 bytes. UTF-8 é popular para HTML
e protocolos semelhantes, porque é bastante compacto quando a maioria dos caracteres (como tags em HTML)
se enquadra no subconjunto ASCII.

· UTF-16 é popular em muitos sistemas operacionais (incluindo Windows e macOS). É bastante conveniente, pois a
maioria dos caracteres cabem em dois bytes, tornando-o razoavelmente compacto
e rápido para processar.

· UTF-32 faz muito sentido para processamento (todos os pontos de código têm o mesmo comprimento), mas consome
memória e tem uso limitado na prática.

Existe um equívoco comum de que o UTF-16 pode mapear diretamente todos os pontos de código com dois bytes,
mas como o Unicode define mais de 100.000 pontos de código, você pode facilmente descobrir
que eles não cabem em elementos de 64K. É verdade, entretanto, que às vezes os desenvolvedores usam apenas
um subconjunto de Unicode, para fazê-lo caber em uma representação de comprimento fixo de 2 bytes por caractere.
No início, esse subconjunto do Unicode era chamado de UCS-2, agora você costuma ver
é referenciado como Plano Multilíngue Básico (BMP). No entanto, este é apenas um subconjunto do Uni-code e um
dos muitos planos.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

170 - 06: tudo sobre cordas

note Um problema relacionado às representações multibyte (UTF-16 e UTF-32) é determinar qual dos
bytes vem primeiro. De acordo com o padrão, todos os formulários são permitidos, então você pode ter UTF-16
BE (big-endian) ou LE (little-endian), e o mesmo para UTF-32. A serialização de bytes big-endian tem o byte
mais significativo primeiro, a serialização de bytes little-endian tem o byte menos significativo primeiro. A
serialização de bytes é frequentemente marcada em arquivos junto com a representação UTF com um
cabeçalho chamado Byte Order Mark (BOM).

A marca de ordem de bytes


Quando você tem um arquivo de texto armazenando caracteres Unicode, existe uma maneira de indicar qual é
o formato UTF usado para os pontos de código. As informações são armazenadas em um cabeçalho ou
marcador no início do arquivo, denominado Byte Order Mark (BOM). Esta é uma assinatura que indica o
formato Unicode que está sendo usado e a forma de ordem de bytes (little- ou big-endian –LE ou
BE). A tabela a seguir fornece um resumo das diversas BOMs, que podem ter 2, 3 ou 4 bytes:

00 00 FE FF UTF-32, big endian

FF FE 00 00 UTF-32, little endian

FE FF UTF-16, big endian

FF ELE UTF-16, little endian

EF BB BF UTF-8

Veremos mais adiante neste capítulo como Object Pascal gerencia a BOM dentro de seu streaming
Aulas. A lista técnica aparece bem no início de um arquivo com os dados Unicode reais imediatamente
após. Portanto, um arquivo UTF-8 com conteúdo AB contém cinco valores hexadecimais (3 para a lista
técnica, 2 para as letras):

EF BB BF 41 42

Se um arquivo de texto não tiver nenhuma dessas assinaturas, geralmente é considerado um arquivo de texto ASCII,
mas também pode conter texto com qualquer codificação.

note Por outro lado, ao receber dados de uma solicitação da Web ou por meio de outros protocolos da Internet, você
pode ter um cabeçalho específico (parte do protocolo) indicando a codificação, em vez de depender de uma
lista técnica.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

06: tudo sobre cordas - 171

Olhando para Unicode


Como criamos uma tabela de caracteres Unicode como as que exibi anteriormente para caracteres
ASCII? Podemos começar exibindo pontos de código no Basic Multilingual Plane (BMP), excluindo
os chamados pares substitutos.

note Nem todos os valores numéricos são pontos de código UTF-16 verdadeiros, pois existem alguns valores numéricos inválidos para
caracteres (chamados substitutos) usados para formar um código emparelhado e representar pontos de código acima de 65535.
Um bom exemplo de par substituto é o símbolo usado em partituras musicais para a clave de Fá (ou baixo). Seu
ponto de código de 3 bytes 1D122 é representado em UTF-16 por 4 bytes usando dois valores, D834 seguido por DD22.

A exibição de todos os elementos do BMP exigiria uma grade de 256 x 256, difícil de acomodar
na tela. É por isso que o exemplo ShowUnicode possui uma aba com duas páginas: a primeira
aba possui o seletor primário com 256 blocos, enquanto a segunda página mostra uma grade com
os elementos Unicode reais, uma seção por vez. Este programa tem uma interface de usuário um
pouco mais do que a maioria dos outros no livro, e você pode simplesmente folhear seu código se
estiver interessado apenas em sua saída e não em seus componentes internos.
Quando o programa é iniciado, ele preenche o controle ListView na primeira página do TabControl
com 256 entradas, cada uma indicando o primeiro e o último caractere de um grupo de 256. Aqui
está o código real do manipulador de eventos OnCreate do formulário e uma função simples usada para
exiba cada elemento, produzindo a saída da Figura 6.3:

// Função auxiliar
função GetCharDescr(NChar: Inteiro): string;
começar
se Char(NChar).IsControl então
Resultado:= + IntToStr(NChar)
'Caracteres #' + else '[ ]'
'
Resultado := 'Caracteres #' + IntToStr(NChar) + [' + Caráter(NChar) + ']' ;
fim;

procedimento TForm2.FormCreate(Remetente: TObject);


era
Eu: Inteiro;
ListItem: TListViewItem;
começar
para I := 0 a 255 comece * 256 caracteres cada
//256 Páginas

ListItem := ListView1.Items.Add;
ListItem.Tag := I;
se (I <216) ou (I > 223) então
ListItem.Text :=
GetCharDescr(I * 256) + '/' + GetCharDescr(I * 256 + 255)
outro
ListItem.Text := 'Pontos de código substitutos';
fim;
fim;

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

172 - 06: tudo sobre cordas

Figura 6.3:
A primeira página do
exemplo ShowUnicode
possui uma longa
lista de seções de Unicode
personagens

Observe como o código salva o número da “página” na propriedade Tag dos itens do ListView, informação
que posteriormente é utilizada para preencher uma página. À medida que um usuário seleciona um dos
itens, a aplicação passa para a segunda página do TabControl, preenchendo sua grade de strings
com os 256 caracteres da seção:

procedimento TForm2.ListView1ItemClick(const Sender: TObject;


const AItem: TListViewItem);
era
I, NStart: Inteiro;
começar
NStart := AItem.Tag para I := *256;
0 a 255 do
começar
StringGrid1.Cells[I mod 16, I div 16] :=
IfThen(não Char(I + NStart).IsControl, Char(I + NStart), '');
fim;
TabControl1.ActiveTab := TabItem2;

A função IfThen usada no código acima é um teste bidirecional: se a condição passada no primeiro
parâmetro for verdadeira, a função retorna o valor do segundo parâmetro; caso contrário, retorna o valor
do terceiro. O teste realizado para o primeiro parâmetro usa o método IsControl do auxiliar do tipo Char
para filtrar caracteres de controle não imprimíveis.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

06: tudo sobre cordas - 173

note A função IfThen opera mais ou menos como o operador ?: da maioria das linguagens de programação baseadas
na sintaxe C. Existe uma versão para strings e outra separada para inteiros. Para a versão string, você deve incluir a unidade
System.StrUtils , enquanto, para a versão Integer de IfThen, você deve incluir a unidade System.SysUtils .

A grade de caracteres Unicode produzida pela aplicação é visível na Figura 6.4.


Observe que a saída varia dependendo da capacidade da fonte selecionada e do sistema
operacional específico de exibir um determinado caractere Unicode.

Figura 6.4:
A segunda página do
exemplo ShowUnicode
contém algumas das informações reais
Caracteres Unicode

O tipo de personagem revisitado


Após esta introdução ao Unicode, vamos voltar ao verdadeiro tópico deste capítulo, que é
como a linguagem Object Pascal gerencia caracteres e strings. Introduzi o tipo de dados
Char no Capítulo 2 e mencionei algumas das funções auxiliares de tipo disponíveis
na unidade de personagem. Agora que você entende melhor o Unicode, vale a pena revisitar
essa seção e analisar mais alguns detalhes.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

174 - 06: tudo sobre cordas

Primeiro de tudo, o tipo Char nem sempre representa um ponto de código Unicode. O tipo de dados, na
verdade, usa 2 bytes para cada elemento. Embora represente um ponto de código para elementos no
Basic Multilingual Plane (BMP) do Unicode, um Char também pode fazer parte de um par de valores
substitutos, representando um ponto de código.

Tecnicamente, existe um tipo diferente que você pode usar para representar qualquer ponto de código
Unicode diretamente, e este é o tipo UCS4Char, que usa 4 bytes para representar um valor. Esse
tipo raramente é usado, pois a memória extra necessária geralmente é difícil de justificar, mas,
como você verá em breve, a unidade Caractere (abordada a seguir) também inclui diversas operações
para esse tipo de dados.

De volta ao tipo Char. Lembre-se que este é um tipo ordinal, portanto tem a noção de sequência e
oferece operações de código como Ord, Inc, Dec, High e Low. A maioria das operações estendidas, incluindo
o tipo específico de ajudante, não fazem parte das unidades RTL do sistema básico, mas requerem a
inclusão da unidade Caractere.

Operações Unicode com a unidade de caracteres


A maioria das operações específicas para caracteres Unicode (e também strings Unicode, é claro)
são definidas em unidades especiais chamadas System.Character. Esta unidade define a classe
auxiliar TCharHelper para o tipo Char, que permite aplicar operações diretamente às variáveis desse
tipo.

note A unidade Character também define um registro TCharacter , que é basicamente uma coleção de funções de
classe estática, além de uma série de rotinas globais mapeadas para esses métodos. Estas são funções
mais antigas e obsoletas. A maneira preferida de trabalhar no tipo Char no nível Unicode é usando o
auxiliar de classe.

A unidade também define dois tipos enumerados interessantes. O primeiro é chamado de categoria
TUnicode e mapeia os vários caracteres em categorias amplas, como controle, espaço, letra
maiúscula ou minúscula, número decimal, pontuação, símbolo matemático e muitos outros.
mais. A segunda enumeração é chamada TUnicodeBreak e define a família dos vários espaços (sim,
há mais de um tipo de espaço), hífens e quebras. Se você está acostumado com operações ASCII,
esta é uma grande mudança. Por um lado, os números em Unicode não são
apenas os caracteres entre 0 e 9; os espaços não estão limitados ao caractere #32; e assim por diante
para muitas outras suposições do alfabeto (muito mais simples) de 256 elementos.

O auxiliar do tipo Char tem mais de 40 métodos que compreendem muitos testes e operações
diferentes que podem ser usados para coisas como:

· Obtendo a representação numérica do caractere (GetNumericValue).


· Solicitando a categoria (GetUnicodeCategory) ou comparando o caractere com um
das diversas categorias (IsLetterOrDigit, IsLetter, IsDigit, IsNumber, IsCon-

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

06: tudo sobre cordas - 175

trol, IsWhiteSpace, IsPunctuation, IsSymbol e IsSeparator). Usei a operação IsCon-trol na demonstração anterior.

· Verificar se o caractere é maiúsculo ou minúsculo (IsLower e IsUpper) ou converter seu caso (ToLower e ToUpper).

· Verificar se faz parte de um par substituto UTF-16 (IsSurrogate, IsLowSurrogate e IsHighSurrogate) e converter pares
substitutos de várias maneiras.

· Convertendo de e para UTF32 (ConvertFromUtf32 e ConvertToUtf32) e


Tipo UCS4Char (ToUCS4Char).

· Verificando se faz parte de uma determinada lista de caracteres (IsInArray).


Observe que algumas dessas operações podem ser aplicadas ao tipo como um todo, em vez de a
uma variável específica. Nessa situação você deve chamá-los usando o tipo Char como pré-correção,
como no segundo trecho de código abaixo.
Para experimentar um pouco essas operações em caracteres Unicode, criei um exemplo chamado
CharTest. Um dos exemplos desta demonstração é o efeito de chamar letras maiúsculas
e operações em minúsculas em elementos Unicode. Na verdade, a função UpCase clássica do RTL
funciona apenas para os 26 caracteres do idioma inglês da representação ANSI, enquanto falha em
alguns caracteres Unicode que possuem representações específicas em maiúsculas (nem todos
os alfabetos têm representações em maiúsculas, então isso não é um conceito universal).
Para testar esse cenário, no exemplo CharTest , adicionei o seguinte trecho que tenta converter
uma letra acentuada em maiúscula:

era
Ch1: Caráter;
começar
Capítulo 1 := 'você' ;
'
Mostrar( 'UpCaseù: + UpCase(Ch1));
'
Mostrar( 'ToUpperù: + Ch1.ToUpper);

A chamada tradicional do UpCase não converterá o caractere com acento latino, enquanto a função
ToUp-per funciona corretamente:

UpCase ù: ù
ToUpper ù: Ù

Existem muitos recursos relacionados ao Unicode no auxiliar do tipo Char, como os destacados
no código abaixo, que define uma string como incluindo também um caractere fora de
o BMP (os primeiros 64K de pontos de código Unicode). O trecho de código, também parte do exemplo
CharTest , possui alguns testes nos vários elementos da string, todos retornando True:

era
Str1: sequência;
começar
Str1 := + #9 + '1.'
Char.ConvertFromUtf32(128) +
Char.ConvertFromUtf32($1D11E);

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

176 - 06: tudo sobre cordas

ShowBool(Str1.Chars[0].IsNumber);
ShowBool(Str1.Chars[1].IsPunctuation);
ShowBool(Str1.Chars[2].IsWhiteSpace);
ShowBool(Str1.Chars[3].IsControl);
ShowBool(Str1.Chars[4].IsSurrogate);
fim;

A função de exibição usada neste caso é uma versão adaptada:


procedimento TForm1.ShowBool(Valor: Boolean);
começar
Show(BoolToStr(Valor, Verdadeiro));
fim;

note O ponto de código Unicode $1D11E é o símbolo musical da clave de sol.

Literais de caracteres Unicode


Vimos em vários exemplos que você pode atribuir um literal de caractere individual ou um literal
de string a uma variável do tipo string. Em geral utilizar a representação numérica de um
caractere com o prefixo # é bastante simples. Existem algumas exceções, no entanto.
Para compatibilidade com versões anteriores, literais de caracteres simples são convertidos
dependendo do contexto. Considere a seguinte atribuição simples do valor numérico 128,
que supostamente indica o uso do símbolo monetário Euro (€):
era
Str1: sequência;
começar
Str1 := #$80;

Este código não é compatível com Unicode, pois o ponto de código para esse símbolo é
$8364. O valor, na verdade, não vem das páginas de código ISO oficiais, mas foi uma
implementação específica da Microsoft para Windows. Para facilitar a movimentação do
código existente para Unicode, o compilador Object Pascal pode tratar literais de string de 2
dígitos como caracteres ANSI (o que pode depender da sua página de código real).
Surpreendentemente, se você pegar esse valor, convertê-lo em Char e exibi-lo
novamente, a representação numérica mudará para a correta. Então, executando a instrução:
' - '
Mostrar(Str1 + + IntToStr(Palavra(Str1[1])));

Vou obter a saída:


€ - 8364

Dado que você pode preferir migrar completamente o código antigo e se livrar dos valores literais
baseados em ANSI, você pode alterar o comportamento do compilador usando a diretiva
especial $HIGHCHARUNICODE. Esta diretiva determina como os valores literais entre #$80 e #$FF

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

06: tudo sobre cordas - 177

são tratados pelo compilador. O que discuti anteriormente é o efeito da opção padrão (OFF). Se você
ativá-lo, o mesmo programa produzirá esta saída:

- 128

O número é interpretado como um ponto de código Unicode real e a saída conterá um caractere de
controle não imprimível. Outra opção para expressar esse ponto de código específico (ou qualquer
ponto de código Unicode abaixo de #$FFFF) é usar a notação de quatro dígitos:

Str1 := #$0080;
Isto não é interpretado como o símbolo da moeda Euro, independentemente da configuração da
diretiva $HIGHCHARUNICODE .

note O código acima e a demonstração correspondente funcionam apenas para localidades dos EUA ou da Europa Ocidental. Com
outras localidades, os caracteres entre 128 e 255 são interpretados de forma diferente.

O interessante é que você pode usar a notação de quatro dígitos para expressar caracteres do Extremo
Oriente, como os dois caracteres japoneses a seguir:

Str1 := #$3042#$3044;
' - '
Show(Str1 + + + IntToStr(Ord(Str1.Chars[0])) +
' - '
IntToStr(Ord(Str1.Chars[1])));
exibido, junto com sua representação inteira, como:

Ai- 12354 – 12356

Você também pode usar elementos literais sobre #$FFFF que serão convertidos no par substituto
adequado.

E quanto a caracteres de 1 byte?


Como mencionei anteriormente, a linguagem Object Pascal mapeia o tipo Char para WideChar, mas
ainda define o tipo AnsiChar , principalmente para compatibilidade com o código existente. A
recomendação geral é usar o tipo Byte para uma estrutura de dados de um byte, mas é verdade que
AnsiChar pode ser útil ao processar caracteres de 1 byte.

Embora, em várias versões, o AnsiChar não estivesse disponível em plataformas móveis, a partir da
versão 10.4 esse tipo de dados funciona da mesma forma em todos os compiladores Delphi. Ao
mapear dados para a API de uma plataforma ou ao salvar em arquivos, você geralmente deve ficar
longe do antigo tipo Char de um byte, mesmo que seja compatível. Usar uma codificação Unicode é de
longe a abordagem preferida. É verdade, porém, que o processamento de caracteres de 1 byte pode
ser mais rápido e usar menos memória do que o equivalente de 2 bytes.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

178 - 06: tudo sobre cordas

O tipo de dados String


O tipo de dados string em Object Pascal é muito mais sofisticado do que um simples array de caracteres com recursos
que vão muito além do que a maioria das linguagens de programação oferece para tipos de dados semelhantes.
Nesta seção apresentarei os principais conceitos por trás desse tipo de dados; e, nas próximas seções, exploraremos
alguns desses recursos com mais detalhes.

Na lista com marcadores a seguir, capturei os principais conceitos para entender como as strings
trabalhe na linguagem (lembre-se, você pode usar string sem saber muito disso, pois o comportamento interno é muito
transparente):

· Os dados do tipo string são alocados dinamicamente no heap. Uma variável de string é apenas uma referência
aos dados reais. Não que você precise se preocupar muito com isso, pois o compilador trata isso de forma
transparente. Como para um array dinâmico, conforme você declara uma nova string, ela fica vazia.

· Embora você possa atribuir dados a uma string de várias maneiras, você também pode alocar um valor específico.
quantidade específica de memória chamando a função SetLength . O parâmetro é o
número de caracteres (de 2 bytes cada), a string deve poder ter. Quando você estende uma string, os dados
existentes são preservados (mas podem ser movidos para um novo local de memória física). Quando você reduz
o tamanho, parte do conteúdo provavelmente será perdida. Definir o comprimento de uma string raramente é
necessário. O único caso comum é quando você precisa passar um buffer de string para uma função do
sistema operacional para uma determinada plataforma.

· Se você deseja aumentar o tamanho de uma string na memória (concatenando-a com outra string), mas há algo
mais na memória adjacente, então a string não pode crescer no mesmo local de memória e uma cópia completa
da string deve portanto, ser feita em outro local.

· Para limpar uma string você não opera na referência em si, mas pode simplesmente configurá-la como uma string
'',
vazia, ou você pode usar a constante Vazia , que corresponde a esse valor.

· De acordo com as regras do Object Pascal, o comprimento de uma string (que você pode obter chamando
Length) é o número de elementos válidos, não o número de elementos alocados. Diferentemente de C, que
tem o conceito de terminador de string (#0), todas as versões de Pascal, desde os primórdios, tendem a
favorecer o uso de uma área específica de memória (parte da string) onde as informações reais de comprimento
são armazenadas. . Às vezes, porém, você encontrará strings que também possuem um terminador.

· As strings Object Pascal usam um mecanismo de contagem de referências , que controla quantas variáveis de string
estão se referindo a uma determinada string na memória. A contagem de referências liberará a memória
quando uma string não for mais usada - ou seja, quando houver
não há mais variáveis de string referentes aos dados e a contagem de referência atinge
zero.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

06: tudo sobre cordas - 179

· Strings usam uma técnica copy-on-write , que é altamente eficiente. Quando você atribui uma string a outra
ou passa uma para um parâmetro de string, nenhum dado é copiado e a contagem de referências
aumenta. Contudo, se você alterar o conteúdo de uma das referências, o sistema primeiro fará
uma cópia e depois alterará apenas essa cópia, permanecendo as outras referências inalteradas.

· O uso da concatenação de strings para adicionar conteúdo a uma string existente é geralmente muito
rápido e não apresenta nenhuma desvantagem significativa. Embora existam abordagens
alternativas, concatenar strings é rápido e poderoso. Isso não é verdade para muitas linguagens de
programação atualmente.

Acho que essa descrição pode ser um pouco confusa, então vamos ver o uso de strings na prática.
Daqui a pouco chegarei a uma demonstração mostrando algumas das operações acima, incluindo contagem
de referência e cópia na gravação. Antes de fazermos isso, entretanto, deixe-me voltar às operações auxiliares
de string e algumas outras funções RTL fundamentais para gerenciamento de strings.

Primeiro, vamos examinar alguns dos elementos da lista anterior em termos de código real.
Dado que as operações com strings são bastante contínuas, é difícil compreender completamente o que
acontece, a menos que você comece a olhar dentro da estrutura de memória das strings, o que farei mais
adiante neste capítulo, pois seria muito avançado nesta fase do livro. Então, vamos começar com algumas
operações simples com strings, extraídas do exemplo Strings101 :

era
String1, String2: string;

começar
Sequência1 := 'Olá Mundo' ;
Sequência2 :=' Sequência1;
Mostrar( '1: + String1);
Mostrar( '2: ' + String2);
String2 := String2
' + ', de novo';
Mostrar( '1: + String1);
'
Mostrar( '2: + String2);
fim;

Este primeiro trecho, quando executado, mostra que se você atribuir duas strings ao mesmo conteúdo, a
modificação de uma não afetará a outra. Ou seja, String1 não é afetado pelas alterações em String2:

1: Olá mundo
2: Olá mundo
1: Olá mundo
2: Olá mundo, de novo

Ainda assim, como descobriremos melhor em uma demonstração posterior, a atribuição inicial não causa uma
cópia da string, a cópia é atrasada, um recurso chamado copy-on-write.

Outra característica importante a ser entendida é como o comprimento é gerenciado. Se você solicitar o
comprimento de uma string, obterá o valor real (que é armazenado nos metadados da string,

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

180 - 06: tudo sobre cordas

tornando a operação muito rápida). Mas se você chamar SetLength, estará alocando memória, que na
maioria das vezes não será inicializada.

Isso geralmente é usado ao passar a string como um buffer para uma função externa do sistema. Se,
no entanto, você precisar de uma string em branco, poderá usar o pseudoconstrutor Create.
Finalmente, você pode usar SetLength para cortar uma string. Tudo isso é demonstrado pelo seguinte
código:

era
Sequência1: sequência;
começar
Sequência1 := 'Olá Mundo' ;
Mostrar(String1); '
Mostrar( 'Comprimento: + String1.Length.ToString);

SetLength(String1, 100);
Mostrar(String1); '
Mostrar( 'Comprimento: + String1.Length.ToString);

Sequência1 := 'Olá Mundo' ;


Mostrar(String1); '
Mostrar( 'Comprimento: + String1.Length.ToString);
' '
String1 := String1 + string.Create(SetLength(String1, , 100);
100);
Mostrar(String1); '
Mostrar( 'Comprimento: + String1.Length.ToString);

A saída é mais ou menos parecida com a seguinte:

Olá Mundo
Comprimento: 11
~ÿ~ÿ~ÿÿ
olá mundo ~ ÿ~ÿ~~~ÿ ÿ
ÿ ÿ u ÿ ÿ ÿ ÿ ÿ ~ÿ~ÿ~ÿ~ÿ~ÿ~;~ ~ÿ~ÿ~ÿ ÿ
Comprimento: 100
Olá Mundo
Comprimento: 11
Olá Mundo
Comprimento: 100

O terceiro conceito que quero sublinhar nesta seção é o de uma string vazia. Uma string está vazia
quando seu conteúdo é, você adivinhou, uma string vazia. Tanto para atribuição quanto para teste você
pode usar duas cotações consecutivas ou funções específicas:

era
Sequência1: sequência;

começar
Sequência1 := 'Olá Mundo'
'' então ;
se Sequência1 =
Mostrar 'Vazio' )
(outro
Mostrar( 'Não está vazio' );

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

06: tudo sobre cordas - 181

'' Ou String1.Empty;
String1 := se ; //
String1.IsEmpty então
Mostrar 'Vazio' )
(outro
Mostrar( 'Não está vazio' );

O código acima gera esta saída simples:

Não está vazio


Vazio

Passando Strings como Parâmetros


Como expliquei, se você atribuir uma string a outra, estará apenas copiando uma referência,
enquanto a string real na memória não será duplicada. No entanto, se você escrever um código que
altere essa sequência, a sequência será primeiro copiada (somente nesse ponto) e depois modificada.

Algo muito semelhante acontece quando você passa uma string como parâmetro para uma função ou
procedimento. Por padrão, você obtém uma nova referência e se modificar a string na função, a
mudança não afetará a string original. Se você deseja um comportamento diferente, ou seja, a
capacidade de modificar a string original na função, você precisa passar a string por referência usando
a palavra-chave var (como acontece com a maioria dos outros tipos de dados simples e gerenciados).

Mas e se você não quiser modificar a string passada como parâmetro? Nesse caso, você
pode aplicar uma otimização usando o modificador const para o parâmetro. Fazer isso significa
que o compilador não permitirá que você altere a string na função ou procedimento, mas
também otimizará a operação de passagem de parâmetro como resultado. Na verdade, uma const
string não exige que a função aumente a contagem de referência da string quando começa e diminua
quando termina, pois sabe que a string não pode ser modificada.

Embora as rotinas de gerenciamento de strings sejam muito rápidas, executá-las milhares ou milhões
de vezes adicionará uma ligeira sobrecarga ao seu programa. É por isso que passar string como const
é recomendado nos casos em que a função não precisa modificar o valor do parâmetro string (embora
haja possíveis problemas abordados na nota abaixo).

Em termos de codificação, estas são as declarações de três procedimentos com um parâmetro string
passado de maneiras diferentes:

procedimento ShowMsg1(Str:string);
procedimento ShowMsg2(var Str: string);
procedimento ShowMsg3(const Str: string);

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

182 - 06: tudo sobre cordas

note Nos últimos anos, tem havido um forte impulso para passar todos os parâmetros de string como const, a menos que o
funções e métodos modificam essas strings. Há uma advertência muito grande, no entanto. Para um parâmetro de
string constante, o compilador pega a referência da string e não a “gerencia” (sem contagem de referências, etc.),
tratando-a como um ponteiro para a localização da memória. O compilador verifica corretamente se o código da rotina
não altera o parâmetro string. No entanto, ele não tem controle sobre o que acontece com a string original à qual o
parâmetro se refere.
Alterações nessa string podem afetar seu layout e localização de memória, algo que um parâmetro de string regular
pode manipular (strings com múltiplas referências fazem uma operação de cópia na gravação automaticamente),
enquanto um parâmetro de string constante será afetado por essas alterações. Em outras palavras, uma alteração na
string original torna o parâmetro const referente a ela inválido, e seu uso provavelmente causará um problema de memória.
erro de acesso.

O uso de [] e contagem de caracteres de string


Modos
Como você provavelmente saberá se tiver usado Object Pascal ou qualquer outra linguagem de
programação, uma operação de string de chave está acessando um dos elementos de uma string, algo
geralmente obtido usando a notação de colchetes ([] ) da mesma maneira que você seria
acessar os elementos de um array.

No Object Pascal existem duas maneiras ligeiramente diferentes de realizar essas operações:

· A operação auxiliar do tipo string Chars[] (a lista inteira está na próxima seção) é uma
acesso de caracteres somente leitura que usa um índice baseado em zero.

· O operador de string padrão [] suporta leitura e escrita, e usa by


padrão é o índice clássico baseado em Pascal. Esta configuração pode ser modificada com uma
diretiva do compilador.

Vou prestar alguns esclarecimentos sobre este assunto após uma breve nota histórica abaixo. A
razão para esta nota, que você pode pular se não lhe interessar, é que seria difícil entender por que a
linguagem se comporta da maneira atual sem olhar para o que aconteceu ao longo do tempo.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

06: tudo sobre cordas - 183

note Deixe-me olhar para trás no tempo por um segundo, para explicar como chegamos onde estamos hoje. Nos primeiros dias
Na linguagem Pascal, as strings eram tratadas como um array de caracteres em que o primeiro elemento (que é o elemento
zero do array) era usado para armazenar o número de caracteres válidos na string, ou o comprimento da string. Naquela
época, enquanto a linguagem C tinha que recalcular o comprimento de uma string a cada vez, procurando por um
terminador NULL, o código Pascal podia simplesmente fazer uma verificação direta naquele byte. Dado que o
o byte número zero foi usado para o comprimento, aconteceu que o primeiro caractere real armazenado na string estava
na posição um.
Com o tempo, quase todas as outras linguagens tinham strings e arrays baseados em zero. Mais tarde, Object Pascal adotou
matrizes dinâmicas baseadas em zero e a maioria das bibliotecas RTL e de componentes usavam estruturas de dados
baseadas em zero, com strings sendo uma exceção significativa.
Ao migrar para o mundo móvel, os designers da linguagem Object Pascal decidiram dar “prioridade” às strings baseadas em
zero, permitindo que os desenvolvedores ainda usassem o modelo mais antigo caso tivessem código-fonte Object Pascal
existente para migrar, controlando o comportamento com um diretiva do compilador. No Delphi 10.4, porém, a decisão
original foi revertida para dar conta de maior consistência do código-fonte, independentemente da plataforma de destino. Em
outras palavras, decidiu-se favorecer o objetivo “multiplataforma de fonte única” em detrimento do objetivo “ser como outras
linguagens modernas”.

Se quisermos fazer uma comparação para explicar melhor as diferenças na base do índice,
consideremos como os pisos são contados na Europa e na América do Norte (sinceramente, não sei
quanto ao resto do mundo). Na Europa, o piso térreo é o piso zero e o primeiro andar é o que está
acima dele (por vezes formalmente indicado como “piso um acima do solo”). Na América do
Norte, o primeiro andar é o térreo e o segundo primeiro é o primeiro acima do nível do solo. Por outras
palavras, a América utiliza um índice mínimo de base um, enquanto a Europa utiliza um índice
mínimo de base zero.

Para strings, a grande maioria das linguagens de programação usa a notação de base zero,
independentemente do continente em que foram inventadas. Delphi e a maioria dos dialetos Pascal
usam a notação baseada em um.

Deixe-me explicar um pouco melhor a situação com índices de string. Como mencionei acima, a
notação Chars[] invariavelmente usa um índice baseado em zero. Então, se você escrever
era
Sequência1: sequência;
começar
String1 := 'Olá Mundo' ;
Mostrar(String1.Chars[1]);

a saída será:
e

E se você usar a notação direta [], qual será o resultado de:

Mostrar(String1[1]);

Por padrão, a saída será H. No entanto, pode ser e se a definição do compilador $ZER-
OBASEDSTRING estiver ativada . A recomendação neste momento (ou seja, após o lançamento do
Delphi 10.4) é usar strings baseadas em um em todo o seu código e evitar problemas que

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

184 - 06: tudo sobre cordas

pode resultar de código legado intermediário projetado em torno do modelo padrão baseado em
zero.

Mas e se você quiser escrever um código que funcione independentemente da configuração


$ZEROBASEDSTRING ? Para esta situação, você pode abstrair o índice usando Low(string) como
índice do primeiro valor e High(string) para o último valor. Eles funcionam retornando o valor adequado
independentemente de como a configuração do compilador para a string base está definida:

era
S: corda;
Eu: Inteiro;
começar
S:= 'Olá Mundo' ;
para I := Baixo(S) a Alto(S) faça
Mostrar(S[I]);

Em outras palavras, uma string invariavelmente possui elementos que vão desde o resultado do Low
função à da função High aplicada à mesma string.

note Uma string é apenas uma string, e o conceito de uma string baseada em zero está completamente errado. A estrutura de dados
na memória não é diferente em nada, então você pode passar qualquer string para qualquer função que use uma notação
com qualquer valor base, e não há problema algum. Em outras palavras, se você tiver um fragmento de código
acessando strings com uma notação baseada em zero, poderá passar a string para uma função que é compilada
usando as configurações para uma notação baseada em um.

Concatenando Strings
Já mencionei que, diferentemente de outras linguagens, Object Pascal tem suporte total para
concatenação direta de strings, que na verdade é uma operação bastante rápida. Neste capítulo, mostrarei
apenas alguns códigos de concatenação de strings com alguns testes de velocidade. Mais tarde, no
Capítulo 18, abordarei brevemente a classe TStringBuilder , que segue a notação .NET
para montar uma corda a partir de diferentes fragmentos. Embora existam razões para usar o
TStringBuilder, o desempenho não é o mais relevante (como mostrará o exemplo a seguir).

Então, como concatenamos strings no Object Pascal? Fazemos isso usando a operação +
tor:

era
Str1, Str2: sequência;
começar
Str1 := 'Olá Mundo' ;
Str2 := ;
' '
Str1 := Str1 + +Str2;

Observe como usei a variável Str1 à esquerda e à direita da tarefa, adicionando mais conteúdo a uma
string existente em vez de atribuir a uma nova

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

06: tudo sobre cordas - 185

um. Ambas as operações são possíveis, mas adicionar conteúdo a uma string existente é onde você pode obter um
bom desempenho.

Este tipo de concatenação também pode ser feita em loop, como o seguinte extraído do exemplo LargeString :

usa
Diagnóstico do sistema;
const
MaxLoop = 2_000_000; // Dois milhões

era
Str1, Str2: sequência;
Eu: Inteiro;
T1: Tcronômetro;
começar
'
Str1 := 'Marco ;
'
Str2 := 'Eu canto ;

T1 := TStopwatch.StartNew;
para I := 1 para MaxLoop faça
Str1 := Str1 + Str2;

T1.Parada; '
'Comprimento: + Str1.Length.ToString);
'
'Concatenação: + T1.ElapsedMilliseconds.ToString);
Mostrar(Mostrar(fim;

Ao executar este código, obtenho o seguinte tempo em uma máquina virtual Windows e em um dispositivo Android
(o computador é um pouco mais rápido):

Comprimento: 12000006 // janelas (em a máquina virtual)

Concatenação: 59

Comprimento: 12000006 // Android (no dispositivo)


Concatenação: 991

O exemplo também possui código semelhante baseado na classe TStringBuilder . Embora eu não queira entrar
em detalhes desse código (novamente, descreverei a classe no Capítulo 18), quero
para compartilhar o tempo real, para comparação com o tempo de concatenação simples que acabamos de exibir

Comprimento: 12000006 // janelas (em a máquina virtual)

StringBuilder: 79

Comprimento: 12000006 // Android (no dispositivo)


StringBuilder: 1057

Como você pode ver, a concatenação pode ser considerada com segurança a opção mais rápida.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

186 - 06: tudo sobre cordas

As operações auxiliares de string


Dada a importância do tipo string, não é surpresa que o auxiliar desse tipo tenha uma lista bastante longa
de operações que você pode executar. E, dada a sua importância e a semelhança destas operações na
maioria das aplicações, penso que vale a pena examinar esta lista com algum cuidado.

Há uma diferença fundamental entre usar as funções clássicas de manipulação de string global do Delphi
e os métodos auxiliares de string: as operações clássicas assumem strings baseadas em um,
enquanto a operação auxiliar de string usa lógica baseada em zero!

Agrupei logicamente as operações auxiliares de string (a maioria das quais tem muitas versões
sobrecarregadas), descrevendo resumidamente o que elas fazem, considerando que, muitas vezes, seus
nomes são bastante intuitivos:

· Operações de cópia ou cópia parcial como Copy, CopyTo, Join e SubString


· Operações de modificação de strings como Inserir, Remover e Substituir
· Para conversão de vários tipos de dados em string, você pode usar Parse e Format
· A conversão para vários tipos de dados, quando possível, pode ser obtida usando ToBoolean,
ToInteger, ToSingle, ToDouble e ToExtended , enquanto você pode transformar uma string em
uma matriz de caracteres com ToCharArray
· Preencha uma string com espaços em branco ou caracteres específicos com PadLeft, PadRight e
uma das versões sobrecarregadas do Create. Ao contrário, você pode remover espaços em branco
em uma extremidade da string ou em ambos usando TrimRight, TrimLeft e Trim
· Comparação de strings e teste de igualdade usando Compare, CompareOrdinal, CompareText,
CompareTo e Equals – mas lembre-se de que você também pode, até certo ponto, usar o
operador de igualdade e os operadores de comparação
· Alterando maiúsculas e minúsculas com LowerCase e UpperCase, ToLower e ToUpper e ToUpper-
Invariante

· Teste o conteúdo da string com operações como Contém, StartsWith, EndsWith. Procurar
na string pode ser feito usando IndexOf para encontrar a posição de um determinado caractere (do
início ou de um determinado local), o semelhante IndexOfAny (que procura um dos elementos de uma
matriz de caracteres), o LastIndexOf e LastIndexO- fQuaisquer operações que funcionem de trás para
frente a partir do final da string e as operações de propósito especial IsDelimiter e LastDelimiter

· Acesse informações gerais sobre a string com funções como Length, que
retorna o número de caracteres; CountChars, que também leva em consideração pares substitutos;
GetHashCode, que retorna um hash da string; e os vários testes de “vazio” que incluem IsEmpty,
IsNullOrEmpty e IsNullOrWhiteSpace
· Operações especiais de strings como Split, que divide uma string em múltiplas strings
com base em um caractere específico e removendo ou adicionando aspas ao redor da string com
QuotedString e DeQuoted

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

06: tudo sobre cordas - 187

· E, por fim, acesse caracteres individuais com Chars[], que possui o número
índice do elemento da string entre colchetes. Isso pode ser usado apenas para ler um valor (não
para alterá-lo) e usa um índice baseado em zero como todas as outras operações auxiliares de
string.

É importante notar, de fato, que todos os métodos auxiliares de string foram construídos seguindo a
convenção de string usada por muitas outras linguagens, que inclui o conceito de que os elementos de
string começam com zero e vão até o comprimento da string. menos um. Em outras palavras, como
já mencionei anteriormente, mas vale a pena sublinhar novamente, todas as operações auxiliares de
string usam índices baseados em zero como parâmetros e valores de retorno.

note A operação Split é relativamente nova no Object Pascal RTL. Uma abordagem anteriormente comum era atribuir
uma string a uma lista de strings, após definir um separador de linha específico e, posteriormente, acessar as
strings ou linhas individuais. A operação Split é significativamente mais eficiente e flexível.

Dada a grande quantidade de operações que você pode aplicar diretamente em strings, eu poderia ter
criado vários projetos demonstrando essas capacidades. Em vez disso, vou me limitar a
algumas operações relativamente simples, embora sejam muito comuns.

O exemplo StringHelperTest possui dois botões. Em cada um deles a primeira parte do código
constrói e exibe uma string:

era
Str1, Str2: sequência;
I, NÍndice: Inteiro;
começar
''
Str1 := ;

// Criar sequência
para I := 1 a 10 faça
'
Str1 := Str1 + 'Objeto ;

Str2:= string.Copy(Str1); '


Str1 := Str2 + 'Pascal + Str2.SubString(10, 30);
Mostrar(Str1);
Observe como usei a função Copy para criar uma cópia exclusiva dos dados da string, em vez de um
alias, embora, nesta demonstração em particular, isso não tenha feito nenhuma diferença. A chamada
SubString no final é usada para extrair uma parte da string. O texto resultante é:

Objeto Objeto Objeto Objeto Objeto Objeto Objeto Objeto Objeto Objeto Objeto
Pascal ect Objeto Objeto Objeto Objeto Objeto
Após esta inicialização, o primeiro botão possui código para buscar uma substring e repetir tal busca,
utilizando um índice especificado pelo usuário, para contar as ocorrências de uma determinada
string (um único caractere no exemplo):

// Encontrar substring

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

188 - 06: tudo sobre cordas

'
'Pascal no: +
Mostrar(Str1.IndexOf('Pascal' ).Para sequenciar);

// Contar ocorrências
Eu:= -1;
NContagem := 0;
repita
I := Str1.IndexOf(se I >= 0 'O' , eu + 1); // Pesquisar a partir do próximo elemento
então
Inc(Ncontagem); // Achei um
até eu < 0;
'
'Ó encontrado: +
'
Mostrar(NCount.ToString + vezes' );

Eu sei que o loop de repetição não é dos mais simples: ele começa com um índice negativo, pois
qualquer pesquisa seguinte começa com o índice após o atual; conta ocorrências; e
seu encerramento se baseia no fato de que, caso o elemento não seja encontrado, retorna -1. A saída
do código é:

Pascal em: 70
Ó encontrado: 14 vezes

O segundo botão possui código para realizar uma busca e substituição de um ou mais
elementos de uma string por outra coisa. Na primeira parte, cria uma nova string copiando
a parte inicial e final e adicionando algum texto novo no meio. No segundo, utiliza a
função Substituir que pode operar em múltiplas ocorrências simplesmente passando-
lhe o sinalizador adequado (rfReplaceAll).
Este é o código:

// Substituição única
NIndex := Str1.IndexOf(Str1 := 'Pascal' );
Str1.SubString(0, NIndex) + Str1.SubString(NIndex + 'Objeto' +
().Length); 'Pascal'
Mostrar(Str1);

// Substituição múltipla
Str1 := Str1.Replace(Mostrar(Str1);'O' , 'o' , [rfReplaceAll]);

Como a saída é bastante longa e difícil de ler, listei apenas a parte central de cada string:

... Objeto Pascal etc. Objeto Objeto...


... Objeto Objeto ect. Objeto Objeto...
...objeto objeto etc. objeto objeto...

Novamente, esta é apenas uma amostra mínima das operações de string ricas que você pode
executar usando as operações disponíveis para o tipo de string usando seu auxiliar de tipo.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

06: tudo sobre cordas - 189

Mais funções de string RTL


Um efeito da decisão de implementar o string helper seguindo os nomes de operações comuns em
outras linguagens de programação é o fato de que os nomes das operações do tipo muitas vezes
divergem dos tradicionais Object Pascal (que ainda estão disponíveis como funções globais hoje.

A tabela a seguir contém alguns dos nomes de funções não correspondentes :

Global Auxiliar de tipo de string


Posição Índice de
IntToStr Analisar
StrToInt Para inteiro
CaracteresOf Criar
StringReplace Substituir

note Lembre-se de que há uma grande diferença entre as operações auxiliares globais e Char: o primeiro grupo usa uma
notação baseada em um para indexar elementos dentro de uma string, enquanto o último grupo usa uma notação
baseada em zero (conforme explicado anteriormente).

Estas são apenas as funções mais comumente usadas da string RTL que mudaram de nome, enquanto
muitas outras ainda usam o mesmo, como UpperCase ou QuotedString. A unidade Sys-tem.SysUtils
tem muito mais, e a unidade System.StrUtils específica também tem muitos
funções focadas na manipulação de strings que não fazem parte do auxiliar de string.

Algumas funções notáveis que fazem parte da unidade System.StrUtils são:

· ResemblesText, que implementa um algoritmo Soundex (um algoritmo que identifica palavras pela
forma como soam em vez de como são realmente escritas);

· DupeString, que retorna o número solicitado de cópias de uma determinada string;

· IfThen, que retorna a primeira string passada se uma condição for verdadeira, caso contrário retornará a
segunda string (usei essa função em um trecho de código anteriormente neste capítulo);

· ReverseString, que retorna uma string com a sequência de caracteres oposta.

Formatando Strings
Ao concatenar strings com o operador de adição (+) e usar algumas das funções de conversão, você
pode realmente construir strings complexas a partir de valores existentes de vários tipos de dados, há
uma abordagem diferente e mais poderosa para formatar números, valores monetários e outras strings
em uma string final. A formatação complexa de strings pode ser obtida chamando a função Format , um
mecanismo muito tradicional, mas ainda extremamente comum, não apenas em Object Pascal, mas na
maioria das linguagens de programação.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

190 - 06: tudo sobre cordas

história A família de “string de formato de impressão” ou funções printf remonta aos primeiros dias da programação.
ming e linguagens como FORTRAN 66, PL/1 e ALGOL 68. A estrutura de string de formato específico ainda em
uso hoje (e usada pelo Object Pascal) é próxima da função printf da linguagem C. Para uma visão geral
histórica, você pode consultar en.wikipedia.org/wiki/Printf_format_string.

A função Format toma como parâmetro uma string com o texto básico e alguns espaços reservados
(marcados pelo símbolo %) e um array de valores, geralmente um para cada um dos espaços reservados. Por
exemplo, para formatar dois números em uma string você pode escrever:

Formato ( 'Primeiro %d, Segundo %d' , [N1, N2]);

onde N1 e N2 são dois valores inteiros. O primeiro espaço reservado é substituído pelo primeiro valor, o
segundo pelo segundo e assim por diante. Se o tipo de saída do espaço reservado (indicado pela letra após
o símbolo % ) não corresponder ao tipo do parâmetro correspondente, ocorrerá um erro de tempo de
execução. Da mesma forma, não passar parâmetros suficientes também causa um erro de tempo de
execução. Não ter verificação de tipo em tempo de compilação é a maior desvantagem de usar a função
Format .

A função Format usa um parâmetro de matriz aberta (um parâmetro que pode ter um número arbitrário de
valores ou tipos de dados arbitrários, conforme abordado no Capítulo 5). Além de usar %d, você pode usar
um dos muitos outros espaços reservados definidos por esta função e listados resumidamente na tabela a
seguir. Esses espaços reservados fornecem uma saída padrão para o tipo de dados determinado. No
entanto, você pode usar outros especificadores de formato para alterar a saída padrão. Um especificador de
largura, por exemplo, determina um número fixo de caracteres na saída; enquanto um especificador de
precisão indica o número de dígitos decimais. Por exemplo,

Formatar( '%8d' , [N1]);

converte o número N1 em uma sequência de oito caracteres, alinhando o texto à direita (use o símbolo de
menos (-) para especificar a justificação à esquerda) preenchendo-o com espaços em branco. Aqui está a
lista de espaços reservados de formatação para os vários tipos de dados:

d (decimais) O valor inteiro correspondente é convertido em uma string decimal


dígitos.
x (hexadecimal) O valor inteiro correspondente é convertido em uma sequência de dígitos hexadecimais.

p (ponteiro) O valor do ponteiro correspondente é convertido em uma string expressa com dígitos
hexadecimais.
s (sequência) O valor de string, caractere ou PChar (ponteiro para uma matriz de caracteres)
correspondente é copiado para a string de saída.
e (exponencial) O valor de ponto flutuante correspondente é convertido em uma string baseada
em notação científica.
f (ponto flutuante) O valor de ponto flutuante correspondente é convertido em uma string baseada na notação
de ponto flutuante.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

06: tudo sobre cordas - 191

g (geral) O valor de ponto flutuante correspondente é convertido para o valor mais curto
possível string decimal usando ponto flutuante ou notação exponencial.

n (número) O valor de ponto flutuante correspondente é convertido em uma sequência de ponto


flutuante usando separadores de milhares geralmente definidos por configurações
regionais.
m (dinheiro) O valor de ponto flutuante correspondente é convertido em uma string que representa
um valor monetário. A conversão é geralmente baseada
em configurações regionais.

A melhor maneira de ver exemplos dessas conversões é experimentar você mesmo strings de formato. Para tornar isso
mais fácil, escrevi o exemplo FormatString que permite ao usuário fornecer strings de formatação para alguns valores
inteiros predefinidos.

O formulário do programa possui uma caixa de edição acima dos botões, contendo inicialmente uma string de
formatação simples predefinida que atua como espaço reservado ('%d - %d - %d'). O primeiro botão
do aplicativo permite exibir uma sequência de formato de amostra mais complexa na caixa de edição (o código tem uma
atribuição simples ao texto de edição da sequência de formato 'Valor %d, Alinhar %4d, Preencher %4.4d'). O
segundo botão permite aplicar a string de formato aos valores predefinidos, usando o seguinte código:

era
StrFmt: string;
N1, N2, N3: Inteiro;
começar
StrFmt := Edit1.Text;
N1:= 8;
N2:= 16;
N3:= 256;

Mostrar(Formato( 'Sequência de formato: %s' , [StrFmt]));


Mostrar(Formato( 'Dados de entrada: [%d, %d, %d]' , [N1, N2, N3]));
Mostrar(Formato( 'Saída: %s'//
'' , [Formato(StrFmt, [N1, N2, N3])]));
Mostrar(); Linha em branco
fim;

Se você exibir a saída primeiro com a sequência de formato inicial e depois com a sequência de formato de amostra
(ou seja, se você pressionar o segundo botão, o primeiro e depois o segundo novamente), deverá obter uma saída
como a seguinte:

Sequência de formato: %d - %d - %d
Dados de entrada: [8, 16, 256]
Resultado: 8 - 16 - 256

String de formato: Valor %d, Alinhar %4d, Preencher %4.4d


Dados de entrada: [8, 16, 256]
Saída: Valor 8, Alinhar 16, Preencher 0256

No entanto, a ideia por trás do programa é editar a sequência de formatação e experimentá-la para ver o que as
várias opções de formatação disponíveis fazem.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

192 - 06: tudo sobre cordas

A estrutura interna das strings


Embora geralmente você possa usar strings sem saber muito sobre seus componentes internos, é interessante
dar uma olhada na estrutura de dados real por trás desse tipo de dados. Nos primórdios da linguagem Pascal, as
strings tinham no máximo 255 elementos de um byte por caractere, sendo o primeiro byte (ou zero byte) para
armazenar o comprimento. Muito tempo se passou desde aqueles primeiros dias, mas o conceito de ter
alguma informação extra sobre a string armazenada como parte de seus dados continua sendo uma abordagem
adotada pela linguagem Object Pascal (ao contrário de muitas linguagens que derivam de C e usam a linguagem
Object Pascal). conceito de um terminador de string).

note ShortString é o nome do tipo tradicional de string Pascal, uma string de caracteres de um byte ou
AnsiChar limitado a 255 caracteres. O tipo ShortString ainda está disponível nos compiladores desktop, mas não
nos móveis. Você pode representar uma estrutura de dados semelhante com uma matriz dinâmica de bytes, ou
TBytes, ou matrizes estáticas simples de elementos Byte .

Como já mencionei, uma variável string nada mais é do que um ponteiro para uma estrutura de dados alocada
no heap. Na verdade, o valor armazenado na string não é uma referência ao início da estrutura de dados, mas
uma referência ao primeiro dos caracteres da string, com metadados de string disponíveis em deslocamentos
negativos desse local. A representação na memória dos dados do tipo string é a seguinte:

-12 -10 -8 -4 Endereço de referência de string

Página de código Tamanho do elemento Contagem de referência Comprimento Primeiro caractere da string

O primeiro elemento (contando regressivamente a partir do início da própria string) é um número inteiro que
define o comprimento da string e o segundo elemento contém a contagem de referência.
Outros campos (usados em compiladores de desktop) são o tamanho do elemento em bytes (1 ou 2 bytes) e a
página de código para tipos de string mais antigos baseados em ANSI.

Surpreendentemente, é possível acessar a maioria desses campos com funções específicas de metadados de
string de baixo nível, além da óbvia função Length :

função StringElementSize (const S: string): Palavra;


função StringCodePage (const S: string): Palavra;
função StringRefCount (const S: string): LongInt;

Por exemplo, você pode criar uma string e pedir algumas informações sobre ela, como fiz no exemplo
StringMetaTest :

era
Str1: sequência;
começar
Str1 := 'F' + string.Create( 'o' , 2);
'
Mostrar( 'Tamanho de:
' + SizeOf(Str1).ToString);
Mostrar( 'Comprimento: + Str1.Length.ToString);

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

06: tudo sobre cordas - 193

'
'StringElementSize: + StringElementSize(Str1).ToString);
'
Mostrar(Mostrar(+StringRefCount(Str1).ToString);
'StringRefCount:
'
'StringCodePágina:
Mostrar(+StringCodePage(Str1).ToString);
se StringCodePage(Str1) = DefaultUnicodeCodePage então
'É Unicode') ;
'
'Tamanho em bytes: +
Show(Show((Comprimento(Str1)
'
* StringElementSize(Str1)).ToString);
Mostrar(+ByteLength(Str1).ToString);
'ByteComprimento:

note Há uma razão específica para o programa construir a string 'Foo' dinamicamente em vez de atribuir um con-
constante, e isso ocorre porque as strings constantes têm a contagem de referência desativada (ou definida como -1).
Na demonstração eu preferi mostrar um valor adequado para a contagem de referência, daí a construção dinâmica da string.

Este programa produz uma saída semelhante à seguinte:

Tamanho de: 4
Comprimento: 3
StringElementSize: 2
StringRefCount: 1
StringCodePágina: 1200
É Unicode
Tamanho em bytes: 6
Comprimento de bytes: 6

A página de código retornada por uma variável do tipo UnicodeString é 1200, um número armazenado na
variável global DefaultUnicodeCodePage. No código acima (e em sua saída) você pode notar claramente
a diferença entre o tamanho de uma variável de string (invariavelmente 4), o comprimento lógico e o
comprimento físico em bytes.

Isso pode ser obtido multiplicando o tamanho em bytes de cada caractere pelo número de caracteres ou
chamando ByteLength. Esta função, no entanto, não suporta alguns dos tipos de string do compilador
de desktop mais antigo.

Olhando para Strings na Memória


A capacidade de examinar os metadados de uma string pode ser usada para entender melhor como a string
o gerenciamento de memória funciona, principalmente quando se trata de contagem de referências. Para
esse propósito, adicionei mais código ao exemplo StringMetaTest .

O programa possui duas strings globais: MyStr1 e MyStr2. O programa atribui uma string dinâmica
à primeira das duas variáveis (pelo motivo explicado anteriormente na nota) e então atribui a segunda
variável à primeira:

MyStr1 := string.Create([MyStr2 := 'H', 'e', 'eu', 'eu', 'o' ]);


MyStr1;

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

194 - 06: tudo sobre cordas

Além de trabalhar nas strings, o programa mostra seu estado interno, utilizando a seguinte função
StringStatus :

função StringStatus (const Str: string): string;


começar
'
Resultado := 'Endereço: + IntToStr(Inteiro(Str)) +
'
' + IntToStr(Comprimento(Str)) +
Apenas:

' + IntToStr(PInteger(Integer(Str) - 8)^) +


Referência:

',', ',Val: + Str;


fim;

É importante na função StringStatus passar o parâmetro string como um const


parâmetro. Passar este parâmetro por cópia (como faria sem const) causará o efeito colateral de ter
uma referência extra à string enquanto a função está sendo executada. Por outro lado, passar o
parâmetro por meio de uma referência (var) ou constante (const) não implica uma referência adicional
à string. Neste caso, usei um parâmetro const ,
já que a função não deve modificar a string.

Para obter o endereço de memória da string (útil para determinar sua identidade real e ver quando duas
strings diferentes se referem à mesma área de memória), simplesmente fiz uma conversão de tipo
codificada do tipo string para o tipo inteiro. Strings são referências - na prática, são ponteiros: seu valor
contém a localização real da string na memória, não a string em si.

O código usado para testar o que acontece com a string é o seguinte:

'
Mostrar( 'MinhaStr1 - ' + StringStatus(MyStr1));
Mostrar( 'MyStr2 - 'a' + StringStatus(MyStr2));
MinhaStr1 [1] := ;
Mostrar( 'Alterar 2º caractere' );
'
Mostrar( 'MinhaStr1 -
' + StringStatus(MyStr1));
Mostrar( 'MeuStr2 - + StringStatus(MyStr2));

Inicialmente, você deve obter duas strings com o mesmo conteúdo, o mesmo local de memória e uma
contagem de referência de 2.

MyStr1 - Endereço: 51837036, Len: 5, Ref: 2, Val: Olá


MyStr2 - Endereço: 51837036, Len: 5, Ref: 2, Val: Olá

À medida que o aplicativo altera o valor de uma das duas strings (não importa qual), a localização da
memória da string atualizada mudará. Este é o efeito da técnica copy-on-write. Esta é a segunda parte
da saída:

Alterar 2º caractere
MyStr1 - Endereço: 51848300, Len: 5, Ref: 1, Val: Hallo
MyStr2 - Endereço: 51837036, Len: 5, Ref: 1, Val: Olá

Você pode estender livremente este exemplo e usar a função StringStatus para explorar o comportamento
de strings longas em muitas outras circunstâncias, com múltiplas referências, quando são passadas
como parâmetros, atribuídas a variáveis locais e muito mais.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

06: tudo sobre cordas - 195

Strings e codificação
Como vimos, o tipo de string em Object Pascal é mapeado para o formato Unicode UTF-16, com 2 bytes por
elemento e gerenciamento de pares substitutos para pontos de código fora do BMP (Basic Multi-lingual Plane).

Existem muitos casos, porém, em que você precisa salvar em um arquivo, carregar do arquivo, transmitir
através de uma conexão de soquete ou receber dados textuais de uma conexão que usa um diferente
representação, como ANSI ou UTF-8.

Para converter arquivos e dados na memória entre diferentes formatos (ou codificações), o Object Pascal
RTL possui uma útil classe TEncoding , definida na unidade System.SysUtils junto com várias classes herdadas.

note Existem diversas outras classes úteis no Object Pascal RTL que você pode usar para ler e escrever dados em
formatos de texto. Por exemplo, as classes TStreamReader e TStreamWriter oferecem suporte para
arquivos de texto com qualquer codificação. Essas classes serão introduzidas no Capítulo 18.

Embora eu ainda não tenha introduzido classes e herança, esse conjunto de classes de codificação é muito fácil
de usar, pois já existe um objeto global para cada codificação disponível para você.

Em outras palavras, um objeto de cada uma dessas classes de codificação está disponível na classe de
codificação TEn , como uma propriedade de classe:

tipo
TEncodificação = classe
...
público
propriedade de classe ASCII: TEncoding read GetASCII;
propriedade de classe BigEndianUnicode: TEncoding
leia GetBigEndianUnicode;
propriedade de classe Padrão: TEncoding read GetDefault;
propriedade de classe Unicode: TEncoding leitura GetUnicode;
propriedade de classe UTF7: TEncoding read GetUTF7;
propriedade de classe UTF8: TEncoding read GetUTF8;

note A codificação Unicode é baseada na classe TUnicodeEncoding que usa o mesmo formato UTF-16 LE (little
endian) usado pelo tipo string. O BigEndianUnicode, em vez disso, usa a representação Big Endian menos
comum. Se você não está familiarizado com “endianness”, este é um termo usado para indicar a sequência
de dois bytes que formam um ponto de código (ou qualquer outra estrutura de dados). Little endian tem o
byte mais significativo primeiro e Big Endian tem o byte mais significativo por último. Para obter mais
informações, consulte en.wikipedia.org/wiki/Endianness.

Novamente, em vez de explorar essas aulas em geral, algo um pouco difícil neste ponto do livro, vamos nos
concentrar em alguns exemplos práticos. A classe TEncoding tem
métodos para ler e escrever strings Unicode em matrizes de bytes e para realizar conversões apropriadas.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

196 - 06: tudo sobre cordas

Para demonstrar conversões de formato UTF por meio de classes TEncoding , mas também para manter
meu exemplo simples e focado e evitar trabalhar com o sistema de arquivos, no exemplo Encoding-sTest
criei uma string UTF-8 na memória usando alguns dados específicos e converti para UTF-16 com uma
única chamada de função:

era
Utf8String: TBytes;
Utf16String: string;
começar
// Processar dados UTF-8
SetComprimento(Utf8String, 3);
'a'// :=
Utf8String[0] := Palavra(); Utf8String[1] Byte único Caractere < 128
$c9; Utf8String[2] := $90; ANSIByte// duplo, latim invertido a

Utf16String := TEncoding.UTF8.GetString(Utf8String);
'
Mostrar( 'Unicode: +Utf16String);

A saída deve ser:

Unicode: aÿ

Agora, para entender melhor a conversão e a diferença nas representações,


Eu adicionei o seguinte código:

Mostrar();
'Bytes Utf8:'
para Abyte em Utf8String faça
Mostrar(AByte.ToString);

'Bytes Utf16:' );
Show(UniBytes := TEncoding.Unicode.GetBytes(Utf16String);
para AByte em UniBytes faça
Mostrar(AByte.ToString);

Este código produz um dump de memória, com valores decimais, para as duas representações da string:
UTF-8 (um ponto de código de um byte e um ponto de código de dois bytes) e UTF-16 (com ambos os códigos
pontos sendo 2 bytes):

Bytes Utf8:
97
201
144
Bytes Utf16:
97
0
80
2

Observe que a conversão direta de caractere para byte, para UTF-8, funciona apenas para caracteres
ANSI-7, ou seja, valores até 127. Para caracteres ANSI de nível superior, não há mapeamento direto
e você deve executar um conversão usando a codificação específica (que, no entanto, falhará em
elementos UTF-8 multibyte). Portanto, ambos os itens a seguir produzem resultados errados:

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

06: tudo sobre cordas - 197

// Erro: não é possível usar char > 128


Utf8String[0] := Palavra(); 'tem'
Utf16String := TEncoding.UTF8.GetString(Utf8String);
'
Mostrar( 'Alto errado ANSI: +Uf16string);
// Tentar conversão diferente
Utf16String := TEncoding.ANSI.GetString(Utf8String);
'
Mostrar(+Utf16String);
'Byte duplo errado:

// Saída
ANSI alto errado:
Byte duplo errado: àÉ

As classes de codificação permitem converter em ambas as direções; então, neste caso, estou
convertendo de UTF-16 para UTF-8 fazendo algum processamento da string UTF-8 (algo a ser feito
com cuidado, dada a natureza de comprimento variável deste formato), e depois converto-o de volta
para UTF-16:

era
Utf8String: TBytes;
Utf16String: string;
Eu: Inteiro;
começar
Utf16String:= 'Este é meu bela string com à e Æ' ;
'
Mostrar( 'Inicial: +Utf16String);

Utf8String := TEncoding.UTF8.GetBytes(Utf16String);
para I: = 0 a Alto (Utf8String) faça
se Utf8String[I] = Ord() então 'eu'
Utf8String[I] := Palavra(); 'EU'
Utf16String := TEncoding.UTF8.GetString(Utf8String);
'
'Final:
Mostrar(+Utf16String);

A saída é:

Inicial: Esta é minha bela string com à e Æ


Final: Esta é a minha bela string com à e Æ

Outros tipos de strings


Embora o tipo de dados string seja de longe o tipo mais comum e amplamente usado para representar
strings, os compiladores de desktop Object Pascal tinham e ainda têm uma variedade de tipos de string.
Alguns desses tipos também podem ser usados em aplicações móveis, onde você também pode usar TBytes
diretamente para manipular strings com uma representação de um byte, como no exemplo descrito na última
seção.

Embora os desenvolvedores que usaram Object Pascal no passado possam ter muito código baseado em
Para esses tipos pré-Unicode (ou gerenciando diretamente UTF-8), os aplicativos modernos exigem suporte
completo a Unicode. Além disso, embora alguns tipos, como UTF8String, estejam disponíveis na linguagem

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

198 - 06: tudo sobre cordas

Por outro lado, o seu apoio em termos de RTL é limitado. A recomendação é usar strings Unicode simples e
padrão.

note Tem havido muita discussão e crítica sobre a falta original de tipos nativos como AnsiString
e UTF8String nos compiladores móveis Object Pascal. No Delphi 10.1 Berlin, o tipo UTF8String e o tipo
RawByteString de baixo nível foram oficialmente reintroduzidos e posteriormente habilitados para o Delphi 10.4
todos os tipos de strings de desktop em dispositivos móveis também. Ainda vale a pena considerar que quase
não existe outra linguagem de programação que possua mais de um tipo de string nativo ou intrínseco. Vários
tipos de strings são mais complexos de dominar, podem causar efeitos colaterais indesejados (como extensas
chamadas de conversão automática que tornam os programas mais lentos) e custam muito para a manutenção de
múltiplas versões de todas as funções de gerenciamento e processamento de strings. Portanto, a recomendação,
além dos casos extremos, é focar no tipo de string padrão, ou UnicodeString.

O tipo UCS4String
Um tipo de string interessante, mas pouco utilizado, é o tipo UCS4String, disponível em todos os compiladores.
Esta é apenas uma representação UTF-32 de uma string e não mais do que uma matriz de elementos UTF32Char
ou caracteres de 4 bytes. A razão por trás desse tipo, conforme mencionado anteriormente, é que ele oferece
uma representação direta de todos os pontos de código Unicode. A desvantagem óbvia é que tal string ocupa
o dobro de memória que uma string UTF-16 (que já ocupa o dobro de uma string ANSI).

Embora este tipo de dados possa ser utilizado em situações específicas, não é particularmente adequado para
circunstâncias gerais. Além disso, esse tipo não suporta cópia na gravação nem possui funções e procedimentos
reais do sistema para processá-lo.

note Embora o UCS4String garanta um UTF32Char por ponto de código Unicode, ele não pode garantir um
UTF32Char por grafema, ou representação de “caractere visual”.

Tipos de string mais antigos


Conforme mencionado, o compilador Object Pascal oferece suporte para alguns tipos de strings tradicionais
mais antigos (e estes estão disponíveis em todas as plataformas de destino a partir do Delphi 10.4).
Esses tipos de string mais antigos incluem:

· O tipo ShortString , que corresponde à string original da linguagem Pascal


tipo. Essas strings têm um limite de 255 caracteres. Cada elemento de uma string curta é do tipo ANSIChar.

· O tipo ANSISString , que corresponde a strings de comprimento variável. Essas strings são alocadas
dinamicamente, as referências são contadas e usam uma técnica de cópia na gravação.
O tamanho dessas strings é quase ilimitado (elas podem armazenar até dois bilhões de caracteres!) Além
disso, esse tipo de string é baseado no tipo ANSIChar e está disponível em dispositivos móveis.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

06: tudo sobre cordas - 199

compiladores, mesmo que a representação ANSI seja específica do Windows e alguns caracteres especiais
possam ser tratados de maneira diferente dependendo da plataforma.

· O tipo WideString é semelhante a uma string Unicode de 2 bytes em termos de representação e é baseado no tipo
Char , mas diferentemente do tipo string padrão, não usa cópia na gravação e é menos eficiente em
termos de memória alocação. Se você está se perguntando por que ele foi adicionado à linguagem, o motivo
foi a compatibilidade com o gerenciamento de strings na arquitetura COM da Microsoft.

· UTF8String é uma string baseada no formato UTF-8 de comprimento de caracteres variável. Como eu
mencionado, há pouco suporte de biblioteca de tempo de execução para esse tipo.

· RawByteString é um array de caracteres sem conjunto de páginas de código, no qual nenhuma conversão de
caracteres é realizada pelo sistema (assim se assemelhando logicamente a uma estrutura TBytes , mas
permitindo algumas operações diretas de string que faltam atualmente em um array de bytes). Este tipo de
dados raramente deve ser usado fora das bibliotecas.

· Um mecanismo de construção de string que permite definir uma string de 1 byte associada
com uma página de código ISO específica, um remanescente do passado pré-Unicode.

Novamente, todos esses tipos de string podem ser usados em compiladores de desktop, mas estão disponíveis
apenas por motivos de compatibilidade com versões anteriores. O objetivo é usar Unicode, TEncoding e outras
técnicas modernas de gerenciamento de strings sempre que possível.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

200 - parte ii: oop no objeto pascal

parte II: oop in


objeto pascal

Muitas linguagens de programação modernas suportam alguma forma de paradigma de


programação orientada a objetos (OOP). Muitos deles usam um baseado em classe que se baseia em três
conceitos fundamentais:

· Classes: tipos de dados com interface pública e estrutura de dados privada, implementando
encapsulamento com instâncias desses tipos de dados geralmente chamados de objetos;
· Extensibilidade ou herança de classe, que é a capacidade de estender um tipo de dados com novos
recursos sem modificar o original;
· Polimorfismo ou ligação tardia, que é a capacidade de se referir a objetos de diferentes
classes com uma interface uniforme e ainda operam em objetos da maneira definida por seu
tipo específico.

note Outras linguagens como IO, JavaScript, Lua e Rebol usam um protótipo baseado em parâmetros orientados a objetos.
digm, onde os objetos podem ser criados a partir de outros objetos em vez de uma classe, dependendo de como o
objeto é criado. Eles fornecem uma forma de herança, mas de outro objeto em vez de uma classe, e tipagem
dinâmica que pode ser usada para implementar polimorfismo, mesmo que de uma maneira bastante diferente.

Você pode escrever aplicações Object Pascal mesmo sem saber muito sobre programação
orientada a objetos. À medida que você cria um novo formulário, adiciona novos componentes
e manipula eventos, o IDE prepara automaticamente a maior parte do código relacionado. Mas sabendo

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

parte ii: oop no objeto pascal - 201

os detalhes da linguagem e sua implementação ajudarão você a entender precisamente o que o


sistema está fazendo e permitirão que você domine completamente a linguagem.
Compreender OOP também ajudará você a criar arquiteturas complexas em seus aplicativos,
e até mesmo bibliotecas inteiras, e a adotar e estender os componentes que acompanham o
ambiente de desenvolvimento.
A segunda parte do livro concentra-se nas principais técnicas de programação orientada a
objetos (OOP). O objetivo desta parte do livro é ensinar os conceitos fundamentais de OOP e
detalhar como Object Pascal os implementa, comparando-o com outras linguagens OOP
semelhantes.

Resumo da Parte II
Capítulo 7: Objetos
Capítulo 8: Herança
Capítulo 9: Tratamento de Exceções
Capítulo 10: Propriedades e Eventos
Capítulo 11: Interfaces
Capítulo 12: Manipulando Classes
Capítulo 13: Objetos e Memória

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

202 - 07: objetos

07: objetos

Mesmo que você não tenha um conhecimento detalhado de programação orientada a objetos
(OOP), este capítulo apresentará cada um dos conceitos-chave. Se você já é fluente em OOP,
provavelmente poderá ler o material com relativa rapidez e focar nas especificidades da linguagem
Object Pascal, em comparação com outras linguagens que você já conhece.
O suporte OOP em Object Pascal tem muitas semelhanças com linguagens como C# e Java,
mas também tem algumas semelhanças com C++ e outras linguagens estáticas e fortemente
tipadas. As linguagens dinâmicas, por outro lado, tendem a oferecer uma interpretação diferente
da POO, pois tratam o sistema de tipos de uma forma mais livre e flexível.

note Muitas das semelhanças conceituais entre C# e Object Pascal se devem ao fato de que as duas linguagens
compartilham o mesmo designer, Anders Hejlsberg. Anders foi o autor original dos compiladores
Turbo Pascal, da primeira versão do Object Pascal do Delphi, e mais tarde mudou-se para a Microsoft
e projetou C# (e mais recentemente o derivado de JavaScript TypeScript). Você pode ler mais sobre a
história da linguagem Object Pascal no Apêndice A.

Apresentando Classes e Objetos


Classe e objeto são dois termos comumente usados em Object Pascal e outras linguagens
OOP. No entanto, como são frequentemente mal utilizadas, certifiquemo-nos de que
concordamos com as suas definições desde o início:

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

07: objetos - 203

· Uma classe é um tipo de dados definido pelo usuário que define um estado (ou uma representação)
e algumas operações (ou comportamentos). Em outros termos, uma classe possui alguns dados
internos e alguns métodos, na forma de procedimentos ou funções. Uma classe geralmente descreve
as características e o comportamento de vários objetos semelhantes, embora existam classes de
propósito especial destinadas a um único objeto.
· Um objeto é uma instância de uma classe, ou seja, uma variável do tipo de dados definido pela classe.
Objetos são entidades reais . Quando o programa é executado, os objetos ocupam memória
para sua representação interna.

· O relacionamento entre um objeto e uma classe é o mesmo que entre qualquer


outra variável e seu tipo de dados com a diferença de que as variáveis de objeto têm um
nome especial, ou seja, instâncias.

história A terminologia OOP remonta às primeiras linguagens que adotaram o modelo, como Smalltalk.
A maior parte da terminologia original, entretanto, foi posteriormente abandonada em favor de termos em uso
em linguagens procedurais. Portanto, embora termos como classes e objetos ainda sejam comumente usados,
geralmente você ouviria o termo invocando um método com mais frequência do que o termo original enviando
uma mensagem a um receptor (um objeto). Um guia completo e detalhado do jargão OOP e como ele evoluiu
ao longo do tempo poderia ser interessante, mas ocuparia muito espaço neste livro.

A definição de uma classe

No Object Pascal você pode usar a seguinte sintaxe para definir um novo tipo de dados de classe
(TDate), com alguns campos de dados locais (Mês, Dia, Ano) e alguns métodos (SetValue,
Ano bissexto):

tipo
DataT = aula
FMonth, FDay, FYear: Inteiro;
procedimento SetValue(M, D, Y: Inteiro);
função Ano bissexto: Booleano;
fim;

note Já vimos uma estrutura semelhante para registros, que são bastante semelhantes às classes em termos de definição. Existem
diferenças no gerenciamento de memória e em outras áreas, conforme detalhado posteriormente neste capítulo.
Historicamente, porém, no Object Pascal essa sintaxe foi adotada pela primeira vez para classes e posteriormente
transportada de volta para registros.

A convenção em Object Pascal é usar a letra T como prefixo para o nome de cada classe que você
escreve, como para qualquer outro tipo (T significa Type, na verdade). Isso é apenas uma convenção –
para o compilador, T é apenas uma letra como qualquer outra – mas é tão comum que segui-la
tornará seu código mais fácil de ser compreendido por outros programadores.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

204 - 07: objetos

Ao contrário de outras linguagens, a definição de classe em Object Pascal não inclui a implementação (ou definição)
real dos métodos, mas apenas sua assinatura (ou declaração). Isso torna o código da classe mais compacto e
significativamente mais legível.

dica Embora possa parecer que chegar à implementação real do método consome mais tempo, o editor permite que
você use a combinação das teclas Shift+Up e Shift+Down para navegar das declarações do método até suas
implementações e vice-versa. . Além disso, você pode deixar o editor gerar um esqueleto da definição dos métodos,
depois de escrever a definição da classe, usando Class Completion (pressionando as teclas Ctrl+C enquanto o
cursor está dentro da definição da classe).

Tenha também em mente que além de escrever a definição de uma classe (com seus campos e métodos), você
também pode escrever uma declaração. Isso tem apenas o nome da classe, como em:

tipo
TMyData = turma;

A razão para tal declaração reside no fato de que talvez seja necessário ter duas classes referenciando uma à
outra. Dado em Object Pascal você não pode usar um símbolo até que ele seja definido, portanto, para poder se referir
a uma classe ainda não definida você precisa de uma declaração. Escrevi o seguinte fragmento de código apenas para
mostrar a sintaxe, não que isso faça alguma diferença.
senso:

tipo
THusband = turma;

Esposa = classe
FMarido: THusband;
fim;

THusband = classe
FEsposa: TEsposa;
fim;

Você encontrará referências cruzadas semelhantes em código real, e é por isso que é importante ter essa
sintaxe em mente. Observe que, assim como acontece com os métodos, uma classe declarada em uma unidade
deve ser totalmente definida posteriormente na mesma unidade.

Aulas em outras linguagens OOP


Como comparação, esta é a classe TDate escrita em C# e em Java (que neste caso simplificado é a mesma) usando
um conjunto mais apropriado de regras de nomenclatura, com o
código dos métodos omitidos:

// Linguagem C# e Java

data da aula
{

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

07: objetos - 205

mês interno;
dia interno;
ano interno;

void setValue(int m, int d, int y) {

// Código
}

bool ano_salto() {

// Código
}
}

Em Java e C# o código do método está dentro da definição da classe; enquanto no Object Pascal os
métodos declarados em uma classe são definidos na parte de implementação da mesma unidade que
inclui a definição da classe. Em outras palavras, no Object Pascal uma classe é sempre completamente
definida em uma única unidade (enquanto uma unidade pode, é claro, conter múltiplas classes).
Por outro lado, enquanto em C++ os métodos são implementados separadamente, como em Object
Pascal, um arquivo de cabeçalho contendo uma definição de classe não tem correspondência estrita com
um arquivo de implementação com o código do método.

Uma classe C++ correspondente seria semelhante a:

//linguagem C++

data da aula {

mês interno;
dia interno;
ano interno;

void setValue(int m, int d, int y);


BOOL ano bissexto(); }

Os métodos de classe
Assim como acontece com os registros, ao definir o código de um método você precisa indicar de qual
classe ele faz parte (neste exemplo a classe TDate ) usando o nome da classe como prefixo e a
notação de ponto, como no código a seguir:

procedimento TDate.SetValue(M, D, Y: Inteiro); começar FMês := M;


FDia :=
D; FAno := Y; fim;

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

206 - 07: objetos

função TDate.LeapYear: Boolean;


começar
// Chame IsLeapYear em SysUtils.pas
Resultado: = IsLeapYear(FYear);
fim;

Diferentemente da maioria das outras linguagens OOP que definem métodos como funções, a
definição de métodos do Object Pascal usa a distinção explícita entre procedimentos e funções
dependendo da presença de um valor de retorno. Este não é o caso em C++, onde uma
implementação de método definida separadamente seria semelhante a:

// C++ método
data nula::setValue(int m, int d, int y)
{
mês = m;
dia = d;
ano = y;
};

Criando um objeto
Após esta comparação com outras linguagens populares, vamos voltar ao Object Pascal para ver
como você pode usar uma classe.
Uma vez definida a classe, podemos criar um objeto deste tipo e utilizá-lo como no seguinte trecho
de código (extraído do exemplo Dates1 como todo o código desta seção):

era
ADia: TData;
começar
// Criar
ADay := TDate.Create;
// Usar
ADay.SetValue(1, 1, 2020);
se ADay.LeapYear então'
Mostrar(' Ano bissexto: + ADay.Year.ToString);

A notação usada não é incomum, mas é poderosa. Podemos escrever uma função complexa (como
LeapYear) e então acessar seu valor para cada objeto TDate como se fosse um tipo de dados
primitivo. Observe que ADay.LeapYear é uma expressão semelhante a ADay.Year, embora a
primeira seja uma chamada de função e a segunda um acesso direto aos dados. Como veremos
no Capítulo 10, a notação usada pelo Object Pascal para acessar propriedades é novamente a mesma.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

07: objetos - 207

note Chamadas de métodos sem parâmetros na maioria das linguagens de programação baseadas na sintaxe da linguagem C
requerem parênteses, como em ADay.LeapYear(). Esta sintaxe também é legal em Object Pascal,
mas raramente usada. Métodos sem parâmetros geralmente são chamados sem parênteses. Isso é
muito diferente de muitas linguagens nas quais uma referência a uma função ou método sem
parênteses retorna o endereço da função. Como vimos na seção “Tipos Procedurais” no Capítulo 4,
Object Pascal usa a mesma notação para chamar uma função ou ler seu endereço, dependendo
do contexto da expressão.

A saída do trecho de código acima é bastante trivial:

Ano bissexto: 2020

Novamente, deixe-me comparar a criação do objeto com código semelhante escrito em outras linguagens de
programação:

// C# e Linguagens Java (modelo de referência de objeto)


Data aDia = new Data();

// C++ idioma (dois estilos alternativos)


Data por dia; // Alocação local
Data* aDia = new Data(); // Referência "Manual"

O modelo de referência de objeto


Em algumas linguagens OOP, como C++, declarar uma variável de um tipo de classe cria uma
instância dessa classe (mais ou menos como acontece com registros em Object Pascal). A memória para
um objeto local é retirada da pilha e liberada quando a função termina. Na maioria dos casos, porém, você
precisa usar explicitamente ponteiros e referências para ter mais flexibilidade no gerenciamento do tempo
de vida de um objeto, adicionando muita complexidade extra.

A linguagem Object Pascal, em vez disso, é baseada em um modelo de referência de objeto, exatamente como
Java ou C#. A ideia é que cada variável de um tipo de classe não retenha o valor real do objeto com seus
dados (para armazenar dia, mês e ano, por exemplo). Em vez disso, ele contém apenas uma referência, ou
um ponteiro, para indicar o local da memória onde os dados reais do objeto estão armazenados.

note Na minha opinião, adotar o modelo de referência de objeto foi uma das melhores decisões de design tomadas pela
equipe do compilador nos primórdios da linguagem, quando esse modelo não era tão comum nas linguagens de
programação (na verdade, na época em que Java era t disponível e C# não existia).

É por isso que nessas linguagens você precisa criar explicitamente um objeto e atribuí-lo a uma variável, pois
os objetos não são inicializados automaticamente. Em outras palavras, quando você declara uma variável,
você não cria um objeto na memória, apenas reserva o local da memória

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

208 - 07: objetos

para uma referência ao objeto. As instâncias de objetos devem ser criadas manual e explicitamente, pelo
menos para os objetos das classes que você definir. (No entanto, no Object Pascal, as instâncias dos
componentes que você coloca em um formulário são criadas automaticamente pela biblioteca de tempo de execução.)

Em Object Pascal, para criar uma instância de um objeto, podemos chamar seu especial Create
método, que é um construtor ou outro construtor personalizado definido pela própria classe. Aqui está o
código novamente:

ADay := TDate.Create;

Como você pode ver, o construtor é aplicado à classe (o tipo), não ao objeto (a variável). Isso ocorre porque
você está pedindo à classe para criar uma nova instância de seu tipo, e o resultado é um novo objeto que
você geralmente atribuiria a uma variável.

De onde vem o método Create ? É um construtor da classe TObject, da qual todas as outras classes
herdam (tópico abordado no próximo capítulo). Porém, é muito comum adicionar construtores personalizados
às suas classes, como veremos mais adiante neste capítulo.

Descarte de objetos
Em linguagens que usam um modelo de referência de objeto, você precisa de uma maneira de criar um
objeto antes de usá-lo e também de um meio de liberar a memória que ele ocupa quando não for mais
necessário. Se você não descartá-lo, acabará enchendo a memória com objetos desnecessários, causando
um problema conhecido como vazamento de memória. Para resolver esse problema, linguagens como C# e
Java, baseadas em um ambiente de execução virtual (ou máquina virtual), adotam a coleta de lixo. Embora isso
facilite a vida de um desenvolvedor, isso
abordagem, no entanto, está sujeita a algumas questões complexas relacionadas ao desempenho que não são
realmente relevante para explicar Object Pascal. Portanto, por mais interessantes que sejam as questões,
não quero me aprofundar nelas aqui.

Em Object Pascal, você geralmente libera a memória de um objeto chamando seu método Free especial
(novamente, um método de TObject, disponível em cada classe). Livre
remove o objeto da memória após chamar seu destruidor (que pode ter um código de limpeza especial).
Portanto, você pode completar o trecho de código acima como:

era
ADia: TData;
começar
// Criar
ADay := TDate.Create;

// Usar (código deixado de fora)

// Livre o memória
ADay.Free;
fim;

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

07: objetos - 209

Embora esta seja a abordagem padrão, a biblioteca de componentes adiciona conceitos como propriedade
de objetos para diminuir significativamente o impacto do gerenciamento manual de memória, tornando este
um problema relativamente simples de lidar.

note Como veremos mais adiante, ao usar interfaces referentes a objetos, o compilador adota uma forma de gerenciamento de
memória de Contagem Automática de Referências (ARC). Por alguns anos, isso também foi usado para variáveis de
tipo de classe regular em compiladores móveis Delphi. A partir da versão 10.4, também conhecida como Sydney,
o modelo de gerenciamento de memória foi unificado adotando o clássico gerenciamento de memória Delphi de
desktop para todas as plataformas alvo.

Há muito mais sobre gerenciamento de memória que você precisa saber; mas, dado que este é um
tópico bastante importante e não simples, decidi oferecer aqui apenas uma breve introdução e ter um
capítulo completo focado neste tópico, nomeadamente o Capítulo 13. Nesse capítulo, mostrarei em
detalhes diferentes técnicas que você pode usar .

O que é “Nil”?

Como mencionei, uma variável pode referir-se a um objeto de uma determinada classe. Mas pode ainda não
ter sido inicializado ou o objeto ao qual se referia pode não estar mais disponível. É aqui que você pode usar
nulo. Este é um valor constante que indica que a variável não está atribuída a nenhum objeto. Outras
linguagens de programação usam o símbolo nulo para expressar o mesmo conceito.

Quando uma variável de um tipo de classe não tem valor, você pode inicializá-la desta forma:

ADia := nulo;

Para verificar se a variável foi atribuída a um objeto, você pode escrever uma das seguintes expressões:

se ADay <> nulo então ...


se atribuído (ADay) então ...

Não cometa o erro de atribuir nulo a um objeto para removê-lo da memória. Definir uma referência de objeto como nula e liberá-
la são duas operações diferentes. Portanto, muitas vezes você precisa liberar um objeto e definir sua referência como nula, ou
chamar um procedimento de propósito especial que execute as duas operações ao mesmo tempo, chamado FreeAndNil. Mais
uma vez, mais informações e algumas demonstrações reais serão apresentadas no Capítulo 13, focado no gerenciamento
de memória.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

210 - 07: objetos

Registros versus classes na memória


Como mencionei anteriormente, uma das principais diferenças entre registros e objetos está relacionada
ao seu modelo de memória. Variáveis do tipo registro usam memória local, são passadas como
parâmetros por valor para funções por padrão e têm um comportamento de “cópia por valor” nas atribuições.
Isso contrasta com variáveis de tipo de classe que são alocadas no heap de memória dinâmica, são passadas
por referência e têm um comportamento de “cópia por referência” nas atribuições (copiando assim a
referência para o mesmo objeto na memória, não os dados reais) .

note Uma consequência desse gerenciamento de memória diferente é que os registros carecem de herança e
polimorfismos, dois recursos nos quais nos concentraremos no próximo capítulo.

O especificador de acesso privado denota campos e métodos de uma classe que não são acessíveis fora da
unidade (o arquivo de código-fonte) que declara a classe.

Por exemplo, ao declarar uma variável de registro na pilha, você pode começar a usá-la imediatamente,
sem precisar chamar seu construtor (a menos que sejam registros gerenciados personalizados). Isso
significa que as variáveis de registro são mais enxutas e eficientes no gerenciador de memória do que os
objetos regulares, pois não participam do gerenciamento da memória dinâmica. Estas são as principais
razões para usar registros em vez de objetos para estruturas de dados pequenas e simples.

Quanto à diferença na forma como registros e objetos são passados como parâmetros, considere que o padrão
é fazer uma cópia completa do bloco de memória que representa o registro (incluindo todos os seus
dados) ou da referência ao objeto (enquanto o os dados não são copiados). Claro, você pode usar parâmetros
de registro var ou const para modificar o comportamento padrão para passar parâmetros de tipo de registro
evitando qualquer cópia.

Privado, Protegido e Público


O especificador de acesso privado estrito denota campos e métodos que não são acessíveis
fora de qualquer método da classe, incluindo métodos de outras classes na mesma unidade.
Isso corresponde ao comportamento da palavra-chave privada na maioria das outras linguagens OOP.

Uma classe pode ter qualquer quantidade de campos de dados e qualquer número de métodos. No entanto,
para uma boa abordagem orientada a objetos, os dados devem ser ocultos ou encapsulados dentro da
classe que os utiliza. Quando você acessa uma data, por exemplo, não faz sentido alterar o valor do dia
sozinho. Na verdade, alterar o valor do dia pode resultar numa data inválida, como 30 de fevereiro.
Utilizar métodos para acessar a representação interna de um objeto limita o risco de gerar situações
errôneas, pois os métodos podem

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

07: objetos - 211

verifique se a data é válida e recuse-se a modificar o novo valor caso não seja. O encapsulamento adequado é
particularmente importante porque dá ao criador da classe a liberdade de modificar a representação interna em uma
versão futura.

O conceito de encapsulamento é bastante simples: basta pensar em uma classe como uma “caixa preta” com uma
pequena porção visível. A parte visível, chamada interface de classe, permite que outras partes de um programa
acessem e utilizem os objetos daquela classe. No entanto, quando você usa os objetos, a maior parte do código fica
oculta. Você raramente sabe quais dados internos o objeto possui e geralmente não tem como acessar os
dados diretamente. Em vez disso, você usa os métodos para acessar os dados de um objeto ou agir sobre ele.

O encapsulamento usando membros privados e protegidos é a solução orientada a objetos para um


objetivo clássico de programação conhecido como ocultação de informações.

Object Pascal possui cinco especificadores básicos de acesso (ou visibilidade): privado, protegido e público. Um
sexto, publicado, será discutido no Capítulo 10. Aqui estão os cinco básicos:

· O especificador de acesso público denota campos e métodos que são de acesso livre
de qualquer outra parte de um programa, bem como na unidade em que estão definidos.

· Os especificadores de acesso protegido e estritamente protegido são usados para indicar métodos e campos com
visibilidade limitada. Somente a classe atual e suas classes derivadas (ou subclasses) podem acessar
elementos protegidos , a menos que estejam na mesma classe ou, em qualquer caso, dependendo do
modificador estrito . Discutiremos essa palavra-chave novamente na seção “Campos protegidos e encapsulamento”
do próximo capítulo.

Geralmente, os campos de uma classe devem ser privados ou estritamente privados e os métodos geralmente são
públicos. No entanto, nem sempre é esse o caso. Os métodos podem ser privados ou protegidos se forem
necessários apenas internamente para realizar algumas operações parciais. Os campos podem ser protegidos se você
tiver certeza de que sua definição de tipo não será alterada e se desejar manipulá-los diretamente em classes
derivadas (conforme explicado no próximo capítulo), embora isso raramente seja recomendado.

Como regra geral, você deve invariavelmente evitar campos públicos e, geralmente, expor alguns
maneira direta de acessar os dados usando propriedades, como veremos em detalhes no Capítulo 10. As propriedades
são uma extensão do mecanismo de encapsulamento de outras linguagens OOP e são muito importantes no Object
Pascal.

Conforme mencionado, os especificadores de acesso privado apenas restringem o código fora de uma unidade
de acessar certos membros das classes declaradas nessa unidade. Isto significa que se duas classes estiverem
na mesma unidade, não há proteção para seus campos privados, a menos que sejam marcados como estritamente
privados, o que geralmente é uma boa ideia.

note A linguagem C++ possui o conceito de classes amigas, ou seja, classes com permissão para acessar dados privados de
outra classe. Seguindo esta terminologia, podemos dizer que no Object Pascal todas as classes da mesma unidade são
automaticamente consideradas como classes amigas.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

212 - 07: objetos

Um exemplo de dados privados


Como exemplo do uso desses especificadores de acesso para implementar o encapsulamento,
considere esta nova versão da classe TDate :

tipo
DataT = aula
privado
Mês, Dia, Ano: Inteiro;
público
procedimento SetValue(M, D, Y: Inteiro);
função Ano bissexto: Booleano;
função GetText: string;
procedimento Aumento;
fim;

Nesta versão, os campos agora são declarados privados e existem alguns novos métodos. A
primeira, GetText, é uma função que retorna uma string com a data. Você pode
pense em adicionar outras funções, como GetDay, GetMonth e GetYear, que simplesmente retornam os
dados privados correspondentes , mas funções semelhantes de acesso direto a dados não são
sempre necessário. Fornecer funções de acesso para cada campo pode reduzir o encapsulamento,
enfraquecer a abstração e dificultar a modificação posterior da implementação interna de uma
classe. As funções de acesso devem ser fornecidas somente se fizerem parte da interface lógica da
classe que você está implementando, e não porque existam campos correspondentes.

O segundo novo método é o procedimento de Aumento , que aumenta a data em um dia. Isso está
longe de ser simples, porque você precisa considerar as diferentes durações dos vários meses, bem
como os anos bissextos e não bissextos. O que farei para facilitar a escrita do código é alterar a
implementação interna da classe para usar Object Pascal
Tipo TDateTime para a implementação interna. Portanto, a classe real mudará para o seguinte código
que você pode encontrar no exemplo Dates2 :

tipo
DataT = aula
privado
FDate: TDateTime;
público
procedimento SetValue(M, D, Y: Inteiro);
função Ano bissexto: Booleano;
função GetText: string;
procedimento Aumento;
fim;

Observe que, como a única alteração está na parte privada da classe, você não precisará modificar
nenhum dos programas existentes que a utilizam. Essa é a vantagem do encapsulamento!

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

07: objetos - 213

note Nesta nova versão da classe, o campo possui um identificador que inicia com a letra “F”. Esta é uma
convenção bastante comum em Object Pascal e geralmente usarei no livro.

Para finalizar esta seção, deixe-me terminar de descrever o projeto, listando o código fonte dos métodos
de classe, que dependem de algumas funções do sistema para mapear datas para a estrutura interna e
vice-versa:

procedimento TDate.SetValue(M, D, Y: Inteiro);


começar
FDate := EncodeDate(Y, M, D);
fim;

função TDate.GetText: string;


começar
Resultado:= DateToStr(FDate);
fim;

procedimento TData.Increase;
começar
FDate := FDate + 1;
fim;

função TDate.LeapYear: Boolean;


começar
// Ligue para IsLeapYear em SysUtils e Ano de em DataUtils
Resultado: = IsLeapYear(YearOf(FDate));
fim;

Observe também como o código para usar a classe não pode mais se referir ao valor Ano, mas só pode
retornar informações sobre o objeto data conforme permitido por seus métodos:

era
ADia: TData;
começar
// Criar
ADay := TDate.Create;

// Usar
ADay.SetValue(1, 1, 2020);
ADay.Increase;

se ADay.LeapYear então
'
'Ano bissexto:
Mostrar(+ADay.GetText);

// Livre a memória
ADay.Free;

A saída não é muito diferente de antes:

Ano bissexto: 02/01/2020

Observe que sua saída pode ser diferente, pois a data é formatada de acordo com as configurações de
localidade do sistema.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

214 - 07: objetos

Encapsulamento e Formulários
Uma das ideias principais do encapsulamento é reduzir o número de variáveis globais usadas por um
programa. Uma variável global pode ser acessada de qualquer parte de um programa. Por esta razão,
uma alteração numa variável global afeta todo o programa. Por outro lado,
ao alterar a representação de um campo de uma classe, basta alterar o código de alguns métodos
daquela classe referentes ao campo determinado, e nada mais. Portanto, podemos dizer que ocultar
informações refere-se a encapsular mudanças.

Deixe-me esclarecer essa ideia com um exemplo prático. Quando você tem um programa com vários
formulários, você pode disponibilizar alguns dados para cada formulário, declarando-os como uma
variável global na parte da interface da unidade do formulário:

era
Formulário1: TForm1;
NClicks: Inteiro;

Isso funciona, mas tem dois problemas. Primeiro, os dados (NCliques) não estão conectados a uma
instância específica do formulário, mas a todo o programa. Se você criar dois formulários do mesmo
tipo, eles compartilharão os dados. Se você quiser que cada formulário do mesmo tipo tenha sua
própria cópia dos dados, a única solução é adicioná-lo à classe do formulário:

tipo
TForm1 = classe(TForm)
público
FNClicks: Inteiro;
fim;

O segundo problema é que se você definir os dados como uma variável global ou como um campo público
de um formulário, você não poderá modificar sua implementação no futuro sem afetar o código que utiliza
os dados. Em vez disso, se você precisar apenas ler o valor atual de outros formulários, poderá declarar
os dados como privados e fornecer um método para ler o valor:

tipo
TForm1 = classe(TForm)
// e manipuladores
Componentesde eventos aqui
público
função GetClicks: Inteiro;
privado
FNClicks: Inteiro;
fim;

função TForm1.GetClicks: Inteiro;


começar
Resultado:=FNClicks;
fim;

Uma solução ainda melhor é adicionar uma propriedade ao formulário, como veremos no Capítulo 10. Você
pode experimentar esse código abrindo o exemplo ClicksCount . Em suma, a forma

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

07: objetos - 215

deste projeto tem dois botões e um rótulo na parte superior, com a maior parte da superfície vazia para o
usuário clicar (ou tocar) nele. Neste caso, a contagem é aumentada e o rótulo é atualizado com o novo
valor:

procedimento TForm1.FormMouseDown(Remetente: TObject; Botão: TMouseButton; Shift: TShiftState; X, Y:


Único); começar Inc(FNClicks);

Label1.Text :=FNClicks.ToString; fim;

Você pode ver o aplicativo em ação na Figura 7.1. O formulário do projeto também possui dois botões,
um para criar um novo formulário do mesmo tipo e outro para fechá-lo (para que você possa voltar o
foco ao formulário anterior).

Isso é feito para enfatizar como diferentes instâncias do mesmo tipo de formulário têm sua própria
contagem de cliques. Este é o código dos dois métodos:

procedimento TForm1.Button1Click(Remetente: TObject);


era
NovoFormulário: TForm1;
começar
NewForm := TForm1.Create(Application); NovoFormulário.Mostrar;
fim;

procedimento TForm1.Button2Click(Remetente: TObject); começar Fechar;


fim;

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

216 - 07: objetos

Figura 7.1:
O formulário do
exemplo ClicksCount
mostrando o número de
cliques ou toques em um
formulário (rastreado
usando dados de formulário privado)

O autoidentificador
Vimos que os métodos são muito semelhantes aos procedimentos e funções. A verdadeira diferença é
que os métodos têm um parâmetro extra implícito. Esta é uma referência ao objeto atual, o objeto
ao qual o método é aplicado. Dentro de um método você pode se referir a este parâmetro – o objeto
atual – usando o identificador Self (como mencionado anteriormente na seção “Self: A magia por trás dos
registros” do Capítulo 5.

Este parâmetro extra oculto é necessário quando você cria vários objetos da mesma classe, de modo
que cada vez que você aplica um método a um dos objetos, o método irá operar apenas nos dados
desses objetos específicos e não afetará os outros objetos. da mesma classe.

note O conceito e a implementação do identificador próprio são muito semelhantes para registros e classes. Historicamente, o Self foi
introduzido primeiro para classes e posteriormente estendido para registros, quando métodos foram adicionados também a
esta estrutura de dados.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

07: objetos - 217

Por exemplo, no método SetValue da classe TDate , listado anteriormente, simplesmente usamos
Mês, Ano e Dia para se referir aos campos do objeto atual, algo que você pode expressar como:

Self.FMonth := M;
Self.FDay := D;

Na verdade, é assim que o compilador Object Pascal traduz o código, não como você deveria escrevê-
lo. O identificador próprio é uma construção de linguagem fundamental usada pelo compilador, mas às
vezes é usado por programadores para resolver conflitos de nomes e tornar o código mais legível.

note As linguagens C++, Java, C# e JavaScript têm um recurso semelhante baseado na palavra-chave this. No entanto, em
JavaScript, usar isso em um método para se referir a campos de objetos é obrigatório, ao contrário de C++, C# e Java.

Tudo o que você realmente precisa saber sobre Self é que a implementação técnica de uma chamada a
um método difere daquela de uma chamada a uma sub-rotina genérica. Os métodos têm aquele
parâmetro extra oculto, Self. Como tudo isso acontece nos bastidores, você não precisa saber como o Self
funciona neste momento.

A segunda coisa importante a saber é que você pode usar explicitamente Self para se referir ao objeto
atual como um todo, por exemplo, passando o objeto atual como parâmetro para outra função.

Criando Componentes Dinamicamente


Como exemplo do que acabei de mencionar, o identificador próprio é frequentemente usado quando você
precisa se referir explicitamente ao formulário atual em um de seus métodos.

Um exemplo típico é a criação de um componente em tempo de execução, onde você deve passar o
proprietário do componente para seu construtor Create e atribuir o mesmo valor à sua propriedade Parent .
Em ambos os casos, é necessário fornecer o objeto do formulário atual como parâmetro ou valor, e a melhor
forma de fazer isso é utilizar o identificador próprio .

note A propriedade de um componente indica um relacionamento de tempo de vida e de gerenciamento de memória entre dois
objetos. Quando o proprietário de um componente for liberado, o componente também será liberado. A paternidade
refere-se aos controles visuais que hospedam o controle infantil em sua superfície.

Para demonstrar esse tipo de código, escrevi o exemplo CreateComps . Esta aplicação possui um
formulário simples, sem componentes e um manipulador para seu evento OnMouseDown , que também
recebe como parâmetro a posição do clique do mouse. Preciso dessas informações para criar um
componente de botão nessa posição.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

218 - 07: objetos

note Um manipulador de eventos é um método especial abordado no Capítulo 10 e parte da mesma família dos botões
manipulador de eventos OnClick de ton que já usamos neste livro.

Aqui está o código do método:

procedimento TForm1.FormMouseDown(Remetente: TObject;


Botão: TMouseButton; Mudança: TShiftState; X, Y: Inteiro);
era
Btn: Botão T;
começar
Btn := TButton.Create(Self);
Btn.Parent := Próprio;
Btn.Posição.X := X;
Btn.Posição.Y := Y;
Btn.Altura := 35;
Btn.Largura := 135;
Btn.Text := Formato(fim; 'Em %d, %d' , [X, Y]);

Observe que pode ser necessário adicionar a unidade StdCtrls à instrução uses para compilar esse
manipulador de eventos.

O efeito deste código é criar botões nas posições de clique do mouse, com uma legenda indicando a
localização exata, como você pode ver na Figura 7.2. (Para este projeto, desativei o FMX Mobile Preview
para mostrar botões do Windows com estilo nativo, pois fica mais claro.) No código acima, observe em
particular o uso do identificador Self , como parâmetro do método Create e como o valor da propriedade
Parent .

Ao escrever um procedimento como o código que acabou de ver, você pode ficar tentado a usar a variável
Form1 em vez de Self. Neste exemplo específico, essa mudança não faria nenhuma diferença prática
(embora não seja uma boa prática de codificação), mas se houver múltiplas instâncias de um formulário,
usar o Form1 seria realmente um erro.

Na verdade, se a variável Form1 se referir a um formulário desse tipo que está sendo criado (geralmente
o primeiro) e se você criar duas instâncias do mesmo tipo de formulário, ao clicar em qualquer formulário
seguinte o novo botão sempre será exibido em o primeiro. Seu proprietário e pai será o Form1 e não o
formulário no qual o usuário clicou.

Em geral, escrever um método no qual você se refere a uma instância específica do mesmo
class quando o objeto atual é necessário é um estilo de codificação OOP realmente ruim.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

07: objetos - 219

Figura 7.2:
A saída do exemplo
CreateComps em um
dispositivo móvel

Construtores
No código acima, para criar um objeto de uma classe (ou alocar memória para um objeto), chamei o método
Create . Este é um construtor, um método especial que você pode aplicar a uma classe para alocar memória
para uma nova instância dessa classe:

ADay := TDate.Create;

A instância é retornada pelo construtor e pode ser atribuída a uma variável para armazenar o objeto e utilizá-lo
posteriormente. Quando você cria um objeto, sua memória é inicializada. Todos os dados da nova instância
são definidos como zero (ou nulos, ou string vazia, ou
o valor “padrão” adequado para um determinado tipo de dados).

Se você deseja que os dados da sua instância comecem com um valor diferente de zero (especialmente quando
um valor zero faz pouco sentido como padrão), você precisa escrever um construtor personalizado para fazer
que. O novo construtor pode ser chamado de Create ou pode ter qualquer outro nome. O que determina sua
função não é o nome, mas o uso da palavra-chave construtor .

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

220 - 07: objetos

note Em outras palavras, Object Pascal suporta construtores nomeados, enquanto em muitas linguagens OOP o con-
o construtor deve receber o nome da própria classe. Com construtores nomeados, você pode ter mais de um
construtor com os mesmos parâmetros (além de sobrecarregar o símbolo Create – a sobrecarga é abordada
na próxima seção). Outra característica muito especial da linguagem, única entre as linguagens OOP, é
que os construtores também podem ser virtuais. Mostrarei alguns exemplos que cobrem as consequências
desse recurso muito interessante mais adiante neste livro, depois de introduzir o conceito de método virtual no
o próximo capítulo.

O principal motivo para adicionar um construtor personalizado a uma classe é inicializar seus dados.
Se você criar objetos sem inicializá-los, chamar métodos posteriormente poderá resultar em um
comportamento estranho ou até mesmo em um erro de tempo de execução. Em vez de esperar que
esses erros apareçam, você deve primeiro usar técnicas preventivas para evitá-los. Uma dessas técnicas
é o uso consistente de construtores para inicializar os dados de um objeto. Por exemplo, devemos chamar
o procedimento SetValue da classe TDate depois de criarmos o objeto. Como alternativa, podemos
fornecer um construtor customizado, que cria o objeto e atribui a ele um valor inicial:

construtor TDate.Create;
começar
FDate := Data; // Hoje
fim;

construtor TDate.CreateFromValues(M, D, Y: Inteiro);


começar
FDate := SetValue(M, D, Y);
fim;

Você pode usar esses construtores da seguinte maneira, como fiz no exemplo Date3 , no código
anexado a dois botões separados:

ADay1 := TDate.Create;
ADay2 := TDate.CreateFromValues(12, 25, 2015);

Embora, em geral, você possa usar qualquer nome para um construtor, tenha em mente que se você
usar um nome diferente de Create, o construtor Create da classe base TObject ainda estará disponível.
Se você estiver desenvolvendo e distribuindo código para outros usarem, um programador que
chamar esse construtor Create padrão poderá ignorar o código de inicialização que você forneceu. Ao
definir um construtor Create com alguns parâmetros (ou nenhum, como no exemplo acima), você
substitui a definição padrão por uma nova e torna seu uso obrigatório.

Da mesma forma que uma classe pode ter um construtor customizado, ela pode ter um destruidor
customizado, um método declarado com a palavra-chave destrutor e invariavelmente chamado
Destroy. O método destruidor pode realizar alguma limpeza de recursos antes que um objeto seja
destruído, mas em muitos casos um destruidor personalizado não é necessário.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

07: objetos - 221

Assim como uma chamada ao construtor aloca memória para o objeto, uma chamada ao destruidor
libera a memória. Os destruidores personalizados são realmente necessários apenas para objetos que
adquirem recursos, como outro objeto, em seus construtores ou durante sua vida útil.

Diferentemente do construtor Create padrão , o destruidor Destroy padrão é virtual e é altamente


recomendado que o desenvolvedor substitua esse destruidor virtual (os métodos virtuais serão
abordados no próximo capítulo).

Isso porque, em vez de chamar um destruidor diretamente para liberar um objeto, é uma boa prática
comum de programação em Object Pascal chamar o método especial Free da classe TObject , que
por sua vez chama Destroy apenas se o objeto existir - isto é, se ele existir. não é nulo. Então, se você
definir um destruidor com um nome diferente, ele não será chamado pelo Free.
Novamente, falaremos mais sobre esse tópico quando nos concentrarmos no gerenciamento de memória no Capítulo 13.

note Conforme abordado no próximo capítulo, Destroy é um método virtual. Você pode substituir sua definição base por uma
nova em uma classe herdada, marcando-a com a palavra-chave override . A propósito, ter um método
estático que chama um método virtual é um estilo de programação muito comum, chamado de padrão de modelo. Em um
destruidor, geralmente você deve escrever apenas código de limpeza de recursos. Tente evitar operações mais
complexas que possam gerar exceções ou levar um tempo significativo para evitar problemas na limpeza de objetos
e porque muitos destruidores são chamados no encerramento do programa, então você deseja mantê-lo o mais
rápido possível.

Gerenciando dados de classes locais com


construtores e destruidores
Mesmo que eu aborde cenários mais complexos posteriormente neste livro, quero mostrar aqui um
caso simples de proteção de recursos usando um construtor e um destruidor. Este é o cenário mais
comum para usar um destruidor.

Suponha que você tenha uma classe com a seguinte estrutura (também parte do exemplo Date3 ):

tipo
TPpessoa = turma
privado
NomeF: string;
Data de nascimento: TDate;
público
construtor Criar (const Nome: string);
destruidor Destruir; sobrepor;
// Alguns métodos reais
informação da função: string;
fim;

Esta classe tem uma referência a outro objeto interno chamado FBirthdate. Quando uma instância
da classe TPerson é criada, esse objeto interno (ou filho) também deve ser criado, e quando a
instância é destruída, o objeto interno (ou filho) também deve ser descartado.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

222 - 07: objetos

Aqui está como você pode escrever o código do construtor e do destruidor substituído e do método interno
que sempre pode assumir que o objeto interno existe:

construtor TPerson.Create(const Nome: string);


começar
FNome := Nome;
FBirthdate := TDate.Create;
fim;

destruidor TPerson.Destroy;
começar
Data de nascimento.Grátis;
herdado;
fim;

função TPerson.Info: string;


começar
'
Resultado := FNome + ': +FBirthdate.GetText;
fim;

note Para entender a palavra-chave override usada para definir o destruidor e a palavra-chave herdada
dentro de sua definição, você terá que esperar até o próximo capítulo. Por enquanto, basta dizer que o primeiro é usado
para indicar que a classe tem uma nova definição substituindo o destruidor Destroy base , enquanto o último é usado
para invocar esse destruidor de classe base. Observe também que override é usado na declaração do método, mas não
no código de implementação do método.

Agora você pode usar um objeto da classe externa como no cenário a seguir, e o objeto interno será
criado corretamente quando o objeto TPerson for criado e destruído em tempo hábil quando
TPerson for destruído:

era
Pessoa: TPPessoa;
começar
Pessoa := TPPessoa.Create('João');
// Usar a classe e seu objeto interno
Mostrar(Pessoa.Info);
Pessoa.Livre;
fim;

Novamente, você pode encontrar esse código como parte do exemplo Dates3 .

Métodos e construtores sobrecarregados

Object Pascal suporta funções e métodos sobrecarregados: você pode ter vários métodos com o
mesmo nome, desde que os parâmetros sejam diferentes. Já vimos como funciona a sobrecarga para
funções e procedimentos globais com as mesmas regras

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

07: objetos - 223

aplicar a métodos. Ao verificar os parâmetros, o compilador pode determinar qual versão do método você
deseja chamar.

Novamente, existem duas regras básicas para sobrecarga:

· Cada versão do método deve ser seguida pela palavra-chave sobrecarga .

· As diferenças devem estar no número ou tipo dos parâmetros ou em ambos. O tipo de retorno não pode
ser usado para distinguir entre dois métodos.

Se a sobrecarga puder ser aplicada a todos os métodos de uma classe, esse recurso é particularmente
relevante para construtores, pois podemos ter vários construtores e chamá-los todos de Create, o que os
torna fáceis de lembrar.

histórico Historicamente, a sobrecarga foi adicionada ao C++ especificamente para permitir o uso de múltiplos
construtores, visto que eles devem ter o mesmo nome (o nome da classe). No Object Pascal, esse recurso
poderia ter sido considerado desnecessário, simplesmente porque vários construtores podem ter nomes
específicos diferentes, mas foi adicionado à linguagem de qualquer maneira, pois também é útil em muitos outros cenários.

Como exemplo de sobrecarga, adicionei à classe TDate duas versões diferentes do método SetValue :

tipo
TDate = procedimento
público
da classe SetValue(Mês, Dia, Ano: Inteiro); sobrecarga; procedimento SetValue(NewDate:
TDateTime); sobrecarga;

procedimento TDate.SetValue(Mês, Dia, Ano: Inteiro); começar FDate :=

EncodeDate(Ano, Mês, Dia); fim;

procedimento TDate.SetValue(NewDate: TDateTime); começar FDate :=

NovaData; fim;

Após esta etapa simples, adicionei à classe dois construtores Create separados , um sem parâmetros,
que oculta o construtor padrão, e outro com os valores de inicialização. O construtor sem parâmetros usa a
data de hoje como valor padrão:

tipo
TDate = construtor
público
de classe Criar; sobrecarga; construtor
Create(Mês, Dia, Ano: Inteiro); sobrecarga;

construtor TDate.Create(Mês, Dia, Ano: Inteiro); começar FDate := EncodeDate(Ano,


Mês,
Dia); fim;

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

224 - 07: objetos

construtor TDate.Create; começar


FDate :=
Data; // Fim de hoje;

Ter esses dois construtores possibilita definir um novo objeto TDate de duas maneiras diferentes:

era
Dia1, Dia2: TData; começar
Dia1 :=
TDate.Create(2020, 12, 25); Dia2 := TDate.Create;
// Hoje

Este código faz parte do exemplo Dates4 .

A aula TDate completa


Ao longo deste capítulo, mostrei partes do código-fonte para diferentes versões de uma classe TDate . A
primeira versão baseava-se em três números inteiros para armazenar o ano, o mês e o dia; uma segunda
versão utilizou um campo do tipo TDateTime fornecido pela RTL. Aqui está a parte completa da interface
da unidade que define a classe TDate :

unidade Datas;

interface

tipo
TData = aula privada

FDate: TDateTime;
construtor
público Criar; sobrecarga; construtor
Create(Mês, Dia, Ano: Inteiro); sobrecarga; procedimento SetValue(Mês, Dia, Ano: Inteiro);
sobrecarga; procedimento SetValue(NewDate: TDateTime); sobrecarga; função Ano
bissexto: Booleano; procedimento Aumento (NumberOfDays: Integer = 1);
procedimento Diminuir (NumberOfDays:
Integer = 1); função GetText: string; fim;

O objetivo dos novos métodos, Aumentar e Diminuir (ambos possuem um valor padrão para seus
parâmetros), é bastante fácil de entender. Se chamados sem parâmetro, alteram o valor da data para
o dia seguinte ou anterior. Se um parâmetro NumberOfDays fizer parte da chamada, eles adicionam ou
subtraem esse número:

procedimento TDate.Increase(NumberOfDays: Integer = 1); começar

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

07: objetos - 225

FDate := FDate + NumberOfDays;


fim;

O método GetText retorna uma string com a data formatada, utilizando o método DateToStr
função para a conversão:

função TDate.GetText: string;


começar
GetText := DateToStr(FDate);
fim;

Já vimos a maioria dos métodos nas seções anteriores, por isso não fornecerei os
listagem completa; você pode encontrá-lo no código do exemplo ViewDate que escrevi para testar
a classe. O formulário é um pouco mais complexo que os outros do livro e tem uma legenda
para exibir uma data e seis botões, que podem ser usados para modificar o valor do objeto.
Você pode ver o formulário principal do exemplo ViewDate em tempo de execução na Figura 7.3. Para
deixar o componente Label bonito, dei a ele uma fonte grande, tornei-o tão largo quanto o formulário,
configurei sua propriedade Alignment como taCenter e configurei sua propriedade AutoSize como False.

Figura 7.3:
A saída do
aplicativo ViewDate em
comece

O código de inicialização deste programa está no manipulador de eventos OnCreate do formulário. No


método correspondente, criamos uma instância da classe TDate , inicializamos esse objeto e depois
mostramos sua descrição textual no Texto do rótulo, conforme mostrado na Figura 7.3.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

226 - 07: objetos

procedimento TDateForm.FormCreate(Sender: TObject);


começar
ADay := TDate.Create;
LabelDate.Text := ADay.GetText;
fim;

ADay é um campo privado da classe do formulário TDateForm. A propósito, o nome da classe é escolhido
automaticamente pelo ambiente de desenvolvimento quando você altera a propriedade Name do formulário
para DateForm.

O objeto de data específico é criado quando o formulário é criado (estabelecendo o mesmo


relacionamento que vimos anteriormente entre a classe person e seu subobjeto de data) e
então é destruído junto com o formulário:

procedimento TDateForm.FormDestroy(Remetente: TObject);


começar
ADay.Free;
fim;

Quando o usuário clica em um dos seis botões, precisamos aplicar o método


correspondente ao objeto ADay e então exibir o novo valor da data no rótulo:

procedimento TDateForm.BtnTodayClick(Remetente: TObject);


começar
ADay.SetValue(Hoje);
LabelDate.Text := ADay.GetText;
fim;

Uma maneira alternativa de escrever o último método é destruir o objeto atual e criar um
novo:

procedimento TDateForm.BtnTodayClick(Remetente: TObject);


começar
ADay.Free;
ADay := TDate.Create;
LabelDate.Text := ADay.GetText;
fim;

Nesta circunstância específica, esta não é uma abordagem muito boa (porque criar um novo
objeto e destruir um existente acarreta muita sobrecarga, quando tudo o que precisamos é alterar
o valor do objeto), mas me permite mostrar alguns de técnicas de Object Pascal. A primeira
coisa a notar é que destruímos o objeto anterior antes de atribuir um novo. A operação de
atribuição, na verdade, substitui a referência, deixando o objeto na memória (mesmo que nenhum
ponteiro esteja se referindo a ele). Quando você atribui um objeto a outro objeto, o compilador
simplesmente copia a referência do objeto na memória para a nova referência do objeto.

Uma questão secundária é como você copia os dados de um objeto para outro. Este caso é muito
simples, porque existe apenas um campo e um método para inicializá-lo. Em geral se você
quiser alterar os dados dentro de um objeto existente, você tem que copiar cada campo, ou pro-

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

07: objetos - 227

forneça um método específico para copiar todos os dados internos. Algumas classes têm um Assign
método, que faz esta operação de cópia profunda .

note Para ser mais preciso, na biblioteca de tempo de execução todas as classes herdadas de TPersistent têm o
Atribuir método, mas a maioria daqueles que herdam de TComponent não o implementam, gerando uma
exceção quando ele é chamado. A razão está no mecanismo de streaming suportado pelas bibliotecas de tempo
de execução e no suporte para propriedades dos tipos TPersistent , mas isso é muito complexo para ser
aprofundado neste ponto do livro.

Tipos aninhados e constantes aninhadas


Object Pascal permite declarar novas classes na seção de interface de uma unidade, permitindo que
outras unidades do programa as referenciem, ou na seção de implementação, onde são acessíveis
apenas a partir de métodos de outras classes da mesma unidade ou de rotinas globais implementadas
naquela unidade após a definição da classe.

Uma adição mais recente é a possibilidade de declarar uma classe (ou qualquer outro tipo de
dados) dentro de outra classe. Como qualquer outro membro da classe, os tipos aninhados podem
ter visibilidade restrita (digamos, privados ou protegidos). Exemplos relevantes de tipos aninhados
incluem enumerações usadas pela mesma classe e classes de suporte à implementação.

Uma sintaxe relacionada permite definir uma constante aninhada, um valor constante associado à
classe (novamente utilizável apenas internamente, se for privado, ou utilizável pelo resto do programa,
se for público). Como exemplo, considere a seguinte declaração de uma classe aninhada (extraída
da unidade NestedClass do exemplo NestedTypes ):

tipo
TOne = classe
privado
FAlgunsDados: Inteiro;
público
// Constante aninhada
const Foo = 12;
// Tipo aninhado
tipo
TInside = classe
público
procedimento InsideHello;
privado
FMsg: sequência;
fim;
público
procedimento Olá;
fim;

procedimento TOne.Hello;

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

228 - 07: objetos

era
Ins: TInside;
começar
Ins := TInside.Create;
Ins.FMsg := ; 'Oi'
Ins.InsideHello;
'
'Constante é + IntToStr(Foo));
Mostrar(Ins.Free;
fim;

procedimento TOne.TInside.InsideHello;
começar
FMsg := 'Nova mensagem' ;
'Chamada interna'
Mostrar();
se não for atribuído (InsIns), então
InsIns := TInsideInside.Create;
InsIns.Dois;
fim;

procedimento TOne.TInside.TInsideInside.Two;
começar
'Isso é a método de um classe aninhada/aninhada' );
Mostrar(fim;

A classe aninhada pode ser usada diretamente dentro da classe (conforme demonstrado na listagem) ou
fora da classe (se for declarada na seção pública), mas com o nome totalmente qualificado TOne.TInside.
O nome completo da classe é utilizado também na definição do método da classe aninhada, neste
caso TOne.TInside. A classe de hospedagem pode ter um campo do tipo de classe aninhada
imediatamente após você ter declarado a classe aninhada (como você pode ver no código do exemplo
NestedClass).
A classe com as classes aninhadas é usada da seguinte forma:

era
Um: TOn;
começar
Um := TOne.Create;
Um.Olá;
Um.Grátis;

Isso produz a seguinte saída:

Chamada interna
Este é um método de uma classe aninhada/aninhada
Constante é 12

Como você se beneficiaria com o uso de uma classe aninhada na linguagem Object Pascal? O conceito
é comumente usado em Java para implementar delegados de manipuladores de eventos e faz sentido
em C#, onde você não pode ocultar uma classe dentro de uma unidade. No Object Pascal, as classes
aninhadas são a única maneira de ter um campo do tipo de outra classe privada (ou classe interna)
sem adicioná-lo ao espaço de nomes global, tornando-o globalmente visível.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

07: objetos - 229

Se a classe interna for usada apenas por um método, você poderá obter o mesmo efeito declarando
a classe na parte de implementação da unidade. Mas se a classe interna for referenciada na
seção de interface da unidade (por exemplo, porque é usada para um campo ou parâmetro), ela
deverá ser declarada na mesma seção de interface e acabará sendo
visível. O truque de declarar tal campo como um tipo genérico ou base e depois convertê-lo para
o tipo específico (privado) é muito menos limpo do que usar uma classe aninhada.

note No capítulo 10 há um exemplo prático em que classes aninhadas são úteis, nomeadamente a implementação de
um iterador personalizado para um loop for-in .

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

230 - 08: herança

08: herança

Se a principal razão para escrever classes é o encapsulamento, a principal razão para usar a
herança entre classes é a flexibilidade. Combine os dois conceitos e você poderá ter tipos
de dados que você pode usar e que não serão alterados com a capacidade de criar versões
modificadas desses tipos, no que foi originalmente conhecido como “princípio aberto-fechado”:

“Entidades de software (classes, módulos, funções, etc.) devem ser abertas para
extensão, mas fechadas para modificação.” – Bertrand Meyer, Construção de
Software Orientado a Objetos, 1988

Agora é verdade que a herança é uma ligação muito forte que leva a um código fortemente
acoplado, algo que hoje em dia é desaprovado, mas também é verdade que oferece grande poder ao
desenvolvedor (e, sim, também a maior responsabilidade que isso acarreta).
Contudo, em vez de abrir um debate sobre esse recurso, meu objetivo aqui é descrever como funciona a herança
de tipos e, especificamente, como funciona na linguagem Object Pascal.

Herdando de tipos existentes


Muitas vezes precisamos usar uma versão ligeiramente diferente de uma classe existente que
escrevemos ou que alguém nos deu.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

08: herança - 231

Por exemplo, pode ser necessário adicionar um novo método ou alterar ligeiramente um método existente.
Você pode fazer isso facilmente modificando o código original, a menos que queira usar as duas versões
diferentes da classe em circunstâncias diferentes. Além disso, se a classe foi originalmente escrita por
outra pessoa (e você a encontrou em uma biblioteca), convém manter suas alterações separadas.

Uma alternativa típica da velha escola para ter duas versões semelhantes de uma classe é fazer uma
cópia da definição de tipo original, alterar seu código para suportar os novos recursos e dar um novo
nome à classe resultante. Isso pode funcionar, mas também pode criar problemas: ao duplicar o código,
você também duplica os bugs; quando um bug é corrigido em uma das cópias do código, você deve se
lembrar de aplicar a correção à outra cópia; e se quiser adicionar um novo recurso, você precisará adicioná-
lo duas ou mais vezes, dependendo do número de cópias do código original que você fez ao longo do
tempo. Mesmo que isso não atrapalhe você ao escrever o código pela primeira vez, esse método é um
desastre para a manutenção do software. Além disso, esta abordagem resulta em dois tipos de dados
completamente diferentes, de modo que o compilador não pode ajudá-lo a tirar vantagem das
semelhanças entre os dois tipos.

Para resolver esses tipos de problemas na expressão de semelhanças entre classes, Object Pa-cal permite
definir uma nova classe diretamente a partir de uma já existente. Essa técnica é conhecida como
herança (ou subclasse, ou derivação de tipo) e é um dos elementos fundamentais das linguagens de
programação orientadas a objetos.

Para herdar de uma classe existente, basta indicar essa classe no início da declaração da subclasse. Na
verdade, isso é feito automaticamente cada vez que você cria um novo formulário:

tipo
TForm1 = classe(TForm)
...
fim;

Esta definição simples indica que a classe TForm1 herda todos os métodos, campos, propriedades e
eventos da classe TForm . Você pode aplicar qualquer método público do TForm
classe para um objeto do tipo TForm1 . TForm, por sua vez, herda alguns de seus métodos de outra classe,
e assim por diante, até a classe TObject (que é a classe base de todas as classes).

Em comparação, C++, C# e Java usariam algo como:

classe Form1: TForm


{
...
}

Como um exemplo simples de herança, podemos alterar ligeiramente o exemplo ViewDate do capítulo
anterior, derivando uma nova classe de TDate e modificando uma de suas funções, GetText. Você pode
encontrar esse código no arquivo Dates.pas do exemplo DerivedDates .

tipo

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

232 - 08: herança

TNovaData = classe(TDate)
público
função GetText: string;
fim;

Neste exemplo, TNewDate é derivado de TDate. É comum dizer que TDate é uma classe ancestral , ou
classe base , ou classe pai de TNewDate e que TNewDate é uma subclasse, classe descendente ou
classe filha de TDate.

Para implementar a nova versão da função GetText , usei o FormatDateTime


função, que usa (entre outros recursos) os nomes dos meses predefinidos. Aqui está o método GetText ,
onde 'dddddd' representa o formato de data longa:

função TNewDate.GetText: string;


começar
Resultado:= FormatDateTime(end; 'dddd' , FDate);

Depois de definirmos a nova classe, precisamos utilizar este novo tipo de dados no código do formulário
do projeto DerivedDates . Basta definir o objeto ADay do tipo TNewDate e chamar seu construtor no
método FormCreate :

tipo
TDataForm = classe(TForm)
...
privado
FDay: TNewDate; fim; // Declaração atualizada

procedimento TDateForm.FormCreate(Sender: TObject);


começar
FDay := TNewDate.Create; // Linha atualizada
DateLabel.Text := FDay.GetText;
fim;

Sem quaisquer outras alterações, o novo aplicativo funcionará corretamente.

A classe TNewDate herda os métodos para aumentar a data, adicionar um número de dias e assim por
diante. Além disso, o código mais antigo que chama esses métodos ainda funciona. Na verdade, para ligar
Na nova versão do método GetText , não precisamos alterar o código fonte! O compilador Object Pascal
vinculará automaticamente essa chamada a um novo método.

O código-fonte de todos os outros manipuladores de eventos permanece exatamente o mesmo, embora


seu significado mude consideravelmente, como demonstra a nova saída (veja a Figura 8.1).

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

08: herança - 233

Figura 8.1: A
saída do programa
DerivedDates com o nome
do mês e do dia
dependendo das
configurações regionais do
Windows

Uma classe base comum


Vimos que se você puder herdar de uma determinada classe base escrevendo:

tipo
TNovaData = classe(TDate)
...
fim;

Mas o que acontece se você omitir uma classe base e escrever:

tipo
TNovaData = turma
...
fim;

Neste caso sua classe herda de uma classe base, chamada TObject. Em outras palavras, Object
Pascal possui uma hierarquia de classes de raiz única, na qual todas as classes herdam direta
ou indiretamente de uma classe ancestral comum. Os métodos mais comumente usados de TObject
são Criar, Libertar e Destruir; mas há muitos outros que usarei ao longo do livro.
Uma descrição completa desta classe fundamental (que pode ser considerada tanto parte de

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

234 - 08: herança

a linguagem e também parte da biblioteca de tempo de execução) com uma referência a todos os seus
métodos está disponível no Capítulo 17.

note O conceito de uma classe ancestral comum está presente também nas linguagens C# e Java, onde é
chamada simplesmente de Object. A linguagem C++, por outro lado, não tem essa ideia, e um programa
C++ geralmente tem múltiplas hierarquias de classes independentes.

Campos Protegidos e Encapsulamento


O código do método GetText da classe TNewDate só é compilado se for escrito na mesma unidade da classe
TDate . Na verdade, ele acessa o campo privado FDate da classe ancestral. Se quisermos colocar a classe
descendente em uma nova unidade, devemos declarar o campo FDate como protegido (ou protegido
estritamente) ou adicionar um campo simples, possivelmente protegido
método na classe ancestral para ler o valor do campo privado.

Alguns desenvolvedores acreditam que a primeira solução é sempre a melhor, porque declarar a maioria
dos campos como protegidos tornará a classe mais extensível e facilitará a escrita de subclasses. No entanto,
isso viola a ideia de encapsulamento. Em uma grande hierarquia de classes, alterar a definição de alguns
campos protegidos das classes base torna-se tão difícil quanto alterar algumas estruturas de dados
globais. Se dez classes derivadas estiverem acessando esses dados, alterar sua definição significa
potencialmente modificar o código em cada uma das dez classes.

Em outras palavras, flexibilidade, extensão e encapsulamento muitas vezes tornam-se objetivos


conflitantes. Quando isso acontecer, você deve tentar favorecer o encapsulamento. Se você puder fazer isso
sem sacrificar a flexibilidade, será ainda melhor. Freqüentemente, essa solução intermediária pode ser obtida
usando um método virtual, um tópico que discutirei em detalhes abaixo na seção “Late Binding and
Polymorphism”. Se você optar por não usar o encapsulamento para
para obter uma codificação mais rápida das subclasses, seu projeto poderá não seguir os princípios orientados
a objetos.

Lembre-se também que os campos protegidos compartilham as mesmas regras de acesso dos privados,
de modo que qualquer outra classe na mesma unidade sempre poderá acessar membros protegidos de
outras classes. Conforme mencionado no capítulo anterior, você pode usar um encapsulamento mais forte
usando o especificador de acesso protegido estrito.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

08: herança - 235

Usando o “Hack protegido”


Se você é novo em Object Pascal e OOP, esta é uma seção bastante avançada que você pode
querer pular na primeira vez que estiver lendo este livro, pois pode ser bastante confusa.

Dado o funcionamento da proteção da unidade, mesmo os membros protegidos das classes base
declaradas na unidade atual podem ser acessados diretamente, a menos que você use a palavra-
chave strict protected . Esta é a lógica por trás do que geralmente é chamado de “hack protegido”, ou seja,
a capacidade de definir uma classe derivada idêntica à sua classe base com o único propósito de obter
acesso ao membro protegido da classe base. Aqui está como funciona.

Vimos que os dados privados e protegidos de uma classe são acessíveis a qualquer função
ou métodos que aparecem na mesma unidade da classe. Por exemplo, considere este simples
classe (parte do exemplo de proteção ):

tipo
TTest = turma
protegido
FProtectedData: Inteiro;
público
Dados Públicos: Inteiro;
função GetValue: string;
fim;

O método GetValue simplesmente retorna uma string com os dois valores inteiros:

função TTest.GetValue: string;


começar
Resultado:= 'Público: %d, Protegido: %d' ,
Formato([PublicData, FProtectedData]);
fim;

Depois de colocar esta classe em sua própria unidade, você não poderá acessar sua parte protegida
diretamente de outras unidades. Assim, se você escrever o código a seguir,

procedimento TForm1.Button1Click(Remetente: TObject);


era
Obj: TTest;
começar
Obj := TTest.Create;
Obj.PublicData := 10;
Obj.FProtectedData := 20; // Não vou compilar
Mostrar(Obj.GetValue);
Obj.Livre;
fim;

o compilador emitirá uma mensagem de erro, Identificador não declarado: “FProtectedData”. Neste ponto,
você pode pensar que não há como acessar os dados protegidos de uma classe definida em uma
unidade diferente. No entanto, há uma maneira de contornar isso.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

236 - 08: herança

Considere o que acontece se você criar uma classe derivada aparentemente inútil, como:

tipo
TTestAccess = classe(TTest);

Agora, na mesma unidade onde você o declarou, você pode chamar qualquer método protegido de
a classe TTestAccess . Na verdade você pode chamar métodos protegidos de uma classe declarada na
mesma unidade.

Mas como isso ajuda a usar um objeto da classe TTest ? Considerando que as duas classes
compartilham exatamente o mesmo layout de memória (já que não há diferenças), você pode forçar
o compilador a tratar um objeto de uma classe como um dos outros, com o que geralmente é uma
conversão de tipo inseguro:

procedimento TForm1.Button2Click(Remetente: TObject);


era
Obj: TTest;
começar
Obj := TTest.Create;
Obj.PublicData := 10;
TTestAccess(Obj).FProtectedData := 20; // Compila!
Mostrar(Obj.GetValue);
Obj.Livre;
fim;

Este código compila e funciona corretamente, como você pode ver executando o Protection
exemplo. Novamente, o motivo é que a classe TTestAccess herda automaticamente os campos
protegidos da classe base TTest e, como a classe TTestAccess está na mesma unidade que o
código que tenta acessar os dados nos campos herdados, os dados protegidos são
acessível.

Agora que mostrei como fazer isso, devo avisá-lo de que violar o mecanismo de proteção de
classe dessa forma provavelmente causará erros em seu programa (ao acessar dados que você
realmente não deveria), e ele será executado contrariar a boa metodologia OOP. No entanto, há
raros momentos em que usar esta técnica é a melhor solução, como você verá na
olhando o código-fonte da biblioteca e o código de muitos componentes.
No geral, esta técnica é um hack e deve ser evitada sempre que possível, embora possa ser
considerada para todos os efeitos como parte da especificação da linguagem e esteja disponível
em todas as plataformas e em todas as versões atuais e passadas do Object Pascal.

Da herança ao polimorfismo
A herança é uma boa técnica em termos de permitir evitar a duplicação de código e compartilhar
métodos de código entre diferentes classes. Seu verdadeiro poder, porém, vem da capacidade de
manusear objetos de diferentes classes de maneira uniforme, algo frequentemente indicado

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

08: herança - 237

em linguagens de programação orientadas a objetos pelo termo polimorfismo ou referenciado como


encadernação tardia.

Existem vários elementos que precisamos explorar para compreender totalmente esse recurso: compatibilidade
de tipos entre classes derivadas, métodos virtuais e muito mais, conforme abordado no próximo
algumas seções.

Herança e compatibilidade de tipo


Como vimos até certo ponto, Object Pascal é uma linguagem estritamente tipada. Isso significa que você não
pode, por exemplo, atribuir um valor inteiro a uma variável booleana , pelo menos não sem uma conversão de
tipo explícita. A regra básica é que dois valores são compatíveis com o tipo somente se forem do mesmo tipo de
dados ou (para ser mais preciso) se o tipo de dados tiver o mesmo nome e sua definição vier da mesma unidade.

Há uma exceção importante a esta regra no caso de tipos de classe. Se você declarar uma classe, como
TAnimal, e derivar dela uma nova classe, digamos TDog, poderá então atribuir um objeto do tipo TDog a uma
variável do tipo TAnimal. Isso porque um cachorro é um animal! Então,
embora isso possa surpreendê-lo, as seguintes chamadas do construtor são legais:

era
MeuAnimal1, MeuAnimal2: TAnimal;
começar
MeuAnimal1 := TAnimal.Create;
MeuAnimal2 := TDog.Create;

Em termos mais precisos, você pode usar um objeto de uma classe descendente sempre que um objeto de
uma classe ancestral é esperada. Contudo, o inverso não é legal; você não pode usar um objeto de uma
classe ancestral quando um objeto de uma classe descendente é esperado. Para simplificar
a explicação, aqui está novamente em termos de código:

MeuAnimal := MeuCão; // Esse // é é OK


Meu Cachorro := MeuAnimal; Esse um erro!!!

Na verdade, embora possamos sempre dizer que um cão é um animal, não podemos presumir que qualquer
animal seja um cão. Isso pode ser verdade às vezes, mas nem sempre. Isto é bastante lógico, e as regras de
compatibilidade de tipo de linguagem seguem esta mesma lógica.

Antes de olharmos para as implicações desta importante característica da linguagem, você pode tentar
Veja o exemplo Animals1 , que define as duas classes simples TAnimal e TDog , herdando uma da outra:

tipo
TAnimal = classe
público
construtor Criar;
função GetKind: string;
privado

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

238 - 08: herança

FKind: string;
fim;

TDog = classe(TAnimal)
público
construtor Criar;
fim;

Os dois métodos Create simplesmente definem o valor de FKind, que é retornado pela função
GetKind .

Figura 8.2: A
forma do exemplo
Animals1 no ambiente de
desenvolvimento

O formulário deste exemplo, mostrado na Figura 8.2, possui dois botões de opção (hospedados em
um painel) para escolher um objeto de uma ou outra classe. Este objeto é armazenado no campo
privado FMyAnimal do tipo TAnimal. Uma instância desta classe é criada e inicializada quando o
formulário é criado e recriado cada vez que um dos botões de opção é selecionado (aqui estou mostrando
apenas o código do segundo botão de opção):

procedimento TFormAnimals.FormCreate(Sender: TObject);


começar
FMyAnimal := TAnimal.Create;
fim;

procedimento TFormAnimals.RadioButton2Change(Sender: TObject);


começar
FMyAnimal.Free;
FMyAnimal := TDog.Create;
fim;

Finalmente, o botão Kind chama o método GetKind para o animal atual e exibe o resultado no memorando
que cobre a parte inferior do formulário:

procedimento TFormAnimals.BtnKindClick(Sender: TObject);


começar
Mostrar(FMyAnimal.GetKind);
fim;

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

08: herança - 239

Ligação Tardia e Polimorfismo


As funções e procedimentos do Object Pascal são geralmente baseados em ligação estática, que
também é chamada de ligação antecipada. Isso significa que uma chamada de método é resolvida pelo
compilador ou vinculador, que substitui a solicitação por uma chamada para o local de memória específico
onde reside a função ou procedimento compilado. (Isso também é conhecido como endereço da função.)
As linguagens de programação orientadas a objetos permitem o uso de outra forma de ligação,
conhecida como ligação dinâmica ou ligação tardia. Neste caso, o endereço real do método a ser chamado
é determinado em tempo de execução com base no tipo de instância usada para fazer a chamada.

A vantagem desta técnica é conhecida como polimorfismo. Polimorfismo significa que você pode escrever
uma chamada para um método, aplicando-o a uma variável, mas qual método o Delphi realmente
as chamadas dependem do tipo de objeto ao qual a variável está relacionada. O Delphi não pode
determinar até o tempo de execução a classe real do objeto ao qual a variável se refere, simplesmente por
causa da regra de compatibilidade de tipos discutida na seção anterior.

note Os métodos Object Pascal são padronizados para ligação antecipada, como C++ e C#. Uma das razões é que isso é mais
eficiente. Java, em vez disso, usa como padrão a ligação tardia (e oferece maneiras de indicar ao compilador que ele
pode otimizar um método usando ligação antecipada).

Suponha que uma classe e sua subclasse (digamos TAnimal e TDog, novamente) definam um método,
e esse método tenha ligação tardia. Agora você pode aplicar este método a uma variável genérica, como
FMyAnimal, que em tempo de execução pode se referir a um objeto da classe TAnimal ou a um objeto
da classe TDog. O método real a ser chamado é determinado em tempo de execução, dependendo da
classe do objeto atual.

O exemplo Animals2 estende o projeto Animals1 para demonstrar esta técnica. Na nova versão, as classes
TAnimal e TDog contam com um novo método: Voice, que significa emitir o som produzido pelo animal
selecionado, tanto como texto quanto como som.
Este método é definido como virtual na classe TAnimal e é posteriormente substituído quando
defina a classe TDog , pelo uso das palavras-chave virtual e override :

tipo
TAnimal = classe
público
função Voz: string; virtual;

TDog = classe(TAnimal)
público
função Voz: string; sobrepor;

Claro, os dois métodos também precisam ser implementados. Aqui está uma abordagem simples:

função TAnimal.Voice: string;


começar
Resultado := 'Voz Animal' ;

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

240 - 08: herança

fim;

função TDog.Voice: string;


começar
Resultado := 'ArmaArma' ;
fim;

Agora qual é o efeito da chamada FMyAnimal.Voice? Depende. Se a variável FMyAnimal atualmente se refere
a um objeto da classe TAnimal , ela chamará o método TAnimal.Voice. Se se referir a um objeto
da classe TDog , em vez disso chamará o método TDog.Voice . Isso acontece apenas porque a
função é virtual.

A chamada para FMyAnimal.Voice funcionará para um objeto que é uma instância de qualquer
descendente da classe TAnimal , mesmo classes que são definidas após esta chamada de método ou fora dela.
seu escopo. O compilador não precisa conhecer todos os descendentes para tornar a chamada
compatível com eles; apenas a classe ancestral é necessária. Em outras palavras, esta chamada para
FMyAnimal.Voice é compatível com todas as subclasses futuras do TAnimal .

Esta é a principal razão técnica pela qual as linguagens de programação orientadas a objetos favorecem
a reutilização. Você pode escrever código que use classes dentro de uma hierarquia sem qualquer
conhecimento das classes específicas que fazem parte dessa hierarquia. Em outras palavras, a hierarquia
—e o programa—ainda é extensível, mesmo quando você escreveu milhares de linhas de código
usando-o. É claro que há uma condição: as classes ancestrais da hierarquia precisam ser projetadas
com muito cuidado.

O exemplo Animals2 demonstra o uso dessas novas classes e possui um formato semelhante ao do
exemplo anterior. Este código é executado clicando no botão, mostrando a saída e também
produzindo algum som:

começar
Mostrar(FMyAnimal.Voice);
MediaPlayer1.FileName := SoundsFolder + FMyAnimal.Voice + '.wav';
MediaPlayer1.Play;
fim;

note O aplicativo usa um componente MediaPlayer para reproduzir um dos dois arquivos de som que acompanham o aplicativo (os
arquivos de som são nomeados de acordo com os sons reais, ou seja, os valores retornados pelo método Voice ). Um
ruído bastante aleatório para o animal genérico e alguns latidos para o cachorro. Agora o código funciona facilmente no
Windows, desde que os arquivos estejam na pasta adequada, mas requer algum esforço para implantação em plataformas
móveis. Dê uma olhada na demonstração real para ver como a implantação e as pastas estão estruturadas.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

08: herança - 241

Substituindo, redefinindo e reintroduzindo


Métodos
Como acabamos de ver, para substituir um método de ligação tardia em uma classe descendente, você
precisa usar a palavra-chave override . Observe que isso só pode ocorrer se o método foi definido
como virtual na classe ancestral, mas também poderia ter sido definido como dinâmico, uma palavra-
chave que abordaremos um pouco mais tarde. Caso contrário, se fosse este seria considerado um
método estático e, como tal, não pode ser modificado usando ligação tardia, a não ser alterando o
código da classe ancestral.

note Você deve se lembrar que usei a mesma palavra-chave também no último capítulo para substituir o Destroy
destruidor padrão, herdado da classe base TObject .

As regras são simples: um método definido como estático permanece estático em todas as
subclasses, a menos que você o esconda com um novo método virtual com o mesmo nome. Um método
definido como virtual permanece vinculado tardiamente em cada subclasse. Não há como mudar
isso, devido à maneira como o compilador gera código diferente para métodos de ligação tardia.

Para redefinir um método estático, basta adicionar um método a uma subclasse com os mesmos
parâmetros ou parâmetros diferentes do original, sem quaisquer especificações adicionais. Para
substituir um método virtual , você deve especificar os mesmos parâmetros e usar a palavra-chave
override :

tipo
TMyClass = turma
procedimento Um; virtual;
procedimento dois; // Método estático
fim;

TMySubClass = classe(TMyClass)
procedimento Um; sobrepor;
procedimento dois;
fim;

O método redefinido, Dois, não possui ligação tardia. Então, quando você o aplica a uma variável da
classe base, ele chama o método da classe base, não importa o que aconteça (ou seja, mesmo que a variável
está se referindo a um objeto da classe derivada, que possui uma versão diferente para aquele
método).

Normalmente existem duas maneiras de substituir um método. Uma é substituir o método da classe
ancestral por uma versão totalmente nova. A outra é adicionar mais código ao método existente. Esta
segunda abordagem pode ser realizada usando o método herdado
palavra-chave para chamar o mesmo método da classe ancestral. Por exemplo, você pode escrever

procedimento TMySubClass.One;
começar
// Novo Código

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

242 - 08: herança

...
// Chama o procedimento herdado TMyClass.One
herdou Um;
fim;

Você pode estar se perguntando por que precisa usar a palavra-chave de substituição . Em outras
linguagens, ao redefinir um método virtual em uma subclasse, você substitui automaticamente o original.
No entanto, ter uma palavra-chave específica permite ao compilador verificar a correspondência
entre o nome do método na classe ancestral e o nome do método na subclasse (escrever incorretamente
uma função redefinida é um erro comum em algumas outras linguagens OOP), verificar se o método
era virtual na classe ancestral, e então
sobre.

note Há outra linguagem OOP popular que possui a mesma palavra-chave de substituição , C#. Isto não é sur-
surpreendente, dado o fato de as linguagens compartilharem um designer comum. Anders Hejlsberg tem alguns longos
artigos explicando por que a palavra-chave override é uma ferramenta de controle de versão fundamental para projetar
bibliotecas, como você pode ler em http://www.artima.com/intv/nonvirtual.html. Mais recentemente, o Swift da Apple
a linguagem também adotou a palavra-chave override para modificar métodos em classes derivadas.

Outra vantagem desta palavra-chave é que se você definir um método estático em qualquer classe
herdada por uma classe da biblioteca, não haverá problema, mesmo que a biblioteca seja
atualizada com um novo método virtual com o mesmo nome de um método que você' definimos.
Como seu método não está marcado pela palavra-chave override , ele será considerado um método
separado e não uma nova versão daquele adicionado à biblioteca (algo que provavelmente quebraria seu
código existente).

O suporte à sobrecarga acrescenta ainda mais complexidade a esse quadro. Uma subclasse pode
fornecer uma nova versão de um método usando a palavra-chave sobrecarga . Se o método tiver
parâmetros diferentes da versão na classe base, ele se tornará efetivamente um método sobrecarregado;
caso contrário, substitui o método da classe base. Aqui está um exemplo:

tipo
TMyClass = turma
procedimento Um;
fim;

TMySubClass = classe(TMyClass)
procedimento Um(S: string); sobrecarga;
fim;

Observe que o método não precisa ser marcado como sobrecarga na classe base. Entretanto, se o
método na classe base for virtual, o compilador emite o aviso Método 'Um' oculta o método virtual do tipo
base 'TMyClass.'

Para evitar esta mensagem do compilador e instruir o compilador com mais precisão sobre
suas intenções, você pode usar a diretiva específica de reintrodução :

tipo

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

08: herança - 243

TMyClass = turma
procedimento Um; virtual;
fim;

TMySubClass = classe(TMyClass)
procedimento Um(S: string); reintroduzir; sobrecarga;
fim;

Você pode encontrar esse código no exemplo ReintroduceTest e experimentá-lo ainda mais.

note Um cenário no qual a palavra-chave reintroduce é usada é quando você deseja adicionar um construtor
Create personalizado a uma classe de componente, que já herda um construtor Create virtual
da classe base TComponent .

Herança e Construtores

Como vimos, você pode usar a palavra-chave herdada para invocar um método de mesmo nome (ou
também um método diferente) em um método de uma classe derivada. O mesmo também se aplica aos
construtores. Enquanto em outras linguagens como C++, C# ou Java, a chamada ao construtor da
classe base é implícita e obrigatória (quando você precisa passar parâmetros para o construtor da classe
base), no Object Pascal chamar um construtor da classe base não é estritamente necessário.

Na maioria dos casos, entretanto, chamar manualmente o construtor da classe base é extremamente
importante. Este é o caso, por exemplo, de qualquer classe de componente, já que a inicialização
do componente é realmente feita no nível da classe TComponent :

construtor TMyComponent.Create(Proprietário: TComponent);


começar
herdado Criar (Proprietário);
// Código específico...
fim;

Isto é particularmente importante porque para componentes Create é um método virtual. Da mesma forma
para todas as classes, o destruidor Destroy é um método virtual e você deve se lembrar de
chamar o método herdado nele.

Fica uma dúvida: Se você está criando uma classe, que só herda de TObject, em seus construtores você
precisa chamar o construtor base TObject.Create ? Do ponto de vista técnico, a resposta é “não”, visto que
o construtor está vazio. Entretanto, considero um bom hábito sempre chamar o construtor da classe
base, não importa o que aconteça. Se você é um maníaco por desempenho, entretanto, admito que isso
pode desacelerar desnecessariamente seu código... em uma fração de microssegundo completamente
imperceptível.

Brincadeiras à parte, há boas razões para ambas as abordagens, mas especialmente para um iniciante
com a linguagem recomendo sempre chamar o construtor da classe base como um bom hábito de
programação, promovendo uma codificação mais segura.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

244 - 08: herança

Métodos Virtuais versus Dinâmicos


No Object Pascal, existem duas maneiras diferentes de ativar a ligação tardia. Você pode declarar um
método como virtual, como vimos antes, ou declará-lo como dinâmico. A sintaxe dessas duas palavras-
chave é exatamente a mesma e o resultado de seu uso também é o mesmo.
A diferença é o mecanismo interno usado pelo compilador para implementar a ligação tardia.

Os métodos virtuais são baseados em uma tabela de métodos virtuais (ou VMT, mas coloquialmente
também conhecida como vtable). Uma tabela de métodos virtuais é uma matriz de endereços de métodos.
Para uma chamada a um método virtual, o compilador gera código para saltar para um endereço
armazenado no enésimo slot da tabela de métodos virtuais do objeto.
As tabelas de métodos virtuais permitem a execução rápida das chamadas de método. Sua principal
desvantagem é que eles exigem uma entrada para cada método virtual de cada classe descendente, mesmo que o
O método não é substituído na subclasse. Às vezes, isso tem o efeito de propagar entradas de tabelas de
métodos virtuais por toda uma hierarquia de classes (mesmo para métodos que não são redefinidos). Isso
pode exigir muita memória apenas para armazenar o mesmo endereço de método que um
número de vezes.

As chamadas de métodos dinâmicos, por outro lado, são despachadas usando um número exclusivo que
indica o método. A busca pela função correspondente é geralmente mais lenta do que a simples consulta de
tabela em uma etapa para métodos virtuais. A vantagem é que as entradas do método dinâmico só se
propagam nos descendentes quando os descendentes substituem o método. Para hierarquias de
objetos grandes ou profundas, usando métodos dinâmicos em vez de virtuais
métodos podem resultar em economias significativas de memória com apenas uma penalidade mínima de velocidade.

Do ponto de vista de um programador, a diferença entre essas duas abordagens reside apenas em uma
representação interna diferente e em uma velocidade ou uso de memória ligeiramente diferente. Além
disso, os métodos virtuais e dinâmicos são iguais.

Agora explicada a diferença entre estes dois modelos, é importante sublinhar que na maior parte dos
casos, os desenvolvedores de aplicações utilizam
em vez de dinâmico.

Manipuladores de mensagens no Windows

Ao criar aplicativos para Windows, um método de vinculação tardia de propósito especial


pode ser usado para lidar com uma mensagem do sistema Windows. Para este propósito, Object Pascal
fornece ainda outra diretiva, message, para definir métodos de tratamento de mensagens, que devem ser
procedimentos com um único parâmetro var do tipo apropriado. A diretiva message é seguida pelo número
da mensagem do Windows que o método deseja tratar. Por exemplo, o código a seguir permite tratar
uma mensagem definida pelo usuário, com o valor numérico indicado pela constante WM_USER do
Windows:

tipo

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

08: herança - 245

TForm1 = classe(TForm)
...
procedimento WmUser(var Msg:TMessage); mensagem WM_USER;
fim;

O nome do procedimento e o tipo real dos parâmetros são com você, desde que a estrutura física dos
dados corresponda à estrutura da mensagem do Windows. As unidades usadas para fazer interface
com a API do Windows incluem vários tipos de registro predefinidos para as diversas mensagens do
Windows. Essa técnica pode ser extremamente útil para programadores veteranos do Windows, que
sabem tudo sobre mensagens do Windows e funções de API, mas essa técnica não é absolutamente
compatível com outros sistemas operacionais (como macOS, iOS e Android).

Abstraindo Métodos e Classes


Ao criar uma hierarquia de classes, às vezes é difícil determinar qual é a classe base, pois ela pode não
representar uma entidade real, mas apenas ser usada para manter algum comportamento compartilhado.
Um exemplo seria uma classe base animal para algo como uma classe de gato ou cachorro. Tal classe
para a qual não se espera que você crie nenhum objeto é frequentemente indicada como uma
classe abstrata , porque não possui implementação concreta e completa. Uma classe abstrata pode
ter métodos abstratos, métodos que não possuem uma implementação real.

Métodos Abstratos
A palavra-chave abstract é usada para declarar métodos virtuais que serão definidos apenas em
subclasses da classe atual. A diretiva abstrata define completamente o método; não é uma declaração
antecipada. Se você tentar fornecer uma definição para o método, o compilador irá reclamar.

No Object Pascal, você pode criar instâncias de classes que possuem métodos abstratos . Entretanto,
quando você tenta fazer isso, o compilador emite a mensagem de aviso: Construindo instância de
<nome da classe> contendo métodos abstratos. Se acontecer de você chamar um método
abstrato em tempo de execução, o Delphi irá gerar uma exceção específica em tempo de execução.

note C++, Java e outras linguagens usam uma abordagem mais rigorosa: nessas linguagens, você não pode criar
instâncias de classes abstratas.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

246 - 08: herança

Você pode estar se perguntando por que deseja usar métodos abstratos . A razão está no uso do
polimorfismo. Se a classe TAnimal possui o método abstrato virtual Voice, cada subclasse pode
redefini-lo.

A vantagem é que agora você pode usar o objeto FMyAnimal genérico para se referir a cada animal
definido por uma subclasse e invocar este método. Se este método não estivesse presente em
Na interface da classe TAnimal , a chamada não teria sido permitida pelo compilador, que realiza
verificação de tipo estático. Usando um objeto FMyAnimal genérico , você pode chamar apenas o
método definido por sua própria classe, TAnimal.

Você não pode chamar métodos fornecidos por subclasses, a menos que a classe pai tenha pelo menos o
declaração deste método – na forma de um método abstrato . O próximo exemplo, Animals3,
demonstra o uso de métodos abstratos e o erro de chamada abstrata. Aqui estão as interfaces das
classes deste novo exemplo:

tipo
TAnimal = classe
privado
FKind: string;
público
construtor Criar;
função GetKind: string;
função Voz: string; virtual; abstrato;
fim;

TDog = classe(TAnimal)
público
construtor Criar;
função Voz: string; sobrepor;
função Comer: string; virtual;
fim;

TCato = classe(TAnimal)
público
construtor Criar;
função Voz: string; sobrepor;
função Comer: string; virtual;
fim;

A parte mais interessante é a definição da classe TAnimal, que inclui um método abstrato virtual :
Voice. Também é importante notar que cada classe derivada substitui esta definição e adiciona um novo
método virtual, Eat. Quais são as implicações destas duas abordagens diferentes? Para chamar o
método Voice , podemos simplesmente escrever o mesmo código da versão anterior do programa:

Mostrar(MeuAnimal.Voice);

Como podemos chamar o método Eat ? Não podemos aplicá-lo a um objeto da classe TAnimal .
A declaração

Mostrar(MeuAnimal.Eat);

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

08: herança - 247

gera o erro do compilador Identificador de campo esperado.

Para resolver este problema, você pode usar um typecast dinâmico e seguro para tratar o TAnimal
objeto como um objeto TCat ou TDog , mas esta seria uma abordagem muito complicada e propensa a
erros:

começar
se MyAnimal for TDog então
Mostrar(TDog(MeuAnimal).Comer)
senão se MyAnimal for TCat então
Show(TCat(MeuAnimal).Comer);

Este código será explicado posteriormente na seção “Operadores de conversão de tipo seguro”. Adicionar
a definição do método virtual à classe TAnimal é uma solução típica para o problema, já que
todos os animais comem, e a presença da palavra-chave abstrata favorece essa escolha. O código acima
parece feio, e evitar tal código é justamente a razão para usar o polimorfismo, e também porque, neste
caso, ele é representativo do mundo real que estamos modelando com nossa implementação.

Finalmente, observe que quando uma classe possui um método abstrato, ela geralmente é considerada
uma classe abstrata. No entanto, você também pode marcar especificamente uma classe com a diretiva abstrata
(e será considerada uma classe abstrata mesmo que não possua métodos abstratos). Novamente, em
Object Pascal isso não impedirá que você crie uma instância da classe, portanto nesta linguagem a
utilidade de uma declaração de classe abstrata é bastante limitada.

Aulas Seladas e Métodos Finais


Como mencionei, Java tem uma abordagem muito dinâmica com ligação tardia (ou funções virtuais)
sendo o padrão. Por esse motivo, a linguagem introduziu conceitos como classes das quais você não pode
herdar (selados) e métodos que você não pode substituir em classes derivadas (métodos finais ou
métodos não virtuais).

Classes seladas são classes das quais você não pode mais herdar. Isso pode fazer sentido se você
estiver distribuindo componentes (sem o código-fonte) ou pacotes de tempo de execução e quiser limitar
a capacidade de outros desenvolvedores de modificar seu código. Um dos objetivos originais também era
aumentar a segurança do tempo de execução, algo que geralmente não será necessário em uma linguagem
totalmente compilada como Object Pascal.

Os métodos finais são métodos virtuais que você não pode substituir em classes herdadas.
Novamente, embora façam sentido em Java (onde todos os métodos são virtuais por padrão e os métodos
finais são significativamente otimizados), elas foram adotadas em C#, onde as funções virtuais são
explicitamente marcadas e são muito menos importantes. Da mesma forma, eles foram adicionados ao
Object Pascal, onde raramente são usados.

Em termos de sintaxe, este é o código de uma classe selada:

tipo

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

248 - 08: herança

TDeriv1 = classe selada (TBase)


procedimento A; sobrepor;
fim;

Tentar herdar dele causa o erro “Não é possível estender a classe selada Tderiv1”. Isso é
a sintaxe de um método final:

tipo
TDeriv2 = classe(TBase)
procedimento A; sobrepor; final;
fim;

Herdar desta classe e substituir o método A causa o erro do compilador, “Não é possível substituir um
método final”.

Operadores TypeCast seguros


Como vimos anteriormente, a regra de compatibilidade de tipo de linguagem para classes descendentes permite
usar uma classe descendente onde uma classe ancestral é esperada. Como mencionei, o inverso não é
possível.

Agora suponha que a classe TDog possua um método Eat , que não está presente na classe TAni-mal . Se a
variável FMyAnimal se referir a um cachorro, você pode querer chamar o
função. Mas se você tentar, e a variável for referente a outra classe, o resultado será um erro. Ao fazer uma
conversão de tipo explícita, poderíamos causar um erro de execução desagradável (ou pior, um
problema sutil de substituição de memória), porque o compilador não pode determinar se o tipo do objeto está
correto e se os métodos que estamos chamando realmente existem.

Para resolver o problema, podemos usar técnicas baseadas em informações do tipo de tempo de execução.
Essencialmente, porque cada objeto em tempo de execução “conhece” seu tipo e sua classe pai. Podemos
solicitar essas informações com o operador is ou utilizando alguns dos métodos da classe TObject . Os parâmetros
do operador is são um objeto e um tipo de classe, e o
o valor de retorno é um booleano:

se FMyAnimal for TDog então


...

A expressão is é avaliada como True somente se o objeto FMyAnimal estiver atualmente se


referindo a um objeto da classe TDog ou a um tipo descendente e compatível com TDog. Isso
significa que se você testar se um objeto TDog armazenado em uma variável TAnimal é realmente um TDog
objeto, o teste será bem-sucedido. Em outras palavras, esta expressão é avaliada como True se você puder
atribua com segurança o objeto (FMyAnimal) a uma variável do tipo de dados (TDog).

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

08: herança - 249

note A implementação real do operador is é fornecida pelo método InheritsFrom do TOb-


classe de projeto . Então você poderia escrever a mesma expressão que FMyAnimal.InheritsFrom(TDog). A razão
para usar este método vem diretamente do fato de que ele também pode ser aplicado a referências de classe
e outros tipos de propósitos especiais que não suportam o operador is .

Agora que você tem certeza de que o animal é um cachorro, você pode usar um molde do tipo direto (que
seria em geral inseguro) escrevendo o seguinte código:

se FMyAnimal for TDog então


começar
Meu Cachorro := TDog(FMyAnimal);
Texto := Meu Cachorro.Eat;
fim;

Essa mesma operação pode ser realizada diretamente por outro operador de conversão de tipo
relacionado, como, que converte o objeto somente se a classe solicitada for compatível com a
classe real, caso contrário, ele falhará com uma exceção de tempo de execução. Os parâmetros do
operador as são um objeto e um tipo de classe, e o resultado é um objeto “convertido” para o novo tipo de classe.
Podemos escrever o seguinte trecho:

Meu Cachorro := FMyAnimal como TDog;


Texto := Meu Cachorro.Eat;

Se quisermos apenas chamar a função Eat , também poderemos usar uma notação ainda mais curta:

(FMyAnimal como TDog).Comer;

O resultado desta expressão é um objeto do tipo de dados da classe TDog , portanto você pode aplicar a
ele qualquer método dessa classe. A diferença entre a conversão tradicional e o uso do as cast é que
o segundo verifica o tipo real do objeto e gera uma exceção se o tipo não for compatível com o tipo
para o qual você está tentando convertê-lo. A exceção levantada é EInvalidCast (as exceções são
descritas no próximo capítulo).

avisar Por outro lado, na linguagem C#, a expressão as retornará nil se o objeto não for compatível com o tipo, enquanto
no a conversão direta do tipo gerará uma exceção. Então basicamente as duas operações são invertidas em
comparação com Object Pascal.

Para evitar esta exceção, use o operador is e, se tiver sucesso, faça um typecast simples (na verdade
não há razão para usar is e como na sequência, fazendo a verificação de tipo duas vezes–
embora você veja frequentemente o uso combinado de is e as):

se FMyAnimal for TDog então


TDog(MeuAnimal).Comer;

Ambos os operadores typecast são muito úteis no Object Pascal porque muitas vezes você deseja
escrever código genérico que pode ser usado com vários componentes do mesmo tipo ou até mesmo
de tipos diferentes. Por exemplo, quando um componente é passado como parâmetro para um

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

250 - 08: herança

método de resposta a evento, um tipo de dados genérico é usado (TObject), portanto, muitas vezes você precisa lançar
de volta ao tipo de componente original:

procedimento TForm1.Button1Click(Remetente: TObject);


começar
se o remetente for TButton então
...
fim;

Esta é uma técnica comum que usarei em alguns exemplos posteriores (os eventos são apresentados no Capítulo 10).

Os dois operadores typecast, is e as, são extremamente poderosos e você pode ficar tentado a considerá-los
como construções de programação padrão. Embora sejam realmente poderosos, você provavelmente deveria
limitar seu uso a casos especiais. Quando você precisar resolver um problema complexo envolvendo várias classes,
tente primeiro usar o polimorfismo.
Somente em casos especiais, onde o polimorfismo por si só não pode ser aplicado, você deve tentar utilizar os
operadores de conversão de tipo para complementá-lo.

note O uso dos operadores typecast tem um leve impacto negativo no desempenho, pois ele deve percorrer a hierarquia
de classes para verificar se o typecast está correto. Como vimos, as chamadas de métodos virtuais requerem
apenas uma consulta à memória, o que é muito mais rápido.

Herança de formulário visual

A herança não é usada apenas em classes de biblioteca ou nas classes que você escreve, mas é bastante difundida
em todo o ambiente de desenvolvimento baseado em Object Pascal. Como vimos, quando você cria um formulário
no IDE, esta é uma instância de uma classe que herda de TForm. Portanto, qualquer aplicação visual possui uma
estrutura baseada em herança, mesmo nos casos em que você acaba escrevendo a maior parte do seu código
em manipuladores de eventos simples.

O que é menos conhecido, mesmo por desenvolvedores mais experientes, é que você pode herdar um novo
formulário de um já criado, um recurso geralmente chamado de herança visual de formulário (e algo bastante
peculiar ao ambiente de desenvolvimento Object Pascal).

O elemento interessante aqui é que você pode ver visualmente o poder da herança em ação e descobrir diretamente
suas regras! Isso é útil também na prática? Bem, depende principalmente do tipo de aplicativo que você está
construindo. Se tiver vários formulários, alguns dos quais são muito semelhantes entre si ou simplesmente incluem
elementos comuns, então você pode colocar os componentes comuns e os manipuladores de eventos comuns no
formulário base e adicionar o comportamento e os componentes específicos às subclasses. Outro cenário comum
é usar herança de formulário visual para personalizar alguns dos formulários de um aplicativo para

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

08: herança - 251

empresas específicas, sem duplicar qualquer código-fonte (que é a principal razão para usar herança em primeiro
lugar).

Você também pode usar herança de formulário visual para personalizar um aplicativo para diferentes sistemas
operacionais e formatos (telefone para tablets, por exemplo), sem duplicar qualquer
código-fonte ou código de definição de formulário; apenas herde as versões específicas para um cliente dos
formulários padrão.

Lembre-se de que a principal vantagem da herança visual é que posteriormente você pode alterar a forma original e
atualizar automaticamente todas as formas derivadas. Esta é uma vantagem bem conhecida da herança em linguagens
de programação orientadas a objetos. Mas há um efeito colateral benéfico: o polimorfismo. Você pode adicionar
um método virtual a um formulário base e substituí-lo em um formulário de subclasse. Então você pode consultar os
dois formulários e chamar esse método para cada um deles.

note Outra abordagem na construção de formas com os mesmos elementos é contar com molduras, ou seja, na composição visual
dos painéis de formas. Em ambos os casos, em tempo de design, você pode trabalhar em duas versões de um formulário.
No entanto, na herança de formulário visual, você define duas classes diferentes (pai e derivada), enquanto com quadros
você trabalha em uma classe de quadro e em uma instância desse quadro hospedada por um formulário.

Herdando de um formulário base


As regras que regem a herança de formas visuais são bastante simples, uma vez que você tenha uma ideia clara
do que é herança. Basicamente, um formulário de subclasse possui os mesmos componentes do formulário pai, bem
como alguns novos componentes. Você não pode remover um componente da classe base, embora (se for um
controle visual) você possa torná-lo invisível. O importante é que você pode alterar facilmente as propriedades dos
componentes que herda.

Observe que se você alterar uma propriedade de um componente no formato herdado, qualquer modificação da
mesma propriedade no formulário pai não terá efeito. A alteração de outras propriedades do componente
também afetará as versões herdadas. Você pode ressincronizar os dois valores de propriedade usando o comando
de menu Reverter para local herdado
do Inspetor de Objetos. O mesmo é feito definindo as duas propriedades com o mesmo valor e recompilando o
código. Depois de modificar múltiplas propriedades, você pode sincronizá-las novamente com a versão base
aplicando o comando Reverter para herdado do menu local do componente.

Além de herdar componentes, o novo formulário herda todos os métodos do formulário base,
incluindo os manipuladores de eventos. Você pode adicionar novos manipuladores no formulário herdado e também
substituir os manipuladores existentes.

Para demonstrar como funciona a herança de formulário visual, criei um exemplo muito simples, chamado
VisualInheritTest. Descreverei passo a passo como construí-lo. Primeiro, inicie um novo projeto para vários
dispositivos, selecione um projeto em branco e adicione dois botões ao seu formulário principal. Então
selecione Arquivo ÿ Novo ÿ Outros e escolha a página “Itens Herdáveis” em Novos Itens

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

252 - 08: herança

caixa de diálogo (veja a Figura 8.3). Aqui você pode escolher o formulário do qual deseja herdar.

Figura 8.3: A
caixa de diálogo Novos
Itens permite criar um
formulário herdado.

O novo formulário possui os mesmos dois botões. Aqui está a descrição textual inicial do novo formulário:

Form2 herdado: TForm2


Legenda = 'Formulário2'
...
fim

E aqui está a declaração inicial da classe, onde você pode ver que a classe base não é o TForm usual ,
mas o formato real da classe base:

tipo
TForm2 = classe(TForm1)
privado
{ Declarações privadas }
público
{ Declarações públicas }
fim;

Observe a presença da palavra-chave herdada na descrição textual; observe também que o formulário
realmente possui alguns componentes, embora eles sejam definidos no formulário da classe base. Se você
alterar a legenda de um dos botões e adicionar um novo botão, a descrição textual será alterada de acordo:

Form2 herdado: TForm2


Legenda = 'Formulário2'
...
Button1 herdado: TButton
Text = 'Ocultar formulário'

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

08: herança - 253

fim
objeto Button3: TButton
...
Text = 'Novo botão'
OnClick = Button3Click
fim
fim

Apenas as propriedades com valor diferente são listadas, pois as demais são simplesmente
herdadas como estão.

Figura 8.4: As
duas formas do exemplo
VirtualInheritTest em
tempo de execução

Cada um dos botões do primeiro formulário possui um manipulador OnClick , com código simples. O
primeiro botão mostra o segundo formulário chamando seu método Show ; enquanto o segundo botão
exibe uma mensagem simples.

O que acontece na forma herdada? Lá, primeiro alteraremos o comportamento do botão Mostrar
para implementá-lo como um botão Ocultar. Isso implica não executar o manipulador de eventos da
classe base (então comentei a chamada herdada padrão). Para o botão Hello, adicionamos uma
segunda mensagem àquela exibida pela classe base, deixando a chamada herdada :

procedimento TForm2.Button1Click(Remetente: TObject);


começar
// Herdado;
Esconder;
fim;

procedimento TForm2.Button2Click(Remetente: TObject);


começar
herdado;

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

254 - 08: herança

'Olá do Form2'
ShowMessage(end; );

Lembre-se que diferentemente de um método herdado, que pode usar a palavra-chave herdada para
chamar o método da classe base com o mesmo nome, em um manipulador de eventos a palavra-
chave herdada representa uma chamada ao manipulador de eventos correspondente do formulário pai
(independentemente de o nome do método do manipulador de eventos).

Claro, você também pode considerar cada método do formulário base como um método do seu
formulário e chamá-los livremente. Este exemplo permite que você explore alguns recursos da herança
de forma visual, mas para ver seu verdadeiro poder você precisará examinar exemplos mais complexos
do mundo real do que este livro tem espaço para explorar.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

09: tratamento de exceções - 255

09: manuseio
exceções

Antes de prosseguirmos com a cobertura de outros recursos de classes na linguagem Object Pascal,
precisamos nos concentrar em um grupo específico de objetos usados para tratar condições de erro, conhecidos
como exceções.

A ideia do tratamento de exceções é tornar os programas mais robustos, adicionando a capacidade de tratar
erros de software ou hardware (e qualquer outro tipo de erro) de maneira simples e uniforme. Um programa pode
sobreviver a tais erros ou terminar normalmente, permitindo ao usuário salvar dados antes de sair. As exceções
permitem separar o código de tratamento de erros do código normal, em vez de entrelaçar os dois. Dessa
forma, você acaba escrevendo um código mais compacto e menos confuso por tarefas de manutenção não
relacionadas ao objetivo real da programação.

Outro benefício é que as exceções definem um mecanismo uniforme e universal de relatório de erros, que
também é usado pelas bibliotecas de componentes. Em tempo de execução, o sistema gera exceções
quando algo dá errado. Se o seu código tiver sido escrito corretamente, ele poderá reconhecer o problema e
tentar resolvê-lo; caso contrário, a exceção é passada para
seu código de chamada e assim por diante. Em última análise, se nenhuma parte do seu código tratar a exceção, o
o sistema geralmente lida com isso, exibindo uma mensagem de erro padrão e tentando continuar o programa.
No cenário incomum, seu código é executado fora de qualquer bloco de tratamento de exceção, gerar
uma exceção fará com que o programa seja encerrado.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

256 - 09: tratamento de exceções

Todo o mecanismo de tratamento de exceções no Object Pascal é baseado em cinco palavras-chave separadas:

· try registra o início de um bloco de código protegido

· except registra o final de um bloco de código protegido e introduz a exceção-


código de manipulação

· on marca as instruções individuais de tratamento de exceção, vinculadas a classes de exceção


específicas, cada uma tendo a sintaxe on do tipo de exceção
· finalmente é usado para especificar blocos de código que sempre devem ser executados, mesmo quando
ocorrem exceções

· raise é a instrução usada para disparar uma exceção tomando como parâmetro um objeto de exceção
(esta operação é chamada de throw em outras linguagens de programação)

Esta é uma tabela de comparação simples de palavras-chave de tratamento de exceções em Object Pascal com
linguagens baseadas na sintaxe de exceções C++ (como C# e Java):

tentar tentar

exceto em pegar
finalmente finalmente
elevação lançar

Usando a terminologia da linguagem C++, você lança um objeto de exceção e o captura por tipo. É o
mesmo em Object Pascal, onde você passa para a instrução raise um objeto de exceção e o recebe
como parâmetro das instruções except on .

Blocos Try-Except
Deixe-me começar com um exemplo bastante simples de tentativa-exceto (parte do ExceptionsTest
exemplo), aquele que possui um bloco geral de tratamento de exceções:

função DividePlusOne (A, B: Inteiro): Inteiro;


começar
tentar
// Gera exceção se B é igual a 0
Resultado:= A div B;
Inc(Resultado);
exceto
Resultado:= 0;
fim;
// Mais código
fim;

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

09: tratamento de exceções - 257

note Quando você executa um programa no depurador Delphi, o depurador irá parar o programa por padrão
quando uma exceção é encontrada mesmo se houver um manipulador de exceções. Normalmente é isso
que você deseja, é claro, porque deseja saber onde ocorreu a exceção e visualizar a chamada do
manipulador passo a passo. Se você quiser apenas deixar o programa ser executado quando a exceção for
tratada corretamente e ver o que um usuário veria, execute o programa com o comando “Executar sem
depuração” ou desative todas (ou algum tipo de) exceções no depurador opções.

“Silenciar” a exceção, como no código acima, e definir o resultado como zero não faz muito sentido em
uma aplicação do mundo real (já que mascarar erros como esse do usuário geralmente é uma prática
ruim), mas o código tem como objetivo ajudá-lo a entender o mecanismo principal em um cenário simples.

Este é o código do manipulador de eventos usado para chamar a função:

era
N: Inteiro;
começar
N := DividePlusOne(10, Random(3));
Mostrar(N.ToString);

Como você pode ver, o programa usa um valor gerado aleatoriamente para que quando você clicar no botão
botão você pode estar em uma situação válida (2 vezes em 3) ou em uma situação inválida. Desta forma,
pode haver dois fluxos de programa diferentes:

· Se B não for zero, o programa faz a divisão, executa o incremento e então pula o bloco except para a
instrução final que o segue (// Mais)

· Se B for zero, a divisão gera uma exceção, todas as instruções a seguir são ignoradas (bem, apenas uma
neste caso) até o primeiro bloco try-expect , que é executado em seu lugar. Após o bloco de exceção, o
programa não retornará à instrução original, mas pulará até depois do bloco except executando a primeira
instrução após ela (// More).

Uma forma de descrever este modelo de exceção é dizer que ele segue uma abordagem de não-retomada.
Em caso de erro, tentar tratar a condição de erro e voltar à instrução que o causou é muito perigoso,
pois o status do programa naquele ponto provavelmente é indefinido. As exceções alteram significativamente
o fluxo de execução, ignorando a execução da instrução a seguir e revertendo a pilha até que o código
de tratamento de erros adequado seja encontrado.

O código acima tinha um bloco except muito simples , sem instrução on . Quando você precisar lidar com
vários tipos de exceções (ou vários tipos de classes de exceção) ou se desejar
para acessar o objeto de exceção passado para o bloco, você precisa ter um ou mais em
declarações:

função DividePlusOneBis (A, B: Inteiro): Inteiro;


começar
tentar
Resultado:= A div B; // Erro se B é igual a 0

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

258 - 09: tratamento de exceções

Resultado := Resultado + 1;
exceto
em E: EDivByZero começa

Resultado: = 0;
ShowMessage(E.Mensagem); fim;
fim;
fim;

Na instrução de tratamento de exceções, capturamos a exceção EDivByZero , que é definida pela


biblioteca de tempo de execução. Existem vários desses tipos de exceção referentes a problemas de tempo
de execução (como uma divisão por zero ou uma conversão dinâmica incorreta), a problemas de sistema
(como erros de falta de memória) ou a erros de componentes (como um índice inválido). ). Todas essas
classes de exceções herdam da classe base Exception, que oferece alguns recursos mínimos, como a
propriedade Message que usei no código acima. Essas classes formam uma hierarquia real com alguma
estrutura lógica.

note Observe que enquanto os tipos em Object Pascal são geralmente marcados com uma letra inicial T, as classes
de exceção aceitam uma exceção à regra e geralmente começam com a letra E.

A hierarquia de exceções
Aqui está uma lista parcial das principais classes de exceção definidas na unidade System.SysUtils da
biblioteca de tempo de execução (a maioria das outras bibliotecas do sistema adiciona seus próprios tipos de
exceção à lista principal abaixo):

Exceção
EArgumentException
EArgumentOutOfRangeException
EArgumentNilException
EPathTooLongException
ENotSupportedException
EDirectoryNotFoundException
EFileNotFoundException
EPathNotFoundException
EListError
EInvalidOpException
ENoConstructException
EAbortar
EHeapException
EOutOfMemory
EInvalidPointer
EInOutError
EExterno
EExternalException
EIntError
EDivByZero
ERangeError

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

09: tratamento de exceções - 259

EIntOverflow
EMathError
EInvalidOp
EZeroDivide
EOverflow
EUunderflow
EAcessViolação
EPrivilégio
EControlC
Ele estava cavalgando

EInvalidCast
EConvertError
ECodesetConversão
EVariantError
EPropReadOnly
EPopWriteOnly
EAssertionFailed
EAbstractError
EIntfCastError
EInvalidContainer
EInválidoInserir
EPackageError
ECFError
EOSError
ESafecallException
EMonitor
EMonitorLockException
ENoMonitorSupportException
EProgrammerNotFound
ENotImplementado
EObjectDisposed
EJNIException

note: Não sei sobre você, mas ainda preciso descobrir o cenário exato de uso do que considero a classe de exceção mais
estranha, a engraçada exceção EProgrammerNotFound ! Existem alguns ovos de Páscoa escondidos nas bibliotecas
do Delphi e este é um deles.

Agora que você viu a hierarquia básica de exceções, posso acrescentar uma informação à
descrição anterior das instruções except-on . Essas instruções são avaliadas em sequência
até que o sistema encontre uma classe de exceção que corresponda ao tipo do objeto de
exceção que foi gerado. Agora, a regra de correspondência usada é a regra de compatibilidade
de tipo que examinamos no último capítulo: um objeto de exceção é compatível com qualquer
um dos tipos base de seu próprio tipo específico (como um objeto TDog era compatível com
a classe TAnimal ).
Isso significa que você pode ter vários tipos de manipuladores de exceção que correspondam à
exceção. Se você quiser ser capaz de lidar com as exceções mais granulares (as classes
mais baixas da hierarquia) junto com as mais genéricas, caso nenhuma das anteriores
corresponda, você deve listar os blocos do manipulador do mais específico para o mais
genérico ( ou da classe de exceção filha até suas classes pai).

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

260 - 09: tratamento de exceções

Além disso, se você escrever um manipulador para o tipo Exception , ele será uma cláusula abrangente,
portanto, precisa ser o último na sequência.

Aqui está um trecho de código com dois manipuladores em um bloco:

função DividePlusOne (A, B: Inteiro): Inteiro;


começar
tentar
Resultado:= A div B; // Erro se B é igual a 0
Resultado := Resultado + 1;
exceto
em EDivByZero fazer
começar
Resultado:= 0;
'Erro de divisão por zero'
MensagemDlg(end; , mtError, [mbOK], 0);

em E: Exceção fazer
começar
Resultado:= 0;
MessageDlg(E.Message, mtError, [mbOK], 0);
fim;
fim; // Fim do bloco except
fim;

Neste código existem dois manipuladores de exceção diferentes após o mesmo bloco try . Você pode ter
qualquer número desses manipuladores, que são avaliados em sequência conforme explicado acima.

Lembre-se de que usar um manipulador para todas as exceções possíveis geralmente não é uma boa
escolha. É melhor deixar exceções desconhecidas no sistema. O manipulador de exceção padrão geralmente
exibe a mensagem de erro da classe de exceção em uma caixa de mensagem e, em seguida, retoma a
operação normal do programa.

dica Na verdade, você pode modificar o manipulador de exceção normal fornecendo um método para o evento
Application.OnException , para, por exemplo, registrar a mensagem de exceção em um arquivo em vez de exibi-la ao
usuário.

Levantando exceções
A maioria das exceções que você encontrará em sua programação Object Pascal será gerada pelo sistema,
mas você também pode gerar exceções em seu próprio código quando descobrir dados inválidos ou
inconsistentes em tempo de execução.

Na maioria dos casos, para uma exceção personalizada, você definirá seu próprio tipo de exceção.
Simplesmente crie uma nova subclasse a partir da classe de exceção padrão ou de uma de suas subclasses
existentes que vimos acima:

tipo
EArrayFull = classe(Exceção);

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

09: tratamento de exceções - 261

Na maioria dos casos, você não precisa adicionar nenhum método ou campo à nova classe de exceção
e a declaração de uma classe derivada vazia será suficiente.

O cenário para este tipo de exceção seria um método que adiciona elementos a um array gerando um
erro quando o array está cheio. Isso é feito criando o objeto de exceção e passando-o para a palavra-
chave raise :

se MyArray.IsFull então
aumentar EArrayFull.Create( 'Matriz completa');

Este método Create (herdado da classe base Exception ) possui um parâmetro string para descrever a
exceção ao usuário.

note Você não precisa se preocupar em destruir o objeto criado para a exceção, pois ele será excluído
automaticamente pelo mecanismo do manipulador de exceções.

Existe um segundo cenário para usar a palavra-chave raise . Dentro de um bloco except , você pode
querer executar algumas ações, mas não capturar a exceção para deixá-la fluir para o próximo
manipulador de exceção. Se é isso que você deseja fazer, basta chamar raise sem parâmetros de
dentro do bloco de exceção . A operação é chamada de recriar uma exceção.

Exceções e a pilha
Quando o programa gera uma exceção e a rotina atual não lida com isso, o que acontece com seu
método e sua pilha de chamadas? O programa começa a procurar um manipulador entre as funções
já na pilha. Isso significa que o programa sai da função atual ignorando as instruções restantes e
saltando para o manipulador de exceções. Para entender como isso funciona, você pode usar o
depurador ou adicionar uma série de linhas de saída simples, para ser informado quando uma
determinada instrução do código-fonte for executada. No próximo exemplo, ExceptionFlow, segui
esta segunda abordagem.

Por exemplo, quando você pressiona o botão Raise1 na forma do ExceptionFlow


Por exemplo, uma exceção é levantada e não tratada, de modo que a parte final do código nunca será
executada:

procedimento TForm1.ButtonRaise1Click(Remetente: TObject);


começar
// Chamada desprotegida
AddToArray(24);
'Programa nunca chega aqui' );
Mostrar(fim;

Observe que esse método chama o procedimento AddToArray , que invariavelmente gera a exceção.
Quando a exceção é tratada, o fluxo inicia novamente após o manipulador e não após o código que
gera a exceção. Considere este método modificado:

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

262 - 09: tratamento de exceções

procedimento TForm1.ButtonRaise2Click(Remetente: TObject);


começar
tentar
// Este procedimento levanta um exceção
AddToArray(24);
Mostrar 'Programa nunca chega aqui' );
(exceto
em EArrayFull faça
'Tratar a exceção' );
Mostrar(fim;
'Chamada ButtonRaise1Click concluída' );
Mostrar(fim;

A última chamada Show será executada logo após a segunda, enquanto a primeira é sempre ignorada.
Sugiro que você execute o programa, altere seu código e experimente-o para compreender completamente
o fluxo do programa quando uma exceção for gerada.

note Dado que o local do código onde você trata a exceção é diferente daquele em que a exceção foi gerada,
seria bom poder saber em qual método a exceção foi realmente gerada. Embora existam maneiras de
obter um rastreamento de pilha quando a exceção é gerada e disponibilizar essas informações no
manipulador, este é realmente um tópico avançado que não pretendo abordar aqui. Na maioria dos casos, Object Pascal
os desenvolvedores contam com bibliotecas e ferramentas de terceiros (como JclDebug da Jedi
Component Library, madExcept ou EurekaLog). Além disso, você deve gerar e incluir no seu código um
arquivo MAP criado pelo compilador e que liste o endereço de memória de cada método e função
da sua aplicação.

O último bloco
Há uma quarta palavra-chave para tratamento de exceções que mencionei, mas que ainda não usei
até agora. Um bloco finalmente é usado para realizar algumas ações (geralmente operações de
limpeza) que sempre devem ser executadas. Na verdade, as declarações no final
bloco são processados independentemente de ocorrer ou não uma exceção. O código simples seguindo um
O bloco try é executado somente se uma exceção não foi levantada ou se foi levantada e tratada.
Em outras palavras, o código do bloco finalmente é sempre executado após o código do bloco try ,
mesmo que uma exceção tenha sido levantada.

Considere este método (parte do exemplo ExceptFinally ), que executa algumas operações
demoradas e mostra seu status na legenda do formulário:

procedimento TForm1.BtnWrongClick(Remetente: TObject);


era
I, J: Inteiro;
começar
Legenda := 'Calculando' ;

J:= 0;

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

09: tratamento de exceções - 263

// cálculo
Longo(e errado)...
para I := 1000 até 0 faça
J := J + J div I;

Legenda := 'Finalizado' ;
'
'Total: + J.ToString);
Mostrar(fim;

Como há um erro no algoritmo (pois a variável I pode atingir o valor 0 e também é usada em uma divisão),
o programa irá quebrar, mas não irá zerar a legenda do formulário.
É para isso que serve um bloco try-finally :

procedimento TForm1.BtnTryFinallyClick(Sender: TObject);


era
I, J: Inteiro;
começar
Legenda := 'Calculando' ;
J:= 0;
tentar
// cálculo
Longo(e errado)...
para I := 1000 até 0 faça
J := J + J div I; '
'Total:
Mostrar(+ J.ToString);
finalmente
Legenda := 'Finalizado' ;
fim;
fim;

Quando o programa executa esta função, ele sempre zera o cursor, ocorrendo ou não uma exceção
(de qualquer tipo). A desvantagem desta versão da função é que ela não trata a exceção.

Finalmente e exceto
Curiosamente, na linguagem Object Pascal, um bloco try pode ser seguido por uma instrução except
ou finalmente , mas não ambas ao mesmo tempo. Dado que muitas vezes você deseja ter os dois
blocos, a solução típica é usar dois blocos try aninhados , associando o interno a uma instrução
finalmente e o externo a uma exceção.
declaração ou vice-versa, conforme a situação exigir. Aqui está o código deste terceiro botão do exemplo
ExceptFinally :

procedimento TForm1.BtnTryTryClick(Remetente: TObject);


era
I, J: Inteiro;
começar
Legenda := 'Calculando' ;
J:= 0;
tente tente
// Longo cálculo (e errado)...
para I := 1000 até 0 faça
J := J + J div I;

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

264 - 09: tratamento de exceções

'
Mostrar 'Total: + J.ToString);
(exceto
em E: EDivByZero do
começar
// Aumente novamente o exceção com um nova mensagem
aumentar Exception.Create(end; 'Erro em Algoritmo' );

fim;
finalmente
Legenda := fim; 'Finalizado' ;

fim;

Restaure o cursor com um bloco final


Um caso de uso comum para blocos try-finally é a alocação e liberação de recursos.
Outro caso relacionado é o de uma configuração temporária que você precisa redefinir após a
conclusão de uma operação, mesmo no caso de essa operação gerar uma exceção.

Um exemplo de configuração temporária que você precisa restaurar é o cursor de ampulheta, exibido
durante uma operação longa e restaurado ao original no final da operação. Mesmo que o código seja
simples, sempre existe a chance de uma exceção
é gerado e, portanto, você deve sempre usar um bloco try-finally .

No exemplo do aplicativo RestoreCursor (um aplicativo VCL, já que o gerenciamento do cursor no


FireMonkey é um pouco mais complexo), escrevi o seguinte código para salvar o cursor atual, configurá-lo
temporariamente para o de ampulheta e restaurar o cursor original em o fim:

var CurrCur := Screen.Cursor;


Screen.Cursor := crHourGlass;
tentar
// Alguma operação lenta
Sono(5000);
finalmente
Screen.Cursor := CurrCur;
fim;

Restaure o cursor com um registro gerenciado


Para proteger uma alocação de recursos ou definir uma configuração temporária para restauração, em vez
de um bloco try-finally explícito , você pode usar um registro gerenciado, que exige que o compilador
adicione um bloco intrínseco de finalmente . Isto resulta na gravação de menos código para proteger um
recurso ou restaurar uma configuração, mesmo que haja algum esforço inicial na definição do registro.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

09: tratamento de exceções - 265

Este é um registro gerenciado que representa o mesmo comportamento do código da seção anterior,
salvando o cursor atual em um campo no método Initialize e redefinindo-o no método Finalize :

tipo
THourCursor = registro
privado
FCurrCur: TCursor;
público
operador de classe Inicializar(out ADest: THourCursor);
operador de classe Finalize(var ADest: THourCursor);
fim;

operador de classe THourCursor.Initialize(out ADest: THourCursor);


começar
ADest.FCurrCur := Tela.Cursor;
Screen.Cursor := crHourGlass;
fim;

operador de classe THourCursor.Finalize(var ADest: THourCursor);


começar
Tela.Cursor := ADest.FCurrCur;
fim;

Depois de definir este registro gerenciado

var HC: THourCursor;


// Alguma operação lenta
Sono(5000);

note Você pode encontrar exemplos mais extensos de proteção de recursos por meio de registros gerenciados na
seguinte postagem do blog de Erik van Bilsen: https://blog.grijjy.com/2020/08/03/automate-restorable-
operações-com-custom-gerenciado -registros/. Isso faz parte de uma série de blogs muito detalhados sobre
registros gerenciados.

Exceções no mundo real


As exceções são um ótimo mecanismo para relatório de erros e tratamento de erros em geral (que não
está dentro de um único fragmento de código, mas como parte de uma arquitetura maior). As exceções em
geral não devem substituir a verificação de uma condição de erro local (embora alguns desenvolvedores
as utilizem dessa forma).

Por exemplo, se você não tiver certeza sobre o nome de um arquivo, verificar se um arquivo existe antes
de abri-lo geralmente é considerado uma abordagem melhor do que abrir o arquivo de qualquer
maneira usando exceções para lidar com o cenário de um arquivo ausente. Porém, verificar se ainda há
espaço em disco suficiente antes de gravar no arquivo é um tipo de verificação que faz pouco sentido fazer
em todos os lugares, pois é uma condição extremamente rara.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

266 - 09: tratamento de exceções

Uma maneira de colocar isso é que um programa deve verificar condições de erro comuns e deixar as
incomuns e inesperadas para o mecanismo de tratamento de exceções. É claro que a linha entre os dois
cenários costuma ser confusa e diferentes desenvolvedores terão diferentes maneiras de julgar.

Onde você invariavelmente usaria exceções é para permitir que diferentes classes e módulos transmitissem
condições de erro entre si. Retornar códigos de erro é extremamente tedioso e sujeito a erros em comparação
ao uso de exceções. Levantar exceções é mais comum em uma classe de componente ou biblioteca do que
em um manipulador de eventos.

O que é extremamente importante e muito comum no código do dia a dia é usar blocos finalmente para
proteger recursos em caso de exceção. Você deve sempre proteger os blocos que se referem a recursos com
uma instrução finalmente , para evitar vazamentos de recursos caso uma exceção seja gerada. Cada vez que
você abre e fecha, conecta e desconecta, cria e destrói algo dentro de uma única função ou método, uma
declaração finalmente é necessária. Por fim, uma instrução finalmente permite manter um programa
estável mesmo no caso de uma exceção ser levantada, permitindo que o usuário continue a usar ou (no caso
de problemas mais significativos) desligue o aplicativo de maneira ordenada.

Tratamento de exceções globais


Se uma exceção levantada por um manipulador de eventos interromper o fluxo padrão de execução, ele
também encerrará o programa se nenhum manipulador de exceção for encontrado? Este é realmente o caso
de um aplicativo de console ou outras estruturas de código de propósito especial, enquanto a maioria dos
aplicativos visuais (incluindo aqueles baseados nas bibliotecas VCL ou FireMonkey) possuem um loop
global de manipulação de mensagens que envolve cada execução em um bloco try-except , de modo que, se
uma exceção for levantada em um manipulador de eventos, ela será interceptada.

note Observe que se uma exceção for gerada no código de inicialização antes do loop de mensagem ser ativado, as
exceções geralmente não serão capturadas pela biblioteca e o programa simplesmente terminará com um erro.
Esse comportamento pode ser parcialmente mitigado adicionando um bloco try-except personalizado no
programa principal. Mesmo com esse nível de proteção, há um código de inicialização da biblioteca que é
executado antes do programa principal ser executado e antes que o bloco try-except personalizado seja inserido,
portanto ainda há a possibilidade de que uma exceção não processada ocorra antes dele.

No caso geral de uma exceção gerada durante a execução, o que acontece depende da biblioteca, mas
geralmente existe uma maneira programática de interceptar essas exceções com manipuladores globais ou
uma maneira de exibir uma mensagem de erro. Embora alguns detalhes sejam diferentes, isso é verdade
tanto para VCL quanto para FireMonkey.

Nas demonstrações anteriores, você viu uma mensagem de erro simples exibida quando uma exceção era
gerada. Se você quiser mudar esse comportamento, você pode manipular o evento OnException

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

09: tratamento de exceções - 267

do objeto Aplicativo global . Embora essa operação pertença mais à biblioteca visual e ao lado do
tratamento de eventos do aplicativo, ela também está vinculada ao tratamento de exceções, por isso vale
a pena abordá-la aqui.

Peguei o exemplo anterior, chamei-o de ErrorLog, e adicionei um novo método ao


formulário principal:

público
procedimento LogException(Remetente: TObject; E: Exceção);

No manipulador de eventos OnCreate , adicionei o código para conectar um método ao evento OnEx-ception
global e, depois disso, escrevi o código real do manipulador global:

procedimento TForm1.FormCreate(Remetente: TObject);


começar
Application.OnException := LogException;
fim;
procedimento TForm1.LogException(Remetente: TObject; E: Exceção);
começar
'
'Exceção + E.Mensagem);
Mostrar(fim;

note Você aprenderá os detalhes de como atribuir um ponteiro de método a um evento (como fiz acima) no
Próximo Capítulo.

Com o novo método no manipulador de exceções globais, o programa grava a mensagem de erro na
saída sem interromper o aplicativo com uma caixa de diálogo de erro.

Exceções e construtores
Há uma questão um pouco mais avançada em torno das exceções, ou seja, o que acontece quando uma
exceção é levantada no construtor de um objeto. Nem todos os programadores Object Pascal sabem que
em tais circunstâncias o destruidor daquele objeto (se disponível) será chamado.

É importante saber isso porque implica que um destruidor pode ser chamado para um objeto
parcialmente inicializado. Presumir que os objetos internos existem em um destruidor porque eles são
criados no construtor pode levar você a situações perigosas em caso de erros reais (ou seja, gerar outra
exceção antes que a primeira seja tratada).

Isso também implica que a sequência adequada para um try-finally deve envolver a criação do objeto fora
do bloco, pois ele é automaticamente protegido pelo compilador. Então se o
o construtor falha, não há necessidade de liberar o objeto. É por isso que o estilo de codificação padrão
em Object Pascal é proteger um objeto escrevendo:

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

268 - 09: tratamento de exceções

AnObject := AClass.Create;
tentar
// Usar o objeto...
finalmente
AnObject.Free;
fim;

note Algo semelhante também acontece para dois métodos especiais da classe TObject , AfterDestruction
e BeforeConstruction, um pseudoconstrutor e um pseudodestruidor introduzidos para compatibilidade
com C++ (mas raramente usados em Object Pascal). Observe que se o método AfterConstruction gerar um
exceção, o método BeforeDestruction é chamado (e também o destruidor regular).

Dado que muitas vezes testemunhei erros no descarte adequado de um objeto em um destruidor,
deixe-me esclarecer melhor o problema com uma demonstração real mostrando o problema junto
com a correção real. Suponha que você tenha uma classe incluindo uma lista de strings e que
escreva o seguinte código para criar e destruir a classe (parte do projeto ConstructorExcept ):

tipo
TObjectWithList = classe
privado
FStringList: TStringList;
público
construtor Criar (Valor: Inteiro);
destruidor Destruir; sobrepor;
fim;

construtor TObjectWithList.Create(Valor: Inteiro);


começar
se valor < 0 então
aumentar Exceção.Create( 'Valor negativo não permitido' );

FStringList := TStringList.Create;
FStringList.Add(Value.ToString);
fim;

destruidor TObjectWithList.Destroy;
começar
FStringList.Clear;
FStringList.Free;
herdado;
fim;

À primeira vista, o código parece correto. O construtor está alocando o subobjeto e o destruidor o
descartando adequadamente. Além disso, o código de chamada é escrito de forma que, se uma
exceção for levantada após o construtor, o método Free seja chamado, mas se a exceção estiver no
construtor nada acontece:

era
Obj: TObjectWithList;
começar
Obj := TObjectWithList.Create(-10);
tentar

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

09: tratamento de exceções - 269

// Fazer algo
finalmente
'Objeto libertador' );
Mostrar(Obj.Livre;
fim;

Então isso funciona? Absolutamente não! Quando este código está envolvido, uma exceção é levantada
no construtor, antes de criar a lista de strings, e o sistema imediatamente invoca o destruidor, que tenta
limpar a lista inexistente, gerando uma violação de acesso ou um erro semelhante.

Por que isso aconteceria? Novamente, se você inverter a sequência no construtor (criar a lista de strings
primeiro, gerar a exceção depois), tudo funcionará corretamente porque o destruidor realmente precisa
liberar a lista de strings. Mas isso não é uma solução real, apenas uma solução alternativa. O que
você deve sempre considerar é proteger o código de um destruidor de uma forma que ele nunca
presuma que o construtor foi completamente executado. Isto é um exemplo:

destruidor TObjectWithList.Destroy;
começar
se atribuído (FStringList) então
começar
FStringList.Clear;
FreeAndNil(FStringList);
fim;
herdado;
fim;

Recursos avançados de exceções


Esta é uma das seções do livro que você pode querer pular na primeira vez que ler.
leia, a menos que você já tenha um bom conhecimento do idioma. Até então
você pode passar para o próximo capítulo e voltar a esta seção no futuro.

Na parte final do capítulo, abordarei alguns tópicos mais avançados relacionados ao tratamento de
exceções. Abordarei exceções aninhadas (RaiseOuterException) e exceções de interceptação de
uma classe (RaisingException).
Esses recursos não faziam parte das primeiras versões da linguagem Object Pascal e adicionam um
poder significativo ao sistema.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

270 - 09: tratamento de exceções

Exceções aninhadas e InnerException


Mecanismo
O que acontece se você gerar uma exceção em um manipulador de exceções? A resposta
tradicional é que a nova exceção substituirá a existente, e é por isso que é uma prática
comum combinar pelo menos as mensagens de erro, escrevendo código como este (sem
qualquer operação real e mostrando apenas as instruções relacionadas às exceções ):

procedimento TFormExceptions.ClassicReraise;
começar
tentar
// Fazer algo...

raise Exception.Create (exceto 'Olá' );


em E: Exception do
// alguma
Tentarcorreção...
'
aumentar Exception.Create(end; 'Outro: + E.Mensagem);

fim;

Este código faz parte do exemplo AdvancedExcept . Ao chamar o método e tratar a


exceção, você verá uma única exceção com a mensagem combinada de ambas as
exceções:

procedimento TFormExceptions.BtnTraditionalClick(Sender: TObject);


começar
tentar
ClássicoReraise;
exceto
em E: Exceção fazer '
Mostrar(+E.Mensagem);
'Mensagem:
fim;
fim;

A saída (bastante óbvia) é:

Mensagem: Outra: Olá

Agora, no Object Pascal, há suporte em todo o sistema para exceções aninhadas. Dentro
de um manipulador de exceção, você pode criar e gerar uma nova exceção e ainda manter
ativo o objeto de exceção atual, conectando-o à nova exceção. Para isso, a classe
Exception possui uma propriedade InnerException , referente à exceção anterior, e uma
propriedade BaseException que permite acessar a primeira exceção de uma série, pois
o aninhamento de exceções pode ser recursivo. Estes são os elementos da classe
Exception relacionados ao gerenciamento de exceções aninhadas:

tipo
Exceção = classe(TObject)
privado
FINnerException: Exceção;
FAcquireInnerException: Boolean;

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

09: tratamento de exceções - 271

protegido
procedimento SetInnerException;
público
função GetBaseException: Exceção; virtual;
propriedade BaseException: Exceção lida GetBaseException;
propriedade InnerException: Exceção lida FINnerException;
procedimento de classe RaiseOuterException(E: Exception); estático;
procedimento de classe ThrowOuterException (E: Exceção); estático;
fim;

note Métodos de classe estáticos são uma forma especial de métodos de classe. Este recurso de idioma será explicado em
Capítulo 12.

Da perspectiva de um usuário, para gerar uma exceção preservando a existente, você deve chamar o
método da classe RaiseOuterException (ou o idêntico ThrowOuterEx-ception, que usa nomenclatura
orientada a C++). Ao lidar com uma exceção semelhante, você pode usar as novas propriedades para
acessar informações adicionais. Observe que você pode chamar RaiseOuterException apenas
dentro de um manipulador de exceção, conforme informa a documentação baseada em código-fonte :

Use esta função para gerar uma instância de exceção de dentro de um manipulador de exceção
e você deseja “adquirir” a exceção ativa e encadeá-la à nova exceção e preservar o contexto.
Isso fará com que o campo seja definido com a exceção atualmente em jogo.
FINnerException

Você só deve chamar esse procedimento de dentro de um bloco except onde se espera que
essa nova exceção seja tratada em outro lugar.

Para obter um exemplo real, você pode consultar o exemplo AdvancedExcept . Neste exemplo
Adicionei um método que gera uma exceção aninhada da nova maneira (em comparação com o método
ClassicReraise listado anteriormente):

procedimento TFormExceptions.MethodWithNestedException;
começar
tentar
levantar Exception.Create (exceto 'Olá' );

Exception.RaiseOuterException(Exception.Create(end; 'Outro' ));

fim;

Agora, no manipulador desta exceção externa, podemos acessar ambos os objetos de exceção (e também
ver o efeito de chamar o novo método ToString ):

tentar
MethodWithNestedException;
exceto
em E: Exceção fazer
começar
'
Mostrar( 'Mensagem: + E.Mensagem);

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

272 - 09: tratamento de exceções

'
Mostrar(+E.ToString);
'Para sequenciar:
se atribuído (E.BaseException) então '
Mostrar( 'Mensagem BaseException: + E.BaseException.Message);
se atribuído (E.InnerException) então '
'Mensagem de InnerException: + E.InnerException.Message);
Mostrar(fim;
fim;

A saída desta chamada é a seguinte:

Mensagem: Outro
ToString: Outro
Olá
Mensagem BaseException: Olá
Mensagem InnerException: Olá

Há dois elementos relevantes a serem observados. A primeira é que, no caso de um único aninhado
exceção, a propriedade BaseException e a propriedade InnerException referem-se ao
mesmo objeto de exceção, o original. A segunda é que, embora a mensagem da nova
exceção contenha apenas a mensagem real, ao chamar ToString você obtém acesso às
mensagens combinadas de todas as exceções aninhadas, separadas por um sLineBreak
(como você pode ver no código do método Exception . Para sequenciar).
A escolha de usar uma quebra de linha neste caso produz uma saída de aparência estranha, mas uma vez
que você saiba disso, você pode formatá-la da maneira que desejar, substituindo as quebras de linha por
um símbolo de sua escolha ou, por exemplo, atribuindo-as à propriedade Text de uma lista de strings.

Como mais um exemplo, deixe-me mostrar o que acontece ao gerar duas exceções aninhadas. Este é
o novo método:

procedimento TFormExceptions.MethodWithTwoNestedExceptions;
começar
tentar
levantar Exception.Create (exceto 'Olá' );

tentar
Exceção.RaiseOuterException(
Exception.Create()); 'Outro'
exceto
Exceção.RaiseOuterException(
Exception.Create()); 'Um terceiro'
fim;
fim;
fim;

Isso chama um método idêntico ao que vimos anteriormente e produz a seguinte saída:

Mensagem: Um terço
ToString: um terço
Outro
Olá

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

09: tratamento de exceções - 273

Mensagem BaseException: Olá


Mensagem InnerException: Outra

Desta vez, a propriedade BaseException e a propriedade InnerException referem-se a objetos diferentes


e a saída de ToString abrange três linhas.

Interceptando uma exceção


Outro recurso avançado adicionado ao longo do tempo ao sistema original de tratamento de exceções do
a linguagem Object Pascal é o método:

procedimento RaisingException(P: PExceptionRecord); virtual;

De acordo com a documentação do código-fonte:

Esta função virtual será chamada logo antes desta exceção ser levantada. No caso de uma
exceção externa, esta é chamada logo após a criação do objeto, pois a condição de “aumento”
já está em andamento.

A implementação da função na classe Exception gerencia a exceção interna (chamando a


SetInnerException interna), o que provavelmente explica por que ela foi introduzida em primeiro lugar,
ao mesmo tempo que o mecanismo de exceção interna.

De qualquer forma, agora que temos esse recurso disponível podemos aproveitá-lo. Ao substituir
esse método, na verdade, temos uma única função pós-criação que é invariavelmente chamada,
independentemente do construtor usado para criar a exceção. Em outras palavras, você pode evitar a
definição de um construtor personalizado para sua classe de exceção e permitir que os usuários chamem
um dos muitos construtores da classe Exception base e ainda assim ter um comportamento personalizado.
Por exemplo, você pode registrar qualquer exceção de uma determinada classe (ou subclasse).

note Outra abordagem para executar o código de inicialização independentemente do construtor que está sendo invocado é
para substituir o método AfterConstruction virtual por TObject.

Esta é uma classe de exceção personalizada (definida novamente no exemplo AdvancedExcept ) que
substitui o método RaisingException :

tipo
ECustomException = classe(Exceção)
protegido
procedimento RaisingException(P:PExceptionRecord); sobrepor;
fim;

procedimento ECustomException.RaisingException(P: PExceptionRecord);


começar
//informações
Registro de exceção '
'Endereço de exceção: + IntToHex(
FormExceptions.Show(Integer(P.ExceptionAddress), 8));'
FormExceptions.show( 'Confusão de exceção: + Mensagem);

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

274 - 09: tratamento de exceções

// Modifique a mensagem
'
Mensagem := Mensagem + (filtrado)' ;

// Processamento padrão
herdado;
fim;

O que a implementação deste método faz é registrar algumas informações sobre a exceção, modificar
a mensagem de exceção e então invocar o processamento padrão das classes base (necessário
para que o mecanismo de exceção aninhada funcione). O método é invocado após a criação do
objeto de exceção, mas antes que a exceção seja gerada.
Isso pode ser percebido porque a saída produzida pelas chamadas Show é gerada antes da exceção
ser capturada pelo depurador! Da mesma forma, se você colocar um ponto de interrupção no
método RaisingException , o depurador irá parar aí antes de capturar a exceção.

Novamente, exceções aninhadas e esse mecanismo de interceptação não são comumente usados no
código do aplicativo, pois são recursos de linguagem mais voltados para desenvolvedores de bibliotecas
e componentes.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

10: propriedades e eventos – 275

10: propriedades e
eventos

Nos últimos três capítulos, abordei os fundamentos da POO em Object Pascal, explicando esses conceitos e
mostrando como os recursos disponíveis na maioria das linguagens orientadas a objetos são implementados
especificamente. Desde os primórdios do Delphi, a linguagem Object Pas-cal era uma linguagem totalmente
orientada a objetos, mas com um sabor específico. Na verdade, também funcionou como a linguagem de uma
ferramenta de desenvolvimento visual baseada em componentes.

Estes não são recursos separados: o suporte para este modelo de desenvolvimento é baseado em alguns
recursos básicos da linguagem, como propriedades e eventos, originalmente introduzidos em Object.
Pascal antes de qualquer outra linguagem, e posteriormente parcialmente copiado por algumas linguagens OOP.
Propriedades, por exemplo, podem ser encontradas em Java e C#, entre outras linguagens, mas
tenho ascendência direta do Object Pascal, embora eu, pessoalmente, prefira a implementação original, como
explicarei em breve.

A capacidade do Object Pascal de suportar o desenvolvimento rápido de aplicações (RAD) e a programação visual
é a razão de conceitos como propriedades, o especificador de acesso publicado, eventos, o conceito de um
componente e algumas outras ideias abordadas neste capítulo. Este modelo de desenvolvimento é frequentemente
descrito usando o termo modelo PME (modelo propriedade-método-evento), que é uma implementação específica
da abordagem RAD.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

276 - 10: propriedades e eventos

Definindo Propriedades
O que é uma propriedade? As propriedades podem ser descritas como identificadores que permitem acessar e modificar
o status de um objeto – algo que pode desencadear a execução de código nos bastidores. No Object Pascal, as propriedades
abstraem e ocultam o acesso aos dados por meio de campos ou métodos,
tornando-os uma implementação primária de encapsulamento. Uma maneira de descrever propriedades é “encapsulamento
ao máximo”.

Tecnicamente, uma propriedade é um identificador com um tipo de dados mapeado para alguns dados reais usando
especificadores de leitura e gravação . Diferentemente de Java ou C#, no Object Pascal o especificador de leitura e gravação
pode ser um método getter ou setter ou fazer referência direta a um
campo.

Por exemplo, aqui está a definição de uma propriedade para um objeto de data usando uma abordagem comum (ler
diretamente do campo, escrever através de um método):

privado
FMês: Inteiro;
procedimento SetMonth(Valor: Inteiro);
público
propriedade Mês: Inteiro lido FMonth gravação SetMonth;

Para acessar o valor da propriedade Mês , este código deve ler o valor do campo privado FMonth, enquanto, para alterar o
valor, chama o método SetMonth. O código para alterar o valor (protegendo contra valores negativos) poderia ser algo
como:

procedimento TDate.SetMonth(Valor: Inteiro);


começar
se (Valor <= 0) ou (Valor> 12) então
Mês := 1
outro
FMês := Valor;
fim;

note No caso de entrada incorreta, como um número de mês negativo, geralmente é melhor mostrar um erro (por
levantando uma exceção) do que ajustar o valor nos bastidores, mas estou mantendo o código como está para
uma simples demonstração introdutória.

Observe que o tipo de dados do campo e da propriedade devem corresponder exatamente (se houver discrepância, você
pode usar um método simples para converter o tipo); da mesma forma, o tipo do parâmetro único de um procedimento setter
ou o valor de retorno de uma função getter deve corresponder exatamente ao tipo de propriedade.

Diferentes combinações são possíveis (por exemplo, também poderíamos usar um método para ler
o valor ou alterar diretamente um campo na diretiva de gravação ), mas o uso de um método para alterar o valor de uma
propriedade é mais comum. Aqui estão algumas implementações alternativas para a mesma propriedade:

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

10: propriedades e eventos - 277

propriedade Mês: Inteiro lido GetMonth write SetMonth;


propriedade Mês: Inteiro lido FMonth escrever FMonth;

note Ao escrever código que acessa uma propriedade, é importante perceber que um método pode ser
chamado. O problema é que alguns desses métodos demoram algum tempo para serem executados; eles também
podem produzir vários efeitos colaterais, muitas vezes incluindo uma repintura (lenta) do controle na tela. Embora os
efeitos colaterais das propriedades raramente sejam documentados, você deve estar ciente de que eles existem,
principalmente quando estiver tentando otimizar seu código.

A diretiva de gravação de uma propriedade também pode ser omitida, tornando a propriedade somente leitura
propriedade:

propriedade Mês: Inteiro lido GetMonth;

Tecnicamente, você também pode omitir a diretiva read e definir uma propriedade somente gravação , mas isso
geralmente não faz muito sentido e raramente é feito.

Propriedades comparadas a outras programações


línguas
Se você comparar isso com Java ou C#, em ambas as linguagens as propriedades são mapeadas para
métodos, mas a primeira possui mapeamento implícito (as propriedades são basicamente uma convenção),
enquanto a última possui mapeamento explícito como Object Pascal, mesmo que apenas para métodos:

// Propriedades em Java
private int mês;

public int getMonth() { return mMonth; }

public void setMonth(int valor) {


se (valor <= 0)
mMês = 1;
outro
mMês = valor;
}

int s = date.getMonth();
data.setMês(s + 1);

// Propriedades em C#
private int mês;

público int Mês {


get { return mMês; }
definir {
se (valor <= 0)
mMês = 1;

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

278 - 10: propriedades e eventos

outro
mMês = valor;
}
}

data.Mês++;

Não que eu não queira discutir em profundidade o mérito relativo das propriedades nas diversas linguagens
de programação, mas como mencionei na introdução deste capítulo, acho que ter propriedades explicitamente
definidas é uma ideia útil, e também que quanto mais O nível de abstração obtido pelo mapeamento de
propriedades para campos sem a carga extra de um método é uma adição muito boa.

É por isso que prefiro a implementação de propriedades do Object Pascal em comparação com outras
linguagens.

Propriedades Implementam Encapsulamento


As propriedades são um mecanismo OOP muito sólido, uma aplicação muito bem pensada da ideia de
encapsulamento. Essencialmente, você tem um nome que esconde a implementação de como acessar as
informações de uma classe (seja acessando os dados diretamente ou chamando um método).

Na verdade, ao usar propriedades você acaba com uma interface que provavelmente não mudará. Ao mesmo
tempo, se você deseja permitir que os usuários acessem apenas alguns campos da sua classe, você pode
facilmente publicar esses campos como propriedades em vez de torná-los completamente públicos.
Você não tem mais código para escrever e ainda pode alterar a implementação da sua classe. Então, mesmo
se você substituir o acesso direto aos dados pelo acesso baseado em método, não será necessário
alterar o código-fonte que usa essas propriedades. Você vai
só precisa recompilá-lo.

Pense nisso como o conceito de encapsulamento elevado à potência máxima!

note Você pode estar se perguntando, se uma propriedade é definida com acesso direto a uma variável privada, não é?
remover uma das vantagens do encapsulamento? O usuário não pode ser protegido contra qualquer alteração no tipo
de dados da variável privada, ao passo que pode estar protegido com getters e setters. No entanto, dado que o usuário
acessará os dados por meio da propriedade, o desenvolvedor da classe pode a qualquer momento alterar o tipo de
dados subjacente e introduzir getters e setters, sem afetar o código que os utiliza. É por isso que chamei isso de
“encapsulamento ao máximo”. Por outro lado, isso mostra o lado pragmático do Object Pascal, na medida em que
permite ao programador escolher qualquer maneira mais fácil (e rápida execução de código) onde for adequado às
circunstâncias, e uma transição suave para uma maneira “OOP adequada” quando isso é necessário.

Porém, há uma ressalva no uso de propriedades no Object Pascal. Geralmente você pode atribuir
um valor para uma propriedade ou lê-lo, e você pode usar propriedades livremente em expressões. Entretanto,
você não pode passar uma propriedade como parâmetro de referência para um procedimento ou método.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

10: propriedades e eventos - 279

Isso ocorre porque uma propriedade não é um local de memória, mas uma abstração, portanto não pode ser
usada como parâmetro de referência (var) . Por exemplo, diferentemente do C#, você não pode chamar Inc em
uma propriedade.

note Um recurso relacionado, a passagem de propriedades por referência, será descrito posteriormente neste capítulo. No
entanto, é um recurso pouco usado que requer a ativação de uma configuração específica do compilador e certamente
não é um recurso principal.

Conclusão de código para propriedades


Se adicionar propriedades a uma classe pode parecer um trabalho tedioso, o editor do IDE permite que você
preencha automaticamente propriedades facilmente quando você escreve a parte inicial de uma declaração
de propriedade (dentro de uma classe), como a seguir:

tipo
TMyClass = turma
público
propriedade Mês: Inteiro;
fim;

Pressione a combinação de teclas Ctrl+Shift+C enquanto o cursor estiver na declaração da propriedade e


você obterá um novo campo adicionado à classe junto com um novo método setter, com o mapeamento
adequado na definição da propriedade e a implementação completa do método setter, com código básico para
alterar o valor do campo. Em outras palavras, o código acima
usando o atalho do teclado (ou o item correspondente do menu local do editor) fica:

tipo
TMyClass = turma
privado
FMês: Inteiro;
procedimento SetMonth(const Valor: Inteiro);
público
propriedade Mês: Inteiro lido FMonth gravação SetMonth;
fim;

{ TMyClass}
procedimento TMyClass.SetMonth(const Valor: Inteiro);
começar
FMês := Valor;
fim;

Você também gostaria de um método getter, substitua a parte lida da definição por
GetMonth, como em:

propriedade Mês: Inteiro lido GetMonth write SetMonth;

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

280 - 10: propriedades e eventos

Agora pressione Ctrl+Shift+C novamente e a função também será adicionada, mas desta vez sem código predefinido
para acessar o valor:

função TMyClass.GetMonth: Inteiro;


começar

fim;

Adicionando propriedades a formulários


Vejamos um exemplo específico de encapsulamento usando propriedades. Em vez de criar uma classe personalizada,
desta vez modificarei a classe de formulário que o IDE gera para cada formulário visual criado; e também vou
aproveitar a conclusão da aula.

Quando um aplicativo tem vários formulários, muitas vezes é útil poder acessar informações de um formulário a partir
de outro. Você pode ficar tentado a adicionar um campo público, mas isso é invariavelmente uma má ideia. Toda
vez que você quiser disponibilizar alguma informação de um formulário para outros formulários, você deve usar
uma propriedade.

Basta escrever na declaração de classe do formulário o nome e o tipo da propriedade:

propriedade Cliques: Inteiro;

Em seguida, pressione Ctrl+Shift+C para ativar a conclusão de código. Você verá o seguinte efeito:

tipo
TFormProp = classe(TForm)
privado
FCcliques: Inteiro;
procedimento SetClicks (const Valor: Inteiro);
público
propriedade Clicks: leitura inteira FCClicks escreve SetClicks;
fim;

implementação

procedimento TForm1.SetClicks(const Valor: Inteiro);


começar
FCcliques := Valor;
fim;

Escusado será dizer que isso economiza muita digitação. Agora, quando um usuário clica em um formulário, você pode
aumente a contagem de cliques escrevendo a seguinte linha, como fiz no OnMouseDown
evento do formulário do exemplo FormProperties :

Cliques := Cliques + 1;

Você deve estar se perguntando: que tal aumentar os FClicks diretamente? Bem, neste cenário específico isso
pode funcionar, mas você também pode usar o método SetClicks para atualizar a interface do usuário e realmente
exibir o valor atual. Se você ignorar a propriedade e

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

10: propriedades e eventos - 281

acessar o campo diretamente, o código adicional no método setter usado para atualizar a interface
do usuário não será executado e a exibição poderá ficar fora de sincronia.
A outra vantagem desse encapsulamento é que, de outra forma, você poderia se referir ao número
de cliques de forma devidamente abstraída. Na verdade, as propriedades nas classes de formulário
podem ser usadas para acessar valores personalizados, mas também para encapsular o acesso aos
componentes do formulário. Por exemplo, se você tiver um formulário com um rótulo usado para
exibir algumas informações e quiser modificar o texto de um formulário secundário, poderá ficar
tentado a escrever:

Form1.StatusLabel.Text := 'novo texto' ;

Esta é uma prática comum, mas não é boa porque não fornece nenhum encapsulamento da estrutura
ou dos componentes do formulário. Se você tiver código semelhante em muitos lugares de um
aplicativo e posteriormente decidir modificar a interface do usuário do formulário
(talvez você queira que algo aconteça sempre que o rótulo for atualizado), você terá que corrigir o
código em vários lugares.

A alternativa é utilizar um método ou, melhor ainda, uma propriedade, para ocultar o controle
específico, conforme a seguir:

propriedade StatusText: string lida GetStatusText escreve SetStatusText;

Se você criar a linha acima e pressionar a combinação Ctrl+Shift+C novamente, o editor adicionará
as definições dos dois métodos para ler e escrever a propriedade:

função TFormProp.GetStatusText: string;


começar
Resultado: = LabelStatus.Text
fim;

procedimento TFormProp.SetStatusText(valor const: string);


começar
LabelStatus.Text := Valor;
fim;

Observe que, neste caso, a propriedade não é mapeada para um campo local de uma classe, mas
sim para o campo de um subobjeto, o rótulo (caso você tenha utilizado a geração automática de
código, lembre-se de realmente deletar a propriedade FStatusText do editor pode ter adicionado
em seu nome).

Nos demais formulários do programa, você pode simplesmente consultar a propriedade StatusText
do formulário, e se a interface do usuário mudar, apenas a implementação do Set e Get
métodos da propriedade são afetados. Mesmo em outros métodos do formulário, você geralmente
deve usar a propriedade em vez de acessar a implementação subjacente:

procedimento TFormProp.SetClicks (valor const: inteiro);


começar
FCcliques := Valor; ' cliques'
StatusText := FCClicks.ToString + ;

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

282 - 10: propriedades e eventos

fim;

Adicionando propriedades à classe TDate


No Capítulo 7, construí uma classe TDate . Agora iremos estendê-lo usando propriedades. Este
novo exemplo, DateProperties, é basicamente uma extensão do exemplo ViewDate do Capítulo
7. Aqui está a nova declaração da classe. Possui alguns novos métodos (usados para definir
e obter os valores das propriedades) e quatro propriedades:

tipo
TData = aula privada

FDate: TDateTime; função


GetYear: Inteiro; procedimento
SetYear(const Valor: Inteiro); função GetDay: Inteiro;
procedimento SetDay(const Valor:
Inteiro); função GetMonth: Inteiro; procedimento
SetMonth(const Valor: Inteiro); construtor
público Criar; sobrecarga; construtor Criar (Y, M, D: Inteiro);

sobrecarga; procedimento SetValue(Y, M, D:


Inteiro); sobrecarga; procedimento SetValue(NewDate: TDateTime);
sobrecarga; função Ano bissexto: Booleano; procedimento Aumento
(NumberOfDays: Integer = 1); procedimento Diminuir (NumberOfDays: Integer
= 1); função GetText: string; virtual;
propriedade Dia: Inteiro lido GetDay escreve SetDay; propriedade Mês:
Inteiro lido GetMonth write SetMonth; propriedade Ano: Inteiro lido
GetYear write SetYear; propriedade Texto: string lida
GetText; fim;

As propriedades Ano, Dia e Mês leem e gravam seus valores usando métodos correspondentes. Aqui estão as
implementações dos métodos necessários para dar suporte à propriedade Mês :

função TDate.GetMonth: Inteiro;


era
Y, M, D: Palavra;
começar
DecodeDate(FDate, Y, M, D);
Resultado :=
M; fim;

procedimento TDate.SetMonth(const Valor: Inteiro); comece se (Valor <1)


ou
(Valor> 12) então aumente EDateOutOfRange.Create(
'Mês inválido' );
SetValue (Valor, Dia, Ano);

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

10: propriedades e eventos - 283

fim;

A chamada para SetValue realiza a codificação real da data, gerando uma exceção no caso de erro usando
uma classe de exceção personalizada que defini para ser gerada sempre que uma data estiver fora do intervalo:

tipo
EDateOutOfRange = classe(Exceção);

A quarta propriedade, Text, mapeia apenas para um método de leitura. Esta função é declarada como virtual,
pois é substituída pela subclasse TNewDate . Não há razão para o Get ou Set
O método de uma propriedade não deve usar ligação tardia (um recurso explicado detalhadamente no
Capítulo 8).

note O que é importante reconhecer neste exemplo é que as propriedades não são mapeadas diretamente para os dados.
Eles são simplesmente calculados a partir de informações armazenadas em um tipo diferente e com uma estrutura diferente
daquela que as propriedades parecem implicar.

Tendo atualizado a classe com as novas propriedades, podemos agora atualizar o exemplo para usar
propriedades quando apropriado. Por exemplo, podemos usar a propriedade Text diretamente e podemos usar
algumas caixas de edição para permitir que o usuário leia ou escreva os valores das três propriedades
principais. Na verdade, isso é o que acontece quando o botão BtnRead é pressionado:

procedimento TDateForm.BtnReadClick(Remetente: TObject);


começar
EditYear.Text := TheDay.Year.ToString;
EditMonth.Text := TheDay.Month.ToString;
EditDay.Text := TheDay.Day.ToString;
fim;

O botão BtnWrite faz a operação inversa. Você pode escrever o código em qualquer um dos
duas maneiras seguintes:

Propriedades de uso
de //direto
TheDay.Year := EditYear.Text.ToInteger;
TheDay.Month := EditMonth.Text.ToInteger;
TheDay.Day := EditDay.Text.ToInteger;

// Atualize tudo valores no uma vez


TheDay.SetValue(EditMonth.Text.ToInteger,
EditDay.Text.ToInteger,
EditYear.Text.ToInteger);

A diferença entre as duas abordagens está relacionada ao que acontece quando a entrada não corresponde
a uma data válida. Quando definimos cada valor separadamente, o programa pode alterar o ano e então
gerar uma exceção e pular a execução do restante do código, de modo que a data seja apenas parcialmente
modificada. Quando definimos todos os valores de uma vez, ou eles estão corretos e todos definidos, ou
um é inválido e o objeto de data retém o

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

284 - 10: propriedades e eventos

valor original. Na verdade, esta é a principal razão pela qual as três propriedades devem ser
transformadas em propriedades somente leitura, ou seja, para preservar a integridade dos dados.

Usando propriedades de matriz


As propriedades geralmente permitem acessar um único valor, mesmo um tipo de dados complexo, mas
Object Pascal também oferece suporte a propriedades de array ou indexadores, como são chamados em
C#. Uma propriedade de array é uma propriedade com um parâmetro adicional de qualquer tipo de dados que
é usado como um índice ou (mais em geral) um seletor do valor real.

Aqui está um exemplo de definição de uma propriedade de array que usa um índice inteiro e se refere a um
valor inteiro:

privado
função GetValue (I: Inteiro): Inteiro;
procedimento SetValue (I: Inteiro; const Valor: Inteiro);
público
valor da propriedade [I: Inteiro]: leitura inteira GetValue gravação SetValue;

Uma propriedade de array deve ser mapeada para métodos de leitura e gravação que possuem um
parâmetro extra representando o índice. Quanto a uma propriedade regular, você pode usar o Code Comple-
tion para definir os métodos.

Existem muitas combinações de valores e índices e algumas classes no RTL fazem muito uso das propriedades
do array. Por exemplo, a classe TStrings define cinco deles:

nomes de propriedades [Índice: Inteiro]: string


leia ObterNome;
objetos de propriedade [Índice: Inteiro]: TObject
leia GetObject escreva PutObject;
valores de propriedade [nome const: string]: string
leia GetValue escreva SetValue;
propriedade ValueFromIndex[Índice: Inteiro]: string
leia GetValueFromIndex escreva SetValueFromIndex;
propriedade Strings[Índice: Inteiro]: string
ler Obter escrever Colocar; padrão;

Embora a maioria dessas propriedades de array use o índice da string como parâmetro na lista, outras usam
uma string como valor de pesquisa ou pesquisa (como a propriedade Values acima).

A última dessas definições utiliza outro recurso importante: a palavra-chave padrão . Esse
é um poderoso auxiliar de sintaxe: o nome de uma propriedade de array pode ser omitido, para que você
possa aplicar o operador de colchete diretamente ao objeto em questão.

Portanto, se você tiver um objeto SList deste tipo TStrings , ambas as instruções a seguir funcionarão:

SList.Strings[1]
Lista S[1]

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

10: propriedades e eventos - 285

Em outras palavras, as propriedades padrão do array oferecem uma maneira de definir um operador [ ] personalizado para
qualquer objeto.

Definir propriedades por referência


Este é um tópico bastante avançado de um recurso pouco usado que você provavelmente deveria ignorar se
ainda não tiver experiência com Object Pascal. Mas se estiver, é provável que você nunca tenha ouvido falar
desse recurso.

Na época, o compilador Object Pascal foi estendido para suportar diretamente o Windows COM.
programação, obteve a capacidade de lidar com propriedades “colocadas por ref” (no jargão COM) ou propriedades que
podem receber uma referência, em vez de um valor.

note “Colocar por ref” é o nome que Chris Bensen deu a esse recurso em sua postagem de blog apresentando-o:
http://chrisbensen.blogspot.com/2008/04/delphi-put-by-ref-properties.html (na época, Chris era engenheiro de
P&D da Delphi).

Isso é feito usando um parâmetro var no método setter. Dado que isso pode levar a cenários bastante estranhos, o
recurso (embora ainda faça parte da linguagem) é considerado mais uma exceção do que uma regra, e é por isso que
não está ativo por padrão. Em outras palavras, para habilitar esse recurso você deve habilitá-lo especificamente usando
a seguinte diretiva do compilador:

{$VARPROJECTS SOBRE}

Sem esta diretiva, o código a seguir não será compilado e emitirá o erro
“E2282 Os configuradores de propriedades não podem aceitar parâmetros var”:

tipo
TMyIntegerClass = classe
privado
FNúmero: Inteiro;
função GetNumber: Inteiro;
procedimento SetNumber(var Valor: Inteiro);
público
propriedade Número: Número inteiro lido GetNumber gravação SetNumber;
fim;

Esta classe faz parte do exemplo VarProp . Agora, o que é muito estranho é que você pode ter efeitos colaterais no
configurador de propriedades:

procedimento TMyIntegerClass.SetNumber(var Valor: Inteiro);


começar
Inc(Valor); // Efeito colateral
FNúmero := Valor;
fim;

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

286 - 10: propriedades e eventos

O outro efeito muito incomum é que você não pode atribuir um valor constante à propriedade,
apenas uma variável (que deveria ser esperada, como acontece com qualquer chamada envolvendo um
parâmetro passado por referência):

era
Microfone: TMyIntegerClass;
N: Inteiro;
começar
...
Número do microfone: = 10; // Erro: Variável E2036 necessária
Número do microfone: = N;

Embora não seja um recurso que você usa regularmente, esta é uma maneira bastante avançada de pensar sobre uma
propriedade que permite inicializar ou alterar o valor atribuído a ela. Isso pode levar a um código extremamente
estranho como este:

N:= 10;
Número do microfone: = N;
Número do microfone: = N;
Mostrar(Mic.Number.ToString);

As duas tarefas consecutivas e idênticas parecem um tanto estranhas, mas produzem um efeito colateral,
transformando o número real em 12. Provavelmente a maneira mais complicada e sem sentido de obter esse
resultado!

O especificador de acesso publicado


Junto com as diretivas de acesso público, protegido e privado (e as menos comumente usadas, strict private e
strict protected), a linguagem Object Pascal tem outra muito peculiar, chamada publicada. Uma propriedade publicada
(ou campo ou método)
não está apenas disponível em tempo de execução como um público, mas também gera informações de tempo
de execução estendido (RTTI) que podem ser consultadas.

Na verdade, em uma linguagem compilada, os símbolos compilados são processados pelo compilador e podem ser
usados pelo depurador durante o teste do aplicativo, mas geralmente não deixam nenhum rastro em tempo de
execução. Em outras palavras (pelo menos nos primeiros tempos do Object Pascal), se uma classe tiver uma
propriedade chamada Name você poderá usá-la em seu código para interagir com a classe, mas isso
a interação é traduzida em endereços numéricos e o conceito de nível de origem de um identificador é perdido em
tempo de execução, o que significa que não há como descobrir se uma classe tem um identificador de propriedade
correspondente a uma determinada string, como “ Nome”.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

10: propriedades e eventos - 287

note Tanto a linguagem Java quanto a linguagem C# são linguagens compiladas que tiram proveito de um ambiente de
execução virtual complexo e por esta razão possuem extensas informações de tempo de execução através de
um conceito chamado reflexão. A linguagem Object Pascal tinha reflexão básica (por meio da palavra-chave
publicada) desde os primeiros dias e antes de outras linguagens compiladas, e alter introduziu uma reflexão mais
ou RTTI estendido. O RTTI básico vinculado à palavra-chave publicada é explorado neste capítulo,
enquanto a reflexão ampliada no Capítulo 16.

Por que essas informações extras sobre uma aula são necessárias? É uma das bases do modelo de
componentes e do modelo de programação visual em que as bibliotecas Object Pascal dependem.
Algumas dessas informações são usadas em tempo de design no ambiente de desenvolvimento, para
preencher o Object Inspector com uma lista de propriedades fornecidas por um componente.
Esta não é uma lista codificada, mas algo gerado através da inspeção em tempo de execução dos
dados RTTI extras incluídos no código compilado.

Outro exemplo, provavelmente um pouco complexo demais para ser aprofundado agora, é o mecanismo de
streaming por trás da criação dos arquivos FMX e DFM e quaisquer formulários visuais que os
acompanham. O streaming será apresentado apenas no Capítulo 18 porque faz mais parte da biblioteca
de tempo de execução do que da linguagem principal.

Para resumir o conceito: o uso regular da palavra-chave publicada é importante quando você escreve
componentes para serem usados por você ou por outras pessoas no ambiente de desenvolvimento.
Normalmente, as partes publicadas de um componente contêm apenas propriedades, enquanto as classes
de formulário também usam campos e métodos publicados, como abordarei mais adiante.

Propriedades em tempo de design


Vimos anteriormente neste capítulo que as propriedades desempenham um papel importante no
encapsulamento dos dados de uma classe. Eles também desempenham um papel fundamental na
viabilização do modelo de desenvolvimento visual. Na verdade, você pode escrever uma classe de
componente, disponibilizá-la no ambiente de desenvolvimento, criar um objeto adicionando-o a um
formulário ou superfície de design semelhante e interagir com suas propriedades com o Object Inspector –
a ferramenta que o ambiente de programação visual fornece para permitir que você acesse
propriedades. Nem todas as propriedades podem ser usadas neste cenário, apenas aquelas marcadas
como publicadas na classe do componente. É por isso que os programadores Object Pascal fazem uma
distinção entre propriedades de tempo de design e propriedades somente de tempo de execução .

Propriedades de tempo de design são aquelas declaradas em uma seção publicada da declaração de
classe e podem ser usadas em tempo de design no IDE e também no código. Qualquer outra propriedade
declarada na seção pública da classe não está disponível em tempo de design, mas apenas no código,
e geralmente é chamada apenas de tempo de execução.

Em outras palavras, você pode ver o valor e alterar os valores das propriedades publicadas em tempo de
design usando o Object Inspector. Em tempo de execução, você pode acessar qualquer propriedade pública
ou publicada de outra classe exatamente da mesma maneira, lendo ou escrevendo seu valor no código.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

288 - 10: propriedades e eventos

Nem todas as classes possuem propriedades. As propriedades estão presentes em componentes e em


outras subclasses da classe TPersistent , porque as propriedades geralmente podem ser transmitidas e
salvas em um arquivo. Na verdade, um arquivo de formulário nada mais é do que uma coleção de
propriedades publicadas dos componentes do formulário.

Para ser mais preciso, você não precisa herdar de TPersistent para suportar o conceito de seção publicada,
mas precisa compilar uma classe com a diretiva do compilador $M .
Cada classe compilada com essa diretiva, ou derivada de uma classe compilada com ela, suporta a seção
publicada. Dado que TPersistent é compilado com esta configuração, qualquer classe derivada tem
este suporte.

note As duas seções a seguir sobre visibilidade padrão e RTTI automático adicionam mais informações ao
efeito da diretiva $M e da palavra-chave publicada.

Publicado e Formulários
Quando o IDE gera um formulário, ele coloca as definições de seus componentes e métodos na parte
inicial de sua definição, antes das palavras-chave públicas e privadas .
Esses campos e métodos da parte inicial da classe são publicados.

Observe que, embora eles sejam visíveis globalmente, isso não significa que seja uma boa ideia acessá-
los globalmente, o que significa que você deve prestar atenção especial ao que está acessando-os
globalmente (como discutido anteriormente na seção sobre como adicionar propriedades aos formulários ).
Observe também que o nível de acesso padrão é publicado quando a diretiva de acesso explícita é
especificada antes de um elemento de uma classe de componente.

note Para ser mais preciso, publicado é a palavra-chave padrão somente se a classe foi compilada com $M+
diretiva do compilador ou é descendente de uma classe compilada com $M+. Como esta diretiva é usada na
classe TPersistent , a maioria das classes da biblioteca e todas as classes de componentes são padronizadas
como publicadas. No entanto, classes não componentes (como TStream e TList) são compiladas com $M- e
têm visibilidade pública por padrão.

Aqui está um exemplo:

tipo
TForm1 = classe(TForm)
Memo1: TMemo;
BtnTest: TButton;

Os métodos atribuídos a qualquer evento deverão ser métodos publicados , e os campos correspondentes
aos seus componentes no formulário deverão ser publicados para serem automaticamente conectados
aos objetos descritos no arquivo do formulário e criados junto com o formulário.
Somente os componentes e métodos na parte inicial publicada da declaração do seu formulário podem
aparecer no Object Inspector (na lista de componentes do formulário ou no

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

10: propriedades e eventos - 289

lista dos métodos disponíveis exibidos quando você seleciona a lista suspensa de um evento).

Por que os componentes de uma classe deveriam ser declarados com campo publicado, embora pudessem
ser privados e seguir melhor as regras de encapsulamento OOP? A razão reside no fato de que esses componentes
são criados através da leitura de sua representação transmitida, mas uma vez criados, eles precisam ser atribuídos
aos campos do formulário correspondentes.

Isso é feito usando o RTTI gerado para os campos publicados.

note Tecnicamente, não é realmente obrigatório usar campos publicados para componentes. Você pode tornar seu
código mais experiente em OOP, tornando-o privado. No entanto, isso requer código de tempo de execução
extra. Explicarei isso um pouco mais na última seção deste capítulo, “RAD e OOP”.

RTTI automático
Outro comportamento especial do compilador Object Pascal é que, se você adicionar a palavra-chave publicada a
uma classe, que não herda de TPersistent, o compilador habilitará automaticamente a geração de RTTI adicionando
automaticamente o comportamento {$M+} .

Suponha que você tenha esta classe:

tipo
TMyTestClass = classe
privado
FValor: Inteiro;
procedimento SetValue (valor const: inteiro);
Publicados
valor da propriedade: leitura inteira FValue gravação SetValue;
fim;

O compilador mostra um aviso como:

[Aviso dcc32] AutoRTTIForm.pas(27): W1055 PUBLISHED fez com que RTTI ($M+) fosse adicionado ao
tipo 'TMyTestClass'

O que acontece é que o compilador injeta automaticamente a diretiva {$M+} no código,


como você pode ver no exemplo do AutoRTTI , que inclui o código acima. Neste programa, você pode
escrever o seguinte código, que acessa uma propriedade dinamicamente (usando a antiga unidade
System.TypInfo ):

usa
TipoInfo;

procedimento TFormAutoRtti.BtnTetClick(Sender: TObject);


era
Teste1: TMyTestClass;
começar
Teste1 := TMyTestClass.Create;

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

290 - 10: propriedades e eventos

tentar
Teste1.Valor := 22;
Memo1.Lines.Add(GetPropValue(Test1, 'Valor' ));
finalmente
Teste1.Grátis;
fim;
fim;

note Embora ocasionalmente eu use a unidade TypInfo e funções como GetPropValue definidas nela, o
O verdadeiro poder do acesso RTTI é dado pela unidade RTTI mais moderna e pelo seu amplo suporte à
reflexão. Dado que este é um tópico bastante complexo, achei importante dedicar um capítulo separado
a ele e também distinguir os dois tipos de RTTI que o moderno Object Pascal suporta.

Programação Orientada a Eventos


Em uma biblioteca baseada em componentes (mas também em muitos outros cenários), o código que você
escreve não é apenas uma sequência simples de ações, mas principalmente uma coleção de reações. Com
isso quero dizer que você define como o aplicativo deve “reagir” quando algo acontecer. Esse “algo” pode
ser uma operação do usuário, como clicar em um botão, uma operação do sistema, uma alteração no
status de um sensor, alguns dados ficando disponíveis por meio de uma conexão remota ou qualquer outra
coisa.

Esses gatilhos externos ou internos de ações são geralmente chamados de eventos. Os eventos eram
originalmente um mapeamento de sistemas operacionais orientados a mensagens, como o Windows, mas
já percorreram um longo caminho desde esse conceito original. Na verdade, nas bibliotecas modernas, a
maioria dos eventos é acionada internamente quando você define propriedades, chama métodos ou interage
com um determinado componente (ou indiretamente com outro).

Como os eventos e a programação orientada a eventos se relacionam com a OOP? As duas abordagens
são diferentes em relação a quando e como você cria uma nova classe herdada em comparação com o uso
de uma classe mais geral.

Numa forma pura de programação orientada a objetos, sempre que um objeto tiver um comportamento
diferente (ou um método diferente) de outro, ele deverá pertencer a uma classe diferente.
Vimos isso em algumas demonstrações.

Vamos considerar este cenário. Um formulário possui quatro botões. Cada botão requer um comportamento
diferente quando você clica nele. Portanto, em termos puros de OOP, você deve ter quatro subclasses de
botões diferentes, cada uma com uma versão diferente de um método de “clique” . Essa abordagem é
formalmente correta, mas exigiria muito código extra para escrever e manter, aumentando a complexidade.

A programação orientada a eventos considera um cenário semelhante e sugere que o desenvolvedor


adicione algum comportamento a objetos de botão que sejam todos da mesma classe. O comportamento

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

10: propriedades e eventos - 291

torna-se uma decoração ou extensão do status do objeto, sem exigir uma nova classe.
O modelo também é chamado de delegação, porque o comportamento de um objeto é delegado a um método
de uma classe diferente da classe do próprio objeto.

Os eventos são implementados de diferentes maneiras por diferentes linguagens de programação. Por
exemplo:

· Usando referências a métodos (chamados ponteiros de método como em Object Pascal) ou a eventos
objetos com um método interno (como acontece em C#)

· Delegar código de evento a uma classe especializada que implementa uma interface (como ela gera
aliado acontece em Java)

· Usar encerramentos como geralmente acontece em JavaScript (uma abordagem que o Object Pascal
também suporta com métodos anônimos, abordada no Capítulo 15). Embora, em JavaScript, todos
os métodos sejam encerramentos, as diferenças entre os dois conceitos são
um pouco confuso nessa linguagem.

O conceito de eventos e programação orientada a eventos tornou-se bastante comum e é suportado por
muitas linguagens de programação e bibliotecas de interface de usuário diferentes.
No entanto, a forma como o Delphi implementa suporte para eventos é bastante singular. A seção a seguir
explica em detalhes a tecnologia por trás disso.

Ponteiros de método
Vimos na última parte do Capítulo 4 que a linguagem possui o conceito de função
ponteiros. Esta é uma variável que contém a localização da memória de uma função que você pode usar para
chamar a função indiretamente. Um ponteiro de função é declarado com uma assinatura específica (como
um conjunto de tipos de parâmetros e um tipo de retorno, se houver).

Da mesma forma, a linguagem possui o conceito de ponteiros de método. Um ponteiro de método é uma
referência ao local de memória de um método que pertence a uma classe. Assim como um tipo de
ponteiro de função, o tipo de ponteiro de método possui uma assinatura específica. Porém, um ponteiro
de método carrega mais algumas informações, ou seja: o objeto ao qual o método será aplicado (ou seja,
o objeto que será usado como parâmetro Self quando o método for chamado).

Descrito de forma diferente, um ponteiro de método é uma referência a um método (em um endereço de
memória específico, compartilhado por todos os objetos de uma determinada classe) para um objeto específico
na memória (uma instância específica em um local de memória específico para seus dados). Ao atribuir um
valor a um ponteiro de método, você deve se referir a um método de um determinado objeto, ou seja, um
método de uma instância específica!

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

292 - 10: propriedades e eventos

note Você pode entender melhor a implementação de um ponteiro de método se observar a definição do
estrutura de dados usada em baixo nível para expressar esta construção, que é chamada TMethod. Este registro
possui dois campos Código e Dados, representando respectivamente o endereço do método e o objeto ao qual
será aplicado. Em outras linguagens semelhantes a referência de código é capturada por uma classe delegada
(C#) ou um método de uma interface (Java).

A declaração de um tipo ponteiro de método é semelhante à de um tipo processual, exceto que possui as palavras-
chave object no final da declaração:

tipo
TIntProceduralType = procedimento(Num: Inteiro);
TStringEventType = procedimento(const S: string) do objeto;

Depois de declarar um ponteiro de método, como o acima, você pode declarar uma variável deste tipo e
atribuir a ela um método compatível de qualquer objeto. Qual é um método compatível? Aquele que possui os
mesmos parâmetros solicitados pelo tipo de ponteiro de método, como um único parâmetro de string no exemplo
acima. A referência a um
O método de qualquer objeto pode ser atribuído a um ponteiro de método, desde que seja compatível com o
tipo de ponteiro de método.

Agora que você tem um tipo de ponteiro de método, você pode declarar uma variável desse tipo e atribuir um
método compatível a ela:

tipo
TEventTest = classe
público
procedimento ShowValue(const S: string);
procedimento UseMethod;
fim;
procedimento TEventTest.ShowValue(const S: string);
começar
Mostrar(S);
fim;

procedimento TEventTest.UseMethod;
era
StringEvent: TStringEventType;
começar
StringEvent := ShowValue;
StringEvent(); 'Olá'
fim;

Agora, esse código simples não explica realmente a utilidade dos eventos, pois se concentra no conceito de
tipo de ponteiro de método de baixo nível. Os eventos são baseados nesta implementação técnica, mas vão
além dela armazenando um ponteiro de método em um objeto (digamos, um botão) para
referem-se a um método de um objeto diferente (digamos, o formulário com o manipulador OnClick para o
botão). Na maioria dos casos, os eventos também são implementados por meio de propriedades.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

10: propriedades e eventos - 293

note Embora seja muito menos comum, no Object Pascal você também pode usar métodos anônimos para definir um
manipulador de eventos. A razão pela qual isso é menos comum é provavelmente porque o recurso foi
introduzido recentemente na linguagem e muitas bibliotecas já existiam naquele momento. Além disso, adiciona
um pouco mais de complexidade. Você pode encontrar um exemplo dessa abordagem no Capítulo 15. Outra
extensão possível é a definição de múltiplos manipuladores para um único evento, como o suporte do C#, que não
é um recurso padrão, mas um que você mesmo pode implementar.

O conceito de delegação
À primeira vista, o objetivo desta técnica pode não ser claro, mas este é um dos pilares da tecnologia de componentes Object
Pascal. O segredo está na palavra delegação. Se alguém construiu um objeto que possui alguns ponteiros de método, você está
livre para alterar o comportamento do objeto simplesmente atribuindo novos métodos aos ponteiros. Isso soa familiar? Deveria.

Quando você adiciona um manipulador de eventos OnClick para um botão, o ambiente de desenvolvimento
faz exatamente isso. O botão possui um ponteiro de método, denominado OnClick, e você pode atribuir direta ou
indiretamente um método do formulário a ele. Quando um usuário clica no botão este método é invocado, mesmo que
você o tenha definido dentro de outra classe (normalmente, no
forma).

O que se segue é uma listagem que descreve o código realmente usado nas bibliotecas Delphi para definir o
manipulador de eventos de um componente de botão e o método relacionado de um formulário:

tipo
TNotifyEvent = procedimento(Remetente: TObject) do objeto;

TMyButton = classe
OnClick: TNotifyEvent;
fim;

TForm1 = classe(TForm)
procedimento Button1Click(Remetente: TObject);
Botão1: TMyButton;
fim;

era
Formulário1: TForm1;

Agora, dentro de um procedimento, você pode escrever:

MeuButton.OnClick := Form1.Button1Click;

A única diferença real entre este fragmento de código e o código da biblioteca é que OnClick é um nome de propriedade e
os dados reais aos quais ele se refere são chamados FOnClick. Um evento
que aparece na página Eventos do Inspetor de Objetos, na verdade, nada mais é do que uma propriedade que é um
ponteiro de método. Isso significa, por exemplo, que você pode dinamicamente

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

294 - 10: propriedades e eventos

modificar o manipulador de eventos anexado a um componente em tempo de design ou até mesmo construir um novo
componente em tempo de execução e atribuir um manipulador de eventos a ele.

O exemplo DynamicEvents mostra ambos os cenários. O formulário possui um botão com um manipulador de eventos
OnClick padrão associado a ele. No entanto, adicionei um segundo público
método para o formulário com a mesma assinatura (os mesmos parâmetros):

público
procedimento BtnTest2Click(Remetente: TObject);

Quando o botão é pressionado, além de exibir uma mensagem, ele alterna o manipulador de eventos para o segundo,
alterando o comportamento futuro da operação de clique:

procedimento TForm1.BtnTestClick(Remetente: TObject);


começar
'Mensagem de teste');
ShowMessage(BtnTest.OnClick := BtnTest2Click;
fim;

procedimento TForm1.BtnTest2Click(Remetente: TObject);


começar
ShowMessage(end;'Mensagem de teste, novamente');

Agora, na primeira vez que você clica no botão, o primeiro manipulador de eventos (padrão) é executado, enquanto,
depois disso, todos os outros cliques acionam o segundo manipulador de eventos.

note Conforme você digita o código para atribuir um método a um evento, o Code Completion irá sugerir o evento disponível
nome para você e transforme-o em uma chamada de função real com parênteses no final. Isso não está correto.
Você tem que atribuir ao evento o próprio método, sem chamá-lo. Caso contrário, o compilador tentará
atribuir o resultado da chamada do método (que sendo um procedimento, não existe) resultando em um erro.

A segunda parte do projeto demonstra uma associação de eventos completamente dinâmica.


À medida que você clica na superfície do formulário, um novo botão é criado dinamicamente com um manipulador
de eventos que mostra a legenda do botão associado (o objeto Sender se referirá ao botão criado dinamicamente
para o qual o manipulador de eventos é invocado):

procedimento TForm1.BtnNewClick(Remetente: TObject);


começar
'
ShowMessage(end;'Você selecionado + (Remetente como TButton).Texto);

procedimento TForm1.FormMouseDown(Remetente: TObject; Botão: TMouseButton;


Mudança: TShiftState; X, Y: Único);
era
Botão A: Botão T;
começar
AButton := TButton.Create(Self);
AButton.Parent := Próprio;
AButton.SetBounds(X, Y, 100, 40);
Inc(FContador);
AButton.Text := 'Botão' + IntToStr(FCounter);

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

10: propriedades e eventos - 295

AButton.OnClick := BtnNewClick;
fim;

Com este código cada um dos botões criados dinamicamente reagirá a um clique do mouse
mostrando uma mensagem que depende do botão, mesmo que utilize um único manipulador de
eventos, graças ao uso do parâmetro Sender do evento. Um exemplo da saída é visível na Figura
10.1.

Figura 10.1:
A mensagem exibida pelos
botões criados
dinamicamente
no exemplo
DynamicEvents

Eventos são propriedades


Um conceito muito importante é que eventos em Object Pascal são quase invariavelmente
implementados como propriedades do tipo ponteiro de método. Isso significa que, para tratar
um evento de um componente, você atribui um método à propriedade do evento correspondente.
Em termos de código, isso significa que você pode atribuir a um manipulador de eventos um
método de um objeto, usando código como o seguinte que já vimos na seção anterior:

Button1.OnClick := ButtonClickHandler;

Novamente, a regra é que o tipo de ponteiro de método do evento deve corresponder à assinatura do
o método que você atribui ou o compilador emitirá um erro.
O sistema definiu vários tipos de ponteiros de método padrão para eventos, que são comumente
usados, começando pelo simples:

tipo

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

296 - 10: propriedades e eventos

TNotifyEvent = procedimento(Remetente: TObject) do objeto;

Geralmente, esse é o tipo do manipulador de eventos OnClick , portanto, isso implica que o método
acima deve ser declarado (dentro de uma classe) como:

procedimento ButtonClickHandler(Remetente: TObject);

Se isso parece um pouco confuso, considere o que acontece no IDE: selecione um botão, digamos Button1,
clique duas vezes nele e, no evento OnClick listado no Object Inspector do ambiente de desenvolvimento,
um novo método vazio é adicionado ao o módulo contêiner (provavelmente um formulário):

procedimento TForm1.Button1Click (Remetente: TObject)


começar

fim;

Você preenche o código do método e pronto, tudo funciona! Isso ocorre porque a atribuição do método
manipulador de eventos ao evento acontece nos bastidores exatamente da mesma maneira que todas
as outras propriedades definidas em tempo de design são aplicadas aos componentes.

Pela descrição acima você pode entender que não há correspondência individual
entre um evento e o método atribuído a ele. Muito pelo contrário. Você pode ter vários eventos que
compartilham o mesmo manipulador de eventos, o que explica o motivo do parâmetro Sender usado
com frequência , que indica qual dos objetos acionou o evento. Por exemplo, se você tiver o mesmo
manipulador de eventos OnClick para dois botões, o valor Sender conterá uma referência ao objeto de
botão que foi clicado.

note Você pode atribuir o mesmo método a diferentes eventos no código, como mostrado acima, mas também em tempo de design.
Ao selecionar um evento no Object Inspector, você pode pressionar o botão de seta à direita do nome do
evento para ver uma lista suspensa de métodos “compatíveis” – uma lista de métodos com o mesmo tipo
de ponteiro de método. Isso facilita a seleção do mesmo método para o mesmo evento de diferentes
componentes. Em alguns casos, você também pode atribuir o mesmo manipulador a diferentes eventos
compatíveis do mesmo componente.

Adicionando um evento à classe TDate


Como adicionamos algumas propriedades à classe TDate , agora podemos adicionar um evento. O evento
vai ser muito simples. Ele se chamará OnChange e poderá ser utilizado para avisar ao usuário do
componente que o valor da data foi alterado. Para definir um evento, simplesmente definimos uma
propriedade correspondente a ele e adicionamos alguns dados para armazenar o ponteiro de método real
ao qual o evento se refere. Estas são as novas definições adicionadas à classe no exemplo DateEvent :

tipo
DataT = aula

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

10: propriedades e eventos - 297

privado
FOnChange: TNotifyEvent;
protegido
procedimento DoChange; dinâmico;
público
propriedade OnChange: TNotifyEvent lê FOnChange escreve FOnChange;
fim;

A definição de propriedade é realmente muito simples. Um desenvolvedor usando esta classe pode atribuir um
novo valor para a propriedade e, portanto, para o campo privado FOnChange . O campo geralmente não é atribuído
quando o programa é iniciado, pois os manipuladores de eventos são para usuários do componente,
não para o autor do componente. Um criador de componentes que precise de algum comportamento
adicione-o aos métodos do componente.

Em outras palavras, a classe TDate simplesmente aceita um manipulador de eventos e chama o método
armazenado no campo FOnChange quando o valor da data muda. Obviamente, a chamada ocorrerá somente
se a propriedade do evento tiver sido atribuída.

O método DoChange (declarado como um método dinâmico como é tradicional com métodos de disparo de eventos)
faz o teste e a chamada do método:

procedimento TData.DoChange;
começar
se atribuído (FOnChange) então
FOnChange(Auto);
fim;

note Como você deve se lembrar do Capítulo 8, um método dinâmico é semelhante a um método virtual, mas usa uma
implementação diferente que reduziu o consumo de memória às custas de uma chamada um pouco mais lenta.

O método DoChange por sua vez é chamado toda vez que um dos valores é atualizado, como no exemplo
seguinte código:

procedimento TDate.SetValue(Y, M, D: Inteiro);


começar
FDate := EncodeDate(Y, M, D);
// Dispare o evento
FazerAlterar;
fim;

Agora, se olharmos para o programa que utiliza esta classe, podemos simplificar consideravelmente seu código.
Primeiro, adicionamos um novo método personalizado à classe do formulário:

tipo
TDataForm = classe(TForm)
...
procedimento DateChange(Remetente: TObject);

O código deste método simplesmente atualiza o rótulo com o valor atual do Text
propriedade do objeto TDate :

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

298 - 10: propriedades e eventos

procedimento TDateForm.DateChange;
começar
LabelDate.Text := TheDay.Text;
fim;

Este manipulador de eventos é então atribuído no método FormCreate :

procedimento TDateForm.FormCreate(Sender: TObject);


começar
ODia := TDate.Init(7, 4, 1995);
LabelDate.Text := TheDay.Text;
// Atribuir o manipulador de eventos para alterações futuras
TheDay.OnChange := DateChange;
fim;

Bem, isso parece muito trabalhoso. Eu estava mentindo quando disse que o manipulador de eventos
nos pouparia alguma codificação? Não. Agora, depois de adicionarmos algum código, podemos esquecer
completamente de atualizar o rótulo quando alterarmos alguns dados do objeto. Aqui como
por exemplo, é o manipulador do evento OnClick de um dos botões:

procedimento TDateForm.BtnIncreaseClick(Sender: TObject);


começar
ODia.Increase;
fim;

O mesmo código simplificado está presente em muitos outros manipuladores de eventos. Depois
de instalar o manipulador de eventos, não precisamos nos lembrar de atualizar o rótulo continuamente.
Isso elimina um número significativo de erros potenciais no programa. Observe também que tivemos que
escrever algum código no início porque este não é um componente instalado no
o ambiente de desenvolvimento, mas simplesmente uma classe. Com um componente, basta selecionar
o manipulador de eventos no Object Inspector e escrever uma única linha de código para atualizar o
rótulo. Isso é tudo.

Isso nos leva à questão: quão difícil é escrever um novo componente em Delphi?
Na verdade, é tão simples que mostrarei como fazer isso na próxima seção.

note Este livro não se aprofunda nos detalhes da escrita de componentes personalizados, mas pretende ser apenas uma
breve introdução ao papel das propriedades e eventos e à escrita de componentes, pois um entendimento básico
desses recursos é importante para todo desenvolvedor Delphi.

Criando um componente TDate


Agora que entendemos propriedades e eventos, o próximo passo é ver o que é um componente.
Exploraremos brevemente este tópico transformando nossa classe TDate em um componente.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

10: propriedades e eventos - 299

Primeiro temos que herdar nossa classe da classe TComponent em vez da classe TOb-ject padrão . Aqui está o
código:

tipo
TDate = classe(TComponent)
...
público
construtor Criar (AOwner: TComponent); sobrecarga; sobrepor;
construtor Criar (Y, M, D: Inteiro); reintroduzir; sobrecarga;

Como você pode ver, a segunda etapa foi adicionar um novo construtor à classe, substituindo o construtor padrão
dos componentes para fornecer um valor inicial adequado. Por haver uma versão sobrecarregada, também
precisamos usar a diretiva reintroduce para ela, para evitar uma mensagem de aviso do compilador. O código do
novo construtor simplesmente define a data para a data de hoje, após chamar o construtor da classe base:

construtor TDate.Create(AOwner: TComponent);


era
Y, D, M: Palavra;
começar
herdado Criar (AOwner);
FDate := Data; // Hoje

Feito isso, precisamos adicionar à unidade que define nossa classe (a unidade Dates do exemplo DateComp ) um
procedimento Register . (Certifique-se de que esse identificador comece com R maiúsculo, caso contrário ele não
será reconhecido.) Isso é necessário para adicionar o componente ao IDE.

Simplesmente declare o procedimento, que não requer parâmetros, na parte de interface da unidade, e então escreva
este código na seção de implementação :

Cadastro de procedimento;
começar
RegistrarComponentes(end; 'Amostra' , [TDate]);

Este código adiciona o novo componente à página Sample da paleta Tools, criando a página, se necessário.

A última etapa é instalar o componente. Para isso precisamos criar um pacote, que é um tipo especial de componentes
de hospedagem de projetos de aplicativos. Tudo o que tem a fazer é:

· Selecione o Arquivo | Novo | Outro menu do IDE, abrindo a caixa de diálogo Novos Itens

· Selecione “Pacote”

· Salve o pacote com um nome (possivelmente na mesma pasta da unidade com o código real do componente)

· No projeto de pacote recém-criado, no painel Gerenciador de Projetos, clique com o botão direito no
Contém o nó para adicionar uma nova unidade ao projeto e selecionar a unidade com o TDate
classe de componente

Marco Cantù, Manual do Object Pascal 11

Você também pode gostar