Сайт о телевидении

Сайт о телевидении

» » Структуры. Инициализация структуры в си (c struct)

Структуры. Инициализация структуры в си (c struct)

Тип переменной определяет: её размер в памяти, тип данных, которые она может хранить и операции, которые можно производить с этой переменной.

Тип данных является категорией. В языке С++ программист может создать любой тип данных на основе базовых типов. Новые типы данных необходимо создавать для решения конкретных практических задач. Например: реализация работы деканата.

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

Для использования структуры необходимо:
1. установить шаблон для структуры
2. объявить переменную, соответствующую этому шаблону
3. осуществить доступ к компонентам структуры.

Шаблон структуры

Шаблон - это схема, описывающая содержание структуры. Установка структурного шаблона телефонный справочник:

struct sprav {
char fio;
long num;
};

Данный шаблон описывает структуру с именем типа структуры sprav, состоящую из двух компонентов: строки fio и целой переменной num типа long. Имя типа структуры sprav необязательно и используется для ссылки на эту структуру. Компоненты структуры - данные любого типа, включая и другие структуры. Имя внутри структуры может быть таким же, как имя объекта вне структуры. Если шаблон описан внутри функции - он доступен только этой функции, если шаблон описан вне функции - он доступен любой функции программы. Установка шаблона не вызывает никаких действий в программе.

Структурные переменные

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

1. Установить структурный шаблон:

struct sprav {
char fio;
long num;
};

Объявить простую переменную, массив структур, указатель на структуру: struct sprav tel1, tel2, *tel3;

2. Установить структурный шаблон с помощью макроопределения:

#define SPRAV struct sprav
SPRAV {
char fio;
long num;
};

Объявить переменные:

SPRAV sp1, sp2, *sp3;

3. Объявить переменные одновременно с установкой шаблона (если на данную структуру вы больше не ссылаетесь):

struct {
char fio;
long num;
} tel1, tel2, *tel3;

4. Ввести новый тип данных (TEL)-структура определенного вида:

typedef struct {
char fio;
long num;
} TEL;

Объявить переменные нового типа:

TEL tel1, tel2, *tel3;

Если программа достаточно объемна, представляется более удобным четвертый способ.

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

Инициализировать можно только внешние или статические структуры.

static struct {
char fio;
long num;
} tel={
"Иванов Ф.А.", 456756,
"Петров В.П.", 632345
};

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

Доступ к компонентам структуры продемонстрируем с помощью примеров.

/* Обращение к элементам структуры через имя переменной */
#include
#include
void main(void)
{
struct{
char fio; /* фамилия */
long num; /* телефон */
} tel1, tel2;

puts("введите фио абонента-");
gets(tel1.fio);
puts("введите его номер-");
scanf("%ld",&tel1.num);
tel2=tel1; /* нельзя так же сравнивать структуры */
puts("Введено:");
printf("Фамилия:%s номер: %ld\n",tel2.fio,tel2.num);
}

/* Динамическое выделение памяти для структуры */

#include
#include
#include
struct sprav {
char fio;
long num;
};

void main(void)
{
struct sprav *tel1, *tel2;

clrscr();
/* Выделение памяти для структуры */
tel1=(struct sprav *)malloc(sizeof(struct sprav));
tel2=(struct sprav *)malloc(sizeof(struct sprav));

gets(tel1->fio);
puts("введите его номер-");
scanf("%ld",&tel1->num);
*tel2= *tel1;
puts("Введено:");
printf("Фамилия:%s номер: %ld\n",(*tel2).fio,(*tel2).num);
}

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

/* Массив структур. Обращение к элементам структуры через */
/* имя элемента массива */
#include
#include
#include
#define SPRAV struct sprav

void main(void)
{
SPRAV{
char fio;
long num;
};

SPRAV tel; /* массив структур - 5 элементов */
char fio_tek;
int i;

clrscr();
/* ввод данных в массив структур */
for(i=0; i<5; i++)
{
puts("введите фио абонента-");
gets(tel[i].fio);
puts("введите его номер-");
scanf("%ld",&tel[i].num);
getchar();
}

gets(fio_tek);
/* поиск структуры по фамилии абонента */
for(i=0; i<5; i++)
if(!strcmp(fio_tek,tel[i].fio)) break;
if(i!=5) /* цикл закончен по break */

tel[i].num);
else /* цикл выполнился полностью */
puts("Абонент не найден");
}

/* Массив структур. Память выделяется динамически. */
/* Обращение к элементам структуры через указатель */
#include
#include
#include
#include

typedef struct{
char fio;
long num;
} TEL;

void main(void)
{
TEL *tel;
char fio_tek;
int i;

clrscr();
/* Выделение памяти для массива - 3 элемента */
tel=(TEL *)malloc(sizeof(TEL)*3);
for(i=0; i<3; i++)
{
puts("введите фио абонента-");
gets((tel+i)->fio);
puts("введите его номер-");
scanf("%ld",&(tel+i)->num);
getchar();
}
puts("Выбор телефона по фамилии");
gets(fio_tek);
for(i=0; i<5; i++,tel++)
if(!strcmp(fio_tek,tel->fio)) break;
if(i!=5)
printf("номер абонента %s равен %ld\n",fio_tek, \
tel->num);
else
puts("Абонент не найден");
}

Передача структуры в функцию

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

/* Передача структуры в функцию через указатель на структуру */
/* Определение комплексного числа через структуру и действия */
/* над комплексными числами (ввод, вывод, вычисление суммы) */

#include
typedef struct { float a; /* действительная часть */
float b; /* мнимая часть */
} COMPLEX;
void vvod(COMPLEX *,float,float);
void sum(COMPLEX *,COMPLEX *,COMPLEX *);
void out(COMPLEX *);
void main(void)
{
COMPLEX x,y,z;
vvod(&x,2.5,6.7);
vvod(&y,6.89,8.45);
puts("Введены числа:");
out(&x);
out(&y);
sum(&x,&y,&z);
puts("Сумма комплексных чисел равна:");
out(&z);
}
/* Вывод комплексного числа */
void out(COMPLEX *p)
{
printf("(%.2f,%.2f)\n", (*p).a,(*p).b);
return;
}

/* Вычисление суммы двух комплексных чисел */
void sum(COMPLEX *p1,COMPLEX *p2,COMPLEX *p3)
{
(*p3).a=(*p1).a+(*p2).a;
(*p3).b=(*p1).b+(*p2).b;
return;
}

/* Ввод значений для элементов структуры */
void vvod(COMPLEX *p,float a, float b)
{
p->a=a;
p->b=b;
return;
}

Вложенные структуры

Структура, являющаяся компонентом другой структуры, называется вложенной.

/* Даны четыре точки - центры четырех окружностей. Заполнить структуру окружность, если все окружности проходят через начало координат. */

#include
#include
#include
#include
struct POINT {
float x;
float y;
};
struct CIRCLE {
struct POINT point; /* вложенная структура */
double r;
} circle, *p;
void main (void)
{
int i,j;
float a,b,c,d;
clrscr();
gotoxy(17,1);
cputs("ВВЕДИТЕ КООРДИНАТЫ ТОЧЕК:\r\n");
for(i=0;i<2;i++)
{
cprintf ("\n\n ВВЕДИТЕ X: ");
cprintf ("X[%d]= ",i+1);
cscanf("%f",&circle[i].point.x);
cprintf ("\n ВВЕДИТЕ Y: ");
cprintf ("Y[%d]= ",i+1);
cscanf ("%f",&circle[i].point.y);
}
p=circle;
gotoxy(17,12);
cputs("РЕЗУЛЬТАТ:\r\n\n");
for(i=0;i<2;i++)
{
a=p->point.x;
b=p->point.y;
c=sqrt(a*a+b*b);
p->r=c;
cprintf("\nРАДИУС: %lf ЦЕНТР (%f,%f)\r\n",p->r,a,b);
p++;
}

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

Структурная переменная, или просто структура, состоит из не­скольких переменных (называемых полями), возможно, разного типа.

Структура

тип "структура" (шаблон)

переменная типа "структура "

Описание шаблона :

Описание структурной переменной

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++ - struct. Господи, да «что же с ними знакомиться» скажете вы? Тем самым вы допустите сразу 2 ошибки: во-первых я не Господи, а во вторых я тоже думал что структуры - они и в Африке структуры. А вот как оказалось и - нет. Я расскажу о нескольких жизненно-важных подробностях, которые кого-нибудь из читателей избавят от часовой отладки…

Выравнивание полей в памяти

Обратите внимание на структуру:

Struct Foo { char ch; int value; };
Ну во-первых какой у этой структуры размер в памяти? sizeof(Foo) ?
Размер этой структуры в памяти зависит от настроек компилятора и от директив в вашем коде…

В общем выравниваются в памяти поля по границе кратной своему же размеру. То есть 1-байтовые поля не выравниваются, 2-байтовые - выравниваются на чётные позиции, 4-байтовые - на позиции кратные четырём и т.д. В большинстве случаев (или просто предположим что сегодня это так) выравнивание размера структуры в памяти составляет 4 байта. Таким образом, sizeof(Foo) == 8 . Где и как прилепятся лишние 3 байта? Если вы не знаете - ни за что не угадаете…

  • 1 байт: ch
  • 2 байт: пусто
  • 3 байт: пусто
  • 4 байт: пусто
  • 5 байт: value
  • 6 байт: value
  • 7 байт: value
  • 8 байт: value
Посмотрим теперь размещение в памяти следующей структуры:

Struct Foo { char ch; short id; int value; };
Оно выглядит вот так:

  • 1 байт: ch
  • 2 байт: пусто
  • 3 байт: id
  • 4 байт: id
  • 5 байт: value
  • 6 байт: value
  • 7 байт: value
  • 8 байт: value
То есть, то что можно впихнуть до выравнивания по 4 байта - впихивается на ура (без увеличения размера структуры в памяти), добавим ещё одно поле:

Struct Foo { char ch; short id; short opt; int value; };
Посмотрим на размещение полей в памяти:

  • 1 байт: ch
  • 2 байт: пусто
  • 3 байт: id
  • 4 байт: id
  • 5 байт: opt
  • 6 байт: opt
  • 7 байт: пусто
  • 8 байт: пусто
  • 9 байт: value
  • 10 байт: value
  • 11 байт: value
  • 12 байт: value
Всё это ой как печально, но есть способ бороться с этим прямо из кода:

#pragma pack(push, 1) struct Foo { // ... }; #pragma pack(pop)
Мы установили размер выравнивания в 1 байт, описали структуру и вернули предыдущую настройку. Возвращать предыдущую настройку - категорически рекомендую. Иначе всё может закончиться очень плачевно. У меня один раз такое было - падало Qt. Где-то заинклюдил их.h-ник ниже своего.h-ника…

Битовые поля

В комментариях мне указали на то, что битовые поля в структурах по стандарту являются «implementation defined» - потому их использования лучше избежать, но для меня соблазн слишком велик...

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

Unsigned field = 0x00530000; // ... field &= 0xFFFF00FF; field |= (id) << 8; // ... field &= 0xFFFFFF83; field |= (proto) << 2;
Всё это пахнет такой печалью и такими ошибками и их отладкой, что у меня сразу же начинается мигрень! И тут из-за кулис выходят они - Битовые Поля. Что самое удивительное - были они ещё в языке C, но кого ни спрашиваю - все в первый раз о них слышат. Этот беспредел надо исправлять. Теперь буду давать им всем ссылку, ну или хотя бы ссылку на эту статью.

Как вам такой кусок кода:

#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)
А дальше в коде мы можем работать с полями как и всегда работаем с полями в C/C++. Всю работу по сдвигам и т.д. берет на себя компилятор. Конечно же есть некоторые ограничения… Когда вы перечисляете несколько битовых полей подряд, относящихся к одному физическому полю (я имею ввиду тип который стоит слева от имени битового поля) - указывайте имена для всех битов до конца поля, иначе доступа к этим битам у вас не будет, иными словами кодом:

#pragma pack(push,1) stuct MyBitStruct { uint16_t a:4; uint16_t b:4; uint16_t c; }; #pragma pack(pop)
Получилась структура на 4 байта! Две половины первого байта - это поля a и b . Второй байт не доступен по имени и последние 2 байта доступны по имени c . Это очень опасный момент. После того как описали структуру с битовыми полями обязательно проверьте её sizeof !

Также порядок размещения битовых болей в байте зависит от порядка байтов. При порядке LITTLE_ENDIAN битовые поля раздаются начиная со первых байтов, при BIG_ENDIAN - наоборот…

Порядок байтов

Меня также печалят в коде вызовы функций htons() , ntohs() , htonl() , nthol() в коде на C++. На C это ещё допустимо, но не на С++. С этим я никогда не смирюсь! Внимание всё нижесказанное относится к C++!

Ну тут я буду краток. Я в одной из своих предыдущих статей уже писал что нужно делать с порядками байтов. Есть возможность описать структуры, которые внешне работают как числа, а внутри сами определяют порядок хранения в байтах. Таким образом наша структура 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)
Внимание собственно обращать на типы 2-байтовых полей - u16be . Теперь поля структуры не нуждаются ни в каких преобразованиях порядка байт. Остаются проблемы с fragment_offset , ну а у кого их нет - проблем-то. Тем не менее тоже можно придумать шаблон, прячущий это безобразие, один раз его оттестировать и смело использовать во всём своём коде.

«Язык С++ достаточно сложен, чтобы позволить нам писать на нём просто» Как ни странно - Я

З.Ы. Планирую в одной из следующих статей выложить идеальные, с моей точки зрения, структуры для работы с заголовками протоколов стека TCP/IP. Отговорите - пока не поздно!

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

Наряду с классами структуры представляют еще один способ создания обственных типов данных в C#. Более того многие примитивные типы, например, int, double и т.д., по сути являются структурами.

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

Struct User { public string name; public int age; public void DisplayInfo() { Console.WriteLine($"Name: {name} Age: {age}"); } }

Как и классы, структуры могут хранить состояние в виде переменных и определять поведение в виде методов. Так, в данном случае определены две переменные - name и age для хранения соответственно имени и возраста человека и метод DisplayInfo для вывода информации о человеке.

Используем эту структуру в программе:

Using System; namespace HelloApp { struct User { public string name; public int age; public void DisplayInfo() { Console.WriteLine($"Name: {name} Age: {age}"); } } class Program { static void Main(string args) { User tom; tom.name = "Tom"; tom.age = 34; tom.DisplayInfo(); Console.ReadKey(); } } }

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

Конструкторы структуры

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

User tom;

Однако если мы таким образом создаем объект структуры, то обязательно надо проинициализировать все поля (глобальные переменные) структуры перед получением их значений или перед вызовом методов структуры. То есть, например, в следующем случае мы получим ошибку, так как обращение к полям и методам происходит до присвоения им начальных значений:

User tom; int x = tom.age; // Ошибка tom.DisplayInfo(); // Ошибка

Также мы можем использовать для создания структуры конструктор по умолчанию, при вызове которого полям структуры будет присвоено значение по умолчанию (например, для числовых типов это число 0):

User tom = new User(); tom.DisplayInfo(); // Name: Age: 0

Также мы можем определить свои конструкторы. Например, изменим структуру User:

Using System; using System.Reflection; namespace HelloApp { struct User { public string name; public int age; public User(string name, int age) { this.name = name; this.age = age; } public void DisplayInfo() { Console.WriteLine($"Name: {name} Age: {age}"); } } class Program { static void Main(string args) { User tom = new User("Tom", 34); tom.DisplayInfo(); User bob = new User(); bob.DisplayInfo(); Console.ReadKey(); } } }

Важно учитывать, что если мы определяем конструктор в структуре, то он должен инициализировать все поля структуры, как в данном случае устанавливаются значения для переменных name и age.

Также, как и для класса, можно использовать инициализатор для создания структуры:

User person = new User { name = "Sam", age = 31 };

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

Struct User { public string name = "Sam"; // ! Ошибка public int age = 23; // ! Ошибка public void DisplayInfo() { Console.WriteLine($"Name: {name} Age: {age}"); } }

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

Обычно все члены структуры связаны друг с другом. Например, информация об имени и адресе, находящаяся в списке рассылки, обычно представляется в виде структуры. Следующий фрагмент кода объявляет шаблон структуры, определяющий имя и адрес. Ключевое слово struct сообщает компилятору об объявлении структуры.

Struct addr {
char name;
char street ; char city;
char state;
unsigned long int zip;
};

Объявление завершается точкой с запятой, поскольку объявление структуры - это оператор. Имя структуры addr идентифицирует структуру данных и является спецификатором типа. Имя структуры часто используют как ярлык.

На данный момент на самом деле не создано никакой переменной. Определена только форма данных. Для объявления настоящей переменной, соответствующей данной структуре, следует написать:

Struct addr addr_info;

В данной строке происходит объявление переменной addr_info типа addr. При объявлении структуры определяется переменная смешанного типа. До тех пор, пока не будет объявлена переменная данного типа, она не будет существовать.

Когда объявлена структурная переменная, компилятор автоматически выделяет необходимый участок памяти для размещения всех ее членов. Рис. показывает размещение addr_info в памяти.

Рисунок: Размещение структуры addr_info в памяти

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

Например:

Struct addr {
char name;
char street;
char city;
char state;
unsigned long int zip;
} addr_info; binfo, cinfo;

объявляет структуру addr и объявляет переменные addr_info, binfo, cinfo данного типа.

Важно понять, что каждая вновь создаваемая структурная переменная содержит свои собственный копии переменных, образующих структуру. Например, поле zip переменной binfo отделено от поля zip переменной cinfo. Фактически, единственная связь между binfo и cinfo заключается в том, что они обе являются экземплярами одного типа структуры. Больше между ними нет связи.

Если необходима только одна структурная переменная, то нет необходимости в ярлыке структуры. Это означает, что

Struct {
char name;
char street;
char city;
char state;
unsigned long int zip;
} addr_info;

Объявляет одну переменную addr_info с типом, определенным предшествующей ей структурой. Стандартный вид объявления структуры следующий:

struct ярлык {
тип имя переменной;
тип имя переменной;
тип имя переменной;
} структурные переменные;

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