数组与字符串操作函数

数组:一组具有相同数据类型的数据的集合
         1) 一维数组
         2) 二维数组
         3) 字符数组

1. 一维数组

定义格式:类型说明符  数组名[整数表达式];
        “类型说明符”:指定数组元素的类型,任意C语言合法的类型都可以
        “数组名”:遵循C语言标识符命名规则,是一个常量,其值为首元素的地址

        “整数表达式”(整型常量、符号常量(#define)、常量表达式):指定数组元素的个数。现在也可以是变量、变量表达式,并且变量、变量表达式必须有明确的值。如果是变量、变量表达式则数组不能初始化

        eg:
                int a[10];  // 定义了一个有10个元素的数组 a,每一个元素都是 int 型

一维数组在内存中存放:
        在连续的地址空间,从低地址到高地址依次存放数组中每一个元素

一维数组元素的引用:只能引用数组元素而不能一次整体调用整个数组全部元素的值
        数组名[下标]
        “下标”:既可以是整型常量、符号常量、常量表达式也可以是变量、变量表达式,但必须是>=0的整数,有明确的值
        在C语言中,下标是从0开始的。最大值是  数组大小-1
                a[0]、a[1]、... a[n-1]

        引用数组元素 a[i] 跟普通变量一样,即可以作为左值也可以作为右值,还可以取地址

   

一维数组的初始化:数组的初始化用 { } ,并且必须在定义时直接初始化
(1) 全部初始化
       
 int a[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 0};
(2) 可以对部分元素初始化,后面的元素就自动初始化为0
      
  int a[10] = {1, 2, 3, 4, 5};
(3) 如果对全部数组元素赋初值,那么可以不指定数组长度(元素个数)
        int a[] = {1, 2, 3, 4, 5, 6};

eg:
        int a[100] = {0}; // 一般用于数组元素清0

#include <stdio.h>
int main(int argc, char *argv[]) {
    int s[12] = {1, 2, 3, 4, 4, 3, 2, 1, 1, 1, 2, 3}, c[5] = {0}, i;
    for(i = 0; i < 12; i++) {
        c[s[i]]++;
    }
    for(i = 1; i < 5; i++) {
        printf("%d ", c[i]); // 4 3 3 2
    }    
    printf("\n");
	return 0;
}

2. 二维数组                                                                                                                 

数组元素可以出现在表达式中,也可以被赋值 

        例如: b[1][2] = a[2][3] / 2   

                                                                          
int a[4];   // 定义了一个数组a,有4个元素,每个元素都是int类型
再定义一个数组 b     typeof(a) <===> int [4]
        typeof(a) b[3]; // 定义了一个数组b,有3个元素,每个元素都是 int[4]类型
             ==> int[4] b[3];
             ==> int b[3][4];
     由推导过程可以得出:
二维数组实际上就是一个一维数组,只不过该一维数组的元素又是
                                         一个一维数组罢了

 

我们再实际应用中,通过把 int b[3][4] 看成一个3行4列的矩阵

     

二维数组定义:
       
 类型说明符 数组名[整数表达式][整数表达式];
                                          多少行       每行多少列

     

二维数组在内存中的存放:
       
 按行存放,即先顺序存放第一行的元素,再放第二行的元素...

     

二维数组的引用:
     
   数组名[第几行][第几列]

     

二维数组的初始化:用{}实现初始化,且在定义时直接初始化
(1) 分行给二维数组赋初值
        int a[3][4] = {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}};
(2) 将所有数值写在一个花括号里,按数组的排序顺序对元素依次赋初值
        int a[3][4] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
(3) 只对部分元素赋初值,其余的元素自动置0
        int a[3][4] = {0};   等价于   int a[3][4] ={ };
        int a[3][4] = {1, 2, 3};
        int a[3][4] = {{1, 2, 3}, {5, 6}, {9, 10, 11, 12}};                                                                 
(4) 如果对全部元素都赋初值,则定义数组时可以对第一维的长度省略
             int a[ ][4] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

二维数组中其实是一个线性数组存放着 其他数组的首地址

图片

3. 字符数组及字符串操作函数

1)C语言中没有字符串类型,也没有字符串变量,字符串是存放在字符数组中的 

           

2)用"%s"格式符输出字符串时,printf函数中的输出项是字符数组名,而不是数组元素名            例:printf("%s", c);  // (一次整体调用整个数组全部元素的值)   

                         

3)如果初值个数小于数组长度,则只将这些字符赋给数组中前面那些元素,其余的元素自动定为空字符('\0')       

                                            

4)  在遇到字符 '\0' 时,表示字符串结束,把它前面的字符组成一个字符串   

                         

5)中文也是字符  

字符数组,就是数组元素是 char(字符型)的数组,它可以一维的,也可以是二维的 
       

char c1[5] = {'c', 'h', 'i', 'n', 'a'};
char c2[6] = {"china"}      等价于   char c2[6]= "china";

        两者有什么区别?
        char c2[] = "china";
        <==> char c2[] = {'c', 'h', 'i', 'n', 'a', '\0'}; 
 //  多一个'\0'

字符串在保存时会在末尾加一个终止符 '\0','\0'的ASCII为0,终止符的作用就是用来标识字符串的结束

我们在进行操作字符串都是带入字符串的首地址,正因为每个字符串都会有一个终止符,所有系统才会知道字符串的具体范围

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

int main(int argc, char *argv[]) {
	char str[3][3] = {
		{'1', '2', '3'},
		{'1', '2', '3'},
		{'1', '2', '3'},
	};
	printf("%s\n", str[1]); // 123123
	return 0;
}

数组名a:

        1. 代表整个数组

                sizeof(a),typeof(a),a (数组名是一个指针常量,是一个指针类型的常量,指向数组的首地址,不能修改),&a (整个数组的地址)

        2. 当指针用

                a+1 ===>&a[0] + 1 ===>&a[1]

                *a

正确:c h i n a
#include <stdio.h>
int main(int argc, char *argv[]) {
    char c[] = "china";
    char *p = c;
    while (*p) { // while (*p != '\0')
        printf("%c ", *p++);
    }
    return 0;
}

错误:p越界但不指向空
#include <stdio.h>
int main(int argc, char *argv[]) {
    char c[] = "china";
    char *p = c;
    while (p) {
        printf("%c\n", *p++);
    }
    return 0;
}

4. ⭐字符串操作函数

 4.1 用gets和puts对一个字符串进行输入输出   

gets[字符数组名];   puts[字符数组名或字符串];
        eg:
                char ch[10];
           
    gets(ch);  // 在键盘上输入一个字符串,保存到ch指向的内存空间中
                puts(ch);  // 把ch指向的空间中保存的字符串打印出来


        注意:
             a) gets有巨大的Bug,
gets这个函数不会考虑保存这个字符串的数组的空间大小,
                  如果超过了分配范围还会继续写数据,可能会改变别的变量的数据
         

#include <stdio.h>
int main() {
    char c[3];
    char b[3] = {'a','b','c'};
    gets(c); // sscc    // ssc
    puts(c); // sscc    // ssc
    puts(b); // c       // 空白并换行
    return 0;
}
// 原因:终端输入的是一个字符串,末尾自动加了一个'\0'
// 字符数组按内存从小到大连续存储(节约内存) 

            b) gets函数不会去获取 '\n',puts在输出时将字符串结束标志'\0'转换成'\n',即自动                   换行
           

            c) gets获取字符串时,可以获取空格、tab等空白字符,回车结束输入
                而 scanf 是遇到空格、tab、回车等空白字符就结束输入

        
终端指令:man gets // 查找man手册中 gets 函数的说明

NAME
    gets - get a string from standard input (DEPRECATED)
SYNOPSIS
    #include <stdio.h>
 
char* gets(char *s);
4.2 strcpy / strncpy:字符串拷贝函数             

strcpy(字符数组名1,字符串或字符数组名2)  

不能用赋值语句将一个字符串常量或字符数组直接给一个字符数组,字符数组名是一个地址常量,不能改变其值

NAME
    strcpy, strncpy - copy a string
SYNOPSIS
    #include <string.h>

strcpy:字符串拷贝函数.用来把src指向的字符串,拷贝到dest指向的内存中去
            
char* strcpy(char *dest, const char *src);
                
@dest:指向用来保存字符串的内存空间
                
@src:指向要拷贝的字符串数据
            
返回值:
    如果成功,返回拷贝后字符串的首地址, 即 dest
    如果失败,返回 NULL
        
注意:strcpy函数也有一个Bug,不关心dest指向的空间是否够用,造成内存溢出
strncpy是为了修复strcpy的bug的,它的功能和strcpy相似,
只不过他顶多拷贝n个字节
            
char* strncpy(char *dest, const char *src, size_t n);
                
@dest:指向用来保存字符串的内存空间
                
@src:指向要拷贝的字符串数据
                
@n:最多拷贝n个字节
            
返回值:
    如果成功,返回拷贝后字符串的首地址, 即 dest
    如果失败,返回 NULL

注意:strncpy到底拷贝了多少个字节呢?
        1) 遇到 '\0',拷贝结束('\0'有拷贝的)
        2) 没遇到'\0',但是已经拷了n个字节了,结束拷贝(没有拷贝'\0')

            如果在复制前未对字符数组1初始化或赋值,则字符数组1各字节中的内容是无法预知的,复制时将字符数组2中的字符串和其后的'\0'一起复制到字符数组1中,取代字符数组1中的前面几个字符,最后几个字符并不一定是'\0',而是字符数组1中的最后几个字节的内容     

字符数组按内存从小到大连续存储(节约内存)                                                            
char ch1[] = {'a', 'b', 'c'};                                                              char ch2[] = {'b', 'c', 'd'}; // 存储 a b c b c d   

// 从小到大排序  a b c a a a b c \0 \0 ......
char ch1[100] = {'a', 'b', 'c'};
char ch2[] = {'a', 'b', 'c', 'a', 'a'};          
puts(ch2); // abcaaabc
strcpy(ch1, ch2);                                       
printf("%s", ch1); // abcaaabc

// 从小到大排序  a b c a a \0 \0 \0 \0 \0 a b c \0 \0 ......
char ch1[100] = {'a', 'b', 'c'};
char ch2[10] = {'a', 'b', 'c', 'a', 'a'};
puts(ch2);  // abcaa  原因:有'\0'
strcpy(ch1, ch2);
printf("%s", ch1); // abcaa

注意事项:

1. 遇到 '\0'拷贝结束('\0'有拷贝的)

2. 字符数组2的大小如果小于字符数组1的大小:必须以 '\0'结束

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

// 错误 数组arr1越界,'\0'是停止拷贝的终止条件
int main(int argc, char *argv[]) {
    // 字符数组按内存从小到大连续存储(节约内存) abcd**********
	char arr1[] = "**********";
	char arr2[] = {'a', 'b', 'c', 'd'}; // 没有'\0'结尾
	printf("%s\n", strcpy(arr1, arr2));
	return 0;
}

// 正确
int main(int argc, char *argv[]) {
    // 字符数组按内存从小到大连续存储(节约内存) abcd**********\0 
	char arr1[14] = "**********"; // 10个‘*’  有'\0'结尾
	char arr2[] = {'a', 'b', 'c', 'd'}; // 没有'\0'结尾
	printf("%s\n", strcpy(arr1, arr2)); // 输出:abcd**********
	return 0;
}

// 正确
int main(int argc, char *argv[]) {
    // 字符数组按内存从小到大连续存储(节约内存) abcd\0********** 
	char arr1[10] = "**********";
	char arr2[] = { 'a', 'b', 'c', 'd', '\0'}; // 有'\0'结尾
	printf("%s\n", strcpy(arr1, arr2)); // 输出abcd
	return 0;
}

// 正确
int main(int argc, char *argv[]) {
	char arr1[4] = "****";
	char arr2[4] = {'a', 'b', 'c', 'd'}; // 没有'\0'结尾
	printf("%s\n", strcpy(arr1, arr2)); // 输出:abcd
	return 0;
}

// 错误
int main(int argc, char *argv[]) {
	char arr1[5] = "****"; // 有'\0'结尾
	char arr2[4] = {'a', 'b', 'c', 'd'}; // 没有'\0'结尾
	printf("%s\n", strcpy(arr1, arr2));
	return 0;
}

3. 目标空间必须足够大,以确保能放源字符串

#include <stdio.h>
#include <string.h>
 
int main(int argc, char *argv[]) {
    // 字符数组按内存从小到大连续存储(节约内存)
	char arr1[6] = "*****";
	char arr2[] = "hello world";
	printf("%s\n", strcpy(arr1, arr2));
    printf("%s\n", arr2);
	return 0;
}

hello world
world

这里虽然拷贝成功并将结果输出了,但程序却崩溃了。目标空间太小,不足以放置拷贝的源字符串,会造成溢出的情况,从而改变字符arr2中的值

4. 目标空间必须可变

#include <stdio.h>
#include <string.h>
 
int main(int argc, char *argv[]) {
	char *str1 = "hello world";
	char str2[10] = "*********";
	printf("%s\n", strcpy(str1, str2));
	return 0;
}

这里的程序也出现了错误。str1指向的是常量字符串,是不可以被修改掉的,目标空间必须是可以被修改的,因为要将拷贝的字符串放在目标空间中。而源字符串可以是能够修改的、也可以是不能修改的,因为strcpy函数的第二个参数已经用const关键字修饰了,保证了拷贝过程中不会被修改

5. arr1如果是指针,必须要有指向,且指向的内容必须可改变

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

// 正确
int main(int argc, char *argv[]) {
	char str[10] = "aa";
	char *arr1 = str; // 字符数组按内存从小到大连续存储(节约内存) arr1以str为依据
	char arr2[] = {'a', 'b', 'c', 'd'}; // 没有'\0'结尾
	printf("%s\n", strcpy(arr1, arr2)); // 输出:abcdaa
	return 0;
}

// 错误
int main(int argc, char *argv[]) {
	char *arr1; // 或 char *arr1 = NULL; // 野指针
	char arr2[5] = {'a', 'b', 'c', 'd'}; // 有'\0'结尾
	printf("%s\n", strcpy(arr1, arr2));
	return 0;
}

// 正确
int main(int argc, char *argv[]) {
	char *arr1 = malloc(sizeof(4));
	char arr2[] = {'a', 'b', 'c', 'd'}; // 没有'\0'结尾
	printf("%s\n", strcpy(arr1, arr2)); // 输出:abcd
	return 0;
}

// 正确
int main(int argc, char *argv[]) {
	const char *s = "hello"; // s指向6个字节大小的字符串
	char *p = malloc(strlen(s)); // 分配5个字节
	printf("%s\n", strcpy(p, s)); // hello
	printf("%ld\n", sizeof("hello")); // 6
	return 0;
}
4.3 strlen

求s指向的字符串的长度,并返回字符串长度(是指字符串里面包含的字符个数(不算后面'\0'))遇到'\0'才结束         %ld

strlen(字符串或字符数组)            字符'\0'的ASCII值是0,字符'0'的ASCII值是48   

NAME
    strlen - calculate the length of a string
SYNOPSIS
    #include <string.h>
               
size_t strlen(const char *s);
char ch1[5] = {'a', 'b', 'c'};
char ch2[3] = {'a', 'b', 'c'};
printf("%ld", strlen(ch1)); // 3 
printf("%ld", strlen(ch2)); // 6
char ch1[] = {'a', 'b', 'c'};
char ch2[3] = {'a', 'b', 'c'};
printf("%ld", strlen(ch1)); // 6 
char ch[] = {'a', 'b', 'c'};  
strlen(ch); // 由于str数组没有字符串结束符,长度不能确定
char ch[3] = {'a', 'b', 'c'};  
strlen(ch); // 3
4.4 strcat / strncat:字符串连接函数                 

strcat(字符数组名1,字符数组名2或字符串)                    

NAME
    strcat, strncat - concatenate two strings
SYNOPSIS
    #include <string.h>
                                 
char* strcat(char *dest, const char *src);

strcat:用来把src指向的字符串拷贝到dest指向的字符串的末尾(覆盖掉'\0')
                
@dest:指向用来保存字符串的内存空间,注意空间大小要足够
                
@src:指向要拷贝的字符串数据
            
返回值:
    如果成功,返回拷贝后字符串的首地址,即 dest
    如果失败,返回 NULL

注意:strcat函数也有一个Bug,不关心dest指向的空间是否够用,造成内存溢出
           strncat正是为了修复strcat的bug的,它的功能和strcat相似

char* strncat(char *dest, const char *src, size_t n);
                
@dest:指向用来保存字符串的内存空间
                
@src:指向要拷贝的字符串数据
                
@n:最多拷贝n个字节

返回值:
    如果成功,返回拷贝后字符串的首地址, 即 dest
    如果失败,返回 NULL
#include <stdio.h>
#include <string.h>
 
int main () {
    char src[50], dest[50];
    strcpy(src, "This is source");
    strcpy(dest, "This is destination");
    strcat(dest, src);
    printf("最终的目标字符串: |%s|", dest); 
   return(0);
}

// 最终的目标字符串: |This is destinationThis is source|
4.5 strcmp / strncmp:字符串比较函数                 

strcmp(字符串1,字符串2)                                                               

那么字符串怎么比较呢?
        都是从第一个字符开始比较,一个字符接着一个字符比较ASCII码值的大小
         if c1 > c2,则返回 正数
         if c1 < c2,则返回 负数
         if c1 == c2,则继续比较下一个,如果全部相等才返回 0

1)在英文字典中位置在后面的ASCII值“大”

2)小写字母比大写字母的ASCII值“大”

NAME
    strcmp, strncmp - compare two strings
SYNOPSIS
    #include <string.h>                                                                                                               
int strcmp(const char *s1, const char *s2);
          
注意:strcmp 无bug,因为没有进行写操作
        
strncmp:功能和strcmp相似,只不过比较前n个字符
            
int strncmp(const char *s1, const char *s2, size_t n);
4.6 大小写转换
#include <string.h>       

strlwr函数-----转换为小写的函数     strlwr(字符串)                                
    将字符串中大写字母换成小写字母
--------------------------------------------------------------------
         
strupr函数-----转换为大写的函数     strupr(字符串)                                                                    
    将字符串中大写字母换成小写字母
4.7 memset初始化函数

memset是一个初始化函数,作用是将某一块内存中的全部设置为指定的值

#include <string.h>        
void* memset(void *s, int c, size_t n); 

s指向要填充的内存块
c是要被设置的值
n是要被设置该值的字符数
返回类型是一个指向存储区s的指针
// 1)
int arr[m][n];
memset(arr, 0, sizeof(int) * m * n); // 全部初始化为0

// 2)
int **dp = (int**)malloc(sizeof(int*) * m);
for (int i = 0; i < m; i++) {
    dp[i] = (int*)malloc(sizeof(int) * n);
    memset(dp[i], 0, sizeof(dp[i]));
}

需要说明的几个地方
一、不能任意赋值
memset函数是按照字节对内存块进行初始化,所以不能用它将int数组去初始化为0和-1之外的其他值(除非该值高字节和低字节相同),可以给bool类型赋值(false、true)
其实c的实际范围应该在0~255,因为memset函数只能取c的后八位给所输入范围的每个字节。也就是说无论c多大只有后八位二进制是有效的

=================================================================================================
对于int a[4];
memset(a, -1, sizeof(a)) 与 memset(a, 511, sizeof(a)) 所赋值的结果一样都为-1:
因为 -1 的二进制码(补码)为(11111111 11111111 11111111 11111111);511 的二进制码为(00000000 00000000 00000001 11111111);
后八位均为(11111111),所以数组中的每个字节都被赋值为(11111111)。
注意int占四个字节,例如a[0]的四个字节都被赋值为(11111111),那么a[0](11111111 11111111 11111111 11111111),即a[0] = -1

二、注意所要赋值的数组的元素类型

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

// 例一:对char类型的数组a初始化,设置元素全为’1’
int main(int argc, char *argv[]) {
    char a[4];
    memset(a, '1', 4);
    for(int i = 0; i < 4; i++) {
		printf("%c ", a[i]); // 输出:1 1 1 1
    }
	putchar('\n');
    return 0;
}

// 例二:对int类型的数组a初始化,设置元素值全为1
int main(int argc, char *argv[]) {
    int a[4];
    memset(a, 1, sizeof(a)); // 或者memset(a, 1, 16)  0000 0001  0000 0001  0000 0001  0000 0001
    for(int i = 0; i < 4; i++) {
		printf("%d ", a[i]); // 输出:16843009 16843009 16843009 16843009 
    }   
	putchar('\n');
    return 0;
}

int main(int argc, char *argv[]) {
    int a[4];
    memset(a, 1, 4); // 0000 0001  0000 0001  0000 0001  0000 0001
    for(int i = 0; i < 4; i++) {
		printf("%d ", a[i]); // 输出:16843009 0 0 0    
    }
	putchar('\n');
    return 0;
}

        例一程序中,数组a是字符型的,字符型占据的内存大小就是1Byte,而memset函数也是以字节为单位进行赋值的,所以输出正确
        例二程序中,数组a是整型的,整型占据的内存大小为4Byte,而memset函数还是按照字节为单位进行赋值,将1(0000 0001)赋给每一个字节。那么对于a[0]来说,其值为(00000001 00000001 00000001 00000001),即十进制的16843009

注意:

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

/*
	错误
	当数组作为参数传递时,其传递的实际上是一个指针,这个指针指向数组的首地址,
	如果用sizeof(a)函数得到的只是指针的长度,而不是数组的长度。
*/
void fun1(int a[]) {
    memset(a, -1, sizeof(a)); // sizeof(a)是指针长度
}

int main(int argc, char *argv[]) {
    int a[6];
    fun1(a);
    for(int i = 0; i < 6; i++) {
        printf("%d ", a[i]);
    }
	printf("\n");
    return 0;
}

// 正确
void fun1(int a[], int len) {
    memset(a, -1, len); 
}

int main(int argc, char *argv[]) {
    int a[6];
    int len = sizeof(a);
    fun1(a, len);
    for(int i = 0; i < 6; i++) {
        printf("%d ", a[i]);
    }
	printf("\n");
    return 0;
}
4.8 strdup:字符串拷贝

一. 函数分析

1. 函数原型:

#include <string.h>
char* strdup(const char *s);

2. 功能:

strdup()函数主要是拷贝字符串s的一个副本,由函数返回值返回,这个副本有自己的内存空间,和s没有关联。strdup函数复制一个字符串,使用完后,要使用delete函数删除在函数中动态申请的内存,strdup函数的参数不能为NULL,一旦为NULL,就会报段错误,因为该函数包括了strlen函数,而该函数参数不能是NULL

3. strdup函数实现

char* __strdup(const char *s) {
    size_t  len = strlen(s) +1;
    void *new = malloc(len);
    if (new == NULL) {
        return NULL;
    }
   return (char*)memcpy(new, s, len);
}

4. 函数实例

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

int main(void) {
	char *src = "This is the jibo";
	char *dest;
	dest = strdup(src);
	printf(“the dest %s\n”, dest);
	free(dest);
	return 0;
}

二. strdup与strcpy函数的区别

1. 共同点:

        两个函数都实现了字符串的拷贝。

2. 不同点:

       1)  strcpy函数:把从src地址开始且含有结束符的字符串复制到以dest开始的地址空间

        2)由strcpy和strdup函数实现可知

  • strdup函数返回指向被复制的字符串的指针,所需空间由malloc()函数分配且可以由free()函数释放。stdrup可以直接把要复制的内容复制给没有初始化的指针,因为它会自动分配空间给目的指针

  • strcpy的目的指针一定是已经分配好的内存指针。

        3)strdup的缺点:

使用strdup函数的时候,往往会忘记内存的释放,因为申请内存空间的动作是在strdup函数内实现,如果对该函数的实现不是很了解,则会忘记使用free函数来释放空间

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值