Подтвердить что ты не робот

Можем ли мы иметь функции внутри функций в C++?

Я имею в виду что-то вроде:

int main() 
{
  void a() 
  {
      // code
  }
  a();

  return 0;
}
4b9b3361

Ответ 1

Модерн C++ - Да, с лямбдами!

В текущих версиях C++ (C++ 11, C++ 14 и C++ 17) у вас могут быть функции внутри функций в форме лямбды:

int main() {
    // This declares a lambda, which can be called just like a function
    auto print_message = [](std::string message) 
    { 
        std::cout << message << "\n"; 
    };

    // Prints "Hello!" 10 times
    for(int i = 0; i < 10; i++) {
        print_message("Hello!"); 
    }
}

Лямбда также может изменять локальные переменные посредством ** захвата по ссылке *. С помощью захвата по ссылке лямбда имеет доступ ко всем локальным переменным, объявленным в области лямбда. Он может изменять и изменять их в обычном режиме.

int main() {
    int i = 0;
    // Captures i by reference; increments it by one
    auto addOne = [&] () {
        i++; 
    };

    while(i < 10) {
        addOne(); //Add 1 to i
        std::cout << i << "\n";
    }
}

C++ 98 и C++ 03 - Не напрямую, но да со статическими функциями внутри локальных классов

C++ не поддерживает это напрямую.

Тем не менее, у вас могут быть локальные классы, и у них могут быть функции (non- static или static), так что вы можете получить это в некоторой степени, хотя это будет немного клуджем:

int main() // it int, dammit!
{
  struct X { // struct as good as class
    static void a()
    {
    }
  };

  X::a();

  return 0;
}

Однако я бы поставил под сомнение практику. Все знают (хорошо, теперь, когда вы это делаете, в любом случае :)) C++ не поддерживает локальные функции, поэтому они привыкли их не иметь. Однако они не используются для этого клуджа. Я потратил бы довольно много времени на этот код, чтобы убедиться, что он действительно только для локальных функций. Нехорошо.

Ответ 2

Для всех намерений и целей C++ поддерживает это через лямбды: 1

int main() {
    auto f = []() { return 42; };
    std::cout << "f() = " << f() << std::endl;
}

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

За кулисами f - это функциональный объект (т.е. Объект типа, который предоставляет operator()). Тип объекта функции создается компилятором на основе лямбды.


1, так как C++ 11

Ответ 3

Локальные классы уже упомянуты, но вот способ позволить им проявиться еще больше как локальные функции, используя перегрузку operator() и анонимный класс:

int main() {
    struct {
        unsigned int operator() (unsigned int val) const {
            return val<=1 ? 1 : val*(*this)(val-1);
        }
    } fac;

    std::cout << fac(5) << '\n';
}

Я не советую использовать это, это просто забавный трюк (может сделать, но imho не должен).


2014 Обновление:

С появлением С++ 11 назад вы можете теперь иметь локальные функции, синтаксис которых немного напоминает JavaScript:

auto fac = [] (unsigned int val) {
    return val*42;
};

Ответ 4

Нет.

Что вы пытаетесь сделать?

обходной путь:

int main(void)
{
  struct foo
  {
    void operator()() { int a = 1; }
  };

  foo b;
  b(); // call the operator()

}

Ответ 5

Старый ответ: Вы можете, вроде как, но вы должны обмануть и использовать фиктивный класс:

void moo()
{
    class dummy
    {
    public:
         static void a() { printf("I'm in a!\n"); }
    };

    dummy::a();
    dummy::a();
}

Более новый ответ: более новые версии C++ также поддерживают лямбды, чтобы сделать это лучше/правильно. Смотрите ответы выше на странице.

Ответ 6

Нет, это не разрешено. Ни С++, ни С++ не поддерживают эту функцию по умолчанию, однако TonyK указывает (в комментариях), что есть расширения для компилятора GNU C, которые активируют это поведение в C.

Ответ 7

Вы не можете определить свободную функцию внутри другого в С++.

Ответ 8

Как уже упоминалось, вы можете использовать вложенные функции, используя расширения gnu-языка в gcc. Если вы (или ваш проект) привязаны к gcc toolchain, ваш код будет в основном переносимым в разных архитектурах, нацеленных на компилятор gcc.

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


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

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

Печально, что C/С++ не поддерживает такие функции, как стандарт. Большинство вариантов pascal и Ada do (почти все языки на Алголе). То же самое с JavaScript. То же самое с современными языками, такими как Scala. То же самое с почтенными языками, такими как Erlang, Lisp или Python.

И так же, как и с C/С++, к сожалению, Java (с которой я зарабатываю большую часть своей жизни) не делает.

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

Короткий ответ: Нет.

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

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

В программном обеспечении (и в любой инженерной дисциплине) моделирование - это вопрос компромиссов. Таким образом, в реальной жизни будут оправданы исключения из этих правил (или, скорее, рекомендации). Однако действуйте осторожно.

Ответ 9

Все эти трюки просто выглядят (более или менее) как локальные функции, но они не работают так. В локальной функции вы можете использовать локальные переменные суперфункций. Это своего рода полу-глобалы. Без этих трюков это можно сделать. Самый близкий лямбда-трюк от С++ 0x, но его закрытие связано во время определения, а не время использования.

Ответ 10

Вы не можете иметь локальные функции в C++. Однако в C++ 11 есть лямбды. Лямбды - это переменные, которые работают как функции.

Лямбда имеет тип std::function (на самом деле это не совсем верно, но в большинстве случаев можно предположить, что это так). Чтобы использовать этот тип, вам нужно #include <functional>. std::function является шаблоном, принимающим в качестве аргумента шаблона тип возвращаемого значения и типы аргумента, с синтаксисом std::function<ReturnType(ArgumentTypes). Например, std::function<int(std::string, float)> - это лямбда-выражение, возвращающее int и принимающее два аргумента, один std::string и один float. Наиболее распространенным является std::function<void()>, который ничего не возвращает и не принимает аргументов.

Как только лямбда объявлена, она вызывается так же, как и обычная функция, используя синтаксис lambda(arguments).

Чтобы определить лямбду, используйте синтаксис [captures](arguments){code} (есть другие способы сделать это, но я не буду здесь их упоминать). arguments - это аргументы, которые принимает лямбда, а code - это код, который должен выполняться при вызове лямбды. Обычно вы помещаете [=] или [&] качестве снимков. [=] означает, что вы перехватываете все переменные в области, в которой значение определяется значением, что означает, что они сохранят значение, которое они имели при объявлении лямбды. [&] означает, что вы захватываете все переменные в области видимости по ссылке, что означает, что они всегда будут иметь свое текущее значение, но если они будут удалены из памяти, программа потерпит крах. Вот некоторые примеры:

#include <functional>
#include <iostream>

int main(){
    int x = 1;

    std::function<void()> lambda1 = [=](){
        std::cout << x << std::endl;
    };
    std::function<void()> lambda2 = [&](){
        std::cout << x << std::endl;
    };

    x = 2;
    lambda1();    //Prints 1 since that was the value of x when it was captured and x was captured by value with [=]
    lambda2();    //Prints 2 since that the current value of x and x was captured by value with [&]

    std::function<void()> lambda3 = [](){}, lambda4 = [](){};    //I prefer to initialize these since calling an uninitialized lambda is undefined behavior.
                                                                 //[](){} is the empty lambda.

    {
        int y = 3;    //y will be deleted from the memory at the end of this scope
        lambda3 = [=](){
            std::cout << y << endl;
        };
        lambda4 = [&](){
            std::cout << y << endl;
        };
    }

    lambda3();    //Prints 3, since that the value y had when it was captured

    lambda4();    //Causes the program to crash, since y was captured by reference and y doesn't exist anymore.
                  //This is a bit like if you had a pointer to y which now points nowhere because y has been deleted from the memory.
                  //This is why you should be careful when capturing by reference.

    return 0;
}

Вы также можете захватить определенные переменные, указав их имена. Просто указав их имя, вы поймете их по значению, указав их имя с & перед тем, как захватите их по ссылке. Например, [=, &foo] будет захватывать все переменные по значению, кроме foo который будет захвачен по ссылке, а [&, foo] захватит все переменные по ссылке, кроме foo который будет захвачен по значению. Вы также можете захватывать только определенные переменные, например, [&foo] захватывает foo по ссылке и не захватывает другие переменные. Вы также можете перехватить переменные вообще без использования []. Если вы попытаетесь использовать переменную в лямбде, которую вы не захватили, она не скомпилируется. Вот пример:

#include <functional>

int main(){
    int x = 4, y = 5;

    std::function<void(int)> myLambda = [y](int z){
        int xSquare = x * x;    //Compiler error because x wasn't captured
        int ySquare = y * y;    //OK because y was captured
        int zSquare = z * z;    //OK because z is an argument of the lambda
    };

    return 0;
}

Вы не можете изменить значение переменной, которая была захвачена значением внутри лямбды (переменные, захваченные значением, имеют тип const внутри лямбды). Для этого вам нужно захватить переменную по ссылке. Вот пример:

#include <functional>

int main(){
    int x = 3, y = 5;
    std::function<void()> myLambda = [x, &y](){
        x = 2;    //Compiler error because x is captured by value and so it of type const int inside the lambda
        y = 2;    //OK because y is captured by reference
    };
    x = 2;    //This is of course OK because we're not inside the lambda
    return 0;
}

Кроме того, вызов неинициализированных лямбда-выражений является неопределенным поведением и обычно приводит к сбою программы. Например, никогда не делайте так:

std::function<void()> lambda;
lambda();    //Undefined behavior because lambda is uninitialized

Примеры

Вот код для того, что вы хотели сделать в своем вопросе, используя лямбды:

#include <functional>    //Don't forget this, otherwise you won't be able to use the std::function type

int main(){
    std::function<void()> a = [](){
        // code
    }
    a();
    return 0;
}

Вот более продвинутый пример лямбды:

#include <functional>    //For std::function
#include <iostream>      //For std::cout

int main(){
    int x = 4;
    std::function<float(int)> divideByX = [x](int y){
        return (float)y / (float)x;    //x is a captured variable, y is an argument
    }
    std::cout << divideByX(3) << std::endl;    //Prints 0.75
    return 0;
}

Ответ 11

Позвольте мне разместить здесь решение для С++ 03, которое я считаю максимально возможным. *

#define DECLARE_LAMBDA(NAME, RETURN_TYPE, FUNCTION) \
    struct { RETURN_TYPE operator () FUNCTION } NAME;

...

int main(){
  DECLARE_LAMBDA(demoLambda, void, (){ cout<<"I'm a lambda!"<<endl; });
  demoLambda();

  DECLARE_LAMBDA(plus, int, (int i, int j){
    return i+j;
  });
  cout << "plus(1,2)=" << plus(1,2) << endl;
  return 0;
}

(*) в мире С++ с использованием макросов никогда не считается чистым.

Ответ 12

Но мы можем объявить функцию внутри main():

int main()
{
    void a();
}

Несмотря на то, что синтаксис верен, иногда это может привести к "наиболее неприятному анализу":

#include <iostream>


struct U
{
    U() : val(0) {}
    U(int val) : val(val) {}

    int val;
};

struct V
{
    V(U a, U b)
    {
        std::cout << "V(" << a.val << ", " << b.val << ");\n";
    }
    ~V()
    {
        std::cout << "~V();\n";
    }
};

int main()
{
    int five = 5;
    V v(U(five), U());
}

= > нет выхода программы.

(Только предупреждение Clang после компиляции).

Самый неприятный синтаксис С++ снова

Ответ 13

Когда вы пытаетесь реализовать функцию в другом теле функции, вы должны получить эту error как недопустимое определение:

error C2601: 'a' : local function definitions are illegal
IntelliSense: expected a ';'

Так что не пытайтесь снова.