Методические указания к выполнению курсовой работы по дисциплине «Основы конструирования программ». 1_Меженная_ОКП_Курсовое_проектирование_Пособие. Курсовое проектирование
![]()
|
Рекомендации по работе с динамически создаваемыми массивами: 1. Для динамического создания массива необходимо запрашивать память в процессе выполнения программы. В контексте курсовой работы объем запрашива- емой памяти соответствует количеству структур в файле. Рекомендуется запро- граммировать отдельную функцию (например, getSizeOfFileWithStudents()) для вычисления количества структур в файле перед созданием динамического масси- ва. Примеры кода, реализующие данную задачу, приведены в подразделе 6.2. 25 int number_of_students = getCountOfStucturesInFile(FILE_OF_DATA); int *arr = new int [ number_of_students ]; // динамически создаваемый массив 2. Для динамически создаваемых массивов необходимо освобождать выде- ленную память в конце программы (в противном случае – утечки памяти): delete []arr; // освобождение памяти, выделенной под динамический массив 3. Способ увеличения размера динамически созданного массива (для добав- ления нового элемента в массив): создаем новый массив, переносим туда содер- жимое старого массива, старый массив удаляем. Пример перевыделения памяти с целью увеличения размера динамически созданного массива приведен в подразде- ле 6.3. 4. Для удаления элемента из динамически созданного массива необходимо сдвинуть все элементы на одну позицию, начиная с номера удаляемого элемента, а затем уменьшить логический размер массива (см. пример кода, реализующего данную задачу, в пояснениях к статически создаваемым массивам). В качестве альтернативы массивам выступают векторы из стандартной биб- лиотеки шаблонов. Векторы безопаснее и удобнее, чем массив, но в общем случае медленнее с точки зрения производительности. Рекомендации по работе с векторами: 1. Для полноценной работы с векторами необходимо подключить следующие библиотеки: #include #include #include 2. Для минимизации операций перевыделения памяти для вектора при считы- вании информации из файла рекомендуется после объявления вектора зарезерви- ровать для него память. При этом целесообразно отталкиваться от количества структур в читаемом файле. Наиболее удобно запрограммировать отдельную функцию (например, getCountOfStucturesInFile( string file_path )) для вычисления количества структур в файле, примеры кода, реализующие данную задачу, приве- дены в подразделе 6.2. Тогда: vector < Student > vector_of_students; /* резервируем память на getSizeOfFileWithStudents() элементов, но ничем не за- полняем: */ vector_of_students.reserve(getCountOfStucturesInFile(FILE_OF_DATA)); 26 3. Передача вектора в функцию осуществляется либо по значению, либо по ссылке (например, если в функции происходят изменения вектора). Размер векто- ра при этом в функцию передавать не нужно (он доступен внутри функции по- средством библиотечного метода size()). Примеры: void showStudents( vector < Student > vec_of_students ); // вывод массива на экран void delStudent( vector < Student > & vec_of_students ); // удаление элемента массива 4. Добавление элемента в конец вектора осуществляется посредством биб- лиотечного метода push_back: void readFileStudents( vector < Student > & vec_of_students ) { ifstream fin(FILE_OF_STUDENTS, ios ::in); if (!fin.is_open()) cout << "Файл не существует!" ; else { Student student_temp; while (!fin.eof()) { fin >> student_temp.surname >> student_temp.name >> student_temp.age; vec_of_students .push_back(student_temp); } } fin.close(); } 5. Удаление элемента из вектора, расположенного по индексу index_for_delete, осуществляется посредством библиотечного метода erase (при этом необходимо с помощью итератора begin() выполнить позиционирование в начало вектора, а далее осуществить сдвиг на требуемое число позиций): vec_of_students .erase( vec_of_students .begin() + index_for_delete); 6. Для доступа к элементу вектора используется метод at: void writeFileStudents( vector < Student > vec_of_students ) 27 { ofstream fout(FILE_OF_STUDENTS, ios ::out); for ( int i = 0; i < vec_of_students .size(); i++) { fout << vec_of_students .at(i).surname << " " << vec_of_students .at(i).name << " " << vec_of_students .at(i).age; if (i < vec_of_students .size() - 1) fout << endl; } fout.close(); } 7. Сортировку вектора можно выполнить с помощью метода sort из библиоте- ки algorithm, при этом требуется создать дополнительную функцию-компаратор, определяющую, по какому полю и в каком порядке (по возрастанию/по убыва- нию) будет выполнена сортировка: void sortStudentsBySurname( vector < Student > & vec_of_students ) // сортировка { sort( vec_of_students .begin(), vec_of_students .end(), mySortBySurname); } bool mySortBySurname( Student student_a , Student student_b ) // функция-компаратор { return student_a .surname < student_b .surname; // по алфавиту: от а до я } 4.4 Минимизация области видимости переменных Областью видимости называют фрагмент программы, в котором переменная известна и может быть использована. В C++ переменная может иметь область ви- димости, соответствующую фрагменту кода, заключенному в фигурные скобки, в котором переменная объявлена и используется (локальная переменная) или всей программе (глобальная переменная; объявляется вне блоков описания функций). Важно: минимизировать область видимости переменной, сделав ее как можно более локальной. 28 Несмотря на то, что глобальные переменные облегчают доступ к ним, так как не нужно беспокоиться о списках параметров и правилах области видимости, удобство доступа к глобальным переменным не может компенсировать связанную с этим опасность. Так программу, в которой каждый метод может вызвать любую переменную в любой момент времени, сложнее понять, чем код, основанный на грамотно организованных методах. Сделав данные глобальными, вы не сможете ограничиться пониманием работы одного метода: вы должны будете понимать ра- боту всех других методов, которые вместе с ним используют те же глобальные данные. Подобные программы сложно читать, сложно отлаживать и сложно изме- нять. В то время как локальная область видимости способствует интеллектуальной управляемости: чем больше информации скрыто, тем меньше нужно удерживать в уме в каждый конкретный момент времени и тем ниже вероятность того, что раз- работчик допустит ошибку, забыв одну из многих деталей, о которых нужно было помнить. Важно: выбирать локальную область видимости для массивов/векторов. Глобальные переменные имеют два главных недостатка: функции, обра- щающиеся к глобальным данным, не знают о том, что другие функции также обращаются к этим данным; или же функции знают об этом, но не могут контролировать, что именно другие функции делают с глобальны- ми данными. 4.5 Разделение программы на независимые cpp-файлы и их подключение с помощью заголовочных h-файлов Следует выносить код логически независимых модулей в отдельные cpp- файлы и подключать их с помощью заголовочных h-файлов (рисунок 4.4). Рисунок 4.4 – Способы объявления функций в С++ 29 Разделение исходного кода программы на несколько файлов становится не- обходимым по ряду причин: 1. Обеспечение удобства работы с небольшими по объему фрагментами кода, локализованными в отдельных файлах. 2. Разделение программы на логически завершенные модули, которые реша- ют конкретные подзадачи. 3. Разделение программы на физически независимые модули с целью повтор- ного использования этих модулей в других программах. 4. Разделение интерфейса и реализации. Последний принцип (разделение интерфейса и реализации) требует особого пояснения. Отдельный модуль состоит из двух файлов: заголовочного h-файла (интерфейс) и cpp-файла реализации. Для удобства h-файл и cpp-файл называют одинаково; имя должно соответствовать смысловой нагрузке данного модуля. Заголовочный файл подключается в одноименный с ним cpp-файл с реализа- цией программной логики модуля, а также в файл, который будет осуществлять вызов функций данного модуля (например, в файл с запускающей функцией main()). В этом смысле заголовочный файл представляет собой связующее звено между файлом, который задействует логику модуля, и файлом, собственно реали- зующим эту логику. Подклчение заголовочного файла производится посредством директивы препроцессора include, например: #include "validation.h" Сам по себе заголовочный файл не является единицей компиляции: на этапе обработки исходного кода препроцессором директива #include "validation.h" заме- няется текстом из файла validation.h. Заголовочный файл может содержать: директивы препроцессора; глобальные константы; описание типов пользователя; прототипы функций. Заголовочный файл не должен содержать описание функций! Заголовочный файл должен иметь механизм защиты от повторного включе- ния. Защита от повторного включения реализуется директивой препроцессора: #pragma once Файл реализации содержит описание функций. Следует отметить, что при не- совпадении сигнатуры функции в прототипе (h-файл) и в определении (cpp-файл) компилятор выдаст ошибку. Также файл реализации может содержать объявления. Объявления, сделанные в файле реализации, будут лексически локальны для этого 30 файла, т. е. будут действовать только для этой единицы компиляции. При этом в файле реализации не должно быть объявлений, дублирующих объявления в соот- ветствующем заголовочном файле. В файле реализации должна быть директива включения соответствующего заголовочного файла. Далее на рисунках 4.5–4.10 приведены примеры выделения в программе неза- висимого модуля, выполняющего проверку вводимых данных на корректность, и его подключения к файлу main.cpp. Данный модуль состоит из заголовочного файла validation.h и файла реализации validation.cpp. Рисунок 4.5 – Добавление в проект заголовочного файла Рисунок 4.6 – Создание заголовочного файла validation.h 31 Рисунок 4.7 – Структура проекта с запускающим файлом main.cpp, а также модулем validation, состоящим из заголовочного файла validation.h и файла реализации validation.cpp Рисунок 4.8 – Пример программной реализации файла main.cpp 32 Рисунок 4.9 – Пример программной реализации файла validation.h Рисунок 4.10 – Пример программной реализации файла validation.cpp 33 5 Рекомендации по разработке алгоритмов работы программы Алгоритм – набор инструкций, описывающих порядок действий исполнителя для достижения некоторого результата.В самом упрощенном виде разработку простейшей программы можно представить в виде схемы: анализ задачи → разра- ботка (обдумывание) алгоритма ее решения → программирование (реализация ал- горитма с использованием конкретного алгоритмического языка программирова- ния).Разработать алгоритм решения задачи означает разбить задачу на последова- тельно выполняемые шаги. При этом должны быть четко указаны как содержание каждого шага, так и порядок выполнения шагов. В блок-схеме алгоритма отдельные шаги изображаются в виде блоков раз- личной формы, соединенных между собой линиями, указывающими направление последовательности. Правила оформления алгоритмов регламентируются ГОСТ 19.701-90 Схемы алгоритмов, программ, данных и систем. Условные обозначения и правила выполнения. В таблице 5.1 приведены основные символы блок-схем алгоритмов и коммен- тарии по их применению. Важно: при начертании элементов необходимо придерживаться строгих размеров, определяемых двумя значениями a и b. Значение a выбирается из ряда 15, 20, 25, ... мм, b рассчитывается из соотношения 2a = 3b. Минимальное количество текста, необходимого для понимания функции символа, следует помещать внутри данного символа. Если объем текста внутри символа превышает его размеры, следует использовать символ комментария. В таблице 5.2 приведены обозначения соединений между символами блок- схем алгоритмов. Соединения между символами показываются линиями и носят название потоков. Направление потока слева направо и сверху вниз считается стандартным, стрелки в таких потоках не указываются. В случаях если поток име- ет направление, отличное от стандартного (справа налево или снизу вверх), при- менение стрелок обязательно. Стрелки выполняются с развалов в 60°. Важно: чтобы линии в схемах подходили к центру символа сверху, а исходили снизу. Исключениями из этого правила являются символы со- единитель, терминатор (с альтернативным сценарием программы), реше- ние и подготовка, допускающие иные направления входных и/или вы- ходных потоков. Важно: пересечения линий следует избегать, так как это существенно затрудняет восприятие алгоритма. 34 Таблица 5.1 – Символы блок-схем алгоритмов Название и обозначение на схеме Функция Терминатор Выход во внешнюю среду и вход из внеш- ней среды (начало или конец программы). На практике имеют смысл следующие описания терминаторов: начало/конец, за- пуск/остановка, ошибка (подразумевает завершение алгоритма с ошибкой), исклю- чение (подразумевает генерацию про- граммного исключения), наимнование действия (например, имя функ- ции/метода), возврат (подразумевает воз- врат из функции/метода в основную про- грамму каких-либо данных) Данные Ввод или вывод данных Процесс Обработка данных любого вида (выполне- ние определенной операции или группы операций, приводящее к изменению зна- чения, формы или размещения информа- ции). Например, инициализация перемен- ной, арифметические действия над данны- ми и др. Предопределенный процесс Отображение процесса, состоящего из ша- гов программы, которые определены в другом месте. Используется для вызова функции/метода. Внутри блока записывается имя вызывае- мой функции или метода 35 Продолжение таблицы 5.1 Название и обозначение на схеме Функция Решение Отображение условия на алгоритме. Вход в элемент обозначается линией, входящей в верхнюю вершину элемента, выходы – ли- ниями, выходящими из оставшихся вершин и сопровождающимися соответствующими значениями условий. Если выходов больше трех (например, в switch-case), то их следует показывать од- ной линией, выходящей из нижней верши- ны элемента, которая затем разветвляется в соответствующее число линий Границы цикла Отображение границ цикла. Обе части символа имеют один и тот же идентификатор. Условия для инициализации, приращения, завершения помещаются внутри символа в начале или в конце в зависимости от распо- ложения операции, проверяющей условие. Символ используется для циклов do-while, while, for Подготовка Подготовительные операции для счетных циклов (циклов for) Соединитель Указание связи между прерванными лини- ями схемы (например, разделение блок- схемы, не помещающейся на листе). Соот- ветствующие символы-соединители долж- ны содержать одно и то же уникальное обо- значение (цифра или буква) 36 Продолжение таблицы 5.1 Название и обозначение на схеме Функция Комментарий Добавление пояснительных записей. Пунк- тирные линии в комментарии связаны с со- ответствующим символом или группой символов (при этом группа выделяется за- мкнутой пунктирной линией). Текст ком- ментария помещается около ограничиваю- щей его по всей высоте скобки. Комментарий также используется, когда объем текста, помещаемого внутри некоего символа, превышает его размер. Комментарии используют с терминаторами для описания входных аргументов функ- ции/метода Таблица 5.2 – Соединения между символами блок-схемы алгоритма Наименование Обозначение на схеме Функция Линии потока Указание направления линии потока: без стрелки, если линия направлена слева направо или сверху вниз; со стрелкой в остальных случаях Излом линии под углом 90° Изменение направления потока Пересечение линий потока Пересечение двух несвязанных потоков следует избегать! Слияние двух линий потока Слияние двух линий потока, каждая из которых направлена к одному и тому же символу на схеме На рисунках 5.1–5.3 приведены примеры, поясняющие использование раз- личных графических символов на блок-схемах алгоритмов. 37 Начало Конец Действие 1 Действие 2 A A Действие 3 Действие 4 Деление на ноль Начало Ввод n, m m = 0 ? Возврат n/m Ошибка Да Нет а б Рисунок 5.1 – Пример использования терминатора и соединителя (а), ввода данных, условия и комментария (б) на блок-схеме алгоритма Условие Действие 1 Действие 2 Нет Да Условие Действие 1 Нет Да а б Условие Действие 3 Действие 2 Действие N Действие 1 Значение 1 Значение 2 Значение 3 Значение N в Рисунок 5.2 – Отображение условной конструкции if-else (а), if (б), оператора выбора switch-case (в) на блок-схеме алгоритма 38 Начало а = 1 Цикл i от 1 до n а = a*i Увеличить i на 1 Цикл i Вывод а Конец Цикл j Тело цикла Конец цикла j Конец цикла i Цикл i а б Рисунок 5.3 – Примеры отображения одного цикла (а) и вложенных циклов (б) на блок-схеме алгоритма 39 Далее на рисунках 5.4–5.9 приведены примеры алгоритмов ряда функций для курсовой работы. Важно: данные примеры носят обучающий характер, не предназначены для копирования в курсовую работу, не являются единственно верным вариантом реализации логики курсовой работы. В частности, данные ал- горитмы могут выглядеть совершенно иначе вследствие авторского под- хода к функциональной декомпозиции задач курсовой работы, а также могут быть расширены за счет дополнительных функциональных воз- можностей, обработки исключительных ситуаций. Начало systemSettings startSession core endSession Конец |