字符串函数的详解

函数介绍

一:求字符串长度

1:strlen

(1):strlen的用法

size_t strlen(const char* str);

  • 字符串以 '' 为结束标志,strlen函数返回的是在字符串中 ‘’ 前面出现的字符个数(不包含’’ )。
  • 参数指向的字符串必须要以 ‘’ 结束。
  • 注意函数的返回值为size_t,是无符号的整数( 易错 )
#include<stdio.h>
#include<string.h>
int main()
{
	const char* str1 = "abcdef";
	const char* str2 = "bbb";
	if (strlen(str2) - strlen(str1) > 0)
	{
		printf("str2>str1n");
	}
	else
	{
		printf("str1>str2n");
	}
	return 0;
}
//看起来打印结果是str1>str2,但实际上是str2>str1
//因为strlen是无符号size_t整型,|3-6|=3>0

(2):用函数的方法来实现strlen

#include<stdio.h>
#include<string.h>
#include<assert.h>
size_t my_strlen(const char* str)
{
	assert(str);//防止空指针
	const char* start = str;
	const char* end = str;
	while (*end != '')
	{
		end++;
	}
	return end - start;
}
//指针-指针方式
size_t my_strlen(const char* s)
{
	char* p = s;
	while (*p != '')
		p++;
	return p - s;
}
//计数器方式
int my_strlen(const char* str)
{
	assert(str);//断言
	int count = 0;
	while (*str)
	{
		count++;
		str++;
	}
	return count;
}
int main()
{
	char arr[] = "abcdef";
	int len = my_strlen(arr);
	printf("%dn", len);
	return 0;
}

二:长度不受限制的字符串函数

1:strcpy

(1):strcpy的用法

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

  • 语法原理:
    • 把source指向地址的空间的数据,拷贝到destination所指向的空间里面去.
  • 特点:
    • 源字符串必须以 ‘’ 结束。
    • 会将源字符串中的 ‘’ 拷贝到目标空间。
    • 目标空间必须足够大,以确保能存放源字符串。
    • 目标空间必须可变。
#include<stdio.h>
#include<string.h>
int main()
{
	char arr1[10] = "xxxxxxxxx";
	char arr2[] = { 'b', 'i', 't', '' };
	strcpy(arr1, arr2);
	printf("%sn", arr1);//bit
	return 0;
}
//结果:bit

//注:因为arr1数组的容量比arr2大,因此如果strcpy(arr2,arr1)的话
//会造成越界访问 导致代码出现错误

(2):用函数的方法来实现strcpy

#include<stdio.h>
#include<string.h>
#include<assert.h>
char* my_strcpy(char* dest, const char* src)
{
	assert(dest && src);
	char* ret = dest;
	while ((*dest++ = *src++))
	{
		;
	}
	return ret;
}
int main()
{
	char arr1[10] = "abcdef";
	char arr2[] = "bit";
	my_strcpy(arr1, arr2);
	printf("%s", arr1);
	return 0;
}
//结果:bit

2:strcat

(1):strcat的用法

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

  • 语法原理:

    • 把source指向地址空间的数据,拼接到destination所指向的空间末尾处,即把第1个字符串(source)拼接到第1个字符串末尾.
  • 特点:

    • 源字符串必须以 ‘’ 结束。
    • 目标空间必须有足够的大,能容纳下源字符串的内容。
    • 目标空间必须可修改。
    • 字符串自己给自己追加,已经被覆盖掉了,这个程序会崩溃
#include<stdio.h>
#include<string.h>
int main()
{
	char arr1[20] = "你好";
	char arr2[] =   "hello world";
	strcat(arr1, arr2);
	printf("%sn", arr1);
	return 0;
}
//结果:你好hello world

(2):用函数的方法来实现strcat

#include<stdio.h>
#include<string.h>
#include<assert.h>
char* my_strcat(char* dest, const char* src)
{
	assert(dest);
	assert(src);
	//1.先找目标空间中的
	char* cur = dest;
	while (*cur != '')
	{
		cur++;
	}
	//2.拷贝源头数据到之后的空间
	while (*cur++ = *src++)
	{
		;
	}
	return dest;//返回目标空间起始地址
}
int main()
{
	char arr1[20] = "hello ";//[]里如果不加数会导致数组越界访问
	char arr2[] = "world";
	printf("%sn", my_strcat(arr1, arr2));
	return 0;
}

3:strcmp

(1):strcmp的用法

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

  • 语法原理:
    • 这个函数会从两个字符串的第一个字符开始对应比较,哪一个字符的ASCII码大,哪一个 字符串大,如果两个字符一样,就继续比较下一个字符。
  • 特点
    • 第一个字符串大于第二个字符串,则返回大于0的数字
    • 第一个字符串等于第二个字符串,则返回0
    • 第一个字符串小于第二个字符串,则返回小于0的数字
#include<stdio.h>
#include<string.h>
int main()
{
	//char arr1[] = "abcdef";
	//char arr2[] = "abq";//返回了一个小于0的数字(-1)

	char arr1[] = "abcd";
	char arr2[] = "abc";//返回了一个大于0的数字(1)

	//char arr1[] = "abc";
	//char arr2[] = "abc";//返回0

//	//char arr1[] = { 'a', 'b', 'c' };//err
//	//char arr2[] = { 'a', 'b', 'c' };//err

	int ret = strcmp(arr1, arr2);
	if (ret < 0)
		printf("arr1<arr2n");
	else if (ret > 0)
		printf("arr1>arr2n");
	else
		printf("arr1==arr2n");
	printf("%dn", ret);
	return 0;
}

(2):用函数的方法来实现strcmp

#include<stdio.h>
#include<string.h>
#include<assert.h>
int my_strcmp(const char* str1, const char* str2)
{
	assert(str1 && str2);
	while (*str1 == *str2)
	{
		if (*str1 == '')
		{
			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);
	printf("%dn", ret);
	return 0;
}

三:长度受限制的字符串函数介绍

1:strncpy

(1):strncpy的使用

char* strncpy(char* destination, const char* source, size_t num);

  • 语法原理:
    • 拷贝num个字符从源字符串到目标空间。
      如果源字符串的长度小于num,则拷贝完源字符串之后,在目标的后边追加’’,直到num个。
  • 特点:
    • 返回的指针指向被改变的字符串的首字符地址。
#include<stdio.h>
#include<string.h>
int main()
{
	char arr1[10] = "abcedf";
	char arr2[10] = "xxxxxxxx";
	strncpy(arr2, arr1, 8);
	//8位看上去多了,但实际多了的位数后面自动添加'',直到补齐8位为止,
	//剩余还有2个空间是本身自带的''
	printf("%sn", arr2);
	return 0;
}
//结果:abcedf('''''''')

(2):用函数的办法来实现strncpy

#include<stdio.h>
#include<string.h>
#include<assert.h>
char* my_strncpy(char* dest, const char* src, size_t num)
{
	assert(dest && src);
	char* ret = dest;
	while (num-- && *src != '')
	{
		*dest++ = *src++;
	}
	return ret;
}
int main()
{
	char arr1[] = "abcdef";
	char arr2[] = "xxxxxxxx";
	int ret = my_strncpy(arr2, arr1, 5);
	printf("%sn", arr2);
	return 0;
}

2:strncat

(1):strncat的使用

char* strncat(char* destination, const char* source, size_t num);

  • 语法原理
    • 可以对字符串加长限定数目的字符
  • 特点
    • 返回的指针指向被改变的字符串的首字符地址
#include<stdio.h>
#include<string.h>
int main()
{
	char arr1[20] = "abcdefqqqq";
	char arr2[] = "xyz";
	strncat(arr1, arr2, 2);
	printf("%sn", arr1);
}
//结果:abcdefxy('')(自己添加的'')

(2):用函数的办法来实现strncat

#include<stdio.h>
#include<assert.h>
char* my_strncat(char* dest, const char* src, size_t num)
{
	assert(dest && src);
	char* ret = dest;
	while (*dest != '')
	{
		dest++;
	}
	while (num-- && *src != '')
	{
		*dest++ = *src++;
	}
	return ret;
}
int main()
{
	char arr1[20] = "abc";
	char arr2[] = "xyz";
	printf("%sn", my_strncat(arr1, arr2, 2));
	return 0;
}

3:strncmp

(1):strncmp的使用

int strncmp(const char* str1, const char* str2, size_t num)

  • 语法原理:
    • 这个函数会从两个字符串的第一个字符开始比较,哪一个字符的ASCII码大,哪一个字符串大,如果两个字符一样,就继续比较下一个字符。
  • 特点:
    • 这个函数可以比较限定数目的字符,由num来决定几个字符比较
#include <stdio.h>
#include <string.h>
int main()
{
	char arr1[12] = "abcdef";
	char arr2[20] = "abcfg";
	int ret = strncmp(arr1, arr2, 3);
	printf("%dn", ret);
	if (ret > 0)
	{
		printf("arr1>arr2");
	}
	else if (ret == 0)
	{
		printf("arr1=arr2");
	}
	else
	{
		printf("arr1<arr2");
	}
	return 0;
}
//结果:0 arr1=arr2

(2):用函数的办法来实现strncmp

#include<stdio.h>
#include<assert.h>
int my_strncmp(const char* str1, const char* str2, size_t num)
{
	assert(str1 && str2);
	while (num-- && *str1 == *str2 && *str1 != '')
	{
		if (num > 1)
		{
			str1++;
			str2++;
		}
	}
	return *str1 - *str2;
}
int main()
{
	char arr1[12] = "abcdef";
	char arr2[20] = "abcfg";
	int ret = my_strncmp(arr1, arr2, 3);
	if (ret > 0)
	{
		printf("arr1>arr2");
	}
	else if (ret == 0)
	{
		printf("arr1=arr2");
	}
	else
	{
		printf("arr1<arr2");
	}
	return 0;
}

四:字符串查找

1:strstr

(1):strstr的使用

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

  • 语法原理:
    • 查找一个字符串中是否存在另一个子字符串
      例如:abcdef中查找bcd就是存在的,打印结果从’b’开始一直到’’为止。而查找acd就是不存在的。
  • 特点:
    • 返回的指针为查找到的位置的地址,(也就是第一次子串出现的位置),如果不存在,则返回NULL.
      比如说上面的例子中我们找到了bcd那么返回的指针就指abcdef中的那个字符’b’
#include<stdio.h>
#include<string.h>
int main()
{
	char arr1[] = "abcdef";
	char arr2[] = "bcd";
	char* p = strstr(arr1, arr2);
	if (p == NULL)
		printf("不存在n");
	else
		printf("%sn", p);
}
//结果:bcdef('')

(2):用函数的办法来实现strstr

#include<stdio.h>
#include<assert.h>
char* my_strstr(const char* str1, const char* str2)
{
	assert(str1 && str2);
	char* s1 = (char*)str1;
	char* s2 = (char*)str2;
	char* cp = (char*)str1;//cp保存首字符的地址
	while (*cp)
	{
		s1 = cp;
		while (*s1 != '' && *s2 != '' && *s1 == *s2)
		{
			s1++;
			s2++;
		}
		if (*s2 == '')
		{
			return cp;
		}
		s2 = (char*)str2;
		cp++;//cp++可以得到原起始位置的下一个位置
	}
	return NULL;
}
int main()
{
	char arr1[] = "abcdef";
	char arr2[] = "bcd";
	char* p = my_strstr(arr1, arr2);
	if (p == NULL)
		printf("不存在n");
	else
		printf("%sn", p);
}

2:strtok

(1):strtok的使用

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

  • 语法原理
    • 第一个数组储存待拆分字符串
    • 第二个数组储存所有的分割符号
    • 函数会在待拆分字符串中从左到右找到一个分隔符号并将其赋值为
    • 返回的指针指向分割的那一部分字符串的开头,同时函数会记住原来分隔符的位置,我们第一个参数传NULL即可继续分割(NULL的时候才会用上记忆功能)
    • 例如:
      我们的数组一设置为:10535715@qq.com
      数组二设置为:.@
      第一次分割:10535715qq.com
      返回的地址指向1,内部存储的指针指向
      再次传递NULL,第二次分割:10535715qqcom
      返回的地址指向q(第一个q),内部存储的指针指向下一个
      以此类推,直到字符串末尾
#include<stdio.h>
#include<string.h>
int main()
{
	char arr1[] = "10535715@qq.com";
	const char* p = "@.";
	//方法一:方便理解
	//首元素地址非空
	//char* str = strtok(arr1, p);
	//printf("%sn", str);//10535715
	//第一个开始算起,strtok函数的第一个参数为NULL,直到遇到.将其变成
	//str = strtok(NULL, p);
	//printf("%sn", str);//qq
	//第二个开始算起,第一个参数还是NULL
	//str = strtok(NULL, p);//com
	//printf("%sn", str);
	
	//方法二:
	char* str = NULL;
	for (str = strtok(arr1, p); str != NULL; str = strtok(NULL, p))
	{
		printf("%sn", str);
	}
	return 0;
}

五:错误信息报告

1:strerror

(1):strerror的使用

char * strerror ( int errnum );

  • 语法原理:
    • 把错误码转换成错误信息
      在C程序中存在一个全局变量errno,要想使用这个变量需要引用头文件errno.h
      当程序出现问题的时候这个变量就会存储一个对应这个错误的数字
      但是只有这个数字我们不知道这个错误是什么
      而这个函数可以根据这个errno的值找到对应的错误,返回错误内容的字符串
#include<stdio.h>
#include<string.h>
#include<errno.h>
int main()
{
	printf("%sn", strerror(0));//0,1,2,3,4分别为错误码(c语言中库函数报错的时候的错误码)
	printf("%sn", strerror(1));
	printf("%sn", strerror(2));
	printf("%sn", strerror(3));
	printf("%sn", strerror(4));
	return 0;
}
//结果:
//No error
//Operation not permitted
//No such file or directory
//No such process
//Interrupted function call

//如果错误信息是字符串的话,返回的是每行首字符的地址
#include<stdio.h>
#include<string.h>
#include<errno.h>
int main()
{
	FILE* pf = fopen("test.txt", "r");
	if (pf == NULL)
	{
		perror("");//打印的依然是errno变量中错误码对应的错误信息
		//perror=printf+strerror
		//printf("%sn", strerror(errno));
		return 1;
	}
	//读文件
	fclose(pf);
	pf = NULL;
	return 0;
}
//No such file or directory

六:字符分类与字符转换函数

1:字符分类函数

可以判断字符是否为所需要的那一种类型的字符

下面是这部分的一些函数:(头文件为ctype.h)

  • iscntrl 任何控制字符
  • isspace 空白字符:空格‘ ’,换页‘f’,换行’n’,回车‘r’,制表符’t’或者垂直制表符’v’
  • isdigit 十进制数字 0~9(如果 c 是一个数字,则该函数返回非零值,否则返回 0)
  • 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 任何可打印字符,包括图形字符和空白字符

2:字符转换函数

  • 可以判断字符是否可以转化为所需要的那一种类型的字符,是则进行相应转换,不是则直接打印。
  • 对于字符串也可以对每一个字符进行相应操作。
    int tolower (int c); 大写字母转成小写字母(ch+32),不是大写字母的字符直接返回原字符。
    int toupper (int c);小写字母转成大写字母(ch-32),不是小写字母的字符直接返回原字符。

七:内存操作函数

1:memcpy

(1):memcpy的使用

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

  • 语法原理:

    • 函数memcpy从source的位置开始向后复制num个字节的数据到destination的内存位置。
  • 特点:

    • 这个函数在遇到 ‘’ 的时候并不会停下来。
    • 如果source和destination有任何的重叠,复制的结果都是未定义的。所以memcpy不能重叠拷贝
#include<stdio.h>
#include<string.h>
int main()
{
	int arr1[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int arr2[10] = { 0 };
	memcpy(arr2, arr1, 8);//把arr1中的前8个字节的数拷贝到arr2里面去
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d ", arr2[i]);
	}
	return 0;
}
//结果:1 2 0 0 0 0 0 0 0 0

memcpy与strncpy很类似,但memcpy没规定参数

(2):用函数的办法来实现memcpy

#include<stdio.h>
#include <assert.h>
#include<string.h>
void* my_memcpy(void* dest, void* src, size_t num)
{
	void* ret = dest;//定义起始位置ret
	assert(dest);
	assert(src);
	while(num--)
	{
		*(char*)dest = *(char*)src;//void*指针无法解引用
		dest = (char*)dest + 1;//有些编译器不能dest++和src++
		src = (char*)src + 1;
	}
	return ret;
}
int main()
{
	int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
	int arr2[10] = { 0 };
	my_memcpy(arr2, arr1, 20);//20为数组arr1中5个int元素的大小
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d ", arr2[i]);//1 2 3 4 5 0 0 0 0 0
	}
	return 0;
}

2:memmove

(1):memmove的使用

void * memmove ( void * destination, const void * source, size_t num );

  • 语法原理:
    • 和memcpy的差别就是memmove函数处理的源内存块和目标内存块是可以重叠的。
    • 如果源空间和目标空间出现重叠,就得使用memmove函数处理。
#include<stdio.h>
#include<string.h>
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	memmove(arr + 2, arr, 20);//在arr数组的首元素+2的位置处拷贝arr数组中前5个元素
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}
//结果:1 2 1 2 3 4 5 8 9 10

(2):用函数的办法来实现memmove

#include<stdio.h>
#include<string.h>
#include<assert.h>
void* my_memmove(void* dest, const void* src, size_t num)
{
	char* s1 = (char*)dest;
	char* s2 = (char*)src;
	if (s1 <= s2)
	{
		while (num--)//dest小于src时,从前向后拷贝
		{
			*s1 = *s2;
			s1++;
			s2++;
		}
	}
	else
	{
		while (num--)
		{
			*(s1 + num) = *(s2 + num);//dest大于src时,从后向前拷贝
		}
	}
	return dest;
}
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	my_memmove(arr + 2, arr, 20);
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d ", arr[i]);//1 2 1 2 3 4 5 8 9 10
	}
	return 0;
}

在这里插入图片描述

3:memset

(1):memset的使用

void * memset ( void * ptr, int value, size_t num );

  • 语法原理:
    • 将ptr所指向的内存块的首个num字节(起始地址开始的字节数)设置成一个特定的值(也可解释为unsigned char)
    • value值需要被设置,该值需要int传递,但函数利用该值是unsigned char类型来填充内存块
#include<stdio.h>
#include<string.h>
int main()
{
	int arr[] = { 1,2,3,4,5 };
	memset(arr, 0, 8);//用0代替数组arr中的前2个元素(8个字节)
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		printf("%d ", arr[i]);//0 0 3 4 5
	}
	return 0;
}

(2):用函数的办法来实现memset

#include<stdio.h>
#include<assert.h>
void* my_memset(void* ptr, int value, size_t num)
{
	void* ret = ptr;
	assert(ptr);
	while (num--)
	{
		*(char*)ptr = value;
		ptr = (char*)ptr + 1;
	}
	return ret;
}
int main()
{
	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
	my_memset(arr, 0, 4);
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d ", arr[i]);//0 2 3 4 5 6 7 8 9 10
	}
	return 0;
}

4:memcmp

(1):memcmp的使用

int memcmp ( const void * ptr1, const void * ptr2, size_t num );

  • 语法原理:
    • 比较从ptr1和ptr2指针开始的num个字节
  • 特点:
    • 如果ptr1里面字节的数据大于ptr2,那就返回>0
    • 如果ptr1里面字节的数据等于ptr2,那就返回 = 0
    • 如果ptr1里面字节的数据小于ptr2,那就返回<0
#include<stdio.h>
#include<string.h>
int main()
{
	int arr1[] = { 1,2,3,4 };//小端存储:01 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00
	int arr2[] = { 1,2,3,0 };//        01 00 00 00 02 00 00 00 03 00 00 00 00 00 00 00
	int ret = memcmp(arr1, arr2, 12);//按照内存在第十二个字节数进行比较->00比00->返回0
	printf("%dn", ret);
	return 0;
}#include<stdio.h>
#include<assert.h>
int my_memcmp(const void* str1, const void* str2, size_t count)
{
	assert(str1);
	assert(str2);
	if (!count)
	{
		return 0;
	}
	while (--count && *(char*)str1 == *(char*)str2)
	{
		str1 = (char*)str1 + 1;
		str2 = (char*)str2 + 1;
	}
	return ((unsigned char*)str1 - (unsigned char*)str2);
}

int main()
{
	int arr1[] = { 1,2,3,4,5 };
	int arr2[] = { 0 };
	int ret = my_memcmp(arr2, arr1, 12);//返回了一个大于0的数
	printf("%dn", ret);
	return 0;
}

memcmp和strncmp很类似,但memcmp没规定参数。

(2):用函数的办法来实现memcmp

#include<stdio.h>
#include<assert.h>
int my_memcmp(const void* str1, const void* str2, size_t count)
{
	assert(str1);
	assert(str2);
	if (!count)
	{
		return 0;
	}
	while (--count && *(char*)str1 == *(char*)str2)
	{
		str1 = (char*)str1 + 1;
		str2 = (char*)str2 + 1;
	}
	return ((unsigned char*)str1 - (unsigned char*)str2);
}

int main()
{
	int arr1[] = { 1,2,3,4,5 };
	int arr2[] = { 0 };
	int ret = my_memcmp(arr2, arr1, 12);//返回了一个大于0的数
	printf("%dn", ret);
	return 0;
}