Last modified: September 25, 2024

This article is written in: 🇵🇱

Praca z plikami

Obsługa plików jest kluczową umiejętnością przy pisaniu aplikacji w C i C++. Zarówno C, jak i C++ oferują bogaty zestaw funkcji do pracy z plikami.

Praca z plikami w C

W języku C obsługa plików odbywa się za pomocą funkcji z biblioteki <stdio.h>. Najważniejsze funkcje do tej operacji to fopen, fclose, fread, fwrite, fprintf, fscanf, fgetc, i fputc.

Otwieranie plików

Aby otworzyć plik, używamy funkcji fopen, która zwraca wskaźnik do pliku:

#include <stdio.h>

int main() {
    FILE *f = fopen("plik.txt", "r");
    // ... operacje na pliku ...
    fclose(f);
}

Argumenty fopen to nazwa pliku oraz tryb otwarcia:

Czytanie z pliku

Jednym ze sposobów czytania z pliku jest użycie fgetc:

#include <stdio.h>

int main() {
    FILE *f = fopen("plik.txt", "r");
    char c;
    
    while ((c = fgetc(f)) != EOF) {
        putchar(c);
    }

    fclose(f);
}

Zapisywanie do pliku

Aby zapisać do pliku, można użyć fputc:

#include <stdio.h>

int main() {
    FILE *f = fopen("wyjscie.txt", "w");
    
    fputc('H', f);
    fputc('i', f);
    
    fclose(f);
}

Lub użyj fprintf dla formatowanego wyjścia:

FILE *f = fopen("wyjscie.txt", "w");
fprintf(f, "Witaj, %s!", "świecie");
fclose(f);

Sprawdzanie błędów

Zawsze warto sprawdzić, czy otwarcie pliku powiodło się:

FILE *f = fopen("plik.txt", "r");
if (!f) {
    perror("Błąd podczas otwierania pliku");
    return 1;
}

W języku C obsługa plików opiera się na wskaźnikach do plików i zestawie funkcji z <stdio.h>. Kluczową funkcją jest fopen, która zwraca wskaźnik do pliku, a także fclose, która zamyka plik. Pamiętaj, aby zawsze zamykać pliki po zakończeniu pracy!

Praca z plikami w C++

Obsługa plików w C++ odbywa się głównie za pomocą klas ifstream, ofstream i fstream z biblioteki <fstream>. Poniżej przedstawiam podstawowe informacje na ten temat.

Otwieranie plików

Aby otworzyć plik, należy stworzyć obiekt jednego z typów ifstream (do czytania), ofstream (do pisania) lub fstream (do obu operacji). Podczas tworzenia obiektu, można od razu podać nazwę pliku.

#include <fstream>

int main() {
  std::ifstream plik_wejsciowy("plik.txt");
  std::ofstream plik_wyjsciowy("wyjscie.txt");
}

Czytanie z pliku

Do czytania z pliku często używa się pętli while w połączeniu z metod getline():

#include <iostream>
#include <fstream>
#include <string>

int main() {
  std::ifstream plik("plik.txt");
  std::string linia;
  
  while (std::getline(plik, linia)) {
    std::cout << linia << std::endl;
  }

  plik.close();
}

Zapisywanie do pliku

Zapisywanie do pliku jest równie proste:

#include <fstream>

int main() {
  std::ofstream plik("wyjscie.txt");
  
  plik << "Witaj, świecie!" << std::endl;
  
  plik.close();
}

Sprawdzanie błędów

Przed przystąpieniem do operacji na pliku warto sprawdzić, czy otwarcie pliku powiodło się:

#include <fstream>
#include <iostream>

int main() {
  std::ifstream plik("plik.txt");
  
  if (!plik) {
    std::cerr << "Błąd podczas otwierania pliku!" << std::endl;
    return 1;
  }
  
  // reszta kodu

  plik.close();
}

Binary Mode

Domyślnie pliki są otwierane w trybie tekstowym. Aby otworzyć plik w trybie binarnym, dodaj std::ios::binary jako drugi argument podczas otwierania:

std::ofstream binarny_plik("plik.bin", std::ios::binary);

Obsługa plików w C++ jest prosta i intuicyjna. Kluczowe klasy to ifstream, ofstream i fstream, które umożliwiają odpowiednio czytanie, zapisywanie lub obie te operacje. Nie zapominaj też o zamykaniu plików po zakończeniu pracy!

Gwarancja bezpiecznego zamknięcia pliku

Prawidłowe zarządzanie zasobami, takimi jak pliki, jest kluczowe dla stabilności i wydajności programu. W językach programowania, które nie mają wbudowanego zarządzania pamięcią, jak C++, błędy w obsłudze zasobów mogą prowadzić do wycieków pamięci, błędów dostępu lub blokowania zasobów.

Dlaczego bezpieczne zamykanie pliku jest ważne?

Jeśli plik nie zostanie zamknięty prawidłowo, może prowadzić to do różnych problemów:

  1. Niezamknięty plik może prowadzić do straty danych, ponieważ nie wszystkie dane mogą zostać zapisane.
  2. Plik otwarty przez jeden program może powodować blokadę zasobów, uniemożliwiając dostęp do niego innym programom.
  3. System operacyjny może rezerwować zasoby dla otwartych plików, co może skutkować wyciekami pamięci w przypadku niezamknięcia pliku.

RAII w C++

RAII to koncepcja przypisania życia zasobu do czasu życia obiektu. Poniżej przedstawiam bardziej rozbudowaną klasę File:

#include <fstream>
#include <stdexcept>

class File {
 public:
  File(const std::string& filename, const std::string& mode = "r") 
    : m_filename(filename) {
      if (mode == "r") m_file.open(filename, std::ios::in);
      else if (mode == "w") m_file.open(filename, std::ios::out);
      else if (mode == "a") m_file.open(filename, std::ios::app);
      else {
          throw std::invalid_argument("Nieznany tryb otwarcia pliku");
      }

      if (!m_file.is_open()) {
          throw std::runtime_error("Nie można otworzyć pliku");
      }
  }

  ~File() {
    if (m_file.is_open()) {
      m_file.close();
    }
  }

  std::fstream& stream() { return m_file; }

 private:
  std::string m_filename;
  std::fstream m_file;
};

int main() {
  try {
      File file("plik.txt", "r");
      char znak;
      while (file.stream().get(znak)) {
        std::cout << znak;
      }
  } catch (const std::exception& e) {
      std::cerr << "Błąd: " << e.what() << std::endl;
  }

  return 0;
}

Kod ten definiuje klasę File, która zarządza otwieraniem i zamykaniem plików w C++. W przypadku tej klasy RAII jest realizowane w następujący sposób:

  1. Przejęcie zasobu w konstruktorze oznacza, że plik jest otwierany podczas tworzenia obiektu. Jeśli otwarcie się nie powiedzie, zostaje rzucony wyjątek, a konstruktor nie kończy się sukcesem, co zapobiega wywołaniu destruktora.
  2. Zwolnienie zasobu w destruktorze gwarantuje, że plik zostanie zamknięty, jeśli jest otwarty, niezależnie od tego, czy operacje na pliku zakończyły się sukcesem, czy wystąpił wyjątek, dzięki czemu plik jest automatycznie zwalniany, gdy obiekt File przestaje istnieć.

Przykład ten pokazuje, jak za pomocą RAII można zarządzać zasobami (w tym przypadku plikami) w bezpieczny i efektywny sposób, minimalizując ryzyko wycieków zasobów.

Spis Treści

    Praca z plikami
    1. Praca z plikami w C
      1. Otwieranie plików
      2. Czytanie z pliku
      3. Zapisywanie do pliku
      4. Sprawdzanie błędów
    2. Praca z plikami w C++
      1. Otwieranie plików
      2. Czytanie z pliku
      3. Zapisywanie do pliku
      4. Sprawdzanie błędów
      5. Binary Mode
    3. Gwarancja bezpiecznego zamknięcia pliku
      1. Dlaczego bezpieczne zamykanie pliku jest ważne?
      2. RAII w C++