Typedef与define解析

来源:互联网 发布:java三大框架教程 编辑:程序博客网 时间:2024/05/21 11:00

typedef常见用法

注:本文可能多次提得同一内容,这是我从别的地方整理的,希望能够谅解,不想看的直接跳过就行


1.常规变量类型定义例如:typedef unsigned char uchar描述:uchar等价于unsigned char类型定义      uchar c声明等于unsignedchar c声明

2.数组类型定义例如: typedef int array[2];注:可理解为typedef int[] array描述: array等价于 int[2]定义;      array a声明等价于inta[2]声明

扩展: typedefint array[M][N];描述: array等价于 int[M][N]定义;      array a声明等价于inta[M][N]声明

3.指针类型定义例如:  typedef  int *pointer;描述: pointer等价于 int*定义;      pointer p声明等价于int*a声明

例如: typedef int *pointer[M];描述: pointer等价于 int*[M]定义;      pointer p声明等价于int*a[M]声明明

4.函数地址说明描述:C把函数名字当做函数的首地址来对待,我们可以使用最简单的方法得到函数地址例如: 函数:int func(void);       unsigned long funcAddr=(unsigned long)func      funcAddr的值是func函数的首地址

5.函数声明例如: typedef int func(void);                   (注:可理解为typedefint (void ) func       func等价于 int(void)类型函数描述1 func f声明等价于 int f(void)声明,用于文件的函数声明描述2 func *pf声明等价于 int (*pf)(void)声明,用于函数指针的生命,见下一条6.函数指针例如: typedef int (*func)(void)         注:可理解为typedef int (void ) *  func描述: func等价于int (*)(void)类型       func pf等价于int(*pf)(void)声明,pf是一个函数指针变量

7.识别typedef的方法:a).第一步。使用已知的类型定义替代typdef后面的名称,直到只剩下一个名字不识别为正确      typedefu32   (*func)(u8);      从上面的定义中找到 typedef__u32  u32;typedef __u8 u8      继续找到 typedef unsigned int__u32;typedef unsigned char __u8;      替代位置名称 typedef unsignedint  (*func)(void);      现在只有func属于未知b).第二步.未知名字为定义类型,类型为取出名称和typedef的所有部分,如上为 func等价于unsignedunsigned  int  (*)(unsigned  char);c).第三部.定义一个变量时,变量类型等价于把变量替代未知名字的位置所得到的类型     func f等价于unsigned unsignedint  (*f)(unsigned char);

下面我们利用上面的方法分析一个稍微复杂点的函数声明:

int (*Instance (int (*pf)(const char *,const char *)))(const char *, const char *);

咋一看还真有点蒙,但是我们试着使用上面的方法整理一下这个声明:

typedef int(*Parameter)(constchar *, const char *);

Parameter等价于int(*)(const char *, const char *);

所以上面的声明等价于Parameter Instance(Parameter pf);

这样一抽象,我们理解起来就相对容易多了.

 

Typedefdefine的区别  

1 首先,二者执行时间不同

关键字typedef在编译阶段有效,由于是在编译阶段,因此typedef有类型检查的功能。

Define则是宏定义,发生在预处理阶段,也就是编译之前,它只进行简单而机械的字符串替换,而不进行任何检查。

#define用法例子:

#define f(x) x*xmain( ){ int a=6,b=2,c; c=f(a) / f(b); printf("%d \\n",c);}

程序的输出结果是: 36,根本原因就在于#define只是简单的字符串替换。

 

2 功能不同

Typedef用来定义类型的别名,这些类型不只包含内部类型(int,char等),还包括自定义类型(如struct),可以起到使类型易于记忆的功能。

如: typedef int (*PF) (const char *, const char *);

定义一个指向函数的指针的数据类型PF,其中函数返回值为int,参数为const char *。

typedef 有另外一个重要的用途,那就是定义机器无关的类型,例如,你可以定义一个叫REAL 的浮点类型,在目标机器上它可以i获得最高的精度:typedef long double REAL;在不支持 long double 的机器上,该 typedef 看起来会是下面这样:typedef double REAL;并且,在连 double 都不支持的机器上,该 typedef 看起来会是这样:typedef float REAL;

 

#include<stdio.h>typedef int arr[10];void main(){ arr a; int i; for(i=0;i<10;i++) {  a=i*i;  printf("%d\n",a); }}


 

#define不只是可以为类型取别名,还可以定义常量、变量、编译开关等。

 

3 作用域不同

#define没有作用域的限制,只要是之前预定义过的宏,在以后的程序中都可以使用。

而typedef有自己的作用域。

void   fun()   
  {   
      #define   A   int   
  }   
    
  void   gun()   
  {   
        //在这里也可以使用A,因为宏替换没有作用域,   
        //但如果上面用的是typedef,那这里就不能用A  ,不过一般不在函数内使用typedef
  }

4 对指针的操作

二者修饰指针类型时,作用不同。

Typedef int * pint;

#define PINT int *

 

Const pint p//p不可更改,p指向的内容可以更改,相当于 int * const p;

Const PINT p//p可以更改,p指向的内容不能更改,相当于 const int *p;或 int const *p


pint s1, s2; //s1和s2都是int型指针
PINT s3, s4; //相当于int * s3,s4;只有一个是指针。

 

 

typedef的四个用途和两个陷阱

用途一: 
定义一种类型的别名,而不只是简单的宏替换。可以用作同时声明指针型的多个对象。比如: 
char*     pa,    pb;       //     这多数不符合我们的意图,它只声明了一个指向字符变量的指针,   
//     和一个字符变量; 
以下则可行: 
typedef     char*    PCHAR;       //     一般用大写 
PCHAR     pa,    pb;                  //     可行,同时声明了两个指向字符变量的指针 
虽然: 
char     *pa,     *pb; 
也可行,但相对来说没有用typedef的形式直观,尤其在需要大量指针的地方,typedef的方式更省事。 

用途二: 
用在旧的C代码中(具体多旧没有查),帮助struct。以前的代码中,声明struct新对象时,必须要带上struct,即形式为:     struct     结构名    对象名,如: 
struct     tagPOINT1 

         int     x; 
         int     y; 
}; 
struct     tagPOINT1    p1;   

而在C++中,则可以直接写:结构名     对象名,即: 
tagPOINT1     p1; 

估计某人觉得经常多写一个struct太麻烦了,于是就发明了: 
typedef     struct     tagPOINT 

         int     x; 
         int     y; 
}POINT; 

POINT     p1;    //     这样就比原来的方式少写了一个struct,比较省事,尤其在大量使用的时候 

或许,在C++中,typedef的这种用途二不是很大,但是理解了它,对掌握以前的旧代码还是有帮助的,毕竟我们在项目中有可能会遇到较早些年代遗留下来的代码。 

用途三:
 
typedef来定义与平台无关的类型。 
比如定义一个叫     REAL     的浮点类型,在目标平台一上,让它表示最高精度的类型为: 
typedef     long    double     REAL;   
在不支持     long    double     的平台二上,改为: 
typedef     double    REAL;   
在连     double     都不支持的平台三上,改为: 
typedef     float    REAL;   
也就是说,当跨平台时,只要改下     typedef     本身就行,不用对其他源码做任何修改。 
标准库就广泛使用了这个技巧,比如size_t。 
另外,因为typedef是定义了一种类型的新别名,不是简单的字符串替换,所以它比宏来得稳健(虽然用宏有时也可以完成以上的用途)。 

用途四: 
为复杂的声明定义一个新的简单的别名。方法是:在原来的声明里逐步用别名替换一部分复杂声明,如此循环,把带变量名的部分留到最后替换,得到的就是原声明的最简化版。举例: 

1.     原声明:int     *(*a[5])(int,    char*); 可以写成int * (int,char *) * [] a;
变量名为a,直接用一个新别名pFun替换a就可以了: 
typedef     int    *(*pFun)(int,     char*);   
原声明的最简化版: 
pFun     a[5];   

2.     原声明:void     (*b[10])    (void     (*)()); 
变量名为b,先替换右边部分括号里的,pFunParam为别名一: 
typedef     void    (*pFunParam)(); 
再替换左边的变量b,pFunx为别名二: 
typedef     void    (*pFunx)(pFunParam); 
原声明的最简化版: 
pFunx     b[10]; 

3.     原声明:doube(*)()     (*e)[9];   
变量名为e,先替换左边部分,pFuny为别名一: 
typedef     double(*pFuny)(); 
再替换右边的变量e,pFunParamy为别名二 
typedef     pFuny    (*pFunParamy)[9]; 
原声明的最简化版: 
pFunParamy     e;   

理解复杂声明可用的右左法则:从变量名看起,先往右,再往左,碰到一个圆括号就调转阅读的方向;括号内分析完就跳出括号,还是按先右后左的顺序,如此循环,直到整个声明分析完。举例: 
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     (*)[]数组指针   
--------------------------------- 

陷阱一: 
记住,typedef是定义了一种类型的新别名,不同于宏,它不是简单的字符串替换。比如: 
先定义: 
typedef     char*     PSTR; 
然后: 
int     mystrcmp(const    PSTR,     const     PSTR); 

const     PSTR实际上相当于const    char*吗?不是的,它实际上相当于char*     const。 
原因在于const给予了整个指针本身以常量性,也就是形成了常量指针char*     const。 
简单来说,记住当const和typedef一起出现时,typedef不会是简单的字符串替换就行。 

陷阱二: 
typedef在语法上是一个存储类的关键字(如autoexternmutablestaticregister等一样),虽然它并不真正影响对象的存储特性,如: 
typedef    static     int    INT2;     //不可行 
编译将失败,会提示“指定了一个以上的存储类”。

#define,const,typedef三者联系与区别

一. #define与const

联系:都可以用来定义常量

区别:

1. const常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行类型安全检查。而对后者只进行字符替换,没有类型安全检查,并且在字符替换可能会产生意料不到的错误(边际效应)。

2. 前者在堆栈分配了空间,而后者只是把具体数值直接传递到目标变量罢了。或者说,const的常量是一个Run-Time的概念,他在程序中确确实实的存在并可以被调用、传递。而#define常量则是一个Compile-Time概念,它的生命周期止于编译期:在实际程序中他只是一个常数、一个命令中的参数,没有实际的存在。


3.const常量存在于程序的数据段,#define常量存在于程序的代码段。

4. 有些集成化的调试工具可以对const常量进行调试,但是不能对宏常量进行调试

 

 

看如下例子:

E), #define INT32 int

unsigned INT32 i =10;

F),typedef int int32;

unsigned int32 j =10;

其中F)编译出错,为什么呢?E)不会出错,这很好理解,因为在预编译的时候INT32

被替换为int,而unsigned int i = 10;语句是正确的。但是,很可惜,用typedef取的别

名不支持这种类型扩展。另外,想想typedefstatic int int32 行不行?为什么?

答:估计编译器将这句话理解为:unsigned ( int)   int  j =10; 即INT32代表的 int  和unsigned ( int ) 是两次声明,所以出错。

typedef在语法上是一个存储类的关键字(如autoexternmutablestaticregister等一样),虽然它并不真正影响对象的存储特性。

 

typedef小结

typedef关键字

typedef的真正意思是给一个已经存在的数据类型(注意:是类型不是变量)取一个别

名,而非定义一个新的数据类型。

比如:华美绝伦的芍药,就有个别名---“将离”。中国古代男女交往,往往以芍药相赠,表达惜别之情,送芍药就意味着即将分离。这样新的名字与原来的名字相比,就更能表达出想要表达的意思。

typedef struct student

{

//code

}Stu_st,*Stu_pst;//命名规则请参考本章前面部分

A),structstudent stu1;和Stu_st stu1;没有区别。

B),structstudent *stu2;和Stu_pst stu2;和Stu_st*stu2;没有区别。

typedef作用范围

typedef的最大作用范围只能是其所在的编译单元,不同C文件里面的typedef相互独立,完全没有任何限制。不同函数体也相互独立

#include <stdio.h>

typedef double INT32;

INT32 gb ='a';    //sizeof gb =8  double

int main(){    typedef intINT32;         INT32b = 'a';     //sizeof b=4  int    return 0;}

  

 

typedef的四个用途和三个陷阱

 

用途一:定义一种类型的别名,而不只是简单的宏替换。可以用作同时声明指针型的多个对象。比如:char*   pa,   pb;  尤其在需要大量指针的地方,typedef的方式更省事。

用途二:定义struct详细分析见 另一篇文章..

用途三:用typedef来定义与平台无关的类型。

比如定义一个叫   REAL   的浮点类型,

在目标平台一上,让它表示最高精度的类型为:typedef   long   double   REAL;   

在不支持   long   double   的平台二上,改为:typedef   double   REAL;   

在连   double   都不支持的平台三上,改为:typedef   float   REAL;   

也就是说,当跨平台时,只要改下   typedef   本身就行,不用对其他源码做任何修改。

标准库就广泛使用了这个技巧,比如size_t。另外,因为typedef是定义了一种类型的新别名,不是简单的字符串替换,所以它比宏来得稳健(虽然用宏有时也可以完成以上的用途)。

用途四:为复杂的声明定义一个新的简单的别名。

方法是:在原来的声明里逐步用别名替换一部分复杂声明,如此循环,把带变量名的部分留到最后替换,得到的就是原声明的最简化版。

理解复杂声明可用的“右左法则”:

从变量名看起,先往右,再往左,碰到一个圆括号就调转阅读的方向;

括号内分析完就跳出括号,还是按先右后左的顺序,如此循环,直到整个声明分析完。

举例: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   (*)[]数组指针  

 

 

陷阱一:不能组合使用

typedef double INT32;

unsigned INT32 ga;  //错误的写法

陷阱二:(const连用)

记住,typedef是定义了一种类型的新别名,不同于宏,它不是简单的字符串替换。比如:

先定义:typedef   char*   PSTR; 

然后:

int   mystrcmp(const   PSTR,   const   PSTR);

const   PSTR相当于const   char*吗?不是的,它实际上相当于char*   const。

原因在于const给予了整个指针本身以常量性,也就是形成了常量指针char*   const。

指针本身是const的指针指向的变量是可以变的。

简单来说,记住当const和typedef一起出现时,typedef不会是简单的字符串替换。

陷阱三:

typedef在语法上是个存储类的关键字(如auto、extern、mutable、static、register等一样),虽然它并不真正影响对象的存储特性,如:

typedef   static   int   INT2;//不可行 编译将失败,会提示“指定了一个以上的存储类”。

原创粉丝点击