Программирование C++ (1 сем, мага) / Лабы / Лабораторная работа №5
.docxМинистерство науки и высшего образования Российской Федерации
Федеральное государственное автономное образовательное учреждение высшего образования
«Национальный исследовательский ядерный университет «МИФИ»
Кафедра экспериментальной ядерной физики и космофизики (№7)
Программирование на С и С++
Отчет по лабораторной работе № 5
Выполнил:
студент группы 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)){…} соответствует случаю, когда закончилось считывание какой-либо из частей числа (предыдущий символ – цифра, а текущий цифрой не является). Он внутри себя содержит ещё ряд проверок:
if (symbol == '.') {…} – т.к. точка является разделителем целой и дробной частей числа, этот блок соответствует ситуации, когда закончилось считывание целой части и произошел переход к считыванию дробной. Здесь переменная currentPart увеличивается на 1 (для целых вещественной и мнимой частей она равна 0 и 2 соответственно, а для дробных – 1 и 3), а переменной j присваивается значение 0, т.к. десятым долям соответствуют элементы массивов fractional и fractionalComplex с индексом ноль.
else if (symbol == '+' or symbol == '-') {…} – закончилось считывание вещественной дробной записи числа и далее следует комплексная часть. Здесь переменной currentPart присваивается значение 2, переменной j – 2, numbersArray[i].isComplex присваивается значение true.
else if (currentPart == 1) {…} – закончилось считывание вещественной дробной части числа, но мнимой части не последовало, т.е. число действительное и полностью считано. В этом блоке numbersArray[i].isComplex присваивается значение false, а также заполняются нулями массивы numbersArray[i].wholeComplex и numbersArray[i].fractionalComplex. Переменным currentPart и j присваиваются значения 0 и 2 соответственно, т.к. текущее число закончено и, как только снова в потоке символов встретится цифра, начнется запись следующего. По этой же причине i увеличивается на единицу.
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 – указатель на первый элемент массива, содержащего дробную часть первого из складываемых чисел;
int *wholeB – указатель на первый элемент массива, содержащего целую часть второго из складываемых чисел;
int *fractionalB – указатель на первый элемент массива, содержащего дробную часть второго из складываемых чисел;
int *wholeResult – указатель на первый элемент массива, в который будет записана целая часть результата сложения двух чисел;int *fractionalResult – указатель на первый элемент массива, в который будет записана дробная часть результата сложения двух чисел.
Внутри функции реализованы несколько условных блоков:
1. Блок if (wholeA[0] == wholeB[0]){…} выполняется, если складываемые числа имеют один знак. При сложении чисел одного знака результат равен сумме модулей этих чисел, а знак результата – знаку, который имеют числа. В этом блоке сначала в цикле for поразрядно складываются дробные части числа, затем опять же поразрядно в цикле for – целые. После сложения чисел также заполняется значение нулевого элемента массива, содержащего целую часть результата, – элемента, описывающего знак числа. Сложение разрядов чисел осуществляется с помощью функции sumDigits(…), caseNumber = 0. Описание этой функции будет приведено ниже.
2. Блок else if (wholeA[0] == 0 and wholeB[0] == 1) {…} – первое из складываемых чисел положительно, а второе отрицательно. С помощью функции checkAbs(…) (описание её работы будет приведено ниже) проверяется, больше или равно положительное число отрицательному. Если это так, переменная isPosBigger = true, иначе false. Далее в циклах с помощью функции sumDigits(…), caseNumber = 1 поразрядно складываются дробные и целые части чисел. Первым в функцию передается большее по модулю число. Знак результата сложения чисел принимает значение, равное знаку большего по модулю числа.
3. Блок else if (wholeA[0] == 1 and wholeB[0] == 0) {…} – первое из складываемых чисел отрицательно, второе положительно. Логика выполнения этого блока аналогично предыдущему.
Рассмотрим функцию int sumDigits(int a, int b, int *addToNext, int caseNumber).
Функция принимает в качестве аргументов:
int a – цифру, стоящую в складываемом разряде первого числа;
int b – цифру, стоящую в этом же разряде второго числа;
int *addToNext – указатель на переменную, хранящую величину, которую нужно добавить к текущему складываемому разряду (1, -1, 0);
int caseNumber – если складываемые числа (именно числа) имеют один знак, то значение равено 0, если числа имеют разный знак – 1.
Возвращает функция значение типа int, равное результату сложения разрядов двух чисел.
Функция состоит из двух условных блоков:
1. if (caseNumber == 0) {…} – складываются разряды чисел одного знака. Если сумма складываемых цифр не превышает 9, результирующее значение равно сумме цифр, а добавка к следующему разряду равна 0. Если же сумма двух цифр превышает 10, то результирующее значение равно остатку от деления этой суммы на 10, а добавка к следующему разряду – +1.
2. else if (caseNumber == 1) {…} – одно из складываемых чисел отрицательно, а второе положительно. Фактически происходит вычитание одного числа из второго. Причем важно передавать в функцию большее по модулю число. Если цифра, стоящая в разряде уменьшаемого с учетом добавки от предыдущего разряда (может быть равно 0 или -1) меньше цифры, стоящей в этом же разряде вычитаемого, то занимается десяток из следующего разряда и выполняется вычитание. Добавка к следующему разряду становится равной -1. Если цифра, стоящая в разряде уменьшаемого с учетом добавки от предыдущего разряда больше или равна цифре, стоящей в этом же разряде вычитаемого, то просто выполняется вычитание, а добавка к следующему разряду равна 0.
Рассмотрим функцию bool checkAbs(int *wholePos, int *fractionalPos, int *wholeNeg, int *fractionalNeg) {…}.
Функция принимает в качестве аргументов:
int *wholePos – указатель на первый элемент массива, содержащего целую часть положительного числа;
int *fractionalPos – указатель на первый элемент массива, содержащего дробную часть положительного числа;
int *wholeNeg – указатель на первый элемент массива, содержащего целую часть отрицательного числа;
int *fractionalNeg – указатель на первый элемент массива, содержащего отрицательного числа.
Функция возвращает true, если положительное число больше, и false, если отрицательное число больше. Если числа равны, возвращается true.
Поразрядно, начиная со старшего разряда, выполняется сравнение чисел. Как только цифра в разряде положительного числа оказывается меньше цифры в соответствующем разряде отрицательного, возвращается false. И наоборот, как только цифра в разряде положительного числа оказывается больше цифры в разряде отрицательного, -- возвращается true.
Иллюстрация работы операции сложения:
Здесь представлены случаи сложения:
двух положительных действительных чисел;
положительного действительного числа и комплексного числа, у которого и вещественная и мнимая части больше нуля;
положительного действительного числа и комплексного числа, у которого и вещественная и мнимая части меньше нуля (причем эта сумма посчитана дважды – числа передавались в функцию в разном порядке, результат получился одинаковый);
комплексного числа с положительными вещественной и мнимыми частями и комплексного числа, у которого вещественная часть положительна, а мнимая – отрицательна;
комплексного числа с положительными вещественной и мнимыми частями и комплексного числа, у которого и вещественная, и мнимая части отрицательны;
комплексного числа, у которого вещественная часть положительна, а мнимая – отрицательна, и комплексного числа, у которого и вещественная, и мнимая части отрицательны.
Полученные результаты сложения чисел корректны.
Для вывода числа, хранящегося в структурной переменной, написана функция void printNumber(longNumber *number) {…}.
Функция принимает в качестве аргумента указатель на структуру longNumber. Результатом ее работы является вывод структуры на экран в виде числа.
Сначала выводится вещественная часть числа. Если эта часть отрицательна, печатается ‘-’, если положительна, то знак не выводится. Далее в цикле for выводится целая часть (причем нули в старших разрядах опускаются), а затем также в цикле for – дробная. Если число комплексное, аналогичным образом выводится его мнимая часть. Перед мнимой частью знак выводится в любом случае, также выводится мнимая единица.
Вывод операции сложения двух чисел осуществляется с помощью функции void printSumOperation(longNumber *a, longNumber *b, longNumber *result):
В качестве аргументов функция принимает указатели на структуры двух складываемых чисел и на число, являющееся их суммой. Эта функция вызывалась в блоке main() для вывода операций сложения в терминал.
Заключение
В лабораторной работе создана структура для хранения длинных чисел и выполнено чтение чисел из файла с последующим их сохранением в структурные переменные.
Для полученных чисел также реализована операция сложения и вывод этих чисел в консоль.
Формат исходных чисел не позволял хранить их в переменных стандартных типов данных, но с использованием структур удалось считать эти числа из файла, записать в структурные переменные и использовать в дальнейшем, например, в операции сложения.
