Linux应用编程-音频应用编程-语音转文字项目

news2024/11/16 1:19:05

文章目录

  • 前言
  • Linux语音识别
    • alsa-lib简介:
    • 安装alsa-lib库:
    • API调用
    • 录音
      • 相关概念
        • 样本长度(Sample)
        • 声道数(channel)
        • 帧(frame)
        • 周期(period)
        • 采样率(Sample rate)
      • 打开声卡设备
      • 设置硬件参数
        • 初始化硬件参数结构对象
        • 设置访问类型
        • 设置数据编码格式
        • 设置采样频率
        • 设置声道
        • 加载硬件参数
      • 使声卡设备处于准备状态
      • 读/写数据
    • 文件IO
      • 打开音频文件
      • 向音频文件写数据
    • 信号
    • 多线程
    • 主循环
    • 实现效果及注意事项
      • 实现效果
      • 注意事项
  • 源代码(转载请注明出处)


前言

本篇分享:

Linux应用编程之音频编程,使用户可以录制一段音频并进行识别(语音转文字)

环境介绍:

系统:Ubuntu 22.04
声卡:电脑自带


Linux语音识别

实现目标 :用户可以录制一段音频并进行识别(语音转文字)
知识点 : C语言、文件IO、信号、多线程、alsa-lib 库的使用、API调用

alsa-lib简介:

alsa-lib 是一套 Linux 应用层的 C 语言函数库,为音频应用程序开发提供了一套统一、标准的接口,应用程序只需调用这一套 API 即可完成对底层声卡设备的操控,譬如播放与录音。
用户空间的 alsa-lib 对应用程序提供了统一的API 接口,这样可以隐藏驱动层的实现细节,简化了应用 程序的实现难度、无需应用程序开发人员直接去读写音频设备节点。所以,主要就是学习 alsa-lib 库函数的使用、如何基于 alsa-lib 库函数开发音频应用程序。
alsa-lib官方说明文档:https://www.alsa-project.org/alsa-doc/alsa-lib/

安装alsa-lib库:

在ubuntu系统上安装alsa-lib库方法:

sudo apt-get install libasound2-dev

API调用

该程序使用的是百度语音识别API

在这里插入图片描述

注册后领取免费额度及创建中文普通话应用(创建前先领取免费额度(180 天免费额度,可调用约 5 万次左右) )

在这里插入图片描述

创建好应用后,可以得到API key和Secret Key(填写到程序中的相应位置)

在这里插入图片描述

调用API相关说明可在图中所示位置查阅,Demo代码中有多种语言的调用示例可以参考,使用C语言的话也可以直接在本项目程序上面直接更改(项目源代码在最下方):

在这里插入图片描述

API相关c文件中需要修改的只有asrmain.c文件:

fill_config函数中(该函数我已修改,原本无file参数,根据实际情况使用),需要修改的有:音频文件格式,API Key以及Secret Key:

RETURN_CODE fill_config(struct asr_config *config,char *file) {
    // 填写网页上申请的appkey 如 g_api_key="g8eBUMSokVB1BHGmgxxxxxx"
    char api_key[] = "填写网页上申请的API key";
    // 填写网页上申请的APP SECRET 如 $secretKey="94dc99566550d87f8fa8ece112xxxxx"
    char secret_key[] = "填写网页上申请的Secret Key";
    // 需要识别的文件
    char *filename = NULL;
    filename = file;

    // 文件后缀仅支持 pcm/wav/amr 格式,极速版额外支持m4a 格式
    char format[] = "pcm";

    char *url = "http://vop.baidu.com/server_api";  // 可改为https

    //  1537 表示识别普通话,使用输入法模型。其它语种参见文档
    int dev_pid = 1537;

    char *scope = "audio_voice_assistant_get"; // # 有此scope表示有asr能力,没有请在网页里勾选,非常旧的应用可能没有
    …………

结合音频录制的程序使用,还需要删除示例中的main函数,run函数中的相关初始化以及API调用函数需要根据实际情况重新调整调用位置。

录音

实现音频录制分为以下步骤:打开声卡设备->设置硬件参数->读写数据

相关概念

样本长度(Sample)

  • 样本长度,样本是记录音频数据最基本的单元,样本长度就是采样位数,也称为位深度(Bit Depth、Sample Size、 Sample Width)。是指计算机在采集和播放声音文件时,所使用数字声音信号的二进制位数,或者说每个采样样本所包含的位数(计算机对每个通道采样量化时数字比特位数),通常有 8bit、16bit、24bit 等。

声道数(channel)

  • 分为单声道(Mono)和双声道/立体声(Stereo)。1 表示单声道、2 表示立体声。

帧(frame)

  • 帧记录了一个声音单元,其长度为样本长度与声道数的乘积,一段音频数据就是由若干帧组成的。把所有声道中的数据加在一起叫做一帧,对于单声道:一帧 = 样本长度 * 1;双声道:一帧 = 样本长度 * 2。
  • 对于本程序中,样本长度为16bit 的单声道来说,一帧的大小等于:16 * 1 / 8 = 2 个字节。

周期(period)

  • 周期是音频设备处理(读、写)数据的单位,换句话说,也就是音频设备读写数据的单位是周期,每一次读或写一个周期的数据,一个周期包含若干个帧;譬如周期的大小为 1024 帧,则表示音频设备进行一次读或写操作的数据量大小为 1024 帧。
  • 一个周期其实就是两次硬件中断之间的帧数,音频设备每处理(读或写)完一个周期的数据就会产生一个中断,所以两个中断之间相差一个周期,即每一次读或写一个周期的数据。
  • 对于本程序中,一周期的大小 = 一周期帧数 * 一帧大小 = 一周期帧数 * (样本长度 * 声道数 / 字节长度) = 1024 * (16 * 1 / 8) = 2048个字节。

采样率(Sample rate)

  • 也叫采样频率,是指每秒钟采样次数,该次数是针对帧而言。

打开声卡设备

代码如下:

/*打开音频采集卡硬件,并判断硬件是否打开成功,若打开失败则打印出错误提示*/
// SND_PCM_STREAM_PLAYBACK 输出流
// SND_PCM_STREAM_CAPTURE  输入流
if ((err = snd_pcm_open(&capture_handle, "hw:0", SND_PCM_STREAM_CAPTURE, 0)) < 0)
{
    printf("无法打开音频设备: %s (%s)\n","hw:0", snd_strerror(err));
    exit(1);
}
printf("音频接口打开成功.\n");

参数:
capture_handle:表示一个 PCM 设备,snd_pcm_open 函数会打开参数 name 所指定的设备,实例化 snd_pcm_t 对象,并将对象的指针通过 capture_handle 返回出来。
"hw:0":参数 name 指定 PCM 设备的名字。alsa-lib 库函数中使用逻辑设备名而不是设备文件名,命名方式为"hw:i,j",i 表示声卡的卡号,j 则表示这块声卡上的设备号
SND_PCM_STREAM_CAPTURE:表示从设备采集音频数据

设置硬件参数

设置硬件参数再细分的话有:初始化硬件参数结构对象,设置访问类型,设置数据编码格式,设置采样频率,设置声道,加载配置好的硬件参数。

初始化硬件参数结构对象

/*对象声明*/
snd_pcm_hw_params_t *hw_params;

/*分配硬件参数结构对象,并判断是否分配成功*/
if ((err = snd_pcm_hw_params_malloc(&hw_params)) < 0)
{
    printf("无法分配硬件参数结构 (%s)\n", snd_strerror(err));
    exit(1);
}
printf("硬件参数结构已分配成功.\n");

/*按照默认设置对硬件对象进行设置,并判断是否设置成功*/
if ((err = snd_pcm_hw_params_any(capture_handle, hw_params)) < 0)
{
    printf("无法初始化硬件参数结构 (%s)\n", snd_strerror(err));
    exit(1);
}
printf("硬件参数结构初始化成功.\n");

参数:
hw_params:此结构包含有关硬件的信息,用于指定PCM流的配置

设置访问类型

/*
设置数据为交叉模式,并判断是否设置成功
interleaved/non interleaved:交叉/非交叉模式。
表示在多声道数据传输的过程中是采样交叉的模式还是非交叉的模式。
对多声道数据,如果采样交叉模式,使用一块buffer即可,其中各声道的数据交叉传输;
如果使用非交叉模式,需要为各声道分别分配一个buffer,各声道数据分别传输。
*/
if ((err = snd_pcm_hw_params_set_access(capture_handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0)
{
    printf("无法设置访问类型(%s)\n", snd_strerror(err));
    exit(1);
}
if(!start_flag) printf("访问类型设置成功.\n");

参数:
SND_PCM_ACCESS_RW_INTERLEAVED:访问类型设置为交错访问模式,通过
snd_pcm_readi/snd_pcm_writei 对 PCM 设备进行读/写操作。

设置数据编码格式

/*设置数据编码格式,并判断是否设置成功*/
if ((err = snd_pcm_hw_params_set_format(capture_handle, hw_params, format)) < 0)
{
    printf("无法设置格式 (%s)\n", snd_strerror(err));
    exit(1);
}
printf("PCM数据格式设置成功.\n");

参数:
format:样本长度,样本是记录音频数据最基本的单元,样本长度就是采样位数,也称为位深度。用的最多的格式是SND_PCM_FORMAT_S16_LE,有符号16位、小端模式。

设置采样频率

/*设置采样频率,并判断是否设置成功*/
if ((err = snd_pcm_hw_params_set_rate_near(capture_handle, hw_params, &rate, 0)) < 0)
{
    printf("无法设置采样率(%s)\n", snd_strerror(err));
    exit(1);
}
printf("采样率设置成功\n");

参数:
rate:采样频率,是指每秒钟采样次数,该次数是针对帧而言,譬如有44100160008000,百度API调用推荐使用160008000

设置声道

/*设置声道,并判断是否设置成功*/
if ((err = snd_pcm_hw_params_set_channels(capture_handle, hw_params, AUDIO_CHANNEL_SET)) < 0)
{
    printf("无法设置声道数(%s)\n", snd_strerror(err));
    exit(1);
}
printf("声道数设置成功.\n");

参数:
AUDIO_CHANNEL_SET:AUDIO_CHANNEL_SET为单声道,值为1。声道分为单声道(Mono)和双声道/立体声(Stereo)1 表示单声道、2 表示立体声。

加载硬件参数

/*将配置写入驱动程序中,并判断是否配置成功*/
if ((err = snd_pcm_hw_params(capture_handle, hw_params)) < 0)
{
    printf("无法向驱动程序设置参数(%s)\n", snd_strerror(err));
    exit(1);
}
printf("参数设置成功.\n");

使声卡设备处于准备状态

/*使采集卡处于空闲状态*/
snd_pcm_hw_params_free(hw_params);

/*准备音频接口,并判断是否准备好*/
if ((err = snd_pcm_prepare(capture_handle)) < 0)
{
    printf("无法使用音频接口 (%s)\n", snd_strerror(err));
    exit(1);
}
printf("音频接口已准备好.\n");

读/写数据

读写数据需要在代码中配置一周期大小的缓冲区:
(读:声卡设备->缓冲区->文件,写:文件->缓冲区->声卡设备)

/*配置一个数据缓冲区用来缓冲数据*/
//snd_pcm_format_width(format) 获取样本格式对应的大小(单位是:bit)
int frame_byte = snd_pcm_format_width(format) * AUDIO_CHANNEL_SET / 8;//一帧大小为2字节
buffer = malloc(buffer_frames * frame_byte); //一周期为2048字节
printf("缓冲区分配成功.\n");

录音从声卡设备读数据即可,读写操作代码如下:
(调用成功,返回实际读取/写入的帧数; 调用失败将返回一个负数错误码。 即使调用成功,实际读取/写入的帧数不一定等于参数 size 所指定的帧数,仅当发生信号或XRUN时,返回的帧数可能会小于参数第三参数buffer_frames。 )

/*从声卡设备读取一周期音频数据:2048字节*/
ret = snd_pcm_readi(capture_handle, buffer, buffer_frames);
if(0 > ret)
{
    printf("从音频接口读取失败(%s)\n", snd_strerror(ret));
    exit(1);
}

/*向声卡设备写一周期音频数据:2048字节*/
ret = snd_pcm_writei(capture_handle,buffer,buffer_frames);
if(0 > ret)
{
    printf("向音频接口写数据失败(%s)\n",snd_strerror(ret));
    exit(1);
}

参数:
buffer:程序数据缓冲区;
buffer_frames:1024,读/写数据的大小,以帧为单位,通常情况下,每次读/写一个周期数据。

文件IO

我们需要将录制的音频文件保存到本地,就需要用到文件IO相关知识,打开音频文件以及向音频文件写数据。

打开音频文件

函数:

函数原型:
FILE *fopen(const char *filename, const char *mode);

参数:
filename -- 字符串,表示要打开的文件名称。
mode -- 字符串,表示文件的访问模式。

作用:
以指定的方式打开文件。

代码:

/*创建一个保存PCM数据的文件*/
if ((pcm_data_file = fopen(argv[1], "wb")) == NULL)
{
    printf("无法创建%s音频文件.\n", argv[1]);
    exit(1);
}
printf("用于录制的音频文件已打开.\n");

参数:
argv[1]:程序执行时传递的参数,./voice record.cpm,则该参数为"record.cpm"
"wb":只写打开或新建一个二进制文件,只允许写数据。

向音频文件写数据

函数:

函数原型:
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);

参数:
ptr -- 这是指向要被写入的元素数组的指针。
size -- 这是要被写入的每个元素的大小,以字节为单位。
nmemb -- 这是元素的个数,每个元素的大小为 size 字节。
stream -- 这是指向 FILE 对象的指针,该 FILE 对象指定了一个输出流。

作用:
向指定文件写入指定大小数据。

代码:

/*从声卡设备读取一周期音频数据:1024帧 2048字节*/
ret = snd_pcm_readi(capture_handle, buffer, buffer_frames);
if(0 > ret)
{
    printf("从音频接口读取失败(%s)\n", snd_strerror(ret));
    exit(1);
}

/*写数据到文件,写入数据大小:音频的每帧数据大小2个字节*ret(从声卡设备实际读到的帧数)*/
fwrite(buffer, ret, frame_byte, pcm_data_file);

信号

函数:

函数原型:
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);

参数:
signum -- 要捕获的信号类型。
act -- 传入参数,新的处理方式。
oldact -- 传出参数,旧的处理方式。

作用:
修改信号处理动作(通常在 Linux 用其来注册一个信号的捕捉函数)。

函数原型:
int sigemptyset(sigset_t *set);

参数:
set -- 需要清空的信号集。

作用:
该函数的作用是将信号集初始化为空。

代码:

/*头文件*/
#include <signal.h>

/*注册信号捕获退出接口*/
struct sigaction act;
act.sa_handler = exit_sighandler;//指定信号捕捉后的处理函数名(即注册函数)
act.sa_flags = 0;//通常设置为0,表使用默认属性
sigemptyset(&act.sa_mask);//将屏蔽的信号集合设为空
sigaction(2, &act, NULL); //Ctrl+c→2 SIGINT(终止/中断) 

void exit_sighandler(int sig)
{
	/*释放数据缓冲区*/
	free(buffer);
	/*关闭音频采集卡硬件*/
	snd_pcm_close(capture_handle);
	/*关闭文件流*/
	fclose(pcm_data_file);
	/*正常退出程序*/
	printf("程序已终止!\n");
	exit(0);
}

多线程

函数:

函数原型:
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);

thread -- 传出参数,保存系统为我们分配好的线程 ID
attr -- 通常传 NULL,表示使用线程默认属性。若想使用具体属性也可以修改该参数。
start_routine -- 函数指针,指向线程主函数,该函数运行结束,则线程结束。
arg -- 线程主函数执行期间所使用的参数。

作用:
创建一个新线程。

函数原型:
int fseek(FILE *stream, long int offset, int whence);

参数:
stream -- 这是指向 FILE 对象的指针,该 FILE 对象标识了流。
offset -- 这是相对 whence 的偏移量,以字节为单位。
whence -- 这是表示开始添加偏移 offset 的位置。

作用:改变文件读写指针的位置。

代码:
(读取用户输入->根据用户输入更改声卡设备工作状态)

/*创建子线程读取用户输入*/
pthread_t tid;
ret = pthread_create(&tid, NULL, read_tfn, NULL);
if (ret != 0) perror("pthread_create failed");

void *read_tfn(void *arg)
{
	char buf[1024];
    int ret;

    while(1)
    {
        memset(buf, 0, sizeof(buf));//清空buf数组
        ret = read(STDIN_FILENO, buf, sizeof(buf));//阻塞等待用户输入
		/*判断输入长度是否正确*/
        if(ret == 2)// A\n or B\n
        {
			/*用户输入的是字符A*/
			if(strcmp(buf, "A\n") == 0)
			{
				snd_pcm_prepare(capture_handle); //使设备恢复进入准备状态
				fseek(pcm_data_file, 0,SEEK_SET);//文件读写指针偏移 使文件从头开始写 等于重新录制音频进行识别
				pcm_flag_now = 1;//PCM状态标志位置1
			}
			/*用户输入的是字符B*/
            else if(strcmp(buf, "B\n") == 0)
			{
				pcm_flag_now = 0;//PCM状态标志位清零
				sleep(1);//sleep 1
				snd_pcm_drop(capture_handle);//停止PCM设备
			}
        }
    }
}

主循环

主循环内判断声卡设备状态是否改变(用户输入决定),若当前声卡为运行状态则进行音频采集,若当前声卡为停止状态则调用API进行识别。

while (1)
{
    /*判断PCM状态是否更新*/
    if(pcm_flag_now != pcm_flag_old)
    {
        /*视当前状态为旧状态*/
        pcm_flag_old = pcm_flag_now;

        /*若PCM为准备状态*/
        if(pcm_flag_now == 1)
            printf("开始采集音频数据...(输入字母B点击回车结束)\n");
        /*若PCM为停止状态*/
        else
        {
            printf("采集结束!\n");

            /*调用API进行识别*/
            run_asr(&config, token);

            printf("请输入字母A点击回车采集音频数据!(CTRL+C退出)\n");
        }
    }

    /*若PCM为准备状态*/
    if(pcm_flag_now == 1)
    {
        /*从声卡设备读取一周期音频数据:1024帧 2048字节*/
        ret = snd_pcm_readi(capture_handle, buffer, buffer_frames);
        if(0 > ret)
        {
            printf("从音频接口读取失败(%s)\n", snd_strerror(ret));
            exit(1);
        }

        /*写数据到文件,写入数据大小:音频的每帧数据大小2个字节*ret(从声卡设备实际读到的帧数)*/
        fwrite(buffer, ret, frame_byte, pcm_data_file);
    }
}

实现效果及注意事项

实现效果

在这里插入图片描述

如图所示A与B之间为音频录制,音频录制完成后会调用百度语言API进行识别,并向用户展示识别的结果。之后用户可自行选择继续识别或退出程序。

注意事项

该程序在声卡不进行录音时是将声卡设备给停止工作了的,在停止声卡设备前需要加入一小段的延时等待,若不添加延时等待,可能会出现子线程使声卡设备停止的同时主线程在读取声卡设备,从而导致下图中出现的错误:
在这里插入图片描述


源代码(转载请注明出处)

在这里插入图片描述

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

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

相关文章

springboot整合JSR303参数校验与全局异常处理

一、前言 我们在日常开发中&#xff0c;避不开的就是参数校验&#xff0c;有人说前端不是会在表单中进行校验的吗&#xff1f;在后端中&#xff0c;我们可以直接不管前端怎么样判断过滤&#xff0c;我们后端都需要进行再次判断&#xff0c;为了安全。因为前端很容易拜托&#…

[面试八股] Mysql

1、Mysql中的索引类型 &#xff08;1&#xff09;普通索引&#xff08;2&#xff09;唯一索引&#xff08;3&#xff09;主键索引&#xff08;4&#xff09;组合索引&#xff08;5&#xff09;全文索引 缺点 1、虽然索引大大提高了查询速度&#xff0c;同时却会降低更新表的速…

Mysql高级部分学习笔记(二)——事务锁

一、buffer pool 1.1 缓冲池&#xff08;buffer pool&#xff09; 简介&#xff1a;InnoDB是基于磁盘存储的&#xff0c;并将其中的数据按页的方式进行管理。因此InnoDB可视为基于磁盘的数据库系统。由于CPU的速度和磁盘IO速度的巨大鸿沟&#xff0c;需要**缓冲池(buffer poo…

windows下通过uiAutomation技术获取ui元素

最近接个需求&#xff0c;要求获取 windows 下 ui 元素&#xff0c;经一番搜索后了解到可通过工具 UISpy.exe 或 inspect.exe 来进行查看。以软件 davinci resolve 为例&#xff1a; 右侧即 UISpy 工具&#xff0c;根据内容可以看出已捕获到 davinci 界面的各属性及对应值。而 …

ItextPdf 字体显示差异分析与处理

ItextPdf 同span下字体显示差异分析与处理 在文章itext7 字体问题解答与相应源代码分析 中是分析了框架的字体设置与相关代码&#xff0c;在本篇文章里将对其生效效果进行分析和相关问题进行处理&#xff08;可持续更新&#xff0c;问题请留言&#xff09; 问题一 问题说明&…

将无风险资产与两种风险资产进行组合

目录 最优风险资产组合。 计算权重的公式。 应用。 最优风险资产组合。 曲线 AB 是两种风险资产的权衡取舍线。 A 点为资产组合中仅有风险资产 1 的情况。将 O 点与 A 点相连&#xff0c;便得到无风险资产与单个风险资产的权衡取舍线。 实际上&#xff0c;曲线 AB 上任一点…

【青训营】分布式理论初探

本文内容总结自 字节跳动青年训练营 第五届后端组 分布式理论初探 一、概述 分布式系统是计算机程序的集合&#xff0c;这些程序利用横跨多个独立计算节点的计算资源实现共同目标&#xff0c;可分为分布式计算、分布式存储、分布式数据库。 其优势是&#xff1a;去中心化、…

ESP32设备驱动-TEA5767收音机模块驱动

TEA5767收音机模块驱动 1、TEA5767介绍 TEA5767HN 是一款用于低压应用的单芯片电子调谐 FM 立体声收音机,具有完全集成的中频 (IF) 选择性和解调功能,频率范围从76—108MHZ自动数字调谐。 该收音机完全无需调整,高灵敏度,高稳定性,低噪音,收音模块。只需要最少的小型低…

Win10安装MySQL、Pycharm连接Mysql、Pycharm中运行django

目录 一、windows系统mysql相关操作 1、检查当前系统是否已安装mysql 1. 按win r 键&#xff08;调出运行窗口&#xff09; 2. 输入service.msc&#xff0c;点击[ 确定 】 3.打开服务列表 - 检查是否有mysql服务 2、windows安装mysql 1.下载mysql 2. 解压 mysql 到自己…

【面试题:三个线程轮流打印A ,B,C】

面试题&#xff1a;三个线程轮流打印A &#xff0c;B&#xff0c;C面试介绍说明思考方案代码实现Print 打印基类APrint 打印字符A 线程CPrint 打印字符C 线程PrintConstant 常量类StrRun 启动类测试结果总结面试介绍说明 当时是2022 年3月 在深圳面试的一家公司。由于疫情比较…

内网渗透(三)之基础知识-域环境的介绍和优点

系列文章 内网渗透(一)之基础知识-内网渗透介绍和概述 内网渗透(二)之基础知识-工作组介绍 注&#xff1a;阅读本编文章前&#xff0c;请先阅读系列文章&#xff0c;以免造成看不懂的情况&#xff01;&#xff01; 域介绍 域的介绍 Windows域是计算机网络的一种形式&#…

DMTet 阅读笔记

介绍 主页 https://nv-tlabs.github.io/DMTet/论文pdf https://nv-tlabs.github.io/DMTet/assets/dmtet.pdf视频汇报 https://slideslive.com/38967642/deep-marching-tetrahedra-a-hybrid-representation-for-highresolution-3d-shape-synthesis?refhomepage疑似代码 在nvdi…

【手写 Promise 源码】第十八篇 - EventLoop 简介

theme: fancy 一&#xff0c;前言 近期公司项目比较忙&#xff0c;粘了老博客几篇 Spring 源码来充数&#xff0c;周末腾出时间把 Promise 做个收尾&#xff1b; 在开始 EventLoop 前&#xff0c;我对 Promise 源码部分进行了简单回顾&#xff0c;并更新了【Promise 源码学习…

【链表面试题】解决环形链表和相交链表问题

在力扣上发现这样的几道题&#xff0c;尝试做了一下&#xff0c;也发现了一个关于这类题的一种做法&#xff1a;快慢指针的使用。废话不多说&#xff0c;上例题 目录 一、环形链表 1.定义&#xff08;概念&#xff09; 2.如何判断是否为环形链表 1.快慢指针 2.为什么快指针…

限期出国|CSC资助赴世界top50名校英国曼彻斯特大学访学

我们先为J老师40天获得佐治亚理工学院&#xff08;美国三大理工学院之一&#xff09;的访问学者邀请函&#xff0c;又成功申报CSC。后因其担心被美国拒签要求重新申请英国名校&#xff0c;10天后拿到跻身世界top50英国曼彻斯特大学的offer&#xff0c;后经ATAS审批、CSC改派、使…

linux基本功系列-help命令实战

文章目录前言&#x1f680;&#x1f680;&#x1f680;一. help命令介绍二. 语法格式及常用选项三. 参考案例3.1 显示某个命令的帮助信息3.2 查看某个命令的简述3.3 以伪man手册格式输出cd信息四. windows中的help总结前言&#x1f680;&#x1f680;&#x1f680; 想要学好Lin…

车载网络 - Autosar网络管理 - 基本概念

Autosar作为当前车载行业使用最为广泛的一种汽车开发系统架构&#xff0c;网上也有很多相关的介绍&#xff1b;不过我看很多有完整的来讲一下这个规范的&#xff0c;一般都是只讲了其中一部分&#xff0c;我这就准备搞出来一套完整版本的Autosar网络管理的规范、测试设计、自动…

新C++(7):多态那些事儿_下

"当人类悬浮到腐朽&#xff0c;有谁愿追随彗星漂流哦~"一、多态原理(1)虚函数表指针(虚表指针)紧接上一篇sizeof(Base)这一小段说起。class Base1 { public:void func(){} private:int _a; };class Base2 { public:virtual void func() {} private:int _a; };我们知道…

【我刚毕业,学习Java开发工程师能学会吗?没有基础?】

对于Java专业来说&#xff0c;学历还是有一定的要求。一般都是本科学历&#xff0c;至少也有个大专&#xff0c;其次就是年龄越年轻越好。现在转行Java的年轻人很多&#xff0c;学历这方面越高越有竞争力一些&#xff0c;尤其是在后期的职业晋升阶段。如果想走管理路线&#xf…

耗时一周整体,这4款黑科技电脑软件,功能强大到离谱

闲话少说&#xff0c;直上狠货。 1、有道云笔记 有道云这是一款国民级的文稿编辑器&#xff0c;俗话说得好&#xff0c;好记性不如烂笔头&#xff0c;强大实用的笔记软件&#xff0c;能让你的工作与学习事半功倍。5大文稿类型&#xff0c;让记录得心应手&#xff0c;随时进行创…