黑马程序员——C语言---预处理指令

------<a href="http://www.itheima.com" target="blank">Java培训、Android培训、iOS培训、.Net培训</a>、期待与您交流! -------


预处理指令

1、基本概念

    以“#”号开头的预处理命令。如包含命令#include,宏定义命令#define等。在源程序中这些命令都放在函数之外,而且一般都放在源程序文件的前面,它们称为预处理部分。


    所谓预处理是指在进行编译的第一遍扫描(词法扫描和语法分析)之前所做的工作。预处理是C语言的一个重要功能,它由预处理程序负责完成。当对一个源文件进行编译时,系统将自动引用预处理程序对源程序中的预处理部分作处理,处理完毕自动进入对源程序的编译。


    C语言提供了许多预处理功能,如宏定义、文件包含、条件编译等。合理地使用预处理功能编写的程序便于阅读、修改、移植和调试,也有利于模块化程序设计。


2、宏的概念及定义方法

    被定义为“宏”的标示符称为“宏名”。在编译预处理时,对程序中所有出现的“宏名”,都用宏定义中的字符串去代换,这称为“宏代换”或“宏展开”。


宏定义是由源程序中的宏定义命令完成的。宏代换是由预处理程序自动完成的。在C语言中,“宏”分为有参数和无参数两种。


    无参宏的宏名后不带参数,其定义的一般形式为:

    #define  标识符  字符串


    其中的“#”表示这是一条预处理命令。凡是以“#”开头的均为预处理命令。“define”为宏定义命令。“标识符”为所定义的宏名。“字符串”可以是常数、表达式、格式串等。


3、无参宏使用注意事项

1)习惯上宏名用大写字母表示,以便于与变量区别。但也允许用小写字母


2)宏定义是用宏名来表示一个字符串,在宏展开时又以该字符串取代宏名,这只是一种简单的代换,字符串中可以含任何字符,可以是常数,也可以是表达式,预处理程序对它不做任何检查。如有错误,智能在编译已被宏展开后的源程序是发现。


3)宏定义不是说明或语句,在行末不必加分号,如果加上分号则连分号也一起置换。


4)宏定义必须写在函数之外,其作用域为宏定义命令起到源程序结束。如果要终止其作用域可使用#undef命令。


5)宏名在源程序中若用“”括起来,则预处理程序不对其作宏代换。



6)宏定义允许嵌套,在宏定义的字符串中可以使用已经定义的宏名。在展开时由预处理程序层层代换。



7)可用宏定义表示数据类型,使书写方便。



8)对“输出格式”作宏定义,可以减少书写麻烦


注意括号问题


/*
 
     宏:
 
         C语言中我们自定义的特殊标示符,习惯大写
 
     宏的定义:
 
         #define 宏名 宏字符串(可以是常量、变量、表达式)
 
     注意:预处理指令,经常写在函数之前
          宏不是一个语句,是一个预处理指令,所以不需要加分号结束
 
     3、宏替换
 
        源程序在编译之前,由预处理程序对我们写的源代码进行处理:会把源代码中所有出现 宏名 的地方一律使用 宏的字符串 去替换
 
 */


#include <stdio.h>
#define M 10
#define M1 y*y+3*y

#define R 4
#define PI 3.14
#define AREA PI*R*R   //嵌套定义
#define INT1 int
#define P struct Person

void test(){

    printf("M = %d\n",M);

}

//#undef M     //此处的作用是,取消宏定义
void test1(){
    
    printf("test1 = %d\n",M);
    
}

int main(int argc, const char * argv[]) {
    
    int a[M+2];  //int a[12]
    printf("%d\n",M);  //把M的值打印出来
    
    int y = 3,result=0;
    
    result = 3*M1+2*M1-50;
    //错误的
    //       3*(y*y+3*y)+2*(y*y+3*y)-50;
    //        54        +36         -50;
    //        40
    
    //正确的
    //       3*y*y+3*y+2*y*y+3*y-50;
    //        27  +9  + 18  + 9 -50;
    //        13

    printf("result = %d\n",result);
    
    //宏使用的注意事项
    //1、宏是有作用域的  #undef 宏名 可以取消宏定义
    test();
    test1();
    
    //2、在字符串中出现的宏名不会被替换
    
    //3、宏可以嵌套定义

    printf("%.2f\n",AREA);
    
    //4、使用宏其别名
    INT1 a1;
    a1 = 10;
    printf("a1 = %d\n",a1);
    
    P{
        int age;
    };
    
    P p1 = {23};
    
    return 0;
}


4、有参宏的定义方法

C语言允许宏带有参数。在宏定义中的参数称为形式参数,在宏调用中的参数称为实际参数。

对带参数的宏,在调用中,不仅要宏展开,而且要用实参去代换形参。

带参宏定义的一般形式为:

    #define  宏名(形参表) 字符串

在字符串中包含有各个形参。

带参宏调用的一般形式为:

宏名(实参表)






5、有参宏使用注意事项

1)带参宏定义中,形参之间可以出现空格,但是宏名和形参之间不能有空格出现。


2)在带参宏定义中,形式参数不分配内存单元,因此不必作类型定义。而宏调用中的实参有具体的值。要用他们去代换形参,因此必须作类型说明。这是与函数中的情况不同的。在函数中,形参和实参是两个不同的量,各有自己的作用域,调用时要把实参值赋予形参,进行“值传递”。而在带参宏中,只是符号代换,不存在值传递的问题。


3)在宏定义中的形参是标识符,而宏调用中的实参可以是表达式。



4)在宏定义中,字符串内的形参通常要用括号括起来以避免出错。



5)宏定义也可以用来定义多个语句,在宏调用时,把这些语句又代换到源程序内。


/*
 
    宏的分类:
 
     无参宏    #define M 10
 
     有参宏    #define SUM(a) a+a
 
     SUM(3)  //不仅要 a+a替换,而且还要把 实参3代入到 字符串中
 
 
 
 */

#include <stdio.h>
#define SUM(a) a+a
#define M1(x  ,y) (x)*(y)+(x)+(y)
#define M2(a) a+3*y
#define M3(m,n) m = a+2;n=a*2;

int main(int argc, const char * argv[]) {
    
    int result = SUM(3);  //6
    //有参宏使用
    result = M1(4, 5);    //29
    
    int y = 2;
    //有参宏使用
    result = M2(3);  //9
    
    //有参宏的使用注意事项
    //1、宏的形参之间可以出现空格,但是宏名和形参之间不能出现空格
    int a = 3;
    //2、有参宏宏的参数最好用括号括起来
    result = M1(a+3, a-1);
    //(x  ,y) x*y+x+y
    //       6*2 +6+2
    // 20
    
    //  a+3*a-1+a+3+a-1
    //  a+9  -1+a+3+a-1
    //  3+9-1+3+3+3-1
    //  19
    
    int i,j;
    printf("%d\n",result);
    printf("i=%d,j=%d\n",i,j);
    
    //3、可以用宏来定义多个语句
    M3(i, j);
    printf("i=%d,j=%d\n",i,j);
    
    return 0;
}


6、#define和typedef的区别

应注意用宏定义表示数据类型和用typedef定义数据说明符的区别。宏定义只是简单的字符串代换,是在预处理完成的,而typedef视在编译时处理的,它不是作简单的代换,而是对类型说明符重新命名。被命名的标识符具有类型定义说明的功能。


//  typedef和#define的区别

#include <stdio.h>
#define INT1 int*   //定义一个宏,宏名是INT1
typedef int* TINT;  //int起一个别名 TINT
int main(int argc, const char * argv[]) {
    
    int num = 10;
    //使用宏定义变量
    INT1 a,b;     //int* a,b
                  //a是指针
    a = #
                  //b是普通的变量
    b = num;

    printf("a = %d,b = %d\n",*a,b);
    
    //使用别名定义变量
    TINT a1,b1;   //int *a1;int* b1;
    
    a1 = #
    b1 = #   //a1 和 b1 都是指针
    
    printf("a1 = %d,b1 = %d\n",*a1,*b1);
    return 0;
}

7、条件编译—为什么要使用条件编译

1)按不同的条件去编译不同的程序部分,因而产生不同的目标代码文件。有利于程序的移植和调试。

2)条件编译当然也可以用条件语句来实现。但是用条件语句将会对整个源程序进行编译,生成的目标代码程序很长,而采用条件编译,则根据条件只编译其中的程序段1或程序段2,生成的目标程序较短。

8、#if-#else条件编译指令

第一种形式的格式为:

#if  常量表达式

   程序段1

#else

  程序段2

#endif


它的功能是,如常量表达式的值为真(非0),则对程序段1进行编译,否则对程序段2进行编译。因此可以使程序在不同条件下,完成不同的功能。

注意:条件编译后面的条件表达式中不能识别变量,它里面只能识别常量和宏定义。


9、#ifdef条件编译指令

第一种形式的格式为:

#ifdef  标识符

  程序段1

#else

  程序段2

#endif

它的功能是,如果标识符已被#define命令定义过,则对程序段1进行编译;否则对程序段2编译。

如果没有程序段2(它为空),本格式中#else可以没有,即可以写为:

#ifdef  标识符

  程序段

#endif

我们常利用这种形式来控制调试信息:


10、#ifndef条件别意指令

第三种形式的格式为:

#ifndef  标识符

  程序段1

#else 

程序段2

#endif

// 使用条件编译指令调试bug


#include <stdio.h>

#define DEBUG1 1

#if DEBUG1 == 1
//显示调试信息
#define Log(format,...) printf(format,## __VA_ARGS__)

#else
//不显示调试信息
#define Log(format,...)

#endif

void test(){

    int a  = 10,b=3;
    Log("test--->%d,%d\n",a,b);

}


void test1(){
    
    printf("xxxxx\n");
    float b  = 10.23f;
    Log("test1--->%.2f\n",b);
    
}

int main(int argc, const char * argv[]) {
    
    //DEBUG1 == 1   显示调试信息
    //DEBUG1 == 0   不显示调试信息
    Log("xxxxxxxxxx-->\n");
    
    test1();
    test();
    
    return 0;
}




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值