“Int & foo ()” có nghĩa là gì trong C ++?


114

Trong khi đọc phần giải thích này về giá trị và giá trị, những dòng mã này đã khiến tôi gặp phải:

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

Tôi đã thử nó trong g ++, nhưng trình biên dịch nói "tham chiếu không xác định đến foo ()". Nếu tôi thêm

int foo()
{
  return 2;
}

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

Nó biên dịch tốt, nhưng chạy nó gây ra lỗi phân đoạn . Chỉ dòng

int& foo();

tự nó cả biên dịch và chạy mà không gặp bất kỳ vấn đề gì.

Mã này có nghĩa là gì? Làm thế nào bạn có thể gán một giá trị cho một lời gọi hàm, và tại sao nó không phải là một rvalue?


25
Bạn không gán giá trị cho một lời gọi hàm , bạn đang gán một giá trị cho tham chiếu mà nó trả về .
Frédéric Hamidi

3
@ FrédéricHamidi chỉ định một giá trị cho đối tượng mà tham chiếu được trả về đang đề cập đến
MM

3
Vâng, đó là một bí danh cho đối tượng; tham chiếu không phải là đối tượng
MM

2
Khai báo hàm cục bộ @RoyFalk là một lỗi, cá nhân tôi rất vui khi thấy chúng bị xóa khỏi ngôn ngữ. (Tất nhiên không bao gồm lambdas)
MM

4
Đã có một lỗi GCC cho việc này .
jotik

Câu trả lời:


168

Lời giải thích là giả định rằng có một số triển khai hợp lý footrả về một tham chiếu giá trị thành một giá trị hợp lệ int.

Cách triển khai như vậy có thể là:

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

int& foo() {
    return a;
} 

Bây giờ, vì footrả về một tham chiếu giá trị, chúng ta có thể gán một cái gì đó cho giá trị trả về, như sau:

foo() = 42;

Điều này sẽ cập nhật toàn cục avới giá trị 42mà chúng ta có thể kiểm tra bằng cách truy cập trực tiếp vào biến hoặc gọi foolại:

int main() {
    foo() = 42;
    std::cout << a;     //prints 42
    std::cout << foo(); //also prints 42
}

Hợp lý như trong toán tử []? Hoặc một số phương thức gia nhập thành viên khác?
Jan Dorniak

8
Do sự nhầm lẫn về các lần lặp lại, có lẽ tốt nhất bạn nên xóa các biến cục bộ tĩnh khỏi các mẫu. Việc khởi tạo thêm sự phức tạp không cần thiết, đây là một trở ngại cho việc học. Chỉ cần biến nó trở thành toàn cầu.
Mooing Duck

72

Tất cả các câu trả lời khác khai báo một tĩnh bên trong hàm. Tôi nghĩ rằng điều đó có thể làm bạn bối rối, vì vậy hãy xem điều này:

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()trả về một tham chiếu, bạn có thể gán một giá trị cho nó. Khi điều này chạy, bsẽ được thay đổi thành 11. Nếu bạn thay đổi phần khởi tạo a, ví dụ, 8, sau đó asẽ được thay đổi thành 11. Đây là một số mã có thể thực sự phục vụ một mục đích, không giống như các ví dụ khác.


5
Có lý do gì để viết int a {3} thay vì int a = 3 không? Cú pháp này có vẻ không phù hợp với tôi.
Aleksei Petrenko

6
@AlexPetrenko đó là khởi tạo phổ quát. Tôi tình cờ sử dụng nó trong ví dụ mà tôi có. Không có gì không phù hợp về nó
Kate Gregory

3
Tôi biết đây là gì. a = 3 chỉ cảm thấy sạch hơn rất nhiều. Sở thích cá nhân)
Aleksei Petrenko

Giống như việc khai báo static bên trong một hàm có thể khiến một số người bối rối, tôi cũng tin rằng việc sử dụng tính năng khởi tạo phổ quát cũng có khả năng xảy ra.
ericcurtin

@AlekseiPetrenko Trong trường hợp int a = 1.3, nó hoàn toàn chuyển đổi thành a = 1. Trong khi int a {1.3} dẫn đến lỗi: thu hẹp chuyển đổi.
Sathvik

32
int& foo();

Khai báo một hàm có tên foo trả về một tham chiếu đến một int. Điều mà các ví dụ không làm được là cung cấp cho bạn định nghĩa về hàm đó mà bạn có thể biên dịch. Nếu chúng ta sử dụng

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

Bây giờ chúng ta có một hàm trả về một tham chiếu tới bar. vì thanh là staticnó sẽ hoạt động sau khi gọi hàm nên việc trả về một tham chiếu cho nó là an toàn. Bây giờ nếu chúng ta làm

foo() = 42;

Điều gì xảy ra là chúng tôi gán 42 cho barvì chúng tôi gán cho tham chiếu và tham chiếu chỉ là bí danh cho bar. Nếu chúng ta gọi lại hàm như

std::cout << foo();

Nó sẽ in 42 kể từ khi chúng tôi đặt barở trên.


15

int &foo();khai báo một hàm được gọi foo()với kiểu trả về int&. Nếu bạn gọi hàm này mà không cung cấp nội dung thì bạn có khả năng gặp phải lỗi tham chiếu không xác định.

Trong lần thử thứ hai, bạn đã cung cấp một chức năng int foo(). Điều này có kiểu trả về khác với hàm được khai báo bởi int& foo();. Vì vậy, bạn có hai khai báo giống nhau fookhông khớp, điều này vi phạm Quy tắc một định nghĩa gây ra hành vi không xác định (không cần chẩn đoán).

Đối với một cái gì đó hoạt động, hãy khai báo hàm cục bộ. Chúng có thể dẫn đến hành vi im lặng không xác định như bạn đã thấy. Thay vào đó, chỉ sử dụng khai báo hàm bên ngoài bất kỳ hàm nào. Chương trình của bạn có thể giống như sau:

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

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

10

int& foo();là một hàm trả về một tham chiếu đến int. Hàm đã cung cấp của bạn trả về intmà không cần tham chiếu.

Bạn có thể làm

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

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

7

int & foo();có nghĩa là foo()trả về một tham chiếu đến một biến.

Hãy xem xét mã này:

#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;
}

Mã này in:

$ ./a.out k = 5

Bởi vì foo()trả về một tham chiếu đến biến toàn cục k.

Trong mã đã sửa đổi của bạn, bạn đang truyền giá trị trả về cho một tham chiếu, sau đó giá trị này không hợp lệ.


5

Trong ngữ cảnh đó, & có nghĩa là một tham chiếu - vì vậy foo trả về một tham chiếu tới một int, thay vì một int.

Tôi không chắc bạn đã làm việc với con trỏ chưa, nhưng đó là một ý tưởng tương tự, bạn không thực sự trả lại giá trị ra khỏi hàm - thay vào đó bạn đang chuyển thông tin cần thiết để tìm vị trí trong bộ nhớ nơi đó int là.

Vì vậy, để tóm tắt, bạn không chỉ định một giá trị cho một lời gọi hàm - bạn đang sử dụng một hàm để nhận tham chiếu và sau đó gán giá trị đang được tham chiếu đến một giá trị mới. Thật dễ dàng để nghĩ rằng mọi thứ xảy ra cùng một lúc, nhưng trên thực tế, máy tính thực hiện mọi thứ theo một trình tự chính xác.

Nếu bạn đang tự hỏi - lý do bạn nhận được một segfault là vì bạn đang trả về một ký tự số '2' - vì vậy đó là lỗi chính xác mà bạn gặp phải nếu bạn định nghĩa một const int và sau đó cố gắng sửa đổi nó giá trị.

Nếu bạn chưa tìm hiểu về con trỏ và bộ nhớ động thì tôi khuyên bạn nên làm điều đó trước tiên vì có một số khái niệm mà tôi nghĩ là khó hiểu trừ khi bạn học tất cả chúng cùng một lúc.


4

Mã ví dụ tại trang được liên kết chỉ là một khai báo hàm giả. Nó không biên dịch, nhưng nếu bạn đã xác định một số hàm, nó sẽ hoạt động chung. Ví dụ có nghĩa là "Nếu bạn có một chức năng với chữ ký này, bạn có thể sử dụng nó như thế".

Trong ví dụ của bạn, foorõ ràng là trả về một giá trị dựa trên chữ ký, nhưng bạn trả lại một giá trị được chuyển đổi thành một giá trị. Điều này rõ ràng được xác định là thất bại. Bạn có thể làm:

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

và sẽ thành công bằng cách thay đổi giá trị của x, khi nói:

foo() = 10;

3

Hàm bạn có, foo (), là một hàm trả về một tham chiếu đến một số nguyên.

Vì vậy, giả sử ban đầu foo trả về 5 và sau đó, trong hàm chính của bạn, bạn nói foo() = 10;, sau đó in ra foo, nó sẽ in 10 thay vì 5.

Tôi hy vọng điều đó đúng :)

Tôi cũng mới học lập trình. Thật thú vị khi thấy những câu hỏi như thế này khiến bạn phải suy nghĩ! :)

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.