Προγραμμα Διαχείρισης Καταλόγου

Προδιαγραφές Προγράμματος

  1. Να κατασκευάσω ένα πρόγραμμα που διαχειρίζεται ένα αρχείο κειμένου.
  2. Να δείχνει στον χρήστη ένα μενού από το οποίο αυτός θα επιλέγει εργασίες.
  3. Το κείμενο θα αποτελείται από γραμμές σταθερού πλήθους χαρακτήρων Ν.
  4. Αν η γραμμή έχει λιγότερους χαρακτήρες, θα συμπληρώνω με κενά μέχρι το Ν.
  5. Σε κάθε γραμμή θα καταχωρώ πληροφορία π.χ. ονομα + τηλέφωνο(α).
  6. Το πρόγραμμα μπορεί να τυπώνει όλες τις γραμμές στην οθόνη.
  7. Μπορεί να τυπώνει μόνο την ν-στή γραμμή.
  8. Μπορεί να βρίσκει τις γραμμές που περιέχουν μία λέξη ή αριθμό που δίδω.
  9. Μπορεί να αφαιρεί μιά γραμμή από το αρχειο.
  10. Μπορεί να αλλάζει το περιεχόμενο της ν-στης γραμμής.
  11. Μπορεί να προσθέτει νέες γραμμές με δεδομένα.
  12. Να προσθέτει στο τέλος της γραμμής αυτόματα την ημερομηνία καταχώρησής της.

Τεχνικές Προδιαγραφές

Χτίζοντας από το γενικό προς την λεπτομέρεια

Το πρόγραμμα θα ξεκινά παρουσιάζοντας στον χρήστη το μενού επιλογής εργασίας.

[cmd-line-menu]

Η συνάρτηση main θα ορίζει την μεταβλητή τύπου LineFile με την οποία γίνεται όλη η δουλειά. Κατόπιν θα κατανέμει την κάθε επιλογή του χρήστη σε μία κατάλληλη συνάρτηση-μέλος της κλάσης η οποία θα αναλαμβάνει την διεκπεραίωση της επιλογής του χρήστη. Μετά την διεκπεραίωση της εργασίας, θα επανεμφανίζεται το μενού επιλογής. Τέλος της εργασίας του προγράμματος θα γίνεται με πληκτρολόγιση του γράμματος q. Οι άλλες επιλογές θα αντιστοιχούν σε άλλα γράμματα. Η μορφή της main να είναι η εξής:

int main(int argc, char* argv[])
{
   LineFile  lf("kat.txt");
   lf.TimeString();
   char ch(1);
   while( ch != 'q' )
   {
      ch = lf.MenuSelection();
      switch(ch)
      {
      case  'a':
         lf.AppendLine();
         break;
      case  'd':
         lf.DeleteLine();
         break;
      case  'p':
         lf.PrintLines();
         break;
      case  'r':
         lf.ReplaceLine();
         break;
      case  's':
         lf.ShowLine();
         break;
      case  'f':
         lf.NewWordSearch();
         break;
      case  'g':
         lf.OldWordSearch();
         break;
      case  'q':   //exit
         break;
      default:
         cout << "Error, please type only one of: a, d, p, r, s, f, g and q = quit" << endl;
         break;
      }
   }
}

Ανάλυση του κώδικα της main

LineFile  lf("kat.txt");
Ο κατασκευαστής ο οποίος ορίζει την μεταβλητή lf τύπου LineFile και δίδει στην fName μεταβλητή της κλάσης το όνομα του αρχείου kat.txt στο οποίο καταχωρούνται οι γραμμές.
lf.TimeString();
lf.MenuSelection();
lf.AppendLine();
lf.DeleteLine();
lf.PrintLines();
lf.ReplaceLine();
lf.ShowLine();
lf.NewWordSearch();
lf.OldWordSearch();
9 συναρτήσεις που κάνουν τις κύριες εργασίες του προγράμματος. Μετά τον ορισμό της μεταβλητής lf ξεκινά ένας κύκλος επαναλαμβανόμενος κατά τον οποίον ο χρήστης καθορίζει την εργασία τυπώνοντας έναν χαρακτήρα. Η παραπομπή από τον χαρακτήρα στην αντίστοιχη συνάρτηση που εκτελεί την εργασία γίνεται από την εντολή switch. Αυτή παίζει το ρόλο ενός διακόπτη-κατανεμητή. Τα ονόματα των συναρτήσεων είναι δηλωτικά των εργασιών που επιτελούν.
 
ch = lf.MenuSelection();
switch(ch)
{
  case  'a':
............
  case  'g':
      lf.OldWordSearch();
    break;
  case  'q':   //exit
    break;
  default:
      cout << "Error ...
    break;
}
 
Απόσπασμα κώδικα του κατανεμητή switch. Η συνάρτηση ch = lf.MenuSelection(); παίρνει από τον χρήστη έναν χαρακτήρα. Οταν ο χαρακτήρας είναι a τότε καλείται η συνάρτηση lf.AppendLine() με την οποία ο χρήστης τυπώνει και καταχωρεί στο αρχείο μιά νέα γραμμή δεδομένων. Ανάλογες εργασίες γίνονται όταν η μεταβλητή ch έχει τις τιμές d, p, r, s, f, p, q . Ειδικά με την τελευταία τιμή q ο χρήστης διακόπτει την λειτουργία του while και τερματίζει το πρόγραμμα. Το απόκομμα του switch που ξεκινά με το default εκτελείται στην περίπτωση που ο χρήστης δώσει έναν χαρακτήρα διαφορετικό από τους προηγουμένους 8. Σ αυτήν την περίπτωση τυπώνεται ένα μήνυμα λάθους.
Σε γενικές γραμμές αυτή είναι η μορφή της main ενός τυπικού προγράμματος που κάνει πλήρως καθορισμένες εργασίες. Υπάρχει ένας επαναλαμβανόμενος κύκλος (while) και ένας κατανεμητής μέσα σ αυτόν (switch). Το πρόγραμμα, με μιά κατάλληλη συνάρτηση (εδώ η ch = lf.MenuSelection();) παίρνει από τον χρήστη την επιλογή του και παραπέμπει μέσω του κατανεμητή στην κατάλληλη συνάρτηση μέσω της οποίας γίνεται η δουλειά. Οι συναρτήσεις που εμφανίζονται στην main μπορούν να θεωρηθούν σαν τις κύριες (τις πιο σπουδαίες) συναρτήσεις του προγράμματος. Κάθε μιά τους, γιά να εκτελέσει τις υποχρεώσεις της χρησιμοποιεί άλλες δευτερεύουσες συναρτήσεις, αυτές, με την σειρά τους χρησιμοποίούν άλλες κ.ο.κ. Δημιουργήται έτσι ένα σύνολο (γράφημα) συναρτήσεων που τελικά εξυπηρετούν την main και τις κύριες συναρτήσεις που αυτή περιέχει. Ας δούμε γιά παράδειγμα τι κάνει η πρώτη συνάρτηση που εμφανίζεται στην main, η ch = lf.MenuSelection();

Ανάλυση του κώδικα της MenuSelection

char   LineFile::MenuSelection()
{
   WriteMessage("<MENU>\n AddLine (a)  , DeleteLine (d) , PrintLines (p) , ReplaceLine (r)");
   WriteMessage(" ShowLine (s) , WordSearch (f) , AgainSearch (g) , Quit (q)");
   WriteMessage("</MENU>");
   cin.getline(line,MXCH); // eat all the line but use only  1st char
   if( GetLineLength() >10 )  // append !! *** user forgot to press a before !!
   {
      AppendLineEndFile();
   }
   return line[0];
}
Η συνάρτηση-μέλος της κλάσης LineFile χρησιμοποιεί μιά άλλη συνάρτηση-μέλος της κλάσης, την WriteMessage μέσω της οποίας γράφει (το παραπάνω) κείμενο στην κονσόλα οθόνης του χρήστη. Εδώ βλέπουμε ότι τυπώνει μερικές γραμμές που καθοδηγούν τον χρήστη στο τί ακριβώς νά κάνει. Συγκεκριμένα τον προτρέπουν να πληκτρολογήσει ένα από τα γράμματα a, d, p, r, s, f, g, q . Κατόπιν η συνάρτηση διαβάζει (μέσω της cin >> ch ;) την επιλογή του χρήστη και επιστρέφει τον χαρακτήρα που διάβασε.
Η κλάση cin μας είναι γνωστή. Χρησιμοποιήται γιά να πάρουμε από την κονσόλα κείμενο που πληκτρολόγησε ο χρήστης. Αντίστοιχα η κλάση cout χρησιμεύει γιά να εμφανίσουμε κείμενο στην κονσόλα. Οι δύο αυτές κλάσεις, όπως και πολλές άλλες, προσφέρονται έτοιμες από την C++ γιά τις ανάγκες μας. Μεταξύ άλλων η cin έχει την συνάρτηση μέλος getline(char* ... , Ν ) μέσω της οποίας διαβάζουμε μιά γραμμή Ν το πολυ χαρακτήρων που τελειώνει με \n (χαρακτήρας αλλαγής γραμμής). Εδώ MXCH συμβολίζει τον αριθμό 1024. Η line είναι διάνυσμα 1024 χαρακτήρων και μεταβλητή-μέλος της κλάσης LineFile. Το 1024 είναι δυνητικό. Συνήθως πληκτρολογούμε 100-150 χαρακτήρες. Το πόσους ακριβώς πληκτρολογήσαμε στην line μας το δίνει μιά άλλη συνάρτηση της κλάσης μας, η GetLineLength():
   int    LineFile::GetLineLength() { return strlen(line);}
Η συνάρτηση strlen βρίσκει το πλήθος των χαρακτήρων μιάς char* μεταβλητής.
Η συνάρτηση μέλος WriteMessage της κλάσης LineFile είναι απλούστατη:
void  LineFile::WriteMessage( char* mess )
{
   cout << mess << endl;
}
Το ίδιο και η συνάρτηση:
void LineFile::AppendLineEndFile()
{
   fstream  kata(fName, ios::app);      // open to append 
   WriteLine( kata );
   kata.close();
}
Τούτη ανοίγει το αρχείο fName και γράφει στο τέλος του τα περιεχόμενα της line. Τέλος η WriteLine κάνει πράγματι την εγγραφή αφού προσθέσει στο τέλος της γραμμής την σφραγίδα (myTime) με την ημερομηνία:
void LineFile::WriteLine( fstream & kata )
{
   kata << line <<  setw(MAIN_LINE_WIDTH-GetLineLength())  << myTime
                 << setw(MARGIN) << "**\n";
}
Το απόσπασμα κώδικα
if( GetLineLength() >10 )  // append !! *** user forgot to press a before !!
{
  AppendLineEndFile();
}
το τοποθέτησα μετά από δοκιμές του προγράμματος. Παρατήρησα ότι όταν ήθελα να καταχωρήσω μιά γραμμή, έπρεπε ΠΡΩΤΑ να δώσω το γράμμα a και κατόπιν να πληκτρολογήσω το κείμενο. Ξεχνούσα λοιπόν να δώσω πρώτα το γράμμα και έγραφα κατ' ευθείαν την γραμμή που ήθελα να καταχωρήσω. Το αποτέλεσμα ήταν να χάνω αυτήν την γραμμή και τον χρόνο μου. Το if λοιπόν εξετάζει το μήκος της line και αν είναι μεγαλύτερο από 10 (η strlen(...) βρίσκει το πλήθος χαρακτήρων ενός char* ) καταχωρεί την πληκτρολογημένη γραμμή στο αρχείο, υποθέτοντας ότι ο χρήστης έκανε το σφάλμα που περιέγραψα. Αν ο χρήστης δώσει ένα μόνο από τα 8 γράμματα και πατήσει το enter το πρόγραμμα λειτουργεί κανονικά.

Βλέπουμε εδώ ένα χαρακτηριστικό στιγμιότυπο της ιεραρχίας των συναρτήσεων. Κάθε μιά τους διασπά την δουλειά σε άλλες μικρότερες (διαίρει και βασίλευε) και τις παραπέμπει σε άλλες συναρτήσεις οι οποίες με την σειρά τους διασπούν την δουλειά τους σε άλλες κ.ο.κ. Το πράγμα θυμίζει την υπαλληλική ιεραρχεία, στην οποία ο διευθυντής αναθέτει στους υποδιευθυντές, αυτοί στους τμηματάρχες κτλ. κτλ. μέχρι να φτάσει η δουλειά κάτω-κάτω στους τελευταίους υπαλλήλους, που ο καθένας τους κάνει μιά εντελώς συγκεκριμένη και περιορισμένη εργασία.

Η συνάρτηση TimeString

Η συνάρτηση αυτή χρησιμοποιήται γιά να μπαίνει η ημερομηνία καταχώρησης σε κάθε νέα γραμμή που εγγράφει ο χρήστης στο αρχείο. Συγκεκριμένα, η κλάση LineFile διαθέτει ένα διάνυσμα χαρακτήρων char* myTime[20]; στο οποιο τοποθετούμε την τρέχουσα ημερομηνία κατά την εκκίνηση του προγράμματος. Η επόμενη συνάρτηση κάνει αυτήν ακριβώς την δουλειά. Βρίσκει με την συνάρτηση time(long_time); τον χρόνο σε δευτερόλεπτα (από την 1.1.1900) και τον τοποθετει στην μεταβλητή long_time τύπου time_t που είναι ισοδύναμη με έναν unsigned int. Κατόπιν η συνάρτηση newtime = localtime( &long_time ); μετατρέπει τον χρόνο από δευτερόλεπτα σε ένα σύστημα ακεραίων tm_mday, tm_mon, ... κτλ. που, συλλογικά αποτελούν την δομή τύπου tm. Αυτή δεν είναι τίποτε άλλο παρά μιά κλάση προϋπάρχουσα που διαχειρίζεται ημερομηνίες. Δεν επεκτείνομαι σε περισσότερες λεπτομέρειες εδώ.
void LineFile::TimeString()
{
    time_t long_time;      // get time is seconds
    time( &long_time );

    struct tm *newtime;    // to format time-info
    newtime = localtime( &long_time ); // transform to date

   sprintf(myTime, "%2d/%2d/%2d ", newtime->tm_mday ,
                           newtime->tm_mon+1,
                           newtime->tm_year-100 );
}
Η συνάρτηση sprintf έχει την ιδια σύνταξη με την printf, μόνο που τυπώνει κείμενο σε διάνυσμα χαρακτήρων (εδώ στο myTime). Έτσι ετοιμάζω κατά κάποιο τρόπο μιά σφραγίδα με την ημερομηνία, με την οποία σφραγίζω κάθε γραμμή που εισάγεται στο αρχείο.

Η συνάρτηση AppendLine

void LineFile::AppendLine( )
{
   WriteMessage( "write a line") ;     // WARNING check length ?????
   cin.getline(line,MXCH);          // get line from cin
   AppendLineEndFile();
}
Η συνάρτηση αυτή κάνει τα εξής:
1) Γράφει μήνυμα στην κονσόλα που προτρέπει τον χρήστη να πληκτρολογήσει νέα γραμμή.
2) Παίρνει την νέα γραμμή από την κονσόλα αποθηκεύοντας την στην line.
3) Γράφει το περιεχόμενο της line στο αρχείο με την συνάρτηση AppendLineEndFile.
Η συνάρτηση κάνει την δουλειά της με τις ίδιες βοηθητικές συναρτήσεις που συναντήσαμε στην MenuSelection.

Η συνάρτηση PrintLines

void LineFile::PrintLines()
{
   int n(0);
   WriteMessage("-----------");
   ifstream kata;             // open file to read from
   kata.open(fName);
   while( kata.getline( line, MXCH ) )
   {
      cout << line << endl;
      n++;
   }
   WriteMessage("-----------");
   cout << n << " lines in " << fName << endl;
}
Η συνάρτηση αυτή τυπώνει στην κονσόλα όλες τις γραμμές του αρχείου μέσω ενός while που λειτουργεί όσο υπάρχει η δυνατότητα ανάγνωσης μιάς ακόμη γραμμής από το αρχείο μας. Με τις εντολές ifstream kata; kata.open(fName); ανοίγουμε το αρχείο μας για ανάγνωση. Η εντολή kata.getline( line, MXCH ) διαβάζει διαδοχικές γραμμές από το αρχείο μας και επιστρέφει το πραγματικό μήκος της γραμμής ή 0 όταν φτάσει στο τέλος του αρχείου. Τότε ακριβώς διακόπτεται το while .Τα υπόλοιπα τα έχουμε ξαναδεί.

Η συνάρτηση ReplaceLine

void LineFile::ReplaceLine(  )
{
   int n = AcceptDelete();       // ask user etc...
   if(n == 0) return;            // user canceled
   WriteMessage("Write new line");
   cin.getline(line,MXCH);       // get line from user
   fstream kata;
   OpenSeekLine(kata, n, false );   // false = to write
   WriteLine( kata );
   kata.close();
}
Η πρώτη εντολή n = AcceptDelete(); προτρέπει τον χρήστη να δώσει έναν ακέραιο που αντιστοιχεί στην γραμμή την οποία θέλει να αλλάξει. Η συνάρτηση αυτή πρίν επιστρέψει τον ακέραιο που έδωσε ο χρήστης, ξαναρωτά αν όντως θέλει να αντικαταστήσει την ν-στη γραμμή. Ο χρήστης πρέπει να πατήσει y (=yes), αν μετανοιώσει και θέλει να κρατήσει την παλιά γραμμή, πατά ένα άλλο γραμμα και η συνάρτηση n = AcceptDelete(); επιστρέφει το 0.
Η επόμενη εντολή τον προτρέπει να γράψει μιά νέα γραμμή στην κονσόλα.
Η επόμενη γραμμή
   OpenSeekLine(kata, n, false );   // false = to write
ανοίγει το αρχείο μας γιά ανάγνωση+εγγραφή και τοποθετεί τον δείκτη στην αρχή της n-στής γραμμής (που θέλουμε να αλλάξουμε). Κατόπιν η συνάρτηση WriteLine(kata); γράφει το περιεχόμενο της line στην n-γραμμή του αρχείο (σβύνοντας την παλιά). Η συνάρτηση
void LineFile::OpenSeekLine( fstream & kata, int count, bool toRead /*true*/ )
{
   fstream::pos_type pos((count-1)*TOTAL_WIDTH);
   kata.open(fName, ios::in | ios::out  );  // WARNING errors ??
   if(toRead) kata.seekg(pos);
   else kata.seekp(pos);
}
ανοίγει ένα αρχείο για ανάγνωση+εγγραφή και τοποθετεί τον δείκτη στην αρχή της count-γραμμής. όταν η toRead αληθεύει τότε πρόκειται γιά ανάγνωση, αλλοιώς γιά εγγραφή.
Η συνάρτηση αυτή προτρέπει τον χρήστη να δώσει έναν ακέραιο που αντιστοιχεί σε μιά γραμμή στο αρχείο. Αν ο ακέραιος δεν αντιστοιχεί σε γραμμή τότε διορθώνεται ώστε να αντιστοιχεί. Για να γίνει αυτό χρειάζεται το πλήθος Ν των γραμμών του αρχείου. Αυτό ευρίσκεται από την NumOfLines.
int  LineFile::GetAnInteger()
{
   int N = NumOfLines();
   cout << "Give an integer  0 < n <= " << N << endl;
   cin.getline(line,MXCH);
   int n = atoi(line);           // transform to int
   if( (n<=N) && ( n > 0) ) return n; // normal case
   else if( n <=0 ) return 1;   // if <0 replace by  1
   else return N;          // if > N replace by  N
}
Η συνάρτηση NumOfLines προσδιορίζει το πλήθος γραμμών του αρχείου από το πλήθος όλων των χαρακτήρων που περιέχει, διαιρώντας το με το μήκος γραμμής του:
int LineFile::NumOfLines()
{
   fstream kata(fName, ios::binary  | ios_base::in );
   kata.seekg(0,ios_base::end);     // set ptr to end of file
   int  n ( kata.tellg()/TOTAL_WIDTH); // this is the total line-nr
   kata.close();
   return n;
}
Εδώ όλη δουλειά γίνεται με συναρτήσεις της fstream. Η τιμή της kata.tellg() είναι το πλήθος όλων των χαρακτήρων του αρχείου. TOTAL_WIDTH είναι το μήκος γραμμής του αρχείου.

Η συνάρτηση ShowLine

void LineFile::ShowLine( )
{
   int n = GetAnInteger();
   GetThisLine(n);
   DisplayNrCommentLine( n , "-line is:" );
}
Η συνάρτηση ζητά πάλι από τον χρήστη έναν αριθμό (n = GetAnInteger();) και διαβάζει από το αρχείο μας την αντίστοιχη γραμμή με την συνάρτηση GetThisLine(n);. Κατόπιν τυπώνει το αποτέλεσμα στην κονσόλα. Η τελευταία συνάρτηση:
void  LineFile::GetThisLine( int count )
{
   fstream kata;
   OpenSeekLine(kata, count);
   kata.getline( line, MXCH );
   kata.close();
}
χρησιμοποιεί πάλι την OpenSeekLine για να ανοίξει το αρχείο και να τοποθετηθεί στην αρχή της count-γραμμής προς ανάγνωση. Αντιγράφει κατόπιν την count-στη γραμμή στην line και κλείνει το αρχείο. Η συνάρτηση
void LineFile::DisplayNrCommentLine( int n, char* comment )
{
   WriteMessage("-----------");
   cout << n << comment << endl <<line << endl;
   WriteMessage("-----------");
}
τυπώνει α) τον αριθμό ν της γραμμής, β) ένα μικρό κείμενο (comment) και γ) το περιεχόμενο της line.

Η συνάρτηση NewWordSearch

Η συνάρτηση αυτή προτρέπει τον χρήστη να δώσει μιά λέξη. Το πρόγραμμα κατόπιν ψάχνει να βρει όλες τις γραμμές (διαδοχικά) που περιέχουν αυτήν την λέξη:
void  LineFile::NewWordSearch()
{
   wordFound=false;                 // not found yet
   wordLineStart=1;                 // line to start search
   if( GetWordSearchFor( wordSearch ) )   // is a real word ??
   {
      int lineFound = FindLineWord( wordSearch , wordLineStart);
      if( lineFound >-1 )              // if some line found
      {
         wordFound = true;          // prepare for next search
         ShowLineFound( lineFound );
      }
      else //nothing found
         cout << "passed end of file" << endl;  // inform user that no other line ...
   }
   else
      cout << "no word to search for" << endl;
}
Η συνάρτηση έχει απλή λογική. Χρησιμοποιεί τρεις μεταβλητές της LineFile που υποβοηθούν το ψάξιμο στις γραμμές:
string   wordSearch;   Εδώ βάζουμε την λέξη για την οποία ψάχνουμε.
int   wordLineStart;   Εδώ βάζουμε τον αριθμό γραμμής από την οποία αρχίζει το ψάξιμο.
bool   wordFound;   Εδώ σημειώνουμε αν βρήκαμε (true) ή όχι (false) την λέξη.
Η συνάρτηση GetWordSearchFor(wordSearch); καλεί τον χρήστη να δώσει μιά λέξη. Επιστρέφει μιά μεταβλητή τύπου bool που έχει τιμή true αν ο χρήστης έδωσε όντως κάποια λέξη.
bool LineFile::GetWordSearchFor( string &  str )
{
   static  char seps[]   = " ,\t\n";

   WriteMessage(" Give a word to search for:");
   cin.getline( line, MXCH );       // get a line from user 
   if(strlen(line)==0) return false;   // nothing to search for
   else
    str = strtok( line, seps );        // its first word (token)
   return true;
}
Η πρώτη εντολή της παραπάνω συνάρτησης ορίζει τον ορμαθό χαρακτήρων , \t\n (κενό, κόμα, διάστημα, αλλαγή γραμμής) οι χαρακτήρες αυτοί υποτίθενται ότι χρησιμοποιούνται στο κείμενό μας γιά να χωρίζουμε τις λέξεις. Ο ορμαθός αυτός χρησιμοποιήται στην συνάρτηση (βιβλιοθήκης <string.h>) η οποία βρίσκει τις λέξεις από τις οποίες αποτελείται η line και επιστρέφει κάθε φορά μιά λέξη (μπορεί να εφαρμοσθεί κατ' επανάληψη στην line και να μας δώσει όλες τις λέξεις που την αποτελούν). Η πραγματική αναζήτηση της λέξης στις γραμμές γίνεται στην lineFound = FindLineWord( wordSearch , wordLineStart); που κάνει τα εξής:
int LineFile::FindLineWord( string & word , int lineToStart)
{
   fstream kata;
   OpenSeekLine( kata, lineToStart );  // open and go to start of <lineToStart>
   int n(lineToStart);
   int result;
   while( kata.getline( line, MXCH ) )  // read line from file
   {
      result = (int)WordInLine( word ) ;         // test if word there
      if( result > -1 ){ kata.close(); return n;}
      n++;
   }
   kata.close();
   return -1;
}
Στις πρώτες γραμμές τις η συνάρτηση ανοίγει πάλι το αρχείο και τοποθετεί τον δείκτη στην αρχή της lineToStart γραμμής γιά ανάγνωση. Κατόπιν αρχίζει να διαβάζει γραμμές (while( kata.getline( line, MXCH ) )) και να ψάχνει σε κάθε γραμμή μήπως βρεί την λέξη (result = (int)WordInLine( word ) ;). Μόλις την βρεί επιστρέφει τον αριθμό γραμμής στην οποία βρήκε την λέξη.Αν δεν βρει τίποτε επιστρέφει την τιμή -1.
Στην περίπτωση που βρεθεί η λέξη σε μία γραμμή, οι μεταβλητές wordSearch, wordLineStart προετοιμάζονται (στην NewWordSearch()) για το επόμενο ψάξιμο (η λέξη μπορεί να περιέχεται σε πολλές γραμμές).

Η συνάρτηση OldWordSearch

Η συνάρτηση αυτή συνεχίζει το ψάξιμο που ξεκίνησε η NewWordSearch. Ψάχνει τις γραμμές από την wordLineStart και πέρα μέχρι το τέλος. Εδώ το πρόγραμμα περιμένει από τον χρήστη να ενεργήσει σωστά. Δηλαδή να ξεκινήσει το ψάξιμο με την NewWordSearch και να συνεχίσει με την OldWordSearch μέχρις ότου βρει όλες τις γραμμές που περιέχουν την λέξη που ψάχνει.
void  LineFile::OldWordSearch()
{
   if( wordFound )               // something has been found previously
   {
      int lineFound;
      if( !(wordLineStart<NumOfLines()) ||
         (lineFound = FindLineWord( wordSearch , wordLineStart+1)) == -1 )
      {
         cout << "passed end of file" << endl;  // nothing new found 
         wordLineStart = 1;                  // to restart search
      }
      else
      {
         ShowLineFound( lineFound );
      }
   }
   else
      NewWordSearch();  // start a new search
}
Και πάλι την πραγματική δουλειά κάνει η lineFound = FindLineWord( wordSearch , wordLineStart); . Οι άλλες εντολές εξετάζουν απλά τα αποτελέσματα που προκύπτουν απ αυτήν την συνάρτηση.

Η συνάρτηση DeleteLine

Η συνάρτηση αυτή ζητά έναν ακέραιο από τον χρήστη που καθορίζει ποιά γραμμή θα αφαιρεθεί από το αρχείο και στην συνέχεια αφαιρεί την γραμμή. Κατα την αφαίρεση μικραίνει το αρχείο κατά μία γραμμή. Υπάρχουν διάφορες στρατηγικές γιά να γίνει αυτό. Η επόμενη δημιουργεί ένα χώρο στην μνήμη (μεταβλητή pch) ο οποίος να χωρά τις Ν-1 γραμμές που απομένουν.
void LineFile::DeleteLine( )
{
   unsigned int n = (unsigned int)AcceptDelete();  // ask user etc...
   if(n == 0 ) return ;        // user canceled ... do nothing

   unsigned int    N       = NumOfLines();   // find total lines
   unsigned int   totpos      = (N-1)*TOTAL_WIDTH; // size after deletion
   unsigned int   beforeSize  = (n-1)*TOTAL_WIDTH; // size before line
   unsigned int   afterSize   = totpos-beforeSize; // size after line

   char *  pch  = new char[ totpos ];      // to receive the new text
   if(n>1)                          // if not first line delete
      PutInBuffer( pch, 0, beforeSize );   // store upper part to buffer
   if(n != N)                       // i.e. if not last line to delete
      PutInBuffer( pch+beforeSize, n*TOTAL_WIDTH,  afterSize );
                                // store lower part to buffer
   OverideFileWithBuffer( pch, totpos ); // write back buffer to file
   delete[] pch;                   // WARNING  memory ???
}
Η λογική της τελευταίας συνάρτησης είναι η εξής: α) Δημιούργησε ένα χώρο στην μνήμη (pch) όπου θα αποθηκευθεί όλο το κείμενο πλήν της γραμμής n προς αφαίρεση. β) Γράψε σ αυτό τον χώρο στην μνήμη τις Ν-1 προηγούμενες και τις Ν-n επόμενες γραμμές, όπου Ν ο συνολικός αριθμός γραμμών. γ) Ξαναγράψε τις υπόλοιπες γραμμές από τον χώρο μνήμης στο αρχείο. Την εγγραφή στο χώρο μνήμης αναλαμβάνει η συνάρτηση:
void LineFile::PutInBuffer( char* pch, unsigned int offset, unsigned int size )
{
   fstream kata(fName, ios::binary | ios_base::out | ios_base::in );
   kata.seekg(offset);        // start here reading from file
   kata.read( pch, size );    // ... put <size> bytes in pch
   kata.close();
}
Η συνάρτηση
void LineFile::OverideFileWithBuffer( char * pch, unsigned int size )
{
   ofstream  kata(fName, ios::binary | ios_base::trunc );
   kata.write( pch, size );
   kata.close();
}
γράφει το περιεχόμενο του char * pch στο αρχείο. Οι εργασίες αυτές υλοποιούν την αναδιάρθρωση του αρχείου που μικραίνει κατά μία γραμμή. Οι εντολές
   char *  pch  = new char[ totpos ];      
   delete[] pch;
Δεσμεύούν και αποδεσμεύουν αντίστοιχα χώρο από totpos bytes στην μνήμη. Κανονικά θα έπρεπε σε κάθε νέα αίτηση μνήμης με την new να εξετάζουμε αν όντως το αίτημα ικανοποιήται ή όχι. Το θέμα αυτό θα το δούμε στις exceptions που είναι ο μηχανισμός αντιμετώπισης σφαλμάτων της C++.

Η κλάση string

Η κλάση αυτή παρέχεται έτοιμη προς χρήση από την C++. Χρησιμοποιήται για την καταχώρηση κειμένου, από μία λέξη ως ένα βιβλίο. Αντικαθιστά τα διανύσματα χαρακτήρων, διαθέτει όμως πολλές συναρτήσεις-μέλη που διευκολύνουν την επεξεργασία κειμένου. Παραθέτω μερικούς τρόπους χρήσης της κλάσης αυτής:
  string  a, b, c, d;
  a = "Omiros";
  b = "Platon";
  c = a;
  int n = a.length(); // το n = 6 (πλήθος χαρακτήρων)
  char    myText[20];
  string  myString(myText);  // στο myString αποδίδεται το περιεχόμενο του myText

  //Στην επόμενη εντολή δημιουργήται πάλι ένα string και αποδίδεται και περιεχόμενο.
  string  myBigString("to be or not to be, that is the question!");

  string  mySmall("to");
  int n = myBigString.find( mySmall );  // ψάχνει να βρει το mySmall στο myBigString
										//επιστρέφει -1 αν δεν το βρει
  bool  what  = mySmall.empty() );  // επιστρέφει true αν το string  ειναι άδειο (χωρις κείμενο)
  char  ch    = myBigString[11]; // το 11-το γράμμα του
  const char*  string::data();  // μιά συνάρτηση της κλάσης που επιστρέφει την διεύθυνση 
				// του πρώτου χαρακτήρα από τον ορμαθό χαρακτήρων που περιέχει η string.
  string  a, b, c;
  a += b;   // αυτή επεκτείνει το a , προσθέτοντας στο τέλος του  όλους τους χαρακτήρες του b
  a += "some text"; // ανάλογα προς την προηγούμενη
  a = b + c; // το a περιέχει την συνένωση των χαρακτήρων των δύο ορμαθών

Οι κλάσεις fstream, ifstream, ofstream

Οι κλάσεις αυτές (streams = ρεύματα) συνδέονται με αρχεία και βοηθούν στην διαχείριση των περιεχομένων τους. Υπάρχουν και αντίστοιχες strstream, istrstream, ostrstream, που διαχειρίζονται τους χαρακτήρες που περιέχει ένα string. Καταγράφω μερικούς τυπικούς τρόπους χρήσης που χρησιμοποίσα στο πρόγραμμα.
  fstream   kata ;	//  ρεύμα ανάγνωσης+εγγραφής χαρακτήρων
  ifstream ikata;	// ρεύμα ανάγνωσης χαρακτήρων
  ofstream okata;	// ρεύμα εγγραφής χαρακτήρων

  kata.open(fName, ios::app);	    // σύνδεση ρεύματος με το αρχείο με όνομα fName
								 // προς εγγραφή στο τέλος του αρχείου, ισοδύναμα:
  fstream kata(fName, ios::app); // ισοδύναμη προς προηγούμενη εντολή
  ofstream kata(fName, ios::app);// ισοδύναμη προς προηγούμενη εντολή
  // οι δύο τελευταίες είναι constructors των αντιστοίχων κλάσεων και ανοίγουν το αντίστοιχο
  // αρχείο αμέσως μετά την κατασκευή του αντικειμένου
  
  // η γνωστή μας κονσόλα cout είναι μιά ειδική ostream 
  // (σαν την ofstream χωρίς σύνδεση με αρχείο)
  // η γνωστή μας κονσόλα cin είναι μιά ειδική istream
  
  ikata.getline( buffer, length ); // inctl μπορεί να είναι ifstream ή fstream
  // buffer είναι διάνυσμα χαρακτήρων (char* ...) και length ο μέγιστος αριθμός
  // χαρακτήρων προς ανάγνωση

  okata << myString;  // okata είναι fstream ή ofstream
  okata << buffer;
  cout << "my name";    // ..οπως ήδη συναντήσαμε

  ofstream  kata(fName, ios::binary | ios_base::trunc );
  // σύνδεση με αρχείο fName για εγγραφή δυαδικών δεδομένων (ios::binary)
  // ενδεχόμενα περιεχόμενα του  αρχείου διαγράφονται (ios_base::trunc)
  kata.write( buffer, length  ); // εγγραφή δυαδικών δεδομένων (bytes) απο το buffer
  kata.read( buffer, length );	//  ανάγνωση από το kata στο buffer length bytes
  kata.close();					// κλείσιμο αρχείου
  kata.seekg(pos);  // τοποθέτηση δείκτη ανάγνωσης (από που ξεκινά επόμενη ανάγνωση)
  kata.seekp(pos);  // τοποθέτηση δείκτη εγγραφής (από που ξεκινά επόμενη εγγραφή)
					// η πρώτη εντολή μόνο για fstream και ifstream
					//  η δεύτερη μόνο γιά fstream και ofstream
  kata.seekg( position, ios_base::end);	//τοποθέτηση δείκτη ανάγνωσης στην θέση position
										// μετρώντας από το τέλος του αρχείου 
  totpos = kata.tellg(); // λήψη του δείκτη ανάγνωσης (που αρχίζει επόμενη ανάγνωση) 
  totpos = kata.tellp(); // λήψη του δείκτη εγγραφής  
  // μπορούμε να χρησιμοποιούμε τους τελεστές << και >> γιά όλους τους στανταρτ τύπους 
  // μεταβλητών :
  int  a;
  char b;
  unsigned int c;
  double  dbl;
  float   flt;
  okata  << a << b << c << dbl << flt << endl;
  ikata  >> a >> b >> c >> dbl >> flt ;
	// όλα αυτά είναι flags, συμβολικοί ακέραιοι που  καθορίζουν ιδιότητες των streams
  ios::app		// καθορίζει ότι θα γίνει καταχώρηση στο τέλος αρχείου
  ios::in       // ότι το αρχείο είναι γιά εγγραφή
  ios::out      // ότι το αρχείο είναι γιά ανάγνωση
  ios::trunc    // ότι θα διαγραφούν τα παλιά και θα εγγραφούν νέα δεδομένα
  ios::binary   // ότι θα γίνει διαχείριση bytes και όχι χαρακτήρων (κειμένου)
  // ορισμένες απ' αυτές μπορούν να συνδιασθούν με το λογικό |:
  (ios::in | ios:: out | ios::binary)  // ...διαχείριση bytes για εγγραφή + ανάγνωση

Παρατηρήσεις