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

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

.docx
Скачиваний:
0
Добавлен:
01.09.2026
Размер:
3 Мб
Скачать

Министерство науки и высшего образования Российской Федерации

Федеральное государственное автономное образовательное учреждение высшего образования

«Национальный исследовательский ядерный университет «МИФИ»

Кафедра экспериментальной ядерной физики и космофизики (№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

Стандартный поток сообщений об ошибках

Ход работы

Файл, содержащий набор чисел, имел следующий вид:

Рисунок 4

Файл содержит как действительные, так и комплексные числа разных знаков. Причем целая часть чисел содержит 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) { … }

Далее выполняется ряд проверок, стобы определить, является ли считанный символ цифрой одного из чисел и если да, то в какую часть числа его необходимо записывать.

Shape2

Первый блок 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, переменной j – 2, 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().

ПРисунок 20 осле заполнения массива структур его элементы имеют следующий вид:

Рассмотрим теперь, как реализовано сложение чисел.

Для того, чтобы сложить два числа, хранящихся в структурных переменных, написана функция stuct longNumber sumNumbers(longNumber *a, longNumber *b):

Функция принимает в качестве аргументов два указателя – каждый из которых указывает на структуру longNumber и возвращает объект типа longNumber.

Внутри функции создается структурная переменная struct longNumber result, которая будет содержать результат сложения. Далее дважды вызывается функция sumRealOrImagParts(…), в которую первый раз передаются указатели на первые элементы массивов вещественных целых и дробных частей складываемых чисел, а так же массивов, в которые будет записан результат сложения. Во второй раз передаются мнимые части. После этого, если одна из складываемых переменных была комплексной, полю isComplex результата сложения присваивается true, в противном случае присваивается false.

ФShape3 ункция 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() для вывода операций сложения в терминал.

Заключение

В лабораторной работе создана структура для хранения длинных чисел и выполнено чтение чисел из файла с последующим их сохранением в структурные переменные.

Для полученных чисел также реализована операция сложения и вывод этих чисел в консоль.

Формат исходных чисел не позволял хранить их в переменных стандартных типов данных, но с использованием структур удалось считать эти числа из файла, записать в структурные переменные и использовать в дальнейшем, например, в операции сложения.