目录
2、strcpy() strcat() strcmp()函数
3、strncpy() strncat() strncmp()函数学习使用
1.2、islower() isupper() isalpha()函数
1、memcpy() memmove() memcmp()内存函数
前言
练习使用字符串求函数,并且模拟部分字符串函数
一、常见字符串函数
1、strlen()
2、strcpy() strcat() strcmp()长度不受限制的字符串函数
3、strncpy() strbcat() strncmp()长度受限制的字符串函数,比2中的函数安全
4、strstr() 查找子串
5、strtok() 切割字符串
1、strlen()求字符串长度
1.1、strlen()
意义:求字符串长度
size_t strlen( const char* str)
strlen()一'\0'作为结束标志,函数返回'\0'之前出现的字符个数
返回值类型是size_t表示unsigned int(无符号整形)
1>、使用计数器
代码如下(示例):
size_t my_strlen(char* str)
{
assert(str); //断言,若str=NULL则会报错
size_t cnt = 0;
while (*str != '\0')
{
cnt++;
str++;
}
return cnt;
}
2>、指针-指针
代码如下(示例):
size_t my_strlen(char* str)
{
assert(str);
char* start = str;
while (*str != '\0')
{
str++;
}
return str-start;
}
3>、使用递归
代码如下(示例):
size_t my_strlen(char* arr)
{
assert(arr);
if (*arr != '\0')
return 1 + my_strlen(arr + 1);
else
return 0;
}
2、strcpy() strcat() strcmp()函数
2.1、strcpy()
意义:拷贝字符串
char* strcpy(char* dest,const char* sorc)
拷贝时将sorc(source)中看不到的'\0',必须一并拷贝到des中,否则出错
dest(目标destination)空间必须足够大,确保能够存放
1>、简单思路
代码如下(示例):
void my_strcpy(char* dest,const char* sorc)
{
assert(dest);
assert(sorc);
while (*sorc!='\0')
{
*dest++ = *sorc++; //最后的'\0'没进循环
}
*dest = *sorc;
}
2>、优化后
代码如下(示例):
char* my_strcpy(char* dest,const char* sorc) //char*,返回一个函数指针,就是这个函数的地址
{
assert(dest);
assert(sorc);
char* start = dest;
while (*dest++ = *sorc++) //当*sorc='\0'时,要发生赋值,但不进入while内部
{ //赋值后再判断
;
}
return start; //函数指针返回值是char*,返回dest字符串首地址
}
2.2、strcat()
意义:追加字符串
char* strcat(char* dest,const char* sorc)
sorc字符串必须以'\0'结束,dest字符串空间必须足够大
不能自己给自己追加如my_strcat(arr1,arr1),因为'\0'被覆盖,无法判断结束
1>、优化后
代码如下(示例):
char* my_strcat(char* dest, const char* sorc)
{
assert(dest && sorc);
char* start = dest;
while (*dest != '\0') //1、找到dest的'\0'的位置用socr覆盖
{
dest++;
}
while (*dest++ = *sorc++)//2、拷贝sorc字符串
{
;
}
return start; //函数指针返回char*
}
2.3、strcmp()
意义:比较两个字符串的大小
int strcmp(const char*str1,const char* str2)
abz>abc abc<abcd abc==abc
规则:1、一对一对地比ASCII码值,当*str1>*str2,str1字符串就大,2、当遇到'\0'和其他字符比,都是先出现'\0'的字符串小
1>、简单思路
代码如下(示例):
int my_strcmp(const char* str1, const char* str2)
{
assert(str1 && str2);
while (*str1 == *str2 && *str1!='\0' &&*str2!='\0') //出循环后都是'\0'
{ //或者出循环后是字符'0'和另一个字符比较('\0'比其他字符ASCII码值小)
str1++;
str2++;
}
if (*str1 == *str2 && *str1 == '\0')
{
return 0;
}
else
{
if (*str1 > *str2)
{
return 1;
}
else
{
return -1;
}
}
}
2>、优化后
代码如下(示例):
int my_strcmp(const char* str1, const char* str2)
{
assert(str1 && str2);
while (*str1 == *str2 && *str1!='\0' &&*str2!='\0') //出循环后都是'\0'
{ //或者出循环后是字符'0'和另一个字符比较('\0'比其他字符ASCII码值小)
str1++;
str2++;
}
return *str1 - *str2; //大于等于小于的返回值都在此处实现
}
3、strncpy() strncat() strncmp()函数学习使用
和2、中的实现差不多,就是多了个参数(个数限制)
3.1、strncpy()
意义:实现sorc字符串前size_t(unsigned int)个字符拷贝
char* strncpy(char* dest,const char* sorc,size_count)
3.2、strncat()
意义:实现sorc字符串前size_个字符追加
char* strncat(char* dest,const char* sorc,size_t count)
3.3、strncmp()
意义:实现sorc字符串前size_t个字符和dest字符串比较
int strncmp(const char *dest,const char *sorc,size_t count)
4、strstr()查找子串
4.1、strstr()
意义:查找子串
char* strstr(char* str1,const char* str2)
规则:从母串str1中查找子串str2,若找到,就返回母串中子串所在位置的首地址,若找不到就返回NULL
1>、代码一
分为第一次就找到:abcde 和 bcd,第n次找到:abcccdef 和 ccd 或 cde
代码如下(示例):
char* my_strstr(char* str1, const char* str2)
{
assert(str1 && str2);
char* s1 = str1;
char* s2 = str2;
char* start = s1; //第一个字符就相等时,start记录最开始的位置
while (*s1 != '\0')
{
while (*s1 != *s2 && *s1 != '\0')
{
s1++;
}
start = s1; //记录每次相等时的第一个字符,然后判断后面的字符是否相等
while (*s1 == *s2)
{
s1++;
s2++;
}
if (*s2 == '\0')
{
return start;
}
else
{
s1 = start + 1;//如果start开始不符合,找start下一位
s2 = str2;
}
}
return NULL;
}
2>、代码二
代码如下(示例):
char* my_strstr(char* str1, const char* str2)
{
assert(str1 && str2);
char* s1 = str1;
char* s2 = str2;
char* start = s1; //第一个字符就相等时,start记录最开始的位置
while (*start)
{
s1 = start;
s2 = str2;
while (*s1 == *s2 && *s1 != '\0' && *s2 != '\0')
{
s1++;
s2++;
}
if (*s2 == '\0')
{
return start;
}
start++;
}
return NULL;
}
5、strtok()切割字符串
5.1、strtok()
意义:切割字符串
char* strtok(char* str,char*sep)
1、sep是一个字符串,定义了用作分隔符的字符集合
2、str是被切割的字符串
规则:
一、1、第一次调用时strtok(str,esp),*str!=NULL时先找到str中第一个分隔符,将它改为'\0',然后返回头指针。2、因为会改变str字符串内容,所以需要先将str拷贝一份拿来使用
二、第二次调用时strtok(NULL,esp),第二次调用时会记住第一次调用后改为'\0'的位置,*str==NULL时,会跳过上一个被修改为'\0'的位置,找下一个分隔符
也可这样打印:
结果展示:
二、字符函数介绍
1、判断字符函数
规则:为假返回0,为真返回非0
1.1、iscntrl()
1>、控制字符
在ASCII码中,第0~31号及第127号(共33个)是控制字符或通讯专用字符,如控制符:LF(换行)、CR(回车)、FF(换页)、DEL(删除)、BS(退格)、BEL(振铃)等
2>、iscntrl()打印控制字符的ASCII码值
1.2、islower() isupper() isalpha()函数
判断字符小写、大写,或字母
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <ctype.h>
int cnt_a(char* str, int* cnt_up, int* cnt_alp)
{
char* start = str;
int cnt_low = 0;
//小写
while(*str!='\0')
{
if (isalpha(*str) != 0)
{
cnt_low++;
}
str++;
}
//大写
str = start;
while (*str != '\0')
{
if (isupper(*str) != 0)
{
(*cnt_up)++;
}
str++;
}
//字母
str = start;
while (*str != '\0')
{
if (isalpha(*str) != 0)
{
(*cnt_alp)++;
}
str++;
}
return cnt_low;
}
int main()
{
int cnt_upper = 0;
int cnt_alpha = 0;
int* up = &cnt_upper;
int* alp = &cnt_alpha;
char a[] = "My heart is hard";
printf("小写%d\n大写%d\n字母%d\n", cnt_a(a,up,alp), *up,*alp);
return 0;
}
2、字符转换函数
2.1、tolower() toupper()
tolower() -->把小写字母转换成大写字母,非小写字母则不变
toupper() -->和上面相对应
三、内存函数使用
1、memcpy() memmove() memcmp()内存函数
memcpy() 内存拷贝,处理不重叠内存之间的数据拷贝(容易覆盖)
memmove() 内存追加,处理重叠内存之间的数据拷贝
memcmp() 内存比较
注意:这组函数是从内存上一个字节一个字节的处理,可以适用任意数据类型。
1.1、memcpy()
void* memcpy(void* dest,const void* src,size_t num)//const保护sorc内容不被改变
意义:内存拷贝,处理不重叠内存之间数据的拷贝。参数中void*表示可以拷贝任意数据类型,函数名前的void*表示返回一个泛型指针。
规则:将sorc中的内存拷贝到dest,大小是num个字节。
标准C语言下,不能自己拷贝自己如:arr[] = {1,2,3,4,5,6,7,8,9,10} memcpy(arr+1,arr1,20)//把23456被拷贝为12345不能实现
模拟实现代码:
void* my_memcpy(void* dest, const void* src, size_t num)
{
assert(dest && src);
void* ret = dest;
while (num--)
{
*(char*)dest = *(char*)src;
dest = (char*)dest + 1;
src = (char*)src + 1;
}
return ret;
}
1.2、memmove()
void* memmove(void* dest,const void* src,size_t num)
意义:内存追加,处理重叠内存之间的数据拷贝,参数类型和意义与memcpy相同
思考:strcmp拷贝有重叠,其实是因为发生了数据覆盖,比如arr[] = {1,2,3,4,5,6,7,8,9,10} memcpy(arr+2,arr1,20)//结果是arr[] = {1,2,1,2,1,2,1,8,9,10}而不是我们要的arr[] = {1,2,1,2,3,4,5,8,9,10},就是遵循从前往后拷贝,这种情况下,src内容从后往前(dest在src后)拷贝给dest就没有问题,所以从前往后(dest在src前)拷贝,还是从后往前拷贝就很重要了。
总结:内存重叠时:dest在前,从前往后拷;dest在后,从后往前拷。
即dest位置决定拷贝方式:
模拟实现代码:
void* my_memmove(void* dest, const void* src, size_t num)
{
assert(dest && src);
void* ret = dest;
if (dest < src)
{
//前-->后
while (num--)
{
*(char*)dest = *(char*)src;
dest = (char*)dest+1;
src = (char*)src+1;
}
}
else
{
//后-->前
while (num--)
{
*((char*)dest + num) = * ((char*)src + num);
}
}
return ret;
}
1.3、memcmp()
int memcpy(const void* ptr1,const void* ptr2,size_t num)
规则:前num个字节一个一个比较,大于返回1等于返回0,小于返回-1
2、memset() 内存赋值函数
void* memset(void* ptr, int value, size_t num)
参数:ptr指向要填充的内存地址,value要被设置的值,num要被设置的值的字节数
注意:memset值初始化可初始化为0,但如果初始化为1,大于一个字节的数据类型类型会出错,比如整形。原因是:memset是对字节操作会将每个字节都改为1,结果就变成一个很大的整形,看下面的例子:
正确使用例子:
错误使用例子:
总结
针对字符串函数的内容,最重要的是要学会画图,确定1、有几种情况2、指针指向哪里3、要模拟实现应当借鉴参数、返回值的设置