初识c语言(字符串函数和内存函数)

一、strlen函数

strlensize_t strlen ( const char * str );

易错点,返回的是无符号整型:如图所示,打印>

还有就是形参类型const char* (加上const更安全)

int main()
{
    if (strlen("ab") - strlen("abcd") > 0)//2-4=-2
    {     // -2在内存中存放的补码形式:11111111 11111111 11111111 11111110 
        printf(">");     //两个无符号数相减,得到无符号数,该补码当无符号处理。所以结果为其正数
    }
    else
    {
        printf("<");
    }
    return 0;
}

strlen函数的三种实现版本:

1、计数器版本:创建一个变量去实现计数,直到字符串结束符’\0‘

int my_strlen(char* shu)//为安全起见,形参类型应加上const
{
	int count = 0;//计数
	while (*shu++)//当*shu为'\0'时,没有再进入循坏,计算的是'\0'之前的字符个数
	{
		count++;
	}
	return count;
}
int main()
{
	char shu[] = "I am Ok!";
	printf("%d ", my_strlen(shu));
	return 0;
}

2、递归版本:在不建立临时变量去计数,用递归方式计数

int my_strlen1(char* shu)
{
	if (*shu)
	{
	   return 1 + my_strlen1(shu+1);
	}
	return 0;
	//或者是:
	//if (*shu != '\0')
	//	return 0;
	//else
	//	return 1 + my_strlen1(shu + 1);
}
int main()
{
	char shu[] = "I am Ok!";
	printf("%d ", my_strlen1(shu));
	return 0;
}

 递归中要注意自加自减和+的区别,否则将会出现问题:如下代码

void convert(char* a, int n)
{
	int i;
	printf("\n%p", a);
	//if ((i = n / 10) != 0)   a+1版本
		//convert(a + 1, i);
	if ((i = n / 10) != 0)
		convert(a++, i);    a++版本
	*a = n % 10 + '0';

	//a++的版本等同于:
	//int i;
	//printf("\n%p", a);
	//if ((i = n / 10) != 0)
	//{
	//	convert(a, i);
	//	a++;
	//}
	//*a = n % 10 + '0';

}

int main()
{
	int number;
	char str[10] = " ";
	scanf("%d", &number);
	convert(str, number); 
	printf("\n");
	puts(str);
	return 0;
}

 

a+1版本:

  

补充:关于递归函数,要记得新函数是旧函数的临时拷贝, 旧函数里的指针a,不是新函数里的a,我们在调试的监视窗口可以发现其实每次递归a的地址(&a)都在变化,因为创建了一个新的a去接收地址,所以其所存的地址(*a)是一样的,最后的打印的地址结果相同,在递归完回去时,开始在该代码中进行相应的自加操作

而在a+1中,相当于b=a+1,在递归过程中已经不是原来的a。

3、指针-指针版本:两指针相减得到两地址间的字符个数

int my_strlen2(char* shu)
{
	char* shu2 = shu;
	while (*shu2++)//这个版本在判断'\0'跳出循环后仍自加1,
		;
	//或者是:
	//while (*shu2)//和上面版本的区别是判断'\0'后不再自加。
	//	shu2++;
	//return shu2 - shu;
	return shu2-shu-1;//所以指向的是结束符后的地址要减1才能得到中间除结束符外的字符个数
}
int main()
{
	char shu[] = "I am Ok!";
	printf("%d ", my_strlen2(shu));
	return 0;
}

二、strcpy函数

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

注意:

1、形参类型和所代表对象,前一个是目的地的地址,后面是拷贝的对象地址

2、注意拷贝对象要有字符串结束符'\0',其中可能出现问题的如下面这种初始化方式

char shu[] = {'w','a' };//'\0'符号位置未知

结果如图所示,'\0'位置未知,wa字符拷贝完成后继续拷贝内容(内容乱码),直至结束符终止

3、要保证目标空间足够大,以确保能存放拷贝的字符串

4、目标空间必须可变

目标内容是常量字符串不可修改

5、strcpy函数的模拟实现

char* my_strcpy(char*shu, char*shu1)
//拷贝的对象应该加上const会比较安全,const char*shu
{
	assert(shu && shu1);
	char* p = shu1;
	while (*shu1++ = *shu++)
		;
	return p;
}
int main()
{
	char shu[] = "what?";
	char shu1[] = "I don't know!";
	my_strcpy(shu,shu1);
	printf("%s ", shu1);
	return 0;
}

2.1、strncpy函数 

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

1、拷贝我们限定数量的字符串到目标空间 ,即形参num接收我们所设定的大小

2、如果拷贝字符串的长度小于num,则拷贝完源字符串之后,在目标的后边追加0,直到num个。

模拟实现strncpy函数

char*  my_strncpy(char* dest, char* sour, int count)//要加const
{
	char* p = dest;
	while (count&&(*dest++ = *sour++))//如果有一方为0则跳出循环
	{            //这个赋值方式刚好将dest指针指向'\0'后面的地址,实现了如果拷贝字符串小于num,则可以直接在后面
		count--;
	}
	if (count)
	{
		while (count--)//跳出循环时count=0,进入条件判断时,判断的就是减后的值,所以当减为0后,没有进入而是立马跳出循环
		{              //如果为count--,跳出循环时count=-1,进入条件判断时先判断未减1的值,进入循环后减1,所以值为0时还在循环体里,再判断时跳出循环,但也自减为-1
			*dest++ = '\0';
		}
	}
	return p;
}

int main()
{
	char shu[] = "what?";
	char shu1[] = "I!";
	my_strncpy(shu,shu1,4);
	printf("%s", shu);
	return 0;
}

关于(*dest++ = *sour++)和while (count--)我们可以发现,

dest指向的是'\0'后面的地址,所以当wile里的判断条件是count--时,我们可以发现其本质是将原本拷贝不足的位数又增加了一位赋为'\0',如下图:

 当while条件里为--count时,其实把之前在dest指向在'\0'后面的那一位给减掉了(因为字符拷贝后指向'\0‘后面的地址,相当于是自动为后面不够拷贝的内容赋上了'\0’)

如下图才是拷贝位数为4的结果:

 但其实可以发现,在最后的打印结果中,二者是一样的(打印字符串时,遇到'\0'就结束了),只是记得要区分自加和自减后分别的值是什么。

三、strcat函数

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

a、追加时时先找到目标字符串的'\0',然后进行追加(拷贝内容从覆盖'\0‘的位置开始),所以自己不能给自己追加:因为追加时‘\0'被覆盖,循环追加没有结束,陷入死循环

b、还有就是函数的追加内容不一定是字符数组:

char* my_strcat(char* shu1,const char* shu)
{
     while (*shu1)
	{
		shu1++;
	}
	while (*shu1++ = *shu++)
		;
	return p;
}

int main()
{
    //char shu1[37] = "haha ! ";
	//printf("%s ", my_strcat(shu1,"but i think you're right." ));
	const char* shu = "but i think you're right.";
    char shu1[37] = "haha ! ";
	printf("%s ", my_strcat(shu1, shu));
	return 0;
}

注意:

1、同上必须以字符串结束符结束

2、目标空间足够大,能容纳追加的字符串长度

3、目标空间必须可变,

4、strcat函数的模拟实现

我们可以知道strcat函数的返回类型时char*,但void也可以实现同样的结果

char* my_strcat(char* shu1, char* shu)
{
	char* p = shu1;
	while (*shu1++)
		;
	shu1 = shu1 - 1;//和注释的分别是shu1指向的位置要-1得到指向'\0'的位置,
	                //记得在条件里判断是否为'\0'时又进行了自加操作
	//while (*shu1)
	//{
	//	shu1++;
	//}
	while (*shu1++ = *shu++)
		;
	return p;
}
int main()
{
	char shu[] = "but i think you're right.";
	char shu1[37] = "haha ! ";
	printf("%s ", my_strcat(shu1, shu));
	return 0;
}

3.1、strncat函数

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

模拟实现:

char* my_strncpy(char* shu2, char* shu1, int count)
{
	char* p = shu2;
	while (*shu2)
	{
		shu2++;
	}//完成循环后的shu2指向'\0'
	while (count--)
	{
		if ((*shu2++ = *shu1++)==0)//当一直到拷贝字符串为'\0'时结束,直接返回p,此时shu2其实时指向'\0'后面地址
			return p;
	}
		*shu2 = '\0';//如果设定的count小于拷贝字符个数,则拷贝完成后要加'\0'结束。
		return p;
}
int main()
{
	char shu1[23];
	char shu2[33];
	strcpy(shu1, "That's tree!");
	strcpy(shu2, "look!");
	my_strncpy(shu2, shu1, strlen(shu1));
	printf("%s ", shu2);
	
}

四、strcmp函数

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

 标准规定:

第一个字符串大于第二个字符串,则返回大于0的数字

第一个字符串等于第二个字符串,则返回0

第一个字符串小于第二个字符串,则返回小于0的数字

int main()
{
	const char* p = "why";
	const char* q = "when";
	if (strcmp(p, q) > 0)//大于则返回大于0的数
	{
		printf(">");//第一个字符小于第二个
	}
	else if (strcmp(p, q))//小于则返回大于0的数
	{
		printf("<");
	}
	else
		printf("=");//相等返回0
	return 0;
}

strcmp函数的模拟实现:

int my_strcmp (const char * src, const char * dst)
{
        int ret = 0 ;
 assert(src != NULL);
   assert(dest != NULL);
        while( ! (ret = *(unsigned char *)src - *(unsigned char *)dst) && *dst)
                ++src, ++dst;  //在这个判断条件中,如果两值相等取反后为非0则继续比较,而和*det逻辑与是因为如果出现'\0'则终止继续往下比较已经可以分出大小
        if ( ret < 0 )
                ret = -1 ;
        else if ( ret > 0 )
                ret = 1 ;
        return( ret );
}

//易读版本:
int my_strcmp(const char* p, const char* q)
{
	while (*p == *q)
	{
		if (*p == '\0')
			return 0;
		p++;
		q++;
	}
	return *p - *q;
	// 等同于:*p > * q ? (p - q) : (q - p); //不一定是1或-1,返回值只要满足大于0或小于0的数即可             
	//*p > * q ? 1 : -1; 
	//等同于:if (*p > *q)
	//{
	//	return 1;
	//}
	//else
	//{
	//	return -1;
	//}
}               

4.1、strncmp函数

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

比较到出现另个字符不一样或者一个字符串结束或者num个字符全部比较完。

模拟实现:

int my_strncmp1(const char* first, const char* last, int count)
{
	int x = 0;
	if (!count)
	{
		return 0;
	}
	for (; x < count; x++)
	{
		if (*first == 0 || *first != *last)
		{
			return(*(unsigned char*)first - *(unsigned char*)last);
		}
		first += 1;
		last += 1;
	}
}
//自己写的版本,当作笔记记下,以后再更改(最好不要看)
int my_strncmp(char* shu, char* shu1, int count)
{
	while (count)
	{
		if (*shu == *shu1&&*shu==0)
		{
			return 0;
		}
		shu++;
		shu1++;
		count--;
		 if (*shu != *shu1)
		{
			 return *shu - *shu1;
		}
	}
	return 0;
}


int main()
{
	char shu[] = "haha";
	char shu1[] = "haxixi";
	printf("%d ", my_strncmp(shu, shu1, 4));
	return 0;
}

五、 strstr函数

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

char* my_strstr(const char* str1, const char* str2)
{
	const char* s1 = NULL;
	const char* s2 = NULL;
	 char* cp = (char*)str1;//cp为返回值,和赋值的形参类型不同,需要强制类型转换
	while (*cp)
	{
		if (*str2 == '\0')//if ( !*str2 )
		{
			return (char*)str1;
		}
		s2 = str2;
		s1 = cp;
		while (*s1 && *s2 && (*s2== *s1))
		{//当判断条件只有*str2== *str1时,我们如过遇到字符匹配过程中同时以'\0'结束的两字符串时,还是会继续进入循环,造成越界访问的错误
			s1++;
			s2++;
		}
		if (*s2 == '\0')//当查找至*s2为'\0'时,我们就算是找到,且返回指针并结束查找了
		{
			return cp;
		}
		cp++;
	}
	return NULL;//当*s1未查出且查找至为'\0‘时跳出循环返回空指针


}
int main()
{
	char shu[] = "wogaizenmebanaaa";
	char shu1[] = "zenmeban";
	printf("%s ", my_strstr(shu, shu1));
	return 0;
}

 六、strtok函数

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

sep参数是个字符串,定义了用作分隔符的字符集合

第一个参数指定一个字符串,它包含了0个或者多个由sep字符串中一个或者多个分隔符分割的标 记。

strtok函数找到str中的下一个标记,并将其用 \0 结尾,返回一个指向这个标记的指针。(注: strtok函数会改变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容 并且可修改。)

strtok函数的第一个参数不为 NULL ,函数将找到str中第一个标记,strtok函数将保存它在字符串 中的位置。

strtok函数的第一个参数为 NULL ,函数将在同一个字符串中被保存的位置开始,查找下一个标 记。

如果字符串中不存在更多的标记,则返回 NULL 指针。

int main()
{
	const char *shu = "19191919.11.22/19";
	char temp[111];
	strcpy(temp, shu);
	const char* p = "./";
	char*ps=strtok(temp, p);
	//while (ps != NULL)//一开始找到标记,并将其改为'\0',第一个参数则记录其地址,然后返回开头开始查找的地址,
	//{
	//	printf("%s\n", ps);
	//	ps=strtok(NULL, p);//从函数中保存的标记位置开始查找下一个标记
	//}
	for (ps; ps != NULL; ps=strtok(NULL, p))//从标记位置开始查找,若后面无内容则返回'\0',
	{//实际的清晰表达(ps=strtok(temp, p),ps != NULL; ps=strtok(NULL, p))
		printf("%s\n", ps);  
	}
	return 0;
}

 七、strerror函数

char * strerror ( int errnum );

 //必须包含的头文件

#include <stdio.h>

#include <string.h>

#include <errno.h>

该函数功能是返回错误码,所对应的错误信息。

我们知道在调用库函数时,都会设置错误码,其保存在erron这个变量中,所以当我们将该变量传给该函数时,可以得到相应的错误信息的解释

和strerror类似的的perror函数:

perror函数,直接打印错误信息,

而strerror是把错误码转换为错误信息,若要打印该信息则要自己操作

函数原型:void perror(const char* str) 

头文件#include<stdio.h>

 如图上下版本区别,perror操作简单且自己可以不用printf打印信息。

字符转换

int tolower ( int c );

int toupper ( int c );

int main()
{
	int i = 0;
	char ch[21] = { 0 };
	scanf("%s",ch);
	while (ch[i])//判断是否为'\0',是则跳出循环
	{
		if (isupper(ch[i]))//判断逐个字符元素是否为大写,是的话进入循环
		{
			ch[i] = tolower(ch[i]);//将i对应的大写字符元素转换为小写再存入相应i的数组位置中
		}
		
		printf("%c", ch[i]);
		i++;//注意这个i的位置,是要在打印后,若是在前则会跳过一个字符元素打印
	}
	return 0;
}
字符分类函数:
函数 如果他的参数符合下列条件就返回真
iscntrl 任何控制字符
isspace 空白字符:空格‘ ’,换页‘\f’,换行'\n',回车‘\r’,制表符'\t'或者垂直制表符'\v'
isdigit 十进制数字 0~9
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 任何可打印字符,包括图形字符和空白字符

八、memcpy函数

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

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

这个函数在遇到 '\0' 的时候并不会停下来。

b、该内存函数可以拷贝所有数据类型,如形参接收的数据类型void*

如果source和destination有任何的重叠,复制的结果都是未定义的

c、模拟实现:

void my_memcpy(void* shu1, const void* shu, int count)
{
    void* start = shu1;//char* start = (char*) shu1;
	while (count--)//拷贝过程中一个字节一个字节拷贝,所以count--可以达到计数的效果
	{
		*(char*)shu1 = *(char*)shu;//考虑到所有数组类型都通用,所以将指针类型强制转换成char*
		shu1 = (char*)shu1 + 1;
		shu = (char*)shu + 1;//因为强制转换是临时的效果,所以在以一个字节一个字节继续向后拷贝时,还要进行强制类型转换成char*才可以继续一个字节一个字节的拷贝

	}
    return start;
}
int main()
{
	int shu[] = { 1,2,33,77,99 };
	int shu1[12] = { 0 };
	my_memcpy(shu1, shu, 12);
	return 0;
}

 d、该函数拷贝不重叠的内存,可以这样理解,在同一个内存空间里,若是改变了一个数,然后又要用该数的原值去赋予另一个数,则是失败的,因为原值已经改变,所以后来的重叠部分所赋予的值不是我们期望的那样,就相当于交换三个数没有设置临时变量去储存,当我们把a用b初始化时,a已经不存在,b再用a去初始化其值还是自己赋予a的那个值,b没有发生变化,达不到预期交换的效果,在这个函数里则是拷贝的效果。

如果要想可以拷贝重叠的内存则可以用下面的函数:

8.1、memmove函数

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

和memcpy的差别就是memmove函数处理的源内存块和目标内存块是可以重叠的。

如果源空间和目标空间出现重叠,就得使用memmove函数处理

void* my_memmove(void* dest, const void* shu, int count)
{
	void* start = dest;//char* start = (char*) shu1;
	if (dest< shu)//记得区分一下目标地址和拷贝地址的位置来确定是否可以成功拷贝
	{
		while (count--)
		{
			*(char*)dest = *(char*)shu;
			dest = (char*)dest + 1;
			shu = (char*)shu + 1;
		}
	}
	else//当在同一块内存中拷贝,目标地址大于拷贝内容地址时,从后往前拷,可以避免重叠部分再次被利用(此时利用的不是初值)
	{
		while(count--)//很巧妙的是。数组下标是0开始,所以指向的地址应为实际字节数-1
		{
			*((char*)dest + count) = *((char*)shu + count);
             //count--实现了指针移动
		}
	}
	
	return start;
}
int main()
{
	int shu[] = { 1,2,33,77,99 };
	my_memmove(shu+1, shu, 12);
	return 0;
}

 

九、memcmp函数

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

a、该函数也是按字节为单位比较,比较内存前前num个字节

b、memcmp在两个字符串的'\0'之后会继续比较,所以要保证num不能超过最短字符串长度。

int my_memcmp(const void* p, const void* q, int n)
{
	char* p1 = (char*)p;
	char* q1 = (char*)q;

	while (n--)
	{
		if (*p1!=*q1)
			return  *p1 - *q1;
		p1++;
		q1++;
	}
	return 0;
}
int main()
{
	char shu[] = "haobuxiangqiaole";
	char shu1[] = "haohaoqiaoba";
	int ret=my_memcmp(shu, shu1, 3);
	if (ret > 0)
	{
		printf(">");
	}
	else if (ret)
	{
		printf("<");
	}
	else
	{
		printf("=");
	}
	return 0;
}

十、memset函数

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

 注意:该函数以字节为单位设置内存

 如上图所示,引用该函数把数组前4位元素的每个字节都初始化成1,注意是每个字节,而不是每个元素,所以我们可以说在对整型数组进行操作时,其实是不能对每个元素进行相应的赋值操作。

后面看到了这篇博客感觉挺好的【C进阶】 字符串函数和字符分类函数_CS semi的博客-CSDN博客 

  • 5
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

忘记578

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

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

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

打赏作者

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

抵扣说明:

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

余额充值