C语言-字符串函数和内存函数

字符串

前言

C语言中对字符和字符串的处理很频繁,但是C语言本身是没有字符串类型的,字符串通常放在 常量字符串中或者字符数组中。字符串常量适用于那些对它不做修改的字符串函数。

对于比赛的时候或者面试的时候,使用这些库函数不仅可以大大减少时间,还可以提高模拟的效率。其实是十分有帮助的。

求字符串长度函数

strlen

size_t strlen(const char* string);
size_t=unsigned int
  • 字符串以’\0’作为结束标志,strlen函数返回的是在字符串中’\0’前面出现的字符个数(不包含’\0’)
  • 参数指向的字符串必须要以’\0’结束
  • 注意函数的返回值为size_t,是无符号数的(易错
  • 学会strlen函数的模拟实现
//1.计数器的方法
//2.递归:不创建临时变量来实现
//3.指针-指针
int my_strlen(const char* str){
    int count=0;
    assert(str!=NULL);
    while(*str!='\0'){
        count++;
   	    str++;
    }return count;
}
int my_strlen(const char* str){
    if(*str=='\0'){
        return 0;
    }
    return 1+my_strlen(str+1);
}
int main(){
    int len=my_strlen("abcdef");//后面有'\0'
    printf("%d\n",len);
    /*错误示范*/
    char arr[]={'a','b','c','d','e','f'};
    int len=strlen(arr);//随机
    /*源码中strlen的返回值是无符号数,3-6溢出了。*/
    if( strlen("abc")-strlen("abcdef")>0){
        printf("hehe\n"); /*实际输出*/
    }
    else{
        printf("haha\n");
    }
}

长度不受限制的字符串函数

strcpy(字符串拷贝)

char* strcpy(char* destination,const char * source);
  • 源字符串必须以’\0’结束 (比如char s[]={‘b’,‘i’,‘t’};不是以’\0’结尾)
  • 目标空间必须足够大,放得下源字符串
  • 目标空间必须可变(常量字符串的空间不可变)
  • 学会模拟实现
char* my_strcpy(char* dest, const char* src){
    assert(dest!=NULL);
    assert(src!=NULL);
    char* ret=dest;
    //拷贝src指向的字符串到dest指向的空间,包含'\0'
    while( *dest++ = *src++ )///精简写法,表达式结果判断是否'\0'
    {
        ;
    }
    //返回目的空间的起始地址
    return ret;
}
int main(){
    char arr1[]="abcdefghi";
    char arr2[]="bit";
    
    /*错误的示范*/
    cbar arr2[]={'b','i','t'};
    char *arr1="abcdefgh";/*目标空间不可变*/
    my_strcpy(arr1,arr2);
    printf("%s\n",arr1);
}

strcat(字符串追加)

char* strcat
  • 源字符串必须以’\0’结束
  • 目标空间必须有足够的大,能容纳下源字符串的内容
  • 目标空间必须可修改
  • 字符串自己给自己追加,如何(程序会崩溃)
char* my_strcat(char* dest,const char*src){
    char *ret=dest;
    assert(dest!=NULL);assert(src!=NULL);
    //1.找到目的字符串的'\0'
    while(dest!='\0') dest++;
    //2.追加(此时就是拷贝)
    while(*dest++=*src++){
        ;
    }return ret;
}
int main(){
    char arr1[]="hello";
    char arr2[]="world";
    strcat(arr1,arr2);//error 因为炸了arr1的大小
    char arr1[30]="hello\0xxxxx";
    char arr2[]="world";
    strcat(arr1,arr2);///追加在\0,不是末尾,helloworld\0xxxx
	my_strcat()    
}

strcmp(字符串比较)

标准规定:

第一个字符串大于第二个字符串返回>0的数字

第一个字符串等于第二个字符串返回=0的数字

第一个字符串小于第二个字符串返回<0的数字

int main(){
    char* p1="abcdef";
    char* p2="sqwer";
	//vs2013
    //>  1 ; == 0 ; < -1
    //linux gcc
    //> >0 ; == 0 ; < <0
    int ret=strcmp(p1,p2);
}
int my_strcmp(const char* str1,const char* str2){
    assert(str1!=NULL);assert(str2!=NULL);
    while(*str1==*str2){
        if(*str1=='\0'){
            return 0; //相等
        }
        str1++;
        str2++;
    }
    if(*str1 > *str2){ 
        return 1; //大于
    }
    else if(*str1<*str2){
        return -1; //小于
    }
    /*linux : return (*str1)-(*str2);*/
}
int main()
{
    char* p1="abcdef";
    char* p2="abcdef";
    int ret=my_strcmp(p1,p2);
}
int main(){
    char arr1[5]="abc";
    char arr2[]="hello bit";
    strcpy(arr1,arr2);
    printf("%s\n",arr1);//RE,使用时不安全
}

长度受限制的字符串函数

strncpy

char* strncpy(char* strDest,const char* strSource,size_t count){
    
}
  • 拷贝num个字符从源字符串到目标空间
  • 如果源字符串的长度小于num,则拷贝完源字符串之后,在目标的后面追加0,直到num个
char* my_strncpy(char* strDest,const char* strSource,int sz){
      char* ret=strDest;
      while( ( *strDest++=*strSource++) &&sz>0){
            sz--;
      }
      while(sz>0){
           *strDest='\0';sz--;
      }
      return ret;
}
int main(){
    char arr1[10]="abcdef";
    char arr2[]="hello bit";
    strcnpy(arr1,arr2,4);//arr1:hellef
    char arr2[]="bit";
    strcnpy(arr1,arr2,6);//arr1:bit \0 \0 \0 不够的地方补\0
}

strncat

char* strncat(char* strDest,const char* strSource,size_t count)
  • 追加之后主动放一个\0。
  • 如果追加的长度超过本身的长度,只追加本身的。
char* strncat(char *strDest,const char* strSource,int sz){
      char* ret=strDest;
      
}
int main(){
    char arr1[30]="hello\0xxxxxxxxxxxxxxxxxx";
    char arr2[]="world";
    strncat(arr1,arr2,3);//追加之后会主动放一个\0进去
    strncat(arr1,arr2,8);//个数比我的本身长,只追加本身的
    printf("%s\n",arr1);
}

strncmp

int strncmp(const char* string1,const char* string2,size_t count){
    
}
int main(void){
    const char* p1="abcdef";
    const char* p2="abcqwer";
    int ret=strcmp(p1,p2);
    printf("%d\n",ret);
    
    int res=strcmp(p1,p2,3);
    printf("%d\n",res);
}

strstr(查找子字符串)

char* strstr(const char* string,const char* strCharset){
     
}
char* my_strstr(const char* p1,const char* p2 ){
      assert(p1!=NULL);assert(p2!=NULL);
      if(*p2=='\0'){
          return (char*)p1;
      }
      while(*p1!='\0'){
          while( (*p1!=*p2) && (*p1!='\0') ){
              p1++;
          }
          char* cnt=(char*)p1;
          if(*cnt=='\0'){
              return NULL;
          }
          char* temp=(char*)p2;
          while((*cnt==*temp)&&(temp!='\0')&&(*cnt!='\0')){
              cnt++;
             temp++;
          }
          if(*temp=='\0'){
              return (char*)p1;
          }
          p1++;
      }
      return NULL;
    /*
    	char* cur=p1;
    	while(*cur){
    		s1=cur;
    		s2=p2;
    		while( (*s1!='\0') &&(*s2!='\0')&&(*s1==*s2)){
    		s1++;s2++;
    		}
    		if(*s2=='\0'){
    			return cur; 
    		}
    		cur++;
    	}
    	return NULL;
    */
   	/*简洁版
   	char* my_strstr(const char* p1,const char* p2 ){
      assert(p1!=NULL);assert(p2!=NULL);
      if(*p2=='\0') return (char*)p1;
      char* now=(char*)p1;
      char* cnt=(char*)p2;
      while(*now!='\0'){
            while((*now!='\0')&&(*cnt!='\0')&&(*now==*cnt)){
                now++;
                cnt++;
            }
            if(*cnt=='\0') return (char*)p1;
            p1++;
            now=(char*)p1;
            cnt=(char*)p2;
      }
      return NULL;
}
	*/
}
int main(){
    char *p1="abcdefabcdef";
    char *p2="def";
    char *ret=strstr(p1,p2);
	if(ret==NULL){
        printf("字串不存在");
    }
    else{
        printf("%s\n",ret);///返回的是第一次出现的起始位置
    }
}

strtok(分割字符)

char* strtok(char* str,const char* sep){
    
}
  • sep参数是个字符串,定义了用作分隔符的字符集合
  • 第一个参数指定一个字符串,它包含了0个或者多个由sep字符串中一个或者多个分隔符分割的标记
  • strtok函数找到str中的下一个标记,并将其用\0结尾,返回一个指向这个标记的指针。(注:strtok函数会改变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容且可修改)
  • strtok函数的第一个参数不为NULL,函数将找到str中第一个标记,strtok函数将保存它在字符串中的位置。
  • strtok函数的第一个参数为NULL,函数将在同一个字符串中被保存的位置开始,查找下一个标记。
  • 如果字符串中不存在更多的标记,则返回NULL指针

一次只能切出一段,函数会记住’\0’的位置。所以函数内部应该是有静态变量的。出去函数不销毁。

int main()
{
    //192.168.32.121
    //192 168 31 121
    //zpw@bitedu.tech
    //zpw bitedu tech
    char arr[]="zpw@bitedu.cn";
    char* p="@.";
    char* ret=NULL;
    
    for(ret=strtok(arr,p);ret!=NULL;ret=strtok(NULL,p)){
        printf("%s\n",ret);
    }
    /*繁琐*/
    char buf[1024] ={0};
    strcpy(buf,arr);
    //切割buf中的字符串
	char* ret=strtok(arr,p);
    printf("%s\n",ret);
    ret=strtok(NULL,p);
    printf("%s\n",ret);
    
}

strerror(返回错误码)

char* strerror(int errnum);

返回错误码----->所对应的错误信息。

#include<errno.h>
int main()
{
    //错误码  错误信息
    //0- No error
    //1 -Operation not permitted
    //2 -
    //...
    char* str =strerror(1);
    //真实写代码
    //errno是一个全局的错误码变量
    //当C语言的库函数在执行过程中,发生了错误,就会把对应的错误码,赋值到error中
    char* str=strerror(errno);
    printf("%s\n",str);
    
    
    ///打开文件
    FILE* pf=fopen("test.txt","r");
	if(pf==NULL){
        printf("%s\n",strerror(errno));
        //perror("测试:") 输出"测试:+错误信息”; ---打印自定义+strerror
    }
    else{
        printf("open file success\n");
    }
}

字符分类函数

#include<ctype.h>
iscntrl //控制字符
isspace //空白字符:空格' ',换页'\f',换行'\n',回车'\r'.制符表'\t',垂直制符表'\v'
isdigit;//十进制0~9
islower;//小写字母a~z
isupper;//大写字母A~Z
isalpha;//字母a~z或A-Z
isalnum;//字母或者数字 a~z,A-Z,0~9
ispunct;//标点符号,任何不属于数字或者字母的图形字符(可打印)
isgraph;//任何图形字符
isprint;//任何可打印字符,包括图形字符和空白字符

字符转换函数

int tolower(int c);

int toupper(int c);

#include<ctype.h>

int main()
{
    char ch=tolower('Q');
    putchar(ch);
   	return 0;
    
    char arr[]="I Am A Student";
    int i=0;
    while(arr[i])
    {
        if(isupper(arr[i]))
        {
            arr[i]=tolower(arr[i]);
        }
        i++;
    }
    printf("%s\n",ret);
}

内存函数

以上的strcpy,strcat,strcmp,strmcpy,strncat,strncmp的操作对象是字符串,’\0’

对于整形数组,浮点型数组,结构体数组。

int main()
{
    int arr1[]={1,2,3,4,5};
    int arr2[5]={0};
    
    strcpy(arr2,arr1)
    //小端法
    // 01 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00 05 00 00 00
    //拷贝到第一个00就停下来了。
}

memcpy(内存拷贝)

因此有memcpy,只要是放内存中的,都可以拷贝。可以对整型,浮点,结构体,字符。因此对应于回调函数,需要使用void*

void* memcpy(void *destination,const void* source,size_t num)
size_t 的单位是字节
int arr1[]={1,2,3,4,5};
int arr2[5]={0};    
memcpy(arr2,arr1,sizeof(arr1));

struct S
{
    char name[20];
    int age;
};
int main()
{
    int arr1[]={1,2,3,4,5};
    int arr2[5]={0};
    struct S arr3[]={ {"张三",20},{"李四",30} };
    struct S arr4[3]={0};
    memcpy(arr4,arr3,sizeof(arr3));
}
//
void* my_memcpy(void* dest,const void* src,size_t num)
{
    assert(dest&&src);
    void* ret=dest;
    while(num--)
    {
        char temp='\0';
        temp=*(char*)dest
    	*(char*)dest=*(char* src);
        *(char*)src=temp;
        dest=(char*)dest+1;
        scr=(char*)src+1;
    }
    return ret;
}

//模拟实现
void* my_memcpy(void* dest,const void* src,size_t num)
{
    assert(dest!=NULL);assert(src!=NULL);
    void* ret=dest;
    while(num--)
    {
     	*(char*)dest=*(char*)src;
        ++(char*)dest;
        ++(char*)src;
        dest=(char*) dest+1;
        src=(char*) src+1;
    }
    return ret;
}
void* my_memcpy(void* dest,const void* src,size_t num)
{
    assert(dest!=NULL);assert(src!=NULL);
    void* ret=dest;
    while(num--)
    {
     	*(char*)dest=*(char*)src;
        ++(char*)dest;
        ++(char*)src;
    }
    return ret;
}
int main()
{
    int arr[]={1,2,3,4,5,6,7,8,9,10};
    int i=0;
    my_memcpy(arr+2,arr,20);
    for(int i=0;i<10;i++) printf("%d ",arr[i]);
    //理想:1 2 1 2 3 4 5 8 9 10
    //现实: 1 2 1 2 1 2 1 8 9 10
    //因为此时指向的是涉及重叠的一块区域,顺序赋值导致的。然而倒序赋值在一些情况也不行。
    //因为要讨论,要分类去做。然而memcpy不是负责这个东西的,负责的是memmove函数(处理内存重叠的情况).
    //C语言标准规定memcpy:只要处理不重叠的内存拷贝就可以
    //memmove 可以处理重叠内存的拷贝
    //但是VS编译器重新实现了memcpy,达到了内存覆盖的处理
}

memmove(处理内存重叠的情况)

void *memmove( void *dest, const void *src, size_t count );
void *my_memove(void* dest,const void* src,size_t num)
{
    

}
int main()
{
    int arr[]={1,2,3,4,5,6,7,8,9,10};
    int i=0;
    memmove(arr+2,arr,20);
    for(int i=0;i<10;i++)
    {
        printf("%d ",arr[i]);
    }
}
#include<stdio.h>
void my_memove(void* dest, const void* source, size_t count)
{
	if (dest >= source)//后往前
	{
		while (count--)
		{
			*((char*)dest + count) = *((char*)source + count);
		}
	}
	else//前往后
	{
		while (count--)
		{
			*(char*)dest = *(char*)source;
			dest = (char*)dest + 1;
			source = (char*)source + 1;
		}
	}
}
int main()
{
	int arr[] = { 0,1,2,3,4,5,6,7,8,9,10 };
	//0 1 2 3 4 5 6 7 8 9 10
	//0 1 0 1 2 3 4 7 8 9 10
	int sz = sizeof(arr) / sizeof(arr[0]);
	my_memove(arr + 2, arr, 5 * sizeof(arr[0]));
	for (int i = 0; i < sz; i++) {
		printf("%d ", arr[i]);
	}printf("\n");
	int drr[] = { 0,1,2,3,4,5,6,7,8,9,10 };
	//0 1 2 3 4 5 6 7 8 9 10
	//2 3 4 5 6 5 6 7 8 9 10
	my_memove(drr, drr + 2, 5 * sizeof(drr[0]));
	for (int i = 0; i < sz; i++)
	{
		printf("%d ", drr[i]);
	}printf("\n");
	return 0;
}

memcmp(内存比较)

按字节比较大小

int memcmp ( const void * ptr1, 
 const void * ptr2, 
 size_t num );

/* memcmp example */
#include <stdio.h>
#include <string.h>

int main ()
{
  char buffer1[] = "DWgaOtP12df0";
  char buffer2[] = "DWGAOTP12DF0";

  int n;

  n=memcmp ( buffer1, buffer2, sizeof(buffer1) );

  if (n>0) printf ("'%s' is greater than '%s'.\n",buffer1,buffer2);
  else if (n<0) printf ("'%s' is less than '%s'.\n",buffer1,buffer2);
  else printf ("'%s' is the same as '%s'.\n",buffer1,buffer2);

  return 0;
}

查了查源码是这样的。但是我略有疑问。

对于 int arr1[]={1,2,3}; int arr2[]={1,2}; 按照arr1的大小来比较3个字节,我觉得应该类似strcmp那样输出greater,但是源码认为是less。

int memcmp2(const void* buffer1, const void* buffer2, int count)
{
	if (!count) return(0);
	while (--count && *(char*)buffer1 == *(char*)buffer2)
	{
		buffer1 = (char*)buffer1 + 1;
		buffer2 = (char*)buffer2 + 1;
	}
	return(*((unsigned char*)buffer1) - *((unsigned char*)buffer2));
}
int main()
{
    int arr1[]={1,2,3,4,5};
    int arr2[]={1,2,3,6,6};
    int ret=memcpy(arr1,arr2,12);
    printf("%d\n",ret);
}
#include<stdio.h>
#include<string.h>
#include <cassert>
int my_memcmp(const void* s, const void* p, size_t num)//比较从s和p指针开始的num个字节
{
	assert(s && p);
	char* ed = (char*)p+ num - 1;///p指针的结尾
	while (num)
	{
		if (*(char*)s > * (char*)p) return 1;
		else if (*(char*)s < *(char*)p) return -1;
		s=(char*)s+1; p=(char*)p+1;
		num--;
        if (p > ed && num > 0) return 1;//s长度长但是p长度短且前面都相等的情况
	}
	return 0;
}
int memcmp2(const void* buffer1, const void* buffer2, int count)
{

	if (!count)

		return(0);

	while (--count && *(char*)buffer1 == *(char*)buffer2)

	{

		buffer1 = (char*)buffer1 + 1;

		buffer2 = (char*)buffer2 + 1;

	}

	return(*((unsigned char*)buffer1) - *((unsigned char*)buffer2));

}
int main()
{
	int buffer1[] = { 1,2,3 };
	int buffer2[] = { 1,2 };
	int n;
	n = my_memcmp(buffer1, buffer2, sizeof(buffer1));
	if (n > 0) printf("buffer1 is greater than buffer2.\n", buffer1, buffer2);
	else if (n < 0) printf("buffer1 is less than buffer2.\n", buffer1, buffer2);
	else printf("buffer1 is the same as buffer2.\n", buffer1, buffer2);

	n = memcmp2(buffer1, buffer2, sizeof(buffer1));

	if (n > 0) printf("buffer1 is greater than buffer2.\n", buffer1, buffer2);
	else if (n < 0) printf("buffer1 is less than buffer2.\n", buffer1, buffer2);
	else printf("buffer1 is the same as buffer2.\n", buffer1, buffer2);
	return 0;
}

memset

注意是按照字节来一个个赋值的。

void *memset( void *dest, int c, size_t count );
count-Number of characters //字节数

int main()
{
    int arr[]={1,2,3,4,5,6};
}

模拟实现

1.memmove

#include<stdio.h>
void my_memove(void* dest, const void* source, size_t count)
{
	if (dest >= source)//后往前
	{
		while (count--)
		{
			*((char*)dest + count) = *((char*)source + count);
		}
	}
	else//前往后
	{
		while (count--)
		{
			*(char*)dest = *(char*)source;
			dest = (char*)dest + 1;
			source = (char*)source + 1;
		}
	}
}
int main()
{
	int arr[] = { 0,1,2,3,4,5,6,7,8,9,10 };
	//0 1 2 3 4 5 6 7 8 9 10
	//0 1 0 1 2 3 4 7 8 9 10
	int sz = sizeof(arr) / sizeof(arr[0]);
	my_memove(arr + 2, arr, 5 * sizeof(arr[0]));
	for (int i = 0; i < sz; i++) {
		printf("%d ", arr[i]);
	}printf("\n");
	int drr[] = { 0,1,2,3,4,5,6,7,8,9,10 };
	//0 1 2 3 4 5 6 7 8 9 10
	//2 3 4 5 6 5 6 7 8 9 10
	my_memove(drr, drr + 2, 5 * sizeof(drr[0]));
	for (int i = 0; i < sz; i++)
	{
		printf("%d ", drr[i]);
	}printf("\n");
	return 0;
}

2.memcpy

#include<stdio.h>
void my_memcpy(void* dest, const void* source, size_t count)
{
	while (count--)
	{
		*(char*)dest = *(char*)source;
		dest = (char*)dest + 1;
		source = (char*)source + 1;
	}
}
struct P {
	int a, b;
};
int main()
{
	P A = { 1,2 };
	P B; my_memcpy(&B, &A, 1 * sizeof(struct P));
	printf("%d %d\n", B.a, B.b);
	return 0;
}

3.strstr

#include<stdio.h>
#include<cassert>
#include<math.h>
#include<iostream>
#include<string.h>
#define max(x,y) ( (x)>(y)?(x):(y) )
const int maxn = 1e5 + 100;
int d[maxn];
char t[maxn], p[maxn];
using namespace std;
//下标从0开始的最长公共前后缀可以直接怼到应该重新开始的匹配的位置
void pre(const char* p)
{
	memset(d, 0, sizeof(d));
	int len = strlen(p);
	int i = 1, j = 0;
	while (i < len)
	{
		if (p[i] == p[j]) d[i++] = ++j; //维护此时的最长公共前后缀
		else {
			if (j > 0) j = d[j - 1];
			else i++;
		}
	}
}
void my_strstr(const char* t, const char* p)
{
	assert(t && p);
	int len1 = strlen(t); int len2 = strlen(p);
	pre(p);
	int i = 0, j = 0;
	while (i < len1)
	{
		if (t[i] == p[j])
		{
			i++; j++;
			if (j == len2)
			{
				printf("%d\n", i - len2+1);//打印t串中匹配成功的开始位置
				j = d[j - 1];
			}
		}
		else
		{
			if (j > 0) j = d[j - 1];
			else i++;//统统失配,新开始
		}
	}
	for (int i = 0; i < len2; i++) cout << d[i] << " ";
}
int main()
{
	cin >> t >> p;
	my_strstr(t, p);
	return 0;
}

4.strcat

#include<stdio.h>
#include <cassert>
void my_strcat(char* dest,const char* source)
{
	 assert(dest && source);
	 assert(dest != source);
	 while (*dest!='\0')  dest++;
	 while (*source != '\0') {
		 *dest = *source;
		 dest++;
		 source++;
	 }*dest = '\0';
}
int main()
{
	char str1[20]="abcd";
	char str2[] = "efg";
	my_strcat(str1, str2);
	printf("%s\n", str1);
	return 0;
}

5.strcmp

#include<stdio.h>
#include <cassert>
int my_strcmp(const char* s, const char* p)
{
	assert(s && p);
	while (*s == *p)
	{
		if (*s == '\0') return 0;
		s++; p++;
	}
	if (*s > * p) return 1;
	else return -1;
}
char str[][4] = { {"ABC"},{"BCD"},{"ACB"},{"AB"},{"AB"} };
int main()
{
	for (int i = 0; i < 5; i++) {
		for (int j = i + 1; j < 5; j++)
		{
			printf("%s %s %d\n", str[i], str[j], my_strcmp(str[i], str[j]));
		}
	}
	return 0;
}

6.strcpy

#include<stdio.h>
#include <cassert>
char* my_strcpy(char* s, const char* p)
{
	assert(s && p);
	char* ret = s;
	while (*s++ = *p++);
	return ret;

}
int main()
{
	char arr1[10] = {  };
	char arr2[5] = { "efgf" };
	printf("%s\n", my_strcpy(arr1, arr2));
	return 0;
}

7.strlen

int my_strlen(const char* str)
{
	if (*str == 0) return 0;
	return 1 + my_strlen(str + 1);
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值