Domovská stránka Programovanie v C++ Kontakt
  6. Znaky a textové súbory

Na prednáške:

Znaková premenná

Znakom rozumieme jedno písmeno, cifru, symbol, medzeru...
  A, B, ..., a, b, ..., 0, 1, 2, ..., +, *, !, @, #, $, %, &
Veľké a malé písmená sú rôzne znaky.

Znaky, s ktorými budeme pracovať, patria do množiny znakov - sú typu char:
  char c;                            // c je  znaková premenná:
  c='A';                             // 'A'
je znaková konštanta - uzatvárame ju medzi apostrofy
  Image1->Canvas->TextOut(0 ,0, c);  // na obrazovke sa uvidíme nakreslené písmeno A

 

Znaková premenná je premenná typu char.

Znaky môžeme testovať na rovnosť a nerovnosť:
  if (c=='A') Image1->Canvas->TextOut(0, 20, "Áno");
 
else Image1->Canvas->TextOut(0, 20, "Nie");

So znakmi súvisia aj textové reťazce, akým je napríklad aj pozdrav "Ahoj!":

Operácie so znakmi

Počítač má znaky zakódované - každému znaku prislúcha nejaký číselný kód.  Existujú rôzne spôsoby, ako môžu byť znakom pridelené číselné kódy. V našom C++ sa používajú kódy podľa štandardu ASCII. V nasledujúcej tabuľke vidíme niektoré znaky a ich kódy:

znak
ASCII kód
 
medzera
32
 
'+'
43
 
'.'
46
 
'0' '1' ... '9'
48 49 ... 57
 
'A' 'B' ... 'Z'
65 66 ... 90
 
'a' 'b' ... 'z'
97 98 ... 122

Aj v pamäti počítača sú znaky uložené ako čísla, preto počítač pracuje s číselnými kódmi znakov. V jazyku C++ je silný vzťah medzi číslami (typ int) a znami (typ char) a to až do takej miery, že tieto dva typy sú navzájom kompatibilné:

Znaky môžeme porovnávať. Znaky sú zoradené podľa ASCII kódov. Preto medzi písmenami platia nasledujúce relácie:

'A'<'B'   'B'<'C'   'C'<'D' ...<... 'Y'<'Z' ...<... 'a'<'b' 'b'<'c'   'c'<'d' ...<... 'y'<'z'

Všimnime si, že veľké písmená majú menší kód, ako malé písmená. Preto platí, že 'A'<'a'.

So znakmi môžeme vykonávať aritmetické operácie. Znak v aritmetickom výraze sa znaková hodnota (typ char) konvertuje na číslo z rozsahu <-128, 127>. Kladné hodnoty zodpovedajú ASCII kódom prvých 127 znakov v ASCII tabuľke:
  i='A'+1;  // i==66 
... k ASCII kódu písmena 'A' sa pripočíta číslo 1, výsledok bude číslo 66
  c='A'+1;  // c=='B'
... k ASCII kódu písmena 'A' sa pripočíta číslo 1, výsledok bude číslo 66 a do premennej c sa uloží znak s týmto ASCII kódom
  c=c+1;    //
v premennej c bude znak 'C'
  c++;      // c=='D'

Úloha: Do grafickej plochy chcem vypísať všetky veľké písmená:

Riešenie:

void __fastcall TForm1::Button1Click(TObject *Sender)
{
 
char z;
  int x=0;
 
for (z='A'; z<='Z'; z++) {
    Image1->Canvas->TextOut(x, 0, z);
    x=x+10;
  }
}

Vylepšené riešenie: Keďže znakové premenné môžeme používať v aritmetických výrazoch, predchádzajúci program sa dá ešte zjednodušiť:

void __fastcall TForm1::Button1Click(TObject *Sender)
{
 
char z;
 
for (z='A'; z<='Z'; z++) Image1->Canvas->TextOut((z-'A')*10, 0, z);
}

Trasujme, ako prebieha predchádzajúci cyklus, ako sa mení premenná z a hodnota výrazu (z-'A')*10:

krok výpisu: premenná z ASCII kód z (z-'A')*10
1. A 65 0
2. B 66 10
3. C 67 20
... ... ... ...
26. Z 90 250

Textový súbor

Textový súbor je postupnosť znakov uložená za sebou v súbore na disku, CD alebo inom zariadení. Textový súbor má väčšinou koncovku .txt. Textový súbor vieme vytvoriť v textovom editore, napríklad v programe Notepad. To, čo v textovom editore vidíme takto:

A
  bc
d

Bude v textovom súbore uložené nasledovne:

A nový riadok     b c nový riadok d

V textovom súbore sú uložené aj špeciálne značky pre nový riadok, ktoré oddeľujú jednotlivé riadky textu. Ak súbor čítame postupne smerom zľava doprava, dozvieme sa celý text. A dozvieme sa aj to, ako je text rozložený do riadkov. Všimnite si, že postupnosť znakov v textovom súbore pripomína tok, prúd (anglicky stream) znakov.

Naučíme sa programovo:

Pri práci so súbormi budeme využívať knižnicu fstream. Preto musíme na začiatku programu písať #include <fstream.h>:

#include <vcl.h>
#include <fstream.h>
#pragma hdrstop

#include "Unit1.h"

Zápis do textového súboru

Ukážka vytvorenia súboru a zápisu do súboru pomocou príkazov:

void __fastcall TForm1::Button1Click(TObject *Sender)
{
  ofstream f;             //
deklarácia súborovej premennej f - je typu ofstream
  f.open("Vystup.txt");   //
otvorenie súboru Vystup.txt
  f.put('A');             //
zápis jedeného znaku - písmena A
  f.put(' ');             //
zápis medzery
  f.put('B');             //
zápis ďalšieho znaku
  f.put('\n');            //
zápis riadiaceho znaku nový riadok
  f.put('C');             //
písmeno C bude na ďalšom riadku
  f.close();              //
zatvorenie súboru
}

Na disku vznikne krátky textový súbor Vystup.txt s takýmto obsahom:

A B
C

Poznámky:

 

Do súboru zapisujeme údaje pomocou súborovej premennej typu ofstream.

Príklad: Vytvoríme textový súboru, ktorý má v každom riadku jedno malé písmeno:

a
b
c
d
 .
 .
 .
z

 

void __fastcall TForm1::Button2Click(TObject *Sender)
{
  ofstream f;
 
char c;
  f.open("Pismena.txt");
 
for (c='a'; c<='z'; c++) {
    f.put(c);
    f.put('\n');
  }
  f.close();
}

Príklad: Vytvoríme textový súbor, v ktorom sú hviezdičky uložené po diagonále:

*
 *
  *
   *
    *
     *
      *
       *
        *
         *

Riešenie: Aby hviezdičky ležali od ľavého okraja, musíme pred každú hviezdičku zapíšeť medzery (pred prvou bude 0 medzier, pred druhou 1 medzera, pred treťou 2 atď.):

void __fastcall TForm1::Button3Click(TObject *Sender)
{
  ofstream f;
 
int i, n;
  f.open("Hviezdicky.txt");
 
for (n=1; n<=10; n++) {
   
for (i=1; i<n; i++) f.put(' ');
    f.put('*');
    f.put('\n');
  }
  f.close();
}

Čítanie z textového súboru

Začnime tým, že si vytvoríme textový súbor Vstup.txt s nasledujúcim obsahom:

Ahoj!

Vieme, že postupnosť znakov v súbore vyzerá nasledovne:

A h o j !

Ukážka čítania zo súboru:

void __fastcall TForm1::Button4Click(TObject *Sender)
{
  ifstream f;            //
deklarácia súborovej premennej f - je typu ifstream
  char c;
 
int i;
  f.open("Vstup.txt");   //
otvorenie súboru Vstup.txt
  f.get(c);              //
čítanie znaku - v premennej c bude prvý znak 'A'
  f.get(c);              //
prečíta ďalší - v premennej c bude ďalší znak 'h'
  f.get(c);              //
v premennej c bude písmeno 'o'
  f.get(c);              //
v premennej c bude písmeno 'j'
  f.get(c);              //
v premennej c bude znak '!'
  f.close();             //
zatvorenie súboru
}

Vysvetlenie:

 

Zo súboru čítame údaje pomocou súborovej premennej typu ifstream.

Riešenie: Ak vieme, že pozdrav má dĺžku 5 znakov, môžeme použiť cyklus:

void __fastcall TForm1::Button4Click(TObject *Sender)
{
  ifstream f;
  char c;
 
int i;
  f.open("Pozdrav.txt");
 
for (i=0; i<5; i++) {
    f.get(c);
    Image1->Canvas->TextOut(i*10, 0, c);
  }
  f.close();
}

Ak by bol pozdrav v súbore kratší alebo dlhší, museli by sme upraviť podmienku cyklu for tak, aby sa prečítal iný počet znakov:

  ...
  for
(i=1; i<=10; i++) {
 
...
 

Elegantnejšie riešenie by bolo, keby sme vedeli prečítať a spracovať celý súbor bez ohľadu na to, koľko znakov obsahuje...

Spracovanie textového súboru neznámej dĺžky

Úloha: Chceme prečítať a zobraziť jednoriadkový pozdrav neznámej dĺžky, napríklad:

Ahoj! Ako sa máš?

Riešenie: Využijeme nový test f.fail() - výsledkom je hodnota true, ak posledná operácia so súborom (teda napríklad otvorenie súboru alebo čítanie zo súboru) skončilo chybou. Vypísanie pozdravu neznámej dĺžky potom bude vyzerať nasledovne:

void __fastcall TForm1::Button4Click(TObject *Sender)
{
  ifstream f;
  char c;
 
int x;                 // x-ová súradnica, kam budeme vypisovať
  f.open("Pozdrav.txt");
 
if (f.fail()) {        // test na chybu pri otvorení súboru
    ShowMessage("Chyba pri otváraní súboru.");
   
return;              // ukončíme vykonávanie tejto funkcie
  }
  x=0;
  f.get(c);              //
skúsime prečítať prvý znak
  while (!f.fail()) {    //
kým nenastala chyba, opakujeme:
    Image1->Canvas->TextOut(x, 0, c);
    x=x+10;
    f.get(c);            //
skúsime prečítať ďalší znak
  }
  f.close();
}

Vysvetlenie:

Pozor, takéto zjednodušené riešenie nie je správne:

  x=0;                                ... pred cyklom neprečítame prvý znak
  while (!f.fail()) {
    f.get(c);                        
... ak by tu nastala chyba (napríklad, čítame z prázdneho súboru alebo sme už na konci súboru),
    Image1->Canvas->TextOut(x, 0, c); ... tento príkaz by vypísal nesprávny znak
    x=x+10;
  }

Problém: Chceme prečítať a zobraziť viacriadkový pozdrav neznámej dĺžky, napríklad:

Ahoj!
Ako sa máš?

Riešenie: Budeme si musieť všímať znaky '\n' a okrem x-ovej súradnice počítať riadky, kam znaky vypyisujeme:

void __fastcall TForm1::Button4Click(TObject *Sender)
{
  ifstream f;
 
int x, y;
 
char c;
  f.open("unit1.cpp");
 
if (f.fail()) {
    ShowMessage("Chyba");
   
return;
  }
  x=0;
  y=0;
  f.get(c);
 
while (!f.fail()) {
    if (c=='\n') {
      x=0;
      y=y+14;
    }
else {
      Image1->Canvas->TextOut(x, y, c);
      x=x+8;
    }
    f.get(c);
  }
  f.close();
}

Kopírovanie textového súboru

Úloha: Chceme skopírovať textový súbor Vstup.txt do nového súboru s názvom Vystup.txt.

Riešenie: Pri kopírovaní textových súborov použijeme dve súborové premenné. Zo vstupného súboru postupne znaky čítame a zapisujeme ich do výstupného súboru:

void __fastcall TForm1::Button8Click(TObject *Sender)
{
  ifstream i;
  ofstream o;
 
char c;
  i.open("Vstup.txt");
 
if (i.fail()) {
    ShowMessage("Chyba pri otváraní.");
   
return;
  }
  o.open("Vystup.txt");
  i.get(c);
 
while (!i.fail()) {
    o.put(c);
    i.get(c);
  }
  o.close();
  i.close();
}

Všimnime si, že:

Zhrnutie

Znaková premenná si pamätá jeden znak a je typu char. V aritmetickom výraze znak funguje ako číslo, ktoré sa rovná ASCII kódu znaku.

Zápis do súboru:

1. deklarovanie súborovej premennej   ofstream f;
2. otvorenie súboru   f.open(meno_súboru);
3. zapísanie znaku   f.put(znak);
4. zatvorenie súboru   f.close();

Čítanie zo súboru:

1. deklarovanie súborovej premennej   ifstream f;
2. otvorenie súboru   f.open(meno_súboru);
3. test chyby pri otváraní   if (f.fail()) ...
4. prečítanie znaku   f.get(znak);
5. zatvorenie súboru   f.close();

Algoritmus, ktorý postupne prečíta celý textový súbor (používame ifstream f a predpokladáme, že súbor je už otvorený):
  f.get(c);             //
čítaj prvý znak
 
while (!f.fail()) {   // opakuj, kým pri čítaní zo súboru nenastala chyba
   
...                   // miesto, kde spracujeme prečítaný znak zo znakovej premennej c
    f.get(c);           //
čítaj ďalší znak
  }

© 2013 Ľ. SALANCI