当前位置:首页 » 《随便一记》 » 正文

【C语言进阶】从入门到入土(字符串和内存函数第一篇)_梁同学今天不想敲代码的博客

27 人参与  2022年02月19日 08:19  分类 : 《随便一记》  评论

点击全文阅读


前言:
本篇重点介绍处理字符和字符串的库函数的使用和注意事项,让我们更好的使用字符串和字符串库函数,去理解运用一些库函数或自定义函数。字符串和内存函数共两篇。

C语言中对字符和字符串的处理很是频繁,但是C语言本身是没有字符串类型的,字符串通常放在常量字符串中或者字符数组 中。 字符串常量适用于那些对它不做修改的字符串函数。

在学习库函数的过程中,我们最好可以去理解和掌握它的原理,那么库函数的定义我们可以在c++的官网查看,也就是下方链接,进去之后搜索自己需要的库函数就可以了。

就是这里 → cplusplus.com

字符串和内存函数:

  • 一.求字符串长度
    • 1.strlen
  • 二.长度不受限制的字符串函数
    • 1.strcpy
    • 2.strcat
    • 3.strcmp
  • 三.长度受限制的字符串函数介绍
    • 1.strncpy
    • 2.strncat
    • 3.strncmp

一.求字符串长度

1.strlen

首先我们大概了解一下这个库函数的基本信息:

strlen函数的功能可以理解为获取字符串中首字母元素的地址,然后往下去找'\0',一边寻找一边数数,每跳过一个字符数1,直到找到'\0',,然后回看一共走过了多少字节。

比如:

#include <string.h>

int main()
{
	char arr[] = { "abcde" };
	char arr2[] = { 'a','b','c','d','e'};
	int sz = strlen(arr);
	int sz2 = strlen(arr2);
	printf("%d\n", sz);//5
	printf("%d", sz2);//17(不唯一)
	return 0;
}

在这里我们需要记得,strlen函数计算的结束符号是'\0',所以在上面代码中,字符串是先天就在末尾上带上'\0'的,而定义的arr2是多个字符组合起来的字符串,是没有带上'\0'的。当strlen计算的时候,到了字符’e’后还不会停,而是直到继续计算下去遇到一个随机的'\0'才结束,所以sz2计算得到的是一个随机值。

然后我们要注意到的是:strlen的返回值是size_t,也就是unsigned int(无符号整形),所以我们要注意一些返回时的参数变化。

比如:

#include <string.h>
int main()
{
	if (strlen("abc") - strlen("abcdef") > 0)
	{
		printf("大于0\n");
	}
	else
	{
		printf("小于0\n");
	}

	return 0;
}

分析这段代码的时候,是不是前面的应该是3,后面的是6,所以3-6是负数,打印的是小于0。在这里打印的实际上是大于0,原因就是类型的问题。

(strlen("abc")得到的是size_t类型的值,而strlen("abcdef")也是,所以相减得到的也是size_t,无符号整形是没有负数的,所以最终打印的是大于0。如果真的想要这样写,而结果也不出错,可以在(strlen("abc")前面强制类型转换得到int。

所以我们在使用strlen需要注意的有:

1.字符串已经 ‘\0’ 作为结束标志,strlen函数返回的是在字符串中 ‘\0’ 前面出现的字符个数(不包含 ‘\0’ )。
2.参数指向的字符串必须要以 ‘\0’ 结束。
3.注意函数的返回值为size_t,是无符号的。

然后我们既然理解了strlen函数的定义和使用功能,我们也可以自己尝试自定义一个函数去实现strlen的功能:

第一种(计数器):

#include <stdio.h>
int my_strlen(char* str) {
	int count = 0;
	while (*str++ != '\0') {
		count++;
	}
	return count;
}

int main() {
	char str[] = "abcdef";
	int len = my_strlen(str);
	printf("%d\n", len);
	return 0;
}

第二种(指针-指针):

#include <stdio.h>
int my_strlen(char* str) {
	char* start = str;
	char* end = str;
	while (*end != '\0') {
		end++;
	}
	return end - start;
}
int main() {
	char str[] = "abcdef";
	int len = my_strlen(str);
	printf("%d\n", len);
	return 0;
}

第三种(递归):

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

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

我们下面学的strcpystrcat还有strcmy,都是长度不受限制的字符串函数,接下来我们还会学习长度受限制的字符串函数。为什么叫不受限制呢,因为他们在处理字符串的时候,都不会去在意他的长度,所以就是不受限制的。

1.strcpy

先看定义:

strcpy从定义上看就是把一个字符串的内容拷贝到另一个字符串里,char * destination就是目的地址, const char * source就是源,就是把源拷贝到目的地址去。

① 那么拷贝的时候对拷贝的内容有什么要求呢,我们看一下这一段代码:

int main()
{
	char arr[] = { "xxxxxxxxxxxxxx" };
	char arr2[] = { "abcde" };
	char cc[] = { "xxxxxxxxxxxxxxxx" };
	char cc2[] = { "abcde\0fgh" };

	strcpy(arr, arr2);
	strcpy(cc, cc2);
	//执行结果是什么?
	printf("%s\n", arr);//abcde
	printf("%s", cc);//abcde
}

为什么cc2被拷贝过去的时候没有把全部都拷贝过去,这是因为strcpy是字符串拷贝,而字符串的意思就是遇到'\0'就结束这段字符,所以在拷贝过去的时候,当strcpy遇到'\0'就停下来了。而且打印的时候没有打印出cc后面的xxx,意味着'\0'也是被拷贝了的。

② 那么下面这个代码呢

int main()
{
	char arr[] = { "xxxxxxxxxxxxxx" };
	char arr2[] = { 'a','b','c','d' };

	strcpy(arr, arr2);//EOF!
	printf("%s", arr);
	return 0;
}

运行起来会是这样的,引发写入冲突异常

实际上,当这段由字符组成的字符串传过去的时候,由于没有'\0'结束标志,strcpy会一直读下去,直到直到'\0',当得到的字符串比arr字符串长的时候,拷贝过去的就要放到非arr区域的内存,这就导致写入错误了。这样子同理得出,我们拷贝过去的目的地址要足够大,能容纳拷贝内存,才可以正常运行。

③ 接下来一段代码


int main()
{
	const char *p = { "xxxxxxxxxxxxxx" };
	char arr2[] = {"abcd"};

	strcpy(p, arr2);//EOF!
	printf("%s", p);
	return 0;
}

这个代码运行的时候也会出现同样的错误,在这里我们定义的是一个常量字符串,是不能修改的,而且加了const修饰,也就是拷贝过去也不能修改其内容,这个代码也是错误的。

接下来又到了我们实现自定义函数的时候了:

my_strcpy(自定义拷贝函数)

#include <assert.h>
char* my_strcpy(char* arr,const char* arr2)
{
	assert(arr && arr2);//断言
	int* p = arr;
	while ((*arr++) = *(arr2++))
	{
		;
	}
	return p;
}

int main()
{
	char arr[20] = "xxxxxxxxxxxxxxx";
	char arr2[] = "bcdefg";
	my_strcpy(arr, arr2);

	printf("%s", arr);
	return 0;
}

总结:

使用strcpy函数的时候需要注意:
1.源字符串必须以 ‘\0’ 结束。
2.会将源字符串中的 ‘\0’ 拷贝到目标空间。
3.目标空间必须足够大,以确保能存放源字符串。
4.目标空间必须可变。

2.strcat

老规矩,先来看一下定义:

这里我们是不是看见跟strcpy有些相似的参数char * destination, const char * source,同样是目的地和源,但这里的意思不是拷贝,而是衔接上,也就是将一段字符串接上另一端字符串。

int main()
{
	char arr1[20] = "abcde";
	strcat(arr1, "fgh");

	printf("%s", arr1);//abcdefgh
	return 0;
}

但是注意,使用strcat要注意以下几点:

① 目的字符串要有足够空间:

至少能容纳下源字符串的内容。

int main()
{
	char arr1[] = "abcde";
	strcat(arr1, "fgh");

	printf("%s", arr1);
	return 0;
}

假设是这样子的代码,虽然可以运行出结果,但是会报告错误,因为我们在创建arr的时候只定义了6个字节,那么接上去访问的就是非法地址了。同样的没有定义足够的空间,代码就会报错。
在这里插入图片描述
② 源字符串必须以'\0' 结束:

int main()
{
	char arr[20] = "abcd";
	char arr2[] = {'e','f','g'};
	strcat(arr, arr2);

	return 0;
}

当你链接起来起源字符串后,肯定需要有结束标准’\0',如果没有,那么同样会导致和strcpy函数一样的结果,继续访问下去未知的字符,知道随机遇到一个'\0'

③ 目标空间必须可修改:

这里的道理和上面的strcpy中一样,大家可以尝试和上面的strcpy函数一样,在目的地的字符数组上加上const,就不能修改了。

最后
自定义函数实现strcat功能

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

char * my_strcat(char* desc, char* src)
{

	assert(desc && src);
	char* p = desc;
	while (*desc != '\0')
	{
		desc++;
	}

	while (*desc++ = *src++)
	{
		;
	}
	return p;
}


int  main()
{
	char arr[20] = "abcd";
	char arr2[] = "efg";
	printf("%s", my_strcat(arr, arr2));
	return 0;
}

3.strcmp

这是一个什么样的库函数呢?我们来看一下:

strcmp是比较字符串的,比较的是字符串的内容,不是长度。

这里的参数是const char * str1, const char * str2两个字符串的首元素地址,我们再来看一下定义:将C字符串str1与C字符串str2进行比较。所以它是一个比较字符串的库函数,这里需要注意的是,当strcmp比较两个字符串的时候,是一个一个的进行比较,而且比较的是这两个字符的ascii码值。

再来看一下,当比较完之后,显示的结果是什么样子的

这里说明了,当字符串str1字符串str2比较的时候,如果字符串str1字符串str2一样,那么返回值就是0。如果不相等,返回值就不是0。当str1str2中第一个不匹配的字符的ascii码值比较,如果str1的大,则返回值是正数,反之就是负数。

附ASCII码表:

① 我们来用代码说明一下

int main()
{
	char arr1[] = "abc";
	char arr2[] = "abc";
	char arr3[] = "bc";
	char arr4[] = "c";

	int ret1 = strcmp(arr1, arr2);
	int ret2 = strcmp(arr1, arr3);
	int ret3 = strcmp(arr2, arr4);
	int ret4 = strcmp(arr4, arr3);

	printf("%d\n", ret1);//0
	printf("%d\n", ret2);//-1
	printf("%d\n", ret3);//-1
	printf("%d\n", ret4);//1
	return 0;
}

一个是"abc""abc"比较,所以结果为0

第二个是"abc""bc"比较,显然不一样,第一个不一样的就是第一个字符,也就是’a’和’b’不一样,而后者的ASCII码较大,所以是后者大于前者,结果打印-1

第三个是"abc""c"比较,也是不一样,然后不一样在第一个字符,a和c比,c大,所以后者比前者大,打印了-1

第四个是"c""bc"比较,还是不一样,不一样也在第一个字符,c比b大,所以前者比后者大,打印的是1

② 然而,当然如果两个字符串是下面这样子的情况,比较的时候就是比较字符串结束标准的'\0'了,打印也是看哪一个大:

int main()
{
	char arr1[] = "abc";
	char arr2[] = "ab";
	//实际上就是"abc\0"和"ab\0"比较
	//第一个不同的字符是c和\0,明显c的ASCII值大
	
	int ret1 = strcmp(arr1, arr2);
	printf("%d\n", ret1);//1
	
	return 0;
}

又到了一年一度的自定义实现函数环节:

int my_strcmp(const char* s1, const char* s2)
{
	assert(s1 && s2);
	while (*s1 == *s2)
	{
		if (*s1 == '\0')
		{
			return 0;
		}
		s1++;
		s2++;
	}

	return *s1 - *s2;

}

int main()
{
	char arr1[] = "abc";
	char arr2[] = "a";
	
	int ret1 =my_strcmp(arr1, arr2);
	
	if (ret1 == 0)
	{
		printf("=");
	}
	else if (ret1 < 0)
	{
		printf("<");
	}
	else if (ret1 > 0)
	{
		printf(">");
	}

	return 0;
}

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

上面我们学习了几个函数,都是不受长度限制的,但是不受限制有时候就会出现一些问题,比如非法访问啊,数组越界之类的,所以为了安全,我们在这几个字符串里面加上了一个n,也就是strncpystrncatstrncmp,多了一个参数限制,也就安全起来了。

1.strncpy

我们先来看一下strncpy的定义,还有比较一下strcpy和它的区别:

这里最明显就是多了一个参数,这个就是最明显的区别啦,当我们使用strncpy的时候,我们最后一个参数决定着我们拷贝字符串的数量,也就是可以决定我们的拷贝范围了。

① 我们来尝试用代码去弄出来

int main()
{
	char arr[] = "xxxxxxxxxx";
	char arr2[] = "abcd";

	strncpy(arr, arr2, 2);
	printf("%s", arr);

	return 0;
}

当我们将代码运行的时候,我们选择的2就显现出作用了,字符串arr2其实有4个字符(不带’\0’的话),然而我们拷贝过去的只是两个字符,这就是这个参数的作用。

② 那如果拷贝的内容比拷贝的源字符串内容多呢:

我们来试一下:

int main()
{
	char arr[] = "xxxxxxxxxx";
	char arr2[] = "abcd";

	strncpy(arr, arr2, 7);
	printf("%s", arr);

	return 0;
}

我们可以调试起来,观察字符串的变化情况
strncpy函数调用前:

strncpy函数调用后:

这样我们可以明显的观察到,我们的拷贝参数是7,而arr2带上'\0'也就5个字符,那拷贝过去的是什么呢,我们通过观察,看见在超出拷贝内容后,拷贝过去的是'\0'也就是说,拷贝超出的每一位,拷贝过去的都会补一个'\0'


自定义函数实现:

#include <assert.h>
char* my_strncpy(char* arr, const char* arr2, int n)
{
	assert(arr && arr2);//断言
	char* p = arr2;
	char* q = arr;
	
	while (n) {
		if ((*arr = *arr2) != 0)
		{
			arr++;
			arr2++;
			n--;
		}
		else
		{
			*arr++ = '\0';
			n--;
		}
			
	}

	return q;
}

int main()
{
	char arr[20] = "xxxxxxxxxxxxxxx";
	char arr2[] = "bcdefg";
	int n = 0;
	printf("请输入需要复制的字符位输:>");
	scanf("%d", &n);
	my_strncpy(arr, arr2, n);

	printf("%s", arr);
	return 0;
}

2.strncat

接下来是strncat

同样的也是多了一个参数限制,让他在追加字符串的时候可以规定追加的数量。
① 我们来用一下试试:


int main()
{
	char arr[20] = "abc";
	char arr2[] = "defg";

	strncat(arr, arr2,2);
	printf("%s", arr);//abcde
	return 0;
}

得到的结果就是:abcde

所以说参数的功能就是控制追加字符串的长度,那追加字符串只追加两个,会被覆盖掉字符串的’\0’然后再追加吗?我们可以试一下,观察变化:

int main()
{
	char arr[20] = "abc\0xxxxxx";
	char arr2[] = "defg";

	strncat(arr, arr2,2);
	printf("%s", arr);
	return 0;
}

    

通过对比我们发现,strncat在追加的时候,如果有'\0',确实是会将'\0'覆盖,然后再在追加字符后再加上一个'\0'

② 当输入参数大于源字符串长度时:

int main()
{
	char arr[20] = "abc";
	char arr2[] = "defg";

	strncat(arr, arr2,7);
	printf("%s", arr);
	return 0;
}

当输入参数大于源字符串长度时,会向strncpy一样在后面追加’\0’吗,我们调试起来观察后得知,是不会的,当strncat的追加参数大于源字符串长度的时候,也仅仅会追加字符串的内容,并不会再补上'\0'


自定义函数环节:

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

char* my_strncat(char* desc, char* src, int n)
{

	assert(desc && src);
	char* p = desc;
	while ( *desc != '\0')
	{
		desc++;
		
	}

	while ((n>0) && (*desc++ = *src++))
	{
		n--;
	}
	return p;
}


int  main()
{
	char arr[20] = "abcd";
	char arr2[] = "efg";
	int n = 0;
	scanf("%d", &n);
	printf("%s", my_strncat(arr, arr2,n));
	return 0;
}

3.strncmp

同样的strncmp也是增加了一个参数,可以让我们选择对比前多少个字符,这里就不再重新阐述了。直接上代码:

int main()
{
	char arr[] = "abcd";
	char arr2[] = "abd";

	int ret1 = strncmp(arr, arr2, 2);
	int ret2 = strncmp(arr, arr2, 3);
	int ret3 = strncmp(arr, arr2, 4);

	printf("%d\n", ret1);//0
	printf("%d\n", ret2);//-1
	printf("%d\n", ret3);//-1


	return 0;
}

结果展示:

在这里,显然看出是选择位数比较得出不同的结果,而ret2ret3的结果是一样的,显然在起码比较不一样之后,后面实际上就不需要比较下去了。


自定义函数实现:

int my_strncmp(const char* s1, const char* s2, int n)
{
	assert(s1 && s2);
	while ((n>0) && (*s1 == *s2))
	{
		if (*s1 == '\0')
		{
			return 0;
		}
		s1++;
		s2++;
		n--;
	}
	if (n == 0)
	{
		return 0;
	}
	else
	{
		return *s1 - *s2;
	}

}

int main()
{
	char arr1[] = "abcde";
	char arr2[] = "abcef";
	int n = 0;
	scanf("%d", &n);
	int ret1 = my_strncmp(arr1, arr2,n);

	if (ret1 == 0)
	{
		printf("=");
	}
	else if (ret1 < 0)
	{
		printf("<");
	}
	else if (ret1 > 0)
	{
		printf(">");
	}

	return 0;


好啦,本篇的内容就先到这里,关于字符串和内存函数还有第二篇噢,敬请期待。关注一波,互相学习,共同进步。

还有一件事:


点击全文阅读


本文链接:http://zhangshiyu.com/post/34962.html

字符串  函数  拷贝  
<< 上一篇 下一篇 >>

  • 评论(0)
  • 赞助本站

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

关于我们 | 我要投稿 | 免责申明

Copyright © 2020-2022 ZhangShiYu.com Rights Reserved.豫ICP备2022013469号-1