io基础知识

IO

标准IO: C库函数提供的文件操作统一接口,携带缓冲;

文件IO:最直观的系统调用,每一次系统调用操作系统都会参与其中,开销较大,不携带缓冲;

1.缓冲区类型

全缓冲:把缓冲区占满后或特殊情况下才会刷新缓冲区;

行缓冲:缓冲区中遇到换行符就会刷新缓冲区;

无缓冲:任何数据进入缓冲区后马上刷新 strerr

setvbuf

int setvbuf(FILE *stream, char *buf, int mode, size_t size);
功能:设置某个流的缓冲区类型;
返回值:成功返回0,失败返回非0;
stream:要更改的流;
buf:要指定的缓冲区地址,对于无缓冲无效,可以写NULL;
mode:要更改的缓冲区类型
    ——IOFBF:全缓冲;
    ——IOLBF:行缓冲;
    ——IONBF:无缓冲;
size:指定的缓冲区大小

2.库函数接口

2.1 打开文件

FILE *fopen (const char *path, const char *mode);
功能:打开或创建后再打开文件;
返回值:成功返回一个流指针,失败返回NULL
path:要打开的文件(可以包含路径);
mode:打开文件的方式;
    r:以只读的方式打开;
    r+:以读写的方式打开;   //前两个一定要文件已存在
    w:以只写的方式打开一个文件,文件不存在则创建,文件存在则清空;
    w+:以读写的方式打开一个文件,文件不存在则创建,文件存在则清空;
    a:以只写的方式打开一个文件,文件不存在则创建,文件存在则内容会写在末尾;
    a+:以读写的方式打开一个文件,文件不存在则创建,文件存在则内容会写在末尾;
r+b
a+b
wb
注:加上b表示打开的是二进制文件,但是Linux不区分文本流和二进制流

使用方式:

#include <stdio.h>
int main(int argc, char *argv[])
{
    FILE *fp = fopen("要打开的文件""打开文件的方式");
    if(fp == NULL){
        perror("fopen");
        return -1;
    }
    printf("Fopen success, fp = %p\n", fp);
    return 0;
}

2.2重定向

FILE *freopen(const char *pathname, const char *mode, FILE *stream);
功能:通过打开一个文件产生的流去替代原来的流;
返回值:成功返回一个流指针,失败返回NULL;
pathname:指定要打开的文件;
mode:打开文件的方式;
stream:要替换的流(原来的流);

使用方式:

#include <stdio.h>
int main(int argc, char *argv[])
{
    printf("Hi! I'm LCW.\n");
    FILE *fp = freopen("要打开的文件""打开的方式",stdout);
    if(fp == NULL){
        perror("freopen");
        return -1;
    }
    printf("Freopen success, fp = %p\n", fp);
    //打开重定向的文件,可以看到“Hi! I'm LCW.”写入了文件中;
    return 0;
}

2.3关闭流

int fclose(FILE *stream);
功能:先刷新流的内容再关掉这个流;
返回值:成功返回0,失败返回-1;
stream:要关掉的流;

使用方式:

#include <stdio.h>
int main(int argc, char *argv[])
{
	FILE *fp = fopen("xxx", "r");
	if(fp == NULL){
		perror("fopen");
		return -1;
	}
     printf("Fopen success, fp = %p\n", fp);
	int ret;
	ret = fclose(fp);
	if(ret < 0){
		perror("fclose");
		return -1;
	}
    printf("Fclose success!\n");
	return 0;
}

3.读写流

3.1按字节读写
3.1.1读
int fgetc(FILE *stream);
功能:从流里面读取一个字符的内容;
返回值:成功返回实际读到的字符ASC码,失败返回EOF;
stream:要读的流;

使用方式:

#include <stdio.h>
int main(int argc, char *argv[])
{
	FILE *fp = fopen("要打开的文件", "r");
    if(fp == NULL)
    {
        perror("fopen");
        return -1;
    }
	char a = fgetc(fp);              //读取流中的第一个字符
	//相当于char a = getchar();
	if(a < 0){
		perror("fgetc");
		return -1;
	}
	printf("%d\n", a);
    return 0;
} 

3.1.2写
int fputc(int c, FILE *stream);
功能:往流里面写入一个字符的内容;
返回值:成功返回实际写入的字符ASC码,失败返回EOF;
stream:要写的流;

使用方式:

#include <stdio.h>
int main(int argc, char *argv[])
{
	FILE *fp = fopen("xxx", "a+");
	if(fp == NULL){
		perror("fopen");
		return -1;
	}
	int a = 97;
	int ret = fputc(a, fp);
	if(ret < 0){
		perror("fputc");
		return -1;
	}
    return 0;
}
3.2练习

编写一个文件mycp.c,执行它能完成cp的功能,及将1.c的内容复制到2.c当中;

#include <stdio.h>
int main(int argc, char *argv[])
{
	if(argc != 3){
		printf("U need input %s <src_file> <dest_file>\n", argv[0]);
		return -1;
	}
	FILE *fp_src = fopen(argv[1], "r");
	if(fp_src == NULL){
		perror("fopen src");
		return -1;
	}
	FILE *fp_dest = fopen(argv[2], "w");
	if(fp_dest == NULL){
		perror("fopen dest");
		return -1;
	}
    char ch = -1;
	while( (ch = fgetc(fp_src)) != EOF ){
		fputc(ch, fp_dest);
	}
    fclose(fp_src);
    fclose(fp_dest);
    return 0;
}

4.标准 IO

4.1按行读写

4.1.1读
char *fgets(char *s, int size, FILE *stream);
功能:读取一行(它想读取一行);
返回值:成功返回字符串存放的地址<s>,失败返回NULL;
s:用于存放读取到的数据的内存空间地址;
size:预计想读多少个字节的内容,不应该超过s的大小;
stream:要读取的流;
注:fgets只会读取size-1个字节,如果读到了换行那么换行会进入到缓冲区并在最后添加一个'\0',如果没有读到换行,则直接在最后添加'\0'

使用方式:

#include <stdio.h>
int main(int argc, char *argv[])
{
	char buf[10];                                  //设置缓冲区
	char *p = fgets(buf, sizeof(buf), stdin);      //从终端输入中读取
	if(p == NULL){
		perror("fgets");
		return -1;
	}
	printf("p = %s", buf);                         //把读取的字符打印出来
}
#include <stdio.h>
int main(int argc, char *argv[])
{
	FILE *fp = fopen("test", "r");
    char buf[64] = {1,2,3,4,5,6,7,8};
    fgets(buf, sizeof(buf), fp);
    int i;
    for(i = 0; i < 8; i++){
        printf("%d\n", buf[i]);
	}
}
4.1.2写
int fputs(const char *s, FILE *stream);
功能:往流中写入一行数据(它想写一行);
返回值:成功返回1,失败返回EOF;
s:要写入的内容存放的内存空间地址;
stream:要写入的流;

使用方式:

#include <stdio.h>
int main(int argc, char *argv[])
{
	char buf[] = {'o', 'k', '\n', 'a', 'b', '\0'};
	int ret = fputs(buf, stdout);                   //往终端中写入缓冲区中的字符  
	if(ret < 0){
		perror("fputs");
		return -1;
	}
}

4.2按对象读写

4.2.1读
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
功能:从流里面读取内容出来;
返回值:成功返回实际读到的对象数,失败返回0;
ptr:存放读取到的内容的内存空间地址;
size:每个对象所占字节数;
nmemb:预计要读多少个对象;
stream:要从哪个流中读取;

使用方式:

#include <stdio.h>
int main(int argc, char *argv[])
{
	FILE *fp = fopen("test", "r");                 //以只读的方式读取文件test
	if(fp == NULL){
        perror("fopen");
        return -1;
    }
	char buf[21] = {0};
	int ret = fread(buf, 7, 3, fp);                //从fp流中读取三个对象,每个对象中有7个字节,并存放到缓冲区buf中
	if(ret == 0){
		perror("fread");
		return -1;
	}
	printf("ret = %d, data = %s\n", ret, buf);
}
4.2.2写
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
功能:往流里面写入内容;
返回值:成功返回实际写入的对象数,失败返回0;
ptr:存放要写入的数据的内存空间地址;
size:每个对象所占字节大小;
nmemb:预计要写多少个对象;
stream:要往哪个流中写入;

4.3文件定位

int fseek(FILE *stream, long offset, int whence);
功能:改变文件内“光标”的位置;
返回值:成功返回0,失败返回-1;
stream:要改变的文件流;
offset:偏移量,正数往后偏移,负数往前偏移;
whence:基准点;
    SEEK_SET:文件头;
    SEEK_CUR:文件当前位置:
    SEEK_END:文件尾;

使用方式:

#include <stdio.h>
int main(int argc, char *argv[])
{
	FILE *fp = fopen("test", "r+");                   //以读写的方式打开test文件
	if(fp == NULL){
        perror("fopen");
        return -1;
    }
	char buf[] = {"hello world"};
	fwrite(buf, 1, sizeof(buf), fp);                  //往fp流中写入buf中的内容
	int ret = fseek(fp, -5, SEEK_CUR);                //光标从当前位置往前偏移五个位移量
	char buffer[] = {"kun"};
	fwrite(buffer, 1, sizeof(buffer), fp);            //往fp流中写入buffer中的内容
}

4.4练习

1:用标准IO计算文件中有多少行,并打印出来:

#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[])
{
	if(argc != 2){
        printf("文件数应该等于1")return -1;
    }
	FILE *fp = fopen(argv[1], "r");                     //以只读的方式打开所给文件
	if(fp == NULL){
		perror("fopen");
		return -1;
	}
	char buf[64] = {0};
	int  count = 0;
	char *p;
	while(1){
		p = fgets(buf, sizeof(buf), fp);                //循环往buf中写入fp流中的内容
		if(p == NULL)
			break;
		if(buf[strlen(buf)-1] == '\n')                  //计算行数
			count++;
	}
	printf("line = %d\n", count);     
}

2:用标准IO计算文件字节大小,并打印出来:

#include <stdio.h>
int main(int argc, char *argv[])
{
	if(argc != 2){
        printf("文件数应该等于1")return -1;
    }
	FILE *fp = fopen(argv[1], "r");
	if(fp == NULL){
		perror("fopen");
		return -1;
	}
	int size = 0;
	char buf[20];
	int ret;
	while(1){
		ret = fread(buf, 1, 20, fp);
		if(ret == 0)
			break;
		size += ret;
	}
	printf("size = %d\n", size);
}

5.文件 IO

5.1打开文件

int open(const char *pathname, int flags, mode_t mode);
头文件:#include <sys/types.h>
       #include <sys/stat.h>
       #include <fcntl.h>       //使用其中之一就可以
功能:打开文件或者创建并打开文件;
返回值:成功返回一个文件描述符,失败返回-1//文件描述符总是最小的、未使用的非负整数,文件描述符用于其他系统调用操作这个文件
pathname:要打开的文件名(包括路径);
flags:打开文件的方式;
    必须要有的{
    O_RDONLY;        //只读
    O_WRONLY;        //只写
    O_RDWR;          //读写
    }                //三个必须有一个,要使用其他需在man手册里面查
    O_APPEND;       //追加
    O_CREAT;        //文件不存在则创建
    O_EXCL;         //与O_CREAT一起使用时,如果文件存在则直接报错
    O_TRUNC;        //文件存在则删除
mode:如果写O_CREAT,那么mode有机会生效,表示创建文件时的权限0777 0664   //权限会用~umask和mode相与,最总的数值用于权限设置,即mode & ~umask ==> 文件真实的权限

使用方式:

#include <stdio.h>
#include <fcntl.h>
int main(int argc, char *argv[])
{
	int fd = open("xxxx", O_RDONLY | O_CREAT, 0777);
	if(fd < 0){
		perror("open");
		return -1;
	}
	printf("open success, fd = %d\n", fd);
}

5.2读文件

ssize_t read(int fd, void *buf, size_t count);
头文件:#include <unistd.h>
功能:从文件描述符表示的文件中读取内容;
返回值:成功返回实际读到的字节数,失败返回-1;
fd:要读的文件描述符;
buf:存放读取到的内容的内存空间地址;
count:预计要读的字节数(不能超过buf的大小);

5.3写文件

ssize_t write(int fd, const void *buf, size_t count);
头文件:#include <unistd.h>
功能:往文件描述符表示的文件中写入内容;
返回值:成功返回实际写入的字节数,失败返回-1;
fd:要写的文件描述符;
buf:存放写入到的内容的内存空间地址;
count:预计要写的字节数(不能超过buf的大小);

5.4关闭文件

int close(int fd);
头文件:#include <unistd.h>;
功能:关闭文件;
返回值:成功返回0,失败返回-1;
fd:要关闭的文件描述符;

6.时间结构体

time_t time(time_t *tloc);
头文件:#include <time.h>
功能:获取当前系统距离1970-01-01 00:00:00所过的秒数;
返回值:成功返回记录的秒数,失败返回-1;
tloc:如果为NULL则无意义,如果不为NULL,那么系统时间会存放在tloc所指向的空间里面;

转换时间格式:

char *ctime(const time_t *timep);
直接将time_t的秒数转换成当前系统的时间,格式固定;
struct tm *localtime(const time_t *timep);
功能:也是转换秒数;
返回值:成功返回转换后的结构体地址,失败NULLstruct tm {
               int tm_sec;    /* Seconds (0-60) */
               int tm_min;    /* Minutes (0-59) */
               int tm_hour;   /* Hours (0-23) */
               int tm_mday;   /* Day of the month (1-31) */
               int tm_mon;    /* Month (0-11) */
               int tm_year;   /* Year - 1900 */
               int tm_wday;   /* Day of the week (0-6, Sunday = 0) */
               int tm_yday;   /* Day in the year (0-365, 1 Jan = 0) */
               int tm_isdst;  /* Daylight saving time */
           };

使用方式:

#include <stdio.h>
#include <time.h>
int main(int argc, char *argv[])
{
	//time_t tm = time(NULL);
	time_t tm;
	time(&tm);
    printf("%s\n", ctime(&tm));              //以函数自有的格式显示时间
	struct tm *4tp;
	tp = localtime(&tm);
	char buf[64] = {0};
	sprintf(buf, "%d-%02d-%02d %2d:%2d:%2d", tp->tm_year+1900,tp->tm_mon+1, tp->tm_mday, tp->tm_hourtp->tm_min, tp->tm_sec);                                    //年要加1900,月要加1
	printf("data : %s\n", buf);
}

练习

以每秒一次的方式,将时间写入文件当中:

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <time.h>
#include <string.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
	int fd = open("log.txt", O_CREAT | O_RDWR, 0777);
	if(fd < 0){
		perror("open");
		return -1;
	}
	char buf[64];
	time_t tim;
	while(1){
		time(&tim);
		struct tm *tp = localtime(&tim);
		memset(buf, 0, sizeof(buf));        //清空垃圾字符
		sprintf(buf, "%d-%02d-%02d  %02d:%02d:%02d\n", tp->tm_year+1900,tp->tm_mon+1, tp->tm_mday, tp->tm_hour,tp->tm_min,tp->tm_sec);
		int len = strlen(buf);
		write(fd, buf, len);
		sleep(1);
	}
}

7.文件夹操作

7.1打开目录

DIR *opendir(const char *name);
头文件:#include <sys/types.h>
       #include <dirent.h>       //使用其中之一就可以
功能:站在内核的角度打开一个文件夹;
返回值:成功返回目录的流指针,失败返回NULL;
name:目录名;

7.2读目录

struct dirent *readdir(DIR *dirp);
头文件:#include <dirent.h>
功能:读一次目录里的文件;
返回值:成功返回某个文件的结构体指针,失败返回NULLstruct dirent {
               ino_t          d_ino;       /* Inode number */
               off_t          d_off;       /* Not an offset; see below */
               unsigned short d_reclen;    /* Length of this record */
               unsigned char  d_type;      /* Type of file; not supported
                                              by all filesystem types */
               char           d_name[256]; /* Null-terminated filename */
              };
dirp:目录流;

7.3练习

写一个能实习ls的程序:

#include <stdio.h>
#include <dirent.h>
int main(int argc, char *argv[])
{
	DIR *dir = opendir(".");
	if(dir == NULL){
		perror("opendir");
		return -1;
	}
	struct dirent *dirp;
	while(1){
		dirp = readdir(dir);
		if(dirp == NULL)
			break;
		printf("%s\t", dirp->d_name);
	}
	puts("");
}

8.文件属性

int stat(const char *pathname, struct stat *statbuf);
头文件:#include <sys/types.h>
       #include <sys/stat.h>
       #include <unistd.h>               
功能:获取文件属性;
返回值:成功返回0,失败返回-1struct stat {
               dev_t     st_dev;         /* ID of device containing file */
               ino_t     st_ino;         /* Inode number */
               mode_t    st_mode;        /* File type and mode */
               nlink_t   st_nlink;       /* Number of hard links */
               uid_t     st_uid;         /* User ID of owner */
               gid_t     st_gid;         /* Group ID of owner */
               dev_t     st_rdev;        /* Device ID (if special file) */
               off_t     st_size;        /* Total size, in bytes */
               blksize_t st_blksize;     /* Block size for filesystem I/O */
               blkcnt_t  st_blocks;      /* Number of 512B blocks allocated */

               /* Since Linux 2.6, the kernel supports nanosecond
                  precision for the following timestamp fields.
                  For the details before Linux 2.6, see NOTES. */

               struct timespec st_atim;  /* Time of last access */
               struct timespec st_mtim;  /* Time of last modification */
               struct timespec st_ctim;  /* Time of last status change */

           #define st_atime st_atim.tv_sec      /* Backward compatibility */
           #define st_mtime st_mtim.tv_sec
           #define st_ctime st_ctim.tv_sec
           };

文件类型:

S_IFMT     0170000   bit mask for the file type bit field

S_IFSOCK   0140000   socket
S_IFLNK    0120000   symbolic link
S_IFREG    0100000   regular file
S_IFBLK    0060000   block device
S_IFDIR    0040000   directory
S_IFCHR    0020000   character device
S_IFIFO    0010000   FIFO
函数:
S_ISREG(m)  is it a regular file?
S_ISDIR(m)  directory?
S_ISCHR(m)  character device?
S_ISBLK(m)  block device?
S_ISFIFO(m) FIFO (named pipe)?
S_ISLNK(m)  symbolic link?  (Not in POSIX.1-1996.)
S_ISSOCK(m) socket?  (Not in POSIX.1-1996.)

文件权限:

S_ISUID     04000   set-user-ID bit (see execve(2))
S_ISGID     02000   set-group-ID bit (see below)
S_ISVTX     01000   sticky bit (see below)

S_IRWXU     00700   owner has read, write, and execute permission
S_IRUSR     00400   owner has read permission
S_IWUSR     00200   owner has write permission
S_IXUSR     00100   owner has execute permission

S_IRWXG     00070   group has read, write, and execute permission
S_IRGRP     00040   group has read permission
S_IWGRP     00020   group has write permission
S_IXGRP     00010   group has execute permission

S_IRWXO     00007   others  (not  in group) have read, write, and execute permission
S_IROTH     00004   others have read permission
S_IWOTH     00002   others have write permission
S_IXOTH     00001   others have execute permission

使用方式::

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h> 
#include <fcntl.h>
int main(int argc, char *argv[])
{
    struct stat buf;
    int ret = stat("888",&buf);
    if(ret < 0){
        perror("stat");
        return -1;
    }
    printf("inode:%ld\n",buf.st_ino);
    printf("mode:%d\n",buf.st_mode);
    if((buf.st_mode & S_IFREG) != 0)
        printf("The file is refular file.\n");
    if(!(buf.st_mode & S_IFIFO))
        printf("The file is not FIFO file.\n");
    if(S_ISREG(buf.st_mode) != 0)
        printf("The file is refular file.\n");
    if((buf.st_mode & S_IROTH) != 0)
        printf("Others have read permission.\n");
    if(!(buf.st_mode & S_IWOTH))
        printf("Others have not write permission.\n");
    return 0;
} 

练习

使用stat函数完成Linux终端ll功能。

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <dirent.h>
#include <time.h>
int main()
{
	DIR *fpdir = opendir(".");
	if(fpdir == NULL){
		perror("opendir");
		return -1;
	}
	struct dirent *dp;
	struct stat sbuf;
	while( (dp = readdir(fpdir)) != NULL ){
		stat(dp->d_name, &sbuf);
		//if(S_ISREG(sbuf.st_mode))
		//	printf("-");
		switch(sbuf.st_mode & 0770000){
			case S_IFSOCK	: printf("s"); break;
			case S_IFLNK	: printf("l"); break;
			case S_IFREG	: printf("-"); break;
			case S_IFBLK	: printf("b"); break;
			case S_IFDIR	: printf("d"); break;
			case S_IFCHR	: printf("c"); break;
			case S_IFIFO	: printf("p"); break;
			default : break;
		}
		//user read
		if((sbuf.st_mode & S_IRUSR) == 0)
			printf("-");
		else
			printf("r");
		//user write
		if((sbuf.st_mode & S_IWUSR) == 0)
			printf("-");
		else
			printf("w");
		//user exe
		if((sbuf.st_mode & S_IXUSR) == 0)
			printf("-");
		else
			printf("x");
		//group read
		if((sbuf.st_mode & S_IRGRP) == 0)
			printf("-");
		else
			printf("r");
		//group write
		if((sbuf.st_mode & S_IWGRP) == 0)
			printf("-");
		else
			printf("w");
		//group exe
		if((sbuf.st_mode & S_IXGRP) == 0)
			printf("-");
		else
			printf("x");
		//other read
		if((sbuf.st_mode & S_IROTH) == 0)
			printf("-");
		else
			printf("r");
		//other write
		if((sbuf.st_mode & S_IWOTH) == 0)
			printf("-");
		else
			printf("w");
		//other exe
		if((sbuf.st_mode & S_IXOTH) == 0)
			printf("-");
		else
			printf("x");
		//link number
		printf(" %ld", sbuf.st_nlink);
#if 1
		//user id
		switch(sbuf.st_uid){
			case 0		: printf(" root"); break;
			case 1000	: printf(" hqyj"); break;
			default : break;
		}
		//group id
		switch(sbuf.st_gid){
			case 0		: printf(" root"); break;
			case 1000	: printf(" hqyj"); break;
			default : break;
		}
#endif
		printf(" %5ld", sbuf.st_size);
		struct tm *tp = localtime(&sbuf.st_mtime);
		printf(" %2d %2d %2d:%02d %d", tp->tm_mon+1, tp->tm_mday,
				tp->tm_hour, tp->tm_min, tp->tm_year+1900);
		printf(" %s", dp->d_name);
		printf("\n");
	}
}

9.库

静态库:在程序编译时所有的库表示的功能都会被编译进整个程序,体积会很大,要占用很多资源。之后的运行不用过多去在意运行环境,移植性特别好;
动态库:在程序编译的时候不加载库的内容进程序,只会在程序使用这个库的地方添加一个标记,这些库文件在程序运行时才会加载。体积小,但是过度依赖运行环境;

静态库编译步骤:

1.gcc -c kunkun.c -o kunkun.o
2.ar crs libkunkun.a kunkun.o      //静态库的全面需要以lib开头,以.a结尾;         
3.gcc main.c -L. -lkunkun          //编译程序时连接静态库,-L指定库文件的路径;

动态库编译步骤:

1.gcc -fPIC -Wall -c kunkun.c           //编译与地址无关的程序,让动态库可以识别到标记;
2.gcc -shared kunkun.o -o libkunkun.so  //生成动态库文件,动态库也需要lib开头,以.so结尾;

注:静态库和动态库可以重名,在编译主程序时连接库默认会连接动态库,加上-static才会指定连接静态库;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值