C语言学习第二十四天(预处理)

1、预处理符号

C语言设置了一些可以直接使用的预处理符号

__FILE__//进行编译的源文件
__LINE__//文件当前的行号
__DATE__//文件被编译的日期
__TIME__//文件被编译的时间
__STOC__//如果编译器遵循ANSI C 其值是1,否则未定义

一个例子:
 

printf("file:%s line:%d\n",__FILE__,__LINE__);

2、#define定义常量

#define name stuff

举个例子

#define MAX 1000
#define reg register //为register这个关键字创建一个简短的名字
#define do_forever for(;;)
#define CASE break;case //这样可以在写case语句的时候自动补上break
//如果定义的stuff过长,可以分成几行写,除了最后一行外,每一行的后面都要加一个反斜杠(续写序)
#define DEBUG_PRINT printf("file:%s\tline:%d\t \
                           date:%s\ttime:%s\n",\
                           __FILE__,__LINE__,     \
                           __DATE__,__TIME__)

并且,需要注意的是,在define定义标识符的时候,最好不要在最后加上  ; 

否则容易导致错误

3、#define定义宏

#define 机制还有一个规定,允许把参数替换到文本当中,这种实现通常称为宏或者是定义宏

下面是宏的声明方式:

#define name(parament-list) stuff

其中的parament-list是一个有逗号隔开的符号表,他们可能出现在stuff当中

需要特别注意的是:参数列表的左括号必须与name紧邻,如果两者之间有任何的空白字符,参数列表就会被解释为stuff的一部分

一个例子:

#define SQUARE(x) x*x

这个宏接受一个参数x。如果在上述声明之后,把SQUARE(5)置于程序当中,预处理器就会用下面这个表达式替换下面的表达式 5*5

这个宏还是有一定的问题的,请看下面的代码:

int a = 5;
printf("%d\n",SQUARE(a+1));

我们感觉,这个代码的最终结果是36,实则不然,运行出来的结果是11

其实这个代码可以等效为:

printf("%d\n",a+1*a+1);

所以这个宏定义需要加上小括号

#define SQUARE(x) (x)*(x)

4、带有副作用的宏参数

当宏参数在宏的定义中出现超过一次的时候,如果参数带有副作用,在使用宏的时候就可能出现危险。副作用就是表达式求值的时候出现的永久性效果

x+1;//不带副作用
x++;//带有副作用

5、宏替换的规则

1、在调用宏的时候,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,他们首先被替换

2、替换文本随着被插入到程序中原来文本的位置。对于宏,参数名被他们的值所替换

3、最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上述过程。

需要注意的是:宏参数和#define定义中可以出现其他#define定义的符号。但是对于宏,不能出现递归;当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索

6、宏函数的对比

比如想在两个数当中找到较大的一个的时候,可以这样写:

#define MAX(a,b) ((a)>(b)?(a):(b))

不使用函数完成这个任务的原因:

1、用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作的时间更多。

2、函数的参数声明一定是要特定的类型,所以函数只能在类型合适的表达式上使用。反之这个宏可以用于各种变量。宏是类型无关的

但是这个宏也有自己的缺点:

1、每次使用宏的时候,一份宏定义的代码插入到程序当中。除非宏比较短,不然的话会大幅度增加程序的长度

2、使用宏是无法调试的

3、宏是类型无关的,也就不够严谨

4、宏可能导致运算符优先级的问题,导致程序容易出错

宏还有一个优点:宏的参数可以出现类型,函数却做不到这一点

#define MALLOC(num,type)\
    (type )malloc(num sizeof(type))


//使用一下
MALLOC(10,int);
//=>相当于是(int )malloc(10,sizeof(int));  

7、#和##

#运算符

#运算符将宏的一个参数转换为字符串字面量。他仅允许出现在带参数的宏的替换列表中。

#运算符所执行的操作可以理解为  “字符串化”。

当我们有一个变量int a = 10;的时候,我们想打印出:the value of a is 10.

可以这样写:

#define PRINT(n) printf("the value of"#n"is %d",n);

这个东东调用的过程是:

PRINT(a);//当我们把a替换到宏的体内的时候,就出现了#a,而#a就是转化为“a”,这个字符串代码就会被解析为:

printf("the value of ""a" " is %d",n);

打印出来就是:

the value of a is 10

##运算符

##可以把位于它两边的符号合成一个符号,它允许宏定义从分离的文本片段创建标识符

这样的链接必须产生一个合法的标识符。否则其结果就是未定义的

举个例子:

int int_max(int x,int y)
{
    return x>y?x:y;
}

float float_max(float x,float y)
{
    return x>y?x:y;
}

也可以将上面的代码转化为宏定义写的代码:

#define GENERIC_MAX(type)\
type type##max(type x,type y)\
{\
     return (x>y?x:y);\
}

下面是使用这个宏的例子:

GENERIC_MAX(int)  //替换到宏体内后int##max生成了新的符号 int_max作为函数名
GENERIC_MAX(float)//替换到宏体内后float##max生成了新的符号 float_max作为函数名

8、命名的约定

把宏名全部大写

函数名不要全部大写

9、#undef

这条指令用于移除一个宏定义

#undef NAME
//如果现存的一个名字需要被重新定义,那么它的旧名字首先被移除。

10、命令行定义

许多C 的编译器提供了⼀种能⼒,允许在命令⾏中定义符号。⽤于启动编译过程。

当我们根据同⼀个源⽂件要编译出⼀个程序的不同版本的时候,这个特性有点⽤处。(假定某 个程序中声明了⼀个某个⻓度的数组,如果机器内存有限,我们需要⼀个很⼩的数组,但是另外⼀个 机器内存⼤些,我们需要⼀个数组能够⼤些。

#include <stdio.h>
int main()
{
 int array [ARRAY_SIZE];
 int i = 0;
 for(i = 0; i< ARRAY_SIZE; i ++)
 {
 array[i] = i;
 }
 for(i = 0; i< ARRAY_SIZE; i ++)
 {
 printf("%d " ,array[i]);
 }
 printf("\n" );
 return 0;
}
//linux 环境演⽰
gcc -D ARRAY_SIZE=10 programe.c

11、条件编译

选择性编译使调试性的代码是否参与编译,举个例子:

#include<stdio.h>
#define __DEBUG__

int main()
{
    int i = 0;
    int arr[10] = {0};

    for (i = 0;i < 10; i++)
    {
        arr[i] = i;
        #ifdef __DEBUG__
        printf("%d\n",arr[i]);
        #endif//__DEBUG__   
    }
 return 0;
}

下面是常见的条件编译指令:

#if 常量表达式
//...
#endif
//常量表达式由预处理求值
如:
#define __DEBUG__ 1
#if __DEBUG__ 
//...
#endif

2.多个分支的条件编译
#if 常量表达式
//...
#elif 常量表达式
//...
#else
//...
#endif

3.判断是否被定义
#if defined(symbol)
#ifdef symbol

#if !defined(symbol)
#ifndef symbol

4.嵌套指令
#if defined(symbol)
    #ifdef OPTION1
        unix_version_option1();
    #endif
    #ifdef OPTION2
        unix_version_option2();
    #endif
#elif defined(OS_MSDOS)
    #ifdef OPTION2
        msdos_version_option2();
    #endif
#endif

为了避免头⽂件的重复引⼊。可以使用条件编译

#ifndef __TEST_H__
#define __TEST_H__
//头⽂件的内容
#endif //__TEST_H__

相关推荐

  1. C语言学习第二预处理

    2023-12-17 10:50:05       40 阅读
  2. 学习c#的第二

    2023-12-17 10:50:05       36 阅读
  3. 学习Android的第二

    2023-12-17 10:50:05       20 阅读
  4. C语言学习

    2023-12-17 10:50:05       7 阅读

最近更新

  1. TCP协议是安全的吗?

    2023-12-17 10:50:05       16 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2023-12-17 10:50:05       16 阅读
  3. 【Python教程】压缩PDF文件大小

    2023-12-17 10:50:05       15 阅读
  4. 通过文章id递归查询所有评论(xml)

    2023-12-17 10:50:05       18 阅读

热门阅读

  1. 数据结构:双链表

    2023-12-17 10:50:05       52 阅读
  2. 在Visual Studio中进行嵌入式ARM设备的调试

    2023-12-17 10:50:05       34 阅读
  3. Ubuntu下安装ONNX、ONNX-TensorRT、Protobuf和TensorRT

    2023-12-17 10:50:05       40 阅读
  4. 牛客小白月赛83

    2023-12-17 10:50:05       39 阅读
  5. C语言指针3

    2023-12-17 10:50:05       36 阅读
  6. 用单片机控制步进电机的程序

    2023-12-17 10:50:05       41 阅读
  7. ssh连接试图写入的管道不存在

    2023-12-17 10:50:05       57 阅读