【一起来学C】C语言字符串+内存函数详解

字符串

前言:

C语言中对字符和字符串的处理很是频繁,但是C语言本身是没有字符串类型的.

字符串通常放在 常量字符串中或者字符数组中。

字符串常量 适用于那些对它不做修改的字符串函数.

strlen

strlen是一个求字符串长度的函数,遇到\0后停止

函数原型

size_t strlen(const char* );

注意:

  1. 返回值是size_t,size_t就是无符号整型
  2. strlen里面存放的是字符指针类型
  3. 遇到\0就停止,长度不包含\0

使用形式:

int main()
{
	//字符指针
	char* p = "abcdef";
	size_t sz = strlen(p);//注意strlen返回的是size_t
	printf("%u\n", sz);//size_t是无符号整形使用%u
	//字符数组
	char arr[] = "abcdefg";
	sz = strlen(arr);
	printf("%u\n", sz);
	return 0;
}

错误的使用:

	char arr[] = { 'a','b','c' };
	size_t sz= strlen(arr);
	printf("%d", sz);

结果:19,显然是错误的

这样的字符数组,因为没有以\0结尾,所以strlen会一直向下查找,直到找到\0为止。

所以,在使用的时候,我们要十分注意这个用法,字符串一定要含有\0

无符号整形的应用:

#include <stdio.h>
int main()
{
	const char*str1 = "abcdef";
	const char*str2 = "bbb";
	if(strlen(str2)-strlen(str1)>0)
	{
		printf("str2>str1\n");
	}
	else
	{
		printf("srt1>str2\n");
	}
	return 0;
}

结果:

str2>str1

因为strlen的结果是size_t是无符号整形,两个size_t的无符号整形相减之后,还是以无符号整形的形式出现的,无符号整形就是没有负数的,所以是大于0的。

模拟实现strlen

//使用计数器来计算
int my_strlen1(char* arr)
{
	int count = 0;
	while ((*arr++) != '\0')
	{
		count++;
	}
	return count;
}
//使用指针相减来计算
int my_strlen2(char* arr)
{
	char* end = arr;
	while ((*end) != '\0')
		end++;
	return end - arr;
}
//使用递归来实现
int my_strlen3(char* arr)
{
	if (*arr == '\0') return 0;
	else return 1 + my_strlen3(arr + 1);
}

int main()
{
	char arr[] = "abcdef";
	printf("%d\n", my_strlen1(arr));
	printf("%d\n", my_strlen2(arr));
	printf("%d\n", my_strlen3(arr));
	return 0;
}

strcpy

字符串拷贝函数,简而言之就是字符串届的赋值,将A的字符串赋值到B的字符串中,并且覆盖了B的字符

函数原型:

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

把source指向的字符拷贝到destination指向的字符中去,(包括’\0’),并且要注意destination的数组长度要足够长以包含source的元素

注意

  1. source字符串必须包含\0
  2. destination的长度必须足够长
  3. 拷贝的时候也拷贝了\0
  4. 遇到\0就停止拷贝了

源字符串包含\0(覆盖)

正确的做法:

int main()
{
	char source[] = "1";
	char destination[100] = "abc";
	strcpy(destination, source);
	printf("%s", destination);
	return 0;
}

错误的做法:

char source[] = { '1','2' };
char destination[] = "abcdef";
strcpy(destination, source);
printf("%s", destination);

原因就是source里面没有\0,它会一直向后寻找直到找到\0为止,所以打印了不属于本数组的内容。

遇到\0就停止

char source[] = "abc\0edf";
char destination[] = "xxxxxxxxxxxxxxxxx";
strcpy(destination, source);
printf("%s", destination);

目标字符串足够长

对于我们的目标字符串,他是为了承载我们的source的函数的,所以我们的destination的字符串长度必须足够长。

	char source[] = "abcdef";
	char destination[] = "1";
	strcpy(destination, source);
	printf("%s", destination);

这样的代码是会报错的

目标字符串可修改

	char source[] = "abcdef";
	char* destination = "12345678";//目标字符串是字符串常量,无法修改会报错
	strcpy(destination, source);
	printf("%s", destination);

这个程序会报错

这个就说明了,目标字符串必须是变量,不能是字符串常量,字符串常量无法修改。

char* my_strcpy(char destination[],const char source[])//注意:返回值要是char*
{
	char* ret = destination;
	assert(source&&destination);//断言,防止source和destination为NULL指针,因为下文解引用了
	while (*destination++ = *source++)
		;
	return ret;

}
int main()
{
	char source[] = "123456";
	char destination[] = "xxxxxxxxxxxxxxxxxxx";
	printf("%s", my_strcpy(destination, source));
	return 0;
}

strncpy

函数原型:

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

注意:

  1. numsource中的字符拷贝到destination中去,不是全部拷贝了
  2. 如果num大于source的字符个数,就补充\0,直到等于num为止
  3. num的值不要超过destination的空间大小

补充\0

	char arr1[] = "abcdef";
	char arr2[] = "12";
	strncpy(arr1, arr2, 4);
	printf("%s", arr1);

上面那个就是自动补充的\0.

模拟实现

char* my_strncpy( char* arr1, const char* arr2, size_t num)
{
	char* ret = arr1;//记录头指针,方便返回
	while (num)
	{
		if (*arr2 != '\0')//如果源字符串还没有达到\0
		{
			*arr1 = *arr2;//就把源字符串的值赋予目标字符串中
			arr2++; arr1++;
			num--;
		}
		else//源字符串没有值了,但是还没有结束
		{
			*arr1 = '\0';//就把\0赋予目标字符串
			num--;
		}
	}
	return ret;
}
int main()
{
	char arr1[] = "abcdef";
	char arr2[] = "12";
	printf("%s", my_strncpy(arr1, arr2, 4));
	return 0;
}

strcat

函数原型:

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

工作原理:

source的字符拼接到destination的后面。source的第一个值会覆盖destination\0,对destination进行拼接,直到找到\0为止

易错点:

源字符串必须以 ‘\0’ 结束。
目标空间必须有足够的大,能容纳下源字符串的内容。
目标空间必须可修改。

下面是对易错点进行示范:

目标字符串足够长

	char source[] = "abc";
	char destination[] = "123";
	strcat(destination, source);
	printf("%s", destination);

这样是不对的,会报错

但是,如果加长目标字符串的长度,就不会报错

	char source[] = "abc";
	char destination[100] = "123";//将目标字符串的空间增大
	strcat(destination, source);
	printf("%s", destination);

源字符串须含有\0

错误示范:

	char source[] = { 'a','b','c' };
	char destination[100] = "123";
	strcat(destination, source);
	printf("%s", destination);

如果source不含有\0,strcat函数就会一直寻找\0直到找到为止,所以会出现上面的乱码。

目标字符串必须可修改

目标字符串不可以是常量字符串,因为常量字符串不可以写入,不可以修改

	char source[] = "abc";
	char* destination = "123";//常量字符串不可以修改,会报错
	strcat(destination, source);
	printf("%s", destination);

自己给自己追加

自己不可以给自己追加,会报错

	char destination[] = "123";
	strcat(destination, destination);
	printf("%s", destination);
//会报错

但是,如果是新创建一个数组去保存,即使是相同的元素,也是不会报错的。

所以,下回如果你想要在一个字符串后面添加一个相同的字符串,就要新创建一个数组了,就像下面这样:

同时使用strcpy函数和strcat函数才可以完成自己对自己的追加

	char arr1[] = "123";
	char arr2[10] = "";
	strcpy(arr2, arr1);
	strcat(arr2, arr1);
	printf("%s", arr2);

模拟实现

char* my_strcat(char* destination, const char* source)
{
	assert(destination && source);
	char* pd = destination;
	char* ps = source;
	//找到destination的\0位置
	while (*pd != '\0')
		pd++;
	//从那开始进行赋值
	while (*pd++=*ps++)
	{
		;
	}
	return pd;

strncat

函数原型:

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

工作原理:

从下面的例子中可以看出来,strncat的作用:

	char arr1[20] = "abc\0xxxxxxxx";
	char arr2[] = "123";
	strncat(arr1, arr2, 2);
	printf("%s", arr1);

结果是abc12

如果num过长,也只能拷贝到\0为止,不能再继续拷贝了

	char arr1[20] = "adc\0xxxxxxxx";
	char arr2[] = "123";
	strncat(arr1, arr2, 6);
	printf("%s", arr1);

工作原理

strncat遇到arr1的\0后,将arr2的元素连接上去。

并在最后自动补上\0

如果num的值大于arr2的值,把arr2的值的内容拷贝完成后,直接在函数的后面加入\0即可

模拟实现

char* my_strncat(char* arr1, const char* arr2, size_t num)
{
	char* p1 = arr1;
	char* p2 = arr2;
	//找到arr1[]的‘\0'
	while (*p1!='\0')
	{
		p1++;
	}
	//开始把arr2的元素接到arr1上面
	while ((*p1++ = *p2++) && num-1)//当num-1等于0或者接到arr2的\0为止
		num--;
	*p1 = '\0';//为字符串的末尾接上\0
	return arr1;


}
int main()
{
	char arr1[20] = "adc";
	char arr2[] = "123";
	printf("%s", my_strncat(arr1,arr2,2));
	return 0;
}

strcmp

这个函数是字符串比较函数。

注意:

  1. 比较的是内容,不是长度。

  2. 如果字符串的每一个字符的内容都相等,就返回0.

    如果一个字符串的某一个字符大于相同位置的某一个字符,就返回1

    如果一个字符串的某一个字符小于相同位置的某一个字符,就返回-1

    (注意不是看长度,不是长返回1,短就返回-1)

比较内容

	char* arr1 = "123456";
	char* arr2 = "124";
	printf("%d", strcmp(arr1, arr2));

这道题,就可以充分的看出来,在‘3’和‘4’比较的时候,虽然arr1的长度比arr2的长度大。但是‘3’的ASCII比‘4’的小。所以返回的还是-1。

模拟实现

int my_strcmp(const char* arr1, const char* arr2)
{
	while (*arr1 == *arr2&&*arr1!='\0'&&*arr2!='\0')
	{
		arr1++; arr2++;
	}
	return *arr1 - *arr2;
}
int main()
{
	char* arr1 = "124";
	char* arr2 = "123";
	printf("%d", my_strcmp(arr1, arr2));
}

strncmp

函数原型:

int strncmp(const char* destination,const char* source,size_t num);

工作原理:

这个strncmp的工作原理和strcmp的工作原理十分相同,num是多少就比较多少元素

模拟实现:

int my_strncmp(const char* arr1, const char* arr2, size_t num)
{
	while (num && (*arr1 == *arr2))
	{
		if (*arr1 == '\0')
			return 0;
		num--;
		arr1++; arr2++;
	}
	return *arr1 - *arr2;
}
int main()
{
	char arr1[] = "1234";
	char arr2[] = "12";
	printf("%d", my_strncmp(arr1, arr2,5));
	return 0;
}

strstr

函数原型:

char* strstr(const char* arr1 ,const char* arr2);

工作原理:

arr1中找arr2,如果找到了,就返回arr2arr1第一次出现的地方的地址;如果没找到,就返回NULL

	char arr1[] = "1 2 3 2 4 5";
	char arr2[] = "2";
	printf("%s", strstr(arr1, arr2));

模拟实现

!!!

char* my_strstr(const char* arr1, const char* arr2)
{
	char* str1 = (char*)arr1;
	char* str2 = (char*)arr2;
	char* cp = str1;
	while (*cp)
	{
		str1 = cp;
		str2 = arr2;
		while (*str1 != '\0' && *str2 != '\0' && *str1 == *str2)
		{
			str1++;
			str2++;
		}
		if (*str2 == '\0')//如果将目标元素全部遍历完成后
			return cp;//就返回找到的
		cp++;
	}
	return NULL;//返回NULL
}

strtok

函数原型:

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

工作原理:

  1. strtok是一个字符串切割函数,如果见到seq中的一些切割字符,就把str根据切割字符成很多小部分。
  2. 第一个参数指定一个字符串,它包含了0个或者多个由sep字符串中一个或者多个分隔符分割的标记。
  3. strtok函数找到str中的下一个标记,并将其用 \0 结尾,返回一个指向这个标记的指针。(注:strtok函数会改
    变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容并且可修改。)
  4. strtok函数的第一个参数不为 NULL ,函数将找到str中第一个标记,strtok函数将保存它在字符串中的位置。
  5. strtok函数的第一个参数为 NULL ,函数将在同一个字符串中被保存的位置开始,查找下一个标记。
  6. 如果字符串中不存在更多的标记,则返回 NULL 指针

具体实现:

int main()
{
	char arr1[] = "E1:T4:U7:78:34,90.";
	char arr2[100] = "";
	char seq[] = ":,";
	strcpy(arr2, arr1);//一般使用的是拷贝的临时变量
	printf("%s\n", arr1);
	char* str = NULL;
	//初始变量
	for (str=strtok(arr2, seq); str!= NULL; str=strtok(NULL, seq))
	{
		printf("%s\n", str);
	}
	return 0;
}

strerror

函数原型:

char* strerror(int errnum);

工作原理:

  1. 根据errnum的内容找到错误码所对应的信息
  2. 返回的是错误码所对应的信息的地址

具体实现

int main()
{
	printf("%s\n", strerror(0));
	printf("%s\n", strerror(1));
	printf("%s\n", strerror(2));
	printf("%s\n", strerror(3));
	printf("%s\n", strerror(4));
	return 0;
}

在C语言内部存在一个全局变量:errno,它就是专门用来记录错误码的。

可以利用strerror来打印errno所对应的错误信息。

	FILE* pf = fopen("text.txt", "r");
	if (pf == NULL)
		printf("%s\n", strerror(errno));
	else
		printf("have found it\n");
	return 0;

perror

strerror很相同的一个函数还有perror,并且perror更加高效。

相当于:printf+strerror

内存函数

memcpy

函数原型:

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

工作原理:

  1. 和strcpy的作用相似,就是拷贝目标数组中的内容。不同的是:strcpy只是拷贝字符串,但是memcpy可以拷贝任意不同类型的数值
  2. 注意:count一定是字节数,不是元素个数

具体实现:

int main()
{
	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
	int arr2[100] = {0};
	memcpy(arr2, arr,10*sizeof(int));
	for (int i = 0; i < 10; i++)
	{
		printf("%d ", arr2[i]);
	}
	return 0;
}

模拟实现(都转化为char*)

注意:

对于这种void*类型的函数,如果想要把它转化为我们的目标类型,是很复杂的。

所以,我们就采取了一个非常nice的方法。

就是把它们都转化为char*类型,这样无论是什么类型的数字,我们都可以采用一次只调用一个字节的方法。

我们查字节数,这样就轻松解决了问题了。

void* my_memcpy(void* arr1, const void* arr2,size_t count){	char* ret = arr1;//先 保存arr1的首地址	while (count--)	{		*(char*)arr1 = *(char*)arr2;//将元素都转化成char*类型的,这样的话就可以遍历count啦		arr1 = (char*)arr1 + 1;//把它们都转化成char*类型。我们就可以按照字节的顺序一次只跳过一个字节		arr2 = (char*)arr2 + 1;	}	return ret;}int main(){	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };	int arr2[100] = { 0 };	my_memcpy(arr2, arr, 10 * sizeof(int));	for (int i = 0; i < 10; i++)	{		printf("%d ", arr2[i]);	}	return 0;}

memmove

工作原理:

memcpy的工作原理一样,只是解决了memcpy的覆盖的问题。

比如说,下面这个:

int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };	my_memcpy(arr1 + 2, arr1, 16);	for (int i = 0; i < 10; i++)	{		printf("%d ", arr1[i]);	}

memcpy的缺点就是它在变化的太快了。不能将原来的元素赋值,反而将新的元素赋值了,但是这不是我们想要看到的

模拟实现

下面我们就来实现一下不会随着改变而改变的memmove函数

void* my_memmove2(void* des, const void* souc, size_t count){	void* ret = des;	if (des > souc && (char*)des <= (char*)souc + count - 1)	{//从后向前拷贝				while (count--)		{			*((char*)des + count) = *((char*)souc + count);		}	}	else//从前向后拷贝	{		while (count--)		{			*(char*)des = *(char*)souc;			des = *(char*)des + 1;			souc = *(char*)souc + 1;		}	}	return ret;}

memcmp

函数原型:

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

工作原理:

还是以字节数num进行比较的.

如果所比较的字节的内存不相等,就返回相应的值

如果所比较的字节的内存相等,就继续比较下一个内存的字节。

memset

函数原型:

void memset(void* arr,int x,size_t count);

工作原理:

将arr的count个字节,全部赋予x

具体实现:

	int arr[] = { 16,8,4,2,0 };	memset(arr, 1, 20);	return 0;

模拟实现:

	void* ret = arr1;	while (count--)	{		*((char*)arr1 + count) = (char)x;	}	return ret;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值