当前位置: 首页 > news >正文

c语言修炼秘籍 - - 禁(进)忌(阶)秘(技)术(巧)【第七式】程序的编译

c语言修炼秘籍 - - 禁(进)忌(阶)秘(技)术(巧)【第七式】程序的编译

【心法】
【第零章】c语言概述
【第一章】分支与循环语句
【第二章】函数
【第三章】数组
【第四章】操作符
【第五章】指针
【第六章】结构体
【第七章】const与c语言中一些错误代码
【禁忌秘术】
【第一式】数据的存储
【第二式】指针
【第三式】字符函数和字符串函数
【第四式】自定义类型详解(结构体、枚举、联合)
【第五式】动态内存管理
【第六式】文件操作
【第七式】程序的编译


文章目录

  • c语言修炼秘籍 - - 禁(进)忌(阶)秘(技)术(巧)【第七式】程序的编译
  • 前言
  • 一、程序的翻译环境和执行环境
  • 二、详解编译和链接
    • 1. 翻译环境
    • 2. 编译本身也分为几个阶段
      • 编译
        • 预编译:
        • 编译:
        • 汇编:
      • 链接
    • 3. 运行环境
  • 三、预处理详解
    • 1. 预定义符号
    • 2. #define
      • 2.1 #define 定义标识符
      • 2.2 #define 定义宏
      • 2.3 #define 替换规则
      • 2.4 #和##
      • 2.5 带副作用的宏参数
      • 2.6 宏和函数的对比
    • 3. #undef
    • 4. 命令行定义
    • 5. 条件编译
    • 6. 文件包含
  • 总结


前言

在本章会对程序的编译过程,进行详细的讲解,重点包括:

  • 程序的翻译环境
  • 程序的执行环境
  • c语言程序的编译+链接
  • 预定义符号介绍
  • 预处理指令#define
  • 宏和函数的对比
  • 预处理操作符#和##的介绍
  • 命令定义
  • 预处理指令#include
  • 预处理指令#undef
  • 条件编译

一、程序的翻译环境和执行环境

在ANSI C的任何一种实现中,都存在两种不同的环境

第1种是翻译环境,在这个环境中源代码被转换为可执行的机器指令;
第2种是执行环境,它用于执行代码;

下图简单的表示了这两个环境的作用:
在这里插入图片描述

二、详解编译和链接

1. 翻译环境

在这里插入图片描述

  • 组成程序的每个源文件通过编译过程分别转换成目标代码(object code);
  • 每个目标文件由链接器(linker)捆绑在一起,形成一个单一而完整的可执行程序;
  • 链接器同时也会引入标准c语言库中任何被该程序使用到的函数,而且它可以搜索程序员个人的程序库,将其需要的函数也链接到程序中;

以之前写过的通讯录程序为例子:
该程序有2个源文件:contact.c、test.c,这两个源文件在翻译环境中单独进行翻译生成对应的目标文件(后缀为.obj);
即生成contact.obj和test.obj
在这里插入图片描述
之后这些生成的目标文件将会一起经过链接器,链接在一起生成一个可执行程序,在这个过程中,还会将程序会使用到的库函数一起链接进来。

所以翻译环境中,代码经历的过程又可以分为两步,编译和链接:
在这里插入图片描述
在VS使用的编译器是cl.exe,使用的链接器是link.exe;

2. 编译本身也分为几个阶段

编译过程还可以详细的分为3个步骤:预编译(预处理),编译,汇编;
在这里插入图片描述
接下来我们会对这三个步骤逐个的详细讨论(在linux环境下进行):

编译

示例代码如下:
在这里插入图片描述

预编译:

gcc test.c -E 这个命令的作用是让源文件在预处理之后就停下;这条命令不生成文件,可以使用重定向将命令执行结果输出到一个文件中;这里使用gcc test.c -E > test.i将该命令等到的信息输出到test.i这个文件中;后缀为.i的文件是预处理后得到的文件;

打开得到的文件可以看到如下的结果:
在这里插入图片描述
在此之前还有很多内容,仅截取部分;可以发现我们并不能看懂这部分的内容;
在它之后的内容我们就能看懂了,这是我们的代码:
在这里插入图片描述
可以看到在我们的代码之前多出来了一大堆东西,但是少了一条语句 - #include <stdio.h>
在我们的心法篇中介绍了#include包含的头文件会以替换的形式将头文件中的内容包含进行源代码中,所以上面那一大堆代码其实就是头文件stdio.h中的内容。

那么我们就来验证一下,看看上面的内容到底是不是stdio.h中的内容,可以看到stdio.h的地址是/usr/include,我们打开这个文件比较一下,两者是否相同。
在这里插入图片描述
此目录下确实存在这个文件,接下来看看文件的内容:
在这里插入图片描述
可以看到之前的确实是stdio.h拷贝到test.c中的内容;

接下来我们就来看看,预处理过程究竟做了什么?
从上图中我们可以看出,预处理

  • 完成了头文件的包含#include;
  • 完成了#define定义的符号和宏的替换
  • 删除了所有注释;

    总结一下,预处理过程完成了源文件到目标文件过程中所有的文本操作
编译:

gcc test.c -S,此命令的作用是对test.c这个文件进行预处理和编译;也可以使用gcc test.i -S,这个命令可以得到同样的结果;命令执行之后会得到一个test.s
在这里插入图片描述
大概看一下test.s中有什么内容:
在这里插入图片描述
在预处理阶段,得到的文件仍是一个c语言代码,经过编译之后就得到了像上面一样的汇编代码;

这个过程中会进行语法分析、词法分析、语义分析、符号汇总;这4个步骤分别会做些什么,这里就不详细介绍,具体内容请自行学习编译原理;这里仅作简单介绍;

从上面的图片中我们得知,编译的过程就是将c语言代码转换成汇编代码的过程;这个转换的过程是由计算机来完成的,这也意味着计算机需要能够“读懂”c语言代码;那么要读懂c语言代码需要做到什么呢?首先需要知道每个语句表示什么,比如,这个东西是一个变量,那个是一个for循环,这就是语法分析大概要完成的事情;除此之外,还需要对每个语句进行拆分,比如,将一个for循环语句中拆分出for关键字,变量名等等;在知道了语法和词法之后,还需要知道一个语句表示的是什么意思,这就是语法分析要做的事;至于符号汇总,我们放在汇编中再作介绍;

汇编:

使用gcc test.c -c可以将对源文件进行预编译、编译和汇编,生成test.o文件,相当于windows系统中的.obj文件,也就是目标文件;
在这里插入图片描述
那么我们打开这个test.o文件看看里面保存的是什么;
在这里插入图片描述
test.o文件打开之后就是一堆乱码,也就是test.o是一个二进制文件;
所以汇编过程的作用是将汇编代码转换成了机器指令(二进制指令);
在这个过程中除了上面我们能看出来的将汇编代码转换为机器指令之外,还有一件非常重要的事情:生成符号表

在这里需要生成的符号表和上一步中的符号汇总有什么联系呢?
在说明上面的联系之前,我们要先知道test.o文件的是一种elf类型的文件,这是一种分段的文件,代码的符号表就保存在这种文件的符号段中,使用readelf test.o --syms就可以获取到这个目标文件的符号表:
在这里插入图片描述
对其进行分析:
在这里插入图片描述
从上图中可以看到,汇编过程中得到的符号表中仅有一些全局的符号,而这些符号又是在编译过程的符号汇总中汇聚在一起的,这一步会将整个程序中所有源文件中存在的全局符号全部汇总在一起,之后再进行处理;

为了能更加清楚的展示编译过程会将符号汇总,在汇编过程中会生成对应的符号表,下面重新在对test.c和add.c进行编译:
在这里插入图片描述
分别对它们进行编译:
在这里插入图片描述
查看它们生成的符号表:
在这里插入图片描述
可以看到在test.o的符号表中也有Add这个符号,但是实际上它并知道这个符号的地址是什么,只是给它填充一个没有意义的地址;
在这里插入图片描述
可以看到单独生成的test.o和add.o中都包含有符号Add,只是一个的地址是有效的,一个是无效的;但是这两个符号在生成可执行程序时,到底使用哪一个呢?这一步就交到链接器来完成;

链接

在链接阶段,链接器会将多个目标文件和链接库进行链接,生成一个可执行文件;在上面的例子中,仅有两个目标文件,test.o和add.o,没有链接库,所以在此程序中,链接操作的作用就是将这两个目标链接成一个可执行文件a.out(可以通过参数设置,修改生成的可执行文件的名字),linux系统中;命令:gcc test.o add.o -o test.out生成一个名为test.out可执行文件;(注意,linux系统中并不以后缀名来区分文件是否可执行,而通过文件权限和文件格式(必须是elf格式)来区分,可以看到test.out文件是有执行的权限的X标志);

在这里插入图片描述

该阶段会进行的具体操作可分为:

  1. 合并段表;
  2. 符号表的合并和重定位;

因为,可执行文件的格式也是elf,所以在由多个目标文件生成可执行文件过程中,需要将这些文件中相同段中的内容进行合并,这就是合并段表;
而符号表的合并和重定位就更容易理解了,在介绍汇编时,我们提到了test.o和add.o这两个文件和符号表中都有Add这个符号,在两个目标文件合并成一个可执行文件的过程中,这两个Add肯定只能保留一个,也就是保留那个有着有效信息的Add,将另一个无效的Add删除,并将Add的地址保存为add.o中的地址;简单来说就是将多个目标文件的符号进行合并,并将有效的符号进行保留;
在这里插入图片描述
看到这里的同学肯定会疑惑,这个符号表有什么用呢?
这个符号表可以使得程序能够通过它们找到并使用对应的符号,比如,程序要调用Add函数时,就会到符号表中找Add这个符号的地址,也就是0x1008,之后程序通过这个地址调用了Add函数,完成了功能;

下面我们在VS环境中举个例子帮助大家更好的理解这点。
将add.c中所有内容注释掉,此时add.c变成了一个空文件,在汇编之后会生成一个空的符号表,test.c文件仍会生成包含两个符号的符号表,其中符号Add的地址是无效地址;
此时进行编译就会出现以下的错误:
在这里插入图片描述
在这里插入图片描述
在链接时,通过合并的符号表寻找Add函数时,链接器去往0x0000这个地址会发现并没有Add这个函数存在,也就发生了错误;

正是因为有了符号表的存在,跨文件的函数调用才得以实现;

总结
翻译过程可以分为编译和链接两个步骤,其中编译又可以分为,预编译、编译、汇编三个步骤;
它们使用的命令分别是:gcc -test.c -Egcc -test.c -Sgcc -test.c -c
预编译过程进行文本操作(test.i),编译过程将c语言代码转换成汇编代码(test.s),汇编过程将汇编代码转换成机器指令(目标文件,test.o);最后由链接器将这些目标文件链接成一个可执行文件;

一图流:
在这里插入图片描述

3. 运行环境

程序运行的过程:

  1. 程序必须载入内存中才可以运行;在有操作系统的环境中,该步骤一般由操作系统来完成;在独立的环境中,程序的载入要么手动操作,要么通过可执行代码置入只读内存来实现;
  2. 程序的执行便开始。接着调用main函数;
  3. 开始执行程序代码。此时程序将使用一个运行时堆栈(stack),存储函数的局部变量和返回地址。程序同时也可以使用静态内存(static),存储于静态内存中的变量在程序执行整个过程中一直保留它们的值;
  4. 终止程序。正常终止main函数;有时候也可能是意外终止;

关于运行时堆栈,这里仅简单介绍,想要详细了解,大家可以去看之前的文章内容,里面有详细介绍;在心法篇中的函数和秘术篇的动态内存分配里都有介绍;

在这里插入图片描述
注意:堆栈指的就是栈,并不是堆+栈

三、预处理详解

1. 预定义符号

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

这些预定义符号都是语言内置的。
举个例子:

#include <stdio.h>int main()
{printf("file:%s\n", __FILE__);printf("line:%d\n", __LINE__);printf("date:%s\n", __DATE__);printf("time:%s\n", __TIME__);printf("function:%s\n", __FUNCTION__);// printf("STDC:%s\n", __STDC__); // VS 2022 未定义return 0;
}

运行结果:
在这里插入图片描述

这些内置符号具体有什么用呢?
可以用来写程序日志,在日志中记录时间和代码行号,在出错时,就可以非常方便的定位错误原因;就算是正常运行过程中,写日志也是一个非常重要的事,它可以让你可以了解程序当前是运行状态,检查可能出现的一些问题;

下面我们给出一个记录日志的例子供大家参考:

#include <stdio.h>int main()
{// 记录日志,就是在写文件// 将程序执行的信息输出到log.txt中FILE* pf = fopen("log.txt", "a");if (pf == NULL){perror(pf);return ;}int i = 0;for (i = 0; i < 10; i++){// 记录每行的信息fprintf(pf, "%s %d %s %s %d\n", __FILE__, __LINE__, __DATE__, __TIME__, i);}fclose(pf);pf = NULL;return 0;
}

文件内容:
在这里插入图片描述

2. #define

2.1 #define 定义标识符

语法:
#define name stuff

示例:

// #define 定义标识符
#define MAX 100 // 定义一个数字
#define uint unsigned int // 有时一个关键字较长,可以创建一个更简短的方式
#define do_forever for(;;) // 用更形象的符号来替换一个种实现,这里实现的是一个死循环
#define CASE break;case // 在写case语句的时候自动把 break写上;// 因为有此语言中switch语句不用加break,习惯了这些语言的程序员可能在使用c语言时,// 害怕自己忘记添加break导致程序出错,就可以使用这种标识符
// 如果定义的stuff过长,可以分成几行来写,除了最后一行外,每一行的后面都加上一个反斜杠 \ (续行符)
#define DEBUG_PRINT printf("file:%s\tline:%d\t \date:%s\ttime:%s\n", \__FILE__, __LINE__, \__DATE__, __TIME__)#include <stdio.h>int main()
{uint i = MAX;// do_forever; // 相较于,for(;;);,该语句以一个更清楚的方式,实现了一个死循环int input = 0;scanf("%d", &input);// 代码1switch (input){case 1:CASE 2:CASE 3:}// 代码2switch (input){case 1:break;case 2:break;case 3:}// 代码1和代码2完全等价DEBUG_PRINT;return 0;
}

大家可能在使用#define定义标识符时,会产生一个疑问,在标识符后面需不需要加上一个分号;呢?
比如:

#define MAX 1000;
#define MAX 1000

这两种定义方式有什么不同呢?

我们都知道,#define定义的标识符是在预处理阶段直接进行文本替换的;
放在代码中:

int main()
{int a = MAX; // 使用第一种定义方式时,这个代码会变成 int a = 1000;;// 赋值语句之后还有一个空语句,在这里语法是没有什么问题的,// 但是换一种情况就会产生问题if(condition)max = MAX; // 在这里一条if语句只能对应一条语句,但是这里有了两条语句,产生了语法错误elsemax = 0;
}

所以我们在使用#define定义标识符时,一般都不会在后面添加;

2.2 #define 定义宏

#define 机制包括了一个规定,允许把参数替换到文本中,这种实现通常称为宏(macro)或定义宏(define macro);

下面是宏的申明方式:

#define name(parament-list) stuff
// parament-list是一个逗号隔开的符号表,它们可能出现 stuff中

注意,参数列表的左括号必须与name紧邻;如果两者之间存在有任何空白存在,参数列表就会被解释为stuff的一部分,就变成了#define定义的标识符了

使用示例:

// 定义一个宏来计算平方
#define SQUARE(x) ((x) * (x))
// 这个宏接受一个参数,x// 在程序中使用
SQUARE(8);
// 该宏会像#define定义的标识符一样,直接在预处理阶段进行替换
// 即程序中的代码变成
((8) * (8));

易错点:
大家初次使用宏可能会疑惑,为什么上面定义的这个宏要有那么多的括号呢?
下面我们就来看看如果没有括号会发生什么:

#include <stdio.h>
#define SQUARE(x) x * xint main()
{int a = 2;int ret = SQUARE(a + 1);printf("%d\n", ret);return 0;
}

这段代码会如预期一样输出9吗?
在这里插入图片描述
可以看到输出结果是5
这是为什么呢?
宏的本质是在文本处理阶段直接替换,所以上面的代码在预处理之后变成了:

#include <stdio.h>int main()
{int a = 2;int ret = a + 1 * a + 1;printf("%d\n", ret);return 0;
}

可以看到,该程序是将a + 1 * a + 1赋值给了ret,也就是 2 + 1 * 2 + 1,结果就是5;

当我们的宏定义成#define SQUARE(x) ((x) * (x))时,上面代码在替换之后为,((2 + 1) * (2 + 1)),结果与预期相同;

这时有人可能还有疑问,在x外部加上括号不就行了,为什么在计算结果外面也要加上括号呢;
我们再看一个例子:

#define DOUBLE(x) (x) + (x)int main()
{int a = 5;int ret = 10 * DOUBLE(a);printf("%d\n", ret);return 0;
}

这段代码在替换之后变成了int ret = 10 * (5) + (5)这与预期的结果也是不符的,当宏的结果外面有了括号,此时int ret = 10 * ((5) * (5))才符合预期;

结论
在使用宏来对数值表达式求值时,宏的定义都应该在参数和总体外面加上括号,这样就可以避免在使用宏时出现预期之外的错误;

2.3 #define 替换规则

在程序中扩展#define定义的符号和宏时,需要涉及以下的几个步骤:

  1. 在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,首先替换掉它们;
  2. 替换文本随后被插入到程序原来文本的位置。对于宏,宏的参数名被它的值替换;
  3. 最后,再对结果文件进行扫描,看看是否还有包含任何由#define定义的符号。如果是,就重复上述步骤;

示例:

#include <stdio.h>#define M 20
#define MAX(x, y) (((x) > (y))? (x) : (y))int main()
{int a = 10;printf("%d\n", MAX(a, M));return 0;
}

这段代码中,MAX这个宏中第二个参数是一个#define定义的符号,所以此时先替换M,变成printf("%d\n", MAX(a, 20));
之后替换文本,printf("%d\n", (((10) > (20)) ? (10) : (20)));

注意

  1. 宏参数和#define定义中可以出现其他#define定义的符号,但对于宏,不能出现递归;
  2. 当预处理器搜索#define定义的符号时,字符串常量的内容并不被搜索;

举个例子说明第二点:

printf("M = %d\n", M);

这段代码中的字符串中的M不会被替换;
输出结果为:M = 20

2.4 #和##

#的作用

如何把参数插入到字符串中?

在介绍这个内容之前,我们先看下面的代码:

#include <stdio.h>int main()
{printf("hello world\n");printf("hello ""world\n");return 0;
}

这两个输出语句,输出的结果相同吗?
在这里插入图片描述
由上图得知,这两句代码效果相同;

在了解了上面的printf函数的使用之后,再来看下面的代码:

#include <stdio.h>int main()
{int a = 10;// 需要得到 the value of a is 10 这样的输出int b = 20;// 需要得到 the value of b is 20 这样的输出int c = 30;// 需要得到 the value of c is 30 这样的输出return 0;
}

要完成上面的需求,应该怎么解决呢?函数?
试试看:

void print(int x)
{printf("the value of x is %d\n", x);
}

能这样写吗?显然不行,写成函数的话,输出结果就被写死了,只有值能随着参数的变化而变化,字符串内容无法改变;只能写成3个不同的函数来实现;

想到上面提到的printf函数的特性,能写成一个宏来实现这个功能吗?

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

这样写正确吗?预想中,先输出字符串the value of ,再输出X,再输出字符串is %d\n%d对应X的值;

实际上:
在这里插入图片描述
这里需要第一个a变成一个字符串,c语言规定,使用#修饰宏的参数,在预处理阶段,该参数会作为字符串进行替换;即,#X会变成"X"
将代码改成:

#include <stdio.h>#define PRINT(X) printf("the value of " #X " is %d\n", X);int main()
{int a = 10;// 需要得到 the value of a is 10 这样的输出PRINT(a);int b = 20;// 需要得到 the value of b is 20 这样的输出PRINT(b);int c = 30;// 需要得到 the value of c is 30 这样的输出PRINT(c);return 0;
}

运行结果:
在这里插入图片描述
可能有人会想可以用3个引号吗?也就是写下面这样:

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

很显然不行,这个与上面提示的预处理器处理时不会替换字符串常量中的符号冲突,此时的输出变成:
在这里插入图片描述

接下来我们再对这个宏进行优化,此时该宏只能处理整型数据,能否让它可以处理任何类型的变量呢?当然是可以的,要处理什么类型的数据,这个问题使用者是清楚的,所以我们的宏在增加一个参数,接收数据的类型;

#include <stdio.h>#define PRINT(X, FORMAT) printf("the value of " #X " is " FORMAT "\n", X);int main()
{int a = 10;// 需要得到 the value of a is 10 这样的输出PRINT(a, "%d");int b = 20;// 需要得到 the value of b is 20 这样的输出PRINT(b, "%d");int c = 30;// 需要得到 the value of c is 30 这样的输出PRINT(c, "%d");float f = 3.14f;PRINT(f, "%f");return 0;
}

在这里插入图片描述

##的作用

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

示例:

#include <stdio.h>#define STR "要你命"
#define CAT(X, Y) X##Yint main()
{// 达文西现在除了要你命3000之外,还有了许多其他的要你命系列的武器// 现在需要根据提供的型号,产生对应的字符串printf("%s\n", CAT(STR, "4000"));// 譬如此时有一个变量名就是Annihilator3000int Annihilator3000 = 100; // 这里为了演示方便使用int类型,也可以使用其他类型printf("%d\n", CAT(Annihilator, 3000));return 0;
}

运行结果:
在这里插入图片描述
注意
使用##时,除了#define定义的标识符会进行替换之外,符号两边是什么,就用什么进行拼接,使用变量时,不会使用变量指代的值,而是直接使用变量名本身;

2.5 带副作用的宏参数

什么叫做带副作用的宏参数呢?
副作用就是表达式求值时出现的永久性效果。比如,x+1这个表达式就没有副作用,x++这个表达式就有副作用,它会永久性的改变x的值;
当宏参数在宏的定义中出现超过一次时,如果参数带有副作用,那么你在使用这个宏时可能出现不可预测的结果;

示例:

#include <stdio.h>#define MAX(X, Y) ((X) > (Y)? (X) : (Y))int main()
{int a = 5; int b = 8;int c = MAX(a++, b++);// 这里的结果会是什么呢?// 会是6, 9, 9吗 printf("%d, %d, %d\n", a, b, c);return 0;
}

运行结果:
在这里插入图片描述
可以看到实际结果与预期不符,这是因为条件表达式在替换之后为int c = ((a++) > (b++)? (a++) : (b++)),两个参数比较之后,还执行了一次b++;所以b变成了10;

所以在使用宏时,尽量不要使用有副作用的参数;

2.6 宏和函数的对比

比较下面两种实现哪种更好

// 代码1
#define Add1(X, Y) ((X) + (Y))// 代码2
int Add2(int x, int y)
{return (x + y);
}int main()
{int a = 10;int b = 20;int c = Add1(a, b);int d = Add2(a, b);return 0;
}

结论是代码1更好;
原因如下:
这是宏的汇编代码,仅有三句;
在这里插入图片描述
下面的是函数实现的汇编执行过程:
在这里插入图片描述
很显然,函数调用比宏的实现要复杂得多;

所以简单的运算使用宏是更优的;

原因有二:

  1. 用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多。所以宏比函数在程序的规模和速度方面更胜一筹
  2. 更为重要的是函数的参数必须声明为特定的类型。所以函数只能在类型合适的表达式上使用。而宏可以适用于整型、长整型、浮点数等可以使用>进行比较的类型,使用更灵活;宏与类型无关

当然宏与函数相比也存在有劣势的地方:

  1. 每次使用宏时,都是通过代码替换的方式实现的。除非一个宏较短,否则,这会大大增加程序的长度;
  2. 宏是无法调试的;因为宏的替换是在预处理阶段,而调试则是发生在可执行程序运行中;
  3. 宏与类型无关,既是优点也是缺点,因为没有类型,所以不够严谨;
  4. 宏可能存在运算符优先级的问题,导致程序更容易出错;

宏也能做到一些函数无法做到的事,比如#中的例子,还有宏的参数可以是类型,函数不行;

#include <stdlib.h>#define MALLOC(x, type) \((type*)malloc(sizeof(type) * x))int main()
{// 开辟10个整型的空间int *p = MALLOC(10, int);return 0;
}
属性#define定义宏函数
代码长度每次使用时,宏代码都会插入程序,程序长度会增加函数代码只出现一次,每次调用都是使用同一份代码
执行速度更快存在函数调用和返回的额外开销,较慢
操作符优先级宏参数的求值是在所有周围上下方环境中,除非加上括号,否则邻近操作符的优先级可能会对求值产生不可预料的影响函数参数只在传参时求值一次,将求值结果传递给函数,结果更容易预测
带副作用的参数参数可能被替换到宏体中的多个位置,所以带有副作用的参数求值可能会产生不可预期的结果函数参数只在传参时求值一次,结果更容易控制
参数类型宏的参数与类型无关,只要对参数的操作是合法的,它就可以使用任何参数函数的参数确定类型的,不同的参数需要不同的函数,即使它们执行的任务相同
调试宏无法调试函数可以逐语句调试
递归宏不可递归函数可以递归

命名约定
一般来说,函数和宏使用的语法非常相似;所以语言本身无法帮助我们区分它们。
所以平时,我们习惯于将:

宏名全部大写
函数名不要全部大写

3. #undef

这条语句用于移除一个宏的定义

#include <stdio.h>
#undef NAME
// 如果存在一个名为 NAME的宏,当你不再想要它时,可以使用#undef来将它移除#define M 100int main()
{int a = M;
#undef Mprintf("%d %d\n", a, M);return 0;
}

在这里插入图片描述

4. 命令行定义

对于许多c语言的编译器提供了在命令行中定义符号的功能;
假如对于一个可移植的代码,代码中有一个数组,有些机器的内存空间有限,所以这个数组的长度就小,对于另一些内存较大的机器,数组的长度就可以更长一点;
此时就可以使用命令行定义:

示例:

在这里插入图片描述
在这里插入图片描述
可以看到直接编译时会出现错误,提示M没有定义;此时可以使用命令行命令定义变量M的值为10,之后再进行编译就可以通过,并且成功运行;

5. 条件编译

c语言中语句的编译可以像条件语句一样,选择性的进行编译;

比如:

#include <stdio.h>
#define __PRINT__int main()
{
// 如果已经定义了__PRINT__,则#ifdef和#endif之间的代码参与编译
#ifdef __PRINT__printf("hehe\n");
#endifreturn 0;
}

常见的条件编译指令:

// 1. 
#if 常量表达式// ....
#endif
// 常量表达式为真时,被包括的内容参与编译;为假则反之
// 常量表达式由预处理器求值// 如:
#define __DEBUG__ 1
#if __DEBUGprintf("hehe");
#endif// 2. 多个分支的条件编译
#if 常量表达式// ...
#elif 常量表达式// ...
#else// ...
#endif
// 可以看到条件编译语句和条件分支语句很像// 3. 判断是否被定义
#if defined(symbol) // ...
#endif
// 写法1
#ifdef symbol // ...
#endif
// 写法2
// 这两种写法等价,意思是如果symbol已经被定义,就编译它们包含的内容#if !defined(symbol)// ...
#endif
// 写法1
#ifndef symbol// ...
#endif
// 写法2
// 与上面相反,如果symbol已经被定义,它们包含的内容就不编译// 4. 嵌套指令
#if defined(HELLO)#ifdef HEHEprintf("HEHE\n");#endif#ifdef HAHAprintf("HAHA\n");#elif HEIHEIprintf("HEIHEI\n");#elseprintf("HELLO\n");#endif
#elif WORLDprintf("WORLD\n");
#endif
// 嵌套指令和嵌套使用条件语句一样	

6. 文件包含

我们已经知道,#include指令可以使另一个文件也被编译进程序。就像直接将它的内容替换到这个地方一样;

这种替换非常简单:
预处理器会先删除这条指令,将用被包含文件的内容来替换;
如果这个文件被包含了10次,那么它的内容就被包含了10次;

因为这个性质在包含头文件时就会产生问题;
如果一个头文件在多个文件中都被包含,那么这个头文件的内容就被重复包含了多次,这使得程序代码变得冗余臃肿;

比如:

此时有4个源文件,common.c, test1.c, test2.c, test.c
它们都有对应的头文件,common.h, test1.h, test2.h, test.h
test1.h 包含了common.h, test2.h 也包含了common.h, test.h 包含了test1.h和test2.h

在这里插入图片描述
可以看到test.h中包含了两次的common.h的内容;

为了解决这种问题就可以使用条件编译语句:

// test1.h
#ifndef COMMON
#define COMMON
#include "common.h"
#endif// test2.h
#ifndef COMMON
#define COMMON
#include "common.h"
#endif// test.h
#ifndef TEST1
#define TEST1
#include "test1.h"
#endif
#ifndef TEST2
#define TEST2
#include "test1.h"
#endif

上面的条件编译语句只有在目标头文件没有被编译到文件中时才会参与编译,在编译之后就设置一个标志,使得之后不会再重复包含这个文件;

或者是使用

#pragma once // 文件内容只会被包含一次

在上面的代码中,包含头文件时,使用的是""并不是之前使用过的<>;这两者有什么区别呢?

  • 本地文件包含:#include "name"
    查找策略:先在源文件所在目录下查找,如果找不到,编译器就会在标准位置(库函数头文件所在位置)查找;此时再找不到就编译错误;

linux系统中的标准位置:/usr/include
VS环境下的标准位置:C:\Program Files (x86)\Windows Kits\10\Include 这个地址以自己的机器为准

  • 库文件包含:#include <name>
    查找头文件时,直接到标准位置查找,找不到就编译错误;

可能有的同学会有疑问,那么包含库函数头文件时,是否可以使用#include "name"来包含呢?
可以,但是不推荐,因为使用这种方法,会先去当前目录查找,这肯定是找不到的,浪费了系统资源,降低了查找效率;


总结

这是c语言学习的最后一个部分了,此章节介绍了一个c语言程序从源文件变成可执行文件的过程,还详解了c语言中使用的预处理指令;以及一些宏的使用方法;
希望这系列文章对大家的c语言学习有帮助;

相关文章:

c语言修炼秘籍 - - 禁(进)忌(阶)秘(技)术(巧)【第七式】程序的编译

c语言修炼秘籍 - - 禁(进)忌(阶)秘(技)术(巧)【第七式】程序的编译 【心法】 【第零章】c语言概述 【第一章】分支与循环语句 【第二章】函数 【第三章】数组 【第四章】操作符 【第五章】指针 【第六章】结构体 【第七章】const与c语言中一些错误代码 【禁忌秘术】 【第一式】…...

[创业之路-377]:企业法务 - 有限责任公司与股份有限公司的优缺点对比

有限责任公司&#xff08;简称“有限公司”&#xff09;与股份有限公司&#xff08;简称“股份公司”&#xff09;是我国《公司法》规定的两种主要公司形式&#xff0c;二者在设立条件、治理结构、股东权利义务等方面存在显著差异。以下从核心特征、设立条件、治理结构、股东权…...

PowerBi中REMOVEFILTERS怎么使用?

在 Power BI 的 DAX 中&#xff0c;REMOVEFILTERS() 是一个非常重要的函数&#xff0c;常用于取消某个字段或表的筛选上下文&#xff08;Filter Context&#xff09;&#xff0c;从而让你的计算不受切片器&#xff08;Slicer&#xff09;、筛选器或视觉对象的限制。 ✅ 一、REM…...

stat判断路径

int stat(const char *pathname, struct stat *buf); pathname&#xff1a;用于指定一个需要查看属性的文件路径。 buf&#xff1a;struct stat 类型指针&#xff0c;用于指向一个 struct stat 结构体变量。调用 stat 函数的时候需要传入一个 struct stat 变量的指针&#xff0…...

智能指针之设计模式4

前面的文章介绍了使用工厂模式来封装智能指针对象的创建过程&#xff0c;下面介绍一下工厂类 enable_shared_from_this的实现方案。 4、模板方法模式 在前面的文章分析过&#xff0c;enable_shared_from_this<T>类是一个工厂基类&#xff0c;提供的工厂方法是shared_f…...

Linux信号的产生

Linux系列 文章目录 Linux系列一、信号的产生1.1 异常1.2 alarm()系统调用 二 、信号的默认行为 一、信号的产生 上篇文章我们已经介绍了信号的三种产生方式&#xff0c;这部分是对上篇文章的补充 1.1 异常 在编写程序时&#xff0c;我们的程序经常会出现如&#xff1a;除零…...

FPGA设计 时空变换

1、时空变换基本概念 1.1、时空概念简介 时钟速度决定完成任务需要的时间&#xff0c;规模的大小决定完成任务所需要的空间&#xff08;资源&#xff09;&#xff0c;因此速度和规模就是FPGA中时间和空间的体现。 如果要提高FPGA的时钟&#xff0c;每个clk内组合逻辑所能做的事…...

客户端本地搭建

connect函数 主要用于客户端套接字向服务器发起连接请求。 头文件 #include <sys/socket.h> #include <arpa/inet.h> 函数原型 int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);参数解释 sockfd&#xff1a;客户端文件描述符。 addr…...

广东食品销售初级考试主要考什么

广东省食品销售初级考试主要考察从业人员对食品安全法律法规、行业规范及基础操作技能的掌握程度&#xff0c;内容涵盖以下几个方面&#xff1a; 1. 食品安全法律法规 考试重点包括《食品安全法》《广东省食品安全条例》等核心法规&#xff0c;要求考生熟悉食品经营许可、从业…...

[盈达科技】GEO(生成式引擎优化)实战指南:从认知重构、技术落地到内容突围的三维战略

GEO&#xff08;生成式引擎优化&#xff09;实战指南&#xff1a;从认知重构、技术落地到内容突围的三维战略 引言&#xff1a;AI搜索重构规则&#xff0c;GEO成为企业新护城河 在生成式AI主导的搜索时代&#xff0c;传统SEO的“关键词游戏”已失效。Google数据显示&#xff0…...

Linux ACL访问控制权限解析:超越传统权限的精细化管理

Linux ACL权限管理示意图 标准输出&#xff1a;Linux文件权限结构&#xff08;支持ACL&#xff09; ├── 传统权限 │ ├── 所有者(user): rwx │ ├── 所属组(group): r-x │ └── 其他用户(other): r-- │ └── ACL扩展权限├── 用户条目│ ├── user…...

全面介绍AVFilter 的添加和使用

author: hjjdebug date: 2025年 04月 22日 星期二 13:48:19 CST description: 全面介绍AVFilter 的添加和使用 文章目录 1.两个重要的编码思想1. 写代码不再是我们调用别人&#xff0c;而是别人调用我们!2. 面向对象的编程方法. 2. AVFilter 开发流程2.1 编写AVFilter 文件2.1.…...

复刻低成本机械臂 SO-ARM100 3D 打印篇

视频讲解&#xff1a; 复刻低成本机械臂 SO-ARM100 3D 打印篇 清理了下许久不用的3D打印机&#xff0c;挤出机也裂了&#xff0c;更换了喷嘴和挤出机夹具&#xff0c;终于恢复了正常工作的状态&#xff0c;接下来还是要用起来&#xff0c;不然吃灰生锈了&#xff0c;于是乎想起…...

基于微信小程序的走失儿童帮助系统-项目分享

基于微信小程序的走失儿童帮助系统-项目分享 项目介绍项目摘要管理员功能图用户功能图系统功能图项目预览首页走失儿童个人中心走失儿童管理 最后 项目介绍 使用者&#xff1a;管理员、用户 开发技术&#xff1a;MySQLJavaSpringBootVue 项目摘要 本系统采用微信小程序进行开…...

C++23 中 static_assert 和 if constexpr 的窄化布尔转换

文章目录 背景与动机C23 的改进限制与例外总结 C23 引入了一项重要的语言特性变更&#xff0c;即在 static_assert 和 if constexpr 中允许窄化按语境转换为 bool。这一特性由 Andrzej Krzemieński 提出的 P1401R5 论文推动&#xff0c;旨在使编译器的行为与标准保持一致&a…...

服务网格在DevOps中的落地:如何让微服务更智能、更稳定?

服务网格在DevOps中的落地:如何让微服务更智能、更稳定? 近年来,DevOps在企业IT架构中变得至关重要,而微服务架构的广泛应用更是加速了这一趋势。然而,随着微服务数量不断增长,我们发现自己掉入了一个运维“泥潭”: 服务之间的流量调控变得复杂可观测性不足,出现问题时…...

el-table表格既出现横向滚动条,又出现纵向滚动条?

横向滚动条 自然出现&#xff1f; 当表格所有列的宽度总和超过表格容器宽度时&#xff0c;el-table会默认出现横向滚动条。 比如&#xff0c;给每个<el-table-column>设置固定宽度&#xff0c;且他们相加超过了<el-table>宽度 就会触发 强制出现&#xff1f; 设…...

STL常用算法——C++

1.概述 2.常用遍历算法 1.简介 2.for_each 方式一&#xff1a;传入普通函数&#xff08;printf1&#xff09; #include<stdio.h> using namespace std; #include<string> #include<vector> #include<functional> #include<algorithm> #include…...

基于国产 FPGA+ 龙芯2K1000处理器+翼辉国产操作系统继电保护装置测试装备解决方案

0 引言 近年来&#xff0c;我国自主可控芯片在国家政策和政 府的支持下发展迅速&#xff0c;并在电力、军工、机械、 通信、电子、医疗等领域掀起了国产化替代之 风&#xff0c;但在芯片自主可控和国产化替代方面还有明 显的不足之处。 2022年我国集成电路进口量多 达 5 3…...

1.3 本书结构概览:从理论基础到实践案例的系统阐述

本书采用由浅入深、理论联系实践的结构设计&#xff0c;旨在为读者提供一个关于大模型与智能代理(Agent)技术的全面认知框架与实施路径。全书共分为十章&#xff0c;系统性地覆盖了从技术基础到企业落地的完整知识链条&#xff0c;现概述如下&#xff1a; 首先&#xff0c;第一…...

【FPGA开发】Vivado开发中的LUTRAM占用LUT资源吗

LUTRAM在Vivado资源报告中的解释 LUTRAM的本质与实现原理&#xff1a; LUTRAM不是一种独立的物理资源&#xff0c;而是LUT&#xff08;Look-Up Table&#xff09;的一种特殊使用方式。在Xilinx FPGA架构中&#xff0c;部分LUT单元可以被配置为小型分布式RAM&#xff08;也称为…...

【动手学强化学习】番外8-IPPO应用框架学习与复现

文章目录 一、待解决问题1.1 问题描述1.2 解决方法 二、方法详述2.1 必要说明&#xff08;1&#xff09;MAPPO 与 IPPO 算法的区别在于什么地方&#xff1f;&#xff08;2&#xff09;IPPO 算法应用框架主要参考来源 2.2 应用步骤2.2.1 搭建基础环境2.2.2 IPPO 算法实例复现&am…...

C++ 的 输入输出流(I/O Streams)

什么是输入输出流 C 的输入输出操作是通过 流&#xff08;stream&#xff09; 机制实现的。 流——就是数据的流动通道&#xff0c;比如&#xff1a; 输入流&#xff1a;从设备&#xff08;如键盘、文件&#xff09;读取数据 → 程序 输出流&#xff1a;程序将数据写入设备&…...

Java 安全:如何防止 SQL 注入与 XSS 攻击?

Java 安全&#xff1a;如何防止 SQL 注入与 XSS 攻击&#xff1f; 在 Java 开发领域&#xff0c;安全问题至关重要&#xff0c;而 SQL 注入和 XSS 攻击是两种常见的安全威胁。本文将深入探讨如何有效防止这两种攻击&#xff0c;通过详细代码实例为您呈现解决方案。 一、SQL 注…...

leetcode day36 01背包问题 494

494 目标和 给你一个非负整数数组 nums 和一个整数 target 。 向数组中的每个整数前添加 或 - &#xff0c;然后串联起所有整数&#xff0c;可以构造一个 表达式 &#xff1a; 例如&#xff0c;nums [2, 1] &#xff0c;可以在 2 之前添加 &#xff0c;在 1 之前添加 - &…...

31Calico网络插件的简单使用

环境准备&#xff1a; 1、删除Flannel 2、集群所有node节点拉取所需镜像&#xff08;具体版本可以依据calico.yaml文件中&#xff09;&#xff1a; docker pull calico/cni:v3.25.0 docker pull calico/node:v3.25.0 docker pull calico/kube-controllers:v3.25.0一、安装Cali…...

进阶篇 第 5 篇:现代预测方法 - Prophet 与机器学习特征工程

进阶篇 第 5 篇&#xff1a;现代预测方法 - Prophet 与机器学习特征工程 (图片来源: ThisIsEngineering RAEng on Pexels) 在前几篇中&#xff0c;我们深入研究了经典的时间序列统计模型&#xff0c;如 ETS 和强大的 SARIMA 家族。它们在理论上成熟且应用广泛&#xff0c;但有…...

实用生活c语言脚本

#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <time.h> // 清理临时文件目录 void clean_temp_directory() { const char* temp_dir "/tmp"; // 可自定义需要清理的目录 char command[1024]; …...

从零开始构建微博爬虫与数据分析系统

从零开始构建微博爬虫与数据分析系统 引言 社交媒体平台蕴含着海量的信息和数据&#xff0c;通过对这些数据的收集和分析&#xff0c;我们可以挖掘出有价值的见解。本文将详细介绍如何构建一个完整的微博爬虫和数据分析系统&#xff0c;从数据爬取、清洗、到多维度分析与可视…...

417. 太平洋大西洋水流问题

题目 有一个 m n 的矩形岛屿&#xff0c;与 太平洋 和 大西洋 相邻。 “太平洋” 处于大陆的左边界和上边界&#xff0c;而 “大西洋” 处于大陆的右边界和下边界。 这个岛被分割成一个由若干方形单元格组成的网格。给定一个 m x n 的整数矩阵 heights &#xff0c; heights…...

chili3d调试笔记8 打印零件属性

无效&#xff0c; 返回的是节点不是坐标啥的&#xff0c; 找他的属性 把document和selectednote&#xff08;空集&#xff09;传给handleshowproperty方法 怎么获得selectnotes和selectnotes的property值 有selectnotes运行这段就行了 明天再搞...

uniapp Vue2升级到Vue3,并发布到微信小程序的快捷方法

目录 前言&#xff1a;升级项目的两种方式步骤一、新建项目 【选择-默认模版】二、修改-pages.json三、补充-缺少的文件四、修改-Main.js按照 [官方文档-vue2升级vue3迁移指南](https://uniapp.dcloud.net.cn/tutorial/migration-to-vue3.html) 修改 五、升级-uni-ui扩展组件的…...

火山RTC 5 转推CDN 布局合成规则

实时音视频房间&#xff0c;转推CDN&#xff0c;文档&#xff1a; 转推直播--实时音视频-火山引擎 一、转推CDN 0、前提 * 在调用该接口前&#xff0c;你需要在[控制台](https://console.volcengine.com/rtc/workplaceRTC)开启转推直播功能。<br> * 调…...

Mujoco xml < sensor>

< sensor> jointposjointveljointactuatorfrcframequatgyroaccelerometerframeposframelinveltouchobjtype"site" objname"imu" 和site"imu"的区别python中与sensor有关的写法传感器名字索引第几个idid索引传感器名字传感器数量sensor中的…...

示例:spring xml+注解混合配置

以下是一个 Spring XML 注解的混合配置示例&#xff0c;结合了 XML 的基础设施配置&#xff08;如数据源、事务管理器&#xff09;和注解的便捷性&#xff08;如依赖注入、事务声明&#xff09;。所有业务层代码通过注解简化&#xff0c;但核心配置仍通过 XML 管理。 1. 项目结…...

同样的html标记,不同语言的文本,显示的字体和粗细会不一样吗

同样的 HTML 标记&#xff0c;在不同语言的文本下&#xff0c;显示出来的字体和粗细确实可能会不一样&#xff0c;原因如下&#xff1a; &#x1f30d; 不同语言默认字体不同 浏览器字体回退机制 CSS 里写的字体如果当前系统不支持&#xff0c;就会回退到下一个&#xff0c;比如…...

Linux进程6-alarm闹钟定时终止、raise发送信号、abort终止、pause挂起进程验证

目录 1.alarm函数 1.1关键点 1.2单个alarm函数定时 1.3两个alarm函数定时 2.raise函数 2.1核心行为‌ 2.2 raise与 kill 的区别 2.3程序&#xff1a; 3.abort函数 4.pause 函数 4.1 pause简单挂起 4.2父进程挂起&#xff0c;子进程发信号 1.alarm函数 函数原型&…...

SpringCloud组件—Eureka

一.背景 1.问题提出 我们在一个父项目下写了两个子项目&#xff0c;需要两个子项目之间相互调用。我们可以发送HTTP请求来获取我们想要的资源&#xff0c;具体实现的方法有很多&#xff0c;可以用HttpURLConnection、HttpClient、Okhttp、 RestTemplate等。 举个例子&#x…...

类加载器与jvm的内存

1. 类加载器与内存的关系 类加载器的字节码放在方法区&#xff08;元空间&#xff09;中&#xff0c;同时类加载器加载类后类的信息&#xff08;成员变量、成员方法及修饰符等&#xff09;存放在方法区中。类的信息所占内存的回收要同时满足两个条件&#xff1a;类的实例被回收…...

【C++】新手入门指南(下)

文章目录 前言 一、引用 1.引用的概念和定义 2.引用的特性 3.引用的使用 4.const引用 5.指针和引用的关系 二、内联函数 三、nullptr 总结 前言 这篇续上篇的内容新手入门指南&#xff08;上&#xff09;&#xff0c;继续带大家学习新知识。如果你感兴趣欢迎订购本专栏。 一、…...

el-table中el-input的autofocus无法自动聚焦的解决方案

需求 有一个表格展示了一些进度信息&#xff0c;进度信息可以修改&#xff0c;需要点击进度信息旁边的编辑按钮时&#xff0c;把进度变为输入框且自动聚焦&#xff0c;当鼠标失去焦点时自动请求更新接口。 注&#xff1a;本例以vue2 element UI为例 分析 这个需求看着挺简单…...

vimplus 如何修改语言支持的版本,以及如何跳转路径

vimplus修改语言版本 默认的vimplus支持c的版本是17 如何修改我们需要修改.ycm_extra_conf.py文件&#xff0c;这个文件管理了我们的插件配置 找到 把他修改为你想要的版本 增添路径 把你安装的gcc位置提供给他&#xff0c;默认的目前比较老 这里都是他提前为我们准备的路…...

麒麟V10安装MySQL8.4

1、下载安装包 wget https://cdn.mysql.com//Downloads/MySQL-8.4/mysql-8.4.5-1.el7.x86_64.rpm-bundle.tar2、解压 mkdir -p /opt/mysql tar -xvf mysql-8.4.5-1.el7.x86_64.rpm-bundle.tar -C /opt/mysql3、安装MySQL 3.1、卸载mariadb rpm -qa | grep mariadb rpm -e m…...

Varjo-XR3在UE5中,头显中间有一个方块一直显示

深色方块显示在屏幕中间的焦点区域中 屏幕中间的对焦区域中显示的黑色方块。 黑色方块是一个已知问题。它在 Varjo VRTemplate 中不可见&#xff0c;因为它使用具有推荐 VR 设置的前向渲染方法。 但是&#xff0c;如果你将延迟渲染方法与高级功能&#xff08;如 Lumen、Nani…...

FastText 模型文本分类实验:从零到一的实战探索

在自然语言处理&#xff08;NLP&#xff09;领域&#xff0c;文本分类是一个基础而重要的任务&#xff0c;广泛应用于情感分析、主题识别、垃圾邮件过滤等多个场景。最近&#xff0c;我参与了一次基于 FastText 模型的文本分类实验&#xff0c;从数据预处理到模型构建、训练和评…...

不同经营性道路运输从业资格证申请条件全解析

在道路运输领域&#xff0c;获取相应的从业资格证是合法从事经营性运输工作的关键前提。不同类型的运输业务&#xff0c;如旅客运输、货物运输以及危险货物运输&#xff0c;对从业者有着不同的条件要求。 经营性道路旅客运输驾驶员 驾驶证年限&#xff1a;需取得相应的机动车…...

WHAT - 静态资源缓存穿透

文章目录 1. 动态哈希命名的基本思路2. 具体实现2.1 Vite/Webpack 配置动态哈希2.2 HTML 文件中动态引用手动引用使用 index.html 模板动态插入 2.3 结合 Cache-Control 避免缓存穿透2.4 适用于多环境的动态策略 总结 在多环境部署中&#xff0c;静态资源缓存穿透是一个常见问题…...

11、Refs:直接操控元素——React 19 DOM操作秘籍

一、元素操控的魔法本质 "Refs是巫师与麻瓜世界的连接通道&#xff0c;让开发者能像操控魔杖般精准控制DOM元素&#xff01;"魔杖工坊的奥利凡德先生轻抚着魔杖&#xff0c;React/Vue的refs能量在杖尖跃动。 ——以神秘事务司的量子纠缠理论为基&#xff0c;揭示DOM…...

crontab 定时备份 mysql 数据库

1、使用 mysqldump 命令备份数据 1.1 备份全部数据库的数据和结构 mysqldump -uroot -p123456 -A > /data/backup/db.sql1.2 备份全部数据库的结构&#xff08;加 -d 参数&#xff09; mysqldump -uroot -p123456 -A -d > /data/backup/db.sql1.3 备份全部数据库的数据…...

数据库对象与权限管理-视图与索引管理

一、视图&#xff08;View&#xff09;管理 1. 视图的定义与本质 视图&#xff08;View&#xff09;是Oracle数据库中的逻辑表&#xff0c;它不直接存储数据&#xff0c;而是通过预定义的SQL查询动态生成结果集。视图的本质可以理解为&#xff1a; 虚拟表&#xff1a;用户可…...