【Linux-基础IO】C语言文件接口回顾 系统文件概念及接口

目录

一、C语言文件接口回顾

C语言基础知识

C++中文件操作示例

二、系统文件概念及接口

重定向基本理解的回顾

文件的基本概念

系统调用接口

open

read

write

close

lseek

什么是当前路径


 

一、C语言文件接口回顾

引言:我们并不理解文件!从语言角度(绝对不可能理解)。我们要进行文件操作,前提是我们的程序跑起来了。文件的打开和关闭,是CPU在执行我们的代码

C语言基础知识

几个函数:

FILE *fopen(const char *path, const char *mode);` 

fopen是C语言标准库函数,用于打开一个文件并返回一个文件指针以便进行读写操作。如果文件打开成功,它会返回一个指向 FILE 类型的指针,该指针后续可以用于其他文件操作函数。如果打开失败,则返回 NULL

其中:

  • const char *path:这是一个字符串,表示要打开的文件的路径名。可以是相对路径或绝对路径。

  • const char *mode:这也是一个字符串,表示打开文件的模式,即如何使用文件

size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);

fread 函数是 C 标准库中的一个函数,用于从指定的流中读取数据到缓冲区中。该函数同样定义在 stdio.h 头文件中。fread 主要用于二进制输入操作。fread 函数返回成功读取的元素个数,这个数可能小于 nmemb,如果遇到错误或文件末尾。如果文件末尾在读取任何字节之前到达,fread 将返回 0。

其中:

  • ptr:指向内存块的指针,该内存块至少要有 size * nmemb 字节的大小。函数从流中读取数据到这个内存块中。

  • size:要读取的每个元素的大小,以字节为单位。

  • nmemb:要读取的元素个数,每个元素的大小为 size 字节。

  • stream:指向 FILE 对象的指针,该对象指定了输入流。

size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);

fwrite 函数是 C 标准库中的一个函数,用于将数据写入到指定的流中。这个函数也是定义在 stdio.h 头文件中。fwrite 主要用于执行二进制输出操作。fwrite 函数返回成功写入的元素个数,这个数可能小于 nmemb,如果遇到写入错误。如果成功写入 nmemb 个元素,则返回值等于 nmemb

其中:

  • ptr:指向要写入数据的内存块的指针。

  • size:要写入的每个元素的大小,以字节为单位。

  • nmemb:要写入的元素个数,每个元素的大小为 size 字节。

  • stream:指向 FILE 对象的指针,该对象指定了输出流

int fprintf(FILE *stream, const char *format, ...);

fprintf 是 C 标准库中的一个函数,用于将格式化的数据写入到指定的流(通常是文件流)。这个函数与 printf 类似,但是 fprintf 允许你指定写入数据的流,而 printf 默认写入到标准输出(通常是控制台)。如果成功,fprintf 返回写入的字符数。如果发生错误,fprintf 返回一个负数。

其中

  • stream:指向 FILE 对象的指针,该对象标识了要写入数据的流。

  • format:指向一个以空字符结尾的字符串,该字符串包含格式规范,用于指定如何写入后续参数。

  • ...:可变数量的参数,它们将被格式化并写入到流中。

int fclose(FILE *stream);

fclose 是 C 标准库中的一个函数,用于关闭由 fopenfreopen 或其他文件打开函数创建的文件流。关闭文件是非常重要的,因为它会释放与文件流关联的所有内部缓冲区,并且确保所有写入的数据都已经被正确地刷新到磁盘上。

其中:

  • 如果文件成功关闭,fclose 返回 0

  • 如果在关闭文件时发生错误,fclose 返回 EOF(通常定义为 -1)。

文件打开方式:

2accc902d1c9494685aaebbdbb625983.png

几个例子:

【示例1】

int main()
{FILE* fp = fopen("log.txt","w");if(fp == NULL){perror("fopen");return 1;}fprintf(fp, "helloworld, %d, %s, %lf\n", 10, "wuxu", 3.14);fclose(fp);return 0;
}

执行结果:

[wuxu@Nanyi lesson18]$ ls
makefile  mytest  test1.c
[wuxu@Nanyi lesson18]$ ./mytest
[wuxu@Nanyi lesson18]$ ls
log.txt  makefile  mytest  test1.c
[wuxu@Nanyi lesson18]$ cat log.t

可以发现,我们以写的方式打开log.txt,我们原先并没有log.txt,是“w”的方式,创建了一个log.txt 并向内格式化写入内容

如果我们修改一下 main函数里的格式化输出,再进行执行

将代码修改为

 fprintf(fp, "helloworld, %d, %s, %d\n", 10, "wuxu", 666);   

699b7088fc6f425ab601a1e74e14c543.png

可以发现:原文件内容被内容完全替代,因此我们对于“w”有个结论

  1. 如果文件不存在,就在当前路径下,新建指定的文件

  2. 默认打开文件的时候,就会先把目标文件清空

如果我们想追加对文件进行写入就需要讲模式换为“a”模式

C++中文件操作示例

#include <iostream>
#include <string>
#include <fstream>#define FILENAME "log.txt"int main()
{std::ofstream out(FILENAME);if(!out.is_open()) return 1;std::string message = "hello C++\n";out.write(message.c_str(), message.size());out.close();return 0;
}

 dc6994291dda4fe2abf42db4883f5b10.png

二、系统文件概念及接口

重定向基本理解的回顾

我们第一次理解重定向是在 echo命令上理解的,echo 命令本身是向显示器进行输出

[wuxu@Nanyi lesson18]$ echo "hello I am echo"
hello I am echo

我们也可以使用echo命令,向指定文件进行写入 

9ed9aa3fbe114822b9cf5bc2719f0497.png

我们也可以使用echo命令,向文件进行追加写入,也就是追加重定向

ae950d47677c4aabb6704fcfdc93a1c7.png 

那么我们也就发现 > 相当于 "w" , >> 相当于 “a“,如果是的话,我们也可以使用 > 或 >> 进行创建文件

8e0b4161ba5743d89fe9e47c5c3b0fb5.png 

所以我们也就发现:输出重定向一定是文件操作

文件的基本概念

当我们使用ls-l查看文件信息时,会显示文件的类型、权限、引用计数、所有者、所属组、大小、文件最新修改时间等信息,这些都是文件的属性

736487f8e7774044b32f68224d8940cb.png

由此我们可以知道:文件 = 内容+属性

 

文件被打开,需要先被加载到内存。由于内存空间有限,不可能将所有的文件全部打开,所以文件可以分为已被打开文件,和未打开文件。

打开文件:本质是进程打开文件。文件没有打开的时候,文件在磁盘中;

一个进程可以打开很多文件,一个系统中可以存在很多进程;所以很多情况下,在操作系统内部,一定存在大量被打开的文件,那么操作系统是如何对这些文件进行管理呢?--->**先描述,在组织

系统调用接口

文件是存在磁盘中的,由冯诺伊曼体系我们可知,磁盘是一个外设,外设是一个硬件。我们向文件中写入,本质是向硬件中写入;但是用户没有权利直接写入,由于操作系统是硬件的管理着,那我们就可以通过操作系统给我们提供的系统调用(OS不相信任何人)来访问文件

09c7ff59146f416e9bdf84ebdab79968.png

下面是系统调用接口的函数,我们来一一介绍使用,看看与C/C++文件操作有什么共同点与不同点

open

6578540c8382491090efb06444d0923d.png 

 

其中:

  • pathname:指向一个以空字符结尾的字符串,该字符串指定了要打开或创建的文件的路径名。

  • flags:指定文件的打开模式。这个参数是以下选项中的一个或多个通过按位或操作(|)组合起来的值:

    • O_RDONLY: 只读打开。

    • O_WRONLY: 只写打开。

    • O_RDWR: 读写打开。

    • O_CREAT: 如果文件不存在,则创建它。需要与 mode 参数一起使用。

    • O_EXCL: 与 O_CREAT 一起使用,确保调用 open 时创建文件。如果文件已存在,则返回错误。

    • O_TRUNC: 如果文件已存在且为写入或读写打开,则将其长度截断为 0。

    • O_APPEND: 追加模式。在每次写入时,数据会被添加到文件的末尾。

    • 其他标志请参考 open 的手册页。

  • mode:当创建新文件时,这个参数指定了文件的模式(权限)。它是一个八进制数,通常由 umask 的值与要求的权限按位取反后相与得到。常见的权限值包括:

    • S_IRUSR: 用户读权限。

    • S_IWUSR: 用户写权限。

    • S_IXUSR: 用户执行权限。

    • S_IRGRP, S_IWGRP, S_IXGRP: 分别对应组成员的读、写、执行权限。

    • S_IROTH, S_IWOTH, S_IXOTH: 分别对应其他用户的读、写、执行权限。

    • 也可以使用数字来表示权限

返回值:

  • 成功时,open 返回一个最小的非负整数,称为文件描述符,用于后续的读写操作。

  • 出错时,open 返回 -1,并设置 errno 来指示错误。

flag选项描述
O_RDONLY只读
O_WRONLY只写
O_CREAT不存在就创建
O_TRUNC清空文件
O_APPEND追加写入

这些选项都是大写,我们在C/C++中什么情况下用上大写?没错就是宏,这里的flag选项与宏并未区别,我们先自己用宏来创建一个传参示例

测试内容:

#include <stdio.h>
#define ONE   1      // 1 0000 0001
#define TWO   (1<<1) // 2 0000 0010
#define THREE (1<<2) // 4 0000 0100
#define FOUR  (1<<3) // 8 0000 1000void print(int flag)
{if(flag&ONE)printf("one\n"); //替换成其他功能if(flag&TWO)printf("two\n");if(flag&THREE)printf("three\n");if(flag&FOUR)printf("four\n");
}int main()
{print(ONE);printf("\n");print(TWO);printf("\n");print(ONE|TWO);printf("\n");print(ONE|TWO|THREE);printf("\n");print(ONE|FOUR);printf("\n");print(ONE|TWO|THREE|FOUR);printf("\n");return 0;
}

输出结果:

2e89f5311332447daccc36ddd96f02ac.png

因此我们发现可以定制宏,通过传入不同的宏,实现定制的不同功能。它的原理底层是位图;一个int类型包含32个比特位,如果我们让低位的 1 表示ONE,次低位的 1 表示 TWO......

30560bf199f04dbbb34df9c09a2eb96e.png 

若此时要实现ONE、TWO、THREE,只需要对这三个数做或位运算ONE|TWO|THREE则会得到一个值为00000000 00000000 00000000 00011010的flags。将它传递给处理函数中,处理函数会将flag与ONE、TWO、THREE这三个数挨个做按位与,如果按位与出来的结果不为0,则表示该选项会被选择

而我们系统调用的flags也是一样的道理

接下来让我们用系统调用接口来操作文件

【示例】

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>int main(){//system callint fda = open("loga.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);printf("fda: %d\n", fda);int fdb = open("logb.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);printf("fdb: %d\n", fdb);int fdc = open("logc.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);printf("fdc: %d\n", fdc);int fdd = open("logd.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);printf("fdd: %d\n", fdd);return 0;
}

 输出结果:

c6ee621585c34c46b075227e41a4c7df.png

 

【问题1】我们定制的权限是666 ,为什么这里是 662?是umask的原因

PS:我们使用系统接口必须要指明权限,否则创建的文件权限是随机的

在 POSIX 兼容的操作系统中,umask 是一个系统调用,用于设置进程的文件模式创建掩码(file mode creation mask)。这个掩码定义了新创建文件的默认权限,通过从权限掩码中“掩蔽”掉一些权限位来限制新文件的权限。我们来查看一下系统的umask

13a7f1cdfa0440fa8f85a032edc63e26.png

接下来我们修改一下umask,看文件权限能不能达到我们想要的结果

在test2.c 加入umask(0)即可

测试结果:

9e83bce22bf845f194788ce4630dcc3c.png

 

【问题2】open系统调用函数返回的数字是什么?这个输出结果有什么含义?

open打开文件后会返回一个数字,这个数字被称为文件描述符,我们一会在下文详细讲解

read

b4287fe0a244451287dd71d9bb594627.png

要从某个文件中读取内容时,第一个参数需要传入该文件的文件描述符,第二个参数需要传入接收文件内容的缓冲区首地址,第三个参数表示要从文件中读取多少字节的内容。

下面程序模拟实现了C语言fopen的r(只读)打开模式

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>int main()
{int fd = open("./log.txt", O_RDONLY);char buffer[1024];read(fd, buffer, sizeof(buffer));printf("%s", buffer);return 0;
}

 6c4f43937af0488ab1c745c230f04894.png

write

write 是 POSIX 系统调用之一,用于将数据写入文件描述符指向的文件或设备。这个函数在 C 语言中被广泛使用,尤其是在 UNIX、Linux 和其他 POSIX 兼容的操作系统中。

b1abe68ccd9845d5975db7b05f9c08de.png

 

参数:

  • fd:要写入数据的文件描述符。这个文件描述符可以是 open 系统调用返回的,也可以是标准输入输出错误文件描述符(例如 STDOUT_FILENOSTDERR_FILENO)。

  • buf:指向要写入数据的缓冲区的指针。

  • count:要写入的字节数。

返回值:

  • 成功时,write 返回实际写入的字节数。

  • 出错时,write 返回 -1,并设置 errno 来指示错误。

 

下面程序模拟实现C语言fopen的w(只读)模式打开

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>int main()
{int fd = open("./log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);char* msg = "Have a good day!\n";write(fd, msg, strlen(msg));return 0;
}

2ff3f8983c3747e8b6b38efa1073a742.png

下面程序模拟实现C语言fopen的a(追加)模式打开

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>int main()
{int fd = open("./log.txt", O_WRONLY | O_CREAT | O_APPEND, 0666);char* msg = "Have a good day!\n";write(fd, msg, strlen(msg));return 0;
}

 51f6a6d9006343f58c6d44935025701c.png

close

8178bfd939784e3e9afca8eedc193119.png 

传入文件描述符即可关闭对应文件,这里就不掩饰了

lseek

lseek 是 POSIX 系统调用之一,用于设置文件描述符 fd 指向的文件的偏移量。这个函数允许你查询或修改文件流的当前位置,这对于随机访问文件非常有用。

d6197ae77ee341cc98c8db326939f012.png

 

参数:

  • fd:文件描述符,指向已经打开的文件。

  • offset:偏移量,根据 whence 的值,这个偏移量可以是绝对值,也可以是相对值。

  • whence:用于确定如何解释offset的值。它可以是以下三个常量之一:

    • SEEK_SET: 将偏移量设置为从文件开头算起的绝对位置。

    • SEEK_CUR: 将偏移量设置为相对于当前文件位置的相对位置。

    • SEEK_END: 将偏移量设置为相对于文件末尾的相对位置。

返回值:

  • 成功时,lseek 返回新的文件偏移量。

  • 出错时,lseek 返回 -1,并设置 errno 来指示错误。

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>int main()
{int fd = open("./log.txt", O_RDONLY | O_APPEND, 0666);char buffer[1024];read(fd, buffer, sizeof(buffer));printf("%s", buffer);//回到文件头再读一遍lseek(fd, 0, SEEK_SET);read(fd, buffer, sizeof(buffer));printf("%s", buffer);close(fd);return 0;
}

d277bc9b7d5f4feb806adfe0f8f20435.png

什么是当前路径

当我们没有指明文件路径,open一个文件时,则open将在当前路径下创建文件那为什么会这个样子呢?

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>int main()
{int fd = open("log.txt", O_CREAT);close(fd);sleep(200);return 0;
}

当程序运行起来后,我们可以使用ps ajx | head -1 && ps ajx | grep mytest | grep -v grep 查看运行该程序的进程pid,进入/proc/进程pid目录,可以看到两个链接文件cwd和exe。其中,cwd是程序的工作路径,也就是我们常说的当前路径,而exe是可执行程序的保存位置。

42199962d22d4069a25a5790e058e94a.png

 

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

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

相关文章

springboot实战学习(7)(JWT令牌的组成、JWT令牌的使用与验证)

接着上篇博客的学习。上篇博客是在基本完成用户模块的注册接口的开发以及注册时的参数合法性校验的基础上&#xff0c;基本完成用户模块的登录接口的主逻辑以及提到了问题&#xff1a;"用户未登录&#xff0c;需要通过登录&#xff0c;获取到令牌进行登录认证&#xff0c;…

TypeError: a bytes-like object is required, not ‘str‘ - 完美解决方法

&#x1f680;TypeError: a bytes-like object is required, not str - 完美解决方法&#x1f4a1; &#x1f680;TypeError: a bytes-like object is required, not str - 完美解决方法&#x1f4a1;摘要引言正文1. 错误背景&#xff1a;字节与字符串的区别&#x1f440;2. 错…

告别ESLint噩梦!轻松几步解决 indent 与 react/jsx-indent-props 的 空格 冲突!

话不多说&#xff0c;直接上代码&#xff0c;下面是截取的一部分 eslint 配置。可以看到我设置了四个空格和标签属性对齐首个。 "rules": {"indent": ["error", 4], // 四个空格"react/jsx-indent-props": ["error", "…

双虚拟机部署php项目

前言 经过前面的学习,我们对分布式部署有了一定的了解,这次我们尝试做些东西 准备 我打算用虚拟机部署一个外联网盘 一台虚拟机安装php另一台安装MySQL,但是之前已经安装过 MariaDB 了,就不打算改了。 通常MariaDB与MySQL兼容性很好,可以作为替代使用。彩虹外链网盘项目…

【C++11】异常处理

目录 一、异常的引入 二、C异常的关键字 三、异常的抛出与处理规则 四、异常缺陷的处理 五、自定义异常体系 六、异常规范 七、异常安全 八、异常的优缺点 1.优点 2.缺点 一、异常的引入 传统的C语言处理异常的方式有两种&#xff1a; 1.终止程序&#xff1a;使用as…

CSS 选择器的分类与使用要点一

目录 非 VIP 用户可前往公众号进行免费阅读 标签选择器 id 选择器 类选择器 介绍 公共类 CSS 中优先用 class 选择器,慎用 id 选择器 后代选择器 交集选择器 以标签名作为开头 以类名作为开头 连续交集 并集选择器(分组选择器) 通配符* 儿子选择器 >(IE7…

变量常量标识符

1. 变量 1.1 变量的概念 变量是计算机内存中的一块存储单元&#xff0c;是存储数据的基本单元变量的组成包括&#xff1a;数据类型、变量名、值&#xff0c;后文会具体描述变量的本质作用就是去记录数据的&#xff0c;比如说记录一个人的身高、体重、年龄&#xff0c;就需要去…

PP-HGNet(High Performance GPU Net)

发展历程&#xff1a; DenseNet -> VoVNet -> HGNet VoVNet是DenseNet的改进&#xff0c;如图&#xff1a; VoVNet论文网址&#xff1a; https://arxiv.org/pdf/1904.09730 HGNet模型简介 PP-HGNet(High Performance GPU Net) 是百度飞桨视觉团队自研的更适用于 GPU…

【AI大模型】对接LLM API

本章节主要介绍四种大语言模型&#xff08;ChatGPTAPI、文心一言、讯飞星火、智谱 GLM&#xff09;的 API 申请指引和 Python 版本的原生 API 调用方法。 文心一言&#xff1a;当前无赠送新用户 tokens 的活动&#xff0c;推荐已有文心 tokens 额度用户和付费用户使用&#xf…

果断收藏!2024年最好用的七款高效论文写作神器

在2024年&#xff0c;随着人工智能技术的飞速发展&#xff0c;AI论文写作工具成为学术研究和写作领域的一大助力。这些工具不仅能够显著提高写作效率&#xff0c;还能帮助学者和学生节省时间&#xff0c;减少熬夜&#xff0c;同时保证论文质量。以下是七款高效且值得收藏的AI论…

Python_控制循环语句

if语句单分支结构的语法形式如下&#xff1a; 【操作】输入一个数字&#xff0c;小于10&#xff0c;则打印这个数字(if_test01.py)&#xff1a; num input("输入一个数字&#xff1a;") if int(num)<10: print("小于10的数&#xff1a;"num)条件表达式…

2024年华为杯数学建模E题-高速公路应急车道启用建模-基于YOLO8的数据处理代码参考(无偿分享)

利用YOLO模型进行高速公路交通流量分析 识别效果&#xff1a; 免责声明 本文所提供的信息和内容仅供参考。尽管我尽力确保所提供信息的准确性和可靠性&#xff0c;但我们不对其完整性、准确性或及时性作出任何保证。使用本文信息所造成的任何直接或间接损失&#xff0c;本人…

资源创建方式-Job

Job: 容器按照持续运行的时间可分为两类&#xff0c;服务类容器&#xff0c;和工作类容器 服务类容器通常持续提供服务&#xff0c;需要一直运行&#xff0c;比如HTTP,Server&#xff0c;Daemon等&#xff0c; 工作类容器则是一次性任务&#xff0c;比如批处理程序&#xff0…

stm32单片机个人学习笔记7(TIM定时中断)

前言 本篇文章属于stm32单片机&#xff08;以下简称单片机&#xff09;的学习笔记&#xff0c;来源于B站教学视频。下面是这位up主的视频链接。本文为个人学习笔记&#xff0c;只能做参考&#xff0c;细节方面建议观看视频&#xff0c;肯定受益匪浅。 STM32入门教程-2023版 细…

数值计算 --- 平方根倒数快速算法(中)

平方根倒数快速算法 --- 向Greg Walsh致敬&#xff01; 1&#xff0c;平方根倒数快速算法是如何选择初值的?WTF中的神秘数字究竟是怎么来的&#xff1f; 花开两朵&#xff0c;各表一枝。在前面的介绍中&#xff0c;我们已经知道了这段代码的作者在函数的最后使用了NR-iteratio…

Python办公自动化教程(003):PDF的加密

【1】代码 from PyPDF2 import PdfReader, PdfWriter# 读取PDF文件 pdf_reader PdfReader(./file/Python教程_1.pdf) pdf_writer PdfWriter()# 对第1页进行加密 page pdf_reader.pages[0]pdf_writer.add_page(page) # 设置密码 pdf_writer.encrypt(3535)with open(./file/P…

mybatis 配置文件完成增删改查(四) :多条件 动态sql查询

文章目录 就是你在接收数据时&#xff0c;有的查询条件不写&#xff0c;也能从查到相应的stauts也可能为空恒等式标签 代替where关键字 就是你在接收数据时&#xff0c;有的查询条件不写&#xff0c;也能从查到相应的 注意是写字段名 还是 属性名 companyName不写也能查出满足…

亚马逊IP关联揭秘:发生ip关联如何处理

在亚马逊这一全球领先的电商平台上&#xff0c;IP关联是一个不可忽视的问题&#xff0c;尤其是对于多账号运营的卖家而言。本文将深入解析亚马逊IP关联的含义、影响以及应对策略&#xff0c;帮助卖家更好地理解和应对这一问题。 什么是亚马逊IP关联&#xff1f; 亚马逊IP关联…

9.22算法题数组篇

数组的遍历 485.最大连续1的个数 题解 class Solution {public int findMaxConsecutiveOnes(int[] nums) {int maxcount0,count0;for (int i 0;i<nums.length;i){if(nums[i]1){count;}else{maxcountMath.max(maxcount,count);count0;}}maxcountMath.max(maxcount,count);r…

2024AI做PPT软件如何重塑演示文稿的创作

现在AI技术的发展已经可以帮我们写作、绘画&#xff0c;最近我发现了不少ai做ppt的工具&#xff01;不体验不知道&#xff0c;原来合理使用AI工具可以有效的帮我们进行一些办公文件的编写&#xff0c;提高了不少工作效率。如果你也有这方面的需求就接着往下看吧。 1.笔灵AIPPT…