[C语言]关键字解析(二) typedef和#define以及区别

typedef关键字

typedef关键字是type define的缩写,之前看一本书上说,也许这个关键字更应该叫typerename,我个人是比较认同的这个观点的.因为这个关键字只是给已有的类型重新定义了一个方便使用的别名,并没有产生新的数据类型。说直白点typedef就是类型重命名
在实际应用中typedef主要有如下几个应用

1 给基本数据类型定义新名字(两方面作用)

①使数据类型名称简洁:

typedef unsigned int UINT;//unsigened int略显冗余,所以重新命名为UNIT (此处结尾有分号;)
int main()
{
	UNIT a; //这里使用了自己重新命名的UNIT
	return 0;
}

②代码移植性
通过C语言的代码移植,运行在不同的平台上,使用typedef就可以定义与平台无关的类型。假如用户的程序代码用到了浮点类型,不同平台支持的最高精度不同,就可以用typedef重新直接定义一个的浮点类型,代码在不同平台运行时修改其定义即可.

//平台一:支持double的精度
typedef double decimal
//平台二:支持float的精度
typedef float decimal   //在不同平台修改decimal的内容即可 

2.给数组的类型重新命名一个新名字

typedef int arr[10];//这里arr就是一个整型数组类型名,当用户需要定义包含10个元素的整型数组时
int main()
{
	arr my_arr; //这里就等同于int my_arr[10]
	return 0;
}

3.给指针类型重新命名一个新名字

//一个经典问题:
//下面的p1,p2是什么类型
int main()
{
	int* p1,p2; //此时int旁边的*属于p1,并不属于p2,所以p1为整形指针,p2为整型
}
//现在换一种情况
typedef int * ptr_t; //typedef对int*整体进行了重命名
int main()
{
	ptr_t p1,p2; //此时ptr_t属于p1,也属于p2,所以p1,p2都是整型指针!!!
}
  1. 给自定义数据类型(结构体,枚举,共用体等)重命名一个新名字
typedef struct node
{
	char a;
	int data;
}stu,*pstu 
//这里在定义结构体数据类型时,利用typedef定义了两个类型,stu和pstu分别为普通变量类型和指向结构的指针类型。

总结:typedef虽然可以让一些基本数据类型和自定义数据类型变得简洁,但也不能大量使用,过度使用typedef可能会增加代码的复杂性。当定义大量的复杂的自定义类型别名时,理解代码可能会变得更加困难。所以要适当运用!

define关键字

在讲解这个关键字之前,想给大家看看一段略带诙谐的代码
在这里插入图片描述

#define 叫做宏定义命令,是C语言预处理命令的一种.
宏定义就是用一个标识符来表示一个字符串,之后代码中出现了该标识符,就全部替换成指定的字符串.
因为只是简单替换,预处理程序对它不作任何检查,如有错误,只能在编译已被宏展开后的源程序时发现.

语法形式: #define name stuff (结尾无分号; 如果加上分号则连分号也一起替换)
#表示这是一条预处理命令,所有的预处理命令都以 # 开头。name是标识符的一种,命名规则和变量相同。stuff可以是数字、表达式、if 语句、函数等。

作用域:书写时必须在函数的外面,作用域为宏定义命令起到源程序结束(或者使用#undef)
宏定义的名字如果出现在引号之内,预处理程序不对其作宏代替

#define PI 3.14
int main()
{
	printf("%d",PI); //正常打印
	printf("PI"); //写在引号里,将不作宏替换,所以打印出来只是PI两个字母,而不是3.14
	return 0;
}
#undef PI
void func(){
    printf("%d",PI);//无法识别PI
}

带参数的#define

C语言允许宏带有参数。在宏定义中的参数称为“形式参数”,在宏调用中的参数称为“实际参数”。
比如下面这段代码

#define SQUARE(x)  x * x
int main()
{
	printf("%d",SQUARE(5));   //结果为25
	printf("%d",SQUARE(5+1)); //结果为11
	return 0;
}

第一个25相信很好理解,直接5×5=25,但是第二个为什么不是6×6=36.
原因在于宏只是一种简单的替换,所以在第二个例子中就被替换成了

printf("%d",5+1 * 5+1); //因为加减乘除的优先级,所以先算1*5,再分别+5再+1

所以用于对数值表达式进行求值的宏定义都应该用这种方式加上括号,避免在使用宏时由于参数中的操作符或邻近操作符之间不可预料的相互作用。

//改成如下形式
#define SQUARE(x)  (x) * (x)

注意: 在带参宏定义中,不会为形式参数分配内存,因此不必指明数据类型

看到这里相信大家也认为函数和带参数的宏有点相像,所以我将分析一下两者的区别
宏通常被应用于执行简单的运算

#define MAX(a, b) ((a)>(b)?(a):(b))  //比较两个数的大小

那为什么不用函数来完成这个任务?

  1. 用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多。所以宏比函数在程序的规模和速度方面更胜一筹。
  2. 更为重要的是函数的参数必须声明为特定的类型。所以函数只能在类型合适的表达式上使用。反之这个宏怎可以适用于整形、长整型、浮点型等.形式上更加灵活

当然和函数相比宏也有劣势的地方:

  1. 每次使用宏的时候,一份宏定义的代码将插入到程序中。除非宏比较短,否则可能大幅度增加程序的长度。
  2. 宏是没法调试的。
  3. 宏可能会带来运算符优先级的问题,导致程容易出现错误

宏有时候可以做函数做不到的事情。比如:宏的参数可以出现类型,但是函数做不到

#define MALLOC(num, type)  (type *)malloc(num * sizeof(type))

//在函数中使用时
MALLOC(10, int);//此处int类型就作为了参数之一
//预处理器替换之后:
(int *)malloc(10 * sizeof(int));

typedef和#define区别

最后,我们应该注意用宏定义表示数据类型和用 typedef 定义数据说明符的区别。

宏定义只是简单的字符串替换,在预处理阶段由预处理器来处理;而 typedef 是在编译阶段由编译器处理的,它并不是简单的字符串替换,而给原有的数据类型起一个新的名字,将它作为一种新的数据类型。

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码
< <上一篇
下一篇>>