【C】预处理详解
在上一篇文章中,简单讲解了一个C程序是如何从一句句C代码变为一个个二进制指令,并最终变成可执行程序成功运行。在预处理、编译、汇编、链接四个步骤中,预处理阶段做的事情特别多,接下来我们就来讲解一下在预处理阶段处理的一些预处理指令。
这些预处理指令包括一些预定义符号,#define,#和##,#undef,条件编译,最后再讲解一下头文件包含的相关问题。
目录
1 预定义符号
2 #define
1) #define定义常量
2) #define定义宏
3) 带有副作用的宏参数
4) 宏替换的规则
5) 宏与函数的对比
3 #和##运算符
1) #运算符
2) ## 运算符
4 #undef
5 条件编译
6 头文件的包含
1) 两种包含方式的区别
2) 重复包含
1 预定义符号
在C语言中,设置了一些可以在预处理阶段处理的预定义符号,这些符号可以直接使用,包括:
//__为两个下划线
1 __FILE__ //进行编译的文件2 __LINE__ //当前代码所在行3 __DATE__ //文件被编译的日期4 __TIME__ //文件被编译的时间5 __STDC__ //当前编译器是否符合ANSI C(标准C),如果符合会变成1,不符合会变成0或者未定义
例子:
#include<stdio.h>int main()
{printf("%c\n", __FILE__);printf("%d\n", __LINE__);printf("%c\n", __DATE__);printf("%c\n", __TIME__);//printf("%d\n", __STDC__);return 0;
}
运行结果为: 由于VS编译器并不是完全按照ANSI C来设计的,所以__STDC__是未定义的,如果大家有别的编译器可以试一下。
2 #define
#define是C语言中经常使用的一个预处理指令,他的作用主要包括两个方面 -- 定义常量和定义宏。
1) #define定义常量
#define 可以把一个符号定义为一个常量,类似于枚举变量,其语法为:
#define name stuff
name是要定义的符号,stuff为该符号对应的常量值,#define与name之间有一个空格,name与stuff之间有一个空格,用#define定义常量的例子如下:
#include<stdio.h>
#define M 100int main()
{int a = M;printf("%d\n", a);return 0;
}
运行结果:
#define定义常量在预处理阶段就会直接把定义的符号替换为其值,比如上面那个代码在预处理阶段就是被处理成如下代码(当然头文件也会在预处理阶段被展开,但是太长,这里就省略了):
int main()
{int a = 100;printf("%d\n", a);return 0;
}
#define除了可以用符号定义常量之外,还可以用符号来定义一些语句,如:
#include<stdio.h>
#define CASE break;caseint main()
{int n = 0;scanf("%d", &n);switch(n){case 1:printf("hello world\n");CASE 2:printf("hello w\n");CASE 3:printf("hello y\n");CASE 4:printf("hello f\n");}return 0;
}
在之前的switch分支语句中,为了不发生错误,需在每一种case后面加上break语句,让程序跳出选择,而用#define定义了CASE之后,就可以直接写CASE就可以了,就不会忘记在case后面加break了,因为用#define定义了CASE之后,程序在预处理阶段就会变为:
int main()
{int n = 0;scanf("%d", &n);switch(n){case 1:printf("hello world\n");break;case 2:printf("hello w\n");break;case 3:printf("hello y\n");break;case 4:printf("hello f\n");}return 0;
}
运行结果为:
当然,#define既然可以用来定义一句代码,如:
#include<stdio.h>
#define PRINT printf("hello world")int main()
{PRINT;return 0;
}
在预处理阶段代码会变为:
int main()
{printf("hello world");return 0;
}
运行结果为:
经过以上代码,其实可以发现#define本身类似于一种替换机制,会将后面定义的值在代码中将前面定义的符号替换掉。
但使用#define时,要注意不要在后面加上分号,因为#define会将后面的内容直接替换,所以有时候会出现错误,如:
#include<stdio.h>
#define M 100;int main()
{printf("%d\n", M);return 0;
}
预处理阶段代码变为:
int main()
{printf("%d\n", 100;);return 0;
}
语法会出现错误,所以会直接报错。
2) #define定义宏
#define 不仅可以用来定义常量,还可以用来定义宏,定义宏的语法为:
#define name(parament-list) stuff
name为宏的名字(一般都命名为大写),parament-list为参数列表,类似于函数的参数列表,stuff为宏要实现的功能,要注意的是,name与后面那个括号必须紧密相连,不能有空格,要不该宏会被认为是定义常量,使用宏的一个例子为:
#include<stdio.h>
#define ADD(X, Y) X + Yint main()
{int a = 5;int b = 10;int ret = ADD(a, b);printf("%d\n", ret);return 0;
}
运行结果为:
但是这样写宏是具有问题的,例如以下这个例子:
#include<stdio.h>
#define MUL(X, Y) X * Yint main()
{int ret = MUL(5+2, 1+3);printf("%d\n", ret);return 0;
}
我们希望这个乘法宏算出的结果为28,但实际上结果确是10:
出现这个错误结果的原因就是#define定义宏也是一种替换机制,就是用后面的后面的表达式来直接替换前面的宏,参数本身并不会进行计算,所以上面那个乘法宏会被替换为以下代码:
int main()
{int ret = 5 + 2 * 1 + 3;printf("%d\n", ret);return 0;
}
所以运算符的优先级会影响运算的结果。要解决这个问题也很简单,只要给每个参数加上括号就可以了:
#include<stdio.h>
#define MUL(X, Y) (X) * (Y)int main()
{int ret = MUL(5+2, 1+3);printf("%d\n", ret);return 0;
}
运行结果为:
但是只给参数加上括号有时还会发生错误,如:
#include<stdio.h>
#define DOUBLE(X) (X) + (X)int main()
{int ret = 11 * DOUBLE(20);printf("%d\n", ret);return 0;
}
我们期望的正确结果为440,但实际上结果为240:
因为宏被替换后,代码变为了:
int main()
{int ret = 11 * (20) * (20);printf("%d\n", ret);return 0;
}
要解决这个问题就需要在整体再套上一个括号:
#include<stdio.h>
#define DOUBLE(X) ((X) + (X))int main()
{int ret = 11 * DOUBLE(20);printf("%d\n", ret);return 0;
}
所以以后在使用#define定义宏的时候,由于其本身的替换机制,我们需要在保证正确的前提下,尽量在能加括号多加括号,才能保证#define定义宏的正确性。
3) 带有副作用的宏参数
当宏参数在宏的定义中出现超过一次的时候,如果参数带有副作用(比如b = a++,不仅b = a + 1,且a也+1),那么在使用宏的时候就可能出现危险,导致不可预测的后果。副作用就是表达式求值的时候出现的永久性后果。
比如以下这个代码,就说明了带有副作用的宏参数出现的问题:
#include<stdio.h>
#define MAX(X, Y) ((X) > (Y) ? (X) : (Y))//下面这个代码结果是多少呢?
int main()
{int a = 5;int b = 6;int max = MAX(a++, b++);printf("%d\n", max);printf("%d\n", a);printf("%d\n", b);return 0;
}
运行结果:
首先,上述代码会在预处理阶段被替换为以下代码:
int main()
{int a = 5;int b = 6;int max = ((a++) > (b++) ? (a++) : (b++));printf("%d\n", max);printf("%d\n", a);printf("%d\n", b);return 0;
}
由于是后置++,所以先使用,后++,所以前面 > 比较结果为假,会执行后面的b++代码,同时a与b都自增了1,变为了6和7,然后后面的b++,也是后置++,会把b的值赋给max,所以max是7,b再自增1,变为了8,所以输出结果就是7,6,8。
所以在这里由于宏参数带有副作用,产生了意想不到的后果,本来我们期望得到的最大值为6,然后a,b各自增1,结果答案出乎我们意料,这就是带有副作用的宏参数可能产生错误的结果。所以,当参数带有副作用时,我们应该尽量用函数来解决。
4) 宏替换的规则
前面我们了解到 #define 定义常量和宏本质上就是一种替换机制,所以这里我们来了解一下宏替换的规则:
(1) 在调用宏时,首先对参数进行检查,看看是否有#define定义的符号,如果有,这些符号首先被替换(2) 替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值所替换。(3) 最后,再对结果文件进行扫描,看看它是否包含任何由 #define 定义的符号。如果有,就重复上述处理过程。如:对于以下代码
#include<stdio.h>
#define M 10
#define ADD(X, Y) ((X) + (Y))int main()
{int a = 20;int add = ADD(a, M);return 0;
}//第一步:首先替换参数中被#define定义的符号
int main()
{int a = 20;int add = ADD(a, 10);return 0;
}//第二步:再把宏替换掉
int main()
{int a = 20;int add = ((a) + (10));return 0;
}
使用宏的时候也有两个注意事项:
(1) 对于宏,其不能递归(2) 当预处理器搜索 #define 定义的符号时,字符串常量的内容不被搜索
对于第二点,如以下这个代码:
#include<stdio.h>
#define M 10int main()
{//"M = %d"字符串中的 M 并不会被替换为10,也就是不会被搜索printf("M = %d", M);return 0;
}
5) 宏与函数的对比
在形式上看起来,宏和函数看起来类似,使用方式都是名字加上后面传参数,所以这里对他们俩进行一个对比:
属性 | 宏 | 函数 |
---|---|---|
代码长度 | 宏在每次使用时都会直接替换,在原代码长度基础上,大大增加了代码长度 | 函数代码只出现在一个地方,只会产生一份代码,每次使用函数时,都会去调用同一份代码 |
执行速度 | 比函数调用更快 | 存在传参,开辟函数栈帧,拷贝返回值等额外开销,比起宏来相对较慢 |
操作符优先级 | 如果不加足够的括号,可能会因为操作符优先级产生错误的结果 | 函数只在函数调用时求值一次的结果传递给函数,并且会返回一个值,不会因为操作符优先级而发生错误 |
带有副作用的参数 | 如果带有副作用的宏参数在宏中被多处替换,就会被多次计算,产生不可预料的后果 | 函数参数只在传值的时候被求值一次,更容易控制 |
参数类型 | 宏参数与类型无关,只要对参数操作合法,任何类型参数都可以 | 函数参数是受参数类型限制的,所以不同类型的参数需要不同的函数 |
调试方面 | 不方便调试 | 可以逐语句调试 |
递归 | 不能递归 | 可以递归 |
3 #和##运算符
1) #运算符
# 运算符是将宏的一个参数转换为字符串字面量,仅允许出现在带参数的宏的替换列表中。
在举具体例子之前,我们先来看一个使用 printf 函数的例子:
#include<stdio.h>int main()
{printf("hello""world\n");return 0;
}
运行结果为: 当printf函数里面有多个字符串时,printf函数会合成一个字符串进行打印。
# 运算符功能类似于“字符串化”,使用#运算符的例子如下:
#include<stdio.h>
#define PRINT(v, f) printf("the value of "#v" is "f, v)int main()
{int a = 10;PRINT(a, "%d\n");float m = 2.2f;PRINT(m, "%f\n");return 0;
}
宏替换之后代码为:
#include<stdio.h>
#define PRINT(v, f) printf("the value of "#v" is "f, v)int main()
{int a = 10;printf("the value of ""a"" is ""%d\n", v);float m = 2.2f;printf("the value of ""m"" is ""%f\n", v)return 0;
}
所以运行结果为:
这里就是将宏参数 v 通过增加一个 # 运算符,在替换时会变成 "v"。
2) ## 运算符
## 运算符的作用是可以将两边的符号合成一个符号,允许宏定义从分离的文本片段创建标识符。因此,## 被称为记号粘合,粘合之后所生成的标识符必须是合法的。
举个例子:
//使用宏定义一个任意类型的求最大值函数
#include<stdio.h>
#define GEN_MAX(type)\
type type##_Max(type x, type y)\
{\return x > y ? x : y;\
}GEN_MAX(int);int main()
{int max = int_Max(3, 5);printf("%d\n", max);return 0;
}
运行结果:
代码中的 \ 为换行符,如果一行代码写不开,可以用这个符号换行,但注意:该符号后面只能跟换行。
那么为什么可以直接调用呢?因为 #define 定义的宏会在预处理阶段就被替换,而运行阶段调用函数时,函数就已经产生了,所以可以直接调用。
4 #undef
#undef 该预处理指令是用来取消 #define 定义的,如:
#define M 10int main()
{printf("%d\n", M);//把M重新定义
//先移除之前M的定义
#undef M
#define M 100printf("%d\n", M);return 0;
}
运行结果:
5 条件编译
如果要将一条语句(一组语句)编译或者放弃就可以使用条件编译指令。这些条件编译指令通过满足一定条件来控制某些语句的编译与否,条件编译指令包括:
(1)
#if 常量表达式(一定要是常量,因为变量在预处理时还未有值)//...
#endif(2) 多个分支的条件编译
#if 常量表达式//...
#elif 常量表达式//...
#else//...
#endif(3)判断是否定义过
//如果symbol被定义过,底下的语句就参与编译
#if defined(symbol)
#ifdef symbol//....//如果symbol没有被定义过,底下的语句就参与编译
#if !defined(symbol)
#ifndef symbol(4)条件编译指令支持嵌套定义
举个例子:
#include<stdio.h>
#define M 10int main()
{
#if defined(M)#if Mprintf("hello world\n");#elseprintf("hello w\n");#endif
#endif#if !defined(M)#if Mprintf("hello y\n");#elseprintf("hello f\n");#endif
#endifreturn 0;
}
运行结果:
使用条件编译指令时需要注意在每一个 #if 后面都必须加上 #endif ,包括 #if,#ifdef ,#if defined,#ifndef,#if !defined。
6 头文件的包含
1) 两种包含方式的区别
我们写C程序的时候都会在前面包含头文件来使用库函数,其实包含头文件有两种方式,一种是在 #include 后面加上尖括号(<>),另一种是在后面加上双引号(""),这两种包含头文件的方式是有区别的,区别就在于查找策略的不同:
(1) <> 包含头文件是在标准路径下查找,如果查找不到就提示编译错误(2) "" 包含头文件是先在源文件所在目录下查找,如果没有查找到,编译器再像查找库函数头文件一样在标准路径位置下查找头文件
所以其实包含库函数的头文件也可以用 "" 来包含,如:
#include "stdio.h"
这样包含也是可以的,不会报错,但是一般不会这样包含,因为这样编译器先默认会去源文件所在目录下去查找,降低了查找效率,所以包含库函数的头文件用 <> 来包含,自己写的函数的头文件只能用 "" 来包含。
2) 重复包含
由于预处理阶段会把 #include 包含的头文件全部展开,将代码放到预处理后的文件中(.i 为后缀的文件),如果一个头文件在一个文件中被多次包含,就会导致多出一些相同的代码,导致代码量大量增加,所以避免头文件的重复包含就显得特别重要。避免头文件包含的方式有两种:
(1) 使用条件编译指令(2) 使用 #pragma once 预处理指令
对于第一种方式例子如下:
//add.h
#include<stdio.h>#ifndef __ADD-H__
#define __ADD-H__int Add(int x, int y)
{return x + y;
}#endif
其实对于第二种方式,我们在新建一个头文件的时候,编译器会自动在第一行加上这句预处理指令:
编译器这样做就是为了防止头文件的重复包含问题。
到这里,C语言就全部结束了。从刚开始的"hello world"再到后来的顺序、分支、循环结构,再到后来的指针、结构体、内存管理,再到编译和链接,以及最后的预处理详解,一步步走过来相信大家也是收获满满。之后,我也会继续学习数据结构以及C++的知识,并不定期更新,请大家敬请期待哦。
相关文章:
【C】预处理详解
在上一篇文章中,简单讲解了一个C程序是如何从一句句C代码变为一个个二进制指令,并最终变成可执行程序成功运行。在预处理、编译、汇编、链接四个步骤中,预处理阶段做的事情特别多,接下来我们就来讲解一下在预处理阶段处理的一些预…...
CES Asia 2025:VR/AR/XR引领科技新潮流
在全球科技领域蓬勃发展的大背景下,CES Asia 2025(赛逸展)即将在京盛大开幕,VR/AR/XR技术作为前沿科技的代表,将在本次展会上大放异彩,展现出令人瞩目的发展趋势和巨大潜力,同时政策优势也将为其…...
Lua调用C#
目录 创建C#入口 Lua调用类 Lua调用枚举 Lua调用数组,列表,字典 Lua调用C#拓展方法 Lua调用C#Ref与Out知识 Lua调用C#函数重载 Lua调用C#委托与事件 Lua调用C#二维数组 Lua调用C#中nil与null的差距 Lua调用C#中让系类型与lua能够互相访问 Lua调用…...
EdgeOne安全专项实践:上传文件漏洞攻击详解与防范措施
靶场搭建 当我们考虑到攻击他人服务器属于违法行为时,我们需要思考如何更好地保护我们自己的服务器。为了测试和学习,我们可以搭建一个专门的靶场来模拟文件上传漏洞攻击。以下是我搭建靶场的环境和一些参考资料,供大家学习和参考࿰…...
springboot使用Easy Excel导出列表数据为Excel
springboot使用Easy Excel导出列表数据为Excel Easy Excel官网:https://easyexcel.opensource.alibaba.com/docs/current/quickstart/write 主要记录一下引入时候的pom,直接引入会依赖冲突 解决方法: <!-- 引入Easy Excel的依赖 -->&l…...
现代 CPU 的高性能架构与并发安全问题
现代 CPU 的设计(如多级缓存、指令重排)为了提升性能,引入了许多优化机制,但这些机制可能导致并发场景下的安全性问题。并发安全性主要体现在三个方面:原子性、有序性 和 可见性。这些问题在底层通过 CAS(C…...
【数模学习笔记】插值算法和拟合算法
声明:以下笔记中的图片以及内容 均整理自“数学建模学习交流”清风老师的课程资料,仅用作学习交流使用 文章目录 插值算法定义三个类型插值举例插值多项式分段插值三角插值 一般插值多项式原理拉格朗日插值法龙格现象分段线性插值 牛顿插值法 Hermite埃尔…...
JavaScript 数组及其常用方法
1. JavaScript 数组概述 数组是 JavaScript 中用于存储多个值的数据结构。它可以存储不同类型的元素,并提供强大的方法来操作和管理数据。数组的元素按索引(从 0 开始)进行访问。 2. 数组的创建方式 1) 使用数组字面量 let fruits [&quo…...
SQL HAVING 子句深入解析
SQL HAVING 子句深入解析 介绍 SQL(Structured Query Language)是一种用于管理关系数据库管理系统的标准编程语言。在SQL中,HAVING子句是与GROUP BY子句一起使用的,用于筛选分组后的数据。它根据聚合函数的结果对组进行条件过滤…...
vue3+ts的几个bug调试
由于编译问题,把几个type检查给关闭了,否则错误太多。 1)第一个检查出的问题,拼写错误数组的length,写成了lengh。 2)数组的对象引用。 torStatus Array(8).fill({ ...defaultStatus }) as TorStatus[]…...
git: hint:use --reapply-cherry-picks to include skipped commits
问: 当我在feture分支写完功能,切换到dev更新了远端dev代码,切回feture分支,git rebase dev分支后出现报错: warning skipped previously applied commit 709xxxx hint:use --reapply-cherry-picks to include skippe…...
Microsoft Sql Server 2019 数据类型
数据类型 bigint、int、smallint、tinyint 使用整数数据的精确数字数据类型。 若要节省数据库空间,请使用能够可靠包含所有可能值的最小数 据类型。 例如,对于一个人的年龄,tinyint 就足够了,因为没人活到 255 岁以上。 但对于建筑物的 年龄,tinyint 就不再适应,因为建…...
C++实现设计模式---代理模式 (Proxy)
代理模式 (Proxy) 代理模式 是一种结构型设计模式,它为其他对象提供一个代理以控制对该对象的访问。代理模式常用于延迟加载、访问控制、智能引用等场景。 意图 提供对某对象的控制。控制对目标对象的访问,通常用于在不改变目标对象的情况下࿰…...
微信小程序用的SSL证书有什么要求吗?
微信小程序主要建立在手机端使用,然而手机又涉及到各种系统及版本,所以对SSL证书也有要求,如果要小程序可以安全有效的访问需要满足以下要求: 1、原厂SSL证书(原厂封)。 2、DV单域名或者DV通配符。 3、兼…...
Flutter中Get.snackbar和Get.dialog关闭冲突问题记录
背景: 在使用GetX框架时,同时使用了Get.snackbar提示框和Get.dialog加载框,当这两个widget同时存在时,Get.dialog加载框调用Get.back()无法正常关闭。 冲突解释: 之所以会产生冲突,是因为Get.snackbar在关…...
命令模式-Command Pattern
什么是命令模式 命令模式是一种行为类设计模式,核心是将每种请求或操作封装为一个独立的对象,从而可以集中管理这些请求或操作,比如将请求队列化依次执行、或者对操作进行记录和撤销。 命令模式通过将请求的发送者(客户端)和接收者(执行请求…...
【Linux笔记】Day1
基于韩顺平老师课程记录: https://www.bilibili.com/video/BV1Sv411r7vd 安装CentOS 给CentOS手动分区 分为三个区: boot分区(给1G就行) 交换分区(和内存相关,这里和虚拟机的内存2G一致) …...
如何明智地提问
如何明智地提问的重要总结,让我为主要观点添加一些具体的实践建议: 提问前的准备工作 尝试在 Google、Stack Overflow 等平台搜索相似问题阅读相关文档和错误日志尝试自己调试和排查问题记录下已尝试过的解决方案 选择合适的提问平台 Stack Overflow…...
【大前端】Vue3 工程化项目使用详解
目录 一、前言 二、前置准备 2.1 环境准备 2.1.1 create-vue功能 2.1.2 nodejs环境 2.1.3 配置nodejs的环境变量 2.1.4 更换安装包的源 三、工程化项目创建与启动过程 3.1 创建工程化项目 3.2 项目初始化 3.3 项目启动 3.4 核心文件说明 四、VUE两种不同的API风格 …...
window.print()预览时表格显示不全
问题描述:使用element的table组件,表格列宽为自适应,但使用window.print()方法预览的页面会直接按预览宽度截取表格内容进行展示,造成表格可能的显示不全问题 解决方法:添加如下样式 media print {::v-deep {// 表头…...
《解锁鸿蒙Next系统人工智能语音助手开发的关键步骤》
在当今数字化时代,鸿蒙Next系统与人工智能的融合为开发者带来了前所未有的机遇,开发一款人工智能语音助手应用更是备受关注。以下是在鸿蒙Next系统上开发人工智能语音助手应用的关键步骤: 环境搭建与权限申请 安装开发工具:首先需…...
计算机网络之---MAC协议
MAC协议的作用 在数据链路层中,MAC(媒介访问控制)协议负责控制设备如何访问共享的通信介质(如以太网、无线电波等),确保在多台设备共享同一传输媒介时能够有效地进行数据传输,避免冲突、控制流…...
系统思考—要素连接
“改变你的思维,就能改变你的世界”— 诺曼皮尔 世界上的所有事物,都在规律的支配下,以系统的方式运转。显性的部分是我们能看到的“要素”,而那些看不见的力量,正是推动系统运作的要素之间的相互作用。更隐秘的&…...
influxdb 采集node_exporter数据
一、打开Scrapers添加 node_exporter地址:http://192.168.31.135:9100/metrics 查看数据...
数据链路层-STP
生成树协议STP(Spanning Tree Protocol) 它的实现目标是:在包含有物理环路的网络中,构建出一个能够连通全网各节点的树型无环逻辑拓扑。 选举根交换机: 选举根端口: 选举指定端口: 端口名字&…...
学技术学英语:ELK是什么
📢📢📢: 先看关键单词,再看英文,最后看中文总结,再回头看一遍英文原文,效果更佳!! 关键词 aggregate 聚合 /ˈɡrɪɡeɪt/ analytics 分析学 /ˌnəˈl…...
快速实现一个快递物流管理系统:实时更新与状态追踪
物流管理是电商、仓储和配送等行业的重要组成部分。随着电子商务的快速发展,快递物流的高效管理和实时状态更新变得尤为关键。本文将演示如何使用Node.js、Express、MongoDB等技术快速构建一个简单的快递物流管理系统,该系统支持快递订单的实时更新和追踪…...
android分区和root
线刷包内容: 线刷包是一个完整的android镜像,不但包括android、linux和用户数据,还包括recovery等。当然此图中没有recovery,但是我们可以自己刷入一个。 主要分区 system.img 系统分区,包括linux下主要的二进制程序。 boot.img…...
【Go】:深入解析 Go 1.24:新特性、改进与最佳实践
前言 Go 1.24 尚未发布。这些是正在进行中的发布说明。Go 1.24 预计将于 2025 年 2 月发布。本文将深入探讨 Go 1.24 中引入的各项更新,并通过具体示例展示这些变化如何影响日常开发工作,确保为读者提供详尽而有价值的参考。 新特性及改进综述 HTTP/2 …...
Vue3组件通讯——自定义事件(子->父)
需求如下: 1.在子组件中,当用户点击提交按钮后,更新数据库 2.数据更新成功后,子组件通知父组件getUserInfo函数,重新获取数据,同步更新 3.子组件等待getUserInfo函数执行完毕后,调用init函数…...
【Rust自学】12.2. 读取文件
12.2.0. 写在正文之前 第12章要做一个实例的项目——一个命令行程序。这个程序是一个grep(Global Regular Expression Print),是一个全局正则搜索和输出的工具。它的功能是在指定的文件中搜索出指定的文字。 这个项目分为这么几步: 接收命令行参数读…...
《OpenCV计算机视觉实战项目》——银行卡号识别
文章目录 项目任务及要求项目实现思路项目实现及代码导入模块设置参数对模版图像中数字的定位处理银行卡的图像处理读取输入图像,预处理找到数字边框使用模版匹配,计算匹配得分 画出并打印结果 项目任务及要求 任务书: 要为某家银行设计一套…...
Windows 下Mamba2 / Vim / Vmamba 环境安装问题记录及解决方法终极版(无需绕过triton)
导航 安装教程导航 Mamba 及 Vim 安装问题参看本人博客:Mamba 环境安装踩坑问题汇总及解决方法(初版)Linux 下Mamba 及 Vim 安装问题参看本人博客:Mamba 环境安装踩坑问题汇总及解决方法(重置版)Windows …...
OpenCV的一种改进型的素描特效算法
实现素描特效的原理主要基于图像处理中的边缘检测和灰度反转等技术。边缘检测能够突出图像中的轮廓信息,而灰度反转则用于增强对比,使图像看起来更像手绘素描。 1 素描特效的常规算法 图像读取与预处理 使用图像处理库(如OpenCV)…...
Maven核心插件之maven-resources-plugin
前言 Maven 插件是 Maven 构建系统的重要组成部分,它们为 Maven 提供了丰富的功能和扩展能力,使得 Maven 不仅是一个构建工具,更是一个强大的项目管理平台。在 Maven 项目中,插件的使用通常通过配置 pom.xml 文件来完成。每个插件…...
LeetCode 283题:移动零
LeetCode 283题:移动零 (Move Zeroes) LeetCode 第283题要求将数组中的所有零移动到数组的末尾,同时保持非零元素的相对顺序。 题目描述 给定一个数组 nums,编写一个函数将所有的 0 移动到数组的末尾,同时保持非零元素的相对顺序…...
常见的开源协议及注意事项【精简版】
注: 以下内容出自Github Copilot。 常见的开源协议有以下几种,每种协议都有其特定的使用场景和注意事项: MIT许可证: 特点:非常宽松,允许用户自由使用、复制、修改、合并、发布、分发、再许可和/或销售软件…...
【Oracle专栏】2个入参,生成唯一码处理
Oracle相关文档,希望互相学习,共同进步 风123456789~-CSDN博客 1.背景 业务需要:2个参数,如 aidbankid ,两个值是联合主键,需要生成一个固定唯一码,长度有限制32位,为了…...
component-动态控制 div width 的值 根据传入的变量决定width的值 vue
1.实现 根据参数的值,div显示不同的长度 <div class"node-line" :style"lineProgress"></div> <script>export default {name: "trainSummaryInfo",data(){return{linePercentage:200,}},computed:{lineProgress…...
<2025 网络安全>《网络安全政策法规-关键信息基础设施安全保护条例》
1 政策出台背景 《关键信息基础设施安全保护条例》的实施背景主要包括以下几个方面: 首先,关键信息基础设施在经济社会中的重要地位使其成为网络安全的核心保护对象。关键信息基础设施包括公共通信和信息服务、能源、交通、水利、金融、公共服…...
【MySQL数据库】基础总结
目录 前言 一、概述 二、 SQL 1. SQL通用语法 2. SQL分类 3. DDL 3.1 数据库操作 3.2 表操作 4. DML 5. DQL 5.1 基础查询 5.2 条件查询 5.3 聚合函数 5.4 分组查询 5.5 排序查询 5.6 分页查询 6. DCL 6.1 管理用户 6.2 权限控制 三、数据类型 1. 数值类…...
acwing_5721_化学方程式配平
acwing_5721_化学方程式配平 这是一道T3的题目,操作起来可能有些棘手,但是耐下心来做一遍会有收获的! 下面是对于大佬的题解进行的注释 #include <iostream> #include <string> #include <map> #include <vector>…...
C++ 中的 template <typename T> 用法 ← 泛型
【语法解析】 ● C 中的 template <typename T> 用法 template <typename T> 是C编程语言中的一个模板声明,用于定义一个模板,其中 T 是一个模板参数,可以是任何类型。这种机制允许程序员编写与类型无关的代码,从而提…...
C语言 操作符_位操作符、赋值操作符、单目操作符
1.位操作符 & - 按(2进制)位与 | - 按(2进制)位或 ^ - 按(2进制)位异或 只适用于整型 例:实现交换两个变量的值,要求不能新建变量 //3^3 0 -> a^a 0 //011 //011 //000 …...
C++ 鼠标轨迹算法 - 防止游戏检测
一.简介 鼠标轨迹算法是一种模拟人类鼠标操作的程序,它能够模拟出自然而真实的鼠标移动路径。 鼠标轨迹算法的底层实现采用C/C语言,原因在于C/C提供了高性能的执行能力和直接访问操作系统底层资源的能力。 鼠标轨迹算法具有以下优势: 模拟…...
计算机网络 笔记 数据链路层3(局域网,广域网,网桥,交换机)
局域网: LAN:在某一区域内由多台计算机互联成的计算机组,使用广播信道 特点: 覆盖范围有限:通常局限在几千米范围内,比如一栋办公楼、一个校园或一个工厂等相对较小的地理区域。 数据传输速率高:一般能达到 10Mbps…...
【Qnx】Qnx常用工具
Qnx常用分析工具 近期项目中Qnx端常用到的工具,总结一下。 查看进程 Display information about the processes in the system (QNX Neutrino) pidin使用pidin命令,可以查看Qnx系统运行的进程信息,包括PID、TID、NAME、PRIO(优先级)、STATE…...
基于 Selenium 实现上海大学校园网自动登录
基于 Selenium 实现上海大学校园网自动登录 一、技术方案 核心工具: Selenium:一个用于自动化测试的工具,能够模拟用户在浏览器上的操作。Edge WebDriver:用于控制 Edge 浏览器的驱动程序。 功能设计: 检测网络状…...
相机小孔成像模型与透视变换
0 背景 本文用于记录小孔相机成像的数学模型推导,并讨论特定条件下两个相机之间看到图像的变换关系。 1 小孔成像模型 小孔成像模型如上图所示。物理世界发光点P,经过小孔O投影到物理成像平面,形成像点I’。 简易起见,构造虚拟成…...
微信小程序订阅消息提醒-云函数
微信小程序消息订阅分2种: 1.一次性订阅:用户订阅一次就可以推送一次,如果需要多次提醒需要多次订阅。 2.长期订阅:只有公共服务领域,如政务、医疗、交通、金融和教育等。在用户订阅后,在很长一段时间内…...