|
Программирование >> Динамические структуры данных
СЕМИНАР 6 Структуры Теоретический материал: с. 67-69. Структуры в С++ обладают практически теми же возможностями, что и классы, но чаще их применяют просто для логаческого объединения связанных между собой данных. В структуру, в противоположность массиву, можно объединять данные различных типов. Например, требуется обрабатывать информащию о расписании работы конференц-зала, и для каждого мероприятия надо знать время, тему, фамилию организатора и количество участников. Поскольку вся эта информация относится к одному событию, логично дать ему имя, чтобы впоследствии можно было к нему обращаться. Для этого описывается новый тип данных (обратите внимание на то, что после описания стоит точка с запятой): struct Event { int hour, min: char themeClOO]. nameClOO]: int num: Имя этого типа данных - Event. Можно описать переменные этого типа точно так же, как переменные встроенных типов, например: Event el. е2[10]: структура и массив структур Если структура испольется только в одном месТе программы, можно совместить описание типа с описанием переменных, при этом имя типа можно не указываты struct { int hour, min: char themeClOO]. name[100]: int num: } el. e2[10]: Переменные структурного типа можно размещать и в динамической области памяти, для этого надо описать указатель на структуру и выделить под нее место: Event *ре - new Event: структура Event *pm - new EventCm]: массив структур Элементы структуры называются полями. Поля могут быть любого основного типа, массивом, указателем, объединением или структурой. Для обращения к полю используется операция выбора (чсточка для переменной и -> для указателя), например: el.hour - 12: el.min - 30: strncpy(e2[0].theme. Выращивание кактусов в условиях Крайнего Севера . 99): pe->num 30: или (*pe).num - 30: pm[2].hour - 14: или (*(pm + 2)).hour - 14; Структуры одного типа можно присваивать друг другу: . *ре - el: pm[l] - el: pm[4] - e2[0]: Ho присваивание - это и все, что можно делать со структурами целиком. Другие операции, например сравнение на равенство или вывод, не определены. Впрочем, пользователь может задать их самостоятельно, поскольку структура является видом класса, а в классах можно определять собственные операции. Мы рассмотрим эту тему во второй части практикума. Ввод/вывод структур, как и массивов, выполняется поэлементно. Вот, например, как выглядит ввод и вывод описанной выше структуры el с использованием классов ввода-вывода (<iostream.h>): cin el.hour el.min: cin.getline(el.theme. 100): cout << el.hour el.min el.theme endl: A вот вариант для любителей ввода-вывода в стиле С (подключается заголовочный файл <stdio.h>): scanf( !l!d3;d . &el.hour. &el.min): gets (el. theme): printf( Xd Xd %s\ el.hour, el.min. el.theme): Структуры (но, конечно, не динамические) можно инициализировать перечислением значений их элементов: Event еЗ = {12. 30. Выращивание кактусов в условиях Крайнего Севера . 25}: Теперь, когда мы познакомились с основными сведениями о структурах, перейдем к решению задач. Задача 6.1. Поиск в массиве структур в текстовом файле хранится база отдела кадров предприятия. На предприятии 100 сотрудников. Каждая строка файла содержит запись об одном сотруднике. Формат записи: фамилия и инициалы (30 поз., фамилия должна начинаться с первой позиции), год рождения (5 поз.), оклад (10 поз.). Написать программу, которая по заданной фамилии выводит на экран сведения о сотруднике, подсчитывая средний оклад всех запрошенных сотрудников. I. Исходные данные, результаты и промежуточные величины Исходные данные. База сотрудников находится в текстовом файле. Прежде всего надо решить, хранить ли в оперативной памяти одновременно всю информацию из файла или можно обойтись буфером на одну строку. Если бы сведения о сотруднике запрашивались однократно, можно было бы остановиться на втором варианте, но поскольку поиск по базе будет выполняться более одного раза, всю информацию желательно хранить в оперативной памяти, поскольку многократное чтение из файла крайне нерационально. Максимальное количество строк файла по условию задачи ограничено, поэтому можно выделить для их хранения массив из 100 элементов. Каждый элемент массива будет содержать сведения об одном сотруднике. Поскольку эти сведения разнородные, удобно организовать их в виде структуры. ПРИМЕЧАНИЕ - Строго говоря, для решения этой конкретной задачи запись о сотруднике может быть просто строкой символов, из которой при необходимости выделяется подстрока с окладом, преобразуемая затем в число, но мы для общности и удобства дальнейшей модификации программы будем использовать структуру. В программу по условию требуется также вводить фамилии искомых сотрудников. Для хранения фамилии опишем строку символов той же длины, что и в базе. Результаты. В результате работы программы требуется вывести на экран требуемые элементы исходного массива. Поскольку эти результаты представляют собой выборку из исходных данных, дополнительная память для них не отводится. Кроме того, необходимо подсчитать средний оклад для найденных сотрудников. Для этого необходима переменная вещественного типа. Промежуточные величины. Для поиска среднего оклада необходимо подсчитать количество сотрудников, для которых выводились сведения. Заведем для этого переменную целого типа. Для описания формата входного файла будем использовать именованные константы. П. Алгоритм решения задачи очевиден: 1. Ввести из файла в массив сведения о сотрудниках. 2. Организовать цикл вывода сведений о сотруднике: ввести с клавиатуры фамилию; выполнить поиск сотрудника в массиве; увеличить суммарный оклад и счетчик количества сотрудников; вывести сведения о сотруднике или сообщение об их отсутствии; 3. Вывести средний оклад. Необходимо решить, каким образом будет производиться выход из цикла вывода сведений о сотрудниках. Для простоты условимся, что для выхода из цикла вместо фамилии следует ввести слово end. III. Программа и тестовые примеры #include <fstream.h> #include <string.h> finclude <stdlib.h> #include <windows.h> int main(){ const int l nanie = 30, 1 уваг = 5. l pay l buf = l naine + 1 ear + l pay: struct Man { int birth ear; char name[l name + 1]: float pay: const int l dbase = 100: Man dbaseCl dbase]: 0 1 2 3 char buf [l buf + 1]: char name[l name + 1]: ifstream fin( dbase.txt . ios::.in ios: :nocreate): if (Ifin) { cout Ошибка открытия файла : return 1: } int i - 0: while (fin.getline(buf. l buf)) { if (i >- l dbase) { cout Слишком длинный файл strncpy(dbase[i].name, buf. l name): dbase[i].name[l name] - XO : dbaseCi].birth ear - atoi(&buf[l name]): dbase[i].pay - atof(&buf[l name + 1 ear]): i++: int n record - i. n man = 0; float meanjDay - 0: while (true) { cout Введите фамилию или слово end: : cin name: OemToChar(name. name): if (strcmp(name, end ) 0 )break: bool not found = true: for (i - 0: i < n record: i++) { if (strstr(dbase[i].name. name)) if (dbase[i].name[strlen(name)] =- ){ strcpy(name. dbase[i].name): CharToOem(name, name): cout name dbase[i].birth year endl: n man++: mean pay +- dbase[i].pay: not found = false: return 1: } dbase[i].pay 4 5 6 7 8 9 10 11 12 13 14 15 if (not found) cout Такого сотрудника нет endl: if (n nian > 0) cout Средний оклад: mean pay / n nian end!: return 0: 17 Рассмотрим приведенную выше программу подробно. В операторе 1 заданы именованные константы, в которых хранится формат входного файла, то есть длина каждого из полей записи (строки файла). Такой подход позволяет при необходимости легко вносить в программу изменения. Длина буфера, в который будет счи-тываться каждая строка файла, вычисляется как сумма длин указанных полей. В операторе 2 определяется структура Man для хранения сведений об одном сотруднике. Длина поля, в котором будет находиться фамилия, задана с учетом завершающего нуль-символа. В операторе 3 определяется массив структур dbase для хранения всей базы. Его размерность также задается именованной константой. В операторах 4 и 5 задаются промежуточные переменные: буфер buf для ввода строки из файла и строка name для фамилии запрашиваемого сотрудника. В операторе 6 выполняется открытие файла dbase. txt для чтения. Предполагается, что этот файл находится в том же каталоге, что и текст программы, иначе следует указать полный путь. Входной файл следует создать в любом текстовом редакторе* до первого запуска программы в соответствии с форматом, заданным в условии задачи. Файл для целей тестирования должен состоять из нескольких строк, причем необходимо предусмотреть случай, когда одна фамилия является частью другой (например, Иванов и Ивановский). Не забудьте проверить, выдается ли диагностическое сообщение, если файл не найден. Цикл 7 выполняет построчное считывание из файла в строку buf и заполнение очередного элемента массива dbase. Счетчик i хранит индекс первого свободного элемента массива. Для формирования полей структуры используются функции копирования строк strncpy, преобразования из строки в целое число atoi и преобразования из строки в вещественное число atof. Они были рассмотрены на предыдущем семинаре (см. с. 94). Обратите внимание на то, что завершающий нуль-символ в поле фамилии заносится вручную , поскольку функция strncpy делает это только в случае, если строка-источник короче строки-приемника. В функцию atoi передается адрес начала подстроки, в которой находится год рождения. Обратите внимание на то, что при каждом проходе цикла выполняется проверка, не превышает ли считанное количество строк размерность массива. При тестировании программы в этот цикл следует добавить контрольный вывод на экран считанной строки, а также сформированных полей структуры. Для проверки выдачи диагностического сообщения следует временно задать константу l dbase равной, а затем меньшей фактического количества строк в файле. ВНИМАНИЕ --- При заполнении массива из файла обязательно контролируйте выход за границы массива и при необходимости вьи(авайте предупреждающее сообщение. О проблемах, связанных с различной кодировкой кириллицы в текстовых редакторах, работающих в среде MS-DOS или в среде Windows, будет сказано ниже. Кстати, можно записать эту проверку и так: while (fin.getline(buf. l buf) && i < l dbase) { i++: if (i >- l dbase) { cout Слишком длинный файл : return 1: } В операторе 8 определяются две переменные: n record для хранения фактического количества записей о сотрудниках и n man - для подсчета сотрудников, о которых будут выдаваться сведения. Следует также не забыть обнулить переменную meanjaay, в которой в следующем цикле будет накапливаться сумма окладов. Цикл поиска сотрудников по фамилии организован как бесконечный (оператор 9) с принудительным выходом (оператор И). Некоторые специалисты считают, что такой способ является плохим стилем, и для выхода из цикла следует определить переменную-флаг, но нам кажется иначе. Впрочем, в данном случае выход из цикла действительно организован не лучшим образом, поскольку пользователь вынужден для окончания работы с программой ввести слово end в нижнем регистре. Более удобным был бы выход из цикла по нажатию, например, клавиши Esc. К сожалению, в рамках стандарта это сделать невозможно, однако в большинстве библиотек есть функции типа getch и kbhit, позволяющие анализировать нажатие клавиш. В общем случае, несомненно, следует выбирать интерфейс, наиболее удобный для пользователя, поскольку именно для него и пишутся все без исключения программы. В операторе 12 определяется переменная-флаг not found для того, чтобы после окончания цикла поиска было известно, завершился ли он успешно. Обратите внимание на имя переменной: его следует выбирать таким образом, чтобы по нему было ясно, какое значение является истинным. Как видите, в этом случае оператор if (not found) cout Такого сотрудника нет endl: хорошо понятен без дополнительных комментариев. В операторе 13 организуется цикл просмотра массива струюур (просматриваются только заполненные при вводе элементы). Проверка совпадения фамилии сотрудника производится в два этапа. В операторе 14 с помощью функции strstr поиска подстроки определяется, содержится ли в поле базы name искомая последовательность букв, а в операторе 15 проверяется, есть ли непосредственно после фамилии пробел (если пробела нет, то искомая фамилия является частью другой, и эта строка нам не подходит). Такая простая проверка возможна из-за условия задачи, по которому фамилия должна начинаться с первой позиции каждой строки. Когда вы дойдете до отладки этой части программы, то, вполне возможно, столкнетесь с непонятной на первый взгляд проблемой: программа не может найти запись с заданной фамилией. Впрочем, этот эффект проявляется, только если вы работаете в среде Windows и только на фамилиях, записанных в базе на русском языке; стоит только перейти на латиницу, как все начинает работать нормально. Здесь есть одна тонкость, связанная с разной кодировкой букв русского алфавита (кириллицы) в операционных системах MS DOS и Windows, о чем уже говорилось на первом семинаре (см. с. 17).
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |