Що означає "int & foo ()" в C ++?


114

Читаючи це пояснення про значення і rvalues, ці рядки коду залишилися мені:

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

Я спробував це в g ++, але компілятор каже "невизначене посилання на foo ()". Якщо я додам

int foo()
{
  return 2;
}

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

Він чудово компілює, але запускаючи це дає помилку сегментації . Просто лінія

int& foo();

сам по собі і компілює, і працює без проблем.

Що означає цей код? Як ви можете призначити значення функційному виклику і чому це не є оцінкою?


25
Ви не призначаєте значення виклику функції , ви присвоюєте значення посиланню, яке воно повертає .
Фредерік Хаміді

3
@ FrédéricHamidi, що присвоює значення об'єкту, на який посилається повернута посилання,
MM

3
Так, це псевдонім для об'єкта; посилання не є об'єктом
ММ

2
Декларації локальних функцій @RoyFalk - це віспа, особисто я радий бачити їх видаленими з мови. (Без урахування лямбдів звичайно)
ММ

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
}

Розумно, як в операторі []? Або якийсь інший член доступу до методів?
Ян Дорняк

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 просто відчуває себе набагато чистішим. Особисті переваги)
Олексій Петренко

Так само, як оголошення статики всередині функції може бентежити деяких людей, я також вважаю, що використання універсальної ініціалізації так само ймовірно.
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. оскільки бар є, staticвін буде жити після виклику функції, тому повернення посилання на нього безпечне. Тепер, якщо ми це зробимо

foo() = 42;

Що трапляється, ми присвоюємо 42, barоскільки ми присвоюємо посилання, а посилання є лише псевдонімом bar. Якщо ми знову назвемо функцію, як

std::cout << foo();

Це було б надруковано 42, оскільки ми встановили barце вище.


15

int &foo();оголошує функцію, викликану foo()з типом returnint& . Якщо ви зателефонуєте на цю функцію, не надаючи тіло, то, швидше за все, ви отримаєте невизначену помилку посилання.

У другій спробі ви надали функцію 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;
}

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.

У вашому переглянутому коді ви кидаєте повернене значення до посилання, яке недійсне.


5

У цьому контексті & означає посилання - тому foo повертає посилання на int, а не на int.

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

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

Якщо вам цікаво - причина, коли ви отримуєте segfault, полягає в тому, що ви повертаєте числову буквальну цифру "2" - тож ви отримаєте точну помилку, якби визначити const int та спробувати змінити його значення.

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


4

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

У вашому прикладі fooчітко повертається значення на основі підпису, але ви повертаєте rvalue, який перетворюється на lvalue. Це однозначно визначається невдачею. Ви можете зробити:

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

і досягти успіху, змінивши значення x, сказавши:

foo() = 10;

3

Функція, яку ви маєте, foo () - це функція, яка повертає посилання на ціле число.

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

Сподіваюся, це має сенс :)

Я також новачок у програмуванні. Цікаво побачити подібні питання, які змушують задуматися! :)

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