Con trỏ hàm Typedef?


459

Tôi đang tìm hiểu cách tải động DLL nhưng điều tôi không hiểu là dòng này

typedef void (*FunctionFunc)();

Tôi có một vài câu hỏi. Nếu ai đó có thể trả lời họ, tôi sẽ rất biết ơn.

  1. Tại sao được typedefsử dụng?
  2. Cú pháp có vẻ kỳ quặc; Sau voidđó không nên có một tên chức năng hay cái gì đó? Nó trông giống như một chức năng ẩn danh.
  3. Là một con trỏ hàm được tạo để lưu trữ địa chỉ bộ nhớ của hàm?

Vì vậy, tôi đang bối rối vào lúc này; bạn có thể làm rõ mọi thứ cho tôi?


5
Hãy xem liên kết (phần cuối) learncpp.com/cpp-tutorial/78-feft-pointers
nhiệt tình

9
Cần lưu ý rằng vì c ++ 11 using FunctionFunc = void (*)();có thể được sử dụng thay thế. Rõ ràng hơn một chút rằng bạn chỉ đang khai báo tên cho một loại (con trỏ tới chức năng)
user362515

chỉ cần thêm vào @ user362515, một hình thức rõ ràng hơn với tôi là:using FunctionFunc = void(void);
topspin

@topspin IIRC hai cái này không giống nhau. Một là kiểu con trỏ hàm, hai là kiểu hàm. Có chuyển đổi ngầm, đó là lý do tại sao nó hoạt động, IANA (C ++) L vì vậy, người ta có thể bước vào và sửa lỗi cho tôi. Trong mọi trường hợp, nếu dự định là xác định một loại con trỏ, tôi nghĩ rằng cú pháp với *rõ ràng hơn một chút.
dùng362515

Câu trả lời:


463

typedeflà một cấu trúc ngôn ngữ liên kết một tên với một loại.
Bạn sử dụng nó giống như cách bạn sẽ sử dụng loại ban đầu, ví dụ

  typedef int myinteger;
  typedef char *mystring;
  typedef void (*myfunc)();

sử dụng chúng như

  myinteger i;   // is equivalent to    int i;
  mystring s;    // is the same as      char *s;
  myfunc f;      // compile equally as  void (*f)();

Như bạn thấy, bạn chỉ có thể thay thế các typedefed tên với định nghĩa của nó đưa ra ở trên.

Khó khăn nằm ở con trỏ đến hàm cú pháp và khả năng đọc trong C và C ++, và typedefcó thể cải thiện khả năng đọc của các khai báo đó. Tuy nhiên, cú pháp là phù hợp, vì các hàm - không giống như các loại đơn giản khác - có thể có giá trị trả về và tham số, do đó, khai báo đôi khi dài và phức tạp của một con trỏ tới hàm.

Khả năng đọc có thể bắt đầu thực sự khó khăn với các con trỏ tới các mảng chức năng và một số hương vị gián tiếp khác.

Để trả lời ba câu hỏi của bạn

  • Tại sao typedef được sử dụng? Để dễ đọc mã - đặc biệt là cho con trỏ tới các hàm hoặc tên cấu trúc.

  • Cú pháp có vẻ kỳ lạ (trong con trỏ để khai báo hàm) Cú pháp đó không rõ ràng để đọc, ít nhất là khi bắt đầu. typedefThay vào đó, sử dụng khai báo giúp giảm bớt việc đọc

  • Là một con trỏ hàm được tạo để lưu trữ địa chỉ bộ nhớ của hàm? Có, một con trỏ hàm lưu địa chỉ của hàm. Điều này không liên quan gì đến typedefcấu trúc chỉ giúp dễ dàng viết / đọc chương trình; trình biên dịch chỉ mở rộng định nghĩa typedef trước khi biên dịch mã thực tế.

Thí dụ:

typedef int (*t_somefunc)(int,int);

int product(int u, int v) {
  return u*v;
}

t_somefunc afunc = &product;
...
int x2 = (*afunc)(123, 456); // call product() to calculate 123*456

6
trong ví dụ cuối cùng, sẽ không chỉ 'hình vuông' đề cập đến cùng một thứ, tức là con trỏ tới hàm thay vì sử dụng & vuông.
pranavk

2
Câu hỏi, trong ví dụ typedef đầu tiên của bạn, bạn có dạng typedef type aliasnhưng với các con trỏ hàm dường như chỉ có 2 đối số , typedef type. Là bí danh mặc định cho tên được chỉ định trong đối số loại?
dchhetri

2
@pranavk: Có, square&square(và, thực sự, *square**square) đều đề cập đến cùng một con trỏ hàm.
Jonathan Leffler

6
@ user814628: Không rõ bạn đang hỏi gì. Với typedef int newname, bạn đang làm newnamethành một bí danh cho int. Với typedef int (*func)(int), bạn đang tạo functhành một bí danh cho int (*)(int)- một con trỏ để hàm lấy một intđối số và trả về một intgiá trị.
Jonathan Leffler

10
Tôi đoán tôi chỉ bối rối về việc đặt hàng. Với typedef int (*func)(int), tôi hiểu rằng func là một bí danh, chỉ hơi bối rối vì bí danh bị rối với loại. Đi qua typedef int INTnhư một ví dụ tôi sẽ dễ dàng hơn nếu con trỏ hàm typedef có dạng typedef int(*function)(int) FUNC_1. Bằng cách đó tôi có thể thấy loại và bí danh trong hai mã thông báo riêng biệt thay vì được chia thành một.
dchhetri

188
  1. typedefđược sử dụng để các loại bí danh; trong trường hợp này bạn đang aliasing FunctionFuncđể void(*)().

  2. Thật vậy, cú pháp trông có vẻ kỳ quặc, hãy xem điều này:

    typedef   void      (*FunctionFunc)  ( );
    //         ^                ^         ^
    //     return type      type name  arguments
    
  3. Không, điều này chỉ đơn giản là cho trình biên dịch biết rằng FunctionFunckiểu sẽ là một con trỏ hàm, nó không định nghĩa một, như thế này:

    FunctionFunc x;
    void doSomething() { printf("Hello there\n"); }
    x = &doSomething;
    
    x(); //prints "Hello there"

27
typedefkhông không khai báo một kiểu mới. bạn có thể có nhiều typedeftên được xác định cùng loại và chúng không khác biệt (ví dụ: wrt. nạp chồng các hàm). có một số trường hợp trong đó, liên quan đến cách bạn có thể sử dụng tên, một typedeftên được xác định không chính xác tương đương với tên được định nghĩa, nhưng nhiều typedeftên được xác định cho cùng một tên, là tương đương.
Chúc mừng và hth. - Alf

Ah tôi hiểu rồi Cảm ơn.
Jack Harvin

4
+1 cho câu trả lời cho câu hỏi 2 - rất rõ ràng. Phần còn lại của câu trả lời cũng rõ ràng, nhưng câu trả lời đó nổi bật với tôi :-)
Michael van der Westhuizen

33

Không có typedeftừ này, trong C ++, khai báo sẽ khai báo một biến FunctionFunccon trỏ kiểu thành hàm không có đối số, trả về void.

Với typedefnó thay vì định nghĩa FunctionFuncnhư một tên cho loại đó.


5
Đây là một cách thực sự tốt để suy nghĩ về nó. Nếu bạn đọc các câu trả lời khác một cách cẩn thận, chúng không thực sự giải quyết sự nhầm lẫn của OP về sự vướng víu của bí danh và tên. Tôi đã học được một cái gì đó mới từ việc đọc bài viết này.
Nhà vật lý điên

9

Nếu bạn có thể sử dụng C ++ 11, bạn có thể muốn sử dụng std::functionusingtừ khóa.

using FunctionFunc = std::function<void(int arg1, std::string arg2)>;

1
không có usingtừ khóa C ++ 11 sẽ như thế nào typedef std::function<void(int, std::string)> FunctionFunc;, chỉ trong trường hợp ai đó muốn một trình bao bọc khác xung quanh các chức năng mà không có C ++ 11
Top-Master

2
#include <stdio.h>
#include <math.h>

/*
To define a new type name with typedef, follow these steps:
1. Write the statement as if a variable of the desired type were being declared.
2. Where the name of the declared variable would normally appear, substitute the new type name.
3. In front of everything, place the keyword typedef.
*/

// typedef a primitive data type
typedef double distance;

// typedef struct 
typedef struct{
    int x;
    int y;
} point;

//typedef an array 
typedef point points[100]; 

points ps = {0}; // ps is an array of 100 point 

// typedef a function
typedef distance (*distanceFun_p)(point,point) ; // TYPE_DEF distanceFun_p TO BE int (*distanceFun_p)(point,point)

// prototype a function     
distance findDistance(point, point);

int main(int argc, char const *argv[])
{
    // delcare a function pointer 
    distanceFun_p func_p;

    // initialize the function pointer with a function address
    func_p = findDistance;

    // initialize two point variables 
    point p1 = {0,0} , p2 = {1,1};

    // call the function through the pointer
    distance d = func_p(p1,p2);

    printf("the distance is %f\n", d );

    return 0;
}

distance findDistance(point p1, point p2)
{
distance xdiff =  p1.x - p2.x;
distance ydiff =  p1.y - p2.y;

return sqrt( (xdiff * xdiff) + (ydiff * ydiff) );
}

1
Trong trường hợp nào thì '&' cần thiết? Ví dụ: 'func_p = & findDistance' và 'func_p = findDistance' có hoạt động tốt như nhau không?
Donna

1
Tên hàm là một con trỏ, vì vậy bạn không cần sử dụng '&' với nó. Nói cách khác, '&' là tùy chọn khi con trỏ hàm được xem xét. Tuy nhiên, đối với kiểu dữ liệu nguyên thủy, bạn cần sử dụng '&' để lấy địa chỉ của nó.
Amjad

1
Nó luôn luôn là kỳ quặc mà '&' có thể là tùy chọn. Nếu một typedef được sử dụng để tạo một con trỏ đến một nguyên thủy (nghĩa typedef (*double) plà '&' là tùy chọn?
Donna

Tôi nghĩ rằng bạn muốn gõ typedef double* pđể xác định một con trỏ đến gấp đôi. Nếu bạn muốn điền pcon trỏ từ một biến nguyên thủy, bạn sẽ cần sử dụng '&'. Một lưu ý phụ, chúng tôi bạn * <tên con trỏ> để hủy đăng ký một con trỏ. Nó *được sử dụng trong con trỏ hàm để hủy bỏ con trỏ (tên hàm) và đi đến khối bộ nhớ nơi đặt các lệnh chức năng.
Amjad

2

Đối với trường hợp chung của cú pháp bạn có thể xem phụ lục A của tiêu chuẩn ANSI C .

Ở dạng Backus-Naur từ đó, bạn có thể thấy nó typedefcó loại storage-class-specifier.

Trong loại declaration-specifiersbạn có thể thấy rằng bạn có thể trộn nhiều loại chỉ định, thứ tự không quan trọng.

Ví dụ, nó là chính xác để nói,

long typedef long a;

để xác định loại alà bí danh cho long long. Vì vậy, để hiểu typedef về việc sử dụng toàn diện, bạn cần tham khảo một số dạng backus-naur xác định cú pháp (có nhiều ngữ pháp chính xác cho ANSI C, không chỉ của ISO).

Khi bạn sử dụng typedef để xác định bí danh cho loại chức năng, bạn cần đặt bí danh ở cùng một nơi bạn đặt định danh của hàm. Trong trường hợp của bạn, bạn xác định loại FunctionFunclà bí danh cho một con trỏ để chức năng kiểm tra loại bị vô hiệu hóa trong cuộc gọi và không trả về gì.

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.