C中typedef函数指针的使⽤
类型定义的语法可以归结为⼀句话:只要在变量定义前⾯加上typedef,就成了类型定义。这⼉的原本应该是变量的东西,就成为了类型。
int integer;    //整型变量
int *pointer;  //整型指针变量
int array [5]; //整型数组变量
int *p_array [5]; //整型指针的数组的变量
int (*array_pointer) [5];//整型数组的指针的变量
int function (int param);//函数定义,也可将函数名看作函数的变量
int *function (int param);//仍然是函数,但返回值是整型指针
int (*function) (int param);//现在就是指向函数的指针了
若要定义相应类型,即为类型来起名字,就是下⾯的形式:
typedef int integer_t;                      //整型类型
typedef int *pointer_t;    //整型指针类型
typedef int array_t [5]; //整型数组类型
typedef int *p_array_t [5];    //整型指针的数组的类型
typedef int (*array_pointer_t) [5]; //整型数组的指针的类型
typedef int function_t (int param);    //函数类型
typedef int *function_t (int param);    //函数类型
typedef int (*function_t) (int param); //指向函数的指针的类型
注意:上⾯的函数类型在C中可能会出错,因为C中并没有函数类型,它的函数变量会⾃动退化成函数指针;在C++中好像是可以的。在这⾥主要说明的是形式上的相似性.
typedef的⼀般形式为:
typedef  类型定义名;
注意:上述⼀般形式中最后的分号不可缺少!
在编程中使⽤typedef⽬的⼀般有两个,⼀个是给变量⼀个易记且意义明确的新名字,另⼀个是简化⼀些⽐较复杂的类型声明。
其实,在C语⾔中声明变量的时候,有个存储类型指⽰符(storage-class-specifier),它包括我们熟悉的extern、static、auto、register。在不指定存储类型指⽰符的时候,编译器会根据约定⾃动取缺省值。另外,存储类型指⽰符的位置也是任意的(但要求在变量名和指针*之前),也就是说以下⼏⾏代码是等价的:
static const int i;
const static int i;
int const static i;
const int static i;
根据C语⾔规范,在进⾏句法分析的时候,typedef和存储类型指⽰符是等价的!所以,我们把上述使⽤static的地⽅替换为typedef:typedef const int i;
const typedef int i;
int const typedef i;
const int typedef i;
上述代码的语义是:将i定义为⼀个类型名,其等价的类型为const int。以后如果我们有i  a代码,就等价于const int a。对于有指针的地⽅也是⼀样的,⽐如:
int const typedef *t;那么代码t  p。就相当于int const *p。
另外,typedef不能和static等存储类型指⽰符同时使⽤,因为每个变量只能有⼀种存储类型,所以代码:typedef static int i;是⾮法的。
typedef有两种⽤法:
⼀、⼀般形式,定义已有类型的别名
  typedef  类型定义名;
⼆、创建⼀个新的类型
typedef  返回值类型新类型名(参数列表);
1)typedef int NUM[10];//声明整型数组类型
NUM n;//定义n为整型数组变量,其中n[0]--n[9]可⽤
2)typedef char* STRING;//声明STRING为字符指针类型
STRING p,s[10];//p为字符指针变量,s为指针数组
3)typedef int (*POINTER)();//声明POINTER为指向函数的指针类型,该函数返回整型值,没有参数
POINTER P1,P2;//p1,p2为POINTER类型的指针变量
说明:
1)⽤typedef可以声明各种类型名,但不能⽤来定义变量,⽤typedef可以声明数组类型、字符串类型、使⽤⽐较⽅便。
例如:定义数组,原来是⽤:int a[10],b[10],c[10],d[10];由于都是⼀维数组,⼤⼩也相同,可以先将此数组类型声明为⼀个名字:
typedef int ARR[10];
然后⽤ARR去定义数组变量:
ARR a,b,c,d;//ARR为数组类型,它包含10个元素。因此a,b,c,d都被定义为⼀维数组,含10个元素。可以看到,⽤typedef可以将数组类型和数组变量分离开来,利⽤数组类型可以定义多个数组变量。同样可以定义字符串类型、指针类型等。
2)⽤typedef只是对已经存在的类型增加⼀个类型名,⽽没有创造新的类型。
3)typedef与#define有相似之处,但事实上⼆者是不同的,#define是在预编译时处理,它只能做简单的字符串替换,⽽typedef是在编译时处理的。它并不是做简单的字符串替换,⽽是采⽤如同定义变量的⽅法那样来声明⼀个类型。
c++string类型例如:typedef int COUNT;和#define COUNT int的作⽤都是⽤COUNT代表int,单事实上它们⼆者是不同的。
两个陷阱:
陷阱⼀:
记住,typedef是定义了⼀种类型的新别名,不同于宏,它不是简单的字符串替换。⽐如:
先定义:
typedef char* PSTR;
然后:
int mystrcmp(const PSTR, const PSTR);
const PSTR实际上相当于const char*吗?不是的,它实际上相当于char* const。
原因在于const给予了整个指针本⾝以常量性,也就是形成了常量指针char* const。
简单来说,记住当const和typedef⼀起出现时,typedef不会是简单的字符串替换就⾏。
陷阱⼆:
typedef在语法上是⼀个存储类的关键字(如auto、extern、mutable、static、register等⼀样),虽然它并不真正影响对象的存储特性,如:typedef static int INT2; //不可⾏
编译将失败,会提⽰“指定了⼀个以上的存储类”。
(1)typedef ⾸先是⽤来定义新的类型,i.e typedef struct {.....}mystruct; 在以后引⽤时,就可以⽤ mystruct 来定义⾃⼰的结构体,mystruct structname1,mystruct structname2.
(2)typedef 常⽤的地⽅,就在定义函数指针,⾏为和宏定义类似,⽤实际类型替换同义字,但是有区别: typedef 在编译时被解释,因此让编译器来应付超越预处理器能⼒的⽂本替换。
案例⼀:
通常讲,typedef要⽐#define要好,特别是在有指针的场合。请看例⼦:
typedef char *pStr1;
#define pStr2 char *;
pStr1 s1, s2;
pStr2 s3, s4;
在上述的变量定义中,s1、s2、s3都被定义为char *,⽽s4则定义成了char,不是我们所预期的指针
变量,根本原因就在于#define只是简单的字符串替换⽽typedef则是为⼀个类型起新名字。
案例⼆:
下⾯的代码中编译器会报⼀个错误,你知道是哪个语句错了吗?
typedef char * pStr;
char string[4] = "abc";
const char *p1 = string;
const pStr p2 = string;
p1++;
p2++;
是p2++出错了。这个问题再⼀次提醒我们:typedef和#define不同,它不是简单的⽂本替换。上述代码中const pStr p2并不等于const char * p2。const pStr p2和const long x本质上没有区别,都是对变量进⾏只读限制,只不过此处变量p2的数据类型是我们⾃⼰定义的⽽不是系统固有类型⽽已。因此,c
onst pStr p2的含义是:限定数据类型为char *的变量p2为只读,因此p2++错误。
⽤法⼀:
typedef  返回类型(*新类型)(参数表)
typedef int ( * MYFUNCTION )( int,int ); 这种⽤法⼀般是在定义函数指针 MYFUNCTION 是⼀个函数指针类型有两个整型的参数,返回⼀个整型。
在对于这样的形式,去掉typedef和别名就剩下了的是原变量的类型如:int (*)(int ,int); 在函数指针中,抽象得看待函数,函数名其实就是⼀个
地址,函数名指向该函数的代码在内存的⾸地址。
⽤法⼆:复杂函数声明类型
下⾯是三个变量的声明⽤typedef 如何来做???
>1 int *(*a[5])(void *,void *);
>2 void (*b[5])(void (*)());
>3 float (*)()(*pa)[10];
分析如下:
>1 int *(*a[5])(void *,void *);
//pFUN是⾃⼰建⽴的类型别名 typedef int *(* pFUN)(void  *,void *); //等价于int *(*a[5])(void *,void *);
pFUN a[5];  a是⼀个数组,包含五个元素,这些元素都是函数指针,该函数指针所指的函数的返回值是int的指针输⼊参数有两个都是void *. >2 void (*b[5])( void (*)() );
// first 为蓝⾊的声明⼀个新的类型 typedef void (*pFUNParam)( );
//整体声明⼀个新类型  typedef void (*pFUN)(FUNParam);
//使⽤定义的新类型声明对象等价于void (*b[5])( void (*)() );
pFUN b[5]; b 是⼀个含有5个元素的数组,每个元素都是⼀个函数指针,该函数指针所指的函数的返回值是void.输⼊参数是另⼀个函数指针,这个函数指针没有参数,返回值为空。在这⾥套⽤了连续的函数指针。本⾝就是⼀个函数指针,⽽且参数也是⼀个函数指针。
>3 float (*)()(*pa)[10];
//first 为上⾯的蓝⾊表达式声明⼀个新类型 typedef float (*pFUN)();
//整体声明⼀个新类型typedef pFUN (* pFunParam)[10];
//使⽤定义的新类型来声明对象等价与float (*)()(*pa)[10];
pa 是⼀个指针,指针指向⼀个含有10个元素的数组,数组的元素是函数指针,函数指针所指的函数没有输⼊参数,返回值为float.
**********************************************
使⽤typedef简化复杂的变量声明
1)、定义⼀个有10个指针的数组,该指针指向⼀个函数,该函数有⼀个整形参数,并返回⼀个整型?
第⼀种⽅法:int (*a[10])(int);
第⼆种⽅法:typedef int (*pfunc)(int);
pfunc a[10];
2)、定义⼀个有10个指针的数组,该指针指向⼀个函数,该函数有⼀个函数指针(不带参数,返回值为空)参数,并返回空。
第⼀种⽅法:void (*a[10])(void (*)(void));
第⼆种⽅法:typedef void (*pfuncParam)(void);
typedef void (*pfunc)(pfuncParam);
pfunc a[10];
3)、⼀个指向有10个函数指针(不带参数,返回值为double)数组的指针
第⼀种⽅法:double (*)(void) (*p)[10];
第⼆种⽅法:typedef double (*pfunc)(void);
typedef pfunc (*pfuncParam)[10];
pfuncParam p;
从变量名看起,先往右,再往左,碰到⼀个圆括号就调转阅读的⽅向;括号内分析完就跳出括号,还是按先右后左的顺序,如此循环,直到整个声明分析完。举例:
int (*func)(int *p);
⾸先到变量名func,外⾯有⼀对圆括号,⽽且左边是⼀个*号,这说明func是⼀个指针;然后跳出这个圆括号,先看右边,⼜遇到圆括号,这说明(*func)是⼀个函数,
所以func是⼀个指向这类函数的指针,即函数指针,这类函数具有int*类型的形参,返回值类型是int。
int (*func[5])(int *);
func右边是⼀个[]运算符,说明func是具有5个元素的数组;func的左边有⼀个*,说明func的元素是指针(注意这⾥的*不是修饰func,⽽是修饰func[5]的,
原因是[]运算符优先级⽐*⾼,func先跟[]结合)。跳出这个括号,看右边,⼜遇到圆括号,说明func数组的元素是函数类型的指针,它指向的函数具有int*类型的形参,
返回值类型为int。
也可以记住2个模式:
type (*)(....)函数指针
type (*)[]数组指针
**********************************************
finally
typedef 使⽤最多的地⽅是创建易于记忆的类型名,⽤它来归档程序员的意图。类型出现在所声明的变量名字中,位于 ''typedef'' 关键字右边。例如:
typedef int size;此声明定义了⼀个 int 的同义字,名字为 size。注意 typedef 并不创建新的类型。它仅仅为现有类型添加⼀个同义字。你可以在任何需要 int 的上下⽂中使⽤ size:
void measure(size * psz); size array[4];size len = length();std::vector <size> vs; typedef 还可以掩饰符合类型,如指针和数组。例如,你不⽤象下⾯这样重复定义有 81 个字符元素的数组:
char line[81];char text[81];定义⼀个 typedef,每当要⽤到相同类型和⼤⼩的数组时,可以这样:
typedef char Line[81]; Line text, secondline;getline(text);同样,可以象下⾯这样隐藏指针语法:
typedef char * pstr;int mystrcmp(pstr, pstr);这⾥将带我们到达第⼀个 typedef 陷阱。标准函数 strcmp()有两个‘const char *'类型的参数。因此,它可能会误导⼈们象下⾯这样声明 mystrcmp():
int mystrcmp(const pstr, const pstr); 这是错误的,按照顺序,‘const pstr'被解释为‘char * const'(⼀个指向 char 的常量指针),⽽不是‘const char *'(指向常量 char 的指针)。这个问题很容易解决:
typedef const char * cpstr; int mystrcmp(cpstr, cpstr); // 现在是正确的记住:不管什么时候,只要为指针声明 typedef,那么都要在最终的typedef 名称中加⼀个 const,以使得该指针本⾝是常量,⽽不是对象。
代码简化
上⾯讨论的 typedef ⾏为有点像 #define 宏,⽤其实际类型替代同义字。不同点是 typedef 在编译时被解释,因此让编译器来应付超越预处理器能⼒的⽂本替换。例如:
typedef int (*PF) (const char *, const char *);这个声明引⼊了 PF 类型作为函数指针的同义字,该函数有两个 const char * 类型的参数以及⼀个 int 类型的返回值。如果要使⽤下列形式的函数声明,那么上述这个 typedef 是不可或缺的:
PF Register(PF pf);Register() 的参数是⼀个 PF 类型的回调函数,返回某个函数的地址,其署名与先
前注册的名字相同。做⼀次深呼吸。下⾯我展⽰⼀下如果不⽤ typedef,我们是如何实现这个声明的:
int (*Register (int (*pf)(const char *, const char *))) (const char *, const char *); 很少有程序员理解它是什么意思,更不⽤说这种费解的代码所带来的出错风险了。显然,这⾥使⽤ typedef 不是⼀种特权,⽽是⼀种必需。持怀疑态度的⼈可能会问:“OK,有⼈还会写这样的代码吗?”,快速浏览⼀下揭⽰ signal()函数的头⽂件 <csinal>,⼀个有同样接⼝的函数。
typedef 和存储类关键字(storage class specifier)
这种说法是不是有点令⼈惊讶,typedef 就像 auto,extern,mutable,static,和 register ⼀样,是⼀个存储类关键字。这并是说 typedef 会真正影响对象的存储特性;它只是说在语句构成上,typedef 声明看起来象 static,extern 等类型的变量声明。下⾯将带到第⼆个陷阱:typedef register int FAST_COUNTER; // 错误编译通不过。问题出在你不能在声明中有多个存储类关键字。因为符号 typedef 已经占据了存储类关键字的位置,在 typedef 声明中不能⽤ register(或任何其它存储类关键字)。
促进跨平台开发
typedef 有另外⼀个重要的⽤途,那就是定义机器⽆关的类型,例如,你可以定义⼀个叫 REAL 的浮点类型,在⽬标机器上它可以i获得最⾼的精度:
typedef long double REAL; 在不⽀持 long double 的机器上,该 typedef 看起来会是下⾯这样:
typedef double REAL; 并且,在连 double 都不⽀持的机器上,该 typedef 看起来会是这样:、
typedef float REAL; 你不⽤对源代码做任何修改,便可以在每⼀种平台上编译这个使⽤ REAL 类型的应⽤程序。唯⼀要改的是 typedef 本⾝。在⼤多数情况下,甚⾄这个微⼩的变动完全都可以通过奇妙的条件编译来⾃动实现。不是吗? 标准库⼴泛地使⽤ typedef 来创建这样的平台⽆关类型:size_t,ptrdiff 和 fpos_t 就是其中的例⼦。此外,象 std::string 和 std::ofstream 这样的 typedef 还隐藏了长长的,难以理解的模板特化语法,例如:basic_string<char, char_traits<char>,allocator<char>> 和 basic_ofstream<char, char_traits<char>>。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。