C语言从入门到实战——预处理详解
预处理详解
- 前言
- 一、预定义符号
- 1.1 `__FILE__`
- 1.2`__LINE__`
- 1.3 `__DATE__`
- 1.4`__TIME__`
- 1.5`__STDC__`
- 二、 `#define`定义常量
- 三、 `#define`定义宏
- 四、 带有副作用的宏参数
- 五、 宏替换的规则
- 六、宏函数的对比
- 七、 `#`和`##`
- 7.1 `#`运算符
- 7.2 `##`运算符
- 八、 命名约定
- 九、 `#undef`
- 十、命令行定义
- 十一、条件编译
- 十二、头文件的包含
- 12.1 头文件被包含的方式:
- 12.1.1 本地文件包含
- 12.1.2 库文件包含
- 12.2 嵌套文件包含
- 十三、 其他预处理指令
- #error
- #pragma
- #line
前言
C语言预处理是C语言编译过程的一个阶段,它在编译之前对源代码进行一系列的处理操作,包括宏替换、文件包含、条件编译等,最终生成经过预处理的代码,然后再进行编译。
C语言预处理的主要功能有:
-
宏替换:通过使用
#define
定义宏,可以将一段代码或表达式抽象成一个标识符,在编译时将标识符替换成对应的代码或表达式。 -
文件包含:通过使用
#include
指令,可以将其他文件的内容包含到当前文件中,方便代码的组织和复用。 -
条件编译:通过使用
#ifdef
、#ifndef
、#endif
、#if
、#elif
、#else
等指令,可以根据条件编译开关的设置决定是否编译某段代码,从而实现不同平台或配置下的代码选择。 -
编译器指令:通过使用
#pragma
指令,可以向编译器发出一些特殊的命令,控制编译过程的行为。
C语言预处理的工作原理如下:
-
预处理器扫描源文件,遇到以#开头的指令时,按照指令的要求进行处理。
-
对于宏替换指令,预处理器将标识符替换成对应的代码或表达式。
-
对于文件包含指令,预处理器将被包含文件的内容复制到当前文件中。
-
对于条件编译指令,预处理器根据条件编译开关的设置决定是否编译某段代码。
-
对于编译器指令,预处理器将其直接传递给编译器。
-
预处理完成后,生成经过预处理的代码,进入下一阶段的编译。
需要注意的是,预处理器只是对源代码进行替换、复制等简单的文本处理操作,并不进行语法检查和语义分析。因此,在使用预处理器时需要谨慎,避免产生预期之外的结果。
一、预定义符号
C语言设置了一些预定义符号,可以直接使用,预定义符号也是在预处理期间处理的。
__FILE__ //进行编译的源文件
__LINE__ //文件当前的行号
__DATE__ //文件被编译的日期
__TIME__ //文件被编译的时间
__STDC__ //如果编译器遵循ANSI C,其值为1,否则未定义
举个例子:
printf("file:%s line:%d\n", __FILE__, __LINE__);
1.1 __FILE__
FILE 是一个特殊的预定义常量,在 PHP
中表示当前文件的路径和文件名。当需要获取当前文件的路径和文件名时,可以使用这个常量。
在进行编译的源文件中,使用 FILE 会返回当前源文件的路径和文件名。例如,如果源文件的路径和文件名为/var/www/html/index.php
,那么 FILE 将返回字符串'/var/www/html/index.php'
。
1.2__LINE__
在 C 语言中,__LINE__
是一个特殊的预处理器宏,用于获取当前代码行的行号。
__LINE__
宏可以在程序中使用,它会在编译时被替换为当前代码行的行号。这个宏在调试和错误排查中非常有用,可以帮助开发人员快速定位代码中的问题。
示例代码:
#include <stdio.h>int main() {printf("当前行号:%d\n", __LINE__);return 0;
}
在上面的例子中,__LINE__
宏被用于输出当前行号到控制台。编译并运行该程序,输出结果为当前行号的值。
1.3 __DATE__
在C语言中,__DATE__
是一个特殊的预处理器宏,用于获取当前编译的日期字符串。
__DATE__
宏可以在程序中使用,它会在编译时被替换为一个字符串,表示编译源文件时的日期。这个宏的字符串格式是 “MMM DD YYYY”,其中 MMM 表示月份的缩写(例如 Jan、Feb、Mar 等),DD 表示日期,YYYY 表示年份。
示例代码:
#include <stdio.h>int main() {printf("编译日期: %s\n", __DATE__);return 0;
}
以上代码会输出当前编译的日期字符串,例如 “Jan 01 2022”。注意,这个日期是编译时的日期,而不是运行时的日期。每次编译程序时,__DATE__
宏的值会自动更新为当前的日期。
1.4__TIME__
在C语言中,__TIME__
是一个特殊的预处理器宏,用于获取当前编译的时间字符串。
__TIME__
宏可以在程序中使用,它会在编译时被替换为一个字符串,表示编译源文件时的时间。这个宏的字符串格式是 “HH:MM:SS”,其中 HH 表示小时(24小时制),MM 表示分钟,SS 表示秒。
示例代码:
#include <stdio.h>int main() {printf("编译时间: %s\n", __TIME__);return 0;
}
以上代码会输出当前编译的时间字符串,例如 “12:34:56”。注意,这个时间是编译时的时间,而不是运行时的时间。每次编译程序时,__TIME__
宏的值会自动更新为当前的时间。
1.5__STDC__
STDC 是 C 语言中的一个预定义宏,用于表示当前编译器是否符合 ANSI C 标准。当编译器符合 ANSI C 标准时,它会定义 STDC 宏,其值常为 1。编写 C 代码时,可以使用该宏来判断编译器是否符合 ANSI C 标准,从而使用标准的 C 语言特性。
例如,有些编译器可能支持非标准的 C 语言扩展,当编写需要兼容 ANSI C 的代码时,可以使用条件编译来控制是否使用这些扩展。下面是一个示例:
#include <stdio.h>int main() {
#ifdef __STDC__printf("This compiler follows the ANSI C standard.\n");
#elseprintf("This compiler does not follow the ANSI C standard.\n");
#endifreturn 0;
}
在这个示例中,如果编译器符合 ANSI C 标准,则会输出"This compiler follows the ANSI C standard.";
如果编译器不符合 ANSI C 标准,则会输出"This compiler does not follow the ANSI C standard."
。
二、 #define
定义常量
基本语法:
#define name stuff
举个例子:
#define MAX 1000
#define reg register //为 register这个关键字,创建一个简短的名字
#define do_forever for(;;) //用更形象的符号来替换一种实现
#define CASE break;case //在写case语句的时候自动把 break写上。
// 如果定义的 stuff过长,可以分成几行写,除了最后一行外,每行的后面都加一个反斜杠(续行符)。
#define DEBUG_PRINT printf("file:%s\tline:%d\t \date:%s\ttime:%s\n" ,\__FILE__,__LINE__ , \__DATE__,__TIME__ )
思考:在define
定义标识符的时候,要不要在最后加上 ;
?
比如:
#define MAX 1000;
#define MAX 1000
建议不要加上 ;
,加上的话更容易导致问题。
比如下面的场景:
if(condition)max = MAX;
elsemax = 0;
如果是加了分号的情况,等替换后,if
和else
之间就是2条语句,而没有大括号的时候,if
后边只能有一条语句。这里会出现语法错误。
三、 #define
定义宏
#define
机制包括了一个规定,允许把参数替换到文本中,这种实现通常称为宏(macro)或定义宏(define macro)。
下面是宏的申明方式:
#define name( parament-list ) stuff
其中的 parament-list
是一个由逗号隔开的符号表,它们可能出现在stuff
中。
注意:
参数列表的左括号必须与name
紧邻,如果两者之间有任何空白存在,参数列表就会被解释为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));
这将打印什么值呢?看上去,好像打印100
,但事实上打印的是55
.
我们发现替换之后:
printf ("%d\n",10 * (5) + (5));
乘法运算先于宏定义的加法,所以出现了 55
这个问题,的解决办法是在宏定义表达式两边加上一对括号就可以了。
#define DOUBLE( x) ( ( x ) + ( x ) )
提示:
所以用于对数值表达式进行求值的宏定义都应该用这种方式加上括号,避免在使用宏时由于参数中的操作符或邻近操作符之间不可预料的相互作用。
四、 带有副作用的宏参数
当宏参数在宏的定义中出现超过一次的时候,如果参数带有副作用,那么你在使用这个宏的时候就可能出现危险,导致不可预测的后果。副作用就是表达式求值的时候出现的永久性效果。
例如:
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
五、 宏替换的规则
在程序中扩展#define
定义符号和宏时,需要涉及几个步骤。
- 在调用宏时,首先对参数进行检查,看看是否包含任何由
#define
定义的符号。如果是,它们首先被替换。 - 替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值所替换。
- 最后,再次对结果文件进行扫描,看看它是否包含任何由
#define
定义的符号。如果是,就重复上述处理过程。
注意:
- 宏参数和
#define
定义中可以出现其他#define
定义的符号。但是对于宏,不能出现递归。 - 当预处理器搜索
#define
定义的符号的时候,字符串常量的内容并不被搜索。
六、宏函数的对比
宏通常被应用于执行简单的运算。
比如在两个数中找出较大的一个时,写成下面的宏,更有优势一些。
#define MAX(a, b) ((a)>(b)?(a):(b))
那为什么不用函数来完成这个任务?
原因有二:
- 用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多。所以宏比函数在程序的规模和速度方面更胜一筹。
- 更为重要的是函数的参数必须声明为特定的类型。所以函数只能在类型合适的表达式上使用。反之这个宏怎可以适用于整形、长整型、浮点型等可以用于
>
来比较的类型。宏是类型无关的。
和函数相比宏的劣势:
- 每次使用宏的时候,一份宏定义的代码将插入到程序中。除非宏比较短,否则可能大幅度增加程序的长度。
- 宏是没法调试的。
- 宏由于类型无关,也就不够严谨。
- 宏可能会带来运算符优先级的问题,导致程容易出现错。
宏有时候可以做函数做不到的事情。比如:宏的参数可以出现类型,但是函数做不到。
#define MALLOC(num, type)\(type )malloc(num sizeof(type))...//使用MALLOC(10, int);//类型作为参数 //预处理器替换之后: (int )malloc(10 sizeof(int));
宏和函数的一个对比
属性 | #define 定义宏 | 函数 |
---|---|---|
代码长度 | 每次使用时,宏代码都会被插入程序中。除了非常小的宏之外,程序的长度会大幅度增长 | 函数代码只出现于一个地方;每次使用函数的时候,都会调用那个地方的同一个代码 |
执行速度 | 更快 | 存在函数的调用和返回的额外开销,所以会相对慢一些 |
操作符优先级 | 宏参数的求值是在所有周围表达式的上下文环境里,除非加上括号,否则临近操作符的优先级可能会产生不可预料的后果,所以建议宏在书写的时候多些括号。 | 函数参数只在函数调用的时候才开始求值,并将结果值传递给函数。表达式的结果更容易预测 |
带有副作用的参数 | 参数可能被替换到宏体中的多个位置,如果宏的参数被多次计算,带有副作用的参数求值可能会产生不可预料的结果。 | 函数参数只在传参的时候求值一次,结果更容易控制 |
参数类型 | 宏的参数与类型无关,只要对参数的操作是合法的,它就可以使用于任何参数类型 | 函数的参数是与类型有关的,如果类型不同,就需要不同的函数,即使他们执行的任务是不同的 |
调试 | 宏是不方便调试的 | 函数是可以逐语句调试的 |
递归 | 宏是不能递归的 | 函数是可以递归的 |
七、 #
和##
7.1 #
运算符
#
运算符将宏的一个参数转换为字符串字面量。它仅允许出现在带参数的宏的替换列表中。
#
运算符所执行的操作可以理解为”字符串化“。
当我们有一个变量 int a = 10;
的时候,我们想打印出: the value of a is 10 .
就可以写:
#define PRINT(n) printf("the value of "#n " is %d", n);
当我们按照下面的方式调用的时候:
PRINT(a);
//当我们把a
替换到宏的体内时,就出现了#a
,而#a
就是转换为'a'
代码就会被处理成
printf("the value of ""a" " is %d", a);
运行代码就能在屏幕上打印:
the value of a is 10
7.2 ##
运算符
##
可以把位于它两边的符号合成一个符号,它允许宏定义从分离的文本片段创建标识符。
##
被称为记号粘合
这样的连接必须产生一个合法的标识符。否则其结果就是未定义的。
这里我们想想,写一个函数求2个数的较大值的时候,不同的数据类型就得写不同的函数。
比如:
int int_max(int x, int y)
{return x>y?x:y;
}
float float_max(float x, float y)
{return x>yx:y;
}
但是这样写起来太繁琐了,现在我们这样写代码试试:
//宏定义
#define GENERIC_MAX(type) \
type type##_max(type x, type y)\
{ \return (x>y?x:y); \
}
使用宏,定义不同函数
GENERIC_MAX(int) //替换到宏体内后int##_max 生成了新的符号 int_max做函数名
GENERIC_MAX(float) //替换到宏体内后float##_max 生成了新的符号 float_max做函数名
int main()
{//调用函数 int m = int_max(2, 3);printf("%d\n", m);float fm = float_max(3.5f, 4.5f);printf("%f\n", fm);return 0;
}
输出:
3
4.500000
在实际开发过程中##
使用的很少,很难取出非常贴切的例子。
八、 命名约定
一般来讲函数的宏的使用语法很相似。所以语言本身没法帮我们区分二者。
那我们平时的一个习惯是:
- 把宏名全部大写
- 函数名不要全部大写
九、 #undef
这条指令用于移除一个宏定义。
#undef NAME
//如果现存的一个名字需要被重新定义,那么它的旧名字首先要被移除。
十、命令行定义
许多C 的编译器提供了一种能力,允许在命令行中定义符号。用于启动编译过程。
例如:当我们根据同一个源文件要编译出一个程序的不同版本的时候,这个特性有点用处。(假定某个程序中声明了一个某个长度的数组,如果机器内存有限,我们需要一个很小的数组,但是另外一个机器内存大些,我们需要一个数组能够大些。)
#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;
}
编译指令:
//linux 环境演⽰
gcc -D ARRAY_SIZE=10 programe.c
十一、条件编译
在编译一个程序的时候我们如果要将一条语句(一组语句)编译或者放弃是很方便的。因为我们有条件编译指令。
比如说:
调试性的代码,删除可惜,保留又碍事,所以我们可以选择性的编译。
#include <stdio.h>
#define __DEBUG__
int main()
{int i = 0;int arr[10] = {0};for(i=0; i<10; i++){arr[i] = i;#ifdef __DEBUG__printf("%d\n", arr[i]);//为了观察数组是否赋值成功。 #endif //__DEBUG__}return 0;
}
常见的条件编译指令:
1.
#if 常量表达式//...
#endif
//常量表达式由预处理器求值。
如:
#define __DEBUG__ 1
#if __DEBUG__//..
#endif2.多个分支的条件编译
#if 常量表达式//...
#elif 常量表达式//...
#else//...
#endif3.判断是否被定义
#if defined(symbol)
#ifdef symbol
#if !defined(symbol)
#ifndef symbol
4.嵌套指令
#if defined(OS_UNIX)#ifdef OPTION1unix_version_option1();#endif#ifdef OPTION2unix_version_option2();#endif
#elif defined(OS_MSDOS)#ifdef OPTION2msdos_version_option2();#endif
#endif
十二、头文件的包含
12.1 头文件被包含的方式:
12.1.1 本地文件包含
#include "filename"
查找策略:先在源文件所在目录下查找,如果该头文件未找到,编译器就像查找库函数头文件一样在标准位置查找头文件。
如果找不到就提示编译错误。
Linux环境的标准头文件的路径:
/usr/include
VS环境的标准头文件的路径:
C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\include
注意按照自己的安装路径去找
12.1.2 库文件包含
#include <filename.h>
查找头文件直接去标准路径下去查找,如果找不到就提示编译错误。
这样是不是可以说,对于库文件也可以使用 ""
的形式包含?
答案是肯定的,可以,但是这样做查找的效率就低些,当然这样也不容易区分是库文件还是本地文件了。
12.2 嵌套文件包含
我们已经知道, #include
指令可以使另外一个文件被编译。就像它实际出现于 #include
指令的地方一样。
这种替换的方式很简单:预处理器先删除这条指令,并用包含文件的内容替换。
一个头文件被包含10次,那就实际被编译10次,如果重复包含,对编译的压力就比较大。
test.c
#include "test.h"
#include "test.h"
#include "test.h"
#include "test.h"
#include "test.h"
int main()
{return 0;
}
test.h
void test();
struct Stu
{int id;char name[20];
};
如果直接这样写,test.c
文件中将test.h
包含5次,那么test.h
文件的内容将会被拷贝5份在test.c
中。
如果test.h
文件比较大,这样预处理后代码量会剧增。如果工程比较大,有公共使用的头文件,被大家都能使用,又不做任何的处理,那么后果真的不堪设想。
如何解决头文件被重复引入的问题?答案:条件编译。
每个头文件的开头写:
#ifndef __TEST_H__
#define __TEST_H__
//头文件的内容
#endif //__TEST_H__
或者
#pragma once
就可以避免头文件的重复引入。
注:
推荐《高质量C/C++编程指南》中附录的考试试卷(很重要)。
笔试题:
- 头文件中的
ifndef/define/endif
是干什么用的? #include <filename.h>
和#include “filename.h"
十三、 其他预处理指令
#error
#pragma
#line
#pragma pack()在结构体部分介绍。
#error
在 C 语言中,#error
是一个预处理指令,用于在编译时产生一个错误消息并停止编译。
#error
指令后面可以跟上一个字符串常量作为错误消息,当编译器遇到 #error
指令时,会输出该错误消息并停止编译。
下面是一个示例:
#include <stdio.h>#ifndef __STDC__
#error "This code requires an ANSI C compiler."
#endifint main() {printf("Hello, World!\n");return 0;
}
在这个示例中,#ifndef __STDC__
检查编译器是否符合 ANSI C 标准,如果不符合,就通过 #error
输出错误消息并停止编译。
编译器在遇到 #error
指令时会显示以下错误消息并停止编译:
main.c: In function 'main':
main.c:5:2: error: #error "This code requires an ANSI C compiler."5 | #error "This code requires an ANSI C compiler."| ^~~~~
通过使用 #error
,可以在编译时做一些约束条件的检查,确保代码在符合特定条件下才能编译。
#pragma
在 C 语言中,#pragma
是一个预处理指令,用于向编译器发送特定的指示或控制命令。
#pragma
指令用于提供与编译器或特定环境相关的一些指令,它通常是与特定的编译器或操作系统相关的,不同的编译器可能会支持不同的 #pragma
指令。
下面是一些常见的 #pragma
指令用法:
-
#pragma once
:用于确保头文件只被包含一次,可以在头文件的开头使用该指令。#pragma once
-
#pragma pack
:用于指定结构体的内存对齐方式,可以指定对齐字节数。#pragma pack(4) struct MyStruct {int a;char b; }; #pragma pack()
-
#pragma message
:用于在编译时输出一条自定义的消息。#pragma message("This is a custom message")
-
#pragma warning
:用于控制编译器的警告信息输出。#pragma warning(disable: 1234) // 禁用特定警告 #pragma warning(push) // 保存当前警告状态 #pragma warning(disable: 5678) // 禁用另一个特定警告 // 一些需要禁用警告的代码 #pragma warning(pop) // 恢复之前的警告状态
注意,#pragma
指令的具体用法和支持的指令会因编译器和操作系统而异。在使用 #pragma
指令时,应查阅对应编译器的文档以了解具体用法和支持情况。
#line
#line
是 C 语言中的一个预处理指令,用于修改编译器生成的行号和文件名。
#line
指令的一般语法是:
#line [行号] ["文件名"]
其中,行号和文件名都是可选的参数。如果只指定行号,则编译器将设置当前行号为指定的行号。如果只指定文件名,则编译器将设置当前文件名为指定的文件名。如果同时指定行号和文件名,则编译器将设置当前行号和文件名为指定的值。
下面是一个示例:
#line 10 "myfile.c"
上述示例中,编译器将当前行号设置为 10,当前文件名设置为 “myfile.c”。
#line
指令通常用于调试和错误报告中,可以帮助开发人员更好地跟踪代码的执行过程或报告错误时准确指示错误位置。
需要注意的是,#line
指令的有效范围通常只在它出现之后的部分。也就是说,如果在某处使用了 #line
指令修改了行号或文件名,那么该指令之前的代码将不受影响。
相关文章:
C语言从入门到实战——预处理详解
预处理详解 前言一、预定义符号1.1 __FILE__1.2__LINE__1.3 __DATE__1.4__TIME__1.5__STDC__ 二、 #define定义常量三、 #define定义宏四、 带有副作用的宏参数五、 宏替换的规则六、宏函数的对比七、 #和##7.1 #运算符7.2 ##运算符 八、 命名约定九、 #undef十、命令行定义十一…...

【LabVIEW FPGA】CIC滤波器
一、CIC滤波器应用概述 在通信数字信号上下变频时,经常会用到对数字信号的升采样和降采样,即通过CIC数字速率器实现变采样率。 二、滤波器IP 首先设置滤波器基本参数(filter specification) 滤波器类型(Filter Type…...

砝码称重 蓝桥杯
在C中,fabs()和abs()都用于计算数字的绝对值,但它们之间有一些区别。 fabs(double x):计算浮点数x的绝对值,返回一个double类型的结果。 abs(int x):计算整数x的绝对值,返回一个int类型的结果。 数组的默…...

AmzTrends x TiDB Serverless:通过云原生改造实现全局成本降低 80%
本文介绍了厦门笛卡尔数据(AmzTrends)在面临数据存储挑战时,选择将其数据分析服务迁移到 TiDB Serverless 的思路和实践。通过全托管的数据库服务,AmzTrends 实现了全局成本降低 80% 的效果,同时也充分展示了 TiDB Ser…...

[最佳实践] Windows上构建一个和Linux类似的Terminal
感谢大佬批评指正,现已更新 preview Target:致力打造最赏心悦目Window下的终端,同时能够很接近Linux的使用习惯 key word:windows终端美化 windows terminal windows powershell 类似Linux下的Window终端 Window也能用ll windows…...

租赁系统|手机租赁软件|租赁系统功能开发
当如今的生活用品越来越多、交流更加便捷时,人们的消费需求也变得越来越丰富。不可避免地,我们会遇到这样一种情况:需要新的手机,但资金有限。此时,手机租赁小程序呼之欲出。这种创意不仅使我们能够充分利用各类闲置物…...

【设计模式 04】建造者模式
如果要构建的对象很复杂,那么可以将整个构建过程拆分成多个步骤,并为每一个步骤定义一个抽象的接口。并添加一个指导者用来控制构建产品的顺序和步骤。 Java实现: // 产品类 class Product {private String part1;private String part2;pub…...
Python使用错误总结
【1】cannot import name ‘ParameterSource’ from ‘click.core’ 其根本原因在于是black模块,其模块版本可能过时,升级black模块版本即可: pip install black --upgrade【2】partially initialized module ‘charset_normalizer’ has n…...

【Java EE初阶三十】JVM的简单学习
1. JVM 内存区域划分 一个运行起来的 Java 进程,就是一个 JVM 虚拟机,需要从操作系统申请一大块内存,就会把这个内存,划分成不同的区域,每个区域都有不同的作用. JVM 申请了一大块内存之后,也会划分成不同的内…...
thinkphp5水平分割表partition,以及查询操作
前言 先交代下背景,在一个项目中,有一个数据表有水平分表的需求。当时想找到一种方法,把对数据库的操作,写到一个模型里,通过去换模型属性中的table来达到代码不变操作的数据表变化的效果。 我们都知道,模型要想关联数据表的话&a…...

docker部署aria2-pro
前言 我平时有一些下载视频和一些资源文件的需求,有时候需要离线下载,也要速度比较快的方式 之前我是用家里的玩客云绝育之后不再写盘当下载机用的,但是限制很多 我发现了aria2 这个下载器非常适合我,而有个大佬又在原来的基础…...

vue中Mixins
使用 Mixins 的主要优点包括: 代码复用: 可以将常用的逻辑封装在 Mixin 中,然后在多个组件中重复使用。逻辑分离: 将不同功能的代码分开管理,使代码更加清晰和易于维护。灵活性: Mixins 允许你在组件中引入多个 Mixin,并且可以根…...
linux常用指令(定期更新)
linux常用指令 1.页相关页大小 2.系统参数3.启动参数4.网络参数查询网卡所属numa节点信息网络测速相关iperf测试sar监控网卡流量查看网卡txqueuelen和mtu抓包tcpdump 网络数据收发状态snmp协议栈netstat -i所有网口TX-OK、RX-OKnetstat-s查看各个协议的收发数据ethtool -S单个网…...

【项目】图书管理系统
目录 前言: 项目要求: 知识储备: 代码实现: Main: Books包: Book: BookList: Operate包: Operate: addOperate: deleteOperate: exitOperate: findOperate:…...

华为OD机试 - 疫情扩散时间计算 - 矩阵(Java 2024 C卷 200分)
目录 专栏导读一、题目描述二、输入描述三、输出描述四、解题思路五、Java算法源码六、效果展示1、输入2、输出3、说明 华为OD机试 2024C卷题库疯狂收录中,刷题点这里 专栏导读 本专栏收录于《华为OD机试(JAVA)真题(A卷B卷C卷&am…...

[数据集][图像分类]棉花叶子病害分类数据集2293张4类别
数据集类型:图像分类用,不可用于目标检测无标注文件 数据集格式:仅仅包含jpg图片,每个类别文件夹下面存放着对应图片 图片数量(jpg文件个数):2293 分类类别数:4 类别名称:["diseased_cotton_leaf"…...

《辐射4》是一款什么样的游戏 怎样在mac电脑上玩到《辐射4》辐射4攻略 辐射4开局加点 怎么在Mac电脑玩Steam游戏
辐射4(Fallout 4)是由Bethesda开发的一款动作角色扮演类游戏,为《辐射》系列游戏作品的第四代,于2015年11月10日发行。游戏叙述了主角一家在核爆当天(2077年10月23日),被Vault-Tec(避…...

视频推拉流EasyDSS平台直播通道重连无法转推的原因排查与解决
视频推拉流EasyDSS视频直播点播平台,集视频直播、点播、转码、管理、录像、检索、时移回看等功能于一体,可提供音视频采集、视频推拉流、播放H.265编码视频、存储、分发等视频能力服务。 用户使用EasyDSS平台对直播通道进行转推,发现只要关闭…...

Javaweb之SpringBootWeb案例之自动配置案例的自定义starter测试的详细解析
3.2.4.3 自定义starter测试 阿里云OSS的starter我们刚才已经定义好了,接下来我们就来做一个测试。 今天的课程资料当中,提供了一个自定义starter的测试工程。我们直接打开文件夹,里面有一个测试工程。测试工程就是springboot-autoconfigurat…...
java包的相关概念
包:有效地管理类的一个机制。 包名的目的:通过隶属不同的包有效的区分不同源文件中名字相同的类 包语句 声明:通过关键字 package声明包语句。 位置:源文件中的第一条语句。 作用:为该源文件中声明的类指定包名。 …...

【Python】 -- 趣味代码 - 小恐龙游戏
文章目录 文章目录 00 小恐龙游戏程序设计框架代码结构和功能游戏流程总结01 小恐龙游戏程序设计02 百度网盘地址00 小恐龙游戏程序设计框架 这段代码是一个基于 Pygame 的简易跑酷游戏的完整实现,玩家控制一个角色(龙)躲避障碍物(仙人掌和乌鸦)。以下是代码的详细介绍:…...

多模态2025:技术路线“神仙打架”,视频生成冲上云霄
文|魏琳华 编|王一粟 一场大会,聚集了中国多模态大模型的“半壁江山”。 智源大会2025为期两天的论坛中,汇集了学界、创业公司和大厂等三方的热门选手,关于多模态的集中讨论达到了前所未有的热度。其中,…...

shell脚本--常见案例
1、自动备份文件或目录 2、批量重命名文件 3、查找并删除指定名称的文件: 4、批量删除文件 5、查找并替换文件内容 6、批量创建文件 7、创建文件夹并移动文件 8、在文件夹中查找文件...
A2A JS SDK 完整教程:快速入门指南
目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库ÿ…...

在Mathematica中实现Newton-Raphson迭代的收敛时间算法(一般三次多项式)
考察一般的三次多项式,以r为参数: p[z_, r_] : z^3 (r - 1) z - r; roots[r_] : z /. Solve[p[z, r] 0, z]; 此多项式的根为: 尽管看起来这个多项式是特殊的,其实一般的三次多项式都是可以通过线性变换化为这个形式…...
「全栈技术解析」推客小程序系统开发:从架构设计到裂变增长的完整解决方案
在移动互联网营销竞争白热化的当下,推客小程序系统凭借其裂变传播、精准营销等特性,成为企业抢占市场的利器。本文将深度解析推客小程序系统开发的核心技术与实现路径,助力开发者打造具有市场竞争力的营销工具。 一、系统核心功能架构&…...

沙箱虚拟化技术虚拟机容器之间的关系详解
问题 沙箱、虚拟化、容器三者分开一一介绍的话我知道他们各自都是什么东西,但是如果把三者放在一起,它们之间到底什么关系?又有什么联系呢?我不是很明白!!! 就比如说: 沙箱&#…...

一些实用的chrome扩展0x01
简介 浏览器扩展程序有助于自动化任务、查找隐藏的漏洞、隐藏自身痕迹。以下列出了一些必备扩展程序,无论是测试应用程序、搜寻漏洞还是收集情报,它们都能提升工作流程。 FoxyProxy 代理管理工具,此扩展简化了使用代理(如 Burp…...

数据结构第5章:树和二叉树完全指南(自整理详细图文笔记)
名人说:莫道桑榆晚,为霞尚满天。——刘禹锡(刘梦得,诗豪) 原创笔记:Code_流苏(CSDN)(一个喜欢古诗词和编程的Coder😊) 上一篇:《数据结构第4章 数组和广义表》…...
大数据治理的常见方式
大数据治理的常见方式 大数据治理是确保数据质量、安全性和可用性的系统性方法,以下是几种常见的治理方式: 1. 数据质量管理 核心方法: 数据校验:建立数据校验规则(格式、范围、一致性等)数据清洗&…...