Języki Programowania – Zajęcia 3

Stringi i pliki tekstowe

Przygotowanie i podstawy

Na początek używamy tylko zmiennych typu string, wejścia z klawiatury, indeksów, pętli i warunków. Dopiero w kolejnej części dodajemy obsługę plików.

Napisy w C++ – string

Do przechowywania tekstu w C++ wykorzystujemy typ string z biblioteki <string>. String można traktować jako ciąg znaków, do których dostęp uzyskujemy podobnie jak do elementów tablicy. W przeciwieństwie do zwykłej tablicy znaków char, napis typu string może automatycznie zmieniać swoją długość.

Przykładowa deklaracja napisu:

#include <iostream>
#include <string>
using namespace std;

int main() {
    string tekst = "Ala ma kota";
    cout << tekst << endl;
}

Dostęp do poszczególnych znaków

Każdy znak w stringu ma swój indeks. Numerowanie zaczyna się od 0, tak jak w tablicach.

string tekst = "kot";

cout << tekst[0] << endl;  // k
cout << tekst[1] << endl;  // o
cout << tekst[2] << endl;  // t

Możemy również wykorzystać pętlę do przejścia przez wszystkie znaki stringu:

string tekst = "kot";

for (int i = 0; i < tekst.length(); i++) {
    cout << tekst[i] << endl;
}

Dzięki indeksowaniu można odczytywać, porównywać i zmieniać poszczególne znaki. Należy jednak pamiętać, że indeks musi wskazywać istniejący znak. Dostęp do ostatniego znaku wymaga, aby string nie był pusty. Do napisów typu string w C++ można dołączać inne stringi za pomocą operatora + lub +=, podobnie jak w Pythonie.

Najważniejsze metody i operacje na stringach

Metoda lub operacja Zastosowanie Przykład
length() Zwraca długość stringu. tekst.length()
size() Również zwraca długość stringu. tekst.size()
empty() Sprawdza, czy string jest pusty. tekst.empty()
at(i) Zwraca znak o indeksie i i sprawdza, czy indeks jest poprawny. tekst.at(0)
find() Wyszukuje znak lub fragment tekstu i zwraca indeks jego pierwszego wystąpienia. tekst.find("kot")
substr() Pobiera fragment stringu. tekst.substr(0, 3)

Przykład wykorzystania kilku metod:

string tekst = "Ala ma kota";

cout << "Dlugosc: " << tekst.length() << endl;
cout << "Pierwszy znak: " << tekst.at(0) << endl;
cout << "Fragment: " << tekst.substr(0, 3) << endl;
cout << "Pozycja slowa: " << tekst.find("kota") << endl;

tekst += "!";
cout << tekst << endl;
Uwaga dotycząca find()

Jeżeli wyszukiwany fragment nie występuje w napisie, metoda find() zwraca string::npos. Można to wykorzystać do sprawdzenia, czy dany fragment został znaleziony.

if (tekst.find("kot") != string::npos) {
    cout << "Znaleziono slowo.";
}

Wczytywanie stringów z klawiatury

Do wczytywania tekstu możemy wykorzystać operator >> lub funkcję getline(). Różnią się one sposobem traktowania spacji.

Sposób A – operator >>

string tekst;

cin >> tekst;

Operator >> wczytuje tekst tylko do pierwszego białego znaku, np. spacji.

Sposób B – funkcja getline()

string tekst;

getline(cin, tekst);

Funkcja getline() wczytuje cały wiersz, łącznie ze spacjami, aż do końca linii. Jest więc przydatna, gdy użytkownik podaje zdanie lub dłuższy tekst.

Ważne: zwykły napis string można indeksować podobnie jak tablicę, ale nie jest on klasyczną tablicą znaków. Ponadto w przypadku tekstu zapisanego w UTF-8 polskie litery, takie jak „ą” lub „ł”, mogą zajmować więcej niż jeden bajt. Metody length() i indeksowanie nie zawsze odpowiadają więc liczbie widocznych znaków.

Jak wczytać string?

#include <iostream>
#include <string>
using namespace std;
string tekst;
getline(cin, tekst);   // wczytuje całą linię, również spacje
// cin >> tekst;      // wczytuje tylko do pierwszej spacji
Ważne: jeśli tekst ma zawierać całe zdanie, użyj getline(cin, tekst). Instrukcja cin >> tekst jest odpowiednia, gdy wystarczy jedno słowo.

Zadanie 1. Odwracanie napisu – dane z klawiatury

Wczytaj tekst z klawiatury i wypisz znaki w odwrotnej kolejności.

Przykład

Dane wejściowe
Ala ma kota
Wynik
atok am alA

Różne sposoby odczytywania znaków z pliku

W C++ można odczytywać plik znak po znaku, linia po linii albo słowo po słowie. Wybór sposobu ma znaczenie: niektóre metody zachowują spacje i nowe linie, a inne je pomijają. Do odczytywania danych z plików tekstowych wykorzystujemy klasę ifstream z biblioteki <fstream>.

Przykłady poniżej zakładają, że plik tekst.txt został już otwarty do odczytu:

#include <fstream> 
ifstream wejscie("tekst.txt");

Sposób 1. wejscie.get(znak) — każdy znak, również spacje

Metoda get(znak) odczytuje jeden znak i zapisuje go w zmiennej typu char. Odczytuje także spacje, tabulatory oraz znaki nowej linii.

char znak;
while (wejscie.get(znak)) {
    cout << znak;
}

Kiedy używać? Przy odwracaniu całej zawartości pliku albo liczeniu wystąpień wybranego znaku, także gdy tym znakiem jest spacja.

Sposób 2. wejscie >> znak — odczyt znaków z pominięciem białych znaków

Operator >> pomija białe znaki, czyli spacje, tabulatory i znaki nowej linii. Dla tekstu Ala ma kota kolejne odczytane znaki to A, l, a, m, a, k i tak dalej — bez spacji.

char znak;
while (wejscie >> znak) {
    cout << znak;
}

Kiedy używać? Gdy chcesz sprawdzać litery i inne niebiałe znaki, a spacje i podziały linii nie są istotne. Nie używaj tej metody do odtwarzania oryginalnego tekstu.

Sposób 3. getline() i indeksowanie — linia po linii

getline(wejscie, linia) odczytuje jedną linię do zmiennej typu string. Potem możesz użyć indeksu, aby uzyskać każdy znak tej linii.

string linia;
while (getline(wejscie, linia)) {
    for (int i = 0; i < linia.size(); i++) {
        cout << linia[i];
    }
    cout << '\n';
}
  • getline(wejscie, linia) pobiera kolejną linię.
  • linia.size() zwraca długość napisu.
  • linia[i] daje znak o indeksie i, numerowanie zaczyna się od zera.

Uwaga: znak końca linii nie trafia do zmiennej linia. Dlatego przykład wypisuje '\n' po przetworzeniu każdej linii.

Kiedy używać? Gdy tekst ma być analizowany wierszami albo chcesz przetworzyć każdą linię osobno.

Sposób 4. wejscie >> slowo, a potem slowo[i] — słowo po słowie

Operator >> może wczytać całe słowo do zmiennej typu string. Później możesz przejść po wszystkich znakach tego słowa.

string slowo;
while (wejscie >> slowo) {
    for (int i = 0; i < slowo.size(); i++) {
        cout << slowo[i] << '\n';
    }
}

Spacje i nowe linie rozdzielające słowa są pomijane. Znaki interpunkcyjne, na przykład kropka na końcu kota., pozostają częścią słowa.

Kiedy używać? Przy liczeniu słów, szukaniu najdłuższego słowa i analizowaniu liter należących do poszczególnych wyrazów.

Uwaga: przykłady używają char i są przeznaczone do ćwiczeń na podstawowym alfabecie łacińskim. Polskie znaki zapisane w UTF-8 mogą zajmować kilka bajtów, więc proste operacje znak po znaku nie zawsze odpowiadają pojedynczym widocznym literom.

Jak zapisywać dane do pliku?

Do zapisywania danych w plikach tekstowych wykorzystujemy klasę ofstream z biblioteki <fstream>.

Podstawowe elementy obsługi plików:

Element Zastosowanie
ofstream Otwieranie pliku do zapisu
open() Otwieranie pliku
is_open() Sprawdzanie, czy plik został otwarty
<< Zapisywanie tekstu i wartości do pliku
close() Zamykanie pliku

Plik zapisujemy podobnie jak wypisujemy tekst na ekran za pomocą cout. Zamiast cout używamy jednak obiektu reprezentującego plik.

Przykład zapisu na ekranie:

cout << "To jest przykladowy tekst.";

Zapis tego samego tekstu do pliku:

ofstream plik("wynik.txt");
    // ofstream plik;           //alternatywna wersja
    // plik.open("wynik.txt");
if (!plik.is_open()) {
    cout << "Nie mozna otworzyc pliku.";
    return 1;
}
plik << "To jest przykladowy tekst.";
plik.close();

Po uruchomieniu programu w odpowiednim katalogu pojawi się plik wynik.txt zawierający zapisany tekst.

Zapisywanie tekstu znak po znaku Operator << pozwala zapisać cały napis za jednym razem. Możemy również zapisywać jego kolejne znaki w pętli. Przykład:

string tekst = "Ala ma kota.";

ofstream plik("wynik.txt");

for (int i = 0; i < tekst.length(); i++) {
    plik.put(tekst[i]);
}

plik.close();

Metoda put() zapisuje pojedynczy znak do pliku.

Porada: instrukcja break

Instrukcja break natychmiast przerywa wykonywanie najbliższej pętli for lub while. Program przechodzi wtedy do instrukcji znajdującej się za pętlą.

Przykład – odczyt pliku do pierwszego znaku końca linii:

char znak;

while (plik.get(znak)) {
    if (znak == '\n') {
        break;
    }
    cout << znak;
}

Program odczytuje i wypisuje znaki aż do napotkania '\n'. Instrukcja break kończy wtedy pętlę, więc pozostała część pliku nie jest już odczytywana.

Najważniejsze polecenia i metody

ElementDo czego służy?Przykład
stringPrzechowuje tekst.string tekst;
getline(cin, tekst)Wczytuje całą linię z klawiatury.getline(cin, tekst);
tekst.size()Zwraca liczbę elementów napisu.tekst.size()
tekst[i]Pobiera znak o indeksie i.cout << tekst[i];
ifstreamOtwiera strumień do odczytu z pliku.ifstream wejscie("tekst.txt");
ofstreamOtwiera strumień do zapisu do pliku.ofstream raport("statystyki.txt");
wejscie.get(znak)Odczytuje kolejny znak, także spację i nową linię.while (wejscie.get(znak))
wejscie >> znakOdczytuje znak, pomijając białe znaki.while (wejscie >> znak)
getline(wejscie, linia)Odczytuje całą linię bez separatora końca linii.while (getline(wejscie, linia))
wejscie.get()Zwraca kolejny znak jako int, a koniec pliku jako EOF.kod = wejscie.get()
wejscie.peek()Sprawdza następny znak bez pobierania go.wejscie.peek()
wejscie >> slowoOdczytuje kolejne słowo oddzielone białymi znakami.while (wejscie >> slowo)
is_open()Sprawdza, czy plik został otwarty.if (!wejscie.is_open())
close()Zamyka plik.wejscie.close();
ios::appOtwiera plik do dopisywania na końcu.ofstream raport("statystyki.txt", ios::app);
<<Zapisuje tekst do strumienia lub pliku.raport << "Liczba slow";