Структуры

Определение структур

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

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

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

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

struct имя_структуры;

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

struct person;

int main(void) {

    return 0;
}

В данном случае мы объявили структуру person. Однако это объявление без определения. С такой структурой сложно что-то сделать, и в данном случае она представляет неполный тип (incomplete type), поскольку мы не знаем, какой размер занимает эта структура.

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

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

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

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

struct person
{
	char* name;
	int age;
};

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

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

Можно объявить структуру с одним и тем же именем несколько раз, но нельзя определить структуру более одного раза.

#include <stdio.h>

struct person;
struct person {
    char * name;
    int age;
};
struct person;
struct person;

int main(void){

    return 0;
}

В примере выше все объявления структуры person будут относиться к одной и той же структуре.

Использование структуры

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

// определение структуры person
struct person
{
    char * name;
    int age;
};

int main(void)
{
    // определение переменной, которая представляет структуру person
    struct person tom;
}

Здесь определена переменная tom, которая представляет структуру person. И при каждом определении переменной типа структуры ей будет выделяться память, необходимая для хранения ее элементов.

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

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

  • По позиции: значения передаются элементам структуры в том порядке, в котором они следуют в структуре:

    struct person tom = {"Tom", 23};

    Так как в структуре person первым определено поле name, которое представляет указатель на тип char или строку, соответственно вначале идет строка. Вторым идет поле age, которое представляет тип int - число, то в фигурных скобках после строки идет число, которое передается элементу age. И так далее для всех элементов структуры по порядку.

  • По имени: значения передаются элементам структуры по имени, независимо от порядка:

    struct person tom = {.name="Tom", .age=23};

    В этом случае перед именем элемента указывается точка, например, .name.

Обращение к элементам структуры

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

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

Теперь объединим все вместе в рамках программы:

#include <stdio.h>

struct person
{
	char * name;
	int age;
};

int main(void)
{
	struct person tom = {"Tom", 23};
	printf("Age: %d \t Name: %s", tom.age, tom.name);
	return 0;
}

Консольный вывод программы:

Age: 23 	 Name: Tom

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

#include <stdio.h>
 
struct person
{
    char * name;
    int age;
};
 
int main(void)
{
    struct person tom;
    tom.name ="Tom";
    tom.age = 22;
    printf("Name:%s \t Age: %d\n", tom.name, tom.age);
    return 0;
}

Объявление, определение и использование

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

#include <stdio.h>

struct person; // структура объявлена, но НЕ определена, она пока представляет неполный тип

// структура определена, мы ее можем полноценно использовать
struct person {
    char * name;
    int age;
};

int main(void){

    struct person tom = {"Tom", 22};
    printf("Name: %s  Age: %d\n", tom.name, tom.age);

    return 0;
}

Здесь сначала мы объявляем структуру без определения:

struct person;

И лишь затем определяем:

struct person {
    char * name;
    int age;
};

После этого в функции main мы сможем создавать ее переменные, обращаться к ее полям

struct person tom = {"Tom", 22};
printf("Name: %s  Age: %d\n", tom.name, tom.age);

Но рассмотрим другую ситуацию:

#include <stdio.h>

struct person; // структура объявлена, но НЕ определена, она пока представляет неполный тип

int main(void){

    // в этой точке структура person объявлена, но НЕ определена. Мы не можем определять ее переменные, обращаться к полям
    // error: variable ‘tom’ has initializer but incomplete type
    // struct person tom = {"Tom", 22};
    // printf("Name: %s  Age: %d\n", tom.name, tom.age);

    return 0;
}
// структура определена, мы ее можем полноценно использовать
struct person {
    char* name;
    int age;
};

Здесь структура person определена уже после функции main, и до своего определения она представляет неполный тип - компилятор не знает его размера, никакие поля в нем, соответственно полноценно использовать мы ее не можем. Единственным сценарием использования неполного типа (в том числе и неопределенной структуры), является определение указателя на этот тип:

#include <stdio.h>

struct person; // структура объявлена, но НЕ определена, она пока представляет неполный тип

int main(void){

    struct person* tom;  // указатель на структуру person

    return 0;
}
// структура определена, мы ее можем полноценно использовать
struct person {
    char* name;
    int age;
};

Здесь переменная tom представляет именно указатель на структуру person. Подробнее мы рассмотрим указатели на структуры далее, но в любом случае это единственная возможность использования неполного типа, поскольку указатель - полный тип - его размер компилятору известен и на 64-разрядных системах составляет 8 байт.

Объединение определения структуры и ее переменных.

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

#include <stdio.h>

struct person
{
	char * name;
	int age;
} tom;		// определение структуры и ее переменной

int main(void)
{
	tom.age = 38;
    tom.name = "Tom";
	printf("Name:%s \t Age: %d\n", tom.name, tom.age);
	return 0;
}

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

Можно тут же инициализировать структуру:

#include <stdio.h>
 
struct person
{
    char * name;
    int age;
} tom = {38, "Tom"};
 
int main(void)
{
    printf("Name:%s \t Age: %d\n", tom.name, tom.age);
    return 0;
}

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

struct person
{
    char * name;
    int age;
} tom, bob, alice;

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

struct
{
	char * name;
	int age;
} tom;

В этом случае компилятор все равно будет знать, что переменная tom представляет структуры с двумя элементами name и age. И соответственно мы также с этими переменными сможем работать. Другое дело, что мы не сможем задать новые переменные этой структуры в других местах программы.

typedef

Еще один способ определения структуры представляет ключевое слово typedef:

#include <stdio.h>

typedef struct
{
	char* name;
	int age;
} person;

int main(void)
{
	person tom = {"Tom", 23};
	printf("Name:%s \t Age: %d\n", tom.name, tom.age);
	return 0;
}

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

Директива define

Еще один способ определить структуру представляет применение препроцессорной директивы #define:

#include <stdio.h>

#define PERSON struct {int age;	char name[20];}

int main(void)
{
	PERSON tom = {23, "Tom"};
	printf("Name:%s \t Age: %d\n", tom.name, tom.age);
	return 0;
}

В данном случае директива define определяет константу PERSON, вместо которой при обработке исходного кода препроцессором будет вставляться код структуры struct {int age; char name[20];}

Область действия структуры

Как и функции, и переменные, структуры имеют область видимость и видны только в той области, в которой они объявлены. Если структура объявлена вне функций на уровне файла, то она видена с точки, где она объявлена, до конца файла./p>

#include <stdio.h>
 
struct person
{
    char * name;
    int age;
};

void print_person(){
    struct person bob = {"Bob", 33};
    printf("Name: %s   Age: %d\n", bob.name, bob.age);
}
int main(void)
{
    struct person tom = {"Tom", 22};
    printf("Name: %s  Age: %d\n", tom.name, tom.age);
    print_person();
    return 0;
}

В данном случае структура person объявлена вне какой-либо функции и видна в любом месте файла после объявления. И для демонстрации в примере выше мы можем ее использовать в функции main и функции print_person (и вообще любой другой функции, которая идет после объявления структуры).

Если структура объявлена в блоке кода, то соответственно она видна до конца этого блока.

#include <stdio.h>

// здесь нельзя использовать структуру person, так как она определена в функции main
void print_person(){
    //struct person bob = {"Bob", 33};
    // printf("Name: %s   Age: %d\n", bob.name, bob.age);
}
// структура person видна только в функции main с момента своего определения
int main(void)
{
    struct person {
        char * name;
        int age;
    };
    struct person tom = {"Tom", 22};
    printf("Name: %s  Age: %d\n", tom.name, tom.age);
    return 0;
}

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

#include <stdio.h>

int main(void){

    // вне этого блока кода структура person не видна
    {
        struct person {
            char * name;
            int age;
        };
        struct person tom = {"Tom", 22};
        printf("Name: %s  Age: %d\n", tom.name, tom.age);
    }
    // здесь структура person уже не видна
    //struct person bob = {"Bob", 33};
    //printf("Name: %s   Age: %d\n", bob.name, bob.age);

    return 0;
}

Здесь структура person объявлена внутри блока кода, соответственно, вне этого блока мы ее использовать не сможем.

Если одна и та же структура объявлена в двух разных областях, то одна структура скрывает ранее объявленную:

#include <stdio.h>

// структура уровня файла
struct number {
    int x;
};

int main(void){

    // структура уровня функции - она скрывает структуру number уровня файла
    struct number {
        long y;
    };
    struct number n1 = {22};
    printf("n1: %ld\n", n1.y);

    return 0;
}

Здесь структура struct number { long y;} скрывает структуру struct number { int x;}. Поэтому внутри функции main обращение к структуре number будет представлять именно структуру struct number { long y;}.

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

#include <stdio.h>

// структура уровня файла
struct number {
    int x;
};

struct number external = {5};

int main(void){

    // структура уровня функции - она скрывает структуру number уровня файла
    struct number {
        long y;
    };
    struct number internal = {22};

    printf("external: %d\n", external.x);  // external: 5
    printf("internal: %ld\n", internal.y);    // internal: 22

    return 0;
}

Здесь мы сначала объявляем структуру number уровня файла. Затем мы определяем переменную external этого типа. В функции main мы объявляем другой тип структуры с тем же именем, который скрывает внешнее объявление. Затем мы объявляем переменную internal с этим новым типом. В функции main мы по-прежнему можем получить доступ к полям обеих переменных. Даже внутри функции main компилятор знает о внешней структуре number, и он также знает, что переменная external принадлежит этому типу.

Копирование структур

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

#include <stdio.h>
 
struct person
{
    char * name;
    int age;
};
 
int main(void)
{
    struct person tom = {"Tom", 38};
    // копируем значения из структуры tom в структуру bob
    struct person bob = tom;
    bob.name = "Bob";
    printf("Name: %s \t Age: %d \n", bob.name, bob.age);
    printf("Name: %s \t Age: %d \n", tom.name, tom.age);
    return 0;
}

Здесь в переменную bob копируются данные из структуры tom. Далее мы для структуры bob меняется значение поля name. В итоге мы получим следующий консольный вывод:

Name: Bob         Age: 38
Name: Tom         Age: 38

Ввод с консоли данных для структуры

С элементами структуры можно производить все те же операции, что и с переменными тех же типов. Например, добавим ввод с консоли:

#include <stdio.h>

struct person
{
	int age;
	char name[20];
};
int main(void)
{
	struct person tom = {23, "Tom"};
	printf("Enter name: ");
	scanf("%s", tom.name);
	printf("Enter age: ");
	scanf("%d", &tom.age);
	printf("Name:%s \t Age: %d\n", tom.name, tom.age);
	return 0;
}

Консольный вывод программы:

Enter name: Eugene
Enter age: 33
Name: Eugene 	 Age: 33
Дополнительные материалы
Помощь сайту
Юмани:
410011174743222
Номер карты:
4048415020898850