【Linux基础IO之 内存文件操作】

news/2024/4/25 22:19:55/文章来源:https://blog.csdn.net/m0_66363962/article/details/129965433

目录:

  • 前言
  • 一、引入
    • C语言中的文件操作
    • 系统文件操作
        • open
      • 位图
      • 权限
        • close、write、read
        • lseek
    • C语言中的文件操作函数与系统文件操作函数的联系
  • 三、文件描述符
    • 1.文件描述符是什么
    • 2.文件缓冲区
        • 再谈重定向
  • 四、文件缓冲区分类
      • 语言级缓冲区
      • 为什么要有两个缓冲区
  • 五、仿写c语言之 FILE结构体
      • my_stdio.h
      • my_stdio.c
      • my_main.c
      • 功能测验
  • 总结

前言

打怪升级:第60天
在这里插入图片描述

一、引入

今天我们要来了解一下操作系统中对文件的操作过程,以及深入理解操作系统对文件的看待方式,那么在开始之前,我们回顾一下自己所学习过的计算机语言中的文件操作方式,这里以C语言为例。

C语言中的文件操作

这里是引用

我们对文件的操作不能仅限于这三个标准文件流,那么当我们想要向其他文件中录入和读取数据时就需要了解文件打开和关闭。

这里是引用


系统文件操作

先来见一见猪跑:

#include<stdio.h>    
#include<sys/types.h>    
#include<sys/stat.h>    
#include<fcntl.h>    
#include<unistd.h>    #define LOG "log.txt"    int main()    
{    int fd = open(LOG, O_WRONLY | O_CREAT | O_TRUNC, 0666);    printf("success open file\n");    close(fd);    printf("success close file\n");                                                                                                                            return 0;    
}    

这里是引用

open

在这里插入图片描述

位图

比特位为基本操作单位来存储数据的方式我们称之为位图
一个整形有32个比特位,就可以表示32种不同的情况;
下方我们进行演示:

#include<stdio.h>#define ONE 0x1
#define TOW 0x2
#define THREE 0x4
#define FOUR 0x8
#define FIVE 0x10void BitMap(int flags)
{if(flags & ONE) printf("ONE\n");if(flags & TOW) printf("TOW\n");if(flags & THREE) printf("THREE\n");if(flags & FOUR) printf("FOUR\n");if(flags & FIVE) printf("FIVE\n");
}int main()
{BitMap(ONE | TOW);printf("\n");BitMap(FIVE);                                                                                                                                              printf("\n");BitMap(TOW | THREE | FOUR);printf("\n");return 0; 
}

这里是引用在这里插入图片描述

权限

补充一点:第二个open函数最后一个参数为:权限
我们设置的0666,表示它是一个八进制数 – 可读可写不可执行 – 当然这只是初始权限,真正的权限还需要和掩码进行运算,具体运算规则这里不再说明;
我们也可以手动设置该文件的掩码:

umask(0002);

close、write、read

现在我们可以打开文件,我们再来了解一下文件关闭以及读写操作。

这里是引用

我们来浅浅练习一下:

#include<stdio.h>    
#include<sys/types.h>    
#include<sys/stat.h>    
#include<fcntl.h>    // open
#include<unistd.h>    // close、write
#include<string.h>    // strlen#define LOG "log.txt"    int main()    
{    umask(002);    int fd = open(LOG, O_WRONLY | O_CREAT | O_TRUNC, 0666);    // 只写 ,文件没有就创建,打开后清空const char* ptr = "hello friend\n"; // 我们手动添加一个\n    for(int i=0; i<5; ++i) // 写入5次    write(fd, ptr, strlen(ptr));    close(fd);    return 0;    
}

在这里插入图片描述

#include<stdio.h>    
#include<sys/types.h>    
#include<sys/stat.h>    
#include<fcntl.h>    
#include<unistd.h>    
#include<string.h>    #define LOG "log.txt"    int main()    
{    umask(002);    int fd = open(LOG, O_RDWR | O_CREAT | O_TRUNC, 0666); // 可读可写     const char* ptr = "hello friend\n"; // 我们手动添加一个\n    for(int i=0; i<5; ++i) // 写入5次    write(fd, ptr, strlen(ptr));    lseek(fd, SEEK_SET, 0); // 重定位文件读取位置                                                                                                              char str[1024]={0};                           read(fd, str, 1023);                          printf("%s", str);                            close(fd);    return 0;    
} 

这里是引用

lseek

这里我们就来介绍一个新的函数接口 – 修改文件位置

在这里插入图片描述

在这里插入图片描述


C语言中的文件操作函数与系统文件操作函数的联系

我们通过打开和关闭两个操作简单了解:

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


三、文件描述符

上面我们已经了解了OS中对文件的操作,我们是否已经清楚了OS中的文件操作呢?好像是?
好像已经了解了,那么我们要问一问自己,

  1. 文件描述符是什么?
  2. C语言为什么不直接使用fd,反而要对它进行进一步封装?
  3. C语言又是如何进行封装的?
  4. 其他语言呢?

文件描述符属于OS层面的文件标识符,当一个进程想要操作文件时,就需要用到文件描述符,这里我们需要注意:在语言层面我们一般用到的是文件指针(例如C语言的FILE*),其实这是各个语言对OS层面的文件描述符进行了对应的封装;

下图是OS对文件的相关管理:

在这里插入图片描述
一个进程可以打开多个文件,那么操作系统就需要对这些文件进行管理 – 先描述再组织
OS会为每一个被打开的文件创建一个file结构体,用来存储该文件的属性信息(类比PCB),那么有了文件结构体file,进程还需要确定哪些文件是自己的,哪些是其他进程的,因此就需要对它们建立联系 – 添加指针
这里,操作系统将一个进程所打开的文件所有的地址全部存储在一个 files结构体中,当然files结构体并不仅仅存储了打开文件的地址,只是这部分最为重要,这些所打开文件的地址存储在fd_array数组中;
进程通过files*指针找到 files结构体,通过文件描述符(fd)在fd_array数组中找到对应文件的地址。

1.文件描述符是什么

#include<stdio.h>    
#include<sys/types.h>    
#include<sys/stat.h>    
#include<fcntl.h>    
#include<unistd.h>    
#include<string.h>    #define LOG "log.txt"    int main()    
{                                                                                                                   int fd = open(LOG, O_RDONLY); // 只读方式    printf("%d\n", fd);    close(fd);    return 0;    
}    

这里是引用
我们上面说文件描述符其实本质上是数组下标,那么既然是数组下标不是应该从0开始吗,这里为什么是3,
难道它是随机存储的吗?

#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#include<string.h>#define LOG "log.txt"int main()
{int fd1 = open(LOG, O_WRONLY | O_CREAT | O_TRUNC, 0666);int fd2 = open(LOG, O_WRONLY | O_CREAT | O_TRUNC, 0666);int fd3 = open(LOG, O_WRONLY | O_CREAT | O_TRUNC, 0666);int fd4 = open(LOG, O_WRONLY | O_CREAT | O_TRUNC, 0666);int fd5 = open(LOG, O_WRONLY | O_CREAT | O_TRUNC, 0666);                                                                                                   int fd6 = open(LOG, O_WRONLY | O_CREAT | O_TRUNC, 0666);printf("%d\n", fd1);printf("%d\n", fd2);printf("%d\n", fd3);printf("%d\n", fd4);printf("%d\n", fd5);printf("%d\n", fd6);return 0;
}

这里是引用
此时我们又发现,每打开一个文件,它的文件标识符就加1,也就是顺序存储,
那么为什么文件的文件标识符是从3开始而不是0呢?
文件标识符真的不是从0开始的吗?

在最前面的C语言文件操作中我们提到过:C语言会默认打开三个文件–> stdin, stdout, stderr,
所以,并不是标识符是从3开始,而是,我们在一开始就打开了三个文件占用了0,1,2三个文件标识符,因此,之后打开的文件依次往后排–也就是从3开始;
并且这三个文件的打开顺序是固定的 : 0–>stdin , 1–>stdout, 2–>stderr。
下面我们来验证一下:
验证方法:
以stdout为例,
既然stdout等也是文件,并且它们占用的文件标识符是固定的,那么我们是否可以关闭它们?
– 当然可以。
既然stdout被关闭了,那么它所占用的文件描述符是否还可以分配给其他文件?
我们知道,printf是默认向标准输出 – 显示器,中打印数据的,那么如果stdout被关闭了printf还可以使用吗?

#include<stdio.h>    
#include<sys/types.h>    
#include<sys/stat.h>    
#include<fcntl.h>    
#include<unistd.h>    
#include<string.h>    #define LOG "log.txt"    int main()    
{    close(1);    int fd = open(LOG, O_WRONLY | O_CREAT | O_TRUNC, 0666);     printf("%d\n", fd);    printf("close stdout\n");    printf("close stdout\n");    printf("close stdout\n");    return 0;    
}  

这里是引用在这里插入图片描述

我们能得出的结论:

  1. 文件描述符的分配规则为–>遍历数组,找到第一个没有被使用的位置,将文件的地址存入其中,返回该位置的下标作为文件的文件描述符;(其实应该多打开几个文件使结果更加明显,但是上面已经写好了就懒得改了,看到了朋友要注意一下欧~)
  2. 之前我们认为的printf函数是将信息打印到显示器,而现在我们发现好像并非如此:printf是将信息默认打印到 文件描述符为1的文件中,之前之所以打印到显示器,只是因为显示器文件占用着1号文件描述符而已,换了其他文件也可以!!

2.文件缓冲区

我们想要对文件中的内容进行操作就需要将文件信息加载到内存,既然需要加载到内存中,我们就需要找地方存储下来,这里就引入了文件缓冲区
操作系统对文件进行管理时会为它创建file结构体,file结构体中会有一块区域(数组)存放从文件的读取到的(向文件中写入的)数据,
而这块区域就称为缓冲区;

这里我们可以解答一个平时使用文件时遇到的问题:为什么台式机写文档的时候很担心突然断电?
– 因为,我们向文件中写入内容的时候并没有直接写到磁盘中,而是写入了文件的缓冲区,只有当OS对缓冲区中的内容进行刷新后,我们写入的内容才会被保存到磁盘,而编译器何时刷新完全由编译器自己决定,因此,如果编译器没有进行刷新时突然断电,内存中的数据就丢失了(当然我们也可以手动刷新)。

这里是引用

#include<stdio.h>    
#include<sys/types.h>    
#include<sys/stat.h>    
#include<fcntl.h>    
#include<unistd.h>    
#include<string.h>    #define LOG "log.txt"    int main()    
{    fprintf(stdout, "write in stout\n");    fprintf(stdout, "write in stout\n");    fprintf(stdout, "write in stout\n");    fprintf(stdout, "write in stout\n");    fprintf(stdout, "write in stout\n");    fprintf(stderr, "write in stderr\n");    fprintf(stderr, "write in stderr\n");                                                                                                                      fprintf(stderr, "write in stderr\n");    fprintf(stderr, "write in stderr\n");    fprintf(stderr, "write in stderr\n");    return 0;    
}  

这里是引用
我们分别向标准输出和标准错误打印数据,可以同时打印到显示器上,说明在 文件标识符1合2中存储的都是显示器文件的地址;
第二步我们将文件中的内容重定向到 log.txt文件,此时我们发现,只有打印到标准输出的内容重定向到log.txt文件,标准错误的信息仍然打印到了显示器,这是因为重定向和printf类似,也是默认只重定向 文件标识符 1 所指向文件的内容,虽然1 和 2都是指向显示器,但是2的指向并没有改变。

再谈重定向

那么我们可以怎么将打印到2中的内容也重定向到log.txt文件呢?

  1. ./a.out &> log.txt (&>本身就是很好的分隔符,所以 &> 两边加不加空格都可以)

在这里插入图片描述
理解:将标准输出和标准错误输出到 log.txt文件。

  1. ./a.out >&log.txt(同上)

这里是引用
理解:同上, 将标准输出和标准错误输出到 log.txt文件。

  1. ./a.out > log.txt 2>&1
    (文件标识符与操作符不可间断,中间不可有空格 – 为什么? – 因为文件中间加空格,OS可以识别区分,但是这些数字如果分开写,OS无法明确这些数字的含义)

在这里插入图片描述
理解:将标准输出重定向到 log.txt文件,再将标准错误重定向到标准输出;

  1. ./a.out 2>log.txt 1>&2
    在这里插入图片描述

理解:同上,将标准错误重定向到 log.txt文件,再将标准输出重定向到标准错误;


四、文件缓冲区分类

语言级缓冲区

我们在一开始提出的第二个问题:C语言提供的FILE结构体在哪里,我们并没有手动设置为什么就可以使用?
C语言为什么要再提供一个FILE结构体而不是直接使用OS提供的fd进行文件操作,难道只是为了展现语言自己的特色吗?
在FILE结构体中到底有什么?

#include<stdio.h>      
#include<sys/types.h>      
#include<sys/stat.h>      
#include<fcntl.h>      
#include<unistd.h>      
#include<string.h>      #define LOG "log.txt"      int main()      
{      fprintf(stdout, "printf\n");      const char* ptr = "write\n";      write(1, ptr, strlen(ptr));      fork();    return 0;                                                                                                                               
}   

这里是引用

这里,我们就需要引入第二个缓冲区的问题:语言自己提供的缓冲区
我们知道,语言封装的函数底层还是调用系统调用的,因此FILE结构体中一定有fd,
有fd,但是如果只是为了调用系统调用而封装了一个FILE结构体,那可就完全是“脱裤子放屁 ” - 多此一举了。
其实在FILE内部,也会为我们封装一个缓冲区,每个打开的文件都会有一个FILE结构体,也就都会有自己的缓冲区 – 语言级别,
下面我们通过图示进一步了解:

在这里插入图片描述

也就是说:对于一个已经打开的文件,它是有两个缓冲区的!而我们平时所说的文件缓冲区指的一般都是语言级别的缓冲区。
关于缓冲区,它的刷新方式一般有三种:

  • 无缓冲
  • 行缓冲
  • 全缓存

我们来解释一下:
无缓冲指的是 不管我们往缓冲区中写入多少数据,只要写入结束就立即从语言级换冲区刷新到系统缓冲区中;
行缓冲指的是 当我们写入结束,并且最后一个写入的是换行符就会刷新到系统缓冲区;
最后的全缓冲则是 无论我们写入的是什么内容,只有当我们把缓冲区写满的时候才会进行刷新。

各举出一个例子:
无缓冲:打印错误,sdterr;
行缓冲:往显示器上打印,stdout;
全缓冲:往一般文件中写入。

需要注意的一点是:我们上面所说的都是从语言级缓冲区刷新到系统级缓冲区的刷新策略,
至于系统级缓冲区何时将内容刷新到磁盘,这是完全由系统自己决定的。

下面我们来验证一下缓冲区的存在:

#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#include<string.h>#define LOG "log.txt"int main()
{fprintf(stdout, "printf");sleep(3);const char* ptr = "write";write(1, ptr, strlen(ptr));sleep(3);printf("\n");return 0;
}

在这里插入图片描述

实验现象:等待三秒、打印 “write” , 等待三秒,打印 “printf”
但是如果按照我们平时的想法:打印 “printf” , 等待三秒,打印 “write” ,等待三秒
那么为什么会和我们的想法出入这么大 – 主要是 “printf” 的打印?
我们这里都是向显示器打印的,
首先是语言层,由于此时语言层面的刷新策略是行缓冲,由于没有遇到换行符,所以 “printf” 会一直在语言层面的缓冲区,而没有被刷新到系统缓冲区 ;
第二步,等待三秒;
第三步,我们直接调用系统接口往显示器上打印,那么此时就没有语言层的缓冲区什么事情,直接写入系统缓冲区,之后系统缓冲区根据自己的刷新策略将 “write” 刷新到了显示器;
第四步,等待三秒;
最后一步:我们 使用 printf向显示器文件写入了一个 换行符,语言层面的刷新策略将 语言级缓冲区中的信息刷新到系统缓冲区,再到显示器,所以到此时 “printf\n” 才被打印。

那下面我们就回到上面的问题中:为什么写入增加一个 fork()之后,将内容重定向输出会多打印一次呢?
其实这里涉及到写时拷贝的问题:

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

这里的write我们就不谈了,它是直接进行系统调用写入到系统级文件缓冲区的;
我们有两点需要注意:1是输出重定向, 2是创建子进程
由于我们将打印内容从显示器重定向到文件,所以语言级缓冲区的刷新策略也从 行缓冲 变为了 全缓存,
那么在fork之前我们就将 "printf\n"写入到了缓冲区中,之后创建子进程,子进程共享父进程的代码和数据,因此缓冲区中的内容父子都可以看到;
再之后程序运行结束,父子进程相继退出,而进程在退出之前都会清空缓冲区中的内容,
那么此时子进程去清空缓冲区时是不是就相当于一次写入,
所以,OS就会进行写时拷贝, 将 “printf\n” 拷贝到其他地方让子进程去刷新,子进程退出,
父进程也刷新缓冲区,父进程退出。

这,就是我们在 log.txt 中查看到两个 "printf\n"的原因。

为什么要有两个缓冲区

为什么C语言要专门封装一个 FILE结构体,再给我们提供一个缓冲区呢?
– 为了提高IO效率。
我们知道,write、read这些函数都是系统调用,每次使用系统调用都需要占用cpu资源,如果没有语言级的缓冲区,我们每次写入一个字节,就进行一次系统调用,每写入一个字节,就进行一次系统调用,这样做是不是会严重拖慢我们cpu的工作效率?
就比如送快递,到底是每有一个快递,快递公司就立即派一名快递员开始派送,还是等待一段时间,等到收到的快递足够装一车时才进行统一配送?
我想我们大多数朋友都经历过:在网上买一个东西,两天过去了快递仍然没有发出的情况吧(特别是双十一期间!)
采用统一配送的方式可以节省很多人力物力,因此,在我们的计算机中也使用着这种方式 – OS级别的缓存区也是这么个道理。


五、仿写c语言之 FILE结构体

我们采用分文件编写的方式,简单模拟文件操作的几个函数:fopen,fclose,fread,fwrite

my_stdio.h

#pragma once 
#include<stdio.h>#define BUFSIZE 1024
enum flush_way  //  缓存方式
{FLUSH_NONE, // 出错FLUSH_ANAY, // 不缓存FLUSH_LINE,  // 行缓存FLUSH_ALL    // 全缓存
};
typedef struct __C_IO_FILE
{int _fd;  //  文件标识符  -- 任何语言的文件操作都必须有char _buf[BUFSIZE]; // c库提供的缓冲区   --  也可malloc到堆区size_t _cur;  //当前位置enum flush_way fway; // 记录刷新方式
}MYFILE;  MYFILE* my_fopen(const char *path, const char *mode);int my_fclose(MYFILE *fp);size_t my_fread(void *ptr, size_t size, size_t nmemb, MYFILE *stream);size_t my_fwrite(const void *ptr, size_t size, size_t nmemb, MYFILE *stream);

my_stdio.c

#include"my_stdio.h"
#include<string.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#include<assert.h>
#include<malloc.h>MYFILE* my_fopen(const char *path, const char *mode)
{// 1.分析权限int flags = 0; if(strcmp(mode, "w") == 0){flags |= O_WRONLY | O_CREAT | O_TRUNC;} else  if(strcmp(mode, "r") == 0){flags |= O_RDONLY;}else if(strcmp(mode, "a") == 0){flags |= O_WRONLY | O_CREAT | O_APPEND;}else  // w+ 等等{}//2. 打开文件int fd = -1;
if(flags | O_CREAT)  fd = open(path, flags, 0666);  // 如果创建文件需要设置权限
else  fd = open(path, flags);assert(fd >= 0);//3. 配置文件信息   --  注意此处需要malloc,之后也需要手动关闭MYFILE* fp = (MYFILE*)malloc(sizeof(MYFILE));assert(fp);fp->_cur = 0;fp->_fd = fd;fp->fway = FLUSH_LINE;memset(fp->_buf, '\0', BUFSIZE);//4. 返回文件指针return fp;
}void my_fflush(MYFILE* stream)  //  刷新库缓冲区
{write(stream->_fd, stream->_buf, stream->_cur);stream->_cur = 0;
}int my_fclose(MYFILE *fp)
{// 1. 不能传空指针assert(fp);// 清空缓冲区my_fflush(fp);
// 2. 关闭os中的文件结构体close(fp->_fd);// 3.释放c库的文件结构体free(fp);fp = NULL;return 0;
}size_t my_fread(void *ptr, size_t size, size_t nmemb, MYFILE *stream)
{assert(ptr);assert(stream);// 首先c库缓存中需要有数据 -- 没有就补满if(stream->_cur == 0){ssize_t rsize = read(stream->_fd, stream->_buf, BUFSIZE);assert(rsize >=0);if(rsize == 0) return 0; // 文件内容读取结束}size_t usrSize = size * nmemb;for(size_t i=0; i<usrSize; ){// c库缓存中的数据足够用户使用if(usrSize - i < BUFSIZE - stream->_cur) {strcpy((char*)ptr + i, stream->_buf + stream->_cur);stream->_cur += usrSize - i;i = usrSize;}// 不够用户使用的时候,读完之后重新补满else {strncpy((char*)ptr + i, stream->_buf + stream->_cur, usrSize - i);ssize_t rsize =read(stream->_fd, stream->_buf, BUFSIZE);assert(rsize >=0);if(rsize == 0) break; // 文件内容读取结束stream->_cur = 0;}}return usrSize;
}size_t my_fwrite(const void *ptr, size_t size, size_t nmemb, MYFILE *stream)
{assert(ptr);assert(stream);// 判断当前缓冲区有多少内容 -- 满了 需要刷新 并清空if(stream->_cur == BUFSIZE){my_fflush(stream);stream->_cur = 0;}// 计算用户需要我们写入的数据量 , 开始写入 size_t usrSize = size * nmemb;for(size_t i=0; i<usrSize; i+=BUFSIZE){size_t tmp =  BUFSIZE - stream->_cur;if(usrSize - i > tmp) // 1. 现有的加上新添加的 如果大于剩余容量,不写入语言缓冲区,直接刷新到系统缓冲区,只写入我们能写的{if(usrSize - i > BUFSIZE + stream->_cur){write(stream->_fd, stream->_buf, stream->_cur);stream->_cur = 0;write(stream->_fd, (char*)ptr+i, BUFSIZE);}else {strncpy(stream->_buf + stream->_cur, (char*)ptr + i, tmp);my_fflush(stream);strncpy(stream->_buf , (char*)ptr + i + tmp, usrSize - i - tmp);}}else // 2. 不大于容量,写入用户需要写入的全部{strcat(stream->_buf, (char*)ptr);stream->_cur += usrSize - i;}}// 3. 判断刷新条件 - 全缓冲  行缓存  无缓冲if((stream->fway == FLUSH_LINE && stream->_buf[stream->_cur-1]=='\n')|| (stream->fway == FLUSH_ALL && stream->_cur == BUFSIZE) || (stream->fway == FLUSH_ANAY)){my_fflush(stream);}return usrSize;
}

my_main.c

为了方便大家阅读,减少文章长度:此处给出第一份测试代码,之后的测试代码省略,只放截图。


/*  my_main.c  */#include"my_stdio.h"
#include<string.h>
#include<unistd.h>int main()
{MYFILE* fp = my_fopen("log.txt", "w");//printf("open myfile success\n");const char* msg = "i write in file of log.txt";for(int i=1; 1; ++i){my_fwrite(msg, strlen(msg), 1, fp);sleep(1);if(i % 5 == 0) // 采用行缓冲,每五行录入一个换行符my_fwrite("\n", strlen("\n"), 1, fp);}my_fclose(fp);//printf("close myfile success\n");return 0;
}

功能测验

w - 写入操作:
在这里插入图片描述
在这里插入图片描述

a - 追加操作
在这里插入图片描述
在这里插入图片描述

r - 读取测试
在这里插入图片描述
在这里插入图片描述


总结

今天我们了解了文件操作的系统调用接口:open、close、write、read、lseek;
搞清楚了文件描述符的含义 – 数组下标;
知道了文件的缓冲区 – OS级 和 语言级;
并且清楚了这两个缓冲区存在的位置,以及为什么存在 – 提高io效率;
最后我们也尝试 写了自己的 FILE结构体 ,
这里需要补充一句:并非只有c语言对有语言级的缓存区,其他语言也会有,并且,不管是哪一个语言,不管他如何进行封装,在这些封装的文件结构体内部,必定存在文件描述符


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

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

OpenGL入门教程之 变换

引言 这是一个闪耀的时刻&#xff0c;因为我们即将能生产出令人惊叹的3D效果&#xff01; 变换 向量和矩阵变换包括太多内容&#xff0c;但由于学过线性代数和GAMES101&#xff0c;因此不在此做过多阐述。仅阐述包括代码的GLM内容。 GLM的使用 &#xff08;1&#xff09;GLM…

8、接口的高级用法

1、索引类型 我们可以使用接口描述索引的类型和通过索引得到的值的类型&#xff0c;比如一个数组[‘a’, ‘b’]&#xff0c;数字索引0对应的通过索引得到的值为’a’。我们可以同时给索引和值都设置类型&#xff0c;看下面的示例&#xff1a; interface RoleDic {[id: number…

Pinia与Vuex区别、Pinia安装与使用

目录 一、Pinia和Vuex区别 二、Pinia使用state、getters、actions 1、安装使用Pinia 2、State 3、actions 4、getters 三、Pinia划分模块 1、目录结构 2、store/user.js 3、某组件使用 四、Pinia持久化存储 1、安装插件 2、store/index.js 3、store/user.js 4、…

收废品小程序开发中的常见问题及解决方法

常见问题 1. 用户界面设计 小程序的用户界面设计至关重要。设计师需要在用户界面中提供清晰的指示&#xff0c;以便用户可以轻松地找到他们需要的功能。同时&#xff0c;设计师还需要确保用户界面的整体风格与公司的品牌形象相符。 2. 功能开发 开发小程序的功能需要考虑到…

5G网络切片路由选择策略介绍

终端保存的NSSP(Network Slice Selection Policy)策略来源于网络侧。 NSSP规则是将应用程序匹配到S-NSSAI(Single network slice selection assistance information),并将应用程序绑定到现有PDU会话或发起新的PDU会话。 NSSP功能 NSSP的作用就是为应用程序选择S-NSSAI和…

堆的原理解析

看这篇文章需要对比较器有一定的了解&#xff0c;可以看我的这篇文章&#xff1a; 认识比较器_鱼跃鹰飞的博客-CSDN博客 堆的实际存储方式是数组&#xff0c;但是脑海中应该把他想象成一种树的结构 依次加入下标0-8的9个数&#xff08;添加过程中会不断的和父节点大小进行比…

QT Graphics View 绘图架构之场景、视图与图形项简介

1、场景、视图与图形项 采用QPainter 绘图时需要在绘图设备的 paintEvent()事件里编写绘图的程序&#xff0c;实现整个绘图过程。这种方法如同使用 Windows 的画图软件在绘图&#xff0c;绘制的图形是位图&#xff0c;这种方法适合于绘制复杂性不高的固定图形&#xff0c;不能…

基于ResNet-attention的负荷预测

一、attention机制 注意力模型最近几年在深度学习各个领域被广泛使用&#xff0c;无论是图像处理、语音识别还是自然语言处理的各种不同类型的任务中&#xff0c;都很容易遇到注意力模型的身影。从注意力模型的命名方式看&#xff0c;很明显其借鉴了人类的注意力机制。我们来看…

机器人教学中游戏化课程案例尝试

本文内容严格按创作模板发布&#xff1a; 2023年LPL春季赛季后赛正在火热进行中&#xff0c;你们心中的总冠军是哪支队伍呢&#xff1f;作为热爱游戏的程序猿&#xff0c;一起来聊聊你那些有意义的游戏开发经历吧&#xff01; 游戏化ROS机器人课程的优势有以下七点&#xff1a…

android studio RadioButton单选按钮

1.定义 <!--单选按钮--> <TextViewandroid:layout_marginTop"10dp"android:layout_width"match_parent"android:layout_height"wrap_content"android:text"请选择你的性别&#xff1a;"> </TextView> <RadioGrou…

首发支持NOA的单征程3行泊一体域控,这家Tier1开“卷”

NOA正成为智能驾驶下半场的竞争焦点之一。 显然&#xff0c;NOA所处的L2/L2区间&#xff0c;在技术上仍然属于驾驶辅助领域&#xff0c;但在传统L2级ADAS功能的基础上增强了部分场景的功能ODD。在部分政策允许的国家和地区&#xff0c;可以实现有条件的「解放双手」。 高工智…

最佳实践|如何写出简单高效的 Flink SQL?

摘要&#xff1a;本文整理自阿里巴巴高级技术专家、Apache Flink PMC 贺小令&#xff0c;在 Flink Forward Asia 2022 生产实践专场的分享。本篇内容主要分为三个部分&#xff1a; 1. Flink SQL Insight 2. Best Practices 3. Future Works Tips&#xff1a;点击「阅读原文」查…

【Linux】一文读懂HTTP协议:从原理到应用

​&#x1f320; 作者&#xff1a;阿亮joy. &#x1f386;专栏&#xff1a;《学会Linux》 &#x1f387; 座右铭&#xff1a;每个优秀的人都有一段沉默的时光&#xff0c;那段时光是付出了很多努力却得不到结果的日子&#xff0c;我们把它叫做扎根 目录 &#x1f449;HTTP协议…

机器人工程师与孔乙己文学

本文内容严格按创作模板发布&#xff1a; 孔乙已是鲁迅笔下人物&#xff0c;穷困流倒还穿着象征读书人的长衫&#xff0c;迁腐、麻木。最近&#xff0c;大家自我调佩是“当代孔乙己”&#xff0c;学历成为思想负担&#xff0c;找工作时高不成低不就。你可以从以下几个角度说说…

Android---启动页+闪屏页

目录 启动页 闪屏页 启动页 app 在进入首页面的过程中&#xff0c;都会线加载一张图片然后再进入闪屏页。这样&#xff0c;可以给用户很好的体验。 作用&#xff1a;避免加载白屏页面&#xff0c;进行业务的预处理&#xff08;网络检测、数据预加载...&#xff09; 界面组成…

一款纯Web化免费SQL工具,重新定义数据库管理

SQL Studio是一款由麦聪软件研发的多数据库管理工具&#xff0c;提供Windows、Linux 和 MacOS三种版本的软件包&#xff0c;支持中英文两种语言。SQL Studio是用Java编写的&#xff0c;默认使用 JDK 8进行编译。 下载看这里: [SQLStudio] (http://www.maicongs.com/#/home/web)…

.Net Framework 4.6.1+版本的Winform程序开启Web服务,支持Http webapi

Winform程序开启Web服务 背景思路方法1方法2方法3&#xff08;本文使用的方法&#xff09; 实现在winform程序中引入几个nuget包新建一个Startup类&#xff08;叫什么名字都行&#xff09;修改Program文件创建controller 运行效果(打开浏览器&#xff0c;输入如下地址&#xff…

ThinkPHP模型操作上

ThinkPHP模型操作上 前言模型一、创建模型二、模型操作 总结 前言 在mvc架构中&#xff0c;模型的解释是写逻辑代码的地方&#xff0c;其实还可以这样理解&#xff0c;就是一串操作写在一个模型类中&#xff0c;就是你要完成某一项功能&#xff0c;将这个功能的代码写在一个mod…

记录-使用双token实现无感刷新,前后端详细代码

这里给大家分享我在网上总结出来的一些知识&#xff0c;希望对大家有所帮助 前言 近期写的一个项目使用双token实现无感刷新。最后做了一些总结&#xff0c;本文详细介绍了实现流程&#xff0c;前后端详细代码。前端使用了Vue3Vite&#xff0c;主要是axios封装&#xff0c;服务…

(栈和队列) 232. 用栈实现队列 ——【Leetcode每日一题】

❓232. 用栈实现队列 难度&#xff1a;中等 请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作&#xff08;push、pop、peek、empty&#xff09;&#xff1a; 实现 MyQueue 类&#xff1a; void push(int x) 将元素 x 推到队列的末尾int pop() 从队列…