LINUX系统下文件的操作


一、文件的创建和读写

标准级文件的输入输出函数(fopen,fread,fwrite)文件的基本操作 -163博客 

系统级的文件操作实际上是为标准级地文件操作服务的。

1、 open ——打开一个文件进行读写操作(参照linux_open——CSDNblog

1>  函数原型:

#include<fcntl.h>

#include<unistd.h>

#include<sys/types.h>

#include<sys/stat.h>

 

int open(const char *pathname,int flags);

int open(const char *pathname,int flags,mode_tmode)

2>  参数含义

pathname:是我们要打开的文件名(包含路径名和文件名;若省略路径名,则缺省认为在当前路径下面)

flag:可以是下面一个值或者几个值的组合

O_RDONLY :以只读方式打开。

O_WRONLY :以只写方式打开。

O_RDWR :以可读可写方式打开。

以上三种旗标是互斥的,不能同时使用。

*******************************************

以下旗标可以利用OR(|)运算符组合。

O_CREAT :若欲打开的文件不存在,则自动建立该文件。

O_EXCL  :如果同时使用了O_CREAT,则此指令会去检查文件是否存在。文件若不存在则建立该文件;若文件存在,则会导致打开错误。

此外,若O_CREATO_EXCL同时设置,并且欲打开的文件为符号连接,则会打开文件失败。

O_NOCITY:如果欲打开的文件为终端机设备时,则不会将该终端机当成进程控制终端机。

O_TRUNC :若文件存在并且以可写的方式打开时,此旗标会令文件长度清为0,而原来存于该文件的资料也会消失。

O_APPEND:当读写文件时会从文件尾开始移动,也就是所写入的数据会以附加的方式加入到文件后面。

O_NOBELOCK:以不可阻断的方式打开文件,也就是无论有无数据读取或等待,都会立即返回进程之中。

O_NDELAY :同O_NONBLOCK

O_SYNC :以同步的方式打开文件。

O_NOFOLLOW :如果参数pathname 所指的文件为一符号连接,则会令打开文件失败。

O_DIRECTORY :如果参数pathname 所指的文件并非为一目录,则会令打开文件失败。

mode:如果使用了O_CREAT,则需要指定mode标志,用来表示文件的访问权限。Mode可以是以下情况的组合:

S_IRUSR( S_IREAD):代表该文件所有者具有可读取的权限。

S_IWUSR (S_IWRITE):代表该文件所有者具有可读取的权限。

S_IXUSR (S_IEXEC):代表该文件所有者具有可写入的权限。

S_IRWXU :代表该文件所有者具有可读、可写及可执行的权限。

-----------------------------------------------

S_IRGRP:代表该文件用户组具有可读的权限。

S_IWGRP:代表该文件用户组具有可写入的权限。

S_IXGRP:代表该文件用户组具有可执行的权限。

S_IRWXG:代表该文件用户组具有可读、可写及可执行的权限。

-----------------------------------------------------------------------

S_IROTH:代表其他用户具有可读的权限。

S_IWOTH:代表其他用户具有可写入的权限。

S_IXOTH:代表其他用户具有可执行的权限。

S_IRWXO:代表其他用户具有可读、可写及可执行的权限。

--------------------------------------------------------------------------

S_ISUID :设置用户执行ID 

S_ISGID :设置 组的执行ID

也可以用5个数字来表示文件地各种权限。

设置用户ID

设置组ID

用户权限

组权限

其他人权限

每个位上的数字可以取以下值的和:

1:执行权限

2:写权限

4:读权限

0:什么也没有

例如:10750可以解释如下:设置用户ID,没有组ID,用户有读写执行权限,组有读和执行权限,其他人没有权限。

3>  返回值

如果打开文件成功,会返回一个文件描述符;失败则返回-1。

2、 close —— 关闭一个已经打开的文件

1> 函数原型

#include<fcntl.h>

#include<unistd.h>

#include<sys/types.h>

#include<sys/stat.h>

int close(fd);

2> 参数意义

Fd:open 函数返回的文件描述符

3> 返回值

成功则返回0;否则返回-1,errno返回具体的出错信息。

3、 read函数

1>  函数原型

#include<unistd.h>

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

2>  参数意义

fd :待读入的文件描述符。

buffer :读入内容存放的缓冲区的内存地址。

count :要读的字节数。

3>  返回值

注意返回值类型是ssize_t,表示有符号的size_t,这样既可以返回正的字节数、0(表示到达文件末尾)也可以返回负值-1(表示出错)。

正常读入的情况下,函数将读到的内容存入缓存区buffer,并返回实际读取得字节数count。

如果遇到信号中断,而且没有读取数据,函数会返回-1,并设置errno为EINTR。

如果在调用read函数之前就已经到文件结尾,函数会返回0。

4、 write函数

1>  函数原型

#include<unistd.h>

ssize_t write(int fd,void *buffer,size_t count);

2>  参数意义

fd :待写入文件的描述符。

buffer :待写入内容所在的内存缓冲区地址。

count :每次写入的字节数。

3>  返回值

成功返回写入的字节数;出错返回-1并设置errno写常规文件时,write的返回值通常等于请求写的字节数

5、 阻塞读终端和非阻塞读终端

读常规文件是不会阻塞的,不管读多少字节,read一定会在有限的时间内返回。从终端设备或网络读则不一定。

如果从终端输入的数据没有换行符,调用read读终端设备就会阻塞。

如果网络上没有接收到数据包,调用read从网络读就会阻塞,至于会阻塞多长时间也是不确定的,如果一直没有数据到达就一直阻塞在那里。

同样,写常规文件是不会阻塞的,面向终端设备或网络写则不一定。

 

阻塞(Block)的概念

当进程调用一个阻塞的系统函数时,该进程被置于睡眠(Sleep)状态,这时内核调度其它进程运行,直到该进程等待的事件发生了(比如网络上接收到数据包,或者调用sleep指定的睡眠时间到了)它才有可能继续运行。

与睡眠状态相对的是运行(Running)状态,在Linux内核中,处于运行状态的进程分为两种情况:

1正在被调度执行

CPU处于该进程的上下文环境中,程序计数器(eip)里保存着该进程的指令地址,通用寄存器里保存着该进程运算过程的中间结果,正在执行该进程的指令,正在读写该进程的地址空间。

2就绪状态

该进程不需要等待什么事件发生,随时都可以执行,但CPU暂时还在执行另一个进程,所以该进程在一个就绪队列中等待被内核调度。

系统中可能同时有多个就绪的进程,那么该调度谁执行呢?内核的调度算法是基于优先级和时间片的,而且会根据每个进程的运行情况动态调整它的优先级和时间片,让每个进程都能比较公平地得到机会执行,同时要兼顾用户体验,不能让和用户交互的进程响应太慢。

 

如果在open一个设备时指定了O_NONBLOCK标志,read/write就不会阻塞。以read为例,如果设备暂时没有数据可读就返回-1,同时置errno为EWOULDBLOCK(或者EAGAIN,这两个宏定义的值相同),表示本来应该阻塞在这里(wouldblock,虚拟语气),事实上并没有阻塞而是直接返回错误,调用者应该试着再读一次(again)。这种行为方式称为轮询(Poll),调用者只是查询一下,而不是阻塞在这里死等,这样可以同时监视多个设备:

while(1) {

非阻塞read(设备1);

if(设备1有数据到达)

  处理数据;

非阻塞read(设备2);

if(设备2有数据到达)

  处理数据;

...

}

非阻塞I/O的优点:

如果read(设备1)是阻塞的,那么只要设备1没有数据到达就会一直阻塞在设备1的read调用上,即使设备2有数据到达也不能处理,使用非阻塞I/O就可以避免设备2得不到及时处理。

非阻塞I/O的缺点:

如果所有设备都一直没有数据到达,调用者需要反复查询做无用功。

如果阻塞在那里,操作系统还可以调度别的进程执行,就不会做无用功了。

在使用非阻塞I/O时,通常不会在一个while循环中一直不停地查询(这称为Tight Loop),而是每延迟等待一会儿来查询一下,以免做太多无用功,在延迟等待的时候可以调度其它进程执行。

while(1) {

   非阻塞read(设备1);

   if(设备1有数据到达)    处理数据;

   非阻塞read(设备2);

   if(设备2有数据到达)    处理数据;

   ...   sleep(n);

  }

 

以下是一个非阻塞I/O的例子。目前我们学过的可能引起阻塞的设备只有终端,所以我们用终端来做这个实验。程序开始执行时在0、1、2文件描述符上自动打开的文件就是终端,但是没有O_NONBLOCK标志。所以就像例 28.2“阻塞读终端”一样,读标准输入是阻塞的。我们可以重新打开一遍设备文件/dev/tty(表示当前终端),在打开时指定

O_NONBLOCK标志。

 

6、 例子

例1.6.1 读写文件的例子

#include <unistd.h>

#include <fcntl.h>

#include <stdio.h>

#include <sys/types.h>

#include <sys/stat.h>

#include <errno.h>

#include <string.h>

#include <stdlib.h>

 

#define BUFFER_SIZE 1024

 

int main(int argc,char **argv)

{

       intfrom_fd,to_fd;

       intbytes_read,bytes_write;

       charbuffer[BUFFER_SIZE];

       char*ptr;

       if(argc!=3)

       {

              fprintf(stderr,"Usage:%sfromfile,tofile\n\a",argv[0]);

              exit(1);

       }

       /*openthe source file*/

       if((from_fd= open(argv[1],O_RDONLY))==-1)

       {

              fprintf(stderr,"Open%s ERROR: %s\n",argv[1],strerror(errno));

              exit(1);

       }

       /*createthe destinate file*/

       if((to_fd= open(argv[2],O_WRONLY|O_CREAT,S_IRUSR|S_IWUSR))==-1)

       {

              fprintf(stderr,"Open%s ERROR: %s\n",argv[2],strerror(errno));

              exit(1);

       }

       /**/

       while(bytes_read= read(from_fd,buffer,BUFFER_SIZE))

       {

              /*afetal error occured,not because of a signal interrupt*/

              if((bytes_read== -1)&&(errno!=EINTR))/**/

                     break;

              /*readfile success*/

              elseif(bytes_read>0)

              {

                     ptr= buffer;

                     while(bytes_write= write(to_fd,ptr,bytes_read))

/*allthe read-contents is writed at one time*/

                     {

                            /*afetal error*/

                            if((bytes_write== -1)&&(errno!=EINTR))

                                   break;

                            /*writesuccessfully*/

                            elseif(bytes_write == bytes_read)

                                   break;

                            /*notwrite all*/

                            elseif(bytes_write > 0)

                            {

                                   ptr+= bytes_write;

                                   bytes_read-= bytes_write;

                            }

                            /*interruptedby signal*/

                            if(bytes_write== -1)

                                   break;

                     }

              }

       }

       close(from_fd);

       close(to_fd);

       exit(0);

}

运行结果:


from.txt内的内容被复制到to.txt中。

例1.6.2阻塞读终端

#include <unistd.h>

#include <stdlib.h>

int main(void)

{

       charbuf[10];

       int n;

       n =read(STDIN_FILENO,buf,10);

       if(n<0)

       {

              perror("readSTDIN_FILENO");

              exit(1);

       }

       write(STDOUT_FILENO,buf,n);

       return0;

}

运行结果如下:


第一次执行test时输出正常,第二次执行时结果不正常。分析原因如下linux read() 函数

Shell进程创建test进程,test进程开始执行,而Shell进程睡眠等待test进程退出。

test调用read时睡眠等待,直到终端设备输入了换行符才从read返回,read只读走10个字符,剩下的字符仍然保存在内核的终端设备输入缓冲区中。

test进程打印并退出,这时Shell进程恢复运行,Shell继续从终端读取用户输入的命令,于是读走了终端设备输入缓冲区中剩下的字符d和换行符,把它当成一条命令解释执行,结果发现执行不了,没有d这个命令。

在输入完”./test”回车后,进程处于等待状态等待用户输入需要读取的字符,这种方式叫做阻塞读终端。

例1.6.3非阻塞读终

#include <unistd.h>

#include <fcntl.h>

#include <errno.h>

#include <string.h>

#include <stdlib.h>

 

#define MSG_TRY "try again\n"

 

int main(void)

{

       charbuf[10];

       intfd,n;

       fd =open("/dev/tty",O_RDONLY|O_NONBLOCK);

       if(fd< 0)

       {

              perror("open/dev/tty error!");

              exit(1);

       }

       tryagain:

       n =read(fd, buf, 10);

       if(n< 0)

       {

              if(errno== EAGAIN)

              {

                     sleep(5);

                     write(STDOUT_FILENO,MSG_TRY,strlen(MSG_TRY));

                     gototryagain;

              }

              perror("read/dev/tty error!");

              exit(1);

       }

       write(STDOUT_FILENO,buf, n);

       close(fd);

       return0;

}

运行结果如下:

例1.6.4非阻塞读终端和等待超时

#include <unistd.h>

#include <fcntl.h>

#include <errno.h>

#include <string.h>

#include <stdlib.h>

 

#define MSG_TRY "try again\n"

#define MSG_TIMEOUT "timeout\n"

int main(void)

{

       charbuf[10];

       intfd,n,i;

       fd =open("/dev/tty",O_RDONLY|O_NONBLOCK);

       if(fd< 0)

       {

              perror("open/dev/tty");

              exit(1);

       }

       for(i=0;i<5; i++)

       {

              sleep(5);

              n= read(fd, buf, 10);

              if(n>=0)

                     break;

              if(errno!= EAGAIN)

              {

                     perror("read/dev/tty error!");

                     exit(1);

              }

             

              write(STDOUT_FILENO,MSG_TRY,strlen(MSG_TRY));

       }

       if(i ==5)

              write(STDOUT_FILENO,MSG_TIMEOUT,strlen(MSG_TIMEOUT));

       else

       write(STDOUT_FILENO,buf, n);

       close(fd);

       return0;

}

运行结果如下:


二、文件的各个属性

文件具有各种各样的属性,除了上面我们知道的文件权限以外,还具有创建时间,大小等性质。以下介绍几种读取文件属性的函数

1、 access函数

1>  函数原型

#include <unistd.h>

int access(const char *pathname,int mode);//判断文件是否可以进行某种操作(读、写等)

2>  形式参数

pathname :文件名称,可以包含文件的路径。

mode :我们要判断的属性,可以是以下值及其它们的组合

R_OK :文件是否可读。

W_OK :文件是否可写。

X_OK :文件是否可。

F_OK :文件是否存在。

3>  返回值

测试成功返回0;否则有一个条件不符时,返回-1。

2、 stat或者fstat

1>  函数原型

#include <sys/stat.h>

#include <unistd.h>

int stat(const char *file_name,struct stat *buf);//用来判断没有打开的文件的属性

int fstat(int filedes,struct stat *buf);//用来判断已经打开的文件

2>  参数意义

file_name :文件名称。

buf :文件信息存储的结构体

struct stat{

       dev_tst_dev; /* 设备 */

ino_t st_ino; /* 节点 */

mode_t st_mode;/* 模式 */

nlink_t st_nlink;/* 硬连接 */

uid_t st_uid; /* 用户ID */

gid_t st_gid; /* 组ID */

dev_t st_rdev; /*设备类型 */

off_t st_off; /* 文件字节数 */

unsigned long st_blksize;/* 块大小 */

unsigned longst_blocks; /* 块数 */

time_t st_atime;/* 最后一次访问时间 */

time_t st_mtime;/* 最后一次修改时间 */

time_t st_ctime;/* 最后一次改变时间(指属性) */

};

其中是用最多的属性是st_mode,通过此属性我们可以判断给定的文件是一个普通文件还是一个目录或者连接等等,通过以下几个宏来判断:

S_ISLNK(st_mode):是否是一个链接        S_ISREG(st_mode):是否是常规文件 S_ISDIR(st_mode):是否是目录              S_ISCHR(st_mode):是否是一个字符设备S_ISBLK(st_mode):是否是一个块设备        S_ISFIFO(st_mode):是否是FIFO文件  S_ISSOCK (st_mode):是否是一个SOCKET文件

3>  返回值

读取属性成功返回0;读取不成功时,返回-1。

三、目录文件的操作

1、getcwd获取当前路径

1>  函数原型

#include <unistd.h>

char *getcwd(char *buffer,size_t size);

2>  参数意义

buffer :我们定义的用来存储路径的缓冲区的内存地址。

size :我们定义的缓冲区buffer的大小。

3>  返回值

如果buffer太小,会返回-1和一个错误号。

2、其他一些常用的目录函数

#include <dirent.h>

#include <unistd.h>

#include <fcntl.h>

#include <sys/types.h>

#include <sys/stat.h>

 

int mkdir(const char *path,mode_t mode);//创建一个目录

DIR *opendir(const char* path);//打开一个目录,为之后的读作准备

struct dirent *readdir(DIR *dir);//读一个打开的目录

void rewinddir(DIR *dir);//重读目录

off_t telldir(DIR *dir);//类似ftee

void seekdir(DIR *dir,off_t off);//类似fseek

int closedir(DIR *dir);//关闭一个目录

3、 例子

例2.3.1

这个程序有一个参数,如果这个参数是一个文件名,我们输出这个文件的大小和最后修改的时间,如果是一个目录我们输出这个目录下所有文件的大小和

修改时间。

#include <unistd.h>

#include <stdio.h>

#include <errno.h>

#include <sys/types.h>

#include <sys/stat.h>

#include <dirent.h>

#include <time.h>

#include <stdlib.h>

 

static int get_file_size_time(const char *filename)

{

       structstat statbuf;

       if(stat(filename,&statbuf)== -1)

       {

              printf("Getstat on %s Error: %s\n",filename,strerror(errno));

              return(-1);          

       }

       if(S_ISDIR(statbuf.st_mode))

              return(1);

       if(S_ISREG(statbuf.st_mode))

       printf("%ssize %ld bytes\tmodified at%s",filename,statbuf.st_size,ctime(&statbuf.st_mtime));

       return(0);

}

int main(int argc,char *argv[])                             

{

       DIR*dirp;

       structdirent *direntp;

       intstats;

       if(argc!=2)//inputthe filename or directory

       {

              printf("Usage:%sfilename\n\a",argv[0]);

              exit(1);

       }

       if(((stats= get_file_size_time(argv[1])) == 0)||(stats == -1))//if it is a regular file

              exit(1);

       if((dirp= opendir(argv[1])) == NULL)//if it is a directory

       {

              printf("OpenDirectory %s Error:%s\n",

                     argv[1],strerror(errno));

              exit(1);

       }

       while((direntp= readdir(dirp))!=NULL)//access every file under the directory

       {

              if(get_file_size_time(direntp-> d_name) == -1)

                     break;

       }

       closedir(dirp);

       exit(1);

}

运行结果如下 :


四、管道文件

inux 管道-CSDN博客

有名管道和无名管道的区别-163博客

有名管道&无名管道-CSDN博客

1、 管道的概念

管道是进程间通信的主要手段之一。一个管道实际上就是个只存在于内存中的文件,对这个文件的操作要通过两个已经打开文件进行,它们分别代表管道的两端。管道是一种特殊的文件,它不属于某一种文件系统,而是一种独立的文件系统,有其自己的数据结构。根据管道的适用范围将其分为:无名管道和命名管道。

1> 无名管道

主要用于父进程与子进程之间,或者两个兄弟进程之间。通常管道指的是无名管道。

在linux系统中可以通过系统调用建立起一个单向的通信管道,且这种关系只能由父进程来建立。

因此,每个管道都是单向的,当需要双向通信时就需要建立起两个管道。管道两端的进程均将该管道看做一个文件,一个进程负责往管道中写内容,而另一个从管道中读取。这种传输遵循“先入先出”(FIFO)的规则。

2> 命名管道(named pipe或者FIFO)

 可以用函数mkfifo()创建。

命名管道是为了解决无名管道只能用于近亲进程之间通信的缺陷而设计的。

命名管道是建立在实际的磁盘介质或文件系统(而不是只存在于内存中)上有自己名字的文件,任何进程可以在任何时间通过文件名或路径名与该文件建立联系。为了实现命名管道,引入了一种新的文件类型——FIFO文件(遵循先进先出的原则)。实现一个命名管道实际上就是实现一个FIFO文件。命名管道一旦建立,之后它的读、写以及关闭操作都与普通管道完全相同。

虽然FIFO文件的inode节点在磁盘上,但是仅是一个节点而已,文件的数据还是存在于内存缓冲页面中,和普通管道相同。

3> 环形缓冲区

从本质上说,管道也是一种文件,但它又和一般的文件有所不同,管道可以克服使用文件进行通信的两个问题,具体表现为:

1 限制管道的大小。实际上,管道是一个固定大小的缓冲区。在Linux中,该缓冲区的大小为1页,即4K字节,使得它的大小不象文件那样不加检验地增长。使用单个固定缓冲区也会带来问题,比如在写管道时可能变满,当这种情况发生时,随后对管道的write()调用将默认地被阻塞,等待某些数据被读取,以便腾出足够的空间供write()调用写。

2 读取进程也可能工作得比写进程快。当所有当前进程数据已被读取时,管道变空。当这种情况发生时,一个随后的read()调用将默认地被阻塞,等待某些数据被写入,这解决了read()调用返回文件结束的问题。

注意:从管道读数据是一次性操作,数据一旦被读,它就从管道中被抛弃,释放空间以便写更多的数据。


2、 pipe 管道创建函数

1>  函数原型

#include <unistd.h>

int pipe(int fields[2]);

2>  参数意义

函数调用成功时,文件描述符fildes[0]是用来读的文件描述符,fildes[1]是用来写的文件描述符。


3、 dup2---重定向操作

1> 函数原型

#include <unistd.h>

int dup2(int oldfd,int newfd);

2> 参数意义

用oldfd 文件描述符来代替newfd 文件描述符,同时关闭newfd 文件描述符。也就是说,所有向newfd 操作都转到oldfd 上面

4、例子

例4.3.1

#include<stdio.h>

#include<stdlib.h>

#include<unistd.h>

#include<string.h>

#include<errno.h>

#include<sys/types.h>

#include<sys/wait.h>

 

#define BUFFER 255

 

int main(int argc,char **argv)

{

       charbuffer[BUFFER+1];

       intfd[2];

       if(argc!=2)//Inputtest and the string

       {

              fprintf(stderr,"Usage:%sstring \n\a",argv[0]);

              exit(1);

       }

       if(pipe(fd)!=0)//pipefailed

       {

              fprintf(stderr,"PipeError:%s\n\a",strerror(errno));

              exit(1);

       }

       if(fork()== 0)//child process - write

       {

              close(fd[0]);

              printf("Child[%d]Write to pipe\n\a",getpid());

              snprintf(buffer,BUFFER,"%s",argv[1]);

              write(fd[1],buffer,strlen(buffer));

              printf("Child[%d]Quit\n\a",getpid());

              exit(0);

       }

       else//parentprocess - read

       {

              close(fd[1]);

              printf("Parent[%d] Read from pipe\n\a",getpid());

              memset(buffer,'\0',BUFFER+1);

              read(fd[0],buffer,BUFFER);

              printf("Parent[%d]Read: %s\n",getpid(),buffer);

              exit(1);

       }

       return0;

}

运行结果如下:


例4.3.2

#include<stdio.h>

#include<unistd.h>

#include<errno.h>

#include<fcntl.h>

#include<string.h>

#include<sys/types.h>

#include<sys/stat.h>

#include<stdlib.h>

 

#define BUFFER_SIZE 1024

 

int main(int argc,char **argv)

{

       int fd;

       charbuffer[BUFFER_SIZE];

       if(argc!=2)

       {

              fprintf(stderr,"Usage:%soutfilename\n\a",argv[0]);

              exit(1);

       }

       if(fd =open(argv[1],O_WRONLY|O_CREAT|O_TRUNC,S_IRUSR|S_IWUSR)==-1)

       {

              fprintf(stderr,"Open%s Error: %s\n\a",argv[1],strerror(errno));

              exit(1);

       }

       if(dup2(fd,STDOUT_FILENO)==-1)

       {

              fprintf(stderr,"RedirectStandard Out Error:%s\n\a",strerror(errno));

              exit(1);

       }

       fprintf(stderr,"Now,pleaseinput string");

       fprintf(stderr,"(Toquit use CTRL+D)\n");

       while(1)

       {

              fgets(buffer,BUFFER_SIZE,stdin);

              if(feof(stdin))break;

              write(STDOUT_FILENO,buffer,strlen(buffer));

       }

       exit(0);

}

输出结果如下所示:

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值