В двух последних уроках мы плотно занимаемся с адресацией данных в языке C, изучили указатели, взятие адреса, разыменование, доступ к адресам массивов и после этого много чего знаем в этом плане.
Хотелось бы отдельно поговорить об указателях, которые указывают на структурированные типы данных, то есть на переменные типов структур. Причём, отдельного внимания заслуживают не сколько указатели на данные типа структур, то есть на их начало, там и так всё ясно и стандартно, а доступ с помощью указателей к отдельным полям структуры, получение их адресов, а также чтение этих полей и их модификация именно при помощи указателей на них.
Начнём потихоньку с этим разбираться. Способов опять же будет несколько.
Для начала создадим обычную классическую структуру
struct student
{
char nm[60];
unsigned char age;
unsigned char course;
};
Создадим переменные её типа заполним её поля
struct student st1;
strcpy(st1.nm, "Иванов Иван Иванович");
st1.age = 18;
st1.course = 2;
И теперь объявим классический указатель на структуру, а вернее на переменную её типа
struct student *st1_p
Здесь всё так же, как и с указателями на любой другой тип.
Также стандартно мы привяжем к этому указателю адрес нашей переменной типа такой структуры
st1_p = &st1;
А вот дальше уже будет немного интереснее. Нам не очень интересен сейчас адрес структуры, нам интересно получить адреса её полей, вернее адреса полей переменной типа нашей структуры.
Конечно же мы можем это получить путём обычного разыменования указателя, то есть сначала его разыменовать, а потом получить стандартным способом доступ к полям разыменованной переменной, то есть через точку
(*st1_p).nm
(*st1_p).course
(*st1_p).age
Но, согласитесь, что это не совсем красиво, и поэтому так никто и никогда не обращается к полям структуры. Для обращения к полям переменной структуры (для получения их адреса, а следовательно для доступа к ним), имеющей указатель, существует специальный оператор — вот такая вот стрелочка
->
Например, в таблице приоритетов операций такая стрелочка носит название 'Выбор элемента структуры или объединения по указателю'.
И теперь, используя такой оператор, мы спокойно можем обратиться к полям нашей структуры, имеющей указатель, вот таким образом
st1_p->nm
st1_p->course
st1_p->age
Стало гораздо симпатичнее, не правда ли?
Нам теперь не требуется никаких звёздочек и скобочек.
Будет ещё передача структур в качестве параметров в функциях с помощью их указателей, но это, скорее всего, в следующем уроке.
Ну и давайте тогда поиграемся с такими указателями в практическом проекте.
Проект мы также создадим из проекта прошлого урока с именем MYPROG27 и присвоим ему имя MYPROG28.
Откроем наш проект в Eclipse, произведём его первоначальную настройку и удалим весь наш код из функции main() за исключением возврата. Функция main() приобретёт вот такой вид
int main()
{
return 0; //Return an integer from a function
}
Первым делом давайте весь функционал по работе с нашими студентами переместим в отдельный модуль. Для этого сначала создадим и присоединим к проекту заголовочный файл student.h следующего содержания
1 2 3 4 5 6 7 8 9 10 11 |
#ifndef STUDENT_H_ #define STUDENT_H_ //------------------------------------------------ struct student { char nm[60]; unsigned char age; unsigned char course; }; //------------------------------------------------ #endif /* STUDENT_H_ */ |
А также создадим файл и тоже присоединим к проекту с именем student.c со следующим содержимым
1 |
#include "student.h" |
Думаю, как подключить это всё грамотно в Makefile, мы знаем, но тем не менее на всякий случай вот текст нашего Makefile с подключенным модулем полностью
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
CC = gcc all: main.o ariph.o utils.o student.o $(CC) -o myprog28.exe main.o ariph.o utils.o student.o main.o: src/main.c inc/main.h inc/ariph.h inc/utils.h inc/student.h $(CC) -Iinc -O0 -g3 -Wall -c src/main.c $(CC) -Iinc -masm=intel -g3 -Wall -c src/main.c -S ariph.o: src/ariph.c inc/ariph.h $(CC) -Iinc -O0 -g3 -Wall -c src/ariph.c $(CC) -Iinc -masm=intel -g3 -Wall -c src/ariph.c -S utils.o: src/utils.c inc/utils.h $(CC) -Iinc -O0 -g3 -Wall -c src/utils.c $(CC) -Iinc -masm=intel -g3 -Wall -c src/utils.c -S student.o: src/student.c inc/student.h $(CC) -Iinc -O0 -g3 -Wall -c src/student.c $(CC) -Iinc -masm=intel -g3 -Wall -c src/student.c -S clean: del -rf *.s *.o *.exe |
Откроем файл main.c и подключим наш модуль
1 2 |
#include "utils.h" #include "student.h" |
Объявления всех структур также удалим.
Функцию addStudent удалим тоже вместе с телом и прототипом, она пока нам не нужна.
Объявление вот этого пользовательского типа также удалим
typedef unsigned int my_int32;
Пока стандартным способом создадим переменную типа нашей структуры и стандартным способом заполним её поля
1 2 3 4 5 6 |
int main() { struct student st1; strcpy(st1.nm, "Иванов Иван Иванович"); st1.age = 18; st1.course = 2; |
Создадим указатель типа нашей структуры и присвоим сразу ему адрес нашей переменной
1 2 |
st1.course = 2; struct student *st1_p = &st1; |
Выведем значение полей, пользуясь уже указателем на структуру, пока через его обычное разыменование, представив, что мы не знаем нового оператора-стрелочки
1 2 |
struct student *st1_p = &st1; printf("%s,\t%d курс,\t%d лет\n", (*st1_p).nm, (*st1_p).course, (*st1_p).age); |
Запустим нашу программу на выполнение
Всё отлично вывелось. То есть такой способ доступа к полям структур через разыменование указателя на них тоже имеет право на существование.
Создадим ещё одну переменную структуры, аналогично присвоим значения полям, создадим переменную-указатель, присвоив ей адрес нашей переменной
1 2 3 4 5 6 |
printf("%s,\t%d курс,\t%d лет\n", (*st1_p).nm, (*st1_p).course, (*st1_p).age); struct student st2; strcpy(st2.nm, "Перов Петр Иванович"); st2.age = 17; st2.course = 1; struct student *st2_p = &st2; |
Теперь выведем значения полей, используя уже оператор-стрелочку
1 2 |
struct student *st2_p = &st2; printf("%s,\t%d курс,\t%d лет\n", st2_p->nm, st2_p->course, st2_p->age); |
Испытаем доступ, запустив проект на выполнение
Также всё прекрасно работает.
Создадим ещё одного такого студента с указателем, только заполним его поля тоже с помощью такого же оператора и выведем поля в консоль
1 2 3 4 5 6 7 |
printf("%s,\t%d курс,\t%d лет\n", st2_p->nm, st2_p->course, st2_p->age); struct student st3; struct student *st3_p = &st3; strcpy(st3_p->nm, "Семенов Игорь Васильевич"); st3_p->age = 20; st3_p->course = 3; printf("%s,\t%d курс,\t%d лет\n", st3_p->nm, st3_p->course, st3_p->age); |
Проверим, как это сработало
Всё отлично.
Оказывается, мы можем объявить переменную структуры сразу же при объявлении самой структуры.
Закомментируем весь наш код, который мы ввели в функции main(), а также закомментируем объявление структуры в файле student.h
1 2 3 4 5 6 7 8 |
/* struct student { char nm[60]; unsigned char age; unsigned char course; }; */ |
Вернёмся в файл main.c и в функции main() теперь объявим одновременно и структуру и переменную с помощью вот такого кода
1 2 3 4 5 6 7 |
*/ struct student { char nm[60]; unsigned char age; unsigned char course; } st; |
Причём мы даже можем использовать для создания переменной неименованную структуру, то есть вот так
1 2 3 4 5 6 |
struct { char nm[60]; unsigned char age; unsigned char course; } st; |
Только разница будет в том, что мы не сможем тогда объявлять ещё переменные типа этой структуры, будет только одна переменная или несколько через запятую. Мы планируем создать только одну переменную, поэтому можем оставить такой тип. Но мы тогда не создадим указатель, поэтому оставим предыдущее именованное объявление.
Аналогично создадим указатель (давайте создадим его отдельно от присваивания адреса для разнообразия) и также присвоим значения полям, затем выведя эти значения в консоль
1 2 3 4 5 6 7 |
} st; struct student *st_p; st_p = &st; strcpy(st_p->nm, "Иванов Иван Иванович"); st_p->age = 18; st_p->course = 2; printf("%s,\t%d курс,\t%d лет", st_p->nm, st_p->course, st_p->age); |
Проверим
По прежнему всё прекрасно работает.
Закомментируем и этот код.
В файле student.h объявим тип структуры через typedef
1 2 3 4 5 6 7 |
*/ typedef struct { char nm[60]; unsigned char age; unsigned char course; } student; |
Вернёмся в функцию main() файла main.c, создадим переменную нашего типа, указатель на неё, с помощью которого также заполним её поля и выведем их в консоль
1 2 3 4 5 6 7 |
*/ student st; student *st_p = &st; strcpy(st_p->nm, "Иванов Иван Иванович"); st_p->age = 18; st_p->course = 2; printf("%s,\t%d курс,\t%d лет\n", st_p->nm, st_p->course, st_p->age); |
Разница с предыдущими кодами здесь лишь в ненадобности использования ключевого слова struct, но по моим практическим наблюдениям, такой вид структур и указателей на переменные их типов используется чаще всего.
Проверим работу кода
Всё работает точно так же.
Мы сегодня не воспользовались отладкой, но нам оно и ни к чему, нам не нужно смотреть память, так как с выравниваниями полей структур мы можем запутаться с их адресацией, а это не тема данного урока, это скорее всего будет отдельная тема по выравниванию полей структур в памяти и как этим можно управлять.
Итак, сегодня мы научились пользоваться указателями на переменные типов структур, также научились адресоваться к отдельным полям при помощи указателей, используя при этом новый оператор.
Всем спасибо за внимание!
Предыдущий урок Программирование на C Следующий урок
Смотреть ВИДЕОУРОК (нажмите на картинку)
Добрый день! Как установить заголовочный файл graphics.h или самостоятельно написоний мное header файлы через командную строку cmd в папку C:\MinGW\include. Проста так закинул не работает!
А зачем Вы закидываете его именно туда, положите его в инклюды внутри вашего проекта, для этого надо будет поправить make файл:
1)Сделать из него объектник
graphics.o: src/graphics.c inc/graphics.h
$(CC) -Iinc -O0 -g3 -Wall -c src/graphics.c
$(CC) -Iinc -masm=intel -g3 -Wall -c src/graphics.c -S
2)Добавить связь с ним при компиляции main
main.o: src/main.c inc/main.h inc/ariph.h inc/utils.h inc/student.h inc/graphics.h
$(CC) -Iinc -O0 -g3 -Wall -c src/main.c
$(CC) -Iinc -masm=intel -g3 -Wall -c src/main.c -S
3)И в зависимости при окончательной сборки
all: main.o ariph.o utils.o graphics.o
$(CC) -o pointer_struct.exe main.o ariph.o utils.o graphics.o
А вообще советую посмотреть предыдущие лекции про make файлы, там все подробно расписано.
К началу урока я бы еще добавил вот такой тоже рабочий вариант без обьявления указателей
//указатель на структуру
struct student *st2_p = &st2;
//вывод через адрес и стрелочку
printf(«%s,\t%d курс,\t%d лет\n», (&st2)->nm, (&st2)->course, (&st2)->age);