速通C语言第十三站 预处理

系列文章目录

 速通C语言系列

 速通C语言第一站 一篇博客带你初识C语言        http://t.csdn.cn/N57xl

 速通C语言第二站 一篇博客带你搞定分支循环   http://t.csdn.cn/Uwn7W

 速通C语言第三站  一篇博客带你搞定函数        http://t.csdn.cn/bfrUM

速通C语言第四站  一篇博客带你学会数组          http://t.csdn.cn/Ol3lz

 速通C语言第五站 一篇博客带你详解操作符      http://t.csdn.cn/OOUBr

速通C语言第六站 一篇博客带你掌握指针初阶   http://t.csdn.cn/7ykR0

速通C语言第七站 一篇博客带你掌握数据的存储 http://t.csdn.cn/qkerU

 速通C语言第八站 一篇博客带你掌握指针进阶    http://t.csdn.cn/m95FK

 速通C语言第八.五站 指针进阶题目练习           http://t.csdn.cn/wWC2x

速通C语言第九站  字符相关函数及内存函数    http://t.csdn.cn/YyBBM

速通C语言第十站  自定义类型                 http://t.csdn.cn/jsGJ7
 速通C语言第十一站  动态内存开辟                  http://t.csdnimg.cn/necjp

 速通C语言第十二站 文件操作                      http://t.csdnimg.cn/PSxs3

感谢佬们支持!


文章目录

  • 系列文章目录
  • 前言
  • 一、程序的翻译环境和执行环境
  • 二、详解C语言的编译+链接
  •        1 编译环境
  •        2 运行环境
  • 三、预处理详解
  •        1 预处理符号
  •        2 #define定义符号
  •        3 #define定义宏 
  •                宏的声明方式
  •                #define替换规则
  •                #和##
  •                带副作用的宏参数
  •                宏和函数的对比
  •         4 命令约定
  •         5 命令行参数
  •         6 条件编译
  •                单分支
  •                多个分支
  •                判断是否被定义
  •                嵌套指令
  •         7 文件包含
  •                嵌套文件包含
  •         8 其他预处理指令
  • 总结

前言

        上篇博客带大家看了文件的相关操作,这篇博客将是速通C语言的最后一篇,预处理,相比之前几篇来看这节有些太底层,可能对C语言初学者有些晦涩,大家有个印象即可(但这并不意味着不重要),通过之后的学习中再来看就会轻松很多啦


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

在ANSIC(C语言国际标准)的任何实现中,我们写的代码会通过两个环境

翻译环境 :用于将我们写的代码转换成计算机能识别的可执行的机器指令(二进制序列)

执行环境:用于执行代码

画个简易的图来看是这样的


二、详解C语言的编译+链接

翻译环境分为两个步骤,编译+链接

我们写的.c文件都会单独的经过编译器,得到各自的目标文件(windows下为.obj结尾的文件,Linux下为.o结尾,目标文件有其特定的格式,叫elf格式)

然后链接器会链接一些链接库(比如我们用的库函数所在的库),在和这些目标文件链接生成了.exe的可执行文件(它的格式也是elf格式)。

举个例子,我们建立一个test.c和add.c

写一点代码以后ctrl+F5可以发现,生成了.exe的文件

查看一下代码所在路径,会发现两个.obj的目标文件


编译环境

其中编译又可以分为三步

1 预编译(预处理)

预处理很简单,主要做4个事:删注释;#define定义的宏替换;头文件展开;条件编译

通过这步,我们的.c文件会变为.i文件 

2 编译

众所周知,我们的C语言代码得底层是汇编代码,而这一步将通过语法分析,词法分析,

语义汇总,符号汇总等操作将我们的代码转变为汇编代码

通过这步,我们在上一阶段得到的.i文件会变为.s文件

3 汇编

这一步将我们上面得到的汇编代码转变为二进制序列,并生成符号表

通过这步,我们最终得到了那个.o文件


希望这么多复杂的概念没有吓到你,

我们写一波代码看一下,为了方便查看中间文件,我们使用gcc编译器,并使用命令行操作

#include<stdio.h>

#define m 5

//只是一行注释

struct S
{
    char c;
    int a;
};

int main()
{
    struct S s;

    int max=m;
    printf("%d\n",max);

  return 0;
}

第一步预处理,我们用-E选项

gcc -E test.c -o test.i

打开test.i文件,发现它很长

 

(这里截取部分),虽然我们别的看不懂,但是我们认识extern void 函数名

(f lockfile,f trylockfile,f unlockfile我们可知是对文件的加锁,尝试加锁及解锁,很多锁诸如互斥锁,读写锁自旋锁等一般都提供lock、trylock、unlock三个函数;听不懂就当我没说哈),

所以这个是函数的声明,所以实际上这一大段是我们展开的头文件stdio.h

然后在最下面,我们找到了我们写的代码

仔细看,我们定义的宏m背替换成了5,而且我们刚刚写的注释也被删掉了


下来要进行编译,用-S选项

gcc -S test.i -o test.s

此时查看这个test.s,发现里面真的是汇编

此时在简单来看看那4个操作(大家有兴趣的话可以看一看一本书叫《编译原理》或者

《程序员的自我修养》)

语法分析是看你有没有语法错误

词法分析是将代码拆成一个个符号的

语义分析是比如看你写的是分支还是循环等等

符号汇总是把全局看到的符号,函数名做一下汇总,将在下一个步骤中起作用


最后一步要用-c选项

gcc -c test.s test.o

由于得到的是二进制相关的内容,我们看不懂

其中,他做了一个事情叫生成符号表,这个事情是和上一阶段的符号汇总有联系的

为了演示何为符号表,我们换个代码,生成其目标文件

#include<stdio.h>


int g_val=2024;

int add(int x,int y)
{
  return x+y;
}

int main()
{
  int x=0;
  int y=10;
  int ret=add(x,y);
    
  return 0;
}

我们想看懂elf格式的目标文件,可以用readelf工具,带上选项-s,就可以显示符号表啦

readelf -s test.o

其中,我们的全局变量和函数都在符号表中

所以,符号汇总就是把我们代码中的函数,全局变量进行汇总

形成符号表就是用其地址和名字形成一个表格,比如下面我画的这个

 (此时3个符号都是有效地址。就是说我们能通过地址找到这个符号)


我们再给一波例子

这次我们给两个文件

add.c

int add(int x,int y)
{
    return x+y;
}

test.c

//声明外部符号
extern int add(int x,int y);

int main()
{
  int x=0;
  int y=10;
  int ret=add(x,y);
    
  return 0;
}

由于每个源文件都会单独得到一个目标文件,所以这次我们有两个目标文件

add.o的符号表

test.c的符号表

这个时候就不一样了,由于add函数在test.c只有一个声明,所以它的地址在哪,我不到啊

所以只能给一个无效地址0x000


然后链接阶段会做两个事

1是合并段表

刚才我们说到生成的目标文件是elf格式,所谓elf格式就是文件分为好几段

然后我们的可执行程序.exe也是elf格式,合并段表就是将目标文件中相同的段合并到一起

(常见的段有哪些请参考《程序员的自我修养》第451页)

2是符号表的合并和符号表的重定位

这个时候就用用到我们上面的第二个例子了,两个目标文件都有add的地址,用哪个?

当然是保留有效地址,所以最终的符号表是这样的

(最终就可以找到add函数的地址了)

另外,如果我们删掉add.c中的内容,运行起来就找不到add函数了,就会报所谓链接错误


运行环境

其中,程序的执行分为4步

1 将程序加载至内存中(本质应该是加载至内存的代码段),再操作系统的环境中,这个事情由操作系统做。在独立的环境中,程序的下载必须手工安排。比如单片机

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

3 开始执行程序代码,这个时候程序将使用一个运行时堆栈(每一次函数调用时,都会开辟一块空间,也叫建立函数栈帧),存储函数的局部变量和返回地址,程序内部也可以使用静态内存,存储静态内存的变量在整个运行过程中一直保留他们的值

 这里举个例子画个图给大家具体看一下函数栈帧

#include<stdio.h>

int add(int x, int y)
{
	return x + y;
}

int main()
{
	int a = 10;
	int b = 20;

	int ret = add(10, 20);

	return 0;
}

首先,函数所在的栈区具有栈的特性,即FILO(先进后出),main函数比add函数先定义,所以

main函数的地址在下面,a先定义,先入栈,b后定义,b再入栈

下来在定义ret那行调用了add函数,所以建立add的栈帧,并进行传参

当add函数返回时,为他开辟的栈帧就被回收,

最后main函数执行完时,main的空间也回收了。

4 终止程序,main函数可能正常终止,也可能异常终止(比如程序有错误或收到某些信号等,暂时先不用关心)


三、预处理详解

1 预处理符号

指 预处理阶段就被处理的 已经定义好的这种符号(底层是#define),可以直接用

这些东西在日志中还是非常常用的

__FILE__进行编译的源文件(绝对路径)
__LINE__当前所在行号
__DATE__日期
__TIME__时间
__FUNCTION__当前所在函数名
__STDC__

如果编译器支持ANSI C

返回1,否则表示未定义

我们简单的用代码演示一波

int add(int x,int y)
{
printf("%s\n", __FUNCTION__);

return x+y;
}

int main()
{
add(1,2);

	printf("当前所在行号:%d,%s\n",__LINE__,__FUNCTION__);
	printf("所编译的源文件;%s",__FILE__);
	printf("时间:%s 日期:%s\n",__TIME__,__DATE__);
	
	//printf("%d ",__STD__);

return 0;
}

显然,当打印__STDC__时报错为未定义的标识符

所以VS不支持ANSI C

但是gcc是支持的


2 #define定义符号

我们可以用#define定义各种东西

例:

#define m 100
//数字

还可以是关键字

#define m 100
#define reg register

int main()
{
	reg int num=m;
return 0;
}

还可以是一段代码

#define do_forever for(;;)

int main()
{
	//reg int num=m;
	do_forever;

return 0;
}

还有更离谱的

#define CASE break; case
//相当于在写case语句的时候自动带上break

int main()
{
    int n=0;
    switch(n)
    {
        CASE 1:
        CASE 2:
        CASE 3:
    }
}

如果要定义的东西过长,我们可以拆成几行写,除了最后这一行外,每行的后面加一个"\"

称为续行符

例:

#define DEBUG_PRINT printf("file:%s\tline:%d\t\
							date:%s\ttime:%s\n",\
							__FILE__,__LINE__,__DATE__,__TIME__)

现在有这么个问题,#define 的东西能不能加分号?

#define m 100;

int main()
{
	int a=m;

return 0;
}

 能是肯定能,因为他底层是替换

显然在替换之后变成了

int a=100;;

但是我们不建议,因为有可能会出错,毕竟你多了一个分号


3 #define定义宏

#define机制包括了一个规定,允许讲参数定义至文本中,这种方式通常称为宏/定义宏

宏的声明方式

# define name(参数列表) 内容

注意这波name必须和参数列表的左括号紧挨,不能有空格

例:

#define SQUARE(X) X*X

void test5()
{
	printf("%d ", SQUARE(3));
}

啊但是

void test5()
{
	printf("%d ", SQUARE(3+1));
}

如果是这样将打印什么呢?会是16吗?

由于宏是直接替换,所以3+1并不会先计算再传参,而是先传参再计算

传上去就变成了 3+1*3+1,结果显然是7,因为乘法优先级高于加法,这显然不合预期

我们加两个括号

#define SQUARE(X) (X)*(X)

总结:由于恶心的优先级问题,我们在定义宏时往往要加很多括号


#define替换规则

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

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

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

#define MAX(X,Y) ((X)>(Y)?(X):(Y))
#define M 100

int main()
{
	int max = MAX(101, M);

	return 0;
}

在这个例子中,M首先被替换成100,然后MAX被替换成我们定义的宏

注意:

1 宏参数和#define定义中可以其他#define定义的常量,但是对于宏,不能递归

就比如在上面的例子中,M就时MAX的参数。

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

例:

#define M 100

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


	return 0;
}

在这个例子中,printf的第一个参数是const char*也就是字符串类型,所以其中的M并不会被识别到,而后面的M会被识别到


#和##

#和##可以把参数插入字符串中

先补充一点

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

相比第一行,第二行有两个字符串,但是这两个字符串会连接到一起

再例:

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

这三个的功能是很类似的,所以写三个printf就太冗余了

但是用函数又是不好解决的

我们传参要传a,b,c的值,但是我们不好传字符

所以我们可以试试宏

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

int main()
{
	int a = 10;
	PRINT(a);
	int b = 20;
	PRINT(b);
	int c = 30;
	PRINT(c);


	return 0;
}

但是这么写直接报错了

此时我们需要用到#

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

此时#的作用不是替换,#X会变成这个参数名a对应的字符串,达到了把参数插入字符串的效果。


 ##用于把两个符号连成一个符号

但是这样的连接必须产生一个合法的标识符,否则其结果就是未定义的

例:

#define CAT(X,Y) X##Y
int main()
{
	int YiGang101 = 100;
	printf("%d\n", CAT(YiGang, 101));


	return 0;
}


带副作用的宏参数

例:

int a = 1;
	int b = a + 1;//b=2,a=1

	int b = ++a;//b=2,a=2

显然,++a是有副作用的,因为它不仅改了a,还改了b,

如果像++a这样的宏参数在宏的定义中出现了不止一次,那么在你使用这个宏的时候

就会有危险

例:

#define MAX(X,Y) ((X)>(Y)?(X):(Y))

int main()
{
	int a = 5;
	int b = 8;

	int m = MAX(a++, b++);
	//printf("%d %d", a, b);
}

如果我们不进行打印,通过调试判断,我们会发现a=6,b=9

经过替换后变成了这样

	int m = ((a++) > (b++) ? (a++) : (b++));

由于a=5,b=8,5<8,所以返回b++(此时的b++是后置,所以不执行),此时已经算判断过一次了,所以a=6,b=9

如果这时我们再打印a,b,b++就会执行

	printf("%d %d", a, b);


宏和函数的对比

宏通常用于执行比较简单的逻辑,比如求两个数的最大值

#define MAX(X,Y) ((X)>(Y)?(X):(Y))

如果用函数来搞,是这样的

int compare(int x, int y)
{
	return x > y ? x : y;
}

同样的逻辑为什么用宏而不用函数?

从底层来看,宏转汇编后长度远小于函数,所以宏更快,而且函数还有压栈的开销

在力扣的题解中为了追求速度很多小的函数会被定义成宏

而且宏无关类型,而函数必须是具体类型

宏还可以传类型,这是函数做不到的

例:

 在使用malloc开辟空间时一般要这么用

malloc(10*sizeof(int));

想要

malloc(int,10);

但是不能传类型,我们可以定义一下宏

#define MALLOC(num,type) (type*)malloc(num*sizeof(type))

int* p =  MALLOC(10, int);

但是宏也有缺点

1 每次使用宏的时候,一份宏的代码就插入到程序中

除非宏很短或使用次数很少,否则会大幅增长程序的长度

这个要和上面的区别开,这并不意味着宏很慢,而是因为宏时替换,所以每次用的宏

就会直接被替换,导致程序长度变长

2 宏没法调试

3 宏由于没有类型,所以不够严谨

4 宏可能会带来运算符优先级的问题,导致程序出错

 这很好理解,我们定义宏的时候经常需要猛加括号


另外,C++的大佬针对于宏的缺点搞出了内联inline,在兼顾了宏的优点时几乎没有什么缺点

而且查阅C Primer Plus后发现C99已经引进了内联

再另外,C99/C11为宏提供了类似printf中的可变参数,使宏参数支持可变宏参数,

还有C11提供的泛型选择关键字_Generic和宏结合起来也是确实听不错的

大家有兴趣可以自己下来看一看,速通C语言系列先不做过多介绍


4 命令约定

一般来讲函数和宏的使用语法相似,所以语言本身没法区别

,平时的使用习惯为

宏名全部大写,但是函数不全部大写


5 命令行参数

许多C的编译器提供了一种能力,允许在命令行中定义符号,用于启动编译过程

需使用gcc的-D选项

我们在gcc上演示一波

#include<stdio.h>

int main()
{
	int arr[m]={0};
	for(int i=0;i<m;i++)
	{
		arr[i]=i;
	}

	for(int j=0;j<m;++j)
	{
		printf("%d ",arr[j]);
	}

	
return 0;
}

正常编译肯定是会报错的

但是如果我们用一下-D选项

gcc test.c -o test -D m=10 -std=c99

 

 (成功运行)


6 条件编译

在编译的时候我们如果是否编译/放弃一条语句是很方便的,因为我们有条件编译。

简而言之就是满足条件就编译,不满足就放弃

使用场景通常是库的实现中的版本控制和跨平台

比如说如果是这个版本,就编译这一段,如果是另外一个版本,就编译下一段

再比如说线程库对于Linux下是pthread原生线程库,但是windows下就是windows自己的线程库

C++11的线程库为了兼容两个平台,其底层就是条件编译加上调用各自的线程接口。

例:

int main()
{
#ifdef PRINT
	printf("SunsetShimmer\n");
#endif

	return 0;
}

由于我们没有定义PRINT,所以不会打印

#define PRINT

int main()
{
#ifdef PRINT
	printf("SunsetShimmer\n");
#endif

	return 0;
}

(成功打印)


常见的条件编译指令

单分支

#if 常量表达式

#endif

例:

#if 1

	printf("hehe");
#endif

运行之后

如果换成



#if 0

	printf("hehe");
#endif

则不能打印

由此我们得到了一种很装杯的注释一段代码的方式,只要我们将要注释的代码最前面加上

#if 0 最后加上#endif,便能完成注释的操作,也是确实挺不错的


多分支

#if 常量表达式


#elif  常量表达式


#else


#endif

简单举个例子

void test1()
{
#if 1==1
	printf("MoFaMaoMi");

#elif  1==2
	printf("Nijiejiede");

#else
	printf("wochihaol");

#endif
}


判断是否被定义

例:

 第一种是如果定义了xxx,就执行以下语句

其写法为

#ifdef TEST
	printf("test\n");
#endif

#if defined TEST
	printf("test\n");
#endif

还有一种是反的,如果未定义xxx,就执行以下语句

#ifndef TEST
	printf("test\n");
#endif

#if! defined TEST
	printf("test\n");
#endif

另外,我们还有一个可以移除宏定义的指令

#undef

简单写个代码

#define M 10
void test4()
{
	printf("%d ", M);
#undef M
	//printf("%d ", M);

}

 此时能打印

啊 但是

#define M 10
void test4()
{
	printf("%d ", M);
#undef M
	printf("%d ", M);

}


嵌套指令

以上条件指令均可嵌套

例:这是一段简易的处理跨平台的代码

#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

7 文件包含

包含的文件具有两种

1 本地文件 #include"filename"

2 库文件     #include<filename>

其中两种不同的包含方式本质其实是查找策略的区别

<>表明直接去库的目录下找,找不到,就报错

VS下的标准头文件路径为C:\Program Files(x86) \Microsoft visual studio12.0 \ vc \include

Linux下的gcc是 /user/include

""则是先去你项目源文件所在目录查找,如果找不到,再去标准库中查找

所以按理来说,库文件是可以用""来包的,只不过他会先去项目源文件所在目录查找,再去标准库中查找,但是这样就太慢了,我们不推荐。


嵌套文件包含

嵌套文件是一种在项目中很常出现的情况

例如现在comm.h和comm.c是公共模块

现在程序员易刚写了test1.h和test1.c,其中test1.c包了头文件comm,h

同时程序员志明写了test2.h和test2.h,其中test2.c包了头文件comm,h

现在又有程序员紫瑶写了test.c,他同时包了test1.h和test2.h(如图所示)

(只看图的话,很像,很像啊,经典的菱形继承,不过这里并没有所谓的数据冗余和数据二义性问题)

众所周知,我们包的头文件在预处理阶段就会展开,所以包几次头文件就会展开几次

对于很长的头文件多次展开会造成代码冗余的问题

我们有两种做法保证头文件只包一次

#pragma once

 还有一种较为麻烦的做法

#ifndef __TEST_H__
#define __TEST_H__


……

#endif // !__TEST_H

显而易见,如果没包含头文件(test.h),就包含(执行下一句),如果包了,第二句就不执行

一个优秀的头文件应该有如上任意一种形式的操作


8 其他预处理指令

其他预处理指令#line #error #pragma以及别的关键字大家可以下来查查资料,没啥可讲的


速通C语言系列结束#


 总结

 做总结,这是速通C语言系列的最后一篇,编译链接里的陌生知识可能会劝退很多人,不过大家

不用着急,可以留到以后再看,预处理相关的东西其实还是相对比较简单的。

水平有限,还请各位大佬指正。如果觉得对你有帮助的话,还请三连关注一波。希望大家都能拿到心仪的offer哦。

每日gitee侠:今天你交gitee了嘛

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

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

相关文章

STM32 NAND FLASH知识点

1.NAND FLASH的简介 NAND FLASH 的概念是由东芝公司在 1989 年率先提出&#xff0c;它内部采用非线性宏单元模式&#xff0c;为固态大容量内存的实现提供了廉价有效的解决方案。 NAND FLASH 存储器具有容量较大&#xff0c;改写速度快等优点&#xff0c;适用于大量数据的存储&…

VR 全景模式OpenGL原理

VR 全景模式OpenGL原理 VR 全景模式原理 VR 全景模式原理将画面渲染到球面上&#xff0c;相当于从球心去观察内部球面&#xff0c;观察到的画面 360 度无死角&#xff0c;与普通播平面渲染的本质区别在渲染图像部分&#xff0c;画面渲染到一个矩形平面上&#xff0c;而全景需…

字节跳动发布SDXL-Lightning模型,支持WebUI与ComfyUI双平台,只需一步生成1024高清大图!

字节跳动发布SDXL-Lightning模型,WebUI与ComfyUI平台,只需一步生成1024高清大图,需要的步数比 LCM 更低了! 什么是SDXL-Lightning: SDXL-Lightning 是一种快速的文本到图像生成模型。SDXL-Lightning模型的核心优势在于其创新的蒸馏策略,它可以通过几个步骤生成高质量的 1…

红黑树的简单介绍

红黑树 红黑树的概念 红黑树&#xff0c;是一种二叉搜索树&#xff0c;但在每个结点上增加一个存储位表示结点的颜色&#xff0c;可以是Red或Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制&#xff0c;红黑树确保没有一条路径会比其他路径长出俩倍&#x…

服务器出现故障如何恢复数据?

服务器数据恢复案例之服务器raid6中3块硬盘离线导致阵列崩溃的数据恢复案例 服务器故障&#xff1a; 服务器中有一组由6块盘组建的 RAID6&#xff0c;这台网站服务器上运行MYSQL数据库和存放其它类型的文件。该组raid中有两块磁盘离线&#xff0c;管理员没有及时更换磁盘&#…

#QT(智能家居界面-界面切换)

1.IDE&#xff1a;QTCreator 2.实验 3.记录 &#xff08;1&#xff09;创建一个新界面&#xff08;UI界面&#xff09; &#xff08;2&#xff09;可以看到新加入一个ui文件&#xff0c;双击打开&#xff0c;设置窗口大小与登录界面一致 &#xff08;3&#xff09;加入几个PUS…

Linux 运维:CentOS/RHEL防火墙和selinux设置

Linux 运维&#xff1a;CentOS/RHEL防火墙和selinux设置 一、防火墙常用管理命令1.1 CentOS/RHEL 7系统1.2 CentOS/RHEL 6系统 二、临时/永久关闭SELinux2.1 临时更改SELinux的执行模式2.2 永久更改SELinux的执行模式 &#x1f496;The Begin&#x1f496;点点关注&#xff0c;…

【CSP试题回顾】201312-3-最大的矩形

CSP-201312-3-最大的矩形 解题思路 1. 遍历所有可能的矩形高度&#xff1a; 通过遍历所有矩形高度来找到最大的矩形&#xff0c;即对每个可能的高度 it&#xff08;从直方图中的最小高度到最大高度 heightMax&#xff09;&#xff0c;代码将尝试找到在这个高度或以上的最长连…

Linux常用命令(超详细)

一、基本命令 1.1 关机和重启 关机 shutdown -h now 立刻关机 shutdown -h 5 5分钟后关机 poweroff 立刻关机 重启 shutdown -r now 立刻重启 shutdown -r 5 5分钟后重启 reboot 立刻重启 1.2 帮助命令 –help命令 shutdown --help&#xff1a; ifconfig --help&#xff1a;查看…

Unity 协程(Coroutine)到底是什么?

参考链接&#xff1a;Unity 协程(Coroutine)原理与用法详解_unity coroutine-CSDN博客 为啥在Unity中一般不考虑多线程 因为在Unity中&#xff0c;只能在主线程中获取物体的组件、方法、对象&#xff0c;如果脱离这些&#xff0c;Unity的很多功能无法实现&#xff0c;那么多线程…

(MATLAB)第十二章-数列与极限

目录 12.1 数列 12.1.1 数列求和 1. 累计求和函数sum() 2. 忽略NaN累计求和函数 nansum() 3. 求此元素位置之前的元素和函数cumsum() 4. 求梯形累计和函数cumtrapz() 12.1.2 数列求积 1. 元素连续相乘函数 prod() 2. 求累计积函数 cumprod() 3. 阶乘函数 ffactorial(n…

bun build

Bun 的快速原生打包器现已进入测试版阶段。可通过 bun build CLI 命令或 Bun.build() JavaScript API 使用。 bun build ./index.tsx --outdir ./build await Bun.build({entrypoints: [./index.tsx],outdir: ./build, }); 速度很快。下面的数字代表 esbuild 在 three.js 基…

Crossbar阵列的电路结构及其基本原理

忆阻器Crossbar阵列是一种先进的神经网络硬件实现技术&#xff0c;它利用忆阻器的物理特性来模拟神经网络中的突触连接&#xff0c;为人工智能和机器学习应用提供了一种高效、低能耗的计算平台。本文将深入探讨忆阻器Crossbar阵列的基本原理及其在Read&#xff08;读取&#xf…

Studio One 6永久激活版 附完整图文安装破解教程

Studio One 6是一款功能强大的音乐制作和录音软件&#xff0c;专为Mac操作系统设计。它提供了多轨录音和混音、MIDI音乐制作、实时效果和处理、VST插件支持以及高级编辑和编排等丰富的功能。无论是专业音乐制作人还是音乐爱好者&#xff0c;都可以使用Studio One 6来创建和编辑…

Android m/mm/mmm/make编译模块

一.编译成模块的前置条件 Android编译环境初始化完成后&#xff0c;我们就可以用m/mm/mmm/make命令编译源代码了。lunch命令其实是定义在build/envsetup.sh文件中的函数lunch提供的。与lunch命令一样&#xff0c;m、mm和mmm命令也分别是由定义在build/envsetup.sh文件中的函数…

istio pod不启动及访问报RBAC错误问题解决

istio pod不启动问题解决 在kubernetes集群中安装istio之后&#xff0c;在创建的depoyment中已经使用了注入注解sidecar.istio.io/inject: true’配置&#xff0c;但是istio pod不创建&#xff0c;代码示例如下 kind: Deployment apiVersion: apps/v1 metadata:name: name-an…

Linux 操作系统概述

GNU计划 GNU --"GNUs Not UNIX" 建立一个自由、开放的UNIX操作系统&#xff08;Free UNIX&#xff09; GNU 通用公共许可证&#xff08;General Public License&#xff0c;GPL&#xff09; ”四项基本自由“ 按照自己的意愿自由地运行该软件自由地学习并根据…

高级大数据技术 实验一 scala编程

​ 高级大数据技术 实验一 scala编程 写的不是很好&#xff0c;大家多见谅&#xff01; 1. 计算水仙花数 实验目标; &#xff08;1&#xff09; 掌握scala的数组&#xff0c;列表&#xff0c;映射的定义与使用 &#xff08;2&#xff09; 掌握scala的基本编程 实验说明 …

【系统需求分析报告-项目案例直接套用】

软件需求分析报告 软件开发要求项目建设内容物理设计安全系统设计安全网络安全设计应用安全设计用户安全管理性能设计稳定性设计安全性设计兼容性设计易操作性设计可维护行设计 软件开发全套精华资料过去进主页领取。

博弈论实用原理浅谈及题目实战【算法竞赛】

一、前言 本篇记录博弈论一些常见原理、做题技巧。 之前没有了解学习过博弈论&#xff0c;这篇文章可以当作记录学习笔记了。 二、初识博弈论 博弈论题目在竞赛中我感觉其实并不少见&#xff0c;只是需要技巧性很强&#xff0c;找到规律打代码很简单&#xff0c;而找不到基本上…