C语言常用的字符函数和字符串函数

       字符型数据是以字符的ASCII代码存储在存储单元中的,一般占一个字节。由于ASCII代码也属于整数形式,因此在C99标准中,把字符类型归纳为整型类型中的一种。C语言中没有字符串类型,字符串是存放在字符数组中的。

一、字符指针(char *类型),通过指针引用字符串

       C程序中字符串是存放在字符数组中的,想引用一个字符串有两种方法:

(1)用字符数组存放一个字符串,可以通过数组名和下标引用字符串中一个字符,也可以通过数组名和格式声明“%s”输出该字符串。

(2)用字符指针变量指向指向一个字符串常量,通过字符指针变量引用字符串常量。例如char *string = "I love China!",对字符指针变量string初始化,实际上是把字符串第1个元素的地址(即存放字符串的字符数组的首元素地址)赋给指针变量string,使string指向字符串的第一个字符,不要错误的以为把字符串赋给string。这个字符数组是没有名字的,因此不能通过数组名来引用,只能通过指针变量来引用。

需要注意:

       char *p = "abcdef"时abcdef是常量字符串,是不能更改的,如果*p = 'w'编译不会报错,因为p没有被const修饰,p的权限大了,但是程序执行起来是会出错的,加const修饰指针语法更准确,若再改编译就会报错了。

二、字符串处理函数

       字符串处理在几乎所有的编程语言中都是一个绕不开的话题,在一些高级语言当中,对字符串的处理支持度更是完善。C语言库函数中已经给我们提供了丰富的字符串处理相关函数,基本常见的字符串处理需求都可以直接使用这些库函数来实现,例如将两个字符串进行拼接、字符串查找、两个字符串进行比较等,这些库函数大致可以分为字符串的输入、输出、合并、修改、比较、转换、复制、搜索等几类,下面是本人对这些库函数的使用和注意事项所总结的笔记。

1.1 strlen(求字符串长度)

函数原型:

#include<string.h>            //使用该函数需包含此头文件

size_t strlen(const char* s);

参数s:需要进行长度计算的字符串,字符串必须包含结束字符' \0 '
返回值:返回字符串长度(以字节为单位),字符串结束字符' \0 '不计算在内。返回值是size_t类型,也即unsigned int类型。

为什么必须包含结束字符' \0 '?

图1 strlen例程1

       上面例程中字符串的两种初始化方式的区别就是有无' \0 ':arr1是用字符串常量来使字符数组初始化(注意字符串的两端是用双撇号而不是单撇号括起来的),这种方法直观方便符合人们的习惯,在字符串常量的最后由系统加上一个' \0 ',所以strlen求得的字符串长度为8;arr字符串这种初始化方式就不包含结束字符' \0 '了,所以strlen在求字符串长度,传入' a '首地址,在内存中一直向后检索直至遇见' \0 ',返回' \0 '之前字符个数,所以printf输出的是一个随机值,调试查看内存窗口可知为什么是15,如下图:

图2 strlen例程1调试

       从内存窗口可知在' a '地址后面经过15个字节才是0x00(也就是' \0 '),所以strlen返回的值是15,这是随机的,没有意义,因此strlen函数参数指向的字符串必须包含结束字符' \0 '。

char c[] = "abcde";
等价于
char c[] = {'a','b','c','d','e','\0'};

返回值size_t类型: 

图3 strlen例程3

       图3例程str2字符串strlen求得的字符串长度为3,str1的长度为5,为什么str2-str1大于0,也就是3-5大于0?就是因为strlen返回的是无符号整型,两个无符号整型相减一定是无符号整型,得出的是-3的补码作为无符号整型时的数。

利用指针-指针模拟实现strlen函数(还有count计数法和递归,不做讨论),输入相同的参数能够求出字符串长度

图4 模拟实现strlen函数

strlen和sizeof的区别:

①sizeof 是 C 语言内置的操作符关键字,而 strlen 是 C 语言库函数;

②编译器在编译时就计算出了 sizeof 的结果,而strlen必须在运行时才能计算出来;

③sizeof 仅用于计算数据类型的大小或者变量的大小,而 strlen 只能以结尾为' \0 '的字符串作为参数,一个包含' \0 ',一个不包含' \0 '

④strlen是库函数,只针对字符串,是求字符串长度的,关注的是字符串中的' \0 ',计算的是' \0 '之前出现的字符的个数;sizeof是操作符,只关注占用内存空间的大小,不管内存中放的是什么。

通过图5例程说明第③点:

图5 sizeof和strlen区别

1.2 strcpy(字符串拷贝)

函数原型:

#include<string.h>        //使用该函数需包含此头文件

char* strcpy(char* dest, const char* src);


参数dest:目标字符串。
参数src:源字符串。
返回值:返回指向目标字符串dest的指针。

strcpy()会把 src(必须包含结束字符' \0 ')指向的字符串复制(包括字符串结束字符' \0 ')到 dest,所以必须保证 dest 指向的内存空间足够大,能够容纳下 src 字符串,否则会导致溢出错误。

src必须包含结束字符' \0 ':

图6 strcpy例程1

       对上面例程调试发现在执行strcpy时报错,这是因为源字符串没有包含结束字符' \0 ' ,导致在复制字符串时不知道什么时候停止,会造成越界访问内存。

src字符串中的结束字符' \0 '也会复制过去:

图7 strcpy例程2

工作中踩过的坑:src字符串中间有' \0 ':

图8 strcpy例程3

       从上图例程调试结果可以看出str1字符串在执行复制到str2字符数组后并没有把' \0 '后面的内容复制过去,我曾经在写一个固件远程升级程序时一开始每收到一个包数据想通过strcpy函数把包中数据拷贝到缓冲区,后来调试发现每包数据都只拷过来一丁点,显而易见这是因为固件升级包一串数据中随处可见0,而' \0 '的ASCII值就是0。因此想要实现一个在数据串中间就包含' \0 '的字符串的完整拷贝用strcpy是不行的,可以用memcpy函数,它是对内存进行操作的,不关心数据内容,后面学习memcpy时再作详细说明。

目标空间必须足够大:

图9 strcpy例程4

       目标字符串如果放不下源字符串内容,会造成越界访问,程序崩溃。

目标区间必须可变:

图10 strcpy例程5

        p指向的空间是常量字符串,常量是不可变的,因此在执行strcpy时会报错。 

模拟实现strcpy函数:

#include<stdio.h>

void my_strcpy(char *dest,char *src)
{
	while (*src != 0)
	{
		*dest++ = *src++;			//*src++,先解引用,再++,自增的是地址,不是src指向的值自增
	}
}

int main()
{
	char dest[20] = { 0 };
	char src[] = "abcdefg";

	my_strcpy(dest, src);
	printf("%s\n", dest);

	return 0;
}

       上面代码有2处错误,还有很多可以精简的地方:src不可修改,在参数设计时可加const修饰;为防止空指针的出现可加assert()断言;while(*src != 0)可写为while(*src)。错误一是上述代码并没有将源字符串的' \0 '拷贝过去,因此在循环结束后要加上*dest = *src;错误二就是没有返回指向目标的字符串,可以在前面定义一个指针变量char* ret = dest;还有最完美的写法就是一句代码实现:

char* my_strcpy(char *dest,const char *src)
{
	assert(dest && src);
	char* ret = dest;
	while (*dest++ = *src++)		//*src++,先解引用,再++,自增的是地址,不是src指向的值自增
		;			
	return ret;
}

        善于使用*str++,可使指针自动移位,注意是先解引用再地址++

要注意' \0 '、' 0 '、0的区别:

' \0 '是字符斜杠零,ASCII值就是0,即' \0 ' == 0,不是' 0 ',编程易于搞混;' 0 '是字符0,ASCII值是0x30,不是0。

1.3 strcat(字符串拼接追加)

函数原型:

#include<string.h>        //使用该函数需包含此头文件

char* strcpy(char* dest, const char* src);


参数dest:目标字符串。
参数src:源字符串。
返回值:返回指向目标字符串dest的指针。

       strcat()函数会把 src 所指向的字符串追加到 dest 所指向的字符串末尾,所以必须要保证 dest 有足够的存储空间来容纳两个字符串,否则会导致溢出错误;dest 末尾的' \0 '结束字符会被覆盖,src 末尾的结束字符' \0 '会一起被复制过去,最终的字符串只有一个' \0 '。

       此函数要注意的地方与strcpy函数类似,一共三点:源字符串必须以' \0 '结尾;目标空间必须足够大;目标空间必须可修改。

模拟实现strcat函数:

#include<stdio.h>

char* my_strcat(char* dest, const char* src)
{
	char* ret = dest;
	while (*dest != '\0')		//找到目标空间的结束字符'\0'
	{
		dest++;
	}
	while (*dest++ = *src++)	//拷贝字符串
	{
		;
	}
	return ret;
}

int main()
{
	char dest[50] = "hello ";
	char src[] = "my love Yrr";
	my_strcat(dest, src);
	printf("%s\n", dest);

	return 0;
}

思考:字符串能否自己给自己追加?

不能,因为在追加时把源字符串的' \0 '整没了,会陷入死循环。两个参数一定不能是相同的指针,如果想实现自己给自己追加,可以将源字符串拷贝至另一个指针指向的字符串。

1.4 strcmp(字符串比较) 

函数原型:

#include<string.h>        //使用该函数需包含此头文件

int strcmp(const char* s1, const char* s2);


参数s1:进行比较的字符串1。
参数s2:进行比较的字符串2。
返回值:返回指小于0表示s1<s2,返回值大于0表示s1>s2,返回值等于0表示s1等于s2。

       strcmp 进行字符串比较,主要是通过比较字符串中的字符对应的 ASCII 码值,strcmp 会根据 ASCII 编码依次比较 str1 和 str2 的每一个字符,直到出现了不同的字符,或者某一字符串已经到达末尾(遇见了字符串结束字符' \0 ',' \0 '也参与比较,' \0 ' == 0)。

       字符串比较不能把字符数组名拿来直接比较,因为数组名表示的是首字符地址,地址比较不是字符串比较。

        strcmp函数一般用来比较两个字符串是否相等,大于小于根据应用场景灵活使用。

模拟实现strcmp函数:

下面是自主编程的代码:

#include<stdio.h>

int my_strcmp(const char *s1,const char *s2)
{
	while ((*s1 != 0) && (*s2 != 0))
	{
		if (*s1 == *s2)
		{
			s1++;
			s2++;
		}
		else
		{
			return *s1 - *s2;
		}
	}
	return *s1 - *s2;
}

int main()
{
	char str1[] = "abcdefg";
	char str2[] = "abcdef";
	int val = my_strcmp(str1, str2);
	if (val > 0)
	{
		printf("str1 > str2\n");
	}
	if (val < 0)
	{
		printf("str1 < str2\n");
	}
	if (val == 0)
	{
		printf("str1 = str2\n");
	}

	return 0;
}

下面是教学视频代码:

int my_strcmp(const char *s1,const char *s2)
{
	while (*s1 == *s2)
	{
		if (*s1 == 0)
		{
			return 0;
		}
		s1++;
		s2++;
	}
	return (*s1 - *s2);
}

1.5 长度受限制的strncpy、strncat、strncmp函数 

       前面讲到strcpy和strcat函数(长度不受限制的函数)使用时要注意目标字符串足够大,否则会报错,这就是这些函数不安全的地方,但是它仍然会实现部分功能,如图9中strcpy例程4。为了避免出现这些错误,C库提供了实现这些功能的相对安全的函数。

strncpy函数:

#include<string.h>        //使用该函数需包含此头文件

char* strncpy(char* dest, const char* src,size_t n);


dest:目标字符串。
src:源字符串。

n:从src中复制的最大字符数。
返回值:返回指向目标字符串dest的指针。

       把 src 所指向的字符串复制到 dest,最多复制 n 个字符。当 n 小于或等于 src 字符串长度(不包括结束字符的长度)时,则复制过去的字符串中没有包含结束字符' \0 ';当 n 大于 src 字符串长度时,则会将 src 字符串的结束字符' \0 '也一并拷贝过去,必须保证 dest 指向的内存空间足够大,能够容纳下拷贝过来的字符 串,否则会导致溢出错误。

       如果n大于源字符串中字符个数,则补' \0 ',可编程验证。

strncat函数:

#include<string.h>        //使用该函数需包含此头文件

char* strncat(char* dest, const char* src,size_t n);


dest:目标字符串。
src:源字符串。

n:要追加的最大字符数。
返回值:返回指向目标字符串dest的指针。

       如果源字符串 src 包含 n 个或更多个字符,则strncat()将 n+1 个字节追加到 dest 目标字符串(src 中的 n 个字符加上结束字符' \0 ')。

      如果n大于源字符串中字符个数,则有多少追加多少,除了加上一个结束字符 \0 ',不会像strncpy那样补' \0 ',可自行验证。

strncmp函数:

#include<string.h>        //使用该函数需包含此头文件

int strncmp(const char* s1, const char* s2,size_t n);


s1:参与比较的第一个字符串。
src:参与比较的第二个字符串。

n:最多比较前n个字符。
返回值:与strcmp函数相同。

1.6 strstr(字符串子串查找) 

#include<string.h>        //使用该函数需包含此头文件

char* strstr(const char* haystack, const char* needle);


haystack:目标字符串。
needle:需要查找的子字符串。
返回值:如果目标字符串 haystack 中包含了子字符串 needle,则返回该字符串首次出现的位置;如果未能找到子字符串 needle,则返回 NULL。

模拟实现strstr函数:

       程序实现起来比较复杂,极易出现bug,尤其是子串前几个字符相等后面不等需要复位指针再逐个比较,本人就是在这个地方查bug花了大量时间,下面是最开始自主编程代码:

#include<stdio.h>

char* my_strstr(char* str1, char* str2)
{
	char* start = NULL;

	while (*str1)
	{
		if (*str1 == *str2)
		{
			start = str1;
			while (*str1 == *str2)
			{
				str1++;
				str2++;
			}
			if (*str2 == 0)
				return start;
			else
				str1 = start;
		}
		str1++;
	}
	return NULL;
}


int main()
{
	char str1[] = "jiangdpk@kehui.cn";
	char str2[] = "kehui";
	char* ret = my_strstr(str1, str2);
	if (ret == NULL)
	{
		printf("找不到\n");
	}
	else
	{
		printf("找到了,%s\n", ret);
	}

	return 0;
}

上面代码运行结果:

图11 模拟实现strstr函数例程

       可以发现少了第一个字符,这是因为在之前查找时有一个k相等导致子串的指针向后移了一位,重新查找时没有记录子串指针起始位置导致子串变为上述结果一样少了个k。改正后的代码如下(感觉我的代码比视频中代码简单,所以不展示视频中代码):

char* my_strstr(char* str1, char* str2)
{
	char* start = NULL;
	char* init = str2;
	while (*str1)
	{
		if (*str1 == *str2)
		{
			start = str1;
			while (*str1 == *str2)
			{
				str1++;
				str2++;
			}
			if (*str2 == 0)
				return start;
			else
			{
				str1 = start;
				str2 = init;		//每次对子串向后比较不等后重新比较要把指针复位
			}
		}
		str1++;
	}
	return NULL;
}

       字符串查找相关的函数还有strchr查找给定字符串当中的某一个字符;strrchr函数同样表示在字符串中查找某一个字符,返回字符第一次在字符串中出现 的位置,如果没找到该字符,则返回值 NULL,但两者唯一不同的是,strrchr()函数在字符串中是从后到前 (或者称为从右向左)查找字符,找到字符第一次出现的位置就返回。

1.7 strtok(切割字符串) 

函数原型:

#include<string.h>        //使用该函数需包含此头文件

char* strtok(char* str, const char* sep);


sep:是个字符串,定义了用作分隔符的字符集合。
str:指定一个字符串,它包含了0个或者多个由sep字符串中一个或者多个分隔符分割的标记。

strtok函数找到str中的下一个标记,并将其用' \0 '结尾,返回一个指向这个标记的指针。strtok函数会改变被操作的字符串,所以在使用strtok函数切分的字符串一般都会临时拷贝的内容并且可修改。

strtok函数的第一个参数不为NULL:函数将找到str中第一个标记,strtok函数将保存它在字符串中的位置。

strtok函数的第一个参数为NULL:函数将在同一个字符串中被保存的位置开始,查找下一个标记。

如果字符串中不存在更多的标记,则返回NULL指针。

通过程序理解此函数功能更加直观易懂,如下:

图12 strtok函数错误例程

正确程序:

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

int main()
{
	const char sep[] = "@.";
	char str[] = "jiangdp2216@qqwzjx.com.cn";
	char cp[30] = { 0 };
	strcpy(cp, str);
	
	char* ret = NULL;
	for (ret = strtok(cp, sep); ret != NULL; ret = strtok(NULL, sep))
	{
		printf("%s\n", ret);
	}
    return 0;
}

程序运行结果(分割字符串):

图13 strtok分割字符串

1.8 strerror(错误报告函数)

       C语言的库函数在执行失败的时候,都会设置错误码。错误码是系统对常见的错误做了一个编号,每一个编号都代表着每一种不同的错误类型,当函数执行发生错误的时候,操作系统会将这个错误所对应的编号赋值给 errno 变量,每一个进程(程序)都维护了自己的 errno 变量,它是程序中的全局变量,该变量用于存储就近发生的函数执行错误编号,也就意味着下一次的错误码会覆盖上一次的错误码。所以由此可知道, 当程序中调用函数发生错误的时候,操作系统内部会通过设置程序的 errno 变量来告知调用者究竟发生了什 么错误!errno 本质上是一个 int 类型的变量,用于存储错误编号。

       但是 errno 仅仅只是一个错误编号,对于开发者来说,即使拿到了 errno 也不知道错误为何,还需要对比源码中对此编号的错误定义,而C库函数 strerror()可以将对应的 errno 转换成适合我们查看的字符串信息,十分友好。

函数原型:

#include<string.h>        //使用该函数需包含此头文件

char* strerror(int errnum);


errnum:错误编号errno。

返回值:对应错误编号的字符串描述信息。

例如在VS2022-X86 环境中打印错误码0、1、2、3对应的错误信息:

图14 strerror错误信息示例

只需要包含头文件<errno.h>头文件就可以获取errno变量,使用示例如下:

图15 strerror例程

除了 strerror 函数之外,我们还可以使用 perror 函数来查看错误信息,一般用的最多的还是这个函数, 调用此函数不需要传入 errno,函数内部会自己去获取 errno 变量的值,调用此函数会直接将错误提示字符串打印出来,而不是返回字符串,除此之外还可以在输出的错误提示字符串之前加入自己的打印信息。

#include<stdio.h>        //使用该函数需包含此头文件

void perror(const char *s);


s:在错误提示字符串信息之前,可加入自己的打印信息,也可不加,不加则传入空字符串即可。

1.9 字符串与数字互转

        C函数库中提供了一系列函数用于实现将一个字符串转为整形数据,主要包括 atoi()、atol()、atoll()以及 strtol()、strtoll();还有strtoul()、strtoull()等,它们之间的区别主要包括以下两个方面:①数据类型(int,long int,unsigned long等)。②不同进制方式表示的数字字符串(八进制、十六进制、十进制)。还有atof()、strtod()、strtof()、strtold()字符串转浮点型数据函数以及数字转字符串函数等。

       atoi()、atol()、atoll()三个函数可用于将字符串分别转换为 int、long int 以及 long long 类型的数据,它们的函数原型如下:

#include<stdlib.h>

int atoi(const char *nptr);                        //用于将字符串转换为int型数据

long atol(const char *nptr);                     //用于将字符串转换为long型数据

long long atoll(const char *nptr);         //用于将字符串转换为long long型数据

示例:

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

int main()
{
	printf("%d\n", atoi("123"));
	return 0;
}

1.10 字符串分类

        使用下列函数需包含头文件<ctype.h>,注意下表函数参数都是字符,用' '括起来。

函数如果它的参数符合下列条件就返回真(非0)
iscntrl任何控制字符
isspace空白字符:空格,换页'\f',换行'\n',回车'\r',制表符'\t'或者垂直制表符'\v'
isdigit十进制数字0~9
isxdigit十六进制数字,包括所有十进制数字,小写字母a~f,大写字母A~F
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);

例程:

图16 字符分类函数例程

        模拟实现上述字符分类函数很简单,只需一个ASCII表即可。

三、给应用程序传参

       一个能够接受外部传参的应用程序往往使用上会比较灵活,根据传入不同的参数实现不同的功能,前面编写示例代码中,信息都是硬编码在代码中的,例如fopen 打开的文件路径是固定的,意味着如果需要打开另一个文件则需要修改代码、修改文件路径,然后再重新编译、运行,非常麻烦不够灵活。其实可以将这些可变的信息通过参数形式传递给应用程序,当执行应用程序的时候,把需要打开的文件路径作为参数传递给应用程序,就可以在不重新编译源码的情况下,通过传递不同的参数打开不同的文件,当然不同应用程序需根据其需要来设计。

显而易见给应用程序传参就是给main函数传参,如果在执行应用程序时,需要向应用程序传递参数,main函数可以写成:

int main(int argc,char *argv[])
{
	//代码
}

第一个参数:传递进来的参数个数,包括应用程序自身路径名名,多个不同的参数之间使用空格分隔开来,如果参数本身带有空格、 则可以使用双引号" "或者单引号' '的形式来表示。

第二个参数:是一个字符指针数组,以字符串的形式传递参数,argv数组存放这些字符串的起始地址。

给应用程序传参例程:

#include<stdio.h>

int main(int argc,char *argv[])
{
    int i = 0;

    printf("[jdp]接收到 %d 个参数\n",argc);
    for(i=0;i<argc;i++)
    {
        printf("[jdp]第 %d 个参数:%s\n",i,argv[i]);
    }
    return 0;
}

运行测试(ZLG_M6Y2C核心板): 

图17 应用程序传参例程测试

四、字符指针笔试题

       解答字符指针类题型只需要注意对字符指针变量初始化时char *指针指向字符串首元素地址,不是指向整个字符串,因为没有字符串数据类型,就不存在这种指针;还有就是一定要画图分析指针类题型。

question  1:

图18 面试题1及测试结果

解析:因为abcdef是一个常量字符串,指针变量指向常量字符串时只有一个地址,只存了一份,所以p1和p2都是指向字符串首地址的相同的指针。而在定义数组arr1和arr2时分别创建了两个内存空间都用来存放abcdef,两个首地址肯定不等。

通过字符数组名或字符指针变量可以输出一个字符串,而对一个数值型数组,是不能企图用数组名输出它的全部元素的。

question  2:

图19 面试题2及注释

 question  3:

图20 面试题3及测试结果

解析: a是char* [ ]类型,也就是指针数组,存放的是三个字符串的三个首地址,a数组名就是首元素地址,也就是work字符串首地址的地址,所以pa是二级指针,指向一个char* [ ]指针数组,所以pa+1就指向这个指针数组的下一个元素,也就是at字符串首地址的地址,对其解引用就是at字符串首地址,所以printf输出at字符串。画图分析如下:

图21 面试题3分析

question  4:

图22 面试题4及测试结果

       首先要明确:[ ]的优先级最高,++的优先级大于*,*的优先级大于+,指针示意图如下:

图23 面试题4分析

解析:①++cpp指向cp数组的c+2,对其解引用就是c+2,再解引用就是指向“POINT”的‘P’了,所以输出POINT;

此时cpp已经指向c+2了(容易忽略造成错误分析),++cpp指向cp数组的c+1,对其解引用就是c+1,再--操作就是c(注意不是指向c+2),解引用指向“ENTER”的第一个‘E’了,再+3就指向“ENTER”的第二个‘E’了,所以输出的是ER;

③ cpp[i]相当于一次解引用,即cpp[i]等效于*(cpp+i)。此时cpp已经指向c+1了,所以cpp[-2]等价于*(cpp-2),指向c+3,再解引用指向“FIRST”的‘F’,+3指向“FIRST”的‘S’,所以输出ST;

④此时cpp仍然指向c+1,cpp[-2]并没有移动指针,cpp[-1][-1]等价于*(*(cpp-1)-1),cpp-1解引用就是c+2,再-1变为c+1,解引用指向“NEW”的‘N’,+1指向“NEW”的‘E’,所以输出EW。

易错点:不要独立分析,例如第一次printf后cpp指针已经指向c+2了,后续移动该指针就要注意了。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值