Jedna funkce pro více Array of (ten samý) Record – Delphi – Fórum – Programujte.com
 x   TIP: Přetáhni ikonu na hlavní panel pro připnutí webu

Jedna funkce pro více Array of (ten samý) Record – Delphi – Fórum – Programujte.comJedna funkce pro více Array of (ten samý) Record – Delphi – Fórum – Programujte.com

 

Navara0
Návštěvník
29. 2. 2020   #1
-
0
-

Zdravím, 

potřeboval bych "nakopnout" s následujícím problémem: mám několik Array of record, a pro zjednodušení (například vyhledání položky) v různých array bych chtěl použít jednu a tutéž funkci, která dostane array of record k prohledání jako parametr a případně vrátí nalezený index. Možná to popisuji dost kostrbatě, z kódu to bude možná pochopitelnější.

type
  TBitRegistru = record
    Hodnota: Boolean;
    Vyznam: ShortString;
  end;

  TZaznamRegistru = record
    Adresa: ShortString;
    ArrBity: array [0..7] of TBitRegistru;
  end;

  TRegistr = array of TZaznamRegistru;

var
  Registr_X: TRegistr;
  Registr_Y: TRegistr;
  Registr_R: TRegistr;
  Registr_S: TRegistr;´

function ZjistitRegistrKProhledani(oznaceni: ShortString): TRegistr;
begin
  Result := NIL;
  if oznaceni = 'x' then Result := Registr_X;
  if oznaceni = 'y' then Result := Registr_Y;
  if oznaceni = 's' then Result := Registr_S;
  if oznaceni = 'r' then Result := Registr_S;
end;

function VyhledatVRegistrech(OznRegistru: ShortString; atAdresa: ShortString): Integer;
var
  i: Integer;
  Prohledavany: TRegistr;
begin
  Result := -1;
  Prohledavany := ZjistitRegistrKProhledani(OznRegistru);
  if Prohledavany = nil then Exit;
  
  if Length(Prohedavany) = 0 then Exit;

  for i := 0 to High(Prohledavany) do
  begin
    if atAdresa = Prohledavany[i].Adresa then
    begin
      Result := i;
      Exit;
    end;
  end;
end;

Problém je v tom, že v samotné hledací funkci je obsah Prohledavany pokaždé NIL. Jak tedy  správně pracovat s různými polemi recordů pomocí jedné funkce? Někde jsem zaznamenal použití var před parametrem, hledání (a další funkce pro přidávání, mazání, editace...) v parametrem dodaném poli by pak vypadala takto:

function VyhledatVRegistrech(var Prohledavany: TRegistr; atAdresa: ShortString): Integer;
var
  i: Integer;
begin
  Result := -1;
  if Prohledavany = nil then Exit;
  
  if Length(Prohedavany) = 0 then Exit;

  for i := 0 to High(Prohledavany) do
  begin
    if atAdresa = Prohledavany[i].Adresa then
    begin
      Result := i;
      Exit;
    end;
  end;
end;

Ani toto ale nefunguje, když zavolám hledací funkci např. VyhledejVRegistrech(Registr_X, '0xFFF'), ačkoliv pole existuje, záznam v něm existuje, funkce skončí už na začátku na řádku if Prohledavany = nil...

Díky. N.

Nahlásit jako SPAM
IP: 46.135.38.–
gna
~ Anonymní uživatel
1891 příspěvků
29. 2. 2020   #2
-
0
-

Var u parametru znamená, že ten parametr bude odkaz na předávaná data, jinak je to jejich dočasná kopie (takže třeba změny hodnot se neprojeví v původním poli).

Tu funkci máš správně a pokud se ten parametr jeví jako nil, tak chybu máš někde jinde a opravdu tam jde nil.

Nahlásit jako SPAM
IP: 213.211.51.–
Navara0
Návštěvník
29. 2. 2020   #3
-
0
-

Pošlu tedy celý zdroják.

Trošku jsem to upravil, něco začalo fungovat, něco nefunguje dále. První problém je na začátku: pokud je pole prázdné, jeví se jako nil. Při tom je samozřejmě problém přidat první položku protože PotvrzeniDialoguPridatBajt skončí hned na začátku testem na nil. Když přidám nějakou první položku ručně (přímo SetLength(Registr_X, 1); Registr_X[0].Adresa = ..., tak pak začnou některé funkce, resp. většina funkcí fungovat. Prázdné pole ale není totéž co nil, ne? Má tam být odkaz na pole které má 0 položek a tedy to lze zjistit, případně s tím odkazovaným polem pracovat, např. přidat do něj tu první položku.

Druhý problém je pak nejspíše v tom, že když si deklaruji v nějaké funkci var ToArray: TArrRegistr takto:

procedure TRegistry.PotvrzeniDialoguPridatBajt;
var
  ToArray: TArrRegistr;
begin
  ...

  ZnakRegistruToArray(DlgRegistr.CB_Registr.Text, ToArray);
  if ToArray = NIL then Exit;
  
  ...
end;

tak když už to neskončí u prázdného pole na nil, bude se dále v celé funkci pracovat s místní kopií pole, a předpokládám že by mělo na konci být něco, co z ToArray přehraje data zpět do jednoho z Registrů, což mi celkově přijde jako špatný návrh, protože ve chvíli kdy pole bude skutečně rozsáhlé, budou se z něho odvozovat stejně velké kopie a zase skládat zpět což zabere čas i prostor, místo aby se pracovalo nad vybraným polem pouze pomocí odkazů.

celý zdroj zde:

unit U_Registry;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, Spin, Grids, Buttons, ExtCtrls, ComCtrls, StrUtils;

type
  TBitRegistru = record
    Hodnota: Boolean;
    Vyznam: ShortString;
  end;
  TZaznamRegistru = record
    AdresaDEC: Integer;
    ArrBity: array [0..7] of TBitRegistru;
  end;
  TArrRegistr = array of TZaznamRegistru;

type
  TRegistry = class

  private
    { Private declarations }
  public
    procedure CallPridatBajt;
    procedure PotvrzeniDialoguPridatBajt;
    function OveritPlatnouAdresuBajtuVDialogu: Boolean;
    function GetIndexByteZRegistru(
      AReg: ShortString; Adr: Integer; var FromArray: TArrRegistr): Integer;
    procedure PridatNovyByteZDialogu(
      AReg: ShortString; Adr: Integer; var ToArray: TArrRegistr);
    procedure ZnakRegistruToArray(AReg: ShortString; var ToArray: TArrRegistr);
    procedure VypsatRegistr(AReg: ShortString);
  end;

var
  Registry: TRegistry;

  Registr_X: TArrRegistr;
  Registr_Y: TArrRegistr;
  Registr_R: TArrRegistr;
  Registr_S: TArrRegistr;

implementation

uses
  U_Main,
  U_TestPrijmu,
  U_DlgRegistr,
  U_Spolecne,
  U_NavaraAE;

{ TRegistry }

function TRegistry.OveritPlatnouAdresuBajtuVDialogu: Boolean;
begin
  Result := True;

  if DlgRegistr.SE_Adresa.Text = ''
  then begin Result := False; Exit; end;

  if NvrConversions.JeCislo(DlgRegistr.SE_Adresa.Text) = False
  then begin Result := False; Exit; end;

  if DlgRegistr.SE_Adresa.Value < 0
  then begin Result := False; Exit; end;

  if DlgRegistr.SE_Adresa.Value > 65535
  then begin Result := False; Exit; end;
end;

procedure TRegistry.ZnakRegistruToArray(AReg: ShortString; var ToArray: TArrRegistr);
begin
  ToArray := nil;

  if AReg = 'X' then begin ToArray := Registr_X; Exit; end;
  if AReg = 'Y' then begin ToArray := Registr_Y; Exit; end;
  if AReg = 'R' then begin ToArray := Registr_R; Exit; end;
  if AReg = 'S' then begin ToArray := Registr_S; Exit; end;
end;

function TRegistry.GetIndexByteZRegistru(
  AReg: ShortString; Adr: Integer; var FromArray: TArrRegistr): Integer;
var
  AInt: Integer;
begin
  Result := -1;
  if FromArray = nil then Exit;

  if Length(FromArray) = 0 then Exit;
  for AInt := 0 to High(FromArray) do
  begin
    if FromArray[AInt].AdresaDEC = Adr then
    begin
      Result := AInt;
      Exit;
    end;
  end;
end;

procedure TRegistry.PridatNovyByteZDialogu(
  AReg: ShortString; Adr: Integer; var ToArray: TArrRegistr);
begin
  if ToArray = nil then Exit;
  SetLength(ToArray, Length(ToArray) +1);
  with ToArray[High(ToArray)] do
  begin
    AdresaDEC := DlgRegistr.SE_Adresa.Value;
  end;
end;

procedure TRegistry.PotvrzeniDialoguPridatBajt;
var
  ToArray: TArrRegistr;
begin
  if OveritPlatnouAdresuBajtuVDialogu = false then
  begin
    ShowMessage('Adresa byte není platná.');
    Exit;
  end;

  ZnakRegistruToArray(DlgRegistr.CB_Registr.Text, ToArray);
  if ToArray = NIL then Exit;

  if DlgRegistr.Caption = 'Přidat byte' then
  begin
    if GetIndexByteZRegistru(
      DlgRegistr.CB_Registr.Text,
      DlgRegistr.SE_Adresa.Value, ToArray) <> -1 then
    begin
      Showmessage('Byte s takovou adresou již existuje.');
      Exit;
    end;

    PridatNovyByteZDialogu(
      DlgRegistr.CB_Registr.Text,
      DlgRegistr.SE_Adresa.Value, ToArray);

    DlgRegistr.Close;
    Registry.VypsatRegistr(DlgRegistr.CB_Registr.Text);
  end;
end;

procedure TRegistry.CallPridatBajt;
begin
  DlgRegistr.Caption := 'Přidat byte';
  DlgRegistr.SE_Adresa.Text := '0';
  DlgRegistr.SetLabel_DecAdresaNaHEX;

  DlgRegistr.Showmodal;
end;

procedure TRegistry.VypsatRegistr(Areg: ShortString);
var
  AInt: Integer;
  ABit: Integer;
  LinkaSG: Integer;
  ArrReg: TArrRegistr;
begin
  NvrGridProc.ClearAllCells(Main.SG_Registry, True, True);
  Main.SG_Registry.RowCount := 2;
  Main.SetLabels_Registry;

  ZnakRegistruToArray(AReg, ArrReg);
  if ArrReg = nil then Exit;

  if Length(ArrReg) = 0 then Exit;

  for AInt := 0 to High(ArrReg) do
  begin
    for ABit := 0 to 7 do
    begin
      LinkaSG := NvrGridProc.GetFirstFreeLine(Main.SG_Registry, false, True);
      with Main.SG_Registry do
      begin
        Cells[0, LinkaSG] := Areg;
        Cells[1, LinkaSG] := Inttostr(ArrReg[AInt].AdresaDEC);
        Cells[2, LinkaSG] := ''; // Areg;
        Cells[3, LinkaSG] := Inttostr(ABit);
        Cells[4, LinkaSG] := BoolToStr(ArrReg[AInt].ArrBity[ABit].Hodnota);
        Cells[5, LinkaSG] := ArrReg[AInt].ArrBity[ABit].Vyznam;
      end;
    end;
  end;
end;

end.

Nahlásit jako SPAM
IP: 46.135.38.–
gna
~ Anonymní uživatel
1891 příspěvků
29. 2. 2020   #4
-
0
-

Já jsem v Pascalu/Delphi už roky nedělal, tak úplně nevím.

Ale myslím si, že prázdné dynamické pole právě je (nebo může být) nil. A pokud je parametr var, tak se už při překladu pohlídá, že to bude platná reference, takže nil bude jedině prázdné pole.

S tím kopírováním při výběru registru si nejsem jistý, ale asi to tak bude. Šlo by to obejít použitím ukazatele (explicitního, ne var parametru), nebo to pole zabalit do třídy, protože objekty si myslím, že se vždycky předávají v podstatě jako ukazatele. Pak se teda vrací to, že někde budeš muset ošetřit nil.

Víc nevím, ale jsou tady nějací Pascalisti, tak snad poradí.

Nahlásit jako SPAM
IP: 213.211.51.–
Sniper
~ Anonymní uživatel
215 příspěvků
1. 3. 2020   #5
-
0
-

Zdroják jsem moc nezkoumal, protože zdaleka není kompletní, ale pár poznatků...

  • třída TRegistry už v Delphi existuje, chtělo by to pojmenovat jinak (jen pro přehlednost)
  • prázdné dynamické pole je skutečně nil pointer, ale proboha nikdy to takhle netestuj, použij standardní funkci Length() - ta pro prázdné pole prostě vrátí 0
  • používej anglické názvy, v tomhle se nedá vyznat
  • pro porovnávání stringů nepoužívej operátor = ale standardní funkce k tomu určené (Ansi)CompareStr, (Ansi)CompareText, (Ansi)SameStr, (Ansi)SameText, ...
  • pokud chceš mít ty pole jako globální proměnné (což tedy nedoporučuji), tak ty metody co s nimi pracují mohou být třídní (class function, class procedure)
  • obecně mi to celé přijde špatně navržené - spravovat takový kód bude peklo, a hledat jakoukoliv chybu ještě větší, nadá se v tom vůbec vyznat - co to má vlastně dělat?

Nahlásit jako SPAM
IP: 92.240.176.–
Navara0
Návštěvník
1. 3. 2020   #6
-
0
-

třída TRegistry už v Delphi existuje...

Máš pravdu, sice nepředpokládám že bych s ní v tomto SW někdy chtěl pracovat, ale pro pořádek jsem přejmenoval.

prázdné dynamické pole je skutečně nil pointer, ale proboha nikdy to takhle netestuj, použij standardní funkci Length() - ta pro prázdné pole prostě vrátí 0

Ten test na nil tam nebyl proto, jestli je pole prázdné nebo ne, ale jestli bylo vůbec nějaké pro další práci zvoleno. Ta pole jsou 4 stejná neboť mají představovat 4 různé paměťové registry složené ze stejných buněk, a pokud nějakou chybou dalších funkcí bude z polí X, Y, S, R požádáno o pole A (které neexistuje), určovací funkce nenajde s čím má pracovat a tedy vrátí nil. Takový byl původní záměr, a vzhledem k tomu že prázdné pole je také nil, řeším to teď jiným způsobem.

obecně mi to celé přijde špatně navržené - spravovat takový kód bude peklo, a hledat jakoukoliv chybu ještě větší, nadá se v tom vůbec vyznat - co to má vlastně dělat?

Simulátor PLC. Přijímá po 232 od řídicího průmyslového PC stavy výstupních registrů a na žádost řídicího PC odesílá stavy registrů vstupních. Na tuto "vrstvu" pak navazují další moduly. Z větší části je program již funkční, komunikace, zpracování a předávání stavových telegramů bylo dnes úspěšně vyzkoušeno. 

Nahlásit jako SPAM
IP: 37.48.24.–
Sniper
~ Anonymní uživatel
215 příspěvků
2. 3. 2020   #7
-
+1
-
Zajímavé

Rozumím. Osobně bych nikdy nikde nepřiřazoval nil do dynamického pole. Sice je to možné, ale prostě bych to nedělal. Já když mám takovouhle funkci, tak ji vždy vytvářím podle následujícího vzoru:

Function Selection(SelectionParameter; out SelectedObject): Boolen;

SelectionParameter mi určuje co chci vybrat (v tvém případě string určující pole). Pokud funkce vrátí true, tak SelectedObject obsahuje vybranou věc, ale pokud vrátí false, tak je SelectedObject nedefinován, nesmím ho dále použít a nesmím o něm vůbec nic předpokládat. Být tebou, napsal bych to podobně.

Nahlásit jako SPAM
IP: 92.240.176.–
Zjistit počet nových příspěvků

Přidej příspěvek

Toto téma je starší jak čtvrt roku – přidej svůj příspěvek jen tehdy, máš-li k tématu opravdu co říct!

Ano, opravdu chci reagovat → zobrazí formulář pro přidání příspěvku

×Vložení zdrojáku

×Vložení obrázku

Vložit URL obrázku Vybrat obrázek na disku
Vlož URL adresu obrázku:
Klikni a vyber obrázek z počítače:

×Vložení videa

Aktuálně jsou podporována videa ze serverů YouTube, Vimeo a Dailymotion.
×
 
Podporujeme Gravatara.
Zadej URL adresu Avatara (40 x 40 px) nebo emailovou adresu pro použití Gravatara.
Email nikam neukládáme, po získání Gravatara je zahozen.
-
Pravidla pro psaní příspěvků, používej diakritiku. ENTER pro nový odstavec, SHIFT + ENTER pro nový řádek.
Sledovat nové příspěvky (pouze pro přihlášené)
Sleduj vlákno a v případě přidání nového příspěvku o tom budeš vědět mezi prvními.
Reaguješ na příspěvek:

Uživatelé prohlížející si toto vlákno

Uživatelé on-line: 0 registrovaných, 6 hostů

 

Hostujeme u Českého hostingu       ISSN 1801-1586       ⇡ Nahoru Webtea.cz logo © 20032024 Programujte.com
Zasadilo a pěstuje Webtea.cz, šéfredaktor Lukáš Churý