我在使用typedef理解指向函数的指针的语法时遇到了问题。我已经阅读了很多答案,但仍然无法理解。 我会尝试解释我是如何看待事物的,这样你才能理解我的想法。
因此我们使用typedef为现有类型提供别名,例如:
typedef int number;
这样我们可以使用与整数相同的数字(类似于预处理器指令 - 我知道在制作指针的typedef时会有一些差异)。 另一个例子:
typedef struct
{
int num;
} MyStruct;
将为未命名的结构提供名为MyStruct的别名。
所以这是指向函数typedef的指针的语法:
typedef int (*pFunc)(int, int);
也许我很难理解这一点,因为typedef就像它给TYPES提供别名的名字而且函数不是完全类型但是无论如何,根据我的理解,这更像是指向某种函数签名的指针,所以第一个int是返回的类型,第二个括号是指示传递给函数的参数是什么类型。 现在我不太明白的是这部分:
(*pFunc)
好吧,说我是对的,通常指向一些内存的指针声明如下:
int *p;
double *p;
.
.
.
因此,按照以下方式进行操作会更有意义:
(pFunc*)
因为对我而言,如果星号位于名称之前,它看起来像pFunc是某种类型的类型指针的变量名,而不是实际的类型指针。
好吧,另一件令我困扰的事情就是语法的顺序。 到目前为止,在所有typedef定义中,我们左边是类型,右边是别名。
typedef int number;
typedef struct
{
int num;
} MyStruct;
我们看到int和struct是左边的类型,我们给它们的别名在右边。
现在,在指向函数typedef的指针中,它不遵循此约定。 我们在右边的函数返回了类型,然后是括号中的typename,然后是括号中的参数类型,这个顺序让我在查看其他typedef如何处理相同的顺序后感到困惑。
做这样的事情会不会更有意义? :
typedef int (int,int) Func;
所以我们首先有一个typedef,我们想要给别名的类型,在这种情况下是一个函数
签名,取2个int并返回一个int,然后在右边
我们有别名。它会更有意义吗?这跟着
其他typedef命令,我只是没有得到函数指针的顺序
多.. ..
答案 0 :(得分:6)
typedef
使用相同的语法来声明类型,通常用于声明值。
例如,如果我们声明名为int
的{{1}},我们会这样做:
myInt
如果我们要将名为int myInt;
的类型声明为int,我们只需添加myIntType
:
typedef
我们可以声明一个函数typedef int myIntType;
,如下所示:
myFunc
告诉编译器,我们可以调用具有该名称和签名的实际函数。
我们也可以通过以下方式声明函数类型int myFunc(int a, int b);
:
myFuncType
我们可以这样做:
typedef int myFuncType(int a, int b);
这相当于先前的myFuncType myFunc;
声明(尽管很少使用此表格)。
功能不是传统值;它表示带有入口点地址的代码块。像上面那样的函数声明是隐式的myFunc
;他们告诉编译器命名的东西存在于其他地方。但是,您可以获取函数的地址,该函数称为函数指针。函数指针可以指向具有正确签名的任何函数。通过在类型/值的名称前加extern
来声明指针,因此,我们可能会尝试:
*
但这是不正确的,因为int *myFuncPtr(int a, int b);
与*
绑定得更紧密,所以我们声明int
是一个返回myFuncPtr
指针的函数。我们必须在指针和名称周围放置parens以更改绑定顺序:
int
要声明类型,我们只需在前面添加int (*myFuncPtr)(int a, int b);
:
typedef
在上面typedef int (*myFuncPtrType)(int a, int b);
的声明中,编译器为变量分配了一些内存。但是,如果我们在不同的编译单元中编写一些代码,并且想要引用myInt
,我们需要将它声明为myInt
(在引用编译单元中),以便我们引用相同的内存。如果没有extern
,编译器将分配第二个extern
,这将导致链接器错误(实际上这不是真的,因为C允许暂定定义,您不应该使用它)。
如上所述,函数不是正常值,并且始终隐式myInt
。但是,函数指针是正常值,如果您尝试从单独的编译单元引用全局函数指针,则需要extern
。
通常,您可以将全局变量(和函数)的extern
放入头文件中。然后,您将标题包含在包含这些变量和函数的定义的编译单元中,以便编译器可以确保类型匹配。
答案 1 :(得分:1)
变量定义或声明的语法是一个类型,后跟一个或多个可能带有修饰符的变量。所以一些例子是:
int a, b; // two int variables a and b
int *a, b; // pointer to an int variable a and an int variable b
int a, *b, **c; // int variable a, pointer to an int variable b, and pointer to a pointer to an int variable c
请注意,在所有这些中,星号会修改星号右侧的变量,将其从int更改为指向int的指针或指向int的指针。所以这些可能会被用作:
int a, *b, **c, d;
a = 5; // set a == 5
b = &a; // set b == address of a
c = &b; // set c == address of b which in this case has the address of int variable a
d = **c; // put value of a into d using pointer to point to an int variable a
d = *b; // put value of a into d using pointer to an int variable a
d = a; // put value of a into d using the variable a directly
extern
语句用于指示变量的定义位于某个其他文件中,并且该变量具有全局可见性。因此,您可以使用extern
关键字声明一个变量,以明确变量,以便C编译器具有在编译时进行良好检查所需的信息。 extern
表示变量实际上是使用其内存分配定义的,而不是使用变量的源所在的文件。
由于函数指针是一个变量,如果你有一个函数指针需要全局可见性,当你为实际定义它的源文件以及分配的内存以外的文件声明变量时,你会使用{{1} } keyword作为函数指针变量声明的一部分。但是,如果它是在函数内的堆栈上分配的变量,或者如果在extern
内使用它来创建struct
的成员,那么您将不会使用struct
修饰符关于变量。
typedef是现代C的一个非常好的功能,因为它允许您创建一个等同于一种中途新类型的别名。要拥有创建新类型的全部功能,确实需要C ++的类类型功能,它允许为新类型定义运算符。但是,typedef确实提供了一种允许程序员为类型创建别名的好方法。
多年前,在将extern
添加到C标准之前的旧C编译器中,人们通常会使用C预处理器来定义宏来为复杂类型创建别名。 typedef
是一种更清洁的方式!
typedef的大多数用法是提供一种方法,使其更简洁,更清晰地编写变量定义。由于这个原因,它与typedef
定义一起使用很多。因此,您可能有struct
定义,如下所示:
struct
这将为typdef struct {
int iA;
int iB;
} MyStruct, *PMyStruct;
创建两个新别名,一个用于struct
本身,另一个用于指向struct
的指针,这些可能会像以下一样使用:
struct
此示例具有MyStruct exampleStruct;
PMyStruct pExampleStrut;
pExampleStruct = &exampleStruct;
关键字的基本结构,根据现有类型定义新类型,以及新类型的名称。
在typedef
添加到C标准之前,您将为结构指定一个标记,结果将是如下所示的代码:
typedef
在哪个指针处,人们通常会添加一个C预处理器定义,以便更容易编写,如下所示:
struct myTagStruct { // create a struct declaration with the tag of myTagStruct
int a;
int b;
};
struct myTagStruct myStruct; // create a variable myStruct of the struct
然后使用它:
#define MYTAGSTRUCT struct myTagStruct
然而,函数指针的MYTAGSTRUCT myStruct;
语法略有不同。它更像是函数声明,其中函数的名称是typedef
的实际名称或实际使用的别名。函数指针的typedef
的基本结构是typedef
关键字,后跟与函数原型相同的语法,其中函数的名称用作typedef
名称。
typedef
括号内容很重要,因为它们强制编译器以特定方式解释源文本。 C编译器具有用于解析源文本的规则,您可以通过使用括号来更改C编译器解释源文本的方式。规则与解析以及C编译器如何定位变量名称有关,然后通过使用关于左右关联性的规则来确定变量的类型。
typedef int (*pFunc)(int a1, int b1);
函数原型不是函数指针变量。 a = 5 * b + 1; // 5 times b then add 1
a = 5 * (b + 1); // 5 times the sum of b and 1
int *pFunc(int a1, int b1); // function prototype for function pFunc which returns a pointer to an int
int **pFunct(int a1, int b1); // function prototype for function pFunc which returns a pointer to a pointer to an int
int (*pfunc)(int a1, int b1); // function pointer variable for pointer to a function which returns an int
int *(*pFunc)(int a1, int b1); // function pointer variable for pointer to a function which returns a pointer to an int
的语法类似于未使用typedef
的变量定义的语法。
typedef
那么有一个指向函数的指针是什么意思?函数入口点具有地址,因为函数是位于特定存储区域的机器代码。函数的地址是函数机器代码的执行应该开始的地方。
编译C源代码时,函数调用被转换为一系列机器指令,跳转到函数的地址。实际的机器指令实际上并不是跳转,而是一个调用指令,它在跳转之前保存返回地址,这样当被调用函数完成时它可以返回到调用它的位置。
函数指针变量用作函数语句。两者之间的差异类似于数组变量和指针变量之间的差异。大多数C编译器将数组变量视为指向变量的常量指针。函数名被视为大多数C编译器对函数的常量指针。
函数指针给你带来的是灵活性,虽然它具有灵活性,因为任何强大的力量也会导致巨大的毁灭。
函数指针变量的一个用途是将函数地址作为参数传递给另一个函数。例如,C标准库有几个排序函数,它们需要一个排序函数的参数来比较被排序的两个元素。另一个例子是一个线程库,当你创建一个线程时,你指定要作为一个线程执行的函数的地址。
另一种情况是提供某种隐藏实现细节的界面。让我们假设您有一个打印功能,您想要用于几个不同的输出接收器或输出的位置,比如文件,打印机和终端窗口。这在本质上类似于C ++编译器如何实现虚函数或COM对象如何通过COM接口实现。所以你可以做类似下面的事情,这是一个非常简单的例子,缺少细节:
typedef int * pInt; // create typedef for pointer to an int
int *a; // create a variable that is a pointer to an int
pInt b; // create a variable that is a pointer to an int
typedef int (*pIntFunc)(int a1, int b1); // create typedef for pointer to a function
int (*pFuncA)(int a1, int b1); // create a variable pFuncA that is a pointer to a function
pIntFunc pFuncB; // create a variable pFuncB that is a pointer to a function
答案 2 :(得分:1)
为了明确其他人给出的解释,在C / C ++中,括号是右关联的,因此以下声明:
typedef int *pFunc(int, int);
相当于:
typedef int *(pFunc(int, int));
这将是函数的声明原型,返回指向整数的指针,而不是指向返回整数的函数的指针的声明。
这就是为什么你需要在(*pFunc)
周围编写括号来打破正确的关联,并告诉编译器pFunc是一个指向函数的指针,而不仅仅是一个函数。