В предыдущей части урока мы узнали, как создать функцию с указателями в её аргументах, как такую функцию вызвать и также поработали с передачей указателей на некоторые типы данных в аргументах функций на практике.
Теперь давайте поработаем с нашими студентами.
Давайте попробуем создать такую функцию, которая будет выводить в консоль данные одного из студентов, тем более что данная функция нам пригодится, так как код вывода данных по студенту в консоль мы используем очень часто.
Подключим в файле student.h следующие библиотеки
1 2 3 4 5 |
#define STUDENT_H_ //------------------------------------------------ #include <stdio.h> #include <string.h> //------------------------------------------------ |
А в файле student.c добавим вот такую функцию, которая, используя указатель на тип структуры и будет выводить в консоль данные переданного студента
1 2 3 4 5 6 |
//-------------------------------------------------------------------------- void printStudent (student *st_ptr) { printf("%-30s%d курсt%d летn", st_ptr->nm, st_ptr->course, st_ptr->age); } //-------------------------------------------------------------------------- |
Как видим, ничего сложного здесь нет, мы пользуемся операцией взятия адреса того или иного поля структуры и выводим значение данных полей по очереди в консоль.
Создадим на данную функцию прототип в заголовочном файле, вернёмся в функцию main() файла main.c, закомментируем там предыдущий код, создадим студента, проинициализируем его поля обычным способом (не через указатель) и передадим с помощью нашей новой функции с помощью взятия адреса указатель на него
1 2 3 4 5 6 |
*/ student st; strcpy(st.nm, "Иванов Иван Иванович"); st.age = 18; st.course = 2; printStudent(&st); |
Проверим, как всё сработало
Сработало всё как надо. Мы не будем проводить различные эксперименты с данной функцией, мы знаем, что мы можем создать указатель заранее и привязать его к переменной структуры, заполнить поля через него и передать этот же указатель (уже без амперсанда) в нашу функцию. Это всё мы прекрасно понимаем и не будем тратить на это время.
Оказывается, есть ещё один способ передачи универсального указателя на любые данные, даже на те типы, которые глобально нигде не объявлены. Мы помним про тип данных void. Мы его используем в качестве формального типа данных, когда нам вообще не нужны никакие данные, а они должны быть, как в случае с параметрами функции — входящими и возвращаемыми.
Но мы можем также создать и указатель на такой тип данных. Это будет такой себе универсальный указатель, который затем с помощью приведения типа мы можем превратить в указатель на любые данные, в т.ч. и структуры, и массивы и т.д.
Давайте добавим ещё одну подобную функцию в файле student.c, которая будет принимать в качестве аргумента именно такой указатель
1 2 3 4 5 6 |
//-------------------------------------------------------------------------- void printStudentvoid (void *st_ptr) { printf("%-30s%d курсt%d летn", ((student*)st_ptr)->nm, ((student*)st_ptr)->course, ((student*)st_ptr)->age); } //-------------------------------------------------------------------------- |
Получилось довольно-таки красиво. Мы, зная о том, что мы передаём именно указатель на переменную структуры, передали его в виде указателя на void или на ничего.
Вроде всё красиво, но при этом теряется смысл void. Мы всё равно используем глобальный тип данных. Нам нужно не это. Мы должны представить, что нигде про существование такой структуры как студент, кроме наших функций main() и этой функции, неизвестно.
Поэтому мы объявляем структуру прямо здесь, затем, применив приведение типа указателя на void к указателю на тип нашей структуры, и функция теперь приобретёт вот такой вид
1 2 3 4 5 6 7 8 9 10 |
void printStudentvoid (void *st_ptr) { typedef struct { char nm[60]; unsigned char age; unsigned char course; } stdn; printf("%s,t%d курс,t%d летn", ((stdn*)st_ptr)->nm, ((stdn*)st_ptr)->course, ((stdn*)st_ptr)->age); } |
Теперь ей не нужны никакие глобальные структуры.
Создадим в заголовочном файле прототип на нашу функцию, вернёмся в функцию main() файла main.c, закомментируем там предыдущий код, объявим подобную структуру (имя уже не важно, главное чтобы поля были таких же типов, причём их имена тоже не важны), создадим переменную её типа, проинициализируем её поля и вызовем нашу новую функцию, передав ей в качестве параметра указатель на нашего студента, приведя его к типу void
1 2 3 4 5 6 7 8 9 10 11 |
*/ struct { char nm[60]; unsigned char age; unsigned char course; } st; strcpy(st.nm, "Иванов Иван Иванович"); st.age = 18; st.course = 2; printStudentvoid((void*)&st); |
Мы создали нашего студента через анонимную структуру, не присвоив никаких имён самой структуре, а сразу создав переменную, мы так тоже можем делать. Хотя можем создать и стандартным образом, объявив структуру и используя имя структуры создать переменную.
Потом мы заполнили поля нашей переменной и передали её для вывода в консоль нашей функции, преобразовав к типу указателя на void. Причём мы можем даже и не применять здесь преобразования типов к типу указателя на void, компилятор за это ругаться не будет, так как такой указатель мы всё равно имеем право использовать для любых типов данных
printStudentvoid(&st);
Но в то же время мы должны помнить, что мы можем применить и приведение типа, а то, мало ли, попадётся какой-нибудь привередливый компилятор и начнёт ругаться.
Запустим на выполнение наш код и увидим, что у нас всё также прекрасно работает через тип указателя на void
Вообщем, сейчас нам не совсем понятно, какой смысл указателя на тип void, но если в структуре будут данные огромной длины, то нам нет смысла это всё совать в глобальную память и мы можем пользоваться нашими структурами локально. Это очень нужный порой механизм и знать его нужно.
Теперь на закуску поработаем с массивом студентов. Мы помним, что у нас была функция добавления студента в массив, теперь мы подобную функцию добавим в файл student.c
1 2 3 4 5 6 7 8 9 10 |
//-------------------------------------------------------------------------- void addStudent (student *res_st, unsigned int *student_counter, char *ch, unsigned int cr, unsigned int ag) { strcpy(res_st->nm, ch); res_st->course = cr; res_st->age = ag; (*student_counter)++; } //-------------------------------------------------------------------------- |
На первый взгляд данная функция может испугать большим количеством параметров, но не всё так страшно и я сейчас всё расскажу.
Первый параметр — указатель на элемент в массиве, так как весь массив мы каждый раз не передаём, с помощью данного указателя будут заноситься данные на отдельного студента в массиве, второй — указатель на счётчик студентов, который мы не должны превышать, в данной функции мы его в конце тела и инкрементируем, третий — указатель на массив с именем, которое мы передаём для занесения в поле элемента, четвёртый — это курс, пятый — возраст студента. Теперь, думаю всё прояснилось. В теле функции мы заносим в элемент массива в его поля имя, курс и возраст студента и инкрементируем счётчик путём его разыменования.
Создадим на данную функцию прототип в заголовочном файле, вернёмся в функцию main() файла main.c, закомментируем там предыдущий код, объявим массив структур, проинициализируем счётчик, чтобы не превысить количество элементов и не попасть за верхнюю границу массива, добавим несколько студентов и выведем их данные в консоль, с помощью одной из наших функций
1 2 3 4 5 6 7 8 9 10 11 12 |
*/ student st[20]; unsigned int st_cnt = 0; if(st_cnt<20) addStudent(&st[st_cnt], &st_cnt, "Иванов Иван Иванович", 2, 18); if(st_cnt<20) addStudent(&st[st_cnt], &st_cnt, "Петров Петр Иванович", 1, 17); if(st_cnt<20) addStudent(&st[st_cnt], &st_cnt, "Сидоров Александр Петрович", 4, 22); if(st_cnt<20) addStudent(&st[st_cnt], &st_cnt, "Попов Иван Сергеевич", 3, 22); if(st_cnt<20) addStudent(&st[st_cnt], &st_cnt, "Васильев Федор Николаевич", 5, 24); if(st_cnt<20) addStudent(&st[st_cnt], &st_cnt, "Саблин Виктор Петрович", 1, 18); if(st_cnt<20) addStudent(&st[st_cnt], &st_cnt, "Веселкин Алексей Алексеевич", 2, 18); if(st_cnt<20) addStudent(&st[st_cnt], &st_cnt, "Трухин Сергей Сергеевич", 5, 23); for(int i=0; i<st_cnt; i++) printStudent(&st[i]); |
Проверим, как всё это работает
Всё отлично сработало.
Также мы можем, зная, что индекс массива — это его смещение относительно адреса начала массива, использовать имя массива в качестве указателя и лишь добавлять к нему смещения, не используя уже амперсанды и квадратные скобки. Исправим наш код за исключением первых двух строчек
if(st_cnt<20) addStudent(st+st_cnt, &st_cnt, "Иванов Иван Иванович", 2, 18);
if(st_cnt<20) addStudent(st+st_cnt, &st_cnt, "Петров Петр Иванович", 1, 17);
if(st_cnt<20) addStudent(st+st_cnt, &st_cnt, "Сидоров Александр Петрович", 4, 22);
if(st_cnt<20) addStudent(st+st_cnt, &st_cnt, "Попов Иван Сергеевич", 3, 22);
if(st_cnt<20) addStudent(st+st_cnt, &st_cnt, "Васильев Федор Николаевич", 5, 24);
if(st_cnt<20) addStudent(st+st_cnt, &st_cnt, "Саблин Виктор Петрович", 1, 18);
if(st_cnt<20) addStudent(st+st_cnt, &st_cnt, "Веселкин Алексей Алексеевич", 2, 18);
if(st_cnt<20) addStudent(st+st_cnt, &st_cnt, "Трухин Сергей Сергеевич", 5, 23);
for(int i=0; i<st_cnt; i++) printStudent(st+i);
Пусть это уже не так читабельно, как с индексами, но очень красиво. Самое главное, что нам в жизни в кодах это будет очень часто встречаться и мы должны данный способ также знать.
Мы также исправили взятие адреса и индекс на имя массива и смещение и в выводе студента в консоль.
Таким образом, на данном уроке мы познакомились с использованием указателей в аргументах функций. Я думаю, данный урок был очень долгожданным, так как многие не совсем понимают, как и что в данном случае происходит и для чего нам это нужно. Чего мы с помощью передачи указателей в аргументах можем достичь, что при этом экономится.
Всем спасибо за внимание!
Предыдущая часть Программирование на C Следующий урок
Смотреть ВИДЕОУРОК (нажмите на картинку)
Благодарю за урок. Есть только вопрос: а не лучше бы было вместо объявления двух разноименных struct в main() и printStudentvoid(void *st_ptr) и передачи их по *void, сделать объявления typedef struct в файле student.h и пользоваться им для построения локальных структур ? вроде тоже самое по памяти и не надо путаться с *void
непонятно, зачем использовать указатель void, да еще объявлять новую структуру, если мы все равно должны создать структуру с полями таких же типов? если не хотим использовать глобальные переменные, то можно использовать локальные переменные. Объявление структуры и создание переменных это разные вещи.
Предыдущий комментатор задает такой же вопрос.
Да можно было конечно, просто автор хотел показать, что в функции мы получим по сути начальный адрес памяти структуры. А как вы дальше будете фрагмент памяти рассматривать в функции — это уже ваше дело. Пропишите что это был указатель на такую же структуру — будут такие же поля в ней как и в структуре в главном модуле, пропишите что это указатель на строку пришел — весь участок памяти с вашей структурой из главного модуля будет интерпретироваться внутри функции как строка…
PS Классный урок, спасибо