Можем ли мы иметь функции внутри функций?
Я имею в виду что-то вроде:
int main()
{
void a()
{
// code
}
a();
return 0;
}
14 ответов
Нет, C++ не поддерживает это.
Изменить: этот ответ старый. Между тем, в C++11 есть лямбды, которые могут достичь аналогичного результата - см. Ответы ниже.
Тем не менее, вы можете иметь локальные классы, и они могут иметь функции (не static
или же static
), так что вы можете получить это в некоторой степени, хотя это немного клудж:
int main() // it's int, dammit!
{
struct X { // struct's as good as class
static void a()
{
}
};
X::a();
return 0;
}
Однако я бы поставил под сомнение практику. Все знают (ну, теперь, когда вы делаете, в любом случае :)
) C++ не поддерживает локальные функции, поэтому они привыкли их не иметь. Однако они не используются для этого клуджа. Я потратил бы довольно много времени на этот код, чтобы убедиться, что он действительно только для локальных функций. Нехорошо.
Для всех намерений и целей C++ поддерживает это через лямбда-выражения:1
int main() {
auto f = []() { return 42; };
std::cout << "f() = " << f() << std::endl;
}
Вот, f
является лямбда-объектом, который действует как локальная функция в main
, Захваты могут быть определены, чтобы позволить функции получить доступ к локальным объектам.
За кулисами, f
является функциональным объектом (т.е. объектом типа, который обеспечивает operator()
). Тип объекта функции создается компилятором на основе лямбды.
1 с C++11
Локальные классы уже упоминались, но вот способ, позволяющий им появляться в большей степени как локальными функциями, используя перегрузку 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 года:
С появлением C++11 некоторое время назад вы можете иметь локальные функции, синтаксис которых немного напоминает JavaScript:
auto fac = [] (unsigned int val) {
return val*42;
};
Вы не можете иметь локальные функции в 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's 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's 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's 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;
}
Нет.
Что ты пытаешься сделать?
обходной путь:
int main(void)
{
struct foo
{
void operator()() { int a = 1; }
};
foo b;
b(); // call the operator()
}
Старый ответ: Вы можете, вроде как, но вы должны обмануть и использовать фиктивный класс:
void moo()
{
class dummy
{
public:
static void a() { printf("I'm in a!\n"); }
};
dummy::a();
dummy::a();
}
Более новый ответ: более новые версии C++ также поддерживают лямбды, чтобы сделать это лучше / правильно. Смотрите ответы выше на странице.
Нет, это не разрешено Ни C, ни C++ не поддерживают эту функцию по умолчанию, однако TonyK указывает (в комментариях), что существуют расширения для компилятора GNU C, которые включают это поведение в C.
Вы не можете определить свободную функцию внутри другой в C++.
Как уже упоминали другие, вы можете использовать вложенные функции, используя расширения языка gnu в gcc. Если вы (или ваш проект) придерживаетесь цепочки инструментов gcc, ваш код будет в основном переносимым между различными архитектурами, предназначенными для компилятора gcc.
Однако, если существует возможное требование, что вам может понадобиться компилировать код с другой цепочкой инструментов, я бы держался подальше от таких расширений.
Я бы также осторожно подходил к использованию вложенных функций. Они являются прекрасным решением для управления структурой сложных, но сплоченных блоков кода (фрагменты которых не предназначены для внешнего / общего использования.) Они также очень полезны для контроля загрязнения пространства имен (очень реальная проблема с естественно сложным / длинные занятия по многословным языкам.)
Но, как и все, они могут быть открыты для злоупотреблений.
Печально, что C/C++ не поддерживает такие функции, как стандарт. Большинство Паскаля и Ада делают (почти все языки на основе Алгола). То же самое с JavaScript. То же самое с современными языками, такими как Scala. То же самое с почтенными языками, такими как Erlang, Lisp или Python.
И, как и в случае с C/C++, к сожалению, Java (на которой я зарабатываю большую часть своей жизни) - нет.
Я упоминаю здесь Java, потому что вижу несколько плакатов, предлагающих использование классов и методов классов в качестве альтернативы вложенным функциям. И это также типичный обходной путь в Java.
Краткий ответ: Нет.
Это приводит к созданию искусственной, ненужной сложности в иерархии классов. При всех равных условиях идеалом является наличие иерархии классов (и охватывающих ее пространств имен и областей), представляющих фактическую область как можно более простой.
Вложенные функции помогают справиться с "частной" сложностью внутри функции. Не имея этих возможностей, нужно стараться не распространять эту "частную" сложность на свою классовую модель.
В программном обеспечении (и в любой инженерной дисциплине) моделирование является вопросом компромисса. Таким образом, в реальной жизни будут оправданные исключения из этих правил (или, скорее, руководящих принципов). Продолжайте с осторожностью, хотя.
Все эти приемы выглядят (более или менее) как локальные функции, но они не работают так. В локальной функции вы можете использовать локальные переменные ее суперфункций. Это своего рода полуглобальные. Ни один из этих трюков не может этого сделать. Наиболее близким является лямбда-трюк из C++0x, но его закрытие ограничено временем определения, а не временем использования.
Позвольте мне опубликовать здесь решение для C++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;
}
(*) в мире C++ использование макросов никогда не считается чистым.
Но мы можем объявить функцию внутри 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 предупреждение после компиляции).
Да, и вы можете делать с ними то, что не поддерживают даже C++20 Lambdas. А именно, чисто рекурсивные вызовы самих себя и связанных функций.
Например, гипотеза Коллатца состоит в том, что некая простая рекурсивная функция в конечном итоге выдаст «1» для ЛЮБОГО положительного целого числа N. Используя явную локальную структуру и функции, я могу написать одну автономную функцию для запуска теста для любого «N ".
constexpr std::optional<int> testCollatzConjecture(int N) {
struct CollatzCallbacks {
constexpr static int onEven(int n) {
return recurse(n >> 1); // AKA "n/2"
}
constexpr static int onOdd(int n) {
if(n==1) return 1; // Break recursion. n==1 is only possible when n is odd.
return recurse(3 * n + 1);
}
constexpr static int recurse(int n) {
return (n%2) ? onOdd(n) : onEven(n); // (n%2) == 1 when n is odd
}
};
// Error check
if(N < 0) return {};
// Recursive call.
return CollatzCallbacks::recurse(N);
}
Обратите внимание на некоторые вещи, которые здесь не могут сделать даже лямбда-выражения С++20:
- Мне не нужны std::function<> клеи ИЛИ лямбда-захваты ("[&]") только для того, чтобы мои локальные рекурсивные функции вызывали себя или друг друга. Мне нужно было 3 обычные функции с именами, и это все, что мне нужно было написать.
- Мой код более читабелен и (из-за (1)) также будет работать намного быстрее.
- Я четко отделяю рекурсивную логику в «CollatzCallbacks» от остальной части «testCollatzConjecture». Все это работает в изолированной песочнице.
- Я смог сделать все «constexpr» и без состояния, поэтому все это может запускаться во время компиляции для любого постоянного значения. Насколько мне известно, мне понадобится С++23 только для выполнения рекурсивной части с лямбда-выражениями без состояния.
Помните: лямбда-функции на самом деле представляют собой просто локальные структуры, сгенерированные компилятором, такие как «CollatzCallbacks», только они безымянные и имеют только одну функцию-член «operator()». Вы всегда можете написать более сложные локальные структуры и функции напрямую, особенно в таких случаях, когда они вам действительно нужны.
Когда вы пытаетесь реализовать функцию в другом теле функции, вы должны получить это error
как незаконное определение:
error C2601: 'a' : local function definitions are illegal
IntelliSense: expected a ';'
Так что не пытайтесь снова.