C语言:文件操作详解

news2025/2/4 18:45:20

                                                     创作不易,友友们给个三连吧!!

一、为什么我们需要使用文件

       我们在写程序的时候,输入的数据是存储在电脑内存中的,如果程序退出内存回收,相应数据也就丢失了,等再次运行程序,就看不到上次输入的数据了,所以为了能够让我们的数据永久化保存,我们可以使用文件!

二、什么是文件

2.1 文件的分类

     在磁盘(硬盘上)的文件就叫做文件,在程序设计中,文件一般分为两种:程序文件、数据文件。

      程序文件包括源文件(.c),目标文件(windows环境后缀为.obj),可执行程序(windows环境后缀为.exe)

      数据文件,因为文件的内容不一定是程序,也可能是程序运行时读写的数据,所以可以理解为程序运行需要从中读取数据的文件,或者输出内容的文件。

      本文内容以数据文件为主!!

 2.2 终端

      终端:包括显示器、鼠标、键盘、耳机、麦克风、显示器和摄像头等等。 这些外围设备就被称为终端,负责向主机输入数据的就叫输入终端,比如鼠标、键盘、麦克风、摄像头,负责接收主机输出数据的设备就被称作输出终端,比如显示器、耳机。

       以往在C语言程序编写中,我们处理数据的输入和输出都是以终端为对象的,即通过终端的键盘输入数据,并将运行结果显示在显示器上。

       但是这样的方式显然不能达到永久保存数据的目的,所以我们需要把信息输出到磁盘的文件中,当我们需要的时候再通过磁盘中的文件将数据读取到内存中去使用!

2.3 文件名

      我们为了将程序中输入的数据永久化保存起来,就需要用到文件,而每个文件都要自己的一个唯一的标识,用来方便用户的识别和引用。我们把这种文件标识叫做文件名。

     文件名包括了3部分:文件路径+文件名主干+文件后缀

     比如c:\code\test.txt 

     c:\code是文件路径,test是文件主干,.txt是文件后缀

一般想要知道某个文件的路径,即找到这个文件,右击属性,就可以找到他的位置

三、二进制文件和文本文件

      根据数据的组织形式,数据文件又被区分为文本文件二进制文件。

      数据在内存中是以二进制形式存储的,所以如果不加以转换输出到磁盘文件中,那该文件就是二进制文件。如果要求以ASCII码的形式是存储在磁盘文件中,那么在存储前需要将二进制转换成ASCII形势存储,那么该文件就被成为文本文件。

     数据在内存中的存储形式:字符一律以ASCII码形式存储,数值型数据既可以用ASCII形式存储,也可以使用二进制形式存储。

      .txt后缀的文件一般为文本文件的格式,是我们肉眼可以看得懂的,而二进制文件是我们肉眼看不懂得,但是编译器可以读懂。

四、文件的打开和关闭

      如何操作文件呢?操作文件的步骤其实和我们喝饮料一样,喝饮料的时候,先打开瓶盖,然后喝,最后关紧瓶盖。而我们想要操作文件,首先是打开文件,然后对文件进行读写操作,最后是关闭文件。

4.1 流

       我们程序的数据需要输出到各种外部设备,也需要从外部设备获取数据,不同的外部设备的输⼊输出操作各不相同,为了方便程序员对各种设备进⾏⽅便的操作,我们抽象出了流的概念,我们可以把流想象成流淌着字符的河。

     也就是说,流其实就是程序底层设计出来的一种东西,他存在于内存中的某一块区域,专门负责对接不同的外部设备,所以我们程序员并不需要关心我们的数据如何和外部设备建立联系,我们只需要关注流!!所以C程序针对⽂件、画面、键盘等的数据输⼊输出操作都是通过流操作的。

     ⼀般情况下,我们要想向流⾥写数据,或者从流中读取数据,都是要打开流,然后操作。

 4.2 标准流

     在C程序中,我们通过键盘输入数据,并向屏幕中输出数据,其实都是通过流来完成。

因为在C语言程序启动的时候,默认打开了3个流:

• stdin - 标准输⼊流,在⼤多数的环境中从键盘输⼊,scanf函数就是从标准输⼊流中读取数据。

• stdout - 标准输出流,⼤多数的环境中输出⾄显⽰器界⾯,printf函数就是将信息输出到标准输出流中。

• stderr - 标准错误流,大多数环境中输出到显示器界流。perror函数就是将错误的信息输出到标准输出流中

      有了这三个流,我们就可以通过scanf/printf/perror函数来进行输入和输出操作。

      而流本身作为程序底层开发出来的内容,存在于内存中的某一块区域,C语言中,通过FILE*的文件指针来维护流的各种操作。

      stdin、stdout、stderr 三个流的类型是: FILE* ,通常称为⽂件指针。

4.3 文件指针

     缓冲⽂件系统中,关键的概念是“⽂件类型指针”,简称“⽂件指针”。

     每一个被使用的文件都在内存中开辟了一个相应的文件区,专门用来存放文件的相关信息(如⽂件的名字,⽂件状态及⽂件当前的位置等)。这些信息是保存在⼀个结构体变量中的。该结构体类型是由系 统声明的,取名FILE。

     例如,VS2013编译环境提供的 stdio.h 头⽂件中有以下的⽂件类型申明:

struct _iobuf {
 char *_ptr;
 int _cnt;
 char *_base;
 int _flag;
 int _file;
 int _charbuf;
 int _bufsiz;
 char *_tmpfname;
 };
typedef struct _iobuf FILE;

    不同的C编译器的FLIE类型包含的内容不完全相同,但是大同小异,理论上来说,越高级的编译器,封装得会越完善,所以越不容易观察到这些细节。

    也就是说,我们每当打开一个文件,系统就会根据文件的情况况⾃动创建⼀个FILE结构的变量,并填充其中的信息,使⽤者不必关⼼细节。

    ⼀般都是通过⼀个FILE*的指针来维护这个FILE结构的变量,这样使⽤起来更加⽅便。

   如图,首先我们打开文件,其实就是向内存中申请了一块空间,该空间的类型是FILE类型,我们把它称为文本信息区,该文本信息区首先会根据原有的文本信息进行填充,之后再由操作者利用FILE*指针对这个文本信息区进行读或者写的操作,我们程序员不需要知道文件(data.txt)是如何和这个用结构体变量存储的文件信息区建立联系的,我们只需要知道我们可以用FILE*来操作文件,该文本信息区就相当于"流",我们通过"流"和外部设备建立联系。

   4.4  文件的打开和关闭

       文件在读写之前要打开文件,使用结束后应关闭文件。

       而我们在打开文件的同时,都会返回一个FILE*类型的指针,来帮助我们建立和该文件的联系,我们可以通过这个指针来进行读写的操作。

      ANSIC 规定使⽤ fopen 函数来打开文件, fclose 来关闭文件。

4.4.1 fopen

const char * filename:传需要打开的文件的文件名
const char * mode :传该文件的打开方式
Open file:作用是打开文件

返回类型:FILE*指针,我们可以通过该指针完成对文件的读写操作(相当于通过该指针打开了一个文件流)

注:文件可能会打开失败,如果FILE*指针成功返回,说明打开成功,如果返回的是NULL,则打开失败!

4.4.2 fclose

FILE * stream:传维护该文件流的指针
Close file:作用是关闭文件

4.4.3 打开模式

   fopen函数中的参数mode表示文件的打开模式,下面都是文件的打开模式。

4.4.3.1 w和a的区别

    w和a都可以写,但是如果我们打开的文件原来保存着一些数据,用w的话,会先清空掉文件的数据再进行写的操作,而用a则是直接在这些数据的后面进行追加

4.4.3.2 r+、w+、a+的区别

   r+是覆盖写,即在原来文件数据的基础上从头开始覆盖写入

   w+是清空写,即原来文件数据会被清空再开始写

   a+是追加写,即在原本文件数据的基础上在后面追加写入

4.4.4 文件的存储路径

1.相对路径

int main()
{
	FILE* pf;
	//打开文件
	pf = fopen("./../data.txt", "w");//桌面生成
	if (pf == NULL)//如果打开失败
	{
		perror("fopen");
		return 1;
	}
	//文件操作
	 
	//关闭文件
	fclose(pf);
	return 0;
}

./   当前目录
../  上一级目录
/    根目录

2.绝对路径

int main()
{
	FILE* pf;
	//打开文件
	pf = fopen("C:\\Users\\lenovo\\Desktop\\data.txt", "w");//桌面生成
	if (pf == NULL)//如果打开失败
	{
		perror("fopen");
		return 1;
	}
	//文件操作
	 
	//关闭文件
	fclose(pf);
	return 0;
}

五、文件的顺序读写

顺序读写函数介绍:

     上⾯说的适⽤于所有输⼊流⼀般指适⽤于标准输⼊流和其他输⼊流(如⽂件输⼊流);所有输出流⼀ 般指适⽤于标准输出流和其他输出流(如⽂件输出流)。

5.1 fputc

int character :传需要输出的字符
FILE * stream:传需要输出的流
Write character to stream:作用是将字符写入流中

      放置的过程其实跟光标有关,最开始光标是在最前面,输出a的时候光标移到b的后面了,所以再输出的b就是在a的后面,以此类推。

     fpuc可适用于所有输出流,所以也可以使用标准输出流

5.2 fgetc

FILE * stream:传需要读取的流
Get character from stream:作用是从流中获取字符

注:如果读取失败,会返回EOF;

原本文件里有abcd,我们调用了四次fgetc将他读取出来并打印在屏幕上。

与fputc一样,fgetc也适用于标准输入流,所以也可以从键盘读取

但是因为读取也是按照光标去逐个读取的,所以如果加上空格,就会有的数据读取不到

写一个代码,完成将data.txt文件的内容,拷贝一份生成data1.txt文件

int main()
{
	//打开文件
	FILE* pfread = fopen("data.txt", "r");
	if (pfread == NULL)//如果打开失败
	{
		perror("fopen->data.txt");
		return 1;
	}
	FILE* pfwrite = fopen("data1.txt", "w");
	if (pfwrite == NULL)//如果打开失败
	{
		//第二次打开文件,如果打开失败,返回前必须把前一个文件关闭掉并置空
		fclose(pfread);
		pfread = NULL;
		perror("open->data1.txt");
		return 1;
	}
	//文件操作
	//数据的拷贝
	int ch = 0;
    while ((ch = fgetc(pfread)) != EOF)//循环读取直到读取不到
	{
		fputc(ch, pfwrite);
	}
//循环结束后可以做到完全拷贝
	//关闭文件
	fclose(pfread);
	fclose(pfwrite);
	return 0;
}

5.3 fputs

 const char * str:传需要输出的字符串
 FILE * stream:传需要输出的流
Write string to stream:作用是将字符串写入流中

5.4 fgets

char * str:传字符串存放的地址(一般是一个字符数组)
int num:传从流中读取的最大字符串长度(由于字符串末尾有一个\0,所以默认其实最多读取num-1个)
FILE * stream:传需要读取的流
Get string from stream:作用是从流中获取字符串

我们可以看到,由于字符串的末尾要默认跟一个\0,所以最多只能读取num-1个到arr数组中

5.5 fprintf

FILE * stream:传需要输出的流
const char * format:传格式化字符串,用于指定输出的格式。
...:可变参数列表
Write formatted data to stream:作用是将一块格式化的数据输出到流中

5.6 fscanf

FILE * stream:传需要读取的流
const char * format:传格式化字符串,用于指定输入的格式。
...:可变参数列表
Read formatted data from stream:作用是从流中读取一块格式化的数据

注:%f打印时可以控制格式

5.7 fwrite

const void * ptr:传要写入的数据的指针
size_t size:传每个数据的大小
size_t count:传要写入数据项的数量
FILE * stream:传需要输出的流
Write block of data to stream:作用是将整块的数据输出到流中

    因为是二进制形式,所以我们看不懂,但是编译器可以看懂,所以要使用fwrite就要配套使用fread!!

注:无论是什么文件,字符都是以ascii码值存储的,所以打印出来都一样!!

5.8 fread

const void * ptr:传要写入的数据的指针
size_t size:传每个数据的大小
size_t count:传要写入数据项的数量
FILE * stream:传需要输入的流
Read block of data to stream:作用是将整块的数据输入到流中

将fwrite输出到文件里的数据通过fread成功读取回来。

六、一些函数的对比

6.1 scanf/fscanf/sscanf

const char * s:用于读取的字符串
const char * format:格式化的字符串,用于输入指定的格式
 ...:可变参数列表
Read formatted data from string:在字符串中读取一个格式化的数据

       对比一下参数,共同点都是读取一个格式化的数据,不同的是scanf是默认的标准输入流,从键盘上读取,而fscanf是所有的标准输入流都可以,参数可以传文件流也可以跟scanf一样传stdin(标准输入流),而sscanf是从一个字符串中读取。

6.2 printf/fprintf/sprintf

const char * s:用于输出的字符串
const char * format:格式化的字符串,用于输出指定的格式
...:可变参数列表
Write formatted data from string:把一个格式化的数据转换成字符串

      对比一下参数,共同点都是输出一个格式化的数据,不同的是printf是默认的标准输出流,输出到屏幕上,而fprintf是所有的标准输出流都可以,参数可以传文件流也可以跟printf一样传stdout(标准输入流),而sprintf是输出到一个字符串中。

6.3 总结

scanf:针对标准输入(键盘)的格式化输入函数

printf:针对标准输出(屏幕)的格式化输出函数

fscanf:针对所有输入流的格式化输入函数

fprintf:针对所有输出流的格式化输入函数

sscanf:从一个字符串中读取格式化的数据

sprintf:把一个格式化的数据转换成字符串

七、文件的随机读写

      顺序读写,顾名思义就是按顺序读写,本质是根据光标的移动按顺序读写,而光标最开始就是在文件的最前面。

     这里的随机,并不是类型随机数的那种随机,而是我们想在文件哪个位置读取就在哪个位置读取,想在文件哪个位置输出就在哪个位置输出,那么我们想要实现文件的随机读写,就要想办法去操控光标

    而由于流是通过FILE*指针维护的,所以本质上,也是通过操控指针来达到操控光标的目的。

7.1 fseek

FILE * stream:需要操作的流
long int offset: 偏移量
int origin:起始位置

这个origin可以传3个参数:

SEEK_SET:起始位置

SEEK_CUR:当前光标的位置

SEEK_END:文件的末尾

Reposition stream position indicator:作用是重新定位光标位置指示器(根据⽂件指针的位置和偏移量来定位⽂件指针

7.2 ftell

 FILE * stream:需要操作的流
Get current position in stream:作用是获取流中的当前位置(返回⽂件指针相对于起始位置的偏移量

利用fseek和ftell来判断文件有多少个字节(fseek操控光标到尾部,再由ftell返回长度)

7.3 rewind

 FILE * stream:需要操作的流
Set position of stream to the beginning:作用是将流的位置设置为开头(即让文件指针返回到文件的起始位置)

八、文件读取结束的判定

8.1 feof

 FILE * stream:需要操作的流
Check end-of-file indicator:作用是检查文件结束指示符(即在文件读取结束后,判断是否是因为遇到文件末尾而结束)

8.2 ferror

 FILE * stream:需要操作的流
Check error indicator:作用是检查错误指示灯(即检查操作目标文件的过程中是否出现了错误)

8.3 判断文本文件是否读取结束的方法

⽂本⽂件读取是否结束,判断返回值是否为 EOF ( fgetc ),或者 NULL ( fgets )

例如:

• fgetc 判断是否为 EOF

• fgets 判断返回值是否为 NULL .

8.4 判断二进制文件是否读取结束的方法

⼆进制⽂件的读取结束判断,判断返回值是否⼩于实际要读的个数。

例如:

• fread判断返回值是否小于实际要读的个数。

8.5 判断文件是因为什么原因结束的

8.5.1 文本文件   

8.5.2 二进制文件

8.6 putchar

int character:要输出的字符
Write character to stdout:作用是将字符写到屏幕上

8.7 getchar

Get character from stdin:作用是从键盘获取字符

九、文件缓冲区

        ANSIC 标准采⽤“缓冲⽂件系统”处理的数据⽂件的,所谓缓冲⽂件系统是指系统⾃动地在内存中为 程序中每⼀个正在使⽤的⽂件开辟⼀块“⽂件缓冲区”。从内存向磁盘输出数据会先送到内存中的缓冲区,装满缓冲区后才⼀起送到磁盘上。如果从磁盘向计算机读⼊数据,则从磁盘⽂件中读取数据输⼊到内存缓冲区(充满缓冲区),然后再从缓冲区逐个地将数据送到程序数据区(程序变量等)。缓冲区的⼤⼩根据C编译系统决定的。

 这⾥可以得出⼀个结论:

        因为有缓冲区的存在,C语⾔在操作⽂件的时候,需要做刷新缓冲区或者在⽂件操作结束的时候关闭⽂件。 如果不做,可能导致读写⽂件的问题(数据丢失)。

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

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

相关文章

【IMAX6U移植OpenCV】

IMAX6U移植OpenCV V1.3 一 安装通用交叉编译器1.1 下载通用交叉编译器1.2 安装通用交叉编译器1.3 验证通用交叉编译器 二 搭建 OpenCV 3.4.1 的编译环境2.1 下载 OpenCV 3.4.1 源码2.2 配置 OpenCV 环境2.3 编译 OpenCV 源码 三 Qt 项目中加入OpenCV3.1 Qt 项目的 pro 文件Open…

Redis单机-主从集群-哨兵集群-分片集群 搭建教程

Redis集群 本章是基于CentOS7下的Redis集群教程,包括: 单机安装RedisRedis主从Redis分片集群 1.单机安装Redis 首先需要安装Redis所需要的依赖: yum install -y gcc tclredis-6.2.4.tar.gz 然后将Redis安装包上传到虚拟机的任意目录&am…

2024美赛A题完整思路代码分析:建立竞争机理方程+遗传算法优化

A题是自由度比较大的场景限定下的模型构建,相对比较容易,核心是找到现有的成熟的数学模型,然后找到合适的数据进行证明得到结论,估计大部分是目标优化问题。(不限制专业) B题属于较为经典的物理建模&#…

Linux基础知识合集

整理了一下学习的一些关于Linux的一些基础知识,同学们也可以通过公众号菜单栏查看! 一、基础知识 Linux基础知识 Linux命令行基础学习 Linux用户与组概念初识 Linux文件与目录权限基础 Linux中文件内容的查看 Linux系统之计划任务管理 二、服务器管理 Vm…

【靶场实战】Pikachu靶场XSS跨站脚本关卡详解

Nx01 系统介绍 Pikachu是一个带有漏洞的Web应用系统,在这里包含了常见的web安全漏洞。 如果你是一个Web渗透测试学习人员且正发愁没有合适的靶场进行练习,那么Pikachu可能正合你意。 Nx02 XSS跨站脚本概述 Cross-Site Scripting 简称为“CSS”&#xff…

HTTP(Java web方向补充篇)

HTTP(Java web方向补充篇) HTTP简介 概念:Hyper Text Transfer Protocol,超文本传输协议,规定了浏览器和服务器之间数据传输的规则 HTTP协议特点: 基于TCP协议:面向连接,安全基于请求-响应模…

C语言指针学习 之 指针是什么

前言 指针是C语言中一个重要概念,也是C语言的一个重要特色,正确而灵活地运用指针可以使程序简洁、紧凑、高效。每一个学习和使用C语言的人都应当深入的学习和掌握指针,也可以说不掌握指针就没有掌握C语言的精华。 一、什么是指针 想弄清楚什…

基于SpringBoot+Redis的前后端分离外卖项目-苍穹外卖(十六)

商家端订单管理模块 1. 订单搜索1.1 需求分析和设计1.2 代码实现1.2.1 admin/OrderController1.2.2 OrderService1.2.3 OrderServiceImpl 2. 各个状态的订单数量统计2.1 需求分析和设计2.2 代码实现2.2.1 admin/OrderController2.2.2 OrderService2.2.3 OrderServiceImpl2.2.4 …

python执行linux系统命令的三种方式

前言 这是我在这个网站整理的笔记,有错误的地方请指出,关注我,接下来还会持续更新。 作者:神的孩子都在歌唱 1. 使用os.system 无法获取命令执行后的返回信息 import osos.system(ls)2. 使用os.popen 能够获取命令执行后的返回信息 impor…

红队渗透靶机:LEMONSQUEEZY: 1

目录 信息收集 1、arp 2、nmap 3、nikto 4、whatweb 目录扫描 1、dirsearch 2、gobuster WEB phpmyadmin wordpress wpscan 登录wordpress 登录phpmyadmin 命令执行 反弹shell 提权 get user.txt 信息收集 本地提权 信息收集 1、arp ┌──(root㉿ru)-[~…

Python如何运用爬虫爬取京东商品评论

寻找数据真实接口 打开京东商品网址(添加链接描述) 查看商品评价。我们点击评论翻页,发现网址未发生变化,说明该网页是动态网页。 我们在浏览器右键点击“检查”,,随后点击“Network”,刷新一下,在搜索框中…

【实战系列----消息队列 数据缓存】rabbitmq 消息队列 搭建和应用

线上运行图,更新不算最新版,但可以使用修改线程等补丁功能,建议使用新版本。 远程服务器配置图: 这个可以更具体情况,因为是缓存队列理所当然 内存越大越好,至于核心4核以上足够使用。4核心一样跑 这里主要是需要配置服…

Linux下gcc的使用与程序的翻译

gcc和程序的翻译过程 gcc介绍程序的翻译过程预编译编译汇编链接 命令行式宏定义 gcc介绍 gcc是一款编译C语言编译器,可以把我们用vim写的代码编译成可执行程序。编译C用g进行编译,C的文件后缀是test.cc或test.cpp或test.cxx 如果要安装g就执行以下命令 …

QPL:一种新型的Text-to-SQL任务中间表示形式

导语 本文提出了一种叫做Query Plan Language (QPL)的语言&#xff0c;用来将复杂SQL语句分解为更加简单的子语句。QPL具有以下优势&#xff1a;1&#xff09;可以转述为简单问题&#xff0c;从而创建了一个 < 复杂问题&#xff0c;分解问题 > <复杂问题&#xff0c…

在Debian11安装Proxmox VE 7及设置GPU工作环境

我们在测试大模型&#xff0c;深度学习的过程中&#xff0c;可能遇到的情况就是不断的清空系统&#xff0c;避免老系统中安装的软件对测试系统造成影响&#xff0c;导致测试结果不准确或莫名报错。今天为小伙伴们介绍使用PVE7做成一个人工智能开发和测试的平台&#xff0c;你可…

【安装记录】Chrono Engine安装记录

本文仅用于个人安装记录。 官方安装教程 https://api.projectchrono.org/8.0.0/tutorial_install_chrono.html Windows下安装 windows下安装就按照教程好了。采用cmake-gui进行配置&#xff0c;建议首次安装只安装核心模块。然后依此configure下irrlicht&#xff0c;sensor…

maven代码规范检查(checkstyle、findbugs)

maven代码规范检查 前言一、使用checkstyle插件1. maven-checkstyle-plugin 介绍2. 接入方式3. 如何排除某个类、包下面的文件不进行检查使用suppressionsLocation 4. 如何关闭 二、使用findbugs插件1.findbugs-maven-plugin介绍2. 接入方式3. 如何排除某个类、包下面的文件不进…

ENSP路由器打不开,查看virtualBox,故障机是 AR_Base

AR_Base错误代码为&#xff1a;Raw-mode is unavailable courtesy of Hyper-V. (VERR_SUPDRV_NO_RAW_MODE_HYPER_V_ROOT). 参考virtualBox解决不能为虚拟电脑 AR_Base 打开一个新任务. Raw-mode is unavailable courtesy of Hyper-V. win11亲测有用。_不能为虚拟电脑ar_base打…

day37WEB攻防-通用漏洞XSS跨站权限维持钓鱼捆绑浏览器漏洞

目录 XSS-后台植入 Cookie&表单劫持&#xff08;权限维持&#xff09; 案例演示 XSS-Flash 钓鱼配合 MSF 捆绑上线 1、生成后门 2、下载官方文件-保证安装正常 3、压缩捆绑文件-解压提取运行 4、MSF 配置监听状态 5、诱使受害者访问 URL-语言要适当 XSS-浏览器网马…

Redis核心技术与实战【学习笔记】 - 6.Redis 的统计操作处理

1.前言 在 Web 业务场景中&#xff0c;我们经常保存这样一种信息&#xff1a;一个 key 对应了一个数据集合。比如&#xff1a; 手机 APP 中的每天用户登录信息&#xff1a;一天对应一系列用户 ID。电商网站上商品的用户评论列表&#xff1a;一个商品对应了一些列的评论。用户…