Guía de Free Pascal 3.2.2
Guía de Free Pascal 3.2.2
Pascal libre
Guia de referencia
Contenido
1 fichas de Pascal 12
2 constantes 21
3 tipos 25
1
Machine Translated by Google
CONTENIDO
4 variables 66
4.1 Definición . . . . . . . . . . . ..... . . . . . . . . . . . . . . ..... . . . . . 66
4.2 Declaración . . . . . . . . . . . . ... . . . . . . . . . . . . . . ..... . . . . . 66
2
Machine Translated by Google
CONTENIDO
5 objetos 75
5.1 Declaración . . . . . . . . . . . . ... . . . . . . . . . . . . . . ..... . . . . . 75
6 clases 86
6.1 Definiciones de clases . . . . . . . . . . . . . . . . . . . . . . . . . . . ..... . . . . . 86
6.2 Clases abstractas y selladas . . ..... . . . . . . . . . . . . . . ..... . . . . . 90
6.3 Campos normales y estáticos . . . . . . ... . . . . . . . . . . . . . . ..... . . . . . 90
6.3.1 Campos/variables normales . . . . . . . . . . . . . . . . . . . ..... . . . . . 90
6.3.2 Campos/variables de clase . . . ... . . . . . . . . . . . . . . ..... . . . . . 91
6.4 Creación de instancias de clase . . . . . . . . . ... . . . . . . . . . . . . . . ..... . . . . . 93
6.5 Destrucción de clase . . . . . . . . . ... . . . . . . . . . . . . . . ..... . . . . . 93
6.6 Métodos . . . . . . . . . . . . . . ... . . . . . . . . . . . . . . ..... . . . . . 94
6.6.1 Declaración . . . . . . ..... . . . . . . . . . . . . . . ..... . . . . . 94
6.6.2 Invocación . . . . . . . . . ... . . . . . . . . . . . . . . ..... . . . . . 95
6.6.3 Métodos virtuales . . . . . . . . . . . . . . . . . . . . . . . ..... . . . . . 95
6.6.4 Métodos de clase . . . . . . ... . . . . . . . . . . . . . . ..... . . . . . 96
6.6.5 Class constructores and destructores . . . . . . . . . . . . . . . . . . . . . . . 97
6.6.6 Métodos de clase estática . . . ... . . . . . . . . . . . . . . ..... . . . . . 98
3
Machine Translated by Google
CONTENIDO
7 interfaces 114
7.1 Definición . . . . . . . . . . . ..... . . . . . . . . . . . . . . ..... . . . . . 114
7.2 Identificación de interfaz: Un GUID . . . . . . . . . . . . . . . . . . ..... . . . . . 115
8 genéricos 123
8.1 Introducción . . . . . . . . . . . . ... . . . . . . . . . . . . . . ..... . . . . . 123
4
Machine Translated by Google
CONTENIDO
10.5 Una nota sobre el alcance y la vida útil de los ayudantes de registro y tipo . . . . . ..... . . . . . 153
10.6 Herencia . . . . . . . . . . . . ... . . . . . . . . . . . . . . ..... . . . . . 155
12 Expresiones 169
13 declaraciones 186
5
Machine Translated by Google
CONTENIDO
6
Machine Translated by Google
CONTENIDO
7
Machine Translated by Google
CONTENIDO
17 excepciones 257
17.1 La declaración de aumento . . . . . . ..... . . . . . . . . . . . . . . ..... . . . . . 257
8
Machine Translated by Google
Lista de tablas
9
Machine Translated by Google
LISTA DE TABLAS
Este documento sirve como referencia para el lenguaje Pascal implementado por el compilador Free
Pascal. Describe todas las construcciones de Pascal admitidas por Free Pascal y enumera todos los tipos
de datos admitidos. Sin embargo, no da una explicación detallada del lenguaje Pascal: no es un tutorial.
El objetivo es enumerar qué construcciones de Pascal son compatibles y mostrar en qué se diferencia la
implementación de Free Pascal de las implementaciones de Turbo Pascal o Delphi.
Los compiladores Turbo Pascal y Delphi Pascal introdujeron varias características en el lenguaje Pascal.
El compilador Free Pascal emula estos compiladores en el modo apropiado del compilador: ciertas
características están disponibles solo si el compilador se cambia al modo apropiado. Cuando sea necesario
para una determinada característica, el uso del modificador de línea de comandos M o la directiva {$MODE}
se indicará en el texto. Puede encontrar más información sobre los distintos modos en el manual del usuario
y en el manual del programador.
Las versiones anteriores de este documento también contenían la documentación de referencia de la unidad del
sistema y la unidad objpas. Esto se ha trasladado a la guía de referencia de RTL.
Notaciones
A lo largo de este documento nos referiremos a funciones, tipos y variables con fuente de máquina de escribir.
Los archivos se denominan con una fuente sans: nombre de archivo.
Diagramas de sintaxis
Todos los elementos del lenguaje Pascal se explican en diagramas de sintaxis. Los diagramas de sintaxis
son como diagramas de flujo. Leer un diagrama de sintaxis significa ir del lado izquierdo al derecho, siguiendo
las flechas. Cuando se llega al lado derecho de un diagrama de sintaxis y termina con una sola flecha, esto
significa que el diagrama de sintaxis continúa en la siguiente línea. Si la línea termina en dos flechas que
apuntan entre sí, entonces el diagrama finaliza.
Los elementos sintácticos se escriben así.
Primera posibilidad
Segunda posibilidad
Esto significa que tanto la primera como la segunda posibilidad son opcionales. Por supuesto, todos estos elementos se
pueden combinar y anidar.
10
Machine Translated by Google
LISTA DE TABLAS
• Sintaxis sencilla, bastante detallada, pero fácil de leer. Ideal para la enseñanza.
• Fuertemente tipificado.
• Procesal.
Los compiladores Turbo Pascal y Delphi Pascal introdujeron varias características en el lenguaje Pascal, en particular un manejo de
cadenas más sencillo y orientación a objetos. El compilador Free Pascal inicialmente emuló la mayor parte de Turbo Pascal y más tarde
Delphi. Emula estos compiladores en el modo apropiado del compilador: ciertas características están disponibles solo si el compilador se
cambia al modo apropiado. Cuando sea necesario para una determinada característica, el uso del modificador de línea de comandos M o
la directiva {$MODE} se indicará en el texto. Puede encontrar más información sobre los distintos modos en el manual del usuario y en el
manual del programador.
11
Machine Translated by Google
Capítulo 1
Fichas de Pascal
Los tokens son los componentes léxicos básicos del código fuente: son las "palabras" del lenguaje:
los caracteres se combinan en tokens de acuerdo con las reglas del lenguaje de programación. Hay
cinco clases de tokens:
palabras reservadas Son palabras que tienen un significado fijo en el idioma. Ellos no pueden ser
cambiado o redefinido.
Identificadores Estos son nombres de símbolos que el programador define. Se pueden cambiar y
reutilizar. Están sujetos a las reglas de alcance del idioma.
operadores Generalmente son símbolos para operaciones matemáticas o de otro tipo: +, , *, etc.
constantes Las constantes numéricas o de caracteres se utilizan para indicar valores reales en el código fuente, como
1 (constante entera) o 2,3 (constante flotante) o “constante de cadena” (una cadena: un fragmento de texto).
En este capítulo describimos todas las palabras reservadas de Pascal, así como las diversas formas de denotar
cadenas, números, identificadores, etc.
1.1 Símbolos
Free Pascal permite todos los caracteres, dígitos y algunos símbolos de caracteres especiales en un archivo fuente de Pascal.
Símbolos reconocidos
carta ARIZONA
Arizona
dígitos 0...9
12
Machine Translated by Google
' . :
+*/=<>[ ] , ( ) ^@{ ps
1.2 Comentarios
Los comentarios son partes del código fuente que el compilador descarta por completo. Ellos existen
sólo para beneficio del programador, para que pueda explicar ciertos fragmentos de código. Para el compilador,
Es como si los comentarios no estuvieran presentes.
El uso de (* y *) como delimitadores de comentarios data de los primeros días del lenguaje Pascal. Él
se ha reemplazado principalmente por el uso de { y } como delimitadores de comentarios, como en el siguiente ejemplo:
{
Mi hermosa función devuelve un resultado interesante,
pero sólo si el argumento A es menor que B.
}
Función Hermosa (A,B: Entero): Entero;
También se pueden hacer comentarios de una sola línea con el delimitador //:
El comentario se extiende desde el carácter // hasta el final de la línea. Este tipo de comentario fue
introducido por Borland en el compilador Delphi Pascal.
Free Pascal admite el uso de comentarios anidados. Las siguientes construcciones son comentarios válidos:
13
Machine Translated by Google
{ Comentario 1 (* comentario 2 *) }
(* Comentario 1 { comentario 2 } *) { comentario
1 // Comentario 2 } (* comentario 1 //
Comentario 2 *) // comentario 1 (* comentario 2
*) // comentario 1 { comentario 2 }
Los dos últimos comentarios deben estar en una línea. Los dos siguientes darán errores:
El compilador reaccionará con un error de "carácter no válido" cuando encuentre tales construcciones, independientemente
del modificador Mtp.
Observación En modo TP y Delphi, no se permiten comentarios anidados, para máxima compatibilidad con el
código existente para esos compiladores.
Las palabras reservadas son parte del lenguaje Pascal y, como tales, el programador no puede redefinirlas.
A lo largo de los diagramas de sintaxis se indicarán utilizando un tipo de letra en negrita . Pascal no distingue entre mayúsculas
y minúsculas, por lo que el compilador aceptará cualquier combinación de letras mayúsculas o minúsculas para palabras
reservadas.
Hacemos una distinción entre palabras reservadas de Turbo Pascal y Delphi. En modo TP, sólo se reconocen
las palabras reservadas de Turbo Pascal, pero las de Delphi se pueden redefinir. Por defecto, Free Pascal
reconoce las palabras reservadas de Delphi.
14
Machine Translated by Google
exportaciones es empaquetada
1.3.3 Modificadores
La siguiente es una lista de todos los modificadores. No son exactamente palabras reservadas en el sentido de que
puedan usarse como identificadores, pero en lugares específicos tienen un significado especial para el compilador,
es decir, el compilador las considera parte del lenguaje Pascal.
Observación Los tipos predefinidos como Byte, Boolean y constantes como maxint no son palabras reservadas.
Son identificadores, declarados en la unidad del sistema. Esto significa que estos tipos se pueden redefinir en otras
unidades. Sin embargo, no se anima al programador a hacer esto, ya que causará mucha confusión.
Observación A partir de la versión 2.5.1, es posible utilizar palabras reservadas como identificadores escapándolas con un signo &.
Esto significa que es posible lo siguiente
era
&var: entero;
comenzar
&var:=1;
Escribir(&var);
fin.
sin embargo, no se recomienda utilizar esta función en código nuevo, ya que hace que el código sea menos legible. Su
objetivo principal es corregir el código antiguo cuando la lista de palabras reservadas cambia y abarca una palabra que
aún no estaba reservada (consulte también la sección 1.4, página 16).
15
Machine Translated by Google
1.4 Identificadores
Los identificadores denotan nombres definidos por el programador para constantes, tipos, variables, procedimientos y
funciones, unidades y programas específicos. Todos los nombres definidos por el programador en el código fuente
(excluidas las palabras reservadas) se designan como identificadores.
Los identificadores constan de entre 1 y 127 caracteres significativos (letras, dígitos y el carácter de subrayado), de los
cuales el primero debe ser una letra (a–z o A–Z) o un guión bajo (_). El siguiente diagrama proporciona la sintaxis básica
de los identificadores.
Identificadores
la letra
Al igual que las palabras reservadas de Pascal, los identificadores no distinguen entre mayúsculas y minúsculas, es decir, ambos
mi procedimiento;
Mi Procedimiento;
Observación A partir de la versión 2.5.1 es posible especificar una palabra reservada como identificador anteponiéndola con un
signo comercial (&). Esto significa que es posible lo siguiente:
procedimiento &do;
empezar
fin;
comenzar
&hacer; fin.
La palabra reservada do se utiliza como identificador para la declaración así como para la invocación del procedimiento
do.
La mayoría de los identificadores (constantes, variables, funciones o métodos, propiedades) pueden tener una directiva de
sugerencia adjunta a su definición:
directivas de sugerencias
dieciséis
Machine Translated by Google
directiva de sugerencia
Obsoleto
constante de cadena
Experimental
Plataforma
No implementado
Siempre que el compilador encuentre un identificador marcado con una directiva de sugerencia, se mostrará una
advertencia correspondiente a la sugerencia especificada.
obsoleto El uso de este identificador está obsoleto; utilice una alternativa en su lugar. La palabra clave obsoleta puede
ir seguida de una cadena constante con un mensaje. El compilador mostrará este mensaje cada vez que
encuentre el identificador.
experimental El uso de este identificador es experimental: se puede utilizar para marcar nuevas características que
debe usarse con precaución.
plataforma Este es un identificador que depende de la plataforma: puede que no esté definido en todas las plataformas.
no implementado Esto debe usarse solo en funciones y procedimientos. Debe usarse para señalar
que una característica particular aún no se ha implementado.
constante
AConst = 12 en desuso;
era
p: plataforma entera;
comenzar
Algo:=P+AConst; fin;
comenzar
algo; fin.
Las directivas de sugerencias pueden seguir todo tipo de identificadores: unidades, constantes, tipos, variables, funciones,
procedimientos y métodos.
1.6 Números
Los números se indican por defecto en notación decimal. Los números reales (o decimales) se escriben utilizando
notación científica o de ingeniería (por ejemplo, 0,314E1).
17
Machine Translated by Google
2. Formato hexadecimal (base 16), del mismo modo que lo hace Turbo Pascal. Para especificar un valor constante en
formato hexadecimal, antepóngale un signo de dólar ($). Por lo tanto, el $FF hexadecimal equivale a 255 decimales.
Tenga en cuenta que las mayúsculas y minúsculas son insignificantes cuando se utilizan constantes hexadecimales.
3. A partir de la versión 1.0.7, también se admite el formato octal (base 8). Para especificar una constante
en formato octal, antepóngale un signo comercial (&). Por ejemplo, 15 se especifica en notación octal
como &17.
4. Notación binaria (base 2). Un número binario se puede especificar precediéndolo con un signo de porcentaje.
(%). Por tanto, 255 se puede especificar en notación binaria como %11111111.
Números
signo + _
18
Machine Translated by Google
1.7 Etiquetas
Una etiqueta es el nombre de una ubicación en el código fuente a la que se puede saltar desde otra ubicación con
una instrucción goto. Una etiqueta es un identificador estándar o una secuencia de dígitos.
Etiqueta
etiqueta identificador de
secuencia de dígitos
Observación Los modificadores Sg o Mtp deben especificarse antes de poder utilizar etiquetas. De forma predeterminada, Free
Pascal no admite declaraciones de etiqueta ni de ir a. La directiva {$GOTO ON} también se puede utilizar para
permitir el uso de etiquetas y la instrucción goto.
Etiqueta
123,
abc;
Una cadena de caracteres (o cadena para abreviar) es una secuencia de cero o más caracteres (tamaño byte), entre
comillas simples y en una sola línea del código fuente del programa: no pueden aparecer caracteres de retorno de
carro literal ni salto de línea en la cadena. .
Un conjunto de caracteres sin nada entre comillas ('') es una cadena vacía.
Cadenas de caracteres
'
cadena entre comillas ' carácter de 6 cadenas
'
carácter de cadena Cualquier personaje excepto o CR
"
La cadena consta de caracteres ASCII estándar de 8 bits o caracteres Unicode (normalmente codificados en
UTF8). La cadena de control se puede utilizar para especificar caracteres que no se pueden escribir en un
teclado, como el número 27 para el carácter de escape.
El carácter de comilla simple se puede incrustar en la cadena escribiéndolo dos veces. La construcción C de
caracteres de escape en la cadena (usando una barra invertida) no es compatible con Pascal.
19
Machine Translated by Google
20
Machine Translated by Google
Capitulo 2
Constantes
Al igual que Turbo Pascal, Free Pascal admite constantes tanto ordinarias como escritas. Se declaran en un
bloque de declaración constante en una declaración de unidad, programa o clase, función o procedimiento
(sección 16.5, página 252).
Declaración constante
El compilador debe poder evaluar la expresión en una declaración constante en el momento de la compilación. Esto
significa que la mayoría de las funciones de la biblioteca RunTime no se pueden utilizar en una declaración constante.
Sin embargo, se pueden utilizar operadores como +, , *, /, not, and, or, div, mod, ord, chr, sizeof, pi,
int, trunc, round, frac, odd. Para obtener más información sobre expresiones, consulte el capítulo
12, página 169.
Cuando se utiliza en el código una constante ordinaria previamente declarada, el compilador insertará el valor
real de la constante en lugar del nombre de la constante. Es decir, los dos fragmentos de código siguientes son
totalmente equivalentes:
constante
Uno = 1;
comenzar
Escribir(Uno);
fin.
21
Machine Translated by Google
CAPÍTULO 2. CONSTANTES
comenzar Writeln(1);
fin.
• Tipos ordinales
• Tipos de conjuntos
• Tipos reales
• carbón,
• Cadena
constante
No se permite asignar un valor a una constante ordinaria. Por lo tanto, dada la declaración anterior, lo
siguiente resultará en un error de compilación:
Para las constantes de cadena, el tipo de cadena depende de algunos modificadores del compilador. Si se desea un
tipo específico, se debe utilizar una constante escrita, como se explica en la siguiente sección.
Antes de la versión 1.9, Free Pascal no admitía correctamente constantes de 64 bits. A partir de la versión 1.9, se pueden
especificar constantes de 64 bits.
A veces es necesario especificar el tipo de constante, por ejemplo para constantes de estructuras complejas
(definidas más adelante en el manual). Su definición es bastante simple.
declaración constante escrita 6 identi er: tipo = directiva de sugerencia constante escrita;
22
Machine Translated by Google
CAPÍTULO 2. CONSTANTES
A diferencia de las constantes ordinarias, se les puede asignar un valor en tiempo de ejecución. Este es un concepto
antiguo de Turbo Pascal, que ha sido reemplazado con soporte para variables inicializadas: para obtener una descripción
detallada, consulte la sección 4.4, página 68.
La compatibilidad con la asignación de valores a constantes escritas está controlada por la directiva {$J}: se puede desactivar,
pero está activada de forma predeterminada (para compatibilidad con Turbo Pascal). Siempre se permiten variables inicializadas.
Observación Cabe destacar que las constantes escritas se inicializan automáticamente al inicio del programa. Esto también es
válido para las constantes escritas localmente y las variables inicializadas. Las constantes escritas localmente también
se inicializan al inicio del programa. Si su valor se cambió durante invocaciones anteriores de la función, conservarán
su valor modificado, es decir, no se inicializarán cada vez que se invoque la función.
Un tipo especial de bloque de declaración constante es el bloque Resourcestring. Las declaraciones de cadenas de
recursos son muy parecidas a las declaraciones de cadenas constantes: las cadenas de recursos actúan como cadenas
constantes, pero pueden localizarse mediante un conjunto de rutinas especiales en la unidad objpas. Un bloque de
declaración de cadena de recursos sólo se permite en los modos Delphi u Objfpc.
Cadena de recursos
Todas las constantes de cadena definidas en la sección de cadena de recursos se almacenan en tablas especiales. Las cadenas
de estas tablas se pueden manipular en tiempo de ejecución con algunos mecanismos especiales en la unidad objpas.
Semánticamente, las cadenas actúan como constantes ordinarias; No está permitido asignarles valores (excepto a
través de mecanismos especiales en la unidad objpas). Sin embargo, se pueden utilizar en asignaciones o expresiones
como constantes de cadena ordinarias. El uso principal de la sección de cadenas de recursos es proporcionar un
medio sencillo de internacionalización.
Puede encontrar más información sobre el tema de las cadenas de recursos en la Guía del programador y en la referencia
de la unidad objpas.
Observación Tenga en cuenta que una cadena de recurso que se proporciona como expresión no cambiará si se cambian las
partes de la expresión:
cadena de recursos
Parte1 = 'Primera parte de una cadena larga.'; Part2 = 'Segunda
parte de una cadena larga.'; Oración = Parte1+' '+Parte2;
Si las rutinas de localización traducen la Parte 1 y la Parte 2, la constante Oración no se traducirá automáticamente:
tiene una entrada separada en las tablas de cadenas de recursos y, por lo tanto, debe traducirse por separado. La
construcción anterior simplemente dice que el valor inicial de Sentencia es igual a Parte1+' '+Parte2.
23
Machine Translated by Google
CAPÍTULO 2. CONSTANTES
Observación Del mismo modo, cuando se utilizan cadenas de recursos en una matriz constante, solo se usarán los valores
iniciales de las cadenas de recursos en la matriz: cuando se traducen las constantes individuales, los elementos
de la matriz conservarán su valor original.
cadena de recursos Sí =
'Sí.'; No = 'No';
Era
SíNo: Matriz[Booleano] de cadena = (No,Sí);
B: booleano;
comenzar
Writeln(SíNo[B]); fin.
Esto imprimirá "Sí". o no." dependiendo del valor de B, incluso si las constantes Sí y No han sido localizadas mediante
algún mecanismo de localización.
24
Machine Translated by Google
Capítulo 3
Tipos
Todas las variables tienen un tipo. Free Pascal admite los mismos tipos básicos que Turbo Pascal, con
algunos tipos adicionales de Delphi y algunos propios.
El programador puede declarar sus propios tipos, lo que en esencia consiste en definir un identificador que puede
usarse para indicar este tipo personalizado al declarar variables más adelante en el código fuente. La declaración
de un tipo ocurre en un bloque Tipo (sección 16.5, página 252), que es una colección de declaraciones de tipo,
separadas por punto y coma:
Declaración de tipo
Tipos
Los tipos básicos o simples de Free Pascal son los tipos Delphi. Discutiremos cada tipo por separado.
25
Machine Translated by Google
CAPÍTULO 3. TIPOS
tipos simples
Con la excepción de los tipos de valores de coma flotante, todos los tipos base son tipos ordinales. Los tipos ordinales
tienen las siguientes características:
1. Los tipos ordinales son contables y ordenados, es decir, en principio, es posible empezar a contarlos uno por uno,
en un orden específico. Esta propiedad permite definir el funcionamiento de funciones como Inc, Ord, Dec en
tipos ordinales.
2. Los valores ordinales tienen el valor más pequeño posible. Intentar aplicar la función Pred en el valor más pequeño
posible generará un error de verificación de rango si la verificación de rango está habilitada.
3. Los valores ordinales tienen el mayor valor posible. Intentando aplicar la función Succ en el más grande
El valor posible generará un error de verificación de rango si la verificación de rango está habilitada.
Observación Int64 y QWord se consideran tipos ordinales en CPU de 64 bits. En los tipos de 32 bits tienen algunas de las
características de los ordinales, pero no se pueden utilizar, por ejemplo, en bucles for.
Enteros
Nombre
Entero
punto corto
PequeñoInt
punto largo
palabra larga
Int64
Byte
Palabra
Cardenal
Palabra Q
ByteBool
PalabraBool
bool largo
QPalabraBool
Los tipos de enteros, y sus rangos y tamaños, que están predefinidos en Free Pascal se enumeran en la tabla (3.2).
Tenga en cuenta que los tipos qword e int64 no son ordinales verdaderos, por lo que algunas construcciones de Pascal
no funcionarán con estos dos tipos de enteros.
26
Machine Translated by Google
CAPÍTULO 3. TIPOS
Palabra Q 18446744073709551615 8
El tipo entero es un alias del tipo Smallint en el modo Free Pascal predeterminado. Es un alias para
el tipo entero largo en modo Delphi u ObjFPC. El tipo cardinal actualmente siempre está mapeado
al tipo de palabra larga.
Observación El compilador decide el tipo de una constante entera basándose en el valor: Una constante entera obtiene
el tipo con signo más pequeño posible. Se utiliza la primera coincidencia de la tabla (3.3).
Tipo de rango
128..127 Punto corto
128..255 bytes
32768..32767 Pequeño
32768..65535 Palabra
Eso significa que las constantes en el rango 128..127 se asignan a shortint, las constantes en el rango 128..255 se asignan
asignadas a bytes, etc. Las constantes en el rango 2147483647..high(cardinal) se analizan como cardinales, y
todas las constantes decimales que no se ajustan a ninguno de los rangos anteriores se analizan como constantes enteras de 64 bits.
Observación En las versiones más nuevas de Delphi, el tipo de entero largo depende de la plataforma y de la CPU. Esto no es así en FPC,
donde longint es de 32 bits en todas las plataformas.
Como compilador de Pascal, Free Pascal realiza conversión y actualización automática de tipos en expresiones.
donde se utilizan diferentes tipos de tipos de números enteros:
1. Cada plataforma tiene un tamaño entero “nativo”, dependiendo de si la plataforma es de 8 bits, 16 bits o
32 bits o 64 bits. [Link]. en AVR esto es de 8 bits.
2. Cada número entero menor que el tamaño "nativo" se promueve a una versión con signo del "nativo"
tamaño. Los números enteros iguales al tamaño "nativo" mantienen su signo.
3. El resultado de los operadores aritméticos binarios (+, , *, etc.) se determina de la siguiente manera:
(a) Si al menos uno de los operandos es mayor que el tamaño entero nativo, se elige el resultado
ser el tipo más pequeño que abarca los rangos de los tipos de ambos operandos. Este
significa que mezclar un sin firmar con uno más pequeño o de igual tamaño firmado producirá un
tipo firmado que es más grande que ambos.
27
Machine Translated by Google
CAPÍTULO 3. TIPOS
(b) Si ambos operandos tienen el mismo signo, el resultado es del mismo tipo que ellos. El único
la excepción es restar (): en el caso de sin firmar, la resta sin signo produce un
resultado firmado en FPC (como en Delphi, pero no en TP7).
(c) La combinación de operandos con y sin signo del tamaño int “nativo” produce un operador con signo más grande
resultado. Esto significa que mezclar longint y longword en plataformas de 32 bits producirá
un int64. De manera similar, mezclar byte y shortint en plataformas de 8 bits (AVR) producirá un
pequeño.
tipos booleanos
Free Pascal admite el tipo booleano, con sus dos valores posibles predefinidos, Verdadero y Falso.
Estos son los dos únicos valores que se pueden asignar a un tipo booleano. Por supuesto, cualquier expresión
que se resuelve en un valor booleano, también se puede asignar a un tipo booleano.
Booleano 11
Booleano16 2 1
Booleano32 4 1
Booleano64 8 1
ByteBool 1 Cualquier valor distinto de cero
WordBool 2 Cualquier valor distinto de cero
Además del tipo booleano simple, los adicionales Boolean16, Boolean32 y Boolean64
existen tipos. De hecho, estos son tipos de números enteros, cuya asignación es compatible con el método booleano simple.
tipo. Como número entero, los valores de Verdadero y Falso son 1 y 0. Esto se puede utilizar para interactuar con
Código C que define un booleano de este tamaño con valores 0 y 1.
Para facilitar aún más la interfaz con C, Free Pascal también soporta ByteBool, WordBool,
Tipos LongBool y QWordBool. Estos son de tipo Byte, Word, Longint o Int64, pero son
nuevamente una asignación compatible con un booleano. La única diferencia con el booleano 16/32/64
tipos es en qué valores se consideran verdaderos o falsos: El valor Falso equivale a 0 (cero) y
cualquier valor distinto de cero se considera verdadero al convertirlo a un valor booleano. Un valor booleano de
True se convierte a Not(0) en caso de que se asigne a una variable de tipo ByteBool, WordBool,
LongBool o QWordBool.
Suponiendo que B sea de tipo booleano, las siguientes son asignaciones válidas:
B := Verdadero;
B := Falso;
B := 1<>2; {Resultados en B:= Verdadero}
Observación En Free Pascal, las expresiones booleanas por defecto siempre se evalúan de tal manera que cuando
Se conoce el resultado, el resto de la expresión ya no se evaluará: esto se llama atajo booleano
evaluación.
En el siguiente ejemplo, nunca se llamará a la función Func, lo que puede tener efectos secundarios extraños.
28
Machine Translated by Google
CAPÍTULO 3. TIPOS
...
B := Falso;
A := B y Func;
Tipos de enumeración
Los tipos de enumeración son compatibles con Free Pascal. Además de la implementación de Turbo Pascal,
Free Pascal también permite una extensión estilo C del tipo de enumeración, donde se asigna un valor a un
elemento particular de la lista de enumeración.
Tipos enumerados
(consulte el capítulo 12, página 169 para saber cómo utilizar expresiones) Cuando se utilizan tipos enumerados
asignados, los elementos asignados deben estar en orden numérico ascendente en la lista, o el compilador se quejará.
Las expresiones utilizadas en los elementos enumerados asignados deben conocerse en el momento de la compilación.
Entonces la siguiente es una declaración de tipo enumerada correcta:
Tipo
Dirección = (Norte, Este, Sur, Oeste);
Tipo
EnumType = (uno, dos, tres, cuarenta := 40,cuarenta y uno);
o puedes usar
Tipo
EnumType = (uno, dos, tres, cuarenta = 40,cuarenta y uno);
Como resultado, el número ordinal de cuarenta es 40, y no 3, como sería si ':= 40' no estuviera presente. El valor ordinal de
cuarenta y uno es entonces 41, y no 4, como sería si la asignación no estuviera presente. Después de una asignación en
una definición enumerada, el compilador agrega 1 al valor asignado para asignarlo al siguiente valor enumerado.
Al especificar dicho tipo de enumeración, es importante tener en cuenta que los elementos enumerados deben mantenerse
en orden ascendente. Lo siguiente producirá un error de compilación:
29
Machine Translated by Google
CAPÍTULO 3. TIPOS
Tipo
EnumType = (uno, dos, tres, cuarenta := 40, treinta := 30);
Es necesario mantener cuarenta y treinta en el orden correcto. Al utilizar tipos de enumeración, es importante tener en
cuenta los siguientes puntos:
1. Las funciones Pred y Succ no se pueden utilizar en este tipo de tipos de enumeración. Intentar hacer esto
de todos modos resultará en un error del compilador.
2. Los tipos de enumeración se almacenan de forma predeterminada, independientemente del número real
de valores: el compilador no intenta optimizar el espacio. Este comportamiento se puede cambiar con la
directiva del compilador {$PACKENUM n}, que le indica al compilador el número mínimo de bytes que
se utilizarán para los tipos de enumeración. Por ejemplo
Tipo
{$PAQUETE 4}
LargeEnum = (BigOne, BigTwo, BigThree);
{$PAQUETE 1}
SmallEnum = (uno, dos, tres); Var S: SmallEnum; L:
Enumeración grande; comenzar
WriteLn ('Enumeración
pequeña:
',SizeOf(S)); WriteLn ('Enumeración grande: ',SizeOf(L)); fin.
Enumeración pequeña: 1
Enumeración grande: 4
Puede encontrar más información en la Guía del programador, en la sección de directivas del compilador.
Tipos de subrango
Un tipo de subrango es un rango de valores de un tipo ordinal (el tipo de host). Para definir un tipo de subrango, se deben
especificar sus valores límite: el valor más alto y más bajo del tipo.
Tipos de subrango
Escriba
Entero largo = $80000000..$7fffffff; Entero =
32768..32767; punto corto = 128..127; byte
= 0..255; Palabra = 0..65535;
30
Machine Translated by Google
CAPÍTULO 3. TIPOS
Tipo
Días = (lunes, martes, miércoles, jueves, viernes,
sabado domingo);
Días Laborales = lunes .. viernes;
Fin de Semana = Sábado .. Domingo;
Tipos de personajes
Un tipo de carácter también es un tipo ordinal: los caracteres están ordenados y se pueden contar. Hay 2 personajes
tipos:
AnsiChar Este es un carácter de 1 byte. La interpretación del carácter depende de la página de códigos.
WideChar Este es un carácter de 2 bytes. La interpretación del carácter depende de la página de códigos.
Los caracteres se pueden usar en un bucle, se pueden usar prev y succ, así como ord.
Para obtener más información sobre los personajes, consulte la sección 3.2, página 32.
Tipo Rango Depende de la plataforma real Sencillo Dígitos significantes ??? Tamaño
El tipo Comp es, de hecho, un entero de 64 bits y no está disponible en todas las plataformas de destino. Para conseguir más
Para obtener información sobre los tipos admitidos para cada plataforma, consulte la Guía del programador.
El tipo de moneda es un tipo de datos real de punto fijo que se utiliza internamente como un tipo entero de 64 bits.
(escalado automáticamente con un factor 10000), esto minimiza los errores de redondeo. Este tipo debe usarse
con cuidado: cuando se usa en expresiones que usan, por ejemplo, multiplicación, la evaluación de la expresión puede
sale mal (pierde precisión) si los resultados intermedios quedan fuera del rango de divisas.
Tenga en cuenta que no todos los tipos de flotadores están disponibles en todas las plataformas. El tipo de flotador único es el único
Se garantiza que estará disponible en todas las plataformas que tengan soporte de punto flotante (por ejemplo, AVR no
tenerlo). El tipo doble está disponible en todas las plataformas con coprocesador, y el tipo extendido
está disponible en todos los procesadores Intel x86, excepto en la plataforma Windows de 64 bits. Más detalles sobre
La disponibilidad se puede encontrar en la Guía del programador.
31
Machine Translated by Google
CAPÍTULO 3. TIPOS
Free Pascal admite el tipo Char. Un Char tiene exactamente un tamaño de 1 byte y contiene un carácter ASCII.
Una constante de caracteres se puede especificar encerrándola entre comillas simples, de la siguiente manera: 'a' o
'A' son ambas constantes de caracteres.
Un carácter también se puede especificar por su valor de carácter (comúnmente un código ASCII), precediendo el
valor ordinal con el símbolo numérico (#). Por ejemplo, especificar #65 sería lo mismo que 'A'.
Además, el carácter de intercalación (^) se puede usar en combinación con una letra para especificar un carácter con un
valor ASCII menor que 27. Por lo tanto, ^G es igual a #7 − G es la séptima letra del alfabeto. El compilador es bastante
descuidado con respecto a los caracteres que permite después del signo de intercalación, pero en general se deben asumir
sólo letras.
Cuando se debe representar el carácter de comilla simple, se debe escribir dos veces seguidas, de esta manera
''''
representa el carácter de comilla simple.
Para distinguir Char de WideChar, la unidad del sistema también define el tipo AnsiChar, que es el mismo que el tipo
char. En versiones futuras de FPC, el tipo Char puede convertirse en un alias para WideChar o AnsiChar.
Free Pascal admite el tipo WideChar. Un WideChar tiene exactamente 2 bytes de tamaño y contiene un carácter
UNICODE en codificación UTF16.
Un carácter Unicode se puede especificar por su valor de carácter (un código UTF16), precediendo el valor ordinal
con el símbolo numérico (#).
También se puede utilizar un literal de carácter ansi (1 byte) normal para un carácter ancho; el compilador lo
convertirá automáticamente a un carácter UTF16 de 2 bytes.
constante
C3: carácter ancho = #$03A8;
C4: carácter ancho = #$03A9;
constante
C3: carácter ancho = carácter ancho ($03A8);
C4: carácter ancho = carácter ancho ($03A9);
define algunos otros tipos de caracteres en la unidad del sistema, como UCS2Char, UCS4Char, UniCodeChar. Sin
embargo, no existe ningún soporte especial para estos tipos de caracteres; se han definido únicamente para
compatibilidad con Delphi.
admite el tipo Cadena tal como se define en Turbo Pascal: una secuencia de caracteres de un solo byte con una
especificación de tamaño opcional. También admite ansistrings (con longitud ilimitada) y
32
Machine Translated by Google
CAPÍTULO 3. TIPOS
Para declarar una variable como una cadena, utilice la siguiente especificación de tipo:
Tipo de cadena
Si hay un especificador de tamaño (usando corchetes), entonces su valor máximo, que indica el tamaño máximo de la
cadena, es 255. Si hay un especificador de página de códigos (usando corchetes), indica una ansistring con información
de página de códigos asociada.
El significado de una declaración de cadena sin indicación de página de código y tamaño se interpreta de manera
diferente según el modificador {$H}:
era
Una cuerda;
Si no hay ninguna indicación de tamaño y página de códigos, la declaración anterior puede declarar una cadena
hermana o una cadena corta.
Cualquiera que sea el tipo real, las cadenas de un solo byte se pueden utilizar indistintamente. El compilador siempre
se encarga de las conversiones de tipos necesarias. Sin embargo, tenga en cuenta que el resultado de una expresión
que contiene ansistrings y cadenas cortas siempre será una ansistring.
cuerdas cortas
Una declaración de cadena declara una cadena corta en los siguientes casos:
1. Si el interruptor $H está desactivado: {$H}, la declaración de cadena siempre será una declaración de cadena corta.
2. Si el interruptor está en {$H+} y hay un especificador de longitud máxima (el tamaño), la declaración es una
declaración de cadena corta.
Siempre se supone que las cadenas cortas utilizan la página de códigos del sistema. El tipo predefinido ShortString se
define como una cadena de tamaño 255:
Si no se especifica el tamaño de la cadena, se toma 255 como valor predeterminado. La longitud real de la cadena se
puede obtener con la rutina de ejecución estándar Longitud. Por ejemplo en
{$H}
Tipo
NombreCadena = Cadena[10];
StreetString = Cadena;
33
Machine Translated by Google
CAPÍTULO 3. TIPOS
NameString puede contener un máximo de 10 caracteres. Mientras que StreetString puede contener hasta 255 caracteres.
Observación Las cadenas cortas tienen una longitud máxima de 255 caracteres: al especificar una longitud máxima, la longitud máxima no
puede exceder los 255. Si se intenta una longitud mayor que 255, el compilador mostrará un mensaje de error:
Para cadenas cortas, la longitud se almacena en el carácter en el índice 0. El código antiguo de Turbo Pascal se basa en esto y
se implementa de manera similar en Free Pascal.
A pesar de esto, para escribir código portátil, es mejor establecer la longitud de una cadena corta con la llamada SetLength y
recuperarla con la llamada Longitud. Estas funciones siempre funcionarán, cualquiera que sea la representación interna de las
cadenas cortas u otras cadenas en uso: esto permite cambiar fácilmente entre los distintos tipos de cadenas.
Ansistrings
Las ansistrings son cadenas que no tienen límite de longitud y tienen una página de códigos asociada a ellas2. Se cuentan por
referencia y se garantiza que tendrán terminación nula.
Internamente, una ansistring se trata como un puntero: el contenido real de la cadena se almacena en el montón y se asigna
tanta memoria como sea necesaria para almacenar el contenido de la cadena.
Si no se proporciona ninguna página de códigos en la declaración, se asume la página de códigos del sistema. La página de
códigos que se trata está determinada por la constante DefaultSystemCodePage en la unidad del sistema.
Todo esto se maneja de forma transparente, es decir, se pueden manipular como una cadena corta normal. Las ansistrings se
pueden definir usando el tipo AnsiString predefinido o usando la palabra clave string en el modo {$H+}.
Observación La terminación nula no significa que no se puedan usar caracteres nulos (char(0) o #0): la terminación nula no se usa
internamente, pero está ahí por conveniencia cuando se trata de rutinas externas que esperan una cadena terminada en nulo
( como lo hacen la mayoría de las rutinas de C).
Si el interruptor {$H} está activado, entonces una definición de cadena que utilice la palabra clave String normal que no contenga
un especificador de longitud también se considerará una ansistring. Si hay un especificador de longitud presente, se utilizará una
cadena corta, independientemente de la configuración {$H}.
Si la cadena está vacía (''), entonces la representación del puntero interno del puntero de cadena es Nil. Si la cadena no está
vacía, entonces el puntero apunta a una estructura en la memoria del montón.
La representación interna como un puntero y la terminación nula automática hacen posible escribir una ansistring a un pchar.
Si la cadena está vacía (por lo que el puntero es Nil), entonces el compilador se asegura de que el pchar encasillado apunte a
un byte nulo.
Asignar una ansistring a otra no implica mover la cadena real. Una declaración
S2:=S1;
Si el recuento de referencias de una cadena llega a cero, la memoria ocupada por la cadena se desasigna automáticamente y el
puntero se establece en Nil, por lo que no surgen pérdidas de memoria.
Cuando se declara una ansistring, el compilador de Free Pascal inicialmente asigna solo memoria para un puntero, no más. Se
garantiza que este puntero será Nil, lo que significa que la cadena está inicialmente vacía. Esto es cierto para ansistrings locales
y globales o ansistrings que forman parte de una estructura (matrices, registros u objetos).
34
Machine Translated by Google
CAPÍTULO 3. TIPOS
Observación Tenga en cuenta que el resultado de una función a este respecto se considera equivalente a un parámetro var y,
por lo tanto, no se inicializará en Nil. Como consecuencia, puede apuntar a una ansistring legítima distinta de Nil
cuando comienza la función.
Era
A: Matriz[1..100000] de cadena;
Copiará el valor Nil 100.000 veces en A. Cuando A sale del alcance, el recuento de referencia de las 100.000
cadenas se reducirá en 1 para cada una de estas cadenas. Todo esto sucede de manera invisible para el
programador, pero cuando se consideran cuestiones de rendimiento, esto es importante.
La memoria para el contenido de la cadena se asignará solo cuando a la cadena se le asigne un valor. Si la cadena sale
del alcance, su recuento de referencias se reduce automáticamente en 1. Si el recuento de referencias llega a cero, se
libera la memoria reservada para la cadena.
Si se asigna un valor a un carácter de una cadena que tiene un recuento de referencia mayor que 1, como en las
siguientes declaraciones:
luego se crea una copia de la cadena antes de la asignación. Esto se conoce como semántica de copia en escritura.
Es posible forzar que una cadena tenga un recuento de referencias igual a 1 con la llamada UniqueString:
S:=T;
R:=T; // El recuento de referencias de T es al menos 3
Cadena única(T);
// El recuento de referencia de T está garantizado 1
Se recomienda hacer esto, por ejemplo, al encasillar una ansistring en una var PChar y pasarla a una rutina C que
modifica la cadena.
La función Longitud debe usarse para obtener la longitud de una ansistring: la longitud no se almacena en el carácter 0
de la ansistring. El constructo
L:=ord(S[0]);
que era válido para cadenas cortas de Turbo Pascal, ya no es correcto para Ansistrings. El compilador avisará si se
encuentra dicha construcción.
Para establecer la longitud de una ansistring, se debe utilizar la función SetLength. Las ansistrings constantes tienen
un recuento de referencia de 1 y se tratan de manera especial. Se debe dar la misma observación que para Longitud:
La construcción
L:=12;
S[0]:=Carbón(L);
que era válido para cadenas cortas de Turbo Pascal, ya no es correcto para Ansistrings. El compilador avisará si se
encuentra dicha construcción.
El compilador convierte las ansistrings en cadenas cortas si es necesario, esto significa que el uso de sistrings y cadenas
cortas se puede mezclar sin problemas.
Var P: Puntero;
PC: PCar;
35
Machine Translated by Google
CAPÍTULO 3. TIPOS
S: Cadena Ansi;
comenzar
S :='Esto es una ansistring';
PC:=Pchar(S);
P :=Puntero(S);
Hay una diferencia entre los dos encasillamientos. Cuando una ansistring vacía se encasilla en un puntero, el puntero será Nil. Si
una ansistring vacía se encasilla en un PChar, entonces el resultado será un puntero a un byte cero (una cadena vacía).
El resultado de tal encasillamiento debe utilizarse con cuidado. En general, es mejor considerar el resultado de este tipo de
encasillamiento como de sólo lectura, es decir, sólo apto para pasar a un procedimiento que necesita un argumento pchar
constante.
1. Expresiones.
2. Cadenas que tienen un recuento de referencias mayor que 1. En este caso, debe llamar a Uniquestring para asegurarse de
que la cadena tenga un recuento de referencias de 1.
Dado que las cadenas tienen información de página de códigos asociada, es importante saber qué página de códigos utiliza una
cadena:
• Las cadenas de un solo byte con una página de códigos declarada utilizan esa página de códigos.
• Las cadenas constantes tienen la página de códigos del archivo fuente. Si no se especifica ninguno, la página de códigos del sistema
se utiliza (CP_ACP).
Consulte la Guía del programador, directiva {$CODEPAGE }.
El compilador convertirá la página de códigos de cadenas según sea necesario: al asignar una cadena, la página de códigos real
de la cadena de origen se convertirá a la página de códigos declarada de la cadena de destino si las páginas de códigos de origen
y de destino declaradas son diferentes.
Si una cadena con una página declarada SOURCE_CP asignada a una cadena con la página de códigos declarada DEST_CP,
en un archivo con la página de códigos CODE_CP, a continuación se describe el mecanismo:
• si (CODE_CP <> CP_ACP), entonces si (DEST_CP = CP_ACP) y (SOURCE_CP = CODE_CP) o viceversa, no se producirá
ninguna conversión, incluso si en tiempo de ejecución DefaultSystemCodePage tiene un valor diferente de SOURCE_CP.
El motivo de esta condición “(CODE_CP <>CP_ACP)” es la compatibilidad con versiones anteriores de FPC: si bien no
admitían AnsiStrings con páginas de códigos arbitrarios, siempre reinterpretaban AnsiStrings de acuerdo con el valor
actual de la página de códigos del sistema.
De lo contrario,
36
Machine Translated by Google
CAPÍTULO 3. TIPOS
• Si (SOURCE_CP <> DEST_CP), los datos de cadena se convertirán de la página de códigos SOURCE_CP a la
página de códigos DEST_CP antes de la asignación, por lo que CP_ACP se interpretará como el valor actual de
DefaultSystemCodePage. De lo contrario,
Estas reglas significan que es perfectamente posible que una variable AnsiString obtenga una página de códigos que
difiera de su página de códigos declarada. [Link]. en el tercer caso SOURCE_CP podría ser CP_ACP, mientras que
después de la asignación puede tener una página de códigos dinámicos igual a DefaultSystemCodePage.
Nota: como se mencionó anteriormente, si se produce o no una posible conversión de página de códigos solo depende
de las páginas de códigos declaradas de las cadenas involucradas. Esto significa que si asigna un AnsiString(X) a otro
AnsiString(X) y el código dinámico del primero era diferente de X, la asignación no convertirá los datos de la cadena a la
página de códigos X.
{$h+}
usa sysutils;
Tipo
TString1 = Escriba cadena (1252);
TString2 = Escriba cadena (1251);
Era
A: TString1;
B: TScadena2;
comenzar
A:='123'+'345'+intToStr(123); B:=A; Writeln('B:
"',B,'" :
',StringRefCount(B),' > ',StringCodePage(B)); Writeln('A: "',A,'" : ',StringRefCount(A),' > ',StringCodePage(A)); fin.
Esto imprimirá:
Esta conversión automatizada de páginas de códigos puede ralentizar seriamente el código, por lo que se debe tener cuidado
para asegurarse de que las conversiones de páginas de códigos se limiten al mínimo.
La página de códigos de una cadena se puede configurar explícitamente utilizando la rutina SetCodePage de la unidad del sistema.
Llamar a esta rutina convertirá el valor de una cadena a la página de códigos solicitada.
Observación Las conversiones de páginas de códigos pueden provocar la pérdida de datos: si un determinado carácter no se puede representar
en la página de códigos de destino, la salida para ese carácter no está definida.
Observación Cuando una cadena cuya página de códigos estáticos es igual a la página de códigos del archivo fuente, a cualquier cosa con la
página de códigos CP_ACP (es decir, una simple ansistring, shortstring o pchar), tampoco se realizará ninguna conversión. No se
realiza ninguna conversión de página de códigos cuando s puede provocar una pérdida de datos: si un determinado carácter no
se puede representar en la página de códigos de destino, la salida para ese carácter no está definida.
Observación La compatibilidad con la página de códigos requiere bastantes rutinas de ayuda, que se implementan en el
administrador de cadenas unicodestring. En Windows se utilizan para ello las rutinas del sistema. En Unices, la unidad cwstring
37
Machine Translated by Google
CAPÍTULO 3. TIPOS
se puede utilizar para vincular a la biblioteca C y utilizar el soporte de conversión de la biblioteca C. Alternativamente, la unidad
fpwidestring contiene un administrador Unicodestring implementado de forma nativa en Object Pascal.
El tipo RawByteString predefinido es un tipo de cadena ansistring sin información de página de códigos
(CP_NINGUNO):
Tipo
RawByteString = escriba ansistring(CP_NONE);
Se trata de manera especial en el sentido de que si las rutinas de conversión encuentran CP_NONE en una cadena de origen o de
destino, no se realiza ninguna conversión de página de códigos y se conserva la página de códigos de la cadena de origen.
Por este motivo, la mayoría de las rutinas de cadenas de un solo byte en las unidades system y sysutils utilizan el tipo RawByteString.
Cadena UTF8
Las cadenas de página de códigos de un solo byte solo pueden almacenar los caracteres disponibles en esa página de códigos. Los
caracteres que no están presentes en la página de códigos no pueden representarse mediante esa cadena. La codificación Unicode
UTF8 es una codificación que se puede utilizar con cadenas de un solo byte: los caracteres ASCII (valor ordinal <128) en esta
codificación se asignan exactamente a la codificación CP_ACP. Este hecho se utiliza para definir un tipo de cadena de un solo byte
que puede contener todos los caracteres:
Tipo
UTF8String = escriba AnsiString(CP_UTF8);
El tipo de cadena UTF8string se puede utilizar para representar todos los caracteres Unicode. Sin embargo, este poder tiene un
precio. Dado que un carácter Unicode puede requerir varios bytes para representarse en la codificación UTF8, hay dos puntos a
tener en cuenta al utilizar UTF8String:
1. El índice de caracteres, que recupera un carácter del tamaño de un byte en una posición determinada, debe usarse con
cuidado: la expresión S[i] no será necesariamente un carácter válido para una cadena S de tipo UTF8String.
2. La longitud en bytes de la cadena no es igual al número de caracteres de la cadena. La longitud de la función estándar no se
puede utilizar para obtener la longitud del carácter, siempre devolverá la longitud del byte.
Para todas las demás páginas de códigos, el número de caracteres en una cadena de página de códigos de un solo byte es igual a
la longitud en bytes de la cadena.
Cadenas Unicode
Las cadenas Unicode (utilizadas para representar cadenas de caracteres Unicode) se implementan de manera muy similar a las
ansistrings: matrices terminadas en nulo y contadas por referencia, solo que se implementan como matrices de WideChars en lugar
de Chars normales. Un WideChar es un carácter de dos bytes (un elemento de un
38
Machine Translated by Google
CAPÍTULO 3. TIPOS
DBCS: Juego de caracteres de doble byte). Básicamente, se aplican las mismas reglas para UnicodeStrings que para
AnsiStrings. El compilador convierte de forma transparente UnicodeStrings a AnsiStrings y viceversa.
De manera similar a la conversión de un Ansistring a una matriz de caracteres terminada en nulo PChar, una cadena
de código Uni se puede convertir en una matriz de caracteres terminada en nulo PUnicodeChar. Tenga en cuenta que
la matriz PUnicodeChar termina con 2 bytes nulos en lugar de 1, por lo que una conversión encasillada a pchar no es
automática.
constante
cuerdas anchas
El tipo Widestring (utilizado para representar cadenas de caracteres Unicode en aplicaciones COM) se implementa de
forma muy parecida a Unicodestring en Windows, y en otras plataformas son simplemente del mismo tipo. Si no se
requiere interacción con COM, se debe utilizar el tipo UnicodeString.
En Windows, a diferencia de UnicodeString, el tipo WideString no cuenta con referencias y se les asigna una función
especial de Windows que permite su uso para la automatización OLE. Esto significa que se implementan como matrices
terminadas en nulo de WideChars en lugar de Chars normales.
WideString obedece las mismas reglas que UnicodeStrings. De manera similar a las cadenas Unicode, el compilador
convierte de forma transparente WideStrings en AnsiStrings y viceversa.
Para el encasillamiento y la conversión, se aplican las mismas reglas que para el tipo UnicodeString.
Tenga en cuenta que en Windows, debido a que una cadena WideString se asigna mediante una función especial de
Windows, el diseño de la memoria difiere de UnicodeString. La longitud, por ejemplo, se almacena en bytes en lugar de
caracteres.
Como puede verse, el carácter de comilla simple está representado por 2 caracteres de comillas simples uno al lado
del otro. Los caracteres extraños se pueden especificar por su valor de carácter (normalmente un código ASCII). El
ejemplo muestra también que se pueden agregar dos cadenas. La cadena resultante es simplemente la concatenación
de la primera con la segunda cadena, sin espacios entre ellas. Sin embargo, las cadenas no se pueden restar.
Si la cadena constante se almacena como una ansistring o una cadena corta depende de la configuración del
modificador {$H}.
39
Machine Translated by Google
CAPÍTULO 3. TIPOS
Free Pascal admite la implementación Delphi del tipo PChar. PChar se define como un puntero a un tipo
Char, pero permite operaciones adicionales. El tipo PChar puede entenderse mejor como el equivalente en
Pascal de una cadena terminada en nulo estilo C, es decir, una variable de tipo PChar es un puntero que
apunta a una matriz de tipo Char, que finaliza con un carácter nulo (#0 ). Free Pascal admite la inicialización
de constantes escritas en PChar o una asignación directa. Por ejemplo, los siguientes fragmentos de código
son equivalentes:
programa uno;
var P: PChar; comenzar
P :=
'Esta es una cadena terminada en nulo.'; EscribirLn (P); fin.
Estos ejemplos también muestran que es posible escribir el contenido de la cadena en un archivo de tipo
Texto. La unidad de cadenas contiene procedimientos y funciones que manipulan el tipo PChar como en
la biblioteca C estándar. Dado que equivale a un puntero a una variable de tipo Char, también es posible
hacer lo siguiente:
Esto tendrá el mismo resultado que los dos ejemplos anteriores. Las cadenas terminadas en nulo no se pueden
agregar como cadenas Pascal normales. Si se deben concatenar dos cadenas PChar; Se deben utilizar las
funciones de las cadenas unitarias.
Sin embargo, es posible hacer algo de aritmética de punteros. Los operadores + y se pueden utilizar para
realizar operaciones en punteros PChar. En la tabla (3.6), P y Q son de tipo PChar e I es de tipo Entero largo.
Operación
P+I Resultado Agrega I a la dirección señalada por P.
yo + p Agrega I a la dirección señalada por P.
PI Resta I de la dirección señalada por P.
PQ Devuelve, como un número entero, la distancia entre dos
direcciones (o el número de caracteres entre P y Q)
40
Machine Translated by Google
CAPÍTULO 3. TIPOS
La memoria ocupada por una cadena depende del tipo de cadena. Algunos tipos de cadenas asignan la cadena
datos en la memoria en el montón, otros tienen los datos de cadena en la pila. La tabla (3.7) resume
el uso de memoria de los distintos tipos de cadenas para los distintos tipos de cadenas. En la tabla, lo siguiente
Se utilizan constantes simbólicas:
2. HS depende de la versión de Free Pascal, pero tiene 16 bytes a partir de Free Pascal 2.7.1.
4. En Windows, el tamaño de WHS es de 4 bytes para todas las versiones de Free Pascal. En todas las demás plataformas, WHS
es igual a UHS porque el tipo WideString es igual al tipo UnicodeString.
Un tipo estructurado es un tipo que puede contener múltiples valores en una variable. Los tipos estructurados pueden ser
anidado en niveles ilimitados.
Tipos estructurados
tipo de registro
tipo de objeto
tipo de clase
tipo de referencia de clase
Tipo de interfaz
tipo de conjunto
tipo de archivo
A diferencia de Delphi, Free Pascal no admite la palabra clave Packed para todos los tipos estructurados. En el
En las siguientes secciones se analiza cada uno de los posibles tipos estructurados. Se mencionará cuando un
type admite la palabra clave empaquetada.
Cuando se declara un tipo estructurado, no se deben hacer suposiciones sobre la posición interna del
elementos del tipo. El compilador dispondrá los elementos de la estructura en la memoria como piensa.
41
Machine Translated by Google
CAPÍTULO 3. TIPOS
será el más adecuado. Es decir, se mantendrá el orden de los elementos, pero no se garantiza la ubicación de los elementos, y se rige
parcialmente por la directiva $PACKRECORDS (esta directiva se explica en la Guía del Programador).
Sin embargo, Free Pascal permite controlar el diseño con las palabras clave Packed y Bitpacked.
El significado de estas palabras depende del contexto:
Bitpacked En este caso, el compilador intentará alinear los tipos ordinales en límites de bits, como por ejemplo
se explica a continuación.
3. En otros modos, con la directiva $BITPACKING configurada en OFF, significa empaquetado normal en límites de bytes.
Empaquetar límites de bytes significa que cada nuevo elemento de un tipo estructurado comienza en un límite de bytes.
El mecanismo de empaquetado de bytes es simple: el compilador alinea cada elemento de la estructura en el primer límite de bytes
disponible, incluso si el tamaño del elemento anterior (tipos enumerados pequeños, tipos de subrango) es menor que un byte.
Cuando se utiliza el mecanismo de empaquetado de bits, el compilador calcula para cada tipo ordinal cuántos bits se necesitan para
almacenarlo. El siguiente tipo ordinal se almacena en el siguiente bit libre. Los tipos no ordinales, que incluyen, entre otros, conjuntos,
flotantes, cadenas, registros (en formato bit), matrices (en formato bit), punteros, clases, objetos y variables de procedimiento, se almacenan
en el primer límite de bytes disponible.
Tenga en cuenta que las partes internas del paquete de bits son opacas: pueden cambiar en cualquier momento en el futuro. Es más: el
empaquetado interno depende del endianidad de la plataforma para la que se realiza la compilación y no es posible ninguna conversión
entre plataformas. Esto hace que las estructuras empaquetadas en bits no sean adecuadas para almacenar en disco o transportar a través
de redes. Sin embargo, el formato es el mismo que el utilizado por el compilador GNU Pascal, y el equipo de Free Pascal pretende mantener
esta compatibilidad en el futuro.
Existen algunas restricciones más para los elementos de estructuras empaquetadas en bits:
• La dirección no se puede recuperar, a menos que el tamaño de bits sea múltiplo de 8 y el elemento ocurra
que se almacenará en un límite de bytes.
• Un elemento de una estructura empaquetada en bits no se puede utilizar como parámetro var, a menos que el tamaño del bit sea
múltiplo de 8 y el elemento esté almacenado en un límite de bytes.
Para determinar el tamaño de un elemento en una estructura empaquetada de bits, existe la función BitSizeOf. Devuelve el tamaño (en bits)
del elemento. Para otros tipos o elementos de estructuras que no están empaquetados en bits, esto simplemente devolverá el tamaño en
bytes multiplicado por 8, es decir, el valor de retorno es entonces el mismo que 8*SizeOf.
La razón es que el desplazamiento de un elemento debe calcularse con el tamaño entero máximo del sistema.
42
Machine Translated by Google
CAPÍTULO 3. TIPOS
3.3.1 Matrices
Free Pascal admite matrices como en Turbo Pascal. También se admiten matrices multidimensionales y matrices
empaquetadas (en bits), así como las matrices dinámicas de Delphi:
Tipos de matriz
matrices estáticas
Cuando el rango de la matriz se incluye en la definición de la matriz, se denomina matriz estática. Intentar acceder a
un elemento con un índice que está fuera del rango declarado generará un error de tiempo de ejecución (si la
verificación de rango está activada). El siguiente es un ejemplo de una declaración de matriz válida:
Tipo
RealArray = Matriz [1..100] de Real;
Los índices válidos para acceder a un elemento del array están entre 1 y 100, donde se incluyen los bordes 1 y 100.
Como en Turbo Pascal, si el tipo de componente de la matriz es en sí mismo una matriz, es posible combinar las dos
matrices en una matriz multidimensional. La siguiente declaración:
Escriba
APoints = matriz[1..100] de Matriz[1..3] de Real;
es equivalente a la declaración:
Escriba
APoints = matriz[1..100,1..3] de Real;
Las funciones Alto y Bajo devuelven los límites alto y bajo del tipo de índice más a la izquierda de la matriz.
En el caso anterior, esto sería 100 y 1. Debe usarlos siempre que sea posible, ya que mejora la mantenibilidad de su
código. El uso de ambas funciones es tan eficiente como el uso de constantes, porque se evalúan en tiempo de
compilación.
Cuando se asignan variables estáticas de tipo matriz entre sí, se copia el contenido de toda la matriz.
Esto también es válido para matrices multidimensionales:
programa testarray1;
Tipo
TA = Matriz[0..9,0..9] de número entero;
era
A,B:TA;
I,J: Entero; comenzar
43
Machine Translated by Google
CAPÍTULO 3. TIPOS
B:=A;
Escribir;
Para I:=0 a 9 hacer
Para J:=0 a 9 hacer
A[9I,9J]:=I*J;
Para I:=0 a 9 hacer
comenzar
Para J:=0 a 9 hacer
Escribir(B[I,J]:2,'');
Escribir; fin;
fin.
matrices dinámicas
A partir de la versión 1.1, Free Pascal también conoce matrices dinámicas: en ese caso se omite el rango de la matriz,
como en el siguiente ejemplo:
Tipo
TByteArray = Matriz de bytes;
Al declarar una variable de tipo matriz dinámica, la longitud inicial de la matriz es cero. La longitud real de la matriz
debe establecerse con la función estándar SetLength, que asignará la memoria necesaria para contener los
elementos de la matriz en el montón.
Era
R: TByteArray;
comenzar
EstablecerLongitud(A,1000);
Después de una llamada a SetLength, los índices de matriz válidos son del 0 al 999: el índice de la matriz siempre tiene base cero.
SetLength también se puede utilizar para matrices multidimensionales. El siguiente ejemplo creará una matriz
"rectangular":
Era
A: Matriz de TByteArray;
comenzar
EstablecerLongitud(A,10,100);
Después de una llamada a SetLength, los índices de matriz válidos son del 0 al 9 para la primera dimensión y del 0 al 99 para
la segunda dimensión.
A diferencia de los arreglos multidimensionales estáticos, los arreglos dinámicos no necesitan ser "rectangulares", es
decir, los distintos elementos pueden tener diferentes longitudes:
44
Machine Translated by Google
CAPÍTULO 3. TIPOS
era
a: matriz de matriz de matriz de LongInt; i, j, k: Int largo; comenzar
Writeln('======='); fin;
fin.
También es posible cambiar el tamaño de la matriz: en ese caso, se conservarán tantos elementos de la matriz como
quepan en el nuevo tamaño. Se puede cambiar el tamaño de la matriz a cero, lo que efectivamente restablece la variable.
En todo momento, intentar acceder a un elemento del array con un índice que no está en la longitud actual del
array generará un error de tiempo de ejecución.
Las matrices dinámicas se cuentan por referencia: la asignación de una variable de tipo matriz dinámica a
otra permitirá que ambas variables apunten a la misma matriz. Al contrario de las ansistrings, una asignación
a un elemento de una matriz se reflejará en la otra: no hay copia en escritura. Considere el siguiente ejemplo:
Era
A,B: TByteArray;
comenzar
EstablecerLongitud(A,10);
A[0]:=33;
B:=A;
A[0]:=31;
programa testarray1;
Tipo
TA = Matriz de matriz de Entero;
45
Machine Translated by Google
CAPÍTULO 3. TIPOS
era
A,B:TA;
I,J: Entero; comenzar
B:=A;
Escribir;
Para I:=0 a 9 hacer
Para J:=0 a 9 hacer
A[9I,9J]:=I*J;
Para I:=0 a 9 hacer
comenzar
Para J:=0 a 9
escriba(B[I,J]:2,''); Escribir; fin; fin.
El resultado de este programa será una matriz de números y luego la misma matriz reflejada.
Como se comentó anteriormente, las matrices dinámicas se cuentan por referencia: si en uno de los ejemplos
anteriores A sale del alcance y B no, entonces la matriz aún no se ha eliminado: el recuento de referencias de
A (y B) se reduce en 1. Tan pronto como el recuento de referencias llega a cero, se elimina la memoria asignada
para el contenido de la matriz.
La llamada a SetLength asegurará que el recuento de referencias de la matriz devuelta sea 1, es decir, si
dos variables de matriz dinámica apuntaban a la misma memoria, ya no lo harán después de la llamada a
setlength:
programa y texto;
Tipo
TA = matriz de números enteros;
era
A,B:TA;
I: Entero;
comenzar
Establecer longitud (A, 10);
Para I:=0 a 9 hacer
A[Yo]:=Yo;
B:=A;
EstablecerLongitud(B,6);
A[0]:=123;
Para I:=0 a 5 hacer
46
Machine Translated by Google
CAPÍTULO 3. TIPOS
Escribir(B[I]); fin.
También es posible copiar y/o cambiar el tamaño de la matriz con la función Copiar estándar, que actúa como
función de copia para cadenas:
programa testerray3;
Tipo
TA = matriz de números enteros;
era
A,B:TA;
I: Entero;
comenzar
Establecer longitud (A, 10);
Para I:=0 a 9 hacer
A[Yo]:=Yo;
B:=Copiar(A,3,6);
Para I:=0 a 5 hacer
Escribir(B[I]);
fin.
La función Copiar copiará seis elementos de la matriz a una nueva matriz. Comenzando en el elemento en el
índice 3 (es decir, el cuarto elemento) de la matriz.
La función Longitud devolverá el número de elementos de la matriz. La función Baja en una matriz dinámica siempre
devolverá 0 y la función Alta devolverá el valor Longitud1, es decir, el valor del índice de matriz más alto permitido.
Object Pascal es un lenguaje estrictamente tipificado. En ocasiones, dos tipos técnicamente distintos se
consideran compatibles con la asignación (es decir, un valor de un tipo puede asignarse a una variable de otro
tipo) en determinadas circunstancias. Las matrices dinámicas se consideran compatibles con la asignación
cuando utilizan el mismo tipo de elemento. Eso significa que se compilará lo siguiente:
{$modo objfpc}
Tipo
TA = Matriz de números enteros;
TB = Matriz de números enteros;
Era
R: TA;
B: tuberculosis;
comenzar
SetLength(A,1); A[0]:=1;
B:=A; fin.
Pero lo siguiente no lo hará, aunque los tipos de enteros y palabras sean compatibles con la asignación:
47
Machine Translated by Google
CAPÍTULO 3. TIPOS
{$modo objfpc}
Tipo
TA = Matriz de palabras;
TB = Matriz de números enteros;
Era
R: TA;
B: tuberculosis;
comenzar
EstablecerLongitud(A,1);
A[0]:=1;
B:=A;
fin.
A partir de la versión 3.0 de Free Pascal, los tipos de matrices dinámicas tienen un constructor. Esto es intrínseco,
el compilador lo proporciona. Hasta la versión 2.6.4, la única forma de inicializar una matriz dinámica era la siguiente:
Tipo
TIntegerArray = Matriz de números enteros;
era
A: TIntegerArray;
comenzar
SetLength(A,3); A[0]:=1;
A[1]:=2;
A[3]:=3;
Writeln(Longitud(A)); fin.
A partir de la versión 3.0 de Free Pascal, una matriz dinámica se puede inicializar utilizando una sintaxis similar a la de un constructor.
El constructor se llama Crear y acepta como parámetros un número variable de parámetros del tipo de
elemento del tipo de matriz. Esto significa que la inicialización anterior ahora se puede realizar como:
Tipo
TIntegerArray = Matriz de números enteros;
era
A: TIntegerArray;
comenzar
A:=[Link](1,2,3); Writeln(Longitud(A));
fin.
Tenga en cuenta que esto no funcionará para matrices dinámicas para las que no se creó ningún tipo. Es decir, lo
siguiente no funcionará:
era
48
Machine Translated by Google
CAPÍTULO 3. TIPOS
comenzar
A:=Matriz de [Link](1,2,3); Writeln(Longitud(A)); fin.
Tipo
TIntegerArray = Matriz de números enteros;
TIntegerArrayArray = Matriz de TIntegerArray;
era
A: TIntegerArrayArray;
comenzar
A:=[Link]([Link](1,2,3), [Link](4,5,6),
[Link](7,8,9)); Writeln('Longitud
',longitud(A)); fin.
Sin embargo, dado que es un constructor (el código se ejecuta en tiempo de ejecución), no es posible usarlo en una
sintaxis de variable inicializada. Es decir, lo siguiente no funcionará:
Tipo
TIntegerArray = Matriz de números enteros;
era
A: TIntegerArray = [Link](1,2,3);
comenzar
Writeln('Longitud ',longitud(A)); fin.
A partir de la versión 3.2 del compilador, se puede construir una matriz utilizando una expresión de matriz en una
asignación explícita o en una variable inicializada. Sin embargo, la expresión es diferente. En una declaración de
asignación, se parece a una expresión establecida; en una variable inicializada, se debe usar la misma sintaxis
que para una matriz constante de longitud fija:
Tipo
TIntegerArray = Matriz de números enteros;
era
A: TIntegerArray = (1,2,3);
B: Matriz de enteros;
comenzar
B:=[3,4,5];
fin.
49
Machine Translated by Google
CAPÍTULO 3. TIPOS
Las matrices se pueden empaquetar y empaquetar en bits. Dos tipos de matriz que tienen el mismo tipo de índice y tipo de elemento,
pero que están empaquetados de manera diferente, no son compatibles con la asignación.
Sin embargo, es posible convertir una matriz normal en una matriz empaquetada en bits con la rutina pack. La operación
inversa también es posible; una matriz empaquetada en bits se puede convertir en una matriz empaquetada normalmente
usando la rutina de desempaquetar, como en el siguiente ejemplo:
Era
comenzar
paquete(foo,'a',bar);
desempaquetar(barra,baz,'0'); fin.
Puede encontrar más información sobre las rutinas de empaque y desempaquetado en la referencia de la unidad del sistema.
Tipos de registros
empaquetado bitpacked
variante 6 constante :( )
, lista de campos
Tipo
Punto = Registro
X,Y,Z : Reales; fin;
RPoint = Registro
50
Machine Translated by Google
CAPÍTULO 3. TIPOS
Caso booleano de
Falso : (X,Y,Z : Real);
Verdadero: (R,theta,phi: Real); fin;
BetterRPoint = Grabar
Caso de usoPolar: booleano de
Falso : (X,Y,Z : Real);
Verdadero: (R,theta,phi: Real); fin;
La parte variante debe ser la última en el registro. El identificador opcional en la declaración del caso sirve para
acceder al valor del campo de etiqueta, que de otro modo sería invisible para el programador. Se puede utilizar para
ver qué variante está activa en un momento determinado3. En efecto, introduce un nuevo campo en el registro.
Tipo
MiRec = Grabar
X: Entero largo;
Byte de caso de
2 : (Y : Entero largo; byte
caso de
3: (Z: Entero largo); );
fin;
• Los requisitos de alineación de los tipos de campos, que dependen de la plataforma. Tenga en cuenta que
los requisitos de alineación de un tipo dentro de un registro pueden ser diferentes de los de una variable
separada de ese tipo. Además, la ubicación de un campo dentro de un registro también puede influir en
los requisitos de alineación de su tipo.
El diseño y el tamaño de las partes variantes en los registros se determina reemplazándolos con un campo cuyo tipo
es un registro con como primer elemento un campo del tipo de campo etiqueta si se declaró un identi cador para este
campo etiqueta, seguido de los elementos de la variante más grande.
El desplazamiento del campo F2 en un registro es igual a la suma del desplazamiento del campo F1 anterior y el tamaño de F1,
redondeado a un múltiplo de la alineación requerida de F2. Esta alineación requerida se calcula de la siguiente manera:
51
Machine Translated by Google
CAPÍTULO 3. TIPOS
– C: la alineación requerida se ajusta de acuerdo con las reglas especificadas en el ABI oficial
para la plataforma actual.
– POWER/POWERPC, MAC68K: el ajuste del valor de alineación se determina siguiendo las reglas oficiales
ABI para resp. las plataformas (clásicas) Macintosh PowerPC o Macintosh 680x0.
El tamaño de un registro es igual a la suma del desplazamiento del último campo del registro y el tamaño de este campo,
redondeado a un múltiplo de la alineación requerida del registro. La alineación requerida del registro se calcula de la
siguiente manera:
• La alineación requerida se establece en la alineación del campo del registro con la alineación más grande, según
se determinó al diseñar el registro.
Observaciones y ejemplos
Free Pascal también admite un "registro empaquetado", que es un registro donde todos los elementos están alineados
en bytes. Como resultado, las dos declaraciones siguientes son equivalentes:
{$PackRegistros 1}
Trec2 = Registro
A: Byte;
B: Palabra; fin;
{$PackRecords predeterminado}
A: Byte;
B: Palabra; fin;
¡Tenga en cuenta el {$PackRecords Default} después de la primera declaración para restaurar la configuración predeterminada!
Dada la naturaleza dependiente de la plataforma de cómo se distribuyen los registros en la memoria, la única manera
de garantizar un diseño compatible entre plataformas (suponiendo que todos los campos se declaren usando un tipo
con el mismo significado en estas mismas plataformas) es usando {$PACKRECORDS 1}.
En particular, si se debe leer un archivo escrito con registros, producido por un programa Turbo Pascal, es
probable que el intento de leer ese archivo correctamente falle. La razón es que el valor predeterminado de Free Pascal
52
Machine Translated by Google
CAPÍTULO 3. TIPOS
La misma observación se aplica a Delphi: solo se garantiza que el intercambio de datos sea posible si tanto el productor
como el consumidor usan un registro empaquetado, o si están en la misma plataforma y usan la misma configuración
{$PACKRECORDS X}.
Free Pascal admite los tipos de conjuntos como en Turbo Pascal. El prototipo de una declaración de conjunto es:
Tipos de conjuntos
Cada uno de los elementos de SetType debe ser de tipo TargetType. TargetType puede ser cualquier tipo ordinal
con un rango entre 0 y 255. Un conjunto puede contener como máximo 256 elementos. Las siguientes son
declaraciones de conjunto válidas:
Tipo
Basura = Conjunto de Char;
Días = (lunes, martes, miércoles, jueves, viernes, sábado, domingo);
Era
WorkDays: Conjunto de días;
Se pueden realizar varias operaciones sobre conjuntos: tomar uniones o diferencias, agregar o quitar elementos,
comparaciones. Estos están documentados en la sección 12.8.6, página 180.
La forma en que el compilador almacena los conjuntos depende del modo y se puede controlar con una directiva. Para
obtener más información, consulte la guía del programador.
Los tipos de archivos son tipos que almacenan una secuencia de algún tipo base, que puede ser cualquier tipo excepto otro
tipo de archivo. Puede contener (en principio) un número infinito de elementos. Los tipos de archivos se utilizan comúnmente
para almacenar datos en el disco. Sin embargo, nada impide que el programador escriba un controlador de archivo que
almacene sus datos, por ejemplo, en la memoria.
Tipos de archivo
53
Machine Translated by Google
CAPÍTULO 3. TIPOS
Si no se proporciona ningún identificador de tipo, entonces el archivo es un archivo sin tipo; puede considerarse equivalente a
un archivo de bytes. Los archivos sin tipo requieren comandos especiales para actuar sobre ellos (consulte Blockread, Blockwrite).
La siguiente declaración declara un fichero de registros:
Tipo
Punto = Registro
X,Y,Z: reales; fin;
Internamente, los archivos están representados por el registro FileRec, que se declara en las unidades Dos o
SysUtils.
Un tipo de archivo especial es el tipo de archivo de texto, representado por el registro TextRec. Un archivo de tipo Texto
utiliza rutinas especiales de entrada y salida. Los tipos de archivos predeterminados de Entrada, Salida y StdErr se definen
en la unidad del sistema: todos son de tipo Texto y se abren mediante el código de inicialización de la unidad del sistema.
3.4 Consejos
Free Pascal admite el uso de punteros. Una variable del tipo puntero contiene una dirección en la memoria, donde
se pueden almacenar los datos de otra variable. Un tipo de puntero se puede definir de la siguiente manera:
Tipos de puntero
ˆ
tipo de puntero tipo de identificador
Como puede verse en este diagrama, los punteros se escriben, lo que significa que apuntan a un tipo particular de
datos. El tipo de estos datos debe conocerse en el momento de la compilación.
Desreferenciar el puntero (indicado agregando ˆ después del nombre de la variable) se comporta entonces como una variable.
Esta variable tiene el tipo declarado en la declaración del puntero y la variable se almacena en la dirección a la que
apunta la variable del puntero. Considere el siguiente ejemplo:
Búfer = Cadena[255];
BufPtr = ^Búfer;
Donde B: Tampón;
PA: BufPtr;
PP: Puntero;
etc..
En este ejemplo, BP es un puntero a un tipo de búfer; mientras que B es una variable de tipo Buffer. B ocupa
256 bytes de memoria y BP sólo ocupa 4 (u 8) bytes de memoria: suficiente memoria para almacenar una dirección.
La expresion
PA^
54
Machine Translated by Google
CAPÍTULO 3. TIPOS
PA^[23]
Observación Free Pascal trata los punteros de la misma manera que C. Esto significa que un puntero a algún tipo puede tratarse
como una matriz de este tipo.
Desde este punto de vista, el puntero apunta al elemento cero de esta matriz. Así, la siguiente declaración de puntero
La diferencia es que la primera declaración asigna memoria sólo para el puntero (no para la matriz), y la segunda
declaración asigna memoria para toda la matriz. Si se utiliza el primero, la memoria debe asignarse manualmente,
utilizando la función Getmem. La referencia Pˆ es entonces la misma que p[0].
El siguiente programa ilustra esto quizás más claro:
si p[i]<>pp[i] entonces
WriteLn ('¡Oh, problema!')
fin.
Free Pascal admite la aritmética de punteros como lo hace C. Esto significa que, si P es un puntero escrito, las
instrucciones
Inc(P);
diciembre(P);
aumentará, respectivamente disminuirá la dirección a la que apunta el puntero con el tamaño del tipo P al
que apunta. Por ejemplo
aumentará P en 4, porque 4 es el tamaño de un entero largo. Si el puntero no tiene tipo, se supone un tamaño
de 1 byte (es decir, como si el puntero fuera un puntero a un byte: ˆbyte).
También se pueden utilizar operadores aritméticos normales en punteros, es decir, las siguientes son operaciones
aritméticas de punteros válidas:
55
Machine Translated by Google
CAPÍTULO 3. TIPOS
P2 := @L; L :=
P1P2; P1 := P14;
P2 := P2+4; fin.
Aquí, el valor que se suma o resta se multiplica por el tamaño del tipo al que apunta el puntero.
En el ejemplo anterior, P1 se reducirá en 16 bytes y P2 se incrementará en 16.
Los programas a menudo necesitan mantener una lista vinculada de registros. Luego, cada registro contiene un
puntero al siguiente registro (y posiblemente también al registro anterior). Para seguridad de tipos, es mejor definir
este puntero como un puntero escrito, de modo que el siguiente registro se pueda asignar en el montón mediante
la llamada Nueva. Para hacerlo, el registro debe definirse de esta manera:
Tipo
TListItem = Registro
Datos: entero;
Siguiente : ^TListItem; fin;
Al intentar compilar esto, el compilador se quejará de que el tipo TListItem aún no está definido cuando encuentra la
declaración Next: Esto es correcto, ya que la definición aún se está analizando.
Para poder tener el elemento Siguiente como puntero escrito, se debe introducir una “declaración de tipo hacia adelante”:
Tipo
PListItem = ^TListItem;
TListItem = Registro
Datos: entero;
Siguiente: PTListItem; fin;
Cuando el compilador encuentra una declaración de puntero escrita donde aún no se conoce el tipo al que se hace
referencia, pospone la resolución de la referencia para más adelante. La definición del puntero es una "declaración de tipo directo".
El tipo al que se hace referencia debe introducirse más adelante en el mismo bloque Tipo. Ningún otro bloque puede
interponerse entre la definición del tipo de puntero y el tipo referenciado. De hecho, es posible que ni siquiera la palabra
Tipo en sí vuelva a aparecer: de hecho, iniciaría un nuevo bloque de tipo, lo que provocaría que el compilador resolviera
todas las declaraciones pendientes en el bloque actual.
En la mayoría de los casos, la definición del tipo referenciado seguirá inmediatamente después de la definición del
tipo de puntero, como se muestra en el listado anterior. El tipo definido hacia adelante se puede utilizar en cualquier
definición de tipo después de su declaración.
Tenga en cuenta que una declaración de tipo directa sólo es posible con tipos y clases de puntero, no con otros
tipos.
Free Pascal tiene soporte para tipos procedimentales, aunque difiere un poco de la implementación de los
mismos Turbo Pascal o Del phi. La declaración de tipo sigue siendo la misma, como se puede ver en el siguiente
diagrama de sintaxis:
56
Machine Translated by Google
CAPÍTULO 3. TIPOS
Tipos procesales
stdcall
safecall
inline
Para obtener una descripción de las listas de parámetros formales, consulte el capítulo 14, página 207. Los dos ejemplos siguientes
son declaraciones de tipo válidas:
1. Nil, tanto para punteros de procedimiento normal como para punteros de método.
2. Una referencia de variable de tipo procedimental, es decir, otra variable del mismo tipo.
3. Un procedimiento global o dirección de función, con encabezado y llamada de función o procedimiento coincidentes.
convención.
...
Proceso := @printit;
Función := @Pi;
A partir de este ejemplo, la diferencia con Turbo Pascal es clara: en Turbo Pascal no es necesario utilizar el
operador de dirección (@) al asignar una variable de tipo procedimental, mientras que en Free Pascal sí es
necesario. En caso de que se utilicen los modificadores MDelphi o MTP, se puede eliminar el operador de dirección.
Observación Los modificadores relacionados con las convenciones de llamada deben ser los mismos que los de la declaración;
es decir, el siguiente código daría un error:
57
Machine Translated by Google
CAPÍTULO 3. TIPOS
En caso de que se agregue la modificación anidada, la variable de procedimiento se puede utilizar con
procedimientos anidados. Esto requiere que las fuentes estén compiladas en modo macpas o ISO, o que el interruptor
de modo nestedprocvars esté activado:
escriba
tnestedprocvar = el procedimiento está anidado;
era
temperatura: tnestedprocvar;
procedimiento n;
comenzar
writeln('llamar a través de n') finalizar;
procedimiento q;
procedimiento r;
comience
si qi = 1 y luego escriba
('éxito para r') en caso contrario
comenzar
a escribir ('fallar');
58
Machine Translated by Google
CAPÍTULO 3. TIPOS
detener(1) fin
fin;
comenzar
qi:= 1;
p1(@r);
p2(@r);
p1(@n);
p2(@n); fin;
comenzar
q;
fin.
En caso de que se desee asignar métodos de una clase a una variable de tipo procedimental, se debe
declarar el tipo procedimental con el modificador of object.
Los dos ejemplos siguientes son declaraciones de tipo válidas para variables de procedimiento de método (también conocidas
como controladores de eventos debido a su uso en el diseño de GUI):
A estas funciones se les puede asignar un método de firma correcta. Cuando se llama, Self apuntará a la instancia del
objeto que se utilizó para asignar el procedimiento del método.
Tipo
TMiObjeto = Clase(TOobjeto)
Procedimiento DoX (Var X: número entero);
Función DoY: Real; fin;
Era
M: TMiObjeto;
comenzar
oproc:=@[Link];
ofunc:=@[Link]; fin;
Observación Al comparar dos variables de tipo método, solo se compara la dirección del método, no el puntero de instancia. Eso
significa que el siguiente programa imprimirá Verdadero:
Tipo
TSomeMethod = Procedimiento del objeto;
59
Machine Translated by Google
CAPÍTULO 3. TIPOS
TMiObjeto = Clase(TOobjeto)
Procedimiento Hacer algo; fin;
Procedimiento [Link];
comenzar
Writeln('En HacerAlgo'); fin;
era
X,Y : TMiObjeto;
P1,P2: TAlgúnMétodo;
comenzar
X:=[Link];
Y:=[Link];
P1:=@[Link] algo;
P2:=@[Link]; Writeln('Mismo
método: ',P1=P2);
fin.
Si se deben comparar ambos punteros, se debe realizar un encasillamiento en TMethod y se deben comparar ambos
punteros. TMethod se define en la unidad del sistema de la siguiente manera:
TMétodo = registro
Tipo
TSomeMethod = Procedimiento del objeto;
TMiObjeto = Clase(TOobjeto)
Procedimiento Hacer algo; fin;
Procedimiento [Link];
comenzar
Writeln('En HacerAlgo'); fin;
era
X,Y : TMiObjeto;
P1,P2: Método T;
comenzar
X:=[Link];
Y:=[Link];
P1:=TMétodo(@[Link]);
P2:=TMétodo(@[Link]);
60
Machine Translated by Google
CAPÍTULO 3. TIPOS
3.7.1 Definición
A partir de la versión 1.1, FPC admite variantes. Para obtener el máximo soporte de variantes, se recomienda
agregar la unidad de variantes a la cláusula de usos de cada unidad que use variantes de alguna manera: la
unidad de variantes contiene soporte para examinar y transformar variantes distintas al soporte predeterminado
ofrecido por las unidades System u ObjPas.
El tipo de valor almacenado en una variante solo se determina en tiempo de ejecución: depende de lo que se
haya asignado a la variante. Casi cualquier tipo simple se puede asignar a variantes: tipos ordinales, tipos de
cadena, tipos int64.
Los tipos estructurados como conjuntos, registros, matrices, archivos, objetos y clases no son compatibles con
la asignación de una variante, ni con los punteros. A una variante se pueden asignar interfaces y objetos COM o
CORBA (básicamente porque son simplemente un puntero).
Tipo
TMyEnum = (Uno, Dos, Tres);
Era
V: Variante;
I: Entero;
B: Byte;
W: Palabra;
P: Int64;
E: Extendido;
D: Doble;
Pero: TMyEnum;
COMO: AnsiString;
WS: cadena ancha;
comenzar
V:=yo;
V:=B;
V:=W;
V:=Q;
V:=E;
P:=Y;
V:=D;
V:=COMO;
V:=WS;
fin;
Una variante puede contener una matriz de valores: todos los elementos de la matriz tienen el mismo tipo (pero pueden
ser del tipo “variante”). Para una variante que contiene una matriz, la variante se puede indexar:
61
Machine Translated by Google
CAPÍTULO 3. TIPOS
utiliza variantes;
Era
Una variante;
yo: número entero;
comenzar
A:=VarArrayCreate([1,10],varInteger);
Para I:=1 a 10 hacer
A[Yo]:=Yo; fin.
Tenga en cuenta que cuando la matriz contiene una cadena, no se considera una "matriz de caracteres" y, por lo tanto, la
variante no se puede indexar para recuperar un carácter en una determinada posición de la cadena.
Como puede verse en la definición anterior, la mayoría de los tipos simples se pueden asignar a una variante.
Asimismo, se puede asignar una variante a un tipo simple: Si es posible, el valor de la variante se convertirá al
tipo que se está asignando. Esto puede fallar: la asignación de una variante que contiene una cadena a un
número entero fallará a menos que la cadena represente un número entero válido. En el siguiente ejemplo, la
primera tarea funcionará, la segunda fallará:
utiliza variantes;
Era
V: Variante;
I: Entero;
comenzar
V:='100';
Yo:=V;
Writeln('Yo: ',Yo);
V:='Algo más';
Yo:=V;
Writeln('Yo: ',Yo);
fin.
La primera asignación funcionará, pero la segunda no, ya que algo más no se puede convertir en un valor entero válido.
El resultado será una excepción EConvertError.
El resultado de una expresión que involucra una variante volverá a ser del tipo variante, pero se puede asignar
a una variable de un tipo diferente, si el resultado se puede convertir a una variable de este tipo.
Tenga en cuenta que las expresiones que incluyen variantes tardan más en evaluarse y, por lo tanto, deben
usarse con precaución. Si es necesario realizar muchos cálculos, es mejor evitar el uso de variantes.
Al considerar conversiones de tipos implícitas (por ejemplo, byte a entero, entero a doble, carácter a cadena), el
compilador ignorará las variantes a menos que una variante aparezca explícitamente en la expresión.
62
Machine Translated by Google
CAPÍTULO 3. TIPOS
Observación La compatibilidad con la interfaz de envío para variantes actualmente no funciona en el compilador.
Las variantes pueden contener una referencia a una interfaz: una interfaz normal (que desciende de IInterface) o
una interfaz de despacho (que desciende de IDispatch). Se pueden usar variantes que contienen una referencia a
una interfaz de despacho para controlar el objeto detrás de él: el compilador usará un enlace tardío para realizar
la llamada a la interfaz de despacho: no habrá verificación en tiempo de ejecución de los nombres de las funciones
y los parámetros o argumentos dados. a las funciones. El tipo de resultado tampoco se verifica. El compilador
simplemente insertará código para realizar la llamada de envío y recuperar el resultado.
Era
W: Variante;
V: Cadena;
comenzar
W:=CreateOleObject('[Link]'); V:=[Link];
Writeln('La versión instalada de MS Word
es: ',V); fin;
La línea
V:=[Link];
se ejecuta insertando el código necesario para consultar la interfaz de despacho almacenada en la variante W, y
ejecuta la llamada si se encuentra la información de despacho necesaria.
Los alias de tipo son una forma de darle otro nombre a un tipo, pero también se pueden utilizar para crear tipos realmente nuevos.
Cuál de los dos depende de la forma en que se define el alias de tipo:
Escriba alias
Tipo
MiEntero = Entero;
Esto crea un nuevo nombre para hacer referencia al tipo Integer, pero no crea un nuevo tipo real. Es
decir, dos variables:
Era
A: MiEntero;
B: Entero;
63
Machine Translated by Google
CAPÍTULO 3. TIPOS
En realidad, tendrá el mismo tipo desde el punto de vista del compilador (es decir, Integer).
Lo anterior presenta una manera de hacer que los tipos sean independientes de la plataforma, utilizando solo los tipos
de alias y luego definiendo estos tipos para cada plataforma individualmente. Cualquier programador que luego utilice
estos tipos personalizados no tiene que preocuparse por el tamaño del tipo subyacente: le resulta opaco. También
permite utilizar nombres de acceso directo para nombres de tipos completos. por ejemplo, defina [Link] como
Olongint y luego redefina longint.
Unidad A;
Interfaz
Utiliza B;
Escriba
MiTipo = [Link];
Esta construcción se ve a menudo después de alguna refactorización, al mover algunas declaraciones de la unidad A a la
unidad B, para preservar la compatibilidad con versiones anteriores de la interfaz de la unidad A.
Tipo
MiInteger = Escriba un número entero;
Esto no solo crea un nuevo nombre para hacer referencia al tipo Integer, sino que también crea un nuevo tipo. Es
decir, dos variables:
Era
A: MiEntero;
B: Entero;
No tendrá el mismo tipo desde el punto de vista del compilador. Sin embargo, estos dos tipos serán compatibles con la
asignación. Eso significa que una tarea
A:=B;
trabajará.
Si TypeInfo(MyInteger)<>TypeInfo(Integer) entonces
Writeln('MyInteger e Integer son tipos diferentes');
La función del compilador TypeInfo devuelve un puntero a la información de tipo en el binario. Dado que los dos tipos
MyInteger e Integer son diferentes, generarán bloques de información de diferentes tipos y los punteros serán diferentes.
1. Que tienen diferente tipo de información, por lo tanto, diferente RTTI (Información de tipo de tiempo de ejecución).
64
Machine Translated by Google
CAPÍTULO 3. TIPOS
funcionará también.
De forma predeterminada, los tipos pascal no están administrados. Eso significa que las variables deben inicializarse,
finalizarse, asignarse memoria, etc. Sin embargo, en Object Pascal se gestionan varios tipos, lo que significa que el
compilador inicializa y finaliza variables de este tipo: esto es necesario, por ejemplo, para tipos de datos contados por
referencia.
Las instancias de clase que contienen tipos administrados también se inicializan, pero el puntero de la instancia de clase en
sí no.
Las variables de tipos gestionados también se finalizarán: esto significa, en general, que su recuento de referencias
disminuirá a más tardar al final del alcance actual.
Observación Tenga en cuenta que no se debe hacer ninguna suposición sobre el momento exacto de esta finalización. todo eso es guaran
Asegúrese de que estén finalizados cuando salgan del alcance.
sesenta y cinco
Machine Translated by Google
Capítulo 4
variables
4.1 De nición
Las variables son ubicaciones de memoria nombradas explícitamente con un tipo determinado. Al asignar valores a
las variables, el compilador de Free Pascal genera código de máquina para mover el valor a la ubicación de memoria
reservada para esta variable. El lugar donde se almacena esta variable depende de dónde se declara:
• Las variables globales son variables declaradas en una unidad o programa, pero no dentro de un
procedimiento o función. Se almacenan en ubicaciones fijas de memoria y están disponibles durante todo el
tiempo de ejecución del programa.
• Las variables locales se declaran dentro de un procedimiento o función. Su valor se almacena en el pro
pila de gramos, es decir, no en ubicaciones fijas.
El compilador de Free Pascal gestiona la asignación de estas ubicaciones de memoria de forma transparente, aunque
esta ubicación puede verse influenciada en la declaración.
El compilador de Free Pascal también maneja la lectura o escritura de valores en las variables de forma
transparente. Pero incluso esto puede ser manejado explícitamente por el programador cuando usa propiedades.
Las variables deben declararse explícitamente cuando sean necesarias. No se asigna memoria a menos que se
declare una variable. El uso de un identificador de variable (por ejemplo, una variable de bucle) que no se declara
primero es un error que será informado por el compilador.
4.2 Declaración
Las variables deben declararse en un bloque de declaración de variables de una unidad o de un procedimiento o función
(sección 16.5, página 252). Tiene el siguiente aspecto:
declaración de variables
66
Machine Translated by Google
CAPÍTULO 4. VARIABLES
Era
curterm1: número entero;
curterm11: entero = 1;
1. La primera forma (curterm1) define una variable regular. El compilador gestiona todo mediante
sí mismo.
2. La segunda forma (curterm2) declara también una variable normal, pero especifica que el nombre del ensamblador
para esta variable es igual al nombre de la variable tal como está escrito en el código fuente.
3. La tercera forma (curterm3) declara una variable que está ubicada externamente: el compilador asumirá que la
memoria está ubicada en otro lugar y que el nombre del ensamblador de esta ubicación está especificado por
el nombre de la variable, tal como está escrito en la fuente. Es posible que no se especifique el nombre.
4. La cuarta forma es completamente equivalente a la tercera, declara una variable que se almacena
externamente y proporciona explícitamente el nombre del ensamblador de la ubicación. Si no se utiliza
cvar, se debe especificar el nombre.
5. La quinta forma es una variante de la cuarta forma, solo el nombre de la biblioteca en la que se encuentra la memoria.
está reservado también se especifica.
6. La sexta forma declara una variable (curterm6) y le dice al compilador que está almacenada en el
misma ubicación que otra variable (curterm1).
7. La séptima forma declara una variable (curterm7) y le dice al compilador que la etiqueta del ensamblador de esta
variable debe ser el nombre de la variable (distingue entre mayúsculas y minúsculas) y debe hacerse pública.
es decir, se puede hacer referencia a él desde otros archivos objeto.
67
Machine Translated by Google
CAPÍTULO 4. VARIABLES
9. Las formas novena y décima son equivalentes: especifican el nombre del ensamblador de la variable.
10. la undécima forma declara una variable (curterm11) y la inicializa con un valor (1 en el
caso anterior).
Tenga en cuenta que los nombres de los ensambladores deben ser únicos. No es posible declarar o exportar dos
variables con el mismo nombre de ensamblador. En particular, no intente exportar variables con un nombre público
que comience con FPC_; el compilador utiliza algunas rutinas internas del sistema con este nombre.
4.3 Alcance
Las variables, como cualquier identificador, obedecen a las reglas generales de alcance. Además, las variables inicializadas
se inicializan cuando entran en el alcance:
• Las variables globalmente inicializadas se inicializan una vez, cuando se inicia el programa.
• Las variables locales inicializadas se inicializan cada vez que se ingresa al procedimiento.
Tenga en cuenta que el comportamiento de las variables inicializadas locales es diferente del de una constante escrita local.
Una constante escrita local se comporta como una variable inicializada global.
Por defecto, las variables simples en Pascal no se inicializan después de su declaración. Cualquier suposición de que
contengan 0 o cualquier otro valor predeterminado es errónea: pueden contener basura. Para remediar esto, existe
el concepto de variables inicializadas. La diferencia con las variables normales es que su declaración incluye un valor
inicial, como se puede ver en el diagrama del apartado anterior.
1. Los tipos administrados son una excepción a esta regla: los tipos administrados siempre se inicializan con un
valor predeterminado: en general, esto significa establecer el recuento de referencia en cero o establecer el
valor del puntero del tipo en Nil. Ver sección 3.9, página 65
Tenga en cuenta que el comportamiento de poner a cero ciertas variables puede generar contenido no válido para las variables:
Tipo
TWeekDays =
(lunes,martes,miércoles,jueves,viernes,sábado,domingo);
TWeekend = sábado..domingo;
era
W: TFin de semana;
comenzar
Escribir(W);
fin.
68
Machine Translated by Google
CAPÍTULO 4. VARIABLES
Por lo tanto, es muy recomendable inicializar siempre las variables antes de usarlas.
Era
S: Cadena = 'Esta es una cadena inicializada';
El valor de la siguiente variable se inicializará con el valor proporcionado. La siguiente es una forma aún mejor de hacer
esto:
constante
Era
S: Cadena = SDpredeterminado;
La inicialización se utiliza a menudo para inicializar matrices y registros. Para las matrices, los elementos inicializados
deben especificarse, encerrados entre corchetes y separados por comas. El número de elementos inicializados debe ser
exactamente igual al número de elementos en la declaración del tipo. Como ejemplo:
Era
tt: matriz[1..3] de cadena[20] = ('no','gij','hij'); ti : matriz[1..3] de Entero largo = (1,2,3);
Para registros constantes, cada elemento del registro que desea inicializar debe especificarse en el formulario Campo:
Valor, separado por punto y coma y entre corchetes. Puede omitir los campos que no desea inicializar, de hecho puedes
omitir todos los campos. Si omite campos, el compilador emitirá una advertencia.
Como ejemplo:
Tipo
Punto = registro
X,Y : Final real;
Era
Origen: Punto = (X:0.0; Y:0.0); Parcial: Punto = (X:0.0);
Vacío: Punto = ();
[Link](7,27) Advertencia: algunos campos que vienen después de "X" no se inicializaron [Link](8,20) Advertencia: algunos campos
que vienen después de "" no fueron inicializados
El orden de los campos en un registro constante debe ser el mismo que en la declaración de tipo; de lo contrario, se
producirá un error en tiempo de compilación.
Observación Cabe destacar que las variables inicializadas se inicializan cuando entran en el alcance, a diferencia de las
constantes escritas, que se inicializan al inicio del programa. Esto también es válido para las inicializaciones locales.
69
Machine Translated by Google
CAPÍTULO 4. VARIABLES
variables. Las variables localizadas inicializadas se inicializan cada vez que se llama a la rutina. Cualquier
cambio que haya ocurrido en la invocación anterior de la rutina se deshará porque se inicializarán nuevamente.
Observación Se debe tener cuidado al utilizar tipos de punteros inicializados como PChars. En los siguientes ejemplos,
S es un puntero que apunta a un bloque de datos de programa constantes (de sólo lectura). Por lo tanto, no
funcionará asignar un carácter en la cadena. Por supuesto, la asignación de S en sí misma funcionará. La
primera rutina dará error, la segunda no:
procedimiento fo1;
era
s:PChar = 'PChar'; comenzar
s[0] :=
'a'; fin;
procedimiento foo2;
era
s: PCar;
comenzar
s := 'PChar'; s[0] :=
'un'; fin;
Algunas variables deben inicializarse porque contienen tipos administrados. Para las variables que se
declaran en la sección var de una función o en el programa principal, esto sucede automáticamente. Para
las variables que se asignan en el montón, este no es necesariamente el caso.
Para ello, el compilador contiene el intrínseco predeterminado. Esta función acepta un identificador de tipo
como argumento y devolverá una variable correctamente inicializada de ese tipo. En esencia, pondrá a
cero toda la variable.
tipo
TRegistro = registro
i: Int Largo; s:
AnsiString; fin;
era
i: Int Largo; o:
TObjeto; r:
TRegistro;
comenzar
i := Predeterminado(LongInt); // 0 o :=
Predeterminado(TObject); // Nil r :=
Predeterminado(TRecord); // (yo: 0; s: '')
fin.
tipo
TRegistro = registro
70
Machine Translated by Google
CAPÍTULO 4. VARIABLES
i: Int Largo; s:
AnsiString; fin;
era
yo: ^LongInt; o:
^TObjeto; r:
^TRegistro; comenzar
i:=GetMem(SizeOf(Longint)); i^ :=
Predeterminado(LongInt); // 0
o:=GetMem(SizeOf(TObject)); o^ :=
Predeterminado(TObject); // Nil
r:=GetMem(SizeOf(TRecord)); r^ :=
Predeterminado(TRecord); // (yo: 0; s: '')
fin.
Funciona para todos los tipos, excepto para los distintos tipos de archivos (o tipos complejos que contienen un tipo de archivo).
Observación
• Para los genéricos, el uso de Default es especialmente útil, ya que es posible que no se conozca el tipo de una
variable durante la declaración de un genérico. Para más información sección 8.7, página 134.
• Los resultados de las funciones están disponibles como un identificador de resultados y, como tales, se parecen a
las variables. No son variables, pero se tratan como parámetros pasados por referencia. Por tanto, no se inicializan.
Para un programa que utiliza subprocesos, las variables pueden ser realmente globales, es decir, las mismas para todos los
subprocesos, o locales: esto significa que cada subproceso obtiene una copia de la variable. Las variables locales (definidas
dentro de un procedimiento) son siempre locales de subproceso. Las variables globales normalmente son las mismas para
todos los subprocesos. Una variable global se puede declarar local de subproceso reemplazando la palabra clave var al inicio
del bloque de declaración de variable con Threadvar:
Varilla de subprocesos
IOResultado: Entero;
Si no se utilizan subprocesos, la variable se comporta como una variable ordinaria. Si se utilizan subprocesos, se realiza
una copia para cada subproceso (incluido el subproceso principal). Tenga en cuenta que la copia se realiza con el valor
original de la variable, no con el valor de la variable en el momento en que se inicia el hilo.
Threadvars debe usarse con moderación: existe una sobrecarga para recuperar o establecer el valor de la variable. Si
es posible, considere utilizar variables locales; siempre son más rápidos que las variables de hilo.
Los hilos no están habilitados de forma predeterminada. Para obtener más información sobre la programación de subprocesos, consulte el
capítulo sobre subprocesos en la Guía del programador.
4.7 Propiedades
Un bloque global puede declarar propiedades, tal como se podrían definir en una clase. La diferencia es que la propiedad
global no necesita una instancia de clase: solo hay una instancia de esta propiedad. Otro
71
Machine Translated by Google
CAPÍTULO 4. VARIABLES
Más allá de eso, una propiedad global se comporta como una propiedad de clase. Los especificadores de lectura/escritura para la
propiedad global también deben ser procedimientos regulares, no métodos.
Propiedades
Lo siguiente es un ejemplo:
Interfaz
Propiedad
MyProp: Entero Leer GetMyInt Escribir SetMyInt;
Implementación
72
Machine Translated by Google
CAPÍTULO 4. VARIABLES
Utiliza sysutils;
Era
FMyInt: Entero;
comenzar
Resultado:=FMyInt; fin;
comenzar
Si ((Valor mod 2)=1) entonces
Raise [Link]('MyProp solo puede contener valores pares');
FMyInt:=Valor; fin;
fin.
Los especificadores de lectura/escritura se pueden ocultar declarándolos en otra unidad que debe estar en la cláusula de
usos de la unidad. Esto se puede utilizar para ocultar los especificadores de acceso de lectura/escritura para los
programadores, como si estuvieran en una sección privada de una clase (que se analiza a continuación). Para el ejemplo
anterior, esto podría verse así:
Interfaz
Implementación
Utiliza sysutils;
Era
FMyInt: Entero;
comenzar
Resultado:=FMyInt; fin;
comenzar
Si ((Valor mod 2)=1) entonces
Raise [Link]('Sólo se permiten valores pares');
FMyInt:=Valor;
73
Machine Translated by Google
CAPÍTULO 4. VARIABLES
fin;
fin.
Interfaz
utiliza prueba;
Propiedad
MyProp: Entero Leer GetMyInt Escribir SetMyInt;
Implementación
fin.
Puede encontrar más información sobre las propiedades en el capítulo 6, página 86.
74
Machine Translated by Google
Capítulo 5
Objetos
5.1 Declaración
Free Pascal admite la programación orientada a objetos. De hecho, la mayor parte del compilador está escrito
utilizando objetos. Aquí presentamos algunas preguntas técnicas sobre la programación orientada a objetos en
Free Pascal.
Los objetos deben tratarse como un tipo especial de registro. El registro contiene todos los
campos declarados en la definición de objetos y punteros a los métodos asociados al tipo de objetos.
Un objeto se declara del mismo modo que se declararía un registro; excepto que ahora, los procedimientos y
funciones pueden declararse como si fueran parte del registro. Los objetos pueden "heredar" campos y métodos de
objetos "principales". Esto significa que estos campos y métodos se pueden utilizar como si estuvieran incluidos en
los objetos declarados como objetos "secundarios".
Los campos o métodos declarados privados solo son accesibles en la unidad actual: su alcance
se limita a la implementación de la unidad actual.
La declaración prototipo de un objeto es la siguiente:
tipos de objetos
lista de componentes
especificador de visibilidad de objetos 6 definición constante
6 definición de campo 6 definición del método
75
Machine Translated by Google
CAPÍTULO 5. OBJETOS
Como puede verse, cualquier bloque de visibilidad se puede especificar varias veces, tantas veces como sea necesario.
Tipo
TObj = objeto
Privado
Título: Cadena corta;
Público
Inicio del constructor;
Destructor hecho;
Procedimiento SetCaption (AValue: String);
Función GetCaption: Cadena; fin;
Contiene un par constructor/destructor y un método para obtener y establecer un título. El campo Título es
privado para el objeto: no se puede acceder a él fuera de la unidad en la que se declara TObj.
Observación En el modo MacPas, la palabra clave Object se reemplaza por la palabra clave class para compatibilidad
con otros compiladores Pascal disponibles en Mac. Eso significa que los objetos no se pueden utilizar en modo
MacPas.
Observación Free Pascal también soporta el objeto empaquetado. Esto es lo mismo que un objeto, solo que los elementos (campos)
del objeto están alineados en bytes, tal como en el registro empaquetado. La declaración de un objeto empaquetado es
similar a la declaración de un registro empaquetado:
Tipo
TObj = objeto empaquetado
Inicio del constructor;
...
fin;
Pobj = ^TObj;
Var PP: Pobj;
Un objeto puede declararse sellado. En ese caso, no es posible declarar un objeto descendiente.
El compilador devolverá un error si encuentra una declaración de un descendiente:
Tipo
TMyClass = objeto sellado
x : número
entero; fin;
TMiClase2 = objeto(TMiClase)
76
Machine Translated by Google
CAPÍTULO 5. OBJETOS
Y: Entero; fin;
empezar
fin.
Una clase abstracta es una clase que no se puede utilizar directamente. En su lugar, siempre se debe utilizar una clase
descendiente. Sin embargo, por compatibilidad con Delphi, el compilador ignora esta directiva.
5.3 Campos
Los campos de objeto son como campos de registro. Se accede a ellos de la misma manera que se accedería a un
campo de registro: utilizando un identificador calificado. Dada la siguiente declaración:
[Link]:= 0;
Dentro de los métodos, se puede acceder a los campos utilizando el identificador corto:
Procedimiento [Link];
comenzar
...
Campo A := 0;
...
fin;
O bien, se puede utilizar el autoidentificador. El autoidentificador se refiere a la instancia actual del objeto:
Procedimiento [Link];
comenzar
...
[Link]:= 0;
...
fin;
No se puede acceder a campos que se encuentran en secciones privadas o protegidas de un objeto desde fuera de
los métodos de los objetos. Si se intenta esto de todos modos, el compilador se quejará de un identificador desconocido.
También es posible utilizar la instrucción with con una instancia de objeto, tal como con un registro:
Fuera := 12;
Un método;
fin;
77
Machine Translated by Google
CAPÍTULO 5. OBJETOS
En este ejemplo, entre el principio y el final, es como si se antepusiera AnObject a los identificadores Afield y Amethod.
Más sobre esto en la sección 13.2.8, página 204.
Un objeto puede contener campos de clase o estáticos: estos campos son globales para el tipo de objeto y actúan
como variables globales, pero solo se conocen en el alcance del objeto. La diferencia entre variables estáticas y de
clase es puramente el modo en que funcionan: la palabra clave estática siempre funcionará, la palabra clave de clase
necesitará {$MODE DELPHI} o {$MODE OBJFPC}.
Se puede hacer referencia a ellos desde dentro de los métodos del objeto, pero también se puede hacer referencia a ellos
desde fuera del objeto proporcionando el nombre completo.
cl=objeto l: entero
largo; estático; clase var v: entero;
fin;
era
cl1,cl2 : cl;
comenzar
Writeln('Estático'); cl1.l:=2;
writeln(cl2.l);
cl2.l:=3; writeln(cl1.l);
Writeln(cl.l);
Writeln('Clase'); cl1.v:=4;
escribir(cl2.v); cl2.v:=5;
writeln(cl1.v); Writeln(cl.v);
fin.
será el siguiente
Estático
2
3
3
Clase
4
5
5
Tenga en cuenta que la última línea de código hace referencia al tipo de objeto en sí (cl) y no a una instancia del
objeto (cl1 o cl2).
78
Machine Translated by Google
CAPÍTULO 5. OBJETOS
Como se puede ver en el diagrama de sintaxis de una declaración de objeto, Free Pascal admite
constructores y destructores. El programador es responsable de llamar explícitamente al constructor y al
destructor cuando utiliza objetos.
La declaración de un constructor o destructor es la siguiente:
Se requiere un par constructor/destructor si el objeto usa métodos virtuales. La razón es que para
un objeto con métodos virtuales, se debe realizar alguna limpieza interna: esta limpieza la realiza
el constructor1.
En la declaración del tipo de objeto, se debe utilizar un identificador simple para el nombre del
constructor o destructor. Cuando se implementa el constructor o destructor, se debe utilizar un
identificador de método calificado, es decir, un identificador de la forma [Link].
Free Pascal también soporta la sintaxis extendida de los procedimientos New y Dispose. En caso de que
se deba asignar una variable dinámica de un tipo de objeto, el nombre del constructor se puede especificar
en la llamada a Nuevo. New se implementa como una función que devuelve un puntero al objeto instanciado.
Considere las siguientes declaraciones:
Tipo
TObj = objeto;
Inicio del constructor;
...
fin;
Pobj = ^TObj;
Var PP: Pobj;
nuevo(pp,calor);
y también
79
Machine Translated by Google
CAPÍTULO 5. OBJETOS
nuevo
(páginas); pp^.init;
En el último caso, el compilador emitirá una advertencia de que se debe utilizar la sintaxis extendida de
new y dispose para generar instancias de un objeto. Es posible ignorar esta advertencia, pero es una
mejor práctica de programación utilizar la sintaxis extendida para crear instancias de un objeto. De
manera similar, el procedimiento Dispose acepta el nombre de un destructor. Luego se llamará al
destructor, antes de eliminar el objeto del montón.
En vista de la advertencia del compilador, el siguiente capítulo presenta el enfoque Delphi para la
programación orientada a objetos, y puede considerarse una forma más natural de programación
orientada a objetos.
5.6 Métodos
Los métodos de objetos son como procedimientos o funciones ordinarias, sólo que tienen un parámetro
extra implícito: self. Self apunta al objeto con el que se invocó el método. Al implementar métodos, se
debe proporcionar el identificador completo en el encabezado de la función. Al declarar métodos, se
debe proporcionar un identificador normal.
5.6.1 Declaración
La declaración de un método es muy parecida a una declaración de función o procedimiento normal, con algunos
especificadores adicionales, como se puede ver en el siguiente diagrama, que forma parte de la declaración de objeto:
métodos
directivas de método
virtual ; modificadores de llamadas ;
abstracto ;
Desde el punto de vista de las declaraciones, las definiciones de métodos son declaraciones de funciones o
procedimientos normales. A diferencia de TP y Delphi, los campos se pueden declarar después de los métodos
en el mismo bloque, es decir, lo siguiente generará un error al compilar con Delphi o Turbo Pascal, pero no con FPC:
Tipo
MiObj = Objeto
Campo: Entero largo;
Procedimiento Debe; fin;
80
Machine Translated by Google
CAPÍTULO 5. OBJETOS
Los métodos normales (estáticos) son métodos que se han declarado sin una palabra clave abstracta o
virtual. Al llamar a un método estático, se utiliza el método declarado (es decir, en tiempo de compilación)
del objeto. Por ejemplo, considere las siguientes declaraciones:
Tipo
TParent = Objeto
...
procedimiento Debe;
...
fin;
PParente = ^TParente;
TChild = Objeto(TParent)
...
procedimiento Debe;
...
fin;
PCniño = ^TNiño;
Como puede verse, tanto el objeto padre como el hijo tienen un método llamado Doit. Consideremos ahora las
siguientes declaraciones y llamamientos:
Era
ParentA,ParentB : PParent;
Niño : Pniño;
comenzar
ParentA := Nuevo(PParent,Init); PadreB :=
Nuevo(PChild,Init); Niño := Nuevo(PChild,Init);
PadreA^.Doit; PadreB^.Doit; Niño^.Hacerlo;
De las tres invocaciones de Doit, sólo la última llamará a [Link], las otras dos llamadas llamarán
a [Link]. Esto se debe a que, para los métodos estáticos, el compilador determina en el momento
de la compilación qué método se debe llamar. Dado que ParentB es de tipo TParent, el compilador
decide que se debe llamar con [Link], aunque se creará como TChild. Puede haber ocasiones
en las que el método que realmente se llama dependa del tipo real de objeto en tiempo de ejecución.
Si es así, el método no puede ser un método estático, sino que debe ser un método virtual.
Métodos virtuales
Para remediar la situación del apartado anterior, se crean métodos virtuales. Esto simplemente se hace
agregando a la declaración del método el modificador virtual. Luego, el objeto descendiente puede anular
el método con una nueva implementación volviendo a declarar el método (con la misma lista de
parámetros) usando la palabra clave virtual.
Volviendo al ejemplo anterior, considere la siguiente declaración alternativa:
Tipo
TParent = Objeto
...
81
Machine Translated by Google
CAPÍTULO 5. OBJETOS
procedimiento Deberá;virtual;
...
fin;
PParente = ^TParente;
TChild = Objeto(TParent)
...
procedimiento Deberá;virtual;
...
fin;
PCniño = ^TNiño;
Como puede verse, tanto el objeto padre como el hijo tienen un método llamado Doit. Consideremos ahora las
siguientes declaraciones y llamamientos:
Era
ParentA,ParentB : PParent;
Niño : Pniño;
comenzar
ParentA := Nuevo(PParent,Init); PadreB :=
Nuevo(PChild,Init); Niño := Nuevo(PChild,Init);
PadreA^.Doit; PadreB^.Doit; Niño^.Hacerlo;
Ahora, se llamarán diferentes métodos, dependiendo del tipo de tiempo de ejecución real del objeto.
Para ParentA, nada cambia, ya que se crea como una instancia de TParent. Para Child, la situación
tampoco cambia: se vuelve a crear como una instancia de TChild.
Sin embargo, para ParentB la situación cambia: aunque fue declarado como TParent, se crea
como una instancia de TChild. Ahora, cuando se ejecuta el programa, antes de llamar a Doit, el
programa comprueba cuál es el tipo real de ParentB y sólo entonces decide qué método se debe llamar.
Al ver que ParentB es del tipo TChild, se llamará a [Link]. El compilador inserta el código para esta
verificación en tiempo de ejecución del tipo real de un objeto en el momento de la compilación.
Se dice que [Link] anula [Link]. Es posible acceder al
[Link] desde [Link], con la palabra clave heredada:
Procedimiento [Link];
En el ejemplo anterior, cuando se llama a [Link], lo primero que hace es llamar a [Link].
La palabra clave heredada no se puede utilizar en métodos estáticos, sólo en métodos virtuales.
Para poder hacer esto, el compilador mantiene – por tipo de objeto – una tabla con métodos virtuales:
la VMT (Virtual Method Table). Esto es simplemente una tabla con punteros a cada uno de los métodos
virtuales: cada método virtual tiene su ubicación fija en esta tabla (un índice). El compilador usa esta
tabla para buscar el método real que debe usarse. Cuando un objeto descendiente anula un método, la
entrada del método principal se sobrescribe en el VMT. Puede encontrar más información sobre VMT
en la Guía del programador.
Como se comentó anteriormente, los objetos que tienen un VMT deben inicializarse con un
constructor: la variable del objeto debe inicializarse con un puntero al VMT del tipo real con el que se creó.
82
Machine Translated by Google
CAPÍTULO 5. OBJETOS
Métodos abstractos
Tipo
TParent = Objeto
...
procedimiento Debe;virtual;abstracto;
...
fin;
PParente=^TParente;
TChild = Objeto(TParent)
...
procedimiento Deberá;virtual;
...
fin;
PCniño = ^TNiño;
Como puede verse, tanto el objeto padre como el hijo tienen un método llamado Doit. Consideremos ahora las
siguientes declaraciones y llamamientos:
Era
ParentA,ParentB : PParent;
Niño : Pniño;
comenzar
ParentA := Nuevo(PParent,Init); PadreB :=
Nuevo(PChild,Init); Niño := Nuevo(PChild,Init);
PadreA^.Doit; PadreB^.Doit; Niño^.Hacerlo;
En primer lugar, la línea 3 generará un error del compilador, indicando que no se pueden generar instancias
de objetos con métodos abstractos: El compilador ha detectado que PParent apunta a un objeto que tiene
un método abstracto. Comentar la línea 3 permitiría la compilación del programa.
Observación Si se anula un método abstracto, no se puede llamar al método principal con herencia, ya que
no existe un método principal; El compilador detectará esto y se quejará de esta manera:
Si, a través de algún mecanismo, se llama a un método abstracto en tiempo de ejecución, se producirá un error en tiempo de ejecución.
(error de tiempo de ejecución 211, para ser precisos)
83
Machine Translated by Google
CAPÍTULO 5. OBJETOS
Los métodos de clase o métodos declarados con la directiva estática son métodos globales para el tipo de objeto.
Cuando se llama, el puntero "auto" implícito no está disponible. Esto significa que no se pueden llamar métodos
normales y no se puede acceder a ninguno de los campos de un objeto. Sin embargo, se pueden utilizar variables de
clase.
Los métodos de clase o estáticos son métodos regulares y se pueden asignar a una variable de procedimiento.
Era
P: Procedimiento;
comenzar
P:=@[Link];
P:=@[Link];
84
Machine Translated by Google
CAPÍTULO 5. OBJETOS
P:=@TTest.Testproc2;
P:=@TTest.Testproc3; fin.
Descomentar una de las declaraciones comentadas e intentar compilar el código resultante generará un error de
compilación:
5.7 Visibilidad
Para los objetos, existen tres especificadores de visibilidad: privado, protegido y público. Si no se especifica un
especificador de visibilidad, se asume público. Tanto los métodos como los campos se pueden ocultar a un programador
colocándolos en una sección privada. La regla de visibilidad exacta es la siguiente:
Privado Sólo se puede acceder a todos los campos y métodos que están en un bloque privado en el módulo (es decir,
unidad o programa) que contiene la definición del objeto. Se puede acceder a ellos desde dentro de los métodos
del objeto o desde fuera de ellos, por ejemplo, desde métodos de otros objetos o funciones globales.
Protegido Es lo mismo que Privado, excepto que los miembros de una sección Protegida también son accesibles para
los tipos descendientes, incluso si están implementados en otros módulos.
Los campos y métodos públicos siempre son accesibles, desde cualquier lugar. Los campos y métodos de una sección
pública se comportan como si fueran parte de un tipo de registro normal.
85
Machine Translated by Google
Capítulo 6
Clases
En el enfoque Delphi de la Programación Orientada a Objetos, todo gira en torno al concepto de “Clases”. Una
clase puede verse como un puntero a un objeto o un puntero a un registro, con métodos asociados a él.
La diferencia entre objetos y clases es principalmente que un objeto se asigna en la pila, como lo haría un registro
normal, y que las clases siempre se asignan en el montón. En el siguiente ejemplo:
Era
A: TAlgúnObjeto; // un objeto
B: TAlgunaClase; // una clase
La principal diferencia es que la variable A ocupará tanto espacio en la pila como el tamaño del objeto
(TSomeObject). La variable B, por otro lado, siempre tomará sólo el tamaño de un puntero en la pila. Los datos de
clase reales están en el montón.
De esto se desprende una segunda diferencia: una clase siempre debe inicializarse a través de su constructor,
mientras que para un objeto esto no es necesario. Llamar al constructor asigna la memoria necesaria en el
montón para los datos de la instancia de clase.
Observación En versiones anteriores de Free Pascal era necesario, para poder usar clases, poner la unidad objpas en la
cláusula de usos de una unidad o programa. Esto ya no es necesario a partir de la versión 0.99.12. A partir de
esta versión, la unidad se cargará automáticamente cuando se especifiquen las opciones MObjfpc o MDelphi,
o se utilicen sus correspondientes directivas:
De hecho, el compilador dará una advertencia si encuentra la unidad objpas en una cláusula de usos.
tipos de clases
86
Machine Translated by Google
CAPÍTULO 6. CLASES
interfaces implementadas 6,
identificador de interfaz
lista de componentes
especificador de visibilidad 6 definición de campo
6 declaración constante
declaración de tipo de parte
declaración de variable de parte
declaración de variable de clase de parte
definición de método
de parte definición de propiedad
dinámica ; modificadores de llamadas ;
Observación En el modo MacPas, la palabra clave Object se reemplaza por la palabra clave class para compatibilidad con
otros compiladores Pascal disponibles en Mac. Eso significa que en el modo MacPas, la palabra reservada
"clase" en el diagrama anterior puede ser reemplazada por la palabra reservada "objeto".
En una declaración de clase, se pueden utilizar tantos bloques de visibilidad como sea necesario: los distintos
bloques se pueden repetir y no hay un orden especial en el que deban aparecer.
Los métodos son declaraciones de funciones o procedimientos normales. Como puede verse, la declaración de
una clase es casi idéntica a la declaración de un objeto. La verdadera diferencia entre objetos y clases está en
la forma en que se crean (ver más adelante en este capítulo).
87
Machine Translated by Google
CAPÍTULO 6. CLASES
Privado A todos los campos y métodos que están en un bloque privado, solo se puede acceder en el módulo (es
decir, unidad) que contiene la definición de clase. Se puede acceder a ellos desde dentro de los métodos de
las clases o desde fuera de ellos (por ejemplo, desde los métodos de otras clases).
Privado estricto Solo se puede acceder a todos los campos y métodos que se encuentran en un bloque privado estricto
desde los métodos de la propia clase. Otras clases o clases descendientes (incluso en la misma unidad) no
pueden acceder a miembros privados estrictos.
Protegido Es lo mismo que Privado, excepto que los miembros de una sección Protegida también son accesibles para
los tipos descendientes, incluso si están implementados en otros módulos.
Estricto Protegido Es lo mismo que Protegido, excepto que los miembros de una sección Protegida también son
accesibles para otras clases implementadas en la misma unidad. Los miembros estrictamente protegidos solo
son visibles para las clases descendientes, no para otras clases en la misma unidad.
Publicado Desde una perspectiva del lenguaje, esto es lo mismo que una sección Pública, pero el compilador también
genera información de tipo necesaria para la transmisión automática de estas clases si el compilador está en el
estado {$M+}. Los campos definidos en una sección publicada deben ser de tipo clase.
Las propiedades de la matriz no pueden estar en una sección publicada.
En el diagrama de sintaxis se puede ver que una clase puede enumerar las interfaces implementadas. Esta característica
se discutirá en el próximo capítulo.
Las clases pueden contener métodos de clase: son funciones que no requieren una instancia. El autoidentificador es
válido en dichos métodos, pero se refiere al puntero de clase (el VMT).
Observación Al igual que con las funciones y los tipos de punteros, a veces se necesita una definición directa de una clase. Una
definición directa de clase es simplemente el nombre de la clase, con la palabra clave Clase, como en el siguiente
ejemplo:
Tipo
TClassB = Clase;
TClassA = Clase
B: Clase TB; fin;
TClassB = Clase
A: Clase TA; fin;
Cuando se utiliza una definición directa de clase, la clase debe definirse en la misma unidad, en la misma sección
(interfaz/implementación). No necesariamente debe estar definido en la misma sección de tipo.
Los tipos de referencia de clases se utilizan para crear instancias de una determinada clase, que aún no se conoce en el
momento de la compilación, pero que se especifica en el tiempo de ejecución. Básicamente, una variable de un tipo de
referencia de clase contiene un puntero a la definición de la clase especificada. Esto se puede utilizar para construir una
instancia de la clase correspondiente a la definición o para comprobar la herencia. El siguiente ejemplo muestra cómo funciona:
88
Machine Translated by Google
CAPÍTULO 6. CLASES
Tipo
TComponentClass = Clase de TComponent;
comenzar // ...
Resultado:=[Link](AOwner); // ... fin;
A esta función se le puede pasar una referencia de clase de cualquier clase que descienda de TComponent. La
siguiente es una llamada válida:
Era
C: Componente T;
comenzar
C:=CrearComponente(TEdit,Form1); fin;
Al regresar la función CreateComponent, C contendrá una instancia de la clase TEdit. Tenga en cuenta que la siguiente
llamada no se podrá compilar:
Era
C: Componente T;
comenzar
C:=CrearComponente(TStream,Form1); fin;
porque TStream no desciende de TComponent y AClass se refiere a una clase de TComponent. El compilador puede
(y lo hará) verificar esto en el momento de la compilación y producirá un error.
Las referencias a clases también se pueden utilizar para comprobar la herencia:
comenzar
Si no (la instancia es TMinClass) o ((la instancia es
TMaxClass)
y ([Link]<>TMaxClass)) luego levanta
[Link](SomeError) end;
El ejemplo anterior generará una excepción si la instancia pasada no es descendiente de TMinClass o descendiente
de TMaxClass.
Puede encontrar más información sobre la creación de instancias de una clase en la sección 6.4, página 93.
89
Machine Translated by Google
CAPÍTULO 6. CLASES
Una clase puede declararse sellada. En ese caso, no es posible declarar una clase descendiente. El compilador
devolverá un error si encuentra una declaración de un descendiente:
Tipo
TMyClass = Clase sellada
x : número entero;
fin;
TMiClase2 = Clase(TMiClase)
Y: Entero; fin;
empezar
fin.
Una clase abstracta es una clase de la que no se puede crear una instancia directamente. En cambio, siempre se debe crear
una instancia de una clase descendiente. Sin embargo, por compatibilidad con Delphi, el compilador ignora esta directiva.
. Las clases pueden tener campos. Dependiendo de cómo estén definidos, los campos contienen datos específicos de una
instancia de una clase o de la clase en su conjunto. Cualquiera que sea la forma en que fueron definidos, los campos observan
las reglas de visibilidad como cualquier otro miembro de la clase.
Hay dos formas de declarar un campo normal. La primera es la forma clásica, similar a una definición en un objeto:
cl1:=[Link]; cl2:=[Link];
cl1.l:=2; writeln(cl1.l);
writeln(cl2.l);
fin.
90
Machine Translated by Google
CAPÍTULO 6. CLASES
será el siguiente
El ejemplo demuestra que los valores de los campos se inicializan con cero (o el equivalente a cero para tipos no
ordinales: cadena vacía, matriz vacía, etc.).
La segunda forma de declarar un campo (solo disponible en versiones más recientes de Free Pascal) es usando
un bloque var:
era
l : entero largo; fin
fin;
Observación A partir de la versión 3.0 del compilador, el compilador puede reordenar los campos en la memoria si esto
conduce a una mejor alineación e instancias más pequeñas. Eso significa que en una instancia, los campos no
necesariamente aparecen en el mismo orden que en la declaración. El RTTI generado para una clase reflejará este cambio.
Nuevamente, hay dos formas de definir variables de clase. El primero es equivalente a la forma en que se hace en los
objetos, usando un modificador estático:
Por ejemplo, la salida del siguiente programa es la misma que la salida de la versión que usa un objeto:
era
cl1,cl2 : cl; comenzar
cl1:=[Link]; cl2:=[Link];
cl1.l:=2; writeln(cl2.l);
cl2.l:=3;
writeln(cl1.l); Writeln(cl.l);
fin.
91
Machine Translated by Google
CAPÍTULO 6. CLASES
Tenga en cuenta que la última línea de código hace referencia al tipo de clase en sí (cl) y no a una instancia de la
clase (cl1 o cl2).
Además del enfoque de campo estático, en las clases se puede utilizar una Class Var. De manera similar a la forma en que se
puede definir un campo en un bloque de variables, se puede declarar una variable de clase en un bloque de clase var:
cl=clase
la clase era
l : entero largo; fin
fin;
Tenga en cuenta que una variable de clase está vinculada a una clase específica. Las clases descendientes se referirán a la
misma instancia, a menos que se vuelva a declarar la variable. El siguiente programa lo demuestra:
TB = clase(TA);
TC = clase(TA);
comenzar
código TA:=0;
Có[Link]:=1;
Có[Link]:=2;
Writeln(Ta.Código:2,Tb.Código:2,Tc.código:2); fin.
222
Debido a que está vinculado a una clase, se puede anular en modo Delphi:
TB = clase(TA)
Código var de clase: entero; fin;
TC = clase(TA)
Código var de clase: entero; fin;
92
Machine Translated by Google
CAPÍTULO 6. CLASES
comenzar
código TA:=0;
Có[Link]:=1;
Có[Link]:=2;
Writeln(Ta.Código:2,Tb.Código:2,Tc.código:2); fin.
Imprimirá lo siguiente:
012
Las clases deben crearse utilizando uno de sus constructores (puede haber varios constructores). Recuerde que una
clase es un puntero a un objeto en el montón. Cuando se declara una variable de alguna clase, el compilador
simplemente asigna espacio para este puntero, no para todo el objeto. El constructor de una clase devuelve un puntero
a una instancia inicializada del objeto en el montón. Entonces, para inicializar una instancia de alguna clase, se haría lo
siguiente:
ClassVar := [Link];
La sintaxis extendida de new y dispose no se puede utilizar para crear instancias y destruir instancias de clase: esa
construcción está reservada para su uso únicamente con objetos. Llamar al constructor provocará una llamada al
método de clase virtual NewInstance, que, en su implementación predeterminada, llama a GetMem para asignar
suficiente espacio para contener los datos de la instancia de clase y luego pone a cero la memoria.
Después de eso, se ejecuta el código del constructor. El constructor tiene un puntero a sus datos, en Self.
Observación
• La directiva {$PackRecords } también afecta a las clases, es decir, a la alineación en memoria del
Los diferentes campos dependen del valor de la directiva {$PackRecords}.
• Al igual que con los objetos y registros, se puede declarar una clase empaquetada. Esto tiene el mismo efecto
que en un objeto o registro, es decir, que los elementos están alineados en límites de 1 byte, es decir, lo más
cerca posible.
• SizeOf(clase) devolverá lo mismo que SizeOf(Pointer), ya que una clase es un puntero a un objeto. Para obtener
el tamaño de los datos de la instancia de clase, utilice el método [Link].
Las instancias de clase deben destruirse utilizando el destructor. A diferencia del constructor, no hay elección en los
destructores: el destructor debe tener el nombre Destroy, debe anular el destructor Destroy declarado en TObject, no
puede tener argumentos y siempre se debe llamar al destructor heredado.
Destroy llamará a FreeInstance, que, en su implementación predeterminada, llama a FreeMem para liberar la memoria
ocupada por la instancia.
93
Machine Translated by Google
CAPÍTULO 6. CLASES
Para evitar llamar a un destructor en una instancia Nil, es mejor llamar al método Free de TObject.
Este método comprobará si Self no es Nil y, de ser así, llama a Destroy. Si Self es igual a Nil, simplemente saldrá.
Era
A: Componente T;
comenzar
A:=[Link];
[Link]:='MiComponente'; [Link];
Writeln('A
todavía está asignado: ',Asignado(A)); fin.
Luego de la llamada a Free, la variable A no será Nil, la salida de este programa será:
Para asegurarse de que la variable A se borre después de llamar al destructor, se puede utilizar la función FreeAndNil
de la unidad SysUtils. Llamará a Free y luego escribirá Nil en el puntero del objeto (A en el ejemplo anterior):
Era
A: Componente T;
comenzar
A:=[Link];
[Link]:='MiComponente'; Libre y nulo
(A); Writeln('A todavía
está asignado: ',Asignado(A)); fin.
Después de la llamada a FreeAndNil, la variable A contendrá Nil, la salida de este programa será:
Tenga en cuenta que si ocurre una excepción durante la ejecución de un constructor, el destructor se llamará automático.
matemáticamente.
6.6 Métodos
6.6.1 Declaración
La declaración de métodos en clases sigue las mismas reglas que las declaraciones de métodos en objetos:
métodos
94
Machine Translated by Google
CAPÍTULO 6. CLASES
directivas de método
dinámica modificadores de llamadas ;
6.6.2 Invocación
La invocación de métodos para clases no es diferente a la de objetos. La siguiente es una invocación de
método válida:
Tipo
ObjParent = Clase
Procedimiento MyProc; virtual; fin;
ObjChild = Clase(ObjPArent)
Procedimiento MyProc; virtual; fin;
Tipo
ObjParent = Clase
Procedimiento MyProc; virtual; fin;
ObjChild = Clase(ObjPArent)
Procedimiento MyProc; anular; fin;
95
Machine Translated by Google
CAPÍTULO 6. CLASES
Tipo
ObjParent = Clase
Procedimiento MyProc; virtual; fin;
ObjChild = Clase(ObjPArent)
Procedimiento MyProc; reintroducir; fin;
Para poder hacer esto, el compilador mantiene – por tipo de clase – una tabla con métodos virtuales: la
VMT (Virtual Method Table). Esto es simplemente una tabla con punteros a cada uno de los métodos
virtuales: cada método virtual tiene su ubicación fija en esta tabla (un índice). El compilador usa esta tabla
para buscar el método real que debe usarse en tiempo de ejecución. Cuando un objeto descendiente
anula un método, la entrada del método principal se sobrescribe en el VMT. Puede encontrar más
información sobre VMT en la Guía del programador.
Observación La palabra clave "virtual" se puede reemplazar con la palabra clave "dinámica": los métodos dinámicos se
comportan igual que los métodos virtuales. A diferencia de Delphi, en FPC la implementación de métodos
dinámicos es igual a la implementación de métodos virtuales.
Los métodos de clase son métodos que no tienen una instancia (es decir, Self no apunta a una instancia de clase)
pero que siguen las reglas de alcance y herencia de una clase. Se pueden utilizar para devolver información sobre
la clase actual, por ejemplo para registro o uso en una fábrica de clases. Como no hay ninguna instancia disponible,
no se puede utilizar ninguna información disponible en las instancias.
Los métodos de clase se pueden llamar desde dentro de un método normal, pero también se pueden llamar usando un
identificador de clase:
Era
Clase A: Clase T; // AClass es de tipo "tipo de clase"
comenzar
..
si CompareText([Link],'TCOMPONENT')=0 entonces
...
Era
MiClase: TObject;
comenzar
96
Machine Translated by Google
CAPÍTULO 6. CLASES
..
si [Link]('TCOMPONENT') entonces
...
Lo contrario no es posible: dentro de un método de clase, el identificador Self apunta a la tabla VMT de la
clase. No hay campos, propiedades o métodos regulares disponibles dentro de un método de clase. Acceder
a una propiedad o método normal provocará un error del compilador.
Tenga en cuenta que los métodos de clase pueden ser virtuales y pueden anularse.
Los métodos de clase se pueden utilizar como especificadores de lectura o escritura para una propiedad normal, pero, naturalmente,
esta propiedad tendrá el mismo valor para todas las instancias de la clase, ya que no hay ninguna instancia disponible en el método
de clase.
• Sólo puede haber un constructor por clase. El nombre es arbitrario, pero no puede tener parámetros.
éteres.
• Sólo puede haber un destructor por clase. El nombre es arbitrario, pero no puede tener parámetro.
ters.
• El constructor/destructor de la clase se llama independientemente del uso de la clase: incluso si una clase
nunca se usa, el constructor y el destructor se llaman de todos modos.
• No hay un orden garantizado en el que se llaman los constructores o destructores de clases. Para
las clases anidadas, el único orden garantizado es que los constructores de las clases anidadas se
llamen después de llamar al constructor de la clase circundante, y para los destructores se utiliza
el orden opuesto.
El siguiente programa:
Tipo
TA = Clase (TOobjeto)
Privado
Función GetA: Entero;
Procedure SetA(AValue : integer);
público
Crear constructor de clase;
Destructor de clase destruir;
Propiedad A: Entero Leer GetA Escribir SetA; fin;
97
Machine Translated by Google
CAPÍTULO 6. CLASES
comenzar
Resultado:=1; fin;
comenzar //
finalizar;
comenzar
Writeln('Constructor de clase TA'); fin;
comenzar
Writeln('Destructor de clase TA');
fin;
Era
R: TA;
empezar
fin.
Constructor de clases TA
Destructor de clase TA
Su uso es principalmente incluir el método en el espacio de nombres de la clase en lugar de tener el procedimiento
en el espacio de nombres de la unidad. Tenga en cuenta que tienen acceso a todas las variables de clase, tipos,
etc., lo que significa que es posible algo como esto:
98
Machine Translated by Google
CAPÍTULO 6. CLASES
Tipo
TA = Clase (TOobjeto)
Clase
privada var myprivatea: entero; Función de clase
pública
GetA: Entero; estático; clase Procedimiento SetA (AValue: Entero);
estático; fin;
comenzar
Resultado:=miprivadoA; fin;
begin
myprivateA:=AValue; end;
comenzar
[Link](123);
Writeln([Link]); fin.
Tipo
TA = Clase
Procedimiento de clase DoIt; virtual;
Procedimiento de clase DoitStatic; estático; fin;
TB = Clase(TA)
Procedimiento de clase DoIt; anular; fin;
comenzar
Writeln('[Link]: ',[Link]); fin;
comenzar
Debe;
99
Machine Translated by Google
CAPÍTULO 6. CLASES
comenzar
Heredado;
Writeln('[Link]: ',[Link]); fin;
comenzar
Writeln('A través del método estático:'); [Link];
Writeln('A través del
método de clase:'); [Link];
fin.
[Link]: TA
A través del método de clase:
[Link]: TB
[Link]: TB
Para el método de clase estática, aunque fue llamado usando TB, la clase (Self, si estuviera disponible) se
establece en TA, la clase en la que se definió el método estático. Para el método de clase, la clase se establece
en la clase real utilizada para llamar al método (TB).
en las clases son los métodos de mensajes. Los punteros a los métodos de mensajes se almacenan en una tabla
especial, junto con el número entero o la constante de cadena con la que fueron declarados. Su objetivo principal es
facilitar la programación de funciones de devolución de llamada en varios kits de herramientas GUI, como Win32 o
GTK. A diferencia de Delphi, Free Pascal también acepta cadenas como identificadores de mensajes. Los métodos de
mensaje son siempre virtuales.
Como se puede ver en el diagrama de declaración de clases, los métodos de mensaje se declaran con una palabra
clave Mensaje, seguida de una expresión constante entera.
La implementación del método de una función de mensaje no es diferente de un método ordinario. También es
posible llamar directamente a un método de mensaje, pero esto no debe hacerse. En su lugar, se debe utilizar el
método [Link]. Los métodos de mensajes son automáticamente virtuales, es decir, pueden anularse
en clases descendientes.
El método [Link] se puede utilizar para llamar a un controlador de mensajes. Se declara en la unidad del
sistema y aceptará un parámetro var que debe tener en la primera posición un cardinal con el ID del mensaje que se
debe llamar. Por ejemplo:
Tipo
100
Machine Translated by Google
CAPÍTULO 6. CLASES
TMsg = Grabar
MSGID: Cardenal;
Datos: puntero;
Era
Mensaje: TMSg;
[Link] (mensaje);
En este ejemplo, el método Dispatch observará el objeto y todos sus antepasados (comenzando en el objeto y buscando
en el árbol de clases de herencia) para ver si se ha declarado un método de mensaje con el mensaje MSGID. Si se
encuentra dicho método, se llama y se le pasa el parámetro Msg.
Si no se encuentra dicho método, se llama a DefaultHandler. DefaultHandler es un método virtual de TObject que no
hace nada, pero que puede anularse para proporcionar cualquier procesamiento que pueda ser necesario. DefaultHandler
se declara de la siguiente manera:
Además del método de mensaje con un identificador entero, Free Pascal también admite un método de
mensaje con un identificador de cadena:
El funcionamiento del controlador de mensajes de cadena es el mismo que el del controlador de mensajes
enteros ordinario: el método [Link] se puede utilizar para llamar a un controlador de mensajes.
Se declara en la unidad del sistema y aceptará un parámetro que debe tener en la primera posición una cadena
corta con el ID del mensaje que se debe llamar. Por ejemplo:
Tipo
TMsg = Grabar
MsgStr: Cadena[10]; // Longitud arbitraria hasta 255 caracteres.
Datos: puntero;
Era
Mensaje: TMSg;
[Link] (mensaje);
En este ejemplo, el método DispatchStr observará el objeto y todos sus antepasados (comenzando en el objeto y
buscando en el árbol de clases de herencia) para ver si se ha declarado un método de mensaje con el mensaje MsgStr.
Si se encuentra dicho método, se llama y se le pasa el parámetro Msg.
Si no se encuentra dicho método, se llama a DefaultHandlerStr. DefaultHandlerStr es un método virtual de TObject que
no hace nada, pero que puede anularse para proporcionar cualquier procesamiento que pueda ser necesario.
DefaultHandlerStr se declara de la siguiente manera:
virtual anulado, a menudo es necesario llamar a la implementación del método virtual de la clase principal. Esto se
puede hacer con la palabra clave heredada. Asimismo, la palabra clave heredada se puede utilizar para llamar a
cualquier método de la clase principal.
101
Machine Translated by Google
CAPÍTULO 6. CLASES
Tipo
TMiClase = Clase(TComponente)
Constructor Crear(AOwner: TComponent); anular; fin;
comenzar
Heredado;
// Hacer que terminen más
cosas;
En el ejemplo anterior, la declaración Inherited llamará a Create de TComponent y le pasará AOwner como parámetro:
los mismos parámetros que se pasaron al método actual se pasarán al método principal. No se deben volver a
especificar: si no se especifica ninguno, el compilador pasará los mismos argumentos que los recibidos.
Si no existe ningún método heredado con el mismo nombre, el Heredado no tendrá ningún efecto en este caso.
Por lo tanto, la presencia de Heredado en este formulario puede interpretarse como "llamar al método anulado
si existe".
Tipo
TMiClase = Clase(TComponente)
Constructor Crear(AOwner: TComponent); anular;
Constructor CreateNew(AOwner: TComponent; DoExtra: Booleano); fin;
fin;
El método CreateNew primero llamará a [Link] y le pasará AOwner como parámetro. No llamará a
[Link].
Si no existe ningún método con el nombre de pila en las clases principales, el compilador dará un error.
Aunque los ejemplos se dieron usando constructores, el uso de heredado no se limita a los constructores, también se
puede usar para cualquier procedimiento, función o destructor.
6.7 Propiedades
6.7.1 Definición
Las clases pueden contener propiedades como parte de su lista de campos. Una propiedad actúa como un
campo normal, es decir, su valor se puede recuperar o establecer, pero permite redirigir el acceso al campo a
través de funciones y procedimientos. Proporcionan un medio para asociar una acción con una asignación o
una lectura de un "campo" de clase. Esto permite, por ejemplo, comprobar que un valor es válido al asignarlo o, al leerlo,
102
Machine Translated by Google
CAPÍTULO 6. CLASES
permite construir el valor sobre la marcha. Además, las propiedades pueden ser de sólo lectura o de sólo escritura.
La declaración prototipo de una propiedad es la siguiente:
Propiedades
Un especificador de lectura es el nombre de un campo que contiene la propiedad o el nombre de una función de
método que tiene el mismo tipo de retorno que el tipo de propiedad. En el caso de un tipo simple, esta función
no debe aceptar ningún argumento. En el caso de una propiedad de matriz, la función debe aceptar un único
argumento del mismo tipo que el índice. En el caso de una propiedad indexada, debe aceptar un número entero
como argumento.
Un especificador de lectura es opcional, lo que hace que la propiedad sea de solo escritura. Tenga en cuenta que los métodos de clase no se pueden
103
Machine Translated by Google
CAPÍTULO 6. CLASES
El argumento debe ser del mismo tipo que la propiedad. De manera similar, en el caso de una propiedad indexada, el primer
parámetro debe ser un número entero.
La sección (privada, publicada) en la que reside la función o procedimiento especificado es irrelevante. Sin embargo,
normalmente este será un método protegido o privado.
Tipo
MiClase = Clase
Privado
Campo1: Entero largo;
Campo2: Entero largo;
Campo3: Entero largo;
Procedimiento Sety (valor: Entero largo);
Función Gety: Entero largo;
Función Getz: Entero largo;
Público
Propiedad X: Entero largo Leer campo1 escribir campo2;
Propiedad Y: Entero largo Leer GetY Escribir Sety;
Propiedad Z: Entero largo Leer GetZ; fin;
Era
MiClase: TMiClase;
MiClase.Z := 0;
Lo que sucede en las declaraciones anteriores es que cuando es necesario leer un valor, el compilador inserta
una llamada a los distintos métodos getNNN del objeto y se utiliza el resultado de esta llamada. Cuando se
realiza una asignación, el compilador pasa el valor que debe asignarse como parámetro a los distintos
métodos setNNN.
Debido a este mecanismo, las propiedades no se pueden pasar como argumentos var a una función o
procedimiento, ya que no se conoce la dirección de la propiedad (al menos no siempre).
de propiedad contiene un índice, entonces los especificadores de lectura y escritura deben ser una función y un
procedimiento. Además, estas funciones requieren un parámetro adicional: un parámetro entero. Esto permite leer o
escribir varias propiedades con la misma función. Para ello las propiedades deben ser del mismo tipo. El siguiente es
un ejemplo de una propiedad con un índice:
{$modo objfpc}
104
Machine Translated by Google
CAPÍTULO 6. CLASES
Tipo
PuntoT = Clase(TOobjeto)
Privado
FX,FY: Entero largo;
Función GetCoord (Índice: Entero): Entero largo;
Procedimiento SetCoord (Índice: Entero; Valor: entero largo);
Público
Propiedad X: índice de entero largo 1 leer GetCoord escribir SetCoord;
Propiedad Y: Índice de entero largo 2 leer GetCoord Escribir SetCoord;
Coords de propiedad[Índice: entero]:Entero largo Leer GetCoord; fin;
Procedimiento [Link] (Índice: Entero; Valor: Entero largo); comenzar índice de casos de
1: FX:= Valor;
2: Año fiscal: = Valor; fin; fin;
Índice de casos de
1: Resultado:= FX;
2: Resultado: = ejercicio
fiscal;
fin; fin;
Era
P: PuntoT;
comenzar
P := [Link]; PX := 2; PA :=
3;
Con P hacer
WriteLn ('X=',X,' Y=',Y);
fin.
Cuando el compilador encuentra una asignación a X, se llama a SetCoord con como primer parámetro
el índice (1 en el caso anterior) y como segundo parámetro el valor a establecer. Por el contrario, al
leer el valor de X, el compilador llama a GetCoord y le pasa el índice 1. Los índices solo pueden ser
valores enteros.
existen propiedades de matriz. Estas son propiedades que aceptan un índice, tal como lo hace una matriz. El
índice puede ser unidimensional o multidimensional. A diferencia de los arreglos normales (estáticos o dinámicos),
el índice de una propiedad de un arreglo no tiene que ser de tipo ordinal, pero puede ser de cualquier tipo.
Un especificador de lectura para una propiedad de matriz es la función del método de nombre que tiene el mismo tipo de
retorno que el tipo de propiedad. La función debe aceptar como único argumento una variable del mismo tipo que el tipo
de índice. Para una propiedad de matriz, no se pueden especificar campos como especificadores de lectura.
Un especificador de escritura para una propiedad de matriz es el nombre de un procedimiento de método que acepta dos
105
Machine Translated by Google
CAPÍTULO 6. CLASES
argumentos: el primer argumento tiene el mismo tipo que el índice y el segundo argumento es un
parámetro del mismo tipo que el tipo de propiedad. Como ejemplo, vea la siguiente declaración:
Tipo
TIntList = Clase
Privado
Función GetInt (I: Entero largo): entero largo;
Función GetAsString (A: Cadena): Cadena;
Procedimiento SetInt (I: Entero largo; Valor: Entero largo;);
Procedimiento SetAsString (A: Cadena; Valor: Cadena);
Público
Elementos de propiedad [i: Entero largo]: Entero largo Leer GetInt
Escribe SetInt;
Propiedad StrItems [S: Cadena]: Cadena Leer GetAsString
Escribe SetAsstring;
fin;
Era
ListaAInt: ListaTInt;
[Link][26] := 1;
[Link]['veinticinco'] := 'cero'; WriteLn ('Artículo 26:
',[Link][26]); WriteLn ('Artículo 25: ',[Link]['veinticinco']);
Tipo
TGrid = Clase
Privado
Función GetCell (I,J: Entero largo): Cadena;
Procedimiento SetCell (I,J: Entero largo; Valor: Cadena);
Público
Propiedad Cellcs [Fila, Col: Entero largo]: Cadena Leer GetCell
Escribe SetCell;
fin;
Si hay N dimensiones, entonces los tipos de los primeros N argumentos del getter y setter deben corresponder
a los tipos de los N especificadores de índice en la definición de propiedad de la matriz.
106
Machine Translated by Google
CAPÍTULO 6. CLASES
Entonces la tarea
[Link][26] := 1;
ListaAInt[26] := 1;
Sólo se permite una propiedad predeterminada por clase, pero las clases descendientes pueden volver a declarar la propiedad
predeterminada.
compiladas en el estado {$M+} (como TPersistent de la unidad de clases) pueden tener una sección publicada. Para los
métodos, campos y propiedades de la sección Publicado, el compilador genera información RTTI (Información de tipo de
tiempo de ejecución), que se puede utilizar para consultar los métodos, campos y propiedades definidos en las secciones
publicadas. La unidad typinfo contiene las rutinas necesarias para consultar esta información, y esta unidad se utiliza en
el sistema de streaming en FPC en la unidad de clases.
El RTTI se genera independientemente de cuáles sean los especificadores de lectura y escritura: campos, funciones/
procedimientos o funciones/procedimientos indexados.
Sólo se pueden publicar campos escritos por clase. Para las propiedades, se puede declarar publicada cualquier propiedad
simple cuyo tamaño sea menor o igual a un puntero: flotantes, enteros, conjuntos (con menos de 32 elementos distintos),
enumerados, clases o matrices dinámicas (no propiedades de matriz).
Aunque la información de tipo en tiempo de ejecución está disponible para otros tipos, estos tipos no se pueden utilizar
para una propiedad o definición de campo en una sección publicada. La información está presente para describir, por
ejemplo, argumentos de procedimientos o funciones.
El compilador ofrece dos especificadores para controlar si una propiedad se transmite mediante un mecanismo de
transmisión, como el implementado en la unidad de clases. Estos especificadores escriben información adicional en el
RTTI generado, que luego se utiliza en el sistema de transmisión:
1. El especificador almacenado.
Estos dos especificadores se evalúan para decidir si una propiedad debe transmitirse.
El argumento del especificador almacenado debe ser una constante booleana, un campo booleano de la clase o una
función sin parámetros que devuelva un resultado booleano. Si el argumento del especificador almacenado se evalúa
como Falso, la propiedad no se transmitirá.
Si la evaluación de Stored resultó en True, se considera el valor predeterminado para una propiedad: el
especificador predeterminado se puede especificar para tipos y conjuntos ordinales. Las propiedades que tienen como
valor este valor predeterminado no serán escritas en la transmisión por el sistema de transmisión, incluso si Stored es True.
El valor predeterminado se almacena en el RTTI que se genera para la clase.
107
Machine Translated by Google
CAPÍTULO 6. CLASES
Las propiedades de cadena, punto flotante y puntero tienen un valor predeterminado implícito de cadena vacía, 0 o nulo,
respectivamente. Las propiedades ordinales y de conjuntos no tienen ningún valor predeterminado implícito.
El especificador nodefault debe usarse para indicar que una propiedad no tiene un valor predeterminado. El efecto es
que el valor de esta propiedad siempre se escribe en la secuencia cuando se transmite la propiedad, si se almacena en
True.
Observación
1. Cuando se crea una instancia de la clase, el valor predeterminado no se aplica automáticamente a la propiedad,
es responsabilidad del programador hacer esto en el constructor de la clase.
2. El valor 2147483648 no se puede utilizar como valor predeterminado, ya que se utiliza internamente para indicar
ningún valor predeterminado.
6. El especificador nodefault se puede utilizar para anular el especificador predeterminado implícito de cadena,
propiedades de punto flotante y puntero.
7. El mecanismo de transmisión aquí descrito es el implementado en la unidad de clases del RTL. Se pueden
implementar otros mecanismos de transmisión y pueden utilizar la información RTTI de una manera diferente.
La redeclaración de propiedad toma acción si se declara el tipo de propiedad; de lo contrario, se anula la propiedad.
La única diferencia es que la anulación de propiedad reemplaza o extiende los modificadores heredados con los nuevos
modificadores, mientras que la redeclaración de propiedad oculta todos los modificadores heredados que no están
presentes en la redeclaración. El tipo de propiedad redeclarada no tiene que ser el mismo que el tipo de propiedad de
clase principal.
tipo
TAncestro = clase
privado
FP1: Entero; propiedad
pública P:
entero Leer FP1 escribir FP1; fin;
108
Machine Translated by Google
CAPÍTULO 6. CLASES
TP1 extiende la propiedad P con un valor predeterminado, TPReadOnly vuelve a declarar la propiedad P como de solo
lectura.
Observación TP1 debe establecer el valor predeterminado de P en 1 en su constructor.
La palabra clave "heredado" se puede utilizar para hacer referencia a la definición principal de la propiedad. Por
ejemplo, considere el siguiente código:
tipo
TAncestro = clase
privado
FP1: Entero; propiedad
pública P:
entero Leer FP1 escribir FP1; fin;
comenzar
P heredado:=Ord(AValue); fin;
comenzar
Resultado:=Char((P heredado) y $FF); fin;
TClassA redefine P como una propiedad de carácter en lugar de una propiedad de número entero, pero usa la propiedad P
del padre para almacenar el valor.
Se debe tener cuidado al utilizar rutinas virtuales get/set para una propiedad: al establecer la propiedad
heredada se siguen las reglas normales de herencia para los métodos. Considere el siguiente ejemplo:
tipo
TAncestor = procedimiento
privado de
clase SetP1(const AValue: entero); virtual; propiedad pública P: escritura entera
SetP1;
109
Machine Translated by Google
CAPÍTULO 6. CLASES
fin;
En este caso, al configurar la propiedad heredada P, se llamará a la implementación TClassA.SetP1, porque se anula
el método SetP1.
Si se debe llamar a la implementación de la clase principal de SetP1, entonces se debe llamar explícitamente:
Las propiedades del ancestro redeclaradas también están disponibles desde dentro y fuera del objeto descendiente
con una conversión directa al ancestro:
TAncestor(AClassA).P; fin;
Las propiedades de clase son muy parecidas a las definiciones de propiedades globales. Están asociados con la
clase, no con una instancia de la clase.
Una consecuencia de esto es que el almacenamiento del valor de la propiedad debe ser una var de clase, no un campo
o variable normal de la clase: los campos o variables normales se almacenan en una instancia de la clase.
Las propiedades de clase pueden tener un método getter y setter como las propiedades normales, pero deben ser
métodos estáticos de la clase.
Eso significa que lo siguiente contiene una definición de propiedad de clase válida:
TA = Clase (TOobjeto)
Privado
clase var myprivatea: entero; clase Función GetB:
Entero; estático; clase Procedimiento SetA (AValue: Entero); estático;
clase Procedimiento ConjuntoB (AValue: Entero); estático; público
110
Machine Translated by Google
CAPÍTULO 6. CLASES
El motivo del requisito es que una propiedad de clase esté asociada a la clase particular en la que está definida,
pero no a clases descendientes. Dado que los métodos de clase pueden ser virtuales, esto permitiría que las
clases descendientes anulen el método, haciéndolos inadecuados para el acceso a las propiedades de la clase.
Una definición de clase puede contener una sección de tipo, una sección constante y una sección variable. Las secciones
de tipo y constante actúan como una sección de tipo normal tal como se encuentra en una unidad o implementación de
método/función/procedimiento. Las variables actúan como campos regulares de la clase, a menos que estén en una sección
de var de clase, en cuyo caso actúan como si estuvieran definidas a nivel de unidad, dentro del espacio de nombres de la
clase (sección 6.3, página 90 ) .
Sin embargo, la visibilidad de estas secciones sí juega un papel: las constantes, tipos y variables privadas y
protegidas (estrictas o no) sólo se pueden utilizar en la medida en que su visibilidad lo permita.
Los tipos públicos se pueden utilizar fuera de la clase, por su nombre completo:
tipo
TA = Clase (TOobjeto)
Público
Escriba TEnum = (a,b,c);
Función de clase Hacer algo: TEnum; fin;
comenzar
Resultado:=a; fin;
era
E: [Link];
comenzar
E:=[Link]; fin.
Mientras
tipo
TA = Clase (TOobjeto)
Estricto privado
Escriba TEnum = (a,b,c);
Público
Función de clase Hacer algo: TEnum; fin;
comenzar
Resultado:=a;
111
Machine Translated by Google
CAPÍTULO 6. CLASES
fin;
era
E: [Link];
comenzar
E:=[Link]; fin.
Tenga en cuenta que para las constantes que se pueden escribir, se aplican las mismas reglas que para las variables de clase con respecto al
alcance y la anulación en descendientes:
TB = clase(TA);
TC = clase(TA);
comenzar
código TA:=0;
Có[Link]:=1;
Có[Link]:=2;
Writeln(Ta.Código:2,Tb.Código:2,Tc.código:2); fin.
Escribirá
222
Pero
TB = clase(TA)
código constante: entero = 98; fin;
TC = clase(TA)
Código constante: entero = 97; fin;
comenzar
código TA:=0;
Có[Link]:=1;
Có[Link]:=2;
Writeln(Ta.Código:2,Tb.Código:2,Tc.código:2); fin.
112
Machine Translated by Google
CAPÍTULO 6. CLASES
Escribirá
012
113
Machine Translated by Google
Capítulo 7
Interfaces
7.1 De nición
A partir de la versión 1.1, FPC admite interfaces. Las interfaces son una alternativa a la herencia múltiple
(donde una clase puede tener varias clases principales) como se implementa, por ejemplo, en C++. Una
interfaz es básicamente un conjunto de métodos y propiedades con nombre: una clase que implementa la
interfaz proporciona todos los métodos tal como se enumeran en la definición de la interfaz. No es posible
que una clase implemente sólo una parte de la interfaz: es todo o nada.
Las interfaces también se pueden ordenar en una jerarquía, exactamente como clases: una definición de
interfaz que hereda de otra definición de interfaz contiene todos los métodos de la interfaz principal, así como
los métodos nombrados explícitamente en la definición de interfaz. Una clase que implementa una interfaz
debe implementar todos los miembros de la interfaz, así como los métodos de la(s) interfaz(es) principal(es).
Una interfaz puede identificarse de forma única mediante un GUID. GUID es un acrónimo de Globally Unique
Iden tifier, un entero de 128 bits que garantiza que siempre será único1. Especialmente en sistemas Windows, el
GUID de una interfaz puede y debe usarse cuando se usa COM.
Tipo de interfaz
Interfaz fin
herencia [' GUID '] lista de componentes
lista de componentes 6
definición del método
definición de la propiedad
• No hay especificadores de visibilidad. Todos los miembros son públicos (de hecho, tendría poco sentido
para hacerlos privados o protegidos).
1En teoría, por supuesto.
114
Machine Translated by Google
CAPÍTULO 7. INTERFACES
• Las propiedades declaradas en una interfaz sólo pueden tener métodos como especificadores de lectura y escritura.
• Sólo los modificadores de convención de llamada pueden estar presentes en la definición de un método. Los
modificadores como virtuales, abstractos o dinámicos y, por lo tanto, también los de anulación, no pueden estar
presentes en la definición de la interfaz.
IInterfaz = IDesconocido;
IMyInterface = Interfaz
Función MyFunc: Entero;
Función MySecondFunc: Entero; fin;
['{HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH}']
Cada carácter H representa un número hexadecimal (0–9, A–F). El formato contiene 844412 números.
Un GUID también puede representarse mediante el siguiente registro, definido en la unidad objpas (incluido
automáticamente cuando está en modo DELPHI u OBJFPC):
2:
matriz[0..7] de
byte; );
(tiempo_bajo:
dword;
115
Machine Translated by Google
CAPÍTULO 7. INTERFACES
time_mid: palabra;
time_hi_and_version: palabra;
clock_seq_hi_and_reserved: byte; clock_seq_low: byte;
nodo: matriz [0..5] de bytes; );
fin;
constante
comenzar
fin.
Normalmente, los GUID sólo se utilizan en Windows, cuando se utilizan interfaces COM. Más sobre esto en la
siguiente sección.
Cuando una clase implementa una interfaz, debe implementar todos los métodos de la interfaz. Si no se implementa un
método de una interfaz, el compilador dará un error. Por ejemplo:
Tipo
IMyInterface = Interfaz
Función MyFunc: Entero;
Función MySecondFunc: Entero; fin;
TMiClase = Clase(TInterfacedObject,IMyInterface)
Función MyFunc: Entero;
Función MyOtherFunc: Entero; fin;
comenzar
Resultado:=23; fin;
comenzar
Resultado:=24; fin;
116
Machine Translated by Google
CAPÍTULO 7. INTERFACES
Normalmente, los nombres de los métodos que implementan una interfaz deben ser iguales a los nombres de los
métodos en la definición de la interfaz. El compilador buscará métodos coincidentes en todos los métodos visibles:
los métodos de la clase y en los métodos de las clases principales con visibilidad protegida o superior.
Sin embargo, es posible proporcionar alias para los métodos que componen una interfaz: es decir, se le puede decir
al compilador que un método de una interfaz se implementa mediante un método existente con un nombre diferente.
Esto se hace de la siguiente manera:
Tipo
IMyInterface = Interfaz
Función MyFunc: Entero; fin;
TMiClase = Clase(TInterfacedObject,IMyInterface)
Función MiOtraFunción: Entero; Función [Link]
= MyOtherFunction; fin;
Esta declaración le dice al compilador que el método MyFunc de la interfaz IMyInterface está implementado en el método
MyOtherFunction de la clase TMyClass.
IParentInterface = interfaz
['{0F78D56E85A6402498D7720D7C7B9573}'] procedimiento Foo; fin;
IChildInterface = interfaz(IParentInterface)
['{1AB2EB856843462E8CE432ECC065011E}'] barra de procedimiento;
fin;
IChildInterface tendrá dos métodos: foo y bar. Por lo tanto, cualquier clase que implemente esta interfaz deberá
implementar ambas interfaces:
procedimientos; fin;
fin;
fin;
117
Machine Translated by Google
CAPÍTULO 7. INTERFACES
Tenga en cuenta que cuando una clase declara una interfaz secundaria, se puede asignar a una variable con la
interfaz secundaria. Dadas las declaraciones anteriores, se compilará lo siguiente:
era
Niño: IChildInterface;
comenzar
Niño := [Link];
Pero esto no implica que automáticamente también sea compatible con la asignación de una variable con el tipo de
interfaz principal. Lo siguiente no se compilará:
era
Padre: IParentInterface;
comenzar
padre := [Link];
TImplementor = clase(TInterfacedObject,
interfaz padre,
IChildInterface)
La razón de esto es que aunque la clase realmente implementa los métodos de IParentInterface, el compilador
solo verifica las interfaces realmente declaradas cuando verifica la compatibilidad de las asignaciones: todas
las interfaces declaradas se colocan en una tabla y solo se verifica el contenido de esta tabla.
La misma verificación se realiza en tiempo de ejecución: el compilador genera una tabla de todas las interfaces
que declara una clase y esta tabla se verifica en tiempo de ejecución. Eso significa que aunque lo siguiente
se compilará si solo se declara IChildInterface:
home:~> ./ti Se
produjo una excepción no controlada en $0000000000411A27: EInvalidCast: conversión de
tipo no válida $0000000000411A27
A veces, los métodos de una interfaz se implementan mediante un objeto auxiliar (o delegado), o la instancia
de clase ha obtenido un puntero de interfaz para esta interfaz y debe usarse. Esto puede ocurrir, por ejemplo,
cuando se debe agregar una interfaz a una serie de clases totalmente no relacionadas: la funcionalidad de
interfaz necesaria se agrega a una clase separada, y cada una de estas clases usa una instancia de la clase
auxiliar para implementar la funcionalidad.
118
Machine Translated by Google
CAPÍTULO 7. INTERFACES
En tal caso, es posible indicarle al compilador que la interfaz no la implemente el objeto en sí, sino
que en realidad resida en una clase o interfaz auxiliar. Esto se puede hacer con el modificador de
propiedad implements.
Si la clase tiene un puntero a la interfaz deseada, lo siguiente le indicará al compilador que cuando se solicite la interfaz
IMyInterface, debe usar la referencia en el campo:
tipo
IMyInterface = procedimiento de interfaz
P1; fin;
fin;
La interfaz no necesariamente debe estar en un campo, se puede utilizar cualquier identificador de lectura.
Si la interfaz se implementa mediante un objeto delegado (un objeto auxiliar que realmente implementa la
interfaz), entonces también se puede usar con la palabra clave implements:
Tenga en cuenta que, a diferencia de Delphi, la clase delegada debe especificar explícitamente la
interfaz: el compilador no buscará los métodos en la clase delegada, simplemente comprobará si la
clase delegada implementa la interfaz especificada.
Es posible implementar múltiples interfaces usando un único objeto delegado:
IMyInterface1 = interfaz
119
Machine Translated by Google
CAPÍTULO 7. INTERFACES
No es posible combinar la resolución del método y la delegación de la interfaz. Eso significa que no es
posible implementar parte de una interfaz mediante resolución de método e implementar parte de la
interfaz mediante delegación. Lo siguiente intenta implementar IMyInterface en parte mediante la
resolución de métodos (P1) y en parte mediante la delegación. El compilador no aceptará el siguiente código:
Error: La interfaz "IMyInterface" no puede ser delegada por "TMyClass", ya tiene resoluciones de métodos
Sin embargo, es posible implementar una interfaz mediante resolución de método y otra mediante delegación:
IMyInterface2 = interfaz
procedimiento P2;
120
Machine Translated by Google
CAPÍTULO 7. INTERFACES
fin;
IMyInterface2
leer FI2 implementa IMyInterface2;
fin;
Tenga en cuenta que la delegación de interfaz se puede utilizar para especificar que una clase implementa interfaces principales:
IGMGetFileName = interfaz(IUDesconocido)
['{D3ECCB42A5634cc4B37579931031ECBA}'] función GetFileName:
Cadena; llamada estándar; propiedad FileName: cadena leída
GetFileName; fin;
IGMGetSetFileName = Interfaz(IGMGetFileName)
['{ECFB879F86F641a3A6850C899A2B5BCA}'] procedimiento
SetFileName(valor constante: cadena); llamada estándar; propiedad FileName: cadena leída
GetFileName escritura SetFileName; fin;
Cuando se utilizan interfaces en Windows que deberían estar disponibles para el subsistema COM, la convención
de llamada debe ser stdcall; esta no es la convención de llamada predeterminada de Free Pascal, por lo que debe
especificarse explícitamente.
COM no conoce propiedades. Sólo conoce métodos. Por lo tanto, cuando especifique definiciones de propiedades
como parte de una definición de interfaz, tenga en cuenta que las propiedades sólo se conocerán en el programa
compilado de Free Pascal: otros programas de Windows no conocerán las definiciones de propiedades.
COM no es la única arquitectura donde se utilizan interfaces. CORBA conoce interfaces, UNO (la API de OpenOffice)
usa interfaces y Java también. Estos idiomas no conocen el IUnknown
121
Machine Translated by Google
CAPÍTULO 7. INTERFACES
interfaz utilizada como base de todas las interfaces en COM. Por lo tanto, sería una mala idea si una interfaz
descendiera automáticamente de IUnknown si no se especificaba ninguna interfaz principal. Por lo tanto, se introdujo
una directiva {$INTERFACES} en Free Pascal: especifica cuál es la interfaz principal de una interfaz, declarada sin
padre. Puede encontrar más información sobre esta directiva en la Guía del programador.
Tenga en cuenta que las interfaces COM se cuentan por referencia de forma predeterminada, porque descienden de IUnknown.
Las interfaces Corba se identifican mediante una cadena simple, por lo que su asignación es compatible con
cadenas y no con TGUID. El compilador no realiza ningún recuento automático de referencias para las interfaces
CORBA, por lo que el programador es responsable de la contabilidad de referencias.
Todas las interfaces COM utilizan el recuento de referencias. Esto significa que cada vez que se asigna una interfaz
a una variable, se actualiza su recuento de referencias. Siempre que la variable sale del alcance, el recuento de
referencias disminuye automáticamente. Cuando el recuento de referencias llega a cero, normalmente se libera la
instancia de la clase que implementa la interfaz.
Se debe tener cuidado con este mecanismo. El compilador puede o no crear variables temporales al evaluar
expresiones y asignar la interfaz a una variable temporal, y solo entonces asignar la variable temporal a la variable
de resultado real. No se deben hacer suposiciones sobre el número de variables temporales o el momento en que
se finalizan; esto puede (y de hecho difiere) de la forma en que otros compiladores (por ejemplo, Delphi) manejan
expresiones con interfaces. [Link]. un tipo de conversión también es una expresión:
Era
B: Clase A;
comenzar // ...
AInterfaz([Link]).testproc; // ...fin;
Supongamos que la interfaz intf cuenta con referencias. Cuando el compilador evalúa [Link], crea una variable
temporal. Esta variable puede liberarse sólo cuando finaliza el procedimiento: por lo tanto, no es válido, por ejemplo,
liberar la instancia B antes de la salida del procedimiento, ya que cuando finalice la variable temporal, intentará
liberar B nuevamente.
Además, los resultados de la función pueden apuntar a una interfaz COM válida no nula al ingresar: esto se debe a que
el resultado de la función se trata como un parámetro var.
122
Machine Translated by Google
Capítulo 8
Genéricos
8.1 Introducción
Los genéricos son plantillas para generar otros tipos. Pueden ser clases, objetos, interfaces e incluso funciones,
matrices, registros. Es un concepto que proviene de C++, donde está profundamente integrado en el lenguaje.
A partir de la versión 2.2, Free Pascal también tiene oficialmente soporte para plantillas o Genéricos. Se
implementan como una especie de macro que se almacena en los archivos unitarios que genera el compilador
y que se reproduce tan pronto como se especializa una clase genérica.
1. La definición de genérico se define como un nuevo tipo: esta es una plantilla de código, una macro que
El compilador puede reproducirlo en una etapa posterior.
2. Un tipo genérico es especializado: esto define un segundo tipo, que es una implementación
específica del tipo genérico: el compilador reproduce la macro que se almacenó cuando se definió
el tipo genérico.
Hay varias unidades distribuidas con pascal libre que implementan contenedores y clases genéricos.
Por ejemplo la unidad fgl.
Una definición de tipo genérico es muy parecida a una definición de tipo, con la excepción de que contiene una lista de
marcadores de posición para tipos, como se puede ver en el siguiente diagrama de sintaxis:
tipo genérico identificador genérico < lista de plantillas > = tipo genérico ;
123
Machine Translated by Google
CAPÍTULO 8. GENÉRICOS
Para clases, objetos, tipos procedimentales y registros extendidos, la declaración de tipo genérico debe ir seguida
de una implementación de tipo. Es lo mismo que una implementación de clase normal con una única excepción,
es decir, que cualquier identificador con el mismo nombre que uno de los identificadores de plantilla debe ser un
identificador de tipo.
Por lo tanto, la declaración de tipo genérico es muy parecida a una declaración de tipo normal, excepto que hay un tipo
aún desconocido presente. Los tipos desconocidos se enumeran en la lista de marcadores de posición y se desconocen
hasta que la clase se especializa.
Escriba
TList genérico<_T>=clase(TObject)
Público
tipo
TCompareFunc = función (const Elemento1, Elemento2: _T): Entero;
era
datos: _T;
procedimiento Agregar (elemento: _T);
procedimiento Ordenar (comparar: TCompareFunc); fin;
datos:=elemento; fin;
fin;
1. Hay un único marcador de posición _T. Será sustituido por un identificador de tipo cuando la clase
genérica sea especializada. El identificador _T no se puede utilizar para nada más que un marcador
de posición de tipo. Esto significa que lo siguiente no sería válido:
124
Machine Translated by Google
CAPÍTULO 8. GENÉRICOS
Era
_t: número entero;
2. El bloque de tipo local contiene un único tipo TCompareFunc. Tenga en cuenta que el tipo real aún no se
conoce dentro de la definición de clase genérica: la definición contiene una referencia al marcador de
posición _T. Todas las demás referencias de identificadores deben conocerse cuando se define la clase
genérica, no cuando la clase genérica está especializada.
TList genérico<_T>=clase(TObject)
Público
tipo
TCompareFunc = función (const Elemento1, Elemento2: _T): Entero;
Público
datos: _T;
procedimiento Agregar (elemento: _T);
procedimiento Ordenar (comparar: TCompareFunc); fin;
4. Tenga en cuenta que los nombres de los parámetros de tipo en la declaración y la implementación deben ser los mismos.
{$modo objfpc}
{$INTERFACES CORBA} tipo
125
Machine Translated by Google
CAPÍTULO 8. GENÉRICOS
Observación Unas palabras sobre la visibilidad: los tipos de plantilla T o _T están disponibles como tipos privados estrictos.
Eso significa que los tipos no están disponibles en las clases descendientes, a menos que estén disponibles a
través de algún mecanismo privado o protegido, como en el siguiente ejemplo:
Una vez definido un tipo genérico, se puede utilizar para generar otros tipos: esto es como reproducir la definición
de los tipos, con los marcadores de posición de la plantilla completados con definiciones de tipos reales.
Esto se puede hacer en cualquier bloque de definición de tipo. El tipo especializado tiene el siguiente aspecto:
tipo especializado
Que es una definición muy simple. Dada la declaración de TList en la sección anterior, la siguiente sería una
definición de tipo válida:
126
Machine Translated by Google
CAPÍTULO 8. GENÉRICOS
Tipo
TPointerList = especializar TList<Pointer>;
TIntegerList = especializar TList<Integer>;
A partir de la versión 3.0 de Free Pascal, la palabra clave especializar también se puede utilizar en una declaración de variable:
Era
P : especializarse TList<Pointer>;
La palabra clave de especialización es parte del tipo especializado, por lo que cuando se utilizan nombres completos, la palabra clave
de especialización debe estar después del nombre de la unidad y de los nombres del tipo principal.
Se debe conocer el tipo en la declaración especializada, excepto en otra definición de tipo genérico. Dadas las
dos definiciones de clases genéricas:
tipo
Genérico TMyFirstType<T1> = Clase(TMyObject);
Genérico TMySecondType<T2> = Clase(TMyOtherObject);
tipo
TMySpecialType = especializar TMySecondType<TMyFirstType>;
porque el tipo TMyFirstType es un tipo genérico y, por lo tanto, no está completamente definido. El compilador
se quejará:
tipo
TA = especializarse TMyFirstType<Tipo>;
TB = especializarse TMySecondType<TA>;
Sin embargo, la palabra clave especializarse se puede utilizar en otra definición de tipo genérico, como se muestra en
el ejemplo anterior:
En estas definiciones, la especialización sólo se realiza cuando el propio tipo genérico está especializado
y, en ese momento, se conocen los tipos.
Observación No es posible hacer una definición directa de una clase que sea una especialización de un genérico, es decir
lo siguiente no se compilará:
TMiClase = Clase;
// Otras declaraciones
127
Machine Translated by Google
CAPÍTULO 8. GENÉRICOS
El diagrama de la sección 8.1, página 123 muestra que la lista de plantillas de tipos puede tener especificadores adicionales
para los tipos. Esto es especialmente útil para tipos de objetos: si el tipo de plantilla debe descender de una determinada
clase, entonces esto se puede especificar en la lista de plantillas:
Escriba
TList genérico<_T: TComponent> = clase(TObject) pública
Escriba
TList genérico<Clave1,Clave2: TComponent; Valor1: TObjeto> = clase(TObjeto)
Además, es posible especificar más de un identificador de tipo para restricciones de tipo de clase y de interfaz. Si se
especifica una clase, entonces el tipo utilizado para la plantilla debe ser igual o inferior al tipo indicado:
Escriba
TList genérico<T: TComponent, IEnumerable> = clase(TObject)
Una clase utilizada para especializar T debe descender de TComponent y debe implementar IEnumerable.
Si se especifica una interfaz, entonces el tipo de plantilla debe implementar al menos esta interfaz, pero
también puede ser una interfaz descendiente de esta interfaz:
Escriba
TGenList genérico<T: IEnumerable> = clase(TObject)
128
Machine Translated by Google
CAPÍTULO 8. GENÉRICOS
fin;
Se pueden especificar múltiples interfaces, en ese caso el tipo de clase debe implementar todas las interfaces
enumeradas: es posible mezclar un nombre de clase con varios nombres de interfaz.
Si no hay restricciones de tipo vigentes, el compilador asumirá que los tipos de plantilla no son compatibles con
la asignación.
Esto es especialmente importante cuando la clase genérica contiene métodos sobrecargados. Dada la siguiente
declaración de tipo genérico:
escriba
TTest genérico<T1, T2> = clase
prueba de procedimiento (aArg: LongInt); prueba de
procedimiento (aArg: T1); prueba de
procedimiento (aArg: T2); que;
Especializarse en lo anterior compilará si T1 y T2 son de dos tipos diferentes y ninguno de ellos es también LongInt. Lo
siguiente compilará:
El soporte de genéricos de FPC se implementa de manera algo diferente a la de Delphi. En esta sección se
destacan las principales diferencias.
La sintaxis que se muestra en los diagramas de sintaxis es la sintaxis requerida en el modo ObjFPC del compilador.
Sin embargo, en modo Delphi, no se deben utilizar las palabras clave especializadas y genéricas, como se
muestra en el siguiente ejemplo:
Tipo
TTest<T> = Clase(TOobjeto)
Privado
FObj : T;
Público
Propiedad Obj: T Leer FObj Escribir FObj; fin;
TIntegerTest = TTest<Entero>;
129
Machine Translated by Google
CAPÍTULO 8. GENÉRICOS
A diferencia del modo Objfpc, los nombres de los tipos de plantilla deben repetirse en las definiciones de métodos.
Tipo
TTest<T> = Clase(TOobjeto)
Privado
FObj : T;
Público
Procedimiento DoIt;
Propiedad Obj: T Leer FObj Escribir FObj; fin;
Procedimiento TTest<T>.DoIt;
empezar
fin;
Este requisito está directamente relacionado con la capacidad de sobrecarga de tipo genérico mencionada en la siguiente
sección.
En modo Delphi, las restricciones de tipo de registro también permitirán el uso de tipos simples:
Tipo
TList<_T: registro> = clase(TObject) pública
ListaTInt = ListaT<Entero>;
La restricción se aplica al especializar el tipo. Eso significa que el modo activo al especializar un tipo determina si se
puede usar un tipo simple o no: si la restricción de registro se compiló usando el modo ObjFPC, el código escrito en
modo Delphi puede especializarlo con un tipo simple.
de todos modos.
Por ejemplo:
unidad tg;
interfaz
{$modo objfpc}
Escriba
TList genérico<_T: registro> = clase(TObject) pública
130
Machine Translated by Google
CAPÍTULO 8. GENÉRICOS
implementación
comenzar
fin;
empezar
fin;
fin.
modo Delphi permite sobrecargas de tipos genéricos. Esto significa que es posible declarar la misma clase
genérica con diferentes listas de tipos de plantilla. Por lo tanto, son posibles las siguientes declaraciones:
Tipo
TTest<T> = Clase(TOobjeto)
Privado
FObj : T;
Público
Propiedad Obj: T Leer FObj Escribir FObj; fin;
TTest<T,S> = Clase(TObjeto)
Privado
FObj1: T;
FObj2: S;
Público
Propiedad Obj1: T Leer FObj1 Escribir FObj1;
Propiedad Obj2: S Leer FObj2 Escribir FObj2; fin;
131
Machine Translated by Google
CAPÍTULO 8. GENÉRICOS
Tipo
TTest<T> = Clase(TOobjeto)
Privado
FObj : T;
Público
Propiedad Obj: T Leer FObj Escribir FObj; fin;
Era
TTest: Entero;
Siempre que una clase genérica se especializa, esto da como resultado un tipo nuevo y distinto. Estos tipos son
compatibles con la asignación si se utilizan los mismos tipos de plantilla.
interfaz
tipo
TMyClass genérico<T> = Clase(TObject)
Procedimiento Hacer algo (A: T; B: INteger); fin;
Implementación
fin.
interfaz
usos realizados;
132
Machine Translated by Google
CAPÍTULO 8. GENÉRICOS
Tipo
TB = Especializar TMyClass<cadena>;
implementación
fin.
las siguientes especializaciones son idénticas, pero aparecen en una unidad diferente:
interfaz
usos realizados;
Tipo
TB = Especializar TMyClass<cadena>;
implementación
fin.
interfaz
Era
B: [Link]; C:
[Link];
implementación
comenzar
B:=C;
fin.
Los tipos [Link] y [Link] son compatibles con la asignación. No importa que los tipos estén definidos en
unidades diferentes. También podrían definirse en la misma unidad:
interfaz
usos realizados;
Tipo
TB = Especializar TMyClass<cadena>;
133
Machine Translated by Google
CAPÍTULO 8. GENÉRICOS
TC = Especializar TMyClass<string>;
Era
B: tuberculosis;
C: TC;
implementación
comenzar
B:=C;
fin.
Cada especialización de una clase genérica con los mismos tipos que parámetros es un tipo nuevo y distinto, pero
estos tipos son compatibles con la asignación si los tipos de plantilla utilizados para especializarlos son iguales.
Si la especialización es con un tipo de plantilla diferente, los tipos siguen siendo distintos, pero ya no son
compatibles con la asignación. es decir, lo siguiente no se compilará:
interfaz
usos realizados;
Tipo
TB = Especializar TMyClass<cadena>;
TC = Especializar TMyClass<entero>;
Era
B: tuberculosis;
C: TC;
implementación
comenzar
B:=C;
fin.
Al escribir rutinas genéricas, a veces se debe inicializar una variable cuyo tipo no se conoce durante la declaración del
genérico. Aquí es donde también entra en juego el valor intrínseco predeterminado (sección 4.5, página 70). Dada la
siguiente declaración genérica:
tipo
134
Machine Translated by Google
CAPÍTULO 8. GENÉRICOS
procedimiento [Link];
era
mito: T;
comenzar // tendrá el valor predeterminado correcto si la clase está especializada myt := Default(T); fin;
Cabe destacar que todos los identificadores distintos de los marcadores de posición de la plantilla deben
conocerse cuando se declara la clase genérica. Al mismo tiempo, no se puede suponer nada sobre el tipo de
plantilla (a menos que se le imponga una restricción).
En ausencia de restricciones de tipo, el código genérico no puede hacer suposiciones sobre el tipo de
plantilla T. Considere la siguiente unidad:
unidad ts;
interfaz
{$modeswitch registros avanzados}
tipo
PListEl = ^TListEl;
TListEl = registro empaquetado
Anterior, Siguiente: PListEl; fin;
implementación
escriba
LstEnumerator genérico<T> = registrar lst privado, lst_save:
T; público
lista:=lista^.siguiente; fin;
135
Machine Translated by Google
CAPÍTULO 8. GENÉRICOS
Tipo
TMyListEnum = especializar LstEnumerator<TListEl>;
fin.
El compilador arrojará un error porque cuando compila la definición genérica, no puede verificar que
lista:=lista^.siguiente;
es correcto. lst es de tipo T, pero el compilador no sabe (todavía) qué es T y, por lo tanto, no puede saber si
tiene un campo siguiente.
unidad ts;
{$modo delphi} interfaz
tipo
TListEl = clase
Anterior, Siguiente: TListEl; fin;
TMyRecord1 = Clase(TListEl)
MiCampo: Cadena; fin;
TMyRecord2 = Clase(TListEl)
MiEntero: Entero; fin;
implementación
tipo
TLstEnumerator<T : TListEl> = clase lista privada, lst_save:
T; público
136
Machine Translated by Google
CAPÍTULO 8. GENÉRICOS
lista:=T([Link]); fin;
Tipo
TMyRecord1Enum = TLstEnumerator<TMyRecord1>;
TMyRecord2Enum = TLstEnumerator<TMyRecord2>;
Aquí, el compilador sabe que lst es al menos de tipo TListEl y, por tanto, contiene los miembros Prev y Next.
Además del tipo de plantilla, se deben conocer todos los demás tipos utilizados en la declaración genérica. Esto
significa que debe existir un identificador de tipo con el mismo nombre. La siguiente unidad producirá un error:
interfaz
tipo
TMyClass genérico<T> = Clase(TObject)
Procedimiento HacerAlgo(A: T; B: TAlgúnTipo); fin;
Tipo
TSomeType = Entero;
TSomeTypeClass = especializar TMyClass<TSomeType>;
Implementación
fin.
El código anterior generará un error, porque el tipo TSomeType no se conoce cuando se analiza la declaración:
Una segunda forma en que esto es visible es la siguiente. Supongamos una unidad
137
Machine Translated by Google
CAPÍTULO 8. GENÉRICOS
interfaz
tipo
TMyClass genérico<T> = Clase(TObject)
Procedimiento Hacer algo (A: T); fin;
Implementación
Procedimiento DoLocalThings;
comenzar
Writeln('[Link]'); fin;
comenzar
hacer cosas locales; fin;
fin.
El compilador no permitirá compilar esta unidad, ya que la función DoLocalThings no será visible cuando el tipo genérico
sea especializado:
Error: la plantilla genérica global hace referencia a una tabla simbólica estática
Ahora, si se modifica la unidad y la función DoLocalThings se mueve a la sección de interfaz, la unidad se compilará. Al
usar este genérico en un programa:
usa mya;
procedimiento DoLocalThings;
comenzar
Writeln('[Link]'); fin;
Tipo
TB = especializarse TMyClass<Integer>;
Era
B: tuberculosis;
comenzar
B:=[Link]; [Link]
algo(1); fin.
138
Machine Translated by Google
CAPÍTULO 8. GENÉRICOS
A pesar de que los genéricos actúan como una macro que se reproduce en el momento de la especialización,
la referencia a DoLocalThings se resuelve cuando se define TMyClass, no cuando se define TB. Esto
significa que la salida del programa es:
1. Un programador especializado en una clase no tiene forma de saber qué procedimientos locales se utilizan, por lo que
no puede “anularlo” accidentalmente.
2. Un programador especializado en una clase no tiene forma de saber qué procedimientos locales se
utilizan, por lo que tampoco puede implementarlo, ya que no conoce los parámetros.
3. Si se utilizan procedimientos de implementación como en el ejemplo anterior, no se puede hacer referencia a ellos
desde fuera de la unidad. Podrían estar en otra unidad y el programador no tiene forma de saber si debe incluirlos
antes de especializar su clase.
La sobrecarga de operadores (capítulo 15, página 233) y los genéricos están estrechamente relacionados. Imagine una clase
genérica que tiene la siguiente definición:
interfaz
tipo
TMyClass genérico<T> = Clase(TObject)
Función Agregar(A,B: T): T; fin;
Implementación
comenzar
Resultado:=A+B; fin;
fin.
Cuando el compilador reproduce la macro genérica, la adición debe ser posible. Para una especialización
como esta:
139
Machine Translated by Google
CAPÍTULO 8. GENÉRICOS
comenzar
Resultado:=A+B; fin;
El compilador sabe cómo sumar dos números enteros, por lo que este código se compilará sin problemas. Pero
el siguiente código:
Tipo
TComplex = registro
Re, Im: Doble; fin;
Tipo
TMyIntegerClass = especializar TMyClass<TComplex>;
No se compilará, a menos que se defina la adición de dos tipos TComplex. Esto se puede hacer usando
operadores de registro:
Tipo
TComplex = registro
Re, Im: Doble; operador
de clase +(a,b: TComplex): TComplex; fin;
comenzar
[Link]:=[Link]+[Link];
[Link]:=[Link]+[Link]; fin;
Tipo
TMyComplexClass = especializar TMyClass<TComplex>;
comenzar
// Código aquí
fin.
usa mya;
Tipo
TComplex = registro
Re, Im: Doble; fin;
140
Machine Translated by Google
CAPÍTULO 8. GENÉRICOS
comenzar
[Link]:=[Link]+[Link];
[Link]:=[Link]+[Link]; fin;
Tipo
TMyComplexClass = especializar TMyClass<TComplex>;
comenzar
// Código aquí
fin.
Se espera soporte para esta construcción en una versión futura de Free Pascal.
141
Machine Translated by Google
Capítulo 9
Registros extendidos
9.1 De nición
Los registros extendidos son en muchos sentidos equivalentes a objetos y, en menor medida, a clases: son
registros que tienen métodos y propiedades asociados. Al igual que los objetos, cuando se definen como una
variable, se asignan en la pila. No necesitan tener constructor. Los registros extendidos tienen limitaciones sobre
los objetos y clases en el sentido de que no permiten herencia ni polimorfismo. Es imposible crear un registro
descendiente de un registro1.
¿Por qué entonces introducir registros extendidos? Fueron introducidos por Delphi 2005 para admitir una de las
características introducidas por .NET. Delphi desaprobó el antiguo estilo TP de objetos y reintrodujo las
características de .NET como registros extendidos. Free Pascal pretende ser compatible con Delphi, por lo que
también se permiten registros extendidos en Free Pascal, pero sólo en modo Delphi.
Si se desean registros extendidos en el modo ObjFPC, entonces se debe usar un interruptor de modo:
{$modo objfpc}
{$modeswitch advancedrecords}
La compatibilidad no es la única razón para introducir registros ampliados. Existen algunas razones prácticas
para utilizar métodos o propiedades en los registros:
1. Está más en línea con un enfoque de programación orientado a objetos: el tipo también contiene cualquier
métodos que funcionan en ello.
2. A diferencia de un enfoque procedimental, colocar todas las operaciones que funcionan en un registro en el
registro mismo permite que un IDE muestre los métodos disponibles en el registro cuando muestra las
opciones de finalización de código.
registro
Fin de la lista de 6 componentes
lleno
1aunque se puede mejorar utilizando asistentes de registro, encontrará más información sobre esto en el capítulo sobre asistentes de registro.
142
Machine Translated by Google
lista de componentes
especi cador de visibilidad de registros 6 definición de campo
6 definición del método de registro 6 definición de propiedad
parte variante
6 de nición del operador de registros
estricto
Algunas de las restricciones en comparación con clases u objetos son obvias en el diagrama de sintaxis:
• No herencia de registros.
• Los métodos de clase (si es posible nombrarlos así) requieren la palabra clave estática.
• Los métodos no pueden ser virtuales ni abstractos; esto es una consecuencia del hecho de que no hay herencia.
tancia.
Aparte de eso, la definición se parece mucho a la de una clase u objeto, excepto que los operadores se pueden
definir en un registro extendido.
Observación En el caso de un registro variante, la palabra clave Case inicia implícitamente una sección var, lo que significa que
las variables o métodos de clase no están permitidos en la parte variante del registro.
TTest1 = registro
a: número entero;
función Prueba(aRecurse: Booleano): Entero; fin;
TTest2 = registro
privado
A,b: número entero;
conjunto
de procedimiento públicoA (AValue: entero);
propiedad SafeA: Entero Lectura A Escritura ConjuntoA; fin;
143
Machine Translated by Google
privado
fA,fb: byte;
procedimiento setA(AValue: Entero); función geta: número
entero; propiedad pública A: Entero Leer
GetA
Escribir SetA; fin;
TTest4 = registro
privado a:
entero; función
protegida getp:
entero; público b: cadena; configuración
del
procedimiento
(unValor: entero); propiedad p: entero leer Getp Write SetP;
caso público x: número entero de
1: (P: cadena[10]);
2: (S: Cadena[10]); fin;
Tenga en cuenta que es posible especificar una visibilidad para los miembros del registro. Esto es particularmente
útil, por ejemplo, al crear una interfaz para una biblioteca C: los campos reales se pueden declarar ocultos y se
pueden exponer más propiedades tipo "pascal" que actúan como los campos reales. La definición de registro
TTest3 muestra que la directiva empaquetada se puede utilizar en registros extendidos. Los registros extendidos
tienen el mismo diseño de memoria que sus contrapartes regulares: los métodos y propiedades no forman parte
de la estructura del registro en la memoria.
La definición de registro TTest4 en los ejemplos anteriores muestra que el registro extendido aún tiene la capacidad
de definir una parte variante. Al igual que con el registro regular, la parte variante debe aparecer al final. No puede
contener métodos.
Los registros extendidos pueden tener un enumerador. Para ello, se debe definir en el registro extendido una
función que devuelva un registro enumerador:
{$mode objfpc}
{$modeswitch advancedrecords} tipo TIntArray =
matriz[0..3] de Integer;
TEnumerador = registro
privado
Índice FI: Entero;
Farray: TIntArray; función
GetCurrent: Entero; función pública MoveNext:
booleana;
propiedad Actual: Entero leído GetCurrent; fin;
144
Machine Translated by Google
TMyArray = registro F:
matriz[0..3] de Entero; función GetEnumerator:
TEnumerator; fin;
Resultado: = FIndex
<Longitud (FArray); fin;
FArray[FIndex]; fin;
Después de estas definiciones, el siguiente código compilará y enumerará todos los elementos en F:
era
Arr: TMyArray;
I: Entero; comenzar
por yo
en Arr do
EscribirLn(I);
fin.
{$mode objfpc}
{$modeswitch advancedrecords} tipo TIntArray =
matriz[0..3] de Integer;
TEnumerador = registro
privado
Índice FI: Entero;
Farray: TIntArray; función
GetCurrent: Entero; función pública MoveNext:
booleana;
propiedad Actual: Entero leído GetCurrent; fin;
TMyArray = registro F:
matriz[0..3] de Entero; fin;
145
Machine Translated by Google
Resultado: = FIndex
<Longitud (FArray); fin;
FArray[FIndex]; fin;
La sobrecarga del operador se trata en detalle en el capítulo sobre sobrecarga del operador capítulo 15, página 233.
Sin embargo, dado que Delphi implementó la sobrecarga de operadores como parte de los registros avanzados, es necesario
decir algunas palabras al respecto aquí.
Como se puede ver en el diagrama de sintaxis para registros extendidos, FPC admite la sintaxis Delphi para operadores
en registros extendidos. Esta sintaxis está disponible tanto en modo ObjFPC como en modo Delphi.
En modo objfpc, los operadores deben definirse con sus nombres simbólicos, al igual que las sobrecargas de
operadores normales:
{$modo objfpc}
{$modeswitch advancedrecords}
Tipo
TComplex = registro
Re, Im: Doble; operador
de clase +(a,b: TComplex): TComplex; fin;
[Link]:=[Link]+[Link]; fin;
Los operadores funcionan tal como lo harían si se definieran utilizando la sintaxis FPC normal para operadores, pero
esta sintaxis, por supuesto, se limita a los tipos de registros. Tenga en cuenta que tienen la palabra clave class como
prefijo y el nombre del tipo de registro antepuesto en la implementación.
Como se indicó anteriormente, en el modo ObjFPC, los operadores deben indicarse con sus nombres de símbolos.
Por el contrario, en modo Delphi también se pueden utilizar los nombres de los operadores, similar a la sintaxis en
Delphi:
{$modo delphi}
146
Machine Translated by Google
Tipo
TComplex = registro
Re, Im: Doble;
operador de clase agregar(a,b: TComplex): TComplex;
fin;
Por supuesto, esto se debe a que la sintaxis debe ser compatible con Delphi.
A continuación se muestra una tabla que vincula los nombres de los operadores simbólicos con el nombre en texto sin formato. Tenga en cuenta que algunos FPC
división div
contra módulo
y bit a bit o lógico y
o bit a bit o lógico
xor bit a bit
no es lógicono
Por ejemplo, el operador de energía (**) se puede utilizar en modo Delphi usando su nombre simbólico:
{$modo delphi}
{$modeswitch registros avanzados}
Tipo
TComplex = registro
Re, Im: Doble;
operador de clase **(a,b: TComplex): TComplex;
fin;
147
Machine Translated by Google
[Link]:=[Link]; fin;
Era
a,b : TComplejo; comenzar
a:=a**b;
fin.
Puede encontrar más información sobre la sobrecarga del operador en el capítulo sobre sobrecarga del operador
capítulo 15, página 233.
148
Machine Translated by Google
Capítulo 10
10.1 Definición
Los ayudantes de clase, registro y tipo se pueden utilizar para agregar métodos a una clase, registro o tipo simple
existente, sin realizar una derivación de la clase ni volver a declarar el registro.
Para un registro o un tipo simple, el asistente de tipo actúa como si el registro o el tipo simple fuera una
clase y se declaran métodos para él. Dentro de los métodos, Self hará referencia al valor del registro o tipo
simple.
Para las clases, el efecto es como insertar un método en la tabla de métodos de la clase. Si la declaración
auxiliar está en el alcance actual del código, entonces los métodos y propiedades del asistente se pueden
usar como si fueran parte de la declaración de clase para la clase o registro que extiende el asistente.
A continuación se presenta el diagrama de sintaxis para una clase, registro o tipo auxiliar:
Tipo de ayudante
El diagrama muestra que una definición auxiliar se parece mucho a una definición de clase normal. Simplemente
declara algunos constructores, métodos, propiedades y campos adicionales para una clase: la clase, registro o
tipo simple para el cual el ayudante es una extensión se indica después de la palabra clave for.
Dado que un enumerador para una clase se obtiene mediante un método normal, los ayudantes de clase también se pueden
utilizar para anular los enumeradores de la clase.
Como se puede ver en el diagrama de sintaxis, es posible crear descendientes de ayudantes: los ayudantes pueden
formar una jerarquía propia, lo que permite anular los métodos de un ayudante principal. También tienen
especificadores de visibilidad, al igual que los registros y las clases.
Como en una instancia de la clase, el identificador Self en un método de una clase auxiliar se refiere a la
instancia de la clase (no a la instancia auxiliar). Para un registro, se refiere al registro.
149
Machine Translated by Google
El siguiente es un asistente de clase simple para la clase TObject, que proporciona una versión alternativa del método
ToString estándar.
[ToString]); fin;
era
o: TObjeto;
comenzar
Writeln([Link]('El nombre del objeto es %s')); fin.
Observación El modificador auxiliar es solo un modificador justo después de las palabras clave de clase o registro. Eso significa que el
primer miembro de una clase o registro no puede ser nombrado ayudante. A un miembro de una clase o registro se le puede
llamar ayudante, pero no puede ser el primero, a menos que se le escape con un &, como ocurre con todos los identificadores
que coinciden con una palabra clave.
Observación La compatibilidad con ayudantes de tipos requiere el uso del modo Delphi o el uso de un interruptor de modo de ayudantes de tipos.
en otros modos:
{$MODESWITCH TYPEHELPERS}
Observación El soporte para asistentes de registros requiere el uso del modo Delphi o el uso de registros avanzados.
cambio de modo en otros modos:
{$MODESWITCH ADVANCEDRECORDS}
No es posible ampliar una clase con ningún método o propiedad. Existen algunas restricciones sobre las posibilidades:
• Los ayudantes de clase no pueden descender de los ayudantes de registros y no pueden ampliar los tipos de registros.
• No se permiten propiedades que hagan referencia a un campo. De hecho, esto es una consecuencia de lo anterior.
artículo.
• Los métodos virtuales de la clase no se pueden anular. Se pueden ocultar dándoles el mismo nombre o se pueden
sobrecargar usando la directiva de sobrecarga.
• A diferencia de los procedimientos o métodos normales, el especificador de sobrecarga debe usarse explícitamente
al sobrecargar métodos de una clase en un asistente de clase. Si no se utiliza la sobrecarga, el método auxiliar
oculta el método del tipo extendido (como ocurre con las clases regulares).
150
Machine Translated by Google
[ToString]); fin;
era
o: TObjeto;
comenzar
Writeln([Link]('El nombre del objeto es %s')); fin.
Los registros no ofrecen las mismas posibilidades que las clases. Esto se refleja en las posibilidades a la hora de crear
ayudas de registro. A continuación se enumeran las restricciones sobre los asistentes de registros:
• No se puede utilizar un asistente de registro para ampliar una clase. Lo siguiente fallará:
• Dentro de la declaración de una ayuda, no se puede acceder a los métodos/campos del registro extendido, por
ejemplo, en una definición de propiedad. Por supuesto, se puede acceder a ellos en la implementación. Esto
significa que lo siguiente no se compilará:
TTest = registro
• Los asistentes de registro solo pueden acceder a campos públicos (en caso de que se trate de un registro extendido con visibilidad específica).
se utiliza fuegos).
• Los ayudantes de registro sólo pueden descender de otros ayudantes de registro, no de ayudantes de clase.
• A diferencia de los ayudantes de clase, un ayudante de registro descendiente debe extender el mismo tipo de registro.
• En modo Delphi, no es posible llamar al método del registro extendido usando heredado. Es posible hacerlo
en modo ObjFPC. El siguiente código necesita el modo ObjFPC para compilarse:
tipo
TTest = registro
151
Machine Translated by Google
Para tipos simples, las reglas son prácticamente las mismas que para los registros, además hay algunos requisitos
adicionales:
• La compatibilidad con los ayudantes de tipo debe activarse mediante los ayudantes de tipo del interruptor de modo:
Este interruptor de modo está habilitado de forma predeterminada solo en el modo Delphi y DelphiUnicode.
• En el modo Delphi (y DelphiUnicode), para una compatibilidad más estricta con Delphi, los asistentes de registro deben
usarse en lugar de un ayudante de tipo.
• Los modos ObjFPC y MacPas usan ayuda de tipo, pero el interruptor de modo TypeHelpers debe estar
usado.
– Tipos como registros, clases, clases Objective C, clases C++, objetos e interfaces también están prohibidos;
el asistente de clase debe usarse para las clases. Eso significa que, por ejemplo, lo siguiente fallará:
Por supuesto, esto significa que se admiten todos los demás tipos simples.
152
Machine Translated by Google
• Los ayudantes de tipo sólo pueden descender de otros ayudantes de tipo, no de ayudantes de clase o de registro.
{$mode objfpc}
{$modeswitch typehelpers}
tipo
TLongIntHelper = ayuda de tipo para el constructor LongInt
create(AValue: LongInt); procedimiento de clase Prueba;
estático; procedimiento DoPrint; fin;
comenzar
Self:=Evalue;
HacerImprimir;
fin;
comenzar
Writeln('Prueba'); fin;
procedimiento [Link];
comenzar
Writeln('Valor:',Self);
fin;
era
i: Int Largo;
comienzo
I:=123; Yo
evalúo;
$[Link];
Prueba LongInt;
Yo:=123;
[Link];
$[Link]; fin.
10.5 Una nota sobre el alcance y la vida útil de los asistentes de registro y tipo
Para las clases, el programador gestiona explícitamente la vida útil de una instancia de una clase. Por tanto, queda
claro qué significa el parámetro Self y cuándo es válido.
Los registros y otros tipos simples se asignan en la pila, lo que significa que quedan fuera de alcance cuando sale la
función, procedimiento o método en el que se utilizan.
153
Machine Translated by Google
Combinado con el hecho de que los métodos auxiliares son compatibles con los tipos de métodos de clase y, por lo tanto, pueden
usarse como controladores de eventos, esto puede conducir a situaciones sorprendentes: el puntero de datos en un método
auxiliar se establece en la dirección de la variable.
{$mode objfpc}
{$modeswitch typehelpers}
usos
Clases;
tipo
TInt32Helper = escriba ayuda para el procedimiento Int32
Foo(Sender: TObject); fin;
Writeln(Self); fin;
era
yo: Int32 = 10; m:
TNotifyEvent; comenzar
m :=
@[Link];
WriteLn('Datos: ',PtrUInt(TMethod(m).Datos)); m(nulo);
fin.
Esto imprimirá algo como (el valor real de los datos puede diferir):
Datos : 6848896
10
{$mode objfpc}
{$modeswitch typehelpers}
usos
Clases;
tipo
TInt32Helper = escriba ayuda para el procedimiento Int32
Foo(Sender: TObject); fin;
Writeln(Self); fin;
154
Machine Translated by Google
era
yo: Int32 = 10;
comenzar
Resultado:=@[Link]; fin;
Era
m: TNotifyEvent; comenzar
m :=
GetHandler;
WriteLn(PtrUInt(TMethod(m).Data)); m(nilo);
fin.
La salida será:
140727246638796
0
El resultado real dependerá de la arquitectura, pero el punto es que i ya no está dentro del alcance, lo que hace que
el resultado de su valor carezca de significado y posiblemente incluso provoque violaciones de acceso y fallas del
programa.
10.6 Herencia
Como se señaló en la sección anterior, es posible crear descendientes de clases auxiliares. Dado que solo se puede
utilizar la última clase auxiliar en el ámbito actual, es necesario descender una clase auxiliar de otra si se deben
utilizar métodos de ambos auxiliares. Más sobre esto en una sección posterior.
Un descendiente de un ayudante de clase puede extender una clase diferente a la de su padre. El siguiente es un
asistente de clase válido para TMyObject:
TMyObjectHelper extiende TObjectHelper, pero no extiende la clase TObject, solo extiende la clase TMyObject.
Dado que los registros no conocen herencia, es obvio que los descendientes de los ayudantes de registros sólo pueden
extender el mismo registro.
Observación Para lograr la máxima compatibilidad con Delphi, es imposible crear descendientes de asistentes de registros en Delphi.
modo.
155
Machine Translated by Google
10.7 Uso
Una vez que se define una clase auxiliar, sus métodos se pueden usar siempre que la clase auxiliar esté dentro del alcance.
Esto significa que si se define en una unidad separada, entonces esta unidad debe estar en la cláusula de usos dondequiera
que se utilicen los métodos de la clase auxiliar.
interfaz
Tipo
TObjectHelper = ayudante de clase para TObject
función AsString(const aFormat: String): String; fin;
implementación
utiliza sysutils;
comenzar
Resultado := Formato(aFormat, [ToString]); fin;
fin.
usa oha;
Era
o : TObjeto;
comenzar
O:=[Link];
Writeln([Link]('O como cadena: %s')); fin.
interfaz
156
Machine Translated by Google
Tipo
TAObjectHelper = ayudante de clase para la función TObject
MemoryLocation: String; fin;
implementación
utiliza sysutils;
comenzar
Resultado := formato('%p',[puntero(Self)]); fin;
fin.
Era
o : TObjeto;
comenzar
O:=[Link];
Writeln([Link]('O como cadena: %s')); Writeln([Link]);
fin.
Entonces el compilador se quejará de que no conoce el método "AsString". Esto se debe a que el compilador
deja de buscar ayudantes de clase tan pronto como encuentra el primer ayudante de clase. Dado que la unidad
ohb ocupa el último lugar en la cláusula de usos, el compilador solo utilizará TAObjectHelper como ayudante
de clase.
La solución es volver a implementar la unidad ohb:
interfaz
usa oha;
Tipo
TAObjectHelper = ayudante de clase (TObjectHelper) para TObject
función Ubicación de la memoria: Cadena; fin;
implementación
157
Machine Translated by Google
utiliza sysutils;
comenzar
Resultado := formato('%p',[puntero(Self)]); fin;
fin.
Y después de reemplazar la unidad ohb con ohc, el programa de ejemplo se compilará y funcionará como se esperaba.
Tenga en cuenta que no es suficiente incluir una unidad con un ayudante de clase una vez en un proyecto; La unidad debe
incluirse siempre que se necesite el ayudante de clase.
158
Machine Translated by Google
Capítulo 11
Clases objetivasPascal
11.1 Introducción
El lenguaje de programación preferido para acceder a los marcos del sistema Mac OS X es ObjectiveC. Para aprovechar
plenamente el potencial que ofrecen las interfaces del sistema escritas en ese lenguaje, existe una variante de Object Pascal
en el compilador Free Pascal que intenta ofrecer la misma funcionalidad que Objective C. Esta variante se llama Objective
Pascal.
El compilador tiene conmutadores de modo para permitir el uso de estas construcciones relacionadas con ObjectiveC. Hay
dos tipos de características del lenguaje ObjectiveC, distinguidas por un número de versión: ObjectiveC 1.0 y ObjectiveC
2.0.
Las características del lenguaje ObjectiveC 1.0 se pueden habilitar agregando un interruptor de modo al archivo fuente:
Las funciones del lenguaje ObjectiveC 2.0 se pueden habilitar usando un interruptor de modo similar:
Las características del lenguaje ObjectiveC 2.0 son un superconjunto de las características del lenguaje ObjectiveC 1.0 y,
por lo tanto, el último cambio implica automáticamente el primero. Los programas que utilizan funciones del lenguaje
ObjectiveC 2.0 solo funcionarán en Mac OS X 10.5 y posteriores.
El hecho de que las funciones de ObjectiveC se habiliten mediante interruptores de modo en lugar de modos de sintaxis
reales, significa que se pueden usar en combinación con todos los modos de sintaxis generales (fpc, objfpc, tp, delphi, mac
pas). Tenga en cuenta que un interruptor de directiva {$Mode } restablecerá los interruptores de modo, por lo que la
instrucción {$modeswitch } debe ubicarse después de él.
159
Machine Translated by Google
clase de objeto
externo
constante de cadena de nombre
herencia fin
6 especificación de
herencia ( )
identificador de tipo de clase objetivoC protocolos implementados
lista de componentes
especificador de visibilidad definición de campo
6
6 clase declaración de variable parte
definición del método
definición de propiedad
;
anular la
constante de cadena de mensaje
estrictamente
protegido
Como puede verse, la sintaxis es aproximadamente equivalente a la sintaxis de Object Pascal, con algunas extensiones.
Para utilizar clases ObjectiveC, existe un modificador externo: esto indica al compilador que la clase
está implementada en un archivo o biblioteca objeto externo, y que la definición está destinada a fines
de importación. El siguiente es un ejemplo de una definición de clase ObjectiveC externa:
160
Machine Translated by Google
Como puede verse, la definición de clase no es tan diferente de una definición de clase de Object Pascal; Solo
la directiva de mensaje está presente de manera más destacada: cada método ObjectiveC u ObjectivePascal
debe tener un nombre de mensaje asociado. En el ejemplo anterior, no se especificó ningún nombre externo
para la definición de clase, lo que significa que el identificador Pascal se utiliza como nombre de la clase ObjectiveC.
Sin embargo, dado que ObjectiveC no es tan estricto en sus convenciones de nomenclatura, a veces se debe crear un
alias para un nombre de clase de ObjectiveC que no obedece las reglas del identificador de Pascal.
La ausencia de la palabra clave externa le dice al compilador que los métodos deben implementarse más adelante en el
archivo fuente: será tratado como una clase pascal de objeto normal. Tenga en cuenta la presencia de la directiva de
anulación: en ObjectiveC, todos los métodos son virtuales. En Object Pascal, la anulación de un método virtual debe
realizarse mediante la directiva override. Esto se ha extendido a las clases ObjectiveC: permite al compilador verificar la
exactitud de la definición.
A menos que la clase esté implementando el método de un protocolo (más sobre esto en una sección posterior), se
espera uno de mensaje o anulación: todos los métodos son virtuales y se inicia (o reintroduce) un nuevo método, o uno
existente. está anulado. Sólo en el caso de un método que forma parte de un protocolo, el método se puede definir sin
mensaje ni anulación.
Tenga en cuenta que la declaración de clase ObjectiveC puede especificar o no una clase principal. En Object Pas cal,
omitir una clase principal automáticamente hará que la nueva clase sea descendiente de TObject. En ObjectiveC, este
no es el caso: la nueva clase será una nueva clase raíz. Sin embargo, ObjectiveC sí tiene una clase que cumple la
función de clase raíz genérica: NSObject, que puede considerarse el equivalente de TObject en Object Pascal. Tiene
otras clases raíz, pero en general, las clases Objective Pascal deberían descender de NSObject. Si de todos modos se
construye una nueva clase raíz, debe implementar NSObjectProtocol, tal como lo hace la propia clase NSObject.
Finalmente, las clases de ObjectivePascal pueden tener propiedades, pero estas propiedades solo se pueden usar
en código Pascal: el compilador actualmente no exporta las propiedades de una manera que las haga utilizables
desde ObjectiveC.
El objeto Pascal tiene el concepto de declaraciones directas. ObjectiveC lleva este concepto un poco más
allá: permite declarar una clase que está definida en otra unidad. Esto ha sido denominado “declaración
formal” en ObjectivePascal. Mirando el diagrama de sintaxis, la siguiente es una declaración válida:
161
Machine Translated by Google
Esta es una declaración formal. Le dice al compilador que MyExternalClass es un tipo de clase ObjectiveC, pero
que no hay ninguna declaración de los miembros de la clase. El tipo se puede usar en el resto de la unidad, pero
su uso está restringido a la asignación de almacenamiento (en una definición de parámetro de método o campo) y
asignación (muy parecido a un puntero).
Tan pronto como se encuentra la definición de clase, el compilador puede imponer la compatibilidad de tipos.
unidad ContainerClass;
{$modo objfpc}
{$modeswitch objetivoc1}
interfaz
tipo
MyItemClass = objcclass externo;
implementación
fin.
unidad ItemClass;
{$modo objfpc}
{$modeswitch objetivoc1}
interfaz
tipo
MyItemClass = objcclass(NSObject) contenido privado:
entero largo;
función pública initWithContent(c:
longint):
MyItemClass;
mensaje 'initWithContent:'; función getContent:
entero largo; mensaje 'obtenerContenido';
fin;
162
Machine Translated by Google
implementación
resultado:=contenido; fin;
fin.
Si se utilizan ambas unidades en un programa, el compilador sabe cuál es la clase y puede verificar la exactitud
de algunas asignaciones:
{$modo objfpc}
{$modeswitch objetivoc1}
usos
Clase de artículo, clase de contenedor;
era
c: MiClaseContenedor; l: entero
largo; comenzar
El diagrama de sintaxis de las clases de ObjectiveC muestra que la noción de constructor y destructor no es
compatible con ObjectiveC. Las nuevas instancias se crean en un proceso de 2 pasos:
1. Llame al método "alloc" (envíe un mensaje "alloc"): este es un método de clase de NSObject y devuelve un puntero
a la memoria para la nueva instancia. El uso de alloc es una convención en ObjectiveC.
2. Envíe un mensaje "initXXX". Por convención, todas las clases tienen uno o más métodos "InitXXX" que inicializan
todos los campos de la instancia. Este método devolverá el puntero de instancia final, que puede ser Nil.
era
objeto: NSObject;
comenzar
163
Machine Translated by Google
Por convención, el método initXXX devolverá Nil si falla la inicialización de algunos campos, por lo que
es imperativo que se pruebe el resultado de la función.
De manera similar, no existe ningún destructor privilegiado; Por convención, el método dealloc realiza la
limpieza de las instancias. Este método se puede anular para realizar cualquier limpieza necesaria. Al igual
que Destroy, nunca se debe llamar directamente; en su lugar, se debe llamar al método de liberación: todas
las instancias en ObjectiveC cuentan con referencias, y la liberación solo llamará a dealloc si el recuento de
referencias llega a cero.
En ObjectiveC, los protocolos desempeñan el papel que desempeñan las interfaces en Object Pascal, pero existen
algunas diferencias:
• Los métodos de protocolo se pueden marcar como opcionales, es decir, la clase que implementa el protocolo puede decidir
no implementar estos métodos.
Las clases de ObjectiveC pueden indicar qué protocolos implementan en la definición de clase, como se puede
ver en el diagrama de sintaxis de las clases de ObjectiveC.
El siguiente diagrama muestra cómo declarar un protocolo. Comienza con la palabra clave objcprotocol:
Tipo de protocolo
objcprotocolo _
externo herencia
constante de cadena de nombre
final de la lista de métodos de protocolo
)
herencia ( 6 identificador de tipo de protocolo
,
Como en el caso de las clases ObjectivePascal, el especificador externo le dice al compilador que la declaración es una
importación de un protocolo definido en otro lugar. Para los métodos, se aplican casi las mismas reglas que para los
métodos en las declaraciones de clase ObjectivePascal. La excepción es que los especificadores de mensajes deben
estar presentes.
Los especificadores obligatorios y opcionales antes de una serie de declaraciones de métodos son opcionales.
Si no se especifica ninguno, se asume requerido. La siguiente es una definición de protocolo:
164
Machine Translated by Google
tipo
MyProtocol = objccprotocol // por
defecto se requiere el
procedimiento aRequiredMethod;
mensaje 'un método requerido';
procedimiento
opcional anOptionalMethodWithPara(para: longint); mensaje 'un método
opcional con parámetro:';
procedimiento otro método opcional;
mensaje 'otroMétodoOpcional'; función requerida
Tenga en cuenta que en la declaración de clase, se omitió el especificador de mensaje. El compilador (y el tiempo de ejecución)
pueden deducirlo de la definición del protocolo.
11.6 Categorías
Al igual que los ayudantes de clase en Object Pascal, ObjectiveC tiene categorías. Las categorías permiten
ampliar clases sin crear realmente un descendiente de estas clases. Sin embargo, las categorías de Objective
C proporcionan más funciones que un ayudante de clase:
1. En Object Pascal, solo una clase auxiliar puede estar dentro del alcance (la última). En ObjectiveC, múltiples
Las categorías pueden estar dentro del alcance al mismo tiempo para una clase en particular.
2. En Object Pascal, un método auxiliar no puede cambiar un método existente presente en la clase original
(pero puede ocultar un método). En ObjectiveC, una categoría también puede reemplazar métodos
existentes en otra clase en lugar de solo agregar nuevos. Dado que todos los métodos son virtuales en
ObjectiveC, esto también significa que este método cambia para todas las clases que heredan de la clase
en la que se reemplazó el método (a menos que lo anulen).
3. Los ayudantes de Object Pascal no se pueden utilizar para agregar interfaces a clases existentes. Por el contrario, un
La categoría ObjectiveC también puede implementar protocolos.
La definición de una clase ObjectiveC se parece mucho a una definición de protocolo y comienza con
la palabra clave objccategory:
tipo de categoría
categoría de objeto
externo herencia
constante de cadena de nombre
fin de la lista de métodos de categoría
165
Machine Translated by Google
Tenga en cuenta nuevamente la posibilidad de un alias para categorías definidas externamente: ObjectiveC
2.0 permite un nombre de categoría vacío. Tenga en cuenta que el modificador reintroducir debe usarse si se
reemplaza un método existente en lugar de agregar un nuevo método.
Al reemplazar un método, llamar a "heredado" no llamará al método original de la clase, sino que llamará a la
implementación del método de la clase principal.
MiProtocolo = protocoloobjc
método protocolo de procedimiento; mensaje 'método de protocolo'; fin;
MiCategoría = objetocategoría(NSObject,MiProtocolo)
hash de función: cuint; reintroducir; método protocolo de
procedimiento; // de MiProtocolo. procedimiento de clase nuevo método; mensaje
'nuevo método'; fin;
Tenga en cuenta que esta declaración reemplaza el método Hash de cada clase que desciende de NSObject (a
menos que lo anule específicamente).
En Object Pascal, cada identificador debe ser único en su espacio de nombres: la unidad. En ObjectiveC, este
no tiene por qué ser el caso y cada identificador de tipo debe ser único entre su tipo: clases, protocolos,
categorías, campos o métodos. Esto se muestra en las definiciones del protocolo básico y la clase de Objective
C: tanto el protocolo como la clase se denominan NSObject.
Al importar clases y protocolos ObjectiveC, los nombres ObjectivePascal de estos tipos deben ajustarse a las
reglas de Object Pascal y, por lo tanto, deben tener nombres distintos. Asimismo, los nombres que son identificadores
válidos en ObjectiveC pueden ser palabras reservadas en Object Pascal. También se les debe cambiar el nombre
cuando se importen.
Para que esto sea posible, los modificadores Externo y "mensaje" permiten especificar un nombre: este es
el nombre del tipo o método tal como existe en ObjectiveC:
NSObjectProtocol = objcprotocol nombre externo función 'NSObject' _class: pobjc_class; nombre del
mensaje 'clase'; fin;
166
Machine Translated by Google
11.8 Selectores
Un Selector en ObjectiveC puede verse como un equivalente a un tipo de procedimiento en Object Pascal.
A diferencia del tipo procedimental, ObjectiveC tiene un solo tipo de selector: SEL. Se define en la unidad objc, que se incluye
automáticamente en la cláusula de usos de cualquier unidad compilada con el interruptor de modo Objectivec1.
Para asignar un valor a una variable de tipo SEL se debe utilizar el método objcselector:
El tipo de identificación es especial en ObjectiveC/Pascal. Es muy parecido al tipo de puntero en Object Pascal, excepto que
es una clase real. Es compatible con asignaciones con instancias de cada tipo de objcclass y objcprotocol, en dos direcciones:
1. las variables de cualquier tipo objcclass/objcprotocol se pueden asignar a una variable del tipo
identificación.
Además, cualquier método de ObjectiveC declarado en una objcclass u objccategory que esté dentro del alcance se puede
llamar cuando se utiliza una variable de tipo id.
Si, en tiempo de ejecución, la instancia de objcclass real almacenada en la variable de tipo id no responde al mensaje enviado,
el programa terminará con un error de tiempo de ejecución: muy parecido al mecanismo de envío para variantes en MS
Windows.
Cuando hay varios métodos con el mismo identificador Pascal, el compilador utilizará la lógica de resolución de sobrecarga
estándar para elegir el método más apropiado. En este proceso, se comportará como si todos los métodos objcclass/
objccategory dentro del alcance hubieran sido declarados como procedimientos/funciones globales con el especificador de
sobrecarga. Del mismo modo, el compilador imprimirá un error si no puede determinar qué método sobrecargado llamar.
En tales casos, se imprimirá como sugerencia una lista de todos los métodos que podrían usarse para implementar la llamada.
Para resolver el error, se debe utilizar una conversión de tipos explícita para indicarle al compilador qué tipo de objcclass
contiene el método necesario.
La enumeración rápida en ObjectiveC es una construcción que permite enumerar los elementos en una clase contenedora
Cocoa de forma genérica. Se implementa mediante un bucle forin en ObjectiveC.
167
Machine Translated by Google
Esto se ha traducido a ObjectivePascal utilizando el mecanismo de bucle forin existente. Por tanto, la función
se comporta de forma idéntica en ambos idiomas. Tenga en cuenta que requiere que se active el interruptor
de modo ObjectiveC 2.0.
{$modo delphi}
{$modeswitch objetivoc2}
usos
CacaoTodos;
era
arr: NSMutableArray; elemento:
NSString; grupo:
NSAutoreleasePool; i: entero largo;
comenzar
grupo:=[Link];
arr:=[Link](
NSSTR('Uno'),
NSSTR('Dos'),
NSSTR('Tres'),
NSSTR('Cuatro'),
NSSTR('Cinco'),
NSSTR('Seis'),
NSSTR('Siete'), nulo);
yo:=0;
para el elemento en arr comience
inc(i); si
i=2 entonces
continúa; si i = 5
entonces
romper;
si estoy en [2,5..10] entonces detener(1);
168
Machine Translated by Google
Capítulo 12
Expresiones
Las expresiones se construyen con dos componentes: operadores y sus operandos. La mayoría de los operadores son binarios,
es decir, requieren dos operandos. Los operadores binarios siempre ocurren entre los operandos (como en X/Y). Pocos
Los operadores son unarios, es decir, requieren sólo un operando. Un operador unario ocurre siempre antes del
operando, como en X.
Una expresión se resuelve en un valor de cierto tipo. El tipo resultante está determinado por los tipos.
de los valores en la expresión y los operadores en la expresión.
Cuando se utilizan múltiples operandos en una expresión, se utilizan las reglas de precedencia de la tabla (12.1) .
1. En operaciones con precedencias desiguales los operandos pertenecen al operador con mayor precedencia. Por
ejemplo, en 5*3+7, la multiplicación tiene mayor prioridad que la
además, por lo que se evalúa primero. El resultado sería 22.
2. Si se utilizan paréntesis en una expresión, primero se evalúa su contenido. Por lo tanto, 5*(3+7)
resultaría en 50.
3. De lo contrario, los operadores binarios de la misma precedencia son asociativos por izquierda. 5 * 3 div 7
evaluar a 2 y no a 0.
Observación No se garantiza que el orden en el que se evalúan las expresiones con la misma precedencia sea de izquierda a derecha. En
general, no se deben hacer suposiciones sobre cuál es la subexpresión que se evalúa primero en tal caso.
caso.
El compilador decidirá qué subexpresión evaluará primero en función de las reglas de optimización. Así, en
la siguiente expresión:
a := g(3) + f(2);
169
Machine Translated by Google
f(2) puede ejecutarse antes de g(3). Este comportamiento es claramente diferente del de Delphi o Turbo
Pascal.
Si una expresión debe ejecutarse antes que la otra, es necesario dividir la declaración utilizando resultados temporales:
e1 := g(3); a := e1
+ f(2);
Una expresión aplica operadores relacionales a expresiones simples. Las expresiones simples son una serie de términos
(a continuación se explica qué es un término), unidos mediante operadores de suma.
Expresiones
<>
en
es
expresión simple 6term
+
o
gratis
ResultadoGráfico<>grError
(Hacerlo hoy=Sí) y (Hacerlo mañana=No);
Día en fin de semana
170
Machine Translated by Google
A+B
Pi
Ser o no ser
Términos
término 6factor
*
/
div
mod
y shl
shr
como
2 * pi
Una división B
Factores
factor ( expresión )
referencia variable
Llamada de función
Las llamadas a funciones son parte de expresiones (aunque, usando sintaxis extendida, también pueden ser declaraciones).
Se construyen de la siguiente manera:
171
Machine Translated by Google
Llamadas a funciones
La referencia de variable debe ser una referencia de variable de tipo procesal. Un designador de método sólo
se puede utilizar dentro del método de un objeto. También se puede utilizar un designador de método calificado
fuera de los métodos de objeto. La función que se llamará es la función con una lista de parámetros declarada
que coincide con la lista de parámetros real. Esto significa que
1. La cantidad de parámetros reales debe ser igual a la cantidad de parámetros declarados (a menos que se utilicen valores
de parámetros predeterminados).
2. Los tipos de parámetros deben ser compatibles. Para parámetros de referencia variables, los tipos de
parámetros deben ser exactamente los mismos.
Si no se encuentra ninguna función coincidente, el compilador generará un error. El error depende, entre otras
cosas, de si la función está sobrecargada o no: es decir, varias funciones con el mismo nombre pero con
diferentes listas de parámetros.
Hay casos en los que el compilador no ejecuta la llamada a la función en una expresión. Este es el caso
cuando se asigna un valor a una variable de tipo procedimental, como en el siguiente ejemplo en modo
Delphi o Turbo Pascal:
Tipo
FuncType = Función: Entero; Var A: Entero;
Función AddOne: Entero;
comenzar A := A+1; AgregarUno := A; fin;
Var F:
Tipo de
función; norte:
AgregarUno;
{Asignar AddOne a F, no llamar a AddOne}
N := AgregarUno; { N := 1 !!} fin.
En el listado anterior, la asignación a F no hará que se llame a la función AddOne. La asignación a N, sin
embargo, llamará a AddOne.
A veces, la llamada es deseada, por ejemplo en recursividad, en ese caso, la llamada debe ser forzada. Esto se
puede hacer agregando el paréntesis al nombre de la función:
172
Machine Translated by Google
era
c: carbón;
comenzar
a leer (c); si
(c='\') entonces c:=rd();
rd:=c; fin;
comenzar
canal:=rd;
escrito(ch);
fin.
Lo anterior leerá un carácter y lo imprimirá. Si la entrada es una barra invertida, se lee un segundo carácter.
Si F = AgregarUno Entonces
¿Debería el compilador comparar las direcciones de F y AddOne, o debería llamar a ambas funciones y comparar el
resultado? En modo fpc y objfpc esto se resuelve considerando una variable de procedimiento como equivalente a un puntero.
Por lo tanto, el compilador dará un error de discrepancia de tipos, ya que AddOne se considera una llamada a una función
con resultado entero y F es un puntero.
Entonces, ¿cómo se debería comprobar si F apunta a la función AddOne? Para hacer esto, se debe usar el operador de
dirección @:
Si F = @AddOne Entonces
WriteLn ('Las funciones son iguales');
El lado izquierdo de la expresión booleana es una dirección. El lado derecho también, por lo que el compilador compara dos
direcciones. ¿Cómo comparar los valores que devuelven ambas funciones? Agregando una lista de parámetros vacía:
Si F()=Agregar entonces
WriteLn ('Las funciones devuelven los mismos valores');
Tenga en cuenta que este último comportamiento no es compatible con la sintaxis de Delphi. Activar el modo
Delphi le permitirá utilizar la sintaxis de Delphi.
Cuando se debe ingresar una constante de tipo conjunto en una expresión, se debe proporcionar un constructor de
conjunto. En esencia, esto es lo mismo que cuando se define un tipo, sólo que no hay ningún identificador con el que
identificar el conjunto. Un constructor de conjuntos es una lista de expresiones separadas por comas, entre corchetes.
Constructores de conjuntos
173
Machine Translated by Google
establecer constructor [ ]
6 establecer grupo
Todos los grupos de conjuntos y elementos de conjuntos deben ser del mismo tipo ordinal. El conjunto vacío se indica con [] y se
puede asignar a cualquier tipo de conjunto. Un grupo de conjuntos con un rango [A..Z] convierte todos los valores del rango en
un elemento de conjunto. Los siguientes son constructores de conjuntos válidos:
[hoy mañana]
[Lunes..Viernes,Domingo] [ 2, 3*2,
6*2, 9*2 ]
['A'..'Z','a'..'z', '0'..'9']
Observación Si el primer especificador de rango tiene un valor ordinal mayor que el segundo, el conjunto resultante
estará vacío, por ejemplo, ['Z'...'A'] denota un conjunto vacío. Hay que tener cuidado al indicar un rango.
A veces es necesario cambiar el tipo de una expresión, o parte de la expresión, para que la asignación sea
compatible. Esto se hace mediante un encasillamiento de valores. El diagrama de sintaxis para un encasillado
de valores es el siguiente:
Encasillamientos
Los encasillamientos de valores no se pueden utilizar en el lado izquierdo de las asignaciones, como encasillamientos de variables. Aquí hay algunos
encasillamientos válidos:
Byte('A')
Char(48)
booleano(1)
entero largo(@Buffer)
En general, el tamaño de tipo de la expresión y el tamaño de la conversión de tipo deben ser los mismos. Sin
embargo, para los tipos ordinales (byte, char, palabra, booleano, enumera) esto no es así, se pueden usar indistintamente.
Es decir, lo siguiente funcionará, aunque las tallas no coincidan.
Entero('A');
Carbón (4875);
booleano(100);
Palabra (@Buffer);
174
Machine Translated by Google
Una variable puede considerarse un factor único en una expresión. Por lo tanto, también se puede encasillar. Una
variable se puede encasillar a cualquier tipo, siempre que el tipo tenga el mismo tamaño que la variable original.
Es una mala idea encasillar tipos enteros en tipos reales y viceversa. Es mejor confiar en la compatibilidad de
asignación de tipos y utilizar algunas de las funciones estándar de cambio de tipos.
Tenga en cuenta que las encasillamientos de variables pueden ocurrir en cualquier lado de una asignación, es decir, las siguientes son
encasillamientos válidos:
Era
C: carbón;
B: Byte;
comenzar
B:=Byte(C);
Carbón (B): = C;
fin;
Las variables de puntero se pueden encasillar en tipos de procedimiento, pero no en punteros de método.
Un encasillamiento es una expresión del tipo dado, lo que significa que el encasillamiento puede ir seguido de un calificador:
Tipo
TWordRec = Registro empaquetado
Era
P: Puntero;
W: Palabra;
S: Cadena;
comenzar
TWordRec(W).L:=$FF;
TPalabraRec(W).H:=0;
S:=TObject(P).ClassName;
Encasillar una expresión con la palabra clave no alineada le indica al compilador que debe acceder a los datos byte
por byte.
Tenga en cuenta que el compilador supone que el acceso a todos los campos/elementos de las estructuras de datos empaquetadas no
está alineado.
Ejemplo:
programarme;
175
Machine Translated by Google
Era
A: Matriz empaquetada [1..20] de Byte; I: Int Largo;
comenzar
Para I:=1 a 20 hacer
A[Yo]:=Yo;
I:=PInteger(No alineado(@A[13]))^; fin.
12.7 El operador @
El operador de dirección @ devuelve la dirección de una variable, procedimiento o función. Se utiliza de la siguiente manera:
factor de dirección
PLargeBuffer: ^arte;
comenzar
PLargeBuffer := @Buffer; fin.
Cambiar {$T} a {$T+} evitará que el compilador compile esto. Dará un error de no coincidencia de tipos.
De forma predeterminada, el operador de dirección devuelve un puntero sin tipo: aplicar el operador de dirección
a un identificador de función, método o procedimiento dará un puntero al punto de entrada de esa función. El
resultado es un puntero sin tipo.
Procedimiento MyProc;
empezar
fin;
176
Machine Translated by Google
Era
P: PCar;
comenzar
P:=@MiProc; fin;
De forma predeterminada, se debe utilizar el operador de dirección si se debe asignar un valor a una variable de tipo
procesal. Este comportamiento se puede evitar utilizando los modificadores Mtp o MDelphi, que dan como resultado
una sintaxis de Delphi o Turbo Pascal más compatible.
12.8 Operadores
Los operadores se pueden clasificar según el tipo de expresión sobre la que operan. Los discutiremos tipo por tipo.
Con la excepción de Div y Mod, que aceptan sólo expresiones enteras como operandos, todos los operadores
aceptan expresiones reales y enteras como operandos.
Observación El operador de exponenciación (**) está disponible para sobrecarga (capítulo 15, página 233), pero no está
definido en ninguno de los tipos estándar de Pascal (flotantes y/o enteros).
Para los operadores binarios, el tipo de resultado será un número entero si ambos operandos son expresiones de tipo entero.
Si uno de los operandos es una expresión de tipo real, entonces el resultado es real.
177
Machine Translated by Google
Para operadores unarios, el tipo de resultado siempre es igual al tipo de expresión. La división (/) y Mod
El operador causará errores en tiempo de ejecución si el segundo argumento es cero.
El signo del resultado de un operador Mod es el mismo que el signo del operando del lado izquierdo del Mod.
operador. De hecho, el operador Mod equivale a la siguiente operación:
I mod J = I (I div J) * J
178
Machine Translated by Google
Observación De forma predeterminada, las expresiones booleanas se evalúan con evaluación de cortocircuito. Esto significa
que desde el momento en que se conoce el resultado de la expresión completa, se detiene la evaluación y se
devuelve el resultado. Por ejemplo, en la siguiente expresión:
El compilador nunca mirará el valor de MaybeTrue, ya que es obvio que la expresión siempre será True. Como
resultado de esta estrategia, si MaybeTrue es una función, ¡no será llamada!
(Esto puede tener efectos sorprendentes cuando se usa junto con propiedades)
Lo siguiente no es:
Era
Nombre de dirección: PChar;
...
Dirección := Dirección+'\';
Si todas las cadenas en una expresión de cadena son ansistrings, entonces el resultado es una ansistring.
El valor del modificador {$H} se puede utilizar para controlar el tipo de cadenas constantes; de forma predeterminada, son
cadenas cortas (y por lo tanto están limitadas a 255 caracteres).
{$mode objfpc}
{$modeswitch arrayoperators}
Cuando está habilitado, su acción es similar a la concación de cadenas: concatenar el contenido de las dos matrices
sobre las que actúa. Por supuesto, el tipo de elemento de la matriz debe ser idéntico para ambas matrices, es decir,
lo siguiente funcionará:
{$mode objfpc}
{$modeswitch arrayoperators}
era
a,b, c: matriz de bytes;
179
Machine Translated by Google
comenzar a:=[0,1,2];
b:=[3,4,5]; c:=a+b;
writeln('C
tiene ',longitud(c),' elementos');
{$mode objfpc}
{$modeswitch arrayoperators}
era
b, c: matriz de bytes; a: matriz de
números enteros;
comenzar a:=[0,1,2];
b:=[3,4,5]; c:=a+b;
writeln('C
tiene ',longitud(c),' elementos');
Con los operadores se pueden realizar las siguientes operaciones sobre conjuntos: unión, diferencia, diferencia
simétrica, inclusión e intersección. Se pueden agregar o eliminar elementos del conjunto con los operadores
Incluir o Excluir. Los operadores necesarios para esto se enumeran en la tabla (12.6).
Operador Acción
+ Unión
Diferencia
* Intersección
>< diferencia simétrica
<= Contiene
>= El conjunto del lado izquierdo es un superconjunto del de la derecha.
incluir incluir un elemento en el conjunto excluir
excluir un elemento del conjunto verificar si un elemento
en está en un conjunto
El tipo establecido de los operandos debe ser el mismo o el compilador generará un error.
Tipo
Día = (lunes, martes, miércoles, jueves, viernes, sábado, domingo);
Días = conjunto de Día;
180
Machine Translated by Google
Writeln('[',S,']'); fin;
constante
Semana laboral = [lunes, martes, miércoles, jueves, viernes];
Fin de semana = [sábado,domingo];
Era
W: Días;
comenzar
Mi:=[lunes,martes]+[miércoles,jueves,viernes]; // es igual a [lun,mar,mié,jue,vier]
Díasdeimpresión(W);
Mi:=[lunes,martes,miércoles][miércoles]; // es igual a [lunes,martes]
Díasdeimpresión(W);
Mi:=[lunes,martes,miércoles][miércoles,jueves]; // también es igual a [lunes,martes]
Díasdeimpresión(W);
Mi:=[lunes,martes,miércoles]*[miércoles,jueves,viernes]; // es igual a [casarse]
Díasdeimpresión(W);
Mi:=[lunes,martes,miércoles]><[miércoles,jueves,viernes]; // es igual a [lun,mar,jue,vier]
Díasdeimpresión(W);
si [lunes, martes]<=Semana laboral entonces
Writeln('Debe trabajar los lunes y martes');
si fin de semana>=[domingo] entonces
Writeln('Puedo descansar el domingo');
fin.
Como se puede observar, la unión equivale a un OR binario, mientras que la intersección equivale a un AND
binario, y la diferencia simétrica equivale a una operación XOR.
Las operaciones Incluir y Excluir equivalen a una unión o diferencia con un conjunto de un elemento. De este modo,
Incluir(W,mié);
es equivalente a
W:=W+[casar];
181
Machine Translated by Google
Excluir(W,mié);
es equivalente a
W:=W[casar];
La operación In da como resultado Verdadero si el operando izquierdo (un elemento) está incluido en el operando derecho
(un conjunto); en caso contrario, el resultado será Falso.
Normalmente, los operandos izquierdo y derecho deben ser del mismo tipo. Hay algunas excepciones notables en las que el
compilador puede manejar expresiones mixtas:
2. Si el operador está sobrecargado y existe una versión sobrecargada cuyos tipos de argumentos coinciden con los tipos
de la expresión.
Al comparar punteros, se comparan las direcciones a las que apuntan. Esto también es válido para los punteros de tipo
PChar. Para comparar las cadenas a las que apunta PChar, se debe utilizar la función StrComp de la unidad de cadenas. In
devuelve True si el operando izquierdo (que debe tener el mismo tipo ordinal que el tipo de conjunto y que debe estar en el
rango 0..255) es un elemento del conjunto que es el operando derecho; de lo contrario, devuelve False.
182
Machine Translated by Google
Una expresión que contiene el operador is da como resultado un tipo booleano. El operador is solo se puede
utilizar con una referencia de clase o una instancia de clase. El uso de este operador es el siguiente:
El objeto es clase
[Link](Clase)
Era
A: TObjeto;
B: Clase T;
comenzar
si A es TComponent entonces;
Si A es B entonces; fin;
El operador as realiza un encasillamiento condicional. Resulta en una expresión que tiene el tipo de clase:
Generar excepció[Link](SErrInvalidTypeCast);
Los siguientes son algunos ejemplos del uso del operador as:
Era
C: Componente T;
O : TObjeto;
comenzar
(C como TEdit).Text:='Algún texto'; C:=O como
TComponente; fin;
Los operadores as e is también trabajan en interfaces (tanto COM como CORBA). Se pueden utilizar para
comprobar si una interfaz también implementa otra interfaz como en el siguiente ejemplo:
{$modo objfpc}
usos
183
Machine Translated by Google
SysUtils;
tipo
IMyInterface1 = interfaz
['{DD70E7BB51E845C38CE85F5188E19255}'] barra de
procedimiento; fin;
IMyInterface2 = interfaz
['{7E5B86C44BC540E6A0DFD27DBF77BCA0}'] procedimiento
Foo; fin;
procedimiento [Link];
comenzar
fin;
procedimiento [Link];
comenzar
fin;
era
i: IMyInterface1; comenzar
i :=
[Link]; [Link];
Además, el operador is se puede utilizar para comprobar si una clase implementa una interfaz, y el
operador as se puede utilizar para encasillar una interfaz en la clase:
{$modo objfpc}
era
i: IMyInterface; comenzar
i :=
[Link]; Writeln(BoolToStr(i
es TMyObject,True)); // imprime verdadero Writeln(BoolToStr(i es TObject,True)); // imprime
verdadero Writeln(BoolToStr(i es TAggregatedObject,True)); // imprime falso (i como
TMyObject).Foo; fin.
Aunque las interfaces deben ser interfaces COM, la conversión encasillada a una clase solo funcionará
si la interfaz proviene de una clase Object Pascal. No funcionará en interfaces obtenidas del sistema.
184
Machine Translated by Google
por COM.
185
Machine Translated by Google
Capítulo 13
Declaraciones
El corazón de cada algoritmo son las acciones que realiza. Estas acciones están contenidas en las declaraciones
de un programa o unidad. Cada declaración se puede etiquetar y saltar (dentro de ciertos límites) con
declaraciones Goto. Esto se puede ver en el siguiente diagrama de sintaxis:
Declaraciones
declaración
etiqueta : declaración simple
declaración estructurada
declaración de ensamblaje
Una declaración simple no se puede descomponer en declaraciones separadas. Básicamente existen cuatro tipos de
declaraciones simples:
Declaraciones simples
asignaciones dan un valor a una variable, reemplazando cualquier valor anterior que la variable pudiera haber tenido:
186
Machine Translated by Google
Asignaciones
Además del operador de asignación estándar de Pascal (:=), que simplemente reemplaza el valor de la variable con el
valor resultante de la expresión a la derecha del operador :=, Free Pascal admite algunas construcciones de estilo C.
Todas las construcciones disponibles se enumeran en la tabla (13.1).
Tarea a += b Resultado
Para que estas construcciones funcionen, se debe especificar el modificador de línea de comandos Sc.
Observación Estas construcciones son sólo para facilitar la escritura, no generan código diferente. Aquí están
Algunos ejemplos de declaraciones de asignación válidas:
X := X+Y;
X+=Y; {Igual que X:= X+Y, necesita el cambio de línea de comando Sc}
X/=2; {Igual que X:= X/2, necesita el cambio de línea de comando Sc}
Hecho := Falso;
Clima := Bueno;
MiPi := 4* Bronceado(1);
Teniendo en cuenta que la desreferenciación de un puntero escrito da como resultado una variable del tipo al que apunta
el puntero, las siguientes también son asignaciones válidas:
Era
L : ^Entero largo;
P: PPChar;
comenzar
L^:=3;
P^^:='A';
Las declaraciones de procedimiento son llamadas a subrutinas. Existen diferentes posibilidades para llamadas a procedimientos:
187
Machine Translated by Google
Declaraciones de procedimiento
El compilador de Free Pascal buscará un procedimiento con el mismo nombre que el indicado en la declaración del
procedimiento y con una lista de parámetros declarada que coincida con la lista de parámetros real. Las siguientes son
declaraciones de procedimiento válidas:
Uso;
WriteLn('¡Pascal es un lenguaje fácil!'); Hazlo();
Observación Cuando se busca una función que coincida con la lista de parámetros de la llamada, los tipos de parámetros deben
ser compatibles con la asignación para los parámetros value y const, y deben coincidir exactamente con los
parámetros que se pasan por referencia.
13.1.3 Ir a declaraciones
Free Pascal admite la declaración goto jump. Su sintaxis prototipo es
Ir a declaración
ir a declaración ir a etiqueta
2. Saltar desde fuera de un bucle hacia dentro de un bucle o viceversa puede tener efectos extraños.
3. Para poder utilizar la instrucción Goto, se debe utilizar el modificador del compilador Sg, o {$GOTO
ON} debe usarse.
Observación En modo iso o macpas, o con el interruptor de modo “nonlocalgoto”, el compilador también permitirá no
gotos locales.
Las declaraciones Goto se consideran una mala práctica y deben evitarse en la medida de lo posible. Siempre es posible
reemplazar una instrucción goto por una construcción que no necesita una instrucción goto, aunque esta construcción
puede no ser tan clara como una instrucción goto. Por ejemplo, la siguiente es una declaración goto permitida:
188
Machine Translated by Google
etiqueta
saltar
...
Salta a :
Declaración;
...
Ir a saltar a;
...
Las declaraciones estructuradas se pueden dividir en declaraciones simples más pequeñas, que deben ejecutarse
de forma repetida, condicional o secuencial:
Declaraciones estructuradas
Declaraciones condicionales
Declaraciones repetitivas
189
Machine Translated by Google
irá seguido de un punto y coma, aunque está permitido. Una declaración compuesta es una forma de
agrupar declaraciones, ejecutándolas secuencialmente. Se tratan como una sola declaración en los
casos en que la sintaxis de Pascal espera una declaración, como en las declaraciones if...then...else.
Declaraciones compuestas
caso 6
constante : declaración
.. constante
,
Las constantes que aparecen en las distintas partes del caso deben conocerse en tiempo de compilación y pueden
ser de los siguientes tipos: tipos de enumeración, tipos ordinales (incluidos caracteres) o tipos de cadena. La expresión
case también debe ser de este tipo o se producirá un error del compilador. Todas las constantes de caso deben ser
del mismo tipo.
El compilador evaluará la expresión del caso. Si el valor de una de las constantes de caso coincide con
el valor de la expresión, se ejecuta la declaración que sigue a esta constante. Después de eso, el
programa continúa después del final.
Si ninguna de las constantes de caso coincide con el valor de la expresión, se ejecuta la lista de instrucciones
después de la palabra clave else o else. Puede ser una lista de declaraciones vacía. Si no hay otra parte presente
y ninguna constante de caso coincide con el valor de la expresión, el flujo del programa continúa después del final.
Las declaraciones case pueden ser declaraciones compuestas (es decir, un bloque Begin...End), pero para el
caso else se permiten declaraciones múltiples (como se puede ver en el diagrama de sintaxis). Puede incluirlos
en un bloque Comienzo...Fin adicional, pero no es necesario.
Observación A diferencia de Turbo Pascal, en Free Pascal no se permiten etiquetas de casos duplicados, por lo que el
siguiente código generará un error al compilar:
190
Machine Translated by Google
El compilador generará un error de etiqueta de caso duplicado al compilar esto, porque el 3 también aparece
(implícitamente) en el rango 1..5. Esto es similar a la sintaxis de Delphi.
Caso C de
'a' : WriteLn ('A presionada'); 'b' : WriteLn ('B
presionado'); 'c' : WriteLn ('C presionada');
demás
WriteLn ('letra desconocida presionada: ',C); fin;
Caso C de
'a','e','i','o','u' : WriteLn ('vocal presionada'); 'y' : WriteLn ('Este depende del
idioma');
demás
WriteLn ('Consonante presionada'); fin;
Número de caso de
1..10: WriteLn ('Número pequeño'); 11..100 : WriteLn
('Normal, número medio'); else WriteLn ('número ENORME'); fin;
Número de caso de
1..10: WriteLn ('Número pequeño'); 11..100 : WriteLn
('Normal, número medio'); else WriteLn ('número ENORME');
Free Pascal permite el uso de cadenas como etiquetas de caso, y en ese caso la variable de caso también debe ser una cadena.
Cuando se utilizan tipos de cadena, la variable de mayúsculas y minúsculas y las distintas etiquetas se comparan distinguiendo
entre mayúsculas y minúsculas.
Writeln('Plataforma comunitaria');
demás
WriteLn ('Otra plataforma'); fin;
191
Machine Translated by Google
El caso con cadenas es equivalente a una serie de declaraciones if then else, no se realizan optimizaciones.
Si entonces declaraciones
La expresión entre las palabras clave if y then debe tener un tipo de resultado booleano. Si la expresión se evalúa como
Verdadera, se ejecuta la declaración que sigue a la palabra clave entonces.
Si la expresión se evalúa como False, entonces se ejecuta la declaración que sigue a la palabra clave else, si está
presente.
• Tenga en cuenta el hecho de que la expresión booleana por defecto se evaluará de forma abreviada, lo que
significa que la evaluación se detendrá en el punto donde se conoce con certeza el resultado.
• Además, antes de la palabra clave else, no se permite punto y coma (;), pero todas las declaraciones pueden ser com.
declaraciones de libras.
• En construcciones anidadas If... entonces... else, puede surgir cierta ambigüedad en cuanto a qué declaración
else se empareja con cuál declaración if. La regla es que la palabra clave else coincide con la primera palabra
clave if (buscando hacia atrás) que aún no coincide con una palabra clave else.
Por ejemplo:
Si exp1 entonces
Si exp2 entonces
Estadística1
demás
estadística2;
Si exp1 entonces
comienza
Si exp2 entonces
Estadística1
demás
estadística2
fin;
192
Machine Translated by Google
y no a
{NO EQUIVALENTE}
Si exp1 entonces
comienza
Si exp2 entonces
Estadística1
fin
demás
estadística2;
Si es esta última construcción la que se necesita, las palabras clave de inicio y fin deben estar presentes. En
caso de duda, es mejor añadirlos.
Para declaración
Aquí, Declaración puede ser una declaración compuesta. Cuando se encuentra la declaración For, la
variable de control se inicializa con el valor inicial y se compara con el valor final. Lo que sucede a
continuación depende de si se usa to o downto:
1. En el caso de que se utilice To, si el valor inicial es mayor que el valor final, entonces la Declaración
nunca ser ejecutado.
2. En el caso de que se utilice DownTo, si el valor inicial es menor que el valor final, entonces Declaración
nunca será ejecutado.
Después de esta verificación, se ejecuta la declaración posterior a Do. Después de la ejecución de la declaración,
la variable de control aumenta o disminuye en 1, dependiendo de si se utiliza To o Downto. La variable de
control debe ser de tipo ordinal; no se pueden utilizar otros tipos como contadores en un bucle.
Observación
193
Machine Translated by Google
• Free Pascal siempre calcula el límite superior exactamente una vez antes de inicializar la variable del contador con
el valor inicial.
• No está permitido cambiar (es decir, asignar un valor) el valor de una variable de bucle dentro del bucle.
• El valor de la variable de bucle no está definido después de que se ha completado un bucle o si un bucle no se
ejecuta en absoluto. Sin embargo, si el bucle finalizó prematuramente con una excepción o una instrucción break
o goto, la variable del bucle conserva el valor que tenía cuando se salió del bucle.
• Para procedimientos anidados, una variable de bucle debe ser una variable local. Si declara una variable de
bucle fuera del procedimiento anidado donde está el bucle, el compilador se quejará. Sin embargo, está
permitido utilizar una variable global en un procedimiento.
• El compilador no prohíbe explícitamente saltar con una instrucción goto a un bloque de bucle for,
pero hacerlo resultará en un comportamiento impredecible.
procedimiento Proc;
era
yo: número entero;
procedimiento anidado;
empezar
por i := 1 a 2 hacer ; fin;
empezar
fin;
empezar
fin.
194
Machine Translated by Google
era
yo: número entero;
procedimiento anidado;
empezar
por i := 1 a 2 hacer ; fin;
comenzar
fin.
Si la declaración es una declaración compuesta, entonces las rutinas del sistema Interrumpir y Continuar se pueden
usar para saltar al final o justo después del final de la declaración For. Tenga en cuenta que Interrumpir y Continuar
no son palabras reservadas y, por lo tanto, pueden sobrecargarse.
Para declaración
Aquí, Declaración puede ser una declaración compuesta. El enumerable debe ser una expresión que consta de
un número fijo de elementos: la variable del bucle se hará igual a cada uno de los elementos por turno y se
ejecutará la declaración que sigue a la palabra clave do.
La expresión enumerable puede ser uno de cinco casos:
1. Un identificador de tipo de enumeración. El bucle abarcará entonces todos los elementos del tipo de
enumeración. La variable de control debe ser del tipo enumeración.
2. Un valor establecido. El bucle entonces abarcará todos los elementos del conjunto, la variable de control debe ser de
el tipo base del conjunto.
195
Machine Translated by Google
3. Un valor de matriz. El bucle abarcará todos los elementos de la matriz y la variable de control debe tener
el mismo tipo que un elemento de la matriz. Como caso especial, una cadena se considera una matriz
de caracteres.
4. Una clase, objeto o instancia de registro extendido enumerable. Esta es una instancia de cualquier tipo
estructurado que admita las interfaces IEnumerator e IEnumerable. En este caso, el tipo de variable de
control debe ser igual al tipo del valor de retorno de [Link].
5. Cualquier tipo para el que se defina un operador enumerador. El operador enumerador debe devolver un tipo
estructurado que implemente la interfaz IEnumerator. El tipo de la variable de control debe ser igual al tipo
del valor de retorno GetCurrent del enumerador.
Tipo
TWeekDay = (lunes, martes, miércoles, jueves, viernes, sábado, domingo);
Era
d : Tdía de la semana;
comience
por d en TWeekday y escriba en
(d);
fin.
Tipo
TWeekDay = (lunes, martes, miércoles, jueves, viernes, sábado, domingo);
Era
d : Tdía de la semana;
comience
para d:=Bajo(TWeekday) a Alto(TWeekday) escriba writeln(d);
fin.
Un segundo caso de bucle for..in es cuando la expresión enumerable es un conjunto, y luego el bucle se
ejecutará una vez para cada elemento del conjunto:
Tipo
TWeekDay = (lunes, martes, miércoles, jueves, viernes, sábado, domingo);
Era
Semana: conjunto de TWeekDay =
[lunes, martes, miércoles, jueves, viernes];
d : Tdía de la semana;
comenzar
196
Machine Translated by Google
Esto imprimirá los nombres de los días de la semana en la pantalla. Tenga en cuenta que la variable d es del mismo
tipo que el tipo base del conjunto.
Tipo
TWeekDay = (lunes, martes, miércoles, jueves, viernes, sábado, domingo);
Era
Semana: conjunto de TWeekDay =
[lunes, martes, miércoles, jueves, viernes];
d : Tdía de la semana;
comenzar
para d:=Bajo(TWeekday) a Alto(TWeekday) hacer si d en la Semana
entonces
escrito(d);
fin.
La tercera posibilidad para un bucle for..in es cuando la expresión enumerable es una matriz:
era
a: Matriz[1..7] de cadena =
('lunes','martes','miércoles','jueves', 'viernes','sábado','domingo');
Era
S: Cadena;
comenzar
Para s en un hacer
Escritura(s);
fin.
era
a: Matriz[1..7] de cadena =
('lunes','martes','miércoles','jueves', 'viernes','sábado','domingo');
Era
yo: número entero;
comenzar
para i:=Bajo(a) a alto(a) hacer
Escribir(a[i]);
fin.
197
Machine Translated by Google
Un tipo de cadena es equivalente a una matriz de caracteres y, por lo tanto, se puede usar una cadena
en un bucle for...in. Lo siguiente imprimirá todas las letras del alfabeto, cada letra en una línea:
Era
c: carbón;
comience
para c en 'abcdefghijklmnopqrstuvwxyz' escriba writeln(c);
fin.
usos
SysUtils;
escriba
TTestStringArray = matriz [0..10] de cadena;
Era
TwoD: matriz[0..3] de TTestStringArray;
era
i,j: número entero;
S: Cadena;
comenzar
para i:=0 a 3 hacer
para j:=0 a 10 hacer
DosD[i,J]:=Format('%.2dx%.2d',[i,j]); para S en dos hacer
Escribir(S);
fin.
La cuarta posibilidad para un bucle for..in es utilizar clases. Una clase puede implementar la interfaz
IEnumerable, que se define de la siguiente manera:
IEnumerable = interfaz(IInterfaz)
función GetEnumerator: IEnumerator; fin;
El tipo de retorno real de GetEnumerator no debe ser necesariamente una interfaz IEnumerator; en cambio, puede
ser una clase que implemente los métodos de IEnumerator:
La propiedad Current y el método MoveNext deben estar presentes en la clase devuelta por el método
GetEnumerator. No es necesario que el tipo real de la propiedad actual sea un TObject.
Cuando encuentre un bucle for..in con una instancia de clase como operando "in", el compilador verificará
cada una de las siguientes condiciones:
198
Machine Translated by Google
El tipo de propiedad debe coincidir con el tipo de variable de control del bucle for..in.
Ni las interfaces IEnumerator ni IEnumerable deben ser declaradas por la clase enumerable: el compilador detectará si
estas interfaces están presentes usando las comprobaciones anteriores.
Las interfaces sólo están definidas para la compatibilidad con Delphi y no se utilizan internamente. (también sería
imposible hacer cumplir su corrección).
La unidad Clases contiene una serie de clases que son enumerables:
Por lo tanto, el siguiente código también imprimirá todos los días de la semana:
Era
Días: TStrings;
D: Cadena;
comenzar
días:=[Link]; prueba
[Link]('Lunes'); Dí[Link]('Martes');
Dí[Link]('Miércoles');
Dí[Link]('Jueves'); Dí[Link]('Viernes');
Dí[Link]('Sábado');
Dí[Link]('Domingo'); Para D en
días, escriba Writeln(D); Finalmente
Dí[Link]; fin; fin.
Tenga en cuenta que el compilador aplica la seguridad de tipos: declarar D como un número entero dará como resultado un error del compilador:
199
Machine Translated by Google
Era
Días: TStrings;
D: Cadena;
E: TStringsEnumerator;
comenzar
días:=[Link]; prueba
[Link]('Lunes'); Dí[Link]('Martes');
Dí[Link]('Miércoles');
Dí[Link]('Jueves'); Dí[Link]('Viernes');
Dí[Link]('Sábado');
Dí[Link]('Domingo');
E:=Dí[Link]; intente
mientras [Link] comienza
D:=[Link]; Escribir(D); fin;
Dí[Link]; fin;
fin.
La quinta y última posibilidad de utilizar un bucle for..in se puede utilizar para enumerar casi
cualquier tipo, utilizando el operador enumerador. El operador enumerador debe devolver una clase
que tenga la misma firma que el método IEnumerator anterior. El siguiente código definirá un
enumerador para el tipo Integer:
Tipo
TEvenEnumerator = Clase
FActual: Entero;
FMax: Entero;
Función MoveNext: Booleana;
Propiedad actual: entero leído FCurrent; fin;
200
Machine Translated by Google
comenzar
FActual:=FActual+2;
Resultado:=FActual<=FMax; fin;
comenzar
Resultado:=[Link]; [Link]:=i;
fin;
era
I: Entero; metro :
Entero = 4;
comenzar
Porque yo en M hago
Escribir(i);
fin.
El bucle imprimirá todos los números pares distintos de cero, menores o iguales al enumerable. (2 y 4 en el
caso del ejemplo).
Se debe tener cuidado al definir los operadores enumeradores: el compilador buscará y utilizará el
primer operador enumerador disponible para la expresión enumerable. Para las clases, esto también
significa que el método GetEnumerator ni siquiera se considera. El siguiente código definirá un operador
enumerador que extrae el objeto de una lista de cadenas:
Tipo
TDayObject = Clase
DíaDeLaSemana: Entero;
Constructor Crear(ADayOfWeek: Entero); fin;
TObjectEnumerator = Clase
Lista FL: TStrings;
Índice FI: Entero;
Función GetCurrent: TDayObject;
Función MoveNext: booleana;
Propiedad actual: TDayObject Leer GetCurrent; fin;
comenzar
DíaDeLaSemana:=UnDíaDeLaSemana;
fin;
201
Machine Translated by Google
comenzar
Inc(FIndex);
Resultado:=(FIndex<[Link]); fin;
comenzar
Resultado:=[Link]; [Link]:=S;
[Link]:=1; fin;
Era
Días: TStrings;
D: Cadena;
O : TdayObject;
comenzar
días:=[Link]; pruebe
[Link]('Monday',[Link](1));
[Link]('Martes',[Link](2));
[Link]('Miércoles',[Link](3));
[Link]('Jueves',[Link](4));
[Link]('Viernes',[Link](5));
[Link]('Sábado',[Link](6));
Dí[Link]('Domingo',[Link](7)); Para O en Días, escriba
Writeln([Link]);
Finalmente Dí[Link]; fin;
fin.
Observación Al igual que el bucle for..to, no está permitido cambiar (es decir, asignar un valor) el valor de un bucle.
variable de control dentro del bucle.
utiliza para ejecutar una instrucción hasta que se alcance una determinada condición. La declaración se ejecutará
al menos una vez. La sintaxis prototipo de la instrucción Repetir... hasta que es
202
Machine Translated by Google
Repetir declaración
Esto ejecutará las declaraciones entre repetición y hasta el momento en que la Expresión se evalúe como Verdadera.
Dado que la expresión se evalúa después de la ejecución de las declaraciones, estas se ejecutan al menos una vez.
Tenga en cuenta el hecho de que la expresión booleana Expression se evaluará de forma abreviada de forma
predeterminada, lo que significa que la evaluación se detendrá en el punto donde se conoce con certeza el resultado.
repetir
EscribirLn('I =',i);
Yo:= Yo+2;
hasta I>100;
repetir
X := X/2 hasta
x<10e3;
Tenga en cuenta que la última declaración antes de la palabra clave hasta no necesita un punto y coma final, pero
está permitida.
Las rutinas del sistema Romper y Continuar se pueden utilizar para saltar al final o justo después del final de la instrucción
repetir...hasta. Tenga en cuenta que Interrumpir y Continuar no son palabras reservadas y, por lo tanto, pueden
sobrecargarse.
Esto ejecutará la Declaración siempre que la Expresión se evalúe como Verdadera. Dado que la Expresión se evalúa
antes de la ejecución de la Declaración, es posible que la Declaración no se ejecute en absoluto.
La declaración puede ser una declaración compuesta.
Tenga en cuenta el hecho de que la expresión booleana Expression se evaluará de forma abreviada de forma
predeterminada, lo que significa que la evaluación se detendrá en el punto donde se conoce con certeza el resultado.
Yo:= Yo+2;
mientras que yo <= 100 lo hago
203
Machine Translated by Google
comenzar
EscribirLn('I =',i);
Yo:= Yo+2; fin;
X := X/2;
mientras que x>=10e3 lo hace
X := X/2;
Con declaración
La referencia de la variable debe ser una variable de tipo registro, objeto o clase. En la declaración with,
se verifica cualquier referencia de variable o referencia de método para ver si es un campo o método del
registro, objeto o clase. Si es así, se accede a ese campo o se llama a ese método. Dada la declaración:
Tipo
Pasajero = Registro
Nombre: Cadena [30];
Vuelo: Cadena[10]; fin;
Era
ElCliente: Pasajero;
Nombre := 'Miguel';
Vuelo := 'PS901'; fin;
La declaración
204
Machine Translated by Google
es equivalente a
con un hacer
Con B hacer
Con C hacer
Con D hacer Declaración;
Este también es un claro ejemplo del hecho de que las variables se prueban del último al primero, es decir, cuando el
compilador encuentra una referencia de variable, primero verificará si es un campo o método de la última variable. De
lo contrario, comprobará el penúltimo, y así sucesivamente. El siguiente ejemplo muestra esto;
PAR=^Ar;
fin.
22
Mostrando así que X,Y en la declaración WriteLn coincide con la variable de registro T.
Observación Cuando se utiliza una instrucción With con un puntero o una clase, no está permitido cambiar el puntero o la clase en
el bloque With. Con las definiciones del ejemplo anterior, a continuación se ilustra de qué se trata:
Donde p: PAR;
comenzar
Con P^ hacer
La razón por la que el puntero no se puede cambiar es que el compilador almacena la dirección en un registro temporal.
Cambiar el puntero no cambiará la dirección temporal. Lo mismo ocurre con las clases.
205
Machine Translated by Google
Una declaración de ensamblador permite insertar código ensamblador directamente en el código Pascal.
Declaraciones de ensamblador
Puede encontrar más información sobre los bloques de ensamblador en la Guía del programador. La lista de registros se
utiliza para indicar los registros que son modificados por una declaración del ensamblador en el bloque del ensamblador.
El compilador almacena ciertos resultados en los registros. Si los registros se modifican en una declaración del
ensamblador, a veces se debe informar al compilador al respecto. Los registros se indican con sus nombres Intel para el
procesador I386, es decir, 'EAX', 'ESI', etc. Como ejemplo, considere el siguiente código ensamblador:
ENSAMBLE
Movimiento $1,%ebx
Movl $0,%eax addl
%eax,%ebx end
['EAX','EBX'];
Esto le indicará al compilador que debe guardar y restaurar el contenido de los registros EAX y EBX cuando encuentre
esta declaración asm.
Free Pascal admite varios estilos de sintaxis ensambladora. De forma predeterminada, se asume la sintaxis de AT&T
para la plataforma 80386 y compatibles. El estilo predeterminado del ensamblador se puede cambiar con el modificador
{$asmmode xxx} en el código o con la opción de línea de comandos R. Puede encontrar más información sobre esto
en la Guía del programador.
206
Machine Translated by Google
Capítulo 14
• Sobrecarga de funciones, es decir, funciones con el mismo nombre pero con diferentes listas de parámetros.
• Parámetros constantes.
Observación En muchos de los párrafos siguientes se utilizarán las palabras procedimiento y función entre sí.
cambiantemente. Las declaraciones realizadas son válidas para ambos, salvo indicación en contrario.
Una declaración de procedimiento define un identificador y lo asocia con un bloque de código. Luego se puede llamar
al procedimiento con una declaración de procedimiento.
Declaración de procedimiento
Consulte la sección 14.4, página 209 para obtener la lista de parámetros. Una declaración de procedimiento seguida
de un bloque implementa la acción del procedimiento en ese bloque. El siguiente es un procedimiento válido:
207
Machine Translated by Google
Una declaración de función define un identificador y lo asocia con un bloque de código. El bloque de código
devolverá un resultado. Luego, la función se puede llamar dentro de una expresión o con una declaración de
procedimiento, si la sintaxis extendida está activada.
Declaración de función
El tipo de resultado de una función puede ser cualquier tipo previamente declarado. al contrario de Turbo Pascal,
donde sólo se podían devolver tipos simples.
El resultado de una función se puede establecer configurando la variable de resultado: este puede ser el identificador de
la función o (solo en modo ObjFPC o Delphi) el identificador de resultado especial:
comenzar
MiFunción:=12; // Devuelve 12 fin;
comenzar
Resultado:=12;
fin;
208
Machine Translated by Google
Como extensión de la sintaxis de Delphi, el modo ObjFPC también admite una extensión especial del
procedimiento Exit:
comenzar
Salir(12); fin;
La llamada Exit establece el resultado de la función y salta al final del bloque de declaración de función. Puede
verse como el equivalente de la instrucción de retorno de C.
Observación Los resultados de la función se tratan como parámetros de paso por referencia. Esto es especialmente importante para los tipos
administrados: el resultado de la función puede ser distinto de nulo en la entrada y establecerse en una instancia válida del tipo.
Cuando se deben pasar argumentos a una función o procedimiento, estos parámetros deben declararse en la lista de
parámetros formales de esa función o procedimiento. La lista de parámetros es una declaración de identificadores a los
que solo se puede hacer referencia en ese procedimiento o bloque de función.
Parámetros
Los parámetros constantes, los parámetros de salida y los parámetros variables también pueden ser parámetros sin tipo si no
tienen ningún identificador de tipo.
A partir de la versión 1.1, Free Pascal admite valores predeterminados tanto para parámetros constantes como para
parámetros de valor, pero sólo para tipos simples. El compilador debe estar en modo OBJFPC o DELPHI para aceptar
valores predeterminados.
Parámetros de valor
209
Machine Translated by Google
Cuando los parámetros se declaran como parámetros de valor, el procedimiento obtiene una copia de los
parámetros que pasa la declaración de llamada. Cualquier modificación de estos parámetros es puramente local
en el bloque del procedimiento y no se propaga al bloque de llamada.
Un bloque que desee llamar a un procedimiento con parámetros de valor debe pasar parámetros compatibles
con la asignación al procedimiento. Esto significa que los tipos no deben coincidir exactamente, pero pueden
convertirse a los tipos de parámetros reales. Este código de conversión lo inserta el propio compilador.
Se debe tener cuidado al utilizar parámetros de valor: los parámetros de valor hacen un uso intensivo de la pila,
especialmente cuando se utilizan parámetros grandes. El tamaño total de todos los parámetros en la lista de
parámetros formal debe ser inferior a 32 K por razones de portabilidad (la versión de Intel limita esto a 64 K).
Las matrices abiertas se pueden pasar como parámetros de valor. Consulte la sección 14.4.5, página 213 para obtener más información
sobre el uso de matrices abiertas.
Para un parámetro de tipo simple (es decir, no estructurado), se puede especificar un valor predeterminado.
Puede ser una constante sin tipo. Si la llamada a la función omite el parámetro, el valor predeterminado se pasará
a la función. Para matrices dinámicas u otros tipos que puedan considerarse equivalentes a un puntero, el único
valor predeterminado posible es Nil.
constante
MiConst = 20;
comenzar
Writeln('Función recibida: ',I); fin;
comenzar
MyRealFunc; fin.
Parámetros variables
Cuando los parámetros se declaran como parámetros variables, el procedimiento o función accede
inmediatamente a la variable que el bloque de llamada pasó en su lista de parámetros. El procedimiento obtiene
un puntero a la variable que se pasó y utiliza este puntero para acceder al valor de la variable. De esto se deduce
que cualquier cambio realizado en el parámetro se propagará de nuevo al bloque de llamada. Este mecanismo
se puede utilizar para devolver valores en los procedimientos. Debido a esto, el bloque que llama debe pasar un
parámetro de exactamente el mismo tipo que el tipo del parámetro declarado. Si no es así, el compilador generará
un error.
210
Machine Translated by Google
Los parámetros variables y constantes pueden no escribirse. En ese caso, la variable no tiene tipo y, por tanto,
es incompatible con todos los demás tipos. Sin embargo, se puede utilizar el operador de dirección o se puede
pasar a una función que también tenga un parámetro sin tipo. Si se utiliza un parámetro sin tipo en una
asignación, o se le debe asignar un valor, se debe utilizar un encasillado.
Las variables de tipo de archivo siempre deben pasarse como parámetros variables.
Las matrices abiertas se pueden pasar como parámetros variables. Consulte la sección 14.4.5, página 213 para obtener más información
sobre el uso de matrices abiertas.
Observación
• Tenga en cuenta que los valores predeterminados no son compatibles con los parámetros variables. Esto tendría poco sentido
ya que anula el propósito de poder devolver un valor a la persona que llama.
• El resultado de una función se trata internamente como un parámetro variable y puede tener un valor inicial distinto de
cero (o distinto de nulo). Esto es especialmente importante para los tipos administrados.
Parámetros de salida
El propósito de un parámetro de salida es devolver valores a la rutina de llamada: la variable se pasa por
referencia. El valor inicial del parámetro al ingresar la función se descarta y no debe usarse.
Si se debe usar una variable para pasar un valor a una función y recuperar datos de la función, entonces se
debe usar un parámetro variable. Si solo se debe recuperar un valor, se puede utilizar un parámetro de salida.
No hace falta decir que los valores predeterminados no son compatibles con nuestros parámetros.
La diferencia entre los parámetros y los parámetros por referencia es muy pequeña (sin embargo, consulte a
continuación los tipos administrados): el primero le da al compilador más información sobre lo que sucede con
los argumentos cuando se pasan al procedimiento: sabe que la variable no tiene por qué inicializarse antes de
la llamada. El siguiente ejemplo lo ilustra:
comenzar
R:=2;
Writeln('A es ',A); fin;
comenzar
B:=2;
Writeln('B es ',B); fin;
211
Machine Translated by Google
Era
C,D: Entero;
comenzar
DoA(C);
DoB(D); fin.
Ambos procedimientos DoA y DoB hacen prácticamente lo mismo. Pero la declaración de DoB proporciona
más información al compilador, permitiéndole detectar que D no tiene que inicializarse antes de llamar a DoB.
Dado que el parámetro A en DoA puede recibir un valor además de devolver uno, el compilador nota que C
no se inicializó antes de la llamada a DoA:
Esto muestra que es mejor usar parámetros cuando el parámetro se usa solo para devolver un valor.
Los parámetros de salida de comentarios solo se admiten en los modos Delphi y ObjFPC. Para los otros modos, out es un
identificador válido.
Observación Para los tipos administrados (tipos con recuento de referencias), el uso de parámetros Out genera cierta sobrecarga: el
compilador debe asegurarse de que el valor esté inicializado correctamente (es decir, tenga un recuento de referencia de cero (0)).
Esta inicialización normalmente la realiza la persona que llama.
Parámetros constantes
parámetro constante constante lista de identi cadores
: tipo de identificador
matriz de
identi er : tipo identi er = valor de parámetro predeterminado
Especificar un parámetro como Constante le da al compilador una pista de que la rutina llamada no cambiará
el contenido del parámetro. Esto permite al compilador realizar optimizaciones que de otra manera no podría
hacer, y también realizar ciertas comprobaciones en el código dentro de la rutina: es decir, puede prohibir
asignaciones al parámetro. Además, un parámetro constante no se puede pasar a otra función que requiera
un parámetro variable: el compilador también puede verificar esto. El uso principal de esto es reducir el
tamaño de la pila, mejorando así el rendimiento y aún conservando la semántica de pasar por valor...
Observación Al contrario de Delphi, no se deben hacer suposiciones sobre cómo se pasan los parámetros constantes
a la rutina subyacente. En particular, la suposición de que los parámetros de gran tamaño se pasan por
referencia no es correcta. Para ello se debe utilizar el tipo de parámetro constref, que está disponible a partir
de la versión 2.5.1 del compilador.
Una excepción es la convención de llamada stdcall: para compatibilidad con los estándares COM, los parámetros
constantes grandes se pasan por referencia.
212
Machine Translated by Google
Observación Tenga en cuenta que especificar const es un contrato entre el programador y el compilador. Es el programador
quien le dice al compilador que el contenido del parámetro constante no se cambiará cuando se ejecute la rutina,
no es el compilador quien le dice al programador que el parámetro no se cambiará.
Esto es particularmente importante y visible cuando se utilizan tipos recontados. Para tales tipos, el incremento y la
disminución (invisible) de cualquier recuento de referencia se omite cuando se utiliza const. Hacerlo a menudo
permite que el compilador omita marcos try/finally invisibles para estas rutinas.
Era
S: Cadena = 'Algo';
comenzar
S:='Algo más';
Escribir(T); fin;
comenzarHazlo(S);
fin.
Escribirá
Algo más
Los parámetros constantes también se pueden anular. Consulte la sección 14.4.2, página 210 para obtener más información
sobre los parámetros sin tipo.
En cuanto a los parámetros de valor, los parámetros constantes pueden obtener valores predeterminados.
Las matrices abiertas se pueden pasar como parámetros constantes. Consulte la sección 14.4.5, página 213 para obtener más información
sobre el uso de matrices abiertas.
soporta el paso de matrices abiertas, es decir, un procedimiento puede declararse con una matriz de longitud no
especificada como parámetro, como en Delphi. Se puede acceder a los parámetros de matriz abierta en el
procedimiento o función como una matriz declarada con índice inicial 0 y índice de último elemento Alto (parámetro).
Por ejemplo, el parámetro
sería equivalente a
Donde N sería el tamaño real de la matriz que se pasa a la función. N1 se puede calcular como Alto (Fila).
Específicamente, si se pasa una matriz vacía, Alto(Parámetro) devuelve 1, mientras que bajo(Parámetro) devuelve 0.
213
Machine Translated by Google
Los parámetros abiertos se pueden pasar por valor, por referencia o como parámetro constante. En los
últimos casos, el procedimiento recibe un puntero a la matriz real. En el primer caso, recibe una copia de la matriz.
En una función o procedimiento, los arreglos abiertos sólo se pueden pasar a funciones que también están declaradas
con arreglos abiertos como parámetros, no a funciones o procedimientos que aceptan arreglos de longitud fija. El
siguiente es un ejemplo de una función que utiliza una matriz abierta:
A partir de FPC 2.2, también es posible pasar matrices parciales a una función que acepte una matriz
abierta. Esto se puede hacer especificando el rango de la matriz que debe pasarse a la matriz abierta.
Dada la declaración
Era
A: Matriz[1..100];
Object Pascal o Delphi, Free Pascal admite la construcción Matriz de constantes para pasar parámetros a una subrutina.
Este es un caso especial de la construcción de matriz abierta, donde se permite pasar cualquier expresión de una
matriz a una función o procedimiento. La expresión debe tener un tipo de resultado simple: las estructuras no se
pueden pasar como argumento. Esto significa que se pueden pasar todos los tipos ordinales, flotantes o de
cadena, así como punteros, clases e interfaces (ya que los dos últimos son en realidad punteros).
Tipo
PVarRec = ^TVarRec;
TVarRec = registro
214
Machine Translated by Google
Por lo tanto, dentro del cuerpo del procedimiento, la matriz del argumento constante es equivalente a un código abierto.
matriz de TVarRec:
comenzar
Si Alto (Args) <0 entonces
comenzar
Writeln ('Sin argumentos');
salida;
fin;
Writeln ('Got ',Alto(Args)+1,' argumentos:');
Para i:=0 a Alto(Args) haga
comenzar
escribir ('Argumento',i,'tiene tipo');
caso Args[i].vtipo de
vtinteger :
Writeln ('Entero, Valor:',args[i].vinteger);
vtbooleano :
Writeln ('Booleano, Valor:',args[i].vboolean);
vtchar :
Writeln ('Char, valor: ',args[i].vchar);
vtextendido:
Writeln ('Extendido, valor: ',args[i].VExtendido^);
vtString :
Writeln ('Cadena corta, valor:',args[i].VString^);
vtPointer :
Writeln ('Puntero, valor: ',Longint(Args[i].VPointer));
vtPChar :
Writeln ('PChar, valor: ',Args[i].VPChar);
vtObject :
Writeln ('Objeto, nombre: ',Args[i].[Link]);
vtClass :
Writeln ('Referencia de clase, nombre:',Args[i].[Link]);
vtAnsiString:
Writeln ('AnsiString, valor:',AnsiString(Args[I].VAnsiString);
demás
Writeln ('(Desconocido) : ',args[i].vtype);
215
Machine Translated by Google
fin;
fin; fin;
Si el procedimiento se declara con el modificador cdecl, entonces el compilador pasará la matriz como lo haría un
compilador de C. Esto, de hecho, emula la construcción C de un número variable de argumentos, como lo mostrará el
siguiente ejemplo:
constante
comenzar
printf(Fmt,[P,123]); fin.
Observación Tenga en cuenta que esto no es cierto para Delphi, por lo que el código que depende de esta característica no será portátil.
Observación Tenga en cuenta que no hay soporte para argumentos DWord (o cardinales) en la matriz de const. Se convierten a
vtInteger/vtLongint. Esto es por compatibilidad con Delphi, y el compilador ignorará cualquier verificación de rango
resultante cuando esté en modo Delphi.
216
Machine Translated by Google
Esto significa que prácticamente todo lo que se puede hacer en la rutina es usar el operador de dirección o pasar el
argumento a otra función que también tenga un parámetro sin tipo.
Dentro de una rutina con un parámetro sin tipo, si el parámetro sin tipo se usa en una expresión, o se le debe asignar un
valor, siempre se debe usar un encasillado.
comenzar
Writeln('Como número entero: ',PInteger(@D)^);
Writeln('Como byte: ',PByte(@D)^); fin;
Era
D: Entero;
comenzar
D:=$0FFFFFF0;
Hazlo(D); fin.
Esto escribirá:
Tenga en cuenta que debido a que se necesita una dirección, no se pueden pasar expresiones constantes a la
función o procedimiento, es decir, dada la definición anterior, lo siguiente no funcionará:
Hazlo ($0FFFFFF0);
(Unicodestring, Ansistring, interfaces, matrices dinámicas) son tratados de manera algo especial por el compilador: los
datos tienen un recuento de referencias que aumenta o disminuye dependiendo de cuántas referencias a los datos existan.
Los calificadores de parámetros en llamadas a funciones o procedimientos influyen en lo que sucede con el recuento de
referencias de los tipos administrados:
• nada (pasar por valor): el recuento de referencia del parámetro aumenta al ingresar y eliminar.
arrugado al salir.
217
Machine Translated by Google
• out: el recuento de referencia del valor que se pasa se reduce en 1 y la variable que se pasa al procedimiento se
inicializa a "vacía" (generalmente Nil, pero ese es un detalle de implementación en el que no se debe confiar).
• var no pasa nada con el recuento de referencias. Se pasa una referencia a la variable original y cambiarla o leerla
tiene exactamente el mismo efecto que cambiar/leer la variable original.
• const este caso es un poco complicado. No sucede nada con el recuento de referencias porque aquí puede
pasar valores que no son valores. En particular, puede pasar una clase que implemente una interfaz en lugar
de la interfaz misma, lo que puede provocar que la clase se libere inesperadamente.
Observación El resultado de la función se trata internamente como un parámetro var de la función y se aplican las mismas reglas que para
Se aplican los parámetros var.
{$modo objfpc}
Tipo
Prueba de TI = Interfaz
Procedimiento DoTest(ACount: Entero); fin;
TTest = Clase(TInterfacedObject,ITest)
Procedimiento DoTest(ACount: Entero);
Destructor destruir; anular; fin;
Destructor [Link];
comenzar
Writeln('Destruir llamado'); fin;
comenzar
Writeln('Prueba ',ACount,': recuento de referencias: ',RefCount); fin;
218
Machine Translated by Google
Procedimiento Prueba1;
era
y: prueba de
TI;
comenzar y := [Link]; //
Árbitro. El recuento es 1 en este momento. [Link](1); //
Llamar a DoIT
aumentará el recuento de referencias y disminuirá al salir.
Hacer1(y,2); // El
recuento de referencias sigue siendo uno. [Link](3);
fin;
Procedimiento Prueba2;
era
Y: Prueba T;
comenzar
Y := [Link]; // aún no hay recuento del objeto // Ref. El recuento es 0 en este
punto. [Link](3); // El recuento de referencias seguirá
siendo cero.
Hacer2(y,4);
[Link](5);
[Link]; fin;
Prueba de Procedimiento3;
era
Y: Prueba T;
comenzar
Y := [Link]; // aún no hay recuento del objeto // Ref. El recuento es 0 en este
punto. [Link](6); // El recuento de referencias seguirá
siendo cero.
Hacer1(y,7);
[Link] (8); fin;
comenzar
Prueba1;
Prueba2;
Prueba3;
fin.
219
Machine Translated by Google
destruir llamado
Prueba 3: recuento de referencias: 0
Prueba 4: recuento de referencias: 0
Prueba 5: recuento de referencias: 0
destruir llamado
Prueba 6: recuento de referencias: 0
Prueba 7: recuento de árbitros: 1
destruir llamado
Prueba 8: recuento de referencias: 0
Como puede verse, en test3, el recuento de referencias disminuye de 1 a 0 al final de la llamada DoIt, lo que hace que la
instancia se libere antes de que regrese la llamada.
{$modo objfpc}
{$H+}
Tipo
PAnsiRec = ^TAnsiRec;
TAnsiRec = Grabar
Página de código : TSystemCodePage;
Tamaño del elemento: Palabra;
{$ifdef CPU64} {alinear
campos}
Ficticio : DPalabra;
{$endif CPU64}
Árbitro : TamañoInt;
Solo : TamañoInt;
fin;
comenzar
si P=Nil entonces
Resultado:=0
demás
Resultado:=PAnsiRec(PSizeOf(TAnsiRec))^.Ref;
fin;
comenzar
Writeln('Por var, recuento de referencias: ',SRefCount(Pointer(S))); fin;
comenzar
Writeln('Const, recuento de referencias: ',SRefCount(Puntero(S))); fin;
220
Machine Translated by Google
comenzar
Writeln('Valor, recuento de referencias: ',SRefCount(Pointer(S))); fin;
comenzar
Writeln('Argumento de función, recuento de referencias: ',SRefCount(Pointer(S)));
Writeln('Resultado de la función, recuento de referencias: ',SRefCount(Pointer(Resultado))); fin;
Era
S,T : Cadena;
comenzar
S:='Alguna cadena';
Writeln('Constant : ',SrefCount(Puntero(S)));
UniqueString(S);
Writeln('Unique T:=S; : ',SRefCount(Puntero(S)));
La sobrecarga de funciones simplemente significa que la misma función se define más de una vez, pero cada vez
con una lista de parámetros formales diferente. Las listas de parámetros deben diferir al menos en uno de sus
tipos de elementos. Cuando el compilador encuentra una llamada a una función, observará los parámetros de la
función para decidir cuál de las funciones definidas debe llamar. Esto puede resultar útil cuando se debe definir la
misma función para diferentes tipos.
Cuando el compilador encuentra una llamada a la función aleatoria, primero buscará qué función debe usar. Por lo tanto,
verifica los parámetros en una llamada de función y busca si hay una definición de función que coincida con la lista de
parámetros especificada. Si el compilador encuentra dicha función, se inserta una llamada a esa función. Si no se
encuentra dicha función, se genera un error del compilador.
Las funciones que tienen un modificador cdecl no se pueden sobrecargar. (Técnicamente, porque este modificador evita
que el compilador altere el nombre de la función).
Antes de la versión 1.9 del compilador, las funciones sobrecargadas debían estar en la misma unidad. Ahora el
compilador continuará buscando en otras unidades si no encuentra una versión coincidente de una función
sobrecargada en una unidad y si la palabra clave sobrecarga está presente.
221
Machine Translated by Google
Si la palabra clave de sobrecarga no está presente, entonces todas las versiones sobrecargadas deben residir en la
misma unidad, y si se trata de métodos que forman parte de una clase, deben estar en la misma clase, es decir, el
compilador no buscará métodos sobrecargados en las clases principales si No se especificó la palabra clave de sobrecarga.
Observación Tenga en cuenta que si redefine (y sobrecarga) las funciones internas del compilador, las funciones originales del
compilador ya no estarán disponibles. Este es, por ejemplo, el caso de Inc() y Dec()
Se puede declarar una función sin que le siga su implementación, haciendo que le siga el procedimiento de avance. La
implementación efectiva de esa función debe seguir más adelante en el módulo. La función se puede utilizar después de
una declaración directa como si ya se hubiera implementado. El siguiente es un ejemplo de una declaración directa.
Una función se puede declarar hacia adelante solo una vez. Asimismo, en unidades, no se permite tener una función
declarada hacia adelante de una función que ha sido declarada en la parte de la interfaz. La declaración de interfaz
cuenta como una declaración directa. La siguiente unidad dará un error cuando se compila:
fin.
A la inversa, las funciones declaradas en la sección de interfaz no se pueden declarar en la sección de implementación.
Lógicamente, puesto que ya han sido declarados.
222
Machine Translated by Google
Directiva externa
programa CmodDemo;
{$Enlacelib c}
Const P: PChar = '¡Esto es divertido!'; Función strlen (P:
PChar): Entero largo; cdec; externo; comenzar WriteLn ('Longitud de (',p,'): ',strlen(p)) fin.
Observación Los parámetros en la declaración de la función externa deben coincidir exactamente con los del
declaración en el archivo objeto.
'nombre' externo;
Luego esto le dice al compilador que la función reside en la biblioteca "lname". Luego, el compilador vinculará
automáticamente esta biblioteca al programa.
Esto le dice al compilador que la función reside en la biblioteca "lname", pero con el nombre "Fname". Luego, el
compilador vinculará automáticamente esta biblioteca al programa y utilizará el nombre correcto para la función. En
WINDOWS y OS/2, también se puede utilizar el siguiente formulario:
Esto le dice al compilador que la función reside en la biblioteca "lname", pero con el índice Ind. Luego, el compilador
vinculará automáticamente esta biblioteca al programa y utilizará el índice correcto para la función.
Finalmente, la directiva externa se puede utilizar para especificar el nombre externo de la función:
Esto le dice al compilador que la función tiene el nombre "Fname". La biblioteca o el archivo objeto correcto (en
este caso myfunc.o) aún debe estar vinculado, asegurando que la función "Fname" esté realmente incluida en la
etapa de vinculación.
223
Machine Translated by Google
Las funciones y procedimientos se pueden implementar completamente en lenguaje ensamblador. Para indicar esto,
utilice la palabra clave del ensamblador:
Funciones de ensamblador
Al contrario de Delphi, la palabra clave ensamblador debe estar presente para indicar una función ensambladora. Para obtener
más información sobre las funciones del ensamblador, consulte el capítulo sobre el uso del ensamblador en la Guía del
programador.
Una declaración de función o procedimiento puede contener modificadores. Aquí enumeramos las diversas posibilidades:
Modi cadores
saveregisters softfloat
stdcall
sysv_abi_default sysv_abi_cdecl vectorcall
Vararg
224
Machine Translated by Google
Free Pascal no admite todos los modificadores de Turbo Pascal (aunque los analiza para comprobar su
compatibilidad), pero sí admite varios modificadores adicionales. Se utilizan principalmente para ensamblador y
referencia a archivos de objetos C.
14.9.1 alias
El modificador de alias permite al programador especificar un nombre diferente para un procedimiento o función.
Esto es especialmente útil para hacer referencia a este procedimiento desde construcciones en lenguaje ensamblador
o desde otro archivo objeto. Como ejemplo, considere el siguiente programa:
Alias de programa;
comenzar
ENSAMBLE
llamar al final de
myprint;
fin.
Observación El alias especificado se inserta directamente en el código ensamblador, por lo que distingue entre mayúsculas y minúsculas.
El modificador de alias no hace que el símbolo sea público para otros módulos, a menos que la rutina también
se declare en la parte de interfaz de una unidad, o se use el modificador público para forzarlo a ser público.
Considera lo siguiente:
interfaz
implementación
fin.
Esto hará que la rutina testroutine esté disponible públicamente para archivos de objetos externos bajo el nombre
de etiqueta ARoutine.
Observación La directiva alias se considera obsoleta. Utilice la directiva de nombre público. Ver sección 14.9.17, página 230.
225
Machine Translated by Google
14.9.2 decl.
El modificador cdecl se puede utilizar para declarar una función que utiliza una convención de llamada de tipo C. Esto
debe usarse al acceder a funciones que residen en un archivo objeto generado por compiladores de C estándar, pero
también debe usarse para funciones de Pascal que se usarán como devoluciones de llamada para bibliotecas de C.
El modificador cdecl permite utilizar la función C en el código. Para funciones C externas, el archivo objeto que contiene
la implementación C de la función o procedimiento debe estar vinculado. Como ejemplo:
programa CmodDemo;
{$LINKLIB c}
Const P: PChar = '¡Esto es divertido!'; Función StrLen(P:
PChar): Longint;cdecl; nombre externo 'strlen'; comenzar WriteLn ('Longitud de (',p,'): ',StrLen(p));
fin.
Al compilar esto y vincularlo a la biblioteca C, se puede llamar a la función strlen en todo el programa. La directiva externa
le dice al compilador que la función reside en un archivo objeto externo o biblioteca con el nombre "strlen" (ver 14.7).
Observación Los parámetros en nuestra declaración de la función C deben coincidir exactamente con los de la declaración.
Cª.
Para funciones que no son externas, pero que se declaran mediante cdecl, no se necesita ningún enlace externo. Estas
funciones tienen algunas restricciones, por ejemplo, la matriz de la construcción const no se puede usar (debido a la
forma en que usa la pila). Por otro lado, el modificador cdecl permite que estas funciones se utilicen como devoluciones
de llamada para rutinas escritas en C, ya que estas últimas esperan la convención de llamada “cdecl”.
14.9.3 cppdecl
El modificador cppdecl se puede utilizar para declarar una función que utiliza una convención de llamada de tipo C++.
Esto debe usarse al acceder a funciones que residen en un archivo objeto generado por compiladores estándar de C++,
pero también debe usarse para funciones de Pascal que se usarán como devoluciones de llamada para bibliotecas de C++.
Tenga en cuenta que vincular al soporte de C++ es, en el mejor de los casos, experimental, por lo que debe usarse con extremo cuidado.
14.9.4 exportar El
modificador de exportación se utiliza para exportar nombres al crear una biblioteca compartida o un programa ejecutable.
Esto significa que el símbolo estará disponible públicamente y podrá importarse desde otros programas. Para obtener
más información sobre este modificador, consulte la sección sobre “Creación de bibliotecas” en la Guía del programador.
14.9.6 en línea
Los procedimientos que se declaran en línea se copian en los lugares donde se llaman. Esto tiene el efecto de que no
hay una llamada al procedimiento real, el código del procedimiento simplemente se copia donde está el
226
Machine Translated by Google
Se necesita un procedimiento, esto da como resultado una velocidad de ejecución más rápida si la función o procedimiento se usa mucho.
Es obvio que incluir funciones grandes no tiene sentido.
De forma predeterminada, no se permiten procedimientos en línea. El código en línea debe habilitarse mediante la directiva de cambio
de línea de comando Si o {$inline on}.
Observación
1. inline es solo una pista para el compilador. Esto no significa automáticamente que todas las llamadas estén integradas; A veces, el
compilador puede decidir que una función simplemente no se puede incluir en línea, o que una llamada particular a la función no
se puede incluir en línea. Si es así, el compilador emitirá una advertencia.
2. En versiones antiguas de Free Pascal, el código en línea no se exportaba desde una unidad. Esto significa que al llamar a un
procedimiento en línea desde otra unidad, se realizará una llamada a un procedimiento normal. Sólo dentro de las unidades, los
procedimientos en línea están realmente en línea. A partir de la versión 2.0.2, en línea funciona en todas las unidades.
3. No se permiten funciones recursivas en línea. es decir, no se permite una función en línea que se llame a sí misma.
14.9.7 interrupción
La palabra clave de interrupción se utiliza para declarar una rutina que se utilizará como controlador de interrupciones.
Al ingresar a esta rutina, se guardarán todos los registros y al salir, se restaurarán todos los registros y se ejecutará una interrupción o
retorno de trampa (en lugar del retorno normal de la instrucción de subrutina).
En plataformas donde no existe un retorno de una interrupción, en su lugar se realizará el código de salida normal de las rutinas. Para
obtener más información sobre el código generado, consulte la Guía del programador.
14.9.8 iocheck
La palabra clave iocheck se utiliza para declarar una rutina que genera un código de verificación de resultados de E/S dentro de un bloque
{$IOCHECKS ON} cada vez que se llama.
El resultado es que si se genera una llamada a este procedimiento, el compilador insertará un código de verificación de E/S si la llamada
está dentro de un bloque {$IOCHECKS ON}.
Este modificador está destinado a rutinas internas de RTL, no a su uso en el código de la aplicación.
14.9.9 locales
El modificador local permite al compilador optimizar la función: una función local no puede estar en la sección de interfaz de una unidad:
siempre está en la sección de implementación de la unidad. De esto se deduce que la función no se puede exportar desde una biblioteca.
En Linux, la directiva local genera algunas optimizaciones. En Windows no tiene ningún efecto. Fue introducido por compatibilidad con
Kylix.
14.9.10 MS_ABI_Default
El modificador MS_ABI_Default se utiliza para indicar que el compilador debe utilizar la versión de Microsoft de la convención de llamada
x8664. Esta es la convención de llamadas predeterminada para Win64 y solo se admite en x8664.
227
Machine Translated by Google
14.9.11 MS_ABI_CDecl
El modificador MS_ABI_CDecl se usa para indicar que el compilador debe usar la versión de Microsoft de la convención
de llamadas x8664, con la excepción de que 'array of const' se interpreta como parámetros cdecl varargs, en lugar de la
matriz pascal normal de const.
14.9.12 MWPascal
El modificador MWPascal se utiliza para indicar que el compilador debe utilizar la convención de llamada Pascal de
Metrowerks. Esta convención de llamada se comporta igual que cdecl, excepto que los parámetros de registro constante
se pasan por referencia en lugar de por valor. Compatible con todas las plataformas.
El modificador noreturn se puede utilizar para indicarle al compilador que el procedimiento no regresa. El compilador
puede utilizar esta información para evitar emitir advertencias sobre variables no inicializadas o resultados que no se
configuran.
En el siguiente ejemplo, el compilador no emitirá una advertencia de que es posible que el resultado no esté configurado
en la función f:
detener(1); fin;
comenzar
si (i <0) entonces
hacer_halt
demás
resultado:=yo;
fin;
El modificador nostackframe se puede utilizar para indicarle al compilador que no debe generar un marco de pila para
este procedimiento o función. De forma predeterminada, siempre se genera un marco de pila para cada procedimiento o
función, pero el compilador lo omitirá si puede.
Para rutinas asm (ensamblador puro), esta directiva se puede utilizar para omitir la generación de un marco de pila.
Se debe tener mucho cuidado al utilizar este modificador: la mayoría de los procedimientos o funciones necesitan un
marco de pila. Son necesarios especialmente para la depuración.
14.9.15 sobrecarga
El modificador de sobrecarga le dice al compilador que esta función está sobrecargada. Es principalmente por
compatibilidad con Delphi, ya que en Free Pascal, todas las funciones y procedimientos se pueden sobrecargar sin este modificador.
Sólo hay un caso en el que el modificador de sobrecarga es obligatorio: si se debe sobrecargar una función que reside
en otra unidad. Ambas funciones deben declararse con el modificador de sobrecarga: el modificador de sobrecarga le
dice al compilador que debe continuar buscando versiones sobrecargadas en otras unidades.
228
Machine Translated by Google
acción unitaria;
interfaz
implementación
comenzar
Writeln('[Link] recibió ',A) finalizar;
fin.
unidad ub;
interfaz
implementación
comenzar
Writeln('[Link] recibió ',A) finalizar;
fin.
programa uab;
comenzar
DoIt('Alguna cadena'); fin.
Cuando el compilador comience a buscar la declaración de DoIt, encontrará una en la unidad ub. Sin la
directiva de sobrecarga, el compilador daría un error de discrepancia de argumentos:
Con la directiva de sobrecarga implementada en ambas ubicaciones, el compilador sabe que debe continuar buscando
una versión sobrecargada con una lista de parámetros coincidente. Tenga en cuenta que ambas declaraciones deben tener
229
Machine Translated by Google
el modificador de sobrecarga especificado; no basta con tener el modificador en la unidad ub. Esto es para
evitar sobrecargas no deseadas: el programador que implementó la unidad ua debe marcar el procedimiento
como apto para sobrecarga.
14.9.16 pascales
El modificador Pascal se puede utilizar para declarar una función que utiliza la convención de llamada de tipo Pascal
clásica (pasando parámetros de izquierda a derecha). Para obtener más información sobre la convención de
llamadas de Pascal, consulte la Guía del programador.
14.9.17 público
La palabra clave Public se utiliza para declarar una función globalmente en una unidad. Esto es útil si no se debe
poder acceder a la función desde el archivo de la unidad (es decir, otra unidad/programa que usa la unidad no
ve la función), pero debe ser accesible desde el archivo objeto. Como ejemplo:
Primero := 0; fin;
Segundo := 1; fin;
fin.
Si otro programa o unidad usa esta unidad, no podrá usar la función Segundo, ya que no está declarada en la parte de
la interfaz. Sin embargo, será posible acceder a la función Segundo en el nivel del lenguaje ensamblador, utilizando su
nombre alterado (consulte la Guía del programador).
El modificador público también puede ir seguido de una directiva de nombre para especificar el nombre del ensamblador, de la
siguiente manera:
El símbolo del ensamblador especificado por la directiva de "nombre público" será "segundo", en todas las letras
minúsculas.
230
Machine Translated by Google
14.9.18 registrarse
La palabra clave de registro se utiliza por compatibilidad con Delphi. En la versión 1.0.x del compilador, esta directiva
no tiene ningún efecto sobre el código generado. A partir de las versiones 1.9.X, esta directiva es compatible.
Para la plataforma Intel i386, los primeros tres argumentos se pasan en los registros EAX, EDX y ECX.
Para otras plataformas, esta palabra clave no tiene ningún efecto; se utiliza la convención de llamada ABI de la plataforma predeterminada.
En general, no se recomienda el uso de esta directiva, a menos que sepa lo que está haciendo.
Se puede encontrar más información sobre este modificador en la Guía del Programador, en la sección sobre el
mecanismo de llamada y el capítulo sobre vinculación.
saveregisters le dice al compilador que todos los registros de la CPU deben guardarse antes de llamar a esta rutina.
Los registros de la CPU que se guardan dependen completamente de la CPU.
El modificador stdcall empuja los parámetros de derecha a izquierda en la pila y también alinea todos los parámetros
con una alineación predeterminada.
Se puede encontrar más información sobre este modificador en la Guía del Programador, en la sección sobre el
mecanismo de llamada y el capítulo sobre vinculación.
14.9.23 SYSV_ABI_Default
El modificador SYS_ABI_Default se utiliza para indicar que el compilador debe utilizar la convención de llamada
específica de x8664 que se ajuste a System V AMD64 ABI. Esta es la convención de llamada predeterminada para
todas las plataformas x8664 excepto Win64, y solo se admite en x8664. .
14.9.24 SYSV_ABI_CDecl
El modificador SYSV_ABI_CDecl se usa para indicar que el compilador debe usar la convención de llamada
específica de x8664 que se ajusta a System V AMD64 ABI, con la excepción de que 'array of const' se interpreta
como parámetros cdecl varargs, en lugar del pascal normal. conjunto de constantes.
231
Machine Translated by Google
14.9.26 Vararg
Este modificador sólo se puede utilizar junto con el modificador cdecl, para procedimientos externos de C. Indica que el
procedimiento acepta un número variable de argumentos después de la última variable declarada.
Estos argumentos se pasan sin ninguna verificación de tipo. Es equivalente a usar la matriz de construcción const para
procedimientos cdecl, sin tener que declarar la matriz de const. No es necesario utilizar los corchetes alrededor de los
argumentos variables cuando se utiliza esta forma de declaración.
Las siguientes declaraciones son dos formas de hacer referencia a la misma función en la biblioteca C:
PrintF1('%d %d\n',1,1);
PrintF2('%d %d\n',[1,1]);
modificador le permite especificar las convenciones de llamada nativas para la plataforma actual: el compilador
seleccionará la convención de llamada correcta dependiendo de la arquitectura del sistema operativo: stdcall en windows
i386 y cdecl en todas las demás plataformas.
Los modificadores que existen en Turbo Pascal, pero que Free Pascal no admite, se enumeran en la tabla (14.1).
El compilador dará una advertencia cuando encuentre estos modificadores, pero de lo contrario los ignorará por completo.
232
Machine Translated by Google
Capítulo 15
15.1 Introducción
Free Pascal admite la sobrecarga de operadores. Esto significa que es posible definir la acción de algunos
operadores sobre tipos autodefinidos y así permitir el uso de estos tipos en expresiones matemáticas.
Además, la sobrecarga de operadores se puede utilizar para algunas acciones especiales que, estrictamente
hablando, no son operadores en el sentido matemático de la palabra. Esto es cierto para los procedimientos Inc y
Dec (que se pueden utilizar sólo como procedimiento) y para la asignación implícita y explícita (:= y encasillamiento).
El operador enumerador (para definir una serie enumerable para un tipo) también forma parte de esta clase de operadores.
Definir la acción de un operador es muy parecido a la definición de una función o procedimiento, sólo que
existen algunas restricciones en las posibles definiciones, como se mostrará a continuación.
La sobrecarga de operadores es, en esencia, una poderosa herramienta de notación; pero tampoco es más que eso,
ya que se pueden obtener los mismos resultados con llamadas a funciones regulares. Al utilizar la sobrecarga de
operadores, es importante tener en cuenta que algunas reglas implícitas pueden producir resultados inesperados.
Esto estará indicado.
Hay 2 formas de declarar sobrecargas de operadores. La primera forma es la forma original implementada
en Free Pascal y es la más poderosa. Se analiza en este capítulo. También existe una segunda forma,
que sólo se puede implementar para registros avanzados (o extendidos), y se analiza en el capítulo sobre
registros extendidos.
233
Machine Translated by Google
definición de operador lógico y ( lista de parámetros )
o
gratis
no
otro enumerador de definición de operador ( lista de parámetros )
aum dec
La lista de parámetros para un operador de comparación o un operador aritmético siempre debe contener
dos parámetros, con la excepción del menos o más unario, donde solo se necesita un parámetro. El tipo
de resultado de los operadores de comparación debe ser booleano.
Los tipos simples definidos por el usuario se pueden utilizar para operadores, así como para registros y matrices. Existen algunas
limitaciones en la sobrecarga de operadores:
• Esta forma de declarar operadores sólo se admite en los modos ObjFPC y FPC.
{$modeSwitch ArrayOperadores}
Observación Al compilar en modo Objfpc, es posible que se elimine el identificador de resultado. El resultado puede ser entonces
Se accede a través del símbolo de resultado estándar.
Si se elimina el identificador de resultado y el compilador no está en uno de estos modos, se producirá un error de sintaxis.
ocurrir.
El bloque de declaraciones contiene las declaraciones necesarias para determinar el resultado de la operación.
Puede contener fragmentos de código arbitrarios de gran tamaño; se ejecuta siempre que la operación se encuentra
en alguna expresión. El resultado del bloque de instrucciones siempre debe estar definido; El compilador no verifica
las condiciones de error y el código debe ocuparse de todos los casos posibles, generando un error de tiempo de
ejecución si se encuentra alguna condición de error.
234
Machine Translated by Google
A continuación se examinarán los tres tipos de definiciones de operadores. Como ejemplo, a lo largo de este capítulo
se utilizará el siguiente tipo para definir operadores sobrecargados en:
tipo
complejo = registro re: real;
soy real; fin;
Las fuentes de la biblioteca en tiempo de ejecución contienen dos unidades que utilizan en gran medida la sobrecarga de operadores:
El operador de asignación define la acción de asignar un tipo de variable a otro. El tipo de resultado debe coincidir con
el tipo de variable a la izquierda de la declaración de asignación, el parámetro único del operador de asignación debe
tener el mismo tipo que la expresión a la derecha del operador de asignación.
Este sistema se puede utilizar para declarar un nuevo tipo y definir una asignación para ese tipo. Por ejemplo, para
poder asignar un tipo recién definido "Complejo"
Era
C,Z: Complejo; // Nuevo tipo complejo
comenzar
Z:=C; // asignaciones entre tipos complejos. fin;
era
R: real;
C: complejo;
comenzar
C:=R;
fin;
Como puede verse en esta declaración, define la acción del operador := con a la derecha una expresión real y a la
izquierda una expresión compleja.
235
Machine Translated by Google
comenzar
[Link]:=r;
[Link]:=0,0; fin;
Como se puede ver en el ejemplo, el identificador de resultado (z en este caso) se utiliza para almacenar el resultado
de la asignación. Al compilar en modo Delphi o en modo ObjFPC, también se permite el uso del identificador especial
Result, que puede sustituirse por la z, por lo que lo anterior equivaldría a
comenzar
[Link]:=r;
[Link]:=0.0; fin;
El operador de asignación también se utiliza para convertir tipos de un tipo a otro. El compilador considerará todos los
operadores de asignación sobrecargados hasta que encuentre uno que coincida con los tipos de expresiones de la mano
izquierda y de la mano derecha. Si no se encuentra dicho operador, se genera un error de "no coincidencia de tipos".
Observación El operador de asignación no es conmutativo; el compilador nunca invertirá el papel de los dos argumentos. En otras
palabras, dada la definición anterior de operador de asignación, no es posible lo siguiente:
era
R: real;
C: complejo;
comenzar
R:=C;
fin;
Si la asignación inversa fuera posible, entonces también se debe definir el operador de asignación para eso. (Esto no
es así para los números reales y complejos).
Observación El operador de asignación también se utiliza en conversiones de tipos implícitos. Esto puede tener efectos no deseados.
Considere las siguientes definiciones:
Era
r1,r2: real;
comenzar
r1:=exp(r2); fin;
La discrepancia se produce porque el compilador encontrará la definición de la función exp con el argumento complejo.
Implícitamente convierte r2 en un complejo, por lo que puede usar la función exp anterior.
El resultado de esta función es un complejo que no se puede asignar a r1, por lo que el compilador dará
236
Machine Translated by Google
un error de "tipo no coincidente". El compilador no buscará más exp que tenga los argumentos correctos.
r1:=[Link](r2);
Al realizar una conversión explícita, el compilador intentará una conversión implícita si hay un operador de
asignación presente. Eso significa que
Era
R1: T1;
R2: T2;
comenzar
R2:=T2(R1);
Sin embargo, se puede definir un operador explícito y luego se utilizará cuando el compilador encuentre un
encasillamiento.
usos
utilidades del sistema;
tipo
TTest1 = registro
TTest2 = registro
f: cadena; fin;
TTest3 = registro
f: booleano; fin;
237
Machine Translated by Google
era
t1: TTprueba1;
t2: TTprueba2;
t3: TTprueba3;
comenzar t1.f :=
42; // Implícito
t2 := t1; // se
utilizará una operación teóricamente explícita, pero implícita, // porque no se define ningún
operador explícito t2 := TTest2(t1); // lo siguiente no se compilaría, //
no se ha definido ningún
operador de asignación (aquí no se utilizará uno
explícito) //t1 := t2; // T1 explícito := TTest1(t2); // primero explícito (TTest2 => TTest1) luego implícito (TTest1
=> TTest3) t3 :=
TTest1(t2); //
implícito t3 := t1; // explícito
t3 := TTest3(t1); fin.
238
Machine Translated by Google
multiplicación Para multiplicar dos tipos, el operador de multiplicación * debe estar sobrecargado.
división Para dividir dos tipos, el operador / división debe estar sobrecargado.
suma Para agregar dos tipos, el operador de suma + debe estar sobrecargado. Tenga en cuenta que + no se puede utilizar
para matrices dinámicas, ya que se trata de una operación interna del compilador para la concatenación de matrices
(consulte también el interruptor de modo ARRAYOPERATORS).
resta Para restar dos tipos, el operador de resta debe estar sobrecargado.
exponenciación Para exponenciar dos tipos, el operador de exponenciación ** debe estar sobrecargado.
El menos unario se utiliza para tomar el negativo del argumento que le sigue.
Diferencia simétrica Para tomar la diferencia simétrica de 2 estructuras, el operador >< debe ser
sobrecargado.
La definición de un operador aritmético toma dos parámetros, excepto el menos unario, que solo necesita un parámetro.
El primer parámetro debe ser del tipo que ocurre a la izquierda del operador, el segundo parámetro debe ser del tipo
que está a la derecha del operador aritmético. El tipo de resultado debe coincidir con el tipo que resulta después de la
operación aritmética.
era
R: real;
C,Z: complejo;
comenzar
C:=R*Z; fin;
comenzar
[Link] := [Link] * r; [Link] := [Link] *
r; fin;
Como puede verse, el primer operador es real y el segundo es complejo. El tipo de resultado es complejo.
La multiplicación y suma de reales y complejos son operaciones conmutativas. El compilador, sin embargo, no tiene
noción de este hecho, por lo que incluso si se define una multiplicación entre un real y un complejo, el compilador no
usará esa definición cuando encuentre un complejo y un real (en ese orden). Es necesario definir ambas operaciones.
era
R: real;
C,Z: complejo;
comenzar
C:=Z*R; fin;
239
Machine Translated by Google
Dado que los tipos de Z y R no coinciden con los tipos en la definición del operador.
La razón de este comportamiento es que es posible que una multiplicación no siempre sea conmutativa.
[Link]. la multiplicación de una matriz (n,m) por una matriz (m,n) dará como resultado una matriz (n,n), mientras que la multiplicación
de una matriz (m,n) por una matriz (n,m) dará como resultado una matriz (m ,m) matriz, que no tiene por qué ser la misma en
todos los casos.
El operador de comparación se puede sobrecargar para comparar dos tipos diferentes o para comparar dos tipos
iguales que no son tipos básicos. Si los operandos no son tipos simples, el tipo de resultado de un operador de
comparación no siempre tiene que ser booleano, pero no se pueden usar en una declaración if, repetición o while.
menor que (<) Para determinar si una variable es menor que otra.
mayor que (>) Para determinar si una variable es mayor que otra.
mayor o igual a (>=) Para determinar si una variable es mayor o igual a otra.
menor o igual a (<=) Para determinar si una variable es mayor o igual a otra.
Si no hay un operador separado para desigual a (<>), entonces, para evaluar una declaración que contiene el
operador desigual a, el compilador usa el operador igual a (=) y niega el resultado. Lo contrario no es cierto: si no
existe un operador “igual a” pero sí “no igual a”, el compilador no lo utilizará para evaluar una expresión que
contenga el operador igual (=).
Era
C1,C2: Complejo;
comenzar
Si C1=C2 entonces
Writeln('C1 y C2 son iguales'); fin;
La definición del operador de comparación necesita dos parámetros, con los tipos que el operador debe comparar.
Aquí también el compilador no aplica la conmutatividad: si los dos tipos son diferentes, entonces es necesario
definir dos operadores de comparación.
En el caso de números complejos, es necesario, por ejemplo, definir 2 comparaciones: una con el tipo complejo
primero y otra con el tipo real primero.
Dadas las definiciones
operador = (z1: complejo;r: real) b: booleano; operador = (r: real; z1: complejo)
b: booleano;
240
Machine Translated by Google
Era
R,S: Real;
C: Complejo;
comenzar
Si (C=R) o (S=C) entonces
Writeln('Aceptar'); fin;
Tenga en cuenta que el orden del tipo real y complejo en las dos comparaciones está invertido.
El siguiente ejemplo muestra que no es necesario que el tipo de resultado sea booleano:
Tipo
TMyRec = registro a,b : entero; fin;
comenzar
si (xa=ya) y (xb=yb) entonces
R:='igual' más
R:='diferir'; fin;
era
x,y : TMyRec;
comenzar: = 1;
de:=1;
Escribe (x = y); de:=2;
de:=3;
Escribe (x
= y); fin.
iguales
difieren
si (x=y) entonces
writeln('Igual');
241
Machine Translated by Google
15.6 En operador
A partir de la versión 2.6 de Free Pascal, el operador In también puede sobrecargarse. El primer argumento
del operador in debe ser el operando a la izquierda de la palabra clave in. Lo siguiente sobrecarga el
operador in para registros:
{$modo objfpc}{$H+}
tipo
TMyRec = registro A: extremo entero;
operador en (const A: Entero; const B: TMyRec): booleano; comenzar Resultado := A = BA; fin;
era
R: TMyRec;
comenzar RA :=
10; Writeln(1 en R); // false Writeln(10 en
R); // verdadero final.
El operador in también se puede sobrecargar para otros tipos distintos de los ordinales, como en el siguiente
ejemplo:
{$modo objfpc}{$H+}
tipo
TMyRec = registro A: extremo entero;
era
S,R: TMyRec;
comenzar RA :=
10; SA:=1;
Escribir(S en R); // false Writeln(R en
R); // verdadero final.
Los operadores lógicos and, or, xor y not pueden sobrecargarse. Estos operadores se utilizan normalmente en
tipos simples de dos maneras diferentes:
242
Machine Translated by Google
Al sobrecargar estos operadores, el tipo de resultado no está restringido. Esto significa que puede definir los
operadores como operadores lógicos booleanos:
Tipo
Rec = grabar
era
r1,r2 : Rec;
fin.
243
Machine Translated by Google
El compilador siempre verificará el tipo de retorno para determinar el tipo final de una expresión, y se
verificará la seguridad de tipos de las asignaciones.
Los procedimientos Inc y Dec son, a efectos de optimización, intrínsecos del compilador. Se pueden anular de
la forma habitual declarando un procedimiento:
comenzar Inc(S,'.');
fin;
comenzar
S:=S+T;
fin;
Pero debido a que son intrínsecos del compilador, también se pueden implementar como operadores para tipos personalizados:
comenzar
Z:=S+T;
fin;
El operador enumerador se puede utilizar para definir un enumerador para cualquier tipo. Debe devolver
una clase, objeto o registro extendido que tenga la misma firma que la interfaz IEnumerator. Tenga en
cuenta que el compilador no reconoce este operador en el modo de sintaxis de Delphi.
El siguiente código definirá un enumerador para el tipo Integer:
Tipo
TEvenEnumerator = Clase
FActual: Entero;
FMax: Entero;
Función MoveNext: Booleana;
Propiedad actual: entero leído FCurrent; fin;
comenzar
FActual:=FActual+2;
Resultado:=FActual<=FMax; fin;
244
Machine Translated by Google
comenzar
Resultado:=[Link];
[Link]:=i; fin;
era
I: Entero; metro :
Entero = 4;
comenzar
Porque yo en M hago
Escribir(i);
fin.
El bucle imprimirá todos los números pares distintos de cero, menores o iguales al enumerable. (2 y 4 en el
caso del ejemplo).
Puede encontrar más información sobre los enumeradores y el operador enumerador en la sección 13.2.5, página 195.
245
Machine Translated by Google
Capítulo 16
Un programa Pascal puede constar de módulos llamados unidades. Una unidad se puede utilizar para agrupar
fragmentos de código o para darle código a alguien sin proporcionar las fuentes. Tanto los programas como las unidades
constan de bloques de código, que son mezclas de declaraciones, procedimientos y declaraciones de variables o tipos.
16.1 Programas
Un programa Pascal consta del encabezado del programa, seguido posiblemente de una cláusula de "usos" y un bloque.
Programas
El encabezado del programa se proporciona para compatibilidad con versiones anteriores y el compilador lo ignora.
La cláusula de usos sirve para identificar todas las unidades que necesita el programa. Todos los identificadores
que se declaran en la sección de interfaz de las unidades en la cláusula de usos se agregan a los identificadores
conocidos del programa. La unidad del sistema no tiene que estar en esta lista, ya que siempre la carga el compilador.
El orden en que aparecen las unidades es importante, determina en qué orden se inicializan.
Las unidades se inicializan en el mismo orden en que aparecen en la cláusula de usos. Los identificadores se buscan
en el orden opuesto, es decir, cuando el compilador busca un identificador, busca primero en la última unidad de la
cláusula de usos, luego en la penúltima, y así sucesivamente. Esto es importante en caso de que dos unidades declaren
tipos diferentes con el mismo identificador.
El compilador buscará versiones compiladas o versiones fuente de todas las unidades en la cláusula de usos en la ruta de
búsqueda de unidades. Si el nombre del archivo de la unidad se mencionó explícitamente usando la palabra clave in, la fuente
se toma del nombre del archivo especificado:
246
Machine Translated by Google
programa programab;
unita se busca en el directorio principal del directorio de trabajo actual del compilador. Puede agregar una
directiva {$UNITPATH ..} para asegurarse de que se encontrará la unidad sin importar dónde se encuentre
el directorio de trabajo actual del compilador.
Cuando el compilador busca archivos de unidad, agrega la extensión .ppu al nombre de la unidad. En LINUX y en
sistemas operativos donde los nombres de archivos distinguen entre mayúsculas y minúsculas al buscar una unidad, se
utiliza el siguiente mecanismo:
Además, si el nombre de una unidad tiene más de 8 caracteres, el compilador primero buscará un nombre de unidad
con esta longitud y luego truncará el nombre a 8 caracteres y lo buscará nuevamente. Por motivos de compatibilidad,
esto también se aplica a las plataformas que admiten nombres de archivos largos.
Tenga en cuenta que la búsqueda anterior se realiza en cada directorio de la ruta de búsqueda.
El bloque de programa contiene las declaraciones que se ejecutarán cuando se inicie el programa. Tenga en cuenta que
estas declaraciones no tienen por qué ser necesariamente las primeras declaraciones que se ejecutan: el código de
inicialización de las unidades también puede contener declaraciones que se ejecutan antes que el código del programa.
16,2 unidades
Una unidad contiene un conjunto de declaraciones, procedimientos y funciones que pueden ser utilizadas por un
programa u otra unidad. La sintaxis de una unidad es la siguiente:
Unidades
.
unidad de encabezado de unidad ?identi cador de unidad ;
directivas de pistas
247
Machine Translated by Google
Como puede verse en el diagrama de sintaxis, una unidad siempre consta de una interfaz y una parte de
implementación. Opcionalmente, hay un bloque de inicialización y un bloque de finalización, que contienen
código que se ejecutará cuando se inicie el programa y cuando se detenga, respectivamente.
Tanto la parte de la interfaz como la parte de la implementación pueden estar vacías, pero se deben especificar las
palabras clave Interfaz e implementación. La siguiente es una unidad completamente válida;
unidad a;
interfaz
implementación
fin.
La parte de la interfaz declara todos los identificadores que deben exportarse desde la unidad. Pueden ser identificadores
de constantes, tipos o variables, así como declaraciones de identificadores de procedimientos o funciones. La parte de la
interfaz no puede contener código que se ejecuta: solo se permiten declaraciones. La siguiente es una parte de interfaz
válida:
unidad a;
interfaz
utiliza b;
Implementación
unidos unidos;
interfaz
248
Machine Translated by Google
implementación
fin.
El bloque de inicialización se utiliza para inicializar determinadas variables o ejecutar código necesario para el
correcto funcionamiento de la unidad. Las partes de inicialización de las unidades se ejecutan en el orden en que
el compilador cargó las unidades al compilar un programa. Se ejecutan antes de que se ejecute la primera
declaración del programa.
La parte de finalización de las unidades se ejecuta en el orden inverso a la ejecución de inicialización. Se utilizan,
por ejemplo, para limpiar cualquier recurso asignado en la parte de inicialización de la unidad o durante la vida útil
del programa. La parte de finalización siempre se ejecuta en el caso de una terminación normal del programa: ya
sea porque se alcanza el final en el código del programa o porque se ejecutó una instrucción de detención en algún
lugar.
En caso de que el programa se detenga durante la ejecución de los bloques de inicialización de una de las
unidades, sólo se finalizarán las unidades que ya estaban inicializadas. Tenga en cuenta que, a diferencia de
Delphi, en Free Pascal puede haber un bloque de finalización sin un bloque de inicialización. Eso significa que lo
siguiente se compilará en Free Pascal, pero no en Delphi.
Finalización
Unidad de limpieza;
fin.
Una sección de inicialización por sí sola (es decir, sin finalización) puede simplemente reemplazarse por un bloque de
instrucciones. Es decir, lo siguiente:
Inicialización
Unidad de inicialización;
fin.
es completamente equivalente a
Comenzar InicializarUnidad;
fin.
Como se puede ver en el diagrama de sintaxis de una unidad, el nombre de una unidad puede contener puntos. Esto significa que las
unidades se pueden organizar en espacios de nombres.
unidad ab;
249
Machine Translated by Google
interfaz
implementación
comenzar
Resultado:=1;
fin;
fin.
programa d;
utiliza ab;
comenzar Writeln(c);
fin.
Al resolver símbolos, los alcances de las unidades siempre tienen prioridad sobre los símbolos dentro de las unidades.
unidad mi unidad;
interfaz
era
registro de la prueba
a: entero largo;
fin;
implementación
inicialización
prueba.a:=2; fin.
unidad [Link];
interfaz
era
a: entero largo;
implementación
inicialización
250
Machine Translated by Google
a:=1;
fin.
usos
miunidad, [Link];
Entonces imprimirá:
[Link].a: 1
usos
[Link], miunidad;
también imprimirá
[Link].a: 1
usos
[Link], miunidad;
comenzar
Writeln('a : ',a);
fin.
imprimirá:
una: 1
De manera similar, el siguiente programa resolverá test.a en la variable test.a en la unidad myunit:
usos
[Link], miunidad;
imprimirá
prueba.a: 2
251
Machine Translated by Google
Cuando un programa usa una unidad (digamos unidadA) y esta unidad usa una segunda unidad, digamos unidadB, entonces el
programa depende indirectamente también de la unidadB. Esto significa que el compilador debe tener acceso a la unidad B
cuando intenta compilar el programa. Si la unidad no está presente en el momento de la compilación, se produce un error.
Tenga en cuenta que los identificadores de una unidad de la que depende indirectamente un programa no son accesibles
para el programa. Para tener acceso a los identificadores de una unidad, la unidad debe estar en la cláusula de usos del
programa o unidad donde se necesitan los identificadores.
Las unidades pueden ser mutuamente dependientes, es decir, pueden hacer referencia entre sí en sus cláusulas de uso.
Esto está permitido, siempre que al menos una de las referencias esté en la sección de implementación de la unidad.
Esto también se aplica a las unidades indirectas mutuamente dependientes.
Si es posible comenzar desde una cláusula de usos de interfaz de una unidad y regresar allí a través de cláusulas
de usos de interfaces únicamente, entonces existe una dependencia circular de la unidad y el compilador generará
un error. Por ejemplo, no se permite lo siguiente:
Unidad UnidadA;
interfaz
Utiliza la Unidad B;
final de la implementación.
Unidad UnidadB
interfaz
Utiliza UnidadA;
final de la implementación.
Unidad UnidadA;
interfaz
Utiliza la Unidad B;
final de la implementación.
Unidad UnidadB
implementación
Utiliza UnidadA;
fin.
En general, es una mala idea tener interdependencias entre unidades, aunque sea sólo en las secciones de implementación.
16,5 bloques
Las unidades y programas están formados por bloques. Un bloque está formado por declaraciones de etiquetas, constantes,
tipos, variables y funciones o procedimientos. Los bloques se pueden anidar de determinadas maneras, es decir, una
declaración de procedimiento o función puede tener bloques en sí mismos. Un bloque se parece a lo siguiente:
Bloques
252
Machine Translated by Google
declaración parte 6
parte de declaración de
etiqueta parte de declaración
constante parte de declaración de cadena
de recursos parte de
declaración de tipo parte de
declaración de variable parte de declaración
de variable de subproceso parte de declaración de procedimiento/función
Las etiquetas que se pueden utilizar para identificar declaraciones en un bloque se declaran en la parte de declaración de
etiquetas de ese bloque. Cada etiqueta sólo puede identificar una declaración.
Las constantes que se van a utilizar sólo en un bloque deben declararse en la parte de declaración de constantes de ese
bloque.
Las variables que se van a utilizar sólo en un bloque deben declararse en la parte de declaración de variables de ese
bloque.
Los tipos que se van a utilizar sólo en un bloque deben declararse en la parte de declaración de tipo de ese bloque.
Por último, las funciones y procedimientos que se utilizarán en ese bloque se pueden declarar en la parte de
declaración de procedimiento/función.
Estas cuatro partes de la declaración se pueden mezclar, no hay ningún orden requerido aparte de que no se pueden
usar (o hacer referencia a) identificadores que aún no han sido declarados.
Después de las diferentes partes de la declaración viene la parte de la declaración. Contiene cualquier acción que el bloque deba
ejecutar. Todos los identificadores declarados antes de la parte de la declaración se pueden utilizar en esa parte de la declaración.
253
Machine Translated by Google
16.6 Alcance
Los identificadores son válidos desde el momento de su declaración hasta el final del bloque en el que se produjo la
declaración. El rango donde se conoce el identificador es el alcance del identificador. El alcance exacto de un identificador
depende de la forma en que se definió.
alcance de una variable declarada en la parte de declaración de un bloque es válido desde el punto de declaración hasta
el final del bloque. Si un bloque contiene un segundo bloque en el que se vuelve a declarar el identificador, dentro de este
bloque la segunda declaración será válida. Al salir del bloque interior, la primera declaración vuelve a ser válida. Considere
el siguiente ejemplo:
fin.
En este ejemplo, dentro del procedimiento, X denota una variable entera. Tiene su propio espacio de almacenamiento,
independiente de la variable X fuera del procedimiento.
identificadores de campo dentro de una definición de registro son válidos en los siguientes lugares:
3. Identificadores dentro de una declaración With que opera en una variable del tipo de registro dado.
identificador de componente (uno de los elementos en la lista de componentes de la clase) es válido en los siguientes lugares:
2. En todos los tipos descendientes de esta clase, a menos que esté en la parte privada de la declaración de clase.
4. En una declaración With que opera en una variable de la definición de clase dada.
254
Machine Translated by Google
Todos los identificadores en la parte de interfaz de una unidad son válidos desde el momento de la declaración hasta el
final de la unidad. Además, los identificadores son conocidos en programas o unidades que tienen la unidad en su cláusula
de usos.
Los identificadores de unidades indirectamente dependientes no están disponibles. Los identificadores declarados en la parte de
implementación de una unidad son válidos desde el momento de la declaración hasta el final de la unidad.
La unidad del sistema se utiliza automáticamente en todas las unidades y programas. Por lo tanto, sus identificadores siempre
se conocen en cada programa, biblioteca o unidad de Pascal.
Las reglas del alcance de la unidad implican que se puede redefinir el identificador de una unidad. Para tener acceso
a un identificador de otra unidad que fue redeclarada en la unidad actual, preceda con el nombre de esa otra unidad,
como en el siguiente ejemplo:
unidad unidadA;
interfaz
Tipo
MiTipo = Real;
final de la implementación.
programa progresivo;
Utiliza UnidadA;
{ Redeclaración de MyType}
Escriba MiTipo = Entero; {Será un
Var A: Mi tipo; B: número entero}
[Link] {Será real} comienzo fin.
Esto es especialmente útil al volver a declarar los identificadores de la unidad del sistema.
16.7 Bibliotecas
Free Pascal admite la creación de bibliotecas dinámicas (DLL en Win32 y OS/2) mediante el uso de la palabra clave
Biblioteca.
Bibliotecas
De forma predeterminada, las funciones y procedimientos que se declaran e implementan en la biblioteca no están
disponibles para un programador que desee utilizar esta biblioteca.
Para que funciones o procedimientos estén disponibles desde la biblioteca, deben exportarse en una cláusula de
exportación:
255
Machine Translated by Google
Cláusula de exportación
En Win32, se puede agregar una cláusula de índice a una entrada de exportación. Una entrada de índice debe
ser un número positivo mayor o igual que 1 y menor que MaxInt.
Opcionalmente, una entrada de exportaciones puede tener un especificador de nombre. Si está presente, el especificador de nombre proporciona el
nombre exacto (distingue entre mayúsculas y minúsculas) mediante el cual se exportará la función desde la biblioteca.
Si ninguna de estas construcciones está presente, las funciones o procedimientos se exportan con los nombres exactos como se
especifica en la cláusula de exportaciones.
256
Machine Translated by Google
Capítulo 17
Excepciones
Las excepciones proporcionan una manera conveniente de programar errores y mecanismos de recuperación de errores, y están
estrechamente relacionadas con las clases. El soporte de excepciones se basa en tres construcciones:
Levantar declaraciones. Para plantear una excepción. Esto generalmente se hace para señalar una condición de error.
Sin embargo, también se puede utilizar para cancelar la ejecución y regresar inmediatamente a un punto conocido
en el ejecutable.
Pruebe... Excepto bloques. Estos bloques sirven para detectar excepciones generadas dentro del alcance del bloque y para
proporcionar código de recuperación de excepciones.
Prueba... Finalmente bloquea. Estos bloques sirven para forzar la ejecución del código independientemente de si
ocurre o no una excepción. Generalmente sirven para limpiar memoria o cerrar archivos en caso de que
ocurra una excepción. El compilador genera muchos bloques Try... Finalmente implícitos alrededor del
procedimiento, para forzar la coherencia de la memoria.
Observación Debido a que las excepciones usan clases, necesita uno de los modos Pascal de objeto para poder usarlas:
{$MODO OBJFPC}
{$MODO DELPHI}
elevar declaración
257
Machine Translated by Google
Esta declaración generará una excepción. Si se especifica, la instancia de excepción debe ser una instancia
inicializada de cualquier clase, que es el tipo de aumento. La dirección de excepción y el marco son opcionales.
Si no se especifican, el compilador proporcionará la dirección por sí mismo. Si se omite la instancia de excepción,
se vuelve a generar la excepción actual. Esta construcción sólo se puede utilizar en un bloque de manejo de
excepciones (ver más adelante).
Observación El control nunca regresa después de un bloque de excepción. El control se transfiere al primer intento...finalmente o
intento...excepto la declaración que se encuentra al desenrollar la pila. Si no se encuentra dicha declaración, la biblioteca
de tiempo de ejecución de Free Pascal generará un error de tiempo de ejecución 217 (consulte también la sección 17.5,
página 261). La dirección de excepción se imprimirá mediante las rutinas de manejo de excepciones predeterminadas.
A modo de ejemplo: la siguiente división comprueba si el denominador es cero y, de ser así, genera una excepción
de tipo EDivException
Si Y=0 entonces
Levante [Link] ('Se produciría una división por cero'); Resultado := X División Y; fin;
La clase Exception se define en la unidad Sysutils del rtl. (sección 17.5, página 261)
Observación Aunque la clase Exception se utiliza como clase base para las excepciones en todo el código, esto es solo un
acuerdo no escrito: la clase puede ser de cualquier tipo y no necesita ser descendiente de la clase Exception.
Por supuesto, la mayor parte del código depende del acuerdo no escrito de que una clase de excepción desciende
de Exception.
El siguiente código muestra cómo omitir una rutina de informe de errores de la pila que se muestra en el controlador
de excepciones:
prueba de procedimiento2;
comenzar
prueba2;
fin.
258
Machine Translated by Google
Excepción: error
$00000000004002D3 línea 15 de [Link] $00000000004002E6
línea 19 de [Link]
declaración de prueba lista de declaraciones de prueba excepto que finalizan los controladores de excepciones
manejadores de excepciones
6 controlador de excepciones
; otra lista de declaraciones
lista de declaraciones
Si no se genera ninguna excepción durante la ejecución de la lista de declaraciones, todas las declaraciones de
la lista se ejecutarán secuencialmente y el bloque de excepción se omitirá, transfiriendo el flujo del programa a
la declaración después del final.
Si ocurre una excepción durante la ejecución de la lista de declaraciones, el flujo del programa se transferirá al
bloque de excepción. Las declaraciones en la lista de declaraciones entre el lugar donde se generó la excepción
y el bloque de excepción se ignoran.
Si ninguno de los controladores On coincide con el tipo de objeto de excepción, entonces se ejecuta la lista de
instrucciones después de else. Si no se encuentra dicha lista, la excepción se vuelve a generar automáticamente. Este
proceso permite anidar intentos... excepto bloques.
Si, por otro lado, se detectó la excepción, entonces el objeto de excepción se destruye al final del bloque de
manejo de excepciones, antes de que continúe el flujo del programa. La excepción se destruye mediante una
llamada al destructor Destroy del objeto.
Intentar
Z := DoDiv (X,Y);
Excepto
259
Machine Translated by Google
Si Y resulta ser cero, entonces el código de función DoDiv generará una excepción. Cuando esto sucede, el flujo
del programa se transfiere a la declaración excepto, donde el controlador de excepciones establecerá el valor de
Z en cero. Si no se genera ninguna excepción, el flujo del programa continúa más allá de la última declaración
final. Para permitir la recuperación de errores, se admite el bloque Intentar... Finalmente. Un bloque Try...Finally
garantiza que se garantice la ejecución de las declaraciones que siguen a la palabra clave Finalmente, incluso si
se produce una excepción.
Si no ocurre ninguna excepción dentro de la Lista de declaraciones, entonces el programa se ejecuta como si las palabras
clave Try, Finalmente y End no estuvieran presentes, a menos que se dé un comando de salida: un comando de salida
primero ejecuta todas las declaraciones en los bloques finalmente antes de salir realmente.
Sin embargo, si ocurre una excepción, el flujo del programa se transfiere inmediatamente desde el punto
donde se generó la excepción a la primera declaración de las declaraciones Finalmente.
Todas las declaraciones después de la palabra clave finalmente se ejecutarán y luego la excepción se volverá
a generar automáticamente. Se omiten todas las declaraciones entre el lugar donde se planteó la excepción y
la primera declaración de las declaraciones finales.
Como ejemplo considere la siguiente rutina:
Manejo de archivos...
Finalmente
cerrar (F); fin;
fin;
Si durante la ejecución del manejo del archivo ocurre una excepción, entonces el flujo del programa continuará
en la declaración de cierre (F), omitiendo cualquier operación de archivo que pueda seguir entre el lugar donde
se generó la excepción y la declaración de cierre. Si no se produce ninguna excepción, se ejecutarán todas las
operaciones del archivo y el archivo se cerrará al final.
Tenga en cuenta que una declaración de salida encerrada en un bloque try...finalmente seguirá ejecutando el bloque
finalmente. Reutilizando el ejemplo anterior:
260
Machine Translated by Google
El archivo seguirá cerrado, incluso si el procesamiento finaliza prematuramente mediante la instrucción Exit.
Es posible anidar bloques Try...Except con bloques Try...Finally. El flujo del programa se realizará de acuerdo con
un principio lifo (último en entrar, primero en salir): el código del último bloque Try...Except o Try...Finally encontrado
se ejecutará primero. Si la excepción no se detecta, o fue una declaración finalmente, el flujo del programa se
transferirá al penúltimo bloque, ad in nitum.
Si se produce una excepción y no hay ningún controlador de excepciones presente que maneje esta excepción,
se generará un error de tiempo de ejecución 217. Cuando se utiliza la unidad SysUtils, se instala un controlador
predeterminado que mostrará el mensaje del objeto de excepción y la dirección donde ocurrió la excepción,
después de lo cual el programa saldrá con una instrucción Detener.
La unidad sysutils contiene una gran cantidad de manejo de excepciones. Define la clase de excepción base,
Exception.
Y utiliza esta declaración para definir un buen número de excepciones, por ejemplo:
{excepciones matemáticas}
261
Machine Translated by Google
EIntError = clase(Excepción);
EDivByZero = clase(EIntError);
ERangeError = clase(EIntError);
EIntOverflow = clase(EIntError);
EMathError = clase(Excepción);
262
Machine Translated by Google
Capítulo 18
Usando ensamblador
Free Pascal admite el uso de ensamblador en el código, pero no de macros de ensamblador en línea. Para obtener
más información sobre la sintaxis del ensamblador específica del procesador y sus limitaciones, consulte la Guía del
programador.
...
Declaraciones;
...
Asamblea
Las instrucciones del ensamblador entre las palabras clave Asm y end se insertarán en el ensamblador
generado por el compilador. Los condicionales se pueden utilizar en código ensamblador, el compilador los
reconocerá y los tratará como cualquier otro condicional.
Los procedimientos y funciones del ensamblador se declaran mediante la directiva Assembler. Esto permite que el
generador de código realice una serie de optimizaciones de generación de código.
El generador de código no genera ningún marco de pila (código de entrada y salida de la rutina) si no
contiene variables locales ni parámetros. En el caso de funciones, los valores ordinales deben devolverse
en el acumulador. En el caso de los valores de coma flotante, éstos dependen del procesador de destino y
de las opciones de emulación.
263
Machine Translated by Google
Índice
Resumen, 83 Directivas
resumen, 76 Pista, 16
Dirección, 176 Despacho, 100
Alias, 225 DespachoStr, 101
Ansisting, 34, 39 Doble, 31
Matriz, 43, 213, 214
Constructor, 48 demás, 190, 192
264
Machine Translated by Google
ÍNDICE
265
Machine Translated by Google
ÍNDICE
PUnicodeChar, 39 constante, 39
Multibyte, 38
Subir, 257 Cadena de bytes sin procesar, 38
Cadena de bytes sin formato, 38 cuerda corta, 33
Leer, 103 Cadena de un solo byte, 32
verdadero, 31
Cadena Unicode, 38
Récord, 50 Cadena UTF8, 38
constante, 69 Cadena ancha, 39
Ayudantes de registro, Símbolos, 12
149 registros, Diagramas de sintaxis, 10
231 reintroducciones, 96 SYSV_ABI_CDecl, 231
Repetir, 202 SYSV_ABI_Default, 231
Palabras reservadas, 14
Delfos, 15 años Texto, 53
Modificadores, 15 luego, 192
Turbo Pascal, 14 Variables de hilo, 71
Cadena de recursos, 23 Threadvar, 71
Fichas, 12
llamada segura, Comentarios, 13
231 guardar registros, 231 Identificadores, 16
Alcance, 35, 46, 68, 75, 85, 87, 254 bloque, Números, 17
254 Palabras reservadas, 14
Clase, 254 Cuerdas, 19
récord, 254 Símbolos, 12
unidad, 255 intentos, 260, 261
sellado, 76 Tipo, 25
Yo, 80, 97 Compatibilidad, 28, 47, 64, 132, 174, 176,
conjunto, 53 210
Cuerda corta, 33 Genéricos, 123, 124
Sencillo, 31 Ayudantes, 149
softfloat, 231 Restricciones, 128
especializados, 126 Especializado, 126
Declaraciones, 186 Tipo sobrecargas, 131
Ensamblador, 206, 263 Encasillado, 34, 35, 39, 174, 175
Tarea, 186 No alineado, 175
Caso, 190 Valor, 174
Compuesto, 189 variable, 175
Excepción, 205 tipos, 25
Para, 193, 195 Ansistring, 34
266
Machine Translated by Google
ÍNDICE
verdadero, 31
Récord, 50
Asistentes de registro, 149
Referencia contada, 34, 36, 38, 44, 46, 122
conjunto, 53
cuerda corta, 33
Cuerda, 32
estructurado, 41
Subrango, 30
Escriba ayudantes, 149
Cadena Unicode, 38
Cadena UTF8, 38
Variante, 61
Cuerda ancha, 39
Unicodestring, 38
unidades, 247,
255 usos, 246
Cadena UTF8, 38
Var, 66
varargs, 232
variable, 66
variables, 66
Inicializado, 22, 68, 70
Variante, 61
VectorLlamada, 231
virtuales, 79, 81, 95, 100
Visibilidad, 75, 85, 114
Privado, 75, 88
Protegido, 88
Público, 75, 88
Publicado, 88
Privado estricto, 88
Estrictamente protegido, 88
267