исследуй сходимость ряда (sqrt(n+a)-sqrt4(n^2+n+b)...
Erstellt am: 13. April 2025
Erstellt am: 13. April 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
) – это удобный способ дать имя новому сложному типу данных (структуре), чтобы потом обращаться к нему в коде покороче.
то есть struct-это пользовательский тип данных, который дает название нескольким другим типам данных, и при его вызове нужно писать struct, typedef-это пользовательский тип данных, который дает название одному конкретному типу данных, при вызове Тоде нужно писать typedef, а typedef struct -тип данных, который дает название нескольким типам данных, но при вызове не нужно писать typedef или struct
Постараемся разложить всё на понятные части:
struct
Ключевое слово struct
(структура) в языке C (и C++) позволяет объединить несколько разных полей (переменных разных типов) в один пользовательский тип.
cstruct MyStruct { int x; float y; };
MyStruct
.typedef
, нужно писать:
иначе компилятор не знает о чём речь.cstruct MyStruct var;
typedef
typedef
создаёт новый псевдоним (alias) для уже существующего типа.Теперь мы можем писатьctypedef int MyInt;
MyInt a;
вместо int a;
.typedef struct { ... } ИмяТипа;
Обычно в C любят объединять typedef
и struct
, чтобы не писать потом каждый раз struct Имя
.
Например:
c// без имени тега (анонимная структура): typedef struct { int age; char name[50]; } Person;
Person
».cPerson p; // вместо struct <что-то> p; Person arr[5];
struct
при объявлении переменных, потому что у нас уже есть готовый «синоним» (alias) — Person
.typedef struct MyTag { ... } ИмяТипа;
typedef
всё же дают имя тегу структуры:
ctypedef struct MyTag { int age; char name[50]; } Person;
Теперь у нас есть два способа объявлять переменные:
struct MyTag var;
(используя тег)Person var;
(используя typedef-имя)В реальном коде разработчики выбирают, какой способ им удобней.
«то есть
struct
— это пользовательский тип данных, который даёт название нескольким другим типам данных ...»
Не совсем так. struct
не «даёт названия нескольким типам». Он объединяет несколько полей разного типа (int, float, массивы, другие структуры...) в единый составной тип (структуру).
«при его вызове нужно писать
struct
…»
Если у вас нет typedef
, то да, чтобы объявить переменную структуры, нужно писать struct ИмяСтруктуры
.
«
typedef
— это пользовательский тип данных, который даёт название одному конкретному типу данных… при вызове тоже нужно писатьtypedef
…?»
Нет, typedef
— это механизм один раз создать «псевдоним» для типа. Затем не нужно повторно писать typedef
всякий раз, когда объявляете переменную. Вы один раз описали typedef
... и дальше можете использовать ваш новый тип просто по имени.
«
typedef struct
— тип данных, который даёт название нескольким типам данных, но при вызове не нужно писатьtypedef
илиstruct
?»
Не совсем. Когда вы пишете typedef struct ... ИмяТипа;
, вы создаёте один структурный тип и тут же делаете для него алиас (ИмяТипа). Потом, при объявлении переменных, вы просто пишете ИмяТипа var;
, без struct
.
typedef
). Или может быть анонимная структура без тега, но всё равно один конкретный тип.struct MyTag { ... };
:
MyTag
.struct MyTag variable;
.typedef struct { ... } SomeAlias;
:
SomeAlias
.SomeAlias var;
без struct
.typedef struct MyTag { ... } SomeAlias;
:
MyTag
.SomeAlias
.struct MyTag var;
или как SomeAlias var;
.Во всех случаях это один тип данных (структура), которая внутри хранит несколько полей. А typedef
даёт удобное короткое имя, чтобы не писать везде struct MyTag
и не путаться.
что значит создать структуру обьсни понятно
Когда мы создаём (определяем) структуру в языке C, мы фактически объявляем новый тип данных, который объединяет несколько отдельных переменных (полей) в одну «составную» переменную.
Например, представим, что нам нужно хранить информацию о победителе олимпиады: фамилию, имя, отчество, год рождения, регион и т. д. Без структуры мы могли бы везде передавать это всё набором отдельных переменных. Но гораздо удобнее создать один тип данных (структуру), внутри которого все эти данные будут лежать вместе.
cstruct Winner { char fio[100]; // строка с ФИО int school_number; // номер школы char region[50]; // регион char subject[50]; // предмет int year; // год };
Здесь мы описали структуру Winner
. Это значит:
struct Winner
».fio
, school_number
, region
, subject
, year
.struct Winner w;
), но внутри неё есть все нужные поля.Создать (определить) структуру – написать struct { ... } Имя;
, описав поля. Это объявляет тип (как будто вы сказали «вот есть тип int, вот есть тип float, а теперь ещё есть struct Winner
»).
Создать переменную структуры – написать, например:
cstruct Winner w;
Это означает: «Завести конкретную переменную (назовём её w
) типа struct Winner
», то есть мы реально выделяем память, чтобы хранить поля fio
, school_number
и т. д.
w
), у которой будут поля w.fio
, w.region
и т. д.struct обьядиняет типы данных в один тип или несколько переменных в одну переменную
Лучше сказать так:
struct
в языке C (и C++) описывает новый тип (составной), внутри которого расположено несколько полей разных типов данных. А когда вы объявляете переменную этого типа (структуру), она содержит все эти поля как «единое целое».
Примерно так это выглядит в коде:
c// 1) Определяем структуру (новый тип) struct Winner { char fio[100]; // поле "fio" (строка) int school_number; // поле "school_number" (число) char region[50]; // поле "region" (строка) char subject[50]; // поле "subject" (строка) int year; // поле "year" (число) }; // 2) Создаём переменную (экземпляр) типа struct Winner struct Winner w; // Теперь w.fio, w.school_number, w.region и т.д. — всё хранится внутри w.
> Так что по сути:
w
) = один объект, в котором «лежат» все эти поля.