Структура в языке программирования Си представляет собой составной тип данных, который состоит из других компонентов. При этом в отличие от массива эти компоненты могут представлять различные типы данных.
Перед тем, как использовать структуру, ее надо объявить. Для определения структуры применяется ключевое слово 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:
#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:
#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