变量定义或声明的语法是类型后面跟着一个或多个变量,可能带有修饰符。一些简单的例子如下:
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
表示该变量实际上是在使用变量的源文件所在位置之外的某个地方定义其内存分配。
使用typedef
typedef
是现代C语言的一个非常好的特性,因为它允许您创建一种类似于半新类型的别名。要完全具备创建新类型的功能,实际上需要C ++的类类型特征,这允许为新类型定义运算符。但是,typedef
确实提供了一种允许程序员为类型创建别名的良好方法。
大多数typedef
的用途是提供一种使变量定义更短、更简洁的方法。出于这个原因,它经常与struct
定义一起使用。因此,您可能会有以下struct
定义:
typdef struct {
int iA;
int iB;
} MyStruct, *PMyStruct;
这将为
struct
创建两个新的别名,一个用于
struct
本身,另一个用于指向
struct
的指针。这些别名可能会被使用,例如:
MyStruct exampleStruct;
PMyStruct pExampleStrut;
pExampleStruct = &exampleStruct;
这个例子展示了
typedef
关键字的基本结构,即通过现有类型定义新类型并为其命名。
typedef
之前的旧式C语言
在早期的C编译器中,人们经常使用C预处理器来定义宏,以创建复杂类型的别名。而
typedef
则是更为简洁的方法!
在
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
通常情况下,人们会在哪个指针处添加一个C预处理器定义,以便更容易地编写,例如:
然后像这样使用它:
MYTAGSTRUCT myStruct;
然而,在使用首选的
typedef
语法和使用预处理器
define
方法之间有一个主要区别。预处理器使用C源代码文件的文本来生成修改后的C源代码版本,然后由C编译器进行编译。而
typedef
关键字是由C编译器编译的C源代码的一部分,因此C编译器知道所定义的类型别名。
为了展示区别,请看下面的源代码。
#define PMYSTRUCT MyStruct *
typedef struct {
int a1;
int b1;
} MyStruct, *PMyStruct;
MyStruct sA, sB;
PMyStruct psA, psB;
PMYSTRUCT psxA, psxB;
psA = &sA;
psB = &sB;
psxA = &sA;
psxB = &sB;
使用typedef
与函数指针
typedef
用于函数指针的语法有些不寻常。它看起来有点像函数声明,但在指针语法上有一些微妙的变化。
typedef int (*pFunc)(int a1, int b1);
这段话的意思是:
- 创建一个名为
pFunc
的变量类型typedef
- 定义为
pFunc
类型的变量是一个指针
- 该变量所指向的是一个带有两个
int
参数并返回一个int
类型的函数
括号很重要,因为它们强制编译器以与默认规则不同的方式解释源文本。C编译器有一些解析源文本的规则,您可以通过使用括号来更改C编译器解释源文本的方式。这些规则涉及解析和如何定位变量名称,然后通过使用左结合和右结合的规则确定变量的类型。
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
的变量定义的语法。
typedef int * pInt;
int *a;
pInt b;
typedef int (*pIntFunc)(int a1, int b1);
typedef int *pFuncWhat(int a1, int b1);
int (*pFuncA)(int a1, int b1);
int *FuncDecl(int a1, int b1);
pIntFunc pFuncB;
那么什么是函数指针呢?函数入口点有一个地址,因为函数是位于特定内存区域的机器代码。函数的地址是执行函数机器代码应该开始的地方。
当C源代码被编译时,函数调用被转换为一系列跳转到函数地址的机器指令。实际的机器指令并不是真正的跳转,而是一个调用指令,在进行跳转之前保存返回地址,以便在被调用的函数完成后可以返回到调用它的位置。
函数指针变量类似于函数声明。两者之间的区别类似于数组变量和指针变量之间的区别。大多数C编译器将数组变量视为指向变量的常量指针。大多数C编译器将函数名称视为指向函数的常量指针。
使用函数指针可以给你带来灵活性,但这种灵活性就像任何强大的力量一样,也可能导致巨大的破坏。
函数指针变量的一个用途是将函数地址作为参数传递给另一个函数。例如,C标准库有几个排序函数,需要一个比较函数的参数,用于比较正在排序的两个元素。另一个例子是线程库,在创建线程时,您需要指定要执行的函数的地址。
由于函数指针是一个变量,因此如果您有一个需要全局可见性的函数指针,当您在除定义它并分配其内存的源文件之外的文件中声明变量时,您将使用extern关键字作为函数指针变量声明的一部分。但是,如果它是在函数内分配的堆栈上的变量,或者如果它在struct中被用来创建struct的成员,则不会在变量上使用extern修饰符。
file1.c
static int myfunc(int a, float b)
{
return (a + (int) (b * 100.0));
}
int(*pmyfunc)(int, float) = myfunc;
file1.h
extern int(*pmyfunc)(int, float);
文件 2.c
int iifunc (int a, int b)
{
return (a + b/10 + 5);
}
int jjfunc (int a, int (*pf)(int, float))
{
return ((a / 10) + pf(a, 2000.0));
}
int kkfunc (int a, char *pName)
{
int(*plocalfunc)(int, float) = pmyfunc;
int k = jjfunc(a, plocalfunc);
int l = iifunc(a, pmyfunc(a, 3000.0));
printf ("%s - %d\n", pName, k);
return k;
}
另一个情况是提供某种接口来隐藏实现细节。假设您有一个打印函数,您想要将其用于多个不同的输出位置或输出目标,例如文件、打印机和终端窗口。这与C++编译器实现虚拟函数或通过COM接口实现COM对象的方式类似。因此,您可以执行以下操作,这是一个非常简单的示例,缺少细节:
typedef struct {
int (*pOpenSink) (void);
int (*pPrintLine) (char *aszLine);
int (*pCloseSink) (void);
} DeviceOpsStruct;
DeviceOpsStruct DeviceOps [] = {
{PrinterOpen, PrinterLine, PrinterClose},
{FileOpen, FileLine, FileClose},
{TermOpen, TermLine, TermClose}
};
int OpenDevice (int iDev)
{
return DeviceOps[iDev].pOpenSink();
}
int LineDevice (int iDev, char *aszLine)
{
return DeviceOps[iDev].pPrintLine (aszLine);
}
int CloseDevice (int iDev)
{
return DeviceOps[iDev].pCloseSink();
}
int x
定义了一个变量。typedef int x
定义了一个类型;struct {...} x
定义了一个结构体变量,typedef struct {...} x
定义了一个类型。函数也是一样的。 - M Oehm