C语言库函数的模拟实现与使用(面试常考)

C语言库函数的模拟实现与使用(面试常考)


1.strlen()的使用与模拟实现

#include <assert.h>
#include<string.h>
#include<stdio.h>
//strlen函数:size_t strlen(const char* str);用来计算字符串的长度
//使用注意:
//1.字符串以'\0'作为结束标志,strlen函数返回的就是在字符串中'\0'前面出现的字符个数(不包括'\0')
//2.参数指向的字符串必须要以\0结束
//3.注意函数的返回值位size_t,是无符号的(绝对不会出现负数)
//学会模拟实现strlen函数

//strlen的使用
int main()
{
	int len = strlen("abcdef");
	printf("%d\n",len);//6
}

//strlen的模拟实现:
int my_strlen(const char* str)//使用const保护源字符串
{
	assert(str);
	int count = 0;
	while (*str!='0')//*str也可以
	{
		count++;
		str++;
	}
	return count;
}

int main()
{
	int len = my_strlen("abcdef");
	printf("%d\n", len);

	return 0;
}

2.strcpy()的使用与模拟实现

//strcpy函数:char* strcpy(char* destination,const char* source);用来拷贝字符串内容
//使用注意:
//1.源字符串必须以\0结束
//2.会将源字符串中的\0拷贝到目标空间中
//3.目标空间必须足够大,以确保能存放源字符串
//4.目标空间必须可变
//学会strcpy的模拟实现

//strcpy的使用
int main()
{
	char arr1[] = { 'a','b','c','d','e','f' };
	char arr2[20] = "xxxxxxxx";
	char arr3[3] = { 0 };
	strcpy(arr2, arr1);//报错,原因:arr1里没有\0
	strcpy(arr3, arr1);//报错,原因:arr3空间太小,放不下arr1的内容
	printf("%s\n", arr2);
	return 0;
}

//strcpy的模拟实现:
char* my_strcpy(char* dest, const char* src)
{
	char* ret = dest;//保存dest
	assert(dest && src);

	//写法1:推荐
	while (*ret)
	{
		*dest = *src;
		dest++;
		src++;
	}

	//写法2:节俭
	/*while (*dest++ = *src++)
	{
		;
	}*/
	return ret;
}

int main()
{
	char arr1[] = {'a', 'b', 'c', 'd', 'e', 'f', '\0'};
	char arr2[20] = "xxxxxxxxxxxx";
	my_strcpy(arr2, arr1);
	printf("%s\n", arr2);
	return 0;
}

3.strcat()的使用与模拟实现

//strcat函数:char* strcat(char* destination,const char* source);用来追加字符串内容
//使用注意:
//1.源字符串必须以\0结束
//2.目标空间必须足够大,能容纳下源字符串的内容
//3.目标空间必须可修改
//模拟实现strcat

//strcat的使用
int main()
{
	char arr1[30] = "hello";
	char arr2[] = " world";
	char arr3[] = { ' ','w','o','r','l','d' };
	strcat(arr1, arr2);
	strcat(arr1, arr3);//错误,光标闪烁。因为arr3没有\0结束,加入\0,就打印出hello world
	printf("%s\n", arr1);//hello world
	return 0;
}

//strcat的模拟实现:
char* my_strcat(char* dest, const char* src)
{
	char* ret = dest;
	assert(dest && src);
	//1.找到目标空间中的\0
	while (*dest)
	{
		dest++;
	}
	//2.追加内容到目标空间
	while (*dest++ = *src++)
	{
		;
	}
	return ret;
}
int main()
{
	char arr1[30] = "hello";
	char arr2[] = "world";// 或{'w', 'o', 'r', 'l', 'd', '\0'};

	printf("%s\n", my_strcat(arr1, arr2));

	return 0;
}

4.strcmp()的使用与模拟实现

//strcmp函数:int strcmp(const char* str1,const char* str2);用于字符串的比较,比较的是对应位置上的字符大小
//使用注意:
//1.第一个字符串 > 第二个字符串  返回大于0的数字
//2.第一个字符串 = 第二个字符串  返回0
//3.第一个字符串 < 第二个字符串  返回小于0的数字
//学会模拟实现stcmp


//strcmp的使用
int main()
{
	char arr1[] = "abcdef";
	char arr2[] = "abbq";
	//strcmp一个字符一个字符的进行比较,相同继续向后,不同就出结果
	int ret = strcmp(arr1, arr2);
	printf("%d\n", ret);//-1
	return 0;
}

//strcmp的模拟实现:
int my_strcmp(const char* str1, const char*str2)
{
	assert(str1 && str2);
	while (*str1 == *str2)
	{
		if (*str1 == '\0')
			return 0;
		
		str1++;
		str2++;
	}
	
	if (*str1 > *str2)
		return 1;
	else
		return -1;
	return *str1 - *str2;
}
int main()
{
	char arr1[] = "abcd";
	char arr2[] = "abc";

	int ret = my_strcmp(arr1, arr2);

	if (ret<0)
	{
		printf("arr1<arr2");
	}
	else if (ret >0)
	{
		printf("arr1>arr2");
	}
	else
	{
		printf("arr1==arr2");
	}

	return 0;
}

5.strncpy()的使用

//strncpy函数:char* strncpy(char* strDest,  const char* strSource, size_t count);用于选择拷贝count个字符
//使用注意:符合strcpy的使用要求
//1.如果拷贝的字符串内容不够你要拷贝的个数,那么多的内容用\0补充

//strncpy的使用
int main()
{
	char arr1[] = "xxxxxxxxxx";
	char arr2[] = "hello";
	int ret = strncmp(arr1, arr2, 5);//拷贝arr2的前5个字符到arr1中
	printf("%d\n", ret);//helloxxxxx

	char arr1[20] = "hello\0xxxxxxx";
	char arr2[] = "world";
	strncat(arr1, arr2, 7);
	printf("%s\n", arr1);//world\0\0xxxxxx
	strncpy(arr1, arr2, 5);
	printf("%s\n", arr1);//world\0xxxxxxx

	return 0;
}

6.strncat()的使用

//strncat:char* strcat(char* destination, const char* source,size_t count);跟strcat一样,只是多了个参数size_t count,能够控制追加的字符个数了
//使用注意:
//1.strncat追加后,会把字符串后默认追加一个\0,满足它也是一个字符串
//2.如果超出源字符串的个数,那么我追加完字符串就不追加了直接末尾放\0

//strncat的使用
int main()
{
	char arr1[20] = "hello";
	char arr2[] = "world";
	strncat(arr1, arr2, 3);
	printf("%s\n", arr1);//hellowor\0
	strncat(arr1, arr2, 7);
	printf("%s\n", arr1);//helloworld  如果超出源字符串的个数,那么我追加完字符串就不追加了直接末尾放\0


	return 0;
}

7.strncmp()的使用

//strncmp函数:int strncmp(char* string1,const char* string2,size_t count);跟strcmp一样,只是可以控制比较的个数
//使用注意:跟strcmp一样

//strncmp的使用
int main()
{
	char arr1[] = "abcdef";
	char arr2[] = "abcqqqq";
	int ret = strcmp(arr1, arr2, 3);
	printf("%d\n", ret);//0
	int ret2 = strcmp(arr1, arr2, 4);
	printf("%d\n", ret2);//-1

	return 0;
}

8.strstr()的使用与模拟实现

//strstr函数:char* strstr(const char* string,const char* strCharSet);用于字符查找,在一个字符串中查找一个子字符串
//使用注意:在string字符串中找strCharSet字符串

//strstr的使用
int main()
{
	char arr1[] = "abcdefabcdef";
	char arr2[] = "bcd";
	char* ret = strstr(arr1, arr2);
	if (NULL == ret)
		printf("没找到!\n");
	else
		printf("%s\n", ret);//找到的位置开始打印string后面的字符串
	//打印结果:bcdefabcdef
	return 0;
}

//strstr的模拟实现:
char* my_strstr(const char* str, const char* substr)
{
	const char* s1 = str;
	const char* s2 = substr;
	const char* cur = str;

	assert(str && substr);
	if (*substr == '\0')
	{
		return (char*)str;
	}
	while (*cur)
	{
		s1 = cur;
		s2 = substr;
		while (*s1 &&  *s2 && *s1 == *s2)
		{
			s1++;
			s2++;
		}
		if (*s2 == '\0')
			return (char*)cur;

		cur++;
	}
	return NULL;
}

int main()
{
	char arr1[] = "abbbcdef";
	char arr2[] = "bbcq";

	char* ret = my_strstr(arr1, arr2);

	if (NULL == ret)
		printf("没找到\n");//没找到
	else
		printf("%s\n", ret);

	return 0;
}

9.strtok()的使用

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



int main()
{
	const char* p = "@.";
	char arr[] = "jianbo@year.net";
	char buf[50] = { 0 };//通过strcpy存"jianbo@year.net"。如果通过strtok处理,第一个参数不为NULL,则会变成jianbo\0year.net,为NULL,则会变成jianbo@year\0net 
	strcpy(buf, arr);//因为strtok会修改源字符串,所以我们拷贝一份,用拷贝的字符串去进行strtok处理
	char* str = NULL;

	//原理:
	//strtok函数找第一个标记的时候,函数的第一个参数不是NULL
	//strtok函数找非第一个标记的时候,函数的第一个参数是NULL
	for (str = strtok(buf, p); str != NULL; str=strtok(NULL, p))
	{
		printf("%s\n", str);
	}

	//上面循环的处理过程:
	//char* str = strtok(buf, p);//jianbo
	//printf("%s\n", str);
	//str = strtok(NULL, p);//year
	//printf("%s\n", str);
	//str = strtok(NULL, p);//net
	//printf("%s\n", str);
	//strtok - 结束返回NULL

	return 0;
}

//C语言可以操作文件
//打开文件 - fopen
//当库函数使用的时候,发生错误会把errno这个全局的错误变量设置为本次执行库函数产生的错误码
//errno是C语言提供的一个全局变量,可以直接使用,放在errno.h文件中的
#include <errno.h>
int main()
{
	//打开文件
	FILE* pf = fopen("test.txt", "r");
	if (NULL == pf)
	{
		//出错误的原因是什么
		printf("%s\n", strerror(errno));
		return 0;
	}
	//读文件
	//...
	
	//关闭文件
	fclose(pf);
	pf = NULL;

	return 0;
}

10.strerror()的使用

//C语言中规定了一些信息,由两部分组成:错误码、错误信息
//比如:
//0 - "No Error"
//1 - 
//2 - 
//3 - 
//...


//strerror函数:char* strerror(int errnum);用于返回错误码和所对应的错误信息

//strerrror的使用:
int main()
{
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d:%s\n", i,strerror(i));
	}
	//打印:
	//0:No error
	//1:Operation not permitted
	//2:No such file or directory
	//3:No such process
	//4:Interrupted function call
	//5:Input/output error
	//6:No such device or address
	//7:Arg list too long
	//8:Exec format error
	//9:Bad file description
	return 0;
}

10.memcpy()的使用与模拟实现

//memcpy函数:void* memcpy(void* dest,const void* src,size_t count);//用于进行内存拷贝,解决strcpy只能拷贝字符串的不足

//memcpy函数的使用
int main()
{
	test1();
	char arr1[] = "abcdef";
	char arr2[20] = { 0 };
	strcpy(arr2, arr1);//拷贝字符串的

	int arr3[] = { 1,2,3,4,5,6,7,8,9,10 };
	int arr4[5] = { 0 };
	//strcpy(arr3,arr4);//错误
	//strcpy不能用来实现拷贝其他类型的内容,所以有了menmcpy函数
	memcpy(arr4, arr3, 5*sizeof(arr3[0]));
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		printf("%d\n", arr4[i]);
	}
	//打印:
	//1
	//2
	//3
	//4
	//5
	return 0;
}


//memcpy函数的模拟实现:
void* my_memcpy(void* dest, const void*src, size_t num)
{
	void* ret = dest;
	assert(dest && src);

	while (num--) 
	{
		*(char*)dest = *(char*)src;
		dest = (char*)dest + 1;
		src = (char*)src + 1;
	}
	return ret;
}

//测试my_memcpy函数,发现功能完成不了,引入memmove解决问题
//得出结论:C语言只要求memcpy能拷贝不重叠的内存空间就可以了,memmove去处理重叠内存拷贝问题!
//关于VS的库函数:memcpy也能实现重叠拷贝,这是VS的库函数对这个问题进行了处理,这并不意味着其他编辑器处理了。

void test1()
{
	int arr3[] = { 1,2,3,4,5,6,7,8,9,10 };
	//1 2 1 2 3 4 5 8 9 10
	//my_memcpy(arr3+2, arr3, 5 * sizeof(arr3[0])); 实现不了功能
	/*int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d ", arr3[i]);
	}*/
	//打印结果:1 2 1 2 1 2 1 8 9 10
	//我们发现打印结果不是我们想要的:1 2 1 2 3 4 5 8 9 10
	//原因:一个字节一个字节的拷贝,到src=3的时候,里面存的又是1,然后是覆盖打印了,所以一直打印1 2,直到达我们想要拷贝的内存地址
	//解决办法:使用memmove进行内存拷贝。memmove在拷贝内存数据的时候,可以重叠
	
	//但是:我们使用库里的memcpy却可以完成任务
	//memcpy(arr3 + 2, arr3, 5 * sizeof(arr3[0])); 执行打印结果满足
	//原因:因为VS库里的memcpy对于重叠内存进行了特殊处理,我们模拟实现它没有考虑这个问题。所以并不是模拟实现错了,而是对于复杂情况没有处理而已。并不是所有编译器的库函数都会处理这个问题
	
	//我们这里还是使用memmove来处理一下问题

	memmove(arr3 + 2, arr3, 20);
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d ", arr3[i]);
	}
	//打印结果:1 2 1 2 3 4 5 8 9 10
}

11.memmove()的使用与模拟实现

//memmove函数:void* memmove(void* dest,const void* src,size_t count);可以在源地址和目标地址内存重叠的情况下复制(移动)数据,不会出现数据覆盖的现象
//memmove函数的模拟实现:
void* my_memmove(void* dest, const void* src, size_t num)
{
	void* ret = dest;
	assert(dest && src);
	
	if (dest < src)
	{
		//前->后
		while (num--)
		{
			*(char*)dest = *(char*)src;
			dest = (char*)dest + 1;
			src = (char*)src + 1;
		}
	}
	else
	{
		//后->前
		while (num--)
		{
			*((char*)dest+num) = *((char*)src + num);
		}
	}
	return ret;
}

12.memcmp()的使用

//memcmp函数:int memcmp(const void* ptr1,const void* ptr2,size_t num);用于比较两个指针的num个字节
//使用细节:
//1.第一个指针num字节内容 > 第二个指针num字节内容  返回大于0的数字
//2.第一个指针num字节内容 = 第二个指针num字节内容  返回0
//3.第一个指针num字节内容 < 第二个指针num字节内容  返回小于0的数字

//memcmp函数的使用
int main()
{
	int arr1[] = { 1,2,7,4,5 };
	int arr2[] = { 1,2,3,4,5 };
	int ret = memcmp(arr1, arr2, 8);//比较arr1和arr2的前8个字节,也就是比较1 2
	printf("%d\n", ret);
	return 0;
}

13.memset()的使用

//memset函数:void* memset(void* dest,int c,size_t count);用于设置内存(不太灵活)

//memset函数的使用:
int main()
{
	char arr[20] = { 0 };
	memset(arr, 'x', 10);//将前10个字节内存设置为'x'  
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	memset(arr, 'x', 10);//将前10个字节内存设置为'x'
	//修改前:01 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00 ...   (00)表示一个字节
	//修改后:00 00 00 00 00 00 00 00 00 00 00 00 04 00 00 00 ...
	return 0;
}

14.atoi()的使用与模拟实现

//atoi函数:int atoi(const char* string);用来把一个字符串转换成整型  头文件#include<stdlib.h>

//atoi函数的使用:
int main()
{
	int ret=atoi("123");
	printf("%d\n", ret);//整型的123
	return 0;
}

//模拟实现atoi函数
#include <stdlib.h>
#include <assert.h>


//不正确的atoi的模拟实现
int my_atoi(char* str)//只能用来处理字符串刚好是数字形式,缺少了字符串的其他情况的处理
{
	//atoi处理需要考虑的字符串情况:
	//1. 空指针
	//2. 空字符串
	//3. 空白字符
	//4. +-
	//5. 非数字字符
	//6. 超大数字
	int n = 0;
	assert(str);//处理空字符串情况

	while (*str)
	{
		n = n * 10 + (*str - '0');
		//*str-'0':字符-字符的本质就是ASCII码的计算,单字符-'0'得到的就是这个字符的数字,比如'8'-'0'=8。因为:'8'的ASCII是72,'0'的ASCII是64
		//ASCII表中,'0'~'9'、'a'~'z'、'A'~'Z'是连续的一段
		//'0':64
		//'a':97
		//'A':65
		str++;
	}
	return n;
}

int main()
{
	int ret = atoi("123");
	printf("%d\n", ret);//整型的123
	return 0;
}


//正确的atoi的模拟实现:
#include <ctype.h>
enum State
{
	INVALID,//非法
	VALID   //合法
};

enum State status = INVALID;

int my_atoi(const char* str)
{
	//1.空指针
	assert(str);
	//2.空字符串
	if (*str == '\0')
		return 0;

	//3.空白字符(跳过)
	while (isspace(*str))
	{
		str++;
	}
	int flag = 1;
	
	//4.+-
	if (*str == '+')
	{
		str++;
		flag = 1;
	}
	else if (*str == '-')
	{
		str++;
		flag = -1;
	}

	long long n = 0;
	while (isdigit(*str))
	{
		n = n * 10 + flag*(*str - '0');
		//5.越界的值(超大的值)
		if (n > INT_MAX || n < INT_MIN)
		{
			return 0;
		}
		str++;
	}
	//6.非数字字符
	if (*str == '\0')
	{
		//合法返回
		status = VALID;
		return (int)n;
	}
	return (int)n;
}


int main()
{
	int ret = my_atoi("   -111");
	if (status == VALID)
		printf("%d\n", ret);//打印:-111
	else
		printf("非法返回\n");
	return 0;
}

扩展:qsort()的使用与模拟实现

//qsort函数的原理
//快速排序库函数:qsort  头文件#include<stdlib.h>
//函数原型:void qsort(void* base, size_t num, size_t width,  int(_cdeel* compare)(const void* eleml,const void* elem2))
//base:Start of target array,目标数组的开始,待排序的数组
//num:Array size is elements,数组的大小
//width:Element size in bytes,一个元素的字节大小
//compare:Comparison function,比较函数。如果sizeof(e1)<sizeof(e2) 返回值<0    大于则返回值>0     相等则返回值=0
//elem1:比较的e1指向的元素
//elem2:比较的e2指向的元素

//学生信息结构体:用于测试qsort
struct Stu
{
	char name[20];
	int age;
	float score;
};

//打印学生信息结构体:用于测试qsort
void print_stu(struct Stu arr[], int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%s %d %f\n", arr[i].name, arr[i].age, arr[i].score);
	}
	printf("\n");
}

//比较函数compare
//比较e1和e2指向的元素
int cmp_int(const void* e1, const void* e2)
{
	//注意:void*是一种无类型的指针,无具体类型的指针
	//void*的指针可以存放任意类型的指针
	//优点:啥类型指针都可以存放   缺点:void*的指针不能直接解引用操作,也不能直接进行+-整数
	//解决方法:用的时候进行强制类型转换为我们所需要的类型
	return *(int*)e1 - *(int*)e2;
}

//比较函数compare:按照成绩来比
int cmp_stu_by_socre(const void* e1, const void* e2)
{
	if (((struct Stu*)e1)->score > ((struct Stu*)e2)->score)
	{
		return 1;
	}
	else if (((struct Stu*)e1)->score < ((struct Stu*)e2)->score)
	{
		return -1;
	}
	else
	{
		return 0;
	}
}

//比较函数compare:按照年龄来排序
int cmp_stu_by_age(const void* e1, const void* e2)
{
	return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}

//比较函数compare:按照名字来排序,首单词比较
int cmp_stu_by_name(const void* e1, const void* e2)
{
	return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
}

//测试qsort排序结构体数据
void test2()
{
	struct Stu arr[] = { {"zhangsan",20,87.5f},{"lisi",22,99.0f},{"wangwu", 10, 68.5f} };
	//按照成绩来排序
	int sz = sizeof(arr) / sizeof(arr[0]);

	//qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_socre);
	//qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_age);
	qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_name);
	print_stu(arr, sz);
}


int main()
{
	int arr[] = { 1,4,2,6,5,3,7,9,0,8 };
	int sz = sizeof(arr) / sizeof(arr[0]);//元素个数
	bubble_sort(arr, sz);//使用冒泡排序进行排序
	qsort(arr, sz,sizeof(arr[0]),cmp_int);//用qsort进行排序,arr是目标数组,sz是目标数组的大小,sizeof(arr[0])是数组元素的大小,cmp_int是比较函数
	return 0;
}



//根据qsort原理写一个冒泡排序,能排序所有类型
//模拟实现qsort版冒泡排序,排序所有类型
void bubble_sort(void* base, int sz, int width, int(*cmp)(const void* e1, const void* e2))
{
	int i = 0;
	for (i = 0; i < sz - 1; i++)
	{
		int j = 0;
		for (j = 0; j < sz - 1 - i; j++)
		{
			//if (arr[j] > arr[j + 1])
			if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
			{
				//两个元素的交换
				Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
			}
		}
	}
}
//交换函数
void Swap(char* buf1, char* buf2, int width)
{
	int i = 0;
	for (i = 0; i < width; i++)
	{
		char tmp = *buf1;
		*buf1 = *buf2;
		*buf2 = tmp;
		buf1++;
		buf2++;
	}
}

//排数组测试
void test3()
{
	int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);
	print_arr(arr, sz);
}

//排结构体测试
void test4()
{
	struct Stu arr[] = { {"zhangsan",20,87.5f},{"lisi",22,99.0f},{"wangwu", 10, 68.5f} };
	//按照成绩来排序
	int sz = sizeof(arr) / sizeof(arr[0]);
	bubble_sort(arr, sz, sizeof(arr[0]), cmp_stu_by_name);
	print_stu(arr, sz);
}
  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

「已注销」

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

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

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

打赏作者

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

抵扣说明:

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

余额充值