Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Программирование C++ (1 сем, мага) / Лабы / Лабораторная работа №5

.pdf
Скачиваний:
0
Добавлен:
01.09.2026
Размер:
1 Мб
Скачать
Министерство науки и высшего образования Российской Федерации
Федеральное государственное автономное образовательное учреждение высшего
образования
«Национальный исследовательский ядерный университет «МИФИ»
Кафедра экспериментальной ядерной физики и космофизики (№7)
Программирование на С и С++
Выполнил: студент группы M22-105 Васильева А.П.
Москва, 2022
Цель работы: знакомство со структурами и работой с файлами. Задача: дан файл, содержащий набор чисел. Необходимо создать структуру, которая
могла бы хранить эти числа. Выполнить чтение чисел из файла с последующим сохранением их в структурные переменные. Реализовать операцию сложения этих чисел.
Введение
Структуры один из примеров составных типов данных, называемых агрегатными
типами.
Структурные переменные (структуры) – это объединение одной или более переменных, возможно, разных типов, в одну область памяти, имеющую одно имя. Отдельные составные части структурной переменной называются полями.
Как и любая переменная, структурная переменная должна описываться. Ее описание состоит из двух этапов:
1) задание шаблона структуры;
2) собственно описание структурной переменной.
Каждый шаблон имеет собственное имя для того, чтобы компилятор мог различать различные шаблоны. В том случае, если в функции используется единственный шаблон, он может не иметь имя, что задается пропуском имени при описании шаблона. Имена шаблонов должны быть уникальными в пределах их области определения, и, в частности, в пределах одной функции может быть только один безымянный шаблон.
Синтаксис задания шаблона таков:
struct имя_шаблона {
тип_1 имя_поля_1;
тип_2 имя_поля_2;
•••
тип_N имя_поля_N;
};
Здесь имя_шаблона – имя шаблона, удовлетворяющее правилам задания идентификаторов языка С++ (С); тип_1, тип_2, ..., тип_N – любые типы, например int, char, float; имя_поля_1, имя_поля_2, ..., имя_поля_N – имена полей, удовлетворяющие правилам задания идентификаторов языка С++ (С).
Когда задан шаблон, может быть описана структурная переменная. Описание
структурной переменной состоит из задания типа и имени структурной переменной.
Файл - набор взаимосвязанных данных, имеющих общее имя, находящихся на каком- либо устройстве и доступных для обработки на компьютере. Существует два подхода по работе с файлами:
информация о потоке (файле) заносится в структуру типа FILE, определенную в файле
stdio.h, и файл оказывается связанным с этой структурой;
файл связывается с дескриптором – целым значением, характеризующим размещение
информации о файле во внутренних таблицах системы.
В начале работы любой программы автоматически открывается три потока со своими
дескрипторами:
Поток
Дескриптор
stdin
0
Стандартный входной поток – обычно клавиатура
stdout
1
Стандартный выходной поток – обычно экран
stderr
2
Стандартный поток сообщений об ошибках
Ход работы
Файл, содержащий набор чисел, имел следующий вид:
Файл содержит как действительные, так и комплексные числа разных знаков. Причем
целая часть чисел содержит 5 разрядов, а дробная 15. Для хранения этих чисел создана
структура longNumber:
Структура содержит следующие поля:
int whole[7] – массив, содержащий 7 элементов типа int. Этот массив
предназначен для хранения вещественной целой части числа. Причем элемент массива
с индексом 0 – whole[0] хранит информацию о знаке вещественной части: 0,
если “+”, и 1, если “—”. Разряду единиц соответствует элемент массива whole[6],
разряду десятков – whole[5] и т.д.
int fractional[15] – массив, содержащий 15 элементов типа int. Предназначен
для хранения вещественной дробной части числа. Разряду десятой доли числа
соответствует fractional[0], разряду сотой доли – fractional[1] и т.д.
int wholeComplex[7] – массив, содержащий 7 элементов типа int. Этот массив
предназначен для хранения мнимой целой части числа. Причем элемент массива с
индексом 0 – wholeComplex[0] хранит информацию о знаке мнимой части: 0, если
“+”, и 1, если “—”. Разряду единиц соответствует элемент
массива wholeComplex[6], разряду десятков – wholeComplex[5] и т.д.
int fractionalComplex[15] – массив, содержащий 15 элементов типа int.
Предназначен для хранения мнимой дробной части числа. Разряду десятой доли числа
соответствует fractionalComplex [0], разряду сотой доли
fractionalComplex [1] и т.д.
bool isComplex – значение этого поля равно true, если число содержит мнимую
часть, и false в противном случае.
Несмотря на то, что целая часть чисел, содержащихся в файле, содержит только
5 разрядов, массивы для её хранения состоят из 7 элементов – нулевой элемент, как уже
было описано выше, содержит информацию о знаке вещественной или мнимой части числа,
а следующий за ним элемент при считывании чисел из файла заполняется нулями, но он
необходим при выполнении операции сложения чисел, т.к. там может происходить переход
в следующий разряд.
Запись значения каждого из разрядов числа в структурную переменную осуществлена
с помощью функции writeNumber(…). Функция принимает в качестве аргументов
указатель на структурную переменную (longNumber *number); цифру, содержащуюся в разряде числа (int symbol); часть числа, в которую происходит запись (int currentPart) и индекс элемента массива, куда должна быть записана данная цифра.
Если currentPart = 0, то запись происходит в вещественную целую часть, –
элементу массива whole[j] структуры number присваивается значение symbol.
Если currentPart = 1, то запись происходит в вещественную дробную часть, –
элементу массива fractional[j] структуры number присваивается значение symbol.
Если currentPart = 2, то запись происходит в мнимую целую часть, – элементу
массива wholeComplex[j] структуры number присваивается значение symbol.
Иначе запись происходит в мнимую дробную часть, – элементу массива
fractionalComplex[j] структуры number присваивается значение symbol.
Вызов функции осуществляется из блока main() программы, в котором и происходит
считывание символов из файла.
Информация о файле заносится в структуру inFile типа файл. Файл открывается
функцией fopen(“task1a.txt”, “r), где task1a.txt– имя файла, r– аргумент, определяющий режим открытия файла – открыть файл только для чтения.
Создается массив структур struct longNumber numbersArray[13],
содержащий 13 элементов, т.к. в файле 13 чисел. Создаются перемнные типа int – для индексации элементов массива структур (i), для индексации элементов массивов внутри структуры (j) и для хранения информации о том, в какую часть числа идет запись (currentPart). Также создаются переменные типа char для хранения текущего считанного символа (symbol) и предыдущего символа (prevSymbol).
Далее проверяется, что файл открыт if (inFile != NULL) {}, и если это так, то
начинается чтение файла в цикле while. Считывается символ из файла с помощью функции fscanf и сохраняется в переменную symbol типа char. Считывание будет происходить до тех пор, пока не будет достигнут конец файла:
while (fscanf(inFile, "%c", &symbol) != EOF) { … }
Далее выполняется ряд проверок, стобы определить, является ли считанный символ
цифрой одного из чисел и если да, то в какую часть числа его необходимо записывать.
Первый блок if:
if (currentPart == 0 and j == 2 and std::isdigit(symbol)) {…}
соответствует случаю, когда начинается новое число, т.к. переменным currentPart и j после окончания записи каждого числа будут присваиваться значения 0 и 2 соответственно. Изначально эти переменные также были инициализированы этими значениями. В этом блоке проверяется, чему был равен предыдущий символ и, если он равен “-”, то numbersArray[i].whole[0] присваивается значение 1 (число отрицательное), иначе – 0 (число положительное). А элементу numbersArray[i].whole[1] присваивается значение 0, т.к. числа в файле не содержат разряда сотен тысяч. Запись
символов в структурную переменную осуществляется с помощью описанной выше
функции writeNumber(…).
Блок if (std::isdigit(prevSymbol) and !std::isdigit(symbol)){…}
соответствует случаю, когда закончилось считывание какой-либо из частей числа (предыдущий символ – цифра, а текущий цифрой не является). Он внутри себя содержит ещё ряд проверок:
1. if (symbol == '.') {…} – т.к. точка является разделителем целой и дробной
частей числа, этот блок соответствует ситуации, когда закончилось считывание целой части и произошел переход к считыванию дробной. Здесь переменная currentPart увеличивается на 1 (для целых вещественной и мнимой частей она равна 0 и 2 соответственно, а для дробных – 1 и 3), а переменной j присваивается значение 0, т.к. десятым долям соответствуют элементы массивов
fractional и fractionalComplex с индексом ноль.
2. else if (symbol == '+' or symbol == '-') {…}закончилось
считывание вещественной дробной записи числа и далее следует комплексная часть. Здесь переменной currentPart присваивается значение 2, переменной j2, numbersArray[i].isComplex присваивается значение
true.
3. else if (currentPart == 1) {…}закончилось считывание
вещественной дробной части числа, но мнимой части не последовало, т.е. число
действительное и полностью считано. В этом блоке numbersArray[i].isComplex присваивается значение false, а также заполняются нулями массивы numbersArray[i].wholeComplex и numbersArray[i].fractionalComplex. Переменным currentPart и j присваиваются значения 0 и 2 соответственно, т.к. текущее число закончено и,
как только снова в потоке символов встретится цифра, начнется запись
следующего. По этой же причине i увеличивается на единицу.
4. else {…} – также соответствует случаю, когда закончилось считывание числа,
однако этот блок выполняется для окончания записи комплексных числа. Переменным currentPart и j присваиваются значения 0 и 2 соответственно,
т.к. текущее число закончено и, как только снова в потоке символов встретится
цифра, начнется запись следующего. По этой же причине i увеличивается на единицу.
Блок if (std::isdigit(symbol)) {…} выполняется, если текущий считанный символ – цифра. В этом случае с помощью функции writeNumber(…) в структурную переменную numbersArray[i] запишется symbol – элементу массива с индексом j, соответствующему части числа currentPart, присвоится значение static_cast<int>(symbol)-‘0’. Здесь выполняется преобразование типа char к типу int. Т.к. результатом static_cast<int>(symbol) будет код символа symbol, а не число ему соответствующее, для получения числа необходимо вычесть ‘0’ – код символа “0”, с которого начинаются символы.
Перед завершением каждой итерации цикла while переменной prevSymbol присваивается значение symbol, чтобы в следующей итерации было доступно значение как текущего, так и предыдущего символов.
Таким образом осуществляется чтение файла и запись чисел в массив структур. После
завершения считывания файл закрывается с помощью функции fclose().
После заполнения массива структур его элементы имеют следующий вид:
Рассмотрим теперь, как реализовано сложение чисел.
Для того, чтобы сложить два числа, хранящихся в структурных переменных, написана
функция stuct longNumber sumNumbers(longNumber *a, longNumber *b):
Функция принимает в качестве аргументов два указателя – каждый из которых указывает на структуру longNumber и возвращает объект типа longNumber.
Внутри функции создается структурная переменная struct longNumber result, которая будет содержать результат сложения. Далее дважды вызывается функция sumRealOrImagParts(…), в которую первый раз передаются указатели на первые элементы массивов вещественных целых и дробных частей складываемых чисел, а так же массивов, в которые будет записан результат сложения. Во второй раз передаются мнимые
части. После этого, если одна из складываемых переменных была комплексной, полю
isComplex результата сложения присваивается true, в противном случае присваивается false.
Функция void sumRealOrImagParts(int *wholeA, int *fractionalA,
int *wholeB, int *fractionalB, int *wholeResult, int
*fractionalResult) может выполнять по отдельности сложение как комплексной, так
и вещественной частей числа.
В качестве аргументов функция получает:
int *wholeA – указатель на первый элемент массива, содержащего целую часть
первого из складываемых чисел;
int *fractionalA – указатель на первый элемент массива, содержащего дробную
часть первого из складываемых чисел;