【Linux】第二十二站:文件(二)深入理解重定向

news2024/12/24 17:58:02

文章目录

  • 一、重定向
    • 1.文件描述符对应的分配规则
    • 2.重定向的接口
  • 二、再次实现myshell
    • 1.实现细节
    • 2.盘点文件与进程替换的一个细节
    • 3.代码
  • 三、1号文件和2号文件的区别
  • 四、如何理解“一切皆文件?”

一、重定向

1.文件描述符对应的分配规则

我们先看如下代码

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

#define filename "log.txt"

int main()
{
    int fd = open(filename,O_CREAT|O_WRONLY|O_TRUNC,0666);
    if(fd < 0)
    {
        perror("open");
        return 1;
    }
    printf("fd : %d\n",fd);
    const char* msg = "hello linux\n";
    int cnt = 5;
    while(cnt)
    {
        write(fd,msg,strlen(msg));
        cnt--;
    }

    close(fd);
    return 0;
}

运行结果为,一切都符合我们的预期

image-20231127162842345

紧接着,我们将代码改为如下

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

#define filename "log.txt"

int main()
{
    close(0);
    int fd = open(filename,O_CREAT|O_WRONLY|O_TRUNC,0666);
    if(fd < 0)
    {
        perror("open");
        return 1;
    }
    printf("fd : %d\n",fd);
    const char* msg = "hello linux\n";
    int cnt = 5;
    while(cnt)
    {
        write(fd,msg,strlen(msg));
        cnt--;
    }

    close(fd);
    return 0;
}

运行结果为

image-20231127165617384

如果我们将代码改为如下,即关闭一号文件

image-20231127165926548

那么运行结果为,将没有任何东西可以打印出来

image-20231127170010669

这是因为1号文件对应的是stout输出流,而printf里面是用到了这个流的。当我们关闭了以后,自然就出现问题了

如果我们关闭的是二号文件

image-20231127172002501

那么结果为

image-20231127172026414

我们可以发现如下现象

当我们关0的时候,为这个新文件分配的文件描述符是0

当我们关1的时候,为这个新文件分配的文件描述符是1

当我们关2的时候,为这个新文件分配的文件描述符是2

这就说明,文件描述符的分配规则很简单,从0下标开始,寻找最小的没有使用的数组位置,它的下标就是新文件的文件描述符

我们来看下面的代码

image-20231127172615148

最终运行结果为

image-20231127172650731

这是因为我们的东西并没有写到这个文件中,而是写入到了显示器文件中。所以才会打印出来。

我们再来看以下代码

image-20231127172836589

运行结果为

image-20231127172917076

这是因为,我们关闭了一号文件,而由于我们又打开了一个新文件,那么最终这个新文件的文件描述符变为了1。所以最终变为了向该文件写入

而这里,我们会发现,本来应该写入到显示器上的内容写入到了文件中,这不就是输出重定向吗?

如下图所示,是我们一开始的状态

image-20231127173921374

后来我们关闭了1号文件,然后打开了一个新的文件。就会将原来的引用计数减减,然后将该指针置空。随后我们创建新文件的时候,会让1号下标的位置指向log.txt这个文件中

image-20231127174302710

而在我们前面的代码中,上层并不知道我们已经将1号文件给改掉了。它只知道要向一号文件写,所以最终变为了向log.txt文件中去写

而上面所说的就是重定向的原理。

所以重定向的本质就是对文件描述符表里面的数组的内容进行修改

2.重定向的接口

我们会发现上面的方法其实有点麻烦,因为我们还需要关闭文件之后,才去打开一个新的文件。

所以操作系统本身就提供了系统调用

image-20231127175304336

int dup2(int oldfd, int newfd);

它的作用是直接将新的文件描述符表数组中的oldfd下标的内容直接拷贝到newfd处。即newfd是要被oldfd所覆盖的

如下图所示,fd代表的是oldfd,1代表的是newfd。

fd的内容最终被拷贝到1号的内容当中。最终保留的就是fd的内容

image-20231127181112181

所以我们就可以写出这个代码了

image-20231127181702696

运行结果为

image-20231127181723935

这样就同样实现了重定向的效果

如果我们将打开的方式换为了O_APPEND

image-20231127181908347

image-20231127181948257

我们会发现这个其实就是追加重定向

我们再来看下面的这段代码

注意这个接口的意思是:从fd中读取count字节个数据到buf中,count是期望读取的数量,返回值是实际读取的数量。

注意它的读取之后,最终不会加上’\0’字符,而fread是C语言的接口,它会自己加上的。所以我们最终需要自己加上这个’\0’字符。

ssize_t read(int fd, void *buf, size_t count);

代码为

image-20231127183759201

运行结果为

image-20231127183823732

然后我们让log.txt的内容如下

image-20231127183946696

代码如下

image-20231127184109563

运行结果为

image-20231127184155076

我们会发现直接读取了,因为我们当前的文件内本身就有内容,所以就默认从文件中读取了

我们会发现它就相当于输入重定向

image-20231127184425755

所以重定向的本质就是对进程的指定文件描述符表中内容拷贝的问题

如果我们的代码是这样子的

image-20231127195455195

运行结果为,符合我们的预期

image-20231127195516197

如果我们将代码改为这样的

image-20231127195817659

那么结果为

image-20231127195804453

他是符合我们的预期的

所以C语言的printf,和fprintf都是往1号文件里写的,不过我们已经提前改了一号文件了。所以就会显示如上的结果

如果我们改为O_APPEND

image-20231127200057332

那么结果也是一样的,符合我们的预期

image-20231127200123154

二、再次实现myshell

1.实现细节

我们知道,像我们平时在命令行中写的重定向是这样的

image-20231127200448554

那么它与我们前面所演示的重定向有什么关系呢?

我们知道,我们前面的代码中,myshell并没有实现重定向功能

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>


#define LEFT "["
#define RIGHT "]"
#define LABLE "#"
#define DELIM " \t"
#define LINE_SIZE 1024
#define ARGC_SIZE 32
#define EXIT_CODE 44

char commandline[LINE_SIZE];
char* argv[ARGC_SIZE] = {NULL};
extern char** environ;
int last_code = 0;
int quit = 0;
char pwd[LINE_SIZE];
char myenv[LINE_SIZE];



const char* getusername()
{
    return getenv("USER");
}
const char* gethostname()
{
    return getenv("HOSTNAME");
}
void getpwd()
{
    getcwd(pwd,sizeof(pwd));
}

void Interact(char* cline,int size)
{
    getpwd();
    printf(LEFT"%s@%s %s"RIGHT""LABLE" ",getusername(),gethostname(),pwd);
    char* s = fgets(cline,size,stdin);
    (void)s;
    assert(s);
    commandline[strlen(cline) - 1] = '\0';
}
int splitstring(char cline[],char* _argv[])
{
    if(strcmp(cline,"") == 0) return 0;
    int i = 0;
    _argv[i++] = strtok(cline,DELIM);
    while(_argv[i++] = strtok(NULL,DELIM));
    return i - 1;
}

void NormalExcute(char* _argv[])
{
    pid_t id = fork();
    if(id < 0)
    {
        perror("fork");
        return;
    }
    else if (id == 0)
    {
        //子进程执行命令
        execvpe(_argv[0],_argv,environ);
        exit(EXIT_CODE);
    }
    else
    {
        int status = 0;
        pid_t rid = waitpid(id,&status,0);
        if(rid == id)
        {
           last_code = WEXITSTATUS(status);
        }
    }
}

int BuildCommand(char* _argv[],int _argc)
{
    if(_argc == 2 && strcmp(_argv[0],"cd") == 0)
    {
        chdir(_argv[1]);
        getpwd();
        sprintf(getenv("PWD"),"%s",pwd);
        return 1;
    }
    else if(_argc == 2 && strcmp(_argv[0],"export") == 0)
    {
        strcpy(myenv,_argv[1]);
        putenv(myenv);
        return 1;
    }
    else if(_argc == 2 && strcmp(_argv[0],"echo") == 0)
    {
        if(strcmp(_argv[1],"$?") == 0)
        {
            printf("%d\n",last_code);
            last_code = 0;
        }
        else if(*_argv[1] == '$')
        {
            char* val = getenv(_argv[1] + 1);
            if(val) printf("%s\n",val);
        }
        else 
        {
            printf("%s\n",_argv[1]);
        }
        return 1;
    }


    if(_argc > 0 && strcmp(_argv[0],"ls") == 0)
    {
        _argv[_argc++] = "--color";
        _argv[_argc] = NULL;
    }
    return 0;
}
int main()
{
    while(!quit)
    {
        //2.交互问题,解决命令行
        Interact(commandline,sizeof(commandline));
        //3.子串分割问题,解析命令行
        int argc = splitstring(commandline,argv);
        if(argc == 0) continue;
        //4.指令的判断(内建命令和普通命令)
        int n = BuildCommand(argv,argc);
        //5.普通命令的执行
        if(!n) NormalExcute(argv);
    }
    return 0;
}

那么我们现在可以为他添加上重定向功能,要解决重定向,我们可以交互函数函数中进行处理一下字符串,当该指令进行执行的时候,处理即可。

如下代码所示

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<ctype.h>
#include<sys/stat.h>
#include<fcntl.h>

#define LEFT "["
#define RIGHT "]"
#define LABLE "#"
#define DELIM " \t"
#define LINE_SIZE 1024
#define ARGC_SIZE 32
#define EXIT_CODE 44
#define NONE       -1
#define IN_RDIR     0
#define OUT_RDIR    1
#define APPEND_RDIR 2

char commandline[LINE_SIZE];
char* argv[ARGC_SIZE] = {NULL};
extern char** environ;
int last_code = 0;
int quit = 0;
char pwd[LINE_SIZE];
char myenv[LINE_SIZE];
char* rdirfilename = NULL;
int rdir = NONE; 

const char* getusername()
{
    return getenv("USER");
}
const char* Gethostname()
{
    return getenv("HOSTNAME");
}
void getpwd()
{
    getcwd(pwd,sizeof(pwd));
}
void check_rdir(char* cmd)
{
    char* pos = cmd;
    while(*pos!='\0')
    {
        if(*pos == '>')
        {
            if(*(pos + 1) == '>')
            {
                *pos++ = '\0';
                *pos++ = '\0';
                while(isspace(*pos)) pos++;
                rdirfilename = pos;
                rdir=APPEND_RDIR;
                break;
            }
            else 
            {
                *pos = '\0';
                pos++;
                while(isspace(*pos)) pos++;
                rdirfilename = pos;
                rdir = OUT_RDIR;
                break;
            }
        }
        else if(*pos == '<')
        {
            *pos = '\0';  //ls -a -l < file.txt
            pos++;
            while(isspace(*pos)) pos++;
            rdirfilename = pos;
            rdir = IN_RDIR;
            break;
        }
        else 
        {
           // do nothing
        }
        pos++;
    }
}
void Interact(char* cline,int size)
{
    getpwd();
    printf(LEFT"%s@%s %s"RIGHT""LABLE" ",getusername(),Gethostname(),pwd);
    char* s = fgets(cline,size,stdin);
    (void)s;
    assert(s);
    commandline[strlen(cline) - 1] = '\0';
    check_rdir(cline);
}
int splitstring(char cline[],char* _argv[])
{
    if(strcmp(cline,"") == 0) return 0;
    int i = 0;
    _argv[i++] = strtok(cline,DELIM);
    while(_argv[i++] = strtok(NULL,DELIM));
    return i - 1;
}

void NormalExcute(char* _argv[])
{
    pid_t id = fork();
    if(id < 0)
    {
        perror("fork");
        return;
    }
    else if (id == 0)
    {
        int fd = 0;
        if(rdir == IN_RDIR)
        {
            fd = open(rdirfilename,O_RDONLY);
            dup2(fd,0);
        }
        else if(rdir == OUT_RDIR)
        {
            fd = open(rdirfilename,O_WRONLY|O_CREAT|O_TRUNC,0666);
            dup2(fd,1);
        }
        else if(rdir == APPEND_RDIR) 
        {
            fd = open(rdirfilename,O_WRONLY|O_CREAT|O_APPEND,0666);
            dup2(fd,1);
        }
        //子进程执行命令
        execvpe(_argv[0],_argv,environ);
        exit(EXIT_CODE);
    }
    else
    {
        int status = 0;
        pid_t rid = waitpid(id,&status,0);
        if(rid == id)
        {
           last_code = WEXITSTATUS(status);
        }
    }
}

int BuildCommand(char* _argv[],int _argc)
{
    if(_argc == 2 && strcmp(_argv[0],"cd") == 0)
    {
        chdir(_argv[1]);
        getpwd();
        sprintf(getenv("PWD"),"%s",pwd);
        return 1;
    }
    else if(_argc == 2 && strcmp(_argv[0],"export") == 0)
    {
        strcpy(myenv,_argv[1]);
        putenv(myenv);
        return 1;
    }
    else if(_argc == 2 && strcmp(_argv[0],"echo") == 0)
    {
        if(strcmp(_argv[1],"$?") == 0)
        {
            printf("%d\n",last_code);
            last_code = 0;
        }
        else if(*_argv[1] == '$')
        {
            char* val = getenv(_argv[1] + 1);
            if(val) printf("%s\n",val);
        }
        else 
        {
            printf("%s\n",_argv[1]);
        }
        return 1;
    }


    if(_argc > 0 && strcmp(_argv[0],"ls") == 0)
    {
        _argv[_argc++] = "--color";
        _argv[_argc] = NULL;
    }
    return 0;
}
int main()
{
    while(!quit)
    {
        rdirfilename = NULL;
        rdir = NONE;
        //2.交互问题,解决命令行
        Interact(commandline,sizeof(commandline));
        //3.子串分割问题,解析命令行
        int argc = splitstring(commandline,argv);
        if(argc == 0) continue;

        //4.指令的判断(内建命令和普通命令)
        int n = BuildCommand(argv,argc);
        //5.普通命令的执行
        if(!n) NormalExcute(argv);
    }
    return 0;
}

在上面的代码中,当我们输入完指令字符串以后,然后去寻找是否存在重定向的符号,如果有,改变当前状态为输入/输出/追加重定向,然后将重定向的文件名给记录下来。最终达到分开指令与文件的目的。

分开以后,当我们进行指令的执行的时候,我们暂时只考虑子进程,如果是存在重定向的话,那么就打开对应的文件,然后将该文件的文件描述符放到对应的输入或输出位置上。就可以了。

最终的结果为如下

image-20231128164958787

image-20231128165006593

2.盘点文件与进程替换的一个细节

我们在前面的代码中

在后面我们做了重定向的工作,后面我们在进行程序替换的时候,难道不会影响吗???

在我们之前,我们已经了解了如下的东西

当一个可执行程序加载到内存的时候,会创建出对应的PCB结构体,在tash_struct这个结构体里面,有一个指针,会指向进程地址空间,然后进程地址空间根据页表找到实际的物理内存。

然后CPU就会去找到这个进程,从而进行去调度

image-20231128170345068

而现在,我们知道当我们打开一个文件的时候,会创建出它的结构体struct file

image-20231128172612430

随后为了管理起来,task_struct中有一个指针,指向文件描述符表,在这个表中的下标对应着每一个文件。

image-20231128173022187

其中左侧的这一堆,我们都把他叫做,内核数据结构

image-20231128173215796

当我们再度创建一个文件的时候

image-20231128174328772

而我们上面进程替换替换是右边的部分,并不会对左边的部分有影响

image-20231128174423713

这里就是内存管理与文件管理的解耦

进程历史打开的文件与进行的各种重定向关系都和未来进行程序替换无关

程序替换,并不影响文件访问

3.代码

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<ctype.h>
#include<sys/stat.h>
#include<fcntl.h>

#define LEFT "["
#define RIGHT "]"
#define LABLE "#"
#define DELIM " \t"
#define LINE_SIZE 1024
#define ARGC_SIZE 32
#define EXIT_CODE 44
#define NONE       -1
#define IN_RDIR     0
#define OUT_RDIR    1
#define APPEND_RDIR 2

char commandline[LINE_SIZE];
char* argv[ARGC_SIZE] = {NULL};
extern char** environ;
int last_code = 0;
int quit = 0;
char pwd[LINE_SIZE];
char myenv[LINE_SIZE];
char* rdirfilename = NULL;
int rdir = NONE; 

const char* getusername()
{
    return getenv("USER");
}
const char* Gethostname()
{
    return getenv("HOSTNAME");
}
void getpwd()
{
    getcwd(pwd,sizeof(pwd));
}
void check_rdir(char* cmd)
{
    char* pos = cmd;
    while(*pos!='\0')
    {
        if(*pos == '>')
        {
            if(*(pos + 1) == '>')
            {
                *pos++ = '\0';
                *pos++ = '\0';
                while(isspace(*pos)) pos++;
                rdirfilename = pos;
                rdir=APPEND_RDIR;
                break;
            }
            else 
            {
                *pos = '\0';
                pos++;
                while(isspace(*pos)) pos++;
                rdirfilename = pos;
                rdir = OUT_RDIR;
                break;
            }
        }
        else if(*pos == '<')
        {
            *pos = '\0';  //ls -a -l < file.txt
            pos++;
            while(isspace(*pos)) pos++;
            rdirfilename = pos;
            rdir = IN_RDIR;
            break;
        }
        else 
        {
           // do nothing
        }
        pos++;
    }
}
void Interact(char* cline,int size)
{
    getpwd();
    printf(LEFT"%s@%s %s"RIGHT""LABLE" ",getusername(),Gethostname(),pwd);
    char* s = fgets(cline,size,stdin);
    (void)s;
    assert(s);
    commandline[strlen(cline) - 1] = '\0';
    check_rdir(cline);
}
int splitstring(char cline[],char* _argv[])
{
    if(strcmp(cline,"") == 0) return 0;
    int i = 0;
    _argv[i++] = strtok(cline,DELIM);
    while(_argv[i++] = strtok(NULL,DELIM));
    return i - 1;
}

void NormalExcute(char* _argv[])
{
    pid_t id = fork();
    if(id < 0)
    {
        perror("fork");
        return;
    }
    else if (id == 0)
    {
        int fd = 0;
        if(rdir == IN_RDIR)
        {
            fd = open(rdirfilename,O_RDONLY);
            dup2(fd,0);
        }
        else if(rdir == OUT_RDIR)
        {
            fd = open(rdirfilename,O_WRONLY|O_CREAT|O_TRUNC,0666);
            dup2(fd,1);
        }
        else if(rdir == APPEND_RDIR) 
        {
            fd = open(rdirfilename,O_WRONLY|O_CREAT|O_APPEND,0666);
            dup2(fd,1);
        }
        //子进程执行命令
        execvpe(_argv[0],_argv,environ);
        exit(EXIT_CODE);
    }
    else
    {
        int status = 0;
        pid_t rid = waitpid(id,&status,0);
        if(rid == id)
        {
           last_code = WEXITSTATUS(status);
        }
    }
}

int BuildCommand(char* _argv[],int _argc)
{
    if(_argc == 2 && strcmp(_argv[0],"cd") == 0)
    {
        chdir(_argv[1]);
        getpwd();
        sprintf(getenv("PWD"),"%s",pwd);
        return 1;
    }
    else if(_argc == 2 && strcmp(_argv[0],"export") == 0)
    {
        strcpy(myenv,_argv[1]);
        putenv(myenv);
        return 1;
    }
    else if(_argc == 2 && strcmp(_argv[0],"echo") == 0)
    {
        if(strcmp(_argv[1],"$?") == 0)
        {
            printf("%d\n",last_code);
            last_code = 0;
        }
        else if(*_argv[1] == '$')
        {
            char* val = getenv(_argv[1] + 1);
            if(val) printf("%s\n",val);
        }
        else 
        {
            printf("%s\n",_argv[1]);
        }
        return 1;
    }


    if(_argc > 0 && strcmp(_argv[0],"ls") == 0)
    {
        _argv[_argc++] = "--color";
        _argv[_argc] = NULL;
    }
    return 0;
}
int main()
{
    while(!quit)
    {
        rdirfilename = NULL;
        rdir = NONE;
        //2.交互问题,解决命令行
        Interact(commandline,sizeof(commandline));
        //3.子串分割问题,解析命令行
        int argc = splitstring(commandline,argv);
        if(argc == 0) continue;

        //4.指令的判断(内建命令和普通命令)
        int n = BuildCommand(argv,argc);
        //5.普通命令的执行
        if(!n) NormalExcute(argv);
    }
    return 0;
}

三、1号文件和2号文件的区别

当我们使用如下代码的时候

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

#define filename "log.txt"

int main()
{
    fprintf(stdout,"hello normal message\n");
    fprintf(stdout,"hello normal message\n");
    fprintf(stdout,"hello normal message\n");
    fprintf(stdout,"hello normal message\n");
    fprintf(stdout,"hello normal message\n");
 
    fprintf(stderr,"hello error message\n");
    fprintf(stderr,"hello error message\n");
    fprintf(stderr,"hello error message\n");
    fprintf(stderr,"hello error message\n");
    fprintf(stderr,"hello error message\n");
    return 0;
}


运行结果为,我们可以看到,这两个文件都是往显示器上打印的,似乎没有什么区别

image-20231128175412685

但是如果我们这样做

image-20231128175558749

我们会发现一部分重定向了,一部分没有重定向

在一开始的时候是这样的

image-20231128180433678

随后发生了重定向,此时这个重定向仅仅是对于1号的文件的重定向

image-20231128180524995

这样的话,凡是原来往1里面写的,就会写进这个normal.log文件了

往2里面写的就往显示屏上打印了

如果我们在命令行中的是这样的

image-20231128180848142

其实上面是一个简写

下面是完整的,代表1重定向到normal.log,2重定向到err.log

./mytest 1>normal.log 2>err.log

这样的话就可以分开了

如果我们就想要重定向到一个文件中,那么可以这样做

./mytest 1>all.log 2>&1

image-20231128181522122

同理,前面的这个1也是可以省略的

image-20231128181631739

这个命令中

2>&1的意思是把1号文件描述符里面的内容写到2号文件描述符中。

这个的前提是已经把前半部分指令的操作做完了。而1已经指向这个这个all了。

所以最终1和2都指向这个文件了。所以就全部写入到一个文件中了

四、如何理解“一切皆文件?”

我们知道系统中有很多设备,如下图所示

这些外设,几乎都要去提供他们的读写方法,只不过对于键盘而言,写方法为空,对于显示器而言,读方法为空而已。但是他们都有读写方法

image-20231128222532291

他们都可以用同一种结构体来表示。然后用类似的读写接口

image-20231128223058653

对于这些硬件,我们都可以以文件的方式用open打开。然后创建对应的struct file内核数据结构。

image-20231128223741964

然后由于我们要对这些设备进行读写

所以linux内核提供了另外一个方法表的数据结构

struct operation_func()
{
	 int (*wirtep)();
	 int (*readp)();
}

当我们想要访问某个设备的时候。会创建这个方法表的数据结构,然后提供一个指针指向这个方法表,方法表中的函数指针就指向对应的接口

image-20231128224429863

如此一来,task_struct就会通过文件描述符表中的数组从而去调用对应的方法数据结构,最终达到调用read函数

这样就可以实现上层都是调用一样的函数了,而根据下层的不同动态的调用不同的方法了

image-20231128225646625

所以所谓的一切皆文件,就是相当于在文件这一层封装了一个文件对象,让文件对象中的指针指向不同设备的函数方法,然后通过上层的一个方法数据结构,来对这些进行汇总。使得我们压根就不需要关心下层是如何的。只需要知道,以后要读取这个文件就调用这个方法即可

image-20231128230206588

所以从文件对象这一层,往上就有了一切皆文件,这一层我们也叫做,VFS(虚拟文件系统)

image-20231128230332248

而我们似乎就可以发现,这不就是C++中的多态吗?

上层都是基类,下层就是派生类。

所以面向对象就是历史的必然!

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

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

相关文章

【Qt开发流程】之打印文档

描述 Qt为打印提供了广泛的跨平台支持。使用每个平台上的打印系统&#xff0c;Qt应用程序可以打印到连接的打印机上&#xff0c;也可以通过网络打印到远程打印机上。Qt的打印系统还支持PDF文件生成&#xff0c;为基本报表生成工具提供了基础。 支持打印的类 下面的类支持选择…

【c++|SDL】三、画图类抽象

every blog every motto: You can do more than you think. https://blog.csdn.net/weixin_39190382?typeblog 0. 前言 1. 画图类抽象 之前将画图&#xff08;显示图像&#xff09;放在Game.cpp中,现在将其单独放在一个类中, 编译运行为&#xff1a; g m14main.cpp Game.…

Course1-Week3-分类问题

Course1-Week3-分类问题 文章目录 Course1-Week3-分类问题1. 逻辑回归1.1 线性回归不适用于分类问题1.2 逻辑回归模型1.3 决策边界 2. 逻辑回归的代价函数3. 实现梯度下降4. 过拟合与正则化4.1 线性回归和逻辑回归中的过拟合4.2 解决过拟合的三种方法4.3 正则化4.4 用于线性回归…

C语言之结构体详解

C语言之结构体详解 文章目录 C语言之结构体详解1. 结构体类型的声明2. 结构体变量的创建和初始化3. 结构体的特殊声明4. 结构体的自引用结构体的自引用匿名结构体的自引用 5. 结构体内存对齐5.1 练习一5.2 练习三 6. 为什么存在内存对⻬? 1. 结构体类型的声明 struct tag {me…

【Web】UUCTF 2022 新生赛 个人复现

目录 ①websign ②ez_rce ③ez_upload ④ez_unser ⑤ezsql ⑥ezpop ⑦funmd5 ⑧phonecode ⑨ezrce ①websign 右键打不开&#xff0c;直接抓包发包看源码 ②ez_rce “反引号” 在PHP中会被当作SHELL命令执行 ?codeprintf(l\s /); ?codeprintf(ta\c /ffffffffffl…

leetCode 131.分割回文串 + 动态规划 + 回溯算法 + 优化 + 图解 + 笔记

我的往期文章&#xff1a; leetCode 647.回文子串 动态规划 优化空间 / 中心扩展法 双指针-CSDN博客https://blog.csdn.net/weixin_41987016/article/details/133883091?spm1001.2014.3001.5501leetCode 131.分割回文串 回溯算法 图解 笔记-CSDN博客https://blog.csdn.n…

有什么值得推荐的node. js练手项目吗?

前言 可以参考一下下面的nodejs相关的项目&#xff0c;希望对你的学习有所帮助&#xff0c;废话少说&#xff0c;让我们直接进入正题 1、 NodeBB Star: 13.3k 一个基于Node.js的现代化社区论坛软件&#xff0c;具有快速、可扩展、易于使用和灵活的特点。它支持多种数据库&…

在Windows中加密文件或文件夹不需要太大的努力就可以实现,主要有两种加密方法

如果你正在寻找一种在Windows计算机上保持文件和文件夹隐私的简单方法,你有几个选择。 得益于Microsoft Office Suite,你可以使用内置的加密功能对Office文件(如Word文档或PowerPoint演示文稿)进行密码保护。 一些Windows操作系统还配备了加密文件系统(EFS),可以对任何…

Set集合的特点

Set系列集合特点&#xff1a; 无序&#xff1a;添加数据的顺序和获取出的数据顺序不一致&#xff1b;不重复&#xff1b;无索引&#xff1b; HashSet&#xff1a;无序&#xff0c;不重复&#xff0c;无索引 LinkedHashSet&#xff1a;有序&#xff0c;不重复&#xff0c;无索引…

python进阶技巧

1.闭包 通过函数嵌套&#xff0c;可以让内部函数依赖外部变量&#xff0c;可以避免全局变量的污染问题 闭包注意事项&#xff1a; 总结&#xff1a; 2.装饰器 2.1装饰器的一般写法 2.2 装饰器的语法糖写法 def outer(func):def inner():print(睡了)func()print(起床)retur…

Zookeeper从零入门笔记

Zookeeper从零入门笔记 一、入门1. 概述2. 特点3. 数据结构4. 应用场景 二、本地1.安装2. 参数解读 三、集群操作3.1.1 集群安装3.2 选举机制1. 第一次启动2. 非第一次启动 3.3 ZK集群启动停止脚本3.4 客户端命令行操作3.2.1 命令行语法3.2.2 节点类型&#xff08;持久/短暂/有…

飞致云开源社区月度动态报告(2023年11月)

自2023年6月起&#xff0c;中国领先的开源软件公司FIT2CLOUD飞致云以月度为单位发布《飞致云开源社区月度动态报告》&#xff0c;旨在向广大社区用户同步飞致云旗下系列开源软件的发展情况&#xff0c;以及当月主要的产品新版本发布、社区运营成果等相关信息。 飞致云开源大屏…

传统算法:使用 Pygame 实现选择排序

使用 Pygame 模块实现了选择排序的动画演示。首先,它生成一个包含随机整数的数组,并通过 Pygame 在屏幕上绘制这个数组的条形图。接着,通过选择排序算法对数组进行排序,动画效果可视化每一步的排序过程。在排序的过程中,程序找到未排序部分的最小元素,并将其与未排序部分…

如何获取阿里巴巴中国站按图搜索1688商品(拍立淘) API接口(item_search_img-按图搜索1688商品(拍立淘))

一、背景介绍 阿里巴巴中国站作为中国领先的B2B电子商务平台&#xff0c;提供了大量的商品信息和交易服务。其中&#xff0c;按图搜索1688商品&#xff08;拍立淘&#xff09;是阿里巴巴中国站特有的功能之一&#xff0c;它可以通过上传图片来搜索与图片相似的商品&#xff0c…

Js页面录屏切片存储数据上传后端

前端 screenShot(){// 获取屏幕共享流navigator.mediaDevices.getDisplayMedia({video: true,audio: true,//preferCurrentTab 共享本次操作的页面preferCurrentTab: true}).then(async stream > {const mediaRecorder new MediaRecorder(stream, { mimeType: video/webm; …

基于AT89C51单片机的秒表设计

1&#xff0e;设计任务 利用单片机AT89C51设计秒表&#xff0c;设计计时长度为9:59:59&#xff0c;超过该长度&#xff0c;报警。创新&#xff1a;设置重启&#xff1b;暂停&#xff1b;清零等按钮。最后10s时播放音乐提示。 本设计是采用AT89C51单片机为中心&#xff0c;利用其…

代码随想录算法训练营第一天 | 704. 二分查找 27. 移除元素

class Solution { public:int search(vector<int>& nums, int target) {int l0;int rnums.size()-1;while(l<r){int mid(lr)>>1;if(targetnums[mid]) return mid;if(target>nums[mid]){lmid1;}else{rmid-1;}}return -1;} }; 之前就已经熟悉二分法了&am…

【Linux】第二十三站:缓冲区

文章目录 一、一些奇怪的现象二、用户级缓冲区三、用户级缓冲区刷新问题四、一些其他问题1.缓冲区刷新的时机2.为什么要有这个缓冲区3.这个缓冲区在哪里&#xff1f;4.这个FILE对象属于用户呢&#xff1f;还是操作系统呢&#xff1f;这个缓冲区&#xff0c;是不是用户级的缓冲区…

Linux实现类似cp的命令

1.利用主函数的三个函数进行传参 1).主函数的三个参数的含义: argc:主函数的参数个数 argv:主函数的参数内容 envp:环境变量; 2).演示代码: #include <stdio.h> #include <stdlib.h> #include <unistd.h> int main(int argc,char *argv[],char *envp[]…

Vue3.x 中 hooks 函数封装和使用

一、hooks 是什么 vue3 中的 hooks 就是函数的一种写法&#xff0c;就是将文件的一些单独功能的 js 代码进行抽离出来进行封装使用。 它的主要作用是 Vue3 借鉴了 React 的一种机制&#xff0c;用于在函数组件中共享状态逻辑和副作用&#xff0c;从而实现代码的可复用性。 注…