Структуры в си (c) - это объединенные данные, у которых есть некоторая логическая взаимосвязь. В отличие от массивов, структуры могут содержать данные разных типов. Вот пару примеров структур в си (c): структура класс (имя учащегося, буква класса, средний балл); структура футбольная команда (тренер, название команды, место в турнирной таблице). Т.е. структуру вы будете использовать довольно часто. Теперь давайте рассмотрим, как описываются структуры в си:
struct klass {
char name;
char klass_name;
float bal;
};
Любая структура в языке си (c) должна начинаться с ключевого слова - struct , которое сообщает компилятору, что тут у нас будет структура. Все данные в структуре (struct) пишутся в фигурных скобках, и в конце ставится запятая с точкой (;). Советую сразу ставить запятую с точкой, что бы не было ошибок.
Как вы видите, в структуре (struct) у нас находятся данные различных типов, но они объединены в логическую связь, так как в моем примере они являются определенным школьным классом. Данные в структуре должны иметь уникальные имена, но в различных структурах можно использовать одинаковые названия.
Структура, которая создана выше не занимает в памяти компьютера места, так как мы, на самом деле, просто создали свой тип данных. Объявление структуры ни чем не отличается от объявления любого типа данных в языке си (c). Вот пример:
struct klass a, b, *c;
Мы объявили переменную а типа struct klass, массив b, состоящий из 5 элементов типа struct klass и указатель на переменную struct klass.
Так же можно объявлять переменные сразу после объявления структуры:
struct klass {
char name;
char klass_name;
float bal;
} a, b, *c;
А какие же операции можно проделывать со структурами? Ответ на этот вопрос лучше перечислить по пунктам:
Инициализация структуры в языке си (c) происходит так же, как и при инициализации массива. Вот пример инициализации структуры:
struct klass a = {"Sergey", "B", 4.5 };
Т.е. мы создаем переменную типа struct klass и присваиваем всем трем полям, которые у нас определенны в структуре, значения. Порядок очень важен при инициализации структуры , так как компьютер сам не может отсортировывать данные. Если какое-либо поле у вас будет не заполненным, то оно автоматом заполнится 0 - для целочисленных типов; NULL - для указателей; \0 (ноль-терминатор) - для строковых типов.
Пока мы рассматривали одну сложную структуру (сложный тип) - массив; одним из основных свойств массива является однотипность его компонент. Многие информационно-логические задачи связаны с обработкой документов, содержащих в себе информация разного типа (числовую, символьную и т. д.) Примеры таких документов: платежные ведомости (фамилии и имена - символьная информация, денежные суммы - числовая), карточки больных в поликлинике, библиотечная информация. Для программирования алгоритмов обработки такой информации необходимо иметь сложный тип, объединяющий разнотипные компоненты. Таким типом является структура в Си (в Паскале запись).
Структурная переменная, или просто структура, состоит из нескольких переменных (называемых полями), возможно, разного типа.
тип "структура" (шаблон) |
переменная типа "структура " |
Описание шаблона : |
Описание структурной переменной |
typedef struct { Тип1 Список1ИменПолей; |
struct ИмяШаблона ИмяПеременной |
Тип2 Список2ИменПолей; | |
ТипN СписокNИменПолей; |
ключевое struct слово не нужно пpи |
} ИмяШаблона |
использовании typedef |
или struct ИмяШаблона | |
{ Тип1 Список1ИменПолей;¦ | |
Тип2 Список2ИменПолей; | |
ТипN СписокNИменПолей; | |
typedef struct {char author; char title;/*описание*/
int year; float price} BOOK; /*шаблона BOOK*/
/*или можно описать тот же самый шаблон так:
struct BOOK {char author; char title;
int year; float price} ;*/
struct BOOK b;/*описание структурной переменной b*/
Память, занимаемая структурой, равна сумме объемов памяти полей (если исключить из рассмотрения особенности, связанные с выравниванием). В любом случае для определения размера памяти структуры можно использовать операцию sizeof(). Шаблон ВООК, например, описывает структуру размером памяти 70.
Обращение к полю структурной переменной:
ИмяСтруктуры.ИмяПоля или АдресСтруктуры ->ИмяПоля
. (точка) и ->являются операциями, соответственно, прямого и косвенного выбора компоненты структурированной переменной.
Например,
struct BOOK a,*pnta=&a;...
a.author="Byron"; pnta->author="Byron"; /*эквивалентные операторы*/
Пример. Задача примера 2 п.3.1.4 в нижеприведенной программе выполнена с использованием структур (а не строк).
#include
#include
#include
{ /*структура сведений об игрушках*/
typedef struct {int nu;/*номер*/
char name;/*наименование*/
int minage,maxage;/*мин. и макс. возраст ребенка*/
double rub /*стоимость*/;}TOYS;
TOYS toy;/*переменная типа записьTOYS */
double max; /*максимальная стоимость*/
char namemax;/*название самого дорогого конструктора*/
int n /*число игрушек*/,i/*номер игрушки*/;
puts("Введите число наименований игpушек");
for (i=0;
i /*в
цикле ввод сведений об игрушках и
проверка условий*/ fflush(stdin); /*очистка буфера
устройства ввода послеscanf
*/ printf("
Введите сведения об игpушке с номеpом
%2d\n",toy.nu); puts("наименование"); puts("мин. и макс. возpаст и стоимость"); scanf("%d%d%lf",&toy.minage,&toy.maxage,&toy.rub); if
((strstr(toy.name,"констpуктоp")!=NULL || strstr(toy.name,"Констpуктоp")!=NULL) && (toy.maxage <= 7) && (toy.rub>max)) strcpy(namemax,toy.name); puts(" Констpуктоpов для детей до семи
лет нет"); {
printf("Cамый доpогой констpуктоp для
детей до семи лет\n"); printf(" %s стоит %8.0f pублей\n",namemax,max); В Си существует еще один сложный тип,
описание которого формально похоже на
структуру. Это тип (и переменная)
объединение
. Объединение - это переменная, содержащая
поля разного типа, помещаемые в одно и
то же место памяти. По существу объединение
дает способ различной интерпретация
содержимого памяти. Описание шаблона
(типа) объединения и переменной этого
типа выполняется также, как для структуры,
только вместо ключевого слова struct
используетсяunion
. Размер памяти,
занимаемой объединением, равен
максимальному из размеров полей. Структура — это агрегатный тип данных, так как может содержать в себе разнотипные элементы. Синтаксис объявления структуры в С++ отличается от C. Хотя версия C остается правильной для C++. Получается, что в С++ можно двумя стилями объявления структур пользоваться, а в языке C — только одной. Смотрим синтаксис объявления структуры в языке С++: Struct Name
{
type atrib;
// остальные элементы структуры
} structVar1, structVar2, ...;
Объявление структуры всегда должно начинаться с ключевого слова struct . Необязательно, чтобы структура имела имя, но тогда такая структура обязательно должна иметь структурные переменные, объявленные между закрывающей фигурной скобкой и точкой с запятой, строка 5. Обязательно в объявлении структуры должны присутствовать фигурные скобочки, они обрамляют тело структуры, в котором объявляются её атрибуты (элементы), строка 3. Структурные переменные, при объявлении структуры, указывать необязательно, строка 5. Так как структура это тип данных, то, для того, чтобы использовать этот тип данных, необходимо объявить структурную переменную, а вместо типа данных указать имя структуры. Синтаксис объявления структуры в языке Си: Typedef struct name
{
type atrib1;
type atrib2;
// остальные элементы структуры...
} newStructName structVar;
Синтаксис объявления структуры в языке Си предполагает два варианта. Первый, опустить ключевое слово
typedef , при этом имя
newStructName тоже не используется, и имя структуры, тогда обязательно необходимо при объявлении структуры использовать структурные переменные —
structVar , строка 6. Смотрим пример:
Struct name structVar;
Или вы можете воспользоваться typedef , для объявления псевдонима структуры newStructName , псевдоним: NewStructName structVar;
В любом случае, если вы хотите, объявить указатель на структуру внутри структуры, вы должны использовать первый синтаксис: Struct name *struct_instance; // указатель на структуру
Синтаксис объявления указателя на структуру в Си неоднозначен. В Си, если вы не используете typedef при определении структуры, то, в обязательном порядке необходимо использовать структурные переменные, между закрывающейся фигурной скобочкой и точкой с запятой. StructName *structVar; // указатель на структуру structName
NewStructName *structVar; // newStructName должно быть объявлено с typedef
или так, тоже для СИ: Struct name *structVar;
Доступ к элементам структуры так же прост, как использование символа «точка». Предположим. что у нас есть структурная переменная с именем car и у нее есть элемент с именем speed , к которому, мы сейчас получим доступ: Car.speed;
Примечание: такой способ доступа к элементам структуры работает только в том случае, когда структура не является указателем на структуру. Чтобы получить доступ к элементам структуры, через указатель на структуру, вместо оператора «точка», используйте оператор стрелка -> : CarPtr->speed;
P.S.: Всем владельцам Android-смартфонов представляю хорошую подборку программ GPS навигаторов для android . В списке представлено около 20 программных продуктов, вы можете любой скачать и установить на свой девайс. Все программы абсолютно бесплатные. Недавно познакомился со структурами C/C++ - struct. Господи, да «что же с ними знакомиться» скажете вы? Тем самым вы допустите сразу 2 ошибки: во-первых я не Господи, а во вторых я тоже думал что структуры - они и в Африке структуры. А вот как оказалось и - нет. Я расскажу о нескольких жизненно-важных подробностях, которые кого-нибудь из читателей избавят от часовой отладки… Struct Foo
{
char ch;
int value;
};
В общем выравниваются в памяти поля по границе кратной своему же размеру. То есть 1-байтовые поля не выравниваются, 2-байтовые - выравниваются на чётные позиции, 4-байтовые - на позиции кратные четырём и т.д. В большинстве случаев (или просто предположим что сегодня это так) выравнивание размера структуры в памяти составляет 4 байта. Таким образом, sizeof(Foo) == 8 . Где и как прилепятся лишние 3 байта? Если вы не знаете - ни за что не угадаете… Struct Foo
{
char ch;
short id;
int value;
};
Struct Foo
{
char ch;
short id;
short opt;
int value;
};
#pragma pack(push, 1)
struct Foo
{
// ...
};
#pragma pack(pop)
Мне становится не то что неспокойно на душе, а вообще становится хреново, когда я вижу в коде заполнение битовых полей при помощи масок и сдвигов, например так: Unsigned field = 0x00530000;
// ...
field &= 0xFFFF00FF;
field |= (id) << 8;
// ...
field &= 0xFFFFFF83;
field |= (proto) << 2;
Как вам такой кусок кода: #pragma pack(push,1)
struct IpHeader
{
uint8_t header_length:4;
uint8_t version:4;
uint8_t type_of_service;
uint16_t total_length;
uint16_t identificator;
// Flags
uint8_t _reserved:1;
uint8_t dont_fragment:1;
uint8_t more_fragments:1;
uint8_t fragment_offset_part1:5;
uint8_t fragment_offset_part2;
uint8_t time_to_live;
uint8_t protocol;
uint16_t checksum;
// ...
};
#pragma pack(pop)
#pragma pack(push,1)
stuct MyBitStruct
{
uint16_t a:4;
uint16_t b:4;
uint16_t c;
};
#pragma pack(pop)
Также порядок размещения битовых болей в байте зависит от порядка байтов. При порядке LITTLE_ENDIAN битовые поля раздаются начиная со первых байтов, при BIG_ENDIAN - наоборот… Ну тут я буду краток. Я в одной из своих предыдущих статей уже писал что нужно делать с порядками байтов. Есть возможность описать структуры, которые внешне работают как числа, а внутри сами определяют порядок хранения в байтах. Таким образом наша структура IP-заголовка будет выглядеть так: #pragma pack(push,1)
struct IpHeader
{
uint8_t header_length:4;
uint8_t version:4;
uint8_t type_of_service;
u16be total_length;
u16be identificator;
// Flags
uint8_t _reserved:1;
uint8_t dont_fragment:1;
uint8_t more_fragments:1;
uint8_t fragment_offset_part1:5;
uint8_t fragment_offset_part2;
uint8_t time_to_live;
uint8_t protocol;
u16be checksum;
// ...
};
#pragma pack(pop)
«Язык С++ достаточно сложен, чтобы позволить нам писать на нём просто»
Как ни странно - Я З.Ы.
Планирую в одной из следующих статей выложить идеальные, с моей точки зрения, структуры для работы с заголовками протоколов стека TCP/IP. Отговорите - пока не поздно! Перед тем как приступить к изучению классов в C++, мы рассмотрим тип данных подобный классу — структуры. Структуры полезны, когда нам надо объединить несколько переменных с разными типами под одним именем. Это делает программу более компактной и более гибкой для внесения изменений. Также структуры незаменимы, когда необходимо сгруппировать некоторые данные, например, запись из базы данных или контакт из книги адресов. В последнем случае структура будет содержать такие данные контакта как имя, адрес, телефон и т.п. В процессе написания программы может потребоваться сгруппировать разные данные. Например, вы захотите хранить координаты некоторых объектов и их имена. Вы можете сделать это с помощью : Int x_coor;
int y_coor;
string names;
Но так как каждый элемент одного массива связан с другим, то при изменении одного, придется менять остальные тоже. И чем больше данных вам надо объединить, тем сложнее будем такая программа. Поэтому для объединения разных данных используются структуры
. Формат объявления
структуры выглядит так: Struct Car
{
int x_coor;
int y_coor;
string name;
};
Объявляя структуру, мы вводим в программу наш собственный тип данных, которым можем пользоваться, так же как и стандартными типами, т.е. объявление переменной нашего типа будет таким: StructName variableName;
structName — имя структуры, variableName — имя переменной. x_coor, y_coor и name — поля
нашей структуры. При объявлении структуры мы создаем составной тип данных, с помощью которого можно создавать переменные, которые сочетают в себе несколько значений (например, координаты и имя). Внутри структуры каждому полю мы даем имя, чтобы потом обращаться к этому значению по его имени. Для доступа к полям структуры используется точка:
// объявляем переменную
Car myCar;
// и используем её
myCar.x_coor = 40;
myCar.y_coor = 40;
myCar.name = "Porche";
Как видите, вы можете хранить в структуре столько полей, сколько вам угодно и они могут иметь разные типы. Рассмотрим пример, демонстрирующий сочетание массивов и структур.
#include Так же как и с простыми типами (int, например), вы можете создавать массивы структур. А с каждым элементом этого массива работать так же как и с отдельной переменной. Для доступа к полю name первого элемента массива структур, просто напишите: Players[ 0 ].name
Очень часто требуется писать функции, которые принимают структуры в качестве аргумента или возвращают структуру. Например, если вам надо написать небольшую космическую аркаду, вам может понадобится функция для инициализации нового противника: Struct EnemySpaceShip
{
int x_coordinate;
int y_coordinate;
int weapon_power;
};
EnemySpaceShip getNewEnemy();
Функция getNewEnemy должна возвращать структуру с инициализированными полями: EnemySpaceShip getNewEnemy ()
{
EnemySpaceShip ship;
ship.x_coordinate = 0;
ship.y_coordinate = 0;
ship.weapon_power = 20;
return ship;
}
На самом деле эта функция вернет копию созданной локальной переменной ship. Это значит, что каждое поле структуры будет скопировано в новую переменную. В нашем случае копирование малого количества полей не заметно, но когда вы работаете с большими объемами данных нужно избегать лишних действий, подробнее об этом поговорим в статье про указатели. Таким образом, для получения новой переменной будем использовать следующий код: EnemySpaceShip ship = getNewEnemy();
Теперь эту переменную можно использовать как обычную структуру. Передавать структуры в функцию можно так: EnemySpaceShip upgradeWeapons (EnemySpaceShip ship)
{
ship.weapon_power += 10;
return ship;
}
Когда мы передаем структуру в функцию, она копируется, так же как и при возвращении структуры. Поэтому любые изменения сделанные внутри функции будут потеряны, поэтому мы возвращаем структуру после изменения. Использование функции: Ship = upgradeWeapons(ship);
Когда вызывается функция, переменная ship копируется и изменяется в функции, а когда переменная возвращается, она снова копируется и перезаписывает поля оргинальной переменной. И наконец, программа для создания и улучшения одного корабля: Struct EnemySpaceShip {
int x_coordinate;
int y_coordinate;
int weapon_power;
};
EnemySpaceShip getNewEnemy() {
EnemySpaceShip ship;
ship.x_coordinate = 0;
ship.y_coordinate = 0;
ship.weapon_power = 20;
return ship;
}
EnemySpaceShip upgradeWeapons(EnemySpaceShip ship) {
ship.weapon_power += 10;
return ship;
}
int main() {
EnemySpaceShip enemy = getNewEnemy();
enemy = upgradeWeapons(enemy);
}
Если вы работаете с на структуру, то для доступа к переменным надо использовать оператор «->» вместо точки. Все свойства указателей не изменяются. Пример:
#include Объявление указателя на структуру
В C++, этого не требуется. Чтобы объявить указатель на структуру, в С++ вы просто перед именем структурной переменной ставите символ указателя — * .Доступ к элементам структуры
Доступ к элементам указателя на структуру
Выравнивание полей в памяти
Обратите внимание на структуру:
Ну во-первых какой у этой структуры размер в памяти? sizeof(Foo) ?
Размер этой структуры в памяти зависит от настроек компилятора и от директив в вашем коде…
Посмотрим теперь размещение в памяти следующей структуры:
Оно выглядит вот так:
То есть, то что можно впихнуть до выравнивания по 4 байта - впихивается на ура (без увеличения размера структуры в памяти), добавим ещё одно поле:
Посмотрим на размещение полей в памяти:
Всё это ой как печально, но есть способ бороться с этим прямо из кода:
Мы установили размер выравнивания в 1 байт, описали структуру и вернули предыдущую настройку. Возвращать предыдущую настройку - категорически рекомендую. Иначе всё может закончиться очень плачевно. У меня один раз такое было - падало Qt. Где-то заинклюдил их.h-ник ниже своего.h-ника…Битовые поля
В комментариях мне указали на то, что битовые поля в структурах по стандарту являются «implementation defined»
- потому их использования лучше избежать, но для меня соблазн слишком велик...
Всё это пахнет такой печалью и такими ошибками и их отладкой, что у меня сразу же начинается мигрень! И тут из-за кулис выходят они - Битовые Поля. Что самое удивительное - были они ещё в языке C, но кого ни спрашиваю - все в первый раз о них слышат. Этот беспредел надо исправлять. Теперь буду давать им всем ссылку, ну или хотя бы ссылку на эту статью.
А дальше в коде мы можем работать с полями как и всегда работаем с полями в C/C++. Всю работу по сдвигам и т.д. берет на себя компилятор. Конечно же есть некоторые ограничения… Когда вы перечисляете несколько битовых полей подряд, относящихся к одному физическому полю (я имею ввиду тип который стоит слева от имени битового поля) - указывайте имена для всех битов до конца поля, иначе доступа к этим битам у вас не будет, иными словами кодом:
Получилась структура на 4 байта! Две половины первого байта - это поля a и b . Второй байт не доступен по имени и последние 2 байта доступны по имени c . Это очень опасный момент. После того как описали структуру с битовыми полями обязательно проверьте её sizeof !Порядок байтов
Меня также печалят в коде вызовы функций htons() , ntohs() , htonl() , nthol() в коде на C++. На C это ещё допустимо, но не на С++. С этим я никогда не смирюсь! Внимание всё нижесказанное относится к C++!
Внимание собственно обращать на типы 2-байтовых полей - u16be . Теперь поля структуры не нуждаются ни в каких преобразованиях порядка байт. Остаются проблемы с fragment_offset , ну а у кого их нет - проблем-то. Тем не менее тоже можно придумать шаблон, прячущий это безобразие, один раз его оттестировать и смело использовать во всём своём коде.Синтаксис
Структуры и функции
Указатели