В предыдущей части урока мы познакомились с тем, как можно объявить указатель на функцию и им воспользоваться, также поработали с этим на практике, а в том числе и с массивом таких указателей.
Перейдём в файл student.c и добавим сразу три функции, которые будут прибавлять курс, возраст и изменять имя студента
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
//-------------------------------------------------------------------------- void addCourse (student *st) { st->course++; } //-------------------------------------------------------- void addAge (student *st) { st->age++; } //-------------------------------------------------------- void modifyName (student *st, char *ch) { strcpy(st->nm, ch); } //-------------------------------------------------------- |
Зачем изменять имя, думаю понятно. Например, студентка вышла замуж и изменила фамилию. Бывает же такое.
Добавим ещё одну функцию, которая будет удалять студента, например, если он отчислен по причине академической неуспеваемости либо по причине окончания учебного заведения
1 2 3 4 5 6 7 8 9 10 11 12 |
//-------------------------------------------------------- void deletePos (student *st_list, unsigned int pos, unsigned int *student_counter) { for(unsigned int i=pos;i<*student_counter-1;i++) { strcpy(st_list[i].nm, st_list[i+1].nm); st_list[i].age = st_list[i+1].age; st_list[i].course = st_list[i+1].course; } (*student_counter)--; } //-------------------------------------------------------- |
Первые три функции вообще простые и не нуждаются в объяснении.
А вот четвёртая интересная.
Мы по сути ничего не удаляем.
Мы просто копируем следующий элемент массива за якобы удаляемым в позицию удаляемого элемента, а в позицию того элемента, который мы скопировали, копируем следующий элемент. И так до тех пор, пока не дойдём до последнего элемента. Последний элемент у нас — это значение счётчика студентов минус один, так как элементы считаются с нуля.
И последний элемент, который мы скопировали, мы никак не удаляем и не обнуляем, мы просто-напросто декрементируем счётчик и данный элемент будет считаться несуществующим, а когда надо будет добавить нового студента, мы его туда и пропишем и нарастим опять счётчик. По идее, добавление студента тоже можно добавить в нашу структуру, но нам пока, думаю, и этих четырёх функций хватит за глаза.
В заголовочном файле student.h добавим на наши функции прототипы
1 2 3 4 5 |
char *ch, unsigned int cr, unsigned int ag); void addCourse (student *st); void addAge (student *st); void modifyName (student *st, char *ch); void deletePos (student *st_list, unsigned int pos, unsigned int *student_counter); |
И там же объявим ещё одну структуру, вернее, тип структуры, в который мы, кроме указателя на студента, который затем будет указателем на нулевой элемент в массиве всех студентов, добавим ещё и указатели на функции, по типам такие же, как и наши 4 только что добавленные функции. Мало того, мы и имена можем использовать те же, так как это уже поля структуры и они с прототипами функций никак не пересекаются и нам незачем выдумывать для указателей новые имена
1 2 3 4 5 6 7 8 9 10 |
} student; //------------------------------------------------ typedef struct { student *st; void (*addCourse)(student *); void (*addAge)(student *st); void (*modifyName)(student *st, char *ch); void (*deletePos)(student *st_list, unsigned int pos, unsigned int *student_counter); } student_list; |
Теперь вернёмся в функцию main() файла main.c, закомментируем предыдущий участок кода, создадим массив студентов, проинициализируем их также, как и в прошлом уроке и выведем их данные в консоль, в том числе и их позиции, а по окончания вывода добавим ещё один переход на новую строку
1 2 3 4 5 6 7 8 9 10 11 12 13 |
*/ 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++) {printf("%dt", i); printStudent(st+i);} printf("n"); |
Теперь создадим переменную типа нашей новой структуры, которая будет хранить указатель на массив со студентами и указатели на функции
1 2 3 4 5 6 7 8 9 |
printf("n"); student_list st_list = { st, addCourse, addAge, modifyName, deletePos }; |
Что круто в структуре?
А круто здесь то, что мы, добавив имена, уже присвоили адреса оригинальных функций нашим указателям и нам не надо будет потом как-то их отдельно присваивать указателям.
Чудеса да и только!
Это потому, что мы не просто создали переменную типа структуры. Она у нас уже есть. Мы её создали и проинициализировали. Вот и весь секрет.
Мы можем собрать наш код и проверить это несколькими способами.
1 способ: вызвать контекстное меню на любом из этих полей и посмотреть куда мы попадём.
Давайте проделаем это
И попадём мы вот куда
Следующие способы посмотрим чуть позже.
Давайте нашу девушку «выдадим замуж», изменив ей фамилию (имя и отчество мы ей оставим прежние), одному студенту добавим возраст, так как у него день рожденья сегодня, а другого переведём на следующий курс, а затем снова всех выведем в консоль
1 2 3 4 5 6 |
deletePos }; st_list.addCourse(st+5); st_list.addAge(st+1); st_list.modifyName(st+2, "Петрова Елена Павловна"); for(int i=0; i<st_cnt; i++) {printf("%dt", i); printStudent(st+i);} |
Проверим, сработало ли наше действие
Всё сработало. Иванова стала Петровой, Петров стал старше на год, а Саблин перешёл на 2 курс.
Теперь второй способ посмотреть, куда смотрят имена.
Поставим точку останова куда-нибудь после объявления и инициализации переменной структуры и зайдём в отладку. Запустим программу до точки останова
Мы видим реальные адреса процедур. Запомним их. Перейдём в файл student.c, не останавливая отладку, и, подводя курсор к именам функций, посмотрим их адреса
Адреса совпали.
Третий способ — дизассемблер.
Зайдём туда
Те же адреса. Отлично.
Осталось нам попробовать отчислить какого-нибудь студента-пятикурсника, будем считать, что он получил диплом.
Давайте это проделаем и затем снова выведем список наших студентов
1 2 3 4 |
for(int i=0; i<st_cnt; i++) {printf("%dt", i); printStudent(st+i);} printf("n"); st_list.deletePos(st, 4, &st_cnt); for(int i=0; i<st_cnt; i++) {printf("%dt", i); printStudent(st+i);} |
Проверим, удалился ли у нас пятикурсник Васильев
Васильева больше нет в массиве. Отлично!
Таким образом, на данном уроке мы познакомились с указателями на функции и узнали о том, что, как оказалось, это очень полезно бывает в практике программирования.
Всем спасибо за внимание!
Предыдущая часть Программирование на C Следующий урок
Смотреть ВИДЕОУРОК (нажмите на картинку)
Добрый день. По хорошему, раз уже есть addStudent и deletePos , то в них и надо проверять количество студентов. Вы совсем не ленивый 🙂
что то я запутался, зачем
strcpy(st_list[i].nm, st_list[i+1].nm);
st_list[i].age = st_list[i+1].age;
st_list[i].course = st_list[i+1].course;
разве нельзя просто st_list[i] = st_list[i+1]
?