Объявление и определение структур

Когда мы говорим о структуре, мы имеем в виду объект, который характеризуется набором параметров, значимых в программе. Например, книга может включать в себя параметры вроде названия, автора и количества страниц, тогда как для окружности важны такие параметры, как координаты центра, диаметр и цвет. В языке программирования C, объявление подобных структур данных может выглядеть следующим образом:

struct book {
    char title[50];
    char author[30];
    int pages;
};
struct circle {
    int x, y;
    float dia;
    char color[10];
};

Фактически, мы создаем новый тип данных, но пока не определяем переменные этих типов. Следует обратить внимание на точку с запятой в конце объявлений.

Обычно структуры определяются следующим образом:

struct circle a, b, c;
struct book mybook;

Здесь мы определяем три структуры типа circle и одну структуры типа book. Существуют иные способы объявления типов структур и их переменных, но для упрощения мы не будем их рассматривать.

Каждый объект типа circle содержит в себе четыре элемента (x, y, dia, color), которые можно рассматривать как вложенные переменные различных типов. Благодаря структурам можно объединять под одним именем несколько разнотипных данных, что делает обработку данных более удобной. Без возможности создавать структуры, пришлось бы создавать множество независимых переменных или массивов, связанные друг с другом только логически, но не явно. Таким образом, использование структур открывает путь к объектно-ориентированному программированию.

Когда мы объявляем структуру, доступ к ее элементам для присвоения, изменения или получения значения осуществляется следующим образом:

	a.x = 10; a.dia = 2.35;
	printf("%.2f ", a.dia);

Элементы структуры можно также инициализировать при объявлении переменной, что напоминает инициализацию массивов:

	struct book lang_c = {"Language C", "Ritchi", 99};

Переменной-структуре можно присвоить значение переменной того же типа:

	struct book { char *title, *author; int pages; };
	struct book old, new;
	old.title = "GNU/Linux"; old.author = "people"; old.pages = 20213;
	new = old;
	new.pages += 2000;
	printf("%d, %d\n", old.pages, new.pages);

В четвертой строке кода данные из переменной old присваиваются new, создавая тем самым копию. Естественно, элементы структуры можно присваивать и отдельно.

Структуры и функции

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

#include <stdio.h>
#include <math.h>
 
struct circle { int x, y; float dia; char color[10]; };
struct circle new_circle();
void cross (struct circle);
 
main () {
	struct circle a;
 
	a = new_circle();
	cross(a);
}
 
struct circle new_circle() {
	struct circle new;
 
	printf("Координаты: "); scanf("%d%d", &new.x, &new.y);
	printf("Диаметр: "); scanf("%f", &new.dia);
	printf("Цвет: "); scanf("%s", new.color);//gets(new.color);
 
	return new;
}
 
void cross (struct circle c) {
	double hyp;
 
	hyp = sqrt((double) c.x * c.x + (double) c.y * c.y);
	printf("Расстояние от центра круга до начала координат: %.2lf\n", hyp);
	if (hyp <= c.dia / 2) puts("Круг пересекает начало координат");
	else puts("Круг не содержит точки начала координат");
}

Примечание. При компиляции программы в GNU/Linux используется команда: gcc program.c -lm. Это необходимо для подключения библиотеки с математическими функциями.

  • Структура circle объявляется как глобальный тип данных, что позволяет любой функции, а не только main(), создавать переменные данного типа.
  • Функция new_circle() возвращает структуру, в то время как cross() принимает структуру по значению. Однако, можно также создавать функции, одновременно принимающие несколько структур и возвращающие структуру.
  • В new_circle() создается переменная new типа struct circle, поля которой заполняются пользователем. Значение возвращается в функцию main() и присваивается переменной a, тоже типа struct circle.
  • cross() определяет, пересекает ли круг начало координат. В функции вычисляется расстояние от центра круга до начала координат, которое является гипотенузой прямоугольного треугольника, длины катетов которого соответствуют значениям x и y. Если гипотенуза меньше радиуса, круг пересекает точку (0, 0).
  • При вызове cross() из main() данные из переменной a копируются и передаются в виде переменной c.

Указатели и структуры

В отличие от массивов, структуры передаются в функции по значению, что может быть неэффективно, особенно если структура занимает много памяти. Чтобы избежать лишнего копирования, можно использовать указатели или передавать адреса структур в функцию.

	struct book new; // переменная-структура
	struct book *pnew; // указатель на структуру
	reader(&new); // передаем адрес
	pnew = &new;
	reader(pnew); // передаем указатель

Здесь функция reader() должна иметь следующий вид:
void reader (struct book *pb);

Встает вопрос, как обращаться к элементам структур при использовании указателей. Сначала необходимо получить доступ к самой структуре. Если pnew является указателем, то сама структура будет *pnew. К полям можно обращаться через точку, например, *pnew.title. Однако, из-за более высокого приоритета операции точки, эта запись будет неверной. Проблема решается с помощью скобок: (*pnew).title. Такая запись обеспечивает правильный порядок выполнения операций: сначала извлекается значение по адресу, чтобы затем получить доступ к полю структуры.

В языке C часто используется запись pnew->title вместо (*pnew).title. Стрелка обозначает, что объект перед стрелкой — это указатель на структуру, а не сама структура.

Пример использования указателей:

#include <stdio.h>
 
struct circle { int x, y; float dia; };
void inversion (struct circle *);
 
main () {
	struct circle cir, *pc = &cir;
 
	pc->x = 10; pc->y = 7; pc->dia = 6;
	inversion(pc);
	printf("x = %d, y = %d\n", cir.x, cir.y);
}
 
void inversion(struct circle *p) {
	p->x = -p->x;
	p->y = -p->y;
}

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

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

Напишем программу для учета персональных компьютеров в организации. Каждая структура будет отображать сведения о различных моделях и содержать поле с указанием количества таких объектов. В объявлении типа данных структуры следует указать такие атрибуты, как тип компьютера, модель процессора и количество.

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

#include <stdio.h>
 
#define N 5
 
struct computer { char *type; char *proc; int qty; };
void viewer (struct computer *);
void changer (struct computer *);
 
main () {
	struct computer comps[N]= {
	"Desktop", "earlier than P4", 10,
	"Desktop", "P4", 30 ,
	"Desktop", "Core", 20 ,
	"Desktop", "AMD", 2 ,
	"Notebook", "Core", 1 };
 
	viewer(comps);
	changer(comps);
	viewer(comps);
}
 
void viewer (struct computer *comp) {
	int i;
 
	for (i=0; i < N; i++, comp++)
		printf("%2d) %-8s - %-15s: %3d\n", i+1, comp->type, comp->proc, comp->qty);
}
 
void changer (struct computer *comp) {
	int i, differ;
 
	printf("Введите номер модели: ");
	scanf("%d", &i); i--;
	printf("На сколько уменьшить или увеличить: ");
	scanf("%d", &differ);
	(comp+i)->qty += differ;
}
  • Массив cтруктур инициализируется при его объявлении.
  • Функции viewer() и changer() принимают указатели на массив структур computer.
  • Внутри viewer(), указатель инкрементируется в заголовке цикла, указывая на следующий элемент массива, то есть на следующую структуру.
  • Скобки в выражении (comp+i)->qty необходимы, так как оператор -> имеет более высокий приоритет. Скобки позволяют сначала получить указатель на i-ый элемент массива, а потом обратиться к его полю.
  • Декремент i в функции changer() объясняется тем, что индексация начинается с нуля, а нумерация элементов массива, которую видит пользователь на экране, начинается с единицы.
  • Для уменьшения количества компьютеров необходимо ввести отрицательное число.

Пример выполнения программы представлен ниже:

Выполнение программы с массивом структур

Задание:

Придумайте собственную программу с использованием массива структур или измените приведенный выше пример, добавив функцию, которая позволяет добавлять в массив новый элемент-структуру.

Вопросы для самопроверки:

  1. Как объявляются переменные структур в языке программирования C?
  2. Для чего используется точка с запятой после объявления структур?
  3. Как передаются структуры в функции в языке C, и как с ними можно взаимодействовать внутри функции?
  4. Как выглядит синтаксис языка C при использовании указателей для доступа к полям структуры?

Программа курса:

  1. Описание курса
  2. Введение в язык программирования C
  3. Типы данных в C и форматированный вывод
  4. Символьные типы и управляющие символы в C
  5. Операторы ветвления и switch в C
  6. Циклы и операторы в языке C
  7. Битовые операции в языке C
  8. Посимвольный ввод и вывод в C - буферизация
  9. Переменные, адреса и указатели в C
  10. Передача аргументов по ссылке и значению в C
  11. Форматированный ввод данных с использованием scanf
  12. Генерация псевдослучайных чисел на C
  13. Адресная арифметика в массивах C
  14. Передача массивов в функции и указатели
  15. Строки в языке C - особенности и функции работы
  16. Функции работы со строками в C
  17. Работа со структурами в C - создание и применение
  18. Динамические структуры данных в C
  19. Ввод и вывод данных из файлов в языке C
  20. Передача аргументов в C и работа с файлами
  21. Препроцессор в языке C - директивы и макросы
  22. Создание и компиляция многофайловых программ в C
  23. Использование статических и динамических библиотек в C