4:使用#和##
4.1:#
#:作用:把参数插入到字符串中
使用方法:
int main() { int a = 20; printf("the value of a is %d\n",a); int b = 15; printf("the value of b is %d\n",b); float f = 4.5f; printf("the value of f is %f\n",f); return 0; } 我们有这么一个需求,可不可以定义一个函数print完成a,b,f三者的打印任务呢? 答案是:不可以, 因为函数的参数必须声明为特定的类型。 所以函数只能在类型合适的表达式上使用。 也就是说: 函数的参数是与类型有关的,如果参数的类型不同,就需要不同的函数,即使他们执行的任务是相同的。
不过宏可以完成这个任务
我们先来介绍关于字符串的一个很容易被忽视的细节
int main() { char* p = "hello ""world\n"; printf("hello ""world\n"); printf("%s", p); }
从中我们发现字符串是有自动连接的特点的。
而
printf("the value of a is %d\n",a); printf("the value of b is %d\n",b); printf("the value of f is %f\n",f); 这三个printf函数中这三者的不同点就是 1:字符串中的a,b,f字符 2:占位符%d,%d,%f不同 又因为:字符串是有自动连接的特点的 所以我们想: printf("the value of" "a" "is" "%d" "\n",a); printf("the value of" "b" "is" "%d" "\n",b); printf("the value of" "f" "is" "%f" "\n",f); 能不能将这两个不同点当成参数传入呢?
所以我们这样去写
从下面的代码中我们就能够看出#的作用来
也就是:把参数插入到字符串中
#define PRINT(n,format) printf("the value of "#n" is " format "\n",n) // 当n为a时: #n:"a" printf("the value of ""a"" is " "%d" "\n",a); // 当n为b时: #n:"b" printf("the value of ""b"" is " "%d" "\n",b); // 当n为f时: #n:"f" printf("the value of ""f"" is " "%f" "\n",f); int main() { int a = 20; PRINT(a,"%d"); int b = 15; PRINT(b,"%d"); float f = 4.5f; PRINT(f, "%f"); return 0; }
不过,这里只有当字符串作为宏参数的时候才可以把字符串放在字符串中
4.2:##
##可以把位于它两边的符号合成一个符号。 它允许宏定义从分离的文本片段创建标识符。 注: 这样的连接必须产生一个合法的标识符。否则其结果就是未定义的。
例子:
#define PT(x,y) x##y int main() { int Hello0716 = 2024; printf("%d\n", PT(Hello,0716)); return 0; }
5.带副作用的宏参数
当宏参数在宏的定义中出现超过一次的时候, 如果参数带有副作用,那么你在使用这个宏的时候就可能出现危险,导致不可预测的后果。 副作用就是表达式求值的时候出现的永久性效果。 例如: int a = 10; int b = ++a;//给b赋值时改变了a,也就是产生了副作用 int b = a + 1;//无副作用 x + 1;//不带副作用 x++;//带有副作用
例子:
#define MAX(a,b) ((a)>(b)?(a):(b)) int c = ((a++) > (b++) ? (a++) : (b++)); 问:a,b,c分别等于什么? int main() { int a = 5; int b = 6; //int c = MAX(a++,b++); int c = ((a++) > (b++) ? (a++) : (b++)); // 5 6 7 //这个最后位置的b++这个整体表达式的值是7,这个7赋值给c // 7 6 8 printf("c=%d\n", c);//7 注意c不是有歧义 printf("a=%d\n", a);//6 printf("b=%d\n", b);//8 return 0; }
6:宏和函数对比
宏通常被应用于执行简单的运算。 比如在两个数中找出较大的一个。 #define MAX(a,b) ((a)>(b)?(a):(b)) 那为什么不用函数来完成这个任务? 原因有二: 1. 用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多。 所以宏比函数在程序的规模和速度方面更胜一筹。 2. 更为重要的是函数的参数必须声明为特定的类型。 所以函数只能在类型合适的表达式上使用。反之这个宏却可以适用于整形、长整型、浮点型等可以 用于 > 来比较的类型。 宏是类型无关的。 宏的缺点:当然和函数相比宏也有劣势的地方: 1. 每次使用宏的时候,一份宏定义的代码将插入到程序中。除非宏比较短,否则可能大幅度增加程序 的长度。 2. 宏是没法调试的。 3. 宏由于类型无关,没有类型检查,也就不够严谨。 4. 宏可能会带来运算符优先级的问题,导致程序容易出现错。
宏有些能力是函数绝对没有的 宏有时候可以做函数做不到的事情。比如:宏的参数可以出现类型,但是函数做不到。 #define MALLOC(num,type) (type*)malloc(num*sizeof(type)) int main094() { //int* p = (int*)malloc(126 * sizeof(int)); int* p = MALLOC(126, int);//传参方便 return 0; }
7:#undef及命名约定
7.1:#undef
#undef 这条指令用于移除一个宏定义 #undef NAME 如果现存的一个名字需要被重新定义,那么它的旧名字首先要被移除。
7.2:命名约定
一般来讲函数的宏的使用语法很相似。
所以语言本身没法帮我们区分二者。
那我们平时的一个习惯是:
把宏名全部大写
函数名不要全部大写
二:条件编译
在编译一个程序的时候
我们如果要将一条语句(一组语句)编译或者放弃是很方便的。
因为我们有条件编译指令。 比如说:
调试性的代码,删除可惜,保留又碍事,所以我们可以选择性地编译。
条件编译在预编译时执行
1:单分支与多分支的条件编译
1.1 单分支
#define M 1 int main() { #if M printf("hello"); #endif return 0; } //等同于注释: #if 0 int main() { return 0; } #endif
1.2 多分支
#define I 2 int main() { #if (I==1) printf("(1)执行"); #elif (I==2) printf("(2)执行"); #else printf("(3)执行"); #endif return 0; }
注意:
1.#if后面必须有#endif
2.#endif前面必须有#if
2:判断是否被定义
判断是否被定义(只关心是否被定义过,而不关心具体的真/假) 一定不要忘记#endif 被定义过则执行 #if defined(symbol) #endif #ifdef symbol #endif 未被定义过才执行 #if !defined(symbol) #endif #ifndef symbol #endif
3:嵌套指令
#define FIRST 0 #define SECOND 0 #define OPTION1 1 #define OPTION2 2 #define OPTION3 3 void do_option1() { printf("do_option1\n"); } void do_option2() { printf("do_option2\n"); } void do_option3() { printf("do_option3\n"); } int main() { #if defined(FIRST) #ifdef OPTION1 do_option1(); #endif #ifdef OPTION2 do_option2(); #endif #elif defined(SECOND) #ifdef OPTION3 do_option3(); #endif #endif }
三:文件包含
1:#include<>与""
本地文件包含 #include "filename" 查找策略:先在源文件所在目录下查找,如果该头文件未找到,编译器就像查找库函数头文件一样在标准位置查找头文件。 如果找不到就提示编译错误。
库文件包含 #include <filename> 查找头文件直接去标准路径下去查找,如果找不到就提示编译错误。 这样是不是可以说,对于库文件也可以使用 “” 的形式包含? 答案是肯定的,可以。 但是这样做查找的效率就低些,当然这样也不容易区分是库文件还是本地文件了。
2:嵌套文件包含
在头文件中写入这两种代码中的任意一种都可以防止头文件被重复引用
//防止头文件被重复引用 #ifndef __TEST_H__ #define __TEST_H__ //头文件的内容 #endif //或者 #pragma once
头文件中的 ifndef / define / endif的用处: 防止头文件被重复引用 #include <filename.h> 和 #include "filename.h"的区别: <>:只在库的标准目录下进行查找 "":先在当前目录下进行查找,如果查找不到,就去库的标准目录下进行查找
四:预定义符号
这些预定义符号都是C语言内置的。
__FILE__ //进行编译的源文件 __LINE__ //文件当前的行号 __DATE__ //文件被编译的日期 __TIME__ //文件被编译的时间 __STDC__ //如果编译器遵循ANSI C,其值为1,否则未定义
int main() { printf("%s\n", __FILE__); printf("%d\n", __LINE__); printf("%s\n", __DATE__); printf("%s\n", __TIME__); //printf("%d\n",__STDC__);//当前VS不遵守ANSI_C(标准C) //C:\Users\86157\Desktop\gitee总\c - code\C_preprocessing_compilation\C_preprocessing_compilation\test.c //90 //Jul 18 2023 //13:55 : 48 return 0; }
以上就是C语言预处理及宏和函数的区别与各自优劣点的详解
的全部内容,希望能对大家有所帮助