2011-09-22 19 views
9

veces queremos un parámetro opcionalpasar una variable booleana opcional

function doSomething(foo:Integer; bar:TObject=nil) 
begin 
    if bar <> nil then // do something optional with bar 
    .... 
end 

¿Cómo hacer el equivalente a un valor lógico, que me permite diferenciar entre los dos valores booleanos y "ningún valor"?

function doSomething(foo:Integer; bar:Boolean=nil) // Won't compile 
begin 
    if bar <> nil then // do something optional with bar 
end 

Obviamente esto no se compilará ya que un booleano no puede ser nulo.

Básicamente, quiero un parámetro con tres estados posibles; verdadero, falso o "no especificado".

Respuesta

18

Esto se puede hacer de otra manera, el uso de la sobrecarga:

function doSomething(foo:Integer): Boolean; overload; 
begin 
    // do something without bar 
end; 

function doSomething(foo:Integer; bar:Boolean): Boolean; overload 
begin 
    // do something optional with bar 
end; 

A continuación, puede utilizarlo como doSomething(1), así como doSomething(1, true)

Usando su ejemplo, será equivalente a:

function doSomething(foo:Integer; bar:Boolean=nil): Boolean; // Won't compile 
begin 
    if bar <> nil then 
     // do something optional with bar 
    else 
     // do something without bar 
end; 
+0

+1 Esta es la mejor manera de lograr esto, IMO. Hay tres estados: 'doSomething (1)', 'doSomething (1, True)' y 'doSomething (1, False)'. –

+0

@Rudy, sí, pero solo porque el OP especificó la necesidad de trabajar con los parámetros * Boolean *. De lo contrario, creo que 3 rutas de código distintas se diferencian más eficientemente mediante el uso de una única variable de estado 3 (como el ejemplo ThreeStateBoolean de Greg Hewgill). – Sam

+0

@Rudy, lo que hace que esta sea la mejor respuesta es que cumple con la frase de OP "a veces queremos un parámetro opcional", no el hecho de que tres estados sean compatibles (porque ambas respuestas lo hacen). IMO. – Sam

4

Los valores del tipo booleano solo pueden ser Verdaderos o Falso. Puede definir su propio tipo que tiene tres estados: verdadero, falso, y no especificadas:

type ThreeStateBoolean = (True, False, Unspecified); 

O bien, puede pasar un punteroa un booleano:

type PBoolean = ^Boolean; 

function doSomething(bar: PBoolean = nil) 
begin 
    if bar <> nil then 
     // do something with bar^ 
end 

Pasar un puntero a una Boolean puede ser incómodo dependiendo de cómo lo llame.

+0

solo la primera parte de su respuesta es correcta, no puede declarar una función como esta en delphi, debe usar tipos predefinidos en los parámetros como 'PBoolean' así debe ser algo como 'procedure doSomething2 (bar: PBoolean = nil); ' – RRUZ

+0

Gracias, voy a cambiar eso. Ha pasado mucho tiempo desde que codifiqué en Pascal ... –

+3

tipo ThreeStateBoolean = (verdadero, falso, sin especificar); Sin embargo, tenga en cuenta que no puede usar True y False sin sobreescribir las declaraciones booleanas True y False del sistema. Esto dará errores de compilación siempre que use booleanos. TTrue, TFalse, Unspecified funcionará. – HMcG

0

O ... puede usar un Entero y convertirlo en Booleano según sea necesario. Use 0 para falso, 1 para verdadero y -1 para "nulo". De todos modos, lo corta, no puede usar una variable booleana para hacer lo que desee, necesitará otro tipo o una manipulación de parámetros como lo sugirió Lynxnake.

EDITAR: Una variación de esto, que es muy ineficiente, sería usar una variante. Con una variante, puede pasar valores Nulos (similar a cero de alguna manera, pero sigue siendo un valor) así como Sin asignar (también similar a cero, pero que representa "sin valor").

4

Otra opción (si tiene una versión relativamente moderna de Delphi) es implementar esto como un registro, con conversión implícita hacia y desde valores booleanos. Con la sobrecarga del operador, también puede habilitar la lógica de 3 estados. Esto es exagerado si todo lo que necesita es un uso ocasional, pero si necesita un sistema lógico de tres estados, funciona muy bien, sobre todo porque le puede asignar valores booleanos. Tenga cuidado con las asignaciones del pensamiento de 3 estados a 2 estados. El siguiente ejemplo asigna False a un booleano < - Asignación 'Troolean' donde el troolean es TNil, según un booleano no asignado en Delphi, pero hay complicaciones obvias.

Tenga en cuenta que esta no es una implementación completa o eficiente de ninguna manera, es solo una demostración para indicar lo que es posible. Incidentalmente, hay un buen Codevy vidoe por Jeroen Pluimer en tipos anulables. This pregunta proporciona un enlace.

unit UnitTroolean; 

interface 

type 

    TTroolean = record 
    private type 
     TThreeState = (TTrue = 1, TFalse = 0, TNil = -1); 

    var 
     fThreeState: TThreeState; 
    public 
     function AsString: string; 
     class operator Implicit(Value: boolean): TTroolean; 
     class operator Implicit(Value: TTroolean): boolean; 
     class operator Implicit(Value: TThreeState): TTroolean; 
     class operator Implicit(Value: TTroolean): TThreeState; 
     class operator LogicalAnd(Left, Right: TTroolean): TTroolean; 
     class operator LogicalOr(Left, Right: TTroolean): TTroolean; 
     class operator LogicalNot(Value: TTroolean): TTroolean; 
    end; 

implementation 

{ TRoolean } 

class operator TTroolean.Implicit(Value: boolean): TTroolean; 
begin 
    if Value then 
    result.fThreeState := TTrue 
    else 
    result.fThreeState := TFalse; 
end; 

class operator TTroolean.Implicit(Value: TTroolean): boolean; 
begin 
    if not(Value.fThreeState = TNil) then 
    result := (Value.fThreeState = TTrue) 
    else 
    result := false; 
end; 

class operator TTroolean.Implicit(Value: TThreeState): TTroolean; 
begin 
    result.fThreeState := Value; 
end; 

class operator TTroolean.Implicit(Value: TTroolean): TThreeState; 
begin 
    result := Value.fThreeState; 
end; 

class operator TTroolean.LogicalAnd(Left, Right: TTroolean): TTroolean; 
begin 
    if (Left.fThreeState = TNil) or (Right.fThreeState = TNil) then 
    result.fThreeState := TNil 
    else if ((Left.fThreeState = TTrue) and (Right.fThreeState = TTrue)) then 
    result.fThreeState := TTrue 
    else 
    result.fThreeState := TFalse; 
end; 

class operator TTroolean.LogicalNot(Value: TTroolean): TTroolean; 
begin 
    begin 
    case value.fThreeState of 
    TNil: result.fThreeState:= TNil; 
    TTrue: result.fThreeState:= TFalse; 
    TFalse: result.fThreeState:= TTrue 
    end; 
    end; 

end; 

class operator TTroolean.LogicalOr(Left, Right: TTroolean): TTroolean; 
begin 
    if (Left.fThreeState = TNil) or (Right.fThreeState = TNil) then 
    result.fThreeState := TNil 
    else if ((Left.fThreeState = TTrue) or (Right.fThreeState = TTrue)) then 
    result.fThreeState := TTrue 
    else 
    result.fThreeState := TFalse; 
end; 

function TTroolean.AsString: string; 
begin 
    case ord(fThreeState) of 
    1: 
     result := 'TTrue'; 
    0: 
     result := 'TFalse'; 
    -1: 
     result := 'TNil'; 
    end; 
end; 

end. 

Y un ejemplo de uso

program ThreeStateLogicTest; 

{$APPTYPE CONSOLE} 

uses 
    SysUtils, 
    UnitTroolean in 'UnitTroolean.pas'; 

var 
    ABoolean: boolean; 
    ATroolean, Anothertroolean, AThirdTroolean: TTroolean; 

begin 

    try 
    { TODO -oUser -cConsole Main : Insert code here } 

    write('Boolean:', BoolToStr(ABoolean, true), #10#13); 
    write(#10#13); 

    ATroolean := TFalse; 
    ABoolean := true; 
    ATroolean := ABoolean; 

    write('Boolean:', BoolToStr(ABoolean, true), #10#13); 
    write('Troolean:', ATroolean.AsString, #10#13); 
    write('Troolean as Boolean:', BoolToStr(ATroolean, true), #10#13); 
    write(#10#13); 

    ATroolean := TTrue; 
    ABoolean := false; 
    ATroolean := ABoolean; 

    write('Boolean:', BoolToStr(ABoolean, true), #10#13); 
    write('Troolean:', ATroolean.AsString, #10#13); 
    write('Troolean as Boolean:', BoolToStr(ATroolean, true), #10#13); 
    write(#10#13); 

    ABoolean := false; 
    ATroolean := TTrue; 
    ABoolean := ATroolean; 

    write('Boolean:', BoolToStr(ABoolean, true), #10#13); 
    write('Troolean:', ATroolean.AsString, #10#13); 
    write('Troolean as Boolean:', BoolToStr(ATroolean, true), #10#13); 
    write(#10#13); 

    ABoolean := true; 
    ATroolean := TFalse; 
    ABoolean := ATroolean; 

    write('Boolean:', BoolToStr(ABoolean, true), #10#13); 
    write('Troolean:', ATroolean.AsString, #10#13); 
    write('Troolean as Boolean:', BoolToStr(ATroolean, true), #10#13); 
    write(#10#13); 

    ABoolean := false; 
    ATroolean := Tnil; 
    ABoolean := ATroolean; 

    write('Boolean:', BoolToStr(ABoolean, true), #10#13); 
    write('Troolean:', ATroolean.AsString, #10#13); 
    write('Troolean as Boolean:', BoolToStr(ATroolean, true), #10#13); 
    write(#10#13); 

    ABoolean := true; 
    ATroolean := Tnil; 
    ABoolean := ATroolean; 

    write('Boolean:', BoolToStr(ABoolean, true), #10#13); 
    write('Troolean:', ATroolean.AsString, #10#13); 
    write('Troolean as Boolean:', BoolToStr(ATroolean, true), #10#13); 
    write(#10#13); 

    ATroolean := TTrue; 
    Anothertroolean := false; 

    AThirdTroolean := ATroolean and Anothertroolean; 
    write('And:', AThirdTroolean.AsString, #10#13); 

    AThirdTroolean := ATroolean or Anothertroolean; 
    write('Or:', AThirdTroolean.AsString, #10#13); 

    ATroolean := TNil; 
    Anothertroolean:= not ATroolean; 
    write('Not TNil:', Anothertroolean.AsString, #10#13); 

    ATroolean := TTrue; 
    Anothertroolean:= not ATroolean; 
    write('Not Ttrue:', Anothertroolean.AsString, #10#13); 

    ATroolean := Tfalse; 
    Anothertroolean:= not ATroolean; 
    write('Not Tfalse:', Anothertroolean.AsString, #10#13); 



    readln; 

    except 
    on E: Exception do 
     Writeln(E.ClassName, ': ', E.Message); 
    end; 

end. 
+0

Ni siquiera sabía que Delphi tenía sobrecarga de operador. – awmross

0

La manera más limpia es utilizar enumeration (también denominado enumerated type).

Esto ya se muestra en the Greg Hewgill's answer - pero incorrectamente, no debe usar false y true predefinidos como identificadores de enumeración¹. Y dentro de the HMcG's answer - pero dentro del tipo de contenedor (ejemplo más complejo). Sugiero escribir algo como: type TTrilean = (triFalse, triTrue, triNull);.

Alternativamente, puede utilizar el tipo existente TCheckBoxState del módulo StdCtrls, si no le importa llevar módulos VCL a su proyecto.


Además, puede escribir funciones de contenedor por the Serhii Kheilyk's answer:

procedure DoSomething(Foo: Integer; Bar: TTrilean); overload; 
begin 
    … //main code 
end; 

procedure DoSomething(Foo: Integer; Bar: Boolean); overload; 
const 
    Trileans: array[Boolean] of TTrilean = (triFalse, triTrue); 
begin 
    DoSomething(Foo, Trileans[Bar]); 
end; 

procedure DoSomething(Foo: Integer); overload; 
begin 
    DoSomething(Foo, triNull); 
end; 

Usted puede incluso hacer la primera privada y las dos últimas pública, si así lo desea.


Notas: 1.
creo (no estoy seguro), formalmente puede escribir type TMyType = (False, True, Unspecified);, como False y True son palabras que no están reservados. Pero esto interrumpirá su acceso al original False y True de tipo Boolean (después de eso tendrá que referirlos como y System.True). Y eso no es compatible con el compilador de terceros (por ejemplo, FPC no lo permite).

Cuestiones relacionadas