Sprawdzenie poprawności wprowadzania danych

0

Witam!
Zrobiłem sobie program, lecz potrzebuję jeszcze sprawdzania poprawności wprowadzania danych. Proszę więc Was o jakieś rady jak to najlepiej zrobić. A drugi problem to chciałbym również,aby kolejne elementy wektora zawierającego rekordy były zapisywane do pliku każdy od nowej lini i najlepiej z jakimiś nagłówkami tak jak w procedurze Wyswietl. Nie mogę użyć writeln ponieważ jest plik typowany,więc jak można inaczej to zrobić? I co można by ewentualnie poprawić(zoptymalizować) w kodzie.
Oto kod:

Program P1;
{$mode objfpc}{$H+}
Uses    //Gotowe biblioteki
{$IFDEF UNIX}{$IFDEF UseCThreads} cthreads,
{$ENDIF}{$ENDIF}
Classes, SysUtils, CustApp,CRT
{ you can add units after this };

Type
TData=record
   Dzien:1..31;           
   Miesiac:string[15];  
   Rok:886..2014;          
   End;

TDaneKsiazek=record   
  Tytul:string[15];       
  Data_Druku:Tdata;      
  Typ:string[50];        
  End;                

Vec=array of TDaneKsiazek;  

Var               
  B_D:Vec;       
  L:byte;       
  Plik:File of TDaneKsiazek;

//==========================WCZYTYWANIE==================================
Procedure Wczytaj(var BD:Vec;var LL:byte);
Var            
  i:byte;        
Begin                
Writeln('Dla ilu ksiazek chcesz stworzyc baze?');
  Readln(LL);           
LL:=LL+1;               
SetLength(BD,LL);   
For i:=1 to LL-1 do
 With BD[i] do
  With Data_druku do
  Begin 
        Writeln('Podaj tytul ksiazki[',i,']');
      Readln(Tytul);    
//===================Data====================
        Writeln('Podaj dzien druku ksiazki[',i,']');
      Readln(Dzien);    
        Writeln('Podaj miesiac druku ksiazki[',i,']');
      Readln(Miesiac);  
        Writeln('Podaj rok druku ksiazki[',i,']');
      Readln(Rok);  
//============================================//
    Repeat  //Instrukcja pętli
        Writeln('Czy ksiazka [',i,'] jest podrecznikiem');
      Readln(Typ);  //Wczyta typ
      Case Typ of   //Instrukcja warunkowa
             'Tak','tak','TAK':Typ:='Podrecznik';
             'Nie','nie','NIE':Typ:='To nie jest podrecznik';
             End;   
        Until (Typ='Podrecznik') or (Typ='To nie jest podrecznik');
Writeln;
  End;
End;            

//==================SORTOWANIE WG.TYTUŁU=================================
Procedure Sortuj_T(var BD:Vec;LL:byte);
Var                
  i,j:byte;  
  tmp:TDaneKsiazek; 
Begin                  
Writeln('//Elementy posortowane wg.tytulu:');
  For i:=1 to LL-1 do                   
   For j:=1 to LL-1 do               
   If BD[i].Tytul<BD[j].Tytul then     
   Begin                
   tmp:=BD[i];              
   BD[i]:=BD[j];            
   BD[j]:=tmp;                          
   End;                                 
End;                                   

//=======================SORTOWANIE WG.ROKU DRUKU========================
Procedure Sortuj_R(var BD:Vec;LL:byte);
Var                
  i,j:byte;      
  tmp:TDaneKsiazek; 
Begin                  
 Writeln('//Elementy posortowane wg.roku druku:');
  For i:=1 to LL-1 do   
   For j:=1 to LL-1 do  
   With BD[i] do
    With Data_druku do
    If BD[i].Data_druku.Rok<BD[j].Data_druku.Rok then 
    Begin   
    tmp:=BD[i]; 
    BD[i]:=BD[j];
    BD[j]:=tmp; 
    End;    
End;       

//==========================WYŚWIETLANIE=================================
Procedure Wyswietl(BD:Vec;LL:byte); 
Var                           
  i:byte;                       
Begin                            
  For i:=1 to LL-1 do
   With BD[i] do         
    With Data_druku do
     Begin
    Write('Tytul:"',Tytul,'"  ');   
           Write('Data druku:',Dzien,' ',Miesiac,' ',Rok,' ');
    Write('Typ:',Typ,' ');  
    Writeln;           
      End;
End;    

//======================ZAPIS DO PLIKU====================================
Procedure Zapis(BD:Vec;LL:byte);
Var
  i:byte;
  Nazwa,Sciezka:string[255];
Begin
Writeln('Podaj sciezke zapisu pliku');
  Readln(sciezka);
Writeln(UTF8ToConsole('Podaj nazwe pliku'));
  Readln(nazwa);
Sciezka:=Sciezka+Nazwa+'.dbf';
Assign(Plik,Sciezka);
Rewrite(Plik);
For i:=1 to LL-1 do 
  Write(Plik,BD[i]);
Close(Plik);
End;

//======================PROGRAM GŁÓWNY====================================
BEGIN                  
  Wczytaj(B_D,L);   
  Clrscr;          
  Sortuj_T(B_D,L);  
  Wyswietl(B_D,L);  
  Zapis(B_D,L);         
  Sortuj_R(B_D,L);  
  Wyswietl(B_D,L);      
  Zapis(B_D,L);        
  Readln;           
END.                   
0

Samo walidowanie wprowadzanych danych można zrobić dość łatwo, pobierając dane z klawiatury zawsze jako łańcuchy znaków; Dopiero później, podczas konwersji wyjdzie, czy podane dane są prawidłowe, czy nie;

Weźmy np. podawaną z klawiatury wartość dla argumentu LL (gratuluję nazwy...) - zamiast podawać ją bezpośrednio do parametru, najpierw wczytaj ją do pomocniczego łańcucha znaków, a przez konwersję łańcucha na liczbę, np. za pomocą procedury Val czy funkcji TryStrToInt okaże się, czy użytkownik faktycznie podał liczbę, czy swoje imię; Dzięki temu program się nie wywali, a użytkownik będzie miał szansę wykazania się ponownie, bez restartu programu;

A drugi problem to chciałbym również,aby kolejne elementy wektora zawierającego rekordy były zapisywane do pliku każdy od nowej lini i najlepiej z jakimiś nagłówkami tak jak w procedurze Wyswietl.

Poczytaj więcej o plikach typowanych, bo one nie dają takiej możliwości; Plik typowany to nic innego jak plik amorficzny, tyle że poszczególne "paczki" danych mają zawsze ten sam rozmiar, stąd z poziomu kodu łatwo je odczytywać i zapisywać; Jeśli potrzebujesz takich kombinacji jak wymieniłeś - skorzystaj ze zwykłych plików tekstowych, w których będziesz miał swoje "linie"; Możesz też skorzystać z plików INI i klasy TINIFile, aby czytelniej ułożyć dane w pliku i łatwiej je odczytywać/zapisywać.


A dodatkowo:

Writeln(UTF8ToConsole('Podaj nazwe pliku'));
  Readln(nazwa);

nigdy nie rób w ten sposób, bo po co wartość podawać pod etykietą? Postaw ładnie dwukropek i podawaj wartość w tej samej linii:

Write(UTF8ToConsole('Podaj nazwe pliku: '));
ReadLn(nazwa);

Poza tym kod nie jest sformatowany, słowa piszesz z dużych liter, a przyjęło się je pisać samymi małymi, zmienne mają nic nie mówiące nazwy, ich nazwy i typy piszesz małymi literami, a nie zaczynając z dużej; No i używasz polskich identyfikatorów, zamiast wyłącznie angielskich, przez co kod źle się analizuje; Nie stosujesz procedury Inc do inkrementacji wartości zmiennych, pętle indeksujesz od 1 itd. itd.; Jeszcze długa droga przed Tobą.

1

Poprawność typów liczbowych, czy nie są na przykłąd stringami albo czy są w prawidłowym zakresie, najprościej sprawdzisz procedurą Val. Co do typów, w stylu czy to jest podręcznik, to ja bym wyświetlił monit z sugestią, że użytkownik ma podać T = Tak, a N = NieI robiąc takie sprawdzenie miał pewność, że wprowadzi to co potrzeba (wycinek z innego mojego kodu, może nieidalny, ale działa ok pod konsolą).

//...
function AnsiUpperCase(const S : string) : string;
var
  Len : integer;
begin
  Len := Length(S);
  SetString(Result, PChar(S), Len);
  if Len > 0 then
  begin
    CharUpperBuff(Pointer(Result), Len);
  end;
end;

//...

var
  ObfuscationAnswer : string;
//...
  repeat
    Write(' Generate file with obfuscation (Y/N): ');
    Readln(ObfuscationAnswer);
    if ObfuscationAnswer = '' then
    begin
      ObfuscationAnswer := #32;
    end;
    ObfuscationAnswer := AnsiUpperCase(ObfuscationAnswer);
  until ObfuscationAnswer[1] in ['Y', 'N'];

Natomiast co do nagłowka pliku z danymi to również nie problem. Zapisujesz go na początku. Najlepiej to używać funkcji WinAPI jak WriteFile oraz ReadFile. Można sobie łatwo zapisać co potrzeba. Sprawdzać późnej długość odczytanego string i to co zwrócono, masz pewność, że to Twoj plik. A wiadomo po zapisie czy odczycie pozycja w pliku ulegnie przesunięciu o odczytaną ilość bajtów. I lepiej wedle mnie posłużyć się funkcjami WinAPI, które spokojnie i zadziałają pod FPC z modułem Windows, w sekcji uses, niż konsturkcjami rodem z Turbo Pascala 7. Na przykład string możesz sobie zapisać tak:

SaveTextToFile(AFileName : string; InStr : string) : boolean;
var
  OutFile : THandle;
  BytesWritten : DWORD;
  OutFileSize : integer;
begin
  OutFile := CreateFile(PChar(AFileName), GENERIC_WRITE,
    FILE_SHARE_READ, nil, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
  Result := OutFile <> INVALID_HANDLE_VALUE;
  if Result then
  begin
    OutFileSize := Length(InStr);
    WriteFile(OutFile, InStr[1], OutFileSize, BytesWritten, nil);
    CloseHandle(OutFile);
  end;
end;

EDIT: @furious programming mnie ubiegł. Ale wspomniał też o plikach INI. To dobre rozwiązanie. Może przestarzałe. Ale dla prostej bazy poglądowej powinno faktycznie wystarczyć. Tylko należy pamiętać, że zapis należy dokonać do pełniej ścieżki, bo bez jje podania zapisze się do %WINDIR%, co się może nie powieśc przy problemach z UAC. Czyli najlepiej sobie wyekstraktować ścieżkę z ParamStr(0) i do niej zapisywać. Nagłowki wtedy specjalnie zbędne. Do operacji na plikach INi, również polecam WinAPI, bo od dawna nie babram się z klasą do tego, nawet w swoich projektach VCL. Przykłady kodów do operowania na plikach INI poniżej.

procedure IniFileWriteString(FileName, Section, Ident, Value : string);
begin
  WritePrivateProfileString(PChar(Section), PChar(Ident), PChar(Value), PChar(FileName));
end;

function IniFileReadString(FileName : string;
  const Section, Ident, Default : string) : string;
var
  Buffer : array[0..2047] of Char;
begin
  SetString(Result, Buffer, GetPrivateProfileString(PChar(Section),
    PChar(Ident), PChar(Default), Buffer, SizeOf(Buffer), PChar(FileName)));
end;

procedure IniFileEraseSection(FileName : string; const Section : string);
begin
  WritePrivateProfileString(PChar(Section), nil, nil, PChar(FileName));
end;

procedure IniFileDeleteKey(FileName : string; const Section, Ident : string);
begin
  WritePrivateProfileString(PChar(Section), PChar(Ident), nil, PChar(FileName));
end;
0

Dziękuję za wszelkie uwagi postaram się do nich zastosować. A co do nazw niektórych zmiennych nakazano mi używanie takich jak w poleceniu miałem np. tak było z L {Długość wektora} a w procedurze LL {W procedurach zmienne nazywam inaczej jak zmienne globalne, nie wiem czy to dobrze},ale postaram się jak najwięcej zmienić bo faktycznie te nazwy zmiennych nic nie mówią. Co do plików typowanych wiem,że nie mogę użyć writeln. Plik tekstowy niestety to nie może być {z takim było by chyba najłatwiej} dostałem polecenie zapisania tego jako bazy danych. Co do podawnia wartości pod etykietą już to poprawiłem. Za chwilę zabiorę się za formatowanie kodu poprawie na angielskie nazwy. Co do indeksowania to wiem,że powinno się indeksować od 0, i tak jak zrobiłem to nie potrzebne marnowanie pamięci, ale chciałem żeby było przy wyświetlaniu ładniej, żeby była instrukcja np.podaj tytuł książki 1-ej, a nie 0-ej {fizycznie takiej książki nie ma}, ale mimo wszystko poprawiłem już, żeby indeksowało od 0 wg.Pana zaleceń, bo z punktu widzenia programowania tak jest poprawniej. I wiem,że jeszcze przede mną długa droga dopiero zaczynam naukę programowania. I prosił bym również o wyjaśnienie tej inkrementacji tak bardziej łopatologicznie najlepiej na przykładzie, co to tak właściwie jest i jak się tego używa, bo nie wiem czy dobrze zrozumiałem, ale jest to tak jakby pętla tak ? Czy mam tego jakoś używać zamiast for czy jakoś dodatkowo? Zamiast for i:=0 to L ,mam zapisać Inc(i) tylko jak górną granicę{L} tu się ustawia?

0

A co do nazw niektórych zmiennych nakazano mi używanie takich jak w poleceniu miałem np. tak było z L {Długość wektora} a w procedurze LL {W procedurach zmienne nazywam inaczej jak zmienne globalne, nie wiem czy to dobrze}

No to ktoś nakazał Ci stosować złe praktyki; Jednoliterowe identyfikatory można używać, ale jedynie do prostych pętli - nigdzie indziej; Sam też takie stosuję, ale tylko i wyłącznie w prostych pętlach; Identyfikator ma oznajmiać do czego dany element służy, ewentualnie można poprawić nazewnictwo przez zastosowanie notacji węgierskiej, aby dodatkowo identyfikator informował o typie elementu (którą także stosuję);

Z drugiej strony parametry funkcji/procedur według szeroko przyjętych zasad powinny posiadać prefiks A, od argument, a np. pola klasy prefiks F, od field; Dzięki temu zarówno parametry, jak i pola będą mogły mieć takie same nazwy, jedynie z innym prefiksem; Potem przepisanie wartości z parametrów do pól klasy jest bajecznie czytelne, np.:

FFileName := AFileName;

Od razu wiadomo co jest czym; Polecam stosować taką notację, bo dzięki niej sam kod tłumaczy swoje przeznaczenie;

Plik tekstowy niestety to nie może być {z takim było by chyba najłatwiej} dostałem polecenie zapisania tego jako bazy danych.

No właśnie, jako bazy danych, a nie jako pliku typowanego; Bazę danych można zrobić z każdego formatu, bez względu na to, czy plik będzie tekstowy, typowany, amorficzny, INI, YAML czy XML - każdy z nich możesz użyć do zrobienia takiej prostej bazy;

I prosił bym również o wyjaśnienie tej inkrementacji tak bardziej łopatologicznie najlepiej na przykładzie, co to tak właściwie jest i jak się tego używa, bo nie wiem czy dobrze zrozumiałem, ale jest to tak jakby pętla tak ?

Inkremetacja to nic innego jak zwiększenie wartości np. zmiennej o jeden (lub więcej); Do tego celu zaleca się stosowanie procedury Inc bo jest szybsza, choć optymalizator i tak pewnie poprawia zwykłe instrukcje przypisania (o ile optymalizacja jest włączona);

Czy mam tego jakoś używać zamiast for czy jakoś dodatkowo? Zamiast for i:=0 to L ,mam zapisać Inc(i) tylko jak górną granicę{L} tu się ustawia?

Jeśli już koniecznie chcesz wykorzystać procedurę Inc, to nie na pętli For, bo i tak Ci się to nie uda; Jeśli kompilator nie wyrzuci błędów, to i tak ilość iteracji pętli ustalana jest jednorazowo; Więc nawet jeśli będziesz próbował zmienić wartość licznika pętli, to i tak nic to nie da; Skorzystaj z pętli While lub Repeat, jeśli chcesz ręcznie manipulować licznikiem, ale takie zabiegi stosuje się tam, gdzie to konieczne, a nie możliwe.

1 użytkowników online, w tym zalogowanych: 0, gości: 1, botów: 0