C语言详解(预编译)
Hi~!这里是奋斗的小羊,很荣幸您能阅读我的文章,诚请评论指点,欢迎欢迎 ~~
💥💥个人主页:奋斗的小羊
💥💥所属专栏:C语言
🚀本系列文章为个人学习笔记,在这里撰写成文一为巩固知识,二为展示我的学习过程及理解。文笔、排版拙劣,望见谅。
目录
- 前言
- 1、预定义符号
- 2、#define定义常量和标识符
- 3、#define定义宏
- 4、带有副作用的宏参数
- 5、宏替换的规则
- 6、宏和函数的对比
- 7、# 和
- 7.1 #运算符
- 7.2 ##运算符
- 8、命名的约定
- 9、#undef
- 10、命令行定义
- 11、条件编译
- 12、头文件的包含
- 12.1 头文件被包含的方式
- 12.1.1 本地文件包含
- 12.1.2 库文件包含
- 12.2 嵌套文件的包含
- 总结
前言
本篇文章将详细介绍编译过程中预编译的具体细节
在C语言的学习中部分人可能会忽视这一部分的学习,因为像VS这样相对强大的集成开发环境,我们在写好代码后只需要开始执行即可,所以部分人认为这一部分不值得我们花费时间去学习
其实不然,学习C语言预编译过程可以帮助我们更深入地了解C语言的编译过程和语法特性,提高代码编写的效率和质量,以及拓展编程技能
1、预定义符号
C语言设置了一些预定义符号,可以直接使用,预定义符号也是在预编译阶段处理的
__FILE__
:正在编译的源文件的文件名__LINE__
:文件当前的行号__DATE__
:文件被编译的日期__TIME__
:文件被编译的时间__STDC__
:如果编译器遵循 ANSI C,其值为1,否则未定义
例如:
2、#define定义常量和标识符
#define
定义的常量和标识符在预编译阶段完成替换
基本语法:
#define name stuff
特别的,为了区分普通常量这个name
我们一般用大写形式
比如:
#define MAX 10000
#define REG register
#define
后面的代码理论上讲只能写一行,但是如果后面的代码过长,我们可以使用'\'
来实现换行,相当于转义转义字符'\'
转义了转义字符'\n'
#define DEBUG_PRINT printf("file:%s\tline:%d\t\date:%s\ttime:%s\n,\__FILE__,__LINE__,\__DATE__,__TIME__)
值得注意的是,行末最好不要加;
,在某些场景下是没什么问题,但是在大多数情况下是有语法错误的,所以我们要养成良好的编程习惯,行末不加;
3、#define定义宏
#define
机制包括了一个规定,允许把参数替换到文本中,这种实现通常称为宏(macro)或定义宏(define macro)
基本语法:
#define name(parament_list) stuff
其中parament-list
(参数列表)是一个由逗号隔开的符号表,它们可能出现在stuff
中
注意: 参数列表的左括号必须与
name
紧邻,如果两者之间有任何空白存在,参数列表就会被解释为stuff
的一部分
举例:输入一个数,输出它的平方数
#include <stdio.h>
#define SQUARE(x) x*xint main()
{int n = 0;scanf("%d", &n);int ret = SQUARE(n);printf("%d\n", ret);return 0;
}
上面的代码看似没有什么问题,但当我们想计算n+1
的平方数时,就会出现问题:
#include <stdio.h>
#define SQUARE(x) x*xint main()
{int n = 0;scanf("%d", &n);int ret = SQUARE(n + 1);printf("%d\n", ret);return 0;
}
这是为什么呢?
原因就是带参数的宏在替换的时候括号内的表达式是不做任何计算的
也就是说,上面替换后的形式是:5 + 1 * 5 + 1
,为了解决这个问题,我们可以在定义宏的时候给x
加上括号:
#define SQUARE(x) (x)*(x)
这样替换后的结果就变成了:(5 + 1)*(5 + 1)
,但是这样给单独的参数加括号的形式在某些场景下还是存在问题,比如:
#include <stdio.h>
#define SQUARE(x) (x)+(x)int main()
{int n = 0;scanf("%d", &n);int ret = 5 * SQUARE(n + 1);printf("%d\n", ret);return 0;
}
那为了解决这个问题,我们可以(x)+(x)整体加上括号:((x) + (x))
#include <stdio.h>
#define SQUARE(x) ((x)+(x))int main()
{int n = 0;scanf("%d", &n);int ret = 5 * SQUARE(n + 1);printf("%d\n", ret);return 0;
}
所以,在写宏的时候一定不要吝啬括号
4、带有副作用的宏参数
当宏参数在宏的定义中出现超过一次的时候,如果参数带有副作用,那么你在使用这个宏的时候就可能出现危险,导致不可预测的后果,副作用就是表达式求值的时候出现的永久性效果
例如:
- x + 1; //不带副作用
- x++; //带有副作用
上面两个表达式的值是相同的,但是第一个表达式x
的本身没有发生改变,而第二个表达式x
本身发现了改变,这就是副作用
例如:使用宏实现求两个数的较大值
#include <stdio.h>
#define MAX(x, y) ((x)>(y)?(x):(y))int main()
{int a = 10;int b = 20;int ret = MAX(a, b);printf("%d\n", ret);return 0;
}
上面代码中宏参数在宏定义中出现了两次,我们使用MAX(a, b);
时没什么问题,但当我们使用MAX(a++, b++);
时问题就会出现:
#include <stdio.h>
#define MAX(x, y) ((x)>(y)?(x):(y))int main()
{int a = 10;int b = 20;int ret = MAX(a++, b++);printf("%d\n", ret);printf("a = %d, b = %d\n", a, b);return 0;
}
可以发现a和b的值会发生改变,就是表达式求值的时候出现了永久性效果。
与函数对比:
#include <stdio.h>int MAX(int x, int y)
{printf("a = %d, b = %d\n", x, y);return (x > y ? x : y);
}int main()
{int a = 10;int b = 20;int ret = MAX(a++, b++);printf("%d\n", ret);return 0;
}
从上面的代码中可以看出来,带参数的宏替换和函数传参是非常相似的,但是它们的传参是有本质区别的。
带参数的宏替换是直接将参数做整体替换,替换过后的表达式是:((a++)>(b++)?(a++):(b++))
;而函数参过后的表达式是:(a > b ? a : b)
。
5、宏替换的规则
在程序中扩展#define
定义符号和宏时,需要涉及几个步骤。
- 在调用宏时,首先对参数进行检查,看看是否包含任何由
#define
定义的符号,如果有,它们首先被替换- 替换文本随后被插入到程序中原来文本的位置,对于宏,参数名被他们的值所替换
- 最后,再次对结果文件进行扫描,看看是否包含任何由
#define
定义的符号,如果有,重复上述步骤
例如:
#include <stdio.h>
#define M 10
#define N M + 2
#define MAX(x, y) ((x)>(y)?(x):(y))int main()
{int ret = MAX(M, N);return 0;
}
MAX(M, N)
首先被替换成:((10)>(M + 2)?(10):(M + 2))
然后((10)>(M + 2)?(10):(M + 2))
再被替换成:((10)>(10 + 2)?(10):(10 + 2))
注意:
- 宏参数和
#define
定义中可以出现其他#define
定义的符号,但宏不能实现递归
比如:#define N M + 2
这个是可以的,但#define N N + 2
是不行的。
- 当预处理器搜索
#define
定义的符号时,字符串常量的内容并不被搜索
比如:
#include <stdio.h>
#define M 10
#define N M + 2
#define MAX(x, y) ((x)>(y)?(x):(y))int main()
{printf("MAX(M, N)");return 0;
}
可以看到宏MAX(M, N)
并没有展开。
6、宏和函数的对比
宏通常被应用于执行简单的运算。
比如在两个数中找较大数,用宏实现更有优势:
#define MAX(x, y) ((x)>(y)?(x):(y))
那为什么不用函数呢?原因有二:
- 用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作需要的时间更多,函数调用还需要一些入栈出栈的过程,所以宏比函数在程序的规模和速度方面更胜一筹。
- 更为重要的是函数的参数必须声明为特定的类型,所以函数只能在类型合适的表达式上使用。但宏可以使用于整型、长整型、浮点型等可以用于
>
来比较的类型,宏参数是无关类型的。
但是和函数相比宏还是有劣势的:
- 每次使用宏的时候,一份宏定义的代码将插入到程序中,除非宏比较短,否则可能大幅度增加程序的长度
- 宏是不能调试的
- 宏由于无关类型,也就不够严禁,所以宏定义是不够安全的
- 宏可能会带来运算符优先级的问题,导致程序容易出错
宏有时候能做到函数做不到的事,比如:宏的参数可以出现类型,但是函数不行
#include <stdio.h>
#define MALLOC(n, type) (type*)malloc(n * sizeof(type))int main()
{//int* p = (int*)malloc(10 * sizeof(int));int* p = MALLOC(10, int);//int *p = (int*)malloc(10 * sizeof(int));return 0;
}
宏和函数的对比:
属性 | #define 定义宏 | 函数 |
---|---|---|
代码长度 | 每次使用时,宏代码都会被插入到程序中,除了非常小的宏之外,程序的长度会大幅度增长 | 函数代码只出现于一个地方,每次使用这个函数时,都调用那个地方的用一份代码 |
执行速度 | 更快 | 存在函数的调用和返回的额外开销,所以相对慢一些 |
操作符优先级 | 宏参数的求值是在所有周围表达式的上下文环境里,除非加上括号,否则邻近操作符的优先级可能会产生不可预料的结果,所以建议宏在书写的时候多写括号 | 函数参数只在函数调用的时候求值一次,它的结果值传递给函数,表达式的求值结果更容易预测 |
带有副作用的参数 | 参数可能被替换到宏体中的多个位置,如果宏的参数被多次计算,带有副作用的参数求值可能会产生不可预测的结果 | 函数参数只在传参的时候求值一次,结果更容易控制 |
参数类型 | 宏的参数与类型无关,只要对参数的操作是合法的,它就可以使用任何参数类型 | 函数的参数是与类型有关的,如果参数的类型不同,就需要不同的函数,即使他们执行的任务是不同的 |
调试 | 宏是不方便调试的 | 函数是可以逐语句调试的 |
递归 | 宏是不能递归的 | 函数是可以递归的 |
7、# 和
7.1 #运算符
#
运算符将宏的一个参数转换为字符串字面量,它仅允许出现在带参数的宏的替换列表中
#
运算符所执行的操作可以理解为“字符串化”
比如:当我们有一个变量int a = 10;
的时候,我们想打印出:the value of a is 10.
下面是常规写法:
#include <stdio.h>int main()
{int a = 10;printf("the value of a is %d\n", a);return 0;
}
如果我们想把打印的这条代码通过宏替换来实现,该怎么做呢?
#include <stdio.h>
#define PRINT(format, n) printf("the value of n is "format"\n", n)int main()
{int a = 10;PRINT("%d", a);//printf("the value of n is ""%d""\n", a);return 0;
}
如果写成上面这种代码很明显并没有解决问题,因为如果我们将n
写成%d
时并不能打印出a
,而只能打印出a的值,那为了能打印出a
本身的字面量,我们就可以使用#
操作符
如下:
#include <stdio.h>
#define PRINT(format, n) printf("the value of "#n" is "format"\n", n)int main()
{int a = 10;PRINT("%d", a);//printf("the value of "a" is ""%d""\n", a);double b = 3.14;PRINT("%lf", b);//printf("the value of "b" is ""%lf""\n", b);return 0;
}
所以我们说:#
运算符所执行的操作可以理解为“字符串化”,上面的代码中是将a和b字符串化了。
当n = a的时候,#n 就相当于“a”
7.2 ##运算符
##
可以把位于它两边的符号合成一个符号,它允许宏定义从分离的文本片段创建标识符。##
被称为记号粘合
这样的连接必须产生一个合法的标识符,否则其结果就是未定义的。
比如现在有这么一个问题:当我们写一个函数来求两个数的较大值的时候,不同的类型我们就需要写不同的函数,这样写太繁琐了,我们可以使用宏来简化这件事:
#include <stdio.h>
#define GENERIC(type) \
type type##_max(type x, type y)\
{\return ((x) > (y) ? (x) : (y));\
}GENERIC(int)
//int int_max(int x, int y)
//{
// return ((x) > (y) ? (x) : (y));
//}GENERIC(double)
//double double_max(double x, double y)
//{
// return ((x) > (y) ? (x) : (y));
//}int main()
{printf("%d\n", int_max(10, 20));printf("%lf\n", double_max(3.14, 6.28));return 0;
}
上面的代码中我们利用宏替换来实现创建不同类型的函数,type##_max
中的##
操作符将type
和_max
连接成了一个新的标识符
8、命名的约定
一般来讲函数和宏的使用语法很相似,所以语言本身没法帮我们区分二者,那我们平时的习惯是:
- 把宏名全部大写
- 函数名不要全部大写或不大写
9、#undef
#undef
这条指令用于移除一个宏定义
#include <stdio.h>
#define M 10int main()
{printf("%d\n", M);
#undef Mprintf("%d\n", M);return 0;
}
如果现存的一个宏名需要被重新定义,那么它的旧名字首先需要被移除
10、命令行定义
许多C编译器提供了一种能力,允许在命令行中定义符号,用于启动编译过程。
例如:当我们根据同一个源文件想要编译出一个程序的不同版本的时候,这个特性有点用处。(假定某个程序中声明了一个一定长度的数组,如果机器内存有限,我们需要一个很小的数组,但是另外一个机器内存大些,我们需要一个较大的数组)
编译指令:
//linux 环境演示
gcc -D ARRAY_SIZE=10 programe.c
11、条件编译
满足条件,就参与编译;不满足条件,就不参与编译
在编译一个程序的时候我们如果要将一条语句(一组语句)编译或者放弃是很方便的,因为我们有条件编译指令:
1.
#if 常量表达式 //常量表达式由预处理器求值
//...
#endif如:
#define _DEBUG_ 1
int main()
{
#if _DEBUG_printf("a");
#endifreturn 0;
}
2.多个分支的条件编译
#if 常量表达式
//...
#elif 常量表达式
//...
#else
//...
#endif如:
#define M 1
int main()
{
#if M == 1printf("a");
#elif M == 2printf("b"):
#elseprintf("C");
#endifreturn 0;
}
3.判断是否被定义
//如果定义了
#if defined(symbol)或
#ifdef symbol如:
#define M 2
int main()
{
#ifdef Mprintf("a");
#endifreturn 0;
}//如果没定义
#if !defined(symbol)或
#ifndef symbol
4.嵌套指令
#ifdef OS_UNIX#ifdef OPTION1unix_version_option1();#endif#ifdef OPTION2unix_version_option2();#endif
#elif defined(OS_MSDOS)#ifdef OPTION2msdos_version_option2():#endif
#endif
条件编译通常用于跨平台性代码的编译
12、头文件的包含
12.1 头文件被包含的方式
12.1.1 本地文件包含
一般指自己创建的头文件
#include "filename.h"
查找策略:
先在源文件所在目录下查找,如果该头文件未找到,编译器就像查找库函数头文件一样在标准位置查找头文件,如果找不到就提示编译错误。
12.1.2 库文件包含
一般指标准库中头文件的包含
#include <filename.h>
查找策略:
直接去标准路径下去查找,如果找不到就提示编译错误。
那这样是不是就说明,对库文件也可以使用" "
的形式包含呢?
答案是可以的。但是这样查找的效率比较低,也不容易区分是库文件还是本地文件
12.2 嵌套文件的包含
我们已经知道,#include
指令可以使另外一个文件被编译,就像它实际出现于#include
指令的地方一样。
这种替换的方式很简单:预编译器先删除这条指令,并用包含文件的内容替换
一个头文件被包含几次,就会被实际编译几次,如果重复包含,对编译的压力就比较大
#include "test.h"
#include "test.h"
#include "test.h"
#include "test.h"
#include "test.h"int main()
{return 0;
}
如果像上面这样写,test.h
文件的内容就会被拷贝5份
如果test.h
文件比较大,这样预处理后代码量会剧增,如果工程比较大,有公共使用的文件,被大家都能用,又不做任何的处理,那么后果会不堪设想。
为了解决头文件被重复引入的问题,就要用到条件编译
我们在每个头文件的开头这样写:
#ifndef __FILENAME_H__
#define __FILENAME_H__//...#endif
或者
#pragma once
就可以避免头文件的重复引入。
总结
- 增强对C语言编译过程的整体理解:预编译是C语言编译过程的第一阶段,在预编译阶段可以对源代码进行预处理,如宏定义、头文件包含等。通过学习预编译过程,可以更全面地理解C语言代码的编译过程。
- 优化代码结构:预编译指令能够简化代码结构、提高代码的重用性和可维护性。学习预编译过程可以帮助程序员更好地利用预编译指令优化代码结构,提高代码的质量。
- 理解条件编译和跨平台编译:条件编译是预编译指令中的重要功能,可以根据不同条件编译不同的代码。通过学习预编译过程,可以了解如何使用条件编译来实现跨平台编译,提高代码的可移植性。
相关文章:

C语言详解(预编译)
Hi~!这里是奋斗的小羊,很荣幸您能阅读我的文章,诚请评论指点,欢迎欢迎 ~~ 💥💥个人主页:奋斗的小羊 💥💥所属专栏:C语言 🚀本系列文章为个人学习…...
解决el-table表格拖拽后,只改变了数据,表头没变的问题
先看看是不是你想要解决的问题 拖拽后表头不变的bug修复 这个问题一般是使用v-for对column的数据进行循环的时候,key值绑定的是个index导致的,请看我上篇文章:eleplus对el-table表格进行拖拽(使用sortablejs进行列拖拽和行拖拽):-…...

简单塔防小游戏
学习目标:熟悉塔防游戏核心战斗 游戏画面 项目结构目录 核心代码: if ( Input.GetMouseButtonDown(0)){if (EventSystem.current.IsPointerOverGameObject()false){//开发炮台的建造Ray ray Camera.main.ScreenPointToRay(Input.mousePosition);Rayca…...

高考之后第一张大流量卡应该怎么选?
高考之后第一张大流量卡应该怎么选? 高考结束后,选择一张合适的大流量卡对于准大学生来说非常重要,因为假期期间流量的使用可能会暴增。需要综合考虑多个因素,以确保选到最适合自己需求、性价比较高且稳定的套餐。以下是一些建议…...

如何从微软官方下载Edge浏览器的完整离线安装包
文章目录 📖 介绍 📖🏡 演示环境 🏡📒 正文内容 📒🚀 官方直链下载🚬 手动选择下载🎈 获取方式 🎈⚓️ 相关链接 ⚓️📖 介绍 📖 在网上搜索Microsoft Edge浏览器的离线安装包时,很多用户都会发现大部分都是在线安装包,无法满足他们在无网络环境下进…...
git 常用的命令
git 常用的命令 一、基础命令1.1 初始化1.2 添加文件1.3 查看缓存区中的文件1.4 查看上次提交到缓存区中的文件1.5 文件从缓存区取出1.6 提交文件1.6 查看提交中包含的文件1.7 查看commit记录 二、回退命令2.1 git reset2.2 将文件从暂存区取出2.3 将文件从仓库取出2.3.1 保留工…...

【StableDiffusion】Embedding 底层原理,Prompt Embedding,嵌入向量
Embedding 是什么? Embedding 是将自然语言词汇,映射为 固定长度 的词向量 的技术 说到这里,需要介绍一下 One-Hot 编码 是什么。 One-Hot 编码 使用了众多 5000 长度的1维矩阵,每个矩阵代表一个词语。 这有坏处,…...

计算机网络(2) 网络层:IP服务模型
一.Internet Protocol在TCP/IP四层模型中的作用 第三层网络层负责数据包从哪里来到哪里去的问题。传输层的数据段提交给网络层后,网络层负责添加IP段,包含数据包源地址与目的地址。将添加IP段的数据包交由数据链路层添加链路头形成最终在各节点传输中所需…...
新人学习笔记之(初识C语言)
一、C语言的简介 1.C语言:1978年1月1日美国贝尔实验室推出的一门非常哇塞计算机语言 2.计算机语言:人与计算机之间进行信息交流沟通的一种特殊语言 二、C语言能做什么 1.操作系统 2.驱动开发 3.引擎开发 4.游戏开发 5.嵌入式开发 三、学习C语言的好处 …...

Unity EasyRoads3D插件使用
一、插件介绍 描述 Unity 中的道路基础设施和参数化建模 在 Unity 中使用内置的可自定义动态交叉预制件和基于您自己导入的模型的自定义交叉预制件,直接创建独特的道路网络。 添加额外辅助对象,让你的场景栩栩如生:桥梁、安全护栏、栅栏、墙壁…...

Redis 地理散列GeoHash
用数据库来算附近的人 地图元素的位置数据使用二维的经纬度表示,经度范围(-180,180],纬度范围 (-90,90],纬度正负以赤道为界,北正南负,经度正负已本初子午线(英国格林尼…...
vim 显示行号
在 Vim 中,你可以通过几种不同的方式来显示行号。以下是两种常用的方法: 临时显示行号: 当你打开 Vim 并想要临时查看文件的行号时,你可以使用 :set number 命令。这个命令会在当前 Vim 会话中显示行号。如果你想要关闭行号显示&a…...
C++:调整数组顺序使奇数位于偶数前面【面试】
在C,如果要调整数组顺序使所有奇数位于偶数前面,这里提供一种简单且常用的方法:双指针技术。这种方法不需要额外的空间,并且时间复杂度为O(n)。 以下是使用双指针技术实现的示例代码: #include <iostream> #in…...

WPF/C#:程序关闭的三种模式
ShutdownMode枚举类型介绍 ShutdownMode是一个枚举类型,它定义了WPF应用程序的关闭方式。这个枚举类型有三个成员: OnLastWindowClose:当最后一个窗口关闭或者调用System.Windows.Application.Shutdown方法时,应用程序会关闭。O…...

登录/注册- 滑动拼图验证码(IOS/Swift)
本章介绍如何使用ios开发出滑动拼图验证码,分别OC代码和swift代码调用 1.导入项目model文件OC代码(下载完整Demo) 2.放入你需要显示的图片 一:OC调用 #import "ViewController.h" #import "CodeView.h"…...

MyBatis进行模糊查询时SQL语句拼接引起的异常问题
项目场景: CRM项目,本文遇到的问题是在实现根据页面表单中输入条件,在数据库中分页模糊查询数据,并在页面分页显示的功能时,出现的“诡异”bug。 开发环境如下: 操作系统:Windows11 Java&#…...

网站调用Edge浏览器API:https://api-edge.cognitive.microsofttranslator.com/translate
Edge浏览器有自带的翻译功能,在运行pc项目可能会遇到疯狂调用Edge的API https://api-edge.cognitive.microsofttranslator.com/translate 这个URL(https://api-edge.cognitive.microsofttranslator.com/translate)指向的是微软服务中的API接…...

css实现优惠券样式
实现优惠券效果: 实现思路: 需要三个盒子元素,使用 css 剪裁,利用 ellipse 属性,将两个盒子分别裁剪成两个半圆,位置固定在另一个盒子元素左右两边适当位置上。为另一个盒子设置想要的样式,圆角…...

函数递归(C语言)(详细过程!)
函数递归 一. 递归是什么1.1 递归的思想1.2 递归的限制条件 二. 递归举例2.1 求n的阶乘2.2 按顺序打印一个整数的每一位 三. 递归与迭代3.1 求第n个斐波那契数 一. 递归是什么 递归是学习C语言很重要的一个知识,递归就是函数自己调用自己,是一种解决问题…...
uniapp 接口请求封装
根目录下创建 config目录 api.js request.js // request.js // 封装一个通用的网络请求函数 适当调整 function httpRequest(options) {const userToken uni.getStorageSync(access_token).token;return new Promise((resolve, reject) > {uni.request({url: ${options.ur…...
Vim 调用外部命令学习笔记
Vim 外部命令集成完全指南 文章目录 Vim 外部命令集成完全指南核心概念理解命令语法解析语法对比 常用外部命令详解文本排序与去重文本筛选与搜索高级 grep 搜索技巧文本替换与编辑字符处理高级文本处理编程语言处理其他实用命令 范围操作示例指定行范围处理复合命令示例 实用技…...

XCTF-web-easyupload
试了试php,php7,pht,phtml等,都没有用 尝试.user.ini 抓包修改将.user.ini修改为jpg图片 在上传一个123.jpg 用蚁剑连接,得到flag...
Linux链表操作全解析
Linux C语言链表深度解析与实战技巧 一、链表基础概念与内核链表优势1.1 为什么使用链表?1.2 Linux 内核链表与用户态链表的区别 二、内核链表结构与宏解析常用宏/函数 三、内核链表的优点四、用户态链表示例五、双向循环链表在内核中的实现优势5.1 插入效率5.2 安全…...

shell脚本--常见案例
1、自动备份文件或目录 2、批量重命名文件 3、查找并删除指定名称的文件: 4、批量删除文件 5、查找并替换文件内容 6、批量创建文件 7、创建文件夹并移动文件 8、在文件夹中查找文件...
Qt Widget类解析与代码注释
#include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this); }Widget::~Widget() {delete ui; }//解释这串代码,写上注释 当然可以!这段代码是 Qt …...

最新SpringBoot+SpringCloud+Nacos微服务框架分享
文章目录 前言一、服务规划二、架构核心1.cloud的pom2.gateway的异常handler3.gateway的filter4、admin的pom5、admin的登录核心 三、code-helper分享总结 前言 最近有个活蛮赶的,根据Excel列的需求预估的工时直接打骨折,不要问我为什么,主要…...

江苏艾立泰跨国资源接力:废料变黄金的绿色供应链革命
在华东塑料包装行业面临限塑令深度调整的背景下,江苏艾立泰以一场跨国资源接力的创新实践,重新定义了绿色供应链的边界。 跨国回收网络:废料变黄金的全球棋局 艾立泰在欧洲、东南亚建立再生塑料回收点,将海外废弃包装箱通过标准…...
基于数字孪生的水厂可视化平台建设:架构与实践
分享大纲: 1、数字孪生水厂可视化平台建设背景 2、数字孪生水厂可视化平台建设架构 3、数字孪生水厂可视化平台建设成效 近几年,数字孪生水厂的建设开展的如火如荼。作为提升水厂管理效率、优化资源的调度手段,基于数字孪生的水厂可视化平台的…...

相机从app启动流程
一、流程框架图 二、具体流程分析 1、得到cameralist和对应的静态信息 目录如下: 重点代码分析: 启动相机前,先要通过getCameraIdList获取camera的个数以及id,然后可以通过getCameraCharacteristics获取对应id camera的capabilities(静态信息)进行一些openCamera前的…...
聊一聊接口测试的意义有哪些?
目录 一、隔离性 & 早期测试 二、保障系统集成质量 三、验证业务逻辑的核心层 四、提升测试效率与覆盖度 五、系统稳定性的守护者 六、驱动团队协作与契约管理 七、性能与扩展性的前置评估 八、持续交付的核心支撑 接口测试的意义可以从四个维度展开,首…...