исследуй сходимость ряда (sqrt(n+a)-sqrt4(n^2+n+b)...
Создано: 13 апреля 2025 г.
Создано: 13 апреля 2025 г.
исследуй сходимость ряда (sqrt(n+a)-sqrt4(n^2+n+b) обьясни понятно по какому признаку доказательство
Анализ ряда:
Рассмотрим общий член ряда
Чтобы проверить сходимость , достаточно сначала понять, как ведут себя сами при больших . Ведь известен необходимый признак сходимости:
Если сходится, то .
Если этот предел или не существует, ряд расходится.
Самый наглядный способ — избавиться от разности корней, используя формулу разности квадратов:
Применим это к и :
Упростим числитель:
Тогда
Числитель: растёт по модулю как и имеет знак «минус» при больших .
Знаменатель: .
Следовательно,
Отсюда видно, что , а, значит, он не просто не стремится к нулю, а вообще уходит в минус бесконечность.
Из условия (необходимый признак) ясно, что если не стремится к нулю, то ряд обязательно расходится.
В нашем случае убывает приблизительно как , значит . Поэтому
расходится (причём члены ряда даже не стремятся к 0).
Мы воспользовались необходимым признаком сходимости:
Если же , то ряд не может сходиться.
Итог: так как ~ , то его предел не равен нулю, следовательно, данный ряд расходится.
Файл 1
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <locale.h>
#include <unistd.h>
#include "winner.h" // Подключаем структуру
// Сообщаем, что они определены в table_part.c
extern const char *filename;
extern void printAll(void);
// Макросы (необязательно, если уже объявлены в table_part.c)
#define INDEX_WIDTH 2
#define FIO_WIDTH 25
#define SCHOOL_WIDTH 6
#define REGION_WIDTH 24
#define SUBJECT_WIDTH 20
#define YEAR_WIDTH 4
#define MAX_CHUNKS 50
#define MAX_LINE_WIDTH 100
//#define PREDEFINED_DATA
// -------------------------------------------------
// Подсчёт количества записей в файле
static long getRecordCount(FILE *fp)
{
fseek(fp, 0, SEEK_END);
long size = ftell(fp);
fseek(fp, 0, SEEK_SET);
return size / (long)sizeof(Winner);
}
// -------------------------------------------------
// rewriteInitialData: инициализация файла
// -------------------------------------------------
static void rewriteInitialData(void) {
#if defined(PREDEFINED_DATA)
// --- ВАРИАНТ: ПРИНУДИТЕЛЬНО используем массив, игнорируем существование файла ---
printf("PREDEFINED_DATA определён: создаём/перезаписываем файл из массива...\n");
textFILE *fp = fopen(filename, "wb"); if (!fp) { perror("Не удалось создать файл (predefined)"); exit(EXIT_FAILURE); } Winner predefinedArray[] = { {"Иванов Иван Иванович", 123, "г. Москва", "Математика", 2023}, {"Петров Пётр Петрович", 456, "Красноярский край", "Информатика", 2024}, {"Сидорова Анна Сергеевна", 72, "Ленинградская обл.","Физика", 2025} }; int n = sizeof(predefinedArray) / sizeof(predefinedArray[0]); for (int i = 0; i < n; i++) { fwrite(&predefinedArray[i], sizeof(Winner), 1, fp); } fclose(fp); printf("Файл перезаписан из массива!\n");
#else
// --- ВАРИАНТ: Если макроса нет, работаем по "старой" схеме ---
// Проверяем, существует ли файл
FILE *chk = fopen(filename, "rb");
if (chk) {
fclose(chk);
printf("Файл уже существует, не перезаписываем.\n");
return;
}
text// Если файл не найден, спрашиваем у пользователя FILE *fp = fopen(filename, "wb"); if (!fp) { perror("Не удалось создать файл (user input)"); exit(EXIT_FAILURE); } int n; printf("Файл не найден! Создаём новый. Введите количество начальных записей: "); scanf("%d", &n); getchar(); for (int i = 0; i < n; i++) { Winner w; printf("\n==== Запись #%d ====\n", i + 1); printf("Введите ФИО: "); fgets(w.fio, sizeof(w.fio), stdin); w.fio[strcspn(w.fio, "\n")] = '\0'; printf("Введите номер школы: "); scanf("%d", &w.school_number); getchar(); printf("Введите регион: "); fgets(w.region, sizeof(w.region), stdin); w.region[strcspn(w.region, "\n")] = '\0'; printf("Введите предмет: "); fgets(w.subject, sizeof(w.subject), stdin); w.subject[strcspn(w.subject, "\n")] = '\0'; printf("Введите год проведения: "); scanf("%d", &w.year); getchar(); fwrite(&w, sizeof(Winner), 1, fp); } fclose(fp); printf("Файл создан и заполнен вводом с клавиатуры.\n");
#endif
}
// -------------------------------------------------
// Функции меню
// -------------------------------------------------
void addRecord(void) {
FILE *fp = fopen(filename, "ab");
if (!fp) {
perror("Ошибка открытия файла для добавления");
return;
}
textWinner w; printf("Введите ФИО: "); fgets(w.fio, sizeof(w.fio), stdin); w.fio[strcspn(w.fio, "\n")] = '\0'; printf("Введите номер школы: "); scanf("%d", &w.school_number); getchar(); printf("Введите регион: "); fgets(w.region, sizeof(w.region), stdin); w.region[strcspn(w.region, "\n")] = '\0'; printf("Введите предмет: "); fgets(w.subject, sizeof(w.subject), stdin); w.subject[strcspn(w.subject, "\n")] = '\0'; printf("Введите год проведения: "); scanf("%d", &w.year); getchar(); fwrite(&w, sizeof(Winner), 1, fp); fclose(fp); printf("Запись успешно добавлена.\n");
}
void deleteRecord(void) {
FILE *fp = fopen(filename, "r+b");
if (!fp) {
perror("Ошибка открытия файла для удаления");
return;
}
textlong count = getRecordCount(fp); if (count == 0) { printf("Файл пуст. Удалять нечего.\n"); fclose(fp); return; } printf("Введите номер записи (1..%ld) для удаления: ", count); long idx; scanf("%ld", &idx); getchar(); if (idx < 1 || idx > count) { printf("Некорректный индекс.\n"); fclose(fp); return; } long recordIndex = idx - 1; if (recordIndex == count - 1) { fseek(fp, recordIndex * sizeof(Winner), SEEK_SET); fflush(fp); ftruncate(fileno(fp), recordIndex * sizeof(Winner)); } else { Winner last; fseek(fp, (count - 1) * sizeof(Winner), SEEK_SET); fread(&last, sizeof(Winner), 1, fp); fseek(fp, recordIndex * sizeof(Winner), SEEK_SET); fwrite(&last, sizeof(Winner), 1, fp); fflush(fp); ftruncate(fileno(fp), (count - 1) * sizeof(Winner)); } fclose(fp); printf("Запись удалена.\n");
}
void searchRecords(void) {
FILE *fp = fopen(filename, "rb");
if (!fp) {
perror("Ошибка открытия файла для поиска");
return;
}
long count = getRecordCount(fp);
if (count == 0) {
printf("Файл пуст. Искать нечего.\n");
fclose(fp);
return;
}
textprintf("Выберите поле для поиска:\n"); printf("1. ФИО\n"); printf("2. Номер школы\n"); printf("3. Регион\n"); printf("4. Предмет\n"); printf("5. Год\n"); printf("Выбор: "); int choice; scanf("%d", &choice); getchar(); char strQuery[100]; int intQuery = 0; switch (choice) { case 1: printf("Введите подстроку для ФИО: "); fgets(strQuery, sizeof(strQuery), stdin); strQuery[strcspn(strQuery, "\n")] = '\0'; break; case 2: printf("Введите номер школы: "); scanf("%d", &intQuery); getchar(); break; case 3: printf("Введите подстроку для региона: "); fgets(strQuery, sizeof(strQuery), stdin); strQuery[strcspn(strQuery, "\n")] = '\0'; break; case 4: printf("Введите подстроку для предмета: "); fgets(strQuery, sizeof(strQuery), stdin); strQuery[strcspn(strQuery, "\n")] = '\0'; break; case 5: printf("Введите год: "); scanf("%d", &intQuery); getchar(); break; default: printf("Некорректный выбор.\n"); fclose(fp); return; } int found = 0; for (long i = 0; i < count; i++) { Winner w; fread(&w, sizeof(Winner), 1, fp); int match = 0; switch (choice) { case 1: if (strstr(w.fio, strQuery)) match = 1; break; case 2: if (w.school_number == intQuery) match = 1; break; case 3: if (strstr(w.region, strQuery)) match = 1; break; case 4: if (strstr(w.subject, strQuery)) match = 1; break; case 5: if (w.year == intQuery) match = 1; break; } if (match) { found++; printf("\nНайдена запись #%ld:\n", i + 1); printf(" ФИО: %s\n", w.fio); printf(" Школа: %d\n", w.school_number); printf(" Регион: %s\n", w.region); printf(" Предмет: %s\n", w.subject); printf(" Год: %d\n", w.year); printf("-------------------------\n"); } } if (!found) { printf("Ничего не найдено.\n"); } fclose(fp);
}
void editRecord(void) {
FILE *fp = fopen(filename, "r+b");
if (!fp) {
perror("Ошибка открытия файла для редактирования");
return;
}
long count = getRecordCount(fp);
if (count == 0) {
printf("Файл пуст. Редактировать нечего.\n");
fclose(fp);
return;
}
textprintf("Введите номер записи (1..%ld) для редактирования: ", count); long idx; scanf("%ld", &idx); getchar(); if (idx < 1 || idx > count) { printf("Некорректный индекс.\n"); fclose(fp); return; } long recIndex = idx - 1; fseek(fp, recIndex * sizeof(Winner), SEEK_SET); Winner w; fread(&w, sizeof(Winner), 1, fp); printf("Текущие данные:\n"); printf(" ФИО: %s\n", w.fio); printf(" Школа: %d\n", w.school_number); printf(" Регион: %s\n", w.region); printf(" Предмет: %s\n", w.subject); printf(" Год: %d\n", w.year); printf("\nОставьте поле пустым, если не хотите менять.\n"); char buffer[100]; printf("Новое ФИО: "); fgets(buffer, sizeof(buffer), stdin); if (buffer[0] != '\n') { buffer[strcspn(buffer, "\n")] = '\0'; strncpy(w.fio, buffer, sizeof(w.fio)); } printf("Номер школы (0 — без изменений): "); int tmp; scanf("%d", &tmp); getchar(); if (tmp != 0) { w.school_number = tmp; } printf("Новый регион: "); fgets(buffer, sizeof(buffer), stdin); if (buffer[0] != '\n') { buffer[strcspn(buffer, "\n")] = '\0'; strncpy(w.region, buffer, sizeof(w.region)); } printf("Новый предмет: "); fgets(buffer, sizeof(buffer), stdin); if (buffer[0] != '\n') { buffer[strcspn(buffer, "\n")] = '\0'; strncpy(w.subject, buffer, sizeof(w.subject)); } printf("Новый год (0 — без изменений): "); scanf("%d", &tmp); getchar(); if (tmp != 0) { w.year = tmp; } fseek(fp, recIndex * sizeof(Winner), SEEK_SET); fwrite(&w, sizeof(Winner), 1, fp); fclose(fp); printf("Запись обновлена.\n");
}
// -------------------------------------------------
// MAIN
// -------------------------------------------------
int main(void)
{
if (!setlocale(LC_ALL, "ru_RU.UTF-8")) {
setlocale(LC_ALL, "");
}
text// Сразу делаем инициализацию (перезапись) файла rewriteInitialData(); typedef void (*MenuFunc)(void); MenuFunc menuFunctions[] = { addRecord, // 1 deleteRecord, // 2 searchRecords, // 3 editRecord // 4 }; while (1) { printf("\n=========================================\n"); printf("1. Добавить запись\n"); printf("2. Удалить запись\n"); printf("3. Поиск записей\n"); printf("4. Редактировать запись\n"); printf("5. Вывести все записи\n"); printf("6. Выход\n"); printf("=========================================\n"); int choice; printf("Ваш выбор: "); scanf("%d", &choice); getchar(); // \n if (choice == 6) { printf("Завершение.\n"); break; } else if (choice >= 1 && choice <= 5) { if (choice == 5) { printAll(); } else { menuFunctions[choice - 1](); } } else { printf("Некорректный ввод.\n"); } } return 0;
}
Файл 2
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <locale.h>
#include <unistd.h>
#include "winner.h" // Подключаем структуру
// Глобальная переменная с именем файла
const char *filename = "winners.bin";
// Макросы для ширины столбцов (таблица)
#define INDEX_WIDTH 2
#define FIO_WIDTH 25
#define SCHOOL_WIDTH 6
#define REGION_WIDTH 24
#define SUBJECT_WIDTH 20
#define YEAR_WIDTH 4
#define MAX_CHUNKS 50
#define MAX_LINE_WIDTH 100
// Утилита для вычисления "видимой" длины строки (UTF-8)
static int utf8_len(const char *s)
{
int n = 0;
while (*s) {
if (((unsigned char)*s & 0xC0) != 0x80) {
n++;
}
s++;
}
return n;
}
static void printCell(const char *text, int width, int side)
{
putchar(' ');
int visibleLen = utf8_len(text);
int pad = (visibleLen < width) ? (width - visibleLen) : 0;
textif (side) { // Выравнивание справа for (int i = 0; i < pad; i++) putchar(' '); fputs(text, stdout); } else { // Выравнивание слева fputs(text, stdout); for (int i = 0; i < pad; i++) putchar(' '); } putchar(' ');
}
static void printSep(char ch)
{
int colWidths[6] = { INDEX_WIDTH, FIO_WIDTH, SCHOOL_WIDTH, REGION_WIDTH, SUBJECT_WIDTH, YEAR_WIDTH };
int total = 0;
for (int i = 0; i < 6; i++) {
total += colWidths[i] + 2;
}
// 7 вертикальных разделителей на 6 столбцов
total += 7;
textfor (int i = 0; i < total; i++) { putchar(ch); } putchar('\n');
}
// Разбивает строку на несколько строк для столбца
static int chunkField(const char *src, int width, char dstLines[][MAX_LINE_WIDTH + 1])
{
char buffer[1024];
strncpy(buffer, src, sizeof(buffer) - 1);
buffer[sizeof(buffer) - 1] = '\0';
textchar line[1024]; line[0] = '\0'; int lineLen = 0; int lineCount = 0; char *token = strtok(buffer, " \t"); while (token) { int tokenLen = utf8_len(token); // Если одно слово больше ширины, режем if (tokenLen > width) { if (lineLen > 0) { strncpy(dstLines[lineCount], line, MAX_LINE_WIDTH); dstLines[lineCount][MAX_LINE_WIDTH] = '\0'; lineCount++; line[0] = '\0'; lineLen = 0; if (lineCount >= MAX_CHUNKS) return lineCount; } size_t byteLen = strlen(token); int wordPos = 0; while (wordPos < (int)byteLen) { char sub[1024]; int subChars = 0; int subBytes = 0; while (wordPos + subBytes < (int)byteLen && subChars < width) { unsigned char c = (unsigned char)token[wordPos + subBytes]; if ((c & 0xC0) != 0x80) { subChars++; if (subChars > width) break; } sub[subBytes] = token[wordPos + subBytes]; subBytes++; } sub[subBytes] = '\0'; strncpy(dstLines[lineCount], sub, MAX_LINE_WIDTH); dstLines[lineCount][MAX_LINE_WIDTH] = '\0'; lineCount++; if (lineCount >= MAX_CHUNKS) return lineCount; wordPos += subBytes; } } else { // Помещается if (lineLen == 0) { strncpy(line, token, sizeof(line) - 1); line[sizeof(line) - 1] = '\0'; lineLen = tokenLen; } else { if (lineLen + 1 + tokenLen <= width) { strcat(line, " "); strcat(line, token); lineLen += 1 + tokenLen; } else { strncpy(dstLines[lineCount], line, MAX_LINE_WIDTH); dstLines[lineCount][MAX_LINE_WIDTH] = '\0'; lineCount++; if (lineCount >= MAX_CHUNKS) return lineCount; strncpy(line, token, sizeof(line) - 1); line[sizeof(line) - 1] = '\0'; lineLen = tokenLen; } } } token = strtok(NULL, " \t"); } if (lineLen > 0) { strncpy(dstLines[lineCount], line, MAX_LINE_WIDTH); dstLines[lineCount][MAX_LINE_WIDTH] = '\0'; lineCount++; } if (lineCount == 0) { strcpy(dstLines[0], ""); lineCount = 1; } return lineCount;
}
static long getRecordCount_local(FILE *fp)
{
fseek(fp, 0, SEEK_END);
long size = ftell(fp);
fseek(fp, 0, SEEK_SET);
return size / (long)sizeof(Winner);
}
// Функция вывода всех записей в табличном виде
void printAll(void)
{
FILE *fp = fopen(filename, "rb");
if (!fp) {
perror("Не удалось открыть файл для чтения");
return;
}
textlong count = getRecordCount_local(fp); if (count == 0) { printf("Файл пуст.\n"); fclose(fp); return; } // Заголовки printSep('='); putchar('|'); printCell("№", INDEX_WIDTH, 1); putchar('|'); printCell("ФИО", FIO_WIDTH, 0); putchar('|'); printCell("Школа", SCHOOL_WIDTH, 1); putchar('|'); printCell("Регион", REGION_WIDTH, 0); putchar('|'); printCell("Предмет",SUBJECT_WIDTH, 0); putchar('|'); printCell("Год", YEAR_WIDTH, 1); puts("|"); printSep('='); // Записи fseek(fp, 0, SEEK_SET); for (long i = 0; i < count; i++) { Winner w; fread(&w, sizeof(Winner), 1, fp); char bufIndex[32], bufSchool[32], bufYear[32]; sprintf(bufIndex, "%ld", i + 1); sprintf(bufSchool, "%d", w.school_number); sprintf(bufYear, "%d", w.year); char linesIndex [MAX_CHUNKS][MAX_LINE_WIDTH + 1]; char linesFIO [MAX_CHUNKS][MAX_LINE_WIDTH + 1]; char linesSchool[MAX_CHUNKS][MAX_LINE_WIDTH + 1]; char linesReg [MAX_CHUNKS][MAX_LINE_WIDTH + 1]; char linesSubj [MAX_CHUNKS][MAX_LINE_WIDTH + 1]; char linesYear [MAX_CHUNKS][MAX_LINE_WIDTH + 1]; int cIndex = chunkField(bufIndex, INDEX_WIDTH, linesIndex ); int cFIO = chunkField(w.fio, FIO_WIDTH, linesFIO ); int cSchool = chunkField(bufSchool, SCHOOL_WIDTH, linesSchool); int cReg = chunkField(w.region, REGION_WIDTH, linesReg ); int cSubj = chunkField(w.subject, SUBJECT_WIDTH, linesSubj ); int cYear = chunkField(bufYear, YEAR_WIDTH, linesYear ); int maxRows = cIndex; if (cFIO > maxRows) maxRows = cFIO; if (cSchool > maxRows) maxRows = cSchool; if (cReg > maxRows) maxRows = cReg; if (cSubj > maxRows) maxRows = cSubj; if (cYear > maxRows) maxRows = cYear; for (int row = 0; row < maxRows; row++) { const char *idxStr = (row < cIndex ) ? linesIndex [row] : ""; const char *fioStr = (row < cFIO ) ? linesFIO [row] : ""; const char *schStr = (row < cSchool) ? linesSchool[row] : ""; const char *regStr = (row < cReg ) ? linesReg [row] : ""; const char *subStr = (row < cSubj ) ? linesSubj [row] : ""; const char *yearStr = (row < cYear ) ? linesYear [row] : ""; putchar('|'); printCell(idxStr, INDEX_WIDTH, 1); putchar('|'); printCell(fioStr, FIO_WIDTH, 0); putchar('|'); printCell(schStr, SCHOOL_WIDTH, 1); putchar('|'); printCell(regStr, REGION_WIDTH, 0); putchar('|'); printCell(subStr, SUBJECT_WIDTH, 0); putchar('|'); printCell(yearStr, YEAR_WIDTH, 1); puts("|"); } printSep('-'); } printSep('='); fclose(fp);
}
Файл 3
#ifndef WINNER_H
#define WINNER_H
// Структура для хранения данных о победителях
typedef struct {
char fio[100]; // ФИО
int school_number; // Номер школы
char region[50]; // Регион
char subject[50]; // Предмет
int year; // Год
} Winner;
#endif // WINNER_H
исправь код так чтобы программа соответствовала заданию: 1. Данные должны храниться в бинарном файле.
2. Каждая операция с данными базы должна быть реализована как функция или набор функций.
3. Выбор и запуск требуемого режима (действия) осуществляется через меню.
4. Реализовать следующие функции обработки данных (каждое действие должно изменять в файле только минимально необходимую часть, а не перезаписывать файл целиком):
∙ добавление записи в файл;
∙ удаление заданной записи из файла по порядковому номеру записи;
∙ поиск записей по заданному пользователем (любому) полю структуры;
∙ редактирование (изменение) заданной записи;
∙ вывод на экран содержимого файла в табличном виде.
5. Структуру (в соответствии с вариантом) определять в отдельном заголовочном файле. С помощью директив условной компиляции определить два способа ввода исходных данных в файл: пользователем с потока ввода на этапе выполнения и из заранее заполненного массива.
Программа должна быть реализована с помощью указателей на функцию.
Ниже приведён вариант кода, в котором учтены требования задания:
winner.h
.main_part.c
(основная логика и меню)c#include <stdio.h> #include <stdlib.h> #include <string.h> #include <locale.h> #include <unistd.h> #include "winner.h" // Подключаем структуру с условной компиляцией // Сообщаем, что они определены в table_part.c extern const char *filename; extern void printAll(void); // --- Прототипы локальных функций --- static long getRecordCount(FILE *fp); static void rewriteInitialData(void); // --- Прототипы функций (операций) --- void addRecord(void); void deleteRecord(void); void searchRecords(void); void editRecord(void); // ------------------------------------------------- // getRecordCount: вспомогательная функция // ------------------------------------------------- static long getRecordCount(FILE *fp) { fseek(fp, 0, SEEK_END); long size = ftell(fp); fseek(fp, 0, SEEK_SET); return size / (long)sizeof(Winner); } // ------------------------------------------------- // rewriteInitialData: инициализация файла // (вызывается в main один раз для начального наполнения) // ------------------------------------------------- static void rewriteInitialData(void) { #if defined(PREDEFINED_DATA) // --- ВАРИАНТ: ПРИНУДИТЕЛЬНО используем массив, игнорируем существование файла --- printf("PREDEFINED_DATA определён: создаём/перезаписываем файл из массива...\n"); FILE *fp = fopen(filename, "wb"); if (!fp) { perror("Не удалось создать файл (predefined)"); exit(EXIT_FAILURE); } Winner predefinedArray[] = { {"Иванов Иван Иванович", 123, "г. Москва", "Математика", 2023}, {"Петров Пётр Петрович", 456, "Красноярский край", "Информатика", 2024}, {"Сидорова Анна Сергеевна", 72, "Ленинградская обл.", "Физика", 2025} }; int n = sizeof(predefinedArray) / sizeof(predefinedArray[0]); for (int i = 0; i < n; i++) { fwrite(&predefinedArray[i], sizeof(Winner), 1, fp); } fclose(fp); printf("Файл перезаписан из массива!\n"); #else // --- ВАРИАНТ: Если макроса нет, спрашиваем пользователя --- // Проверяем, существует ли файл FILE *chk = fopen(filename, "rb"); if (chk) { fclose(chk); printf("Файл уже существует, не перезаписываем.\n"); return; } // Если файла нет, создаём новый и наполняем данными от пользователя FILE *fp = fopen(filename, "wb"); if (!fp) { perror("Не удалось создать файл (user input)"); exit(EXIT_FAILURE); } int n; printf("Файл не найден! Создаём новый. Введите количество начальных записей: "); scanf("%d", &n); getchar(); // убрать \n for (int i = 0; i < n; i++) { Winner w; printf("\n==== Запись #%d ====\n", i + 1); printf("Введите ФИО: "); fgets(w.fio, sizeof(w.fio), stdin); w.fio[strcspn(w.fio, "\n")] = '\0'; // убрать \n printf("Введите номер школы: "); scanf("%d", &w.school_number); getchar(); printf("Введите регион: "); fgets(w.region, sizeof(w.region), stdin); w.region[strcspn(w.region, "\n")] = '\0'; printf("Введите предмет: "); fgets(w.subject, sizeof(w.subject), stdin); w.subject[strcspn(w.subject, "\n")] = '\0'; printf("Введите год проведения: "); scanf("%d", &w.year); getchar(); fwrite(&w, sizeof(Winner), 1, fp); } fclose(fp); printf("Файл создан и заполнен вводом с клавиатуры.\n"); #endif } // ------------------------------------------------- // Реализация функций-операций // ------------------------------------------------- // 1. Добавление записи void addRecord(void) { FILE *fp = fopen(filename, "ab"); if (!fp) { perror("Ошибка открытия файла для добавления"); return; } Winner w; printf("Введите ФИО: "); fgets(w.fio, sizeof(w.fio), stdin); w.fio[strcspn(w.fio, "\n")] = '\0'; printf("Введите номер школы: "); scanf("%d", &w.school_number); getchar(); printf("Введите регион: "); fgets(w.region, sizeof(w.region), stdin); w.region[strcspn(w.region, "\n")] = '\0'; printf("Введите предмет: "); fgets(w.subject, sizeof(w.subject), stdin); w.subject[strcspn(w.subject, "\n")] = '\0'; printf("Введите год проведения: "); scanf("%d", &w.year); getchar(); fwrite(&w, sizeof(Winner), 1, fp); fclose(fp); printf("Запись успешно добавлена.\n"); } // 2. Удаление записи по индексу (1..count) void deleteRecord(void) { FILE *fp = fopen(filename, "r+b"); if (!fp) { perror("Ошибка открытия файла для удаления"); return; } long count = getRecordCount(fp); if (count == 0) { printf("Файл пуст. Удалять нечего.\n"); fclose(fp); return; } printf("Введите номер записи (1..%ld) для удаления: ", count); long idx; scanf("%ld", &idx); getchar(); if (idx < 1 || idx > count) { printf("Некорректный индекс.\n"); fclose(fp); return; } // В вычислениях используем «0-based» индекс long recordIndex = idx - 1; // Если удаляем последнюю запись, просто укорачиваем файл if (recordIndex == count - 1) { fseek(fp, recordIndex * sizeof(Winner), SEEK_SET); fflush(fp); ftruncate(fileno(fp), recordIndex * sizeof(Winner)); } else { // Читаем последнюю запись, копируем её на место удаляемой Winner last; fseek(fp, (count - 1) * sizeof(Winner), SEEK_SET); fread(&last, sizeof(Winner), 1, fp); fseek(fp, recordIndex * sizeof(Winner), SEEK_SET); fwrite(&last, sizeof(Winner), 1, fp); // Укорачиваем файл, чтобы убрать последнюю (дублированную) запись fflush(fp); ftruncate(fileno(fp), (count - 1) * sizeof(Winner)); } fclose(fp); printf("Запись удалена.\n"); } // 3. Поиск записей по любому полю void searchRecords(void) { FILE *fp = fopen(filename, "rb"); if (!fp) { perror("Ошибка открытия файла для поиска"); return; } long count = getRecordCount(fp); if (count == 0) { printf("Файл пуст. Искать нечего.\n"); fclose(fp); return; } printf("Выберите поле для поиска:\n"); printf("1. ФИО\n"); printf("2. Номер школы\n"); printf("3. Регион\n"); printf("4. Предмет\n"); printf("5. Год\n"); printf("Выбор: "); int choice; scanf("%d", &choice); getchar(); // убрать \n char strQuery[100] = {0}; int intQuery = 0; switch (choice) { case 1: printf("Введите подстроку для ФИО: "); fgets(strQuery, sizeof(strQuery), stdin); strQuery[strcspn(strQuery, "\n")] = '\0'; break; case 2: printf("Введите номер школы: "); scanf("%d", &intQuery); getchar(); break; case 3: printf("Введите подстроку для региона: "); fgets(strQuery, sizeof(strQuery), stdin); strQuery[strcspn(strQuery, "\n")] = '\0'; break; case 4: printf("Введите подстроку для предмета: "); fgets(strQuery, sizeof(strQuery), stdin); strQuery[strcspn(strQuery, "\n")] = '\0'; break; case 5: printf("Введите год: "); scanf("%d", &intQuery); getchar(); break; default: printf("Некорректный выбор.\n"); fclose(fp); return; } int found = 0; for (long i = 0; i < count; i++) { Winner w; fread(&w, sizeof(Winner), 1, fp); int match = 0; switch (choice) { case 1: if (strstr(w.fio, strQuery)) match = 1; break; case 2: if (w.school_number == intQuery) match = 1; break; case 3: if (strstr(w.region, strQuery)) match = 1; break; case 4: if (strstr(w.subject, strQuery)) match = 1; break; case 5: if (w.year == intQuery) match = 1; break; } if (match) { found++; printf("\nНайдена запись #%ld:\n", i + 1); printf(" ФИО: %s\n", w.fio); printf(" Школа: %d\n", w.school_number); printf(" Регион: %s\n", w.region); printf(" Предмет: %s\n", w.subject); printf(" Год: %d\n", w.year); printf("-------------------------\n"); } } if (!found) { printf("Ничего не найдено.\n"); } fclose(fp); } // 4. Редактирование записи (по её порядковому номеру) void editRecord(void) { FILE *fp = fopen(filename, "r+b"); if (!fp) { perror("Ошибка открытия файла для редактирования"); return; } long count = getRecordCount(fp); if (count == 0) { printf("Файл пуст. Редактировать нечего.\n"); fclose(fp); return; } printf("Введите номер записи (1..%ld) для редактирования: ", count); long idx; scanf("%ld", &idx); getchar(); if (idx < 1 || idx > count) { printf("Некорректный индекс.\n"); fclose(fp); return; } long recIndex = idx - 1; fseek(fp, recIndex * sizeof(Winner), SEEK_SET); Winner w; fread(&w, sizeof(Winner), 1, fp); printf("Текущие данные:\n"); printf(" ФИО: %s\n", w.fio); printf(" Школа: %d\n", w.school_number); printf(" Регион: %s\n", w.region); printf(" Предмет: %s\n", w.subject); printf(" Год: %d\n", w.year); printf("\nОставьте поле пустым, если не хотите менять.\n"); char buffer[100]; printf("Новое ФИО: "); fgets(buffer, sizeof(buffer), stdin); if (buffer[0] != '\n') { buffer[strcspn(buffer, "\n")] = '\0'; strncpy(w.fio, buffer, sizeof(w.fio) - 1); w.fio[sizeof(w.fio) - 1] = '\0'; } printf("Номер школы (0 — без изменений): "); int tmp; scanf("%d", &tmp); getchar(); if (tmp != 0) { w.school_number = tmp; } printf("Новый регион: "); fgets(buffer, sizeof(buffer), stdin); if (buffer[0] != '\n') { buffer[strcspn(buffer, "\n")] = '\0'; strncpy(w.region, buffer, sizeof(w.region) - 1); w.region[sizeof(w.region) - 1] = '\0'; } printf("Новый предмет: "); fgets(buffer, sizeof(buffer), stdin); if (buffer[0] != '\n') { buffer[strcspn(buffer, "\n")] = '\0'; strncpy(w.subject, buffer, sizeof(w.subject) - 1); w.subject[sizeof(w.subject) - 1] = '\0'; } printf("Новый год (0 — без изменений): "); scanf("%d", &tmp); getchar(); if (tmp != 0) { w.year = tmp; } // Возвращаемся в нужное место и перезаписываем только изменённую запись fseek(fp, recIndex * sizeof(Winner), SEEK_SET); fwrite(&w, sizeof(Winner), 1, fp); fclose(fp); printf("Запись обновлена.\n"); } // ------------------------------------------------- // MAIN // ------------------------------------------------- int main(void) { // Установим локаль (для корректного вывода в консоли Windows/Linux) if (!setlocale(LC_ALL, "ru_RU.UTF-8")) { setlocale(LC_ALL, ""); } // Сразу вызываем инициализацию (перезапись) файла - один раз rewriteInitialData(); // Массив указателей на функции typedef void (*MenuFunc)(void); MenuFunc menuFunctions[] = { addRecord, // 1 deleteRecord, // 2 searchRecords, // 3 editRecord // 4 }; while (1) { printf("\n=========================================\n"); printf("1. Добавить запись\n"); printf("2. Удалить запись\n"); printf("3. Поиск записей\n"); printf("4. Редактировать запись\n"); printf("5. Вывести все записи\n"); printf("6. Выход\n"); printf("=========================================\n"); int choice; printf("Ваш выбор: "); scanf("%d", &choice); getchar(); // убрать \n if (choice == 6) { printf("Завершение.\n"); break; } else if (choice >= 1 && choice <= 5) { if (choice == 5) { // Печать всех записей — функция из второго файла printAll(); } else { // Запуск нужной операции menuFunctions[choice - 1](); } } else { printf("Некорректный ввод.\n"); } } return 0; }
table_part.c
(вывод таблицы и глобальные переменные)c#include <stdio.h> #include <stdlib.h> #include <string.h> #include <locale.h> #include <unistd.h> #include "winner.h" // Подключаем структуру // Глобальная переменная с именем файла (используется во всех модулях) const char *filename = "winners.bin"; // Макросы для ширины столбцов (таблица) #define INDEX_WIDTH 2 #define FIO_WIDTH 25 #define SCHOOL_WIDTH 6 #define REGION_WIDTH 24 #define SUBJECT_WIDTH 20 #define YEAR_WIDTH 4 #define MAX_CHUNKS 50 #define MAX_LINE_WIDTH 100 // ------------------------------------------------- // Вспомогательные функции для печати таблицы // ------------------------------------------------- // Подсчёт видимой длины UTF-8 строки (для выравнивания) static int utf8_len(const char *s) { int n = 0; while (*s) { // Символ считается началом, если его два старших бита не равны 10xxxxxx if (((unsigned char)*s & 0xC0) != 0x80) { n++; } s++; } return n; } // Печать одной «ячейки» с учётом выравнивания // side=1 — выравнивание справа, side=0 — слева static void printCell(const char *text, int width, int side) { putchar(' '); int visibleLen = utf8_len(text); int pad = (visibleLen < width) ? (width - visibleLen) : 0; if (side) { // Выравнивание справа for (int i = 0; i < pad; i++) putchar(' '); fputs(text, stdout); } else { // Выравнивание слева fputs(text, stdout); for (int i = 0; i < pad; i++) putchar(' '); } putchar(' '); } // Печать разделительной строки static void printSep(char ch) { int colWidths[6] = { INDEX_WIDTH, FIO_WIDTH, SCHOOL_WIDTH, REGION_WIDTH, SUBJECT_WIDTH, YEAR_WIDTH }; int total = 0; for (int i = 0; i < 6; i++) { total += colWidths[i] + 2; // ширина + отступы } // 7 вертикальных разделителей на 6 столбцов total += 7; for (int i = 0; i < total; i++) { putchar(ch); } putchar('\n'); } // Разбивает строку на несколько строк по словам, чтобы не выходить за пределы столбца static int chunkField(const char *src, int width, char dstLines[][MAX_LINE_WIDTH + 1]) { char buffer[1024]; strncpy(buffer, src, sizeof(buffer) - 1); buffer[sizeof(buffer) - 1] = '\0'; char line[1024]; line[0] = '\0'; int lineLen = 0; int lineCount = 0; char *token = strtok(buffer, " \t"); while (token) { int tokenLen = utf8_len(token); // Если одно слово больше ширины, режем его посимвольно if (tokenLen > width) { // Сначала, если в буфере что-то уже есть, выведем это if (lineLen > 0) { strncpy(dstLines[lineCount], line, MAX_LINE_WIDTH); dstLines[lineCount][MAX_LINE_WIDTH] = '\0'; lineCount++; line[0] = '\0'; lineLen = 0; if (lineCount >= MAX_CHUNKS) return lineCount; } // Теперь режем слово на кусочки size_t byteLen = strlen(token); int wordPos = 0; while (wordPos < (int)byteLen) { char sub[1024]; int subChars = 0; int subBytes = 0; while (wordPos + subBytes < (int)byteLen && subChars < width) { unsigned char c = (unsigned char)token[wordPos + subBytes]; // Начало нового юникод-символа? if ((c & 0xC0) != 0x80) { subChars++; if (subChars > width) break; } sub[subBytes] = token[wordPos + subBytes]; subBytes++; } sub[subBytes] = '\0'; strncpy(dstLines[lineCount], sub, MAX_LINE_WIDTH); dstLines[lineCount][MAX_LINE_WIDTH] = '\0'; lineCount++; if (lineCount >= MAX_CHUNKS) return lineCount; wordPos += subBytes; } } else { // Если текущее слово целиком помещается в строку if (lineLen == 0) { // Строка пуста — просто вставляем слово strncpy(line, token, sizeof(line) - 1); line[sizeof(line) - 1] = '\0'; lineLen = tokenLen; } else { // Проверяем, влезет ли со вставкой пробела if (lineLen + 1 + tokenLen <= width) { strcat(line, " "); strcat(line, token); lineLen += 1 + tokenLen; } else { // Выводим текущую строку в массив strncpy(dstLines[lineCount], line, MAX_LINE_WIDTH); dstLines[lineCount][MAX_LINE_WIDTH] = '\0'; lineCount++; if (lineCount >= MAX_CHUNKS) return lineCount; // Начинаем новую строку с текущего слова strncpy(line, token, sizeof(line) - 1); line[sizeof(line) - 1] = '\0'; lineLen = tokenLen; } } } token = strtok(NULL, " \t"); } // Если что-то осталось в line — записываем if (lineLen > 0) { strncpy(dstLines[lineCount], line, MAX_LINE_WIDTH); dstLines[lineCount][MAX_LINE_WIDTH] = '\0'; lineCount++; } // Если вообще ничего не было (пустая строка), кладём пустую if (lineCount == 0) { strcpy(dstLines[0], ""); lineCount = 1; } return lineCount; } // Локальный getRecordCount static long getRecordCount_local(FILE *fp) { fseek(fp, 0, SEEK_END); long size = ftell(fp); fseek(fp, 0, SEEK_SET); return size / (long)sizeof(Winner); } // ------------------------------------------------- // printAll: вывод всех записей в табличном виде // ------------------------------------------------- void printAll(void) { FILE *fp = fopen(filename, "rb"); if (!fp) { perror("Не удалось открыть файл для чтения"); return; } long count = getRecordCount_local(fp); if (count == 0) { printf("Файл пуст.\n"); fclose(fp); return; } // Заголовки printSep('='); putchar('|'); printCell("№", INDEX_WIDTH, 1); putchar('|'); printCell("ФИО", FIO_WIDTH, 0); putchar('|'); printCell("Школа", SCHOOL_WIDTH, 1); putchar('|'); printCell("Регион", REGION_WIDTH, 0); putchar('|'); printCell("Предмет", SUBJECT_WIDTH, 0); putchar('|'); printCell("Год", YEAR_WIDTH, 1); puts("|"); printSep('='); // Читаем каждую запись и выводим fseek(fp, 0, SEEK_SET); for (long i = 0; i < count; i++) { Winner w; fread(&w, sizeof(Winner), 1, fp); char bufIndex[32], bufSchool[32], bufYear[32]; sprintf(bufIndex, "%ld", i + 1); sprintf(bufSchool, "%d", w.school_number); sprintf(bufYear, "%d", w.year); // Массивы строк (подстроки) для построчного разбиения char linesIndex [MAX_CHUNKS][MAX_LINE_WIDTH + 1]; char linesFIO [MAX_CHUNKS][MAX_LINE_WIDTH + 1]; char linesSchool[MAX_CHUNKS][MAX_LINE_WIDTH + 1]; char linesReg [MAX_CHUNKS][MAX_LINE_WIDTH + 1]; char linesSubj [MAX_CHUNKS][MAX_LINE_WIDTH + 1]; char linesYear [MAX_CHUNKS][MAX_LINE_WIDTH + 1]; // Разбиваем на "строки", чтобы не выходить за ширину столбца int cIndex = chunkField(bufIndex, INDEX_WIDTH, linesIndex ); int cFIO = chunkField(w.fio, FIO_WIDTH, linesFIO ); int cSchool = chunkField(bufSchool, SCHOOL_WIDTH, linesSchool); int cReg = chunkField(w.region, REGION_WIDTH, linesReg ); int cSubj = chunkField(w.subject, SUBJECT_WIDTH, linesSubj ); int cYear = chunkField(bufYear, YEAR_WIDTH, linesYear ); // Определяем, сколько строк нужно вывести для данной записи int maxRows = cIndex; if (cFIO > maxRows) maxRows = cFIO; if (cSchool > maxRows) maxRows = cSchool; if (cReg > maxRows) maxRows = cReg; if (cSubj > maxRows) maxRows = cSubj; if (cYear > maxRows) maxRows = cYear; // Построчно печатаем for (int row = 0; row < maxRows; row++) { const char *idxStr = (row < cIndex ) ? linesIndex [row] : ""; const char *fioStr = (row < cFIO ) ? linesFIO [row] : ""; const char *schStr = (row < cSchool) ? linesSchool[row] : ""; const char *regStr = (row < cReg ) ? linesReg [row] : ""; const char *subStr = (row < cSubj ) ? linesSubj [row] : ""; const char *yearStr = (row < cYear ) ? linesYear [row] : ""; putchar('|'); printCell(idxStr, INDEX_WIDTH, 1); putchar('|'); printCell(fioStr, FIO_WIDTH, 0); putchar('|'); printCell(schStr, SCHOOL_WIDTH, 1); putchar('|'); printCell(regStr, REGION_WIDTH, 0); putchar('|'); printCell(subStr, SUBJECT_WIDTH, 0); putchar('|'); printCell(yearStr, YEAR_WIDTH, 1); puts("|"); } printSep('-'); } printSep('='); fclose(fp); }
winner.h
(структура и условная компиляция)c#ifndef WINNER_H #define WINNER_H // Структура для хранения данных о победителях typedef struct { char fio[100]; // ФИО int school_number; // Номер школы char region[50]; // Регион char subject[50]; // Предмет int year; // Год } Winner; // -- Директива условной компиляции -- // Если раскомментировать строку ниже, программа при запуске // всегда перезапишет файл из массива `predefinedArray`: // #define PREDEFINED_DATA #endif // WINNER_H
Бинарный файл.
Все операции (чтение/запись) ведутся через fwrite
/ fread
по структурам Winner
. Имя файла хранится в глобальной переменной filename = "winners.bin"
.
Функции для операций:
addRecord
— добавляет запись в конец (режим "ab"
).deleteRecord
— удаляет запись по индексу, «затыкая дыру» последней записью, а затем укорачивает файл с помощью ftruncate
.searchRecords
— последовательно читает и сравнивает поля.editRecord
— считывает нужную запись, частично изменяет поля и записывает обратно только её одну.printAll
(во втором файле) — выводит весь файл в табличном виде, разбивая длинные поля, чтобы не выходить за границы.Меню:
В main
организовано меню с помощью массива из 4 указателей на функции (добавление, удаление, поиск, редактирование), а 5‑й пункт вызывает printAll
.
Условная компиляция:
winner.h
раскомментировать строку #define PREDEFINED_DATA
, тогда при запуске программа перезапишет файл из массива predefinedArray
(для теста).Минимальная модификация файла:
addRecord
) запись дописывается в конец.deleteRecord
) меняется лишь одна запись (замена удаляемой на последнюю) и укорачивается файл на одну структуру.editRecord
) меняется только нужный блок, перезаписи всего файла не происходит.searchRecords
) и выводе (printAll
) происходит только чтение.Таким образом, программа удовлетворяет всем требованиям:
как в предыдущей программе осуществляется переход между двумя режимами ввода исходных данных в файл: пользователем с потока ввода на этапе выполнения и из заранее заполненного массива.
В программе переключатель между двумя способами первоначального заполнения файла сделан через макрос предпроцессора PREDEFINED_DATA
.
c/* winner.h */ ... //#define PREDEFINED_DATA ← строка закомментирована по‑умолчанию
‑DPREDEFINED_DATA
, макрос будет «виден» всем *.c‑файлам, где подключён winner.h
.В функции rewriteInitialData()
(файл main_part.c
) стоит условная компиляция:
cstatic void rewriteInitialData(void) { #if defined(PREDEFINED_DATA) /* Вариант 1: жёстко перезаписываем файл готовым массивом predefinedArray[] */ ... #else /* Вариант 2: если файла ещё нет – спрашиваем пользователя, иначе оставляем существующие данные нетронутыми */ ... #endif }
Когда PREDEFINED_DATA
определён
#if ... #else
компилирует «верхнюю» ветку.winners.bin
создаётся заново и заполняется элементами массива predefinedArray[]
.Когда PREDEFINED_DATA
не определён
Что сделать | Как это выглядит |
---|---|
Использовать массив | 1) В winner.h убрать // перед #define PREDEFINED_DATA или<br>2) при компиляции добавить флаг: gcc ... -DPREDEFINED_DATA ... |
Использовать ввод с клавиатуры | Оставить строку закомментированной и не передавать -DPREDEFINED_DATA . |
Таким образом выбор режима происходит во время компиляции: макрос задаёт, какая часть кода попадёт в итоговый исполняемый файл. В работе программы ничего переключать не нужно.
напиши подробные комментарии к программе
Файл 1
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <locale.h>
#include <unistd.h>
#include "winner.h" // Подключаем структуру с условной компиляцией
// Сообщаем, что они определены в table_part.c
extern const char *filename;
extern void printAll(void);
// --- Прототипы локальных функций ---
static long getRecordCount(FILE *fp);
static void rewriteInitialData(void);
// --- Прототипы функций (операций) ---
void addRecord(void);
void deleteRecord(void);
void searchRecords(void);
void editRecord(void);
// -------------------------------------------------
// getRecordCount: вспомогательная функция
// -------------------------------------------------
static long getRecordCount(FILE *fp)
{
fseek(fp, 0, SEEK_END);
long size = ftell(fp);
fseek(fp, 0, SEEK_SET);
return size / (long)sizeof(Winner);
}
// -------------------------------------------------
// rewriteInitialData: инициализация файла
// (вызывается в main один раз для начального наполнения)
// -------------------------------------------------
static void rewriteInitialData(void) {
#if defined(PREDEFINED_DATA)
// --- ВАРИАНТ: ПРИНУДИТЕЛЬНО используем массив, игнорируем существование файла ---
printf("PREDEFINED_DATA определён: создаём/перезаписываем файл из массива...\n");
textFILE *fp = fopen(filename, "wb"); if (!fp) { perror("Не удалось создать файл (predefined)"); exit(EXIT_FAILURE); } Winner predefinedArray[] = { {"Иванов Иван Иванович", 123, "г. Москва", "Математика", 2023}, {"Петров Пётр Петрович", 456, "Красноярский край", "Информатика", 2024}, {"Сидорова Анна Сергеевна", 72, "Ленинградская обл.", "Физика", 2025} }; int n = sizeof(predefinedArray) / sizeof(predefinedArray[0]); for (int i = 0; i < n; i++) { fwrite(&predefinedArray[i], sizeof(Winner), 1, fp); } fclose(fp); printf("Файл перезаписан из массива!\n");
#else
// --- ВАРИАНТ: Если макроса нет, спрашиваем пользователя ---
// Проверяем, существует ли файл
FILE *chk = fopen(filename, "rb");
if (chk) {
fclose(chk);
printf("Файл уже существует, не перезаписываем.\n");
return;
}
text// Если файла нет, создаём новый и наполняем данными от пользователя FILE *fp = fopen(filename, "wb"); if (!fp) { perror("Не удалось создать файл (user input)"); exit(EXIT_FAILURE); } int n; printf("Файл не найден! Создаём новый. Введите количество начальных записей: "); scanf("%d", &n); getchar(); // убрать \n for (int i = 0; i < n; i++) { Winner w; printf("\n==== Запись #%d ====\n", i + 1); printf("Введите ФИО: "); fgets(w.fio, sizeof(w.fio), stdin); w.fio[strcspn(w.fio, "\n")] = '\0'; // убрать \n printf("Введите номер школы: "); scanf("%d", &w.school_number); getchar(); printf("Введите регион: "); fgets(w.region, sizeof(w.region), stdin); w.region[strcspn(w.region, "\n")] = '\0'; printf("Введите предмет: "); fgets(w.subject, sizeof(w.subject), stdin); w.subject[strcspn(w.subject, "\n")] = '\0'; printf("Введите год проведения: "); scanf("%d", &w.year); getchar(); fwrite(&w, sizeof(Winner), 1, fp); } fclose(fp); printf("Файл создан и заполнен вводом с клавиатуры.\n");
#endif
}
// -------------------------------------------------
// Реализация функций-операций
// -------------------------------------------------
// 1. Добавление записи
void addRecord(void) {
FILE *fp = fopen(filename, "ab");
if (!fp) {
perror("Ошибка открытия файла для добавления");
return;
}
textWinner w; printf("Введите ФИО: "); fgets(w.fio, sizeof(w.fio), stdin); w.fio[strcspn(w.fio, "\n")] = '\0'; printf("Введите номер школы: "); scanf("%d", &w.school_number); getchar(); printf("Введите регион: "); fgets(w.region, sizeof(w.region), stdin); w.region[strcspn(w.region, "\n")] = '\0'; printf("Введите предмет: "); fgets(w.subject, sizeof(w.subject), stdin); w.subject[strcspn(w.subject, "\n")] = '\0'; printf("Введите год проведения: "); scanf("%d", &w.year); getchar(); fwrite(&w, sizeof(Winner), 1, fp); fclose(fp); printf("Запись успешно добавлена.\n");
}
// 2. Удаление записи по индексу (1..count)
void deleteRecord(void) {
FILE *fp = fopen(filename, "r+b");
if (!fp) {
perror("Ошибка открытия файла для удаления");
return;
}
textlong count = getRecordCount(fp); if (count == 0) { printf("Файл пуст. Удалять нечего.\n"); fclose(fp); return; } printf("Введите номер записи (1..%ld) для удаления: ", count); long idx; scanf("%ld", &idx); getchar(); if (idx < 1 || idx > count) { printf("Некорректный индекс.\n"); fclose(fp); return; } // В вычислениях используем «0-based» индекс long recordIndex = idx - 1; // Если удаляем последнюю запись, просто укорачиваем файл if (recordIndex == count - 1) { fseek(fp, recordIndex * sizeof(Winner), SEEK_SET); fflush(fp); ftruncate(fileno(fp), recordIndex * sizeof(Winner)); } else { // Читаем последнюю запись, копируем её на место удаляемой Winner last; fseek(fp, (count - 1) * sizeof(Winner), SEEK_SET); fread(&last, sizeof(Winner), 1, fp); fseek(fp, recordIndex * sizeof(Winner), SEEK_SET); fwrite(&last, sizeof(Winner), 1, fp); // Укорачиваем файл, чтобы убрать последнюю (дублированную) запись fflush(fp); ftruncate(fileno(fp), (count - 1) * sizeof(Winner)); } fclose(fp); printf("Запись удалена.\n");
}
// 3. Поиск записей по любому полю
void searchRecords(void) {
FILE *fp = fopen(filename, "rb");
if (!fp) {
perror("Ошибка открытия файла для поиска");
return;
}
long count = getRecordCount(fp);
if (count == 0) {
printf("Файл пуст. Искать нечего.\n");
fclose(fp);
return;
}
textprintf("Выберите поле для поиска:\n"); printf("1. ФИО\n"); printf("2. Номер школы\n"); printf("3. Регион\n"); printf("4. Предмет\n"); printf("5. Год\n"); printf("Выбор: "); int choice; scanf("%d", &choice); getchar(); // убрать \n char strQuery[100] = {0}; int intQuery = 0; switch (choice) { case 1: printf("Введите подстроку для ФИО: "); fgets(strQuery, sizeof(strQuery), stdin); strQuery[strcspn(strQuery, "\n")] = '\0'; break; case 2: printf("Введите номер школы: "); scanf("%d", &intQuery); getchar(); break; case 3: printf("Введите подстроку для региона: "); fgets(strQuery, sizeof(strQuery), stdin); strQuery[strcspn(strQuery, "\n")] = '\0'; break; case 4: printf("Введите подстроку для предмета: "); fgets(strQuery, sizeof(strQuery), stdin); strQuery[strcspn(strQuery, "\n")] = '\0'; break; case 5: printf("Введите год: "); scanf("%d", &intQuery); getchar(); break; default: printf("Некорректный выбор.\n"); fclose(fp); return; } int found = 0; for (long i = 0; i < count; i++) { Winner w; fread(&w, sizeof(Winner), 1, fp); int match = 0; switch (choice) { case 1: if (strstr(w.fio, strQuery)) match = 1; break; case 2: if (w.school_number == intQuery) match = 1; break; case 3: if (strstr(w.region, strQuery)) match = 1; break; case 4: if (strstr(w.subject, strQuery)) match = 1; break; case 5: if (w.year == intQuery) match = 1; break; } if (match) { found++; printf("\nНайдена запись #%ld:\n", i + 1); printf(" ФИО: %s\n", w.fio); printf(" Школа: %d\n", w.school_number); printf(" Регион: %s\n", w.region); printf(" Предмет: %s\n", w.subject); printf(" Год: %d\n", w.year); printf("-------------------------\n"); } } if (!found) { printf("Ничего не найдено.\n"); } fclose(fp);
}
// 4. Редактирование записи (по её порядковому номеру)
void editRecord(void) {
FILE *fp = fopen(filename, "r+b");
if (!fp) {
perror("Ошибка открытия файла для редактирования");
return;
}
long count = getRecordCount(fp);
if (count == 0) {
printf("Файл пуст. Редактировать нечего.\n");
fclose(fp);
return;
}
textprintf("Введите номер записи (1..%ld) для редактирования: ", count); long idx; scanf("%ld", &idx); getchar(); if (idx < 1 || idx > count) { printf("Некорректный индекс.\n"); fclose(fp); return; } long recIndex = idx - 1; fseek(fp, recIndex * sizeof(Winner), SEEK_SET); Winner w; fread(&w, sizeof(Winner), 1, fp); printf("Текущие данные:\n"); printf(" ФИО: %s\n", w.fio); printf(" Школа: %d\n", w.school_number); printf(" Регион: %s\n", w.region); printf(" Предмет: %s\n", w.subject); printf(" Год: %d\n", w.year); printf("\nОставьте поле пустым, если не хотите менять.\n"); char buffer[100]; printf("Новое ФИО: "); fgets(buffer, sizeof(buffer), stdin); if (buffer[0] != '\n') { buffer[strcspn(buffer, "\n")] = '\0'; strncpy(w.fio, buffer, sizeof(w.fio) - 1); w.fio[sizeof(w.fio) - 1] = '\0'; } printf("Номер школы (0 — без изменений): "); int tmp; scanf("%d", &tmp); getchar(); if (tmp != 0) { w.school_number = tmp; } printf("Новый регион: "); fgets(buffer, sizeof(buffer), stdin); if (buffer[0] != '\n') { buffer[strcspn(buffer, "\n")] = '\0'; strncpy(w.region, buffer, sizeof(w.region) - 1); w.region[sizeof(w.region) - 1] = '\0'; } printf("Новый предмет: "); fgets(buffer, sizeof(buffer), stdin); if (buffer[0] != '\n') { buffer[strcspn(buffer, "\n")] = '\0'; strncpy(w.subject, buffer, sizeof(w.subject) - 1); w.subject[sizeof(w.subject) - 1] = '\0'; } printf("Новый год (0 — без изменений): "); scanf("%d", &tmp); getchar(); if (tmp != 0) { w.year = tmp; } // Возвращаемся в нужное место и перезаписываем только изменённую запись fseek(fp, recIndex * sizeof(Winner), SEEK_SET); fwrite(&w, sizeof(Winner), 1, fp); fclose(fp); printf("Запись обновлена.\n");
}
// -------------------------------------------------
// MAIN
// -------------------------------------------------
int main(void)
{
// Установим локаль (для корректного вывода в консоли Windows/Linux)
if (!setlocale(LC_ALL, "ru_RU.UTF-8")) {
setlocale(LC_ALL, "");
}
text// Сразу вызываем инициализацию (перезапись) файла - один раз rewriteInitialData(); // Массив указателей на функции typedef void (*MenuFunc)(void); MenuFunc menuFunctions[] = { addRecord, // 1 deleteRecord, // 2 searchRecords, // 3 editRecord // 4 }; while (1) { printf("\n=========================================\n"); printf("1. Добавить запись\n"); printf("2. Удалить запись\n"); printf("3. Поиск записей\n"); printf("4. Редактировать запись\n"); printf("5. Вывести все записи\n"); printf("6. Выход\n"); printf("=========================================\n"); int choice; printf("Ваш выбор: "); scanf("%d", &choice); getchar(); // убрать \n if (choice == 6) { printf("Завершение.\n"); break; } else if (choice >= 1 && choice <= 5) { if (choice == 5) { // Печать всех записей — функция из второго файла printAll(); } else { // Запуск нужной операции menuFunctions[choice - 1](); } } else { printf("Некорректный ввод.\n"); } } return 0;
}
Файл 2
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <locale.h>
#include <unistd.h>
#include "winner.h" // Подключаем структуру
// Глобальная переменная с именем файла (используется во всех модулях)
const char *filename = "winners.bin";
// Макросы для ширины столбцов (таблица)
#define INDEX_WIDTH 2
#define FIO_WIDTH 25
#define SCHOOL_WIDTH 6
#define REGION_WIDTH 24
#define SUBJECT_WIDTH 20
#define YEAR_WIDTH 4
#define MAX_CHUNKS 50
#define MAX_LINE_WIDTH 100
// -------------------------------------------------
// Вспомогательные функции для печати таблицы
// -------------------------------------------------
// Подсчёт видимой длины UTF-8 строки (для выравнивания)
static int utf8_len(const char *s)
{
int n = 0;
while (*s) {
// Символ считается началом, если его два старших бита не равны 10xxxxxx
if (((unsigned char)*s & 0xC0) != 0x80) {
n++;
}
s++;
}
return n;
}
// Печать одной «ячейки» с учётом выравнивания
// side=1 — выравнивание справа, side=0 — слева
static void printCell(const char *text, int width, int side)
{
putchar(' ');
int visibleLen = utf8_len(text);
int pad = (visibleLen < width) ? (width - visibleLen) : 0;
textif (side) { // Выравнивание справа for (int i = 0; i < pad; i++) putchar(' '); fputs(text, stdout); } else { // Выравнивание слева fputs(text, stdout); for (int i = 0; i < pad; i++) putchar(' '); } putchar(' ');
}
// Печать разделительной строки
static void printSep(char ch)
{
int colWidths[6] = { INDEX_WIDTH, FIO_WIDTH, SCHOOL_WIDTH, REGION_WIDTH, SUBJECT_WIDTH, YEAR_WIDTH };
int total = 0;
for (int i = 0; i < 6; i++) {
total += colWidths[i] + 2; // ширина + отступы
}
// 7 вертикальных разделителей на 6 столбцов
total += 7;
textfor (int i = 0; i < total; i++) { putchar(ch); } putchar('\n');
}
// Разбивает строку на несколько строк по словам, чтобы не выходить за пределы столбца
static int chunkField(const char *src, int width, char dstLines[][MAX_LINE_WIDTH + 1])
{
char buffer[1024];
strncpy(buffer, src, sizeof(buffer) - 1);
buffer[sizeof(buffer) - 1] = '\0';
textchar line[1024]; line[0] = '\0'; int lineLen = 0; int lineCount = 0; char *token = strtok(buffer, " \t"); while (token) { int tokenLen = utf8_len(token); // Если одно слово больше ширины, режем его посимвольно if (tokenLen > width) { // Сначала, если в буфере что-то уже есть, выведем это if (lineLen > 0) { strncpy(dstLines[lineCount], line, MAX_LINE_WIDTH); dstLines[lineCount][MAX_LINE_WIDTH] = '\0'; lineCount++; line[0] = '\0'; lineLen = 0; if (lineCount >= MAX_CHUNKS) return lineCount; } // Теперь режем слово на кусочки size_t byteLen = strlen(token); int wordPos = 0; while (wordPos < (int)byteLen) { char sub[1024]; int subChars = 0; int subBytes = 0; while (wordPos + subBytes < (int)byteLen && subChars < width) { unsigned char c = (unsigned char)token[wordPos + subBytes]; // Начало нового юникод-символа? if ((c & 0xC0) != 0x80) { subChars++; if (subChars > width) break; } sub[subBytes] = token[wordPos + subBytes]; subBytes++; } sub[subBytes] = '\0'; strncpy(dstLines[lineCount], sub, MAX_LINE_WIDTH); dstLines[lineCount][MAX_LINE_WIDTH] = '\0'; lineCount++; if (lineCount >= MAX_CHUNKS) return lineCount; wordPos += subBytes; } } else { // Если текущее слово целиком помещается в строку if (lineLen == 0) { // Строка пуста — просто вставляем слово strncpy(line, token, sizeof(line) - 1); line[sizeof(line) - 1] = '\0'; lineLen = tokenLen; } else { // Проверяем, влезет ли со вставкой пробела if (lineLen + 1 + tokenLen <= width) { strcat(line, " "); strcat(line, token); lineLen += 1 + tokenLen; } else { // Выводим текущую строку в массив strncpy(dstLines[lineCount], line, MAX_LINE_WIDTH); dstLines[lineCount][MAX_LINE_WIDTH] = '\0'; lineCount++; if (lineCount >= MAX_CHUNKS) return lineCount; // Начинаем новую строку с текущего слова strncpy(line, token, sizeof(line) - 1); line[sizeof(line) - 1] = '\0'; lineLen = tokenLen; } } } token = strtok(NULL, " \t"); } // Если что-то осталось в line — записываем if (lineLen > 0) { strncpy(dstLines[lineCount], line, MAX_LINE_WIDTH); dstLines[lineCount][MAX_LINE_WIDTH] = '\0'; lineCount++; } // Если вообще ничего не было (пустая строка), кладём пустую if (lineCount == 0) { strcpy(dstLines[0], ""); lineCount = 1; } return lineCount;
}
// Локальный getRecordCount
static long getRecordCount_local(FILE *fp)
{
fseek(fp, 0, SEEK_END);
long size = ftell(fp);
fseek(fp, 0, SEEK_SET);
return size / (long)sizeof(Winner);
}
// -------------------------------------------------
// printAll: вывод всех записей в табличном виде
// -------------------------------------------------
void printAll(void)
{
FILE *fp = fopen(filename, "rb");
if (!fp) {
perror("Не удалось открыть файл для чтения");
return;
}
textlong count = getRecordCount_local(fp); if (count == 0) { printf("Файл пуст.\n"); fclose(fp); return; } // Заголовки printSep('='); putchar('|'); printCell("№", INDEX_WIDTH, 1); putchar('|'); printCell("ФИО", FIO_WIDTH, 0); putchar('|'); printCell("Школа", SCHOOL_WIDTH, 1); putchar('|'); printCell("Регион", REGION_WIDTH, 0); putchar('|'); printCell("Предмет", SUBJECT_WIDTH, 0); putchar('|'); printCell("Год", YEAR_WIDTH, 1); puts("|"); printSep('='); // Читаем каждую запись и выводим fseek(fp, 0, SEEK_SET); for (long i = 0; i < count; i++) { Winner w; fread(&w, sizeof(Winner), 1, fp); char bufIndex[32], bufSchool[32], bufYear[32]; sprintf(bufIndex, "%ld", i + 1); sprintf(bufSchool, "%d", w.school_number); sprintf(bufYear, "%d", w.year); // Массивы строк (подстроки) для построчного разбиения char linesIndex [MAX_CHUNKS][MAX_LINE_WIDTH + 1]; char linesFIO [MAX_CHUNKS][MAX_LINE_WIDTH + 1]; char linesSchool[MAX_CHUNKS][MAX_LINE_WIDTH + 1]; char linesReg [MAX_CHUNKS][MAX_LINE_WIDTH + 1]; char linesSubj [MAX_CHUNKS][MAX_LINE_WIDTH + 1]; char linesYear [MAX_CHUNKS][MAX_LINE_WIDTH + 1]; // Разбиваем на "строки", чтобы не выходить за ширину столбца int cIndex = chunkField(bufIndex, INDEX_WIDTH, linesIndex ); int cFIO = chunkField(w.fio, FIO_WIDTH, linesFIO ); int cSchool = chunkField(bufSchool, SCHOOL_WIDTH, linesSchool); int cReg = chunkField(w.region, REGION_WIDTH, linesReg ); int cSubj = chunkField(w.subject, SUBJECT_WIDTH, linesSubj ); int cYear = chunkField(bufYear, YEAR_WIDTH, linesYear ); // Определяем, сколько строк нужно вывести для данной записи int maxRows = cIndex; if (cFIO > maxRows) maxRows = cFIO; if (cSchool > maxRows) maxRows = cSchool; if (cReg > maxRows) maxRows = cReg; if (cSubj > maxRows) maxRows = cSubj; if (cYear > maxRows) maxRows = cYear; // Построчно печатаем for (int row = 0; row < maxRows; row++) { const char *idxStr = (row < cIndex ) ? linesIndex [row] : ""; const char *fioStr = (row < cFIO ) ? linesFIO [row] : ""; const char *schStr = (row < cSchool) ? linesSchool[row] : ""; const char *regStr = (row < cReg ) ? linesReg [row] : ""; const char *subStr = (row < cSubj ) ? linesSubj [row] : ""; const char *yearStr = (row < cYear ) ? linesYear [row] : ""; putchar('|'); printCell(idxStr, INDEX_WIDTH, 1); putchar('|'); printCell(fioStr, FIO_WIDTH, 0); putchar('|'); printCell(schStr, SCHOOL_WIDTH, 1); putchar('|'); printCell(regStr, REGION_WIDTH, 0); putchar('|'); printCell(subStr, SUBJECT_WIDTH, 0); putchar('|'); printCell(yearStr, YEAR_WIDTH, 1); puts("|"); } printSep('-'); } printSep('='); fclose(fp);
}
Файл 3
#ifndef WINNER_H
#define WINNER_H
// Структура для хранения данных о победителях
typedef struct {
char fio[100]; // ФИО
int school_number; // Номер школы
char region[50]; // Регион
char subject[50]; // Предмет
int year; // Год
} Winner;
// -- Директива условной компиляции --
// Если раскомментировать строку ниже, программа при запуске
// всегда перезапишет файл из массива predefinedArray
:
#define PREDEFINED_DATA
#endif // WINNER_H
Ниже приведён код с подробными комментариями на русском языке.
main_part.c
c#include <stdio.h> #include <stdlib.h> #include <string.h> #include <locale.h> #include <unistd.h> #include "winner.h" // Подключаем заголовочный файл со структурой Winner и условной компиляцией // Эти объекты (переменная filename и функция printAll) объявлены в другом модуле (table_part.c) extern const char *filename; extern void printAll(void); // ---------------------------------------------- // Прототипы локальных (внутренних) функций // ---------------------------------------------- static long getRecordCount(FILE *fp); // Подсчёт количества записей в файле static void rewriteInitialData(void); // Инициализация файла (создание/перезапись) // ---------------------------------------------- // Прототипы функций (операций над базой) // ---------------------------------------------- void addRecord(void); // Добавление записи void deleteRecord(void); // Удаление записи void searchRecords(void); // Поиск записей void editRecord(void); // Редактирование записи // =========================================================================== // getRecordCount: подсчитывает, сколько структур Winner хранится в указанном файле // =========================================================================== static long getRecordCount(FILE *fp) { // Переходим в конец файла и считаем, сколько байт fseek(fp, 0, SEEK_END); long size = ftell(fp); // Возвращаемся в начало fseek(fp, 0, SEEK_SET); // Количество записей = общий размер / размер одной структуры return size / (long)sizeof(Winner); } // =========================================================================== // rewriteInitialData: инициализация файла (создаём/перезаписываем при необходимости) // Вызывается один раз в main() для наполнения файла начальными данными // =========================================================================== static void rewriteInitialData(void) { #if defined(PREDEFINED_DATA) //---------------------------------------------------------------------- // Режим, когда мы ПРИНУДИТЕЛЬНО перезаписываем файл из заранее // определённого массива (macros PREDEFINED_DATA) //---------------------------------------------------------------------- printf("PREDEFINED_DATA определён: создаём/перезаписываем файл из массива...\n"); // Открываем (или создаём) файл "winners.bin" в режиме "wb" (write binary), // тем самым удаляя его предыдущее содержимое FILE *fp = fopen(filename, "wb"); if (!fp) { perror("Не удалось создать файл (predefined)"); exit(EXIT_FAILURE); } // Массив готовых записей Winner predefinedArray[] = { {"Иванов Иван Иванович", 123, "г. Москва", "Математика", 2023}, {"Петров Пётр Петрович", 456, "Красноярский край", "Информатика", 2024}, {"Сидорова Анна Сергеевна", 72, "Ленинградская обл.", "Физика", 2025} }; int n = sizeof(predefinedArray) / sizeof(predefinedArray[0]); // Определяем, сколько элементов в массиве // Записываем все элементы массива в файл for (int i = 0; i < n; i++) { fwrite(&predefinedArray[i], sizeof(Winner), 1, fp); } fclose(fp); printf("Файл перезаписан из массива!\n"); #else //---------------------------------------------------------------------- // Режим, когда PREDEFINED_DATA НЕ определён. // Мы спрашиваем у пользователя, сколько записей ввести, и создаём файл. // Но если файл уже существует, ничего не трогаем. //---------------------------------------------------------------------- // Сначала проверяем, существует ли уже файл FILE *chk = fopen(filename, "rb"); if (chk) { // Если открылся в режиме чтения, значит файл действительно есть fclose(chk); printf("Файл уже существует, не перезаписываем.\n"); return; } // Если файла не было, создаём новый файл в режиме "wb" FILE *fp = fopen(filename, "wb"); if (!fp) { perror("Не удалось создать файл (user input)"); exit(EXIT_FAILURE); } int n; // Спрашиваем у пользователя, сколько записей он хочет ввести printf("Файл не найден! Создаём новый. Введите количество начальных записей: "); scanf("%d", &n); getchar(); // Считываем и отбрасываем символ перевода строки // Поочерёдно вводим нужное количество записей for (int i = 0; i < n; i++) { Winner w; printf("\n==== Запись #%d ====\n", i + 1); printf("Введите ФИО: "); fgets(w.fio, sizeof(w.fio), stdin); w.fio[strcspn(w.fio, "\n")] = '\0'; // Убираем '\n' из конца printf("Введите номер школы: "); scanf("%d", &w.school_number); getchar(); // удаляем \n printf("Введите регион: "); fgets(w.region, sizeof(w.region), stdin); w.region[strcspn(w.region, "\n")] = '\0'; printf("Введите предмет: "); fgets(w.subject, sizeof(w.subject), stdin); w.subject[strcspn(w.subject, "\n")] = '\0'; printf("Введите год проведения: "); scanf("%d", &w.year); getchar(); // Записываем заполненную структуру в бинарный файл fwrite(&w, sizeof(Winner), 1, fp); } fclose(fp); printf("Файл создан и заполнен вводом с клавиатуры.\n"); #endif } // =========================================================================== // Реализация функций-операций // =========================================================================== // ------------------------------------------------- // 1. addRecord: добавление новой записи в конец файла // ------------------------------------------------- void addRecord(void) { // Открываем файл в режиме "ab" (append binary), чтобы дописать запись в конец FILE *fp = fopen(filename, "ab"); if (!fp) { perror("Ошибка открытия файла для добавления"); return; } // Создаём структуру, считываем с клавиатуры поля Winner w; printf("Введите ФИО: "); fgets(w.fio, sizeof(w.fio), stdin); w.fio[strcspn(w.fio, "\n")] = '\0'; printf("Введите номер школы: "); scanf("%d", &w.school_number); getchar(); printf("Введите регион: "); fgets(w.region, sizeof(w.region), stdin); w.region[strcspn(w.region, "\n")] = '\0'; printf("Введите предмет: "); fgets(w.subject, sizeof(w.subject), stdin); w.subject[strcspn(w.subject, "\n")] = '\0'; printf("Введите год проведения: "); scanf("%d", &w.year); getchar(); // Сохраняем запись в файл fwrite(&w, sizeof(Winner), 1, fp); fclose(fp); printf("Запись успешно добавлена.\n"); } // ------------------------------------------------- // 2. deleteRecord: удаление записи по порядковому номеру (1..count) // ------------------------------------------------- void deleteRecord(void) { // Открываем файл в режиме "r+b" (читать+писать в бинарном формате) FILE *fp = fopen(filename, "r+b"); if (!fp) { perror("Ошибка открытия файла для удаления"); return; } // Считаем, сколько записей всего long count = getRecordCount(fp); if (count == 0) { printf("Файл пуст. Удалять нечего.\n"); fclose(fp); return; } // Спрашиваем у пользователя, какую запись удалить printf("Введите номер записи (1..%ld) для удаления: ", count); long idx; scanf("%ld", &idx); getchar(); // Проверяем корректность индекса if (idx < 1 || idx > count) { printf("Некорректный индекс.\n"); fclose(fp); return; } // Индекс в формате "0-based" (так удобнее для чтения/записи в бинарном файле) long recordIndex = idx - 1; // Если удаляем последнюю запись: // Достаточно сдвинуться на её позицию и «урезать» файл на один размер структуры if (recordIndex == count - 1) { fseek(fp, recordIndex * sizeof(Winner), SEEK_SET); fflush(fp); // ftruncate сокращает файл до нужной длины ftruncate(fileno(fp), recordIndex * sizeof(Winner)); } else { // Если удаляем не последнюю: // Считаем последнюю запись Winner last; fseek(fp, (count - 1) * sizeof(Winner), SEEK_SET); fread(&last, sizeof(Winner), 1, fp); // Перезаписываем удаляемую запись данными последней fseek(fp, recordIndex * sizeof(Winner), SEEK_SET); fwrite(&last, sizeof(Winner), 1, fp); // Укорачиваем файл на 1 запись fflush(fp); ftruncate(fileno(fp), (count - 1) * sizeof(Winner)); } fclose(fp); printf("Запись удалена.\n"); } // ------------------------------------------------- // 3. searchRecords: поиск записей по выбранному полю // ------------------------------------------------- void searchRecords(void) { // Открываем файл для чтения в бинарном режиме FILE *fp = fopen(filename, "rb"); if (!fp) { perror("Ошибка открытия файла для поиска"); return; } // Считаем общее число записей long count = getRecordCount(fp); if (count == 0) { printf("Файл пуст. Искать нечего.\n"); fclose(fp); return; } // Предлагаем пользователю выбрать, по какому полю искать printf("Выберите поле для поиска:\n"); printf("1. ФИО\n"); printf("2. Номер школы\n"); printf("3. Регион\n"); printf("4. Предмет\n"); printf("5. Год\n"); printf("Выбор: "); int choice; scanf("%d", &choice); getchar(); // убрать \n // Две переменные для хранения запроса. // strQuery для текстовых полей, intQuery для числовых char strQuery[100] = {0}; int intQuery = 0; // В зависимости от выбора пользователя считываем запрос switch (choice) { case 1: printf("Введите подстроку для ФИО: "); fgets(strQuery, sizeof(strQuery), stdin); strQuery[strcspn(strQuery, "\n")] = '\0'; break; case 2: printf("Введите номер школы: "); scanf("%d", &intQuery); getchar(); break; case 3: printf("Введите подстроку для региона: "); fgets(strQuery, sizeof(strQuery), stdin); strQuery[strcspn(strQuery, "\n")] = '\0'; break; case 4: printf("Введите подстроку для предмета: "); fgets(strQuery, sizeof(strQuery), stdin); strQuery[strcspn(strQuery, "\n")] = '\0'; break; case 5: printf("Введите год: "); scanf("%d", &intQuery); getchar(); break; default: // Если ввод некорректен, завершаем поиск printf("Некорректный выбор.\n"); fclose(fp); return; } // Переменная для подсчёта, сколько записей нашли int found = 0; // Перебираем все записи файла for (long i = 0; i < count; i++) { Winner w; fread(&w, sizeof(Winner), 1, fp); // Считываем одну структуру // Флаг «совпадения» int match = 0; // Смотрим, по какому полю ищем, и сравниваем switch (choice) { case 1: // strstr(...) проверяет, входит ли одна строка в другую if (strstr(w.fio, strQuery)) match = 1; break; case 2: if (w.school_number == intQuery) match = 1; break; case 3: if (strstr(w.region, strQuery)) match = 1; break; case 4: if (strstr(w.subject, strQuery)) match = 1; break; case 5: if (w.year == intQuery) match = 1; break; } // Если нашли совпадение, выводим информацию об этой записи if (match) { found++; printf("\nНайдена запись #%ld:\n", i + 1); printf(" ФИО: %s\n", w.fio); printf(" Школа: %d\n", w.school_number); printf(" Регион: %s\n", w.region); printf(" Предмет: %s\n", w.subject); printf(" Год: %d\n", w.year); printf("-------------------------\n"); } } // Если ничего не нашли, сообщаем if (!found) { printf("Ничего не найдено.\n"); } fclose(fp); } // ------------------------------------------------- // 4. editRecord: редактирование записи по порядковому номеру // ------------------------------------------------- void editRecord(void) { // Открываем файл "r+b" FILE *fp = fopen(filename, "r+b"); if (!fp) { perror("Ошибка открытия файла для редактирования"); return; } // Смотрим, пуст ли файл long count = getRecordCount(fp); if (count == 0) { printf("Файл пуст. Редактировать нечего.\n"); fclose(fp); return; } // Узнаём, какую именно запись нужно изменить printf("Введите номер записи (1..%ld) для редактирования: ", count); long idx; scanf("%ld", &idx); getchar(); if (idx < 1 || idx > count) { printf("Некорректный индекс.\n"); fclose(fp); return; } // Переходим на нужную позицию в файле long recIndex = idx - 1; fseek(fp, recIndex * sizeof(Winner), SEEK_SET); // Считываем текущие данные записи, чтобы показать Winner w; fread(&w, sizeof(Winner), 1, fp); // Выводим имеющиеся поля printf("Текущие данные:\n"); printf(" ФИО: %s\n", w.fio); printf(" Школа: %d\n", w.school_number); printf(" Регион: %s\n", w.region); printf(" Предмет: %s\n", w.subject); printf(" Год: %d\n", w.year); printf("\nОставьте поле пустым, если не хотите менять.\n"); char buffer[100]; // Редактирование ФИО printf("Новое ФИО: "); fgets(buffer, sizeof(buffer), stdin); if (buffer[0] != '\n') { buffer[strcspn(buffer, "\n")] = '\0'; strncpy(w.fio, buffer, sizeof(w.fio) - 1); w.fio[sizeof(w.fio) - 1] = '\0'; } // Редактирование номера школы (если введён 0 — не меняем) printf("Номер школы (0 — без изменений): "); int tmp; scanf("%d", &tmp); getchar(); if (tmp != 0) { w.school_number = tmp; } // Редактирование региона printf("Новый регион: "); fgets(buffer, sizeof(buffer), stdin); if (buffer[0] != '\n') { buffer[strcspn(buffer, "\n")] = '\0'; strncpy(w.region, buffer, sizeof(w.region) - 1); w.region[sizeof(w.region) - 1] = '\0'; } // Редактирование предмета printf("Новый предмет: "); fgets(buffer, sizeof(buffer), stdin); if (buffer[0] != '\n') { buffer[strcspn(buffer, "\n")] = '\0'; strncpy(w.subject, buffer, sizeof(w.subject) - 1); w.subject[sizeof(w.subject) - 1] = '\0'; } // Редактирование года (если введён 0 — не меняем) printf("Новый год (0 — без изменений): "); scanf("%d", &tmp); getchar(); if (tmp != 0) { w.year = tmp; } // Возвращаемся на начало этой записи (чтобы перезаписать только её) fseek(fp, recIndex * sizeof(Winner), SEEK_SET); fwrite(&w, sizeof(Winner), 1, fp); fclose(fp); printf("Запись обновлена.\n"); } // =========================================================================== // MAIN // =========================================================================== int main(void) { // Устанавливаем локаль, чтобы корректно отображать русские символы if (!setlocale(LC_ALL, "ru_RU.UTF-8")) { // Если не удалось установить "ru_RU.UTF-8", пробуем дефолтную локаль setlocale(LC_ALL, ""); } // Вызываем функцию rewriteInitialData для создания/перезаписи файла один раз rewriteInitialData(); // Подготавливаем массив указателей на функции // Пункты меню: 1=add, 2=del, 3=search, 4=edit typedef void (*MenuFunc)(void); MenuFunc menuFunctions[] = { addRecord, // 1 deleteRecord, // 2 searchRecords, // 3 editRecord // 4 }; // Запускаем «бесконечный» цикл меню while (1) { printf("\n=========================================\n"); printf("1. Добавить запись\n"); printf("2. Удалить запись\n"); printf("3. Поиск записей\n"); printf("4. Редактировать запись\n"); printf("5. Вывести все записи\n"); printf("6. Выход\n"); printf("=========================================\n"); // Считываем выбор пользователя int choice; printf("Ваш выбор: "); scanf("%d", &choice); getchar(); // убрать \n if (choice == 6) { // Пункт «Выход» — завершаем программу printf("Завершение.\n"); break; } else if (choice >= 1 && choice <= 5) { if (choice == 5) { // Печать всех записей — вызываем printAll() (определена в другом файле) printAll(); } else { // Для пунктов 1..4 вызываем соответствующую функцию-операцию menuFunctions[choice - 1](); } } else { // Некорректный ввод printf("Некорректный ввод.\n"); } } return 0; }
table_part.c
c#include <stdio.h> #include <stdlib.h> #include <string.h> #include <locale.h> #include <unistd.h> #include "winner.h" // Подключаем структуру из заголовочного файла // Глобальная переменная, хранящая имя файла (общая для всех модулей) const char *filename = "winners.bin"; // Макросы, определяющие ширину столбцов для табличного вывода #define INDEX_WIDTH 2 // Ширина столбца с порядковым номером #define FIO_WIDTH 25 // Ширина столбца с ФИО #define SCHOOL_WIDTH 6 // Столбец "Школа" #define REGION_WIDTH 24 // "Регион" #define SUBJECT_WIDTH 20 // "Предмет" #define YEAR_WIDTH 4 // "Год" #define MAX_CHUNKS 50 // Максимальное число строк для одного поля при разбиении #define MAX_LINE_WIDTH 100 // Максимальная длина одной "подстроки" // // Вспомогательные функции и переменные, используемые при табличном выводе // // ------------------------------------------------- // utf8_len: возвращает количество символов в UTF-8 строке // (каждый байт, начинающийся не с "10xxxxxx", считается новым символом) // ------------------------------------------------- static int utf8_len(const char *s) { int n = 0; while (*s) { // Проверяем, является ли байт началом нового юникод-символа // (биты 110xxxxx или 1110xxxx и т.п.) if (((unsigned char)*s & 0xC0) != 0x80) { n++; } s++; } return n; } // ------------------------------------------------- // printCell: печать одной «ячейки» таблицы с заданной шириной // side = 1 => выравнивание по правому краю // side = 0 => выравнивание по левому краю // ------------------------------------------------- static void printCell(const char *text, int width, int side) { putchar(' '); // начальный пробел // Считаем видимую длину в символах int visibleLen = utf8_len(text); // Вычисляем, сколько пробелов нужно для выравнивания int pad = (visibleLen < width) ? (width - visibleLen) : 0; if (side) { // Выравнивание вправо: сначала пробелы, потом текст for (int i = 0; i < pad; i++) { putchar(' '); } fputs(text, stdout); } else { // Выравнивание влево: сначала текст, потом пробелы fputs(text, stdout); for (int i = 0; i < pad; i++) { putchar(' '); } } putchar(' '); // завершающий пробел } // ------------------------------------------------- // printSep: печатает горизонтальную разделительную линию // ------------------------------------------------- static void printSep(char ch) { // Массив ширин столбцов int colWidths[6] = { INDEX_WIDTH, FIO_WIDTH, SCHOOL_WIDTH, REGION_WIDTH, SUBJECT_WIDTH, YEAR_WIDTH }; int total = 0; // Суммируем ширины, плюс по 2 символа на отступ справа/слева внутри ячейки for (int i = 0; i < 6; i++) { total += colWidths[i] + 2; } // Плюс 7 вертикальных разделителей (между 6 столбцами и по краям) total += 7; // Печатаем нужное количество символов ch for (int i = 0; i < total; i++) { putchar(ch); } putchar('\n'); } // ------------------------------------------------- // chunkField: разбивает одну длинную строку src на несколько, // чтобы они поместились в указанный width (разбиваем по словам). // Записываем результат в массив dstLines[]. // Возвращаем количество строк, на которое удалось разбить. // ------------------------------------------------- static int chunkField(const char *src, int width, char dstLines[][MAX_LINE_WIDTH + 1]) { // Копируем строку src в локальный буфер (чтобы не портить оригинал) char buffer[1024]; strncpy(buffer, src, sizeof(buffer) - 1); buffer[sizeof(buffer) - 1] = '\0'; // line[] — временный буфер для сборки текущей строки char line[1024]; line[0] = '\0'; int lineLen = 0; // длина строки в «символах» (учитываем юникод-счёт) int lineCount = 0; // сколько строк уже сформировано // Разбиваем buffer на токены по пробелам/табуляциям char *token = strtok(buffer, " \t"); while (token) { int tokenLen = utf8_len(token); // Если одно слово само по себе длиннее ширины // — нам придётся порезать его посимвольно if (tokenLen > width) { // Если в line что-то уже есть, сначала добавляем этот остаток if (lineLen > 0) { strncpy(dstLines[lineCount], line, MAX_LINE_WIDTH); dstLines[lineCount][MAX_LINE_WIDTH] = '\0'; lineCount++; line[0] = '\0'; lineLen = 0; if (lineCount >= MAX_CHUNKS) return lineCount; } // Теперь режем слово по символам (с учётом UTF-8) size_t byteLen = strlen(token); int wordPos = 0; while (wordPos < (int)byteLen) { char sub[1024]; int subChars = 0; int subBytes = 0; while (wordPos + subBytes < (int)byteLen && subChars < width) { unsigned char c = (unsigned char)token[wordPos + subBytes]; // Начало нового юникод-символа? if ((c & 0xC0) != 0x80) { subChars++; if (subChars > width) break; } sub[subBytes] = token[wordPos + subBytes]; subBytes++; } sub[subBytes] = '\0'; // Записываем «кусочек» в dstLines strncpy(dstLines[lineCount], sub, MAX_LINE_WIDTH); dstLines[lineCount][MAX_LINE_WIDTH] = '\0'; lineCount++; if (lineCount >= MAX_CHUNKS) return lineCount; wordPos += subBytes; } } else { // Слово по длине не больше ширины if (lineLen == 0) { // Если текущая строка пустая, просто вставляем слово strncpy(line, token, sizeof(line) - 1); line[sizeof(line) - 1] = '\0'; lineLen = tokenLen; } else { // Проверяем, влезет ли ещё слово (с учётом пробела) if (lineLen + 1 + tokenLen <= width) { strcat(line, " "); strcat(line, token); lineLen += 1 + tokenLen; } else { // Если не влезает, записываем текущую строку в dstLines strncpy(dstLines[lineCount], line, MAX_LINE_WIDTH); dstLines[lineCount][MAX_LINE_WIDTH] = '\0'; lineCount++; if (lineCount >= MAX_CHUNKS) return lineCount; // Начинаем новую строку с текущего слова strncpy(line, token, sizeof(line) - 1); line[sizeof(line) - 1] = '\0'; lineLen = tokenLen; } } } // Следующий токен token = strtok(NULL, " \t"); } // Если после всего в line что-то осталось, тоже отправляем в dstLines if (lineLen > 0) { strncpy(dstLines[lineCount], line, MAX_LINE_WIDTH); dstLines[lineCount][MAX_LINE_WIDTH] = '\0'; lineCount++; } // Если вообще ничего не добавилось, кладём пустую строку if (lineCount == 0) { strcpy(dstLines[0], ""); lineCount = 1; } return lineCount; } // ------------------------------------------------- // Локальный getRecordCount_local: аналогичный способ // посчитать, сколько структур в файле // ------------------------------------------------- static long getRecordCount_local(FILE *fp) { fseek(fp, 0, SEEK_END); long size = ftell(fp); fseek(fp, 0, SEEK_SET); return size / (long)sizeof(Winner); } // ------------------------------------------------- // printAll: вывод всех записей из файла в табличном виде // ------------------------------------------------- void printAll(void) { // Открываем файл на чтение (бинарно) FILE *fp = fopen(filename, "rb"); if (!fp) { perror("Не удалось открыть файл для чтения"); return; } // Определяем, есть ли там вообще записи long count = getRecordCount_local(fp); if (count == 0) { printf("Файл пуст.\n"); fclose(fp); return; } // Печатаем "шапку" таблицы printSep('='); putchar('|'); printCell("№", INDEX_WIDTH, 1); putchar('|'); printCell("ФИО", FIO_WIDTH, 0); putchar('|'); printCell("Школа", SCHOOL_WIDTH, 1); putchar('|'); printCell("Регион", REGION_WIDTH, 0); putchar('|'); printCell("Предмет", SUBJECT_WIDTH, 0); putchar('|'); printCell("Год", YEAR_WIDTH, 1); puts("|"); printSep('='); // Перебираем все записи fseek(fp, 0, SEEK_SET); for (long i = 0; i < count; i++) { Winner w; // Считываем структуру fread(&w, sizeof(Winner), 1, fp); // Формируем буферы для числовых значений (чтобы превратить их в строки) char bufIndex[32], bufSchool[32], bufYear[32]; sprintf(bufIndex, "%ld", i + 1); // Порядковый номер sprintf(bufSchool, "%d", w.school_number); sprintf(bufYear, "%d", w.year); // Массивы строк для построчного разбиения (чтобы текст не превышал ширину столбца) char linesIndex [MAX_CHUNKS][MAX_LINE_WIDTH + 1]; char linesFIO [MAX_CHUNKS][MAX_LINE_WIDTH + 1]; char linesSchool[MAX_CHUNKS][MAX_LINE_WIDTH + 1]; char linesReg [MAX_CHUNKS][MAX_LINE_WIDTH + 1]; char linesSubj [MAX_CHUNKS][MAX_LINE_WIDTH + 1]; char linesYear [MAX_CHUNKS][MAX_LINE_WIDTH + 1]; // Разбиваем поля на подстроки (по словам), чтобы не обрезать текст int cIndex = chunkField(bufIndex, INDEX_WIDTH, linesIndex ); int cFIO = chunkField(w.fio, FIO_WIDTH, linesFIO ); int cSchool = chunkField(bufSchool, SCHOOL_WIDTH, linesSchool); int cReg = chunkField(w.region, REGION_WIDTH, linesReg ); int cSubj = chunkField(w.subject, SUBJECT_WIDTH, linesSubj ); int cYear = chunkField(bufYear, YEAR_WIDTH, linesYear ); // Определяем, сколько строк потребуется вывести, берём максимум // из количества строк для каждого поля int maxRows = cIndex; if (cFIO > maxRows) maxRows = cFIO; if (cSchool > maxRows) maxRows = cSchool; if (cReg > maxRows) maxRows = cReg; if (cSubj > maxRows) maxRows = cSubj; if (cYear > maxRows) maxRows = cYear; // Выводим данные по строкам for (int row = 0; row < maxRows; row++) { const char *idxStr = (row < cIndex ) ? linesIndex [row] : ""; const char *fioStr = (row < cFIO ) ? linesFIO [row] : ""; const char *schStr = (row < cSchool) ? linesSchool[row] : ""; const char *regStr = (row < cReg ) ? linesReg [row] : ""; const char *subStr = (row < cSubj ) ? linesSubj [row] : ""; const char *yearStr = (row < cYear ) ? linesYear [row] : ""; // Печатаем каждое поле как отдельную ячейку putchar('|'); printCell(idxStr, INDEX_WIDTH, 1); putchar('|'); printCell(fioStr, FIO_WIDTH, 0); putchar('|'); printCell(schStr, SCHOOL_WIDTH, 1); putchar('|'); printCell(regStr, REGION_WIDTH, 0); putchar('|'); printCell(subStr, SUBJECT_WIDTH, 0); putchar('|'); printCell(yearStr, YEAR_WIDTH, 1); puts("|"); } // Разделитель после каждой записи printSep('-'); } // Финальный разделитель printSep('='); fclose(fp); }
winner.h
c#ifndef WINNER_H #define WINNER_H // ------------------------------------------------------------------- // Структура для хранения данных о победителях // ------------------------------------------------------------------- typedef struct { char fio[100]; // ФИО (строка) int school_number; // Номер школы char region[50]; // Регион (строка) char subject[50]; // Предмет (строка) int year; // Год проведения } Winner; // ------------------------------------------------------------------- // Директива условной компиляции: // Если раскомментировать следующую строку, // при запуске программа каждый раз будет пересоздавать файл "winners.bin" // из заранее заготовленного массива predefinedArray[]. // Если эта строка закомментирована, то при первом запуске программа // предложит ввести записи с клавиатуры (если файл отсутствует). // ------------------------------------------------------------------- #define PREDEFINED_DATA #endif // WINNER_H
main_part.c
– основной модуль, где реализовано:
rewriteInitialData()
: создаёт файл либо заполняет массивом (зависит от PREDEFINED_DATA
);main()
, в которой есть меню (цикл с выбором пользователя).table_part.c
– отдельный модуль, в котором:
filename
, указывающая на winners.bin
;printAll()
, отвечающая за табличный вывод содержимого файла (с разбивкой длинных полей на несколько строк).winner.h
– заголовок со структурой Winner
и макросом #define PREDEFINED_DATA
.
Таким образом, вся логика по хранению, редактированию и выводу в таблицу разбита на несколько файлов, а выбор режима заполнения происходит через условную компиляцию (макрос).
обьясни понятно и подробно как работает все программа построчно
обьясни понятно и подробно как работает все программа построчно
Ниже идёт пошаговое разъяснение по каждому файлу, что именно делает код и в каком порядке вызываются функции. Постараемся раскрыть суть, не увязая в избыточных деталях, но при этом «покрыть» все интересные моменты и логику работы программы.
Вся программа разделена на три файла:
winner.h
– заголовочный файл.
Winner
(одно «место» с данными о победителе).PREDEFINED_DATA
, влияющий на то, как будет заполнен файл в начале.main_part.c
– основной модуль.
main()
.table_part.c
– модуль, отвечающий за вывод всего содержимого файла в «табличной» форме.
filename
, содержащая имя файла (все модули знают, что файл называется "winners.bin"
).printAll()
, которая аккуратно выводит данные о всех победителях из бинарного файла.winner.h
c#ifndef WINNER_H #define WINNER_H // Структура для хранения данных о победителях typedef struct { char fio[100]; // ФИО int school_number; // Номер школы char region[50]; // Регион char subject[50]; // Предмет int year; // Год } Winner; // -- Директива условной компиляции -- // Если раскомментировать строку ниже, программа при запуске // всегда перезапишет файл из массива `predefinedArray`: #define PREDEFINED_DATA #endif // WINNER_H
#ifndef WINNER_H
/ #define WINNER_H
/ #endif
– это защита от повторного включения. Чтобы, если вдруг в нескольких местах мы подключаем winner.h
, содержимое не дублировалось много раз.
typedef struct { ... } Winner;
– описывает, какие поля (ФИО, номер школы, регион, предмет, год) хранятся в одной записи.
sizeof(Winner)
байт в бинарном файле.#define PREDEFINED_DATA
– это директива условной компиляции. Если она включена (как сейчас), то при запуске программа перезапишет файл из массива (есть внутри main_part.c
соответствующий код). Если бы мы её закомментировали, то при первом запуске программа спросила бы у пользователя, сколько записей ввести с клавиатуры, и только после этого создала файл.
main_part.c
Это – «сердце» программы. В нём есть:
#include "winner.h"
– чтобы видеть структуру Winner
и знать о макросе PREDEFINED_DATA
.addRecord
, deleteRecord
, searchRecords
, editRecord
и т. д.main()
, в которой организовано меню.main_part.c
по порядку:c#include <stdio.h> #include <stdlib.h> #include <string.h> #include <locale.h> #include <unistd.h> #include "winner.h" // Подключаем структуру с условной компиляцией
stdio.h
— ввод-вывод, fopen
, fwrite
, fread
, printf
и т.д.stdlib.h
— для exit
, malloc
, free
и др.string.h
— для строковых функций (strcpy
, strstr
, strcspn
и т.д.).locale.h
— чтобы настроить локаль (кириллица).unistd.h
— для ftruncate
(укоротить файл).winner.h
, где описана структура Winner
и макрос PREDEFINED_DATA
.c// Сообщаем, что они определены в table_part.c extern const char *filename; extern void printAll(void);
extern const char *filename;
– это значит: «где-то в другом месте существует глобальная переменная filename
; здесь мы лишь говорим компилятору, что она есть».extern void printAll(void);
– там же, в другом файле, определена функция printAll()
. Мы хотим вызывать её из main_part.c
.c// --- Прототипы локальных функций --- static long getRecordCount(FILE *fp); static void rewriteInitialData(void); // --- Прототипы функций (операций) --- void addRecord(void); void deleteRecord(void); void searchRecords(void); void editRecord(void);
static long getRecordCount(FILE *fp);
– «подсчёт количества записей в файле».static void rewriteInitialData(void);
– «инициализация» файла.getRecordCount(FILE *fp)
cstatic long getRecordCount(FILE *fp) { fseek(fp, 0, SEEK_END); long size = ftell(fp); fseek(fp, 0, SEEK_SET); return size / (long)sizeof(Winner); }
fseek(fp, 0, SEEK_END);
– переходим в конец файла, чтобы узнать его размер (в байтах).ftell(fp);
– получаем текущее положение «курсора» — это и есть размер файла в байтах (длина).fseek(fp, 0, SEEK_SET);
– возвращаемся в начало, чтобы файл был готов к обычному чтению или записи.return size / (long)sizeof(Winner);
– считаем, сколько целых структур (каждая из которых занимает sizeof(Winner)
байт) помещается в общем размере size
. Это и есть число записей.rewriteInitialData(void)
cstatic void rewriteInitialData(void) { #if defined(PREDEFINED_DATA) // ... (ветка 1) #else // ... (ветка 2) #endif }
Эта функция вызывается один раз (в начале main()
) и проверяет, нужно ли перезаписывать файл готовым массивом (PREDEFINED_DATA
) или запросить у пользователя данные (если файла нет).
PREDEFINED_DATA
определёнcprintf("PREDEFINED_DATA определён: ...\n"); FILE *fp = fopen(filename, "wb"); if (!fp) { ... } Winner predefinedArray[] = { {"Иванов Иван Иванович", 123, "г. Москва", "Математика", 2023}, {"Петров Пётр Петрович", 456, "Красноярский край", "Информатика",2024}, {"Сидорова Анна Сергеевна", 72, "Ленинградская обл.","Физика", 2025} }; int n = sizeof(predefinedArray) / sizeof(predefinedArray[0]); for (int i = 0; i < n; i++) { fwrite(&predefinedArray[i], sizeof(Winner), 1, fp); } fclose(fp); printf("Файл перезаписан из массива!\n");
winners.bin
) в режиме "wb"
(write binary) – старое содержимое удаляется.predefinedArray
из трёх записей.for
: записываем каждую запись структурой Winner
в файл.PREDEFINED_DATA
не определёнc// Проверяем, существует ли файл FILE *chk = fopen(filename, "rb"); if (chk) { // Если да, закрываем его и выходим fclose(chk); printf("Файл уже существует, не перезаписываем.\n"); return; } // Иначе (файл не существует), спрашиваем у пользователя FILE *fp = fopen(filename, "wb"); if (!fp) { ... } int n; printf("Файл не найден! ... Введите количество начальных записей: "); scanf("%d", &n); getchar(); for (int i = 0; i < n; i++) { Winner w; // спрашиваем у пользователя поля: // w.fio, w.school_number, w.region, w.subject, w.year // записываем через fwrite(...) } fclose(fp); printf("Файл создан и заполнен...\n");
winners.bin
уже есть, вообще ничего не делаем.n
).addRecord()
cvoid addRecord(void) { FILE *fp = fopen(filename, "ab"); ... // Считываем с клавиатуры данные в структуру Winner w // fwrite(&w, sizeof(Winner), 1, fp); fclose(fp); printf("Запись успешно добавлена.\n"); }
"ab"
(append binary) — запись добавляется в конец файла.fwrite(...)
записывает структуру w
сразу за предыдущими в бинарном файле.deleteRecord()
cvoid deleteRecord(void) { FILE *fp = fopen(filename, "r+b"); long count = getRecordCount(fp); ... // Считываем, какой номер записи удалить (1..count) // Если удаляем последнюю => просто укорачиваем файл // Иначе замещаем удаляемую запись последней и укорачиваем fclose(fp); printf("Запись удалена.\n"); }
"r+b"
(читать и писать в бинарном формате).fseek(fp, recordIndex * sizeof(Winner), SEEK_SET);
ftruncate(fileno(fp), recordIndex * sizeof(Winner));
searchRecords()
cvoid searchRecords(void) { FILE *fp = fopen(filename, "rb"); long count = getRecordCount(fp); // Предлагаем выбрать поле поиска: 1=ФИО, 2=школа, 3=регион... // Считываем либо строку strQuery, либо число intQuery // Перебираем все записи, если совпадает, выводим на экран fclose(fp); }
strQuery
. Если числовое, intQuery
.strstr(w.fio, strQuery)
— ищем, есть ли подстрока.editRecord()
cvoid editRecord(void) { FILE *fp = fopen(filename, "r+b"); long count = getRecordCount(fp); ... // Читаем нужную запись, показываем её пользователю // Даём возможность «не трогать» поле (просто нажать Enter), // или ввести новое значение // Потом возвращаемся fseek(...) и fwrite(...) перезаписываем одну структуру fclose(fp); printf("Запись обновлена.\n"); }
Winner w
.fseek
возвращаемся на начало той же записи и делаем fwrite(&w, sizeof(Winner), 1, fp);
. Получается, мы перезаписали ровно эту одну структуру, а не весь файл.main()
cint main(void) { // 1. Устанавливаем локаль if (!setlocale(LC_ALL, "ru_RU.UTF-8")) { setlocale(LC_ALL, ""); } // 2. Вызываем rewriteInitialData() - см. выше // Если PREDEFINED_DATA включён, файл перезапишется массивом. // Иначе, если файла нет, попросит пользователя ввести начальные данные. rewriteInitialData(); // 3. Подготавливаем массив указателей на функции (для меню) typedef void (*MenuFunc)(void); MenuFunc menuFunctions[] = { addRecord, // меню п.1 deleteRecord, // меню п.2 searchRecords, // меню п.3 editRecord // меню п.4 }; // 4. Запускаем цикл меню while (1) { printf("\n=========================================\n"); printf("1. Добавить запись\n"); printf("2. Удалить запись\n"); printf("3. Поиск записей\n"); printf("4. Редактировать запись\n"); printf("5. Вывести все записи\n"); printf("6. Выход\n"); printf("=========================================\n"); int choice; printf("Ваш выбор: "); scanf("%d", &choice); getchar(); // убрать '\n' if (choice == 6) { // если выбрали пункт «Выход», завершаем программу printf("Завершение.\n"); break; } else if (choice >= 1 && choice <= 5) { // если пункт 1..5: if (choice == 5) { // вызываем функцию printAll() (определена в другом файле) printAll(); } else { // иначе вызываем одну из четырёх функций // addRecord, deleteRecord, searchRecords, editRecord menuFunctions[choice - 1](); } } else { // некорректный ввод printf("Некорректный ввод.\n"); } } return 0; }
rewriteInitialData()
.table_part.c
Этот модуль отвечает только за вывод данных «табличкой» + содержит глобальную переменную filename
.
c#include <stdio.h> #include <stdlib.h> #include <string.h> #include <locale.h> #include <unistd.h> #include "winner.h"
winner.h
.c// Глобальная переменная с именем файла const char *filename = "winners.bin";
filename
. В main_part.c
было extern const char *filename;
, а здесь – само объявление. Так что вся программа знает, что работает с "winners.bin"
.c// Макросы для ширины столбцов #define INDEX_WIDTH 2 #define FIO_WIDTH 25 #define SCHOOL_WIDTH 6 #define REGION_WIDTH 24 #define SUBJECT_WIDTH 20 #define YEAR_WIDTH 4 #define MAX_CHUNKS 50 #define MAX_LINE_WIDTH 100
utf8_len(...)
cstatic int utf8_len(const char *s) { int n = 0; while (*s) { if (((unsigned char)*s & 0xC0) != 0x80) { n++; } s++; } return n; }
s
юникод-символов (подразумеваем UTF-8). Если байт не начинается с 0x80...
, это начало нового символа.printCell(const char *text, int width, int side)
cstatic void printCell(const char *text, int width, int side) { putchar(' '); // начальный пробел int visibleLen = utf8_len(text); int pad = (visibleLen < width) ? (width - visibleLen) : 0; if (side) { // выравнивание вправо ... } else { // выравнивание влево ... } putchar(' '); }
text
, нужно вывести её ровно width
символов шириной, с учётом выравнивания.side == 1
→ выравнивание вправо, side == 0
→ влево.width
.printSep(char ch)
cstatic void printSep(char ch) { // Вычисляем, сколько всего символов нам нужно для «горизонтальной линии» // Печатаем ch нужное количество раз }
=====================================
или:
-------------------------------------
– зависит от того, какой ch
передадим.chunkField(...)
cstatic int chunkField(const char *src, int width, char dstLines[][MAX_LINE_WIDTH + 1]) { // Разбивает длинный текст (src) на несколько «строк», каждая не длиннее width (с учётом UTF-8). // Возвращает, сколько получилось строк (записывая их в dstLines). }
src
на слова (через strtok
по пробелам).width
, режем его посимвольно.dstLines
и начинаем новую.getRecordCount_local(FILE *fp)
cstatic long getRecordCount_local(FILE *fp) { // То же самое, что и в main_part.c ... }
printAll()
cvoid printAll(void) { FILE *fp = fopen(filename, "rb"); if (!fp) { perror(...); return; } long count = getRecordCount_local(fp); if (count == 0) { printf("Файл пуст.\n"); fclose(fp); return; } // Печатаем «шапку» (заголовок таблицы) printSep('='); putchar('|'); printCell("№", INDEX_WIDTH, 1); putchar('|'); printCell("ФИО", FIO_WIDTH, 0); putchar('|'); printCell("Школа", SCHOOL_WIDTH, 1); putchar('|'); printCell("Регион", REGION_WIDTH, 0); putchar('|'); printCell("Предмет", SUBJECT_WIDTH, 0); putchar('|'); printCell("Год", YEAR_WIDTH, 1); puts("|"); printSep('='); // Цикл по всем записям (от 0 до count-1) for (long i = 0; i < count; i++) { Winner w; fread(&w, sizeof(Winner), 1, fp); // Превращаем i+1 (номер записи), school_number, year в строки // чтобы печатать их в ячейках char bufIndex[32], bufSchool[32], bufYear[32]; sprintf(bufIndex, "%ld", i + 1); sprintf(bufSchool, "%d", w.school_number); sprintf(bufYear, "%d", w.year); // Массивы «подстрок» для каждого поля char linesIndex [MAX_CHUNKS][MAX_LINE_WIDTH + 1]; char linesFIO [MAX_CHUNKS][MAX_LINE_WIDTH + 1]; char linesSchool[MAX_CHUNKS][MAX_LINE_WIDTH + 1]; char linesReg [MAX_CHUNKS][MAX_LINE_WIDTH + 1]; char linesSubj [MAX_CHUNKS][MAX_LINE_WIDTH + 1]; char linesYear [MAX_CHUNKS][MAX_LINE_WIDTH + 1]; // Разбиваем поля на «строчки» int cIndex = chunkField(bufIndex, INDEX_WIDTH, linesIndex ); int cFIO = chunkField(w.fio, FIO_WIDTH, linesFIO ); int cSchool = chunkField(bufSchool, SCHOOL_WIDTH, linesSchool); int cReg = chunkField(w.region, REGION_WIDTH, linesReg ); int cSubj = chunkField(w.subject, SUBJECT_WIDTH, linesSubj ); int cYear = chunkField(bufYear, YEAR_WIDTH, linesYear ); // Определяем, сколько строк надо напечатать (максимум из cIndex, cFIO, ...) int maxRows = cIndex; if (cFIO > maxRows) maxRows = cFIO; if (cSchool > maxRows) maxRows = cSchool; if (cReg > maxRows) maxRows = cReg; if (cSubj > maxRows) maxRows = cSubj; if (cYear > maxRows) maxRows = cYear; // Печатаем построчно (row от 0 до maxRows-1) for (int row = 0; row < maxRows; row++) { const char *idxStr = (row < cIndex ) ? linesIndex [row] : ""; const char *fioStr = (row < cFIO ) ? linesFIO [row] : ""; const char *schStr = (row < cSchool) ? linesSchool[row] : ""; const char *regStr = (row < cReg ) ? linesReg [row] : ""; const char *subStr = (row < cSubj ) ? linesSubj [row] : ""; const char *yearStr = (row < cYear ) ? linesYear [row] : ""; // Выводим ячейки: putchar('|'); printCell(idxStr, INDEX_WIDTH, 1); putchar('|'); printCell(fioStr, FIO_WIDTH, 0); putchar('|'); printCell(schStr, SCHOOL_WIDTH, 1); putchar('|'); printCell(regStr, REGION_WIDTH, 0); putchar('|'); printCell(subStr, SUBJECT_WIDTH, 0); putchar('|'); printCell(yearStr, YEAR_WIDTH, 1); puts("|"); } // Печатаем линию-разделитель после каждой записи printSep('-'); } // В самом конце печатаем «итоговую» линию (из '=') printSep('='); fclose(fp); }
text=============================================== ... | № | ФИО | Школа | Регион | ... =============================================== ... | 1 | Иванов Иван Иванович | 123 | г. Москва | ... ----------------------------------------------- ... | 2 | Петров Пётр... | 456 | ... | ... ----------------------------------------------- ... ... =============================================== ...
Запуск программы: управление переходит в main()
(файл main_part.c
).
setlocale(...)
– устанавливаем локаль, чтобы русские буквы шли правильно.
rewriteInitialData()
(один раз в начале):
#define PREDEFINED_DATA
раскомментирован (как сейчас), создаём или перезаписываем winners.bin
готовым массивом.winners.bin
. Если есть – ничего не делаем. Если нет – спрашиваем, сколько записей ввести, и заполняем файл.Переходим в «бесконечный» цикл меню. Пункты:
addRecord()
. Спрашивает у пользователя поля, добавляет запись в конец winners.bin
.deleteRecord()
. Спрашивает номер, удаляет соответствующую структуру (коротко «затыкает дыру» последней записью либо укорачивает файл).searchRecords()
. Предлагает выбрать поле, вводим значение, программа ищет совпадения или подстроки, выводит найденные записи.editRecord()
. Спрашивает, какую запись изменить, позволяет перезаписать некоторые поля (или оставить без изменений), потом сохраняет изменения.printAll()
(определена в table_part.c
). Открывается файл на чтение, каждую структуру аккуратно печатаем.main()
, программа заканчивается.table_part.c
просто хранит функцию printAll()
, которая, когда её вызывают, всё читает из winners.bin
и строит красивую таблицу в консоли.
PREDEFINED_DATA
, исходные три записи будут перезаписаны.PREDEFINED_DATA
(закомментировать) и запустить программу, а файла ещё нет – она спросит, сколько записей вводить. Если запустить ещё раз – увидит, что файл уже есть, оставит его как есть.После начального этапа, всё остальное время программа даёт вам меню для манипуляций с файлом: добавлять записи, удалять, искать, редактировать, выводить содержимое.
Таким образом, построчно (и по файлам) программа делает следующее:
main()
→ rewriteInitialData()
→ создание/перезапись winners.bin
(или ничего, если он уже есть и макрос выключен).main()
→ цикл меню → в зависимости от выбора:
addRecord()
(добавить)deleteRecord()
(удалить)searchRecords()
(поиск)editRecord()
(редактировать)printAll()
(вывести таблицу)выход
обьясни понятно для чего нужны директивы #ifndef WINNER_H
#define WINNER_H
В файлах на C (и C++) очень часто используют подобные «защитные» конструкции в заголовочных файлах. Они называются Include Guards («защита от повторного включения»).
c#ifndef WINNER_H #define WINNER_H // ... Содержимое заголовочного файла ... #endif // WINNER_H
#ifndef WINNER_H
«Если ещё не определён макрос WINNER_H
…»
#define WINNER_H
«…то сейчас его определить».
Таким образом, если компилятор видит этот блок кода впервые, макрос WINNER_H
ещё не определён, значит блок внутри #ifndef ... #endif
будет скомпилирован.
А теперь WINNER_H
становится определённым.
#endif // WINNER_H
Окончание блока.
Если в проекте, например, 10 разных файлов *.c
, и многие из них подключают один и тот же заголовок #include "winner.h"
, то при обычном повторном включении могло бы возникнуть несколько объявлений структуры или функций из этого заголовка — что вызвало бы ошибки компиляции («redefinition error»).
Но благодаря конструкции:
c#ifndef WINNER_H #define WINNER_H // Определения структуры Winner и т. д. #endif
макрос WINNER_H
определится при первом включении заголовка. При повторных подключениях компилятор проверит: «А нет ли уже WINNER_H
?» И обнаружит, что этот макрос уже определён, значит второй раз содержимое заголовка не будет вставлено. В результате структура, функции и т. п. объявляются ровно один раз, и все проблемы с дублированием исчезают.
Таким образом, #ifndef WINNER_H / #define WINNER_H / #endif
— это стандартный способ избежать повторного определения одного и того же кода при множественных #include
.
то есть при повторном включении заготовочного файла осуществляется проверка на #ifndef WINNER_H и если выражение ложно то заново файл с помощью #define WINNER_H не определяется?
Да, именно так. Логика такова:
Впервые при подключении заголовка winner.h
компилятор встречает:
c#ifndef WINNER_H #define WINNER_H // ... код заголовочного файла ... #endif
WINNER_H
пока не определён, значит #ifndef WINNER_H
→ «истина» → весь код вставится.#define WINNER_H
, благодаря которому компилятор «запоминает», что теперь макрос WINNER_H
есть.При повторном включении (в другом месте кода, где снова пишут #include "winner.h"
) компилятор снова дойдёт до того же блока, но теперь:
WINNER_H
уже определён.#ifndef WINNER_H
→ «ложь».#ifndef
и #endif
(где находятся объявления структуры и т. д.) пропускается, и код повторно не вставляется.Так достигается «защита от повторного включения» (англ. Include Guard): заголовок вставляется в результирующий код только один раз, даже если мы на всякий случай сделали #include "winner.h"
во многих местах.
обьясни понятно что делает команда tupedef struct
В языке C (а также C++) конструкция вида:
ctypedef struct { /* поля структуры */ } SomeName;
делает сразу две вещи:
SomeName
.Другими словами, typedef
позволяет не использовать синтаксис struct SomeName
при создании переменных, а писать просто SomeName
.
typedef
Если бы мы описали структуру так:
cstruct Winner { char fio[100]; int school_number; char region[50]; char subject[50]; int year; };
то при создании переменных нужно писать:
cstruct Winner w; // или struct Winner array[10];
typedef
Если же мы используем:
ctypedef struct { char fio[100]; int school_number; char region[50]; char subject[50]; int year; } Winner;
тогда компилятор создаёт тип с именем Winner
. Мы можем объявлять переменные без ключевого слова struct
:
cWinner w; Winner arr[10];
И т.д.
Таким образом, typedef struct { ... } SomeName;
(или аналогичная форма с объявлением struct SomeName
) – это удобный способ дать имя новому сложному типу данных (структуре), чтобы потом обращаться к нему в коде покороче.