特殊的转义字符—— \b 退格字符 ASCII 0x08

引入

我们在写 C 语言题目时,经常会碰见类似于 数字 分隔符 数字 分隔符 数字 分隔符 这样的输出。比如下面这段代码:

1=1
1+2=3
1+2+3=6
1+2+3+4=10

如果用循环的话,这个加号是个大问题。直接用 printf("%d+"),最后面会多一个加号;用 printf("+%d") 则最前面会多一个加号。
想要解决,则必须判断当前输出的是否为第一个或者最后一个数字,然后做特殊处理。

新思路

有人就发现了,转义字符里有一个\b,这是个退格字符,能不能用它把多余的加号给删了呢?
那就试试呗,先输出个从 1 加到 5 试试

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
	int sum = 0;
	for (int i = 1; i <= 5; i++)
	{
		printf("%d+", i);
		sum += i;
	}
	printf("\b");
	printf("=%d\n", sum);
	return 0;
}

好像没什么毛病,对吧?
在这里插入图片描述
让我们再提交到判题平台上试试
在这里插入图片描述
加号居然没删掉,而且还多了个点出来!

真实含义

我们把上面的代码稍稍改动一下

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
	int sum = 0;
	for (int i = 1; i <= 5; i++)
	{
		printf("%d+", i);
		sum += i;
	}
	printf("\b");
	//printf("=%d\n", sum); //<---- 注释这一行
	return 0;
}

运行效果:
在这里插入图片描述
这段代码和上面的一模一样,只是把等号后面的输出给删掉了而已。
但是,最后的加号居然神奇地又出现了! 这是为什么呢?

我们先来看一下“退格”究竟为何含义。
\b 字符的确是退格字符,但此退格非“删除”。“退格”就是字面含义上的退格,即“往前退一格”,相当于你在 Word 里按一下左方向键。

也就是说,\b 并不能删除上一个字符,它只是把光标往前移了一下而已

那开头的代码为什么能正常输出呢?
很简单,因为后面输出的字符覆盖掉了前面的字符,因此看起来好像是把上一个字符给删了。
为了更清晰的表示这个过程,我做了一个动图。(偷一下懒,图里只制作了三个数字求和,但原理是一样的。)
在这里插入图片描述
事实上退格键在早期打印机上的作用就是“往前退一格”,后来退格键的含义变了,变成了“往前退一格 + 删除一个字符”。

实际输出与显示

问题还没有完全解决:为什么在本地可以,但是上传到判题平台就不行了呢?
因为“显示的内容 ≠ 输出的内容”

请看以下代码:

#include <stdio.h>
#include <stdlib.h>

int main()
{
	printf("123\n");
	printf("123\b\n");
	printf("123\b4\n");
	return 0;
}

运行结果
在这里插入图片描述

然而,我们把它编译,然后把程序的输出结果重定向到文件里,得到的结果是这样的:

123
123
1234

这段输出在不同的地方显示的内容可能不相同
Windows 记事本:一个框

Visual Studio 2015:啥也没有
在这里插入图片描述
Visual Studio 2019:一个带空心圈的实心框
![[Pasted image 20221125225938.png]]
Sublime:
![[Pasted image 20221125230132.png]]
可以看到我们的 \b 字符,也就是 ASCII 码为 0x08 的字符被原样输出了出来, 在文本编辑器里并没有实现退格的效果。
判题平台上使用的就是类似的方法,把程序的输出直接导出,传到网站上显示,但浏览器可不认 \b,于是就显示为了一个红点。

实际应用

利用这个退格字符,我们可以做一个进度条出来
第一种:

#include <stdio.h>
#include <stdlib.h>
#include <windows.h>

int main()
{
	int index = 0;
	char ch[] = {'|', '\\', '-', '/'};
	while (1)
	{
		putchar(ch[index]);

		index++;
		if (index >= 4)
			index = 0;

		Sleep(200); // Sleep(200) 的作用是延时 200 毫秒(0.2 秒)再继续执行下面的代码
		putchar('\b');
	}
	return 0;
}

演示
在这里插入图片描述

第二种:

#include <stdio.h>
#include <stdlib.h>
#include <windows.h>

int main()
{
	//假设要做一个耗时较长的操作
	//为了更好的用户体验,我们需要一个进度条
	
	double progress = 0.1; //当前进度
	int length = 15; //进度条字符长度

	for (progress = 0.1; progress <= 1; progress += 0.05)
	{
		//先输出 length 个 \b,把光标倒到开头去
		//也可以直接用一个 \r
		for (int j = 0; j < length + 2; j++)
			putchar('\b');

		putchar('[');
		//已经完成部分的进度条
		int count = (int)(length * progress); 
		for (int j = 0; j < count; j++)
			putchar('#');
		//未完成部分的进度条
		for (int j = 0; j < length - count; j++)
			putchar(' ');
		putchar(']');
		
		Sleep(100);
	}
	return 0;
}

演示
在这里插入图片描述

<think>嗯,用户想找HAL_TIM_PeriodElapsedCallback函数的定义位置和用法。首先,我需要回忆一下STM32的HAL库结构。这个回调函数应该和定时器中断相关,对吧?当定时器的周期到了,就会触发这个回调函数。 根据用户提供的引用资料,特别是引用[3],里面提到使用HAL_TIM_Base_Start_IT来启动定时器并开启中断,然后实现中断服务程序中的任务。所以HAL_TIM_PeriodElapsedCallback应该是在用户代码中定义的,用来处理周期性的中断事件。HAL库通常采用回调机制,用户需要自己实现这些回调函数,比如这个函数可能是在定时器更新事件时被调用。 接下来,关于定义的位置,HAL库中的弱定义应该在某个头文件或源文件中,比如stm32xx_hal_tim.h或者对应的.c文件中。用户需要在自己的代码中重新定义这个函数,覆盖弱符号。例如,在main.c或者专门的定时器处理文件中定义。 用法方面,用户需要在初始化定时器并启动中断后,编写这个回调函数的内容,比如翻转LED或者处理数据。同时要注意定时器实例的判断,如果有多个定时器的话,需要用htim的参数来选择正确的定时器。 可能用户遇到的问题是没有正确实现回调函数,或者没有启动中断。需要检查是否调用了正确的启动函数,比如HAL_TIM_Base_Start_IT而不是HAL_TIM_Base_Start,后者可能不会开启中断。 另外,用户可能需要知道如何查找HAL库中的弱定义,这通常是在HAL的源文件中,使用__weak关键字修饰。例如在stm32xx_hal_tim.c中会有这个函数的弱定义,用户在自己的代码中重新实现即可。 还需要提醒用户,中断服务函数的名字可能和CubeMX生成的有关,确保中断向量表正确,以及中断优先级设置合理。如果有多个定时器,回调函数中需要通过htim->Instance来判断是哪个定时器触发的,从而执行不同的操作。 总结一下,回答应该包括回调函数的定义位置在用户代码中,需要覆盖HAL的弱定义,用法是在定时器中断发生时执行用户代码,并注意定时器实例的判断。同时提供示例代码和常见问题解答,帮助用户正确使用。</think>### HAL_TIM_PeriodElapsedCallback 函数解析 #### 1. 函数定义位置 `HAL_TIM_PeriodElapsedCallback` 是STM32 HAL库中定时器更新事件(周期中断)的**弱定义回调函数**,其默认实现位于HAL库的定时器驱动文件中(例如:`stm32xx_hal_tim.c`),但用户需要在工程中**重新实现该函数**以覆盖默认的弱定义[^3]。 具体来说: - **弱定义位置**:`stm32xx_hal_tim.c`(例如`stm32h7xx_hal_tim.c`) - **用户实现位置**:用户代码中(如`main.c`或自定义的定时器处理文件) #### 2. 函数作用 当定时器的计数器溢出(或达到预设周期值)时,触发更新事件中断,此时`HAL_TIM_PeriodElapsedCallback` 会被调用,用于执行用户自定义的周期性任务,例如:LED闪烁、传感器数据采集等[^3]。 #### 3. 函数原型 ```c __weak void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { // 默认空实现(需用户覆盖) } ``` #### 4. 使用步骤 1. **定时器初始化** 配置定时器参数(预分频系数、计数周期等),例如: ```c TIM_HandleTypeDef htim3; htim3.Instance = TIM3; htim3.Init.Prescaler = 8399; // 84MHz/(8400) = 10kHz htim3.Init.CounterMode = TIM_COUNTERMODE_UP; htim3.Init.Period = 9999; // 10kHz/10000 = 1Hz HAL_TIM_Base_Init(&htim3); ``` 2. **启动定时器中断** 使用 `HAL_TIM_Base_Start_IT` 启动定时器并开启中断[^3]: ```c HAL_TIM_Base_Start_IT(&htim3); ``` 3. **实现回调函数** 在用户代码中重新定义函数: ```c void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if (htim->Instance == TIM3) // 判断触发源 { HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5); // 示例:翻转LED } } ``` #### 5. 注意事项 - 若使用多个定时器,需在回调函数中通过 `htim->Instance` 判断具体触发源[^3]。 - 必须调用 `HAL_TIM_Base_Start_IT`(而非 `HAL_TIM_Base_Start`)以启用中断功能。 - 确保中断服务函数 `TIMx_IRQHandler` 已正确关联到定时器(通常由CubeMX自动生成)。 --- ###
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值