2016-02-26 47 views
18

Tôi có một vấn đề với sự hiểu biết một số C++ cú pháp kết hợp với chức năng gợi ý và tờ khai chức năng, đó là:Giải thích về con trỏ hàm

Thông thường khi chúng ta muốn khai báo một loại chức năng chúng ta làm một cái gì đó như:

typedef void(*functionPtr)(int); 

và điều này là tốt cho tôi. Từ bây giờ trên hàm functionPtr là một kiểu, biểu thị con trỏ cho hàm , hàm này trả về void và lấy int bằng một giá trị làm đối số.

Chúng ta có thể sử dụng nó như sau:

typedef void(*functionPtr)(int); 

void function(int a){ 
    std::cout << a << std::endl; 
} 

int main() { 

    functionPtr fun = function; 
    fun(5); 

    return 0; 
} 

Và chúng tôi nhận 5 in trên một màn hình.

chúng tôi đã có con trỏ đến hàm fun, chúng tôi chỉ định một số con trỏ hiện có để hoạt động - function và chúng tôi thực thi hàm này bằng con trỏ. Mát mẻ.

Bây giờ khi đọc trong một số sách, chức năng và con trỏ đến hàm được xử lý bằng cách nào đó giống nhau, vì vậy trên thực tế sau khi khai báo hàm function() mỗi khi chúng ta nói hàm, nghĩa là hàm thực và con trỏ hoạt động cùng loại, biên dịch và mọi hướng dẫn cho kết quả tương tự (5 in trên một màn hình):

int main() { 

    functionPtr fun = function; 
    fun(5); 
    (*fun)(5); 
    (*function)(5); 
    function(5); 

    return 0; 
} 

Bây giờ miễn là tôi có thể tưởng tượng, rằng con trỏ đến chức năng và chức năng là khá nhiều như nhau, thì đó là bằng cách nào đó tốt cho tôi .

Sau đó, tôi mặc dù, nếu con trỏ đến hoạt động và chức năng thực sự là như vậy, thì tại sao tôi không thể làm như sau:

typedef void(functionPtr)(int); //removed * 

void function(int a){ 
    std::cout << a << std::endl; 
} 

int main() { 

    functionPtr fun = function; 
    fun(5); 

    return 0; 
} 

này mang lại cho tôi lỗi sau:

prog.cpp:12:14: warning: declaration of 'void fun(int)' has 'extern' and is initialized functionPtr fun = function;

do đó tôi hiểu, vì một số lý do trình biên dịch giờ đây đã hiểu, niềm vui đó là đã tồn tại chức năng. Sau đó, tôi đã thử theo dõi:

int main() { 

    functionPtr fun; 
    fun(5); 

    return 0; 
} 

Và tôi gặp lỗi liên kết. Tôi bằng cách nào đó hiểu, rằng như trình biên dịch bây giờ xử lý niềm vui như chức năng đã tồn tại, sau đó do thực tế, niềm vui đó là hư không được xác định, tôi sẽ nhận được lỗi liên kết. Vì vậy, tôi đã thay đổi tên của biến:

typedef void(functionPtr)(int); 

void function(int a){ 
    std::cout << a << std::endl; 
} 

int main() { 

    functionPtr function; 
    function(5); 

    return 0; 
} 

Bây giờ chức năng trong bóng tối chính chức năng tên toàn cầu, vì vậy function(5) được sử dụng từ khai functionPtr function; Nó hoạt động tốt và in 5 trên màn hình.

Vì vậy, bây giờ tôi bị sốc. Tại sao điều này xảy ra?Ngoài ra điều sai lầm được, rằng khi con trỏ hàm được khai báo như thế này:

typedef void(*functionPtr)(int); 

tôi có thể tạo ra chức năng của loại functionPtr ở sau cách:

functionPtr function(int a){ 
    std::cout << a << std::endl; 
} 

trong khi, khi tuyên bố cái gì đó như:

typedef void(functionPtr)(int); 

làm điều này:

functionPtr function(int a){ 
    std::cout << a << std::endl; 
} 

được biên dịch bởi một trình biên dịch như hàm trả về hàm. Nếu điều này là như vậy, tại sao khai báo trước đó (typedef void(functionPtr)(int);) biết rằng đây là một hàm trả về void và không trả về hàm functionPtr?

Có thể ai đó vui lòng giải thích điều gì thực sự đang xảy ra thiếu sót đối với tôi không?

Tôi đang sử dụng trình biên dịch g ++ C++ với tùy chọn C++ 14 được bật.

Trả lời

6

Vâng, nó khó hiểu một chút.

Loại chức năng và con trỏ đến loại chức năng thực sự là hai loại khác nhau (không giống nhiều hơn int và con trỏ đến int). Tuy nhiên, có một quy tắc, rằng một loại hàm phân rã thành con trỏ đến kiểu hàm trong hầu hết các ngữ cảnh. Ở đây phân rã lỏng lẻo có nghĩa là đã chuyển đổi (có sự khác biệt giữa chuyển đổi loại và phân rã, nhưng có thể bạn không quan tâm đến nó ngay bây giờ).

Điều quan trọng, là gần như mỗi khi bạn sử dụng một loại hàm, bạn kết thúc bằng con trỏ đến loại hàm. Lưu ý gần như, tuy nhiên - hầu hết thời gian không phải là luôn luôn!

Và bạn đang gặp phải một số trường hợp khi không.

typedef void(functionPtr)(int); 
functionPtr fun = function; 

Mã này cố gắng sao chép một hàm (không phải con trỏ! Chức năng!) Sang một hàm khác. Nhưng tất nhiên, điều này là không thể - bạn không thể sao chép các hàm trong C++. Trình biên dịch không cho phép điều này, và tôi không thể tin rằng bạn đã nhận nó biên dịch (bạn đang nói rằng bạn có mối liên kết lỗi?)

Bây giờ, mã này:

typedef void(functionPtr)(int); 
functionPtr function; 
function(5); 

function không shadow bất cứ điều gì. Trình biên dịch biết nó không phải là một con trỏ hàm có thể được gọi và chỉ gọi số function gốc của bạn.

+0

đoạn mã này chưa được biên soạn. Các đoạn mã, đó là: 'functionPtr vui vẻ; fun (5); '(không gán) Ở đây vui được xem như một số hàm, ở đâu đó tồn tại, nhưng liên kết không thể nhìn thấy nó, vì vậy tôi gặp lỗi liên kết – DawidPi

+0

Vì vậy, tại sao khi' typedef void (functionPtr) (int); 'là như thế này và tôi có: functionPtr vui vẻ; vui vẻ(); Tôi gặp lỗi liên kết? Xem ở đây: https://ideone.com/e20FmU – DawidPi

+0

@DawidPi Bởi vì, như SergeyA cũng giải thích, mã đó là không chính xác. Chỉ có thể làm việc trong trường hợp được đăng trong câu trả lời này, nơi chức năng 'hàm' được triển khai. Trong trường hợp của bạn vui vẻ là không. – LPs

3

Hãy xem từng ví dụ của bạn và ý nghĩa thực sự của chúng.

typedef void(functionPtr)(int); 

void function(int a){ 
    std::cout << a << std::endl; 
} 

int main() { 

    functionPtr fun = function; 
    fun(5); 

    return 0; 
} 

Ở đây bạn đang tạo ra một typedef functionPtr cho các chức năng mà mất và int, và không trở về giá trị. functionPtr không thực sự là một typedef cho một con trỏ hàm, mà là một hàm thực tế.

Sau đó, bạn đang cố gắng khai báo hàm mới fun và gán cho nó function. Thật không may bạn không thể gán cho các chức năng, do đó, điều này không hoạt động.

int main() { 

    functionPtr fun; 
    fun(5); 

    return 0; 
} 

Một lần nữa, bạn đang khai báo hàm fun bằng chữ ký bạn đã chỉ định. Nhưng bạn không định nghĩa nó, vì vậy bạn đã thất bại trong giai đoạn liên kết.

typedef void(functionPtr)(int); 

void function(int a){ 
    std::cout << a << std::endl; 
} 

int main() { 

    functionPtr function; 
    function(5); 

    return 0; 
} 

Điều gì xảy ra ở đây? Bạn xác định số typedef và trong chính bạn viết functionPtr function;. Về cơ bản, đây chỉ là một nguyên mẫu cho hàm bạn đã viết, function. Nó nghỉ ngơi rằng chức năng này tồn tại, nhưng ngược lại nó không làm gì cả. Thực tế, bạn có thể viết:

typedef void(functionPtr)(int); 

void function(int a){ 
    std::cout << a << std::endl; 
} 

int main() { 

    functionPtr function; 
    functionPtr function; 
    functionPtr function; 
    void function(int); 

    function(5); 

    return 0; 
} 

Số lần bạn muốn, nó sẽ không thay đổi gì cả. Các function(5) bạn đang gọi sau khi luôn luôn là điều tương tự.

Một điều bạn có thể làm trong C++ là khai báo nguyên mẫu của hàm bằng cách sử dụng typedef như vậy, nhưng bạn không thể xác định nó theo cách đó.

typedef void(*functionPtr)(int); 

functionPtr function(int a){ 
    std::cout << a << std::endl; 
} 

Tại đây bạn xác định hàm trả về con trỏ hàm, nhưng sau đó bạn không trả về hàm. Trình biên dịch, tùy thuộc vào cài đặt của bạn, có thể hoặc không thể khiếu nại. Nhưng một lần nữa, function được tách hoàn toàn khỏi functionPtr. Về cơ bản, bạn đã viết một cách cơ bản

void (*)(int) function(int a) { 
    ... 
} 

Ví dụ cuối cùng, một ví dụ mà bạn có chức năng trả về hàm chỉ đơn giản là không được phép, vì nó sẽ vô nghĩa.

4

Thú vị nhất của ví dụ của bạn là thế này, sao chép ở đây mà không cần dùng typedef:

void function(int a) { // declaration and definition of 'function' 
    std::cout << a << std::endl; 
} 

int main() { 
    void function(int); // declaration of 'function' 
    function(5); 
} 

Trong hầu hết các tình huống trong C++, việc tái tuyên bố function trong phạm vi địa phương sẽ shadow toàn cầu ::function. Vì vậy, mong đợi một mối liên kết-lỗi có ý nghĩa - main()::function không có định nghĩa đúng?

Ngoại trừ các chức năng đặc biệt trong vấn đề này. Từ một lưu ý trong [basic.scope.pdel]:

Function declarations at block scope and variable declarations with the extern specifier at block scope refer to declarations that are members of an enclosing namespace, but they do not introduce new names into that scope.

Vì vậy mà mã ví dụ là chính xác tương đương với:

void function(int a) { /* ... */ } 
void function(int); // just redeclaring it again, which is ok 

int main() { 
    function(5); 
} 

Bạn cũng có thể xác minh điều này bằng cách đặt toàn cầu function vào một số namespace, N . Tại thời điểm này, khai báo phạm vi địa phương sẽ thêm một tên vào ::, nhưng nó sẽ không có định nghĩa - do đó bạn nhận được một lỗi liên kết.


Điều thú vị khác mà bạn chạm vào là khái niệm chuyển đổi chức năng sang con trỏ, [ch.đổifunc]:

An lvalue of function type T can be converted to a prvalue of type “pointer to T”. The result is a pointer to the function.

Khi bạn có biểu thức gọi hàm - lần đầu tiên bạn chuyển đổi thành con trỏ thành hàm. Đó là lý do tại sao đây là tương đương:

fun(5);   // OK, call function pointed to by 'fun' 
(*fun)(5);  // OK, first convert *fun back to 'fun' 
function(5); // OK, first convert to pointer to 'function' 
(*function)(5); // OK, unary* makes function get converted to a pointer 
       // which then gets dereferenced back to function-type 
       // which then gets converted back to a pointer 
-1
typedef void functionPtr (int); 

void function (int a){ 
    std::cout << a << std::endl; 
} 

int main() { 

    functionPtr *func; 
    func = function; 
    func(5); 
    return 0; 
} 

Bạn có thể sử dụng nó theo cách này, tôi đã được thử nghiệm. Thực sự có một vấn đề rắc rối về vấn đề này.

+0

Điều này không trả lời câu hỏi * tại sao * sự cố này xảy ra. Vui lòng chỉ thêm câu trả lời khác với các câu trả lời trước đó hoặc thêm một số điểm có liên quan. – izlin

Các vấn đề liên quan