zoukankan      html  css  js  c++  java
  • 【C语言】15预处理指令1宏定义

    说明:这个C语言专题,是学习iOS开发的前奏。也为了让有面向对象语言开发经验的程序员,能够快速上手C语言。如果你还没有编程经验,或者对C语言、iOS开发不感兴趣,请忽略

    预处理指令简介

    1.C语言在对源程序进行编译之前,会先对一些特殊的预处理指令作解释(比如之前使用的#include文件包含指令),产生一个新的源程序(这个过程称为编译预处理),之后再进行通常的编译
    2.为了区分预处理指令和一般的C语句,所有预处理指令都以符号"#"开头,并且结尾不用分号
    3.预处理指令可以出现在程序的任何位置,它的作用范围是从它出现的位置到文件尾。习惯上我们尽可能将预处理指令写在源程序开头,这种情况下,它的作用范围就是整个源程序文件
    4.C语言提供的预处理指令主要有:宏定义文件包含条件编译
     
    这一讲先介绍一下宏定义,宏定义可以分为2种:不带参数的宏定义 和 带参数的宏定义。

    一、不带参数的宏定义

    1.一般形式

    #define 宏名 字符串

     比如#define ABC 10

    右边的字符串也可以省略,比如#define ABC

    2.作用

    它的作用是在编译预处理时,将源程序中所有"宏名"替换成右边的"字符串",常用来定义常量。

    接下来写个程序根据圆的半径计算周长

     1 #include <stdio.h>
     2 
     3 // 源程序中所有的宏名PI在编译预处理的时候都会被3.14所代替
     4 #define PI 3.14
     5 
     6 // 根据圆的半径计radius算周长
     7 float girth(float radius) {
     8     return 2 * PI *radius;
     9 }
    10 
    11 int main ()
    12 {
    13     float g = girth(2);
    14     
    15     printf("周长为:%f", g);
    16     return 0;
    17 }

    在第4行定义了一个叫PI的宏,在编译预处理之后,第8行中的2 * PI *radius就会变成2 * 3.14 * radius。

    输出结果:

    3.使用习惯与注意

    1> 宏名一般用大写字母,以便与变量名区别开来,但用小写也没有语法错误

    2> 对程序中用双引号扩起来的字符串内的字符,不进行宏的替换操作。比如:

    1 #define R 10
    2 int main ()
    3 {
    4     char *s = "Radio";
    5     return 0;
    6 }

    在第1行定义了一个叫R的宏,但是第4行中"Radio"里面的'R'并不会被替换成10

    3> 在编译预处理用字符串替换宏名时,不作语法检查,只是简单的字符串替换。只有在编译的时候才对已经展开宏名的源程序进行语法检查

    1 #define I 100
    2 int main ()
    3 {
    4     int i[3] = I;
    5     return 0;
    6 }

    在做编译预处理的时候,不管语法对不对,第4行的I都会被替换为100。不过在编译的时候就会报第4行的错。

    4> 宏名的有效范围是从定义位置到文件结束。如果需要终止宏定义的作用域,可以用#undef命令
    1 #define PI 3.14
    2 /*
    3 .
    4 .
    5 .
    6 .
    7 */
    8 #undef PI

    PI这个宏在第1行到第8行之间是有效的,第8行后就无效了

    5> 定义一个宏时可以引用已经定义的宏名
    #define R  3.0
    #define PI 3.14
    #define L  2*PI*R
    #define S  PI*R*R

    二、带参数的宏定义

    1.一般形式

    #define 宏名(参数列表) 字符串

    2.作用

    在编译预处理时,将源程序中所有宏名替换成字符串,并且将 字符串中的参数 用 宏名右边参数列表 中的参数替换

     1 #include <stdio.h>
     2 
     3 #define average(a, b) (a+b)/2
     4 
     5 int main ()
     6 {
     7     int a = average(10, 4);
     8     
     9     printf("平均值:%d", a);
    10     return 0;
    11 }

     第3行中定义了一个带有2个参数的宏average,第7行其实会被替换成:int a = (10 + 4)/2;,输出结果为:。是不是感觉这个宏有点像函数呢?

    3.使用注意

    1> 宏名和参数列表之间不能有空格,否则空格后面的所有字符串都作为替换的字符串

    1 #define average (a, b) (a+b)/2
    2 
    3 int main ()
    4 {
    5     int a = average(10, 4);
    6     return 0;
    7 }

    注意第1行的宏定义,宏名average跟(a, b)之间是有空格的,于是,第5行就变成了这样:

    int a = (a, b) (a+b)/2(10, 4);

    这个肯定是编译不通过的

    2> 带参数的宏在展开时,只作简单的字符和参数的替换,不进行任何计算操作。所以在定义宏时,一般用一个小括号括住字符串的参数。

    下面定义一个宏D(a),作用是返回a的2倍数值:

    • 如果定义宏的时候不用小括号括住参数
     1 #include <stdio.h>
     2 
     3 #define D(a) 2*a
     4 
     5 int main ()
     6 {
     7     int b = D(3+4);
     8     
     9     printf("%d", b);
    10     return 0;
    11 }

    7行将被替换成int b = 2*3+4;,输出结果:

    • 如果定义宏的时候小括号括住参数,把上面的第3行改成:
    #define D(a) 2*(a)

    注意右边的a是有括号的,第7行将被替换成int b = 2*(3+4);,输出结果:

    3> 计算结果最好也用括号括起来

    下面定义一个宏P(a),作用是返回a的平方:

    • 如果不用小括号括住计算结果
     1 #include <stdio.h>
     2 
     3 #define Pow(a) (a) * (a)
     4 
     5 int main(int argc, const char * argv[]) {
     6     int b = Pow(10) / Pow(2);
     7     
     8     printf("%d", b);
     9     return 0;
    10 }

    注意第3行,没有用小括号扩住计算结果,只是括住了参数而已。第6行代码被替换为:

    int b = (10) * (10) / (2) * (2);

    简化之后:int b = 10 * (10 / 2) * 2;,最后变量b为:

    • 如果小括号括住计算结果

    将上面的第3行代码改为:

    #define Pow(a) ( (a) * (a) )

    那么第6行被替换为:

    int b = ( (10) * (10) ) / ( (2) * (2) );

    简化之后:int b = (10 * 10) / (2 * 2);,最后输出结果:。这个才是我们想要的结果。

    也就意味着前面的#define average(a, b) (a+b)/2应该写成#define average(a, b) (((a)+(b))/2)

    5.与函数的区别

    从整个使用过程可以发现,带参数的宏定义,在源程序中出现的形式与函数很像。但是两者是有本质区别的:

    1> 宏定义不涉及存储空间的分配、参数类型匹配、参数传递、返回值问题

    2> 函数调用在程序运行时执行,而宏替换只在编译预处理阶段进行。所以带参数的宏比函数具有更高的执行效率

  • 相关阅读:
    进程池,线程池,协程,gevent模块,协程实现单线程服务端与多线程客户端通信,IO模型
    线程相关 GIL queue event 死锁与递归锁 信号量l
    生产者消费者模型 线程相关
    进程的开启方式 进程的join方法 进程间的内存隔离 其他相关方法 守护进程 互斥锁
    udp协议 及相关 利用tcp上传文件 socketserver服务
    socket套接字 tcp协议下的粘包处理
    常用模块的完善 random shutil shevle 三流 logging
    day 29 元类
    Django入门
    MySQL多表查询
  • 原文地址:https://www.cnblogs.com/mjios/p/2969817.html
Copyright © 2011-2022 走看看