注:本文可能多次提得同一内容,这是我从别的地方整理的,希望能够谅解,不想看的直接跳过就行
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);
这样一抽象,我们理解起来就相对容易多了.
Typedef和define的区别
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在语法上是一个存储类的关键字(如auto、extern、mutable、static、register等一样),虽然它并不真正影响对象的存储特性,如:
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在语法上是一个存储类的关键字(如auto、extern、mutable、static、register等一样),虽然它并不真正影响对象的存储特性。
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;//不可行 编译将失败,会提示“指定了一个以上的存储类”。