3. Потоци II

План:
Низови потоци
Аргументи от командния ред
Пряк достъп
Файлове с променлива или фиксирана дължина на записа

** Низови (текстови) потоци

В езика С++ има възможност да се чете/пише от/в низове вместо да се използват стандартни устройства (cin и cout) или файлове.

В заглавния файл sstream.h са дефинирани обекти, функции и операции за работа с текстови потоци.

* Входен поток - с обект от клас istringstream.

Пример: Превръщане на дата и година от низ (string input;) в стойности на числови променливи (int day, year;).

string input = "January 23, 1881";
istringstream instr(input);
string month;
int day;
string comma;
int year;
instr >> month >> day >> comma >> year;

* Изходен поток - с обект от клас ostringstream. 

Пример:
Превръщане число тип double (sqrt(2)) в низ (string output;).

ostringstream outstr;
outstr << setprecision(8) << sqrt(2);
string output = outstr.str();

Пример: Четене на час и минути в различни формати  и отпечатване в двата стандартни формата.

В САЩ има два формата за записване на време с разлика в часовете:
- am/pm
- денонощието започва с 12 am следват 1-11 am, 12 pm и 1-11 pm.
- military - часовете са от 00 до 23. 

Формат за четене:
- час - 10, 22, 10 am, 10 pm
- час и минути - 10:20, 22:00, 10:20 am, 10:00 pm

// readtime.cpp
 
#include <iostream>
#include <sstream>
using namespace std;

string int_to_string(int n)
{
  ostringstream outstr;

  outstr << n;
  return outstr.str();
}

void read_time(int &hours, int &minutes)
{
  string line;

  getline(cin, line);
 
  istringstream instr(line);
  instr >> hours;
 
  minutes = 0;
  char ch;
  instr.get(ch);
  if (ch == ':') instr >> minutes;
  else           instr.unget();

  string suffix;
  instr >> suffix;
  if (suffix == "pm") hours += 12;
}

string time_to_string(int hours, int minutes, bool am_pm)
{
  string suffix;

  if (am_pm)
  {
    if (hours < 12) suffix = "am";

    else { suffix = "pm"; hours -= 12; }
    if (hours == 0) hours = 12;
  }
  string result = int_to_string(hours) + ":";
  if (minutes < 10) result = result + "0";
  result = result + int_to_string(minutes);
  if (am_pm) result = result + " " + suffix;
  return result;
}

int main()
{
  cout << "Please enter the time: ";

  int hours, minutes;
  read_time(hours, minutes);

  cout << "Using am/pm:   " << time_to_string(hours, minutes, true) << "\n";
  cout << "Military time: " << time_to_string(hours, minutes, false) << "\n";
  return 0;
}

nkirov@cpp % ./a.out
Please enter the time: 18:30

Military time: 18:30
Using am/pm:   6:30 pm
nkirov@cpp % ./a.out
Please enter the time: 18
Military time: 18:00
Using am/pm:   6:00 pm
nkirov@cpp % ./a.out
Please enter the time: 12 pm
Military time: 24:00
Using am/pm:   12:00 pm
nkirov@cpp % ./a.out
Please enter the time: 8:24 am
Military time: 8:24
Using am/pm:   8:24 am

** Аргументи от командния ред

* Определение и използване.

Операционните системи могат да предават данни на програма на С и С++ от командния ред (на конзолата/терминала) при стартиране на програмата.
За тази цел главната функция main се дефинира с два формални параметри от типове int и char*.

Пример:
  Отпечатване на аргументите от командния ред.

// simple.cpp
 
#include <iostream>
using namespace std;

int main(int argc, char* argv[])
{
   for (int i=0; i<argc; i++)
       cout << "argv[" << i << "]=" << argv[i] << endl;
   return 0;
}

nkirov@cpp % c++ simple.cpp
nkirov@cpp % ./a.out abc 23 oo
argv[0]=./a.out
argv[1]=abc
argv[2]=23
argv[3]=oo

Пример: Шифър на Цезар.

Шифрирането се състои в замяна на буква от даден (изходен) текст с друга буква, която се намира е след key букви в азбуката. 

Пример 1:
> caesar a.txt b.txt
Чете текста във файла a.txt, отмества всяка латинска буква на 3 позиции напред (ключ 3) в азбуката и записва получения текст във файла b.txt.

Дешифрирането на шифриран текст се състои във възстановяване на изходния текст.

Пример 2:
> caesar -d b.txt c.txt
Чете текста в шифрирания файл b.txt, отмества всяка латинска буква на 3 позиции назад (ключ 3) в азбуката и записва получения текст във файла c.txt.

Програмта работи със следните аргументи от командния ред:
- флаг -d (незадължителен) за работа на програмата по дешифриране;
- флаг -k<число> (незадължителен) за задаване на ключ (по подразбиране ключът е 3);
- име на входен файл (задължителен);
- име на изходен файл  (задължителен).

// caesar.cpp
 
#include <iostream>
#include <fstream>
#include <string>
#include <sstream>
using namespace std;

void usage(string program_name)
{
  cout << "Usage: " << program_name

       << " [-d] [-kn] infile outfile\n";
  exit(1);
}

void open_file_error(string filename)
{
  cout << "Error opening file " << filename << "\n";

  exit(1);
}

int remainder(int a, int n)
{
  if (a >= 0) return a % n;

  else        return n - 1 - (-a - 1) % n;
}

char encrypt(char ch, int k)

  const int NLETTER = 26;

  if ('A' <= ch && ch <= 'Z')
         return static_cast<char>('A' + remainder(ch - 'A' + k, NLETTER));
  if ('a' <= ch && ch <= 'z')
         return static_cast<char>('a' + remainder(ch - 'a' + k, NLETTER));
  return ch;
}

void encrypt_file(ifstream& in, ofstream& out, int k)
{
  char ch;

  while (in.get(ch)) out.put(encrypt(ch, k));
}

int string_to_int(string s)
{
  istringstream instr(s);

  int n;
  instr >> n;
  return n;
}

int main(int argc, char* argv[])
{
  bool decrypt = false;

  int key = 3;
  int nfile = 0;
 
  ifstream infile;
  ofstream outfile;

  if (argc<3 or argc>5) usage(string(argv[0]));
 
  for (int i = 1; i < argc; i++)
  {
    string arg = string(argv[i]);

    if (arg.length() >= 2 and arg[0] == '-')
    {
      char option = arg[1];

      if (option == 'd') decrypt = true;
      else if (option == 'k')
        key = string_to_int(arg.substr(2, arg.length() - 2));
    }
    else
    {
      nfile++;

      if (nfile == 1)
      {
        infile.open(arg.c_str());

        if (infile.fail()) open_file_error(arg);
      }
      else if (nfile == 2)
      {
        outfile.open(arg.c_str());

        if (outfile.fail()) open_file_error(arg);
      }
    }
   }
 
   if(nfile != 2) usage(string(argv[0]));
   if (decrypt) key = -key;
 
   encrypt_file(infile, outfile, key);
 
   infile.close();
   outfile.close();
   return 0;
}

Изпълнение на програмата:
>caesar
>Usage: CAESAR.EXE [-d] [-kn] infile outfile

Файл input.txt:
This is a text to encrypt (1-st) and decrypt (2-nd).
One more line. End.

Изпълнение на програмата:
>caesar input.txt encrypt1.txt

Файл encrypt1.txt
Wklv lv d whaw wr hqfubsw (1-vw) dqg ghfubsw (2-qg).
Rqh pruh olqh. Hqg.

Изпълнение на програмата:
>caesar -k7 input.txt encrypt2.txt

Файл encrypt2.txt
Aopz pz h alea av lujyfwa (1-za) huk kljyfwa (2-uk).
Vul tvyl spul. Luk.

Изпълнение на програмата:
>caesar -d -k7 encrypt2.txt output.txt

Файл output.txt
This is a text to encrypt (1-st) and decrypt (2-nd).
One more line. End.


** Произволен (пряк) достъп (random access)

Последователен достъп до данните във файл означава, че можем да четем/пишем от/във файл само последователно - елемент след елемент, байт след байт.
Сега бихме искали да можем да четем и пишем на всяко място във файла, което се нарича пряк или произволен достъп.

Включване на заглавен файл:
    #include <fstream>
 
Дефиниране на файлова променлива за четене и писане:
    fstream fs;
 
Във всеки файл са дефинирани две специални позиции:

Преместване на позициите за четене и писане с член-функции на класа fstream:
    long n = 10;
 
/* преместване на позицията за четене 10 байта след началото на файла */
    fs.seekg(n, ios::beg);

/* преместване на позицията за писане 10 байта след началото на файла */
    fs.seekp(n, ios::beg);
 
/* преместване на позицията за писане в края на файла */
    fs.seekp(n, ios::end);
 
/* преместване на позицията за писане 10 байта напред относно текущата позиция */
    fs.seekp(n, ios::cur);

Константите ios::beg, ios::end и ios::cur са дефинирани в най-базовия клас ios от потоковата йерархия.

Намиране на текущите позициите за четене и писане с член-функции на класа fstream:

    long n;
    n = fs.tellg();
    n = fs.tellp();

Дължината на файл можем да получим по следния начин:
fs.seekg(0, ios::end);
long file_length = fs.tellg();

** Файлове с променлива или фиксирана дължина на записа

 Пример: Променлива дължина
Harry Hacker 500 Johny Johnson 600 Tedy Tompson 700.20
1234567890123456 12345678901234567 1234567890123456789

Пример: Фиксирана дължина
Harry Hacker       500 Johny Johnson      600 Tedy Tompson    700.20
1234567890123456789012 1234567890123456789012 1234567890123456789012

Пример: Програма за четене на файл със записи с фиксирана дължина, съдържащи име на служител и заплата и промяна на заплатата на даден служител. 

Примерен файл: employee.dat

// database.cpp
 
#include <iostream>
#include <iomanip>
#include <fstream>
#include <sstream>
using namespace std; 

#include "ccc_empl.h"

const int NEWLINE_LENGTH = 2; /* или 1 за Unix */
const int RECORD_SIZE = 30 + 10 + NEWLINE_LENGTH;

double string_to_double(string s)
{
  istringstream instr(s);

  double x;
  instr >> x;
  return x;
}

void raise_salary(Employee &e, double percent)
{
  double new_salary = e.get_salary()*(1 + percent/100);

  e.set_salary(new_salary);
}

void read_employee(Employee &e, fstream &fs)
{
  string line;

  getline(fs, line);
  if (fs.fail()) return;
 
  string name = line.substr(0, 30);
  double salary = string_to_double(line.substr(30, 10));
  e = Employee(name, salary);
}

void write_employee(Employee e, fstream &fs)
{
  fs << e.get_name()

     << setw(10 + (30 - e.get_name().length()))
     << fixed << setprecision(2)
     << e.get_salary() << "\n";
}

int main()
{
  cout << "Please enter the data file name: ";

  string filename;
  cin >> filename;
 
  fstream fs;
  fs.open(filename.c_str());
  if (fs.fail())
  { 
     cout << "The file " << filename << " does not exist!" << endl;
     exit(1);
  }

  fs.seekg(0, ios::end);
  int nrecord = fs.tellg()/RECORD_SIZE;

  cout << "Please enter the record to update: (0 - "
       << nrecord - 1 << ") ";
  int pos;
  cin >> pos;

  if (cin.fail() or (pos < 0) or (pos >= nrecord))
  { 
      cout << "The record is out of range!" << endl;
      exit(1);
  }

  const double SALARY_CHANGE = 5.0;
 
  Employee e;
  fs.seekg(pos*RECORD_SIZE, ios::beg);
  read_employee(e, fs);
 
  raise_salary(e, SALARY_CHANGE);
  fs.seekp(pos*RECORD_SIZE, ios::beg);
  write_employee(e, fs);

  fs.close();
  return 0;
}
 
Файл data.txt преди изпълнение на програмата:
Harry Hacker                     500.00
Johny Johnson                    600.00
Tedy Tompson                     777.00

Изпълнение на програмата:
Please enter the data file name: data.txt
Please enter the record to update: (0 - 2) 1

Файл data.txt след изпълнение на програмата:
Harry Hacker                     500.00
Johny Johnson                    630.00
Tedy Tompson                     777.00