Sobrecarga de operadores

Qualidade: 

Estrela ativaEstrela ativaEstrela ativaEstrela ativaEstrela ativa
 

Uma característica poderosa que o Delphi possui é a sobrecarga de operdadores (Operator Overloading), a qual basicamente permite que objetos complexos e records possam ser usados como operandos simples em expressões como "A + B" ou "A * B", onde A e B são objetos complexos (ou records).

No Delphi 2006, onde o exemplo deste artigo foi construído, é possível fazer a sobrecarga de operadores em records na personalidade Win32 e em records ou classes na personalidade .NET. Como não tenho interessem em programação .NET vou apenas explicar a sobrecarga em records que ao meu ver já ajuda bastante. Se você quiser saber como fazer a sobrecarga de operadores em classes no .NET ou está usando uma versão mais recente do Delphi que permita a sobrecarga de operadores em classes na personalidade Win32, consulte a ajuda do Delphi, aliás, aconselho você a olhar a ajuda do Delphi de qualquer maneira, pois este artigo é apenas uma abordagem básica para expor esta funcionalidade pouco explorada por nós, desenvolvedores

Sobrecarregando...

A título de exemplo vamos criar um record simples, com alguns membros de tipos simples. Esse record poderia ser a representação de um registro de um arquivo qualquer que armazena valores a respeito de determinado assunto. Quaisquer tipos podem ser usados, incluindo outros tipos complexos, records e objetos, pois as regras do que deve ser feito quando da utilização de qualquer operador são definidas nos métodos conhecidos como class operators. Para uma lista completa de todos os class operators consulte a ajuda do Delphi buscando por "Operator Overloading". Chega de bla bla bla, vamos ao código. A implementação completa da unit encontra-se a seguir:

program SDO;

{$APPTYPE CONSOLE}

uses
  SysUtils, Windows, DateUtils, Classes;

type
  TRegistro = record
    ValorString: String;
    ValorInteiro: Integer;
    ValorDouble: Double;
    ValorCurrency: Currency;
    class operator Add(POperando1, POperando2: TRegistro): TRegistro;
    class operator Subtract(POperando1, POperando2: TRegistro): TRegistro;
    class operator Multiply(POperando1, POperando2: TRegistro): TRegistro;
    class operator Divide(POperando1, POperando2: TRegistro): TRegistro;
  end;

{ TExemplo }

function ColocaParenteses(POperando: String): String;
begin
  Result := POperando;

  if (Pos('+',Result) > 0)
  or (Pos('-',Result) > 0)
  or (Pos('*',Result) > 0)
  or (Pos('/',Result) > 0) then
  begin
    Result := '(' + Result + ')';
  end;
end;

class operator TRegistro.Add(POperando1, POperando2: TRegistro): TRegistro;
begin
  ZeroMemory (@Result,SizeOf(TRegistro));

  with Result do
  begin
    ValorString := ColocaParenteses(POperando1.ValorString) + ' + ' + ColocaParenteses(POperando2.ValorString);
    ValorInteiro := POperando1.ValorInteiro + POperando2.ValorInteiro;
    ValorDouble := POperando1.ValorDouble + POperando2.ValorDouble;
    ValorCurrency := POperando1.ValorCurrency + POperando2.ValorCurrency;
  end;
end;

class operator TRegistro.Divide(POperando1, POperando2: TRegistro): TRegistro;
begin
  ZeroMemory (@Result,SizeOf(TRegistro));

  with Result do
  begin
    ValorString := ColocaParenteses(POperando1.ValorString) + ' / ' + ColocaParenteses(POperando2.ValorString);
    ValorInteiro := POperando1.ValorInteiro div POperando2.ValorInteiro;
    ValorDouble := POperando1.ValorDouble / POperando2.ValorDouble;
    ValorCurrency := POperando1.ValorCurrency / POperando2.ValorCurrency;
  end;
end;

class operator TRegistro.Multiply(POperando1, POperando2: TRegistro): TRegistro;
begin
  ZeroMemory (@Result,SizeOf(TRegistro));

  with Result do
  begin
    ValorString := ColocaParenteses(POperando1.ValorString) + ' * ' + ColocaParenteses(POperando2.ValorString);
    ValorInteiro := POperando1.ValorInteiro * POperando2.ValorInteiro;
    ValorDouble := POperando1.ValorDouble * POperando2.ValorDouble;
    ValorCurrency := POperando1.ValorCurrency * POperando2.ValorCurrency;
  end;
end;

class operator TRegistro.Subtract(POperando1, POperando2: TRegistro): TRegistro;
begin
  ZeroMemory (@Result,SizeOf(TRegistro));

  with Result do
  begin
    ValorString := ColocaParenteses(POperando1.ValorString) + ' - ' + ColocaParenteses(POperando2.ValorString);
    ValorInteiro := POperando1.ValorInteiro - POperando2.ValorInteiro;
    ValorDouble := POperando1.ValorDouble - POperando2.ValorDouble;
    ValorCurrency := POperando1.ValorCurrency - POperando2.ValorCurrency;
  end;
end;

var
  Op1: TRegistro;
  Op2: TRegistro;
  OpR: TRegistro;
begin
  ZeroMemory(@Op1,SizeOf(TRegistro));
  ZeroMemory(@Op2,SizeOf(TRegistro));

  Op1.ValorString := 'Carlos';
  Op1.ValorInteiro := 25;
  Op1.ValorDouble := 23.8;
  Op1.ValorCurrency := 123.6789;

  WriteLn('Op1:');
  WriteLn('> ValorString = ', Op1.ValorString);
  WriteLn('> ValorInteiro = ', Op1.ValorInteiro);
  WriteLn('> ValorDouble = ', FormatFloat('0.##########',Op1.ValorDouble));
  WriteLn('> ValorCurrency = ', FormatFloat('0.##########',Op1.ValorCurrency));

  WriteLn;

  Op2.ValorString := 'Lara';
  Op2.ValorInteiro := 30;
  Op2.ValorDouble := 478.333;
  Op2.ValorCurrency := 17899.4788;

  WriteLn('Op2:');
  WriteLn('> ValorString = ', Op2.ValorString);
  WriteLn('> ValorInteiro = ', Op2.ValorInteiro);
  WriteLn('> ValorDouble = ', FormatFloat('0.##########',Op2.ValorDouble));
  WriteLn('> ValorCurrency = ', FormatFloat('0.##########',Op2.ValorCurrency));

  WriteLn;
  WriteLn('------------------------------------------------------------------');
  WriteLn;

  WriteLn('Op1 + Op2:');
  OpR := Op1 + Op2;
  WriteLn('> ValorString = ', OpR.ValorString);
  WriteLn('> ValorInteiro = ', OpR.ValorInteiro);
  WriteLn('> ValorDouble = ', FormatFloat('0.##########',OpR.ValorDouble));
  WriteLn('> ValorCurrency = ', FormatFloat('0.##########',OpR.ValorCurrency));

  WriteLn;

  WriteLn('Op1 * Op2:');
  OpR := Op1 * Op2;
  WriteLn('> ValorString = ', OpR.ValorString);
  WriteLn('> ValorInteiro = ', OpR.ValorInteiro);
  WriteLn('> ValorDouble = ', FormatFloat('0.##########',OpR.ValorDouble));
  WriteLn('> ValorCurrency = ', FormatFloat('0.##########',OpR.ValorCurrency));

  WriteLn;

  WriteLn('Op1 + Op2 - Op1:');
  OpR := Op1 + Op2 - Op1;
  WriteLn('> ValorString = ', OpR.ValorString);
  WriteLn('> ValorInteiro = ', OpR.ValorInteiro);
  WriteLn('> ValorDouble = ', FormatFloat('0.##########',OpR.ValorDouble));
  WriteLn('> ValorCurrency = ', FormatFloat('0.##########',OpR.ValorCurrency));

  WriteLn;

  WriteLn('Op2 + Op1 - Op2:');
  OpR := Op2 + Op1 - Op2;
  WriteLn('> ValorString = ', OpR.ValorString);
  WriteLn('> ValorInteiro = ', OpR.ValorInteiro);
  WriteLn('> ValorDouble = ', FormatFloat('0.##########',OpR.ValorDouble));
  WriteLn('> ValorCurrency = ', FormatFloat('0.##########',OpR.ValorCurrency));

  WriteLn;

  WriteLn('Op2 + Op1 * Op2:');
  OpR := Op2 + Op1 * Op2;
  WriteLn('> ValorString = ', OpR.ValorString);
  WriteLn('> ValorInteiro = ', OpR.ValorInteiro);
  WriteLn('> ValorDouble = ', FormatFloat('0.##########',OpR.ValorDouble));
  WriteLn('> ValorCurrency = ', FormatFloat('0.##########',OpR.ValorCurrency));

  WriteLn;

  WriteLn('(Op2 + Op1) * Op2:');
  OpR := (Op2 + Op1) * Op2;
  WriteLn('> ValorString = ', OpR.ValorString);
  WriteLn('> ValorInteiro = ', OpR.ValorInteiro);
  WriteLn('> ValorDouble = ', FormatFloat('0.##########',OpR.ValorDouble));
  WriteLn('> ValorCurrency = ', FormatFloat('0.##########',OpR.ValorCurrency));

  WriteLn;

  WriteLn('Op2 / (Op1 * Op2):');
  OpR := Op2 / (Op1 * Op2);
  WriteLn('> ValorString = ', OpR.ValorString);
  WriteLn('> ValorInteiro = ', OpR.ValorInteiro);
  WriteLn('> ValorDouble = ', FormatFloat('0.##########',OpR.ValorDouble));
  WriteLn('> ValorCurrency = ', FormatFloat('0.##########',OpR.ValorCurrency));

  WriteLn;

  WriteLn('Op2 / Op1 * (Op2 / Op2) + Op1 * (Op1 + Op2):');
  OpR := Op2 / Op1 * (Op2 / Op2) + Op1 * (Op1 + Op2);
  WriteLn('> ValorString = ', OpR.ValorString);
  WriteLn('> ValorInteiro = ', OpR.ValorInteiro);
  WriteLn('> ValorDouble = ', FormatFloat('0.##########',OpR.ValorDouble));
  WriteLn('> ValorCurrency = ', FormatFloat('0.##########',OpR.ValorCurrency));

  WriteLn;

  WriteLn('Op1 - Op1:');
  OpR := Op1 - Op1;
  WriteLn('> ValorString = ', OpR.ValorString);
  WriteLn('> ValorInteiro = ', OpR.ValorInteiro);
  WriteLn('> ValorDouble = ', FormatFloat('0.##########',OpR.ValorDouble));
  WriteLn('> ValorCurrency = ', FormatFloat('0.##########',OpR.ValorCurrency));

  WriteLn;

  WriteLn('Op2 - Op2:');
  OpR := Op2 - Op2;
  WriteLn('> ValorString = ', OpR.ValorString);
  WriteLn('> ValorInteiro = ', OpR.ValorInteiro);
  WriteLn('> ValorDouble = ', FormatFloat('0.##########',OpR.ValorDouble));
  WriteLn('> ValorCurrency = ', FormatFloat('0.##########',OpR.ValorCurrency));

  WriteLn;

  WriteLn('Op1 / Op1:');
  OpR := Op1 / Op1;
  WriteLn('> ValorString = ', OpR.ValorString);
  WriteLn('> ValorInteiro = ', OpR.ValorInteiro);
  WriteLn('> ValorDouble = ', FormatFloat('0.##########',OpR.ValorDouble));
  WriteLn('> ValorCurrency = ', FormatFloat('0.##########',OpR.ValorCurrency));

  WriteLn;

  WriteLn('Op2 / Op2:');
  OpR := Op2 / Op2;
  WriteLn('> ValorString = ', OpR.ValorString);
  WriteLn('> ValorInteiro = ', OpR.ValorInteiro);
  WriteLn('> ValorDouble = ', FormatFloat('0.##########',OpR.ValorDouble));
  WriteLn('> ValorCurrency = ', FormatFloat('0.##########',OpR.ValorCurrency));
  
  ReadLn;
end.

Na linha 9 declaramos o record TRegistro. Usei quatro membros de tipos simples de dados, mas poderiam ser quaisquer tipos, inclusive tipos complexos, como outros records ou mesmo classes, não importa. Abaixo da declaração destes membros está a declaração da sobrecarga dos operadores + (Add), - (Subtract), * (Multiply) e / (Divide) o que significa que poderemos usar os operadores "+", "-", "*" e "/" para operar em instâncias de TRegistro. Add, Subtract, Multiply e Divide são class operators.

Na linha 22 declaramos uma função utilitária que coloca parênteses em volta de um operando, caso este operando seja composto de dois outros operandos. Isso é apenas para que o resultado da concatenação do membro ValorString fique coeso com a operação realizada e nada interfere no resultado final do exemplo. Esta função, fará com que a soma Op1 + Op2 - Op1, por exemplo, seja traduzida como (Op1 + Op2) - Op1, provando que, neste caso, a soma foi realizada primeiro, ao passo que, ao fazer Op1 + (Op2 - Op1), o resultado da função vai ser exatamente este Op1 + (Op2 - Op1), porque forçamos a precedência usando parênteses. Aliás, esta é uma excelente forma de verificar a precedência de operadores. Fica isso como dica de aprendizado.

Na linha 35 temos a implementação da sobrecarga do operador "+". Toda vez que usarmos o operador "+" com operandos do tipo TRegistro, este método será executado. Os parâmetros deste método são os operandos sendo "somados". Aqui a regra para somar os dois records é simplesmente somar os membros numéricos destes records e concatenar o membro que é uma string com o separador "+".

Na linha 48 temos a implementação da sobrecarga do operador "/". Toda vez que usarmos o operador "/" com operandos do tipo TRegistro, este método será executado. Os parâmetros deste método são os operandos sendo "divididos". Aqui a regra para dividir os dois records é simplesmente dividir os membros numéricos destes records. Já o membro string será concatenado utilizando o caractere "/" como separador.

Na linha 61 temos a implementação da sobrecarga do operador "*". Toda vez que usarmos o operador "*" com operandos do tipo TRegistro, este método será executado. Os parâmetros deste método são os operandos sendo "multiplicados". Aqui a regra para multiplicar os dois records é simplesmente multiplicar os membros numéricos destes records e concatenar o membro que é uma string com o separador "*".

Na linha 74 temos a implementação da sobrecarga do operador "-". Toda vez que usarmos o operador "-" com operandos do tipo TRegistro, este método será executado. Os parâmetros deste método são os operandos sendo "subtraidos". Aqui a regra para subtrair os dois records é simplesmente subtrair os membros numéricos destes records e concatenar o membro que é uma string com o separador "-".

As linhas 92 a 117 inicializam e exibem duas instâncias de TRegistro (Op1 e Op2). Estas duas instâncias serão operadas neste exemplo

As linhas 123 a 137 operam em Op1 e Op2, somando-os e multiplicando-os. Os valores obtidos são bem óbvios. As operações ocorrem sempre atuando em dois operandos de cada vez, observando-se a precedência. Usar parênteses afeta a precedência e o valor final também muda. Alguns operadores, como "*" e "/" também afetam a precedência.

As linhas 141 a 155 operam 3 operandos, somando-os e subtraindo-os. Não há parênteses nas duas expressões apresentadas, logo as operações serão realizadas na ordem em que aparecem na expressão. O resultado final vai exibir parênteses a fim de explicitar como as operações matemáticas são realizadas. Já nas linhas 159 a 173 existem duas operações com 3 operandos. Na primeira (linha 159) existe uma multiplicação entre o segundo e o terceiro operandos e nenhum parêntese e na segunda (linha 168) existe a mesma expressão, mas desta vez há um parêntese em volta da operação entre o primeiro e o segundo operandos. Na primeira expressão a matemática ensina que as multiplicações acontecem antes das somas e subtrações, mesmo sem que hajam parênteses, logo, o resultado final da operação vai apresentar parênteses em volta da operação entre o segundo e o terceiro parâmetros. Na segunda expressão, por outro lado, nós forçamos a precedência de forma que a soma acontecesse primeiro e é exatamente isso que acontece, observando o resultado final da operação.

Tal como a soma e a subtração, as multiplicações e divisões são feitas na ordem em que aparecem. Nas linhas 177 a 182 mostramos uma expressão onde usamos parênteses para forçar a multiplicação a ser efetuada primeiro. Comprove novamente usando breakpoints nos métodos "Multiply" e "Divide".

As linhas 186 a 191, mostram uma operação mais complexa. O resultado final vai conter vários parênteses aninhados devido à forma como as operações são efetuadas. Use breakpoints em todos os métodos e entenda melhor

As linhas 195 a 227 são simples validadores que usam expressões de resultados óbvios ao se usarem operandos iguais, servindo apenas para comprovar o funcionamento da técnica de sobrecarga de operadores.

E daí?

Se você leu tudo até aqui, você é um guerreiro mesmo, mas talvez isso prove que meu artigo foi interessante, obrigado :) Você pode estar perguntando onde isso vai ser útil e a resposta a esta pergunta depende do quanto você compreendeu o conceito da sobrecarga de operadores. Eu por exemplo, jamais usei a técnica em absolutamente nada (na verdade eu a descobri há pouco tempo) e mesmo sem nunca tê-la usado eu enxergo sua utilidade, não para mim no momento, mas para pessoas que fazem uso extensivo de objetos e precisam eventualmente realizar operações entre seus membros.

e-max.it: your social media marketing partner
Ajude nosso site visitando nossos patrocinadores!

Temos 64 visitantes e Nenhum membro online nos últimos 10 minutos (6.4 visitantes por minuto).