8Лабораторная работа №7. Структуры и работа с ними
8.1Цели и задачи работы:
Цель данной лабораторной работы — научиться создавать свои типы данных и использовать функции и перегруженные операторы для работы с ними.
Задача данной лабораторной работы — выполнить выданные типовые задания (2 задачи).
8.2Теоретическая часть
Вязыке «C» типы данных можно группировать, получая новые на основе стандартных. Сгруппированные типы называются «структурой», а сами данные, находящиеся в структуре - полями. Структуры объявляются следующим образом:
struct <имя>
{
<тип1> <название_поля1>; <тип2> <название_поля2>;
...
<типN> <название_поляN>; };
Полями структуры могут быть любые объявленные типы, как базовые, так и массивы, и другие структуры («вложенные»). Например, следующим образом можно создать и использовать тип данных «Геометрическая точка»:
struct MyPoint // Объявили новый тип данных «MyPoint»
{
float x; // Координата x точки float y; // Координата y точки
};
void main()
{
//Объявляем переменную point типа MyPoint
MyPoint point;
//Присваиваем значения, используя оператор
//«точка» для доступа к полям структуры point.x = 5;
128
point.y = 8;
// Получение значения поля x структуры «point» float f = point.x;
}
Значения полей одной структурной переменной могут быть присвоены значениям полей другой структурной переменной, если обе переменные одного типа.
MyPoint newpoint = point;
Поля экземпляра структуры не обязательно располагаются подряд в оперативной памяти. Компилятор для оптимизации доступа к ним может произвести «выравнивание» - добавить к некоторым, меньшим по размеру, полям структуры в оперативной памяти дополнительные байты, которые хоть и не задействованы, но занимают дополнительное место. Например, размещение полей структуры
struct Test
{
char simbol; int chislo;
};
в оперативной памяти может оказаться таким:
№ байта |
1 |
2 |
|
3 |
|
4 |
5 |
6 |
|
7 |
8 |
Содержимое |
simbol |
|
не используется |
|
|
|
chislo |
|
|||
Величина выравнивания полей экземпляра структуры в оперативной памяти и как следствие общий размер структуры в памяти зависит от настроек компилятора. Для вышеприведѐнного случая sizeof(Test) вернѐт значение 8.
В тех случаях, когда заранее известно, что поля структуры одновременно использоваться не будут, можно использовать «объединение» (union). Объединение подобно структуре. Оно способно хранить в пределах одной зарезервированной области памяти различные типы данных. Но в каждый определенный момент времени в объединении хранится только один из этих типов данных и возможно использовать лишь значение этого поля. Синтаксически объединение определяется аналогично структуре:
129
union Test
{
char symbol; int chislo;
};
sizeof(Test) вернѐт значение 4 == sizeof(int) ≥ sizeof(char)
Доступ к элементам объединения осуществляется так же, как и к элементам структур. Применение объединений способствует экономии памяти, когда нужно хранить и использовать данные разных типов, но обращаться к ним нужно не одновременно.
Ещѐ одна разновидность пользовательских типов - «перечисление». Перечисление по своей сути является набором констант, называемых перечислителями, например:
enum Season {Summer, Autumn, Winter, Spring};
Перечисления удобно использовать в конструкциях условий таких как
«if-else» или «switch-case»
Season season = Summer;
...
if (season == Summer)
{
// какой-то код
}
switch(season)
{
case Summer:
// какой-то код break;
case Autumn:
// какой-то код break;
// ...
}
Для работы со структурами удобно использовать функции. Например:
130
//Тип данных «целочисленный массив» struct Massiv
{
unsigned int quantity; // Количество данных int *data; // Указатель на данные
};
//Функция для создания массива
void CreateMassiv(Massiv& mass, unsigned int size)
{
mass.quantity = size; // Запоминаем размер mass.data = new int[mass.quantity]; // Выделяем память
// Заполняем созданный массив нулями for(unsigned int i = 0; i < mass.quantity; i++)
{
mass.data[i] = 0;
}
}
//Функция для удаления массива void DeleteMassiv(Massiv& mass)
{
delete[] mass.data;
}
//Функция для вывода элементов массива на экран void Vivod(const Massiv& mas)
{
for(unsigned int i = 0; i < mas.quantity; i++)
{
std::cout<<mas.data[i]<<"\t";
}
std::cout<<"\n\n";
}
//Теперь в основной программе можно записать: void main()
{
Massiv m; // Создаём структуру "Целочисленный массив" CreateMassiv(m, 5); // Выделяем память под массив из 5 элементов
m.data[0] = 3; |
// Задаём значения некоторым элементам массива |
m.data[1] = 5; |
|
m.data[3] = 10; |
|
Vivod(m); |
// Вывод заполненного массива на экран |
DeleteMassiv(m); |
// Удаление массива |
system("pause"); |
|
} |
|
|
131 |
Экземпляры структур обычно передаются в функции по ссылке или по указателю, в этом случае их копии создаваться не будет (как при передаче по значению), что сэкономит оперативную память.
Операторы по своей сути так же являются функциями. От прочих функций они отличаются лишь тем, что имеют сокращѐнную форму записи. Например, записи a + b и operator+(a, b) эквивалентны.
Как и остальные функции, операторы можно перегружать. Например, если есть тип данных (структура), описывающий вектор на плоскости, выходящий из начала координат:
struct MyVector
{
float x; float y; };
оператор «+» для сложения двух векторов можно перегрузить так:
// Глобальная перегрузка оператора «+»
MyVector operator+(const MyVector& A, const MyVector& B)
{
MyVector C;
C.x = A.x + B.x;
C.y = A.y + B.y; return C;
}
Структурные переменые передаются в функцию по ссылке, чтобы не занимать лишнюю память их копиями. Модификатор const используется для того, чтобы изнутри функции сложения случайно не изменить значения аргументов.
После реализации функции operator+ для «сложения» структур MyVector в тексте программы можно будет записать:
...
void main()
{
...
MyVector vect1; // Создаём один вектор vect1.x = 3;
132