学习笔记 day7 C语言:函数与内存管理

1

#include <stdio.h>
int main(int argc, const char *argv[])
{
	char *p = NULL;
	char *tmp = "12345678";
	p = (char *)(tmp + 4);
	//p[-1] = ? p[-5] = ?
	return 0;
}	
p指向字符5,p[-1]指向字符4,ascii码为52
p[-2]指向字符3,p[-3]指向字符2,p[-4]指向字符1
p[-5]指向整个字符串的前一个位置,为空,ascii值为0

2

#include<stdio.h>
#include<stdlib.h>
int main(int argc, const char *argv[])
{
	char *data="12345678";
	short *tmp=NULL;
	char p[6]={0};
	
	tmp=(short *)&p[2];
	*tmp=atoi(&data[4]);
	for(int i=0;i<6;i++){
		printf("%x ",p[i]);
	}
	putchar(10);
	return 0;
}
result:0 0 2e 16 0 0
tmp=(short *)&p[2];   //tmp指向p[2]的地址
*tmp=atoi(&data[4])   //p[2]=5678,二进制为0001 0110 0010 1110
以小端序存入   p[2]存入0010 1110   剩下一个字节存入p[3] 0001 0110
所以p[2]=2e   p[3]=16

3 实现字符串内单词的反转

#include<stdio.h>
int main(int argc, const char *argv[]){
	char s[]="hello world nihao beijing";
	char t[100]={0};
	char *p[100];    //定义一个指针数组
	int i=1,j=0,k=0;
	int a=0,b=0;
	p[a++]=s;
	while(s[i]){
		if(s[i]==' '){
			p[a++]=&s[i+1];
		}
		i++;
	}
	for(j=a-1;j>=0;j--){
		k=0;
		while(*(p[j]+k)!=' ' && (*(p[j]+k))){
			t[b++]=*(p[j]+k);
			k++;
		}
		t[b++]=' ';
	}
	t[b]='\0';
	puts(s);
	puts(t);
	return 0;
}

4 a[N],1到N-1个整数,有一个重复的整数,设计一个时间复杂度为O(N)的函数找出这个整数。

#include<stdio.h>
int main(int argc, const char *argv[])
{
	int a[]={1,3,6,4,2,4,5};
	int t;
	while(a[0]!=a[a[0]]){
		t=a[0];
		a[0]=a[t];
		a[t]=t;
	}
	printf("出现次数最多的数为:%d ",a[0]);
	putchar(10);
	return 0;
}

1 malloc与free函数

#include <stdlib.h>   都包含在stdlib.h头文件下
void *malloc(size_t size);
功能:在堆区动态开辟一段内存空间
参数:size:申请空间的字节个数
返回值:
    成功:返回申请到空间的首地址,此处void*为万能指针类型
	失败:返回NULL    
void free(void *ptr);
功能:释放一段在堆区开辟的空间
参数:ptr:要释放的目标空间的首地址
返回值:无
注意:malloc和free一定要成对出现,否则可能会出现内存泄漏。
	释放完空间之后,指针要置空,防止产生野指针,也称为指针悬挂。

2 把malloc’封装成函数时

bool InitFunc(int **ptr)//此处应使用一个二级指针来作为函数参数,若要使用一级指针,函数类型应为指针函数,表示用指针p在堆区开辟20个字节
{
	*ptr = (int *)malloc(20);  
	if(NULL == *ptr)      //判断指针p所指向的内容是否为空
	{
		printf("malloc faliure !\n");
		return false;    //内容为空直接直接返回false
	}
	return true;
}
int main(int argc, const char *argv[])
{
	int *p = NULL;
	InitFunc(&p);         //调用InitFunc函数,参数应该为&p
	for(int i = 0 ; i < 5;i++)
	{
		p[i] = i + 1;
	}
	for(int i = 0 ; i < 5;i++)
	{
		printf("p[%d] = %d\n",i,p[i]); //输出可直接写成p[i],或者写为*(p+1),不能写成*p+i  
	}
	free(p); 
	p = NULL; 
	q = NULL;
	return 0;
}	


3 回调函数

是一个通过函数指针调用的函数。如果把函数指针传递给另一个函数,当这个函数指针被用来调用它所指向的函数时,这个函数就是回调函数。
typedef int(*T)(int,int);   //声明T为函数指针类型
int Max(int x,int y){}    //定义两个函数,Max和Min
int Min(int x,int y){}
void MaoPao(int *a,int length,T p){}   //定义MaoPao函数
int main(int argc, const char *argv[])
{
	MaoPao(a,length,Min);         //在MaoPao函数的参数中调用函数指针
}	

4 define的其他用法

int min;
#define MAX(a,b) ({int max;if(a > b) max = a;else max = b;max;})  
//最后一个语句就是整个宏的返回值

#define MIN(a,b) do{if(a < b) min = a;else min = b;}while(0) 
//最后一句话不是返回值

#define PRINT_MSG(msg) do{printf("%s\n",msg);return -1;}while(0)  
//注意:这里的return不是宏函数的返回值,而是替换到main函数中,作为main函数的返回值

#define ERR_INTERNET_DISCONNECTED -5
#define S(n) #n  //#代表字符串化
#define STRING "helloworld"
#define NAME(a,b) a##b  //##代表标识符拼接

printf("%s\n",S(10));                                    //结果为10,为字符串
printf("%d\n",ERR_INTERNET_DISCONNECTED);            //-5
printf("%s\n",S(ERR_INTERNET_DISCONNECTED));//结果为ERR_INTERNET_DISCONNECTED
printf("%s\n",S(-5));                                    //结果为-5,字符串
printf("hello = %s\n",NAME(STRING,));                    //结果为helloworld
printf("%s\n",NAME(STR,ING));                          //结果为helloworld

5 存储类型

static:
		1.限定作用域只能在本文件中使用(修饰的变量或者函数)
		2.延长变量的生命周期n
const:修饰的变量是只读变量,const修饰的局部变量在栈上
		const修饰的全局变量,在.ro段
extern:延长变量或者函数的作用域
		扩展其他文件的作用域到本文件中
extern int num1 = 400;  //如果初始化,会造成变量的重复定义
volatile:保证数据每次都是从内存中获取的最新值,而不从缓存中取值,防止编译器对代码进行优化。
使用场景:
			1.在多线程中访问同一个变量
			2.在使用c语言操作硬件地址的时候,这个地址就需要加volatile
			3.在中断处理函数和进程间都访问到的变量,这个变量就适合加上volatile。
volatile int *p = (volatile int *)0xC001a000;
*p = 1;
#define p *((volatile int *)0xc001a000)
p = 1;
p = 2;

6 内存管理

栈区:	局部变量,正在被调用的函数,形参等都在栈区,
		操作系统自动申请自动释放,释放的时候,系统不会将
		栈区的值清0,不初始化的化,其都是一些随机值
堆区:使用malloc分配的内存都在堆区,其特点:空间大,需要手动申请,手动释放
静态区:  
.bss:使用static修饰的未初始化的变量(全局和局部)和全局未初始化的变量
		.data:使用static修饰的已初始化的变量(全局和局部)和全局已初始化的变量
		.text:文本段(代码段)
		.ro:只读数据段
		char *p = "helloworld";
		const int a;  //全局变量
#include <stdio.h>
static int count; // .bss段
int num = 10; //.data段
int num2;  //.bss段
const int a2; //.ro段
int main(int argc, const char *argv[])
{
	int a; //栈区
	int *p = (int *)malloc(sizeof(int) * 10);//p:栈区    malloc:堆区
	const int a1; //栈区
	static int count; //.bss段
	static int count2 = 900; //.data

	char *s = "nihao";  //s:栈区   “nihao”://.ro段
	return 0;
}

7 数组指针与指针数组

数组指针:
		数组指针,即是指向数组的指针,数组指针中存放的是数组的地址。数组指针也称行指针。

指针数组:
		是一个由n个指针类型元素(地址)组成的数组,当一个数组中的元素为地址时,这个数组就可以认为是一个指针数组。

数组指针:

int(*p)[n];    //定义了指向含有n个元素的数组指针
int a[n];        //定义数组
p=a;             //将一维数组首地址赋值给数组指针p

int(* p)[4];   //定义了指向含有4个元素的一维数组的指针
int a[3][4];
p=a;  //将二维数组的首地址赋值给p,也可是a[0]或&a[0][0]
p++;           //表示p跨过行a[0][ ],指向了行a[1][ ]

指针数组:

int *p[3];     //定义指针数组
int a[3][4];     //定义二维数组
for(i=0;i<3;i++)
	p[i]=a[i];      //通过循环将a数组每行的首地址分别赋值给p里的元素  

8 函数指针与指针函数

函数指针:即指向这个函数的指针,定义为“数据类型 (*fun)(参数列表)”
指针函数:即返回值为指针的函数,定义为“数据类型 *fun(参数列表)”

在c语言中,变量有地址,函数也有地址。把函数的地址赋给函数指针,再通过函数指针调用这个函数就可以了。
		函数指针工程应用作用:
				1 隐藏调用接口和调用形式(提供统一的调用方式)
				2 间接体现多态,提高代码的扩展性
①	 定义函数指针,如int (*p)(int,int)
②	 定义函数,如int fun(int x,int y)
③	 把函数的地址赋给函数指针,即p=fun;或者p=&fun
④	 通过函数指针调用函数,p(a,b)或者(*p)(a,b)

当函数的返回值为指针类型时,应该尽量不要返回局部变量的指针,因为局部变量是定义在函数内部的,当这个函数调用结束了,局部变量的栈内存也被释放了,因此不能够正确得到返回值。实际上,在内存被释放后,这个指针的地址已经返回,该地址已经是无效的,此时,对这个指针的使用是很危险的。

9 数组和指针的区别

空间分配:数组静态分配、指针动态分配
	动态分配:
		能在需要新内存的时候得到内存,不需要内存时就显式释放这部分内存,这种在程序运行时获取新内存空间的过程称为动态分配。
	静态分配:
		当声明一个全局变量时,编译器给在整个程序中持续使用的变量分配内存空间,这种分配方式称为静态分配,因为变量分配到了内存的固定位置。
访问效率:数组的地址是连续的,数组效率高,指针更加灵活。
安全性:数组安全性高,指针容易造成野指针、内存泄漏等问题。
函数形参:当数组作为函数形参时,会退化成指针,传入指针数组时,数组名退化为二维指针

10 复杂指针声明

int *(*(*fp1)(int))[10];
fp1是一个**函数指针**,指向的函数形参为1个int型,返回值为指针,指针指向具有10个int*类型的数组。

右左法则:从变量名开始,从右开始看,碰到括号就调转方向。

一 多级指针

image-20221205174126742
#include <stdio.h>

int main(int argc, const char *argv[])
{
	int a = 100;
	int *p = &a;  //一级指针保存普通变量的地址
	int **q = &p; //二级指针保存一级指针的地址
	int ***w = &q;//三级指针保存二级指针的地址

	//printf("a = %d %d %d %d\n",a,*p,**q,***w); //打印a的值
	//&&a不能这么做,因为&a的结果是一个常量,常量不能取地址
	printf("p = %p %p %p %p\n",&a,p,*q,**w);  //打印a的地址
	printf("q = %p %p %p\n",&p,q,*w);  //打印p的地址
	printf("w = %p %p\n",&q,w);    //打印q的地址
	return 0;
}

二 const关键字

6.1 全局变量和局部变量

const修饰全局变量:该变量存放在.ro段上(常量区)    
const修饰局部变量: 该变量存放在栈上

6.2 const修饰全局变量和局部变量

#include <stdio.h>
const int num = 100; //全局变量

int a;  //未初始化的全局变量默认值为0
int main(int argc, const char *argv[])
{
	//num = 900;  被const修饰的全局变量不可以通过变量名去修改,编译会报错
	int *p = &num;
	//(*p)++;    //通过指针去修改全局变量会引发程序崩溃
	printf("num = %d\n",num);
	
	const int num2 = 200;  //const修饰的局部变量存放在栈区
	//num2 = 300;  //不可以通过变量名修改

	int *q = &num2;
	*q = 500;  //可以通过指针修改被const修饰的局部变量
	printf("num2 = %d\n",num2);
	
	int a = 200;  //当全局变量和局部变量命名冲突的时候,根据就近原则,优先使用局部变量
	printf("a = %d\n",a);
	return 0;
}

6.3 const修饰指针变量和修饰指针变量的类型

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fUHDxCcp-1677223480010)(D:\1aaaasuqian\day\img\image-20221205174148917.png)]

#include <stdio.h>

int main(int argc, const char *argv[])
{
	int a = 100,b = 80;
	//const int *p = &a; //const修饰的是*p,表示p指向的地址的值是一个常量,p的值可以被修改
	//int * const p = &a;  //const修饰的是p,表示p的值是一个常量,但是p指向的地址的值可以被修改
	//int const *p = &a;   //同第六行代码
	const int * const p = &a; //第一个const修饰的是*P,第二个const修饰的是p,都不能修改
	printf("a = %d,p = %p\n",a,p);
	//*p = 300;
	//p = &b;
	printf("a = %d,p = %p\n",a,p);
	return 0;
}

三 register关键词

register修饰的变量会在寄存器中开辟空间,使得运行效率大大提高。
寄存器空间不一定能申请到,如果申请不到,就会自动变成auto修饰
#include <stdio.h>

int main(int argc, const char *argv[])
{
	register int i,j;
	//&i;被register关键词修饰的变量不能取地址
	//&j;
	for(int i = 0 ; i < 10000;i++)
	{
		for(j = 1;j < 10000;j++)
		{

		}
	}
	return 0;
}
static,extern

四 函数

8.1 函数的相关概念

函数就是将一对要执行的代码放在一个代码块中,然后给这个代码起个名字,只要使用这个名字,就相当于使用这段代码,所以说函数很好的解决了代码的重用性,函数的编写尽量保证函数的功能单一性。
1.通过函数名找到函数的入口地址(函数名就是地址)   int main(int argc,char *argv[])
2.给形参分配空间
3.传参,传值(把实参的值传递给形参的值)(值传递,地址传递)
4.执行函数体
5.结果返回到调用的地方
6.释放空间(栈空间)

8.2 定义一个函数以及函数的调用

#include <stdio.h>
/*
 *日期:
 *作者:
 *功能:
 *参数:
 *返回值:
 * */
//第一步:写出函数名
//第二步:实现函数的功能
//第三步:实现函数功能的过程中,如果过有一些参数需要从外部获取,考虑传参(实现形参)
//第四步:根据具体的需要决定返回值的类型

int Func(int x,int y);   //函数的声明
int Func(int x,int y);   //函数的声明可以声明多次
//void Func2();
int main(int argc, const char *argv[])
{
	printf("helloworld\n");
	int a = 1,b = 2,c = 3;
	int ret = printf("a = %d,b = %d,c = %d\n",a,b,c);
	printf("ret = %d\n",ret);


	ret = Func(a,b);
	printf("ret = %d\n",ret);
	printf("%d\n",Func(b,c));
	return 0;
}
int Func(int x,int y)   //函数的定义或者叫函数的实现
{
	return x + y;
}
/*int Func(int x,int y)   //函数的定义只能定义一次
{
	return x + y;
}*/


8.3 函数中调用函数

#include <stdio.h>

void func1();
void func2();
void func3();
void func4();
int main(int argc, const char *argv[])
{
	func1();
	return 0;
}

void func1()
{
	printf("this is func1!\n");
	func2();
}
void func2()
{
	printf("this is func2!\n");
	func3();
}
void func3()
{
	printf("this is func3!\n");
	func4();
}
void func4()
{
	printf("this is func4!\n");
	//func1();
}

练习:自己实现strcmp函数的功能

int MyStrcmp(const char *s1, const char *s2);
#include <stdio.h>
int MyStrcmp(const char *s1,const char *s2)
{
	const char *p = s1,*q = s2;
	while(*p == *q)
	{
		if(*p == '\0')
		{
			return *p -*q;
		}
		p++;
		q++;
	}
	return *p -*q;
}
int main(int argc, const char *argv[])
{
	char ch1[] = "helloeorld";
	char ch2[] = "hellow";
	int ret = MyStrcmp(ch1,ch2);
	printf("ret = %d\n",ret);
	return 0;
}

8.4 函数的传参方式

函数的传参方式一般分为三种:
全局变量传参:
	一般不用,因为全局变量本身就可以在函数的内部直接使用,不需要专门传参。
值传参:将实参的值传递给形参
地址传递:将实参的地址传递给形参

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4e12Ah4T-1677223480013)(D:\1aaaasuqian\day\img\image-20221205174228284.png)]

#include <stdio.h>
void Swap(int *x,int *y)
{
	int temp = *x;
	*x = *y;
	*y = temp;
}
int main(int argc, const char *argv[])
{
	int a = 100,b = 200;
	printf("a = %d,b = %d\n",a,b);
	//Swap(a,b);
	Swap(&a,&b);
	printf("a = %d,b = %d\n",a,b);
	return 0;
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1wepNue8-1677223480014)(D:\1aaaasuqian\day\img\image-20221205174244270.png)]

8.5 一维数组的传参

#include <stdio.h>
#include <string.h>
//void Func(int *p,int lenth)
//void Func(int p[],int lenth)
void Func(int p[100],int lenth)
{
	for(int i = 0;i < lenth;i++)
	{
		printf("%d ",p[i]);
	}
	putchar(10);
}

void Func2(char *s)
{
	char *tmp = s;
	//printf("s = %s\n",s);
	while(*tmp != '\0')
	{
		printf("%c",*tmp++);
	}
	putchar(10);
	printf("sizeof(s) = %ld\n",sizeof(s));
	printf("strlen(s) = %ld\n",strlen(s));

}
int main(int argc, const char *argv[])
{
	int arr[] = {1,2,3,4,4,5,6,7};	
	//一维数组的传参:数组首元素地址 + 数组的长度
	int len = sizeof(arr)/sizeof(arr[0]);
	Func(arr,len);


	char ch1[] = "helloworld";
	Func2(ch1);
	return 0;
}
 作业1:编写函数实现strlen,strcpy,strcat的功能
    size_t strlen(const char *s);
    char *strcpy(char *dest, const char *src);
    char *strcat(char *dest, const char *src);
 作业2:编写函数,实现冒泡排序
 作业3:有一个字符串,找出重复次数最多的那个字符,比如"abccccdddee"中的字符'c'.
 作业410q

8.6 二维数组的传参

#include <stdio.h>
//二维数组传参
//1.传数组指针
//2.传原型
//void func(int (*p)[4])  
void func(int p[3][4])  
{
	int i,j;
	for(i = 0 ; i < 3;i++)
	{
		for(j = 0;j < 4;j++)
		{
			printf("%-5d",p[i][j]);
		}
		putchar(10);
	}
}
int main(int argc, const char *argv[])
{
	int a[3][4] = {10,20,30,40,50,60,70,80,90,100,110,120};
	func(a);
	return 0;
}

8.7 指针数组传参

#include <stdio.h>
//指针数组传参:
//1.传二级指针
//2.传指针数组
//void func(char **p,int length)
void func(char *p[],int length)
{
	int i;
	for(int i = 0 ; i < length;i++)
	{
		printf("%s\n",p[i]);
	}
}
int main(int argc, const char *argv[])
{
	char *s[4];
	char ch1[] = "helloworld";
	char ch2[] = "nihao";
	char ch3[] = "hello nanjing";
	char ch4[] = "welcome to jsetc";
	s[0] = ch1;
	s[1] = ch2;
	s[2] = ch3;
	s[3] = ch4;
	int len = sizeof(s)/sizeof(s[0]);
	func(s,len);
	return 0;
}

8.8 命令行参数

#include <stdio.h>
//argc:存储的是命令行参数的个数
//argv:表示命令行参数的内容
int main(int argc, const char *argv[])
{
	if(argc < 3)
	{
		printf("error,命令行参数太少!\n");
		return -1;
	}
	printf("argc = %d\n",argc);
	for(int i = 0 ; i < argc;i++)
	{
		printf("%s\n",argv[i]);
	}
	return 0;
}

五 指针函数

指针函数:本质是一个函数,函数的返回值是一个地址
#include <stdio.h>
#include <string.h>
char *FindSub(const char *ch1,const char *ch2)
{
	char *p = (char *)ch1,*q = (char *)ch2;
	int Ch1Len = strlen(p);
	int Ch2Len = strlen(q);
	for(int i = 0 ; i < Ch1Len - Ch2Len + 1;i++)
	{
		if(strncmp(p,q,Ch2Len) == 0)
		{
			return p;
		}
		p++;
	}
	return NULL;
}
int main(int argc, const char *argv[])
{
	char ch1[] = "helloworld";
	char ch2[] = "oword";
	char *p = FindSub(ch1,ch2);
	if(NULL != p)
	{
		printf("p = %s\n",p);
	}
	else
	{
		printf("ch2 不是ch1的子串!\n");
	}
	return 0;
}```

六 malloc/free

#include <stdlib.h>
void *malloc(size_t size);
功能:在堆区动态开辟一段内存空间
参数:size:申请空间的字节个数
返回值:
    成功:返回申请到空间的首地址,此处void*为万能指针类型
	失败:返回NULL    
void free(void *ptr);
功能:释放一段在堆区开辟的空间
参数:ptr:要释放的目标空间的首地址
返回值:无
注意:malloc和free一定要成对出现,否则可能会出现内存泄漏。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OjjmkWnS-1677223480014)(D:\1aaaasuqian\day\img\image-20221207174225789.png)]

#include <stdio.h>
#include <stdlib.h>
int main(int argc, const char *argv[])
{
	//int a[5]  ={1,2,3,4,5};  //在栈区开辟5个int,并且将其赋值为 1,2,3,4,5

	int *p = (int *)malloc(20);  //表示用指针p在堆区开辟20个字节
	if(NULL == p)
	{
		printf("malloc faliure !\n");
		return -1;
	}
	int *q = p;
	for(int i = 0 ; i < 5;i++)
	{
		p[i] = i + 1;
		//*q++ = i + 1;
	}

	for(int i = 0 ; i < 5;i++)
	{
		printf("p[%d] = %d\n",i,p[i]);
	}

	free(p);  //释放p开辟的一段内存(此处参数为开辟的空间的首地址)
	p = NULL;  //释放完空间之后,指针要置空,防止产生野指针,也称为指针悬挂
	q = NULL;
	return 0;
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-es8ZdoEv-1677223480015)(D:\1aaaasuqian\day\img\image-20221207174202477.png)]

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
bool InitFunc(int **ptr)
{
	*ptr = (int *)malloc(20);  //表示用指针p在堆区开辟20个字节
	if(NULL == *ptr)
	{
		printf("malloc faliure !\n");
		return false;
	}
	return true;

}

int main(int argc, const char *argv[])
{
	//int a[5]  ={1,2,3,4,5};  //在栈区开辟5个int,并且将其赋值为 1,2,3,4,5
	int *p = NULL;
	InitFunc(p);
	int *q = p;
	printf("q = %p\n",q);
	for(int i = 0 ; i < 5;i++)
	{
		p[i] = i + 1;
		//*q++ = i + 1;
	}

	for(int i = 0 ; i < 5;i++)
	{
		printf("p[%d] = %d\n",i,p[i]);
	}

	free(p);  //释放p开辟的一段内存(此处参数为开辟的空间的首地址)
	p = NULL;  //释放完空间之后,指针要置空,防止产生野指针,也成为指针悬挂
	q = NULL;
	return 0;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

牛奶奥利奥

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值