Tại sao các định nghĩa con trỏ hàm hoạt động với bất kỳ số lượng ký hiệu '&' hoặc dấu hoa thị '*'?


216

Tại sao làm công việc sau đây?

void foo() {
    cout << "Foo to you too!\n";
};

int main() {
    void (*p1_foo)() = foo;
    void (*p2_foo)() = *foo;
    void (*p3_foo)() = &foo;
    void (*p4_foo)() = *&foo;
    void (*p5_foo)() = &*foo;
    void (*p6_foo)() = **foo;
    void (*p7_foo)() = **********************foo;

    (*p1_foo)();
    (*p2_foo)();
    (*p3_foo)();
    (*p4_foo)();
    (*p5_foo)();
    (*p6_foo)();
    (*p7_foo)();
}

Câu trả lời:


224

Có một vài phần cho phép tất cả các tổ hợp toán tử này hoạt động theo cùng một cách.

Lý do cơ bản tại sao tất cả các công việc này là một hàm (như foo) hoàn toàn có thể chuyển đổi thành một con trỏ đến hàm. Đây là lý do tại sao void (*p1_foo)() = foo;hoạt động: foođược chuyển đổi hoàn toàn thành một con trỏ thành chính nó và con trỏ đó được gán cho p1_foo.

Unary &, khi được áp dụng cho một hàm, tạo ra một con trỏ tới hàm, giống như nó mang lại địa chỉ của một đối tượng khi nó được áp dụng cho một đối tượng. Đối với các con trỏ tới các hàm thông thường, nó luôn luôn dư thừa do chuyển đổi hàm-con-hàm-hàm ẩn. Trong mọi trường hợp, đây là lý do tại sao void (*p3_foo)() = &foo;làm việc.

Unary *, khi được áp dụng cho một con trỏ hàm, tạo ra hàm trỏ, giống như nó mang lại đối tượng nhọn khi nó được áp dụng cho một con trỏ bình thường cho một đối tượng.

Những quy tắc này có thể được kết hợp. Xem xét ví dụ thứ hai của bạn đến cuối cùng , **foo:

  • Đầu tiên, foođược chuyển đổi hoàn toàn thành một con trỏ thành chính nó và đầu tiên *được áp dụng cho con trỏ hàm đó, mang lại chức năng foomột lần nữa.
  • Sau đó, kết quả lại được chuyển đổi hoàn toàn thành một con trỏ thành chính nó và lần thứ hai *được áp dụng, một lần nữa mang lại hàm foo.
  • Sau đó, nó được chuyển đổi hoàn toàn thành một con trỏ hàm một lần nữa và được gán cho biến.

Bạn có thể thêm bao nhiêu *s tùy thích, kết quả luôn giống nhau. Càng nhiều *, càng tốt.

Chúng tôi cũng có thể xem xét ví dụ thứ năm của bạn , &*foo:

  • Đầu tiên, foođược chuyển đổi hoàn toàn thành một con trỏ đến chính nó; unary *được áp dụng, năng suất foomột lần nữa.
  • Sau đó, &được áp dụng cho foo, mang lại một con trỏ tới foo, được gán cho biến.

Tuy nhiên, &chỉ có thể được áp dụng cho một chức năng, không phải cho một chức năng đã được chuyển đổi thành một con trỏ hàm (tất nhiên trừ khi con trỏ hàm là một biến, trong trường hợp đó, kết quả là một con trỏ-con trỏ-con trỏ- to-a-function; ví dụ, bạn có thể thêm vào danh sách của mình void (**pp_foo)() = &p7_foo;).

Đây là lý do tại sao &&fookhông hoạt động: &fookhông phải là một chức năng; nó là một con trỏ hàm là một giá trị. Tuy nhiên, &*&*&*&*&*&*foosẽ hoạt động, như vậy &******&foo, bởi vì trong cả hai biểu thức đó, &luôn luôn được áp dụng cho một hàm và không áp dụng cho một con trỏ hàm rvalue.

Cũng lưu ý rằng bạn không cần sử dụng unary *để thực hiện cuộc gọi thông qua con trỏ hàm; cả hai (*p1_foo)();(p1_foo)();có cùng một kết quả, một lần nữa vì chuyển đổi hàm-con-hàm-con trỏ.


2
@Jimmy: Đó không phải là các tham chiếu đến con trỏ hàm, chúng chỉ là các con trỏ hàm. &foolấy địa chỉ của foo, dẫn đến một con trỏ hàm trỏ vào foo, như người ta mong đợi.
Dennis Zickefoose

2
Bạn không thể chuỗi &toán tử cho các đối tượng: đã cho int p;, đưa &pra một con trỏ tới pvà là một biểu thức giá trị; các &nhà khai thác đòi hỏi một biểu thức vế trái.
James McNellis

12
Tôi không đồng ý. Càng nhiều *, càng ít vui vẻ .
Seth Carnegie

28
Xin vui lòng không chỉnh sửa cú pháp của các ví dụ của tôi. Tôi đã chọn các ví dụ rất cụ thể để thể hiện các tính năng của ngôn ngữ.
James McNellis

7
Là một lưu ý phụ, tiêu chuẩn C nêu rõ rằng sự kết hợp &*triệt tiêu lẫn nhau (6.5.3.2): "The unary & operator yields the address of its operand."/ - / "If the operand is the result of a unary * operator, neither that operator nor the & operator is evaluated and the result is as if both were omitted, except that the constraints on the operators still apply and the result is not an lvalue.".
Lundin

9

Tôi nghĩ cũng hữu ích khi nhớ rằng C chỉ là một bản tóm tắt cho máy bên dưới và đây là một trong những nơi mà sự trừu tượng đó bị rò rỉ.

Từ quan điểm của máy tính, một chức năng chỉ là một địa chỉ bộ nhớ mà nếu được thực thi sẽ thực hiện các hướng dẫn khác. Vì vậy, một hàm trong C được mô hình hóa như một địa chỉ, điều này có thể dẫn đến thiết kế rằng một hàm "giống" như địa chỉ mà nó trỏ đến.


0

&*là các hoạt động tạm thời trên một biểu tượng được khai báo là một hàm trong C có nghĩa là func == *func == &func == *&funcvà do đó*func == **func

Nó có nghĩa là loại int ()giống như int (*)()một tham số hàm và một func được xác định có thể được truyền dưới dạng *func, funchoặc &func. (&func)()cũng giống như func(). Liên kết Godbolt.

Do đó, một hàm thực sự là một địa chỉ *&không có ý nghĩa gì, và thay vì tạo ra một lỗi, trình biên dịch chọn giải thích nó là địa chỉ của func.

&trên một biểu tượng được khai báo là một con trỏ hàm, tuy nhiên sẽ lấy địa chỉ của con trỏ (vì bây giờ nó có một mục đích riêng), trong khi funcp*funcpsẽ giống hệt nhau

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.