unix环境编程练习 (2)

系统调用

系统调用是由操作系统核心提供,运行于核心态;普通函数调用由库函数或者用户自定义,处于用户态。那些标准函数都是由系统调用完成的。
查看系统调用的接口:

[edemon@CentOS ~]$ man 2 syscalls

进程不能访问内核,系统调用则是接口,由他们告诉内核进程的请求是什么。

错误码

errno: 1–34存在于: /usr/include/asm-generic/errno-base.h
errno: 35–133记录在: /usr/include/asm-generic/errno.h

#define EPERM        1  /* Operation not permitted */
#define ENOENT       2  /* No such file or directory */
#define ESRCH        3  /* No such process */
#define EINTR        4  /* Interrupted system call */
#define EIO      5  /* I/O error */
#define ENXIO        6  /* No such device or address */
#define E2BIG        7  /* Argument list too long */
#define ENOEXEC      8  /* Exec format error */
#define EBADF        9  /* Bad file number */
#define ECHILD      10  /* No child processes */
#define EAGAIN      11  /* Try again */
#define ENOMEM      12  /* Out of memory */
#define EACCES      13  /* Permission denied */
#define EFAULT      14  /* Bad address */
#define ENOTBLK     15  /* Block device required */
#define EBUSY       16  /* Device or resource busy */
#define EEXIST      17  /* File exists */
#define EXDEV       18  /* Cross-device link */
#define ENODEV      19  /* No such device */
#define ENOTDIR     20  /* Not a directory */
#define EISDIR      21  /* Is a directory */
#define EINVAL      22  /* Invalid argument */
#define ENFILE      23  /* File table overflow */
#define EMFILE      24  /* Too many open files */
#define ENOTTY      25  /* Not a typewriter */
#define ETXTBSY     26  /* Text file busy */
#define EFBIG       27  /* File too large */
#define ENOSPC      28  /* No space left on device */
#define ESPIPE      29  /* Illegal seek */
#define EROFS       30  /* Read-only file system */
#define EMLINK      31  /* Too many links */
#define EPIPE       32  /* Broken pipe */
#define EDOM        33  /* Math argument out of domain of func */
#define ERANGE      34  /* Math result not representable */
#define EDEADLK     35  /* Resource deadlock would occur */
#define ENAMETOOLONG    36  /* File name too long */
#define ENOLCK      37  /* No record locks available */
#define ENOSYS      38  /* Function not implemented */
#define ENOTEMPTY   39  /* Directory not empty */
#define ELOOP       40  /* Too many symbolic links encountered */
#define EWOULDBLOCK EAGAIN  /* Operation would block */
#define ENOMSG      42  /* No message of desired type */
#define EIDRM       43  /* Identifier removed */
#define ECHRNG      44  /* Channel number out of range */
#define EL2NSYNC    45  /* Level 2 not synchronized */
#define EL3HLT      46  /* Level 3 halted */
#define EL3RST      47  /* Level 3 reset */
#define ELNRNG      48  /* Link number out of range */
#define EUNATCH     49  /* Protocol driver not attached */
#define ENOCSI      50  /* No CSI structure available */
#define EL2HLT      51  /* Level 2 halted */
#define EBADE       52  /* Invalid exchange */
#define EBADR       53  /* Invalid request descriptor */
#define EXFULL      54  /* Exchange full */
#define ENOANO      55  /* No anode */
#define EBADRQC     56  /* Invalid request code */
#define EBADSLT     57  /* Invalid slot */

#define EDEADLOCK   EDEADLK

#define EBFONT      59  /* Bad font file format */
#define ENOSTR      60  /* Device not a stream */
#define ENODATA     61  /* No data available */
#define ETIME       62  /* Timer expired */
#define ENOSR       63  /* Out of streams resources */
#define ENONET      64  /* Machine is not on the network */
#define ENOPKG      65  /* Package not installed */
#define EREMOTE     66  /* Object is remote */
#define ENOLINK     67  /* Link has been severed */
#define EADV        68  /* Advertise error */
#define ESRMNT      69  /* Srmount error */
#define ECOMM       70  /* Communication error on send */
#define EPROTO      71  /* Protocol error */
#define EMULTIHOP   72  /* Multihop attempted */
#define EDOTDOT     73  /* RFS specific error */
#define EBADMSG     74  /* Not a data message */
#define EOVERFLOW   75  /* Value too large for defined data type */
#define ENOTUNIQ    76  /* Name not unique on network */
#define EBADFD      77  /* File descriptor in bad state */
#define EREMCHG     78  /* Remote address changed */
#define ELIBACC     79  /* Can not access a needed shared library */
#define ELIBBAD     80  /* Accessing a corrupted shared library */
#define ELIBSCN     81  /* .lib section in a.out corrupted */
#define ELIBMAX     82  /* Attempting to link in too many shared libraries */
#define ELIBEXEC    83  /* Cannot exec a shared library directly */
#define EILSEQ      84  /* Illegal byte sequence */
#define ERESTART    85  /* Interrupted system call should be restarted */
#define ESTRPIPE    86  /* Streams pipe error */
#define EUSERS      87  /* Too many users */
#define ENOTSOCK    88  /* Socket operation on non-socket */
#define EDESTADDRREQ    89  /* Destination address required */
#define EMSGSIZE    90  /* Message too long */
#define EPROTOTYPE  91  /* Protocol wrong type for socket */
#define ENOPROTOOPT 92  /* Protocol not available */
#define EPROTONOSUPPORT 93  /* Protocol not supported */
#define ESOCKTNOSUPPORT 94  /* Socket type not supported */
#define EOPNOTSUPP  95  /* Operation not supported on transport endpoint */
#define EPFNOSUPPORT    96  /* Protocol family not supported */
#define EAFNOSUPPORT    97  /* Address family not supported by protocol */
#define EADDRINUSE  98  /* Address already in use */
#define EADDRNOTAVAIL   99  /* Cannot assign requested address */
#define ENETDOWN    100 /* Network is down */
#define ENETUNREACH 101 /* Network is unreachable */
#define ENETRESET   102 /* Network dropped connection because of reset */
#define ECONNABORTED    103 /* Software caused connection abort */
#define ECONNRESET  104 /* Connection reset by peer */
#define ENOBUFS     105 /* No buffer space available */
#define EISCONN     106 /* Transport endpoint is already connected */
#define ENOTCONN    107 /* Transport endpoint is not connected */
#define ESHUTDOWN   108 /* Cannot send after transport endpoint shutdown */
#define ETOOMANYREFS    109 /* Too many references: cannot splice */
#define ETIMEDOUT   110 /* Connection timed out */
#define ECONNREFUSED    111 /* Connection refused */
#define EHOSTDOWN   112 /* Host is down */
#define EHOSTUNREACH    113 /* No route to host */
#define EALREADY    114 /* Operation already in progress */
#define EINPROGRESS 115 /* Operation now in progress */
#define ESTALE      116 /* Stale file handle */
#define EUCLEAN     117 /* Structure needs cleaning */
#define ENOTNAM     118 /* Not a XENIX named type file */
#define ENAVAIL     119 /* No XENIX semaphores available */
#define EISNAM      120 /* Is a named type file */
#define EREMOTEIO   121 /* Remote I/O error */
#define EDQUOT      122 /* Quota exceeded */

#define ENOMEDIUM   123 /* No medium found */
#define EMEDIUMTYPE 124 /* Wrong medium type */
#define ECANCELED   125 /* Operation Canceled */
#define ENOKEY      126 /* Required key not available */
#define EKEYEXPIRED 127 /* Key has expired */
#define EKEYREVOKED 128 /* Key has been revoked */
#define EKEYREJECTED    129 /* Key was rejected by service */

/* for robust mutexes */
#define EOWNERDEAD  130 /* Owner died */
#define ENOTRECOVERABLE 131 /* State not recoverable */

#define ERFKILL     132 /* Operation not possible due to RF-kill */

#define EHWPOISON   133 /* Memory page has hardware error */

系统调用部分函数练习

writev

将多个字符串一次性全部写入文件中。
函数相关:

#include <sys/uio.h>
ssize_t writev(int fd, const struct iovec *iov, int iovcnt);

struct iovec {
   void  *iov_base;    /* Starting address */
   size_t iov_len;     /* Number of bytes to transfer */
};

The writev() system call writes iovcnt buffers of data described by iov to the file associated with  the  file  descriptor  fd
       ("gather output").
The writev() system call works just like write(2) except that multiple buffers are written out.

例子:

#include <stdio.h>
#include <stdlib.h>
#include <sys/uio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main(){
    char *buff[4];
    buff[0] = "My song for you this evening\n";
    buff[1] = "It's not to make you sad\n";
    buff[2] = "Not for adding to the sorrows\n";
    buff[3] = "Over troubled northern land\n";
    struct iovec *iov = (struct iovec *)malloc(sizeof(struct iovec)*4);
    iov[0].iov_base = buff[0];
    iov[0].iov_len = strlen(buff[0]);
    iov[1].iov_base = buff[1];
    iov[1].iov_len = strlen(buff[1]);
    iov[2].iov_base = buff[2];
    iov[2].iov_len = strlen(buff[2]);
    iov[3].iov_base = buff[3];
    iov[3].iov_len = strlen(buff[3]);
    int iovcnt = sizeof(iov);
    int fd = open("writev.txt",O_RDWR|O_TRUNC|O_CREAT,0644);
    if(fd == -1){
        perror("open ");
        exit(1);
    }
    ssize_t bytes = writev(fd,iov,iovcnt);
    if(bytes < 0){
        perror("writev ");
        exit(1);
    }
    close(fd);
    free(iov);
    return 0;
}

readv

与writev()相反的函数。它一次能读取数据到多个字符数组中。函数和writev一样。
ssize_t readv(int fd, const struct iovec *iov, int iovcnt);

#include <stdio.h>
#include <stdlib.h>
#include <sys/uio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main(){
    char *buff[4];
    int i = 0;
    for(i=0;i<4;i++){
        buff[i] = (char *)malloc(40);
    }
    struct iovec *iov = (struct iovec *)malloc(sizeof(struct iovec)*4);
    iov[0].iov_base = buff[0];
    iov[0].iov_len = 40;
    iov[1].iov_base = buff[1];
    iov[1].iov_len = 40;
    iov[2].iov_base = buff[2];
    iov[2].iov_len = 40;
    iov[3].iov_base = buff[3];
    iov[3].iov_len = 40;
    int iovcnt = sizeof(iov);
    int fd = open("writev.txt",O_RDONLY,0644);
    if(fd == -1){
        perror("open ");
        exit(1);
    }
    ssize_t bytes = readv(fd,iov,iovcnt);
    if(bytes < 0){
        perror("readv ");
        exit(1);
    }
    for(i=0;i<4;i++){
        printf("%s",buff[i]);
    }
    close(fd);
    free(iov);
    return 0;
}

执行

[edemon@CentOS workspace]$ gcc readv.c
[edemon@CentOS workspace]$ ./a.out 
My song for you this evening
It's not to make you sad
Not for adding to the sorrows
Over troubled northern land

lseek, dup

lseek()用于重置光标位置。dup()则是赋值文件描述符。

off_t lseek(int fd, off_t offset, int whence);
int dup(int oldfd);

下面的例子是打开文件txt,描述符是fd,写入”hello world” 复制文件描述符成new_fd,隔行写入”this is new_fd”. 最后读取文件内容。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>

int main(){
    int fd = open("txt",O_WRONLY|O_CREAT|O_TRUNC,0644);
    lseek(fd,0,SEEK_SET);
    char *str = "hello world";
    int ret = write(fd,str,strlen(str));
    if(ret <= 0){
        perror("write to fd");
        exit(1);
    }
    int new_fd = dup(fd);
    if(new_fd == -1){
        perror("dup ");
        exit(1);
    }
    str = (char *)malloc(50);
    strcpy(str,"\nthis is new_fd.");
    ret = write(new_fd,str,strlen(str));
    if(ret <= 0){
        perror("write to new_fd");
        exit(1);
    }
    new_fd = open("txt",O_RDONLY);  
    ret = read(new_fd,str,50);
    if(ret <= 0){
        perror("read ");
        printf("errno is %d\n",errno);
        exit(1);
    }
    printf("%s",str);
    close(fd);
    close(new_fd);
    free(str);
    return 0;
}

执行:

hello world
this is new_fd.

poll

poll和select相似,用于等待fd集状态的改变。
int poll(struct pollfd *fds, nfds_t nfds, int timeout);

#include <stdio.h>
#include <stdlib.h>
#include <poll.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(){
    struct pollfd *fds = (struct pollfd*)calloc(2,sizeof(struct pollfd));
    fds[0].fd = open("/tmp/test1",O_RDONLY);
    fds[0].events = POLLIN;
    fds[1].fd = open("/tmp/test2",O_RDONLY);
    fds[1].events = POLLIN;
    int milliSec = 500, i;
    while(1){
        int ret = poll(fds,2,milliSec);
        if(ret == -1){
            perror("poll ");
            exit(1);
        }
        else if(ret == 0){
            puts("waiting a short time...");
            sleep(3);
            continue;
        }
        else {
            for(i=0;i<ret;i++){
                if(fds[i].revents & POLLIN){
                    char *buff = (char *)calloc(30,1);
                    if(read(fds[i].fd,buff,30) == -1){
                         perror("read ");
                         exit(1);
                    }
                    printf("test%d, buff is : %s",i+1,buff);
                    free(buff);
                }
            }
            break;
        }
    }
    for(i=0;i<2;i++) close(fds[i].fd);
    free(fds);
    return 0;
}

编译运行:

$ ./a.out 
test1, buff is : hello world
test2, buff is : I love linux

access

检查文件是否有相应的权限。int access(const char *pathname, int mode);
比如检查文件/tmp/linux 是否存在:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(){
    const char *str = "/tmp/linux";
    int ret = access(str,F_OK);
    if(ret == -1){
        puts("the file /tmp/linux is not there.");
    }
    else {
        puts("here is /tmp/linux.");
    }
    return 0;
}

chdir

chdir用以改变程序的工作目录。
int chdir(const char *path);

#include <stdio.h>
#include <stdlib.h>
int main(){
    const char *str1 = "/tmp";
    system("pwd");
    if(chdir(str1) == -1){
         perror("chdir ");
         exit(1);
    }
    system("pwd");
    return 0;
}

编译执行:

[edemon@CentOS workspace]$ ./a.out 
/home/edemon/workspace
/tmp

symlink用于建立新的(符号)连接,readlink用于查看符号连接内容(对象的名字)。

int symlink(const char *target, const char *linkpath);
ssize_t readlink(const char *path, char *buf, size_t bufsiz);

正常执行后buff即含有内容信息。

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>

int main(){
    const char *str = "./readlink";
    symlink("./read",str);
    char *buff = (char *)calloc(20,1);
    if(readlink(str,buff,19) == -1){
        printf("readlink : %d",errno);
        exit(1);
    }
    printf("read buff: %s",buff);
    return 0;
}

执行:

[edemon@CentOS workspace]$ ./a.out 
read buff: read

[edemon@CentOS workspace]$ ls -l readlink
lrwxrwxrwx. 1 edemon edemon 4 Dec 15 20:19 readlink -> read

link创建一个文件的(硬)连接。
int link(const char *oldpath, const char *newpath);

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>

int main(){
    if(link("./test.txt","./test2.txt") == -1){
        printf("link errno is %d\n",errno);
        exit(1);
    }
    system("ls -l |grep test");
    return 0;
}

软连接和硬连接的区别:如果将源文件删除了,软连接文件是不能访问的,但是硬连接文件仍然正常;软连接可以跨磁盘分区,但是硬连接不能。

remove

int remove(const char *pathname);
如果pathname是一个文件,unlink会被调用;如果pathname是文件夹,那么rmdir则会被调用。

#include <stdio.h>
#include <stdlib.h>

int main(){
    int tag = 0;
    tag = tag|remove("./test_dir");
    tag = tag|remove("./test");
    if(tag == 0){
        printf("remove test_dir and test.txt ok.\n");
    }
    else printf("remove failed.\n");
    return 0;
}

opendir readdir

DIR *opendir(const char *name);
DIR*是一种目录流。
struct dirent *readdir(DIR *dirp);
readdir的返回值是指向struct dirent的指针。

struct dirent {
   ino_t          d_ino;       /* inode number */
   off_t          d_off;       /* not an offset; see NOTES */
   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]; /* filename */
};

在dirent.h中说明了d_type:

/* File types for `d_type'.  */
enum
  {
    DT_UNKNOWN = 0,
# define DT_UNKNOWN DT_UNKNOWN
    DT_FIFO = 1,
# define DT_FIFO    DT_FIFO
    DT_CHR = 2,
# define DT_CHR     DT_CHR
    DT_DIR = 4,
# define DT_DIR     DT_DIR
    DT_BLK = 6,
# define DT_BLK     DT_BLK
    DT_REG = 8,
# define DT_REG     DT_REG
    DT_LNK = 10,
# define DT_LNK     DT_LNK
    DT_SOCK = 12,
# define DT_SOCK    DT_SOCK
    DT_WHT = 14
# define DT_WHT     DT_WHT
  };

查找当前目录下所有的文件夹:

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <dirent.h>

int main(){
    DIR* here = opendir("./");
    struct dirent *dir = NULL;
    while((dir=readdir(here)) != NULL){
        if(dir->d_type == 4)
        printf("type of file is dir, name of file is %s\n",dir->d_name);
    }
    return 0;
}
/*
$ ./a.out 
type of file is dir, name of file is ..
type of file is dir, name of file is .
type of file is dir, name of file is mydir
type of file is dir, name of file is code
type of file is dir, name of file is .metadata
type of file is dir, name of file is test_dir
*/

index, rindex

index用于寻找字符串中第一个出现的指定字符串。
char *index(const char *s, int c);
rindex用于查找字符串中最后一个出现的指定字符。
char *rindex(const char *s, int c);

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(){
    const char* str = "0123456789";
    char* dex = index(str,'4');
    printf("%s\n",dex); 
    return 0;
}
/*
$ ./a.out 
456789
*/

和index相似的函数memchr用于在s所指内存中前n个字节查找有木有c存在。void *memchr(const void *s, int c, size_t n);

memmove

void *memmove(void *dest, const void *src, size_t n);
内存拷贝函数memmove和memcpy都是用于复制src前n个字节到dest中.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(){
    const char* str = "01234543210";
    printf("first location of 4: %s\n",index(str,'4')); 
    printf("last location of 4: %s\n",rindex(str,'4'));
    return 0;
}
/*
$ ./a.out 
first location of 4: 4543210
last location of 4: 43210
*/

strcasecmp

strcasecmp()是一个可以忽略大小写比较的函数,这在某些数据库密码验证中很有用。
int strcasecmp(const char *s1, const char *s2);
例如:

#include <stdio.h>
#include <stdlib.h>

int main(){
    const char* str1 = "ASdF";
    const char* str2 = "asDf";
    if(strcasecmp(str1,str2) == 0) printf("%s is same as %s\n",str1,str2);
    return 0;
}
/*
./a.out 
ASdF is same as asDf
*/

strcspn, strspn

size_t strcspn(const char *s, const char *reject);
strcspn用于统计源字符串s中不含指定字符串reject中字符的连续字符个数。
size_t strspn(const char *s, const char *accept);
与之相反是strspn,它用于统计包含accept字符串中字符的连续字符的个数。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(){
    const char* str1 = "a34b_c2>?<2dfh.L";
    printf("the length of continuous string without '._?<' is %d\n",strcspn(str1,"._?<"));
    printf("the length of continuous string without '_c2a43' is %d\n",strspn(str1,"_c2a43"));
    return 0;
}
/*
./a.out 
the length of continuous string without '._?<' is 4
the length of continuous string without '_c2a43' is 3
*/

strdup

strdup会先malloc出一块内存,然后将字符串拷贝到该内存,最后将相应的指针返回。该内存可以被free.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(){
    const char* str = "hello world";
    char* new = strdup(str);
    printf("%s\n",new);
    free(new);
    return 0;
}
/*
$ ./a.out
hello world
*/

strpbrk

char *strpbrk(const char *s, const char *accept);
strpbrk()用于查找在s中第一个出现在accept中的字符的位置。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(){
    const char* s = "0123456789";
    printf("for 2: %s\n",strpbrk(s,"a2"));
    printf("for 4: %s\n",(char *)strpbrk(s,"b4"));
    return 0;
}
/*
./a.out 
for 2: 23456789
for 4: 456789
*/

strstr

char *strstr(const char *haystack, const char *needle);
strstr用于确定子字符串的位置。和index相比,第二个参数不是char,而是string.

strtok

char *strtok(char *str, const char *delim);
strtok()用于将字符串分割成不同的片段,它在参数s字符串中发现delim中的字符后将之变成0,第一次调用时必须给于str字符串参数,之后的调用将其设置成NULL,每一次调用成功将会返回下一个指向待分割字符串的指针。

#include <stdio.h>
#include <string.h>

int main(int argc,char *argv[]){
    printf("%s\n",strtok(argv[1],argv[2]));
    char *p = NULL;
    while((p = strtok(NULL,argv[2])) != NULL) printf("%s\n",p); 
    return 0;
}
/*
edemon@ubuntu1:~/workspace$ gcc -o strtok strtok.c
edemon@ubuntu1:~/workspace$ ./strtok 12-34.45-78.90-11 .-
12
34
45
78
90
11
*/

ctime

char *ctime(const time_t *timep);
ctime用于将指向time_t的指针信息转化成字符串时间格式。我们需要先获取它的参数,可以通过time_t time(time_t *t);实现,time() returns the time as the number of seconds since the Epoch, 1970-01-01 00:00:00 +0000 (UTC).

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main(){
    time_t time_p;
    time(&time_p);
    printf("%s",ctime(&time_p));
    return 0;
}
/*
$ ./a.out 
Tue Dec 20 21:47:00 2016
*/

字符串转化成数值

functionformatintroduce
atofdouble atof(const char *nptr);convert a string to a double
atoiint atoi(const char *nptr);convert a string to an int integer
atollong atol(const char *nptr);convert a string to an long integer
atolllong long atoll(const char *nptr);convert a string to an long long integer

gcvt

char *gcvt(double number, size_t ndigit, char *buf);
浮点数转化成字符串,四舍五入。函数返回buf指针所指空间。

#include <stdio.h>
#include <stdlib.h>

int main(){
    double number = 123.456;
    char* buf = (char* )malloc(10);
    printf("3 digits: %s\n",gcvt(number,3,buf));
    printf("4 digits: %s\n",gcvt(number,4,buf));
    printf("5 digits: %s\n",gcvt(number,5,buf));
    return 0;
}
/*
./a.out 
3 digits: 123
4 digits: 123.5
5 digits: 123.46
*/

toascii

int toascii(int c);
toascii()用于将参数c转化成转化成7位无符号char型数值,参数的高位将被抹去。
比如:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(){
    int a = toascii(1<<8);
    printf("2^8: %d(%c)\n",a,a);
    a = toascii((1<<8)-1);  
    char str[5];
    str[0] = a;
    str[1] = 0;
    printf("2^8-1: %d(%s)\n",a,str);
    return 0;
}
/*
./a.out 
2^8: 0()
2^8-1: 127)
*/

1000000002010NUL
011111111212710DEL
让我感觉奇怪的是DEL不是删除右边字符吗,为什么这里变成删除左边的(了?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值