Поиск…


Вступление

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

Простые структуры данных

Структурные типы данных полезны для упаковки связанных данных и ведут себя как одна переменная.

Объявление простой struct , содержащей два члена int :

struct point 
{
    int x;
    int y; 
};

x и y называются членами (или полями ) point структуры.

Определение и использование структур:

struct point p;    // declare p as a point struct
p.x = 5;           // assign p member variables
p.y = 3;

Структуры могут быть инициализированы при определении. Вышеупомянутое эквивалентно:

struct point p = {5, 3};

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

Доступ к полям также выполняется с помощью . оператор

printf("point is (x = %d, y = %d)", p.x, p.y);

Typedef Structs

Объединение typedef со struct может сделать код более понятным. Например:

typedef struct 
{
    int x, y;
} Point;

в отличие от:

struct Point 
{
    int x, y;
};

может быть объявлено как:

Point point;

вместо:

struct Point point;

Еще лучше использовать следующие

typedef struct Point Point;

struct Point 
{
    int x, y;
};

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

typedef имена для структур могут быть в конфликте с другими идентификаторами других частей программы. Некоторые считают это недостатком, но для большинства людей, имеющих struct и другой идентификатор, это очень тревожно. Печально известный, например, POSIX ' stat

int stat(const char *pathname, struct stat *buf);

где вы видите функцию stat которой есть один аргумент, который является struct stat .

typedef 'd structs без имени тега всегда налагают, что все объявление struct видимо для кода, который его использует. Затем все объявление struct должно быть помещено в файл заголовка.

Рассматривать:

#include "bar.h"

struct foo 
{
    bar *aBar;
};

Таким образом, с помощью typedef d struct которая не имеет имени тега, файл bar.h всегда должен включать в себя все определение bar . Если мы используем

typedef struct bar bar;

в bar.h , детали структуры bar могут быть скрыты.

Посмотреть Typedef

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

Когда у вас есть переменная, содержащая struct , вы можете получить доступ к своим полям, используя оператор точки ( . ). Однако, если у вас есть указатель на struct , это не сработает. Вы должны использовать оператор стрелки ( -> ) для доступа к своим полям. Вот пример ужасно простой (некоторые могут сказать «ужасной и простой») реализации стека, который использует указатели на struct s и демонстрирует оператор стрелки.

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

/* structs */
struct stack
{
    struct node *top;
    int size;
};

struct node
{
    int data;
    struct node *next;
};

/* function declarations */
int push(int, struct stack*);
int pop(struct stack*);
void destroy(struct stack*);

int main(void)
{
    int result = EXIT_SUCCESS;

    size_t i;

    /* allocate memory for a struct stack and record its pointer */
    struct stack *stack = malloc(sizeof *stack);
    if (NULL == stack)
    {
        perror("malloc() failed");
        return EXIT_FAILURE;
    }

    /* initialize stack */
    stack->top = NULL;
    stack->size = 0;

    /* push 10 ints */
    {
        int data = 0;
        for(i = 0; i < 10; i++)
        {
            printf("Pushing: %d\n", data);
            if (-1 == push(data, stack))
            {
                perror("push() failed");
                result = EXIT_FAILURE;
                break;
            }

            ++data;
        }
    }

    if (EXIT_SUCCESS == result)
    {
        /* pop 5 ints */
        for(i = 0; i < 5; i++)
        {
            printf("Popped: %i\n", pop(stack));
        }
    }

    /* destroy stack */
    destroy(stack);

    return result;
}

/* Push a value onto the stack. */
/* Returns 0 on success and -1 on failure. */
int push(int data, struct stack *stack)
{
    int result = 0;

    /* allocate memory for new node */
    struct node *new_node = malloc(sizeof *new_node);
    if (NULL == new_node)
    {
        result = -1;
    }
    else
    {
        new_node->data = data;
        new_node->next = stack->top;
        stack->top = new_node;
        stack->size++;
    }

    return result;
}

/* Pop a value off of the stack. */
/* Returns the value popped off the stack */
int pop(struct stack *stack)
{
    struct node *top = stack->top;
    int data = top->data;
    stack->top = top->next;
    stack->size--;
    free(top);
    return data;
}

/* destroy the stack */
void destroy(struct stack *stack)
{
    /* free all pointers */
    while(stack->top != NULL)
    {
        pop(stack);
    }
}

Гибкие члены массива

C99

Объявление типа

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

struct ex1 
{
    size_t foo;
    int flex[];
};

struct ex2_header 
{
    int foo;
    char bar;
};

struct ex2 
{
    struct ex2_header hdr;
    int flex[];
};

/* Merged ex2_header and ex2 structures. */
struct ex3 
{
    int foo;
    char bar;
    int flex[];
};

Эффекты на размер и заполнение

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

/* Prints "8,8" on my machine, so there is no padding. */
printf("%zu,%zu\n", sizeof(size_t), sizeof(struct ex1));

/* Also prints "8,8" on my machine, so there is no padding in the ex2 structure itself. */
printf("%zu,%zu\n", sizeof(struct ex2_header), sizeof(struct ex2));

/* Prints "5,8" on my machine, so there are 3 bytes of padding. */
printf("%zu,%zu\n", sizeof(int) + sizeof(char), sizeof(struct ex3));

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

использование

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

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

/* invalid: cannot initialize flexible array member */
struct ex1 e1 = {1, {2, 3}};
/* invalid: hdr={foo=1, bar=2} OK, but cannot initialize flexible array member */
struct ex2 e2 = {{1, 2}, {3}};
/* valid: initialize foo=1, bar=2 members */
struct ex3 e3 = {1, 2};

e1.flex[0] = 3; /* undefined behavior, in my case */
e3.flex[0] = 2; /* undefined behavior again */
e2.flex[0] = e3.flex[0]; /* undefined behavior */

Вместо этого вы можете использовать malloc , calloc или realloc для распределения структуры с дополнительным хранилищем, а затем бесплатно, что позволяет вам использовать гибкий член массива по своему усмотрению:

/* valid: allocate an object of structure type `ex1` along with an array of 2 ints */
struct ex1 *pe1 = malloc(sizeof(*pe1) + 2 * sizeof(pe1->flex[0]));

/* valid: allocate an object of structure type ex2 along with an array of 4 ints */
struct ex2 *pe2 = malloc(sizeof(struct ex2) + sizeof(int[4]));

/* valid: allocate 5 structure type ex3 objects along with an array of 3 ints per object */
struct ex3 *pe3 = malloc(5 * (sizeof(*pe3) + sizeof(int[3])));

pe1->flex[0] = 3; /* valid */
pe3[0]->flex[0] = pe1->flex[0]; /* valid */
C99

«Структурный взлом»

Элементы гибкого массива не существовали до C99 и рассматриваются как ошибки. Общим обходным путем является объявление массива длиной 1, метод, называемый «struct hack»:

struct ex1 
{
    size_t foo;
    int flex[1];
};

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

/* Prints "8,4,16" on my machine, signifying that there are 4 bytes of padding. */
printf("%d,%d,%d\n", (int)sizeof(size_t), (int)sizeof(int[1]), (int)sizeof(struct ex1));

Чтобы использовать элемент flex как элемент гибкого массива, вы должны выделить его с помощью malloc как показано выше, за исключением того, что sizeof(*pe1) (или эквивалентный sizeof(struct ex1) ) будет заменен на offsetof(struct ex1, flex) или более длинный, тип-агностический sizeof(*pe1)-sizeof(pe1->flex) выражения sizeof(*pe1)-sizeof(pe1->flex) . В качестве альтернативы вы можете вычесть 1 из требуемой длины «гибкого» массива, поскольку она уже включена в размер структуры, если желаемая длина больше 0. Такая же логика может быть применена к другим примерам использования.

Совместимость

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

#if __STDC_VERSION__ < 199901L
#define FLEXMEMB_SIZE 1
#else
#define FLEXMEMB_SIZE /* nothing */
#endif

struct ex1 
{
    size_t foo;
    int flex[FLEXMEMB_SIZE];
};

При распределении объектов вы должны использовать форму offsetof(struct ex1, flex) чтобы ссылаться на размер структуры (за исключением элемента гибкого массива), поскольку это единственное выражение, которое останется согласованным между компиляторами, которые поддерживают гибкие члены массива и компиляторы, которые делают не:

struct ex1 *pe10 = malloc(offsetof(struct ex1, flex) + n * sizeof(pe10->flex[0]));

Альтернативой является использование препроцессора для условного вычитания 1 из указанной длины. Из-за возросшего потенциала несогласованности и общей человеческой ошибки в этой форме я переместил логику в отдельную функцию:

struct ex1 *ex1_alloc(size_t n)
{
    struct ex1 tmp;
#if __STDC_VERSION__ < 199901L
    if (n != 0)
        n--;
#endif
    return malloc(sizeof(tmp) + n * sizeof(tmp.flex[0]));
}
...

/* allocate an ex1 object with "flex" array of length 3 */
struct ex1 *pe1 = ex1_alloc(3);

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

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

struct coordinates
{
    int x;
    int y;
    int z;
};

// Passing and returning a small struct by value, very fast
struct coordinates move(struct coordinates position, struct coordinates movement)
{
    position.x += movement.x;
    position.y += movement.y;
    position.z += movement.z;
    return position;
}

// A very big struct
struct lotsOfData
{
    int param1;
    char param2[80000];
};

// Passing and returning a large struct by value, very slow!
// Given the large size of the struct this could even cause stack overflow
struct lotsOfData doubleParam1(struct lotsOfData value)
{
    value.param1 *= 2;
    return value;
}

// Passing the large struct by pointer instead, fairly fast
void doubleParam1ByPtr(struct lotsOfData *value)
{
    value->param1 *= 2;
}

Объектно-ориентированное программирование с использованием структур

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

/* coordinates.h */

typedef struct coordinate_s 
{
    /* Pointers to method functions */
    void (*setx)(coordinate *this, int x);
    void (*sety)(coordinate *this, int y);
    void (*print)(coordinate *this);
    /* Data */
    int x;
    int y;
} coordinate;

/* Constructor */
coordinate *coordinate_create(void);
/* Destructor */
void coordinate_destroy(coordinate *this);

И теперь реализующий файл C:

/* coordinates.c */

#include "coordinates.h"
#include <stdio.h>
#include <stdlib.h>

/* Constructor */
coordinate *coordinate_create(void)
{
    coordinate *c = malloc(sizeof(*c));
    if (c != 0)
    {
        c->setx = &coordinate_setx;
        c->sety = &coordinate_sety;
        c->print = &coordinate_print;
        c->x = 0;
        c->y = 0;
    }
    return c;
}

/* Destructor */
void coordinate_destroy(coordinate *this)
{
    if (this != NULL)
    {
        free(this);  
    }  
}

/* Methods */
static void coordinate_setx(coordinate *this, int x)
{
    if (this != NULL)
    {    
        this->x = x;
    }
}

static void coordinate_sety(coordinate *this, int y)
{
    if (this != NULL)
    {
        this->y = y;
    }
}

static void coordinate_print(coordinate *this)
{
    if (this != NULL)
    {
        printf("Coordinate: (%i, %i)\n", this->x, this->y);
    }
    else
    {
        printf("NULL pointer exception!\n");
    }
}

Пример использования нашего координатного класса:

/* main.c */

#include "coordinates.h"
#include <stddef.h>

int main(void) 
{
    /* Create and initialize pointers to coordinate objects */
    coordinate *c1 = coordinate_create();
    coordinate *c2 = coordinate_create();
    
    /* Now we can use our objects using our methods and passing the object as parameter */
    c1->setx(c1, 1);
    c1->sety(c1, 2);

    c2->setx(c2, 3);
    c2->sety(c2, 4);

    c1->print(c1);
    c2->print(c2);

    /* After using our objects we destroy them using our "destructor" function */
    coordinate_destroy(c1);
    c1 = NULL;
    coordinate_destroy(c2);
    c2 = NULL;

    return 0;
}


Modified text is an extract of the original Stack Overflow Documentation
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow