PDA

Просмотр полной версии : [Урок] [Цикл уроков программиста] 11. Структуры, объединения, перечисления



Tracker1
30.07.2013, 20:33
Сегодня мы рассмотрим три совершенно невзаимосвязанные темы: структуры, перечисления и объединения. Так как они не слишком сложные, мы рассмотрим их все вместе.
Структуры (struct)
Использование структур обусловлено необходимостью объединить несколько переменных.

Структуры - это, обычно, группа переменных описывающих какую-нибудь сущность.

Например, в игре танк можно описать группой переменных: количество снарядов, количество горючего и т.д. У нас в примере танк будет обладать следующими параметрами: координаты и количество топлива (от нуля до двадцати).

Зачем нам использовать какие-то структуры спросите Вы? Ведь переменные и так прекрасно хранятся.

Причин две: переменные в структурах хранятся в одном месте и можно создавать несколько структурных переменных и у всех у них будут одинаковые характеристики.

Объявления у структуры нет. Её нужно сразу определять. Тело структуры должно находиться до начала main.


struct tank
{
int x, y;
int fuel;
};


Здесь мы видим структуру с именем tank. Перед именем стоит ключевое слово struct (от structure - структура). В теле структуры находятся объявления переменных отвечающих за координаты и кол-во топлива. Обратите внимание, что после закрывающей фигурной скобки стоит точка с запятой. Не забывайте о ней в своих структурах.

На самом деле мы только что создали новый тип данных. Такие типы данных - определяемые программистом, называются пользовательскими. К пользовательским типам данных, помимо структур, относятся и классы, но об этом в следующий раз.
Когда мы определяем структуру, под неё не выделяется память. Структура - это как шаблон. Теперь на основе структуры tank можно создать много структурных переменных. Все эти переменные будут иметь тип tank, и каждая структурная переменная будет иметь доступ к своим переменным x, y, fuel.

Сейчас нам нужно определить переменную типа tank. Или даже две:


tank t34 = {0,0,20}; // x, y, fuel
tank pz4 = {8,7,20};


Обе переменные мы сразу проинициализировали (хотя этого можно было и не делать). Для этого использовали список значений в фигурных скобках. Значения в скобках присваиваются переменным внутри структуры в том порядке, в котором они были объявлены при определении структуры: x, y, fuel.

Теперь у нас есть две переменные t34, pz4 типа tank. У каждой есть поля x, y, fuel. Подчёркиваю красным: каждая структурная переменная обладает своим набором переменных, которые были объявлены в структуре.

Чтобы получить доступ к полю, нужно воспользоваться операцией доступа (операцией точки). Например:


pz4.fuel = 100;
cout << pz4.fuel << "\n";


Сначала мы указываем имя структурной переменной, затем ставим точку и в конце указываем имя поля структуры. Здесь мы присвоили значение полю fuel структурной переменной pz4, а затем вывели значение поля fuel на экран.

Напоследок, вот как получить доступ ко всем полям двух структурных переменных:


t34.x;
t34.y;
t34.fuel;
pz4.x;
pz4.y;
pz4.fuel;

Структурные переменные в памяти компьютера

А вот как структурные переменные располагаются в памяти компьютера (адреса в шестнадцатиричном формате):

http://breedpmnr.ru/i/251a00e996a40333af652648de8b.jpg

Каждая переменная типа int занимает в памяти четыре байта. Вся структурная переменная структуры tank займёт в памяти 12 байт - три переменные типа int.

Адрес переменной - младший байт этой переменной. Например, адрес переменной t34.fuel - 0012FEA2.

Кроме того стоит заметить, что адрес самой стуктурной переменной t34 совпадает с адресом первого поля x. Т.е. адрес t34.x равен 0012FE94, адрес t34 равен 0012FE94.

Также обратите внимание, что все поля t34 расположены друг за другом. Заканчивается переменная x (адрес 0012FE97) и следующий байт уже принадлежит y (0012FE98), заканчивается y (адрес 0012FEA1) и сразу же начинается fuel (адрес 0012FEA2). Запомните, поля структурной переменной хранятся в памяти друг за другом, между ними нет никаких других данных.

И ещё одно, две структурные переменные t34 и pz4 необязательно расположены в памяти рядом. В данном примере между ними больше 140-а байтов. Кроме того, переменная pz4 расположена в памяти "раньше" - адрес этой переменной меньше адреса t34.

Хорошенько разберитесь со всем этим. Когда дойдём до рассмотрения указателей, вы поймёте, зачем необходимо понимание того, как устроена память.
Перечисления (enum)

Перечисления - это ещё один пользовательский тип данных. Перечисления используют для описания какого-то небольшого множества значений.

С помощью перечислений можно задать дни недели, месяцы, ну или что-нибудь подобное. В качестве примера рассмотрим стороны света. Стороны света на экране расположены так: вверху - север (north), справа - восток (east), слева - запад (west) и внизу - юг (south).


enum cardinal_dirs { north, west, east, south };

Здесь мы определили перечисление cardinal_dirs. В начале строки стоит ключевое слово enum (enumeration - перечисление). Затем указывается имя перечисления, после которого, через пробел, в фигурных скобках задаются значения, которые смогут принимать переменные типа cardinal_dirs. После фигурных скобок ставится точка с запятой.

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


cardinal_dirs ch = north;


Обратите внимание, что переменные типа cardinal_dirs могут принимать только четыре значения: north, south, east, west. Кроме того, элементы в перечислении нумеруются от нуля. Т.е. north = 0, east = 1, south = 2, west = 3. Вместо перечисления мы могли бы создать четыре константы:


const int north = 0;
const int east = 1;
const int south = 2;
const int west = 3;


Результат одинаковый.

Как видите использовать перечисления довольно легко. Но стороны света у нас закодированы клавишами стрелочек, поэтому нам нужно инициализировать элементы перечисления числами. Как я уже писал, отсчёт ведётся с нуля. Но, к счастью, это можно изменить. Если мы переопределим какой-либо элемент значением 75, то следующий, получит значение 76. Нам нужно переопределить все четыре значения.


enum cardinal_dirs { north = 72, west = 75, east = 77, south = 80 };


Про структуры и перечисления всё. Есть ещё некоторые важные моменты по структурам, но нам они пока не нужны.

Ещё раз повторюсь, точно такого же результата можно было бы добиться с помощью четырёх констант.
Объединения (union)

Объединения используются когда необходимо получить доступ к одним и тем же данным разными способами.

Допустим мы хотим вывести все поля структуры tank. Пока что у нас есть только один способ сделать это:


cout << t34.x << "\n";
cout << t34.y << "\n";
cout << t34.fuel << "\n";


Гораздо удобнее это было бы сделать с помощью цикла. Но тогда нам пришлось бы использовать массив, а определение структуры выглядело бы вот так:


struct tank
{
int t[3];
};


Теперь вывести структурную переменную на экран проще:


for (int i; i < 3; i++)
{
cout << t34.t[i] << "\n";
}


Но при использовании массивов переменная потеряла в гибкости, и код стал не таким понятным:


t34.x = 3; // До..
t34.y = 4;

t34.t[0] = 3; // ... и после.
t34.t[1] = 4;


Можно объединить эти два способа с помощью объединения. :)

При этом структура tank будет выглядеть вот так:


union tank
{
struct
{
int x,y;
int fuel;
};
int t[3];
};


Во-первых мы заменили struct на union (union - объединение). Внутри объединения расположена безымянная структура в которой определены все те переменные, которые были в структуре tank.

После безымянной структуры определён массив целых чисел из трёх элементов.

Теперь создадим переменную и обратимся к её полям:


tank t34;

t34.x = 5;
t34.y = 1;
t34.fuel = 20;

cout << t34.t[0] << "\n";
cout << t34.t[1] << "\n";
cout << t34.t[2] << "\n";


На экран будет выведено:


5
1
20


Дело в том, что x и t[0] - разные имена одного и того же участка памяти, также как и: y и t[1], fuel и t[2].

Вот как переменная типа tank расположено в памяти:

http://breedpmnr.ru/i/251a00e996a40333af652648de8b.jpg

union tank имеет тот же адрес, что и x и t[0], просто у нас картинки рисует чрезвычайно толковый парень, поэтому получается так.

На картинке перед каждым адресом стоит: 0x (ноль икс). Это обозначение используется с шестнадцатиричными числами, т.е. если перед числом вы увидите 0x - то оно представлено в шестнадцатиричном формате.

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


union tank
{
int x;
int y;
int fuel;
int t[3];
};


Ничего хорошего из этого не выйдет. Здесь x, y, fuel и t[0] - имена одного участка памяти.

В будущем мы будем использовать объединения следующим образом:


struct tank
{
union
{
struct
{
int x,y;
int fuel;
};
int t[3];
};
};


Но вместо слово struct в первой строке будет стоять class. Сейчас вот этот вариант (struct tank) и предыдущий (union tank) работают одинаково.

Применение объединений очень сильно ограничено. Мы их рассмотрели, так как они особенно часто встречаются в математических библиотеках.

vovandolg
16.10.2016, 12:11
Не хватает буквы С

Например, адрес переменной t34.fuel - 0012FEA2.