【C语言】字符串函数+内存函数的总结与剖析


🥇字符串函数

  C语言中本身并没有字符串这一类型,字符串通常存放在常量字符串或者字符数组之中。而在我们平时编程的时候,却需要频繁地对字符串进行操作、处理,这时候又该怎么办呢?C语言为我们提供了一系列处理字符串的库函数,接下来我将详细地介绍这些意义非凡的函数。


1.strlen

1️⃣是什么?

  strlen函数是string头文件中最常见的一个函数,用于求字符串的长度。 strlen英文全称string length,既字符串长度,可以顾名思义地来记忆。

2️⃣具体用法:
在这里插入图片描述

  ✅这是从c++ reference上截取的介绍,strlen函数的用法就是向其传入一个字符串数组的首元素地址,其返回值是字符的个数。

⭕演示代码如下:

#include<string.h>
int main()
{
	char str[] = "abc";
	int len1 = strlen(str);
	int len2 = strlen("abc");
	printf("%d\n%d", len1,len2);

	return 0;
}

!!这里应该注意的是,"abc"表示的也是一个字符串数组,其传给strlen函数的是首元素地址。

运行结果:
在这里插入图片描述
💭下面我们试着运行一下这个代码:

#include<string.h>
int main()
{
	char str1[] = "abc";
	int len1 = strlen(str1);
	char str2[] = { 'a','b','c'};
	int len2 = strlen(str2);
	printf("%d\n%d", len1,len2);

	return 0;
}

运行结果:
在这里插入图片描述
  ❓为什么这里的str2的长度会是13呢?乍一看不是abc三个元素,长度为3吗?

这里需要补充一个知识点。在C语言中,系统会在字符串数组最后加上一个’\0’作为结束标志。而strlen函数的工作原理就是计算传入数组 (首元素地址指针指向的数组)在’\0’之前的元素个数,既该字符串长度。
而该代码块中所定义的str数组并不是一个字符串数组,因此’\0’的位置并不在最后一个元素后面,而是一个随机的位置,strlen函数会一直计算长度直到遇见’\0’才停止工作并返回结果。
因此,strlen(str2)会返回一个随机值,该值不代表该数组的长度。
在这里插入图片描述
通过调试验证了str1字符串数组末端有一个’\0’而str2没有。


2.strcpy

1️⃣是什么?

strcpy,英文全称copies strcpy,也是一个string头文件中较为常见的函数,用于将一个字符串的内容复制到另一个数组中。

2️⃣具体用法
在这里插入图片描述

(再次引用c++ reference中的描述,发现这种英文的工具网站的描述比中文的清晰地多)

   ✅strcpy的使用方法是向其传入两个指针,前者是目标数组首元素地址指针,后者是原字符串数组地址指针,将原字符串数组(包括末尾结束标志’\0’)拷贝到目标数组中。若目标数组中本身具有元素,则根据原字符串数组的长度一一覆盖。(❗注意:为了防止数组溢出,目标数组长度应大于原字符串数组。)

⭕演示代码如下:

#include<string.h>
int main()
{
	char str1[] = "abcdefg";
	char str2[] = "123";
	strcpy(str1, str2);//str2==>str1
	printf("%s", str1);//打印拷贝处理后的str1

	return 0;
}

运行结果:
在这里插入图片描述
  可见,str2的内容完美地拷贝到str1中了。
在这里插入图片描述

通过调试可以发现,str2的内容覆盖了str1前四个元素,而后面的元素依然存在。但是因为str2的’\0’也跟着拷贝过去了,所以在打印的时候,由于printf格式控制符是%s(既字符串类型),在遇到第一个’\0’时则认为它是字符串结束标志,结束打印。因此,打印出来的结果是字符串"123"。

💭综上所述,在使用strcpy函数时,我们应该注意以下几点:

  • 源字符串必须以’\0’结束
  • 会将源字符串的’\0’拷贝到目标空间中
  • 目标空间必须足够大以存放源字符串
  • 目标空间必须可变




3.strcmp

1️⃣是什么?

  strcmp函数,英文全称compares string(字符串比较), 既用于比较两个字符串,那么这里是比较字符串的什么呢?
比较规则:
先比较两个字符串的第一个字符的ASCII码值(这里视为第一对字符),(下面的比较均是ASCII码值的比较)如果第一个字符串的第一个字符大于第二个字符串的第一个字符,则返回大于0的数;反之,则返回小于0的数;若两个字符相等,则进行下一对字符的比较,直到出现有一对字符不同则返回相应的值。若两个字符串所有的字符都相同,则返回数字0。

2️⃣具体用法:
在这里插入图片描述
  传入两个指针,代表两个字符串数组的首元素地址。

⭕演示代码如下:

#include <stdio.h>
#include <string.h>
int main()
{
	char str1[20] = { 0 };
	char str2[20] = { 0 };
	scanf("%s%s", str1, str2);
	int ret = strcmp(str1, str2);

	if (ret > 0)
	{
		printf(">\n");
	}
	else if (ret < 0)
	{
		printf("<\n");
	}
	else
	{
		printf("=\n");
	}
	return 0;
}

运行结果:
在这里插入图片描述
在这里插入图片描述在这里插入图片描述

可见,这里的比较和我们所描述的相一致。
⭕这里应该注意,'\0'的ASCII码是0,它与其他字符都小。



4.strcat

1️⃣是什么?

strcat,英文全称concatenates string(连接字符串),这里很容易理解,strcat函数就是用于连接两个字符串的,那么它是如何连接的呢?接下来我将介绍一下它的具体用法。

2️⃣具体用法
在这里插入图片描述

✅如图所示,使用strcat函数需向其传入两个指针,前者是目标数组首元素地址指针,后者是源字符串数组地址指针(与strcpy类似,只不过这里是将原字符串接到目标字符串后面)。传参成功后,strcat会将原字符串的第一个字符覆盖到目标字符串的’\0’位置,后面再接着原字符串剩下的全部字符,直到遇见’\0’后结束,保证了拼接后的字符串只有一个结束标志’\0’。

⭕演示代码如下:

#include<string.h>
int main()
{
	char str1[5]="ab";
	strcat(str1, "cd");
	printf("%s\n", str1);

	char str2[5] = "ab";
	char str3[3] = "cd";
	strcat(str2, str3);
	printf("%s\n", str2);
	return 0;
}//原字符串可以用字符串数组也可以直接用常量字符串,结果相同

运行结果:
在这里插入图片描述
如果我们想让拼接后的字符串更长,能不能修改一下原字符串实现呢?让我们来试试看💭

int main()
{
	char str1[5]="ab";
	strcat(str1, "cde");
	printf("%s\n", str1);
}

运行结果:
在这里插入图片描述
运行出错,引发了异常,这是为什么呢?

💭这里是因为发生了数组溢出,由于"cde"字符串加上’\0’有四个字符,而str1我们规定了长度为5,当"cde"拼接在str1后覆盖了它的’\0’之后,拼接字符串一共有六个字符,超过了目标字符串str1的长度,无法容纳拼接后的字符串,所以发生了错误。
⭕由此我们可得,在使用strcat函数时,要规定给目标字符串一个合适的长度,防止数组溢出。

💡那么,既然我们可以实现字符串的追加,那么一个字符串是否可以追加它自己呢?
我们来看看下面的代码

int main()
{
	char str[20] = "abcd";
	strcat(str, str);
	printf("%s\n", str);
}

运行结果发生了异常
在这里插入图片描述

这是为什么呢?strcat在追加时,会把目标空间的’\0’覆盖掉,然后源字符串再依次追加直到遇到源字符串的’\0’为止。而这里的目标字符串和源字符串相同,一旦‘\0’被覆盖了,就无法停止追加,最后引发异常,陷入死循环

如图:
在这里插入图片描述

💭综上所述,在使用strcat函数时应该注意以下几点:

  • 源字符串必须以’\0’结束
  • 目标空间应该足够大以容纳拼接后的字符串
  • 目标空间必须可变
  • 字符串不能自己追加自己


🔎下面介绍几个和上述几个函数长得很像的“兄弟”函数,他们在上述函数的基础上多了一个参数n,限制了预处理字符串的字符个数,让处理字符串更加灵活起来。

5.strncpy

1️⃣是什么?

功能与strcpy相同,都是拷贝字符串,不过strncpy可以规定拷贝的字符个数

2️⃣具体用法
在这里插入图片描述

这里的参数num的意思是从源字符串复制到目标空间中的字符个数,可以由程序员自行规定。
⭕演示代码如下:

int main()
{
	char str[10] = "abc";
	strncpy(str, "defg", 3);
	printf("%s\n", str);
	return 0;
}

运行结果:
在这里插入图片描述
如果源字符串的长度小于num,则拷贝完源字符串之后,在目标的后边追加0,直到num个

int main()
{
	char str[10] = "abcxxxxxx";
	strncpy(str, "defg", 7);
	printf("%s\n", str);
	return 0;
}

在这里插入图片描述
在这里插入图片描述
如果源字符串的长度大于num,则拷贝完num个字符之后,不会再末尾再追加\0

💭使用strncpy函数依然需要注意以下几点:

  • 目标空间应该足够大以容纳拼接后的字符串
  • 目标空间必须可变

6.strncat

1️⃣是什么?

功能与strcat一样都是追加字符串,而strncat可以限制所要追加的字符个数

2️⃣具体用法
在这里插入图片描述
参数num为要从源字符串追加到目标字符串中的字符个数
⭕代码演示

int main()
{
	char str[20] = "hello ";
	strncat(str, "world", 3);
	printf("%s\n", str);
	return 0;
}

运行结果:
在这里插入图片描述

🔎值得注意的是

  1. strncat函数会在追加后的字符串末尾加上一个'\0'
  2. 当num大于源字符串长度时,strncat不同于strncpy,而是只把整个源字符串追加到目标空间

证明1:
⭕运行以下代码

int main()
{
	char str[20] = "hello ";
	*(str + 9) = 'x';
	strncat(str, "world", 3);
	printf("%s\n", str);
	return 0;
}

在这里插入图片描述
在这里插入图片描述

证明2:
⭕运行以下代码

int main()
{
	char str[20] = "hello\0xxxxxxxxxxxxx";
	strncat(str, "world", 8);
	printf("%s\n", str);
	return 0;
}

在这里插入图片描述


7.strncmp

1️⃣是什么?
在这里插入图片描述
比较到出现另个字符不一样或者一个字符串结束或者num个字符全部比较完。

2️⃣具体用法

num=3时

int main()
{
	char str1[] = "abcde";
	char str2[] = "abcdxy";
	int ret = strncmp(str1, str2, 3);
	printf("%d", ret);
	return 0;
}

在这里插入图片描述

num=5时

int main()
{
	char str1[] = "abcde";
	char str2[] = "abcdxy";
	int ret = strncmp(str1, str2, 5);
	printf("%d", ret);
	return 0;
}

在这里插入图片描述


8.strstr

1️⃣是什么

strstr,英文全称Locate substring,既定位子字符串。strstr的返回值比较特殊,它是通过传入一个母字符串和一个子字符串,然后在母字符串中定位子字符串的位置,返回值为指向母字符串中第一次出现的子字符串的指针。若子字符串不属于母字符串的一部分,则返回空指针NULL。

2️⃣具体用法
在这里插入图片描述

⭕演示代码如下:

#include <stdio.h>
#include <string.h>
int main()
{
	char str[20] = "I love you";
	char* p1 = strstr(str, "love");
	char* p2 = strstr(str, "me");
	printf("%p\n%p\n%p\n",str, p1, p2);
	return 0;
}

运行结果:

在这里插入图片描述


9.strtok

1️⃣是什么

这是一个比较“奇怪”的函数,用于切割字符串,可以把一个字符串按照特定的标记分割开来,之所以说他奇怪,是因为使用方法比较奇怪。

2️⃣具体用法
在这里插入图片描述

🎈这里的参数部分比较特殊。delimiters是一个字符串,定义了用作分隔符的字符集合('\0’也算一个)。str是指向想要分割的字符串的指针,它包含了0个或者多个由delimiters字符串中一个或者多个分隔符分割的标记。

🔎工作原理:

  • strtok函数找到str中的下一个标记,并将其用 ‘\0’ 替换,返回一个指向这个‘\0’之前字符串的指针。(注:strtok函数会改变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容并且可修改。)
  • strtok函数的第一个参数不为 NULL ,函数将找到str中第一个标记,strtok函数将保存它在字符串中的位置。
  • strtok函数的第一个参数为 NULL ,函数将在同一个字符串中被保存的位置开始,查找下一个标 记。
  • 如果字符串中不存在更多的标记,则返回 NULL 指针。

⭕演示代码如下:

#include <stdio.h>
#include <string.h>
int main()
{
	char str1[] = "abcdef@xxx.com";
	char str2[20] = { 0 };
	const char deli[] = "@.";//'\0'也是一个分割标记
	
	strcpy(str2, str1);//拷贝到str2中进行操作
	char* ret = str2;
	for (ret = strtok(str2, deli);ret != NULL;ret = strtok(NULL, deli))
	{
		printf("%s\n", ret);
	}
	return 0;
}

⭕运行结果
在这里插入图片描述
值得注意的是,当两个分隔符挨在一起时,后一个分隔符会被strtok跳过

#include <stdio.h>
#include <string.h>
int main()
{
	char str1[] = "abcdef@@xxx.com";//两个分隔符挨在一起
	char str2[20] = { 0 };
	const char deli[] = "@.";

	strcpy(str2, str1);
	char* ret = str2;

	for (ret = strtok(str2, deli);ret != NULL;ret = strtok(NULL, deli))
	{
		printf("%s\n", ret);
	}
	return 0;
}

结果相同
在这里插入图片描述


10.strerror

1️⃣是什么

返回错误码所对应的错误信息

2️⃣具体用法

#include <stdio.h>
#include <string.h>
#include <errno.h>//必须包含的头文件
int main()
{
	FILE* pFile;
	pFile = fopen("unexist.ent", "r");
	if (pFile == NULL)
		printf("Error opening file unexist.ent: %s\n", strerror(errno));
	//errno: Last error number
	//errno是一个全局变量,它的不同值对应不同的错误信息,具体取决于编译器如何实现
	return 0;
}

在这里插入图片描述


11.字符转换函数

#include <ctype.h>//包含头文件
int toupper(int c)//小写转大写
int tolower(int c)//大写转小写

注意:如果c不是字母,或者使用toupper时c本来就是大写字母、使用tolower时c本来就是小写字母,则此时c不发生变化。

🎈示例

#include <stdio.h>
#include <ctype.h>
int main()
{
	char str[] = "ABCdef";
	char* p = str;
	while (*p)
	{
		if (*p >= 65 && *p <= 90)
		{
			*p = tolower(*p);
		}
		else if(*p >=97 && *p <= 132)
		{
			*p = toupper(*p);
		}
		p++;
	}
	printf("%s\n", str);
	return 0;
}

运行结果:
在这里插入图片描述


12.字符分类函数

在这里插入图片描述

⭕利用字符分类函数我们可以改进一下11中的代码:

#include <stdio.h>
#include <ctype.h>
int main()
{
	char str[] = "ABCdef";
	char* p = str;
	while (*p)
	{
		if (isupper(*p))//利用函数进行判断
		{
			*p = tolower(*p);
		}
		else if(islower(*p))//利用函数进行判断
		{
			*p = toupper(*p);
		}
		p++;
	}
	printf("%s\n", str);
	return 0;
}

🥈 内存函数

🎈内存函数,顾名思义就是处理内存的函数。从前面的字符串函数的学习我们知道,字符串可以进行拷贝、追加、比较等操作,对字符串的操作即是字符数组的操作。那么,如果我们要处理整型数组,或是其他各种类型的数组、结构体,又该怎么做呢?显然,我们可以通过内存函数来实现。下面我将介绍几个内存函数,他们也都包含着string.h头文件中。

1.memcpy

1️⃣是什么
在这里插入图片描述

内存拷贝,函数memcpy从source的位置开始向后拷贝num个字节的数据到destination的内存位置。

💡 需要注意的是 :

  • 这个函数在遇到 ‘\0’ 的时候并不会停下来。
  • 如果source和destination有任何的重叠,复制的结果都是未定义的

2️⃣具体用法

⭕演示代码如下:

#include <stdio.h>
int main()
{
	int arr1[20] = { 0 };
	int arr2[] = { 1,2,3,4,5 };
	memcpy(arr1, arr2, 20);
	int i = 0;
	for (i = 0;i < 5;i++)
	{
		printf("%d ", arr1[i]);
	}
	return 0;
}

运行结果:
在这里插入图片描述


2.memmove

1️⃣是什么
在这里插入图片描述

memmove和memcpy的功能大同小异,唯一的区别就是memmove函数处理的源内存块和目标内存块是可以重叠的。 如果源空间和目标空间出现重叠,就得使用memmove函数处理。

2️⃣具体用法
⭕演示代码如下:

int main()
{
	char str[] = "abcdef";
	memmove(str, str + 2, 3);
	printf("%s\n", str);
	return 0;
}

⭕运行结果
在这里插入图片描述


3.memcmp

1️⃣是什么
在这里插入图片描述

比较从ptr1和ptr2指针开始的num个字节,比较规则与strcmp相同

2️⃣具体用法
⭕演示代码如下:

#include <stdio.h>
#include <string.h>
int main()
{
	char buffer1[] = "DWgaOtP12df0";
	char buffer2[] = "DWGAOTP12DF0";
	int 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;
}

⭕运行结果
在这里插入图片描述


4.memset

1️⃣是什么
在这里插入图片描述

将 ptr 所指向的内存空间的前num个字节数设置为指定值value

2️⃣具体用法
⭕演示代码如下:

int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	memset(arr, 0, 40);
	int i = 0;

	for (i = 0;i < 10;i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

⭕运行结果
在这里插入图片描述

🥉库函数的模拟实现

1.strlen模拟实现

🌈三种方法
方法1:

//计数器法
#include <stdio.h>
#include <assert.h>
int my_strlen(char* ps)
{
	assert(ps);
	int count = 0;
	while(*ps)
	{
		count++;
		ps++;
	}
	return count;
}
int main()
{
	char str[] = "abcdef";
	int len = my_strlen(str);
	printf("%d\n", len);
	return 0;
}

方法2:

//指针-指针法
#include <stdio.h>
#include <assert.h>
int my_strlen(char* ps)
{
	assert(ps);
	char* tmp = ps;
	while (*ps)
	{
		ps++;
	}
	return ps - tmp;
}
int main()
{
	char str[] = "abcdef";
	int len = my_strlen(str);
	printf("%d\n", len);
	return 0;
}

方法3:

//递归法
#include <stdio.h>
#include <assert.h>
int my_strlen(char* ps)
{
	assert(ps);
	if (*(ps + 1) == '\0')
	{
		return 1;
	}
	else
	{
		return 1 + my_strlen(ps + 1);
	}
}
int main()
{
	char str[] = "abcdef";
	int len = my_strlen(str);
	printf("%d\n", len);
	return 0;
}

2.strcpy模拟实现

#include <stdio.h>
#include <assert.h>
char* my_strcpy(char* dest, const char* src)
{
	assert(dest && src);
	char* ret = dest;
	while (*dest++ = *src++)
		;

	return dest;
}
int main()
{
	char str1[20] = { 0 };
	char str2[] = "abcdef";
	my_strcpy(str1, str2);
	printf("%s\n", str1);
	return 0;
}

3.strcmp模拟实现

#include <stdio.h>
#include <assert.h>
int my_strcmp(const char* str1, const char* str2)
{
	assert(str1 && str2);
	while (*str1 && *str2 && (*str1 == *str2))
	{
		str1++;
		str2++;
	}
	if (*str1 > *str2)
	{
		return 1;
	}
	else if (*str1 < *str2)
	{
		return -1;
	}
	else
	{
		return 0;
	}
}
int main()
{
	char str1[] = "abcd";
	char str2[] = "abef";
	int ret = my_strcmp(str1, str2);
	if (ret > 0)
	{
		printf("str1>str2\n");
	}
	else if (ret < 0)
	{
		printf("str1<str2\n");
	}
	else
	{
		printf("str1=str2\n");
	}
	return 0;
}
//改进版
#include <stdio.h>
#include <assert.h>
int my_strcmp(const char* str1, const char* str2)
{
	assert(str1 && str2);
	while (*str1 && *str2 && (*str1 == *str2))
	{
		str1++;
		str2++;
	}
	return *str1 - *str2;
	//因为返回的值只是大于0、小于0或等于0的数字,所以可以直接用两个字符做差作为返回值
}
int main()
{
	char str1[] = "abcd";
	char str2[] = "abef";
	int ret = my_strcmp(str1, str2);
	if (ret > 0)
	{
		printf("str1>str2\n");
	}
	else if (ret < 0)
	{
		printf("str1<str2\n");
	}
	else
	{
		printf("str1=str2\n");
	}

	return 0;
}

4.strcat模拟实现

#include <stdio.h>
#include <assert.h>
char* my_strcat(char* dest, const char* src)
{
	assert(dest && src);
	//找到目标空间中\0的位置
	char* tmp = dest;
	while (*tmp)
	{
		tmp++;
	}
	//从\0位置开始拷贝src
	while (*tmp++ = *src++)
		;

	return dest;
}
int main()
{
	char str1[20]="I love ";
	my_strcat(str1, "you");
	printf("%s\n", str1);
	return 0;
}

5.strstr模拟实现

#include <stdio.h>
#include <assert.h>
const char* my_strstr(const char* str1, const char* str2)
{
	assert(str1 && str2);
	const char* p1 = str1;
	const char* p2 = str2;
	const char* p = str1;
	while (*p)
	{
		while (*p1 == *p2 && *p2)//比较停止的可能情况:1.两个字符不同 2.有一个字符串遇到了\0
		{
			p1++;
			p2++;
		}
		if (*p2 == '\0')
		{
			return p;
		}
		p++;
		p1 = p;
		p2 = str2;
	}
	return NULL;
}
int main()
{
	char str[] = "abbbcde";
	char* ptr = my_strstr(str, "bbc");

	printf("%d\n", ptr - str);
	return 0;
}

⭕原理图
在这里插入图片描述

6.memcpy模拟实现

#include <stdio.h>
#include <assert.h>
void* my_memcpy(void* dest, const void* src, size_t num)
//我们无法确定内存中的数据的类型,因此需要用到泛型指针,
//再通过类型强转成char*的指针,使得每次可以访问一个字节(单位化)
{
	assert(dest && src);
	char* pd = (char*)dest;
	char* ps = (char*)src;
	while (num--)
	{
		*pd++ = *ps++;
	}
	return dest;
}
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int arr_copy[10] = { 0 };
	my_memcpy(arr_copy, arr, 40);
	int i = 0;
	for (i = 0;i < 10;i++)
	{
		printf("%d ", arr_copy[i]);
	}
	return 0;
}

7.memmove模拟实现

#include <stdio.h>
#include <assert.h>
void* my_memmove(void* dest, const void* src, size_t num)
{
	assert(dest && src);
	char* pd = (char*)dest;
	char* ps = (char*)src;
	if (pd < ps)//从前往后
	{
		while (num--)
		{
			*pd++ = *ps++;
		}
	}
	else//从后往前
	{
		while (num--)
		{
			*(pd + num) = *(ps + num);
		}
	}
	return dest;
}
int main()
{
	char str[] = "abcdef";
	my_memmove(str, str + 2, 3);
	printf("%s\n", str);
	return 0;
}

⭕原理图
在这里插入图片描述



📌总结

  🎈🎈库函数的灵活应用是程序员的一大重要技能!在总结这篇文章的时候我发现自己的string库函数以及其他库函数仍有不了解的地方,需要不断地查资料去了解。总结下来,巩固了自己对这部分知识的掌握,也希望能为您带来帮助,感谢支持!欢迎大佬雅正。

  • 22
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 20
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值