C Урок 29. Указатели в аргументах функций. Часть 2



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

 

Теперь давайте поработаем с нашими студентами.

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

Подключим в файле student.h следующие библиотеки

 

 

А в файле student.c добавим вот такую функцию, которая, используя указатель на тип структуры и будет выводить в консоль данные переданного студента

 

 

Как видим, ничего сложного здесь нет, мы пользуемся операцией взятия адреса того или иного поля структуры и выводим значение данных полей по очереди в консоль.

Создадим на данную функцию прототип в заголовочном файле, вернёмся в функцию main() файла main.c, закомментируем там предыдущий код, создадим студента, проинициализируем его поля обычным способом (не через указатель) и передадим с помощью нашей новой функции с помощью взятия адреса указатель на него

 

 

Проверим, как всё сработало

 

 

Сработало всё как надо. Мы не будем проводить различные эксперименты с данной функцией, мы знаем, что мы можем создать указатель заранее и привязать его к переменной структуры, заполнить поля через него и передать этот же указатель (уже без амперсанда) в нашу функцию. Это всё мы прекрасно понимаем и не будем тратить на это время.

Оказывается, есть ещё один способ передачи универсального указателя на любые данные, даже на те типы, которые глобально нигде не объявлены. Мы помним про тип данных void. Мы его используем в качестве формального типа данных, когда нам вообще не нужны никакие данные, а они должны быть, как в случае с параметрами функции — входящими и возвращаемыми.

 

 

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

Давайте добавим ещё одну подобную функцию в файле student.c, которая будет принимать в качестве аргумента именно такой указатель

 

 

Получилось довольно-таки красиво. Мы, зная о том, что мы передаём именно указатель на переменную структуры, передали его в виде указателя на void или на ничего.

Вроде всё красиво, но при этом теряется смысл void. Мы всё равно используем глобальный тип данных. Нам нужно не это. Мы должны представить, что нигде про существование такой структуры как студент, кроме наших функций main() и этой функции, неизвестно.

Поэтому мы объявляем структуру прямо здесь, затем, применив приведение типа указателя на void к указателю на тип нашей структуры, и функция теперь приобретёт вот такой вид

 

 

Теперь ей не нужны никакие глобальные структуры.

 

 

Создадим в заголовочном файле прототип на нашу функцию, вернёмся в функцию main() файла main.c, закомментируем там предыдущий код, объявим подобную структуру (имя уже не важно, главное чтобы поля были таких же типов, причём их имена тоже не важны), создадим переменную её типа, проинициализируем её поля и вызовем нашу новую функцию, передав ей в качестве параметра указатель на нашего студента, приведя его к типу void

 

 

Мы создали нашего студента через анонимную структуру, не присвоив никаких имён самой структуре, а сразу создав переменную, мы так тоже можем делать. Хотя можем создать и стандартным образом, объявив структуру и используя имя структуры создать переменную.

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

 

printStudentvoid(&st);

 

Но в то же время мы должны помнить, что мы можем применить и приведение типа, а то, мало ли, попадётся какой-нибудь привередливый компилятор и начнёт ругаться.

Запустим на выполнение наш код и увидим, что у нас всё также прекрасно работает через тип указателя на void

 

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

Теперь на закуску поработаем с массивом студентов. Мы помним, что у нас была функция добавления студента в массив, теперь мы подобную функцию добавим в файл student.c

 

 

На первый взгляд данная функция может испугать большим количеством параметров, но не всё так страшно и я сейчас всё расскажу.

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

Создадим на данную функцию прототип в заголовочном файле, вернёмся в функцию main() файла main.c, закомментируем там предыдущий код, объявим массив структур, проинициализируем счётчик, чтобы не превысить количество элементов и не попасть за верхнюю границу массива, добавим несколько студентов и выведем их данные в консоль, с помощью одной из наших функций

 

 

Проверим, как всё это работает

 

 

Всё отлично сработало.

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

 

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 Следующий урок

 

Исходный код

 

 

Смотреть ВИДЕОУРОК (нажмите на картинку)

 

C Указатели в аргументах функций

 

3 комментария на “C Урок 29. Указатели в аргументах функций. Часть 2
  1. max:

    Благодарю за урок. Есть только вопрос: а не лучше бы было вместо объявления двух разноименных struct в main() и printStudentvoid(void *st_ptr) и передачи их по *void, сделать объявления typedef struct в файле student.h и пользоваться им для построения локальных структур ? вроде тоже самое по памяти и не надо путаться с *void

  2. Иван:

    непонятно, зачем использовать указатель void, да еще объявлять новую структуру, если мы все равно должны создать структуру с полями таких же типов? если не хотим использовать глобальные переменные, то можно использовать локальные переменные. Объявление структуры и создание переменных это разные вещи.
    Предыдущий комментатор задает такой же вопрос.

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

    PS Классный урок, спасибо

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

*