zoukankan      html  css  js  c++  java
  • C语言_宏

    C语言_宏

    一.预处理

    编译一个C语言程序的第一步骤就是预处理阶段,这一阶段就是宏发挥作用的阶段。C预处理器在源代码编译之前对其进行一些文本性质的操作,主要任务包括删除注释、插入被#include进来的文件内容、定义和替换由#define 定义的符号以及确定代码部分内容是否根据条件编译(#if )来进行编译。”文本性质”的操作,就是指一段文本替换成另外一段文本,而不考虑其中任何的语义内容。宏仅仅是在C预处理阶段的一种文本替换工具,编译完之后对二进制代码不可见

    二.宏定义用法

    1. 宏常量
    我们最常使用到的#define的用法就是用#define来定义一个符号常量,而要修改时,只需修改#define这条语句就行了,不必每处代码都修改 例:

    #include"stdio.h"
    #define PI 3.14
    #define STR "圆周率约等于"
    int main()
    {
    	printf("%s %f",STR,PI); //预处理时会被替换为 printf("%s %f","圆周率约等于",3.14);
    	return 0;
    }
    
    #define BUFFER_SIZE 1024
    
    预处理阶段,foo = (char *) malloc (BUFFER_SIZE);会被替换成foo = (char *) malloc (1024);
    

    2. 宏语句
    我们还可以用宏定义一条或多条语句 例:

    #include"stdio.h"
    #define Print printf("hello world!")
    int main()
    {
    	Print;  //预处理时会被替换为 printf("hello world!");
    	return 0;
    }
    
    

    执行结果:
    hello world!

    3. 宏函数
    我还可以用宏来定义函数,因为宏定义也可以带参数例:

    #include"stdio.h"
    #define Print(str) printf("%s",str)
    int main()
    {
    	Print("这是一个只有一条语句的宏函数!");
        //预处理时会被替换为 printf("%s","这是一个只有一条语句的宏函数!")
    	return 0;
    }
    

    结果 :这是一个只有一条语句的宏函数!

    4. 其它

    1. .#undef 是用来撤销宏定义的,用法如下:
    #define PI 3.141592654
     
    …
     
    // code
    #undef PI
    //下面开始 PI 就失效了
    
    1. 使用ifndef防止头文件被重复包含和编译
      这是宏定义的一种,它可以根据是否已经定义了一个变量来进行分支选择,一般用于调试等等.实际上确切的说这应该是预处理功能中三种(宏定义,文件包含和条件编译)中的一种----条件编译。 C语言在对程序进行编译时,会先根据预处理命令进行“预处理”。C语言编译系统包括预处理,编译和链接等部分。
    #ifndef x //先测试x是否被宏定义过
    #define x //如果没有宏定义下面就宏定义x并编译下面的语句
    ...
    ...
    ...
    #endif //如果已经定义了则编译#endif后面的语句
    
    

    条件指示符#ifndef检查预编译常量在前面是否已经被宏定义。如果在前面没有被宏定义,则条件指示符的值为真,于是从#ifndef到#endif之间的所有语句都被包含进来进行编译处理。相反,如果#ifndef指示符的值为假,则它与#endif指示符之间的行将被忽略。条件指示符#ifndef 的最主要目的是防止头文件的重复包含和编译。
      千万不要忽略了头件的中的#ifndef,这是一个很关键的东西。比如你有两个C文件,这两个C文件都include了同一个头文件。而编译时,这两个C文件要一同编译成一个可运行文件,于是问题来了,大量的声明冲突。

    所以还是把头文件的内容都放在#ifndef和#endif中吧。不管你的头文件会不会被多个文件引用,你都要加上这个。一般格式是这样的:

      #ifndef <标识>
    
      #define <标识>
    
      ......
    
      #endif
    

    <标识>在理论上来说可以是自由命名的,但每个头文件的这个“标识”都应该是唯一的。标识的命名规则一般是头文件名全大写,前后加下划线,并把文件名中的“.”也变成下划线,如:stdio.h

    
      #ifndef _STDIO_H_
    
      #define _STDIO_H_
    
      ......
    
        #endif
    

    "#ifndef xxx //如果没有定义xxx
    "#define xxx //定义xxx
    "#endif //结束如果
    这个用法主要是在头文件中,主要是为了防止类重复的include,所以在类的头文件之前加上前面两个,用类名替代xxx,在最后加上最后一句

    三.宏定义相关作用符

    1. 换行符 ""
    我们定义宏语句或者宏函数时不可能总是一条语句呀,那要是有很多条语句时怎么办?都写在一行吗?这样显然代码就不美观,可读性不好,所以有多条语句时,我们就在每行末尾(除了最后一行)加上"",代表换行的意思
    例:

    #include"stdio.h"
    #define Print   printf("这是第1条语句
    ");
     		    	printf("这是第2条语句
    ");
     		    	printf("这是第3条语句
    ")
     		    	
    #define Show(str1,str2,str3)
    {
    	printf("%s
    ",str1);
    	printf("%s
    ",str2);
    	printf("%s
    ",str3);	
    }
    int main()
    {
    	Print;  //无参数宏函数
    	Show("first","second","third"); //带参数宏函数
    	return 0;
    }
    
    

    这是第1条语句
    这是第2条语句
    这是第3条语句
    first
    second
    third

    2.字符串化符 "#"
     "#"是“字符串化”的意思,将出现在宏定义中的#是把跟在后面的参数转换成一个字符串 例:

    #include"stdio.h"
    #define Print(str)
    {
    	printf(#str"的值是%d",str);	
    }
    int main()
    {
    	int x=3,y=4;
    	Print(x+y); //此处等价于printf("x+y""的值是%d",x+y);
    	            //#str等价于"x+y",所以#str不需要再用双引号引起来 
    	return 0;
    }
    
    

    x+y的值是7

    3.片段连接符"##"
    “##”是一种分隔连接方式,它的作用是先分隔,然后进行强制连接。在普通的宏定义中,预处理器一般把空格解释成分段标志,对于每一段和前面比较,相同的就被替换。但是这样做的结果是,被替换段之间存在一些空格。如果我们不希望出现这些空格,就可以通过添加一些##来替代空格。
    例:

    #include"stdio.h"
    #define Add(n,value)
    {
    	num##n+=value;
     } 
    int main()
    {
    	int num1=1;
    	int num2=10;
    	Add(2,10); //等价于num2+=10; 这里把num和2连接成了num2 
    	printf(" num1=%d
     num2=%d",num1,num2); 
    	return 0;
    }
    

    四.宏函数的巧用

    1.类型传递

    我们知道函数虽然可以传递参数,但是却不能把类型作为参数传递,有时我们为了实现函数的复用性,就要使用STL模板,但是我们这个时候还有另外一种选择,就是写成宏函数
    例:
    一个开辟内存的函数

    #define Malloc(type,size) (type*)malloc(sizeof(type)*size)
    

    这个时候,我们只有把类型,容量作为参数传递进行,就可以开辟各种类型的内存了

    int *p=Malloc(int,100); //开辟int类型的内存
    char *q=Malloc(char,100); //开辟字符类型的内存
    

    2.传递数组
    由数组作为函数参数传递时,会失去其数组特性,也就是无法使用sizeof()函数计算出数组的大小,比如我们写一个排序函数,排序时我们不仅需要知道数组的首地址,还需要知道数组的大小,但是仅仅把数组名作为参数传递时,无法得知其数组大小,这时我们的函数就需要传递第二个参数,也就是数组的大小,于是函数就要写成Sort(int *a,int size).但宏函数就可以解决这个问题
    例:
    下面用宏函数写一个插入排序算法

    #include"stdio.h"
    #define InsertSort(list)
    {
    	int s=sizeof(list)/4;
    	int i,j;
    	for(i=2;i<=s;i++)
    	{
    		list[0]=list[i];
    		for(j=i-1;list[j]>list[0];j--)
    				list[j+1]=list[j];	
    		list[j+1]=list[0];		
    	} 
    }
    int main()
    {
    	int num[]={0,2,5,7,3,1,8,0,8,22,57,56,74,18,99,34,31,55,41,12,9,4};
    	InsertSort(num);
    	for(int i=1;i<sizeof(num)/4;i++)	
    		printf("%d ",num[i]);
    	return 0;
    } 
    

    五.注意事项

    1. 运算符优先级问题

    #define MULTIPLY(x, y) x * y
    

    这是一个很简单的乘法函数,当计算MULTIPLY(10, 10),结果是100,这个大家都知道,但是当你计算MULTIPLY(5+5, 10)时,你以为结果还是100吗?当然不是,MULTIPLY(5+5, 10)=5+5*10=55,所以结果是55,所以我们写宏函数时要特别注意运算符的优先级,这里稳妥一点的写法应该这样写

    #define MULTIPLY(x, y) ((x)*(y))
    

    2. 宏参数重复调用

    #define MAX(a,b) ((a)>(b)?(a):(b))
    int a=0;
    int b =1;
    int c =MAX(++a,++b);
    
    

    这里很多人都以为是c=MAX(1,2)=2;而实际上上面代码等价于

    int c =((++a)>(++b)?(++a):(++b));
    

    可以看到实际上a b都各自加了两次,所以c=1>2?2:3=3,所以结果是3

    3. 分号吞噬问题

    #include"stdio.h"
    #define FUN(n)
    {
    	while(n>0)
    	{
    		if(n==3)
    			break;	
    	}	
    }
    int main()
    {
    	int num=10;
    	if(num>0)
    		FUN(num);
    	else
    		num=-num;
    	return 0;
    }
    

    看似代码没有问题,但是一编译就报错,编译器显示"error: ‘else’ without a previous ‘if’",原来是因为FUN函数是一个代码块,然后if(num>0) FUN(num); 就等价于if(num>0) {…}; 这不就是在大括号后面打分号了吗?这样else当然就缺少if了
      这个时候我们可以用do{…}while(0)来解决这个问题,写成如下就没问题了,因为while()后面正好需要一个分号

    #define FUN(n) 
    do
    {
    	while(n>0)
    	{
    		if(n==3)
    			break;	
    	}	
    }while(0)
    
    

    4. 递归调用问题

    #define NUM (4 + NUM)
    

    按前面的理解,(4 + NUM)会展开成(4 + (4 + NUM)),然后一直展开下去,直至内存耗尽。但是,预处理器采取的策略是只展开一次。也就是说,NUM只会展开成(4 + NUM),而展开之后NUM的含义就要根据上下文来确定了。

    5. 宏参数预处理
    宏参数中若包含另外的宏,那么宏参数在被代入到宏体之前会做一次完全的展开,除非宏体中含有#或##。

    有如下宏定义:

    #define A(y) X_##y
    #define B(y) A(y)
    #define SIZE 1024
    #define S SIZE
    
    

    A(S)会被展开成X_S。因为宏体中含有##,宏参数直接代入宏体。
    B(S)会被展开成X_1024。因为B(S)的宏体是A(S),并没有#或##,所以S在代入前会被完全展开成1024,然后才代入宏体,变成X_1024。

  • 相关阅读:
    shapely and geos break在distance方法
    linux运维
    【未完待补充】linux 设置So动态库链接路径
    用python建立最简单的web服务器
    nginx + keepalived 双机热备
    nagios监控linux主机监控内存脚本
    linux普通用户权限设置为超级用户权限方法、sudo不用登陆密码
    zato server启动后自动关闭问题解决
    Linux下几种文件传输命令 sz rz sftp scp
    python风味之大杂烩
  • 原文地址:https://www.cnblogs.com/michaelcjl/p/14819735.html
Copyright © 2011-2022 走看看