• 【学习笔记之我要C】预处理


    预定义符号:

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

    #define

      #define定义标识符:

    #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之后的东西都会被直接替换,所以一般我们不会在最末尾加上 ; ,因为这样容易导致语法错误。

       #define定义宏:

    #define 机制包括了一个规定,允许把参数替换到文本中,这种实现通常被我们称为宏(定义宏)。
    #define name( parament-list ) stuff
    其中的parament-list是一个由逗号隔开的符号表,它们可能出现在stuff中。
    我们一般讲宏的名字全部写成大写,用于与函数名进行区分
    注意:
    参数列表的左括号必须与name紧邻。
    如果两者之间有任何空白存在,参数列表就会被解释为stuff的一部分

    #define SQUARE( x ) x * x
    int main() {
    	printf("%d", SQUARE(5));//25
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    #define SQUARE( x ) x * x
    int main() {
    	int a = 4;
    	printf("%d", SQUARE(a + 1));//9
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

      上面两个代码结果不同,因为第二个代码传过去的是4 + 1,而不是计算之后的结果5,所以用于对数值表达式进行求值的宏定义都应该加上括号,避免在使用宏时由于参数中的操作符或邻近操作符之间不可预料的相互作用。

    #define SQUARE( x )  ( ( x ) * ( x ) )
    int main() {
    	int a = 4;
    	printf("%d", SQUARE(a + 1));//9
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
       #define替换规则:
    1. 在调用宏时,首先对参数进行检查,看是否包含任何由#define定义的符号。如果包含,他们首先被替换;
    2. 替换文本后被插入到程序中原来的文本位置。对于宏,参数名被他们的值所替换;
    3. 最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果包含,则重复上述操作。
    4. 宏参数和#define定义中可以出现其他#define定义的符号。但是对于宏,不能出现递归;
    5. 当预处理器搜索#define定义的符号时,字符串常量的内容并不被搜索。
      宏和函数对比:
    1. 用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需的时间更多,所以宏比函数在程序的规模和速度方面更胜一筹。
    2. 函数的参数必须声明为特定的类型,所以函数只能在类型合适的表达式上使用。而宏可以适用于整型、长整型、浮点型等类型。因为宏是类型无关的,但这也导致了宏是不够严谨的。
    3. 每次使用宏时,一份宏定义的代码将被插入到程序中。除非宏比较短,否则可能大幅度增加程序的长度。
    4. 宏无法进行调试,并且可能会导致运算符优先级问题的出现,导致程序出错。
    属性#define定义宏函数
    代码长度每次使用时,宏代码都会被插入到程序中。除了非常小的宏之外,程序的长度会大幅度增加函数代码只出现于一个地方;每次使用这个函数时,都调用那个地方的同一份代码
    执行速度更快存在函数的代用和返回,需要额外开销,速度相对较慢一些
    操作符优先级宏参数的求值是在所有周围表达式上文环境里,除非加上括号,否则临近操作符的优先级会影响运算结果,所以一般在写宏时,我们会尽可能多的加括号函数参数只在代用的时候求值一次,它的结果传递给函数。表达式的求值结果更容易预测
    带有副作用的参数参数可能被替换到宏体中的多个位置,所以如果参数能对自身操作,就会导致求值结果出现问题函数参数只在传参的时候求值一次,结果明确更易控制
    参数类型宏的参数与类型无关,只要对参数的操作是合法就是可以函数的参数是与类型无关的,如果参数的类型不同,就需要不同的函数,即使他们执行的任务是相同的
    调试宏是不方便调试的函数可以进行逐句调试
    递归不能递归可以递归

    #undef

    用于移除一个宏定义

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

    条件编译

      在编译一个程序的时候我们如果要将一条语句(一组语句)编译或者放弃是很方便的。因为我们有条件编译指令。

    #if 常量表达式
    //...
    #endif
    //常量表达式由预处理器求值。
    如:
    #define __DEBUG__ 1
    #if __DEBUG__
     //..
    #endif
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    多个分支的条件编译

    #if 常量表达式
     //...
    #elif 常量表达式
     //...
    #else
     //...
    #endif
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    判断是否被定义

    #if defined(symbol)
    #ifdef symbol
    
    #if !defined(symbol)
    #ifndef symbol
    
    • 1
    • 2
    • 3
    • 4
    • 5

    嵌套指令

    #if defined(OS_UNIX)
    	#ifdef OPTION1
    		unix_version_option1();
    	#endif
    	#ifdef OPTION2
    		unix_version_option2();
    	#endif
    #elif defined(OS_MSDOS)
    	#ifdef OPTION2
    		msdos_version_option2();
    	#endif
    #endif
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    文件包含

    #include “filename”
    查找策略:先在源文件所在目录下查找,如果该头文件未找到,编译器就像查找库函数头文件一样在标
    准位置查找头文件。
    如果找不到就提示编译错误。

    #include
    查找头文件直接去标准路径下去查找,如果找不到就提示编译错误。
    库函数也可以用""包含来查找,但是这样会先从本地文件中查找,效率会有一定的降低,并且如果本地文件与库文件有重名的,也不能进行区分

    嵌套文件包含

    在这里插入图片描述

    comm.h和comm.c是公共模块。
    test1.h和test1.c使用了公共模块。
    test2.h和test2.c使用了公共模块。
    test.h和test.c使用了test1模块和test2模块。
    这样最终程序中就会出现两份comm.h的内容。这样就造成了文件内容的重复。
    这时我们可以使用条件编译解决这一问题。
    我们一般使用#pragma once来避免头文件重复引入。

    注:个人觉得#和##很鸡肋,所以没写。

  • 相关阅读:
    【NAS备份】摆脱丢数据的噩梦,群晖备份硬核实战教程分享
    “分布式锁”,一直以来你的选择依据正确吗?
    Lua 调试库( debug )
    算法设计与分析 SCAU11076 浮点数的分数表达(优先做)
    SpringCloud进阶-消费者模块
    Linux文件与文件系统的压缩
    appium+jenkins实例构建
    【计算机组成原理】考研真题攻克与重点知识点剖析 - 第 1 篇:计算机系统概述
    async与await
    Vue3-provide 和 inject 跨组件传递数据
  • 原文地址:https://blog.csdn.net/qq_47658735/article/details/127593857