Что означает «int & foo ()» в C ++?

114

Когда я читал это объяснение lvalues ​​и rvalues, мне запомнились следующие строки кода:

int& foo();
foo() = 42; // OK, foo() is an lvalue

Я пробовал это в g ++, но компилятор говорит «неопределенная ссылка на foo ()». Если я добавлю

int foo()
{
  return 2;
}

int main()
{
  int& foo();
  foo() = 42;
}

Он компилируется нормально, но при его запуске возникает ошибка сегментации . Просто линия

int& foo();

сам по себе компилируется и запускается без каких-либо проблем.

Что означает этот код? Как можно присвоить значение вызову функции и почему это не rvalue?

Sossisos
источник
25
Вы не присваиваете значение вызову функции , вы присваиваете значение возвращаемой ссылке .
Фредерик Хамиди
3
@ FrédéricHamidi присваивает значение объекту, на который ссылается возвращенная ссылка
MM
3
Да, это псевдоним объекта; ссылка не является объектом
MM
2
Объявления локальных функций @RoyFalk - это чушь, лично я был бы рад, если бы они были удалены из языка. (Без лямбд, конечно)
MM
4
Для этого уже есть ошибка GCC .
jotik

Ответы:

168

Объяснение предполагает, что существует некая разумная реализация, для fooкоторой возвращается ссылка lvalue на действительный int.

Такой реализацией может быть:

int a = 2; //global variable, lives until program termination

int& foo() {
    return a;
} 

Теперь, поскольку fooвозвращает ссылку lvalue, мы можем присвоить что-нибудь возвращаемому значению, например:

foo() = 42;

Это обновит глобал aсо значением 42, которое мы можем проверить, обратившись к переменной напрямую или вызвав fooснова:

int main() {
    foo() = 42;
    std::cout << a;     //prints 42
    std::cout << foo(); //also prints 42
}
ТартанЛлама
источник
Разумно, как в operator []? Или какие-то другие методы доступа к членам?
Ян Дорняк
8
Учитывая путаницу в отношении ссылок, вероятно, лучше всего удалить статические локальные переменные из образцов. Инициализация добавляет ненужную сложность, что затрудняет обучение. Просто сделайте это глобальным.
Mooing Duck
72

Все остальные ответы объявляют статику внутри функции. Я думаю, это может вас запутать, поэтому взгляните на это:

int& highest(int  & i, int  & j)
{
    if (i > j)
    {
        return i;
    }
    return j;
}

int main()
{
    int a{ 3};
    int b{ 4 };
    highest(a, b) = 11;
    return 0;
}

Поскольку highest()возвращает ссылку, вы можете присвоить ей значение. При его запуске значение bбудет изменено на 11. Если вы изменили инициализацию a, например, на 8, то aоно будет изменено на 11. Это некоторый код, который действительно может служить определенной цели, в отличие от других примеров.

Кейт Грегори
источник
5
Есть ли причина писать int a {3} вместо int a = 3? Мне этот синтаксис не подходит.
Алексей Петренко
6
@AlexPetrenko это универсальная инициализация. Я использовал это в примере, который у меня был под рукой. Ничего неуместного в этом
Кейт Грегори
3
Я знаю что это. a = 3 кажется намного чище. Личное предпочтение)
Алексей Петренко
Подобно тому, как объявление static внутри функции может сбить с толку некоторых людей, я также считаю, что использование универсальной инициализации с такой же вероятностью.
ericcurtin
@AlekseiPetrenko В случае int a = 1.3 он неявно преобразуется в a = 1. Тогда как int a {1.3} приводит к ошибке: сужающее преобразование.
Сатвик,
32
int& foo();

Объявляет функцию с именем foo, которая возвращает ссылку на int. Эти примеры не позволяют дать вам определение функции, которую вы могли бы скомпилировать. Если мы используем

int & foo()
{
    static int bar = 0;
    return bar;
}

Теперь у нас есть функция, которая возвращает ссылку на bar. так как bar staticон будет жить после вызова функции, поэтому возврат ссылки на него безопасен. Теперь, если мы сделаем

foo() = 42;

Что происходит, мы назначаем 42, barпоскольку мы назначаем ссылку, а ссылка является просто псевдонимом для bar. Если мы снова вызовем функцию, например

std::cout << foo();

Он напечатает 42, так как мы установили barэто выше.

NathanOliver
источник
15

int &foo();объявляет функцию, вызываемую foo()с возвращаемым типом int&. Если вы вызовете эту функцию без указания тела, вы, вероятно, получите ошибку неопределенной ссылки.

Во второй попытке вы предоставили функцию int foo(). Это имеет другой тип возвращаемого значения по сравнению с функцией, объявленной int& foo();. Итак, у вас есть два объявления одного и того же, fooкоторые не совпадают, что нарушает правило одного определения, вызывая неопределенное поведение (диагностика не требуется).

Если что-то работает, удалите объявление локальной функции. Как вы видели, они могут вести к бесшумному неопределенному поведению. Вместо этого используйте только объявления функций вне любой функции. Ваша программа могла бы выглядеть так:

int &foo()
{
    static int i = 2;
    return i;
}  

int main()
{
    ++foo();  
    std::cout << foo() << '\n';
}
ММ
источник
10

int& foo();это функция, возвращающая ссылку на int. Ваша предоставленная функция возвращается intбез ссылки.

Вы можете сделать

int& foo()
{
    static int i = 42;
    return i;
}

int main()
{
    int& foo();
    foo() = 42;
}
Джарод42
источник
7

int & foo();означает, что foo()возвращает ссылку на переменную.

Рассмотрим этот код:

#include <iostream>
int k = 0;

int &foo()
{
    return k;
}

int main(int argc,char **argv)
{
    k = 4;
    foo() = 5;
    std::cout << "k=" << k << "\n";
    return 0;
}

Этот код печатает:

$ ./a.out k = 5

Потому что foo()возвращает ссылку на глобальную переменную k.

В пересмотренном коде вы приводите возвращаемое значение к ссылке, которая в таком случае недействительна.

vy32
источник
5

В этом контексте & означает ссылку, поэтому foo возвращает ссылку на int, а не на int.

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

Итак, чтобы подвести итог, вы не присваиваете значение вызову функции - вы используете функцию для получения ссылки, а затем присваиваете значение, на которое ссылается, новому значению. Легко представить, что все происходит сразу, но на самом деле компьютер делает все в точном порядке.

Если вам интересно - причина, по которой вы получаете segfault, заключается в том, что вы возвращаете числовой литерал '2' - так что это точная ошибка, которую вы получили бы, если бы вы определяли const int, а затем пытались изменить его стоимость.

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

Дар Бретт
источник
4

Пример кода на связанной странице - это просто объявление фиктивной функции. Он не компилируется, но если бы у вас была определена какая-то функция, она работала бы в целом. Пример означал: «Если у вас есть функция с этой подписью, вы можете использовать ее так».

В вашем примере fooявно возвращается lvalue на основе подписи, но вы возвращаете rvalue, преобразованное в lvalue. Это явно обречено на провал. Вы могли сделать:

int& foo()
{
    static int x;
    return x;
}

и преуспеет, изменив значение x, когда скажет:

foo() = 10;
Ледяной огонь
источник
3

У вас есть функция foo (), которая возвращает ссылку на целое число.

Итак, предположим, что изначально foo вернул 5, а позже в вашей основной функции, вы говорите foo() = 10;, затем распечатает foo, он напечатает 10 вместо 5.

Надеюсь это имеет смысл :)

Я тоже новичок в программировании. Интересно видеть такие вопросы, которые заставляют задуматься! :)

psj01
источник