Методические указания к выполнению курсовой работы по дисциплине «Основы конструирования программ». 1_Меженная_ОКП_Курсовое_проектирование_Пособие. Курсовое проектирование
Скачать 2.04 Mb.
|
Предварительные настройки: подключение русскоязычной локали; вывод информации о программе: название, ФИО разработчика Начало сеанса работы программы: объявление и резервирование памяти под массивы/векторы; чтение информации из файлов в массивы/векторы Ядро работы программы: авторизация; регистрация (если предусмотрена); реализация функционала пользователя или администратора Завершение сеанса работы программы: запись информации из массивов/векторов в файлы; очистка памяти (для динамически созданных массивов) Рисунок 5.4 – Пример алгоритма главной функции main 40 core mainMenu choiceMenuItem Конец Ядро работы программы: входные параметры: массив/вектор аккаунтов, массив/вектор данных, размеры массивов Вывод главного меню: 1-авторизация 2-регистрация (если предусмотрена) 0-завершение работы программы Ввод пункта меню возвращает item - целое положительное число из диапазона возможных пунктов меню Бесконечный цикл 1 item logIn Бесконечный цикл 1 registration = 0 = 1 = 2 Рисунок 5.5 – Пример алгоритма функции core: основная логика программы 41 logIn authorization Конец Вход в систему: входные параметры: массив/вектор аккаунтов, массив/вектор данных, размеры массивов Авторизация: входные параметры: массив/вектор аккаунтов, размер массива аккаунтов; возвращает role (роль) Бесконечный цикл 1 role Бесконечный цикл 1 adminModule = 0 иное = 1 userModule Такой пользователь не найден repeatRequest Повторить авторизацию? Запрос на повтор действия; возвращает ответ Нет Да Рисунок 5.6 – Пример алгоритма функции logIn: вход в систему 42 authorization Возврат current_role Авторизация: входные параметры: массив/вектор аккаунтов, размер массива аккаунтов; возвращает role (роль) Обнаружено совпадение? Нет Да Сравниваем current_login и current_password с соответствующими полями i-го элемента массива/вектора int current_login; int current_password; int current_role = - 1; int i = 0 Цикл 1 Пока i < размера массива/вектора и current_role = = - 1 current_role присваиваем значение роли i-го элемента массива/вектора i = i + 1 Цикл 1 Ввод current_login current_password Рисунок 5.7 – Пример алгоритма функции authorization: авторизация 43 userModule Конец Модуль пользователя: входные параметры: массив/вектор данных, размер массива данных item Бесконечный цикл 1 Бесконечный цикл 1 showUserMenu Вывод меню пользователя choiceMenuItem Ввод пункта меню возвращает item - целое положительное число из диапазона возможных пунктов меню searchData sortData individualTask showData = 0 = 1 = 2 = 3 = 4 Рисунок 5.8 – Пример алгоритма функции userModule: функционал пользователя 44 showData Конец Вывод данных на экран: входные параметры: массив/вектор данных, размер массива данных showTableTitle i = 0; i < размера массива/вектора; i = i + 1 Вывод i-го элемента массива/вектора Вывод названий столбцов таблицы данных Рисунок 5.9 – Пример алгоритма функции showData: вывод данных на экран 45 6 Рекомендации по программированию курсовой работы 6.1 Типичные ошибки начинающих при написании кода. Способы отслеживания и устранения ошибок. Создание exe-файла проекта Типичные ошибки начинающих при написани кода: 1. Пропуск «;» в конце инструкции или размещение «;» там, где это не нужно. 2. Путаница в количестве открывающихся «{» и закрывающихся «}» скобок (пишите обе скобки одновременно!). 3. Использование в условных конструкциях знака присвоения «=» вместо «= =». 4. Ни о чем не говорящие имена переменных и функций. 5. Объявление служебных переменных и попытка их использования без инициализации (без присвоения начального значения). 6. «Магические» числа и строки в коде. 7. Отсутствие освобождения памяти (с помощью оператора delete) после ее ручного выделения (с помощью оператора new). 8. Использование слишком длинных функций, которые в реальности следо- вало бы разбить на несколько функций. 9. Сложная (неочевидная, неоптимальная) логика решения задачи. 10. Аргументация «Но программа же работает!» в ответ на замечания по ло- гике работы программы, «недружественному» интерфейсу и оформлению кода. Для отслеживания и устранения ошибок рекомендуется использовать сле- дующие подходы в том порядке, в котором они перечислены далее: 1. Построение (сборка) проекта в среде разработки. 2. Отладка в пошаговом режиме. 3. Рефакторинг. Частая сборка проекта, выполняемая в процессе работы над кодом, позволяет быстро выявлять ошибки компиляции и компоновки, например неверный синтак- сис, несоответствия типов и другое (рисунок 6.1) и своевременно их устранять. Успешная сборка – это подтверждение правильности синтаксиса кода прило- жения и корректного разрешения всех статических ссылок на библиотеки. Однако успешная сборка не исключает наличие логических ошибок, о которых будут сви- детельстовать неверные результаты работы программы. Логические ошибки мо- гуть быть эффективно локализованы посредством отладки программы, позволяю- щей узнать текущие значения переменных; выяснить, по какому пути выполня- лась программа. Существуют две взаимодополняющие технологии отладки: 1. Пошаговое выполнение программы с остановками на строчках исходного кода (рисунок 6.2). 2. Логирование – вывод текущего состояния программы с помощью располо- женных в критических точках программы операторов вывода. 46 Рисунок 6.1 – Пример обнаружения ошибки сборки проекта 1 – устанавливаем точку остановки, 2 – после запуска программы переходим от точки остановки к следующей инструкции посредством панели инструментов для пошаговой отладки; 3 – отслеживаем значения переменных в процессе отладки Рисунок 6.2 – Пример отладки программы в пошаговом режиме 47 Помимо синтаксических и логических ошибок собственно качество кода про- граммы зачастую нуждается в улучшении – рефакторинге. Рефакторинг – процесс изменения внутренней структуры программы, не затрагивающий ее внешнего по- ведения и имеющий целью облегчить понимание ее работы. Ни о чем не говоря- щие имена переменных и функций, «магические» числа и строки в коде, исполь- зование слишком длинных функций, сложная (неочевидная, неоптимальная) логи- ка – вот примеры проблем, которые необходимо решить на стадии рефакторинга. После полного тестирования приложения и рефакторинга целесообразно скомпилировать итоговую release-версию проекта (помимо отладочной debug- версии) – рисунки 6.3, 6.4. Рисунок 6.3 – Выбор версии проекта для сборки: debug или release Рисунок 6.4 – Изменение динамического связывания библиотек времени выполнения на статическое связывание 48 Следует отметить, что приложения, созданные с помощью Microsoft Visual Studio, зависят от Visual С++ Redistibutable и динамически связаны с библиотека- ми MSVCR**.dll (Microsoft C Runtime Library). Для последующих запусков exe- файла на других компьютерах, которые потенциально могут не содержать требуе- мых библиотек (не установлена Microsoft Visual Studio или версия установленной Microsoft Visual Studio ниже требуемой) перед сборкой итоговой версии необхо- димо изменить динамическое связывание библиотек времени выполнения на ста- тическое связывание. Для этого требуется перейти к свойствам проекта (щелкнув правой кнопкой мыши на имени проекта в Обозревателе решений), а в разделе C/С++ → Создание кода найти параметр Библиотека времени выполнения. Его необходимо изменить с Многопоточная DLL (/MD) на Многопоточная (/MT) – см. рисунок 6.4. 6.2 Структуры. Запись и чтение из файла В языке C++ потоки, которые позволяют читать и записывать информацию в файл, являются объектами классов ifstream, ofstream. Они находятся в библиотеке с заголовочным файлом ifstream: класс, функции которого используются для чтения файлов; ofstream: класс, функции которого используются для записи файлов. Название класса эквивалентно типу переменной, поэтому после названия класса объявляется объект, тип которого будет соответствовать классу. Для ини- циализации объектов в конструктор класса необходимо передать параметры: пер- вый параметр – путь к файлу, второй параметр – режим работы с файлом. Кон- станты режима файлов: ios::in – открыть файл для чтения; ios::out – открыть файл для записи; ios::ate – перейти к концу файла после открытия; ios::app – добавлять к концу файла; ios::binary – бинарный файл. Далее приведены два примера записи и чтения в файл. Первый пример: работа с файлом выполняется в текстовом режиме. В качестве строковых полей структуры используем класс string, структуры объеди- няем в статически создаваемые локальные массивы, запись и чтение выполняем посредством указания всех полей структуры. 49 Приведенный ниже пример ориентирован на статически создаваемые масси- вы, а именно включает целый ряд проверок выхода за пределы зарезервированной под статический массив памяти. С точки зрения скорости данный вариант работы с файлом уступает второму варианту, однако сам файл содержит информацию в текстовом виде (также при ручной записи в файл информации требуемого формата она впоследствии может быть корректно считана программно). #include #include #include using namespace std; struct Student { string name; string surname; int age; }; // Запись в файл (если что-то было в файле, то исходные данные будут удалены): void writeFileStudents( Student * arr_of_students , int number_of_students ); // Добавление в конец файла одной строки: void writeEndFileStudents( Student new_student ); // Чтение из файла в массив: void readFileStudents( Student * arr_of_students , int & number_of_students ); // Заполнение массива студентов void generateStudentArray( Student * arr_of_students , int & number_of_students ); // Добавление студента в массив void addStudentInArray( Student * arr_of_students , int & number_of_students ); // Удаление студента из массива void delStudentFromArray( Student * arr_of_students , int & number_of_students ); // Вывод содержимого массива на экран void showStudentArray( Student * arr_of_students , int number_of_students ); const string FILE_OF_DATA = "MyFile.txt" ; //Путь к файлу const int RESERVE_SIZE = 100; //Максимальное количество элементов массива 50 void main() { setlocale( LC_ALL , "rus" ); Student arr_of_students[RESERVE_SIZE]; int number_of_students = 0; generateStudentArray(arr_of_students, number_of_students); writeFileStudents(arr_of_students, number_of_students); addStudentInArray(arr_of_students, number_of_students); showStudentArray(arr_of_students, number_of_students); delStudentFromArray(arr_of_students, number_of_students); showStudentArray(arr_of_students, number_of_students); Student arr_new_of_students[RESERVE_SIZE]; /* Создаем новый массив исключительно для того, чтобы продемонстрировать корректность чтения данных из файла */ int new_number_of_students = 0; readFileStudents(arr_new_of_students, new_number_of_students); showStudentArray(arr_new_of_students, new_number_of_students); system( "pause" ); } void generateStudentArray( Student * arr_of_students , int & number_of_students ) { number_of_students = 2; arr_of_students [0].name = "Alex" ; arr_of_students [0].surname = "Black" ; arr_of_students [0].age = 20; arr_of_students [1].name = "Ivan" ; arr_of_students [1].surname = "Ivanov" ; arr_of_students [1].age = 25; } void addStudentInArray( Student * arr_of_students , int & number_of_students ) { 51 //добавление студента, если не происходит выход за пределы массива if ( number_of_students + 1 <= RESERVE_SIZE) { number_of_students ++; cout << "Введите имя студента: " ; cin >> arr_of_students [ number_of_students - 1].name; cout << "Введите фамилию студента: " ; cin >> arr_of_students [ number_of_students - 1].surname; cout << "Введите возраст студента: " ; cin >> arr_of_students [ number_of_students - 1].age; writeEndFileStudents( arr_of_students [ number_of_students - 1]); } else cout << "Недостаточно памяти для добавления нового элемента!" << endl; } void delStudentFromArray( Student * arr_of_students , int & number_of_students ) { int number_of_deleted_item; cout << "Введите номер удаляемой записи: " ; cin >> number_of_deleted_item; // пользователь мыслит с 1, но индексы нумеруются с 0 : number_of_deleted_item--; if (number_of_deleted_item >= 0 && number_of_deleted_item < number_of_students ) { for ( int i = number_of_deleted_item; i < number_of_students - 1; i++) arr_of_students [i] = arr_of_students [i + 1]; number_of_students --; writeFileStudents( arr_of_students , number_of_students ); } else cout << "Введен некорректный номер удалемой записи!" << endl; } void showStudentArray( Student * arr_of_students , int number_of_students ) { for ( int i = 0; i< number_of_students ; i++) cout << arr_of_students [i].name << " " 52 << arr_of_students [i].surname << " " << arr_of_students [i].age << endl; } void writeFileStudents( Student * arr_of_students , int number_of_students ) { ofstream fout(FILE_OF_DATA, ios ::out); // Открыли файл для записи for ( int i = 0; i< number_of_students ; i++) { fout << arr_of_students [i].name << " " << arr_of_students [i].surname << " " << arr_of_students [i].age; if (i< number_of_students - 1) { fout << endl; } } fout.close(); } void writeEndFileStudents( Student new_student ) { ofstream fadd(FILE_OF_DATA, ios ::app); // Открыли файл для дозаписи fadd << endl; fadd << new_student .name << " " << new_student .surname << " " << new_student .age; fadd.close(); } void readFileStudents( Student * arr_of_students , int & number_of_students ) { ifstream fin(FILE_OF_DATA, ios ::in); // Открыли файл для чтения if (!fin.is_open()) cout << "Указанный файл не существует!" << endl; else { int i = 0; while (!fin.eof()) { if (i < RESERVE_SIZE) 53 { fin >> arr_of_students [i].name >> arr_of_students [i].surname >> arr_of_students [i].age; i++; } else { cout << "Недостаточно памяти для чтения всех данных!" << endl; break ; } } number_of_students = i; } fin.close(); //Закрыли файл } При работе с динамически создаваемыми массивами целесообразно запраши- вать память в процессе выполнения программы, соответствующую количеству структур, записанных в файле. Для этого ниже приведена функция определения количества структур, записанных в текстовый файл (что соответствует количеству строчек в файле) – getCountOfStucturesInFile( string file_path ). // Определение количества структур в файле (при необходимости) int getCountOfStucturesInFile( string file_path ) { ifstream file( file_path , ios ::in); // Открыли текстовый файл для чтения int number_of_strings = 0; if (file.is_open()) { string buffer; while (getline(file, buffer)) number_of_strings++; } file.close(); return number_of_strings; } Второй пример: работа с файлом выполняется в бинарном режиме. Сна- чала приведем ряд пояснений по особенностям чтения и записи в файл в бинарном режиме. 54 При записи единичной структуры в бинарный файл записываем всю структу- ру целиком и сразу: ofstream fadd(FILE_OF_DATA, ios ::binary | ios ::app); fadd.write(( char *)& new_student , sizeof ( new_student )); /* Записываем структуру new_student в открытый нами файл; для этого узнаем адрес структуры new_student и приводим указатель на new_student к однобайтовому типу; указываем, что в структуре new_student находится sizeof( new_student) байт */ fadd.close(); Из файла можно прочитать всю структуру целиком и сразу: ifstream fin(FILE_OF_DATA, ios ::binary | ios ::in); if (!fin.is_open()) cout << "Указанный файл не существует!" << endl; else { fin.read(( char *) & temp_student , sizeof ( temp_student )); } fin.close(); При записи массива структур в бинарный файл записываем весь массив цели- ком и сразу: ofstream fout(FILE_OF_DATA, ios ::binary | ios ::out); fout.write(( char *)& arr_of_students [0], sizeof ( Student )* number_of_students ); fout.close(); Из файла целесообразно читать весь массив структур целиком и сразу, однако для этого необходимо знать количество структур в бинарном файле: int getCountOfStucturesInFile( string file_path ) { //Открываем файл и перемещаем указатель в конец файла ifstream file(FILE_OF_DATA, ios ::ate | ios ::binary); /* file.tellg() возвращает значение типа int, которое показывает, сколько ука- зателем пройдено в байтах от начала файла до текущей позиции */ int number_of_strings = file.tellg() / sizeof ( Student ); file.close(); 55 return number_of_strings; } Тогда собственно чтение в массив структур из файла программно реализуется следующим образом: ifstream fin(FILE_OF_DATA, ios ::binary | ios ::in); if (!fin.is_open()) cout << "Указанный файл не существует!" << endl; else { fin.read(( char *)& arr_of_students [0], sizeof ( Student )* getCountOfStucturesInFile(FILE_OF_DATA)); } fin.close(); Когда приходится записывать структуру или объект в файл, то внутри струк- туры может быть очень много информационных полей и при записи структуры в файл в текстовом режиме придется записывать каждый элемент структуры, а это отнимет много времени. В этом отношении данный вариант работы с файлом име- ет преимущество по скорости. Но заполненный таким способом файл при откры- тии в текстовом режиме нечитабельный, а попытка вручную записать информа- цию, чтобы считать ее программно, не приведет к желаемому результату. Далее приведен пример работы с файлом в бинарном режиме. В качестве строковых полей структуры используем строковые массивы, структуры объединя- ем в статически создаваемые локальные массивы. Приведенный ниже пример ориентирован на статически создаваемые масси- вы, а именно, включает целый ряд проверок выхода за пределы зарезервированной под статический массив памяти. #include #include #include using namespace std; const int SIZE_CHAR = 20; const int RESERVE_SIZE = 100; const string FILE_OF_DATA = "MyFile.txt" ; //Путь к файлу 56 struct Student { char name[SIZE_CHAR]; char surname[SIZE_CHAR]; int age; }; // Запись в файл (если что-то было в файле, то исходные данные будут удалены): void writeFileStudents( Student * arr_of_students , int number_of_students ); // Добавление в конец файла одной строки: void writeEndFileStudents( Student new_student ); // Чтение из файла в массив: void readFileStudents( Student * arr_of_students , int & number_of_students ); // Определение количества структур в файле (при необходимости) int getCountOfStucturesInFile( string file_path ); //0 Заполнение массива студентов void generateStudentArray( Student * arr_of_students , int & number_of_students ); // Добавление студента в массив void addStudentInArray( Student * arr_of_students , int & number_of_students ); // Удаление студента из массива void delStudentFromArray( Student * arr_of_students , int & number_of_students ); // Вывод содержимого массива на экран void showStudentArray( Student * arr_of_students , int number_of_students ); void main() { setlocale( LC_ALL , "rus" ); Student arr_of_students[RESERVE_SIZE]; int number_of_students = 0; generateStudentArray(arr_of_students, number_of_students); writeFileStudents(arr_of_students, number_of_students); addStudentInArray(arr_of_students, number_of_students); showStudentArray(arr_of_students, number_of_students); delStudentFromArray(arr_of_students, number_of_students); showStudentArray(arr_of_students, number_of_students); 57 Student arr_new_of_students[RESERVE_SIZE]; /* Создаем новый массив ис- ключительно для того, чтобы продемонстрировать корректность чтения данных из файла */ int new_number_of_students = 0; readFileStudents(arr_new_of_students, new_number_of_students); showStudentArray(arr_new_of_students, new_number_of_students); system( "pause" ); } void generateStudentArray( Student * arr_of_students , int & number_of_students ) { number_of_students = 2; strcpy_s( arr_of_students [0].name, "Alex" ); strcpy_s( arr_of_students [0].surname, "Black" ); arr_of_students [0].age = 20; strcpy_s( arr_of_students [1].name, "Alex1" ); strcpy_s( arr_of_students [1].surname, "Black1" ); arr_of_students [1].age = 27; } void addStudentInArray( Student * arr_of_students , int & number_of_students ) { //добавление студента, если не происходит выход за пределы массива if ( number_of_students + 1 <= RESERVE_SIZE) { number_of_students ++; cout << "Введите имя студента: " ; cin >> arr_of_students [ number_of_students - 1].name; cout << "Введите фамилию студента: " ; cin >> arr_of_students [ number_of_students - 1].surname; cout << "Введите возраст студента: " ; cin >> arr_of_students [ number_of_students - 1].age; writeEndFileStudents( arr_of_students [ number_of_students - 1]); } 58 else cout << "Недостаточно памяти для добавления нового элемента!" << endl; } void delStudentFromArray( Student * arr_of_students , int & number_of_students ) { int number_of_deleted_item; cout << "Введите номер удаляемой записи: " ; cin >> number_of_deleted_item; // пользователь мыслит с 1, но индексы нумеруются с 0: number_of_deleted_item--; if (number_of_deleted_item >= 0 && number_of_deleted_item < number_of_students ) { for ( int i = number_of_deleted_item; i < number_of_students - 1; i++) arr_of_students [i] = arr_of_students [i + 1]; number_of_students --; writeFileStudents( arr_of_students , number_of_students ); } else cout << "Введен некорректный номер удалемой записи!" << endl; } void showStudentArray( Student * arr_of_students , int number_of_students ) { for ( int i = 0; i < number_of_students ; i++) cout << arr_of_students [i].name << " " << arr_of_students [i].surname << " " << arr_of_students [i].age << endl; } void writeFileStudents( Student * arr_of_students , int number_of_students ) { //Открываем файл для записи: ofstream fout(FILE_OF_DATA, ios ::binary | ios ::out); fout.write(( char *)& arr_of_students [0], sizeof ( Student )* number_of_students ); fout.close(); } void writeEndFileStudents( Student new_student ) 59 { //Открываем файл для дозаписи: ofstream fadd(FILE_OF_DATA, ios ::binary | ios ::app); fadd.write(( char *)& new_student , sizeof ( new_student )); //Записали структуру fadd.close(); } void readFileStudents( Student * arr_of_students , int & number_of_students ) { ifstream fin(FILE_OF_DATA, ios ::binary | ios ::in); if (!fin.is_open()) cout << "Указанный файл не существует!" << endl; else { // определяем количество строк в файле int sizeOfFileWithStudents = getCountOfStucturesInFile(FILE_OF_DATA); /* если выделенная память под статический массив вмещает все строч- ки в файле */ if (sizeOfFileWithStudents <= RESERVE_SIZE) { // будем считывать все строчки number_of_students = sizeOfFileWithStudents; } else { // иначе считаем ровно столько, насколько хватает места в массиве number_of_students = RESERVE_SIZE; cout << "There is not enough memory for read all data!" << endl; } /* читаем сразу number_of_students-строчек из файла и сохраняем их в массиве */ fin.read(( char *)& arr_of_students [0], sizeof ( Student )* number_of_students ); } fin.close(); } int getCountOfStucturesInFile( string file_path ) { //Открываем файл и перемещаем указатель в конец файла ifstream file(FILE_OF_DATA, ios ::ate | ios ::binary); |