语法形式
-
宏定义:使用
#define
关键字进行定义,通常以一个标识符来代表一段代码片段或一个常量值。定义常量时,形式为#define 常量名 常量值
;定义带参数的宏时,形式为#define 宏名(参数列表) 宏体
。 -
函数调用:函数需要先进行声明或定义,包括函数的返回值类型、函数名、参数列表和函数体等。调用函数时,使用函数名并传入相应的参数,形式为
函数名(参数值)
。
执行时机
- 宏定义:宏定义是在预处理阶段进行文本替换,即在编译之前,预处理器会将程序中所有出现的宏名替换为对应的宏体。
- 函数调用:函数调用发生在程序运行时,当执行到函数调用语句时,程序会跳转到函数的定义处执行函数体中的代码,执行完毕后再返回调用点继续执行后续代码。
参数传递
- 宏定义:带参数的宏在替换时,只是简单地将参数值代入到宏体中,不会进行数据类型检查和转换。
- 函数调用:函数的参数传递会进行严格的数据类型检查和必要的类型转换,确保传递的参数类型与函数定义中的参数类型匹配。
返回值
- 宏定义:宏本身并没有返回值的概念,它只是进行简单的文本替换,替换后的表达式或语句会按照正常的程序逻辑执行,其结果取决于替换后的代码。
-
函数调用:函数可以通过
return
语句返回一个值,该值的类型与函数声明的返回值类型一致,函数调用表达式的值就是函数的返回值。
代码展开
- 宏定义:宏在预处理阶段会进行简单的文本替换,将宏名替换为宏体,可能会导致代码膨胀,尤其是当宏体比较复杂且在程序中多次使用时。
- 函数调用:函数在调用时,无论在程序中被调用多少次,函数体的代码只存在一份,不会像宏定义那样进行多次展开,相对来说代码的空间复杂度较低。
调试难度
- 宏定义:由于宏是在预处理阶段进行替换,调试时可能会因为替换后的代码与原始代码差异较大而增加调试难度,难以直接定位问题所在。
- 函数调用:函数调用可以通过调试器方便地跟踪进入函数内部,查看函数的执行过程和中间结果,调试相对容易。
作用域
-
宏定义:宏定义的作用域从定义处开始,到文件末尾结束,但可以通过
#undef
指令提前取消宏定义。宏定义没有局部作用域的概念,一旦定义,在其作用域内都可以使用。 - 函数调用:函数具有明确的作用域,可以在不同的文件中定义和调用,通过函数声明和作用域规则来控制函数的可见性和可访问性。
示例
- 宏定义
#define SQUARE(x) ((x) * (x))
int main() {
int num = 5;
int result = SQUARE(num);
printf("The square of %d is %d\n", num, result);
return 0;
}
在上述代码中,SQUARE
宏在预处理阶段会被替换为((num) * (num))
,从而计算出num
的平方。
- 函数调用
int square(int x) {
return x * x;
}
int main() {
int num = 5;
int result = square(num);
printf("The square of %d is %d\n", num, result);
return 0;
}