预定义详解

学习流程

————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————

学习预定义之前需要了解,编译和链接基础知识,有兴趣的可以去看看我我的博客。

预定义符号 

包含 

C语⾔设置了⼀些预定义符号,可以直接使⽤,预定义符号也是在预处理期间处理的。
__FILE__ //进⾏编译的源⽂件
__LINE__ //⽂件当前的⾏号
__DATE__ //⽂件被编译的⽇期
__TIME__ //⽂件被编译的时间
__STDC__ //如果编译器遵循ANSI C,其值为1,否则未定义

举例

__FILE__ //进⾏编译的源⽂件

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int main()
{
	printf("文件夹所在位置__FILE__:\n%s\n\n\n", __FILE__);

	return 0;
}

可以很清晰的看到这个预定义符号就是打找到文件在你电脑的位置。 

__LINE__ //⽂件当前的⾏号

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int main()
{

	printf("文件当前的行号__LINE__:\n%d \n\n\n", __LINE__);
	return 0;
}

文件当前所在的行号。

__DATE__ //⽂件被编译的⽇期

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int main()
{
	printf("文件被编译的日期__DATE__(也就是当前日期)__DATE__:\n%s \n\n\n", __DATE__);
	return 0;
}

文件被编译出来的日期。如果你是当天编译是,显示的就是当天的日期,如果你今天编译明天看看的话,标注的是今天的日期。

__TIME__ //⽂件被编译的时间

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int main()
{
	printf("文件被编译的时间__TIME__(也就是当前时间)__TIME__:\n%s \n\n\n", __TIME__);
	return 0;
}

文件被编译出来的时间。如果你是当天编译是,显示的就是当天的,如果你今天编译明天看看的话,标注的是今天的时间。就像这个,几分钟前编译的,香樟我要看看,自然显示之前的时间

__STDC__ //如果编译器遵循ANSI C,其值为1,否则未定义

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int main()
{
	printf("如果编译器遵循ANSI C,其值为1,否则未定义__STDC__:\n%s \n\n\n", __STDC__);
	return 0;
}

这里子啊vs2022里面是不支持__STDC__的

预定义处理是在预编译阶段的就进行处理,预编译阶段就是预处理阶段

总结

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int main()
{
	printf("文件夹所在位置__FILE__:\n%s\n\n\n", __FILE__);
	printf("文件当前的行号__LINE__:\n%d \n\n\n", __LINE__);
	printf("文件被编译的日期__DATE__(也就是当前日期)__DATE__:\n%s \n\n\n", __DATE__);
	printf("文件被编译的时间__TIME__(也就是当前时间)__TIME__:\n%s \n\n\n", __TIME__);
	//printf("如果编译器遵循ANSI C,其值为1,否则未定义__STDC__:\n%s \n\n\n", __STDC__);
	printf("文件当前的行号__LINE__:\n%d \n\n\n", __LINE__);

	return 0;
}

————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————

#define定义常量

基本语法

 #define name stuff
//(#define)(变量名)(定义的数值)

这里记得,是不加分号的 

定义常量(这里 就要涉及我们经常说的宏定义)

定义常量的使用 

  1. 提高代码可读性: 常量可以帮助开发者理解代码的含义。例如,使用常量 MAX_USERS 比 使用数字 100 更能表达一个变量应该表示的最大用户数。

  2. 便于维护: 如果一个值在程序中多次使用,比如一个应用的版本号,将其定义为常量(如 VERSION)可以使得未来的更新更加容易。你只需要改变常量的定义,而不是每一个出现该值的地方。

  3. 避免魔法数: 魔法数(Magic Numbers)是指在代码中直接出现的、没有解释的数字。使用常量代替魔法数可以增加代码的可读性和可维护性。

  4. 配置管理: 对于需要在不同环境中部署的应用程序,常量可以用来管理配置参数,如数据库连接字符串、API密钥等。这些值可以在不修改代码的情况下,通过外部配置文件或环境变量来设置。

  5. 条件编译: 在C语言中,可以使用条件编译指令 #ifdef#ifndef#if 等,结合常量定义来控制哪些代码应该在编译时包含或不包含。这可以用于创建跨平台的代码或者在调试版本和生产版本之间切换。

  6. 性能优化: 编译器通常会对常量表达式进行优化,因为它们的值在编译时是已知的。这可以提高程序的执行效率。

  7. 接口和API设计: 在设计库、框架或API时,常量可以用来定义固定的参数值、错误代码、状态标识等,使得用户更容易理解和使用这些接口。

#include <stdio.h>

#define PI 3.14159
#define MIN_RADIUS 10.0
#define MAX_RADIUS 100.0

double calculateArea(double radius) {
    if (radius < MIN_RADIUS || radius > MAX_RADIUS) {
        printf("半径必须在 %f 和 %f 之间。\n", MIN_RADIUS, MAX_RADIUS);
        return -1;
    }
    return PI * radius * radius;
}

int main() {
    double radius = 20.0;
    double area = calculateArea(radius);
    if (area >= 0) {
        printf("半径为 %f 的圆的面积是:%.2f\n", radius, area);
    }
    return 0;
}

 定义常量使用的注意事项

 1.常量在使用期间是直接进行宏替换的

比如:

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#define MAX 3+5//(这里我们定义了一个常量)
int main()
{
	int n = 2;
	int m = 3;
	int count = n * MAX * m;//n*3+5*3=2*3+5*3=6+15=21
	printf("%d ", count);//此时的数值的21
	return 0;
}

2.定义的宏后面是不加分号的(不加分号不是不能加,而是加分号容易出错)(本质上还是直接进行宏替换)

比如

3.使用宏定义常量的时候,在还不熟悉的时候,你直接进行带入计算,不要想着直接计算宏,而是带入以后,根据运算符的顺序进行先后计算。

定义常量在编译期间的使用

当我们定义好之后,我需要知道,宏是如何在计算机里面进行运行的,首先是预处理生成.i文件(在csdn编译速通里面我们已经讲到了,这里复述一遍)

翻译环境的过程
1,我们写的代码先创建文件test.c的文件,也就是test.c,然后经过编译器的处理,生成目标文件(test.obj)也就是(obj)

2,目标文件和链接库一起经过链接器的处理,生成可执行程序。

3,生成目标文件(obj)的过程称之为编译(也就是生成obj的过程)

4,生成可执行程序的过程称之为链接。

5,最后整个我们写代码的地方也就是集成开发环境

而我们的宏定义常量的时候,直接就是在.i文件里面就已经完成了替换,.i文件也就是翻译环境里面的编译。我们了解就好。 

这里会进行替换,在.i文件里面,全部替换成常量

在 i文件里面 可以 很好的说明

当然我们,也可以这样定义 #define str for( ::);

#define str(变量名称) for( ::)(给一个循环);

什么意思呢,什么进行图解说明一下

#include <stdio.h>

#define FOREVER for (;;)

int main() {
    int count = 0;

    FOREVER {
        printf("这是第 %d 次循环\n", count);
        count++;

        // 在某种条件下退出循环
        if (count >= 10) {
            break;
        }
    }

    printf("已退出无限循环。\n");

    return 0;
}

 这里会 进行无线循环打印,但是我们给一个判断条件

续行符号

续行符号在下面的定义宏里面会二次详解,这里了解一下 

 在C语言中,续行符号用于将一行代码延续到下一行。这通常在以下情况下使用:
1. 当一行代码太长,超出了编辑器或标准推荐的80个字符的宽度限制时。
2. 为了提高代码的可读性,将长的表达式或参数列表分成多行。

续行符号是反斜杠 `\`,它放在行的末尾,表示该行将在下一行继续。在C语言中,续行符号只能在以下情况下使用:
- 在字符串字面量中,用于创建跨越多行的字符串。
- 在宏定义中,用于将宏的定义延续到下一行。
- 在预处理指令中,用于将指令延续到下一行。

下面是一些使用续行符号的例子:

1. **字符串字面量的续行**:
   ```c
   char* message = "这是一个非常非常非常非常非常非常非常非常非常非常非常非常非常非常非常非常非常非常非常非常非常非常非常非常非常非常非常非常非常非常非常非常非常非常非常非常非常非常非常非常长的字符串。";
   ```
   可以使用续行符号将其分成多行:
   ```c
   char* message = "这是一个非常非常非常非常非常非常非常非常非常非常非常非常非常非常非常非常非常非常非常非常非常非常非常非常非常非常非常非常非常非常非常非常非常非常非常非常非常非常非常非常\
                    非常非常非常非常非常非常非常非常非常非常非常非常非常非常非常非常非常非常非常非常非常非常非常非常非常非常非常非常非常非常非常非常非常非常非常非常非常非常非常非常\
                    长的字符串。";
   ```
2. **宏定义的续行**:
   ```c
   #define LONG_MACRO(x, y) x ## y \
                            x ## _ ## y \
                            y ## _ ## x
   ```
   在这个例子中,宏 `LONG_MACRO` 被定义为三个部分的组合,每个部分都在新的一行上。
3. **预处理指令的续行**:
   ```c
   #include <stdio.h> \
          <stdlib.h> \
          <string.h>
   ```

虽然在实际中,`#include` 指令通常不使用续行符号,因为每个头文件都应该单独一行,但这个例子展示了如何使用续行符号。
请注意,续行符号不能用于一般代码的断行,比如在表达式或语句的中间。如果您需要在代码中换行,您应该将表达式或语句合理地分成多个部分,并确保每个部分都是有效的C语言表达式或语句。

当然我们也可以写成这样,后面的宏定义和运算符李米娜我们会讲解到。这里了解一下。

————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————

#define定义宏

基本语法:

#define name( parament-list ) stuff
其中的 parament-list 是⼀个由逗号隔开的符号表,它们可能出现在stuff中。
简单的说就是和函数差不多,在宏里面写一个类似于函数的计算方法,但是比函数运行的速度要快。

#define宏替换的使用

 在C语言中,`#define` 预处理器指令用于定义宏,这些宏可以在编译时被预处理器替换为指定的文本。

下面是一些使用 `#define` 宏替换的例子:

1. **定义一个简单的宏**:
   ```c
   #define PI 3.14159
   #define MIN(a, b) ((a) < (b) ? (a) : (b))
   #define SQUARE(x) ((x) * (x))
   ```
   在这些例子中,`PI` 被定义为圆周率的值,`MIN` 宏接受两个参数并返回它们中的最小值,`SQUARE` 宏接受一个参数并返回其平方。
2. **使用宏替换字符串**:
   ```c
   #define GREETING "你好,世界!"
   ```
   这个宏可以在代码中任何需要字符串 "你好,世界!" 的地方使用。
3. **使用宏替换代码块**:
   ```c
   #define PRINT_INT(i) printf("整数:%d\n", i)
   ```
   这个宏可以在代码中任何需要打印整数的地方使用。
4. **在宏中使用续行符号**:
   ```c
   #define LONG_MACRO(x, y) x ## y \
                            x ## _ ## y \
                            y ## _ ## x
   ```
   这个宏使用了续行符号 `\` 来将宏的定义延续到下一行。它将生成三个串联的字符串。
5. **在宏中包含条件编译**:
   ```c
   #define DEBUG 1
   #if DEBUG
   #define LOG(msg) printf("调试信息:%s\n", msg)
   #else
   #define LOG(msg)
   #endif
   ```

这里我们主要讲解1,4。因为2,3,5基本上就是定义常量,直接进行替换就可以。
下面是一个完整的例子,展示了如何在 `main` 函数中使用这些宏:

```c
#include <stdio.h>
#define PI 3.14159
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#define SQUARE(x) ((x) * (x))
#define GREETING "你好,世界!"
#define PRINT_INT(i) printf("整数:%d\n", i)
int main() {
    double radius = 10.0;

    double area = PI * SQUARE(radius);//

    printf("圆的面积是:%.2f\n", area);
    int a = 5;
    int b = 10;

    PRINT_INT(MIN(a, b));

    printf(GREETING "\n");
    return 0;
}
```

使用详解1

在这个例子中,我们使用了多个宏来简化代码,并提高了代码的可读性和可维护性。

宏定义,也就是squart(x) 就散是x乘x

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#define MUL(x) ((x)*(x)) 
//宏的使用有很多小的点
//1,宏是直接进行替换的,所以在需要进行宏运算的时候,要尽量多加上括号,防止因为优先级的问题导致使用错误
int main()
{
	int a = 3;
	int ret = MUL(a);
	printf("%d ", ret);
	return 0;
}

使用详解2

这里打印出来是7

宏是直接替换的 这里就变成了3+1*3+1=3+3+1=7

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#define MUL(x) ((x)*(x)) 
#define MULL(x) x+1*x+1
//宏的使用有很多小的点
//1,宏是直接进行替换的,所以在需要进行宏运算的时候,要尽量多加上括号,防止因为优先级的问题导致使用错误
int main()
{
	int a = 3;
	int ret = MUL(a);
	int ret1 = MULL(a);

	printf("%d %d", ret, ret1);
	return 0;
}

预处理阶段宏定义

我们这里看一下预处理

如果想变成36 我们可以加上括号

所以可以书写乘((x)*(X))这样 书写宏的时候不要吝啬括号

所以

————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————

#define的副作用 (带有副作⽤的宏参数

++是有副作用的 这就是带有副作用

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

副作用宏举例1

这里解释一下为什么有副作用

就比如下面的这个例子 ,首先什么定义一个宏,这里是一个三目操作符。如果我们传递的时候,把++传递过去就会导致宏的直接替换。

#define MAX(a, b) ( (a) > (b) ? (a) : (b) )
x = 5;
y = 8;
z = MAX(x++, y++);
printf("x=%d y=%d z=%d\n", x, y, z);//输出的结果是什么?

也就是变成了

1,我们要先把++,替换进去。

2,z = ( (x++) > (y++) ? (x++) : (y++));已知 x = 5;y = 8;

3,得到z = ( (x=5++) > (y=8++) ? (x++) : (y++));这里因为是后置++,所以是先比较后++,5大于8

4,所以此时x=6,y=9,但是这里是一个三目操作符,所以会导致的情况就是满足y++的条件,所以z=(y=8++)++=9,。

5,因为后置++,就是这个运算结束之后才会进行++,这里y++两次,所以y=10,进行z判断的时候,y++一次,所以z=9

6,这里我们需要知道,++是会改变数值的,也就是++上去的数值,数值是会进行变化的

z = ( (x++) > (y++) ? (x++) : (y++));
x=6 y=10 z=9

宏替换的潜规则

这里还是拿上个例子来进行举例 

这里每一次都进行++

这里我们看一下预处理阶段发生的事情

‘M’这里的字符在预处理阶段是不会被搜索到的,哪怕定义了宏M 

宏替换的规则

在程序中扩展#define定义符号和宏时,需要涉及⼏个步骤。
1. 在调⽤宏时,⾸先对参数进⾏检查,看看是否包含任何由#define定义的符号。如果是,它们⾸先
被替换。
2. 替换⽂本随后被插⼊到程序中原来⽂本的位置。对于宏,参数名被他们的值所替换。
3. 最后,再次对结果⽂件进⾏扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上
述处理过程。
注意:
1. 宏参数和#define 定义中可以出现其他#define定义的符号。但是对于宏,不能出现递归。
2. 当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索。

————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————

宏和函数的对比

 宏(Macro)和函数(Function)是编程语言中的两个重要概念,它们在代码中执行特定任务,但它们之间存在一些关键的区别。以下是宏和函数的对比:

1. **定义和替换**:

- **宏**:在编译前,预处理器会替换宏调用。宏可以是对一个词条的简单替换,也可以包含更复杂的文本生成。
 - **函数**:函数是一段可以被重复调用的代码块,它在程序运行时被调用和执行。


2. **展开**:

 - **宏**:宏会在编译前被展开,因此宏调用不会增加程序的运行时间。
 - **函数**:函数调用会在程序运行时执行,通常会增加程序的运行时间。


3. **参数传递**:

 - **宏**:宏通常不支持参数传递,或者只在宏定义中做文字替换,不进行参数的动态替换。
 - **函数**:函数可以接受参数,并且根据传入的参数执行不同的代码。

4. **可变性**:

 - **宏**:宏在定义时通常是不可变的,它们不会根据程序的运行状态改变。
  - **函数**:函数内部可以有变量的声明和使用,这些变量在函数的作用域内是可变的。

5. **目的**:

 - **宏**:宏主要用于文本替换,比如简化代码、创建标识符等。
 - **函数**:函数用于封装可重用的代码块,实现特定的功能。

6. **效率**:

 - **宏**:由于宏在编译前就被展开,所以理论上它们的执行效率高于函数调用。
 - **函数**:函数调用可能比宏展开慢,因为它涉及到跳转和栈帧的创建,但是对于复杂的代码逻辑,函数可以提高代码的可读性和可维护性。

7. **适用场景**:

- **宏**:宏适用于简单的文本替换,或者在编译器级别需要进行的一些特殊处理。
- **函数**:函数适用于需要重复执行且带有逻辑处理的代码块。
在实际的编程实践中,宏和函数的选择取决于具体的需求和上下文。在需要优化性能时,可以考虑使用宏;而在需要代码抽象和模块化时,函数是更好的选择。在高级编程语言中,宏通常作为语言提供的一种机制,而函数则是核心的语言特性。 

函数 

函数的代码 反汇编,运算的完成需要十几条指令

宏定义会产生的情况

这里的一套指令产生的汇编代码是比这个函数少的

宏的参数是没有类型的我们是完成替换的

但是宏通常是解简单运算的适合小型运算 不适合大型的运算。

那为什么不⽤函数来完成这个任务?
原因有⼆:
1. ⽤于调⽤函数和从函数返回的代码可能⽐实际执⾏这个⼩型计算⼯作所需要的时间更多。所以宏⽐
函数在程序的规模和速度⽅⾯更胜⼀筹。
2. 更为重要的是函数的参数必须声明为特定的类型。所以函数只能在类型合适的表达式上使⽤。反之
这个宏怎可以适⽤于整形、⻓整型、浮点型等可以⽤于 > 来⽐较的类型。宏的参数是类型⽆关
的。

宏的劣势

可能会产生,宏替代的前后的代码你看见的可能是不一样的,也就是调试的时候可能是不方便调试的。

和函数相⽐宏的劣势:
1. 每次使⽤宏的时候,⼀份宏定义的代码将插⼊到程序中。除⾮宏⽐较短,否则可能⼤幅度增加程序
的⻓度。
2. 宏是没法调试的。
3. 宏由于类型⽆关,也就不够严谨。
4. 宏可能会带来运算符优先级的问题,导致程容易出现错。

宏可以做到函数做不到的事情

宏的参数可以出现类型,什么意思呢,简单的说就是,有些计算公式里面是有类型的,可能是int类型,可能是char类型。

这个图解里面,我们可以看到,我们是可以定义类型的,并且不需要进行强制类型转化

这里我们传递一个数值,所以就变成了,Malloc(10,int)->

(int*)malloc(10*sizeof(int));

最后的效果和int*p是一样的 

宏和函数的对比

也就是相对简单的时候用宏解决,相对难的时候用函数

宏的应用 

C语言-写一个宏,可以将一个整数的二进制位的奇数位和偶数位交换。-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/Jason_from_China/article/details/137179856————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————

#运算符和##运算符

这里提前补充一点printf

C语言里面会天然的吧printf里面俩字符串合并为一个

#的使用

在C语言中,`#`符号主要用于预处理器指令。预处理器是在代码编译之前执行的,它处理所有以`#`开始的指令。以下是一些常见的使用情况:
1. **包含头文件**:

使用`#include`指令来包含其他头文件。

   ```c
   #include <stdio.h>
   #include "myheader.h"
   ```


2. **定义宏**:

使用`#define`指令来定义宏,这可以在程序中替换为其他文本。

   ```c
   #define PI 3.14159
   printf("The value of PI is: %f\n", PI);
   ```


3. **条件编译**:

使用`#ifdef`、`#ifndef`、`#if`、`#else`、`#elif`、`#endif`等指令来进行条件编译。

   ```c
   #ifdef DEBUG
   printf("Debugging information\n");
   #endif
   ```


4. **行号控制**

:使用`#line`指令来改变编译器输出的行号。
 

  ```c
   #line 100 "newfilename.c"
   // 这行代码的行号将被报告为100,且来源文件被报告为newfilename.c
   ```


5. **文件包含 guard**:

使用`#ifndef`和`#define`来防止头文件被多次包含。

   ```c
   #ifndef MY_HEADER_H
   #define MY_HEADER_H
   // 头文件内容
   #endif // MY_HEADER_H
   ```


6. **整型大小指定**:

在C99标准之前,使用`#`后跟整数来指定整型的大小。

   ```c
   #define SHORT int
   #define LONG long
   ```


7. **编译器指令**:

使用`#`来给编译器提供额外的指令,如`#pragma`。

   ```c
   #pragma once
   ```

请注意,C语言的预处理器不执行任何编程逻辑,它只是简单地替换预处理器指令中的文本。预处理器指令必须在源代码文件中以`//`或`/* */`注释掉之前执行。

具体举例

这里需要知道的是,“format”负责接收类型,这里用双引号,表示占位

也就是,如果你打印的是整数,这里双引号,输入你定义的类型,则此时后面打印的函数会变成你所定义的类型

所以我们可以做到,定义一个宏之后,我们可以使用多个模版进行套用,减少代码的冗余性。

这里我想不只是n,那我用#+变量

# 在宏定义中是一个操作符,用于连接字符串和宏参数。它并不是双引号的一部分。双引号用于定义宏中的字符串字面量,如 "数值的计算"

结果是一样的,但是字符发生了变化

#n 是在宏定义中使用双引号包围的字符串字面量的一部分,它会在宏展开时被替换为 n 的值。

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#define M(n,format) printf("数值的计算"#n ":is="format"\n",n)
int  main()
{
	int a = 1;
	M(a, "%d"); 


	long long b = 10;
	M(b, "%d"); 


	double c = 0.1;
	M(c, "%lf"); 


	return 0;
}


#define M(n,format) printf("数值的计算"#n ":is="format"\n",n)

这个宏 `M` 定义了一个带两个参数的宏:`n` 和 `format`。当这个宏被调用时,它会展开为以下代码:

printf("数值的计算"#n ":is="format"\n",n)

其中 `#n` 会被替换为 `n` 的实际值,`format` 会被替换为实际的格式字符串。例如,如果 `M(a, "%d")` 被调用,
那么它会展开为:printf("数值的计算1:is=%d\n",a)

这里的 `"数值的计算1:is=%d\n"` 是一个字符串字面量,其中 `%d` 是一个格式占位符,用于在运行时由 `a` 的值替换。

 所以这里我们得到的答案也就是我们预期的答案

##运算符 

可以把位于两端的符号合成一个符号 这里就是链接max,

简单说就是我们可以定义类型,如果type传过来的是int类型,那么我们就可以把所有的type换成int,此时会发发现,这个宏有了类型之分。但是我们计算的变量的名字我们希望是独一无二的,此时我们可以用##链接类型,也就是变成了

type type__max(int x,int y);这个就变成了宏的定义

可以进行一个两个数值的比较大小,并且只需要进行定义一个类型就哭哭完成相应的计算。

怎么使用的 就等于

我们在gcc里面进行观察 

 批量创造相似的函数

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>

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

//函数的定义
COMPUTE(int);//int_COMPUTE
COMPUTE(float);//float_COMPUTE

int main()
{
    int ret1 = int_COMPUTE(3, 5);
    printf("%d\n", ret1);
    float ret2 = float_COMPUTE(3.1f, 4.5f);
    printf("%f\n", ret2);

	return 0;
}

————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————

命名约定

⼀般来讲函数的宏的使⽤语法很相似。所以语⾔本⾝没法帮我们区分⼆者。
那我们平时的⼀个习惯是:
把宏名全部⼤写
函数名不要全部⼤写

#undef

这条指令⽤于移除⼀个宏定义。
# undef NAME
// 如果现存的⼀个名字需要被重新定义,那么它的旧名字⾸先要被移除。

————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————

条件编译(常见的编译指令)

#if(开始(判断条件))+#endif(结束)

条件满足就参与编译,这里是一个判断的语句,当M大于0的时候,打印hehe不然就不打印

或者注释代码也好用

当#if 0的时候 ,也就是大规模的注释代码使用

当然结尾需要加上#endif

———————————————————————————————————————————

 #endif(结束)

条件编译指令都需要#endif来结束

———————————————————————————————————————————

#if defined(判断是否已经被定义,定义了就执行)+#endif(结束)

如果没有定义#define max 那就不打印

如果定义了#define max 那就打印

#if defined(MAX)
// 如果 MAX 已经被定义,则执行这里的代码
printf("hehe\n");
#endif

———————————————————————————————————————————

 #if !defined(判断是否未被定义,未定义执行)+#endif(结束)

#if !defined(MAX)
// 如果 MAX 未定义,则执行这里的代码
printf("hehe\n");
#endif

———————————————————————————————————————————

条件编译的嵌套使用


#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


这段代码是使用条件编译来根据不同的操作系统定义来执行不同的代码块。这里的 `OS_UNIX` 和 `OS_MSDOS` 是用来表示不同操作系统的宏,而 `OPTION1` 和 `OPTION2` 可能是与特定操作系统相关的其他宏。
让我们逐行解释代码:
```c
#if defined(OS_UNIX)
```
这行代码检查 `OS_UNIX` 是否被定义。如果当前编译的操作系统是 Unix(包括类Unix系统如Linux、BSD等),那么接下来的代码块会被编译和执行。
```c
 #ifdef OPTION1
 unix_version_option1();
 #endif
```
这段代码首先检查 `OPTION1` 是否被定义。如果在 `OS_UNIX` 定义的文件中定义了 `OPTION1`,那么 `unix_version_option1();` 这行代码会被编译和执行。`OPTION1` 可能是特定于Unix系统的功能或配置选项。
```c
 #ifdef OPTION2
 unix_version_option2();
 #endif
```
这段代码与上一段类似,但它检查 `OPTION2` 是否被定义。如果在 `OS_UNIX` 定义的文件中定义了 `OPTION2`,那么 `unix_version_option2();` 这行代码会被编译和执行。这允许特定于Unix系统的不同选项可以根据需要启用或禁用。
```c
#elif defined(OS_MSDOS)
```
这行代码是 `#if` 的备用分支,用于检查 `OS_MSDOS` 是否被定义。如果当前编译的操作系统是 MS-DOS 或其他兼容系统,那么 `#elif` 块中的代码会被编译和执行。
```c
 #ifdef OPTION2
 msdos_version_option2();
 #endif
```
这段代码与之前解释的类似,但它是在 `OS_MSDOS` 定义的情况下使用的。它检查 `OPTION2` 是否被定义,如果在 `OS_MSDOS` 定义的文件中定义了 `OPTION2`,那么 `msdos_version_option2();` 这行代码会被编译和执行。这允许特定于MS-DOS系统的不同选项可以根据需要启用或禁用。
```c
#endif
```
这行代码标记了条件编译块的结束。
总结一下,这段代码允许根据当前编译的操作系统选择性地编译和执行特定的功能。如果操作系统是Unix,则执行与 `OPTION1` 和 `OPTION2` 相关的代码。如果操作系统是MS-DOS,则只执行与 `OPTION2` 相关的代码。如果操作系统既不是Unix也不是MS-DOS,那么整个代码块将被忽略。

———————————————————————————————————————————

#ifdef(定义了宏继续运行)+ #endif(结束)

#ifndef(没有定义宏则继续运行)+ #endif(结束)

在宏定义里面,我们有时候会定义的宏过多导致不知道宏是否已经定义了。所以我们可以·

- `#ifdef` 检查是否定义了某个宏,如果定义了,则编译后续的代码块。
- `#ifndef` 检查是否没有定义某个宏,如果没有定义,则编译后续的代码块。

下面是如何使用这些指令的例子:

```c
#ifdef MAX
// 如果 MAX 已经被定义,则编译这里的代码
printf("MAX is defined.\n");
#endif


#ifndef MAX
// 如果 MAX 没有被定义,则编译这里的代码
printf("MAX is not defined.\n");
#endif
```


在第一个 `#ifdef MAX` 指令之后,预处理器会检查 `MAX` 是否已经被定义。如果 `MAX` 已经被定义(比如通过 `#define MAX 10`),那么 `printf("MAX is defined.\n");` 这行代码会被编译和执行。如果 `MAX` 没有被定义,预处理器会忽略整个 `#ifdef` 和随后的 `#endif` 指令之间的代码块。
在第二个 `#ifndef MAX` 指令之后,预处理器会检查 `MAX` 是否没有被定义。如果 `MAX` 没有被定义,那么 `printf("MAX is not defined.\n");` 这行代码会被编译和执行。如果 `MAX` 已经被定义,预处理器会忽略整个 `#ifndef` 和随后的 `#endif` 指令之间的代码块。
这些指令通常用于条件编译外部头文件,或者在不同的编译环境中编译相同的代码,以避免重复包含和潜在的冲突。

#ifdef和#ifndef 总结

  • #ifdef 指令检查是否已经定义了某个宏。如果该宏已定义,则执行 #ifdef 和相应的 #endif 指令之间的代码。
  • #ifndef 指令检查是否没有定义某个宏。如果没有定义该宏,则执行 #ifndef 和相应的 #endif 指令之间的代码。

————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————

头文件的包含

本地⽂件包含

  # include "filename"
查找策略:先在源⽂件所在⽬录下查找,如果该头⽂件未找到,编译器就像查找库函数头⽂件⼀样在
标准位置查找头⽂件。
如果找不到就提⽰编译错误。

Linux环境的标准头⽂件的路径:

1 /usr/include

VS环境的标准头⽂件的路径:

C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\include
// 这是 VS2013 的默认路径

库⽂件包含

1 # include <filename.h>
查找头⽂件直接去标准路径下去查
找,如果找不到就提⽰编译错误。
这样是不是可以说,对于库⽂件也可以使⽤ “” 的形式包含?
答案是肯定的,可以,但是这样做查找的效率就低些,当然这样也不容易区分是库⽂件还是本地⽂件 了。

总结

“”双引号 先去文件找,找不到去标准头文件里面去找(范围较大)

<>直接去标准库文件里面去查找

分开定义的目的是效率比较高

———————————————————————————————————————————

如何防止头文件多次引入

这里就是头文件包含五次,我们需要知道头文件是不能相互包含的

如何解决这个问题

#pragma()

这个是默认对齐数

———————————————————————————————————————————

 #pragma once(解决方式1)

`#pragma once` 是一个编译器指令,它告诉编译器在包含文件的任何地方只包含该文件一次,即它实现了文件级别的重复包含保护。这个指令被广泛支持,但并非所有编译器都必须遵守。
在 C 和 C++ 编程中,头文件通常包含宏定义、函数原型和类声明等,这些内容在被多个源文件包含时可能会被多次编译,导致代码冗余和可能的冲突。

使用 `#pragma once` 可以避免这种情况。
当编译器遇到 `#pragma once` 指令时,它会检查该文件是否已经被包含过。如果文件尚未被包含,编译器将继续包含该文件并继续编译;如果文件已经被包含,编译器将忽略该文件,不会重复包含。
这里有一个简单的例子:


// header.h
#pragma once
// 一些宏定义和函数原型
#define MY_MACRO 1
void function();
// main.c
#include "header.h"
int main() {
    // 使用宏和函数
    printf("%d\n", MY_MACRO);
    function();
    // header.h 不会被重复包含
    #include "header.h"
    // 这里不会引起编译错误,因为 header.h 已经被包含过了
}

在这个例子中,`header.h` 文件中包含了 `#pragma once` 指令。当 `main.c` 文件包含 `header.h` 时,`#pragma once` 确保了 `header.h` 中的内容不会因为多次包含而重复编译。因此,即使 `#include "header.h"` 在 `main.c` 文件中被重复写了一次,它也不会对 `header.h` 产生第二次包含的效果。
需要注意的是,`#pragma once` 并不是 C 或 C++ 标准的一部分,它的支持取决于具体的编译器。然而,由于它非常简单且易于实现,大多数现代编译器都支持这个指令。

———————————————————————————————————————————

#ifndef +#define解决方式2

 每个头⽂件的开头写:

# ifndef __TEST_H__//是否被定义(没有则下一步)
# define __TEST_H__//进行定义
//头⽂件的内容
# endif //__TEST_H__//结束标语
也就是

什么意思呢,也就是首先我们判断

1,宏是否被定义没有则继续执行

 2,宏是否被定义,定义了则不执行

补充说明 

  • #ifdef 指令检查是否已经定义了某个宏。如果该宏已定义,则执行 #ifdef 和相应的 #endif 指令之间的代码。
  • #ifndef 指令检查是否没有定义某个宏。如果没有定义该宏,则执行 #ifndef 和相应的 #endif 指令之间的代码。

 所以

  • #ifndef 指令检查是否没有定义某个宏。如果没有定义该宏,则执行 #ifndef 和相应的 #endif 指令之间的代码。
  • #define 指令定义一个宏,它将随后的所有出现替换为指定的文本
  • 这两个指令的典型用途是在头文件中确保只有当宏尚未定义时,头文件的内容才会被包含。这防止了头文件的内容在多个地方被重复包含,从而避免了可能的编译错误或重复定义错误。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值