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

struct klass {
char name;
char klass_name;
float bal;
};

struct

Любая структура в языке си (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;

А какие же операции можно проделывать со структурами? Ответ на этот вопрос лучше перечислить по пунктам:

  1. присваивание полю структуры значение того же типа
  2. можно получить адрес структуры. Не забываем операцию взятия адреса (&)
  3. можно обращаться к любому полю структуры
  4. для того, что бы определить размер структуры можно использовать операцию sizeof()

Инициализация структуры

Инициализация структуры в языке си (c) происходит так же, как и при инициализации массива. Вот пример инициализации структуры:

struct klass a = {"Sergey", "B", 4.5 };

Т.е. мы создаем переменную типа struct klass и присваиваем всем трем полям, которые у нас определенны в структуре, значения. Порядок очень важен при инициализации структуры , так как компьютер сам не может отсортировывать данные. Если какое-либо поле у вас будет не заполненным, то оно автоматом заполнится 0 - для целочисленных типов; NULL - для указателей; \0 (ноль-терминатор) - для строковых типов.

Структуры

Как вам должно быть уже известно, классы относятся к ссылочным типам данных. Это означает, что объекты конкретного класса доступны по ссылке, в отличие от значений простых типов, доступных непосредственно. Но иногда прямой доступ к объектам как к значениям простых типов оказывается полезно иметь, например, ради повышения эффективности программы. Ведь каждый доступ к объектам (даже самым мелким) по ссылке связан с дополнительными издержками на расход вычислительных ресурсов и оперативной памяти.

Для разрешения подобных затруднений в C# предусмотрена структура , которая подобна классу, но относится к типу значения, а не к ссылочному типу данных. Т.е. структуры отличаются от классов тем, как они сохраняются в памяти и как к ним осуществляется доступ (классы - это ссылочные типы, размещаемые в куче, структуры - типы значений, размещаемые в стеке), а также некоторыми свойствами (например, структуры не поддерживают наследование). Из соображений производительности вы будете использовать структуры для небольших типов данных. Однако в отношении синтаксиса структуры очень похожи на классы.

Главное отличие состоит в том, что при их объявлении используется ключевое слово struct вместо class. Ниже приведена общая форма объявления структуры:

struct имя: интерфейсы { // объявления членов }

где имя обозначает конкретное имя структуры.

Как и у классов, у каждой структуры имеются свои члены: методы, поля, индексаторы, свойства, операторные методы и события. В структурах допускается также определять конструкторы, но не деструкторы. В то же время для структуры нельзя определить конструктор, используемый по умолчанию (т.е. конструктор без параметров). Дело в том, что конструктор, вызываемый по умолчанию, определяется для всех структур автоматически и не подлежит изменению. Такой конструктор инициализирует поля структуры значениями, задаваемыми по умолчанию. А поскольку структуры не поддерживают наследование, то их члены нельзя указывать как abstract, virtual или protected.

Объект структуры может быть создан с помощью оператора new таким же образом, как и объект класса, но в этом нет особой необходимости. Ведь когда используется оператор new, то вызывается конструктор, используемый по умолчанию. А когда этот оператор не используется, объект по-прежнему создается, хотя и не инициализируется. В этом случае инициализацию любых членов структуры придется выполнить вручную.

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

Using System; namespace ConsoleApplication1 { // Создадим структуру struct UserInfo { public string Name; public byte Age; public UserInfo(string Name, byte Age) { this.Name = Name; this.Age = Age; } public void WriteUserInfo() { Console.WriteLine("Имя: {0}, возраст: {1}",Name,Age); } } class Program { static void Main() { UserInfo user1 = new UserInfo("Alexandr", 26); Console.Write("user1: "); user1.WriteUserInfo(); UserInfo user2 = new UserInfo("Elena",22); Console.Write("user2: "); user2.WriteUserInfo(); // Показать главное отличие структур от классов user1 = user2; user2.Name = "Natalya"; user2.Age = 25; Console.Write("\nuser1: "); user1.WriteUserInfo(); Console.Write("user2: "); user2.WriteUserInfo(); Console.ReadLine(); } } }

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

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

Назначение структур

В связи с изложенным выше возникает резонный вопрос: зачем в C# включена структура, если она обладает более скромными возможностями, чем класс? Ответ на этот вопрос заключается в повышении эффективности и производительности программ. Структуры относятся к типам значений, и поэтому ими можно оперировать непосредственно, а не по ссылке. Следовательно, для работы со структурой вообще не требуется переменная ссылочного типа, а это означает в ряде случаев существенную экономию оперативной памяти.

Структура — это агрегатный тип данных, так как может содержать в себе разнотипные элементы. Синтаксис объявления структуры в С++ отличается от C. Хотя версия C остается правильной для C++. Получается, что в С++ можно двумя стилями объявления структур пользоваться, а в языке C — только одной. Смотрим синтаксис объявления структуры в языке С++:

Struct Name { type atrib; // остальные элементы структуры } structVar1, structVar2, ...;

  • struct — ключевое слово, которое начинает определение структуры
  • Name — имя структуры
  • type — тип данных элемента структуры
  • atrib — элемент структуры
  • structVar1-2 — структурные переменные

Объявление структуры всегда должно начинаться с ключевого слова struct . Необязательно, чтобы структура имела имя, но тогда такая структура обязательно должна иметь структурные переменные, объявленные между закрывающей фигурной скобкой и точкой с запятой, строка 5. Обязательно в объявлении структуры должны присутствовать фигурные скобочки, они обрамляют тело структуры, в котором объявляются её атрибуты (элементы), строка 3. Структурные переменные, при объявлении структуры, указывать необязательно, строка 5.

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

struct_name structVariable;

Синтаксис объявления структуры в языке Си:

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 при определении структуры, то, в обязательном порядке необходимо использовать структурные переменные, между закрывающейся фигурной скобочкой и точкой с запятой.
В C++, этого не требуется. Чтобы объявить указатель на структуру, в С++ вы просто перед именем структурной переменной ставите символ указателя — * .

StructName *structVar; // указатель на структуру structName

NewStructName *structVar; // newStructName должно быть объявлено с typedef

или так, тоже для СИ:

Struct name *structVar;

Доступ к элементам структуры

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

Car.speed;

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

Доступ к элементам указателя на структуру

Чтобы получить доступ к элементам структуры, через указатель на структуру, вместо оператора «точка», используйте оператор стрелка -> :

CarPtr->speed;

P.S.: Всем владельцам Android-смартфонов представляю хорошую подборку программ GPS навигаторов для android . В списке представлено около 20 программных продуктов, вы можете любой скачать и установить на свой девайс. Все программы абсолютно бесплатные.

Последнее обновление: 09.10.2017

Ранее для определения классов мы использовали ключевое слово class . Однако C++ предоставляет еще один способ для определения пользовательских типов, который заключается в использовании структур. Данный способ был унаследован языком С++ еще от языка Си.

Структура в языке C++ представляет собой производный тип данных, который представляет какую-то определенную сущность, также как и класс. Нередко структуры применителько к С++ также называют классами. И в реальности различия между ними не такие большие.

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

Struct имя_структуры { компоненты_структуры };

Имя_структуры представляет произвольный идентификатор, к которому применяются те же правила, что и при наименовании переменных.

После имени структуры в фигурных скобках помещаются Компоненты_структуры , которые представляют набор описаний объектов и функций, которые составляют структуру.

Например, определим простейшую структуру:

#include #include struct person { int age; std::string name; }; int main() { person tom; tom.name = "Tom"; tom.age = 34; std::cout << "Name: " << tom.name << "\tAge: " << tom.age << std::endl; return 0; }

Здесь определена структура person , которая имеет два элемента: age (представляет тип int) и name (представляет тип string).

После определения структуры мы можем ее использовать. Для начала мы можем определить объект структуры - по сути обычную переменную, которая будет представлять выше созданный тип. Также после создания переменной структуры можно обращаться к ее элементам - получать их значения или, наоборот, присваивать им новые значения. Для обращения к элементам структуры используется операция "точка":

Имя_переменной_структуры.имя_элемента

По сути структура похожа на класс, то есть с помощью структур также можно определять сущности для использования в программе. В то же время все члены структуры, для которых не используется спецификатор доступа (public, private), по умолчанию являются открытыми (public). Тогда как в классе все его члены, для которых не указан спецификатор доступа, являются закрытыми (private).

Кроме того мы можем инициализировать структуру, присвоив ее переменным значения с помощью синтаксиса инициализации:

Person tom = { 34, "Tom" };

Инициализация структур аналогична инициализации массивов: в фигурных скобках передаются значения для элементов структуры по порядку. Так как в структуре person первым определено свойство, которое представляет тип int - число, то в фигурных скобках вначале идет число. И так далее для всех элементов структуры по порядку.

При этом любой класс мы можем представить в виде структуры и наоборот. Возьмем, к примеру, следующий класс:

Class Person { public: Person(std::string n, int a) { name = n; age = a; } void move() { std::cout << name << " is moving" << std::endl; } void setAge(int a) { if (a > 0 && a < 100) age = a; } std::string getName() { return name; } int getAge() { return age; } private: std::string name; int age; };

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

#include #include struct user { public: user(std::string n, int a) { name = n; age = a; } void move() { std::cout << name << " is moving" << std::endl; } void setAge(int a) { if (a > 0 && a < 100) age = a; } std::string getName() { return name; } int getAge() { return age; } private: std::string name; int age; }; int main() { user tom("Tom", 22); std::cout << "Name: " << tom.getName() << "\tAge: " << tom.getAge() << std::endl; tom.setAge(31); std::cout << "Name: " << tom.getName() << "\tAge: " << tom.getAge() << std::endl; return 0; }

И в плане конечного результата программы мы не увидели бы никакой разницы.

Когда использовать структуры? Как правило, структуры используются для описания таких данных, которые имеют только набор публичных атрибутов - открытых переменных. Например, как та же структура person, которая была определена в начале статьи. Иногда подобные сущности еще называют аггрегатными классами (aggregate classes).

Структура - это удобное хранилище для разнородных данных, которые хочется объединить. К примеру, вы можете создать структуру, описывающую параметры вашего устройства - сетевые настройки, таймаут спящего режима, его идентификатор и прочее подобное, типа какой-нибудь строки приветствия и состояния светодиода. Раз все параметры будут храниться в одном месте - они всегда будут на виду, да и нормальные IDE будут вам подсказывать поля структуры при обращении к ним. Ещё мы рассмотрим хранение и восстановление структур из архива, а также их передачу по сети.

Объявление такой структуры:

Struct { uint32_t ID; char IP; uint16_t timeout; bool led; char text; } params;

Как это работает?

В си довольно удобный синтаксис, в том плане что многие вещи записываются как «тип_данных переменная», начиная с «int i» заканчивая «void main() {}». Так и здесь, кодовое слово struct начинает объявление структуры, и весь кусок кода «struct { … }» просто задаёт новый тип. Соответственно, params - это уже готовая переменная (экземпляр типа), которую можно использовать. Внутри фигурных скобок перечислены все поля структуры, которые потом будут доступны так: params.ID или params.IP. Длина полей должна быть фиксированной, поэтому нельзя использовать строки вида *text, только массивы вида text.

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

Typedef struct { uint32_t ID; char IP; uint16_t timeout; bool led; char text; } params_struct; params_struct params;

Так появляется возможность оставить все объявления структурных типов в отдельном файле (header), а в главном файле просто использовать уже готовые структурные типы для объявления структур прямо по месту.

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

Struct { uint32_t ID; char IP; uint16_t timeout; bool led; char text; } params1, params2, params;

Вариант с массивом особенно удобен для сервера в клиент-серверной топологии сети - на каждом клиенте хранятся в структуре его собственные параметры, а на мастер-устройстве располагается таблица параметров всех клиентов в виде массива структур.

В принципе, ничего сложного в структурах нет, а с темой серверов и клиентов мы плавно подошли к более интересной теме:

Хранение, передача и синхронизация структур

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

Начальное смещение получим так:

Char *Bytes = ¶ms;

мы объявили указатель char и поместили в него адрес params. Теперь Bytes указывает на первый байт структуры, и при последовательном чтении мы побайтно прочитаем всю структуру. Но сколько байт нужно прочитать? Для этого рассмотрим две интересных функции.

sizeof и offsetof

Это даже не функции, а встроенные макросы языка Си. Начнём с более простой, sizeof .

Компилятор заменяет все записи вида sizeof X на значение длины Х. В качестве X может выступать как тип, так и экзмепляр типа, т.е. в нашем случае можно подставить в sizeof и тип структуры (если мы его заводили с помощью typedef), и саму переменную структуры так: sizeof params_struct или sizeof params. Она пройдёт по всем полям структуры, сложит их длины и отдаст сумму, которая и будет длиной структуры.

offsetof - настоящий макрос, который принимает два параметра (структуру _s_ и поле _m_ в ней) и отдаёт положение этого поля в структуре, его смещение относительно начала структуры. Выглядит этот макрос очень просто:

Offsetof(s, m) (size_t)&(((s *)0)-›m).

Как он работает?

  1. Берём число 0
  2. Преобразуем его к типу «указатель на структуру s»: (s*)0
  3. Обращаемся к полю m из этой структуры: ((s*)0)->m
  4. Вычисляем его адрес: &(((s*)0)->m)
  5. Преобразуем адрес к целому числу: (size_t)&(((s*)0)->m)

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

Здесь нужно сделать небольшое отступление. Дело в том, что я рассматривал самый простой случай, когда поля упакованы точно вслед друг за другом. Есть и другие методы упаковки, которые называются «выравнивание». К примеру, можно выдавать каждому полю «слот», кратный 4 байтам, или 8 байтам. Тогда даже char будет занимать 8 байт, и общий размер структуры вырастет, а все смещения сдвинутся и станут кратны выравниванию. Эта штука полезна при программировании для компьютера, поскольку из-за грануляции ОЗУ процессор гораздо быстрее умеет извлекать из памяти выровненные данные, ему требуется на это меньше операций.

Работа с массивом из структуры

Окей, теперь мы умеем представлять любую структуру в виде массива байт, и обратно. Вы поняли фишку? У нас теперь одна и та же область памяти имеет роли «структура» и «массив». Изменяем что-то в структуре - меняется массив, меняем массив - меняется структура.

В этом - суть процесса! У нас нет отдельного массива, потому что сама структура - это уже массив, и мы просто обращаемся к памяти разными методами. И у нас нет никаких копирующих циклов по полям или по байтам, этот цикл будет уже сразу в функции передачи.

Теперь осталось лишь научиться удобно с этим всем работать.

Хранение и передача структуры

Чтобы создать архивную копию структуры, для передачи по сети или для складывания её в надёжное место - отдайте в вашу функцию передачи данных адрес этого массива. К примеру, моя функция записи массива данных в EEPROM выглядит так: I2C_burst_write (I2Cx, HW_address, addr, n_data, *data). Вам просто нужно вместо n_data передать sizeof params, а вместо *data - ¶ms:

I2C_burst_write (I2Cx, HW_address, addr, sizeof params, ¶ms)

Функции передачи данных по сети обычно выглядят примерно так же. В качестве данных передавайте ¶ms, а в качестве длины данных - sizeof params.

Приём и восстановление структуры

Всё точно так же. Моя функция чтения массива из EEPROM: I2C_burst_read (I2Cx, HW_address, addr, n_data, *data). n_data = sizeof params, *data = ¶ms:

I2C_burst_read (I2Cx, HW_address, addr, sizeof params, ¶ms)

Не забывайте, что вы сразу пишете принятые байты непосредственно в структуру. При медленной или ненадёжной передаче имеет смысл записать данные во временный буфер, и после их проверки передать их в структуру через

Memcpy(¶ms, &temp_buffer, sizeof params).

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

Хранение/восстановление отдельных полей

И зачем же мы так долго рассматривали макрос offsetof? Его очень удобно использовать для чтения и записи отдельных полей структуры, например так:

I2C_burst_write (I2Cx, HW_address, addr + offsetof(params, IP), sizeof params.IP, ¶ms.IP) I2C_burst_read (I2Cx, HW_address, addr + offsetof(params, IP), sizeof params.IP, ¶ms.IP)

Ну и вообще, было бы неплохо сделать удобные макросы-обёртки для этой цели.

#define store(structure, field) I2C_burst_write (I2Cx, HW_address, addr + offsetof(structure, field), sizeof(structure.field), &(structure.field)) #define load(structure, field) I2C_burst_read (I2Cx, HW_address, addr + offsetof(structure, field), sizeof(structure.field), &(structure.field))