C语言——#define的使用

#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博客https://blog.csdn.net/Jason_from_China/article/details/137179856

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值