Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Тема10_Струкруры.doc
Скачиваний:
2
Добавлен:
07.09.2019
Размер:
179.2 Кб
Скачать

Тема 11. Структуры Зачем нужны структуры?

Одна из задач любого языка программирования заключается в предоставлении программисту инструментов, позволяющих представлять данные в удобной для работы форме. Структуры являются одним из таких инструментов, который хорошо подходит в ряде случаев, когда сложно обойтись только переменными и массивами. Некоторые языки программирования, например, Паскаль, применительно к структурам используют термин запись (record), тем не менее, принципы работы с записями и структурами одинаковы.

Предположим, что перед нами стоит задача реализовать базу данных, предназначенную для работы с информацией о сотрудниках некоторой фирмы. С каждым сотрудником связан набор разнотипных данных, таких как имя (name), возраст (age), зарплата (salary) и др. Чтобы решить эту задачу, можно описать несколько массивов, каждый из которых будет отвечать за свою характеристику сотрудника:

char name[][20];

float salary[];

int age[];

Однако такой способ совсем неудобен при большом количестве характеристик товара и, следовательно, большом количестве массивов. Представьте себе, как будет выглядеть сортировка десяти массивов, если понадобится упорядочить сотрудников по возрасту; очевидно, что сортировать массивы придется синхронно, чтобы элементы с одинаковыми индексами относились к одному человеку.

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

Описание структуры

Рассмотрим, как описать структуру для представления сотрудника:

struct Worker {

char name[20];

float salary;

int age;

};

В данном фрагменте кода struct – это ключевое слово, информирующее о начале описания структуры. Далее указывается имя структуры Worker, а затем в фигурных скобках перечисляются переменные, входящие в структуру, и указывается их тип; применительно к структурам такие переменные называются полями. Описав структуру Worker, мы фактически определили новый составной тип данных, называемый Worker. После его определения можно описать переменную этого типа:

Worker director;

При описании также можно задать значения полей структуры, перечислив эти значения в том порядке, который указан при описании структуры:

Worker director = {"Андрей", 34, 1000.6};

Заметим, что при описании структуры Worker память под нее не выделяется; это происходит только при описании переменной director типа Worker, которая занимает столько места, сколько ее поля все вместе. Проверим это с помощью оператора sizeof:

printf("Тип\t\tРазмер\n");

printf("----------------------\n");

printf("char[20]\t%d\n", 20*sizeof(char));

printf("int\t\t%d\n", sizeof(int));

printf("float\t\t%d\n", sizeof(float));

printf("----------------------\n");

printf("Worker\t\t%d\n", sizeof(Worker));

Строка длины 20 занимает 20 байт; целое и вещественное числа занимают по 4 байта; итого получается 28 байт:

Обратите внимание на то, что во избежание путаницы имена структур следует начинать с заглавной буквы, а имена переменных – со строчной. Такое соглашение позволяет к тому же объявлять переменные и структуры с «почти» одинаковыми именами, поскольку язык Си различает регистр букв:

Worker worker;

Обращение к полям структуры и оператор «точка»

Обратиться к значению, которое хранит переменная, можно, просто написав ее имя. Структура является не просто переменной, а в некотором смысле составной переменной, поэтому для обращения к данным, которые хранит структура необходимо указать ее имя и имя поля. Для этого существует специальный оператор «точка», который разделяет имена структуры и ее полей. Рассмотрим пример использования этого оператора для того, чтобы присвоить значения элементам структуры:

Worker manager;

strcpy(manager.name, "Сергей");

manager.salary = 1000.55;

manager.age = 33;

Обратите внимание на то, что поля manager.salary и manager.age являются обычными переменными, и работать с ними нужно, как с переменными. Поле manager.name является строкой, поэтому вместо присваивания мы используем функцию strcpy().

Ввод и вывод содержимого структуры

Вывод значений полей структуры осуществляется аналогично переменным с учетом их типа:

printf("Имя: %s\n", manager.name);

printf("Возраст: %d\n", manager.age);

printf("Зарплата: %g\n", manager.salary);

При вводе необходимо ставить амперсанд:

scanf("%d", &manager.age);

Оператор точка имеет более высокий приоритет, поэтому ставить скобки, т.е. писать &(manager.age) не нужно. К строке manager.name применимы функции gets() и puts():

gets(manager.name);

puts(manager.name);

Можно также обратиться к отдельным буквам этой строки:

printf("Имя начинается с буквы %c\n",

manager.name[0]);

Вложенные структуры

Пусть для каждого сотрудника требуется хранить его адрес, который состоит из нескольких полей: улица (street), номер дома (house) и номер квартиры (flat). В таком случае адрес разумно также представить в виде структуры

struct Address {

char street[50];

int house;

int flat;

};

и одним из полей структуры Worker сделать структуру Address

struct Worker {

char name[20];

int age;

float salary;

Address address;

};

Обращение к полям вложенной структуры Address происходит через поля структуры Worker; например, задать адрес сотрудника и вывести его на экран можно так:

strcpy(manager.address.street, "Ленина");

manager.address.house = 10;

manager.address.flat = 7;

printf("Улица: %s\n", manager.address.street);

printf("Дом: %d\n", manager.addrress.house);

printf("Квартира: %d\n",manager.address.flat);

Массив структур

Как мы отмечали в самом начале, важной причиной введения структур является желание работать со списком записей. Такой список представляется в виде массива структур. Аналогично обычному массиву, где элементами являются числа или символы, в массиве структур элементами являются структуры. Рассмотрим, как объявляется массив структур, в котором будет храниться информация о сотрудниках.

#define SIZE 5

Worker workers[SIZE];

Значения полям элементов массива структур можно задать при его объявлении, перечислив поля в том порядке, который указан при объявлении структуры:

Worker workers[SIZE] = {

{"Андрей", 30, 5000},

{"Светлана", 51, 2000},

{"Дмитрий", 45, 3000},

{"Анна", 28, 4000},

{"Василий", 37, 1000}

};

а можно и после объявления:

strcpy(workers[0].name, "Игорь");

workers[0].age = 53;

workers[0].salary = 2500;

strcpy(workers[1].name, "Демьян");

workers[1].age = 57;

workers[1].salary = 1500;

Причем грамотная группировка элементов выглядит именно так, как показано выше, а не так:

//Неправильная группировка!

strcpy(workers[0].name, "Игорь");

strcpy(workers[1].name, "Демьян");

workers[0].age = 53;

workers[1].age = 57;

workers[0].salary = 2500;

workers[1].salary = 1500;

поскольку структура предполагает группировку разнотипных данных, относящихся к одному объекту, а не группировку данных согласно их типу.

Вывод массива структур в виде таблицы

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

for (int i=0; i<SIZE; i++) {

printf("%s ", workers[i].name);

printf("%d ", workers[i].age);

printf("%g\n", workers[i].salary);

}

Такой способ отображает весь массив, но колонки оказываются неровными:

Для ровного вывода следует воспользоваться табуляцией – «\t»

for (int i=0; i<SIZE; i++) {

printf("%s\t", workers[i].name);

printf("%d\t", workers[i].age);

printf("%g\n", workers[i].salary);

}

Однако и в данном случае могут возникнуть проблемы. Например, если одно из имен слишком длинное:

Хорошим способом решения этой проблемы является использование модификатора количества позиций под переменную:

for (int i=0; i<SIZE; i++) {

printf("%10s\t", workers[i].name);

printf("%d\t", workers[i].age);

printf("%g\n", workers[i].salary);

}

И, наконец, для выравнивания по левому краю существует модификатор «-».

for (int i=0; i<SIZE; i++) {

printf("%-10s\t", workers[i].name);

printf("%d\t", workers[i].age);

printf("%g\n", workers[i].salary);

}

Сортировка массива структур

Алгоритмы сортировки применимы к любым объектам, в том числе и к структурам. Рассмотрим особенности, которые нужно учитывать при сортировке массива структур.

Во-первых, структуры сортируются по какому-то полю, поэтому сравнение нужно указывать это поле:

if (workers[j].age < workers[j+1].age) {

}

Во-вторых, в отличие от строк, структуры можно присваивать друг другу напрямую, поэтому перестановка двух структур выглядит следующим образом:

Worker temp = workers[j];

workers[j] = workers[j+1];

workers[j+1] = temp;

Алгоритм пузырьковой сортировки массива структур выглядит следующим образом:

for (int i=1; i<SIZE; i++) {

for (int j=SIZE-2; j>=0; j--) {

if (workers[j].age < workers[j+1].age) {

worker temp = workers[j];

workers[j]=workers[j+1];

workers[j+1]=temp;

}

}

}

Результат сортировки по возрасту:

Передача структур в качестве аргументов функций

Переменные-структуры можно передавать в качестве аргументов функций. Следующий фрагмент кода демонстрирует определение функции printWorker(), которая принимает структуру Worker в качестве аргумента и выводит ее на экран:

void printWorker(Worker worker) {

printf("%s\n", worker.name);

printf("%d\n", worker.age);

printf("%g\n", worker.salary);

}

Вызвать эту функцию можно, передав в нее структуру типа Worker, например, элемент массива workers:

printWorker(workers[1]);

Возврат структуры в качестве значения функции

Переменные-структуры можно возвращать в качестве значения функции. Следующая функция принимает две структуры типа Worker и возвращает сотрудника с большей зарплатой:

Worker getWellPaid(Worker w1, Worker w2) {

return w1.salary > w2.salary ? w1 : w2;

}

Обратите внимание на то, что здесь используется сокращенная запись оператора if-else, эквивалентная такой конструкции:

Worker getWellPaid(Worker w1, Worker w2) {

if (w1.salary > w2.salary) {

return w1;

} else {

return w2;

}

}

При вызове функции getWellPaid() совместно с функцией printWorker()

Worker director = {"Андрей", 30, 5000};

Worker manager = {"Сергей", 35, 3000};

printf("Сотрудник\n\n");

printWorker(getWellPaid(director, manager));

printf("\nЗарабатывает больше\n");

получается следующий результат: