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

Когда я читал это объяснение о 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?

9 ответов

Решение

Объяснение предполагает, что существует некоторая разумная реализация 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
}

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

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. Это некоторый код, который может фактически служить цели, в отличие от других примеров.

int& foo();

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

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

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

foo() = 42;

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

std::cout << foo();

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

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

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

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

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

int main()
{
    ++foo();  
    std::cout << foo() << '\n';
}

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

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

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

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

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,

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

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

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

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

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

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

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

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

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

и преуспеет, изменив значение х, говоря:

foo() = 10;

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

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

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

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

Другие вопросы по тегам