На структуры, как и на объекты других типов, можно определять указатели. Например, указатель на структуру person:
struct person *p;
Указатели на структуры можно создавать и для безымянных структурных типов:
struct
{
int age;
char name[20];
} *p1, *p2;
В качестве значения такому указателю присваивается адрес объекта структуры того же типа:
struct person kate = {31, "Kate"};
struct person *p_kate = &kate;
Используя указатель на структуру, можно получить доступ к ее элементам. Для этого можно воспользоваться двумя способами. Первый способ представляет применение операции разыменования:
(*указатель_на_структуру).имя_элемента
Второй способ предполагает использование операции -> (операция стрелка):
указатель_на_структуру->имя_элемента
Используем оба этих способа для обращения к элементам структуры:
#include <stdio.h>
struct person
{
int age;
char name[20];
};
int main(void)
{
struct person kate = {31, "Kate"};
// указатель на переменную kate
struct person * p_kate = &kate;
// получаем значение элемента name
// получаем значение элемента age
char * name = p_kate->name;
int age = (*p_kate).age;
printf("name = %s \t age = %d \n", name, age);
// изменим элемент age в структуре
p_kate->age = 32;
printf("name = %s \t age = %d \n", kate.name, kate.age);
return 0;
}
Здесь определяется указатель p_kate на переменную kate. И используя указатель, мы можем получить или изменить значения элементов структуры.
Элементы структуры могут представлять указатель на структуру этого же типа. Подобная ситуация встречается довольно часто в различных типах данных, которые состоят из связанных звеньев, например, в связных списках, где каждый элемент имеет указатель на следующий и/или предыдущий элемент этого же типа структуры. Например:
struct node
{
char* value; // значение
struct node* next; // указатель на следующий узел
};
Структура node имеет два элемента. Элемент value хранит собственно некоторое значение. А элемент next представляет указатель на следующий объект
структуры node. Как мы можем использовать эту структуру и зачем нам указатель на следующий объект node? Рассмотрим следующий пример:
#include <stdio.h>
struct node
{
char* value;
struct node* next;
};
int main(void)
{
struct node kate = {.value = "Kate"};
struct node tom = {.value = "Tom" };
struct node bob = {.value = "Bob"};
kate.next = &tom; // Kate - Tom
tom.next = &bob; // Tom - Bob
// устанавливаем указатель на первую структуру в цепочке
struct node * pointer = &kate;
while(pointer != NULL)
{
printf("value = %s \n", pointer->value);
pointer = pointer->next; // переходим к следующему объекту
}
return 0;
}
Здесь определяем три переменных структуры: kate, tom и bob. У объекта kate элемент next указывает на объект tom:
kate.next = &tom;
А у объекта tom элемент next указывает на объект bob:
tom.next = &bob;
Таким образом, фактически мы получим цепочку Kate - Tom - Bob или грубо говоря список объектов структуры node.
Используя эти указатели, мы можем перемещаться вперед по этому списку. Для этого сначала устанавливаем указатель на первую структуру:
struct node * pointer = &kate;
Далее в цикле while пока указатель pointer не будет указать ни на какой объект структуры (то есть будет равен NULL) выводим значение value
текущего объекта, на который указывает pointer, и затем присваиваем ему значение указателя pointer->next, то есть переходим к следуюшей структуре в списке:
while(pointer != NULL)
{
printf("value = %s \n", pointer->value);
pointer = pointer->next;
}
Таким образом, можно определять связанные наборы объектов, не прибегая к массивам.
Указатели на структуры могут быть полезны в том случае, если определение одной структуры идет после ее использования. Например:
struct rectangle
{
struct color background;
unsigned width;
unsigned height;
};
struct color
{
unsigned char red;
unsigned char green;
unsigned char blue;
unsigned char alpha;
};
Здесь структура rectangle (прямоугольник) определяет поле, которое представляет структуру color (фоновый цвет). Но структура color определена после структуры rectangle, поэтому при компиляции мы получим ошибку:
error: field 'background' has incomplete type
Как говорит ошибка, тип поля background - структура color является неполным. С одной стороны, мы могли бы объявить структуру color до структуры rectangle, но в комплексных программах из множества файлов может быть сложно проследить весь порядок определения и использования типов. С другой стороны, возможно потребуется определить структуры, которые рекурсивно ссылаются друг на друга, наподобие следующего:
struct A
{
struct B b; // Ошибка - incomplete type
}
struct B
{
struct A a;
}
В этом случае мы можем вначале объявить структуру (не указывая определение ее полей), а при использовании применять указатель на структуру:
#include <stdio.h>
struct color; // объявление структуры
struct rectangle
{
struct color* background; // указатель на структур
unsigned width;
unsigned height;
};
struct color // определение структуры
{
unsigned char red;
unsigned char green;
unsigned char blue;
unsigned char alpha;
};
int main(void)
{
// пример использования
struct color backgroundColor = {255, 0, 0, 125};
struct rectangle rect = {.width=100, .height=50, .background = &backgroundColor};
printf("Rectangle color: %d, %d, %d",
rect.background->red,
rect.background->blue,
rect.background->green);
return 0;
}
Аналогично с рекурсивными структурами:
struct B; // объявление
struct A
{
struct B* b; // указатель на структуру
};
struct B // определение
{
struct A* a;
};
Рассмотрим следующий случай:
#include <stdio.h>
// указатель на константную структуру
const struct Number { int value; } * p;
int main(void)
{
struct Number num = {6};
num.value = 7;
printf("num: %d\n", num); // num: 7
//p->value = 9; // нельзя
//printf("pvalue: %d\n", p->value); // Segmentation fault
p = # // переустанавливаем указатель, но изменить по нему значение опять нельзя
printf("pvalue: %d\n", p->value); // pvalue: 7
// p->value = 9; // нельзя
return 0;
}
Здесь при определении структуры Number тут же определен указатель p. Причем это указатель на структуру-константу. То есть мы не можем изменить значение по этому указателю.
Другой пример:
#include <stdio.h>
// PNumber - тип указателей на константную структуру
typedef const struct Number { int value; } *PNumber;
int main(void)
{
struct Number num = {6};
num.value = 7;
printf("num: %d\n", num); // num: 7
PNumber p = # // PNumber - это тип - указатель на константу-структуру
printf("pvalue: %d\n", p->value); // pvalue: 7
//p->value = 9; // нельзя
// но можно изменить значение самого указателя
struct Number num2 = {8};
p = &num2;
printf("pvalue: %d\n", p->value); // pvalue: 8
return 0;
}
Здесь определен PNumber, который выступает в роли типа указателей на структуру-константу Number. Соответственно изменить значение по указателю типа PNumber мы также не сможем. Но мы можем изменить значение самого указателя - установить его на удрес другой переменной.
Но также такой указатель можно сделать константным:
#include <stdio.h>
// PNumber - тип указателей на константную структуру
typedef const struct Number { int value; } *PNumber;
int main(void)
{
int x = 5;
const int cx = 10;
struct Number num1 = {6};
struct Number num2 = {7};
const PNumber p = &num1;
printf("pvalue: %d\n", p->value); // pvalue: 6
// p = &num2; // так нельзя
// printf("pvalue: %d\n", p->value);
return 0;
}