Основи програмування на С ++ для початківців

Указатели на функции

Вже було зазначено, що покажчики можуть вказувати на найрізноманітніші типи об'єктів в програмі на мові C ++. Точнее, на все і на будь-які види об'єктів, є даними в програмі.

Але такими ж об'єктами, як і традиційні об'єкти даних, є функции в програмі. Тому напрошується бажання спробувати визначити і використовувати покажчик на функцію. Створимо ось таку просту програму (ex1.cc):

Тут з функцією область() все ясно: вона обчислює площу круга того радіуса, який відданий їй, як параметр. Але пізніше ми оголошуємо змінну покажчик на функцію:

У момент цього оголошення покажчик pfunc являє собою нічого більш, як якусь з адрес у внутрішньому поданні комп'ютера (4 байта в 32-біт операційній системі, 8 байт в 64-біт операційній системі). Це в точності той же внутрішній вигляд, котрий має, скажем, покажчик на целочисленную змінну INT *.

Але цей покажчик має свій строго певний тип: покажчик на функцію, приймаючу один параметр типу double, і повертає значення типу double. Але ось на яку конкретно функцію вказує покажчик, в точці його визначення - не важливо: значення покажчика не визначене.

А ось таким оператором присвоєння ми прив'язуємо покажчик на функцію до конкретної функції область(). Абсолютно правильним було б записати присвоєння вказівником адреси функції: pfunc = &область. Але компілятор C ++ настільки розумний, що і згадка імені функції в операторі присвоєння інтерпретує як її адресу.

Таким чином запис pfunc = площа також абсолютно коректна. З цього моменту ми можемо використовувати покажчик pfunc для виклику функції на яку він вказує. Для цього ми записуємо значення на яке вказує покажчик *pfunc (операция * в такому контексті називається разименованія указателя).

Дужки навколо разименованного значення pfunc в запису (*pfunc)( R ) потрібні з міркувань пріоритетів розкриття операцій у виразі. Виконання цього прикладу:

покажчик на функцію, з ++, програмування для початківців, C ++

Поки використання покажчиків на функції не принесло нічого принципово нового в розглянутому прикладі, крім деяких хитромудрих синтаксису. Але в цьому прикладі ми поки тільки дізналися яким чином можна визначити і використовувати покажчики на функції. А тепер ми можемо перейти до питання навіщо це потрібно і як ми можемо це використати.

Предположим, для деякого великого проекту ми готуємо послідовність тестів, виконуваних в процес розвитку і зростання самого тестованого проекту (це так звана технологія розробки тестування, і дуже продуктивна). Число покрокових тестів в такій ситуації буде постійно зростати в міру просування готовності базового проекту.

У цій ситуації ми можемо вчинити так (ex2.cc):

І ось що ми маємо на виконанні:

покажчик на функцію, з ++, програмування для початківців, C ++

У цьому лістингу тести[ ] – це массив покажчиків на функції без параметрів і без значень, що повертаються. красиве рішення, чи не правда? Ми можемо не замислюючись додавати нові функції масиву викликаються тестів, і вони все послідовно будуть викликатися при виконанні.

Ми можемо піти ще далі: якщо покажчик на функцію представляє функцію у виразах, можна передати покажчик на функцію в якості параметра інший, охоплює функції, і остання буде виконувати в своєму тілі передану параметром функцію, не знаючи яку дію при цьому виконується.

Для ілюстрації всього сказаного створимо програму самого примітивного калькулятора, виконує арифметичні дії над цілочисельними операндами (ex3.cc):

Тут власне обчислення виконує функція обчислювати(). Але вона нічого не «знає» про виконувані дії і про арифметичних операціях взагалі: вона застосовує до 2-м першим своїми параметрами одну з 6-ти функцій, яку їй передали 3-м параметром для виконання.

Хто не знає про ЬурейеЕ читайте тут.

У цьому коді функція strtod() – стандартна функція бібліотеки мови C (ANSI C, стандарту POSIX), яка витягує десяткове число з рядка, отриманої з стандартного потоку введення. У контексті наших обговорень це цікаво тим, що:

    • програма на C ++ може використовувати ввесь люд бібліотечних викликів мови C;
  • програма C ++ використовує на етапі виконання колективні бібліотеки мови C (.так или .DLL файли), і при відсутності стандартної бібліотеки C, програми на C ++ стають непрацездатними.

Але повернемося до роботи показаного калькулятора (його гранична спрощеність пов'язана з тим, що ми не займаємося проблемами введення, його формату і не обробляємо помилки введення користувачем – в реальних програмах так робити не можна):

покажчик на функцію, з ++, програмування для початківців, C ++

Наглядова читач міг би помітити, что функция обчислювати() при всьому своєму бажанні і не могла б виконати жодне із потрібних арифметичних дій, так як виконують ці дії функції sum(), диф(), мені() і ДІВ() описані пізніше функции обчислювати() і не видимі в коді функції обчислювати().

Таким образом, ми розглянули кілька різних випадків, коли функції в C ++ використовуються, як екземпляри даних. Вони можуть об'єднуватися в масиви, вбудовуватися в якості полів структур, або передаватися іншим функціям як параметри.

Всі ці та докладні можливості реалізуються за рахунок покажчиків на функції (хоча по синтаксису записів вони можуть і не виглядати як покажчики, за рахунок розумного компілятора C ++, який по контексту розуміє, що повинні використовуватися адреси функцій).

Дивіться також урок Покажчики на об'єкти

18 думки про "Указатели на функции

  1. Зайва крапка з комою в першому лістингу після функції double area(подвійний R).

    “якусь з адрес у внутрішньому поданні комп'ютера”
    просто “якусь з адрес”, до чого ця добавка про “внутрішнєподання”? – вона щось додає до суті?

    “нового в розглянутому прімерt”
    друкарська помилка.

    “можна передати покажчик на функцію в якості параметра інший, охоплює функції, ”
    Що за що охоплює функція? Що вона охоплює?

    “Хто не знає про typedef - читайте тут.”
    посилання чудова, але краще написати про це тут (це ж зовсім не багато).

    Останній приклад сумовитий. ИМХО по темі покажчиків на функції треба було сказати про це в цілому (як в першому прикладі статті), потім привести приклад з стандартною функцією qsort, яка приймає покажчик на функцію порівняння елементів. Ну і завершити всі чимось на зразок функції пошуку кореня методом половинного ділення (це дуже простий приклад і в ньому так само як в qsort зручно передавати функцію в якості параметра).

    Тобто,. юзер повинен побачити що є покажчики на функції і розібратися з синтаксисом (в перший приклад можна додати опис typedef). потім побачив, що це реально потрібно (раз використовується в стандартній бібліотеці). Ну і під кінець навчився писати такі ж функції, приймають інші функції за вказівником, як і qsort.

    Ну це ІМХО.

    1. > Просто "якусь з адрес", до чого ця добавка про "внутрішнє уявлення"? - Вона щось додає до суті?

      звичайно додає. Тому як, з точки зору типізації C, а особливо C ++, синтаксично “просто адрес” или “деяких адрес” не існує взагалі (хіба що за винятком void *). Всі покажчики типізовані (і тим радикально відрізняються один від одного). І тільки у внутрішньому машинному поданні покажчик на функцію-метод класу, скажем, і символ * – вже нічим не відрізняються, вони просто невиразні.

      1. > звичайно додає. Тому як, з точки зору типізації C, а особливо C ++, синтаксично "просто адрес" або "деяких адрес" не існує взагалі (хіба що за винятком void *). Всі покажчики типізовані … І тільки у внутрішньому машинному поданні покажчик на функцію-метод класу, скажем, і char * - уже нічим не відрізняються, вони просто невиразні.

        Адреса – він і в Африці адреса, незалежно на що ти пишеш.

        #include

        int main() {
        INT Вел = 12345;
        INT * INTP = &Вел; // типу збірний покажчик
        символ * СЬагр; // ще один збірний покажчик
        СЬагр; (char*)INT;

        std::cout << (int) *СЬагр << " " << *(INT *)СЬагр << std::endl;
        }

        Тут показано що покажчик на int можна скастовать в покажчик на char.
        Ну а потім можна звернутися до даних за цією адресою. У першому випадку разименовивается покажчик char *, тому вибирається перший байт за адресою змінної val. У другому випадку покажчик char * Кастуся в int * і разименовивается, тому беруться вже все 4 байта.

        висновок: 57 12345

        У будь-якому випадку з адресами в Сі можна працювати як завгодно. Ти можеш брати дані по будь-якою адресою, зчитувати їх і записувати по ним що завгодно.

        Наприклад ти міг би дописати в кінець цієї програми щось типу:
        *СЬагр; 56;

        std::cout << Вел << std::endl;

        тобто за адресою змінної val можна замінити перший байт, працюючи з ним через покажчик на char. А можна і не перший байт, адже в Сі все добре з адресною арифметикою.

    2. > Останній приклад сумовитий. ИМХО по темі покажчиків на функції треба було

      А ось з цього приводу: не “треба було” – а треба було взяти і написати як свій текст, так і свої приклади. А чи не сумно розповідати “як треба б було б”.

      1. Я і пишу “свої тексти” :). До критики стався спокійніше. Особливо при тому, що уроки у тебе не виходять.

        >> У контексті наших обговорень це цікаво тим, що: … програма C ++ використовує на етапі виконання колективні бібліотеки мови C (.так чи .dll), і при відсутності стандартної бібліотеки C, програми на C ++ стають непрацездатними.

        “Контекст наших обговорень” – це “покажчики на функції” і в ньому нам взагалі не цікаво що там відбувається з розділяються бібліотеками.

        Тим більше, що достовірність цієї інформації може викликати сумніви. Ти говориш, що програми на С ++ використовують “колективні бібліотеки” мови Сі. термін “Колективна бібліотека” передбачає, що бібліотека одночасно використовується декількома процесами.

        Дак от, бібліотеки включаються всередину кожного виконуваного файлу, вони ніфіга не “колективні”. Як розділяються вони може бути використовуються при налагодженні, але релізи збираються з опцією –static завжди. Інакше твоя програма не запуститься на комп'ютері, якщо на ньому не встановлено компілятор тієї ж версії, яку ти використовував при складанні програми і не будуть прописані відповідні шляхи в системну змінну PATH. Ну або ти можеш, звичайно, запускати всі програми, написані на С ++ з одного каталогу, в корінь якого помістити відповідні .dll / .so, але так ніхто не робить

      2. INT Powr(INT op1, INT op2)
        {
        INT RET = 1;
        while (op2–) правий * = op1;
        повернення у відставці;
        }

        Це типу зведення цілу в ступінь. Мало того, що це дико неефективно і може бути замінено використанням стандартної функції. Так воно ще й впаде при від'ємному значенні op2.

        PS. Якщо писати статтю про покажчики на функції в С ++, то ІМХО варто розповісти про std::функціональна. З одою боку в заголовку заявлено С ++, не його. З іншого – funtional майже з усіх точок зору краще і зручніше. Наприклад я через нього можу лямбда передавати (та й будь-який інший callable об'єкт), а через покажчик на функцію – не можу.

        Але замість цього автор написав якусь недостовірну інформацію про “колективні бібліотеки”.

      3. Абсолютно згоден з наступним коментаторів, уроки у тебе НЕ виходять.

  2. int main()
    {
    //…
    return 0; // вже 4 року ця строчка зайва. Якщо включення програми завершилася аварійно, вона * автоматично * поверне нульовий код помилки. Тобто,. ти тут виконуєш роботу, яку і без тебе виконує компілятор

    >> Предположим, для деякого великого проекту ми готуємо послідовність тестів, виконуваних в процес розвитку і зростання самого тестованого проекту (це так звана технологія розробки тестування, і дуже продуктивна). … У цій ситуації ми можемо вчинити так (ex2.cc):

    У цій ситуації ні в якому разі не можна чинити так, як ти пишеш. Всі твої старання дико далекі від “розробки через тестування”. Подивися на тест Google рамки або на форсування тест рамки одиниці або на 100500 аналогів.

    >>
    for (int i = 0; i > за рахунок розумного компілятора C ++, який по контексту розуміє, що повинні використовуватися адреси функцій

    Пол статті ти розповідаєш про “розумний компілятор”. Реально там нічого особливо розумного немає – якщо за ім'ям функції немає круглої дужки, то стопудово це не виклик функції, а покажчик на неї. Ніяких перекрутити типу &function я ніде крім твоєї статті не бачив.

    1. Може і так, але мені як новачкові стало зрозуміло, що просто назва функції це по суті адреса функції, оскільки щось зрозуміліше щодо абстрактного розуміння

  3. Як же важко у мене все йде з цими покажчиками:(
    Цікавий урок і змушує багато думати. Напевно це добре, але деякі моменти для мене так і залишилися незрозумілими. Особливо з масивом покажчиків на функції і може хто-небудь відповісти мені на рахунок typedef.
    ЬурейеЕ INT(*fint_t)(int, int);
    fint_t foper[] =
    {
    summ, різниця, багато, divd, Bals, Powr
    };
    Тут мається на увазі, що покажчик на функцію перетворюється в тип даних?
    Поясніть будь ласка…

    1. typedef не вводить нового типу, він тільки визначає короткий синонім для довгого написання “покажчик на функцію ось такого типу”.
      А далі визначається массив таких покажчиків на функції (з ім'ям foper) і він тут же явно инициализируется. Оскільки з ініціалізації виводиться розмір масиву, то при його описі ми можемо його не вказувати ([]).

      1. Дякую за відповідь! начебто розібрався!

  4. Понятно, що sizeof повертає довжину в байтах, але ось умова:
    i < sizeof(тести) / sizeof(тести[0])
    ніяк не можу зрозуміти. Поясніть пжл.

    1. Що може бути простіше?
      Так виходить число елементів масиву.
      Якщо масив 1-байтних елементів (char[]) містить 10 элементов, то це 10/1 = 10.
      Якщо масив 100-байтних елементів (структур)містить 10 элементов, то це 1000/100 = … і знову ж 10.

      1. Дайте побільше задачок по темам на цьому сайті…спасибі за уроки!

    2. Значить так , sizeof(тести) повертає обсяг пам'яті(називайте як хочете) який: 1)Використовується при роботі масиву , якщо він був описаний ось так
      [код] void (*тести[])(void) = {test1 , test2 , test3 , test4}[/код] – тут буде 4 пункти. 2)А ось якщо використовувати інше оголошення: [код]void (*тести[5])(void) = {test1 , test2 , test3 , test4}[/код]- тут як не крути , а пам'яті виділено на 5 элементов , по сему [код]sizeof(тести) / sizeof(тести[0])[/код]
      равно 5 , а не 4 як можна було подумати. З цього краще в даному випадку не вказувати розмір массіва.Так як в циклі ми спробуємо на порожньому місці викликати функцію і отримаємо помилку виконання

Залишити коментар до Olej Скасувати відповідь

Ваша електронна адреса не буде опублікований. Обов'язкові поля позначені * *