C语言 字符串

引言

C语言中对字符和字符串的处理很频繁,但 C语言本身是没有字符串类型的,所以字符串通常放在常量字符串中或者字符数组中。

一、字符串的创建方式

程序清单:

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

int main()
{
	char arr1[] = "abcdef";
	char arr2[] = "abc\0def";
	char arr3[] = { 'a', 'b', 'c', 'd', 'e', 'f' };
	char arr4[] = { 'a', 'b', 'c', 'd', 'e', 'f', '\0'};

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

	printf("%d\n", strlen(arr1));
	printf("%d\n", strlen(arr2));
	printf("%d\n", strlen(arr3)); // 随机值
	printf("%d\n", strlen(arr4));

	return 0;
}

输出结果:

1-1

调试窗口:

1-2

注意事项:

① 在 C语言中,被双引号括起来是字符串字面值,简称为字符串。同样地,字符串也只能被双引号括起来。

② 字符串的结束标志是一个 ’ \0 ’ 的转义字符。在使用格式化输出时,’ \0 ’ 的作用相当于告诉了编译器,它是一个停止的标志。在使用 strlen 这个库函数计算字符串的长度时,也是一样的道理,它只计算 ’ \0 ’ 之前的长度。

③ 对比 arr1 和 arr3 这两个创建字符数组的方式,可以发现,由于在 C语言 中,内存具有连续性,所以如果没有 ’ \0 ’ 作为结束标志,就会导致我们使用 printf / strlen 的时候,一直向后找 ’ \0 ’ 这个结束标志。

1-3

二、字符串函数

1. strlen 函数

strlen - string length - 计算字符串长度

size_t strlen ( const char* str );

// 返回值:字符串中出现的字符个数(不包含 '\0')
// 参数:需要计算的起始位置的指针

这里应该注意 strlen 返回的类型为无符号数。

使用示例1

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

int main() {

	char arr[] = "abcdef";
	char arr2[] = "abc\0def";
	printf("%d\n", strlen(arr)); // arr 表示数组名,即数组的起始地址
	printf("%d\n", strlen(arr2));

	return 0;
}

// 输出结果:
// 6
// 3

使用示例2

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

int main(){
	const char* str1 = "abcdef";  // 6
	const char* str2 = "bbb";	  // 3

	if (strlen(str2) - strlen(str1) < 0){
		printf("正确\n");
	}else{
		printf("错误\n");
	}

	return 0;
}

// 输出结果:错误

注意: 由于 strlen 函数的返回值是无符号类型的,所以我们所理解的 " -3 ",其实输出的是一个很大的正整数。

模拟 strlen 函数

#include <stdio.h>
#include <assert.h>

size_t my_strlen(const char* start) {
	assert(start != NULL);

	int count = 0;
	while (*start != '\0') {
		start++;
		count++;
	}
	return count;
}

int main() {

	char arr[] = "abcdef";
	char arr2[] = "abc\0def";
	printf("%d\n", my_strlen(arr));
	printf("%d\n", my_strlen(arr2));

	return 0;
}

// 输出结果:
// 6
// 3

2. strcpy 函数

strcpy - string copy - 字符串拷贝

char* strcpy(char* destination, const char* source );

// 返回值:返回 destination 的副本
// 参数:destination 为粘贴的起始指针,source 为字符串复制的起始指针

注意事项:

① 源字符串必须以 ‘\0’ 结束。
② 拷贝操作会将源字符串中的 ‘\0’ 拷贝到目标空间。
③ 目标空间必须足够大,以确保能存放源字符串。
④ 目标空间必须为一个可修改的字符串,而不是字符串常量值。

使用示例

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

int main() {

	char arr1[] = "hello world";
	char arr2[] = "xxxxxxxxxxxxxx";

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

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

	return 0;
}

// 输出结果:
// xxxxxxxxxxxxxx
// hello world

调试过程:

1-4

注意事项:

在调试窗口中,我们可以看到 arr2 数组中也被存入了 ‘\0’ 字符。由于 printf 打印的时候,是将 ‘\0’ 之前的字符作为输出内容的,所以后面的 XX 便不会被打印出来。

模拟 strcpy 函数

模拟思想:将目标空间的字符,进行逐个替换。

#include <stdio.h> 
#include <assert.h>

char* my_strcpy(char* end, char* start) {

	assert(start != NULL);		// 断言
	assert(end != NULL);

	char* ret = end;			// 返回粘贴数组的起始地址

	while (*start != '\0') {
		*end = *start;			// 解引用替换值
		start++;
		end++;
	}
	*end = *start;				// 把 '\0' 也进行替换

	return ret;
}

int main() {

	char arr1[] = "hello world";
	char arr2[] = "xxxxxxxxxxxxxx";

	printf("%s\n", arr2);
	printf("%s\n", my_strcpy(arr2, arr1)); // 函数的链式访问

	return 0;
}

// 输出结果:
// xxxxxxxxxxxxxx
// hello world

3. strcat 函数

strcat - 字符串追踪

char* strcat ( char* destination, const char* source );

// 返回值:返回 destination 的副本
// 参数:destination 追踪目标的指针,source 为字符串的源头指针

注意事项:

① 源字符串必须以 ‘\0’ 结束。
② 追踪操作会将源字符串中的 ‘\0’ 放到目标空间。
② 目标空间必须有足够的大,能容纳下源字符串的内容。
③ 目标空间必须为一个可修改的字符串,而不是字符串常量值。

使用示例

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

int main() {

	char arr1[20] = "hello";
	char arr2[] = "world";
	printf("%s\n", arr1);

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

	return 0;
}

// 输出结果:
// hello
// helloworld

模拟 strcat 函数

模拟思想:

1. 找到目标空间的 ‘\0’.
2. 对目标空间 ‘\0’ 之后的字符,进行字符串替换。

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

char* my_strcat(char* dest, const char* source) {
	assert(dest != NULL);
	assert(source != NULL);
	char* ret = dest;

	// 1. 找到目标空间的 '\0'
	while (*dest != '\0') {
		dest++;
	}

	// 2. 进行字符拷贝
	while (*source != '\0') {
		*dest = *source;
		dest++;
		source++;
	}
	*dest = *source;
	return ret;
}

int main() {

	char arr1[20] = "hello";
	char arr2[] = "world";
	printf("%s\n", arr1);
	printf("%s\n", my_strcat(arr1, arr2));

	return 0;
}

// 输出结果:
// hello
// helloworld

4. strcmp 函数

strcmp - string compare - 字符串比较

int strcmp ( const char * str1, const char * str2 );

// 参数:两个待比较字符串的起始指针
// 返回值:
// 第一个字符串大于第二个字符串,则返回大于 0 的数字
// 第一个字符串等于第二个字符串,则返回 0
// 第一个字符串小于第二个字符串,则返回小于 0 的数字

注意事项:

strcmp 函数比较不是字符串的长度,而是比较字符串中对应位置上的字符大小,如果相同就比较下一对,直到两个比较的字符不同或者都遇到了 ‘\0’ 才会停止。实际上,在 C语言 中,比较的也就是字符对应的 ASCII 码值。

使用示例

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

int main() {

	char arr1[] = "abcd";
	char arr2[] = "abc";
	char arr3[] = "abcz";
	char arr4[] = "abcd";
	char arr5[] = "abz";

	printf("%d\n", strcmp(arr1, arr2));
	printf("%d\n", strcmp(arr1, arr3));
	printf("%d\n", strcmp(arr1, arr4));
	printf("%d\n", strcmp(arr1, arr5));

	return 0;
}

// 输出结果:
// 1
// -1
// 0
// -1

注意事项: 虽然我们看到在 VS 编译器底下输出的 1,-1,0. 但在官方的标准文档中,输出规定的是大于0,小于0,或者为0.

模拟 strcmp 函数

模拟思想:挨个比较字符之间的 ASCII 码值,相同的字符就直接往后跳,如果遇到两个不同的字符,则开始比较。直至遇到 ‘\0’ 结束。

#include <stdio.h>
#include <assert.h>

int my_strcmp(const char* str1, const char* str2) {
	assert(str1 != NULL);
	assert(str2 != NULL);
	
	int ret = 0;
	
	while (*str1 == *str2) {
		if (*str1 == '\0' || *str2 == '\0') {
			break;
		}
		str1++;
		str2++;
	}

	if (*str1 < *str2) {
		ret = -1;
	}else if (*str1 > *str2) {
		ret = 1;
	}

	return ret;
}

int main() {

	char arr1[] = "abcd";
	char arr2[] = "abc";
	char arr3[] = "abcz";
	char arr4[] = "abcd";
	char arr5[] = "abz";
	
	printf("%d\n", my_strcmp(arr1, arr2));
	printf("%d\n", my_strcmp(arr1, arr3));
	printf("%d\n", my_strcmp(arr1, arr4));
	printf("%d\n", my_strcmp(arr1, arr5));

	return 0;
}

// 输出结果:
// 1
// -1
// 0
// -1

5. strncpy、strncat、strncmp

strcpy、strcat、strcmp 是一组长度不受限制的字符串函数。
而 strncpy、strncat、strncmp 是一组长度受限制的字符串函数。

所谓长度受限制,就是相比于前者,多了一个 size_t 类型的参数。例如:strncpy 就可以指定拷贝字符的数量、strncat 就可以指定追踪字符的个数、strncmp 就可以指定比较的字符个数。

6. strstr 函数

strstr - string string - 找子串

char* strstr ( const char* str1, const char* str2);

// 返回值:返回 str1 中的子串起始指针
// 参数:str1 为主串,str2 为子串

使用示例

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

int main() {

	char arr1[] = "abcdefxyxyzwe";
	char arr2[] = "bcd";
	char arr3[] = "xyz";
	char arr4[] = "xyw";

	printf("%s\n", strstr(arr1, arr2));
	printf("%s\n", strstr(arr1, arr3));
	printf("%s\n", strstr(arr1, arr4));

	return 0;
}

// 输出结果:
// bcdefxyxyzwe
// xyzwe
// (null)

模拟 strstr 函数

模拟思路:额外创建三个指针,s1, s2, flag.
flag 指针作为标志主串中子串的起始位置,s2 指针用来重置子串的开头,s1 指针用来和 s2 进行比较是否为相同字符。

模拟的三个场景:

1-5

#include <stdio.h>
#include <assert.h>

char* my_strstr(const char* str1,  const char* str2) {

	assert(str1 != NULL);
	assert(str2 != NULL);

	const char* s1 = str1;			
	const char* s2 = str2;			// str2 用来重置 s2
	char* flag = str1;				// flag 指针用来记录需要返回的位置

	while (*flag != '\0') {
		s1 = flag;
		s2 = str2;
		while ( (*s1 == *s2) && (*s1 != '\0') && (*s2 != '\0') ) {
			s1++;
			s2++;
		}
		if (*s2 == '\0') {
			// s2 走到字符串末尾,说明子串已经被找到
			return flag;
		}
		flag++;
	}

	return NULL;
}

int main() {

	char arr1[] = "abcdefxyxyzwe";
	char arr2[] = "bcd";
	char arr3[] = "xyz";
	char arr4[] = "xyw";

	printf("%s\n", my_strstr(arr1, arr2));
	printf("%s\n", my_strstr(arr1, arr3));
	printf("%s\n", my_strstr(arr1, arr4));

	return 0;
}

// 输出结果:
// bcdefxyxyzwe
// xyzwe
// (null)

7. strtok 函数

strtok - 字符串分割函数

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

// 返回值:返回被分割的标记指针,若字符串被分割没有更多的标记,则返回 NULL
// 参数:str 为目标字符串,sep 为标记分隔符

使用示例1

1-6

经过上面的程序,可以得出结论:

① 字符串的内容经 strtok 函数分割后,会被改变,标记分隔符处会被改变为 ‘\0’.
② 当 strtok 函数的第一个参数不为 NULL 时,则会找到字符串的第一个起始指针;当 strtok 函数的第一个参数为 NULL 时,则表示从分隔符后继续寻找。

分割过程如下:

hello | world , 你好 | 世界

hello \0 world , 你好 | 世界			-> return 'h' 的地址
hello \0 world \0 你好 | 世界		-> return 'w' 的地址
hello \0 world \0 你好 \0 世界		-> return '你' 的地址
									-> return NULL

使用示例2

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

int main() {

	char arr[] = "hello|world,你好|世界";
	const char* sep = ",|";
	char* str = NULL;

	for (str = strtok(arr, sep); str != NULL; str = strtok(NULL, sep)) {
		printf("%s\n", str);
	}

	printf("\n%s\n", arr);

	return 0;
}

// 输出结果:
// hello
// world
// 你好
// 世界

三、字符函数

1-7

使用示例

#include <stdio.h>
#include <ctype.h>

int main() {

	char ch = 'A';
	int ret = islower(ch);
	printf("%d\n", ret); 			// 0

	printf("%c\n", tolower(ch)); 	// a
	return 0;
}

四、内存操作函数

注意事项:

内存操作函数,顾名思义,它是对内存操作的,换言之,它就是以字节为单位来操作数据的,明白这一点很重要。单位既不是字符、也不是整型…

1. memset 函数

memset - memory set - 内存设置函数 - 以字节为单位设置数据

void* memset( void* dest, int ch, size_t count );

// 返回值:dest 的副本
// 参数:dest 表示需要操作的起始指针位, ch 表示需要填充的数据, count 表示需要设置字节的数量

使用示例

将 arr 数组的前 8 个字节分别设置成 1;千万不要理解成将前两个元素设置成 1.

1-8

2. memcmp

memcmp - memory compare - 内存比较函数 - 以字节为单位比较两个数据

int memcmp( const void* str1, const void* str2, size_t count );

// 返回值:
// 第一个数据大于第二个数据,则返回大于 0 的数字
// 第一个数据等于第二个数据,则返回 0
// 第一个数据小于第二个数据,则返回小于 0 的数字

// 参数:str1 为第一个数据的起始指针,str2 为第二个数据的起始指针, count 表示需要比较的字节数

使用示例

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

int main() {

	int arr1[] = { 1,2,3,4,5 };
	int arr2[] = { 1,2,3,4,0x11223305 };
	int ret1 = memcmp(arr1, arr2, 16);
	int ret2 = memcmp(arr1, arr2, 18);
	
	printf("%d, %d\n", ret1, ret2);

	return 0;
}

// 输出结果: 0, -1

// 01 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00 05 00 00 00 (十六进制 arr1)
// 01 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00 05 33 22 11 (十六进制 arr2)

3. memcpy 函数

memcpy - memory copy - 内存拷贝函数 - 以字节为单位拷贝数据

void* memcpy ( void* destination, const void* source, size_t count );

// 返回值:destination 的副本
// 参数:destination 为目标空间、source 为源数据、count 表示需要拷贝的字节数

memcpy 函数的使用思想和 strncpy 基本一致,但 memcpy 更加强大,它能够针对各种数据类型进行拷贝,只要涉及内存的,都可以。

使用示例

1-9

4. memmove 函数

memmove 函数和 memcpy 使用的思想基本相同,但早期的 memcpy 在使用时,若源空间和目标空间重叠,就可能出现数据覆盖的情况,所以才有 memmove 的诞生。然而,现在两者几乎没有区别,因为主流的 VS 编译器已经将两者实现差不多了。

使用示例

1-10

2-1

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

十七ing

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

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

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

打赏作者

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

抵扣说明:

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

余额充值