2010-11-17 89 views
8

Tôi đã viết đoạn mã sau:C++ - Các câu hỏi về con trỏ hàm

#include "stdafx.h" 
#include <iostream> 
using namespace std; 

double funcA() 
{ 
    return 100.0; 
} 

int g(double (*pf)()) 
{ 
    cout << (*pf)() << endl; 
    return 0; 
} 

int g2(double pf()) 
{ 
    cout << pf() << endl; 
    return 0; 
} 

int _tmain(int argc, _TCHAR* argv[]) 
{ 
    g(&funcA); // case I 
    g(funcA); // case II 

    g2(funcA); // case III 
    g2(&funcA); // case IV 
    return 0; 
} 

tôi đã chạy mã ở trên vào VS2008 và mỗi lần gọi hàm lợi nhuận '100'. Đây là câu hỏi:

Q1> Có vấn đề gì trong mã không?

Q2> Dường như C++ không tạo sự khác biệt giữa * pf và pf. Đúng không?

Cảm ơn bạn

+1

+1 cho một câu hỏi rất rõ ràng. –

+0

bạn cũng có thể sử dụng tuyên bố của g với cơ thể của g2 – UncleZeiv

+1

Tôi đề nghị hướng dẫn này về các con trỏ hàm: http://www.newty.de/fpt/index.html .. nó rất kỹ lưỡng – Jack

Trả lời

8

C++ hiện, trên thực tế, tạo sự khác biệt giữa các loại double()double(*)(), nhưng sự khác biệt là tinh tế. Khi bạn chuyển một kiểu hàm làm đối số cho một hàm, kiểu hàm sẽ tự động "làm giảm" thành một con trỏ hàm. (Điều này là tương tự, tôi cho rằng, làm thế nào một loại mảng làm giảm đến một loại con trỏ khi được thông qua như một đối số hàm.)

Tuy nhiên, một kiểu hàm và kiểu con trỏ vẫn là khác nhau các loại, theo hệ thống kiểu C++. Hãy xem xét trường hợp sau:

void g() { } 

template <class F> 
struct Foo 
{ 
    Foo(const F& f) : func(f) 
    { } 

    void operator()() { func(); } 

    F func; 
}; 


int main() 
{ 
    Foo<void()> f(g); 
    f(); 
} 

Điều này sẽ không biên dịch vì bạn không thể khai báo loại chức năng dưới dạng biến tự động. (Hãy nhớ rằng, các hàm không phải là các đối tượng hạng nhất trong C++.) Vì vậy, khai báo F func; không hợp lệ. Tuy nhiên, nếu chúng ta thay đổi chức năng của chúng tôi để main thay vì nhanh chóng các mẫu sử dụng một hàm con trỏ, như vậy:

int main() 
{ 
    typedef void(*function_pointer)(); 
    Foo<function_pointer> f(g); 
    f(); 
} 

... bây giờ nó biên dịch.

+3

+1 cho sự giúp đỡ heaping của pedantry, mà làm cho lập trình C++ bên trong của tôi mỉm cười. –

+0

Vì vậy, nó giống như sự khác biệt giữa một tham chiếu và một con trỏ? – joshperry

+0

Lưu ý rằng 'F func' sẽ hợp lệ nếu bạn đã làm' typedef void F(); struct A {F func; }; '(và định nghĩa lớp sẽ tương đương với' struct A {void func();}; '). Nó gây ra lỗi trong mã của bạn vì 'F' phụ thuộc và không được khai báo hàm không sử dụng hàm khai báo hàm (dạng' (params) ') bằng kiểu phụ thuộc (văn bản approp. Ở 14.3) .1/3). –

2

Các chức năng sau đây là giống hệt nhau:

int g(double (*pf)()) 
{ 
    cout << (*pf)() << endl; 
    return 0; 
} 

int g2(double pf()) 
{ 
    cout << pf() << endl; 
    return 0; 
} 

dereferencing một con trỏ hàm (như trong g) cũng giống như gọi tên của chức năng đó.

Q2> Dường như C++ không tạo sự khác biệt giữa 2 pf và pf. Có phải là chính xác không?

Có sự khác biệt giữa * pf và pf (dưới dạng biến). Nếu pf là một hàm, * pf và pf() giống hệt nhau (Lưu ý các dấu ngoặc đơn).

+0

+1 cho đúng (không giống như một số câu trả lời ở đây) –

0

Với hầu hết các trình biên dịch hiện đại, không có sự khác biệt giữa "(* biến)". và "biến->". Tuy nhiên, người ta phải kiểm tra lớp đang sử dụng để xem nó có ghi đè toán tử dereference hay không.

Nhiều lập trình viên sử dụng typedef khi xác định con trỏ hàm, chủ yếu để làm cho việc đọc dễ dàng hơn. Ngoài ra, cú pháp double pf() có thể dễ bị lỗi khả năng đọc và có thể bị nhầm lẫn với việc thực hiện một hàm trên dòng tham số.

+0

Toán tử dereference là một lỗi không liên quan. Hơn nữa, những gì bạn nói là không đúng sự thật. Có hay không '(* a) .b' bằng' a-> b' không liên quan gì đến trình biên dịch - nó chỉ là câu hỏi liệu các toán tử có quá tải một cách thích hợp cho kiểu 'a' hay không. –

0

Không có sự cố hoặc sự khác biệt nào trong mã bạn đã đăng. Tuy nhiên, nếu bạn đang viết các mẫu có các hàm functors, bạn nên sử dụng cú pháp trong g2.Hãy xem xét những điều sau đây:

template<typename Iter, typename Func> 
void for_each(Iter begin, Iter end, Func functor) 
{ 
    for(; begin != end; ++begin) 
    { 
     functor(*begin); 
    } 
} 

Lưu ý rằng nếu bạn đặt các toán tử tham chiếu trước khi functor, bạn giới hạn các tiện ích của thuật toán bạn đã viết thư cho con trỏ hàm. Tuy nhiên, nếu bạn không đặt nó ở đó, ai đó có thể vượt qua một functor STL, chẳng hạn như một cái gì đó được trả về bởi std::bind2nd.

Do đó, tôi sẽ khuyên bạn nên sử dụng cú pháp thứ hai (không có *) nếu có thể.

0

Hãy xem xét các đoạn mã sau đây

void pf(); 

void (&prf)() = pf; // OK, bind prf to pf 
void (&prf)() = &pf; // ill-formed, can't bind prf to an function pointer value 

Mặt khác

void (*ppf)() = pf; // OK, function decays to a pointer 
void (*ppf)() = &pf; // OK, pointer assigned to a pointer 

Vì vậy, có một chuyển đổi ngầm từ một chức năng để một con trỏ (được gọi là "phân rã"). Điều này cũng làm cho bạn có thể nói ***...***pf - tùy ý nhiều lần dereference nó - trong mỗi bước một chức năng để chuyển đổi con trỏ xảy ra mà undoes ảnh hưởng của dereference trước đó.

Trong danh sách tham số chức năng, một T f()T (*f)() nhiều cách tương đương (trừ chính tả) của khai báo một tham số

void f(void g()); // g has type void (*)() 
void f(void (*g)()); // g has type void (*)() 

Một tham chiếu sẽ ức chế điều chỉnh kiểu tham số này

void f(void (&g)()); // g has *not* the type void (*)() 

Đây là chính xác giống như đối với tham số được khai báo mảng: Tham số không bao giờ là mảng, nhưng chúng sẽ luôn là con trỏ, nếu chúng được khai báo là mảng.