当前位置: 首页 > news >正文

【Linux】基础IO_文件系统IO_“一切皆文件”_缓冲区

目录

1. 理解"⽂件"

1-1 狭义理解

1-2 ⼴义理解

1-3 ⽂件操作的归类认知

1-4 系统⻆度

访问文件,需要先打开文件!那么是由谁打开文件???

操作系统要不要把被打开的文件管理起来?

2. 回顾C⽂件接⼝

2.1 fopen("文件名","打开方式"); 

snprintf()

打开的myfile⽂件在哪个路径下?

2-2 hello.c写⽂件

2-3 hello.c读⽂件

稍作修改,实现简单cat命令:

2-4 输出信息到显⽰器,你有哪些⽅法

2-5 stdin & stdout & stderr

系统调用接口:

open:

对于系统写入write:

结论:

3.系统读文件:

3.1.open返回值:

那么为什么语言层要进行各自的封装呢?

语言为什么要增加自己的可移植性???

文件描述符是什么???

3-2 ⽂件描述符fd

文件描述符的创建:

重定向:

3.3 在minishell中添加重定向功能(上一章节的后续添加重定向功能)

4.理解一切皆文件

这里就体现了多态的思想:

5.缓冲区

5-1 什么是缓冲区

5-2 为什么要引⼊缓冲区机制

5-3 缓冲类型

5-4 FILE

5-5 简单设计⼀下libc库:

my_stdio.h

my_stdio.c

main.c

总结不易~本章节对我有很大的收获,希望对你也是!!!


1. 理解"⽂件"

文件大小为0,文件要不要再磁盘上占据空间呢???
是要的,文件=内容+属性,存取操作围绕着内容+属性展开的!

1-1 狭义理解

⽂件在磁盘⾥
磁盘是永久性存储介质,因此⽂件在磁盘上的存储是永久性的
磁盘是外设(即是输出设备也是输⼊设备)
磁盘上的⽂件 本质是对⽂件的所有操作,都是对外设的输⼊和输出 简称 IO

1-2 ⼴义理解

Linux 下⼀切皆⽂件(键盘、显⽰器、⽹卡、磁盘…… 这些都是抽象化的过程)(后⾯会讲如何去理解)

1-3 ⽂件操作的归类认知

对于 0KB 的空⽂件是占⽤磁盘空间的
⽂件是⽂件属性(元数据)和⽂件内容的集合(⽂件 = 属性(元数据)+ 内容)
所有的⽂件操作本质是⽂件内容操作和⽂件属性操作

1-4 系统⻆度

对⽂件的操作本质是进程对⽂件的操作
磁盘的管理者是操作系统
⽂件的读写本质不是通过 C 语⾔ / C++ 的库函数来操作的(这些库函数只是为⽤⼾提供⽅便),⽽是通过⽂件相关的系统调⽤接⼝来实现的

访问文件,需要先打开文件!那么是由谁打开文件???

是由进程来打开文件!对文件操作,本质是:进程对文件的操作!

操作系统要不要把被打开的文件管理起来?

要!就是先描述,在组织!!!

2. 回顾C⽂件接⼝

2.1 fopen("文件名","打开方式"); 

虽然这里只传入了一个文件名,但是该调用会将当前路径pwd拼接上当前文件名来进行寻找并打开,在之前的创建文件也是同样如此,获取当前位置的pwd后来创建当前路径的文件!
来认识一下snprintf()这个安全的字符串格式化函数,常用于格式化并将结果写入字符数组中。它可以防止因数组边界溢出导致的安全问题。
int snprintf(char *str, size_t size, const char *format, ...);
  • str:目标缓冲区,用于存储格式化后的字符串。
  • size:缓冲区的大小(包括结尾的空字符 \0)。如果 size 为 0,则 snprintf() 不会向目标缓冲区写入任何字符。
  • format:格式字符串,与 printf 的格式类似,支持 %d%s 等格式说明符。
  • ...:可变参数,提供用于格式化的值。
eg:
#include <stdio.h>int main()
{int cnt = 1;const char* msg = "hello,bit: ";while (cnt < 10) {char buffer[1024];snprintf(buffer, sizeof(buffer), "%s%d",msg, cnt++);printf("%s\n", buffer); // 输出到屏幕}return 0;
}

snprintf()

1.每次都是写入这个局部变量buffer内;2.然后计算输入的大小,保证不会发生越界;3.输入的内容“字符串”;4.可变参数列表

输出为:

hello,bit: 1
hello,bit: 2
hello,bit: 3
hello,bit: 4
hello,bit: 5
hello,bit: 6
hello,bit: 7
hello,bit: 8
hello,bit: 9

size_t fwrite(const void *ptr, size_t size, size_t count, FILE *stream);
  • ptr:指向要写入数据的内存地址。
  • size:每个数据单元的大小(以字节为单位)。
  • count:写入的数据单元数量
  • stream:目标文件的指针(FILE * 类型,通常通过 fopen 获得)。
eg:
fwrite(buffer,strlen(buffer),1,fp);  

size_t fread(void *ptr, size_t size, size_t count, FILE *stream);
  1. ptr:目标缓冲区的指针,用于存储读取到的数据。
  2. size:每个数据单元的大小(以字节为单位)。
  3. count:要读取的数据单元数量。
  4. stream:目标文件指针(FILE * 类型,通常通过 fopen 打开文件获得)。
  • 返回成功读取的 数据单元数量count)。
  • 如果返回值小于请求的 count,可能是因为:
    • 文件结束(EOF)。
    • 发生错误。
  • 可以通过 feof()ferror() 来检查文件状态。

feof()函数可以判断是否读取到文件的末尾

2-1 hello.c打开⽂件
#include <stdio.h>
int main()
{FILE *fp = fopen("myfile", "w");if(!fp){printf("fopen error!\n");
}while(1);fclose(fp);return 0;
}

打开的myfile⽂件在哪个路径下?

在程序的当前路径下,那系统怎么知道程序的当前路径在哪⾥呢?
可以使⽤ ls /proc/[ 进程 id] -l 命令查看当前正在运⾏进程的信息:
[hyb@VM-8-12-centos io]$ ps ajx | grep myProc
506729 533463 533463 506729 pts/249 533463 R+ 1002 7:45 ./myProc
536281 536542 536541 536281 pts/250 536541 R+ 1002 0:00 grep --
color=auto myProc
[hyb@VM-8-12-centos io]$ ls /proc/533463 -l
total 0
......
-r--r--r-- 1 hyb hyb 0 Aug 26 17:01 cpuset
lrwxrwxrwx 1 hyb hyb 0 Aug 26 16:53 cwd -> /home/hyb/io
-r-------- 1 hyb hyb 0 Aug 26 17:01 environ
lrwxrwxrwx 1 hyb hyb 0 Aug 26 16:53 exe -> /home/hyb/io/myProc
dr-x------ 2 hyb hyb 0 Aug 26 16:54 fd
......
其中:
cwd:指向当前进程运⾏⽬录的⼀个符号链接。

exe:指向启动当前进程的可执⾏⽂件(完整路径)的符号链接。
打开⽂件,本质是进程打开,所以,进程知道⾃⼰在哪⾥,即便⽂件不带路径,进程也知道。由此OS就能知道要创建的⽂件放在哪⾥。

2-2 hello.c写⽂件

#include <stdio.h>
#include <string.h>
int main()
{FILE* fp = fopen("myfile", "w");if (!fp) {printf("fopen error!\n");}const char* msg = "hello bit!\n";int count = 5;while (count--) {fwrite(msg, strlen(msg), 1, fp);}fclose(fp);return 0;
}

2-3 hello.c读⽂件

#include <stdio.h>
#include <string.h>
int main()
{FILE* fp = fopen("myfile", "r");if (!fp) {printf("fopen error!\n");return 1;}char buf[1024];const char* msg = "hello bit!\n";while (1) {//注意返回值和参数,此处有坑,仔细查看man⼿册关于该函数的说明size_t s = fread(buf, 1, strlen(msg), fp);if (s > 0) {buf[s] = 0;printf("%s", buf);}if (feof(fp)) {break;}}fclose(fp);return 0;
}

稍作修改,实现简单cat命令:

#include <stdio.h>
#include <string.h>//cat myfile    
int main(int argc, char* argv[])
{if (argc != 2){printf("Usage: %s filename\n", argv[0]);return 1;}FLIE* fp = fopen(argv[1], "r")if (fp == NULL){perror("fopen");return 2;}while (1){char buffer[128];memset(buffer, 0, sizeof(buffer));int n = fread(buffer, sizeof(buffer) - 1, 1, fp);if (n > 0) printf("%s", buffer);if (feof(fp)) break;}fclose(fp);return 0;
}

2-4 输出信息到显⽰器,你有哪些⽅法

#include <stdio.h>
#include <string.h>
int main()
{const char* msg = "hello fwrite\n";fwrite(msg, strlen(msg), 1, stdout);printf("hello printf\n");fprintf(stdout, "hello fprintf\n");return 0;
}

2-5 stdin & stdout & stderr

C默认会打开三个输⼊输出流,分别是stdin, stdout, stderr
仔细观察发现,这三个流的类型都是FILE*, fopen返回值类型,⽂件指针
#include <stdio.h>
extern FILE *stdin;
extern FILE *stdout;
extern FILE *stderr;

系统调用接口:

O_CREAT:创建
O_WRONLY:只写
O_TRUNC:清空

open:

int open(const char *pathname, int flags);
       int open(const char *pathname, int flags, mode_t mode);
可以看出此时的log.txt的权限是随机的:
必须要在open后跟上相应的八进制权限设置:
open指定第三个参数主要就是为了我们的新建文件的权限
但是这里我们设置的是666,可是创建出来的还是664,这是为什么呢?
就是因为存在系统umask的影响!
可以看到这里并没进行清空文件,而是直接进行覆盖式的写入,所以这里仍然缺少一个系统调用,跟语言层面没有关系: O_TRUNC:清空
所以系统调用就要传入以下几个参数:【路径文件名】+【功能:新建,写入,清空】+【权限】
int fd = open("log.txt",O_CREAT | O_WRONLY | O_TRUNC,0666);

所以由上面看到,想要追加就不能清空:O_APPEND

所以语言是不可能直接访问底层的存储系统的,只是封装了操作系统底层的接口来进行访问

对于系统写入write:

有以下两种再语言层面进行封装的二进制写入和字符写入

结论:

再系统层面上,并不关心你是二进制写入还是字符写入,最终系统都会转换成二进制来进行识别
对于语言层进行封装的二进制 或 字符写入的接口都是调用的系统write!

3.系统读文件:

那么就不能再打开的时候进行新建,因为当前文件不存在还要新建再打开读是没有意义的!所以读文件不需要新建文件,也不需要清空文件,不需要写入文件,只需要转换为二进制 O_RDONLY,只读方式打开文件
int fd = open("log.txt",O_RDONLY);  

这也就是为什么write存在两参数调用的接口,就是为读来准备的

int n = read(fd,buffer,sizeof(buffer)-1);

3.1.open返回值:

那为什么返回值是从3开始打印呢?
因为返回值0,1,2是叫做标准输入,标准输出,标准错误!
其中FILE是C语言提供的一个结构体typedef struct FILE{;;;;;}; 
再OS界面,只认fd即文件描述符,那么大胆猜测这个FILE里面一定是封装了文件描述符!!!
所以顶层无论怎么封装,底层都只认识文件描述符

那么为什么语言层要进行各自的封装呢?

就是因为每个OS的实现不同,就是我们上面写的代码放在windows下就跑步过去,因为OS不同,底层接口实现的就不一样,如果我们实现一种语言来将各个OS进行封装,我们写一套C语言代码,就可以实现跨平台移植的作用,在各个OS上只需呀裁掉别的平台的所有代码,只保留当前OS的代码,就是当前OS的接口封装即可,这样就凸显出了语言的可移植性!

语言为什么要增加自己的可移植性???

就是为了能够满足各个平台的人,让更多人去使用,占有市场利用率,防止被淘汰

文件描述符是什么???

对于上面打印的这些数字是什么呢?
是数组下标~

3-2 ⽂件描述符fd

通过对open函数的学习,我们知道了⽂件描述符就是⼀个⼩整数

文件描述符的创建:

FILE是由一个结构体来进行封装的,每一个文件的属性都被封装到一个struct_file内,多个struct_file由一个双链表进行链接,这样先描述,在组织;本质上也就是对一个链表进行增删查改
对于整个链表的管理,还设置了一个文件描述符表 struct file *fd array[] 指针数组,来将整个文件链表的每一个节点存入这个指针数组内,也就有了为什么可以打印出fd的值的下标,这个指针数组再由一个struct files_struct *files指针进行指向
通常我们在申请这个文件描述符的时候,通常就是找到一个最小的,没有被使用过的文件描述符,作为一个被新打开的位置

重定向:

首先进入程序close(1)关掉了stdout标准输出,然后此时的系统就将fd分配给1位置进行指向,然后printf()底层封装的就是原来指向stdout的1,现在又重新指向log.txt位置进行打印,就往文本log.txt内进行打印得到该现象!
重定向dup2(int oldfd,int newfd);
作用就是将当前文件描述符覆盖到newfd这个位置,让newfd也指向oldfd的文件处!dup2()后一般要手动关闭oldfd : close(oldfd)
所以重定向的原理是:打开文件的方式 + dup2()
输入重定向:将打开文件设置为只读方式打开,将当前文件设置到标准输入的位置,dup(fd,0);
fgets(buffer,sizeof(buffer),stdin) fgets()函数用于从指定的流(在这个例子中是 stdin,标准输入流)中读取一行字符,并将其存储到 buffer中。成功了就打印buffer~

设置任意文本的输出重定向: 

⽰例代码
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
int main() {int fd = open("./log", O_CREAT | O_RDWR);if (fd < 0) {perror("open");return 1;}close(1);dup2(fd, 1);for (;;) {char buf[1024] = { 0 };ssize_t read_size = read(0, buf, sizeof(buf) - 1);if (read_size < 0) {perror("read");break;}printf("%s", buf);fflush(stdout);}return 0;
}
printf是C库当中的IO函数,⼀般往 stdout 中输出,但是stdout底层访问⽂件的时候,找的还是fd:1, 但此时,fd:1下标所表⽰内容,已经变成了myfifile的地址,不再是显⽰器⽂件的地址,所以,输出的任何消息都会往⽂件中写⼊,进⽽完成输出重定向。那追加和输⼊重定向如何完成呢?

3.3 在minishell中添加重定向功能(上一章节的后续添加重定向功能)

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <ctype.h>
using namespace std;
const int basesize = 1024;
const int argvnum = 64;
const int envnum = 64;
// 全局的命令⾏参数表
char* gargv[argvnum];
int gargc = 0;
// 全局的变量
int lastcode = 0;
// 我的系统的环境变量
char* genv[envnum];
// 全局的当前shell⼯作路径
char pwd[basesize];
char pwdenv[basesize];
// 全局变量与重定向有关
#define NoneRedir 0
#define InputRedir 1
#define OutputRedir 2
#define AppRedir 3
int redir = NoneRedir;
char* filename = nullptr;
// " "file.txt
#define TrimSpace(pos) do{\
while(isspace(*pos)){\
pos++;\
}\
}while(0)
string GetUserName()
{string name = getenv("USER");return name.empty() ? "None" : name;
}
string GetHostName()
{string hostname = getenv("HOSTNAME");return hostname.empty() ? "None" : hostname;
}
string GetPwd()
{if (nullptr == getcwd(pwd, sizeof(pwd))) return "None";snprintf(pwdenv, sizeof(pwdenv), "PWD=%s", pwd);putenv(pwdenv); // PWD=XXXreturn pwd;//string pwd = getenv("PWD");//return pwd.empty() ? "None" : pwd;
}
string LastDir()
{string curr = GetPwd();if (curr == "/" || curr == "None") return curr;// /home/whb/XXXsize_t pos = curr.rfind("/");if (pos == std::string::npos) return curr;return curr.substr(pos + 1);
}
string MakeCommandLine()
{// [whb@bite-alicloud myshell]$char command_line[basesize];snprintf(command_line, basesize, "[%s@%s %s]# ", \GetUserName().c_str(), GetHostName().c_str(), LastDir().c_str());return command_line;
}
void PrintCommandLine() // 1. 命令⾏提⽰符
{printf("%s", MakeCommandLine().c_str());fflush(stdout);
}
bool GetCommandLine(char command_buffer[], int size) // 2. 获取⽤⼾命令
{// 我们认为:我们要将⽤⼾输⼊的命令⾏,当做⼀个完整的字符串// "ls -a -l -n"char* result = fgets(command_buffer, size, stdin);if (!result){return false;}command_buffer[strlen(command_buffer) - 1] = 0;if (strlen(command_buffer) == 0) return false;return true;
}
void ResetCommandline()
{memset(gargv, 0, sizeof(gargv));gargc = 0;// 重定向redir = NoneRedir;filename = nullptr;
}
void ParseRedir(char command_buffer[], int len)
{int end = len - 1;while (end >= 0){if (command_buffer[end] == '<'){redir = InputRedir;command_buffer[end] = 0;filename = &command_buffer[end] + 1;TrimSpace(filename);break;}else if (command_buffer[end] == '>'){if (command_buffer[end - 1] == '>'){redir = AppRedir;command_buffer[end] = 0;command_buffer[end - 1] = 0;filename = &command_buffer[end] + 1;TrimSpace(filename);break;}else{redir = OutputRedir;command_buffer[end] = 0;filename = &command_buffer[end] + 1;TrimSpace(filename);break;}}else{end--;}}
}
void ParseCommand(char command_buffer[])
{// "ls -a -l -n"const char* sep = " ";gargv[gargc++] = strtok(command_buffer, sep);// =是刻意写的while ((bool)(gargv[gargc++] = strtok(nullptr, sep)));gargc--;
}
void ParseCommandLine(char command_buffer[], int len) // 3. 分析命令
{ResetCommandline();ParseRedir(command_buffer, len);ParseCommand(command_buffer);//printf("command start: %s\n", command_buffer);// "ls -a -l -n"// "ls -a -l -n" > file.txt// "ls -a -l -n" < file.txt// "ls -a -l -n" >> file.txt//printf("redir: %d\n", redir);//printf("filename: %s\n", filename);//printf("command end: %s\n", command_buffer);
}
void debug()
{printf("argc: %d\n", gargc);for (int i = 0; gargv[i]; i++){printf("argv[%d]: %s\n", i, gargv[i]);}
}
//enum
//{
// FILE_NOT_EXISTS = 1,
// OPEN_FILE_ERROR,
//};
void DoRedir()
{// 1. 重定向应该让⼦进程⾃⼰做!// 2. 程序替换会不会影响重定向?不会// 0. 先判断 && 重定向if (redir == InputRedir){if (filename){int fd = open(filename, O_RDONLY);if (fd < 0){exit(2);}dup2(fd, 0);}else{exit(1);}}else if (redir == OutputRedir){if (filename){int fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0666);if (fd < 0){exit(4);}dup2(fd, 1);}else{exit(3);}}else if (redir == AppRedir){if (filename){int fd = open(filename, O_CREAT | O_WRONLY | O_APPEND, 0666);if (fd < 0){exit(6);}dup2(fd, 1);}else{exit(5);}}else{// 没有重定向,Do Nothong!}
}
// 在shell中
// 有些命令,必须由⼦进程来执⾏
// 有些命令,不能由⼦进程执⾏,要由shell⾃⼰执⾏ --- 内建命令 built command
bool ExecuteCommand() // 4. 执⾏命令
{// 让⼦进程进⾏执⾏pid_t id = fork();if (id < 0) return false;if (id == 0){//⼦进程DoRedir();// 1. 执⾏命令execvpe(gargv[0], gargv, genv);// 2. 退出exit(7);}int status = 0;pid_t rid = waitpid(id, &status, 0);if (rid > 0){if (WIFEXITED(status)){lastcode = WEXITSTATUS(status);}else{lastcode = 100;}return true;}return false;
}
void AddEnv(const char* item)
{int index = 0;while (genv[index]){index++;}genv[index] = (char*)malloc(strlen(item) + 1);strncpy(genv[index], item, strlen(item) + 1);genv[++index] = nullptr;
}
// shell⾃⼰执⾏命令,本质是shell调⽤⾃⼰的函数
bool CheckAndExecBuiltCommand()
{if (strcmp(gargv[0], "cd") == 0){// 内建命令if (gargc == 2){chdir(gargv[1]);lastcode = 0;}else{lastcode = 1;}return true;}else if (strcmp(gargv[0], "export") == 0){// export也是内建命令if (gargc == 2){AddEnv(gargv[1]);lastcode = 0;}else{lastcode = 2;}return true;}else if (strcmp(gargv[0], "env") == 0){for (int i = 0; genv[i]; i++){printf("%s\n", genv[i]);}lastcode = 0;return true;}else if (strcmp(gargv[0], "echo") == 0){if (gargc == 2){// echo $?// echo $PATH
// echo helloif (gargv[1][0] == '$'){if (gargv[1][1] == '?'){printf("%d\n", lastcode);lastcode = 0;}}else{printf("%s\n", gargv[1]);lastcode = 0;}}else{lastcode = 3;}return true;}return false;
}
// 作为⼀个shell,获取环境变量应该从系统的配置来
// 我们今天就直接从⽗shell中获取环境变量
void InitEnv()
{extern char** environ;int index = 0;while (environ[index]){genv[index] = (char*)malloc(strlen(environ[index]) + 1);strncpy(genv[index], environ[index], strlen(environ[index]) + 1);index++;}genv[index] = nullptr;
}
int main()
{InitEnv();char command_buffer[basesize];while (true){PrintCommandLine(); // 1. 命令⾏提⽰符// command_buffer -> outputif (!GetCommandLine(command_buffer, basesize)) // 2. 获取⽤⼾命令{continue;}//printf("%s\n", command_buffer);//ls//"ls -a -b -c -d"->"ls" "-a" "-b" "-c" "-d"//"ls -a -b -c -d">hello.txt//"ls -a -b -c -d">>hello.txt//"ls -a -b -c -d"<hello.txtParseCommandLine(command_buffer, strlen(command_buffer)); // 3. 分析命令if (CheckAndExecBuiltCommand()){continue;}ExecuteCommand(); // 4. 执⾏命令}return 0;
}

其实文件描述符1标准输出,2标准错误,都是指向同一个文件,说明只做了标准输入的重定向,并没有做出标准错误的重定向:

就想把标准错误给重定向到指定文件呢?
为什么非要存在标准错误呢?printf()、perror???cout/cerr
就是因为他们单独占据文件描述符,可以通过重定向能力,把常规消息和错误消息进行分离!方便日志的形成
那如何把标准输出和标准错误都追加到一个文件内呢??
log.txt就相当于是文件描述符3,然后3进行覆盖到1内,然后文件描述符1就指向该文件,进行标准输出写入,然后重定向到log.txt内
2 > &1就是再将文件描述符1里面的内容覆盖的写入2内,此时的2也跟1一样指向log.txt文件,也可以进行重定向写入

4.理解一切皆文件

⾸先,在windows中是⽂件的东西,它们在linux中也是⽂件;其次⼀些在windows中不是⽂件的东西,⽐如进程、磁盘、显⽰器、键盘这样硬件设备也被抽象成了⽂件,你可以使⽤访问⽂件的⽅法访问它们获得信息;甚⾄管道,也是⽂件;将来我们要学习⽹络编程中的socket(套接字)这样的东西, 使⽤的接⼝跟⽂件接⼝也是⼀致的。
这样做最明显的好处是,开发者仅需要使⽤⼀套 API 和开发⼯具,即可调取 Linux 系统中绝⼤部分的资源。举个简单的例⼦,Linux 中⼏乎所有读(读⽂件,读系统状态,读PIPE)的操作都可以⽤read 函数来进⾏;⼏乎所有更改(更改⽂件,更改系统参数,写 PIPE)的操作都可以⽤ write 函数来进⾏。 之前我们讲过,当打开⼀个⽂件时,操作系统为了管理所打开的⽂件,都会为这个⽂件创建⼀个file结构体,该结构体定义在 /usr/src/kernels/3.10.0- 1160.71.1.el7.x86_64/include/linux/ fs.h 下,以下展⽰了该结构部分我们关系的内容:
值得关注的是 struct file 中的 f_op 指针指向了⼀个 file_operations 结构体,这个结构体中的成员除了struct module* owner 其余都是函数指针。该结构和 struct file 都在fs.h下。

这里就体现了多态的思想:

  • 是面向对象编程中的一个重要特性,它允许使用统一的接口来表示不同的行为。在 C++ 中,多态主要通过虚函数(Virtual Function)来实现。
  • 简单来说,多态就是 “多种形态”。例如,对于不同类型的动物(如猫、狗),它们都有 “叫” 这个行为,但叫声不同。多态可以让我们通过一个共同的 “叫” 函数接口,来实现不同动物的不同叫声。
file_operation 就是把系统调⽤和驱动程序关联起来的关键数据结构,这个结构的每⼀个成员都对应着⼀个系统调⽤。读取 file_operation 中相应的函数指针,接着把控制权转交给函数,从⽽完成了Linux设备驱动程序的⼯作。
介绍完相关代码,⼀张图总结:
上图中的外设,每个设备都可以有⾃⼰的read、write,但⼀定是对应着不同的操作⽅法!!但通过struct file 下 file_operation 中的各种函数回调,让我们开发者只⽤file便可调取 Linux 系统中绝⼤部分的资源!!这便是“linux下⼀切皆⽂件”的核⼼理解

5.缓冲区

好比有了菜鸟驿站,就不用让快递员直接跟用户打交道,直接的维护了快递员的效率;而我本身,就不用得到快递员的电话后立马就下去,而是等我有空闲的时间随时去哪

5-1 什么是缓冲区

缓冲区是内存空间的⼀部分。也就是说,在内存空间中预留了⼀定的存储空间,这些存储空间⽤来缓冲输⼊或输出的数据,这部分预留的空间就叫做缓冲区。缓冲区根据其对应的是输⼊设备还是输出设备,分为输⼊缓冲区和输出缓冲区。

5-2 为什么要引⼊缓冲区机制

读写⽂件时,如果不会开辟对⽂件操作的缓冲区,直接通过系统调⽤对磁盘进⾏操作(读、写等),那么每次对⽂件进⾏⼀次读写操作时,都需要使⽤读写系统调⽤来处理此操作,即需要执⾏⼀次系统调⽤,执⾏⼀次系统调⽤将涉及到CPU状态的切换,即从⽤⼾空间切换到内核空间,实现进程上下⽂的切换,这将损耗⼀定的CPU时间,频繁的磁盘访问对程序的执⾏效率造成很⼤的影响。
为了减少使⽤系统调⽤的次数,提⾼效率,我们就可以采⽤缓冲机制。⽐如我们从磁盘⾥取信息,可以在磁盘⽂件进⾏操作时,可以⼀次从⽂件中读出⼤量的数据到缓冲区中,以后对这部分的访问就不需要再使⽤系统调⽤了,等缓冲区的数据取完后再去磁盘中读取,这样就可以减少磁盘的读写次数,再加上计算机对缓冲区的操作⼤ 快于对磁盘的操作,故应⽤缓冲区可⼤ 提⾼计算机的运⾏速度。⼜⽐如,我们使⽤打印机打印⽂档,由于打印机的打印速度相对较慢,我们先把⽂档输出到打印机相应的缓冲区,打印机再⾃⾏逐步打印,这时我们的CPU可以处理别的事情。可以看出,缓冲区就是⼀块内存区,它⽤在输⼊输出设备和CPU之间,⽤来缓存数据。它使得低速的输⼊输出设备和⾼速的CPU能够协调⼯作,避免低速的输⼊输出设备占⽤CPU,解放出CPU,使其能够⾼效率⼯作。

5-3 缓冲类型

标准I/O提供了3种类型的缓冲区。
全缓冲区:这种缓冲⽅式要求填满整个缓冲区后才进⾏I/O系统调⽤操作。对于磁盘⽂件的操作通
常使⽤全缓冲的⽅式访问。
⾏缓冲区:在⾏缓冲情况下,当在输⼊和输出中遇到换⾏符时,标准I/O库函数将会执⾏系统调⽤
操作。当所操作的流涉及⼀个终端时(例如标准输⼊和标准输出),使⽤⾏缓冲⽅式。因为标准
I/O库每⾏的缓冲区⻓度是固定的,所以只要填满了缓冲区,即使还没有遇到换⾏符,也会执⾏
I/O系统调⽤操作,默认⾏缓冲区的⼤⼩为1024。
⽆缓冲区:⽆缓冲区是指标准I/O库不对字符进⾏缓存,直接调⽤系统调⽤。标准出错流stderr通
常是不带缓冲区的,这使得出错信息能够尽快地显⽰出来。
除了上述列举的默认刷新⽅式,下列特殊情况也会引发缓冲区的刷新:
1. 缓冲区满时;
2. 执⾏flush语句;
⽰例如下:
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main() {close(1);int fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);if (fd < 0) {perror("open");return 0;}printf("hello world: %d\n", fd);close(fd);return 0;
}
我们本来想使⽤重定向思维,让本应该打印在显⽰器上的内容写到“log.txt”⽂件中,但我们发现,
程序运⾏结束后,⽂件中并没有被写⼊内容:
[hyb@VM-8-12-centos buffer]$ ./myfile
[hyb@VM-8-12-centos buffer]$ ls
log.txt makefile myfile myfile.c
[hyb@VM-8-12-centos buffer]$ cat log.txt
[hyb@VM-8-12-centos buffer]$
这是由于我们将1号描述符重定向到磁盘⽂件后,缓冲区的刷新⽅式成为了全缓冲。⽽我们写⼊的内容并没有填满整个缓冲区,导致并不会将缓冲区的内容刷新到磁盘⽂件中。怎么办呢?可以使⽤fflush强制刷新下缓冲区。
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main() {
close(1);
int fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);
if (fd < 0) {
perror("open");
return 0;
}
printf("hello world: %d\n", fd);
fflush(stdout);
close(fd);
return 0;
}
还有⼀种解决⽅法,刚好可以验证⼀下stderr是不带缓冲区的,代码如下:
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main() {close(2);int fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);if (fd < 0) {perror("open");return 0;}perror("hello world");close(fd);return 0;
}
这种⽅式便可以将2号⽂件描述符重定向⾄⽂件,由于stderr没有缓冲区,“hello world”不⽤fflash
就可以写⼊⽂件:
[hyb@VM-8-12-centos buffer]$ ./myfile
[hyb@VM-8-12-centos buffer]$ cat log.txt
hello world: Success

5-4 FILE

因为IO相关函数与系统调⽤接⼝对应,并且库函数封装系统调⽤,所以本质上,访问⽂件都是通
过fd访问的。
所以C库当中的FILE结构体内部,必定封装了fd。
来段代码在研究⼀下:
#include <stdio.h>
#include <string.h>
int main()
{const char* msg0 = "hello printf\n";const char* msg1 = "hello fwrite\n";const char* msg2 = "hello write\n";printf("%s", msg0);fwrite(msg1, strlen(msg0), 1, stdout);write(1, msg2, strlen(msg2));fork();return 0;
}
运⾏出结果:
hello printf
hello fwrite
hello write
但如果对进程实现输出重定向呢? ./hello > file , 我们发现结果变成了:
hello write
hello printf
hello fwrite
hello printf
hello fwrite
我们发现 printf fwrite (库函数)都输出了2次,⽽ write 只输出了⼀次(系统调⽤)。为
什么呢?肯定和fork有关!
⼀般C库函数写⼊⽂件时是全缓冲的,⽽写⼊显⽰器是⾏缓冲。
printf fwrite 库函数+会⾃带缓冲区(进度条例⼦就可以说明),当发⽣重定向到普通⽂
件时,数据的缓冲⽅式由⾏缓冲变成了全缓冲。
⽽我们放在缓冲区中的数据,就不会被⽴即刷新,甚⾄fork之后
但是进程退出之后,会统⼀刷新,写⼊⽂件当中。
但是fork的时候,⽗⼦数据会发⽣写时拷⻉,所以当你⽗进程准备刷新的时候,⼦进程也就有了
同样的⼀份数据,随即产⽣两份数据。
write 没有变化,说明没有所谓的缓冲。
综上: printf fwrite 库函数会⾃带缓冲区,⽽ write 系统调⽤没有带缓冲区。另外,我们这
⾥所说的缓冲区,都是⽤⼾级缓冲区。其实为了提升整机性能,OS也会提供相关内核级缓冲区,不过不再我们讨论范围之内。
那这个缓冲区谁提供呢? printf fwrite 是库函数, write 是系统调⽤,库函数在系统调⽤的
“上层”, 是对系统调⽤的“封装”,但是 write 没有缓冲区,⽽ printf fwrite 有,⾜以说
明,该缓冲区是⼆次加上的,⼜因为是C,所以由C标准库提供。
如果有兴趣,可以看看FILE结构体:
typedef struct _IO_FILE FILE; /usr/include/stdio.h

5-5 简单设计⼀下libc库:

my_stdio.h

$ cat my_stdio.h
#pragma once
#define SIZE 1024
#define FLUSH_NONE 0
#define FLUSH_LINE 1
#define FLUSH_FULL 2
struct IO_FILE
{int flag; // 刷新⽅式int fileno; // ⽂件描述符char outbuffer[SIZE];int cap;int size;// TODO
};
typedef struct IO_FILE mFILE;
mFILE* mfopen(const char* filename, const char* mode);
int mfwrite(const void* ptr, int num, mFILE* stream);
void mfflush(mFILE* stream);
void mfclose(mFILE* stream);

my_stdio.c

$ cat my_stdio.c
#include "my_stdio.h"
#include <string.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
mFILE* mfopen(const char* filename, const char* mode)
{int fd = -1;if (strcmp(mode, "r") == 0){fd = open(filename, O_RDONLY);}else if (strcmp(mode, "w") == 0){fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0666);}else if (strcmp(mode, "a") == 0){fd = open(filename, O_CREAT | O_WRONLY | O_APPEND, 0666);}if (fd < 0) return NULL;mFILE* mf = (mFILE*)malloc(sizeof(mFILE));if (!mf){close(fd);return NULL;}mf->fileno = fd;mf->flag = FLUSH_LINE;mf->size = 0;mf->cap = SIZE;return mf;
}
void mfflush(mFILE* stream)
{if (stream->size > 0){// 写到内核⽂件的⽂件缓冲区中!write(stream->fileno, stream->outbuffer, stream->size);// 刷新到外设fsync(stream->fileno);stream->size = 0;}
}
int mfwrite(const void* ptr, int num, mFILE* stream)
{// 1. 拷⻉memcpy(stream->outbuffer + stream->size, ptr, num);stream->size += num;// 2. 检测是否要刷新if (stream->flag == FLUSH_LINE && stream->size > 0 && stream -
> outbuffer[stream->size - 1] == '\n'){mfflush(stream);}return num;
}
void mfclose(mFILE* stream)
{if (stream->size > 0){mfflush(stream);}close(stream->fileno);
}

main.c

$ cat main.c
#include "my_stdio.h"
#include <stdio.h>
#include <string.h>
#include <unistd.h>
int main()
{mFILE* fp = mfopen("./log.txt", "a");if (fp == NULL){return 1;}int cnt = 10;while (cnt){printf("write %d\n", cnt);char buffer[64];snprintf(buffer, sizeof(buffer), "hello message, number is : %d", cnt);cnt--;mfwrite(buffer, strlen(buffer), fp);mfflush(fp);sleep(1);}mfclose(fp);
}

总结不易~本章节对我有很大的收获,希望对你也是!!!

相关文章:

【Linux】基础IO_文件系统IO_“一切皆文件”_缓冲区

目录 1. 理解"⽂件" 1-1 狭义理解 1-2 ⼴义理解 1-3 ⽂件操作的归类认知 1-4 系统⻆度 访问文件&#xff0c;需要先打开文件&#xff01;那么是由谁打开文件&#xff1f;&#xff1f;&#xff1f; 操作系统要不要把被打开的文件管理起来&#xff1f; 2. 回顾…...

基于ZYNQ-7000系列的FPGA学习笔记7——按键控制蜂鸣器(模块化编写)

基于ZYNQ-7000系列的FPGA学习笔记7——按键控制蜂鸣器&#xff08;模块化编写&#xff09; 1. 实验要求2. 功能分析3. 模块设计4. 波形图4.1 按键消抖模块4.2 按键控制蜂鸣器模块 5.代码编写5.1 rtl代码5.2 测试代码 6. 代码仿真7. 添加约束文件并分析综合 在上期的内容中&…...

Mnesia(三)

在表中保存复杂数据 Mnesia是被设计用来保存Erlang数据结构的。可以把任意类型的Erlang数据结构保存到Mnesia表中。 -export([init_mnesia_schema/0, start/0]). -export([add_plans/0, get_plan/1]). -include_lib("stdlib/include/qlc.hrl"). -record(shop, {ite…...

ELK的Filebeat

目录 传送门前言一、概念1. 主要功能2. 架构3. 使用场景4. 模块5. 监控与管理 二、下载地址三、Linux下7.6.2版本安装filebeat.yml配置文件参考&#xff08;不要直接拷贝用&#xff09;多行匹配配置过滤配置最终配置&#xff08;一、多行匹配、直接读取日志文件、EFK方案&#…...

【WPF中ControlTemplate 与 DataTemplate之间的区别?】

前言 WPF中ControlTemplate 与 DataTemplate之间的区别&#xff1f; 1. 定义&#xff1a; ControlTemplate 是用于定义 WPF 控件的外观和结构的模板。它允许您重新定义控件的视觉表现&#xff0c;而不改变控件的行为。 DataTemplate 是用于定义如何呈现数据对象的模板。它通…...

FFmpeg源码中,计算CRC校验的实现

一、CRC简介 CRC(Cyclic Redundancy Check)&#xff0c;即循环冗余校验&#xff0c;是一种根据网络数据包或电脑文件等数据产生简短固定位数校核码的快速算法&#xff0c;主要用来检测或校核数据传输或者保存后可能出现的错误。CRC利用除法及余数的原理&#xff0c;实现错误侦…...

js面试题

面试题&#xff1a;说一下call、apply、bind区别 共同点&#xff1a;功能一致 可以改变this指向 语法&#xff1a;函数.call() 函数.apply() 函数.bind() 区别&#xff1a;1.call、apply可以立即执行。bind不会立即执行&#xff0c;因为bind返回的是一个函数需要加入&#xff…...

Python酷库之旅-第三方库Pandas(255)

目录 一、用法精讲 1206、pandas.tseries.offsets.SemiMonthEnd.is_on_offset方法 1206-1、语法 1206-2、参数 1206-3、功能 1206-4、返回值 1206-5、说明 1206-6、用法 1206-6-1、数据准备 1206-6-2、代码示例 1206-6-3、结果输出 1207、pandas.tseries.offsets.S…...

STM32 进阶 定时器3 通用定时器 案例2:测量PWM的频率/周期

需求分析 上一个案例我们输出了PWM波&#xff0c;这个案例我们使用输入捕获功能&#xff0c;来测试PWM波的频率/周期。 把测到的结果通过串口发送到电脑&#xff0c;检查测试的结果。 如何测量 1、输入捕获功能主要是&#xff1a;测量输入通道的上升沿和下降沿 2、让第一个…...

【计算机网络】实验10:开放最短路径优先OSPF

实验10 开放最短路径优先OSPF 一、实验目的 本实验的主要目的是验证OSPF&#xff08;开放最短路径优先&#xff09;协议的作用&#xff0c;深入理解其在动态路由中的重要性。通过实验&#xff0c;我们将观察OSPF如何在网络中高效地传播路由信息&#xff0c;从而实现不同网络之…...

线程信号量 Linux环境 C语言实现

既可以解决多个同类共享资源的互斥问题&#xff0c;也可以解决简易的同步问题 头文件&#xff1a;#include <semaphore.h> 类型&#xff1a;sem_t 初始化&#xff1a;int sem_init(sem_t *sem, int pshared, unsigned int value); //程序中第一次对指定信号量调用p、v操…...

Tree搜索二叉树、map和set_数据结构

数据结构专栏 如烟花般绚烂却又稍纵即逝的个人主页 本章讲述数据结构中搜索二叉树与HashMap的学习&#xff0c;感谢大家的支持&#xff01;欢迎大家踊跃评论&#xff0c;感谢大佬们的支持! 目录 搜索二叉树的概念二叉树搜索模拟实现搜索二叉树查找搜索二叉树插入搜索二叉树删除…...

Linux-GPIO应用编程

本章介绍应用层如何控制 GPIO&#xff0c;譬如控制 GPIO 输出高电平、或输出低电平。 只要是用到GPIO的外设&#xff0c;都有可能用得到这些操作方法。 照理说&#xff0c;GPIO的操作应该是由驱动层去做的&#xff0c;使用寄存器操作或者GPIO子系统之类的框架。 但是&#xff0…...

【模板】排序(py)/ 【深基9.例4】求第 k 小的数

题目描述 将读入的 NN 个数从小到大排序后输出。 输入格式 第一行为一个正整数 NN。 第二行包含 NN 个空格隔开的正整数 aiai​&#xff0c;为你需要进行排序的数。 输出格式 将给定的 NN 个数从小到大输出&#xff0c;数之间空格隔开&#xff0c;行末换行且无空格。 输…...

Linux获取文件属性

目录 stat函数 获取文件属性 获取文件权限 实现“head -n 文件名”命令的功能 编程实现“ls -l 文件名”功能 stat/fstat/lstat的区别&#xff1f; stat函数 int stat(const char *path, struct stat *buf); 功能&#xff1a;获取文件属性 参数&#xff1a; path&…...

一文说清:Git创建仓库的方法

0 引言 本文介绍如何创建一个 Git 本地仓库&#xff0c;以及与远程仓库的关联。 1 初始化仓库&#xff08;git init&#xff09; 1.1 概述 Git 使用 git init 命令来初始化一个 Git 仓库&#xff0c;Git 的很多命令都需要在 Git 的仓库中运行&#xff0c;所以 git init 是使…...

ASP.NET Core 负载/压力测试

文章目录 一、第三方工具二、使用发布版本进行负载测试和压力测试 负载测试和压力测试对于确保 web 应用的性能和可缩放性非常重要。 尽管负载测试和压力测试的某些测试相似&#xff0c;但它们的目标不同。 负载测试&#xff1a;测试应用是否可以在特定情况下处理指定的用户负…...

红日靶场vulnstack (五)

前言 好久没打靶机了&#xff0c;今天有空搞了个玩一下&#xff0c;红日5比前面的都简单。 靶机环境 win7&#xff1a;192.168.80.150(外)、192.168.138.136(内) winserver28&#xff08;DC&#xff09;&#xff1a;192.168.138.138 环境搭建就不说了&#xff0c;和之前写…...

k8s,声明式API对象理解

命令式API 比如&#xff1a; 先kubectl create&#xff0c;再replace的操作&#xff0c;我们称为命令式配置文件操作 kubectl replace的执行过程&#xff0c;是使用新的YAML文件中的API对象&#xff0c;替换原有的API对象&#xff1b;而kubectl apply&#xff0c;则是执行了一…...

【人工智能】用Python和Scikit-learn构建集成学习模型:提升分类性能

《Python OpenCV从菜鸟到高手》带你进入图像处理与计算机视觉的大门! 集成学习(Ensemble Learning)是通过组合多个弱学习器提升模型性能的机器学习方法,广泛应用于分类、回归及其他复杂任务中。随机森林(Random Forest)和梯度提升(Gradient Boosting)是集成学习的两种…...

MSBUILD : error MSB1009: 项目文件不存在。

环境&#xff1a;win10 vscode1.95.3 clang15.0.4 问题&#xff1a;最近用vscodecmakeclang在windows上编译c项目&#xff0c;用mingw32不报错&#xff0c;用clang报错,错误如下&#xff0c; MSBUILD : error MSB1009: 项目文件不存在。 原因&#xff1a;与其他生成器(如Mak…...

洛谷P1827 [USACO3.4] 美国血统 American Heritage(c嘎嘎)

题目链接&#xff1a;P1827 [USACO3.4] 美国血统 American Heritage - 洛谷 | 计算机科学教育新生态 题目难度&#xff1a;普及 首先介绍下二叉树的遍历&#xff1a; 学过数据结构都知道二叉树有三种遍历&#xff1a; 1.前序遍历&#xff1a;根左右 2.中序遍历&#xff1a;左根…...

YOLOv8模型pytorch格式转为onnx格式

一、YOLOv8的Pytorch网络结构 model DetectionModel((model): Sequential((0): Conv((conv): Conv2d(3, 64, kernel_size(3, 3), stride(2, 2), padding(1, 1))(act): SiLU(inplaceTrue))(1): Conv((conv): Conv2d(64, 128, kernel_size(3, 3), stride(2, 2), padding(1, 1))(a…...

工业—使用Flink处理Kafka中的数据_ProduceRecord1

1 、 使用 Flink 消费 Kafka 中 ProduceRecord 主题的数据&#xff0c;统计在已经检验的产品中&#xff0c;各设备每 5 分钟 生产产品总数&#xff0c;将结果存入Redis 中&#xff0c; key 值为 “totalproduce” &#xff0c; value 值为 “ 设备 id &#xff0c;最近五分钟生…...

RNACOS:用Rust实现的Nacos服务

RNACOS是一个使用Rust语言开发的Nacos服务实现&#xff0c;它继承了Nacos的所有核心功能&#xff0c;并在此基础上进行了优化和改进。作为一个轻量级、快速、稳定且高性能的服务&#xff0c;RNACOS不仅包含了注册中心、配置中心和Web管理控制台的功能&#xff0c;还支持单机和集…...

响应式编程一、Reactor核心

目录 一、前置知识1、Lambda表达式2、函数式接口 Function3、StreamAPI4、Reactive-Stream1&#xff09;几个实际的问题2&#xff09;Reactive-Stream是什么&#xff1f;3&#xff09;核心接口4&#xff09;处理器 Processor5&#xff09;总结 二、Reactor核心1、Reactor1&…...

聚簇索引与非聚簇索引

目录 一、聚簇索引&#xff08;Clustered Index&#xff09; 二、非聚簇索引&#xff08;Non-Clustered Index&#xff09; 三、示例说明 一、聚簇索引&#xff08;Clustered Index&#xff09; 定义&#xff1a; 聚簇索引是一种将数据存储和索引合为一体的索引方式。 表中…...

鸿蒙 Next 可兼容运行 Android App,还支持出海 GMS?

最近 「出境易」和 「卓易通」 应该算是鸿蒙和 Android 开发圈“突如其来”的热门话题&#xff0c;而 「出境易」可能更高频一些&#xff0c;主要也是 Next 5.0 被大家发现刚上架了一个名为「出境易」的应用&#xff0c;通过这个 App 用户可以直接运行不兼容 Next 的 Android A…...

opencv常用图像处理操作

OpenCV 处理图像的通用流程通常包括以下几个步骤&#xff0c;根据具体需求可以调整或跳过某些步骤。以下是一个通用的框架&#xff1a; 读取图像 加载图像文件到内存中以进行后续处理。 import cv2 读取图像 image cv2.imread(‘image.jpg’) # 彩色图像 gray_image cv2…...

<三>51单片机PWM开发SG90和超声测距

目录 1,PWM开发SG90 1.1简介 1.2控制舵机 1.3编程实现 2,超声测距 2.1简介 2.2,超声波测距代码实现 1,PWM开发SG90 1.1简介 PWM&#xff0c;英文名Pulse Width Modulation&#xff0c;是脉冲宽度调制缩写&#xff0c;它是通过对一系列脉冲的宽度进 行调制&#xff0c;等…...

如何加强游戏安全,防止定制外挂影响游戏公平性

在现如今的游戏环境中&#xff0c;外挂始终是一个困扰玩家和开发者的问题。尤其是定制挂&#xff08;Customized Cheats&#xff09;&#xff0c;它不仅复杂且隐蔽&#xff0c;更能针对性地绕过传统的反作弊系统&#xff0c;对游戏安全带来极大威胁。定制挂通常是根据玩家的需求…...

《矿物学报》

1 简介 《矿物学报》由中国科学院地球化学研究所和中国矿物岩石地球化学学会联合主办&#xff0c;1981年创刊&#xff0c;目前是双月刊&#xff0c;国内外公开发行。主要报道矿物学及相关学科的高水平研究成果&#xff0c;矿物学研究的现状综述和动态分析&#xff0c;以及国内…...

如何将快捷指令添加到启动台

如何将快捷指令添加到启动台/Finder/访达&#xff08;Mac&#xff09; 1. 打开快捷指令创建快捷指令 示例创建了一个文件操作测试的快捷指令。 2. 右键选择添加到程序坞 鼠标放在待添加的快捷指令上。 3. 右键添加到访达 鼠标放在待添加的快捷指令上。 之后就可以在启…...

Python NumPy学习指南:从入门到精通

Python NumPy学习指南&#xff1a;从入门到精通 第一部分&#xff1a;NumPy简介与安装 1. 什么是NumPy&#xff1f; NumPy&#xff0c;即Numerical Python&#xff0c;是Python中最为常用的科学计算库之一。它提供了强大的多维数组对象ndarray&#xff0c;并支持大量的数学函…...

rabbitmq 安装延时队列插件rabbitmq_delayer_message_exchange(linux centOS 7)

1.插件版本 插件地址&#xff1a;Community Plugins | RabbitMQ rabbitmq插件需要对应的版本&#xff0c;根据插件地址找到插件 rabbitmq_delayer_message_exchange 点击Releases 因为我rabbitmq客户端显示的版本是&#xff1a; 所以我选择插件版本是&#xff1a; 下载 .ez文…...

【C++】new与malloc、三种传参、函数重载及内联函数

一、new-delete以及malloc-freequbie 1.new&#xff1a; &#xff08;1&#xff09;运算符&#xff0c;没有返回值概念&#xff0c;但开出来空间有类型&#xff0c;不需要设置大小 &#xff08;2&#xff09;可以在堆区动态开辟空间&#xff0c;并且可以默认执…...

C语言实例_23之计算阶乘和(5!+4!+…+1!)

1. 题目 计算阶乘和(5! 4! … 1!)&#xff0c;即先分别计算出从1到5每个数的阶乘&#xff0c;再将这些阶乘值进行累加求和。 2. 阶乘概念及分析 实现主要是迭代和递归&#xff1a; - 迭代实现思路&#xff1a; - 可以通过两层循环来实现&#xff0c;外层循环控制数字从5到…...

zotero中pdf-translate插件和其他插件的安装

1.工具–》插件 2.找插件 3.点击之后看到一堆插件 4.找到需要的&#xff0c;例如pdf-translate 5.点击进入&#xff0c;需要看一下md文档了解下&#xff0c;其实最重要的就是找到特有的(.xpi file) 6.点击刚刚的蓝色链接 7.下载并保存xpi文件 8.回到zotero&#xff0c;安装并使…...

【Linux系统编程】——理解冯诺依曼体系结构

文章目录 冯诺依曼体系结构硬件当代计算机是性价比的产物冯诺依曼的存储冯诺依曼的数据流动步骤冯诺依曼结构总结 冯诺依曼体系结构硬件 下面是整个冯诺依曼体系结构 冯诺依曼结构&#xff08;Von Neumann Architecture&#xff09;是现代计算机的基本结构之一&#xff0c;由数…...

C++中实现多态有几种方式

一&#xff09;虚函数&#xff08;Virtual Functions&#xff09;实现多态 概念&#xff1a; 虚函数是在基类中使用关键字virtual声明的成员函数。当一个类包含虚函数时&#xff0c;编译器会为该类创建一个虚函数表&#xff08;v - table&#xff09;&#xff0c;这个表存储了虚…...

WPF+MVVM案例实战与特效(三十四)- 日志管理:使用 log4net 实现高效日志记录

文章目录 1、概述2、日志案例实现1、LogHelper 类详解2、代码解释3、配置文件4、实际应用案例场景 1:记录系统运行日志场景 2:记录数据库操作日志场景 3:记录 HTTP 请求日志5、总结1、概述 在WPF软件开发中,良好的日志记录机制对于系统的调试、维护和性能优化至关重要。lo…...

AI与低代码技术融合:如何加速企业智能化应用开发?

引言 随着全球数字化转型的步伐加快&#xff0c;企业在智能化应用开发方面面临着前所未有的挑战和机遇。传统的软件开发方式往往需要大量的技术人员、时间和资源&#xff0c;而在瞬息万变的市场环境中&#xff0c;这种模式显得效率低下且难以满足企业快速迭代和创新的需求。 与…...

git常用操作

通过分支src_br开发合并代码 在remote网页端从master分支创建一个src_br(master为合并代码的target分支)本地git仓库更新代码 git pull 切换到src_br分支 git checkout src_br 同步master分支的代码 git rebase master or git merge master or git cherry-pick commit-id…...

DVWA 靶场 SQL 注入报错 Illegal mix of collations for operation ‘UNION‘ 的解决方案

在 dvwa 靶场进行联合 SQL 注入时&#xff0c;遇到报错 Illegal mix of collations for operation UNION报错如下图&#xff1a; 解决办法&#xff1a; 找到文件MySQL.php 大致位置在dvwaincludesDBMS 目录下 使用编辑器打开 检索$create_db 第一个就是 在{$_DVWA[ ‘db_d…...

单片机的基本构成与工作原理

单片机&#xff0c;即微控制器&#xff08;Microcontroller Unit&#xff0c;MCU&#xff09;&#xff0c;是一种将中央处理器(CPU)、存储器(ROM/RAM)、定时/计数器(Timer/Counter)、中断系统、输入输出(I/O)接口等集成在一块芯片上的微型计算机。它具有体积小、功耗低、成本低…...

qt QSettings详解

1、概述 QSettings是Qt框架中用于应用程序配置和持久化数据的一个类。它提供了一种便捷的方式来存储和读取应用程序的设置&#xff0c;如窗口大小、位置、用户偏好等。QSettings支持多种存储格式&#xff0c;包括INI文件、Windows注册表&#xff08;仅限Windows平台&#xff0…...

微信小程序全屏显示地图

微信小程序在界面上显示地图&#xff0c;只需要用map标签 <map longitude"经度度数" latitude"纬度度数"></map>例如北京的经纬度为&#xff1a;116.407004,39.904595 <map class"bgMap" longitude"116.407004" lati…...

【Linux课程学习】:文件第二弹---理解一切皆文件,缓存区

&#x1f381;个人主页&#xff1a;我们的五年 &#x1f50d;系列专栏&#xff1a;Linux课程学习 &#x1f337;追光的人&#xff0c;终会万丈光芒 &#x1f389;欢迎大家点赞&#x1f44d;评论&#x1f4dd;收藏⭐文章 Linux学习笔记&#xff1a; https://blog.csdn.net/d…...

浅谈CI持续集成

1.什么是持续集成 持续集成&#xff08;Continuous Integration&#xff09;&#xff08;CI&#xff09;是一种软件开发实践&#xff0c;团队成员频繁地将他们的工作成果集成到一起(通常每人每天至少提交一次&#xff0c;这样每天就会有多次集成)&#xff0c;并且在每次提交后…...

微信创建小程序码 - 数量不受限制

获取小程序码&#xff1a;小程序码为圆图&#xff0c;且不受数量限制。 目录 文档 接口地址 请求方式 功能描述 注意事项 获取 scene 值 请求参数 返回参数 对接 请求方法 获取小程序码 调用获取小程序码 总结 文档 接口地址 https://api.weixin.qq.com/wxa/get…...