Logo

Marco Cantù
Pascal Esencial

Capítulo 6 Actualizar
Procedimientos y funciones

Otra idea importante enfatizada por Pascal es el concepto de rutina, básicamente una serie de instrucciones con un único nombre, que pueden ser activadas muchas veces utilizando el mismo. De esta manera, se evita repetir las mismas instrucciones una y otra vez, y teniendo una única versión del código le permite modificarlo con efecto sobre todo el programa. Desde este punto de vista, puede pensar en las rutinas como el mecanismo básico de encapsulamiento de código. Volveré a tratar de este asunto con un ejemplo después de introducir la sintaxis de las rutinas en Pascal.

Procedimientos y funciones en Pascal

En Pascal, una rutina puede asumir dos formas: un procedimiento y una función. En teoría, un procedimiento es una operación que se pide a la computadora que realice, y una función es un cálculo que devuelve un valor. Esta diferencia se enfatiza por el hecho de que una función tiene un resultado, un valor de salida, mientras que un procedimiento no. Ambos tipos de rutinas pueden tener múltiples parámetros, de tipos de datos dados.

En la práctica, sin embargo, la diferencia entre funciones y procedimientos es muy limitada: puede hacer una llamada a una función para realizar cierta tarea y luego saltarse el resultado (que podría ser un código de error opcional o algo similar) o puede hacer una llamada a un procedimiento que transmite un resultado dentro de sus parámetros (más sobre parámetros de referencia se comentará más tarde en este capítulo).

Aquí están las definiciones de un procedimiento y dos versiones de la misma función, usando una sintaxis ligeramente distinta :

procedure Hello;
begin
  ShowMessage ('Hello world!');
end;

function Double (Value: Integer) : Integer;
begin
  Double := Value * 2;
end;

// ó, como alternativa
function Double2 (Value: Integer) : Integer;
begin
  Result := Value * 2;
end;

El uso de Result en vez del nombre de la función para asignar el valor que devuelve una función se está haciendo bastante popular, y, en mi opinión, tiene a hacer el código más legible.

Una vez que estas rutinas han sido definidas, podemos hacer llamadas a ellas cuantas veces queramos. Al procedimiento se le hace una llamada para conseguir que realice su tarea, y se hace una llamada a la función para calcular un valor :

procedure TForm1.Button1Click (Sender: TObject);
begin
  Hello;
end;
 
procedure TForm1.Button2Click (Sender: TObject);
var
  X, Y: Integer;
begin
  X := Double (StrToInt (Edit1.Text));
  Y := Double (X);
  ShowMessage (IntToStr (Y));
end;

Nota: Por el momento, no se preocupe de la sintaxis de los dos procedimientos que aparecen arriba, que son, de hecho, métodos. Sencillamente, coloque dos botones en un formulario Delphi, haga clic en ellos durante el diseño, y el IDE de Delphi generará el código de apoyo adecuado: ahora sólo tiene que cumplimentar las líneas entre begin y end. Para compilar el código de arriba necesitará añadir un control de edición al formulario.

Ahora podemos volver al concepto de encapsulación de código que introduje antes. Cuando haga una llamada a la función Double, no necesita saber cuál es el algoritmo usado para implementarlo. Si más tarde encuentra un método mejor para doblar el valor de un número, puede cambiar el código asociado a la función, con facilidad, sin que se vea afectado el código de llamada a la función (¡aunque la ejecución será más rápida!). El mismo principio puede ser aplicado al procedimiento Hello: podemos modificar la salida del programa cambiando el código de este procedimiento, y el método Button2Click cambiará su efecto automáticamente. He aquí cómo podemos cambiar el código :

procedure Hello;
begin
  MessageDlg ('Hello world!', mtInformation, [mbOK]);
end;

Consejo: Cuando efectúa una llamada a una función o procedimiento Delphi, o a cualquier método VCL, debería recordar el número y tipo de los parámetros. El editor de Delphi le ayuda sugiriendo la lista de parámetros de una función o procedimiento con un hint emergente en cuanto usted introduce el nombre de la función o procedimiento, y abre paréntesis. Esta característica se llama Code Parameters (parámetros de código) y es parte de la tecnología Code Insight.

Parámetros de referencia

Las rutinas en Pascal permiten la transmisión de parámetros mediante valores y por referencias. Transmitir parámetros mediante valores es lo normal: el valor se copia a la pila (stack) y la rutina usa y maneja la copia, no el valor original.

Transmitir un parámetro por referencia significa que su valor no se copia en la pila del parámetro formal de la rutina (evitar una copia a menudo significa que el programa se ejecuta más rápido). En su lugar, el programa hace uso del valor original, también en el código de la rutina. Esto permite al procedimiento o función cambiar el valor del parámetro. La transmisión de parámetros por referencia se expresa con la palabra clave var.

Esta técnica está disponible en la mayoría de lenguajes de programación. No está presente en C, pero ha sido introducida en C++, donde se usa el símbolo & (transmisión por referencia). En Visual Basic, cada parámetro no especificado mediante ByVal es transmitido por referencia.

He aquí un ejemplo de transmisión de un parámetro por referencia mediante el uso de la palabra clave var:

Here is an example of passing a parameter by referene using the var keyword:

procedure DoubleTheValue (var Value: Integer);
begin
  Value := Value * 2;
end;

En este caso, el parámetro se usa tanto para transmitir un valor al procedimiento como para devolver un nuevo valor al código de llamada. Cuando se escribe ...

var
  X: Integer;
begin
  X := 10;
  DoubleTheValue (X);

... el valor de la variable X se convierte en 20, porque la función usa una referencia a la posición de memoria original de X, afectando a su valor inicial.

La transmisión de parámetros por referencia tiene sentido para tipos ordinales, para cadenas tradicionales y para registros (records) de gran tamaño. Los objetos Delphi, de hecho, son siempre transmitidos mediante valores, porque ellos mismos son referencias. Por esta razón, transmitir un objeto por referencia tendría poco sentido (aparted de casos muy especiales), porque consistiría en transmitir una referencia a otra.

Las cadenas largas de Delphi tienen un comportamiento ligeramente distinto: se comportan como referencias, pero si se cambia una de las variables de cadena que apuntan a la misma cadena en memoria, esto se copia antes de actualizarla. Una cadena larga transmitida como parámetro de valor se comporta como una referencia sólo en términos de uso de la memoria y velocidad de la operación. Pero si modifica el valor de la cadena, el valor original no se ve afectado. Por el contrario, si transmite la cadena larga por referencia, puede alterar el valor original.

Delphi 3 introdujo un nuevo tipo de parámetro, out. Un parámetro out no tiene valor inicial, y se usa sólo para devolver un valor. Estos parámetros deberían usarse sólo para procedimientos y funciones COM; en general, es mejor ceñirse a los parámetros var, más eficientes. Excepto por no tener valor inicial, los parámetros out se comportan como parámetros var.

Parámetros constantes

Como alternative a los parámetros de referencia, puede usar un parámetro const. Ya que no puede asignar un nuevo valor a un parámetro constante dentro de una rutina, el compilador puede optimizar la transmisión de parámetros. El compilador puede elegir un enfoque similar para parámetros de referencia (o una referencia const, términos de C++), pero el comportamiento seguirá siendo similar a los parámetros de valor, porque el valor original no se verá afectado por la rutina.

De hecho, si intenta compilar el siguiente código (trivial), Delphi producirá un error :

function DoubleTheValue (const Value: Integer): Integer;
begin
  Value := Value * 2;      // error de compilación
  Result := Value;
end;


Parámetros de vector abierto (Open Array)

Contrariamente a lo que sucede en C, una función o procedimiento Pascal siempre tiene un número fijo de parámetros. De cualquier forma, hay una manera de transmitir un número variable de parámetros a una rutina, utilizando un vector abierto.

La definición básica de un parámetro de vector abierto es el de un vector abierto con tipo. Esto significa que se indica el tipo de parámetro, pero no se sabe cuántos elementos de tal tipo va a tener el vector. Aquí aparece un ejemplo de una definición así:

function Sum (const A: array of Integer): Integer;
var
  I: Integer;
begin
  Result := 0;
  for I := Low(A) to High(A) do
    Result := Result + A[I];
end;

Usando High(A) podemos averiguar el tamaño del vector. Tenga en cuenta también el uso del valor de salida de la función, Result, que consiste en almacenar valores temporales. Puede hacer una llamada a esta función transmitiéndole un valor de expresiones enteras (Integer) :

X := Sum ([10, Y, 27*I]);

Dado un vector de enteros, de cualquier tamaño, puede transmitirlo directamente a una rutina que requiera un parámetro de vector abierto o, en su lugar, hacer una llamada a la función Slice para transmitir sólo una porción del vector (como lo indique su segundo parámetro). He aquí un ejemplo, donde el vector completo se transmite como parámetro:

var
  List: array [1..10] of Integer;
  X, I: Integer;
begin
  // initialize the array
  for I := Low (List) to High (List) do
    List [I] := I * 2;
  // call
  X := Sum (List);

Si desea transmitir sólo una porción del vector a la función Slice, simplemente efectúe la llamada de esta manera :

X := Sum (Slice (List, 5));

Podrá encontrar todos los fragmentos de código que aparecen en esta sección, dentro del ejemplo OpenArr (vea la Figura 6.1, más abajo, para el formulario)

Figura 6.1: El ejemplo OpenArr, cuando se pulsa el botón Partial Slice

Los vectores abiertos con tipo de Delphi 4 son totalmente compatibles con los vectores dinámicos (introducidos en Delphi 4 y discutidos en el Capíitulo 8). Los vectores dinámicos usan la misma sintaxis que los vectores dinámicos, con la diferencia de que puede usar una notación como array of Integer para declarar una variable, no sólo para transmitir un parámetro.

Parámetros de vector abierto, de tipo variable

Aparte de estos vectores abiertos con tipo, Delphi le permite definir vectores de tipo variable o sin tipo. Este tipo especial de vector tiene un número de valores indefinido, lo que puede ser más fácil de manejar a la hora de transmitir parámetros.

Técnicamente, la forma vectorial const le permite transmitir un vector con un número indefinido de elementos de distintos tipos a una rutina, de una sola vez. Por ejemplo, he aquí la definición de la función Format (veremos cómo usarla en el Chapter 7, que trata de las cadenas):

function Format (const Format: string;
  const Args: array of const): string;

El segundo parámetro es un vector abierto, que obtiene un número indefinido de valores. De hecho, puede hacer una llamada a tal función de las siguientes formas:

N := 20;
S := 'Total:';
Label1.Caption := Format ('Total: %d', [N]);
Label2.Caption := Format ('Int: %d, Float: %f', [N, 12.4]);
Label3.Caption := Format ('%s %d', [S, N * 2]);

Dése cuenta de que puede transmitir un parámetro bien como valor constante, el valor de una variable, o una expresión. Declarar una función de este tipo es sencillo, pero ¿cómo se codifica? ¿Cómo se sabe los tipos de los parámetros? Los valores de un parámetro de vector abierto de tipo variable son compatibles con los elementos de tipo TVarRec.

Nota: No confunda el registro TVarRec con el registro TVarData usado por el tipo Variant propiamente dicho. Estas dos estructuras tienen una finalidad distinta, y no son compatibles. Incluso la lista de tipos posibles es diferente, porque TVarRec puede contener tipos de datos de Delphi, mientras que TVarData puede contener tipos de datos OLE.

El registro TVarRec tiene la siguiente estructura :

type
  TVarRec = record
    case Byte of
      vtInteger:    (VInteger: Integer; VType: Byte);
      vtBoolean:    (VBoolean: Boolean);
      vtChar:       (VChar: Char);
      vtExtended:   (VExtended: PExtended);
      vtString:     (VString: PShortString);
      vtPointer:    (VPointer: Pointer);
      vtPChar:      (VPChar: PChar);
      vtObject:     (VObject: TObject);
      vtClass:      (VClass: TClass);
      vtWideChar:   (VWideChar: WideChar);
      vtPWideChar:  (VPWideChar: PWideChar);
      vtAnsiString: (VAnsiString: Pointer);
      vtCurrency:   (VCurrency: PCurrency);
      vtVariant:    (VVariant: PVariant);
      vtInterface:  (VInterface: Pointer);
  end;

Cada registro posible tiene el campo VType, aunque no es fácil de ver a primera vista, porque se declara sólo una vez, junto con los datos de tamaño entero, en sí (generalmente, una referencia o un puntero).

Usando esta información, podemos, de hecho, escribir una función capaz de operar sobre distintos tipos de datos. En él ejemplo de la función SumAll, quiero ser capaz de sumar valores de distintos tipos, transformando cadenas en enteros, caracteres en su valor ordinal correspondiente, y añadiendo 1 a los valores True Booleanos. El código está basado en una instrucción case, y es bastante simple, aunque tengamos que desreferenciar punteros a menudo :

function SumAll (const Args: array of const): Extended;
var
  I: Integer;
begin
  Result := 0;
  for I := Low(Args) to High (Args) do
    case Args [I].VType of
      vtInteger: Result :=
        Result + Args [I].VInteger;
      vtBoolean:
        if Args [I].VBoolean then
          Result := Result + 1;
      vtChar:
        Result := Result + Ord (Args [I].VChar);
      vtExtended:
        Result := Result + Args [I].VExtended^;
      vtString, vtAnsiString:
        Result := Result + StrToIntDef ((Args [I].VString^), 0);
      vtWideChar:
        Result := Result + Ord (Args [I].VWideChar);
      vtCurrency:
        Result := Result + Args [I].VCurrency^;
    end; // case
end;

He añadido este código al ejemplo OpenArr, que hace una llamada a la función SumAll cuando se pulsa un botón dado :

procedure TForm1.Button4Click(Sender: TObject);
var
  X: Extended;
  Y: Integer;
begin
  Y := 10;
  X := SumAll ([Y * Y, 'k', True, 10.34, '99999']);
  ShowMessage (Format (
    'SumAll ([Y*Y, ''k'', True, 10.34, ''99999'']) => %n', [X]));
end;

Puede ver la salida de esta llamada, y el formulario del ejemplo OpenArr, en la Figura 6.2.

Figura 6.2: El formulario form del ejemplo OpenArr, mostrando la ventana de mensajes cuando se pulsa el botón Untyped.


Convenios de llamada en Delphi

La versión de 32 bits de Delphi ha introducido un nuevo enfoque a la transmisión de parámetros, llamada fastcall: cuandoquiera que sea posible, hasta tres parámetros pueden ser transmitidos en registros CPU, haciendo la llamada a la función mucho más rápida. El convenio de llamada rápida (usado por defecto en Delphi 3) se indica mediante la palabra clave register.

El problema es que este es el convenio por defecto, y las funciones que lo usan no son compatibles con Windows: las funciones de la API de Win32 tienen que ser declaradas usando el convenio de llamada stdcall, una mezcla del convenio de llamada original de Pascal para la API Win16 y el convenio de llamada cdecl del lenguaje C.

No hay, en general, ninguna razón para no usar el nuevo convenio de llamada rápida, a no ser que haga llamadas externas Windows o defina funciones callback Windows. Veremos un ejemplo usando el convenio stdcall antes del fin de este capítulo. Podrá encontrar un sumario de los convenios de llamada de Delphi en la sección "Calling conventions" de la ayuda de Delphi.


¿Qué es un método?

Si ya ha trabajado con Delphi o ha leído los manuales, seguramente habrá oído hablar del término método. Un método es un tipo especial de función o procedimiento relacionado con un tipo de datos, una clase. En Delphi, cada vez que manejamos un evento, necesitamos definir un método, generalmente un procedimiento. En general, sin embargo, el término método se usa para indicar ambas funciones y procedimientos relacionados con una clase.

Ya hemos visto un cierto número de métodos en los ejemplos en este y los capítulos anteriores. Sigue un método vacío, añadido automáticamente por Delphi al código fuente de un formulario :

procedure TForm1.Button1Click(Sender: TObject);
begin
  {here goes your code}
end;


Declaraciones forward

Cuando necesite usar un identificador (de cualquier tipo), el compilador debe haber visto ya algún tipo de declaración para saber a qué apunta el identificador. Por esta razón, normalmente se proporciona una declaración completa antes de usar rutina alguna. De cualquier modo, hay casos en que esto no es posible. Si el procedimiento A hace una llamada al procedimiento B, y el procedimiento B hace lo propio con el A, cuando comience a escribir el código, tendrá que hacer una llamada a una rutina para la que el compilador aún no ha visto una declaración.

Si quiere declarar la existencia de un procedimiento o función con un cierto nombre y parámetros dados, sin proporcionar su código efectivo, puede escribir el procedimiento o función, seguido de la palabra clave forward :

procedure Hello; forward;

Más tarde, el código debería incluir una definición completa del procedimiento, pero este puede ser llamado incluso antes de ser totalmente definido. He aquí un ejemplo trivial, sólo para que se haga una idea :

procedure DoubleHello; forward;

procedure Hello;
begin
  if MessageDlg ('Do you want a double message?',
      mtConfirmation, [mbYes, mbNo], 0) = mrYes then
    DoubleHello
  else
    ShowMessage ('Hello');
end;

procedure DoubleHello;
begin
  Hello;
  Hello;
end;

Este enfoque permite escribir recursividad mutua: DoubleHello hace una llamada a Hello, pero, por su parte, Hello podría llamar a DoubleHello. Por supuesto, tiene que haber una condición que ponga fin a la recursión, para evitar el desbordamiento de la pila. Puede encontrar este código, con ligeros cambios, en el ejemplo DoubleH.

Aunque la declaración de un procedimiento forward no es muy común en Delphi, hay un caso similar, que es mucho más frecuente. Cuando declara un procedimiento o función en la porción de la interfaz, dentro de una unidad (lea más sobre unidades en el siguiente capítulo), se la considera una declaración forward, incluso aunque la palabra clave forward no esté presente. De hecho, no puede escribir el cuerpo de una rutina en la porción de la interfaz de una unidad. Al mismo tiempo, debe proporcionar en la misma unidad la implementación efectiva de cada rutina que haya declarado.

Esto también es cierto para la declaración de un método en el interior de un tipo de clase que haya sido generado automáticamente por Delphi (al añadir un evento a un formulario o uno de sus componentes). Los gestores de eventos declarados en el interior de una clase TForm son declaraciones forward: el código será proporcionado en la porción de implementación de la unidad. He aquí un extracto del código fuente de un ejemplo anterior, con la declaración del método Button1Click :

type
  TForm1 = class(TForm)
    ListBox1: TListBox;
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  end;


Tipos de procedimiento

Otra característica que hace único a Object Pascal es la presencia de tipos de procedimiento. Este tema es un aspecto realmente avanzado, que sólo unos pocos programadores en Delphi usarán regularmente. Sin embargo, ya que discutiremos temas relacionados en capítulos posteriores (en concreto, punteros de método, una técnica muy usada en Delphi), merece la pena echarle un vistazo rápido. Si es usted un programador en sus comienzos, puede saltarse esta sección por ahora, y volver a ella cuando se sienta preparado.

En Pascal, existe el concepto de tipo de procedimiento (que es similar al concepto de puntero de función del lenguaje C). La declaración de un tipo de procedimiento indica la lista de parámetros y, en el caso de las funciones, el tipo de salida. Por ejemplo, se puede declarar el tipo de procedimiento con un parámetro Integer transmitido por referencia como :

type
  IntProc = procedure (var Num: Integer);

Este tipo de procedimiento es compatible con cualquier rutina que tenga exactamente los mismos parámetros (o la misma firma de función (function signature), parafraseando el argot de C). Sigue un ejemplo de una rutina compatible:

procedure DoubleTheValue (var Value: Integer);
begin
  Value := Value * 2;
end;

Nota: En la versión de 16 bits de Delphi, las rutinas deben ser declaradas usando la directiva far, para poder utilizadas como valores efectivos de un tipo de procedimiento.

Los tipos de procedimiento pueden ser utilizados con dos fines diferentes: puede declarar variables de un tipo de procedimiento o transmitir un tipo de procedimiento --un puntero de función-- como parámetro a otra rutina. Dadas las declaraciones precedentes de tipo y procedimiento, puede escribir este código:

var
  IP: IntProc;
  X: Integer;
begin
  IP := DoubleTheValue;
  X := 5;
  IP (X);
end;

Este código tiene el mismo efecto que la versión siguiente, más breve :

var
  X: Integer;
begin
  X := 5;
  DoubleTheValue (X);
end;

La primera versión es claramente más compleja, así que ¿por qué deberíamos usarla? En algunos casos, ser capaz de decidir a qué función se debe hacer una llamada y efectuar ésta sólo más tarde, puede ser útil. Es posible construir un ejemplo complejo, usando este enfoque. Sin embargo, prefiero dejarle que explore uno bastante sencillo, que he llamado ProcType. Este ejemplo es más complejo que los que hemos visto hasta ahora, para hacer la situación un poco más realista.

Sencillamente, cree un proyecto vacío, y coloque en él dos casillas circulares (radio buttons) y un botón, como se muestra en la Figura 6.3. Este ejemplo se basa en dos procedimientos. Uno de ellos se usa para doblar el valor del parámetro. Este procedimiento es similar a la versión que ya mostré en esta sección. Un segundo procedimiento se usa para triplicar el valor, y, por tanto, se llama TripleTheValue:

Figura 6.3: El formulario del ejemplo ProcType.

procedure TripleTheValue (var Value: Integer);
begin
  Value := Value * 3;
  ShowMessage ('Value tripled: ' + IntToStr (Value));
end;

Ambos procedimientos muesrtran lo que está pasando, para hacernos saber que han sido llamados. Esta es una característica de depuración simple que puede usar para comprobar si y cuándo una cierta porción de código es ejecutada, en vez de añadir una interrupción para averiguarlo.

Cada vez que el usuario pulsa el botón Apply, uno de las dos procedimientos es ejecutado, dependiendo del estado de las casillas circulares. De hecho, cuando se tienen dos casillas circulares en un formulario, sólo una de ellas puede estar activada a la vez.
Este código podría haber sido implementado comprobando el valor de las casillas circulares dentro del código correspondiente al evento OnClick del botón Apply. Para mostrar el uso de tipos de procedimiento, en su lugar, he usado un enfoque más largo, pero interesante. Cada vez que el usuario hace clic en una de las casillas circulares, uno de los procedimientos se almacena en una variable:

procedure TForm1.DoubleRadioButtonClick(Sender: TObject);
begin
  IP := DoubleTheValue;
end;

Cuando el usuario hace clic en el botón, el procedimiento que hemos almacenado, se ejecuta :

procedure TForm1.ApplyButtonClick(Sender: TObject);
begin
  IP (X);
end;

Para permitir que tres funciones distintas accedan a las variable IP y X, tenemos que hacerlas visibles a todo el formulario; no pueden ser declaradas de forma local (dentro de uno de los métodos). Una solución a este problema es situar estas variables dentro de la declaración del formulario :

type
  TForm1 = class(TForm)
    ...
  private
    { Private declarations }
    IP: IntProc;
    X: Integer;
  end;

Veremos qué significa esto exactamente en el próximo capítulo, pero, por el momento, necesitará modificar el código generado por Delphi para el tipo de clase, como se indica arriba, y añadir la definición del tipo de procedimiento que mostré anteriormente. Para inicializar estas dos variables con valores adecuados, podemos manejar el evento OnCreate del formulario (seleccione este evento en el Object Inspector después de activar el formulario, o simplemente haga doble clic en el mismo). Le sugiero que consulte el listado para estudiar los detalles del código fuente de este ejemplo.

Puede ver un ejemplo práctico del uso de tipos de procedimiento en el Capítulo 9, en la sección "Funciones callback de Windows".


Sobrecarga de funciones

La idea de sobrecarga es sencilla: el compilador le permite definir dos funciones o procedimientos usando el mismo nombre, supuesto que los parámetros sean distintos. Al comprobar los parámetros, de hecho, el compilador puede determinar a cuál de las versiones de la rutina se refiere usted.

Considere esta sucesión de funciones extraída de la unidad Math de la VCL:

function Min (A,B: Integer): Integer; overload;
function Min (A,B: Int64): Int64; overload;
function Min (A,B: Single): Single; overload;
function Min (A,B: Double): Double; overload;
function Min (A,B: Extended): Extended; overload;

Cuando efectúe una llamada a Min (10, 20), el compilador determina fácilmente que está llamando a la primera función del grupo, con lo que el valor de salida será un entero (Integer).

Las reglas básicas son dos :

He aquí tres versiones sobrecargadas de un procedimiento ShowMsg que añadí al ejemplo OverDef (una aplicación que hace evidente la sobrecarga y parámetros por defecto):

procedure ShowMsg (str: string); overload;
begin
  MessageDlg (str, mtInformation, [mbOK], 0);
end;

procedure ShowMsg (FormatStr: string;
  Params: array of const); overload;
begin
  MessageDlg (Format (FormatStr, Params),
    mtInformation, [mbOK], 0);
end;

procedure ShowMsg (I: Integer; Str: string); overload;
begin
  ShowMsg (IntToStr (I) + ' ' + Str);
end;

Las tres funciones muestran una ventana de mensajes con una cadena, después de dar formato a la cadena de distintas maneras. Siguen las tres llamadas del programa :

ShowMsg ('Hello');
ShowMsg ('Total = %d.', [100]);
ShowMsg (10, 'MBytes');

Lo que me sorprendió de forma positiva es que la tecnología de parámetros de código de Delphi (Code Parameters) funciona muy bien con procedimientos y funciones sobrecargados. Al introducir el paréntesis abierto tras el nombre de la rutina, se enumeran todas las alternativas disponibles. Al introducir los parámetros, Delphi usa su tipo para determinar cuál de las alternativas están aún disponibles. En la Figura 6.4 se puede ver, tras comenzar a teclear una cadena constante, que Delphi muestra sólo las versiones compatibles (omitiendo la versión del procedimiento ShowMsg que tiene un entero como primer parámetro).

Figura 6.4: Las múltiples alternativas ofrecidas por Code Parameters para rutinas sobrecargadas son filtrados de acuerdo con los parámetros que ya están disponibles.

El hecho de que cada versión de una rutina sobrecargada debe ser marcada adecuadamente implica que no se puede sobrecargar una rutina existente de la misma unidad que no esté marcada con la palabra clave overload. (El mensaje de error que se obtiene cuando se intenta es "Previous declaration of '<name>' was not marked with the 'overload' directive" -- la declaración anterior de <nombre> no está marcada con la directiva 'overload'.) Sin embargo, puede sobrecargar una rutina que fue declarada originalmente en una unidad distinta. Esto se hace para conseguir compatibilidad con versiones anteriores de Delphi, que permitían que distintas unidades usasen el mismo nombre de rutina varias veces. Observe, de cualquier modo, que este caso especial no es una característica extra de la sobrecarga, sino una indicación de los problemas con que uno se puede encontrar.

Por ejemplo, puede añadir el siguiente código a una unidad :

procedure MessageDlg (str: string); overload;
begin
  Dialogs.MessageDlg (str, mtInformation, [mbOK], 0);
end;

Este código realmente no sobrecarga la rutina MessageDlg original. De hecho, si escribimos ...

MessageDlg ('Hello');

... obtendremos un simpático mensaje de error, que indicará que algunos de los parámetros faltan. La única manera para efectuar una llamada a la versión local en vez de a la de la VCL es aludir explícitamente a la unidad local -- nada más lejos de la idea de sobrecarga :

OverDefF.MessageDlg ('Hello');


newParámetros por defecto

Una característica nueva de Delphi 4 es que se puede dar un valor por defecto al parámetro de una función, y se puede hacer una llamada a la función con o sin dicho parámetro. Déjeme mostrarle un ejemplo. Podemos definir el siguiente encapsulamiento del método MessageBox del objeto global Application, que usa PChars en vez de cadenas, proveyendo dos parámetros por defecto:

procedure MessBox (Msg: string;
  Caption: string = 'Warning';
  Flags: LongInt = mb_OK or mb_IconHand);
begin
  Application.MessageBox (PChar (Msg),
    PChar (Caption), Flags);
end;

Con esta definición, podemos hacer una llamada al procedimiento en cualquiera de las siguientes formas :

MessBox ('Something wrong here!');
MessBox ('Something wrong here!', 'Attention');
MessBox ('Hello', 'Message', mb_OK);

En la Figura 6.5 puede verse que el Code Parameters de Delphi usan un estilo diferente para indicar los parámetros que tienen un valor por defecto, de manera que se puede determinar con facilidad qué parámetros pueden omitirse.

Figura 6.5: El Code Parameters marca con corchetes los parámetros que tienen valores por defecto; se pueden omitir éstos en la llamada.

Observe que Delphi no genera código especial para ser compatible con los parámetros por defecto, ni tampoco crea copias múltiples de las rutinas. Los parámetros que faltan son, sencillamente, añadidos por el compilador al código de la llamada.

Hay una restricción importante que afecta al uso de los parámetros por defecto : no se puede uno "saltar" los parámetros. Por ejemplo, no es posible transmitir el tercer parámetro a la función tras haber omitido el segundo :

MessBox ('Hello', mb_OK); // error  

Esta es la regla principal para los parámetros por defecto: en una llamada, sólo se puede omitir parámetros comenzando por el último. En otras palabras, para omitir un parámetro habrá de omitir también los siguientes.

Hay unas pocas reglas más para los parámetros por defecto :

La presencia simultánea de parámetros por defecto y sobrecargas puede causar bastantes problemas, ya que estas características podrían entrar en conflicto. Por ejemplo, si añado al ejemplo anterior la siguiente nueva versión del procedimiento ShowMsg ...

procedure ShowMsg (Str: string; I: Integer = 0); overload;
begin
  MessageDlg (Str + ': ' + IntToStr (I),
    mtInformation, [mbOK], 0);
end;

... entonces, el compilador no se quejará -- se trata de una definición legal. Sin embargo, la llamada ...

ShowMsg ('Hello');

... es señalada por el compilador como Llamada sobrecargada ambigua a 'ShowMsg' (Ambiguous overloaded call to 'ShowMsg'). Dése cuenta de que este error aparece en una línea de código que se había compilado correctamente antes de la nueva definición sobrecargada. En la práctica, no hay manera de efectuar una llamada al procedimiento ShowMsg con un solo parámetro de cadena, ya que el compilador no sabría si queremos llamar a la versión que tiene sólo el parámetro de cadena o a aquella que tiene dicho parámetro más un parámetro entero con un valor por defecto.
Cuando tiene tal duda, el compilador para y pide al programador que aclare sus intenciones.


Conclusión

Escribir procedimientos y funciones es un elemento clave de la programación, aunque en Delphi tenderá a escribir métodos -- procedimientos y funciones, conectados con clases y objetos.

En vez de pasar a discutir las características de orientación a objetos, los capítulos que siguen inmediatamente le darán algunos detalles sobre otros elementos de programación en Pascal, comenzando con las cadenas.


Capítulo siguiente: Manejo de cadenas (de caracteres)

© Copyright Marco Cantù, Wintech Italia Srl 1995-99
© Copyright de la traducción, Rafael Barranco-Droege, 2000