梦开始的地方—— C语言预处理+编译过程

news2025/1/12 2:42:56

文章目录

  • C语言程序的编译(预处理)
    • 1.编译和链接
      • 1) 编译的几个阶段
        • 预编译阶段
        • 编译阶段
        • 汇编阶段
      • 2) 链接
    • 2. 预处理
      • 1) 预定义符号
      • 2) #define
      • 3) #和##
      • 4) 带副作用的宏参数
      • 5) 宏和函数对比
    • 3. 常见预处理命令
      • 1) #undef
      • 2) 命令行定义
      • 3) 条件编译
      • 4) 文件包含
      • 5) 实现offsetof


C语言程序的编译(预处理)

在C语言标准规定在C的任何一种实现中,存在两个不同的环境。

第一种是翻译环境,在这个环境中被转换为可执行的机器指令,第二种是执行环境,它用于实际执行代码。

1.编译和链接

假设有一个test.c的源代码,它需要经过编译——>链接——>可执行程序

在这里插入图片描述

如果有多个.c的源代码文件,它们每个都会单独的进行编译再通过链接器最后变成可执行程序

在这里插入图片描述

  • 组成一个程序的每个源文件通过编译转换成目标代码
  • 每个目标文件又链接器捆绑在一起,形成一个单一而完整的可执行程序
  • 链接器同时也会引入C库函数中任何被该程序所用到的函数,链接器还可以搜索程序员个人的程序库,将其需要的函数也链接到程序中

在这里插入图片描述

1) 编译的几个阶段

注意:此时我的环境是Centos7.6的gcc编译器

代码:

test.c

#include <stdio.h>
#define MAX 666666
//声明外部函数
extern int add(int x, int y);
int main()
{
	int a = 10;
	int b = 20;
    int tmp = MAX;
	printf("%d\n", a + b);

	return 0;
}

add.c

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

翻译环境中的编译又可以分为3个阶段

  • 预编译
  • 编译
  • 汇编

预编译阶段

在预编译期间编译器会做那么几件事

  1. 头文件的包含
  2. 注释的删除
  3. #define定义符号的替换
  4. 预处理指令

我们在Linux上使用gcc -E test.c > test.itest.c文件进行预编译,预编译之后立马就会停下来,预编译的解结果保存到test.i文件中方便查看

我们会发现,预编译后。我们的写的注释不见了,写的头文件也不见了,多了一堆函数声明(这只是部分截图)。

在这里插入图片描述

我们发现几点

  1. #include <stdio.h>头文件不见了
  2. 写的注释被删除了
  3. #define定义的MAX也被替换了

我们可以验证以下头文件的包含,在我的Linux系统中的/usr/include/stdio.h保存了stdio.h文件,查看后发现里面的函数信息的确是我们上面所看到了。所以预处理接段就会把头文件中的内容包含到源文件中。

在这里插入图片描述

编译阶段

在编译阶段会把C代码翻译成汇编代码,做这么几件事

  1. 语法分析
  2. 词法分析
  3. 语义分析
  4. 符号汇总

语法分析简单就是检查代码是否有语法错误

词法分析:把C语言代码一个个拆分开来,建立一个语法树之类的东西

语义分析:简单来说就是把C语言的代码怎么转换成对应的汇编代码,C语言的额一个语义

通过gcc -S test.ctest.c文件进行编译,编译完后会停下来将结果保存到test.s文件中。test.s中保存的就是汇编代码。

在这里插入图片描述

符号汇总是编译阶段一个非常终要的过程。

符号汇总就是把文件中重要的符号给提取出来

我们简单修改一下test.c文件

#include <stdio.h>
#define MAX 666666
//声明外部函数
extern int add(int x, int y);
int count = 0;
void print()
{
    
}
int main()
{
	int a = 10;
	int b = 20;
        int tmp = MAX;
	printf("%d\n", a + b);

	return 0;
}

在Linux文件下通过命令gcc -c test.c生成一个test.o的目标文件对于前面所讲的windows中的.obj文件

再通过readelf -s test.o命令查看里面的内容发现,只记录另外关键的一些全局的函数和变量

在这里插入图片描述

再来看add.c的源文件,这个文件中只有一个add函数

在这里插入图片描述

把它们的符号进行汇总,把主要的符号进行汇总,就会符号汇总。

在这里插入图片描述

汇编阶段

再Linux环境下通过命令gcc -c test.stest.s中的汇编代码转换为二进制指令,生成一个test.o的二进制文件

再汇编阶段还会形成符号表,在前面的编译阶段只是将符号进行汇总。而这里汇编阶段会会生成一个.o的文件(windows中是.obj文件),把前面汇总的符号形成一个符号表,符号表中记录的了汇总的符号并给它们分配了一个地址。

注意:main函数里的add只是一个声明,给这个add分配的这个地址是没有任何意义的,相当于就是一个标识符,这函数有没有还是取决去前面是否定义这个add函数

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-p0rmGJpA-在这里插入图片描述

2) 链接

在Linux环境下通过gcc test.o对目标文件进行链接,生成一个a.out的可执行文件(相当于windos中的.exe文件)

在链接期间主要会做那么两件事情

  1. 合并段表
  2. 符号表的合并和重定位

简单来说就是它会把多个.o的目标文件进行链接,因为一个项目编译后会有多个目标文件,这些文件又没有任何关系,通过它们的函数声明进行链接,把这些文件都关联起来。

如果一个函数没有被定义就会出现的链接错误(无法解析的外部命令)

在这里插入图片描述

和并段表和符号表,简单理解就是多个目标文件中相同的段只保留一个,比如合并符号表保留add函数的符号和地址。链接期间就是检查外部的一些函数和符号定义是否合法。

在这里插入图片描述

链接完毕后就生成了可执行程序。

图解过程

在这里插入图片描述

运行环境

  1. 编译完后的可执行程序,程序必须加载到内存中,在有操作系统的环境中,这个操作一般由操作系统完成。在独立的环境中,程序的载入必须由手工安排,也可能是通过可执行代码置入只读内存来完成
  2. 程序开始执行,接着就要调用main函数
  3. 开始执行程序代码,这个时候程序将为函数开辟栈帧,存储函数的局部变量何返回地址。程序同时开始也可以使用静态(static)内存。存储在静态内存中的变量在整个执行过程一直保留着它们的值
  4. 终止程序,正常终止main函数,也有可能意外终止

2. 预处理

1) 预定义符号

C语言中由一些预定义的符号,它们分别保存这一些信息,它们也是在预处理阶段被直接替换的。

__FILE__ //进行编译的源文件
__LINE__ //文件当前的行号
__DATE__ //文件被编译的日期
__TIME__ //文件被编译的时间
__STDC__ //如果编译器遵循ANSI C,其值为1,否则未定义
#include <stdio.h>

int main()
{
	printf("进行编译的源文件: %s\n",__FILE__);
	printf("文件当前的行号: %d\n",__LINE__);
	printf("文件被编译的日期: %s\n",__DATE__);
	printf("文件被编译的时间: %s\n",__TIME__);

	return 0;
}

在vs2019中没有__STDC__没有定义整个符号,说明vs2019对ANSI C的支持是不好的,而我在Linux环境下正常输出1说明在LInux环境下是严格遵循C语言标准的。

2) #define

通过#define可以定义标识符,也可以定义宏

#fefine定义标识符

#include <stdio.h>
#define MAX 100000
#define STR "hello"
#define PRINTLN printf("\n")
int main()
{
	printf("%d", MAX);
	PRINTLN;
	printf("%s", STR);
	
	

	return 0;
}

运行结果

100000
hello

#define定义宏的时候,后面要不要加分号;?

建议是不加,加上分号分号也会被替换过去,需要的时候加就可以了。

#define 定义宏

#define 机制包括了一个规定,允许把参数替换到文本中,这种实现通常称为宏(macro)或定义宏(define macro)。

写一个宏来计算两个数的和

#include <stdio.h>
#define ADD(x,y) x+y
int main()
{
	int a = 10;
	int b = 20;
	printf("%d\n", add(a, b));
	
	

	return 0;
}

这中写法是存在问题的,当写出这样的代码的时候就会出现问题

#include <stdio.h>
#define ADD(x,y) x+y
int main()
{
	int a = 10;
	int b = 20;
	printf("%d\n", add(a, b)*add(a,b));



	return 0;
}

打印结果

230

这并不是我们想要的结果,因为在替换后发生了优先级的问题

printf("%d\n", ADD(a, b)*ADD(a,b));
	//等价于printf("%d\n", 10+20*10+20);

解决方法就是给宏的每一个参数加上括号,整体再加上括号

#include <stdio.h>
#define ADD(x,y) ((x)+(y))
int main()
{
	int a = 10;
	int b = 20;
	printf("%d\n", ADD(a, b)*ADD(a,b));


	return 0;
}

所以以后用宏求这种数值表达式的时候,最后把每一个参数加上括号,避免再使用宏。

define替换宏的规则

在程序中进行宏替换的时候,需要涉及到以下几个步骤

  1. 在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号
  2. 替换文本随后被插入到程序中原来文本的位置,对于宏,参数名被他们的值替换
  3. 最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上述处理过程

注意

  1. 宏参数和#define 定义中可以出现其他#define定义的变量。但是对于宏,不能出现递归
  2. 当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索

比如下面这种写法是没有问题的

#define MAX 1000
#define add(x,y) ((x)+(y))*MAX

这种宏里写#define定义的符号没有问题,但宏是不支持自己调用自己的

3) #和##

如何把参数插入到字符串中?

解答这个问题前先来看一下C语言的另外一种字符串写法


#include <stdio.h>

int main()
{
	char* str = "hello" "world;";
	printf("%s\n", str);
	printf("123" "abc\n");
	return 0;
}

打印结果

helloworld;
123abc

把两个字符串写一起,在编译阶段它们会自动拼接成一个字符串。

现想完成这么一个打印,把一个变量的变量名和值打印出来,且插入在字符串中,我们发现这并不好实现。这个时候就可以用到宏。

#include <stdio.h>

int main()
{
	float f = 4.5f;
	printf("the value of f is %f\n", f);

	int a = 10;
	printf("the value of a is %d\n", a);

	int b = 20;
	printf("the value of b is %d\n", b);

	return 0;
}

通过宏定义可以把代码写成这样,也能达到上面代码的效果,避免了代码的冗余。

#include <stdio.h>
#define PRINT(data, format) printf("the value of "#data" is %"#format"\n",data)
int main()
{
	float f = 4.5f;
	PRINT(f, f);

	int a = 10;
	PRINT(a, d);


	int b = 20;
	PRINT(b,d);

	return 0;
}

#data等价于“data”,在预编译期间就会被替换成对于的字符。

##的作用

##可以把位于它两边的符号合成一个符号。 它允许宏定义从分离的文本片段创建标识符。

#include <stdio.h>
#define APPEND(str,number) str##number
int main()
{
	int day100 = 2022;
	printf("%d\n", APPEND(day,100));

	return 0;
}

运行结果

2022

4) 带副作用的宏参数

当宏参数的定义出现超过一次的时候,如果参数带有副作用,那么在使用这个宏的时候就可能出现危险,导致不可预测的后果。

比如下面这个代码救会出现副作用

#include <stdio.h>
#define MAX(x,y) ((x)>(y)?(x):(y))
int main()
{
	int a = 10;
	int b = 20;
	printf("%d\n", MAX(a++, b++));
	printf("a=%d b=%d\n", a, b);

	return 0;
}

这里的**b++**被执行了两次,相当于替换后的表达式就是

printf("%d\n", ((a++) > (b++) ? (a++) : (b++)));

这就是带有副作用的宏参数

5) 宏和函数对比

宏通常用来做一些简单的运算,比如我们求两个数的和

#define ADD(x,y) ((x)+(y))

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

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

宏对比函数的优势

  1. 用调用函数和从函数返回的代码可能比实际执行这么一个小型计算工作所需要的时间更多,所以宏比函数的规模和速度上更胜一筹

    来看一段代码对比

    这是通过宏来计算两数之和的汇编代码

在这里插入图片描述

然后再看下通过函数计算两数之和代码转换为汇编代码的代码量

在这里插入图片描述

我们发现通过宏实现代码量只有7条,而通过函数实现则由十几行汇编代码。

宏在预编译期间就把定义的代码进行替换后面进行运算就可以了,而函数则存在一个调用+运算+返回三个过程。

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

    上面的代码宏能计算各种类型的和,而函数只能只能计算整形的和。

    再举个列子,我们常用的malloc函数用来开辟空间,我们可以写一个宏来开辟空间,而传递类型函数是做不到的。

    #include <stdio.h>
    #include <stdlib.h>
    #define MALLOC(size,type) (type*)(malloc(sizeof(type)*size)) 
    
    int main()
    {
    	int* arr = MALLOC(10, int);
    	int i = 0;
    	for (i = 0; i < 10; i++)
    	{
    		arr[i] = i;
    	}
    	for (i = 0; i < 10; i++)
    	{
    		printf("%d ", arr[i]);
    	}
    	return 0;
    }
    

宏对比函数的劣势

  1. 每次使用宏的时候,一份宏定义的代码将插入到程序中。除非宏比较短,否则可能大幅度增加程序的长度
  2. 宏是没法调试的
  3. 宏由于类型无关,也就不够严谨
  4. 宏可能会带来运算符优先级的问题,导致程容易出现错

对比总结

#define定义宏函数
代码每次使用时,宏代码都会被插入到程序中。除了非常小的宏之外,程序的长度会大幅度增长函数代码只出现于一个地方;每次使用这个函数时,都调用那个地方的同一份代码
执行速度更快存在函数的调用和返回的额外开销,所以相对慢一些
操 作 符优先级宏参数的求值是在所有周围表达式的上下文环境里,除非加上括号,否则邻近操作符的优先级可能会产生不可预料的后果,所以建议宏在书写的时候多些括号函数参数只在函数调用的时候求值一次,它的结果值传递给函数。表达式的求值结果更容易预测
带 有 副 作参数可能被替换到宏体中的多个位置,所以带有副作用的参数求值可能会产生不可预料的结果函数参数只在传参的时候求值一次,结果更容易控制
参 数宏的参数与类型无关,只要对参数的操作是合法的,它就可以使用于任何参数类型函数的参数是与类型有关的,如果参数的类型不同,就需要不同的函数,即使他们执行的任务是不同的
调式宏是不方便调试的函数是可以逐语句调试的
递归宏是不能递归的函数是可以递归的

3. 常见预处理命令

1) #undef

这条指令用来一处一个宏定义

#include <stdio.h>
#define MAX 1000

int main()
{
	int tmp = MAX;
#undef MAX
	int ret = MAX;//报错

	
	return 0;
}

2) 命令行定义

许多C 的编译器提供了一种能力,允许在命令行中定义符号。用于启动编译过程。 例如:当我们根据同一个源文件要编译出不同的一个程序的不同版本的时候,这个特性有点用处。

#include <stdio.h>


int main()
{
	int arr[SIZE] = { 0 };
	int i = 0;
	for (i = 0; i < SIZE; i++)
	{
		arr[i] = i;
	}
	for (i = 0; i < SIZE; i++)
	{
		printf("%d ", arr[i]);
	}

	
	return 0;
}

再Linux64位环境下通过命令gcc -D SIZE=10 test.ctest.c文件进行编译,生成a.out文件,运行就是一个大小为10的数组

[root@aliyun code]# ./a.out 
0 1 2 3 4 5 6 7 8 9

3) 条件编译

在编译一个程序的时候我们如果要将一条语句(一组语句)编译或者放弃是很方便的。因为我们有条件编译指令。
比如说:
调试性的代码,删除可惜,保留又碍事,所以我们可以选择性的编译

#include <stdio.h>
#define DEBUG 1

int main()
{
	printf("hello world!\n");
#ifdef DEBUG
	printf("test");
#endif // DEBUG


	
	return 0;
}

如果把DEBUG设置为0,打印test的那一行代码就不会进行编译

当然也可以多个分支

#include <stdio.h>
#define DEBUG 0

int main()
{
	int a = 0;
	printf("hello world!\n");
#if DEBUG
	printf("test");
#elif a
	printf("false");
#else
	printf("haha");
#endif // DEBUG

	return 0;
}

嵌套定义

#include <stdio.h>
#define DEBUG 0

int main()
{
	int a = 0;
	printf("hello world!\n");
#if defined(DEBUG)
	#if 0
	printf("0");
	#elif a-1
	printf("0");
	#else a+1
	printf("1");
	#endif

#endif



	
	return 0;
}

4) 文件包含

我们已经知道, #include 指令可以使另外一个文件被编译。就像它实际出现于 #include 指令的地方一样。
这种替换的方式很简单: 预处理器先删除这条指令,并用包含文件的内容替换。 这样一个源文件被包含10次,那就实际被编译10次 。

头文件被包含方式

  • 本地文件包含

    #include "add.h"
    

    查找方式:先再源文件所在目录查找add.h的头文件,如果头文件未查找到,编译器就像查找库函数头文件一样再标准位置查找头文件,如果找不到就提示编译错误。

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

  • 库文件包含

    #include <stdio.h>
    

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

    “”也可以显示对库中的头文件进行包含,但是这样效率低一点,也不容易区分是本地文件还是库文件。

避免头文件的重复引入

有一个时候头文件多了,可能就会出现重复引入头文件的情况.

比如向这样引入多次

#include "add.h"
#include "add.h"
#include "add.h"
int main()
{	
	return 0;
}

假设add.c的实现是这样的

int Add(int a, int b);

那么预编译后的文件就是这样的,多次引入导致了代码的冗余。

# 1 "test.c"
# 1 "add.h" 1
int Add(int a, int b);
# 2 "test.c" 2
# 1 "add.h" 1
int Add(int a, int b);
# 3 "test.c" 2
# 1 "add.h" 1
int Add(int a, int b);
# 4 "test.c" 2
int main()
{
 return 0;
}

那么入何避免这种情况呢?

那就是条件编译

通过ifndef来判断,ADD_FUNC是否宏定义过,定义过就不在定义

#ifndef ADD_FUNC
#define ADD_FUNC
int Add(int a, int b);
#endif

还有一种更简单的写法,通过#pragma once也可以达到效果。

#pragma once
int Add(int a, int b);

5) 实现offsetof

通过宏可以模拟实现 offsetof

将0转换为一个结构体指针,就认为0就是结构体的地址,再找到其对应的成员变量取出地址再转换为整形,就能求出偏移量。

类似于指针加减,但这里的起始是0就没必要减了。

#include <stdio.h>
#include <stddef.h>
#define OFFSETOF(structName,member) (size_t)(&(((structName*)0)->member))
struct S
{
	char c;
	int i;
	double d;
};
int main()
{	
	printf("%d\n", offsetof(struct S,i));
	printf("%d\n",OFFSETOF(struct S, i));
	
	return 0;
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Spring 中 @Autowired 修饰构造方法时注意事项

代码演示 给定一个类 One&#xff0c;然后看下的几种构造方法什么时候被调用 1、假设现在只有一个默认的空构造方法&#xff0c;代码如下&#xff1a; Component public class One {}然后追踪源码&#xff0c;如下所示&#xff1a; 先拿到所有声明的构造方法 然后挨个判断构…

总结Python设置Excel单元格样式的一切,比官方文档还详细

总结Python设置Excel单元格样式的一切&#xff0c;比官方文档还详细 Python对Excel表格处理非常方便&#xff0c;本文专门对Excel单元格样式设置进行总结&#xff0c;日常用到的设置基本都可以用openpyxl库完成。 创建一个表格 openpyxl是第三方库&#xff0c;如果你还没有安…

如何撰写好的科研论文:摘要(1)

导读 本系列将切片介绍如何写好科研论文&#xff0c;包含了&#xff1a;摘要&#xff0c;背景介绍&#xff0c;方法&#xff0c;结果&#xff0c;讨论等&#xff0c;本文[1]将从摘要开始。 1. 标准 Criteriapointline一般背景听众中的每个人都关心的事情。具体背景从每个人都关…

免费内网穿透工具测评对比,谁更好用 1

文章目录1. 前言2. 对比内容1.1官网主页对比1.2 用户注册对比1.3 用户功能页面对比1.4 客户端对比3. 结语1. 前言 自从接触到内网穿透服务&#xff0c;知道能把自家的电脑、树莓派、NAS等等一堆硬件改造成服务器后&#xff0c;笔者就陷入其中无法自拔&#xff0c;一会儿把树莓…

jmeter接口测试之大家都来我家领豆子

一、测试目的&#xff1a; 2万用户不停请求云豆领取接口时&#xff0c;查看服务器内存占用情况&#xff0c;从而确认服务器内存占用异常的情况是否得到修复。 二、测试策略&#xff1a; 用2万个账号&#xff0c;以每2秒100次请求的速度向服务器发出请求&#xff0c;观察内存…

c#入门-顶级语句和Main方法

程序入口 在你运行程序以后会弹出一个窗口&#xff0c;显示一行文字&#xff1a;Hello world 现在将代码中的所有东西再复制一遍。然后运行&#xff0c;就会得到两行Hello world 显然&#xff0c;我们的程序是写在这里的。 在这里写了什么&#xff0c;什么就会生效。 Main方…

【自动化测试】Pytest+Appium+Allure 做 UI 自动化的那些事

文本主要介绍下 PytestAllureAppium 记录一些过程和经历。 法主要用了啥: Python3 Appium Allure-pytest Pytest Appium 不常见却好用的方法 Appium 直接执行 adb shell 方法 Appium 启动时增加 --relaxed-security 参数 Appium 即可执行类似adb shell的方法 appium -p 4…

短视频账号搭建之Banner图和视频封面

前面在我赢小禾呈序里学了账号名称、头像和个人简介设置&#xff0c;今天把账号搭建的最后两部分一起公开&#xff1a; banner图是你主页上面的这个主图。 同样它的存在可以有三个作用&#xff1a; 第一个作用比较简单&#xff0c;就是让你的主页更好看。 听起来太简单了&am…

【软件测试】测试人在团队中没地位?怎么办?为什么会出现这样的问题?

目录&#xff1a;导读前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09;前言 为什么会出现这样的…

Linux 学习之:如何让代码在后台保持运行

文章目录nohup 命令使用场景使用方法nohup ... &nohup ... > train.log 2>&1 &结束进程参考文章nohup 命令 使用场景 比如我要在服务器里运行如下代码来训练我的深度学习模型&#xff1a; python train.py但是这样运行你一旦合上笔记本电脑或者换个工作环…

java版商城多商家入驻商城 直播带货商城 电子商务

一个好的SpringCloudSpringBoot b2b2c 电子商务平台涉及哪些技术、运营方案&#xff1f;以下是我结合公司的产品做的总结&#xff0c;希望可以帮助到大家&#xff01; 搜索体验小程序&#xff1a;海哇 1. 涉及平台 平台管理、商家端&#xff08;PC端、手机端&#xff09;、买家…

vxe table 虚拟滚动 表格每一行的高度不一致 出现空白

今天在做表格数据时&#xff0c;发现滚动表格会出现空白区域&#xff0c;如图所示 虚拟滚动表格每一行的高度不一致, 导致表格滚动时出现空白区域 然后在查阅资料时发现有设置:row-config"{height: 70}"这种 &#xff0c;试过发现不行 以下这个不可行 <vxe-grid…

论文常用 | FineBI v6.0 新图表 | 箱形图

箱形图&#xff08;Box-plot&#xff09;又称为盒须图、盒式图或箱线图&#xff0c;是一种用作显示一组数据分散情况资料的统计图&#xff0c;因形状如箱子而得名。在各种领域也经常被使用&#xff0c;常见于品质管理。它主要用于反映原始数据分布的特征&#xff0c;还可以进行…

突破重围,攻“新”为上!凯里亚德与郁锦香酒店以创新势能获投资者青睐

近日&#xff0c;汇聚国内众多投资人的锦江酒店(中国区)品牌沙龙会烟台站顺利举行。本次沙龙活动以“齐风鲁韵 锦绘未来”为主题&#xff0c;锦江酒店(中国区)旗下众多优秀品牌共同亮相。凯里亚德酒店与郁锦香酒店在本次活动中向投资人展示了在如今复杂多变的酒店市场中如何以强…

载波层叠调制在多电平变换器及两电平变换器中的应用

1. 载波层叠调制在MMC中的应用 载波层叠调制在MMC中应用广泛。通过上下桥臂的调制波和多个载波进行比较&#xff0c;得到每个桥臂应该投入的模块数。如下图所示&#xff0c;上下桥臂各有4个模块&#xff0c;每个模块的电容电压是uc&#xff0c;直流侧电压是4uc。A相下桥臂的调制…

Qt 模型视图编程之 ItemDataRole

背景 Qt 中的模型视图架构是用来实现大量数据的存储、处理及其显示的&#xff0c;主要原理是将数据的存储与显示分离&#xff1a;模型定义了标准接口对数据进行访问&#xff1b;视图通过标准接口获取数据并定义显示方式&#xff1b;模型使用信号与槽机制通知视图数据变化。 Q…

C语言百日刷题第十四天

前言 今天是刷题第14天&#xff0c;放弃不难&#xff0c;但坚持一定很酷~ 临近期末&#xff0c;集中把模拟卷的编程题都刷一下 C语言百日刷题第十四天前言模拟题&#xff08;一&#xff09;1.设计程序实现比较两数大小2.排序成绩模拟题&#xff08;二&#xff09;1.求最大值…

力扣(LeetCode)138. 复制带随机指针的链表(C++)

模拟 第一趟遍历&#xff0c;在结点的右侧复制映射。第二趟遍历&#xff0c;复制 randomrandomrandom。第三趟遍历&#xff0c;将链表中的映射结点取出作为新链表。 初始链表如图①。 有必要说明&#xff0c;原结点如 111~555 &#xff0c;映射结点就是 1‘11‘~5‘55‘。 复…

学习笔记--截止12.9 CVAT使用方法、STCN代码使用方法、bitahub使用方法

CVAT使用方法&#xff08;12.5-12.7&#xff09; 对学长来说是一个标注数据集的好工具&#xff0c;但对我来说是个新的知识点 使用这个工具&#xff0c;我们要得到一张有蒙层的图片 然后CVAT的使用方法&#xff08;网上居然没有教程&#xff0c;&#xff0c;&#xff0c;官网的…

金山表单结果如何自动通知至钉钉

金山表单内置了丰富的模版&#xff0c;从表单、接龙、问卷、投票&#xff0c;可以满足你各种表单数据数据收集的需求。但是很多用户经常也会有一个痛点&#xff0c;通过金山表单收集的信息&#xff0c;如何才能实时通知企业微信/钉钉/飞书呢&#xff1f; 比如防疫登记、安全复工…