【C语言】字符函数和字符串函数

今天我分享一下我学习一些新的函数的心得。

主要有以下两大类:

一.字符函数

二.函数使用或模拟其实现(这个对于提高能力有重要作用

一.字符函数

1.字符分类函数    (注意:都要包含<ctype.h>的头文件

我们看一下有哪些函数:

(假如这个函数符合条件,就会返回一个非0的数表示真,反之返回0表示假)

 

通俗来讲,你把相应的参数传进这个函数里面,它会自动判断你输入的符不符合要求,符合则返回真,不符合则返回假。

这些我们最好记住,以后写题目会有一定的优势。

2.字符转换函数

我们C语言提供了两个转换函数,tolower(将大写字母转换成小写字母),toupper(将小写字母转换成大写字母),下面我们来结合实例来看看这些函数的使用方法和便利性:

*将大写字母统一转换成小写字母

思路:遍历一下先判断是不是大写字母,然后再转换,最后打印出来。

代码如下:

#include<stdio.h>
#include<ctype.h>
int main()
{
	char arr[11] = "HELLOworld";
	int x = 0;
	for (x = 0; x < strlen(arr); x++)
	{
		if (isupper(arr[x]))//判断是不是大写字母
		{
			arr[x]=tolower(arr[x]);//是就改为小写字母
		}
	}
	printf("%s", arr);//打印
	return 0;
}

这里注意:我们只是单纯的把大写字母传过去是不会改变原来的内容的,我们要把原来的位置去接受改变了的元素,才可以正确的打印在我们面前。

其它的函数如islower,isupper,isdigit,isspace,isblank等都非常类似,感兴趣的小伙伴们可以自己去尝试。

上面的函数我们想要去模拟实现的话,抓准ascii码值加上加减判断等,应该就可以较为容易的完成模拟实现了。(例如:A的ascii码值是65,a的ascii码值是97,以此类推,完成字母转换的实现)

二.函数使用或模拟其实现

1.strlen函数的使用和模拟实现(注意:无符号+-无符号=无符号,即使得到负数也会把负号抹去)

我们在C++的官方网站上去寻找strlen方面的内容:

这里我们要注意这个函数的返回类型是size_t(无符号整型),参数是起始元素的地址,我们不希望我们的元素被改变,故加上const。

我们先去用一下strlen函数:(功能:求字符串的长度)

#include<stdio.h>
#include<string.h>
int main()
{
	char arr[11] = "helloworld";
	printf("%zd", strlen(arr));
	return 0;
}

%zd是专门用来打印无符号整型的。

下面我们来模拟实现一下strlen函数:

我认为,满足模拟的条件包括但不限于:

字符(串)要有‘\0’,指针要避免野指针,常量字符串不能改变内容,数组要足够大。

我们看一下模拟实现的效果:

#include<stdio.h>
#pragma warning(disable:4996)
#include<ctype.h>
#include<string.h>
#include<assert.h>
size_t my_strlen(const char*str)
{
	assert(str != NULL);
	if (*str != '\0')
	{
		return 1 + my_strlen(str + 1);//用递归可以避免创建新变量
	}
	else
	{
		return 0;
	}
}
int main()
{
	char arr[11] = "helloworld";
	printf("%zd", my_strlen(arr));
	return 0;
}

#include<stdio.h>
#pragma warning(disable:4996)
#include<ctype.h>
#include<string.h>
#include<assert.h>
size_t my_strlen(const char* arr)
{
	char* p = arr;//先保留一个
	assert(p);//防止arr是野指针
	while (*p != '\0')
	{
		p++;
	}
	return p - arr;
	
}
int main()
{
	char arr[11] = "helloworld";
	printf("%d", my_strlen(arr));//指针-指针的方法获取元素个数
	return 0;
}

注意:里面的str+1不建议写成++str或str++(先使用再加加,总是传进去h的地址,永远没有尽头),可读性差,而且前者没问题,后者死循环,容易搞错,要写得清楚明白。

2.strcpy函数的使用和模拟实现

我们先在c++官网上查看strcpy的使用:(功能:拷贝字符串)

我们如果要去模拟实现,就要重点模仿它的形式,功能等,而且不仅仅是会模仿,而且要去不断简化

我们先去使用它:

#include"20230905定义.h"
#include<stdio.h>
#pragma warning(disable:4996)
#include<ctype.h>
#include<string.h>
#include<assert.h>
int main()
{
	char arr1[11] = "helloworld";//将要被替换的字符串
	char* p = "xxxxxxxxxx";//主动替换别人的字符串
	printf("%s\n", strcpy(arr1,p));
	return 0;
}

看一下结果:

我们顺便去剖析一下strcpy函数:它的返回类型是char*,说明它要传回去最终的地址,然后打印出来,左边的char*destination是被拷贝的数组的首元素地址,const char*source是主动去替换别人的数组首元素地址,而且这个数组的内容我们不希望被改变

考虑到这里,我们应该想到,要模拟实现这个函数,我们至少要注意下面几个内容:包括但不限于:被替换的数组足够大,斜杠0要同时拷贝过去,注意指针要有效,尽力去简化等。

我们在原来的基础上再去改造一下:

#include<stdio.h>
#pragma warning(disable:4996)
#include<ctype.h>
#include<string.h>
#include<assert.h>
char* my_strcpy(char* dest, const char* source)
{
    char* tmp = dest;//因为下面dest会被改变,故存到一份来返回,等一下传出来就好
    assert(dest && source);
    while (*dest++ = *source++)//既满足了遍历,又考虑了把字符'\0'弄进去,跳出循环,简洁明了
    {
        ;
    }
    return tmp;//返回被改变后的原地址
}
int main()
{
    char arr1[11] = "helloworld";//将要被替换的字符串
    char* p = "xxxxxxxxxx";//主动替换别人的字符串(且它是常量字符串,根本不能被更改,上面的const只是为了更严谨)
    printf("%s\n",my_strcpy(arr1,p));
    return 0;
}

注意:中间的循环也可以分步骤去写,但是斜杠0要在循环外面加上。

3.strcat的使用及模拟实现(功能:拼接字符串)

我们先来了解一下strcat函数:

它是用来连接字符串的,返回前面这个字符串的首元素的地址,将待拼接的字符串的首元素覆盖掉上一个字符串末尾的\0,且防止后面的字符串的内容被改变。

我们先来使用一下:

#include<stdio.h>
#pragma warning(disable:4996)
#include<ctype.h>
#include<string.h>
#include<assert.h>
int main()
{
	char arr1[20] = "hello";
	char* p = "world";
	printf("%s", strcat(arr1, p));
	return 0;
}

我认为我们若要模拟,包括但不限于注意以下几点:

保证数组足够大,能塞得下;常量字符串的内容不可以被修改;注意后面接上去的内容不可以被修改;找到前面那个字符串的\0然后用后面这个字符串覆盖;指针防;内存可以改动

我们模拟一下:

#include<stdio.h>
#pragma warning(disable:4996)
#include<ctype.h>
#include<string.h>
#include<assert.h>
char* my_strcat(char* dest, const char* src)
{
	assert(dest && src);
	char* tmp = dest;//存一份备用
	while (*dest!= '\0')//使arr挪动到'\0'的位置
	{
		dest++;
	}
	//然后偷偷依次把后面字符串的内容塞过去,相当于拷贝部分
	while (*dest++ = *src++)
	{
		;
	}
	return tmp;
}
int main()
{
	char arr[20] = "hello";
	char* p = "world";
	printf("%s\n", my_strcat(arr, p));
	return 0;
}

注意:灵活去使用拷贝的思路,使代码简洁明了。

4.strcmp的使用和模拟实现(功能:比较字符串的大小)

我们先来了解一下这个函数:

它的返回类型是int类型,输入两个字符串的首元素的地址,且不希望两个字符串的内容被改变,当str1<str2返回负数,str1>str2返回正数,str1==str2返回0。

现在我们来操作一下:

#include<stdio.h>
#pragma warning(disable:4996)
#include<ctype.h>
#include<string.h>
#include<assert.h>
int main()
{
	char arr1[10] = "hello";
	char arr2[10] = "world";
	printf("%d", strcmp(arr1, arr2));
}

然后我们来模拟实现一下,我认为我们至少应该注意以下几点:

字符串里面最好要有\0,创建的数组不要越界,传入的是字符,不希望字符的内容被更改

我们来试着模拟一下:

int my_strcmp(const char* str1, const char* str2)
{
	while (*str1 == *str2)//按照常理来说,一般比较的就是前段部分相同的字符串
	{
		if (*str1 == '\0')//双双碰到\0就停下来,没有必要再比下去了。
		{
			return 0;
		}
		str1++;
		str2++;
	}//当不相等的时候会跳出来
	if (*str1 > *str2)
	{
		return 1;
	}
	else//上面因为不相等才跳出来,所以我们不需要考虑是否相等的情况,思维要严谨
	{
		return -1;
	}
}
int main()
{
	char arr1[10] = "hello";
	char arr2[10] = "world";
	printf("%d", my_strcmp(arr1, arr2));
	return 0;
}

这里我忘记了断言,以防止传入的是空指针,伙伴们记得加上去哦。

5.strncpy的使用和模拟实现(功能:有限制的去拷贝字符串)

我们先来看一下它的大致情况:

它比strcpy更好的地方在于它可以限制拷贝多少个字符过去,安全性更高。

num指的是能拷贝过去的字符个数,假如用三个字符"ab\0"拷贝过去,但num传递5过去,剩下两个多余的用\0补充。

我们试着使用一下它:

#include<stdio.h>
#pragma warning(disable:4996)
#include<ctype.h>
#include<string.h>
#include<assert.h>
int main()
{
	char arr1[11] = "helloworld";
	char arr2[11] = "xxxxxx";
	printf("%s",strncpy(arr1, arr2, 3));
	return 0;
}

它很老实,说了几个就几个,\0也不会乱补充。

再试一下:改8个:

现在我们要去模拟一下:

我认为我们至少要在strcmp函数的基础上,搞清楚它的循环条件。

我们在原来的基础上改造一下:

#include<stdio.h>
#pragma warning(disable:4996)
#include<ctype.h>
#include<string.h>
#include<assert.h>
char* my_strcpy(char* arr1, const char* arr2, size_t n)
{
	char* p = arr1;
	int i = 0;
	for (i = 0; i < n; i++)
	{
		if (*arr2 != '\0')
		{
			*arr1 = *arr2;
			arr1++;
			arr2++;
		}
		else
		{
			*arr1 = '\0';//为了尽量和strncpy的样子保持一致
			arr1++;
		}
	}
	return p;
}
int main()
{
	char arr1[10] = "helloworld";
	char arr2[7] = "xxxxxx";
	printf("%s", my_strcpy(arr1, arr2, 8));
	return 0;
}

我忘记了断言,伙伴们记得加上去

6.strncat函数的使用和模拟实现(功能:有条件的去拼接字符串)

我们也来一起看一下:

它就是能限定拼接的字符串个数,如果到了\0后面就停止拼接。

我们先来一起使用一下:

#include<stdio.h>
#pragma warning(disable:4996)
#include<ctype.h>
#include<string.h>
#include<assert.h>
int main()
{
	char arr1[20] = "hello\0twuwuwwqq";
    char arr2[11] = "world";
	printf("%s", strncat(arr1, arr2, 7));
	return 0;
}

是没有问题的。

现在我们来模拟实现一下,我认为我们至少要注意在strcat的基础上,尽量不要超过数组的最大容量,而且拼接过头了也不要多送"\0"。

#include<stdio.h>
#pragma warning(disable:4996)
#include<ctype.h>
#include<string.h>
#include<assert.h>
char* my_strncat(char* arr1, const char* arr2, size_t num)
{
	char* p = arr1;
	int n = 0;
	while (*arr1 != '\0')
	{
		arr1++;//跳到'\0'的位置上
	}
	for (n = 0; n < num; n++)
	{
		if (*arr2 != '\0')
		{
			*arr1 = *arr2;
			arr1++;
			arr2++;
		}
		else
		{
			*arr1 = *arr2;
			return p;
		}
	}
	return p;
}
int main()
{
	char arr1[20] = "hello\0twuwuwwqq";
    char arr2[11] = "world";
	printf("%s", my_strncat(arr1, arr2, 7));
	return 0;
}

记得凡是遇到指针,都要考虑断言

如果有一些改进或不足的话,欢迎各位大佬指点。

7.strncmp的使用和模拟实现(功能:有条件的去比较字符串)

我们先看一下这个函数:

它可以规定双双比较字符的大小,直到不相等返回数据。

我们先来使用一下:

#include<stdio.h>
#pragma warning(disable:4996)
#include<ctype.h>
#include<string.h>
#include<assert.h>
int main()
{
	char arr1[20] = "abcdefg";
	char arr2[20] = "abcdefgh";
	printf("%d", strncmp(arr1, arr2, 5));
	return 0;
}

然后我们去模拟实现一下,我认为我们应该至少注意停止的条件。

看代码:

#include<stdio.h>
#pragma warning(disable:4996)
#include<ctype.h>
#include<string.h>
#include<assert.h>
int my_strncmp(const char* arr1, const char* arr2, size_t num)
{
	int i = 0;
	for (i = 0; i < num; i++)
	{
		if (*arr1 == *arr2)//判断是否相等即可
		{
			if (*arr1 == '\0')
			{
				return 0;
			}
			arr1++;
			arr2++;
		}
		else
		{
			break;
		}
	}
	if (*arr1 - *arr2 > 0)
	{
		return 1;
	}
	else if(*arr1-*arr2<0)
	{
		return -1;
	}
	else
	{
		return 0;
	}
}
int main()
{
	char arr1[20] = "abcdefg";
	char arr2[20] = "abcdefgh";
	printf("%d", my_strncmp(arr1, arr2, 5));
	return 0;
}

看见指针就要去断言啊!

其实还可以继续简化,待各位大佬指点。

7.strstr函数的使用和模拟实现(功能:在字符串中查找子字符串)

我们先来看一下这个函数:

我们不希望这两段字符串被改变,故用上const修饰;函数返回的指针对应的内容我们不希望被改变,返回类型我们在模拟实现的时候可以加上const修饰。

我们现在来使用一下:

#include<stdio.h>
#pragma warning(disable:4996)
#include<ctype.h>
#include<string.h>
#include<assert.h>
int main()
{
	char arr1[60] = "helloworwoworworldnnxxd";
	char arr2[60] = "world";
	char* p=strstr(arr1, arr2);
	printf("%s\n", p);
	return 0;
}

那我们来模拟实现一下:

至少,我们要注意:

指针的有效性,对应内容的不可改性,循环等停止的条件,还有要考虑要不要预留指针等。

我们看一下代码:

#include<stdio.h>
#pragma warning(disable:4996)
#include<ctype.h>
#include<string.h>
#include<assert.h>
const char* my_strstr(const char* str1, const char* str2)//分可以找到和不可以找到的两种情况
{
	assert(str1);
	if (*str2 == '\0')
	{
		return str1;//这是一种特殊的情况,我们要考虑在内,这在原版strstr函数里面是包括在内的
	}
	const char* cp = str1;//用来定位
	const char* s1 = NULL;//用来跟在cp及后面,依次和s2++,s1++检验是否符合
	const char* s2 = NULL;//从str2处开始走,和s1判断
	while (*cp != '\0')//cp对应0了还怎么去判断呢,退出来就是了
	{
		s1 = cp;//从cp的位置继续走
		s2 = str2;
		while (*s1 == *s2&&s1&&s2)//要循环多次判断,双方都还在的情况下
		{
			s1++;
			s2++;//相同就一起往后面移动
		}
		if (*s2 == '\0')//s2走到了头
		{
			return cp;//找到了
		}
		cp++;//移动cp,如果寻找失败,分别确定新位置
	}
	return NULL;//没有找到

	
}
int main()
{
	char arr1[60] = "helloworwoworworldnnxxd";
	char arr2[60] = "world";
	char* p=my_strstr(arr1, arr2);
	printf("%s\n", p);
	return 0;
}

我们来画图来解释一下:

也可以用调试的方法去观察,这里就不展示了。

拓展:我们也可以使用KMP算法,有兴趣的小伙伴们可以去试一下。

8.strtok的使用(功能:分割字符和符号)

我们先来看一下这个函数:

这里我们传的第一个指针是指向整个字符串的指针,第二个参数是指向里面分隔符的指针,这个函数会把标点符号等转变为‘\0’,这样就起到了可以分割字符串的作用。

所以这里我们的str指向的内容是需要被修改的,而delimiters指向的内容是不希望被改变的,我们用const修饰;它返回str。

我们现在来使用一下:

#include<stdio.h>
#pragma warning(disable:4996)
#include<ctype.h>
#include<string.h>
#include<assert.h>
int main()
{
	char arr1[100] = "helloworld@122.234.554.net.com";
	char arr2[100] = "@....";
	printf("%s\n",strtok(arr1, arr2));
	return 0;
}

现在我们来监视一下看一下:

它就刚好改了一个为‘\0’。

如果要把它连续打印呢?

#include<stdio.h>
#pragma warning(disable:4996)
#include<ctype.h>
#include<string.h>
#include<assert.h>
int main()
{
    char arr1[100] = "helloworld@122.234.554.net.com";
    char arr2[100] = "@....";
    char* p = NULL;
    for (p = strtok(arr1, arr2); p != NULL; p = strtok(NULL, arr2))
    {
        printf("%s\n",p);
    }
    return 0;
}

虽然这个循环很奇怪,但是它就是不断这样记住这些地址然后去打印,但是我们这里传了空指针,所以这个函数内部应该是有static修饰,记住了上一次的位置等,有兴趣的小伙伴们可以自行去尝试。

9.strerror函数以及改版perror函数的使用(功能:前者可以用来读取错误信息代号对应的代码的地址,后者可以在前者的基础上顺便进行打印)

我们先看一下strerror函数的基本情况:

在这种情况我们一般在使用的时候传参进去errno,这个errno可以自动判断上面的程序出现了什么问题,并把错误的代号赋值到errno里面去,然后经过这个strerror函数的加工可以返回具体错误信息的地址,我们把它拿过来打印一下就好了。

我们看一下代码:(用来先理解一下)

#include<stdio.h>
#pragma warning(disable:4996)
#include<ctype.h>
#include<string.h>
#include<assert.h>
int main()
{
    int i = 0;
    for (i = 0; i < 10; i++)
    {
        printf("i:%s\n", strerror(i));
    }
    return 0;
}

这样我们就可以很好的查看代号对应的代码了。

我们不妨看一下其它的情况:

#include<stdio.h>
#pragma warning(disable:4996)
#include<ctype.h>
#include<string.h>
#include<assert.h>
#include<errno.h>
int main()
{
    FILE *  pFile=NULL;
    pFile = fopen("test.txt","r");
    if (pFile == NULL)
    {
        printf("打不开的原因是:%s", strerror(errno));
    }
    else
    {
        printf("打开成功\n");
    }
    return 0;
}

要查找的文件必须在test系列里的文件夹才能找到,而且要是展开了的文件名对应才可以找到。

而perror函数多了一个打印的效果,如图:

#include<stdio.h>
#pragma warning(disable:4996)
#include<ctype.h>
#include<string.h>
#include<assert.h>
#include<errno.h>
int main()
{
    FILE *  pFile=NULL;
    pFile = fopen("test.txt","r");
    if (pFile == NULL)
    {

        perror("打不开");
        printf("打不开的原因是:%s", strerror(errno));
    }
    else
    {
        printf("打开成功\n");
    }
    return 0;
}

效果一目了然。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值