C语言预处理命令

预处理

  • 什么是预处理命令?
  • 源文件包含
  • 宏定义#define
  • #、 ##、 @#、 #undef
  • macro body 宏展开过程
  • 可变参的宏!!!
  • 预定义的宏
  • 条件编译
    • **#if,#else,#elif和#endif**
    • #ifdef和 #ifndef
    • 使用defined
  • #line 与 #error
  • 参考文献

什么是预处理命令?

C语言源文件要经过编译链接才能生成可执行程序:

  1. 编译(Compile)会将源文件(.c文件)转换为目标文件。对于 VC/VS,目标文件后缀为.obj;对于GCC,目标文件后缀为.o

编译是针对单个源文件的,一次编译操作只能编译一个源文件,如果程序中有多个源文件,就需要多次编译操作。

  1. 链接(Link)是针对多个文件的,它会将编译生成的多个目标文件以及系统中的库、组件等合并成一个可执行程序。

在实际开发中,有时候在编译之前还需要对源文件进行简单的处理。
例如: 不同的环境下系统的头文件定义和函数名有所不同,比如说:假如现在要开发一个C语言程序,让它暂停 5 秒以后再输出内容,并且要求跨平台,在 Windows 和 Linux 下都能运行,怎么办呢?

这个程序的难点在于,不同平台下的暂停函数和头文件都不一样:

  • Windows 平台下的暂停函数的原型是void Sleep(DWORD dwMilliseconds)(注意 S 是大写的),参数的单位是“毫秒”,位于 头文件。
  • Linux 平台下暂停函数的原型是unsigned int sleep (unsigned int seconds),参数的单位是“秒”,位于 头文件。

不同的平台下必须调用不同的函数,并引入不同的头文件,否则就会导致编译错误,因为 Windows 平台下没有 sleep() 函数,也没有 头文件,反之亦然。这就要求我们在编译之前,也就是预处理阶段来解决这个问题。请看下面的代码:

#include 
//不同的平台下引入不同的头文件
#if _WIN32  //识别windows平台
#include 
#elif __linux__  //识别linux平台
#include 
#endif
int main() {
    //不同的平台下调用不同的函数
    #if _WIN32  //识别windows平台
    Sleep(5000);
    #elif __linux__  //识别linux平台
    sleep(5);
    #endif
    puts("hello jei");
    return 0;
}

当此函数在linux环境下编译时经预处理后等同于:

#include 
#include 
int main() {
    sleep(5);
    puts("hello jie");
    return 0;
}

这些在编译之前对源文件进行简单加工的过程,就称为预处理(即预先处理、提前处理)。

  • 预处理主要是处理以#开头的命令,例如#include 等。预处理命令要放在所有函数之外,而且一般都放在源文件的前面。
  • 预处理是C语言的一个重要功能,由预处理程序完成。当对一个源文件进行编译时,系统将自动调用预处理程序对源程序中的预处理部分作处理,处理完毕自动进入对源程序的编译。
  • 编译器会将预处理的结果保存到和源文件同名的.i文件中,例如 main.c 的预处理结果在 main.i 中。和.c一样,.i也是文本文件,可以用编辑器打开直接查看内容。
  • C语言提供了多种预处理功能,如宏定义、文件包含、条件编译等,合理地使用它们会使编写的程序便于阅读、修改、移植和调试,也有利于模块化程序设计

源文件包含

包含另一源文件,到当前源文件中立即在指令下一行的位置。

  • #include <文件名> (1)
  • #include “文件名” (2)
    将 文件名 所标识的源文件包含(copy)到当前源文件中
  1. 在系统目录下搜索文件

  2. 在用户自定目录下搜索文件,如果未找到再到(1)下找

注意

  • 在头文件中定义定义函数和全局变量」这种认知是原则性的错误!不管是标准头文件,还是自定义头文件,都只能包含变量和函数的声明,不能包含定义,否则在多次引入时会引起重复定义错误
  • 也就是说,使用双引号比使用尖括号多了一个查找路径,它的功能更为强大。

宏定义#define

#define简但的字符串替换

#include "stdio.h"
//define N 100
#define STR "liujie"
int main(){
     int a[N] = {};
    printf("%lud ",sizeof (a));
    puts(STR);
    return 0;
}
//root@VM-Ali-ubuntu:~/tmp/LinuxC# gcc -DN=10 main.c -o main && ./main
//40d liujie

带变元的宏

  • 用类似函数的宏替换函数的一大好处是∶宏替换提高了代码的执行速度,因为不存在函数调用的开
    。然而,如果类似函数的宏很大,则这种速度的提高就会由于重复代码而导致了程序长度的增加
    (代码膨胀)
#include "stdio.h"
#define ABS(x) (x)>0?(x):-(x)
int main(){
    printf("%d ", ABS(2-3));
    return 0;
}

#、 ##、 @#、 #undef

#运算符,通常称为stringifie(字符串化)运算符,使得在它后面的变元转换成带双引号的串

#include 
#define mkstr(s) #s
int main(void) {
    printf(mkstr(I Like C));
    return 0;
}
// >>I like C

##操作符,称为 pasting(粘贴,链接)运算符,用于连接两个符号

#include 
#define concat(a,b) a##b
int main(void) {
    int xy = 10;
    printf("%d",concat(x,y)); //宏的哑实结合不存在类型,也没有类型转换。
    // printf("%x",concat("hello","tulun"));
    return 0;
}
//>>10

#@的功能是将其后面的变元进行字符化VS环境下有效

#include
#define TO_CHAR(x) #@x
int main() {
    
        printf("%c", TO_CHAR(1));
        printf("%c", TO_CHAR(3));
        printf("%c", TO_CHAR(4));
        printf("%c", TO_CHAR(5));
    return 0;
    
}

#undef 指令解除定义 标识符 ,即它取消先前 #define 对 标识符 的定义。若标识符无与之关联的宏,则忽略此指令。

macro body 宏展开过程

  • 1.先进行# stringified操作,只对变元字符串化。
  • 2.再对变元进行替换
  • 3.执行## pasted 操作。
  • 4.对展开后的结果查看是否有#define定义的符号,如果有重复上述处理过程,但不允许对同一宏名进行第二次展开

例:

可变参的宏!!!

C语言预处理命令_第1张图片

预定义的宏

__FILE__ //展开成当前文件名,为字符串常量, 可用 #line 指令更改
__LINE__ //展开成源文件行号,为整数常量, 可用 #line 指令更改
__DATE__ //展开成翻译的日期,格式为 “mm dd yyyy” 的字符串常量。若月之日期小于 10 则 “dd” 的首 //字符为空格
__TIME__ //展开成翻译的时间,格式为 “hh:mm:ss” 的字符串常量
__STDC__ //如果编译器遵循ANSI C,展开成整数常量1,否则未定义。判断当前的编译器是否为标 准C编译器
__func__ //在每个函数体内,拥有块作用域和静态存储期的预定义数组 func 可用, //它如同立即通过以下方式定义 void fun() { static const char __func__[]="fun"; } //C++ 添加的宏定义
__cplusplus //代表所用的 C++ 标准版本,展开成值

例:

#include
#include
int main() {
    printf("%s\n",__FILE__);
    printf("%d\n",__LINE__);
    printf("%s\n",__DATE__);
    printf("%s\n",__TIME__);
    printf("%s",__func__);
    return 0;
}
///*/root/tmp/LinuxC/cmake-build-debug/LinuxC
///root/tmp/LinuxC/main.c
//7
//Jul 16 2022
//09:44:14
//main*/
      

例:

#include
#include
#include 

#define ASSERT(exp)   (void)((exp) || (my_assert(#exp,__FILE__,__LINE__,__func__),0))

void my_assert(const char * _Message,char * _file,int _line,char const* func){
    printf("%s:",_file);
    printf("%d ",_line);
    printf("%s: ",func);
    printf("Assertion \'%s\' failed \n",_Message);
    abort();
}
void print( int * arr,int n){
    ASSERT(arr!=NULL);
   // assert(arr!=NULL);
    for(int i = 0;i<n;i++){
        printf("%d ",arr[i]);
    }
}
int main() {
    int arr[] = {1,2,3,4,5,6,7,8};
    int n = 8;
    int * ip = NULL;
    print(ip,n);
    return 0;
}

条件编译

条件指令允许你对程序源代码的各部分有选择地进行编译,这个过程称为条件编译。商业软件公司
广泛应用条件编译来提供和维护某一程序的许多定制版本。

#if,#else,#elif和#endif

  • 最常用的条件编译指令为#if,#else,#elif和#endif。这些指令允许你根据常量表达式的结果,有
    条件地选择代码指令
#if constant-expression  // 常量表达式 
	statement sequence       // 语句序列
#endif

#ifdef和 #ifndef

  • 条件编译的另一种方法使用#ifdef和#ifndef指令。它们分别表示 " 如果有定义 " 和 " 如果无定义
    "。
#include  
#define TED 10 
int main (void) {
#ifdef TED 
	 printf("Hi Ted \n"); 
#else
	 printf("Hi anyone \n"); 
#endif 

#ifndef RALPH 
	 printf("RALPH not defined\n");
#endif 
	 return 0;
 }

使用defined

  • 如果macro-name 已被定义,则表达式为真,否则为假。例如,要确认宏MYFILE是否已被定义,可以
    用下面两种预处理命令之一∶
#ifdef defined MYFILE 

//或 #ifdef MYEILE

#line 与 #error

#include
#include
#include 

#line 100 "jie.c"
#error "编译出错"
#define ASSERT(exp)   (void)((exp) || (my_assert(#exp,__FILE__,__LINE__,__func__),0))

void my_assert(const char * _Message,char * _file,int _line,char const* func){
    printf("%s:",_file);
    printf("%d ",_line);
    printf("%s: ",func);
    printf("Assertion \'%s\' failed \n",_Message);
    abort();
}

void print( int * arr,int n){
    ASSERT(arr!=NULL);
   // assert(arr!=NULL);
    for(int i = 0;i<n;i++){
        printf("%d ",arr[i]);
    }
}

int main() {
#ifdef ASSERT
    printf("%d ",1);
#endif
    int arr[] = {1,2,3,4,5,6,7,8};
    int n = 8;
    int * ip = NULL;
    print(ip,n);
 
    return 0;
}
// /root/tmp/LinuxC/cmake-build-debug/LinuxC
// 1 jie.c:111 print: Assertion 'arr!=NULL' failed 

// 进程已结束,退出代码134
------------------------------------------------------------------------------------------------------------
==============================[ 构建 | LinuxC | Debug ]=====================================
///usr/bin/cmake --build /root/tmp/LinuxC/cmake-build-debug --target LinuxC -- -j 6
//Scanning dependencies of target LinuxC
//[ 50%] Building C object CMakeFiles/LinuxC.dir/main.c.o

-------------------------------------
//jie.c:100:2: error: #error "编译出错"
-------------------------------------

//make[3]: *** [CMakeFiles/LinuxC.dir/build.make:63: CMakeFiles/LinuxC.dir/main.c.o] Error 1
//make[2]: *** [CMakeFiles/Makefile2:76: CMakeFiles/LinuxC.dir/all] Error 2
//make[1]: *** [CMakeFiles/Makefile2:83: CMakeFiles/LinuxC.dir/rule] Error 2
//make: *** [Makefile:118: LinuxC] Error 2


参考文献

首页 > C语言入门 > 预处理命令
C++中文 - API参考文档
腾讯课堂->全部课程IT·互联网后台开发C/C++C语言入门到进阶

你可能感兴趣的:(#,C/C++,c语言,linux,开发语言)