Struct (c++)

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

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

public struct Point3D
{
    public double X = 10;
    public double Y = 5;
    public double Z = 8;
    public override string ToString()
    {
        return $"({X},{Y},{Z})";
    }

    public Point3D(double x) : this()
    {
        X = x;
        Y = 0;
        Z = 0;
    }

    public Point3D(double x, double y) : this(x)
    {
        Y = y;
        Z = 0;
    }

    public Point3D(double x, double y, double z) : this(x, y)
    {
        Z = z;
    }
}

Создаем структуры ()

Point3D point1 = new(10);
Point3D point2 = new(10, 20);
Point3D point3 = new(10, 30, 40);
Console.WriteLine(point1);
Console.WriteLine(point2);
Console.WriteLine(point3);

Вывод консоли

(10,0,0)
(10,20,0)
(10,30,40)

Начиная с версии C# 10 мы можем также создать для структуры свой конструктор без параметров:

//конструктор без параметров
public Point3D()
{
    X = 10;
    Y = 20;
    Z = 30;
}

Структура программы

В C# основными понятиями организационной структуры являются *программы _, пространства имен, типы, элементы и сборки. В программе объявляются типы, которые содержат члены. Эти типы можно организовать в пространства имен. Примерами типов являются классы, структуры и интерфейсы. К членам относятся поля, методы, свойства и события. При компиляции программы на C# упаковываются в сборки. Сборка — это файл, обычно с расширением или , если она реализует приложение или _*библиотеку**, соответственно.

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

Полное имя этого класса: . Этот класс содержит несколько членов: поле с именем , два метода с именами и , а также вложенный класс с именем . Класс , в свою очередь, содержит три члена: свойство с именем , свойство с именем и конструктор.  — это универсальный класс. Он имеет параметр одного типа , который замещается конкретным типом при использовании.

Стек — это коллекция типа FILO (прибыл первым — обслужен последним). Новые элементы добавляются в верх стека. Удаляемый элемент исключается из верхней части стека. В предыдущем примере объявляется тип , который определяет хранилище и поведение для стека. Можно объявить переменную, которая ссылается на экземпляр типа для использования этой возможности.

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

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

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

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

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

Характеристики целочисленных типов

C# поддерживает следующие предварительно определенные целочисленные типы:

Ключевое слово или тип C# Диапазон Размер Тип .NET
От -128 до 127 8-разрядное целое число со знаком System.SByte
От 0 до 255 8-разрядное целое число без знака System.Byte
От -32 768 до 32 767 16-разрядное целое число со знаком System.Int16
От 0 до 65 535 16-разрядное целое число без знака System.UInt16
От -2 147 483 648 до 2 147 483 647 32-разрядное целое число со знаком System.Int32
От 0 до 4 294 967 295 32-разрядное целое число без знака System.UInt32
От -9 223 372 036 854 775 808 до 9 223 372 036 854 775 807 64-разрядное целое число со знаком System.Int64
От 0 до 18 446 744 073 709 551 615 64-разрядное целое число без знака System.UInt64
Зависит от платформы 32- или 64-разрядное целое число со знаком System.IntPtr
Зависит от платформы 32- или 64-разрядное целое число без знака System.UIntPtr

Во всех строках таблицы, кроме двух последних, каждое ключевое слово типа C# из крайнего левого столбца является псевдонимом для соответствующего типа .NET. Ключевое слово и имя типа .NET являются взаимозаменяемыми. Например, следующие объявления объявляют переменные одного типа:

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

По умолчанию все целочисленные типы имеют значение . Все целочисленные типы, кроме целых чисел собственного размера, имеют константы и с минимальным и максимальным значением этого типа.

Используйте структуру System.Numerics.BigInteger, чтобы представить целое число со знаком без верхней и нижней границ.

Оператор равенства ==

Оператор равенства возвращает значение , если его операнды равны. В противном случае возвращается значение .

Равенство типов значений

Операнды равны, если равны их значения.

Примечание

У операторов , , , и , если какой-то из операндов не является числом (Double.NaN или Single.NaN), результатом операции является . Это означает, что значение не больше, не меньше и не равно любому другому значению (или ), включая . Дополнительные сведения и примеры см. в справочных статьях по Double.NaN или Single.NaN.

Два операнда одного типа enum равны, если равны соответствующие значения базового целочисленного типа.

По умолчанию пользовательские типы struct не поддерживают оператор . Чтобы поддерживать оператор , пользовательская структура должна перегружать его.

Начиная с версии C# 7.3 операторы и поддерживаются кортежами C#. Дополнительные сведения см. в разделе статьи Типы кортежей.

Равенство ссылочных типов

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

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

Равенство типов записей

Типы записей, доступные в C# 9.0 и более поздних версий, поддерживают операторы и , которые по умолчанию обеспечивают семантику равенства значений. То есть два операнда записи равны, когда оба они равны или равны соответствующие значения всех полей и автоматически реализуемых свойств.

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

Равенство строк

Два операнда равны, если они оба имеют значение или оба экземпляра строки имеют одинаковую длину и идентичные символы в каждой позиции символа.

Это порядковое сравнение, учитывающее регистр. Дополнительные сведения о том, как сравнивать строки, см. в статье Сравнение строк в C#.

Равенство делегатов

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

Подробные сведения см. в разделе (Операторы равенства делегатов) в спецификации языка C#.

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

Наследование

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

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

Начальная инициализация структур

Структуру можно инициализировать во время создания как массив. Поля в этом случае будут присваиваться по порядку.

#include <conio.h>
#include <stdio.h>
#include <math.h>

struct gasket {
	float weight;
	unsigned height;
	unsigned diameter;
};

void main() {
	struct gasket obj = { 12.f, 120, 30 };

	printf("gasket info:\n");
	printf("-------------------\n");
	printf("weight: %4.3f kg\n", obj.weight);
	printf("height: %6d cm\n", obj.height);
	printf("diameter: %4d cm\n", obj.diameter);

	getch();
}

Замечание: таким образом можно только иницализировать структуру. Присваивать значение всей структуре таким образом нельзя.

Современный стандарт си позволяет инициализировать поля структуры по имени. Для этого используется следующий синтакис:

#include<stdio.h>

typedef struct thing {
    int a;
    float b;
    const char *c;
} thing_t;

int main() {
    thing_t t = {
        .a = 10,
        .b = 1.0,
        .c = "ololololo"
    };
    printf("%s\n", t.c);
    printf("%d\n", t.a);
    printf("%f\n", t.b);
    _getch();
}

Размер структуры и выравнивание структуры данных

Обычно размер структуры – это сумма размеров всех ее членов, но не всегда!

Рассмотрим структуру , но с целочисленными типами фиксированного размера и размером , равным половине размера . На многих платформах размер составляет 8 байтов, поэтому мы ожидаем, что будет 2 + 4 + 8 = 14 байтов. Чтобы узнать точный размер , мы можем использовать оператор :

На машине автора эта программа печатает:

Оказывается, мы можем сказать только то, что размер структуры будет не меньше размера всех содержащихся в ней переменных. Но может быть и больше! По соображениям производительности компилятор иногда добавляет разрывы в структуры (это называется заполнением).

В приведенной выше структуре компилятор невидимо добавляет 2 байта заполнения после члена , делая размер структуры равным 16 байтов вместо 14. Причина, по которой он это делает, выходит за рамки этого руководства, но читатели, которые хотят узнать подробнее о выравнивании структур, данных могут прочитать об этом в Википедии. Это необязательный материал и не требуется для понимания структур или C++!

Специальные возможности

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

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

  • public
  • protected
  • internal
  • protected internal
  • private
  • private protected.

По умолчанию используется режим доступа .

Указатели на поля структуры и на вложенные структуры

Указатели на поля структуры определяются также, как и обычные указатели. Указатели на вложенные структуры возможны только тогда,
когда структура определена. Немного переделаем предыдущий пример: «деанонимизируем» вложенную безымянную структуру и
возьмём указатели на поля структуры Model:

#include <conio.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define YEAR_OFFSET 1890

//Отдельно выделили структуру "Марка"
typedef struct Make {
	int id;
    char *name;
} Make;

//Теперь полем структуры "Модель" является структура "Марка"
typedef struct Model {
    int id;
	Make make;
    char *name;
    unsigned char year; //year is an offset to 1890
} Model;

char* mallocByString(const char *str) {
    char* p = (char*) malloc(strlen(str) + 1);
    strcpy(p, str);
    return p;
}
 
void freeModel(Model* model) {
    free(model->make.name);
    free(model->name);
}

void main() {
	Make *make = NULL;
	Model cl;
	int *id;

    cl.id = 2;
    cl.make.id = 1;
    cl.make.name = mallocByString("Acura");
    cl.name = mallocByString("CL");
    cl.year = (2003 - YEAR_OFFSET);

	//Получаем указатель на вложенную структуру
	make = &cl.make;
	//Получаем указатель на поле структуры
	id = &cl.id;
	printf("make.name = %s\n", make->name);
	printf("make.id = %d\n", make->id);
	printf("model.id = %d\n", *id);

	freeModel(&cl);
	scanf("1");
}

Как уже говорилось ранее, в си, даже если у двух структур совпадают поля, но структуры имеют разные имена, то их нельзя приводить к одному типу. Поэтому
приходится избавляться от анонимных вложенных структур, если на них нужно взять указатель. Можно попытаться взять указатель типа char*
на поле структуры, но нет гарантии, что поля будут расположены непрерывно.

Отличие структуры от класса в C#

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

Структура — тип значений, класс — ссылочный тип

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

Структуры не поддерживают наследование

В отличие от классов C#, наследование структур не поддерживается, то есть вот такой код приведет к ошибке:

struct Point3DType2 : Point3D
{ }

Когда использовать структуры (struct), а когда классы (class) в C#

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

Например, использование структур вполне оправдано в примерах выше — описание точки в трехмерном пространстве. Максимум логики, которую мы можем добавить в структуру — это переопределить операторы сложения, вычитания и равенства.

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

Объединения

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

union ИмяОбъединения{  тип ИмяОбъекта1;  тип ИмяОбъекта2;  . . .  тип ИмяОбъектаn;};


Объединения применяются для следующих целей:

  • для инициализации объекта, если в каждый момент времени только один из многих объектов является активным;
  • для интерпретации представления одного типа данных в виде другого типа.

Например, удобно использовать объединения, когда необходимо вещественное число типа float представить в виде совокупности байтов

123456789101112131415161718

#define _CRT_SECURE_NO_WARNINGS#include <stdio.h>#include <stdlib.h>union types{  float f;  unsigned char b;};int main(){  types value;  printf(«N = «);  scanf(«%f», &value.f);  printf(«%f = %x %x %x %x», value.f, value.b, value.b, value.b, value.b);  getchar();  getchar();  return 0;}

Пример

123456789101112131415161718192021222324

#define _CRT_SECURE_NO_WARNINGS#include <stdio.h>#include <stdlib.h>int main() {  char temp;  system(«chcp 1251»);  system(«cls»);  union  {    unsigned char p;    unsigned int t;  } type;  printf(«Введите число : «);  scanf(«%d», &type.t);  printf(«%d = %x шестн.\n», type.t, type.t);  // Замена байтов  temp = type.p;  type.p = type.p;  type.p = temp;  printf(«Поменяли местами байты, получили\n»);  printf(«%d = %x шестн.\n», type.t, type.t);  getchar(); getchar();  return 0;}

Результат выполнения

Объявление структуры

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

struct  {
	 ;
	 ;
	...
	 ;
};

Например

struct point_t {
	int x;
	int y;
}; //Тут стоит точка с запятой!

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

struct node {
	void* value;
	struct node next;
};

Нельзя, нужно

struct node {
	void* value;
	struct node *next;
};

В том случае, если несколько полей имеют один тип, то их можно перечислить через запятую:

struct Point3D {
	int x, y, z;
};

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

#include <conio.h>
#include <stdio.h>
#include <math.h>

struct point_t {
	int x;
	int y;
};

void main() {
	struct point_t A;
	float distance;

	A.x = 10;
	A.y = 20;

	distance = sqrt((float) (A.x*A.x + A.y*A.y));

	printf("x = %.3f", distance);
	getch();
}

Структура, объявленная в глобальном контексте, видна всем. Структура также может быть объявлена внутри функции:

#include <conio.h>
#include <stdio.h>
#include <math.h>

void main() {
	struct point_t {
		int x;
		int y;
	};
	struct point_t A;
	float distance;

	A.x = 10;
	A.y = 20;

	distance = sqrt((float) (A.x*A.x + A.y*A.y));

	printf("x = %.3f", distance);
	getch();
}

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

struct point_t {
		int x;
		int y;
	} A;
	float distance;

Структура также может быть анонимной. Тогда мы не сможем использовать имя структуры в дальнейшем.

#include <conio.h>
#include <stdio.h>
#include <math.h>

void main() {
	struct {
		int x;
		int y;
	} A;
	float distance;

	A.x = 10;
	A.y = 20;

	distance = sqrt((float) (A.x*A.x + A.y*A.y));

	printf("x = %.3f", distance);
	getch();
}

В этом примере мы создали переменную A. Она является структурой с двумя полями.

Объявление и определение структур

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

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

Предупреждение

Одна из самых простых ошибок C++ – это забыть о точке с запятой в конце объявления структуры. Это вызовет ошибку компилятора в следующей строке кода. Современные компиляторы, такие как Visual Studio 2010, укажут вам, что вы, возможно, забыли точку с запятой, но более старые или менее сложные компиляторы могут этого не делать, что может затруднить обнаружение реальной ошибки.

Чтобы использовать структуру , мы просто объявляем переменную типа :

Это определяет переменную типа с именем . Как и в случае с обычными переменными, определение переменной типа структуры выделяет память для этой переменной.

Можно определить несколько переменных одного и того же типа структуры:

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

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

Как и обычные переменные, переменные-члены структуры не инициализируются и обычно содержат мусор. Мы должны инициализировать их вручную.

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

Переменные-члены структуры действуют как обычные переменные, поэтому с ними можно выполнять обычные операции:

Структуры

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

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

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

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

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

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

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

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

#include <iostream>
#include <string>

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;
}

Здесь определена структура , которая имеет два элемента: (представляет тип int) и
(представляет тип 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 <iostream>
#include <string>

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).

НазадВперед

Синтаксис

спецификатор-структуры-или-объединения:
    struct-or-unionidentifieropt{struct-declaration-list}
    struct-or-unionidentifier

структура-или-объединение:
    
    

список-объявлений-структуры:
    struct-declaration
    struct-declaration-liststruct-declaration

объявление-структуры:
    specifier-qualifier-liststruct-declarator-list;

список-спецификаторов-и-квалификаторов:
    type-specifierspecifier-qualifier-listopt
    type-qualifierspecifier-qualifier-listopt

список-деклараторов-структуры:
    struct-declaratorstruct-declarator-list,struct-declarator

декларатор-структуры:
    declarator
    type-specifierdeclaratoroptconstant-expression

Объявление типа структуры не оставляет места для структуры. Это всего лишь шаблон для последующих объявлений структурных переменных.

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

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

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

Невозможно объявить элемент так, чтобы он имел тип структуры, в которой отображается. Однако элемент можно объявить как указатель на структурный тип, в котором он отображается, при условии, что этот структурный тип имеет тег. Это позволяет создавать связанные списки структур.

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

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

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

оба объявления являются допустимыми:

Указатели на структуру

Указатель на структуру создаётся как обычно. Отличие заключается в том, что можно обращаться к полям структуры через указатель с помощью операции «стрелка» (минус + больше).
Пример – пользователь вводит число – размер массива пользователей. Поле этого вводит для каждого из них логин и пароль. Третье поле — идентификатор – задаётся
автоматически. После этого все пользователи выводятся на экран.

#include <conio.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAX_SIZE 20

typedef struct User {
	char *login;
	char *password;
	int id;
} User;

void jsonUser(User *user) {
	printf("{id: %d, login: \"%s\", password: \"%s\"}\n", 
			user->id, user->login, user->password);
}

void freeUsersArray(User** users, unsigned size) {
	unsigned i;
	for (i = 0; i < size; i++) {
		free((*users).login);
		free((*users).password);
	}
	free(*users);
}

void main() {
	User *users = NULL;
	unsigned size;
	char buffer;
	unsigned i;

	printf("Enter number of users: ");
	scanf("%d", &size);

	size = size <= MAX_SIZE? size: MAX_SIZE;
	users = (User*) malloc(size * sizeof(User));

	for (i = 0; i < size; i++) {
		printf("user #%d\nname: ", i);
		scanf("%127s", buffer);
		users.id = i;
		users.login = (char*) malloc(strlen(buffer) + 1);
		strcpy(users.login, buffer);
		printf("password: ");
		scanf("%127s", buffer);
		users.password = (char*) malloc(strlen(buffer) + 1);
		strcpy(users.password, buffer);
	}

	for (i = 0; i < size; i++) {
		jsonUser(&users);
	}

	freeUsersArray(&users, size);
	getch();
}

Обратите внимание на удаление массива структур: при удалении экземпляра структуры он не удаляет своих полей самостоятельно, поэтому необходимо сначала
удалять поля, после этого удалять сам массив.
При вызове функции jsonUser мы передаём указатель на экземпляр структуры, поэтому внутри функции доступ до полей осуществляется с помощью оператора стрелка.

Рейтинг
( Пока оценок нет )
Понравилась статья? Поделиться с друзьями:
Мой редактор ОС
Добавить комментарий

;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!: