十五.程序环境和预处理
文章目录
- 一.程序翻译环境和执行环境
- 1.ANSI C 标准
- 2.程序的翻译环境和执行环境
- 二.程序编译和链接
- 1.翻译环境
- 2.编译本身的几个阶段
- 3.运行环境
- 三.预处理
- 1.预定义符号
- 2.#define
- (1)#define定义标识符
- (2)#define定义宏
- (3)#define替换规则
- 3.#和##
- (1)#
- (2)##
- 4.#undef
- 5.带"副作用"的宏参数
- 6.宏和函数对比
- 7.命名约定
- 四.命令行编译
- 五.条件编译
- 1.条件编译常量表达式
- 2.多分支的条件编译
- 3.条件编译是否被定义
- 4.条件编译的嵌套
- 六.文件包含
- 1.头文件被包含的方式
- 2.嵌套文件的包含
一.程序翻译环境和执行环境
1.ANSI C 标准
ANSI C是由美国国家标准协会(ANSI)及国际化标准组织(ISO)推出的关于C语言的标准。ANSI C 主要标准化了现存的实现, 同时增加了一些来自 C++ 的内容 (主要是函数原型) 并支持多国字符集 (包括备受争议的三字符序列)。
ANSI C 几乎被所有广泛使用的编译器所支持,且多数C代码是在ANSI C基础上写的。
2.程序的翻译环境和执行环境
ANSI C 的任何一种实现中,存在两种不同的环境:
- 翻译环境:在该环境中,源代码被转换为可执行的机器指令。
- 执行环境:用于实际执行代码。

二.程序编译和链接
1.翻译环境

- 组成一个程序的每个源文件(.c)通过编译过程分别转换成目标代码(.obj)
- 每个目标文件由链接器捆绑在一起,形成一个单一而完整的可执行程序。
- 链接器同时也会引入标准C库函数中任何被该程序所用到的函数,且可以搜索程序员个人的程序库,将其需要的函数也链接到程序中。
举个例子:test.c、add.c、minu.c

2.编译本身的几个阶段
举个例子:
① sum.c
int global_val = 2021;
void print(const char* string) {printf("%s\n", string);
}
② test.c
#include <stdio.h>int main(void) {extern void print(char* string);extern int global_val;printf("%d\n", global_val);printf("Hello,World!\n");return 0;
}
编译阶段为:

解析图如下:

3.运行环境
程序执行过程:
- 程序必须载入内存中。在有操作系统的环境中:程序的载入一般由操作系统完成。在独立环境中:程序的载入必须手工安排,也可能是通过可执行代码置入只读内存来完成。
- 程序的执行便开始。接着便调用 main 函数。
- 开始执行程序代码,这个时候程序将使用一个运行时堆栈(stack),内存函数的局部变量和返回地址。程序同时也可以使用静态(staic)内存,存储与静态内存中的变量在整个执行过程中一直保留他们的值。
- 终止程序。正常终止 main 函数(也有可能是意外终止)。
三.预处理
1.预定义符号
1.__FILE__ //进行变异的源文件
2.__LINE__ //文件当前的行号
3.__DATE__ //文件被编译的日期
4.__TIME__ //文件被编译的时间
5.__STDC__ //如果编译器遵循ANSI C,其值为1,否则未定义
6.__FUNCTION__ //返回所在函数的函数名
在预处理阶段被处理的已经定义好的符号为预定义符号。这些符号是可以直接使用的,是在C语言中已经内置好的。
注意:值得注意的是,__ 为两个下划线!
用法演示:
#include <stdio.h>int main(void) {printf("%s\n", __FILE__); // 返回使用行代码所在的源文件名,包括路径printf("%d\n", __LINE__); // 返回行号printf("%s\n", __DATE__); // 返回程序被编译的日期printf("%s\n", __TIME__); // 返回程序被编译的时间printf("%s\n", __FUNCTION__); // 返回所在函数的函数名return 0;
}
运行结果:

那么这些预定义符号有什么用?
- 如果一个工程特别复杂,这时去调试时可能会无从下手。所以需要代码在运行的过程中记录一些日志信息,通过日志信息分析程序哪里出了问题,再进行排查就如同瓮中捉鳖。
2.#define
(1)#define定义标识符
#define NAME stuff
用法演示:
#include <stdio.h>#define TIMES 100int main(void) {int t = TIMES;printf("%d\n", t);return 0;
}
运行结果:100
在预处理阶段会把 TIMES 替换为 100。预处理结束后 int t = TIMES 就没有TIMES 了,会变为 int t = 100。
// 预处理前
int t = TIMES;
// 预处理后
int t = 100;
当然了, #define 定义的符号可不仅仅只有数字,还可以用来做很多事,比如:
1.#define REG register //给关键字register,创建一个简短的名字
2.#define DEAD_LOOP for(;;) //用更形象的符号来替换一种实现
① #define REG register,给关键字 register,创建一个简短的名字:
#define REG registerint main(void) {register int num = 0;REG int num = 0; // 这里REG就等于registerreturn 0;
}
② #define DEAD_LOOP for(;;),用更形象的符号来替换一种实现:
#define DEAD_LOOP for(;;)int main(void) {DEAD_LOOP // 预处理后替换为 for(;;); ; // 循环体循环的是一条空语句DEAD_LOOP; // 那么可以这么写,这个分号就是循环体,循环的是一个空语句return 0;
}
③ #define CASE break;case ,在写case语句的时候自动字上break(很巧妙的偷懒):
#define CASE break;case // 在写case语句的时候自动字上breakint main(void) {int n = 0;//switch (n) {// case 1:// break;// case 2:// break;// case 3:// break;//}switch (n) {case 1: // 第一个case不能替换CASE 2: // 相当于 break; case 2:CASE 3: // 相当于 break; case 3:}return 0;
}
有个细节,再前面 #define 定义标识符时,为什么末尾没有加上分号呢?
#define TIMES 100;
#define TIMES 100
这是因为,分号也会被当作替换内容替换到文本当中,可能会导致出现错误:
#define _CRT_SECURE_NO_WARNINGS 1#include <stdio.h>#define TIMES 100;int main(void) {int a, b;if (a > 10)b = TIMES; // b = 100;;else //else没有匹配对象b = -TIMES; // b = 100;;return 0;
}
所以,在 #define 定义标识符时,尽量不要在末尾加分号!(必须加的情况除外)
(2)#define定义宏
#define NAME(parament-list) stuff
#define 机制允许把参数替换到文本中,这种实现通常被称为宏(macro)或 定义宏(define macro),parament-list 是一个由逗号隔开的符号表,他们可能出现在 stuff 中。
注意:
- 参数列表的左括号必须与 name 紧邻。
- 如果两者之间由任何空白存在,参数列表就会将其解释为 stuff 的一部分。
用法演示:3*3=9
#include <stdio.h>#define SQUARE(X) X*Xint main(void) {printf("%d\n", SQUARE(3)); // printf("%d\n", 3 * 3);return 0;
}
那么,(3+1) 的结果是什么?
#include <stdio.h>#define SQUARE(X) X*Xint main(void) {printf("%d\n", SQUARE(3+1));return 0;
}
运行结果:7
这是因为替换是在预处理阶段时替换,表达式真正计算出结果是在运行时计算。所以先替换:
3+1*3+1=7
如果想获得 3+1 相乘(也就是得到 4×4 = 16) 的结果,我们需要给他们添加括号:
#include <stdio.h>// 整体再括一个括号,严谨
#define SQUARE(X) ((X)*(X))int main(void) {printf("%d\n", SQUARE(3+1));return 0;
}
另外,整体再套一个括号!让代码更加严谨,防止产生不必要的错误。比如,,我希望得到 10* DOUBLE,可能会得到以下情况:
#include <stdio.h>#define DOUBLE(X) (X)+(X)int main(void) {printf("%d\n", 10 * DOUBLE(3+1));// printf("%d\n", 10 * (4) + (4)); // 我们本意是想得到80,但是结果为44,因为整体没带括号return 0;
}
*所以,用于对数值表达式进行求值的宏定义都应该用这种方式加上括号,可以有效避免在使用宏时由于参数中的操作符或邻近操作符之间不可预料地相互作用。*不要吝啬括号!!!
(3)#define替换规则
在程序中扩展 #define 定义符号或宏时,需要涉及的步骤如下:
- 检查:在调用宏时,首先对参数进行检查,看看是否包含任何由 #define 定义的符号。如果包含,它们首先被替换。
- 替换:替换文本随后被插入到程序中原来的文本位置。对于宏,函数名被它们的值替换。
- 再次扫描:最后,再次对结果文件进行扫描,看看是否包含任何由 #define 定义的符号。如果包含,就重复上述处理过程。
注意事项:
- 宏参数 和 #define 定义中可以出现 #define 定义的变量。但是对于宏绝对不能出现递归!
- 当预处理器搜索 #define 定义的符号的时候,字符串常量的内容并不被搜索。
3.#和##
我们知道,宏是把参数替换到文本中。那么如何把参数插入到字符串中呢?
比如这种情况,使用函数是根本做不到的:
void print(int x) {printf("变量?的值是%d\n", ?) 函数根本做不到
}int main(void) {int a = 10;// 打印内容:变量a的值是10print(a);int b = 20;// 打印内容:变量b的值是20print(b);int c = 30;// 打印内容:变量c的值是30print(c);return 0;
}
这种情况,就可以用 宏 来实现。
(1)#
# //把一个宏参数变成对应的字符串
#把一个宏参数变成对应的字符串。
使用 # 解决上面的问题:
#include <stdio.h>
#define PRINT(X) printf("变量"#X"的值是%d\n", X);
// #X 就会变成 X内容所定义的字符串int main(void) {// 打印内容:变量a的值是10int a = 10;PRINT(a); // printf("变量""a""的值是%d\n", a);// 打印内容:变量b的值是20int b = 20;PRINT(b); // printf("变量""b"的值是%d\n", b);// 打印内容:变量c的值是30int c = 30;PRINT(c); // printf("变量""c""的值是%d\n", c);return 0;
}
运行结果:


改进:让程序不仅仅支持打印整数,还可以打印其他类型的数(比如浮点数):
#include <stdio.h>
#define PRINT(X, FORMAT) printf("变量"#X"的值是 "FORMAT"\n", X);int main(void) {// 打印内容:变量a的值是10int a = 10;PRINT(a, "%d");// 打印内容:变量f的值是5.5float f = 5.5f;PRINT(f, "%.1f"); //printf("变量""f""的值是 ""%.1f""\n", f);return 0;
}
运行结果:


(2)##
## //把位于它两边的符号合并成一个符号
##可以把位于它两边的符号融合成一个符号。它允许宏定义从分离的文本片段创建标识符。
用法演示:
#include <stdio.h>#define CAT(X,Y) X##Yint main(void) {int vs2003 = 100;printf("%d\n", CAT(vs, 2003)); // printf("%d\n", vs2003);return 0;
}
运行结果:

##也可以将多个符号合成一个符号,比如 X##Y##Z
4.#undef
#undef NAME //移除一个宏定义
用于移除一个宏定义。
用法演示:用完 M 之后移除该定义
#include <stdio.h>#define M 100int main(void) {int a = M;printf("%d\n", M);
#undef M // 移除宏定义return 0;
}
5.带"副作用"的宏参数
什么是副作用?
副作用就是表达式求值的时候出现的永久性效果,例如:
//不带有副作用
x + 1;
//带有副作用
x++; int a = 1;
//不带有副作用
int b = a + 1; //b=2, a=1
//带有副作用
int b = ++a; //b=2, a=2
当宏参数在宏的定义中出现超过一次的情况下,如果参数带有副作用,那么在使用这个宏的时候就可能出现危险,导致不可预料的后果。这种带有副作用的宏参数如果传到宏体内,这种副作用会一直延续到宏体内。
举个例子:
#include <stdio.h>#define MAX(X,Y) ((X)>(Y)?(X):(Y))int main(void) {int a = 5;int b = 8;int m = MAX(a++, b++);printf("m = %d\n", m);printf("a=%d, b=%d\n", a, b);return 0;
}
运行结果:


所以,写宏的时候尽量避免使用这种带副作用的参数。
6.宏和函数对比
举个例子:在两数中找较大值
① 用宏:
#include <stdio.h>#define MAX(X,Y) ((X)>(Y)?(X):(Y))int main(void) {int a = 10;int b = 20;int m = MAX(a, b); // int m = ((a)>(b) ? (a):(b))printf("%d\n", m);return 0;
}
② 用函数:
#include <stdio.h>int Max(int x, int y) {return x > y ? x : y;
}int main(void) {int a = 10;int b = 20;int m = Max(a, b);printf("%d\n", m);return 0;
}
那么,宏和函数那种更好呢?
答案是宏
- 用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多,所以宏比函数在程序的规模和速度方面更胜一筹。
- 更为重要的是函数的参数必须声明为特定的类型。所以函数只能在类型合适的表达式上使用。反之,宏可以适用于整型、长整型、浮点型等可以用于比较的类型。因为宏是类型无关的。
当然,宏也有劣势的地方:
- 每次使用宏的时候,一份宏定义的代码将插入到程序中。除非宏比较短,否则可能大幅度增加程序的长度。
- 宏不能调试。
- 宏由于类型无关,因为没有类型检查,所以不够严谨。
- 宏可能会带来运算符优先级的问题,导致程容易出现错。
宏有时候可以做函数做不到的事情。比如:宏的参数可以出现类型,但是函数做不到:
#include <stdio.h>
#include <stdlib.h>#define MALLOC(num, type) (type*)malloc(num*sizeof(type))int main(void) {// 原本的写法:malloc(10*sizeof(int));// 但我想这么写:malloc(10, int);int* p = MALLOC(10, int); // (int*)malloc(10*sizeof(int))...return 0;
}
所以,如果一个运算的逻辑足够简单,建议使用宏。反之,如果一个运算的逻辑足够复杂,建议使用函数。
7.命名约定
命名约定,一般来讲函数的宏的使用语法很相似,所以语言本身没法帮我们区分二者。约定俗成的一个习惯是: 宏名全部大写,函数名不要全部大写。
四.命令行编译
什么是命令行编译?
在编译的时候通过命令行的方式对其进行相关的定义,叫做命令行编译。
许多C的编译器提供的一种能力,允许在命令行中定义符号。用于启动编译过程。当我们根据同一个源文件要编译出不同的一个程序的不同版本的时,可以用到这种特性,增加灵活性。
比如:假如某个程序中声明了一个某个长度的数组,假如机器甲内存有限,我们需要一个很小的数据,但是机器丙的内存较大,我们需要一个大点的数组。
#include <stdio.h>int main() {int arr[ARR_SIZE];int i = 0;for (i = 0; i < ARR_SIZE; i++) {arr[i] = i;}for (i = 0; i < ARR_SIZE; i++) {printf("%d ", arr[i]);}printf("\n");return 0;
}
gcc 环境下测试:(VS 里面不太好演示)
gcc -D ARRAY_SIZE=10 programe.c
五.条件编译
在编译一个程序时,通过条件编译指令将一条语句(一组语句)编译或者放弃是很方便的。
调试用的代码删除了可惜,保留了又碍事。我们就可以使用条件编译来选择性地编译:
#include <stdio.h>#define __DEBUG__ // 就像一个开关一样int main(void)
{int arr[10] = {0};int i = 0;for (i = 0; i < 10; i++) {arr[i] = i;#ifdef __DEBUG__ // 因为__DEBUG__被定义了,所以为真printf("%d ", arr[i]); // 就打印数组 #endif // 包尾}return 0;
}
运行结果:1 2 3 4 5 6 7 8 9 10
如果不想用了,就把 #define DEBUG 注释掉:
#include <stdio.h>// #define __DEBUG__ // 关int main(void)
{int arr[10] = {0};int i = 0;for (i = 0; i < 10; i++) {arr[i] = i;#ifdef __DEBUG__ // 此时ifdef为假printf("%d ", arr[i]); #endif}return 0;
}
1.条件编译常量表达式
#if 常量表达式……
#endif
如果常量表达式为真,参加编译。反之如果为假,则不参加编译。
用法演示:常量表达式为真
#include <stdio.h>int main(void) {
#if 1printf("Hello,World!\n");
#endifreturn 0;
}
2.多分支的条件编译
#if 常量表达式……
#else if 常量表达式……
#else……
#endif
多分支的条件编译,直到常量表达式为真时才执行。
用法演示:
#include <stdio.h>int main(void) {
#if 1 == 2 // 假printf("rose\n");
#elif 2 == 2 // 真printf("you jump\n");
#else printf("i jump\n")
#endifreturn 0;
}
运行结果:you jump
3.条件编译是否被定义
#if defined(symbol)
#ifdef symbol
#if !defined(symbol)
#ifndef symbol
ifdef 和 if defined() ,ifndef 和 if !defined() 效果是一样的,用来判断是否被定义。
用法演示:
#include <stdio.h>#define TEST 0
// #define TEST2 // 不定义int main(void) {
/* 如果TEST定义了,下面参与编译 */
// 1
#ifdef TESTprintf("1\n");
#endif// 2
#if defined(TEST)printf("2\n");
#endif/* 如果TEST2不定义,下面参与编译 */
// 1
#ifndef TEST2printf("3\n");
#endif// 2
#if !defined(TEST2)printf("4\n");
#endifreturn 0;
}
运行结果:

4.条件编译的嵌套
和 if 语句一样,是可以嵌套的:
#if defined(OS_UNIX)#ifdef OPTION1unix_version_option1();#endif#ifdef OPTION2unix_version_option2();#endif
#elif defined(OS_MSDOS)#ifdef OPTION2msdos_version_option2();#endif
#endif
六.文件包含
我们已经知道,#include 指令可以使另外一个文件被编译。就像它实际出现于 #include 指令的地方一样。替换方式为,预处理器先删除这条指令,并用包含文件的内容替换。这样一个源文件被包含10次,那就实际被编译10次。
1.头文件被包含的方式
#include "filename"
#include <filename.h>
< > 和 " " 包含头文件的本质区别:查找的策略的区别:
- " " 的查找策略:先在源文件所在的工程目录下查找。如果该头文件未找到,则在库函数的头文件目录下查找。(如果仍然找不到,就提示编译错误)
- < > 的查找策略:直接去标准路径下去查找。(如果仍然找不到,就提示编译错误)
既然如此,那么对于库文件是否也可以使用 " " 包含?
答案是可以的。但是这样做查找的效率就低些,当然这样也不容易区分是库文件还是本地文件了。为了效率不建议这么做。
2.嵌套文件的包含
头文件被重复包含的情况:

- comm.h 和 comm.c 是公共模块。
- test1.h 和 test1.c 使用了公共模块。
- test2.h 和 test2.c 使用了公共模块。
- test.h 和 test.c 使用了 test1 模块和 test2 模块。
这样最终程序中就会出现多份 comm.h 的内容,会造成文件内容的重复。
那么如何避免头文件的重复引入呢?
使用条件编译指令,每个头文件的开头写:
#ifndef __TEST_H__
#define __TEST_H__
// 头文件的内容
#endif
还有一种非常简单的方法:
#pragma once // 让头文件即使被包含多次,也只编译一份
—————————————————————————————————
本篇到此结束,码文不易,还请多多支持!
相关文章:
十五.程序环境和预处理
文章目录一.程序翻译环境和执行环境1.ANSI C 标准2.程序的翻译环境和执行环境二.程序编译和链接1.翻译环境2.编译本身的几个阶段3.运行环境三.预处理1.预定义符号2.#define(1)#define定义标识符(2)#define定义宏(3&…...
高并发系统设计之负载均衡
本文已收录至Github,推荐阅读 👉 Java随想录 文章目录DNS负载均衡Nginx负载均衡负载均衡算法负载均衡配置超时配置被动健康检查与主动健康检查LVS/F5Nginx当我们的应用单实例不能支撑用户请求时,此时就需要扩容,从一台服务器扩容到…...
嵌入式Linux从入门到精通之第十四节:Linux IO控制技术
目录 设备控制概述 操作设备文件函数 监听文件描述符 示例 设备控制概述 对于硬件设备,Linux采用了与裸机完全不同的机制进行管理。 Linux下的所有硬件(IO、键盘、鼠标等)均是以文件的形式进行统一管理的,每个设备在/dev/目录下都有一个设备文件与之对应。操作相应的文件…...
/etc/fstab文件
文件/etc/fstab存放的是系统中的文件系统信息,当系统启动的时候,系统会自动地从这个文件读取信息,并且会自动将此文件中指定的文件系统挂载到指定的目录。当正确的设置了该文件,则可以通过mount /directoryname命令来加载一个文件…...
深度学习神经网络基础知识(一) 模型选择、欠拟合和过拟合
专栏:神经网络复现目录 深度学习神经网络基础知识(一) 本文讲述神经网络基础知识,具体细节讲述前向传播,反向传播和计算图,同时讲解神经网络优化方法:权重衰减,Dropout等方法,最后进行Kaggle实…...
同样做软件测试,为什么有人月入3k-5k,有人能拿到17-20k?
同样做软件测试,为什么有人月入3k-5k,有人能拿到17-20k? 虽然各大培训机构一直鼓吹软件测试行业薪资高,但是依旧有一些拿着3-5k薪资,甚至找不到软件测试工作的人。 先来看一些例子: 小A在一家培训机构学完…...
如何运行YOLOv5的代码,实现目标识别
YOLOv5和v8都由Ultralytics这家创业公司开发的https://github.com/ultralytics/yolov5环境配置git clone https://github.com/ultralytics/yolov5.git作者要求python3.6(我用的3.8也能跑通)torch1.7.0pip install -r requirements_my_version.txtrequire…...
【正点原子FPGA连载】第十四章SD卡读写TXT文本实验 摘自【正点原子】DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南
1)实验平台:正点原子MPSoC开发板 2)平台购买地址:https://detail.tmall.com/item.htm?id692450874670 3)全套实验源码手册视频下载地址: http://www.openedv.com/thread-340252-1-1.html 第十四章SD卡读写…...
【人工智能AI :Open AI】我想写一本书,书名是《中国文学史》,帮我列一下目录,细化到三级目录,不少于2000字。
我想写一本书,书名是《中国文学史》,帮我列一下目录,细化到三级目录,不少于2000字。 中国文学史 第一章 经典文学 1.1 先秦文学 1.1.1 先秦诗歌 1.1.1.1 小雅 1.1.1.2 大雅 1.1.1.3 颂 1.1…...
「文档数据库之争」MongoDB和CouchDB的比较
MongoDB和CouchDB都是基于文档的NoSQL数据库类型。文档数据库又称mdocument store,通常用于存储半结构化数据的文档格式及其详细描述。它允许创建和更新程序,而不需要引用主模式。移动应用程序中的内容管理和数据处理是可以应用文档存储的两个字段。Mong…...
c++11 标准模板(STL)(std::unordered_set)(三)
定义于头文件 <unordered_set> template< class Key, class Hash std::hash<Key>, class KeyEqual std::equal_to<Key>, class Allocator std::allocator<Key> > class unordered_set;(1)(C11 起)namespace pmr { templ…...
事件循环机制eventLoop?Js事件流?JavaScript如何实现异步编程?
单线程模式:由用户交互和修改dom的问题,只能决定js就是单线程任务异步模式诞生:同步模式遇到耗时操作页面便会阻塞,就像图片加载,接口获取,页面会一直等待;在执行主线程时,先执行同步…...
视频播放器倍速、清晰度切换、m3u8下载
视频上很容易就可以做到倍速播放,一般的视频格式都是每秒固定的帧数,按比例跳帧就可以了。音频上其实也可以用这种方式来直接删除一些周期,因为电脑里的音频也是数字化离散化地储存的。但是为了使声音不失真,应该都用了稍复杂一点…...
将Nginx 核心知识点扒了个底朝天(五)
什么叫 CDN 服务? CDN ,即内容分发网络。 其目的是,通过在现有的 Internet中 增加一层新的网络架构,将网站的内容发布到最接近用户的网络边缘,使用户可就近取得所需的内容,提高用户访问网站的速度。 一般…...
【基础算法】差分
🌹作者:云小逸 📝个人主页:云小逸的主页 📝Github:云小逸的Github 🤟motto:要敢于一个人默默的面对自己,强大自己才是核心。不要等到什么都没有了,才下定决心去做。种一颗树,最好的时间是十年前…...
【LeetCode】剑指 Offer(5)
目录 写在前面: 题目: 题目的接口: 解题思路1: 代码: 过啦!!! 解题思路2: 代码: 过啦!!! 写在最后:…...
外包出来,朋友内推我去一家公司,问的实在是太...
外包出来,没想到算法死在另一家厂子,自从加入这家公司,每天都在加班,钱倒是给的不少,所以也就忍了。没想到8月一纸通知,所有人不许加班,薪资直降30%,顿时有吃不起饭的赶脚。 好在有…...
刷题记录:牛客NC54585小魂和他的数列 [线段树卡常,真恶心]
传送门:牛客 题目描述: 一天,小魂正和一个数列玩得不亦乐乎。 小魂的数列一共有n个元素,第i个数为Ai。 他发现,这个数列的一些子序列中的元素是严格递增的。 他想知道,这个数列一共有多少个长度为K的子序列是严格递增的。 请你帮…...
2019蓝桥杯真题旋转 C语言/C++
题目描述 图片旋转是对图片最简单的处理方式之一,在本题中,你需要对图片顺时针旋转 90 度。 我们用一个 nm 的二维数组来表示一个图片,例如下面给出一个 34 的 图片的例子: 1 3 5 7 9 8 7 6 3 5 9 7 这个图片顺时针旋转 90 度…...
<JVM上篇:内存与垃圾回收篇>11 - 垃圾回收相关算法
对象存活判断 在堆里存放着几乎所有的 Java 对象实例,在 GC 执行垃圾回收之前,首先需要区分出内存中哪些是存活对象,哪些是已经死亡的对象。只有被标记为己经死亡的对象,GC 才会在执行垃圾回收时,释放掉其所占用的内存…...
网络六边形受到攻击
大家读完觉得有帮助记得关注和点赞!!! 抽象 现代智能交通系统 (ITS) 的一个关键要求是能够以安全、可靠和匿名的方式从互联车辆和移动设备收集地理参考数据。Nexagon 协议建立在 IETF 定位器/ID 分离协议 (…...
多云管理“拦路虎”:深入解析网络互联、身份同步与成本可视化的技术复杂度
一、引言:多云环境的技术复杂性本质 企业采用多云策略已从技术选型升维至生存刚需。当业务系统分散部署在多个云平台时,基础设施的技术债呈现指数级积累。网络连接、身份认证、成本管理这三大核心挑战相互嵌套:跨云网络构建数据…...
手游刚开服就被攻击怎么办?如何防御DDoS?
开服初期是手游最脆弱的阶段,极易成为DDoS攻击的目标。一旦遭遇攻击,可能导致服务器瘫痪、玩家流失,甚至造成巨大经济损失。本文为开发者提供一套简洁有效的应急与防御方案,帮助快速应对并构建长期防护体系。 一、遭遇攻击的紧急应…...
智能在线客服平台:数字化时代企业连接用户的 AI 中枢
随着互联网技术的飞速发展,消费者期望能够随时随地与企业进行交流。在线客服平台作为连接企业与客户的重要桥梁,不仅优化了客户体验,还提升了企业的服务效率和市场竞争力。本文将探讨在线客服平台的重要性、技术进展、实际应用,并…...
将对透视变换后的图像使用Otsu进行阈值化,来分离黑色和白色像素。这句话中的Otsu是什么意思?
Otsu 是一种自动阈值化方法,用于将图像分割为前景和背景。它通过最小化图像的类内方差或等价地最大化类间方差来选择最佳阈值。这种方法特别适用于图像的二值化处理,能够自动确定一个阈值,将图像中的像素分为黑色和白色两类。 Otsu 方法的原…...
学习STC51单片机32(芯片为STC89C52RCRC)OLED显示屏2
每日一言 今天的每一份坚持,都是在为未来积攒底气。 案例:OLED显示一个A 这边观察到一个点,怎么雪花了就是都是乱七八糟的占满了屏幕。。 解释 : 如果代码里信号切换太快(比如 SDA 刚变,SCL 立刻变&#…...
OPENCV形态学基础之二腐蚀
一.腐蚀的原理 (图1) 数学表达式:dst(x,y) erode(src(x,y)) min(x,y)src(xx,yy) 腐蚀也是图像形态学的基本功能之一,腐蚀跟膨胀属于反向操作,膨胀是把图像图像变大,而腐蚀就是把图像变小。腐蚀后的图像变小变暗淡。 腐蚀…...
现有的 Redis 分布式锁库(如 Redisson)提供了哪些便利?
现有的 Redis 分布式锁库(如 Redisson)相比于开发者自己基于 Redis 命令(如 SETNX, EXPIRE, DEL)手动实现分布式锁,提供了巨大的便利性和健壮性。主要体现在以下几个方面: 原子性保证 (Atomicity)ÿ…...
Selenium常用函数介绍
目录 一,元素定位 1.1 cssSeector 1.2 xpath 二,操作测试对象 三,窗口 3.1 案例 3.2 窗口切换 3.3 窗口大小 3.4 屏幕截图 3.5 关闭窗口 四,弹窗 五,等待 六,导航 七,文件上传 …...
MySQL 部分重点知识篇
一、数据库对象 1. 主键 定义 :主键是用于唯一标识表中每一行记录的字段或字段组合。它具有唯一性和非空性特点。 作用 :确保数据的完整性,便于数据的查询和管理。 示例 :在学生信息表中,学号可以作为主键ÿ…...
