Работа с файлами C++. Введение.
На прошлой домашке мы поработали с syscall в линуксе. А сейчас хочется писать кросс - платформенный код.
Мы пользовались: sys_read(fd, buffer, size)
Сегодня мы будем говорить про файловые комманды. Например про:
Примеры комманд вывода:
write
Например если пользоваться пользовательскими вызовами:
#include <unistd.h>
int main() {
const char(&arr)[6] = "hello";
const char * str = arr;
write(1, str, 5);
}
Данная программа выведет hello
.
fwrite
#include <cstdio>
int main() {
// Используем std::fwrite для вывода строки "hello"
// 1-й аргумент: указатель на данные для записи
// 2-й аргумент: размер каждого элемента (1 байт для char)
// 3-й аргумент: количество элементов для записи (5 символов)
// 4-й аргумент: поток вывода (stdout для вывода на консоль)
std::fwrite("hello", 1, 5, stdout);
}
Еще один пример использования std::fwrite:
"
int arr[] = {'a','b','c'};
std::fwrite(&arr, sizeof(int), std::size(arr), stdout);
Важно отметить, что строки в C и C++ обычно нультерминированы. Это означает, что в конце строки стоит символ '\0' (нулевой байт). Этот нулевой символ служит признаком конца строки.
Например, строковый литерал "hello" на самом деле занимает 6 байт в памяти: 'h', 'e', 'l', 'l', 'o', '\0'. Как мы и писали ранее.
#include <cstdio>
#include <iterator>
int main() {
int arr[] = {'a' * 256 + 'z', 'b', 'c'};
std::fwrite(&arr, sizeof(int), std::size(arr), stdout);
}
Оно выведет za b c
, из-за политики little-endian у нас z выведится первым, так и должно быть.
fputs, puts
signed main() {
std::fputs("hello\n",stdout);
}
У него есть аналог puts
- выводит в stdout. При этом puts добавляет еще перенос строки.
Примеры комманд ввода:
fread
size_t fread(void* ptr, size_t size, size_t count, FILE* stream);
Параметры:
- ptr: указатель на блок памяти, в который будут записаны прочитанные данные.
- size: размер каждого элемента в байтах.
- count: количество элементов, которые нужно прочитать.
- stream: указатель на объект FILE, представляющий файл, из которого читаются данные.
"hello"; //not const char*
//const char(&)[6]
Примеры кода:
Вывод из ввода консоли:
#include <cstdio>
int main(int argc, char **argv) {
for (int i = 0; i < argc; i++) {
std::puts(argv[i]);
}
}
Это программа при вводе aboba aboba aboba
выведет:
path\practice1.exe
aboba aboba aboba
Причина, по которой программа выводит "path\practice1.exe" в первой строке, заключается в том, что имя файла и путь к нему включаются в аргументы командной строки при запуске программы. Конечно, какой-то плохой человек может передать 0 аргументов, но на то это и плохой человек :)
Это гарантируется вызовом execve
, можете в мануале про него почитать.
Прочитать из файлика и построчно вывести:
#include <cstdio>
int main(int argc, char **argv) {
std::FILE *fd = std::fopen(argv[1], "r");
int current = std::fgetc(fd);
while (current != EOF) {
std::putchar(current);
current = std::fgetc(fd);
}
return 0;
}
Лучше писать return 0
в конце программы.
Но теперь наша программа может вылетать с ошибками, обработаем их:
#include <cstdio>
int main(int argc, char **argv) {
if (argc != 2) {
std::fputs("Wrong number of arguments\n", stderr);
return 1;
}
const char *file_path = argv[1];
std::FILE *fd = std::fopen(file_path, "r");
if (fd == nullptr) {
std::fprintf(stderr, "Could not open file: %s\n", file_path);
/*
Или аналогично:
std::fputs("No such file:", stderr);
std::fputs(file_path, stderr);
std::fputs("\n", stderr);
*/
return 1;
}
int current = std::fgetc(fd);
while (current != EOF) {
std::putchar(current);
current = std::fgetc(fd);
}
if (std::ferror(fd)) {
std::fputs("Read error\n", stderr);
std::fclose(fd);
return 1;
}
std::fclose(fd);
return 0;
}
Что делать если fclose завершился с ошибкой - вопрос философский.
Но теперь у нас слабенькие выводы ошибок, давайте поменяем:
#include <cstdio>
#include <cstring>
#include <cstdlib>
int main(int argc, char **argv) {
if (argc != 2) {
std::perror("Wrong number of arguments\n");
return EXIT_FAILURE;
}
const char *file_path = argv[1];
std::FILE *fd = std::fopen(file_path, "r");
if (fd == nullptr) {
std::fprintf(stderr, "Could not open file: %s: %s\n", file_path, std::strerror(errno));
return EXIT_FAILURE;
}
int current = std::fgetc(fd);
while (current != EOF) {
std::putchar(current);
current = std::fgetc(fd);
}
if (std::ferror(fd)) {
std::perror("Read error\n");
std::fclose(fd);
return EXIT_FAILURE;
}
std::fclose(fd);
return EXIT_SUCCESS;
}
Совпадение содержимого файла со строчкой
#include <cstdio>
#include <cstring>
#include <memory>
int main(int argc, char **argv) {
// Проверка правильного количества аргументов
if (argc != 3) {
std::fprintf(stderr, "Expected 2 arguments, got %d\n", argc - 1);
return 1;
}
const char *filename = argv[1]; // Имя файла из первого аргумента
std::FILE *f = std::fopen(filename, "r"); // Открытие файла для чтения
if (!f) {
std::fputs("File not opened!\n", stderr);
return 1;
}
bool equals = true; // Флаг совпадения содержимого
const char *const pattern = argv[2]; // Строка для сравнения из второго аргумента
size_t len = std::strlen(pattern); // Длина строки для сравнения
// Посимвольное сравнение содержимого файла со строкой
for (size_t i = 0; i < len; ++i) {
int c = std::fgetc(f); // Чтение символа из файла
if (c == EOF) { // Если достигнут конец файла раньше времени
equals = false;
break;
}
unsigned char cc = static_cast<unsigned char>(c);
char ccc = static_cast<char>(cc);
if (ccc != pattern[i]) { // Если символы не совпадают
equals = false;
break;
}
}
// Проверка на ошибки чтения
if (std::ferror(f)) {
std::fputs("Error while reading\n", stderr);
std::fclose(f);
return 1;
}
// Проверка, что файл не содержит дополнительных символов
if (std::fgetc(f) != EOF) {
equals = false;
}
// Вывод результата
std::fputs(equals ? "Yes\n" : "No\n", stdout);
std::fclose(f); // Закрытие файла
return 0;
}
Данную практику писал Чепелин Вячеслав. Скопирован файл Артема и Льва с нашей практики и на нем объяснен код