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。

  • 相关阅读:
    BZOJ1527 : [POI2005]Pun-point
    2016-2017 ACM-ICPC Southwestern European Regional Programming Contest (SWERC 2016)
    2016-2017 ACM-ICPC Northwestern European Regional Programming Contest (NWERC 2016)
    NAIPC-2016
    BZOJ2498 : Xavier is Learning to Count
    ACM ICPC Vietnam National Second Round
    XVI Open Cup named after E.V. Pankratiev. GP of Ukraine
    XVI Open Cup named after E.V. Pankratiev. GP of Peterhof
    HDU5509 : Pattern String
    BZOJ4583 : 购物
  • 原文地址:https://www.cnblogs.com/michaelcjl/p/14819735.html
Copyright © 2011-2022 走看看