【Linux】基础IO-文件描述符

embedded/2024/11/28 3:12:51/

【Linux】基础IO

  • C语言的文件接口
  • 文件的初步理解
  • 文件IO的系统接口
    • 打开文件
    • write
    • read
  • 文件描述符fd
  • 语言层的fd
  • 文件描述符的分配规则
  • 重定向和缓冲区的理解
    • 重定向
    • 缓冲区
      • 作用
      • 刷新策略
      • C语言的缓冲区
  • 模拟实现重定向
    • 检查是否是重定向
    • 执行命令
  • 0、1、2的作用

C语言的文件接口

这里我们简单回顾一下C语言的文件操作

使用 “w” 模式打开文件 log.txt,如果文件不存在就创建文件;文件存在,打开文件后清空内容

使用 fprintf 向文件中写入字符串

int main()    
{    const char* message = "hello Linux!";    FILE* fp = fopen("log.txt", "w");    fprintf(fp, "%s\n", message);    fclose(fp);                                                                                                                                                return 0;    
}

在这里插入图片描述

每次使用 w 模式打开文件都会清空文件内容,如果先要追加内容就要使用 a 模式打开文件

FILE* fp = fopen("log.txt", "a");

在这里插入图片描述

C语言文件操作:

  • 使用 w 模式打开文件时,文件不存在就创建;文件存在就清空内容
  • 使用 a 模式打开文件时,会在文件原有内容基础上追加内容

Linux 的重定向操作符与此有异曲同工之妙:

  • >:将数据重定向到指定文件,如果文件不存在就创建,存在就清空内容

在这里插入图片描述

  • >>:将数据追加到指定文件

在这里插入图片描述

由此我们可知,重定向也是在打开文件,将数据写入到文件

文件的初步理解

虽然我们可以使用接口来进行文件相关的操作,但是我们真的理解文件吗?

我们通过上面的程序打开了文件,其而程序启动后就会被加载为进程,所以打开文件的本质就是进程打开了文件,是 CPU 执行我们写的代码

而一个进程可以打开多个文件吗?当然可以,就是多调用几次接口嘛。也就是说一个进程可以打开多个文件,而系统中的进程肯定不止一个,那么系统内就会存在大量的文件。所以就需要操作系统将这些文件统一管理起来,学习了进程后,我们猜想可能会有类似于 PCB 的数据结构,管理这些数据结构就是管理文件。关于这一点我们后面就知道了,这里先放一放

当我们创建一个空文件时,它会占用空间吗?会占用,虽然它的内容是空的,但是还是需要空间来储存文件属性的,例如文件创建时间,文件类型等。也就是说文件=属性+内容

文件IO的系统接口

除了C语言的文件接口,我们还可以使用系统接口进行文件访问

打开文件

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);

参数说明:

pathname:要打开/创建的文件名

flags:标记位,打开文件的方式,为什么是一个 int 呢?——这个 int 是位图,打开方式可以是多个,传递时进行或运算就可以将多种打开方式一并传递给 flags

  • O_RDONLY:只读打开
  • O_WRONLY:只写打开
  • O_RDWR:读写打开
  • 这三个常量,必须指定一个且只能指定一个
  • O_CREAT:若文件不存在,则创建它。需要使用mode选项,来指明新文件的访问权限
  • O_APPEND:追加写
  • O_TRUNC:打开时清空文件内容

mode:定义新创建文件的权限,例如要求所有人可以读写:0666

返回值:

失败返回 -1;成功返回文件描述符号,这里先不管,作用类似于C语言的FILE*,用来找到打开的文件

write

#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);

参数:

fd:文件描述符,可以找到打开的文件

buf:缓冲区首地址,可以理解为要写入的数据的首地址

count:要写入多少字节的数据

返回值:实际写了多少字节数据

现在将我们上面代码中的C语言接口替换为系统调用接口

#include <stdio.h>    
#include <sys/types.h>    
#include <sys/stat.h>    
#include <fcntl.h>    
#include <string.h>    
#include <unistd.h>    int main()    
{    const char* message = "hello Linux!\n";// 打开                                                                                                         int fd = open("log.txt", O_WRONLY | O_CREAT, 0666); // 写入数据write(fd, message, strlen(message));    close(fd); // 关闭文件描述符指向的文件    return 0;    
}

read

与 write 的使用类似

int main()
{const char* message = "hello Linux!\n";// 只读打开int fd = open("log.txt", O_RDONLY);char buf[1024];// 将数据读入到 bufread(fd, buf, strlen(message));printf("%s",buf);return 0;
}

在这里插入图片描述

以上就是关于文件的系统接口的简单使用,我们可以感觉到和C语言文件接口的使用很相似。

而实际上语言是给我们用户层面使用的,而用户是不被允许直接修改操作系统的数据的,所以才会有系统调用接口供我们使用,而C语言的文件接口就是封装的系统调用接口

在这里插入图片描述

文件描述符fd

可以多打开几个文件,看看这 fd 的值到底是怎么样的:

int main()    
{    int fd1 = open("fd1.txt", O_WRONLY|O_CREAT);    printf("%d\n", fd1);    int fd2 = open("fd2.txt", O_WRONLY|O_CREAT);    printf("%d\n", fd2);                                                                                                                                       int fd3 = open("fd3.txt", O_WRONLY|O_CREAT);    printf("%d\n", fd3);    close(fd1);close(fd2);close(fd3);return 0;    
} 

在这里插入图片描述

可以看到我们打开的三个文件的描述符分别是 3、4、5,难道文件描述符是从 3 开始的吗?——当然不是,文件描述符是从 0 开始的,而 0、1、2 这三个描述符已经被其他文件占用了,它们分别是:

  • 0:标准输入,键盘
  • 1:标准输出,显示器
  • 2:标准错误,显示器

在C语言中,我们知道,当程序启动时,会自动打开三个文件:stdin、stdout、stderr,这就是对标的 0、1、2 三个文件描述符

在这里插入图片描述

那么说了这么多,文件描述符它到底是什么呢?这里我们就要回归到操作系统是怎么组织管理文件这个问题了

实际上,打开一个文件后,就会在系统内核中创建一个名为 strcut file 的数据结构,file 中储存着文件的属性

而文件=属性+内容,打开文件的同时,会开辟一块空间来当作文件的缓冲区,而打开文件后,无论是读还是写,都会将文件的内容从硬盘加载到缓冲区,file 会指向这一块空间

在这里插入图片描述

系统中不可能只存在一个打开的文件,不同的文件的 file 会相互链接在一起,形成一个链表

在这里插入图片描述

这么多的文件,一个进程可能打开很多文件,那么进程如何知道自己打开的是哪一个文件呢?

进程 PCB 中,存在一个指针 struct files_struct* files,指向了一张表 files_struct。这张表最重要的部分就是包含了一个指针数组 struct flie* fd_array[],数组中每个元素都是一个指针,指向了打开文件,这个数组就叫文件描述符表

在这里插入图片描述

数组的下标就是文件描述符,凭借文件描述符就可以找到打开的文件

现在我们理解了什么是文件描述符,那么为什么 0、1、2 这三个东西是默认打开的?它们又不是文件,而是硬件啊?

这些硬件在冯诺依曼体系中属于IO外设,虽然是硬件,但是在 Linux 看来:一切皆文件

一切皆文件,这些外设也可以抽象为一个个数据结构 struct device,其中存储着设备的名称、类型、状态等等。也就是说 device 中存储的是设备的属性,那么就可以被 struct file 指向,因为 file 中存储的就是文件的属性

在这里插入图片描述

而想使用这些设备,例如读取键盘的数据,向屏幕写入数据等等,需要特定的方法,这些方法由驱动层提供。而我们想从缓冲区写出数据到设备或者将设备的数据读入到缓冲区,势必离不开这些方法。所以这些方法也应该放到 file 中,这些方法是以函数指针的形式存入到 file 中的,且是一个集合,称作方法集

这样加一层后,虽然每个设备的方法、属性不同,但是都被封装到了 file 中,在 file 层面来看是没有这些不同的

在这里插入图片描述

所以现在我们理解了外设可以被加载为文件,我们可以通过一些手段查看一下 0、1、2 指向的设备

先启动一个进程并获取它的 pid

int main()
{pid_t pid = getpid();    while(1)    {    sleep(1);                                                                                                                                                printf("pid:%d\n", pid);    }    return 0;    
} 

然后到 etc/proc/进程id 目录下,可以看到有一个 fd 文件夹,里面就存放着此进程打开的文件的 fd

在这里插入图片描述

可以看到,确实有0、1、2 三个文件描述符,它们都指向了一个设备,这是因为我这里的机器是云服务器,所以指向的设备是同一个终端。此时我们可以编写代码,向这个设备写入数据,就会看到终端有数据输出

int main()
{const char* msg = "hello!\n";int fd = open("/dev/pts/1", O_WRONLY);if (fd < 0) return -1;  else{                   while(1)              {                     sleep(1);           write(fd,msg, strlen(msg));                                            }                                         }                return 0;        
}

在这里插入图片描述

语言层的fd

了解了什么是 fd 之后,我们知道,文件的操作离不开 fd。而其他语言的文件操作是对系统调用接口的封装,所以肯定离不开对 fd 进行封装

例如C语言,打开文件时会返回一个指针 FILE*,后面对文件的操作离不开这个指针。FILE 就是C语言封装的一个结构体,里面包含了文件描述符 fd

我们可以把 FILE* 中的数据打印出来看看

int main()
{ FILE* pf1 = fopen("fd1.txt", "w");    printf("pf1:%d\n", pf1->_fileno);    FILE* pf2 = fopen("fd2.txt", "w");    printf("pf2:%d\n", pf2->_fileno);    FILE* pf3 = fopen("fd3.txt", "w");    printf("pf3:%d\n", pf3->_fileno);    fclose(pf1);    fclose(pf2);    fclose(pf3);                                                                                                                                               return 0;    
}

在这里插入图片描述

可以看到,和之前使用系统调用打印出来的文件描述符一样

文件描述符的分配规则

我们已经知道,进程启动时会默认打开三个文件,0、1、2就会被占用,后面打开的文件的描述符都是从 3 开始

如果一开始就把 0、1、2这三个文件关掉,再再打开我们自己的文件,那么文件描述符又会怎么分配呢?

尝试关闭0、2,然后打开文件并打印文件描述符

int main()                               
{                                        close(0);                              close(2);                              int fd1 = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);      if (fd1 < 0) return 1;      int fd2 = open("log2.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);      if (fd2< 0) return 1;      printf("this is fd1:%d\n", fd1);      fprintf(stdout, "this is fd2:%d\n", fd2);                                                                                                                  close(fd1);    close(fd2);                return 0;      
}

在这里插入图片描述

从代码运行结果来看,文件描述符的分配规则是这样的:当一个新文件打开时,会从文件描述符表 fd_array 寻找最小的没有被占用的下标,作为新文件的文件描述符

那为什么只关闭0、2,不关闭1呢?因为 1 是标准输出,关闭了就看不到打印结果了。但是我们可以通过别的方式查看:关闭 1 以后,新打开一个文件,理论上这个文件的文件描述符就会分配 1,然后我们向这个文件中写入数据,不就可以查看结果了么

int main()                                                                                                                                                   {   close(1);int fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);if (fd < 0) return 1;                                            printf("this is fd:%d\n", fd);fprintf(stdout, "this is fd:%d\n", fd);fflush(stdout); // 刷新缓冲区               close(fd);    return 0;     
}

在这里插入图片描述

但是很奇怪的一点是:printf 是向标准输出写入数据,fprintf 我们也规定向标准输出写入数据,但是怎么都写到了 log.txt 中了?

上面已经说过,C语言的文件接口都是封装的系统调用,其中 stdout 是一个结构体FILE对象的指针,FILE结构体封装了文件描述符 fd,且 stdout 的 fd 为1。这样 stdout 就可以通过 fd 找到文件描述符为 1 的文件,但是此时文件描述符 1 指向的文件已经被我们更改了,不再指向标准输出,而是指向文件 log.txt

所以我们向 stdout 中写入数据,stdout 又拿着 fd 找到了文件 log.txt,将数据写入到文件中

在这里插入图片描述

那么代码中的 fflush(stdout) 又是什么作用呢?可不可以去掉呢?如果去掉,数据就会丢失

在这里插入图片描述

这是为什么呢?——文件在内核中有缓冲区,而C语言也有语言层的缓冲区,通常叫做用户级缓冲区,存在于结构体FILE中。我们使用语言层的文件接口写数据时,默认是向用户级缓冲区写数据,然后达成某些条件时,就会被刷新到内核层的缓冲区,而系统调用的 close 关闭文件时,会将内核缓冲区的数据刷入到硬盘,然后关闭文件

在这里插入图片描述

如果使用 close 关闭文件时数据还留在用户级缓冲区没有刷新,然后内核级缓冲区是空的,系统调用将内核缓冲区数据刷新到文件,导致了数据的丢失

重定向和缓冲区的理解

重定向

上面我们把本来要写入到标准输出的数据写到了文件中,这不就是重定向吗?但是这个重定向的实现方式有点挫啊,有没有优雅一点的方法呢?有的,系统中有个接口,可以更改文件描述符指向的内容

#include <unistd.h>
int dup2(int oldfd, int newfd);

描述:

在这里插入图片描述

简单来说,就是将 oldfd 的内容拷贝到 newfd,那么 oldfd 和 newfd 都会指向同一个文件。所以说,重定向的本质就是文件描述符下标内容的拷贝

例如,进程中默认打开 0、1、2三个文件,我们又打开自己的文件 log.txt,文件描述符为 fd。如果我们想把写入到标准输出的内容重定向到文件,就可以把文件描述符表中,fd 对应的内容拷贝到 1 号下标中,这样 1 号文件描述符指向的文件就是 log.txt 了,向标准输出写数据就是向 log.txt 写内容

实践一下:

int main()                                                                                                                                                   {   // 打开 log.txtint fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);if (fd < 0) return 1;                                            // 重定向dup2(fd, 1); // 写入数据到 stdprintf("this is fd:%d\n", fd);    fprintf(stdout, "this is fd:%d\n", fd);    fflush(stdout);close(fd);                                                                                                                                                 return 0;     
}

在这里插入图片描述

缓冲区

作用

为什么语言层也要设置一个用户级缓冲区呢?我不可以直接将数据写到内核缓冲区吗?这不是多此一举吗?其实缓冲区也是有妙用的:

  1. 解耦:把用户和操作系统解耦,用户只需要将数据写到缓冲区即可,不需要操心怎么把数据搞到内核缓冲区,再刷新到硬盘
  2. 提高效率:多了一层不是还要拷贝吗?为什么还提高了效率了?——提高的是用户使用的效率
    1. 用户只需要向缓冲区读写数据即可,效率更高
    2. 系统调用是有消耗的,越少调用,效率越高。如果先把数据丢到用户级缓冲区,达成某个条件时,直接使用一次系统调用即可将数据刷新到内核缓冲区;内核缓冲区同理,先缓存数据,然后一次刷新到硬盘文件即可,极大减少了频繁使用系统调用的消耗

刷新策略

  1. 立即刷新:例如C语言的 fflush,系统调用的 fsync,都可以刷新缓冲区
  2. 行刷新:这个比较特殊,显示器采用的策略就是这样的,因为这样更符合用户的使用习惯
  3. 满了才刷新:普通文件

特殊情况:

  1. 程序结束,会自动刷新缓冲区。之前进程说过的 exit 会刷新缓冲区,_exit 则不会
  2. 强制刷新,类似于立即刷新

此时我们写一个代码,先使用语言层接口向标准输出打印数据,再使用系统调用打印数据。标准输出也就是屏幕,这里采用的刷新策略是行刷新

int main()
{                          // C                     printf("hello,printf!\n");fprintf(stdout, "hello, fprintf!\n");// sysytem callconst char* msg = "hello write!\n";                                                                                                                        write(1, msg, strlen(msg));return 0;                      
}

在这里插入图片描述

可以看到,确实是按照我们输出的顺序一行一行地刷新的。那么我们再将这些数据进行重定向输出到 log.txt 文件看看效果

在这里插入图片描述

输出的顺序发生了变化,这是为什么呢?这是因为我们进行了重定向输出,本质还是打开了 log.txt 文件,因为是文件,所以缓冲区刷新策略变为了满了才刷新

printf 和 fprintf 都是语言层的接口,所以默认将数据写入了用户级缓冲区,而 write 是系统接口,直接将数据写入到了内核缓冲区

在这里插入图片描述

代码中还有一个细节,没有关闭文件。根据刷新策略,程序结束时,用户级缓冲区的数据会被刷新到内核缓冲区,接着内核缓冲区的数据被刷新到硬盘的文件中。所以打印的顺序发生了变化

在这里插入图片描述

这时候我们再来添加一条语句,分别将数据输出到屏幕和文件

int main()
{                          // C                     printf("hello,printf!\n");fprintf(stdout, "hello, fprintf!\n");// sysytem callconst char* msg = "hello write!\n";                                                                                                                        write(1, msg, strlen(msg));fork(); // 创建子进程return 0;                      
}

输出到屏幕:

在这里插入图片描述

没什么变化,再来看看重定向输出到文件

在这里插入图片描述

为什么 printf 和 fprintf 多打印了一遍呢?问题肯定出在 fork() 上,创建了子进程

上面我们说过,在程序结束之前,printf 和 fprintf 一开始是在用户级缓冲区中的。然后父进程创建子进程,此时程序也要结束了,那么父子进程都会刷新用户级的缓冲区,也就是刷新了两次数据到内核缓冲区,所以才会有两对 printf 和 fprintf 的输出

C语言的缓冲区

说了这么多,我们可以看一下C语言的缓冲区长什么样子

在 /usr/include/stdio.h

在这里插入图片描述

而 struct _IO_FILE 在 /usr/include/libio.h

在这里插入图片描述

模拟实现重定向

把我们之前写的 shell-链接,模拟实现一下重定向的实现

检查是否是重定向

如果输入的命令是这样 ls -a -l > log.txt在获取到用户输入命令后,需要进行检查是否具有重定向符号

// 获取用户输入命令
// ...CheckRedir(usercommand);// 分割命令行字符串
// ...

我们需要获取命令的重定向相关信息,例如重定向的类型,重定向的文件

// 重定向相关                                                                   #define No_Redir 0                                                              #define In_Redir 1 // <                                           #define Out_Redir 2 // >                                                         #define Add_Redir 3 // >>int redir_type = No_Redir; // 类型 char* filename = NULL; // 重定向文件名

接下来编写 CheckRedir

  • 遍历字符串,寻找重定向符号 > >> <
  • 找到之后,修改相应的重定向类型,然后把重定向符号改为 0,断开命令
  • 跳过重定向后面的空格,寻找文件名
void CheckRedir(char cmd[])
{// 寻找重定向符号int pos = 0;int end = strlen(cmd);while(pos < end){// ls -a -l >   log.txtif (cmd[pos] == '>'){if (cmd[pos + 1] == '>'){// 追加重定向redir_type = Add_Redir;cmd[pos++] = 0;pos++; // 跳过两个>// 跳过空格SkipSpace(cmd, pos);filename = cmd + pos;}else{// 输出重定向redir_type = Out_Redir;cmd[pos++] = 0;SkipSpace(cmd, pos);filename = cmd + pos;}}else if (cmd[pos] == '<'){// 输入重定向redir_type = In_Redir;cmd[pos++] = 0;// 跳过空格,寻找文件名SkipSpace(cmd, pos);filename = cmd + pos;}else{pos++;}}                                                                                                                                                          
}                                                                                                                                                  

这里写一个跳过空格的宏函数

#define SkipSpace(cmd, pos) do{\while(1)\{\if(cmd[pos] == ' ') pos++;\else break;\}\}while(0)

先把下面的代码屏蔽,测试一下:

在这里插入图片描述

在这里插入图片描述

看上去是没什么问题,那么接下来写执行重定向的命令

执行命令

在子进程执行命令之前,打开相关文件,然后调用 dup2 即可

void ExecuteCmd()
{pid_t id = fork();if (id < 0) exit(1);else if (id == 0){// 重定向,打开文件if (filename != NULL){if (redir_type == In_Redir){// 输入重定向int fd = open(filename, O_RDONLY);dup2(fd, 0);}else if (redir_type == Out_Redir){// 输出重定向int fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0666);dup2(fd, 1);                                                                                                                                         }else if (redir_type == Add_Redir){// 追加重定向int fd = open(filename, O_WRONLY | O_CREAT | O_APPEND, 0666);dup2(fd, 1);}}// childexecvp(gArgv[0], gArgv);exit(errno); // 执行失败}// fatherint status = 0;pid_t rid = waitpid(id, &status, 0);if (rid > 0){// wait sucesslastcode = WEXITSTATUS(status);if (lastcode)printf("%s:%s:%d\n", gArgv[0], strerror(lastcode), lastcode);}
}                                                                                                                                                            

测试:

输出重定向

在这里插入图片描述

追加重定向

在这里插入图片描述

0、1、2的作用

这里我们总结一下 0、1、2 的作用。我们写的程序不就是对数据进行操作吗,例如计算、存储等。那么这些数据从哪里来,又到哪里去呢?

  • 数据从标准输入来,也就是0
  • 数据要输出给用户看,也就是输出到标准输出1

那么 2 又有什么作用呢?1 和 2 不都是指向的标准输出吗?

我们写的程序,输出信息一般有两种:正确的和错误的。一般进行打印时,都会打印在屏幕上,如下:

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

在这里插入图片描述

如果我们使用>,将输出信息重定向到 log.txt 文件中,会发生奇怪的现象:

在这里插入图片描述

向 stderr 写入的数据并没有输出到文件中,依然输出在了屏幕。这是为什么呢?——因为>符号默认情况下是标准输出重定向,也就是只把文件描述符表中 1 号下标的内容修改,指向了 log.txt 文件;而 2 号下标的内容依然指向屏幕

在这里插入图片描述

那么如何把 2 号下标的内容也重定向呢?正确写法是这样的:

在这里插入图片描述

其中 1>log.txt 就不说了,而 2>&1 的意思就是把 1 号下标的内容拷贝给 2 号下标。那么 1、2都会指向 log.txt 文件

在这里插入图片描述

还可以这样,把正确信息和错误信息输出到两个不同文件中

在这里插入图片描述

C语言的 perror 接口,默认就是向 stderr,也就是系统的 2 中写入数据

在这里插入图片描述

到这里我们也可以体会到 2 的作用了,就是可以将程序输出的正确信息和错误信息输入到不同文件,使用重定向即可分离输出


http://www.ppmy.cn/embedded/141080.html

相关文章

QT入门详解,创建QT应用

一、Qt概述 1.1 什么是Qt Qt是一个跨平台的C++应用程序开发框架。它由Qt Company(前身为为Nokia)开发,用于开发图形用户界面、嵌入式系统、以及其他应用程序,支持Windows、macOS、Linux、Android和iOS等多个操作系统。 Qt框架提供了丰富的功能和工具,包括图形用户界面设计Q、…

Vue 中父子组件间的参数传递与方法调用

1. 引言 Vue中&#xff0c;组件化设计是构建用户界面的核心理念。 Vue.js 作为一个流行的前端框架&#xff0c;采用了组件化的方式&#xff0c;使得开发者能够更好地管理和复用代码。 在 Vue 中&#xff0c;父子组件之间的参数传递和方法调用是实现组件间交互的重要手段。 本文…

docker如何安装mysql8

第一步 直接docker pull 拉取镜像 docker pull mysql:8 如果使用这个命令出现类似这种错误 Error response from daemon: Get "https://registry-1.docker.io/v2/": dial tcp 124.11.210.175:443: connect: connection refused 首先看443端口是否在云服务器上打开&a…

【速通GO】数据类型与变量和常量

独立站原文 数据类型 总览 布尔型数字类型字符串类型派生类型 派生类型 指针类型&#xff08;Pointer&#xff09;数组类型结构化类型 (struct)Channel 类型函数类型切片类型接口类型&#xff08;interface&#xff09;Map 类型 数值类型 整型 序号类型描述1uint8无符号…

主键、外键和索引之间的区别?

主键、外键和索引是数据库设计中的三个关键概念&#xff0c;它们各自有不同的作用和目的。以下是它们之间的区别&#xff1a; 主键&#xff08;Primary Key&#xff09; 定义&#xff1a;主键是表中唯一标识每条记录的字段或字段组合。 作用&#xff1a;主键用于确保数据的唯一…

2024下半年——【寒假】自学黑客计划(网络安全)

CSDN大礼包&#xff1a;&#x1f449;基于入门网络安全/黑客打造的&#xff1a;&#x1f449;黑客&网络安全入门&进阶学习资源包 前言 什么是网络安全 网络安全可以基于攻击和防御视角来分类&#xff0c;我们经常听到的 “红队”、“渗透测试” 等就是研究攻击技术&a…

matlab学习笔记:第五章5.3.3字符向量元胞数组的综合练习

案例1&#xff1a; 请将每行信息重新格式化为“姓名, 电话号码, 电子邮件”的字符向量形式&#xff0c;并保存到元胞数组s中&#xff08;注意&#xff0c;第二行有一个vip1的额外备注&#xff0c;这个备注不需要出现在s中&#xff09;&#xff1b;接下来使用换行符连接s中的各…

js:基础

js是什么 JavaScript是一种运行在客户端的编程语言&#xff0c;实现人机交互的效果 js只要有个浏览器就能跑 js可以做网页特效、表单验证、数据交互、服务端编程 服务端编程是前端人拿他们特有的后端语言node.js来干后端干的事情 js怎么组成 JavaScriptECMAScript(语言基…