【读书笔记-《30天自制操作系统》-23】Day24

news2024/11/15 7:10:17

本篇内容依然比较简单,主要是优化窗口功能以及开发定时器应用程序。首先是优化窗口的切换功能,实现通过键盘和鼠标切换窗口,然后是实现通过鼠标关闭窗口。接着实现不同窗口输入状态的切换,最后是实现定时器的API与应用程序。
在这里插入图片描述

1. 窗口优化

前面已经完成了窗口的基本功能,现在需要对窗口功能进行进一步优化。首先是增加窗口的切换功能。

1.1 通过按键实现窗口切换

先从简单的开始,实现按下F11后将最下面的窗口放在最上面。F11的按键编码为0x57,只需要在主程序中添加如下代码:

……
if (i == 256 + 0x57 && shtctl->top > 2) 
{	/* F11 */
	sheet_updown(shtctl->sheets[1], shtctl->top - 1);
}
……

代码很简单,sheets[0]表示背景图层,sheets[1]即为最下层的窗口。top图层为鼠标图层,按下F11键后将最下层的窗口调整到鼠标图层下一层即可(因为不能覆盖鼠标)。
在这里插入图片描述
1.2 通过鼠标实现窗口切换

接下来实现通过鼠标点击实现的窗口切换。鼠标点击画面上的某个地方时,我们需要按照从上到下的顺序判断鼠标的位置落在哪个图层的范围内,并且还需要确保该位置不是透明色区域:

else if (512 <= i && i <= 767) 
{ /* 鼠标数据 */
	if (mouse_decode(&mdec, i - 512) != 0) 
	{
		/* 鼠标指针移动 */
		mx += mdec.x;
		my += mdec.y;
		if (mx < 0) 
		{
			mx = 0;
		}
		if (my < 0) 
		{
			my = 0;
		}
		if (mx > binfo->scrnx - 1) 
		{
			mx = binfo->scrnx - 1;
		}
		if (my > binfo->scrny - 1) 
		{
			my = binfo->scrny - 1;
		}
		sheet_slide(sht_mouse, mx, my);
		if ((mdec.btn & 0x01) != 0) 
		{
			/* 按下左键 */
			/* 按照从上到下的顺序寻找鼠标所指向的图层 */
			for (j = shtctl->top - 1; j > 0; j--) 
			{
				sht = shtctl->sheets[j];
				x = mx - sht->vx0;
				y = my - sht->vy0;
				if (0 <= x && x < sht->bxsize && 0 <= y && y < sht->bysize) 
				{
					if (sht->buf[y * sht->bxsize + x] != sht->col_inv) 
					{
						sheet_updown(sht, shtctl->top - 1);
						break;
					}
				}
			}
		}
	}

这一部分的逻辑也比较清楚。按照从上到下的顺序寻找鼠标点击的图层,如果鼠标点击的位置处于该图层的范围之内,就将该图层移动到最上面。如果将命令行窗口移动到最上面,则会遮挡其他窗口。不过这时又可以通过F11将下面的窗口切换上来。
在这里插入图片描述

1.3 窗口移动

窗口的切换功能基本完成了,接下来实现窗口的移动功能。之前单独实现了任务A窗口的实现功能,现在有了多个任务窗口,需要重新进行实现。

当鼠标左键点击窗口时,如果点击窗口的标题栏区域,则进入窗口移动模式,使窗口位置随着鼠标指针移动;而放开鼠标左键时,退出窗口移动模式,返回普通模式。实现窗口的移动需要记录鼠标移动的距离,这里添加了两个变量mmx和mmy,用于记录移动之前的坐标,并且规定mmx=-1时不处于窗口移动模式。

……
int j, x, y, mmx = -1, mmy = -1;
struct SHEET *sht = 0;

else if (512 <= i && i <= 767) 
{ /* 鼠标数据 */
	if (mouse_decode(&mdec, i - 512) != 0) 
	{
		/*按下左键 */
		mx += mdec.x;
		my += mdec.y;
		if (mx < 0) 
		{
			mx = 0;
		}
		if (my < 0) 
		{
			my = 0;
		}
		if (mx > binfo->scrnx - 1) 
		{
			mx = binfo->scrnx - 1;
		}
		if (my > binfo->scrny - 1) 
		{
			my = binfo->scrny - 1;
		}
		sheet_slide(sht_mouse, mx, my);
		if ((mdec.btn & 0x01) != 0) 
		{
			/* 按下左键 */
			if (mmx < 0) 
			{
				/* 处于通常模式,不处于窗口移动模式 */
				/* 按照从上到下的顺序寻找鼠标指针所在的图层 */
				for (j = shtctl->top - 1; j > 0; j--) 
				{
					sht = shtctl->sheets[j];
					x = mx - sht->vx0;
					y = my - sht->vy0;
					if (0 <= x && x < sht->bxsize && 0 <= y && y < sht->bysize) 
					{
						if (sht->buf[y * sht->bxsize + x] != sht->col_inv) 
						{
							sheet_updown(sht, shtctl->top - 1);
							if (3 <= x && x < sht->bxsize - 3 && 3 <= y && y < 21) 
							{
								mmx = mx;	/* 进入窗口移动模式 */
								mmy = my;
							}
							break;
						}
					}
				}
			} 
			else 
			{
				/* 如果处于窗口移动模式 */
				x = mx - mmx;	/* 计算鼠标移动距离 */
				y = my - mmy;
				sheet_slide(sht, sht->vx0 + x, sht->vy0 + y);
				mmx = mx;	/* 更新移动后窗口坐标*/
				mmy = my;
			}
		} 
		else 
		{
			/* 没有按下左键 */
			mmx = -1;	/* 返回通常模式 */
		}
	}
}

虽然代码长了一些,但基本没有什么新东西,主要还是利用之前已经实现的内容与熟悉的方法。这样就可以通过鼠标移动窗口,更有操作系统的样子了。

在这里插入图片描述

1.4 用鼠标关闭窗口

有了前面用鼠标切换窗口的基础,用鼠标关闭窗口的实现也就顺理成章了。原理基本一致,只是需要增加判断鼠标点击的位置范围是否在窗口右上角的"X"号,并根据点击结束程序就可以了。

……
if (0 <= x && x < sht->bxsize && 0 <= y && y < sht->bysize) 
{
	if (sht->buf[y * sht->bxsize + x] != sht->col_inv) 
	{
		sheet_updown(sht, shtctl->top - 1);
		if (3 <= x && x < sht->bxsize - 3 && 3 <= y && y < 21) 
		{
			mmx = mx;	
			mmy = my;
		}
		if (sht->bxsize - 21 <= x && x < sht->bxsize - 5 && 5 <= y && y < 19) 
		{
			/* 点击"X"按钮 */
			if (sht->task != 0) 
			{	/* 该窗口是否为应用程序窗口 */
				cons = (struct CONSOLE *) *((int *) 0x0fec);
				cons_putstr0(cons, "\nBreak(mouse) :\n");
				io_cli();	/* 强制结束处理时禁止切换任务 */
				task_cons->tss.eax = (int) &(task_cons->tss.esp0);
				task_cons->tss.eip = (int) asm_end_app;
				io_sti();
			}
		}
		break;
	}
}
……

在这里插入图片描述
在这里插入图片描述

1.5 切换输入到应用窗口

现在有了多个应用程序窗口,并且像walk这样的应用程序已经可以接受键盘输入了。应该使应用程序的窗口能够切换到输入状态,然后通过键盘进行输入。这里仍然通过tab键来切换多个应用程序窗口,使用key_win变量保存当前处于输入状态的窗口地址。另外如果应用程序的窗口处于输入状态时被关闭,这是就让操作系统自动切换到最上层的窗口。

if (256 <= i && i <= 511) 
{ /* 键盘数据 */
……
	if (s[0] != 0) 
	{ /* 一般字符 */
		if (key_win == sht_win) 
		{	/* 发送给任务A */
			if (cursor_x < 128) 
			{
				/* 显示一个字符,光标后移一位 */
				s[1] = 0;
				putfonts8_asc_sht(sht_win, cursor_x, 28, COL8_000000, COL8_FFFFFF, s, 1);
				cursor_x += 8;
			}
		} 
		else 
		{	/* 发送至命令行窗口 */
			fifo32_put(&key_win->task->fifo, s[0] + 256);
		}
	}
	if (i == 256 + 0x0e) 
	{	/* BackSpace键 */
		if (key_win == sht_win) 
		{	/*  发送给任务A */
			if (cursor_x > 8) 
			{
				/* 空格擦除光标后后移一位 */
				putfonts8_asc_sht(sht_win, cursor_x, 28, COL8_000000, COL8_FFFFFF, " ", 1);
				cursor_x -= 8;
			}
		} 
		else 
		{	/* 发送至命令行窗口 */
			fifo32_put(&key_win->task->fifo, 8 + 256);
		}
	}
	if (i == 256 + 0x1c) 
	{	/* Enter */
		if (key_win != sht_win) 
		{	/* 发送至任务A */
			fifo32_put(&key_win->task->fifo, 10 + 256);
		}
	}
	if (i == 256 + 0x0f) 
	{	/* Tab */
		cursor_c = keywin_off(key_win, sht_win, cursor_c, cursor_x);
		j = key_win->height - 1;
		if (j == 0) 
		{
			j = shtctl->top - 1;
		}
		key_win = shtctl->sheets[j];
		cursor_c = keywin_on(key_win, sht_win, cursor_c);
	}
……
}

key_winon与key_winoff两个函数用于控制串口标题栏的颜色与任务A窗口的光标:

int keywin_off(struct SHEET *key_win, struct SHEET *sht_win, int cur_c, int cur_x)
{
	change_wtitle8(key_win, 0);
	if (key_win == sht_win) {
		cur_c = -1; /* 删除光标 */
		boxfill8(sht_win->buf, sht_win->bxsize, COL8_FFFFFF, cur_x, 28, cur_x + 7, 43);
	} else {
		if ((key_win->flags & 0x20) != 0) {
			fifo32_put(&key_win->task->fifo, 3); /* 命令行窗口光标OFF */
		}
	}
	return cur_c;
}

int keywin_on(struct SHEET *key_win, struct SHEET *sht_win, int cur_c)
{
	change_wtitle8(key_win, 1);
	if (key_win == sht_win) {
		cur_c = COL8_000000; /* 显示光标 */
	} else {
		if ((key_win->flags & 0x20) != 0) {
			fifo32_put(&key_win->task->fifo, 2); /* 命令行窗口光标ON */
		}
	}
	return cur_c;
}

通过tab键可以实现窗口输入的切换:
在这里插入图片描述

1.6 鼠标切换输入窗口

实现鼠标的窗口输入切换,只需要再增加一点改动:

……
if (0 <= x && x < sht->bxsize && 0 <= y && y < sht->bysize) 
{
	if (sht->buf[y * sht->bxsize + x] != sht->col_inv) 
	{
		sheet_updown(sht, shtctl->top - 1);
		/*鼠标点击的图层不是当前输入的图层,切换*/
		if (sht != key_win) 
		{
			cursor_c = keywin_off(key_win, sht_win, cursor_c, cursor_x);
			key_win = sht;
			cursor_c = keywin_on(key_win, sht_win, cursor_c);
		}
		if (3 <= x && x < sht->bxsize - 3 && 3 <= y && y < 21) 
		{
			mmx = mx;	
			mmy = my;
		}
……
	}
}

2. 定时器API与应用程序

2.1 定时器应用程序

为了编写定时器的应用程序,先编写了定时器的API:

_api_alloctimer:	; int api_alloctimer(void);
		MOV		EDX,16
		INT		0x40
		RET

_api_inittimer:		; void api_inittimer(int timer, int data);
		PUSH	EBX
		MOV		EDX,17
		MOV		EBX,[ESP+ 8]		; timer
		MOV		EAX,[ESP+12]		; data
		INT		0x40
		POP		EBX
		RET

_api_settimer:		; void api_settimer(int timer, int time);
		PUSH	EBX
		MOV		EDX,18
		MOV		EBX,[ESP+ 8]		; timer
		MOV		EAX,[ESP+12]		; time
		INT		0x40
		POP		EBX
		RET

_api_freetimer:		; void api_freetimer(int timer);
		PUSH	EBX
		MOV		EDX,19
		MOV		EBX,[ESP+ 8]		; timer
		INT		0x40
		POP		EBX
		RET
……
else if (edx == 16) 
{
	reg[7] = (int) timer_alloc();
} 
else if (edx == 17) 
{
	timer_init((struct TIMER *) ebx, &task->fifo, eax + 256);
} 
else if (edx == 18) 
{
	timer_settime((struct TIMER *) ebx, eax);
} 
else if (edx == 19) 
{
	timer_free((struct TIMER *) ebx);
}
……

分别实现了获取定时器、设置定时器的发送数据、设置定时器时间、释放定时器的API。应用程序如下:

#include <stdio.h>

int api_openwin(char *buf, int xsiz, int ysiz, int col_inv, char *title);
void api_putstrwin(int win, int x, int y, int col, int len, char *str);
void api_boxfilwin(int win, int x0, int y0, int x1, int y1, int col);
void api_initmalloc(void);
char *api_malloc(int size);
int api_getkey(int mode);
int api_alloctimer(void);
void api_inittimer(int timer, int data);
void api_settimer(int timer, int time);
void api_end(void);

void HariMain(void)
{
	char *buf, s[12];
	int win, timer, sec = 0, min = 0, hou = 0;
	api_initmalloc();
	buf = api_malloc(150 * 50);
	win = api_openwin(buf, 150, 50, -1, "noodle");
	timer = api_alloctimer();
	api_inittimer(timer, 128);
	for (;;) {
		sprintf(s, "%5d:%02d:%02d", hou, min, sec);
		api_boxfilwin(win, 28, 27, 115, 41, 7 /* 白色*/);
		api_putstrwin(win, 28, 27, 0 /* 黑色 */, 11, s);
		api_settimer(timer, 100);	/* 1秒 */
		if (api_getkey(1) != 128) {
			break;
		}
		sec++;
		if (sec == 60) {
			sec = 0;
			min++;
			if (min == 60) {
				min = 0;
				hou++;
			}
		}
	}
	api_end();
}

实现的效果就是显示时间:
在这里插入图片描述

2.2 取消定时器

定时器超时时,会发送设置好的数据。但如果定时器超时之前应用程序已经退出了,定时器超时时会产生什么效果呢?
运行定时器程序,关闭应用程序。过1秒钟后(产生超时),命令行窗口中会出现一个异常字符。

在这里插入图片描述

为了解决这种异常的情况,我们需要再应用程序关闭后取消定时器。

首先编写用于取消指定定时器的函数:

int timer_cancel(struct TIMER *timer)
{
	int e;
	struct TIMER *t;
	e = io_load_eflags();
	io_cli();	/*设置过程中禁止改变定时器状态 */
	if (timer->flags == TIMER_FLAGS_USING) {	/* 是否需要取消? */
		if (timer == timerctl.t0) {
			/* 取消第一个定时器的处理 */
			t = timer->next;
			timerctl.t0 = t;
			timerctl.next = t->timeout;
		} else {
			/* 非第一个定时器的取消处理 */
			/* 找到timer前的一个定时器 */
			t = timerctl.t0;
			for (;;) {
				if (t->next == timer) {
					break;
				}
				t = t->next;
			}
			t->next = timer->next; 
		}
		timer->flags = TIMER_FLAGS_ALLOC;
		io_store_eflags(e);
		return 1;	/* 取消处理成功 */
	}
	io_store_eflags(e);
	return 0; /* 不需要取消处理 */
}

在此基础上,再来编写应用结束时取消定时器的函数。同时需要在定时器上增加一个标记,防止命令行窗口中的光标定时器也被取消:

struct TIMER {
	struct TIMER *next;
	unsigned int timeout;
	char flags, flags2;// 增加标志位
	struct FIFO32 *fifo;
	int data;
};

通常情况下将flags2置为0:

struct TIMER *timer_alloc(void)
{
	int i;
	for (i = 0; i < MAX_TIMER; i++) {
		if (timerctl.timers0[i].flags == 0) {
			timerctl.timers0[i].flags = TIMER_FLAGS_ALLOC;
			timerctl.timers0[i].flags2 = 0;
			return &timerctl.timers0[i];
		}
	}
	return 0; 
}

对于应用程序申请的定时器,将flags2置为1:

else if (edx == 16) 
{
		reg[7] = (int) timer_alloc();
		((struct TIMER *) reg[7])->flags2 = 1;	
}

这样就可以在程序结束取消不需要的定时器了:

void timer_cancelall(struct FIFO32 *fifo)
{
	int e, i;
	struct TIMER *t;
	e = io_load_eflags();
	io_cli();	
	for (i = 0; i < MAX_TIMER; i++) {
		t = &timerctl.timers0[i];
		if (t->flags != 0 && t->flags2 != 0 && t->fifo == fifo) {
			timer_cancel(t);
			timer_free(t);
		}
	}
	io_store_eflags(e);
	return;
}

运行定时器程序并关闭,超时也不会再出现异常字符了。
在这里插入图片描述
本篇的内容逻辑清楚,代码简单,但却使操作系统看起来更像样了。下一篇继续优化命令行窗口,敬请期待。

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

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

相关文章

mybatisplus乐观锁

使用方法&#xff1a; 1.添加version锁标记字段 2.实体类添加对应字段&#xff0c;并加上Version注解 3.添加配置类 Configuration public class MpComfig {Beanpublic MybatisPlusInterceptor mpInterceptor(){MybatisPlusInterceptor mpInterceptor new MybatisPlusIntercep…

VuePress搭建文档网站/个人博客(详细配置)主题配置-侧边栏配置

天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物。 每个人都有惰性&#xff0c;但不断学习是好好生活的根本&#xff0c;共勉&#xff01; 文章均为学习整理笔记&#xff0c;分享记录为主&#xff0c;如有错误请指正&#xff0c;共同学习进步。…

沟通更高效:微信群转移至企业微信操作攻略!

微信群转移到企业微信并不难&#xff0c;具体操作如下&#xff1a; 打开移动端企业微信主页&#xff0c;找到微信聊天栏中的【接收微信中的工作消息】&#xff1b; 点击【前往微信选择群聊】&#xff0c; 跳转到微信&#xff1b; 选择微信上的工作群聊&#xff0c;只能选择作…

elasticsearch同步mysql方案

文章目录 1、1. 使用数据库触发器2. 使用定时任务3. 监听MySQL二进制日志&#xff08;binlog&#xff09;4. 使用数据管道5. 使用第三方工具或服务6. 编写自定义脚本注意事项 2、1. 使用Logstash步骤&#xff1a;示例配置&#xff1a; 2. 使用Debezium步骤&#xff1a; 3. 自定…

基于python flask的高血压疾病预测分析与可视化系统的设计与实现,使用随机森林、决策树、逻辑回归、xgboost等机器学习库预测

研究背景 随着现代社会的快速发展&#xff0c;生活方式的改变和人口老龄化的加剧&#xff0c;心血管疾病&#xff0c;尤其是高血压&#xff0c;已成为全球范围内的重大公共健康问题。高血压是一种常见的慢性疾病&#xff0c;其主要特征是动脉血压持续升高。长期不控制的高血压…

wordpress更换域名后用户图片头像不显示

&#x1f3c6;本文收录于《全栈Bug调优(实战版)》专栏&#xff0c;主要记录项目实战过程中所遇到的Bug或因后果及提供真实有效的解决方案&#xff0c;希望能够助你一臂之力&#xff0c;帮你早日登顶实现财富自由&#x1f680;&#xff1b;同时&#xff0c;欢迎大家关注&&am…

进程间的通信 2 消息队列

system V IPC IPC : Inter-Process Communication (进程间通讯) System V IPC 对象共有三种&#xff1a; 消息队列共享内存信号量 System V IPC 是由内核维护的若干个对象&#xff0c;通过ipcs命名查询 每个 IPC 对象都有一个唯一的 ID&#xff0c;可以通过ftok()函数生成 …

re题(36)BUUCTF-[WUSTCTF2020]Cr0ssfun

BUUCTF在线评测 (buuoj.cn) 查一下壳&#xff0c;64位elf文件 ctrle找到main()函数 只进行了一个比较函数&#xff0c;看一下check() 猜测是a1中存放的flag&#xff0c;往下继续查看函数 把a1中存的数据都给出来了 写个脚本&#xff0c;输出一下a1&#xff0c;直接就是我们要的…

通过MCGS在ARMxy边缘计算网关上实现物流自动化

随着电子商务和智能制造的快速发展&#xff0c;物流行业面临着前所未有的挑战与机遇。高效的物流系统不仅可以加快货物周转速度&#xff0c;降低运营成本&#xff0c;还能显著提升客户满意度。 1. ARMxy BL340系列简介 ARMxy BL340系列是针对工业自动化领域设计的一款高性能、…

虚拟机的ip总是自己变化如何解决?

目录 修改配置文件&#xff1a; 如果出现错误E212&#xff1a;无法打开并写入文件&#xff1a; 如果显示当前用户没有在sudoers文件中&#xff0c;就按照下面方法操作。 修改配置文件&#xff1a; 变化的原因&#xff1a;在配置文件/etc/sysconfig/network-scripts/ifcfg-e…

Docker 里面按照ifconfig

1. 进入Docker 容器内部 docker exec -it xxx bash2. 安装 net-tools iputils-ping apt-get update && apt-get install -y net-tools apt-get update && apt-get install -y iputils-ping 3. 执行ifconfig 执行ping

如日中天的AI大模型,也到了发展幻灭期!

近期 Gartner发布了《新兴技术成熟度曲线》&#xff0c;其中生成式 AI &#xff08;GenAI&#xff09; 正式进入到了幻灭期。 2018 年 6 月&#xff0c;OpenAI发布GPT-1模型&#xff0c;生成式AI开始向产品化发展。 到2022年的GPT-3.5发布&#xff0c;并且ChatGPT首次向公众推…

单链表(c语言简单实现)

单链表是一种常见的数据结构 一、结构特点 1. 由一系列节点组成&#xff0c;每个节点包含数据域和指向下一个节点的指针域。 2. 最后一个节点的指针域为 null&#xff0c;表示链表的结尾。 二、主要操作 1. 插入节点&#xff1a;可以在链表的头部、尾部或特定位置插入新节点。…

IPD流程体系:IPD在硬件产品开发中的应用

目录 1、内容简介 2、开发各阶段介绍 3、PVT阶段 4、资源群更新 作者简介 1、内容简介 在硬件类相关产品的开发过程中&#xff0c; 每个阶段的工作都是需要按照一定的流程、规范和标准去进行的。 整体还是相对瀑布化的流程&#xff0c; 每个阶段的输入、输出、准入、准…

不可思议的效率飞跃:RPA如何重塑你的工作流程,释放人力潜能!

RPA简介 机器人流程自动化&#xff08;Robotic Process Automation&#xff0c;简称RPA&#xff09;是一种模拟人类用户操作的软件技术&#xff0c;它通过自动化执行重复性、规律性强的任务来提高工作效率和准确性。RPA软件机器人可以模拟鼠标点击、键盘输入、数据复制粘贴等操…

使用 Bedrock 模型进行 SQL 查询生成:高效自动化的全新体验!

引言 在当今高度重视可持续发展的时代&#xff0c;亚马逊通过其 Bedrock 模型&#xff0c;展示了公司在运营和增长方面的战略愿景。同时&#xff0c;Amazon SageMaker 为机器学习领域的专业人士提供了强大的工具&#xff0c;加速了模型的开发和部署。 探索亚马逊的 Bedrock 模…

【数据结构】什么是二叉搜索(排序)树?

&#x1f984;个人主页:修修修也 &#x1f38f;所属专栏:数据结构 ⚙️操作环境:Visual Studio 2022 目录 &#x1f4cc;二叉搜索(排序)树的概念 &#x1f4cc;二叉搜索(排序)树的操作 &#x1f38f;二叉搜索树的查找 &#x1f38f;二叉搜索树的插入 &#x1f38f;二叉搜索树的…

如何选择OS--Linux不同Distribution的选用

写在前言&#xff1a; 刚写了Windows PC的不同editions的选用&#xff0c;趁热&#xff0c;把Linux不同的Distribution选用也介绍下&#xff0c;希望童鞋们可以了解-->理解-->深入了解-->深入理解--...以致于能掌握特定版本的Linux的使用甚者精通。……^.^…… so&a…

【刷题日记】15. 三数之和

15. 三数之和 两数之和可以用巧思也可以用map 三数之和会更加复杂一点&#xff0c;且这道题还需要考虑避免重复答案&#xff01; 思路&#xff1a; 特判&#xff1a;检如果nums 为 null 或长度小于 3直接返回空数组。排序&#xff1a;使用 sort对数组进行升序排序。就变成了…

OpenAI GPT o1技术报告阅读(4)- 填字游戏推理

✨继续阅读报告&#xff1a;使用大模型来学习推理(Reason) 原文链接&#xff1a;https://openai.com/index/learning-to-reason-with-llms/ 这次我们继续看一个填字游戏的案例。 我们先看下问题&#xff1a; 解决以下填字游戏&#xff1a; Across&#xff08;横向&#xff09…