Funciones varias del lenguaje

Continuamos con la introducción al lenguaje Pascal Moderno.

Esta entrada es una traducción al español, del texto original escrito por Michalis Kamburelis.

Rutinas locales (anidadas)

Dentro de una rutina más grande (función, procedimiento, método) puede definir una rutina auxiliar.

La rutina local puede acceder libremente (leer y escribir) a todos los parámetros de un padre y a todas las variables locales del padre que se declararon sobre él. Esto es muy poderoso. A menudo permite dividir rutinas largas en un par de pequeñas sin mucho esfuerzo (ya que no tiene que pasar toda la información necesaria en los parámetros). Tenga cuidado de no abusar de esta característica — si muchas funciones anidadas usan (e incluso cambian) la misma variable del padre, el código puede ser difícil de seguir.

Estos dos ejemplos son equivalentes:

Otra versión, donde dejamos que la rutina local Square acceda directamente a I:

Las rutinas locales pueden llegar a cualquier profundidad — lo que significa que puede definir una rutina local dentro de otra rutina local. Así que puedes volverte loco (pero no te vuelvas loco demasiado, o el código se volverá ilegible :).

Callbacks (también conocidos como eventos, también conocidos como punteros a funciones, también conocidos como variables de procedimiento)

Permiten llamar a una función de forma indirecta, a través de una variable. La variable se puede asignar en tiempo de ejecución para apuntar a cualquier función con tipos de parámetros coincidentes y tipos de devolución.

La devolución de llamada puede ser:

  • Normal, lo que significa que puede apuntar a cualquier rutina normal (no un método, no local).

Un método: declarar con el objeto al final.

Tenga en cuenta que no puede pasar procedimientos/funciones globales como métodos. Son incompatibles. Si tiene que proporcionar una devolución de llamada de objeto, pero no desea crear una instancia de clase ficticia, puede pasar los métodos de clase como métodos.

Desafortunadamente, debe escribir feo @TMyClass(nil).Add en lugar de solo @TMyClass.Add.

  • Una (posiblemente) rutina local: declare with está anidada al final, y asegúrese de usar la directiva {$modeswitch nestedprocvars} para el código. Estos van de la mano con las rutinas locales (anidadas).

Genéricos

Una poderosa característica de cualquier lenguaje moderno. La definición de algo (típicamente, de una clase) se puede parametrizar con otro tipo. El ejemplo más típico es cuando necesitas crear un contenedor (una lista, diccionario, árbol, gráfico…​): puedes definir una lista de tipo T, y luego especializarla para obtener instantáneamente una lista de enteros, una lista de cadenas , una lista de TMyRecord, etc.

Los genéricos en Pascal funcionan de forma muy similar a los genéricos en C++. Lo que significa que se «expanden» en el momento de la especialización, un poco como las macros (pero mucho más seguras que las macros; por ejemplo, los identificadores se resuelven en el momento de la definición genérica, no en la especialización, por lo que no puede «inyectar» nada inesperado). comportamiento al especializar el genérico). En efecto, esto significa que son muy rápidos (pueden optimizarse para cada tipo en particular) y funcionan con tipos de cualquier tamaño. Puede usar un tipo primitivo (entero, flotante), así como un registro, así como una clase al especializar un genérico.

Los genéricos no se limitan a las clases, también puede tener funciones y procedimientos genéricos:

Consulte también los Contenedores (listas, diccionarios) que usan genéricos sobre clases estándar importantes que usan genéricos.

Sobrecarga

Se permiten métodos (y funciones y procedimientos globales) con el mismo nombre, siempre que tengan parámetros diferentes. En tiempo de compilación, el compilador detecta cuál desea usar, sabiendo los parámetros que pasa.

De forma predeterminada, la sobrecarga utiliza el enfoque FPC, lo que significa que todos los métodos en un espacio de nombres dado (una clase o una unidad) son iguales y ocultan los otros métodos en espacios de nombres con menos prioridad. Por ejemplo, si define una clase con los métodos Foo(Integer) y Foo(string), y desciende de una clase con el método Foo(Float), entonces los usuarios de su nueva clase no podrán acceder al método Foo( Float) fácilmente (todavía pueden — si encasillan la clase a su tipo de antepasado). Para superar esto, utilice la palabra clave de sobrecarga.

Preprocesado

Puede usar directivas de preprocesador simples para:

  • compilación condicional (código dependiendo de la plataforma, o algunos modificadores personalizados)
  • incluir un archivo en otro
  • también puede utilizar macros sin parámetros.

Tenga en cuenta que no se permiten macros con parámetros. En general, debe evitar usar el preprocesador… a menos que esté realmente justificado. El procesamiento previo ocurre antes del análisis, lo que significa que puede «romper» la sintaxis normal del lenguaje Pascal. Esta es una característica poderosa, pero también algo sucia.

Los archivos de inclusión tienen comúnmente la extensión .inc y se usan para dos propósitos:

  • El archivo de inclusión solo puede contener otras directivas del compilador, que «configuran» su código fuente. Por ejemplo, podría crear un archivo myconfig.inc con estos contenidos:

Ahora puede incluir este archivo usando {$I myconfig.inc} en todas sus fuentes.

  • El otro uso común es dividir una unidad grande en muchos archivos, y al mismo tiempo mantenerla en una sola unidad en lo que respecta a las reglas del idioma. No abuse de esta técnica — su primer instinto debe ser dividir una sola unidad en múltiples unidades, no dividir una sola unidad en múltiples archivos de inclusión. Sin embargo, esta es una técnica útil.
    1. Permite evitar la «explosión» del número de unidades, al mismo tiempo que mantiene cortos los archivos de código fuente. Por ejemplo, puede ser mejor tener una sola unidad con «controles de interfaz de usuario de uso común» que crear una unidad para cada clase de control de interfaz de usuario, ya que este último enfoque haría que la cláusula típica de «usos» fuera larga (ya que un código de interfaz de usuario típico dependen de un par de clases de interfaz de usuario). Pero colocar todas estas clases de interfaz de usuario en un solo archivo myunit.pas lo convertiría en un archivo largo, difícil de navegar, por lo que dividirlo en varios archivos de inclusión puede tener sentido.
    2. Permite tener una interfaz de unidad multiplataforma con implementación dependiente de la plataforma fácilmente. Básicamente puedes hacer

A veces, esto es mejor que escribir un código largo con muchos {$ifdef UNIX}, {$ifdef MSWINDOWS} mezclados con código normal (declaraciones de variables, implementación de rutinas). El código es más legible de esta manera. Incluso puede usar esta técnica de manera más agresiva, usando la opción de línea de comandos -Fi de FPC para incluir algunos subdirectorios solo para plataformas específicas. Luego puede tener muchas versiones del archivo de inclusión {$I my platform_specific_implementation.inc} y simplemente incluirlas, permitiendo que el compilador encuentre la versión correcta.

Registros

Record es solo un contenedor para otras variables. Es como una clase mucho más simplificada: no hay herencia ni métodos virtuales. Es como una estructura en lenguajes tipo C

Si usa la directiva {$modeswitch advancedrecords}, los registros pueden tener métodos y especificadores de visibilidad. En general, las características del idioma que están disponibles para las clases y que no rompen el diseño de memoria predecible simple de un registro, son posibles.

En el Object Pascal moderno, su primer instinto debería ser diseñar una clase, no un registro, porque las clases están repletas de funciones útiles, como constructores y herencia.

Pero los registros siguen siendo muy útiles cuando necesita velocidad o un diseño de memoria predecible:

  • Los registros no tienen ningún constructor o destructor. Simplemente define una variable de un tipo de registro. Tiene contenidos indefinidos (basura de memoria) al principio (excepto los tipos administrados automáticamente, como cadenas; se garantiza que se inicializarán para estar vacíos y se finalizarán para liberar el recuento de referencias). Por lo tanto, debe tener más cuidado cuando se trata de registros, pero le brinda cierta ganancia de rendimiento.
  • Las matrices de registros son muy lineales en la memoria, por lo que son compatibles con la memoria caché.
  • El diseño de la memoria de los registros (tamaño, relleno entre campos) está claramente definido en algunas situaciones: cuando solicita el diseño C o cuando utiliza un registro empaquetado. Esto es útil:
    • para comunicarse con bibliotecas escritas en otros lenguajes de programación, cuando exponen una API basada en registros
    • para leer y escribir archivos binarios
    • para hacer trucos sucios de bajo nivel (como encasillar inseguro un tipo a otro, ser consciente de su representación de memoria).
  • Los registros también pueden tener partes de casos, que funcionan como uniones en lenguajes similares a C. Permiten tratar la misma pieza de memoria como de un tipo diferente, según sus necesidades. Como tal, esto permite una mayor eficiencia de la memoria en algunos casos. Y permite más trucos inseguros sucios y de bajo nivel 🙂

Objetos al estilo antiguo

En los viejos tiempos, Turbo Pascal introdujo otra sintaxis para la funcionalidad de clase, utilizando la palabra clave de objeto. Es algo así como una mezcla entre el concepto de un disco y una clase moderna.

  • Los objetos de estilo antiguo se pueden asignar/liberar, y durante esa operación puede llamar a su constructor/destructor.
  • Pero también pueden declararse y usarse simplemente, como registros. Un registro simple o un tipo de objeto no es una referencia (puntero) a algo, son simplemente los datos. Esto los hace cómodos para datos pequeños, donde la asignación de llamadas / gratis sería molesta.
  • Los objetos de estilo antiguo ofrecen herencia y métodos virtuales, aunque con pequeñas diferencias con las clases modernas. Tenga cuidado — sucederán cosas malas si intenta usar un objeto sin llamar a su constructor, y el objeto tiene métodos virtuales.

Se desaconseja utilizar los objetos de estilo antiguo en la mayoría de los casos. Las clases modernas proporcionan mucha más funcionalidad. Y cuando sea necesario, los registros (incluidos los registros avanzados) se pueden utilizar para el rendimiento. Estos conceptos suelen ser una mejor idea que los objetos de estilo antiguo.

Punteros

Puede crear un puntero a cualquier otro tipo. El puntero para escribir TMyRecord se declara como ^TMyRecord y, por convención, se llama PMyRecord. Este es un ejemplo tradicional de una lista enlazada de enteros usando registros:

Tenga en cuenta que la definición es recursiva (el tipo PMyRecord se define mediante el tipo TMyRecord, mientras que TMyRecord se define mediante PMyRecord). Se permite definir un tipo puntero a un tipo aún no definido, siempre que se resuelva dentro del mismo bloque de tipo.

Puede asignar y liberar punteros usando los métodos New/Dispose, o (más bajo nivel, no tipo seguro) métodos GetMem/FreeMem. Elimina la referencia del puntero (para acceder a las cosas señaladas por) agrega el operador ^ (por ejemplo, MyInteger := MyPointerToInteger^). Para realizar la operación inversa, que consiste en obtener un puntero de una variable existente, se antepone el operador @ (por ejemplo, MyPointerToInteger := @MyInteger).

También hay un tipo de puntero sin tipo, similar a void* en lenguajes tipo C. Es completamente inseguro y puede encasillarse en cualquier otro tipo de puntero.

Recuerde que una instancia de clase también es, de hecho, un puntero, aunque no requiere ningún operador ^ o @ para usarlo. Una lista enlazada usando clases es ciertamente posible, sería simplemente esto:

Sobrecarga de operadores

Puede anular el significado de muchos operadores de idiomas, por ejemplo, para permitir la adición y multiplicación de sus tipos personalizados. Como esto:

También puede anular operadores en clases. Dado que generalmente crea nuevas instancias de sus clases dentro de la función del operador, la persona que llama debe recordar liberar el resultado.

También puede anular operadores en registros. Esto suele ser más fácil que sobrecargarlos para las clases, ya que la persona que llama no tiene que lidiar con la gestión de la memoria.

Para registros, se recomienda usar {$modeswitch advancedrecords} y anular operadores como operadores de clase dentro del registro. Esto permite utilizar clases genéricas que dependen de la existencia de algún operador (como TFPGList, que depende de la disponibilidad del operador de igualdad) con dichos registros. De lo contrario, no se encontraría la definición «global» de un operador (no dentro del registro) (porque no está disponible en el código que implementa TFPGList), y no podría especializar una lista como special TFPGList.

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Este sitio esta protegido por reCAPTCHA y laPolítica de privacidady losTérminos del servicio de Googlese aplican.

El periodo de verificación de reCAPTCHA ha caducado. Por favor, recarga la página.