程序环境及预处理

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

在ANSI C(标准c)的任何一种实现中,存在两个不同的环境。

计算机是能够执行二进制指令的,但是我们写出的c语言代码是文本信息,计算机不能直接理解

第1种是翻译环境,在这个环境中源代码被转换为可执行的机器指令。c语言的代码--》二进制指令,会放入可执行程序中(编译+链接生成.exe可执行程序)

第2种是执行环境,它用于实际执行代码。执行二进制代码(运行.exe可执行程序)

二. 详解编译+链接

2.1 翻译环境

690a229339354885a4574843ec598475.jpeg

翻译就是将每个.c文件单独经过编译器编译,生成.obj目标文件,再将目标文件和库经过链接器链接,生成可执行程序.exe。

df28985b7bcf4681b7709c636855a98d.png6dad4bb95d1341fcbe66290cc4686599.png我们是可以找到这些文件的

 

vs2019 集成开发环境 集成了:编译器(cl.exe)+链接器(link.exe)+调试器

在vs2019这样的集成开发环境中不方便观察这些细节,这里我使用Linux系统,使用gcc这个编译器给大家演示一下整个过程

翻译过程(这里挑关键的给大家描述)

758b96feb78742819cf19e8e7cc9c531.png

编译分为预处理,编译,汇编,链接只有链接

预处理:主要做的是注释的删除,头文件的包含,符号的替换

编译:主要是将c语言代码进行拆解翻译成汇编指令,语法分析,词法分析,语义分析,符号汇总(关键)

汇编:将汇编代码翻译成二进制指令,并形成符号表(重要)

链接:1.合并段表.   2符号表的合并和重定位(重要)

1. 预处理 选项 gcc -E test.c -o test.i

预处理完成之后就停下来,预处理之后产生的结果都放在test.i文件中。

2. 编译 选项 gcc -S test.c

编译完成之后就停下来,结果保存在test.s中。

3. 汇编 gcc -c test.c

汇编完成之后就停下来,结果保存在test.o中。

linux下gcc编译产生的目标文件test.o,可执行程序test都是按照ELF这种文件格式来存储的

readelf 工具能识别elf格式的工具

readelf test.o -s命令可以看到全局属性的变量,不能看到局部描述性的变量

8b3a6524543543a8a449e99afe7bfb47.png

69d5eed69a0a42a99a0b4eac51d647ff.png段表抽象图

        段表可以理解为.o文件的存储格式,分为一个个小段,合并段表就是将相同区域的数据合并,并填入可执行程序里

符号表的合并和重定位,是下边过程

9327e59609d240c6ba1d6ae2ef7e7401.png

编译阶段符号汇总(将全局属性的符号进行汇总,比如printf,Add,main等),汇编阶段形成符号表并填充地址,链接阶段符号表合并且重定位(test.c形成的Add是无效地址,如果我们将add.c中的Add屏蔽,会报链接错误,因为符号表填充的是Add的无效地址,链接找不到函数定义),合并段表(合并两张段表)

我们程序中有三种常见错误:编译时错误和链接时错误和运行时错误

编译时错误主要是语法有错误,因为编译器在编译阶段会进行语法分析

链接时错误主要是函数名写错(函数未定义),因为链接时会进行符号表的合并,连名字都对不上肯定有问题,或者函数在使用之后定义未声明,导致的错误。

运行时错误主要是空指针的解引用,数组越界等等编译器未在编译链接查找到的错误

以上就是翻译环境的介绍

2.2 运行环境

程序执行的过程:

1. 程序必须载入内存中。在有操作系统的环境中:一般这个由操作系统完成。在独立的环境中,程序的载入必须由手工安排,也可能是通过可执行代码置入只读内存来完成。

2. 程序的执行便开始。接着便调用main函数。

3. 开始执行程序代码。这个时候程序将使用一个运行时堆栈(stack)(函数栈帧),存储函数的局部变量和返回地址。程序同时也可以使用静态(static)内存,存储于静态内存中的变量在程序的整个执行过程一直保留他们的值。

4. 终止程序。正常终止main函数;也有可能是意外终止。

三.预处理详解

1 预定义符号

__FILE__      //进行编译的源文件

__LINE__     //文件当前的行号

__DATE__    //文件被编译的日期

__TIME__    //文件被编译的时间

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

9f187fe9d3804c238f0cc006fa7064bf.png

经过预处理,我们确实看到了预处理的结果

2 #define

2.1 #define 定义标识符

语法: #define name stuff

不管是整数,字符串,还是循环结构都能替换

04146782e20445c28ebc2c0d36f03395.png

甚至可以为了简便将break,case 定义为一个大写CASE,在预处理阶段也会替换

297bc483fc8a41ee9cd93962235ddcc7.png

甚至可以替换打印,特别牛逼

fe3d6415c89d490bb2764e647002f56a.png

提问:在define定义标识符的时候,要不要在最后加上 ; ?

比如

 

#define MAX 1000;

#define MAX 1000

建议不要加上 ; ,这样容易导致问题。

比如下面的场景:

if(condition)

max = MAX;//这里会替换为1000;;   两个分号,无法与if语句匹配,语法错误

else

max = 0;

2.2 #define 定义宏

3b06523a20d04df7a58ebe9835fac1a1.png

会在预处理阶段进行宏替换(直接在原位置替换)

e671c8aa38f948cb8d726e6398597a77.png

注意:

参数列表的左括号必须与name紧邻。(就是讲MAX必须和左边的括号相邻,不能有空格)

如果两者之间有任何空白存在,参数列表就会被解释为stuff的一部分。

我们看一个例子

#define SQUARE( x ) x * x

这个宏接收一个参数 x .

如果在上述声明之后,你把SQUARE( 5 );

置于程序中,预处理器就会用下面这个表达式替换上面的表达式:5 * 5

警告:

这个宏存在一个问题:

观察下面的代码段:

int a = 5;

printf("%d\n" ,SQUARE( a + 1) );

乍一看,你可能觉得这段代码将打印36这个值。

事实上,它将打印11.

为什么?

替换文本时,参数x被替换成a + 1,所以这条语句实际上变成了:

printf ("%d\n",a + 1 * a + 1 );

这样就比较清晰了,由替换产生的表达式并没有按照预想的次序进行求值。

在宏定义上加上两个括号,这个问题便轻松的解决了:

#define SQUARE(x) (x) * (x)

这样预处理之后就产生了预期的效果:

printf ("%d\n",(a + 1) * (a + 1) );

这里还有一个例子

这里还有一个宏定义:

#define DOUBLE(x) (x) + (x)

定义中我们使用了括号,想避免之前的问题,但是这个宏可能会出现新的错误。

int a = 5;

printf("%d\n" ,10 * DOUBLE(a));

这将打印什么值呢?

warning

看上去,好像打印100,但事实上打印的是55.

我们发现替换之后:

printf ("%d\n",10 * (5) + (5));

乘法运算先于宏定义的加法,所以出现了55 .

这个问题,的解决办法是在宏定义表达式两边加上一对括号就可以了。

#define DOUBLE( x)   ( ( x ) + ( x ) )

总结:所以用于对数值表达式进行求值的宏定义都应该用这种方式加上括号,避免在使用宏时由于参数中的操作符或邻近操作符之间不可预料的相互作用。

2.3 #define 替换规则(简单来说就是在预处理阶段在原文直接宏替换)

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

1. 在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们首先被替换。

2. 替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值所替换。

3. 最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上述处理过程。

注意:

1. 宏参数和#define 定义中可以出现其他#define定义的符号。但是对于宏,不能出现递归。

2. 当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索。

2.4 #和##

我们可以看到这样一段程序,既可以在一个" " 内部打印,又可以拆解成两个" " ,打印是一个效果,是不是很神奇呢?

#include<stdio.h>

int main()

{

    printf("hello world\n");

    printf("hello ""world\n");

    return 0;

}

2c7cb983338a4a128bd7df7bcc18b75d.png

我们如果想实现一个函数,能打印a,b,f是不现实的,但是我们可以用宏解决这个问题,将宏定义的n打印的时候在n前加一个#,是为#n,在预处理阶段能将a替换过去,预处理完成后是下边这个效果,可以看到都替换过去了(注意,我们多传了一个format是为了能识别不同类型去打印)

553e410161d84c8b9b6a16cca79fbd21.png

ea833d022220470ab9ae8560b9602517.png打印是这个效果

 

#include<stdio.h>

#define PRINT(n, format) printf("the value of "#n" is " format "\n", n)

int main()

{

    int a = 20;

    //printf("the value of a is %d\n", a);

    PRINT(a, "%d");

 

    int b = 15;

    //printf("the value of b is %d\n", b);

    PRINT(b, "%d");

 

    float f = 4.5f;

    //printf("the value of f is %f\n", f);

    PRINT(f, "%f");

    return 0;

}

所以#的作用是把一个宏参数变成对应的字符串

#include<stdio.h>

//##

#define CAT(x,y) x##y

 

int main()

{

    int Happyjzy = 2025;

    printf("%d\n", CAT(Happy, jzy));

    printf("%d\n", Happyjzy);

    return 0;

}

##的作用很简单,在预处理阶段将传过去的字符串拼接

59152bae14614c7da9e52bc4d803257f.png我们看到都打印了2025

2.5 带副作用的宏参数

当宏参数在宏的定义中出现超过一次的时候,如果参数带有副作用,那么你在使用这个宏的时候就可能出现危险,导致不可预测的后果。副作用就是表达式求值的时候出现的永久性效果。例如:

x+1;//不带副作用

x++;//带有副作用,一定要避免传这种参数,会连续影响不符合预期

MAX宏可以证明具有副作用的参数所引起的问题。

#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);//输出的结果是什么?

这里我们得知道预处理器处理之后的结果是什么:

z = ( (x++) > (y++) ? (x++) : (y++));

所以输出的结果是:

x=6 y=10 z=9

2.6 宏和函数对比

宏通常被应用于执行简单的运算。

比如在两个数中找出较大的一个。

#define MAX(a, b) ((a)>(b)?(a):(b))

那为什么不用函数来完成这个任务?

原因有二:

1. 用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多。

所以宏比函数在程序的规模和速度方面更胜一筹。(函数调用需要经历下边的阶段,但宏只需要替换就可以计算逻辑,没有创建栈帧和调用函数的额外时间)

81e1584fb3cc4df09dfd71f8833e3f95.png

2. 更为重要的是函数的参数必须声明为特定的类型。所以函数只能在类型合适的表达式上使用。反之这个宏怎可以适用于整形、长整型、浮点型等可以用于>来比较的类型。宏是类型无关的

宏的缺点:当然和函数相比宏也有劣势的地方:

1. 每次使用宏的时候,一份宏定义的代码将插入到程序中。除非宏比较短,否则可能大幅度增加程序的长度。(编译的时候宏可能会替换多次,就多余了很多行代码,但是函数只有调用时的代码

2. 宏是没法调试的。(直接宏替换了,没法调试

3. 宏由于类型无关,也就不够严谨。(没有函数的类型检查了

4. 宏可能会带来运算符优先级的问题,导致程容易出现错误。

         宏有时候可以做函数做不到的事情。比如:宏的参数可以出现类型,但是函数做不到。

f75b11db5a044befb6f3c62d2fbd6ffa.png

宏可以封装,外界只用传大小和类型就能用malloc,很方便

宏和函数的一个对比(基本上前面都讲过了)

179c63ab3a6b49edac59c79849afdc5c.png

2.7 命名约定

一般来讲函数的宏的使用语法很相似。所以语言本身没法帮我们区分二者。

那我们平时的一个习惯是:把宏名全部大写 函数名不要全部大写

8cd8f1a09b214a4b946e1f8caf272b6d.png

3 #undef

这条指令用于移除一个宏定义。

#include<stdio.h>

#define MAX(x, y) ((x)>(y)?(x):(y))

int main()

{

    int c = MAX(3, 5);

    printf("%d\n", c);

#undef MAX

    c = MAX(5, -5);

    printf("%d\n", c);

    return 0;

}

我们可以看到确实报错了(未定义错误)

d65f2a111bac453d82b9974df9408d78.png

4 命令行定义

许多C 的编译器提供了一种能力,允许在命令行中定义符号。用于启动编译过程。(gcc支持,vs不支持)

例如:当我们根据同一个源文件要编译出不同的一个程序的不同版本的时候,这个特性有点用处。(假定某个程序中声明了一个某个长度的数组,如果机器内存有限,我们需要一个很小的数组,但是另外一个机器内存大写,我们需要一个数组能够大写。)

我们看下边一段代码

#include <stdio.h>
int main()
{
  	int array [ARRAY_SIZE];
 	int i = 0;
 	for(i = 0; i< ARRAY_SIZE; i ++)
  	{
	 array[i] = i;
	 }
	for(i = 0; i< ARRAY_SIZE; i ++)
	{
 	printf("%d " ,array[i]);
 	}
 	printf("\n" );
 	return 0;
}

出现了这样的报错,是因为没有指定数组大小

360c75fa35304ceeb24eeb960ac51f9b.png

我们可以采用-D  ARRAY_SIZE =指定大小来解决

e3112468399e4aada40081fdd3b0c5a0.png

问题得到了解决

5 条件编译

在编译一个程序的时候我们如果要将一条语句(一组语句)编译或者放弃是很方便的。因为我们有条件编译指令。

常见的条件编译指令:

#if 常量表达式

//...

#endif

5ec1f2cd1c4e42bdbbd137d8204bffa6.png

ce10db2034454c2384068fb99edf696f.png

编译无法通过,因为if为假

2.多个分支的条件编译

#if 常量表达式

//...

#elif 常量表达式

//...

#else

//...

#endif

bd1c3d960d39415a87b14aa7edeef37c.png

960fcf81c7b84d45a4b504b3968b478c.pngM为2打印heihei,因为前边的都进不去,很简单吧

3.判断是否被定义

#if defined(symbol)    #ifdef symbol     这两个等价

#if !defined(symbol)      #ifndef symbol    这两个也等价

7888f2eeb5fc42c082b0373f7607ca55.png2754dcbfcf8a453eb916aac58b95890f.png

cc97151713fa42c489ffb2fd51a119a9.png这两个是一个意思结果都一样

问:为什么WIN 是0也可以通过编译呢?

答:因为这个是判断是否被定义,跟是否是0没关系

ifndef就不演示了,是一个道理

18f559bac13048aab3627a919652fa01.png当我们屏蔽定义时,就不会打印了

4.嵌套指令(这个了解一下就行)

#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

有的同学看到这里会产生疑惑,条件编译的作用是什么呢?

其实这个主要用于防止头文件重复包含

我们建一个.h文件,里边只放一个声明

02266dae0b8146fbb1185f5ee06474cb.png

在.c文件包含一下,然后预处理,我们看到只有一个头文件,那我们如果把注释取消呢

70e8f684e9514286b142b80135477bbe.png

我们可以看到出现了四份头文件代码

0b9512f5b7e2434e9caf5923a1ac3295.png

如果头文件里的代码不多,倒也没啥问题

但是如果是企业级开发,可能一个头文件会有几千行代码,那我们编译时要处理这么多多余的代码,是不是不太好呢

 

条件编译的作用就来了

我们在头文件中添加这样一段代码(代码解释:如果没有定义这个符号,我们就定义并且执行里边的内容,等第二次要定义的时候发现已经定义过了,头文件只包含了一次,条件编译就结束了)

d6304d64a5244df792e8dd62a701ea7c.png

我们再编译一下代码,在.i文件里就可以看到只有一个头文件内容,这就是条件编译的主要用途-》防止头文件重复包含

9d9be966fdc04d2998cb3a8ec48a9735.png

 

6.文件包含

我们不仅可以用上述方法解决头文件重复包含的问题,#pragma once也可以一劳永逸的解决,效果是一样的。现在编译器都升级了,会在我们建立头文件时自动加上#pragma once。

头文件被包含的方式:

本地文件包含   #include "filename"

查找策略:先在源文件所在目录下查找,如果该头文件未找到,编译器就像查找库函数头文件一样在标准位置查找头文件。如果找不到就提示编译错误

Linux环境的标准头文件的路径:/usr/include

VS环境的标准头文件的路径:C:\Program Files (x86)\Microsoft VisualStudio12.0\VC\include

//这是VS2013的默认路径  (注意按照自己的安装路径去找)

库文件包含,就是我们常写的标准库的头文件    #include <filename.h>

查找头文件直接去标准路径下去查找,如果找不到就提示编译错误。

这样是不是可以说,对于库文件也可以使用 “” 的形式包含?

答案是肯定的,可以。但是这样做查找的效率就低些,当然这样也不容易区分是库文件还是本地文件了。

总结:是库文件就<>包含,是自己定义的文件就" " 包含,可以保证效率

笔试题:

1. 头文件中的 ifndef/define/endif是干什么用的?  答:防止头文件重复包含

2. #include <filename.h> 和 #include "filename.h"有什么区别? 答:#include <filename.h>只会在标准库目录下查找, #include "filename.h"先在当前工作目录查找,找不到->去库工作目录查找,再找不到就报错

四.. 其他预处理指令

#error

#pragma

#line

...

感兴趣的可以自己了解,这里把关键部分都讲完了

#pragma pack()在结构体部分介绍。

可参考《C语言深度解剖》学习

感谢支持!!!

 

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/949823.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

回顾 Tableau 2024 亮点功能,助力 2025 数据分析新突破

2024 年&#xff0c;Tableau 用更智能、更高效的工具&#xff0c;重新定义了数据分析的可能性。 回顾 2024 年&#xff0c;Tableau 凭借一系列创新功能&#xff0c;在数据可视化与分析领域再次引领潮流。无论是深度整合 AI 技术&#xff0c;还是优化用户体验的细节&#xff0c;…

【姿态估计实战】使用OpenCV和Mediapipe构建锻炼跟踪器【附完整源码与详细说明】

《------往期经典推荐------》 一、AI应用软件开发实战专栏【链接】 项目名称项目名称1.【人脸识别与管理系统开发】2.【车牌识别与自动收费管理系统开发】3.【手势识别系统开发】4.【人脸面部活体检测系统开发】5.【图片风格快速迁移软件开发】6.【人脸表表情识别系统】7.【…

快速上手Python,制作趣味猜数字游戏

在编程学习的旅程中&#xff0c;游戏是一个极佳的切入点。今天&#xff0c;我们将一起创建一个简单而有趣的猜数字游戏&#xff0c;借此机会深入学习Python编程的基础知识和一些实用的编程技巧。无论你是初学者还是有一定基础的开发者&#xff0c;相信你都能从中获得乐趣和收获…

AI驱动的可演化架构与前端开发效率

1. 引言 在当今快节奏的数字时代&#xff0c;软件系统需要具备强大的适应能力才能在瞬息万变的市场需求中保持竞争力。软件可演化架构的重要性日益凸显&#xff0c;它能够让软件系统在面对需求变更、技术升级以及市场波动时&#xff0c;能够快速、高效地进行调整和升级&#x…

用豆包MarsCode IDE打造精美数据大屏:从零开始的指南

原标题&#xff1a;用豆包MarsCode IDE&#xff0c;从0到1画出精美数据大屏&#xff01; 豆包MarsCode IDE 是一个云端 AI IDE 平台&#xff0c;通过内置的 AI 编程助手&#xff0c;开箱即用的开发环境&#xff0c;可以帮助开发者更专注于各类项目的开发。 作为一名前端开发工…

基于RK3568/RK3588大车360度环视影像主动安全行车辅助系统解决方案,支持ADAS/DMS

产品设计初衷 HS-P2-2D是一款针对大车盲区开发的360度全景影像 安全行车辅助系统&#xff0c;通过车身四周安装的超广角像机&#xff0c;经算法合成全景鸟瞰图&#xff0c;通过鸟瞰图&#xff0c;司机非常清楚的看清楚车辆四周情况&#xff0c;大大降低盲区引发的交通事故。 产…

pygame飞机大战

飞机大战 1.main类2.配置类3.游戏主类4.游戏资源类5.资源下载6.游戏效果 1.main类 启动游戏。 from MainWindow import MainWindow if __name__ __main__:appMainWindow()app.run()2.配置类 该类主要存放游戏的各种设置参数。 #窗口尺寸 #窗口尺寸 import random import p…

c++ 两线交点计算程序(Program for Point of Intersection of Two Lines)

给定对应于线 AB 的点 A 和 B 以及对应于线 PQ 的点 P 和 Q&#xff0c;找到这些线的交点。这些点在 2D 平面中给出&#xff0c;并带有其 X 和 Y 坐标。示例&#xff1a; 输入&#xff1a;A (1, 1), B (4, 4) C (1, 8), D (2, 4) 输出&#xff1a;给定直线 AB 和…

Taro+react 开发第一节创建 带有redux状态管理的项目

Taro 项目基于 node&#xff0c;请确保已具备较新的 node 环境&#xff08;>16.20.0&#xff09;&#xff0c;推荐使用 node 版本管理工具 nvm 来管理 node&#xff0c;这样不仅可以很方便地切换 node 版本&#xff0c;而且全局安装时候也不用加 sudo 了。 1.安装 npm inf…

2024AAAI SCTNet论文阅读笔记

文章目录 SCTNet: Single-Branch CNN with Transformer Semantic Information for Real-Time Segmentation摘要背景创新点方法Conv-Former Block卷积注意力机制前馈网络FFN 语义信息对齐模块主干特征对齐共享解码头对齐 总体架构backbone解码器头 对齐损失 实验SOTA效果对比Cit…

代码随想录 数组test5(leetcode 59.螺旋矩阵)

59. 螺旋矩阵 II - 力扣&#xff08;LeetCode&#xff09; 大致的想法是从起点开始以顺时针走到中心&#xff0c;有两种实现方式:一圈一圈赋值或者每走一步就赋值 方法一:按圈循环 思路: 外层循环是要循环的圈数&#xff0c;这里需要分奇偶讨论&#xff0c;若题目给出的n为偶…

向成电子XC3588H工控主板助力内窥镜应用升级

随着微创手术在全球范围内普及&#xff0c;内窥镜应用越来越广泛。利用内窥镜&#xff0c;医生可以看到X射线不能显示的病变&#xff0c;对医疗诊断有非常重要的作用。内窥镜设备凝聚了先进的影像技术&#xff0c;提供高画像精度诊断微小的病变。在设备智能化的今天&#xff0c…

基于SpringBoot的音乐网站与分享平台

基于SpringBoot的音乐网站与分享平台 摘要1. 研究背景2.研究内容3.系统功能 3.1前台首页功能模块3.2在线听歌功能模块3.3后台登录功能模块3.4在线听歌管理模块 4.部分功能代码实现5.源码分享(免费获取) 需要源码联系我即可(免费获取)~ ??大家点赞、收藏、关注、评论啦 、查…

nginx-灰度发布策略(split_clients)

一. 简述&#xff1a; 基于客户端的灰度发布&#xff08;也称为蓝绿部署或金丝雀发布&#xff09;是一种逐步将新版本的服务或应用暴露给部分用户&#xff0c;以确保在出现问题时可以快速回滚并最小化影响的技术。对于 Nginx&#xff0c;可以通过配置和使用不同的模块来实现基于…

【数据结构】栈与队列(FIFO)

在阅读该篇文章之前&#xff0c;可以先了解一下堆栈寄存器和栈帧的运作原理&#xff1a;<【操作系统】堆栈寄存器sp详解以及栈帧>。 栈(FILO) 特性: 栈区的存储遵循着先进后出的原则。 例子: 枪的弹夹&#xff0c;最先装进去的子弹最后射出来&#xff0c;最后装入的子弹…

python基础案例

#一个年份如果能被4整除但不能被 100整除&#xff0c;或能被 400整除&#xff0c;那么这个年份就是闰年。 year int(input(请输入年份&#xff1a;)) if (year %40 and year %100!0) or year %4000:print("这个年份就是闰年") else:print("这个年份不是闰…

微服务框架,Http异步编程中,如何保证数据的最终一致性

一、背景 在微服务框架下&#xff0c;跨服务之间的调用&#xff0c;当遇到操作耗时或者量大的情况&#xff0c;我们一般会采用异步编程实现。 本文出现的问题是&#xff1a;异步回调过来时&#xff0c;却未查询到数据库中的任务&#xff0c;导致未能正常处理回调。 下面是当…

Kafka详解 ③ | Kafka集群操作与API操作

目录 1、Kafka集群操作 1.1、创建 topic 1.2、查看主题命令 1.3、生产者生产 1.4、消费者消费数据 1.5、运行 describe topics命令 1.6、增加 topic分区数 1.7、增加配置 1.8、删除配置 1.9、删除 topic 2、Kafka的Java API操作 2.1、生产者代码 2.2、消费者代 2…

Echarts集成Vue2个人总结与反思

协同净焦水处理系统 统计模块 环境部署 1、创建数据库ry-cloud并导入数据脚本ry_2021xxxx.sql&#xff08;必须&#xff09;&#xff0c;quartz.sql&#xff08;可选&#xff09; 2、创建数据库ry-config并导入数据脚本ry_config_2021xxxx.sql&#xff08;必须&#xff09; …

aardio —— 虚表 —— 模拟属性框

写了个简单的属性框例程&#xff0c;抛砖引玉&#xff0c;期待你做出更丰富强大的功能。 本例演示&#xff1a;折叠子行、选择框、输入文本、输入数值、下拉选择、选择图片、选择颜色、选择字体等功能。 只有想不到&#xff0c;没有做不到&#xff0c;发挥你的想象力吧。 imp…