Чому я не можу визначити функцію всередині іншої функції?


80

Це не питання лямбда-функції, я знаю, що можу призначити лямбда змінній.

Який сенс дозволяти нам оголошувати, але не визначати функцію всередині коду?

Наприклад:

#include <iostream>

int main()
{
    // This is illegal
    // int one(int bar) { return 13 + bar; }

    // This is legal, but why would I want this?
    int two(int bar);

    // This gets the job done but man it's complicated
    class three{
        int m_iBar;
    public:
        three(int bar):m_iBar(13 + bar){}
        operator int(){return m_iBar;}
    }; 

    std::cout << three(42) << '\n';
    return 0;
}

Отже, я хочу знати, чому С ++ дозволить те, twoщо здається марним, а threeяке здається набагато складнішим, але забороняє one?

РЕДАГУВАТИ:

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


3
Перший one- це визначення функції , два інших - це декларації .
Якийсь програміст, чувак,

9
Я думаю, ви неправильно зрозуміли терміни - ви хочете запитати: "Який сенс дозволяти нам заявляти, але не визначати функцію всередині коду?". І поки ми це робимо, ви, мабуть, маєте на увазі "всередині функції ". Це все "код".
Пітер - Відновити Моніку

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

4
@MikeSeymour правильно це зробив. С не так добре структурований, як, скажімо, Паскаль, і завжди допускає лише визначення функцій верхнього рівня. Тож причина історична, плюс відсутність необхідності її змінювати. Те, що декларації функцій можливі, - це лише наслідок того, що загалом можливі декларації з масштабом. Заборона на те, що для функцій означало б додаткове правило.
Пітер - Відновити Моніку

3
@JonathanMee: Можливо, тому, що загалом декларації дозволяються в блоках, і немає особливих причин, щоб спеціально забороняти декларації функцій; простіше просто дозволити будь-яку декларацію без особливих випадків. Але "чому" насправді не відповідає на запитання; мова є такою, яка вона є, тому що так вона еволюціонувала.
Майк Сеймур

Відповіді:


41

Не очевидно, чому oneзаборонено; вкладені функції були запропоновані давно в N0295, де сказано:

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

Очевидно, що цю пропозицію було відхилено, але оскільки у нас немає протоколів засідань, доступних в Інтернеті, 1993ми не маємо можливого джерела обґрунтування цього відхилення.

Насправді ця пропозиція зазначена у виразах Лямбда та закриттях для C ++ як можлива альтернатива:

Одна стаття [Bre88] та пропозиція N0295 до комітету C ++ [SH93] пропонують додати вкладені функції до C ++. Вкладені функції подібні до лямбда-виразів, але визначаються як оператори в тілі функції, і отримане закриття не може використовуватися, якщо ця функція не активна. Ці пропозиції також не включають додавання нового типу для кожного лямбда-виразу, а натомість реалізують їх більше, як звичайні функції, включаючи дозволяючи спеціальний тип покажчика функції посилатися на них. Обидві ці пропозиції передують додаванню шаблонів до C ++, і тому не згадують про використання вкладених функцій у поєднанні із загальними алгоритмами. Крім того, ці пропозиції не мають можливості скопіювати локальні змінні в закриття, і тому вкладені функції, які вони виробляють, абсолютно непридатні за межами своєї функції, що включає

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

Щодо цієї частини Вашого запитання:

// This is legal, but why would I want this?
int two(int bar);

Бувають випадки, коли це був би корисний спосіб викликати потрібну вам функцію. Проект стандартного розділу C ++ 3.4.1 [basic.lookup.unqual] дає нам один цікавий приклад:

namespace NS {
    class T { };
    void f(T);
    void g(T, int);
}

NS::T parm;
void g(NS::T, float);

int main() {
    f(parm); // OK: calls NS::f
    extern void g(NS::T, float);
    g(parm, 1); // OK: calls g(NS::T, float)
}

1
Питання до прикладу 3.4.1, який ви наводите: Чи не міг абонент, який телефонує в основному, просто написати ::g(parm, 1), щоб викликати функцію в глобальному просторі імен? Або дзвінок, g(parm, 1.0f);який повинен призвести до кращого збігу з бажаним g?
Пітер - Відновити Моніку

@PeterSchneider Я зробив там занадто сильну заяву, я її відкоригував.
Шафік Ягмор

1
Я хотів би додати коментар тут: Ця відповідь була прийнята не тому, що вона найкращим чином пояснила, чому в коді дозволено функціонування декларацій; а тому, що він найкраще спрацював з описом того, чому в коді не дозволяються визначення функції, що було актуальним питанням. І конкретно це окреслює, чому гіпотетична реалізація функцій коду буде відрізнятися від реалізації лямбда. +1
Джонатан Мі

1
@JonathanMee: Як у світі: "... у нас немає можливого джерела обґрунтування цього відхилення". кваліфікуватися як найкраща робота з опису, чому визначення вкладених функцій заборонено (або навіть спроба описати це взагалі?)
Джеррі Коффін

@JerryCoffin Відповідь включала офіційне обгрунтування того, чому лямбди вже є набором визначень функцій коду, що робить їх реалізацію непотрібною: "Отримане закриття не можна використовувати, якщо ця функція не активна ... Крім того, ці пропозиції не мають можливості скопіювати локальні змінні в закриття. " Я припускаю, що ви запитуєте, чому ваш аналіз додаткової складності, покладеної на компілятори, не був відповіддю, яку я прийняв. Якщо так: Ви говорите про складність чогось, що вже виконано лямбдами, у визначеннях коду можна чітко реалізувати точно так само, як лямбди.
Джонатан Мі

31

Ну, відповідь - "історичні причини". У C ви могли б мати декларації функцій в області блоку, і дизайнери C ++ не бачили вигоди у видаленні цієї опції.

Прикладом використання може бути:

#include <iostream>

int main()
{
    int func();
    func();
}

int func()
{
    std::cout << "Hello\n";
}

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


10
"Це, як правило, вважається поганою ідеєю" - необхідне цитування.
Річард Ходжес,

4
@RichardHodges: Ну, декларації функцій належать до файлів заголовків, а реалізація у файлах .c або .cpp, тому наявність цих декларацій усередині визначень функцій порушує будь-яку з цих двох рекомендацій.
MSalters

2
Як це заважає декларації відрізнятися від визначення?
Річард Ходжес,

1
@JonathanMee: Я кажу, що якщо декларація, яку ви використовуєте, недоступна там, де визначена функція, компілятор може не перевіряти, чи відповідає декларація визначенню. Отже, ви можете мати локальну декларацію some_type f();та визначення в іншій одиниці перекладу another_type f() {...}. Компілятор не може сказати вам, що вони не збігаються, і дзвінок fіз неправильним оголошенням дасть невизначену поведінку. Тож є гарною ідеєю мати лише одне оголошення в заголовку та включати той заголовок, де визначена функція, а також де вона використовується.
Майк Сеймур,

6
Я думаю, що ви кажете, що звичайна практика розміщення оголошень функцій у файлах заголовків загалом корисна. Не думаю, що хтось із цим не погодиться. Я не бачу підстав для твердження, що декларування зовнішньої функції в обсязі функцій "загалом вважається поганою ідеєю".
Річард Ходжес,

23

У наведеному прикладі void two(int)оголошення оголошується як зовнішня функція, причому це оголошення є дійсним лише в межах mainфункції .

Це розумно, якщо ви хочете лише зробити ім’я twoдоступним всередині main(), щоб уникнути забруднення глобального простору імен у поточному блоці компіляції.

Приклад у відповідь на коментарі:

main.cpp:

int main() {
  int foo();
  return foo();
}

foo.cpp:

int foo() {
  return 0;
}

немає необхідності у файлах заголовків. компілювати та посилати на

c++ main.cpp foo.cpp 

він буде скомпільований і запущений, і програма поверне 0, як очікувалося.


Чи не twoдоведеться також визначати у файлі, тим самим спричиняючи забруднення?
Джонатан Мі

1
@JonathanMee ні, two()можна було б визначити в абсолютно іншому блоці компіляції.
Річард Ходжес,

Мені потрібна допомога, щоб зрозуміти, як це може працювати. Чи не потрібно було б включати заголовок, у якому він був оголошений? У який момент це буде оголошено, правда? Я просто не розумію, як ви можете визначити це в коді і якось не включити файл, який це оголошує?
Джонатан Мі

5
@JonathanMee У заголовках немає нічого особливого. Вони просто зручне місце для розміщення декларацій. Оголошення у функції настільки ж дійсне, як і оголошення в заголовку. Отже, ні, вам не потрібно буде включати заголовок того, до чого ви посилаєтесь (може навіть не бути заголовка взагалі).
Кубік,

1
@JonathanMee У мові C / C ++ визначення та реалізація - це одне й те саме. Ви можете оголошувати функцію скільки завгодно часто, але можете визначити її лише один раз. Оголошення не повинно бути у файлі, що закінчується на .h - у вас може бути файл use.cpp, який має функціональну панель, яка викликає foo (оголошує foo у своєму тілі), і файл provides.cpp, який визначає foo, і це буде працювати нормально, якщо ви не зіпсуєте крок зв’язування.
Cubic

19

Ви можете робити ці речі, значною мірою тому, що насправді це не все так складно зробити.

З точки зору компілятора, наявність оголошення функції всередині іншої функції є досить тривіальним для реалізації. Компілятору потрібен механізм, який дозволить оголошенням усередині функцій обробляти інші оголошення (наприклад, int x;) всередині функції.

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

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

Це залишає питання про те, чим відрізняється вкладена функція. Вкладена функція відрізняється тим, як вона впливає на генерацію коду. У мовах, що дозволяють вкладені функції (наприклад, Pascal), ви зазвичай очікуєте, що код у вкладеній функції має прямий доступ до змінних функції, в яку він вкладений. Наприклад:

int foo() { 
    int x;

    int bar() { 
        x = 1; // Should assign to the `x` defined in `foo`.
    }
}

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

int f() { 
   int x;
   int y;
   x = 1;
   y = x;
   return y;
}

Компілятор (припускаючи, що він не оптимізував зайвий код) може генерувати код для цього приблизно еквівалентно цьому:

stack_pointer -= 2 * sizeof(int);      // allocate space for local variables
x_offset = 0;
y_offset = sizeof(int);

stack_pointer[x_offset] = 1;                           // x = 1;
stack_pointer[y_offset] = stack_pointer[x_offset];     // y = x;
return_location = stack_pointer[y_offset];             // return y;
stack_pointer += 2 * sizeof(int);

Зокрема, має одне розташування, що вказує на початок блоку локальних змінних, і весь доступ до локальних змінних є як відступ від цього розташування.

З вкладеними функціями це вже не так - натомість функція має доступ не тільки до власних локальних змінних, але і до змінних, локальних до всіх функцій, у яких вона вкладена. Замість того, щоб просто мати один "stack_pointer", з якого він обчислює зміщення, йому потрібно повернутися вгору по стеку, щоб знайти stack_pointers локально до функцій, в яких він вкладений.

Тепер у тривіальному випадку це не все так страшно - якщо barвін вкладений всередину foo, тоді barможна просто шукати стек у попередньому вказівнику стека, щоб отримати доступ fooдо змінних. Правда?

Неправильно!Ну, бувають випадки, коли це може бути правдою, але це не обов'язково так. Зокрема,bar може бути рекурсивним, і в цьому випадку дане викликbarможливо, доведеться шукати якусь майже довільну кількість рівнів у резервному стеці, щоб знайти змінні оточуючої функції. Взагалі кажучи, вам потрібно зробити одну з двох речей: або ви поміщаєте додаткові дані в стек, щоб він міг здійснити пошук резервної копії стека під час виконання, щоб знайти кадр стека навколишньої функції, або ви фактично передаєте вказівник на фрейм стека навколишньої функції як прихований параметр для вкладеної функції. О, але тут також не обов’язково є лише одна оточуюча функція - якщо ви можете вкласти функції, ви, можливо, можете вкласти їх (більш-менш) довільно глибоко, тому вам слід бути готовими до передачі довільної кількості прихованих параметрів. Це означає, що у вас зазвичай виходить щось на зразок пов’язаного списку кадрів стека з оточуючими функціями,

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

Це складність, якої C уникав, забороняючи вкладені функції. Зараз, безумовно, правда, що нинішній компілятор C ++ - це зовсім інший вид звіра від старовинного компілятора C 1970-х. З такими речами, як множинне віртуальне успадкування, компілятор С ++ у будь-якому випадку повинен мати справу з речами того самого загального характеру (тобто пошук розташування змінної базового класу в таких випадках також може бути нетривіальним). На відсотках, підтримка вкладених функцій не додасть особливої ​​складності поточному компілятору C ++ (а деякі, такі як gcc, вже підтримують їх).

У той же час, це також рідко додає великої корисності. Зокрема, якщо ви хочете визначити щось, що діє як функція всередині функції, ви можете використовувати лямбда-вираз. Це насправді створює об'єкт (тобто екземпляр якогось класу), який перевантажує оператор виклику функції ( operator()), але все одно надає функціональні можливості. Це робить збір (або відсутність) даних із навколишнього контексту більш чітким, що дозволяє йому використовувати існуючі механізми, а не винаходити цілком новий механізм та набір правил для його використання.

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


5

Перше - це визначення функції, і воно не допускається. Очевидно, що wt - це використання розміщення визначення функції всередині іншої функції.

Але інші двійки - це лише декларації. Уявіть, вам потрібно використовувати int two(int bar);функцію всередині основного методу. Але це визначено нижче main()функції, тому оголошення функції всередині функції змушує вас використовувати цю функцію з оголошеннями.

Те саме стосується і третього. Оголошення класів усередині функції дозволяють використовувати клас усередині функції без надання відповідного заголовка або посилання.

int main()
{
    // This is legal, but why would I want this?
    int two(int bar);

    //Call two
    int x = two(7);

    class three {
        int m_iBar;
        public:
            three(int bar):m_iBar(13 + bar) {}
            operator int() {return m_iBar;}
    };

    //Use class
    three *threeObj = new three();

    return 0;
}

2
Що таке "уповільнення"? Ви маєте на увазі "декларація"?
Пітер Мортенсен,

4

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

Отже, щоб підсумувати відповідь:

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


Завдяки коментарю нижче:

Прототип функції масштабується до функції, в якій він оголошений, тож можна отримати більш акуратний глобальний простір імен - посилаючись на зовнішні функції / символи без #include.


метою є контроль сфери назви, щоб уникнути глобального забруднення простору імен.
Річард Ходжес,

Гаразд, гадаю, це корисно для ситуацій, коли ви хочете звернутися до зовнішніх функцій / символів, не забруднюючи глобальний простір імен #include! Дякуємо, що вказали на це. Я зроблю правку.
mr.pd

4

Насправді є один варіант використання, який можна уявити корисним. Якщо ви хочете переконатись, що викликається певна функція (і ваш код компілюється), незалежно від того, що декларує оточуючий код, ви можете відкрити власний блок і оголосити в ньому прототип функції. (Натхнення походить від Йоханнеса Шауба, https://stackoverflow.com/a/929902/3150802 , через TeKa, https://stackoverflow.com/a/8821992/3150802 ).

Це може бути особливо корисно, якщо вам доводиться включати заголовки, якими ви не керуєте, або якщо у вас є багаторядковий макрос, який може використовуватися в невідомому коді.

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

// somebody's header
void f();

// your code
{   int i;
    int f(); // your different f()!
    i = f();
    // ...
}

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


Тож допоможіть мені тут, де це можна fвизначити у вашому прикладі? Чи не отримаю я помилку перевизначення функції, оскільки вони відрізняються лише типом повернення?
Джонатан Мі

@JonathanMee hmmm ... f () можна визначити в іншій одиниці перекладу, я думав. Але, ймовірно, компонувальний компонент згубиться, якщо ви також зробите посилання проти передбачуваної бібліотеки, я гадаю, ви маєте рацію. Тож ви не можете цього зробити ;-) або, принаймні, доведеться ігнорувати кілька визначень.
Пітер - Відновити Моніку

Поганий приклад. Немає різниці між void f()і int f()в C ++, оскільки повертане значення функції не є частиною підпису функції в C ++. Змініть другу декларацію на, int f(int)і я приберу свій голос проти.
Девід Хаммен,

@DavidHammen Спробуйте скомпілювати i = f();після оголошення void f(). "Ніяких відмінностей" - це лише половина правди ;-). Я насправді використовував неперевантажувані функції "підписів", оскільки в іншому випадку вся обставина була б непотрібною в C ++, оскільки дві функції з різними типами / номерами параметрів могли б щасливо співіснувати.
Пітер - відновити Моніку

@DavidHammen Дійсно, прочитавши відповідь Шафіка, я вважаю, що у нас є три випадки: 1. Підпис відрізняється параметрами. У C ++ немає проблем, працюють прості перевантаження та найкращі правила відповідності. 2. Підпис абсолютно не відрізняється. Немає проблем на мовному рівні; функція вирішується шляхом прив'язки до бажаної реалізації. 3. Різниця є лише у типі повернення. Там є проблемою на рівні мови, як показано; вирішення перевантаження не працює; ми повинні оголосити функцію з іншим підписом та відповідним посиланням.
Пітер - Відновити Моніку

3

Це не відповідь на питання ОП, а скоріше відповідь на кілька коментарів.

Я не згоден із цими пунктами в коментарях та відповідях: 1, що вкладені декларації нібито нешкідливі, а 2, що вкладені визначення є марними.

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

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

private:
inline void bar(int abc)
{
    // Do the repeating operation
}

public: 
void foo()
{
    int a, b, c;
    bar(a);
    bar(b);
    bar(c);
}

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


1
Я думаю, це дає хороший підсумок мотивації мого запитання. Якщо ви подивитесь на оригінальну версію, я цитував MVP, але мені постійно відмовляють у коментарях (на моє власне запитання), коли кажуть, що MVP не має значення :( Я просто не можу зрозуміти, наскільки потенційно шкідливі в деклараціях коду все ще тут , але потенційно корисними у визначеннях коду ні. Я дав вам +1 за корисні приклади.
Джонатан Мі

2

Конкретно відповідаючи на це питання:

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

Тому що розглянемо цей код:

int main()
{
  int foo() {

    // Do something
    return 0;
  }
  return 0;
}

Запитання для дизайнерів мов:

  1. Повинен foo() бути доступним для інших функцій?
  2. Якщо так, то якою має бути його назва? int main(void)::foo()?
  3. (Зверніть увагу, що 2 не було б можливим на мові С, хто створює С ++)
  4. Якщо ми хочемо локальну функцію, у нас вже є спосіб - зробити її статичним членом локально визначеного класу. То чи варто додати ще один синтаксичний метод досягнення того самого результату? Навіщо це робити? Чи не збільшить це навантаження на підтримку розробників компілятора C ++?
  5. І так далі...

Очевидно, що така поведінка визначена для лямбд? Чому не функції, визначені в коді?
Джонатан Мі

Лямбда - це просто скорочення для запису об’єкта функції. Особливий випадок лямбди, який не фіксує аргументів, еквівалентний визначенню локальної функції, як і написання об’єкта функції, що не має членів даних.
Річард Ходжес,

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

@JonathanMee, якщо ви сильно до цього ставитесь, неодмінно подайте RFC до комітету зі стандартів c ++.
Річард Ходжес,

Відповідь Шафіка Ягмура висвітлював те, що вже робиться. Я особисто хотів би побачити скасування можливості оголошувати функції в коді, якщо вони не дозволяють нам їх визначати. Відповідь Річарда Ходжеса добре пояснює, чому нам все-таки потрібна можливість заявляти в декларації коду.
Джонатан Мі

1

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


Можливість оголошувати заголовки функцій всередині інших функцій, я знайшов корисним у наступному випадку:

void do_something(int&);

int main() {
    int my_number = 10 * 10 * 10;
    do_something(my_number);

    return 0;
}

void do_something(int& num) {
    void do_something_helper(int&); // declare helper here
    do_something_helper(num);

    // Do something else
}

void do_something_helper(int& num) {
    num += std::abs(num - 1337);
}

Що у нас тут? В основному, у вас є функція, яку передбачається викликати з main, тож ви робите те, що вперед оголошуєте її як нормальну. Але тоді ви розумієте, що цій функції також потрібна інша функція, яка допоможе їй у тому, що вона робить. Отож замість того, щоб оголошувати цю допоміжну функцію над основною, ви оголошуєте її всередині функції, яка її потребує, і тоді її можна викликати з цієї функції та лише цієї функції.

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


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

0

Оголошення вкладених функцій дозволено, ймовірно, для 1. Пересилання посилань 2. Щоб мати можливість оголосити вказівник на функції (функції) та обходити інші функції у обмеженому обсязі.

Визначення вкладених функцій заборонені, ймовірно, через такі проблеми, як 1. Оптимізація 2. Рекурсія (вкладені та вкладені визначені функції) 3. Повторне входження 4. Проблеми з паралельним та багатопотоковим доступом.

З мого обмеженого розуміння :)

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.