Linux/Uinx 系统编程:进程管理(3)

news2024/10/5 16:26:49

Linux/Uinx 系统编程:进程管理(3)

本章来讲解进程管理的最后一部分内容。

文章目录

  • Linux/Uinx 系统编程:进程管理(3)
    • I/O重定向
      • 原理
        • FILE结构体的内部结构
        • 重定向的实现过程
      • scanf 与 printf
        • scanf
        • printf
      • 重定向标准输入
        • 重定向示例代码
    • 管道
      • 管道的使用方式
        • 管道命令处理
      • 命名管道
        • 命名管道终端示例
        • 命名管道C程序示例

I/O重定向

shell中,我们可以通过:>或者<来执行重定向

将文件中的内容,输入到运行的程序中或者将程序的输出输入到文件中。

但是他们的原理是怎么样的呢?

原理

实际上,sh进程有三个用于终端I/O的文件流:stdin,stdout,stderr,每一个流本质上都是指向执行映像区FILE结构体的一个指针

下面给出FILE结构体的内容:

FILE结构体的内部结构

下面给出FILE结构体的组成,这里只提出一点,不多做介绍,感兴趣的读者可以自行查询相关资料。

#ifndef _FILE_DEFINED
struct _iobuf {
        char *_ptr;  //文件输入的下一个位置
        int   _cnt;   //当前缓冲区的相对位置
        char *_base;  //文件的起始位置
        int   _flag;  //文件标志
        int   _file;  //文件的有效性验证
        int   _charbuf;//检查缓冲区状况,若无缓冲区则不读取
        int   _bufsiz; //文件的大小
        char *_tmpfname;//临时文件名
        };
typedef struct _iobuf FILE;
#define _FILE_DEFINED
#endif  /* _FILE_DEFINED */
重定向的实现过程

上文已经说到,在每个C程序中,有三个用于输入输出的IO流。事实上,每个IO流都对应着Linux内核中的一个打开文件,用**文件描述符(数字)**表示。

stdinstdoutstderr的文件描述符号分别为0、1、2

当某个进程复刻出一个子进程时,该子进程会继承父进程的所有打开文件。因此,子进程也具有与父进程相同的文件流和文件描述符号。

最后一句话说明:Linux内核中的IO文件只有三个,所有进程共用这些文件

scanf 与 printf

scanf和printf函数本质上都是借用上面的三个文件来进行输入输出。下面来详细介绍一下具体原理:

scanf

scanf函数的工作原理是这样的:

  1. scanf首先会检查stdin(标准输入)这个流指向的FILE结构体中的缓冲数组是否有数据。
  2. 如果缓冲数组为空,scanf会执行read系统调用,通过FILE结构体中的文件描述符从0号文件(也就是标准输入)读取内容。
  3. 读取到的内容会被存放到缓冲数组中,然后scanf会从这个数组中读取信息。

因此他们之间的联系可以看成这样:

scanf与FILE和输入文件的关系

printf

printf函数的工作原理与scanf类似,但方向相反。以下是printf函数的基本工作原理:

  1. printf首先将你提供的格式化字符串和参数处理成一个完整的字符串。
  2. 这个字符串首先被放入stdout(标准输出)这个流指向的FILE结构体中的缓冲数组。
  3. 如果缓冲数组满了,或者遇到了换行符,或者你调用了fflush函数,printf会执行write系统调用,通过FILE结构体中的文件描述符将缓冲数组的内容写入1号文件(也就是标准输出)。

如下图:
printf与FILE和输出文件的关系

重定向标准输入

如何做到重定向呢?

由上面的原理可知:printf函数和scanf都是通过文件描述符在文件中读取内容的,虽然中间有缓冲区,但是本质上,就是在文件中的读取,因此我们能不能尝试更改FILE中的文件描述符呢?

答案是可以的,Linux下的C语言提供了一个函数dup将fd复制到数值最小的未使用的文件描述符号中。具体使用方法如下:

#include <unistd.h>
dup(fd);

其中fd是文件描述符。

重定向示例代码

我们先创建一个文件input表示输入的内容,以替换标准输入文件(fd = 0):

1 2 3 4 5

结尾没有换行和空格。

接下来写出重定向文件的内容:

/*************************************************************************
	> File Name: io.c
	> Author:Royi 
	> Mail:royi990001@gmail.com 
	> Created Time: Thu 01 Feb 2024 05:45:49 PM CST
	> Describe: 
 ************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>
#include <time.h>
#include <fcntl.h>
#include <unistd.h>


int main() {
    int fd = open("./input", O_RDONLY);
    char ch;
    close(0);
    dup(fd);
    while ((ch = getchar()) != '\n' && ch != EOF) {
        putchar(ch);
    }
    return 0;
}

这个程序先使用open函数以只读的形式打开了input文件,返回文件描述符号fd。

接下来使用close函数关闭文件描述符0,也就是标准输入文件,此时最小的未打开的文件描述符就是0

最后使用dup(fd),将创建的fd符号复制到最小的未打开的文件描述符的上。

经过这样的一番操作,scanf函数对应的stdin指向的FILE结构体中的fd不再是0,而是新的fd,也就是open函数返回的值。

运行程序时,会直接看到输出:

1 2 3 4 5

这样就完成了重定向。

当然C语言还有别的函数dup2,它具有两个参数:

#include <unistd.h>

dup2(fd1, fd2);

这个函数的作用是将fd1复制到fd2中,如果fd2是打开的状态,那么就先关闭它,然后进行复制,这样不需要我们调用close函数去关闭想要重定向的文件描述符,同时也可以重定向非最小未使用的文件描述符,大大提高了程序的灵活性,这里不再做解释。

管道

管道是用于进程交换数据的单项进程间通信通道。有一个读入端和写入端。

管道有两类:

  • 普通管道(匿名管道):用于相关进程(父子进程)。
  • 命名管道:用于不相关进程(非父子进程)。

管道的读、写进程按照以下的方式进行同步:

  • 管道上有数据时,读进程会根据需要读取(不会超过管道大小)
  • 管道上没有数据时,但仍有写进程读进程等待数据
  • 写进程将数据写入管道时,会唤醒读进程,使他们继续读取
  • 如果管道没有数据也没有写进程,读进程返回0,并停止读取
  • 如果管道仍然有写进程读进程等待数据
  • 写进程写入管道,如果管道有空间,会根据需要尽可能多的写入
  • 如果管道没有空间但是有读进程写进程会等待空间
  • 读进程读出管道时,会唤醒等待的写进程
  • 如果管道不再有读进程写进程会将这种情况视为管道中断错误,并终止写入

总结:0返回值意味着管道没有数据也没有写进程。只要有写进程,读进程就不会消失。如果读进程消失但是写进程没有消失的话就报错。

管道的使用方式

进程不能通过管道给自己传输数据,原因如下:

  • 如果进程先从管道中读取,那么将无法从读取的系统调用中返回,因为管道中没有内容,读进程会等待写进程写入,但是写进程是谁呢?是自己,这样就将自己锁死了
  • 相反,如果写入管道的话,需要读进程接收(在4KB以内没有问题,没有超出管道大小,可以成功,但是大部分数据超过4KB),在管道满了之后,写进程会等待读进程读出,但是读进程是谁呢?是自己,也相当于把自己锁死了

使用管道时,必须有两个进程,一个作为管道的输入进程,另一个作为管道的输出进程

接下来以一个程序示例:

/*************************************************************************
	> File Name: pipe.c
	> Author:Royi 
	> Mail:royi990001@gmail.com 
	> Created Time: Thu 01 Feb 2024 09:36:16 PM CST
	> Describe: parent to child with pipe 
 ************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>

int pd[2], n, i;
pid_t pid;
char line[256];


int main() {
    pipe(pd);
    printf("pd = [%d, %d]\n", pd[0], pd[1]);
    if ((pid = fork()) == -1) {
        perror("fork");
        exit(1);
    }

    // parent 
    if (pid) {
        close(pd[0]);
        printf("I'm parent %d, and I closed pd[0]\n", getpid());
        while (i++ < 10) {
            printf("Parent %d is wrinting to pipe\n", getpid());
            n = write(pd[1], "I'm your PAPA", 16);
            printf("Parent %d wrote %d bytes to pipe\n", getpid(), n);
            sleep(1);
        }
        printf("Parent %d exited.", getpid());
    } else {
        // child
        close(pd[1]);
        printf("I'm child %d, and I closed pd[1]\n", getpid());
        while (1) {
            printf("child %d is reading from pipe\n", getpid());
            if ((n = read(pd[0], line, 128))) {
                line[n] = 0;
                printf("child read %d bytes from pipe: %s\n", n, line);
            } else {
                // pipe has no data and to writer.
                exit(0);
            }
            sleep(1);
        }
    }
    return 0;
}
  • 函数pipe()创建了一管道并且在pd[2]中返回了两个文件描述符。其中pd[0]用于从管道读取,pd[1]用于向管道中写入。
  • 为了让两个进程交替运行,在每个循环中加入了sleep函数。
  • 多运行几次发现,读进程在管道中没有数据时,会持续等待

运行示例结果:

pd = [3, 4]
I'm parent 3030, and I closed pd[0]
Parent 3030 is wrinting to pipe
Parent 3030 wrote 16 bytes to pipe
I'm child 3031, and I closed pd[1]
child 3031 is reading from pipe
child read 16 bytes from pipe: I'm your PAPA
Parent 3030 is wrinting to pipe
Parent 3030 wrote 16 bytes to pipe
child 3031 is reading from pipe
child read 16 bytes from pipe: I'm your PAPA
Parent 3030 is wrinting to pipe
Parent 3030 wrote 16 bytes to pipe
child 3031 is reading from pipe
child read 16 bytes from pipe: I'm your PAPA
Parent 3030 is wrinting to pipe
Parent 3030 wrote 16 bytes to pipe
child 3031 is reading from pipe
child read 16 bytes from pipe: I'm your PAPA
Parent 3030 is wrinting to pipe
Parent 3030 wrote 16 bytes to pipe
child 3031 is reading from pipe
child read 16 bytes from pipe: I'm your PAPA
Parent 3030 is wrinting to pipe
child 3031 is reading from pipe
Parent 3030 wrote 16 bytes to pipe
child read 16 bytes from pipe: I'm your PAPA
Parent 3030 is wrinting to pipe
child 3031 is reading from pipe
Parent 3030 wrote 16 bytes to pipe
child read 16 bytes from pipe: I'm your PAPA
child 3031 is reading from pipe
Parent 3030 is wrinting to pipe
Parent 3030 wrote 16 bytes to pipe
child read 16 bytes from pipe: I'm your PAPA
Parent 3030 is wrinting to pipe
child 3031 is reading from pipe
Parent 3030 wrote 16 bytes to pipe
child read 16 bytes from pipe: I'm your PAPA
Parent 3030 is wrinting to pipe
child 3031 is reading from pipe
Parent 3030 wrote 16 bytes to pipe
child read 16 bytes from pipe: I'm your PAPA
child 3031 is reading from pipe
Parent 3030 exited.%  

通过对程序修改可以实现先让父进程死亡(输入进程消失),会发现接收进程返回,但是如果接收进程只读几次的话,会出现141号(BROKEN_PIPE)报错,这个报错就是因为读取进程死亡而导致管道文件失效造成的。

下面是修改后的示例代码:

/*************************************************************************
	> File Name: pipe.c
	> Author:Royi 
	> Mail:royi990001@gmail.com 
	> Created Time: Thu 01 Feb 2024 09:36:16 PM CST
	> Describe: parent to child with pipe 
 ************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <signal.h>

int pd[2], n, i;
pid_t pid;
char line[256];


int main() {
    pipe(pd);
    printf("pd = [%d, %d]\n", pd[0], pd[1]);
    if ((pid = fork()) == -1) {
        perror("fork");
        exit(1);
    }

    // parent 
    if (pid) {
        signal(SIGPIPE, SIG_IGN);
        close(pd[0]);
        printf("I'm parent %d, and I closed pd[0]\n", getpid());
        while (i++ < 10) {
            printf("Parent %d is wrinting to pipe\n", getpid());
            n = write(pd[1], "I'm your PAPA", 16);
            if (n == -1) {
                perror("write");
                exit(1);
            }
            printf("Parent %d wrote %d bytes to pipe\n", getpid(), n);
            sleep(1);
        }
        printf("Parent %d exited.", getpid());
    } else {
        // child
        close(pd[1]);
        printf("I'm child %d, and I closed pd[1]\n", getpid());
        i = 0;
        while (i++ < 3) {
            printf("child %d is reading from pipe\n", getpid());
            if ((n = read(pd[0], line, 128))) {
                line[n] = 0;
                printf("child read %d bytes from pipe: %s\n", n, line);
            } else {
                // pipe has no data and to writer.
                printf("Child had not find bytes of needing to read.");
                exit(0);
            }
        }
        printf("Child is exiting...\n");
    }
    return 0;
}


BROKEN_PIPE报错
在修改中的代码上,由于write函数为系统调用,而在执行write函数时出现了管道中断的错误,因此内核会发出一个信号“BROKEN_PIPE”,导致程序直接停止,因此无法看到报错信息,在程序开头添加signal(SIGPIPE, SIG_IGN);可以忽略掉信号继续执行程序,并通过程序看到报错信息。

管道命令处理

在Linux中,管道是如何使用的呢?

命令行:

cmd1 | cmd2

包含一个管道符号“ | ”。

sh将通过一个进程运行cmd1,另一个进程运行cmd2,他们通过一个管道连接在一起。因此cmd1的输出变成cmd2的输入。下文展示了管道命令的使用方法:

当sh获取命令行cmd1 | cmd2时,会复刻出一个子进程sh,并且等待子进程sh照常终止。

子进程sh:浏览命令行中是否有|符号。在这种情况下,cmd1 | cmd2有一个管道符号。将命令函划分为头部=cmd1尾部=cmd2

然后子进程sh执行以下类似的代码片段:

int pd[2];
pipe(pd);
pid = fork();
if (pid) {
	close(pd[0]);
	close(1);
	dup(pd[1]);
	close(pd[1]);
	exec(head);
} else {
	close(pd[1]);
	close(0);
	dup(pd[0]);
	close(pd[0]);
	exec(tail);
}

管道写进程重定向其 fd = 1pd[1],管道读进程重定向其 fd = 0pd[0]。这样一来就可以通过管道连接起来了。

命名管道

命名管道又叫FIFO,它们有”名称“,并且在文件系统中以特殊文件的形式存在。

它们会一直存在下去,直到使用rm和unlink将其删除。它们可与非相关进程一起使用,并不局限于管道创建进程的子进程。

命名管道终端示例

在sh中,通过mknod命令创建一个命名管道:

mknod mypipe p

或者在C程序中发出mknod()系统调用:

int r = mknod("mypipe", S_IFIFO, 0);

两种方式都会在当前目录中创建一个名为mypipe的管道文件。

使用:

ls -l mypipe

可以查看文件属性。

创建mypipe
其中,数字1代表连接数,0代表大小。

进程可以像访问普通文件一样访问命名管道。

对命名管道的写入和读取是由Linux内核同步的。

如何使用这个管道呢?我们来做一个最基本的演示:

我们需要两个sh,这里我以我的服务器为例,打开两个sh终端:

在第一个终端上的该目录下执行:

echo "hello" > mypipe

将”hello“重定向到mypipe中,如下图:

终端1

此时会发现陷入了阻塞状态。

这是因为管道中存在内容还没有读出,sh进程正在等待。

接下来在第二个终端上,执行:

cat mypipe

读出管道中的文件,如下图:

终端2

读出管道中的内容之后,第一个终端也退出了阻塞状态:

终端1

读者可以自行尝试一下。

命名管道C程序示例

如何在C程序中使用命名管道呢?

接下来展示示例代码:

/*************************************************************************
	> File Name: named_pipe.c
	> Author:Royi 
	> Mail:royi990001@gmail.com 
	> Created Time: Fri 02 Feb 2024 03:20:10 PM CST
	> Describe: 
 ************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

char *line = "testing named pipe";

int main() {
    int fd;
    mknod("mypipe", S_IFIFO | 0677, 0);
    fd = open("./mypipe", O_WRONLY);
    write(fd, line, strlen(line));
    close(fd);
    return 0;
}

/*************************************************************************
	> File Name: named_pipe.c
	> Author:Royi 
	> Mail:royi990001@gmail.com 
	> Created Time: Fri 02 Feb 2024 03:20:10 PM CST
	> Describe: 
 ************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

char buf[128];

int main() {
    int fd = open("./mypipe", O_RDONLY);
    read(fd, buf, 128);
    printf("%s\n", buf);
    close(fd);
    return 0;
}

编译成对应名称的文件,结果如下:

编译后结果

测试方法与直接的shell命令行测试一模一样,先执行write程序,然后在另一个终端上的执行read程序:

执行write程序:

write程序执行

进入阻塞态。

在另一个终端上执行read程序:

read程序

此时返回第一个终端发现已经退出了阻塞态:

退出阻塞态


以上就是Linux\Uinx系统编程中进程管理的所有内容啦!!!创作不易,希望读者给个关注给个点赞收藏支持一下!!!!下一章将更新多进程编程的内容,敬请期待!!!

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

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

相关文章

LDRA Testbed软件静态分析_Jenkins持续集成_(2)配置邮件自动发送静态分析结果

系列文章目录 LDRA Testbed软件静态分析_操作指南 LDRA Testbed软件静态分析_自动提取静态分析数据生成文档 LDRA Testbed软件静态分析_Jenkins持续集成_(1)自动进行静态分析的环境搭建 LDRA Testbed软件静态分析_Jenkins持续集成_(2)配置邮件自动发送静态分析结果 LDRA Testb…

10 排序的概念

目录 1.排序的概念和运用 2.排序的分类 1. 排序的概念及运用 1.1 排序的概念 排序: 所谓排序&#xff0c;就是使一串记录&#xff0c;按照其中的某个或某些关键字的大小&#xff0c;递增或递减的排列起来的操作 稳定性&#xff1a;假定在待排序的记录序列中&#xff0c;存在…

微调实操一: 增量预训练(Pretraining)

1、前言 《微调入门篇:大模型微调的理论学习》我们对大模型微调理论有了基本了解,这篇结合我们现实中常见的场景,进行大模型微调实操部分的了解和学习,之前我有写过类似的文章《实践篇:大模型微调增量预训练实践(二)》利用的MedicalGPT的源码在colab进行操作, 由于MedicalGPT代…

编译Duilib库

编译Duilib&#xff0c;遇到几个错误&#xff1b; 最终生成的lib如下&#xff1b; 报一个错误&#xff0c;无法打开源文件"StdAfx.h"&#xff0c; 查了一下资料&#xff0c;反正我的在下图 C/C - 常规 - 附加包含目录&#xff0c;填入下图内容就可以了&#xff0c;这…

还在用findViewById,不来了解下其它方式?

众所周知&#xff0c;都2225年了&#xff0c;如果你还在用Java敲安卓代码&#xff0c;findViewById已经是一种非常繁琐的操作&#xff0c;如果要去获取的id数量多&#xff0c;则对开发更加不友好。如果一个页面id过多&#xff0c;经常会有如下场景&#xff1a; TextView title…

100 C++内存高级话题 new 细节探秘,重载类内 operator new ,delete

一 new 内存分配细节探秘 我们以分配10个char为例&#xff0c;说明&#xff0c;观察内存发现&#xff0c;当delete 的时候&#xff0c;实际上很多内存都改变了。 实际上 new 内存不是一个简单的事情。为了记录和管理分配出去的内存&#xff0c;额外分配了不少内存&#xff0c;…

clickhouse行转列的转换

1、原表select * from test 2、一个人的每个科目作为一行记录 改为一个人的所有科目作为一行记录 方式1 select name, sum(case when subject‘语文’ then score else 0 end) as chinese, sum(case when subject‘数学’ then score else 0 end) as math from test group by …

Redis学习——高级篇⑨

Redis学习——高级篇⑨ Redis7高级之Redlock算法和Redisson的使用&#xff08;十&#xff09; 10.1 Redlock 红锁算法1.解决手写分布式锁的单点故障问题2.设计理念3. 解决方案 10.2 Redisson进行代码改造10.3 多机案例&#xff08;解决单点故障&#xff09;10.4 R…

线性代数:矩阵的秩

目录 一、矩阵的子式 二、矩阵的秩 三、重要性质定理推论 一、矩阵的子式 二、矩阵的秩 三、重要性质定理推论

Linux多线程服务端编程:使用muduo C++网络库 学习笔记 第十一章 反思C++面向对象与虚函数(下)

11.7.2 值语义与生命期 值语义的一个巨大好处是生命期管理很简单&#xff0c;就跟int一样——你不需要操心int的生命期。值语义的对象要么是stack object&#xff0c;要么直接作为其他object的成员&#xff0c;因此我们不用担心它的生命期&#xff08;一个函数使用自己stack上…

TryHackMe-File Inclusion练习

本文相关的TryHackMe实验房间链接&#xff1a;TryHackMe | Why Subscribe 路径遍历(目录遍历) LocationDescription/etc/issue包含要在登录提示之前打印的消息或系统标识。/etc/profile控制系统范围的默认变量&#xff0c;例如导出&#xff08;Export&#xff09;变量、文件创…

纯国产,3款黑科技软件,被误认为外国佬开发

闲话不多说&#xff0c;直接为大家推荐三款实用工具。 1、知犀思维导图 这款国产的思维导图工具&#xff0c;堪称业界的良心之选。它不仅能捕捉你每一个稍纵即逝的灵感&#xff0c;而且界面简洁、操作轻松。无论是团队协作、灵感记录、规划制定&#xff0c;还是日常笔记&…

jenkins添加linux节点

jenkins添加linux节点并创建任务_创建linux jenkins结点-CSDN博客文章浏览阅读1.1k次。jenkins添加linux节点并创建任务_创建linux jenkins结点https://blog.csdn.net/qq_32828053/article/details/128905581

加速知识检索:伯克利DeepMind联合研究,RaLMSpec让语言模型服务飞速提升2-7倍!

近年来&#xff0c;随着大型语言模型&#xff08;LLM&#xff09;的出现&#xff0c;在多样化的 NLP 任务上取得了令人瞩目的成果。然而&#xff0c;知识密集型任务仍是 NLP 领域中的一项挑战&#xff0c;因为这些任务不仅要求模型要理解和生成自然语言&#xff0c;还要能够访问…

【论文笔记】Multi-Chain Reasoning:对多思维链进行元推理

目录 写在前面1. 摘要2. 相关知识3. MCR方法3.1 生成推理链3.2 基于推理链的推理 4. 实验4.1 实验设置4.2 实验结果 5. 提及文献 写在前面 文章标题&#xff1a;Answering Questions by Meta-Reasoning over Multiple Chains of Thought论文链接&#xff1a;【1】代码链接&…

江科大stm32学习笔记11——旋转编码器计次

一、接线 旋转编码器&#xff0c;旋钮会不断接触断开触点产生电波。 由于两个电波之间相差90&#xff0c;即为正交波&#xff0c;一个电波处于高电平时另一个处于低电平&#xff0c;所以可以用来判断旋转方向。 二、代码 复制粘贴4-1的工程文件&#xff0c;重命名为“5-2 旋转…

Could not resolve host: github.com问题解决

git clone的时候发现机器无法解析github.com&#xff0c;其实应该改用ssh协议去clone&#xff0c;但是我用的是公用的机器&#xff0c;密钥对一直没配置好&#xff0c;所以也就堵死了。那么如果想让机器能解析github.com&#xff0c;&#xff08;机器本身没有ping命令&#xff…

深度学习入门笔记(五)前馈网络与反向传播

接着上一节,本节讲解模型自我学习的数学计算过程究竟是怎么样的。 5.1 前馈网络 一个最简单的前馈神经网络如图所示,对于每一个隐藏层,输入对应前一层每一个节点权重乘以节点输出值,输出则是经过激活函数(例如sigmoid函数)计算后的值。 在这样的网络中,输入的数据 x 经…

数据结构—基础知识(16):哈夫曼编码

数据结构—基础知识&#xff08;16&#xff09;&#xff1a;哈夫曼编码 哈夫曼编码的主要思想 在进行数据压缩时&#xff0c;为了使压缩后的数据文件尽可能短&#xff0c;可采用不定长编码。其基本思想是&#xff1a;为出现次数较多的字符编以较短的编码。为确保对数据文件进…

一、创建Vue3项目

1. 下载 node.js 下载地址&#xff1a;https://nodejs.org/zh-cn 优先选择 16 版本; node -v || node -version 可以检查本地 node.js 版本 2. 设置淘宝镜像源 npm config set registry https://registry.npmmirror.com/ 设置淘宝镜像源 npm config get registry 查看当前镜像…