Выбери формат для чтения
Загружаем конспект в формате pdf
Это займет всего пару минут! А пока ты можешь прочитать работу в формате Word 👇
Лекция 8
Структуры данных
Вы могли задаваться вопросом: а как писать по-настоящему сложные программы?
Оперировать числовыми с строковыми переменными не слишком удобно. Почему нет
возможности сделать свой супер тип? Возможность есть, и сейчас мы узнаем, как она
выглядит в коде.
Простейшие данные можно объединять. Возможно, кто-то заметил, что держать размер
массива в другой переменной не слишком удобно. В конце концов, почему массив сам не
знает своего размера? В языках высокого уровня массивы лишены этого недостатка, но Си —
язык, который экономит на всем, на чем можно. Хранение размера штука затратная, и не
всегда это нужно, поэтому программист сам заботится об этом. Я не буду приводить пример
для массива и его размера, это довольно уныло. Давайте рассмотрим другой пример.
Допустим нам понадобилось хранить данные о клиентах. Упростим задачу, оставим имя
(сhar*), адрес (address) и номер счета (int). Для удобства мы можем объединить даже
разнотипные данные в одну переменную:
struct Client
{
char *Name;
Address Adr;
int NumberCount;
};
Мы создали свой тип. Си не знает что такое Address, но это тоже наш тип, мы могли объявить
его ранее:
struct Address
{
char *Street;
int HouseNumber;
int FlatNumber
};
Обратите внимание на точку с запятой после структуры, она очень важна. Если ее забыть
ошибки будут очень непонятные, иногда разные, и указывать совсем на другую строку.
Объявление переменной от пользовательского типа (или проще структуры) нечем не
отличается от встроенного:
Client c;
Уже в структуре клиента я объявил переменную адрес.
Теперь о неприятном: стандарт Си очень вежливо просит различать встроенные типы от
пользовательских. Все выше написанное не скомпилируется, потому что при записи
пользовательского типа мы обязаны ставить struct.
В С++ этой проблемы нет, потому что вездесущие struct очень сильно засоряют код, но
напишем согласно стандарту Си:
#include
#include
struct Address
{
char *Street;
int HouseNumber;
int FlatNumber;
};
struct Client
{
char *Name;
struct Address Adr;
int NumberCount;
};
int main ()
{
struct Client c;
return 0;
}
Работать с переменной пользовательского типа столь же просто. Но для доступа к
конкретным полям структуры, например, к счету, нужно воспользоваться оператором .
(точка). Вообще, называется он member selector, но в русской литераторе по языкам С/С++
это название редко встречается. Тем не менее довольно безграмотно зазывать его оператором
точкой.
c.NumberCount = 4;
strcpy(c.Name, “Max”); //уже не сработает
Почему вторая строка не будет работать вы уже знаете, но как записать значение в поле
Name? Переделать в массив (char Name[256])? И как часто клиенты меняют имена? Нет, тут
поможет инициализация:
struct Client c = {.Name = "Max"};
Для инициализации адреса код будет выглядеть чуть сложнее:
struct Client c = {.Name = "Max",
.Adr =
{
.Street = "Pushkina",
.HouseNumber = 4,
.FlatNumber = 4
},
.NumberCount = 5};
printf("%s, %s, %i, %i", c.Name, c.Adr.Street,
c.Adr.HouseNumber, c.Adr.FlatNumber);
Код выглядит запутанным, но только с непривычки.
Для структур мы также можем выделять память динамически:
struct Client *c1 = malloc(sizeof(struct Client));
free(c1);
Для указателей оператор селектор превращается из точки в стрелку (->). У многих возникает
с этим путаница. На самом деле, тут нет путаницы, точка также продолжает работать, просто,
обращаясь к полям через указатель, вы обращаетесь к адресу переменной, так что нужно
писать:
(*c1).NumberCount = 3;
или короче:
c1->NumberCount = 3;
С инициализацией для динамических переменных сложнее, а конкретно — мы не можем их
инициализировать. Но мы можем скопировать блок памяти и разместить его по их адресу:
int main ()
{
struct Client c = {.Name = "Max",
.adr =
{
.Street = "Pushkina",
.HouseNumber = 4,
.FlatNumber = 4
},
.NumberCount = 5};
printf("%s, %s, %i, %i\n", c.Name, c.Adr.Street,
c.Adr.HouseNumber, c.Adr.FlatNumber);
struct Client *c1 = malloc(sizeof(struct Client));
memcpy(c1, &c, sizeof(struct Client)); //копирование блока памяти в с1
printf("%s, %s, %i, %i", c1->Name, c1->Adr.Street,
c1->Adr.HouseNumber, c1->Adr.FlatNumber);
free(c1);
}
Переменные пользовательского типа также можно объединять и в массивы, и работать с
ними точно также, как и с обычными массивами.
struct Client clients[10];
clients[0].NumberCount = 4;
Передача в качестве аргумента структур также не отличается от встроенных типов:
void func(struct Client client)
{
}
Имейте ввиду, что при такой передаче происходит копирование структуры, как и в случае,
описанном на прошлой лекции. Только теперь копируется не 4 байта, а гораздо больше.
Сколько? Можно узнать выведя на экран sizeof(struct Client)
Так что передавайте структуры через указатели:
void func(struct Client *client)
{
}
И если функция не меняет аргумент, то используйте const.
Интересная штука, насчет размера структуры: сейчас он равен 20 байт. Действительно 4*5 =
20. Но изменив всего тип одной переменной мы можем увеличить размер в 2 раза. Первый,
кто изменит тип так, что сделает размер структуры Client равным 40, получит +1 балл.
Стоило бы упомянуть еще и о union, но суровая правда жизни говорит о том, что
объединения можно редко удачно использовать, так что это будет материалом для
самостоятельного изучения.
Это основы работы со структурами, но самые классные фичи мы рассмотрим в 9 лекции.
Лабораторная работа №8
Задание 0.
Дана информация о десяти рабочих цеха. Структура имеет вид: фамилия, размер зарплаты,
стаж работы. Вывести данные о рабочих с наибольшей зарплатой и наименьшим стажем в
порядке убывания этого отношения.
Задание 1.
Создать базу студентов. У студента есть имя, оценка по математике, русскому и литературе.
Программа должна уметь добавлять студента с консоли, выводить весь список студентов,
удалять студентов и находить отличников и двоечников. Массивы для хранения студентов
должны быть динамическими.
Задание 2.
Программа должна уметь сохранять базу в файл, читать ее из файла, модифицировать
выбранного студента, меняя имя и оценки по выбору пользователя. Также программа должна
сортировать студентов в лексикографическом порядке по имени.