文章目录
求字符串长度
strlen
函数功能
求字符串长度,求一个字符串中字符的个数(不包括’\0’)
函数原型
size_t strlen ( const char* str )
size_t 是函数的返回类型
char* s是函数参数
函数的使用
#include <stdio.h>
#include <string.h>
int main()
{
char arr[] = "abcdef";
int len = strlen(arr);
printf("%d\n", len);
return 0;
}
模拟实现
1.计数器实现
#include <stdio.h>
#include <string.h>
#include <assert.h>
//求字符串长度
size_t my_strlen(const char* str)
{
//检查指针有效性
assert(str);
int count = 0;
while (*str != '\0')
{
count++;
str++;
}
return count;
}
2.指针-指针的方式
#include <stdio.h>
#include <assert.h>
#include <string.h>
//求字符串长度
size_t my_strlen(const char* str)
{
//检查指针有效性
assert(str);
char* head = str; //记录字符串起始位置
//找到字符串结束位置
while (*str != '\0')
{
str++;
}
return str - head; //指针-指针得到字符的个数
}
3.递归的方式
#include <stdio.h>
#include <assert.h>
#include <string.h>
//求字符串长度
size_t my_strlen(const char* str)
{
//检查指针有效性
assert(str);
if (*str == '\0')
return 0;
else
return 1 + my_strlen(str + 1);
}
注意事项
字符串已经 ‘\0’ 作为结束标志,strlen函数返回的是在字符串中 ‘\0’ 前面出现的字符个数(不包含 ‘\0’ )
参数指向的字符串必须要以 ‘\0’ 结束,否则得到的是一个随机值,没有意义。(strlen会一直往后找,直到遇到’\0’才结束)
注意函数的返回值为size_t,是无符号的( 易错 ,可能出现算数转换)
长度不受限制的字符串函数
strcpy
函数功能
字符串拷贝,把一个字符串里面的内容拷贝到另一个字符串中(包括’\0’)
函数原型
char* strcpy ( char* destination, const char* source )
char* 是函数的返回值类型,返回的是目标空间的起始地址
source 是要拷贝的字符串
destination 是目标空间(字符串拷贝到的地方)
函数使用
#include <stdio.h>
#include <string.h>
int main()
{
char arr1[] = "abcdef";
char arr2[10] = { 0 };
strcpy(arr2, arr1);
printf("%s\n", arr2);
return 0;
}c
模拟实现
初阶
#include <stdio.h>
#include <assert.h>
#include <string.h>
//字符串拷贝
char* my_strcpy(char* dest, const char* src)
{
//检查指针有效性
assert(dest && src);
char* ret = dest; //记录目标空间的起始地址
while (*src != '\0')
{
*dest++ = *src++;
}
*dest = *src; //拷贝字符'\0'
return ret;
}
进阶
#include <stdio.h>
#include <assert.h>
#include <string.h>
//字符串拷贝
char* my_strcpy(char* dest, const char* src)
{
//检查指针有效性
assert(dest && src);
char* ret = dest;//记录目标空间的起始地址
//全部字符和'\0'拷贝到目标空间中
//*dest++ = *src++的返回值为dest指向的内容,当到'\0'时,返回值为0,结束循环
while (*dest++ = *src++)
{
;
}
return ret;
}
注意事项
源字符串必须以 ‘\0’ 结束
会将源字符串中的 ‘\0’ 拷贝到目标空间
目标空间必须足够大,以确保能存放源字符串,确保能够存放得下源字符串
目标空间必须可变,不可变则不能拷贝
strcat
函数功能
字符串追加,在一个字符串的末尾追加另外一个字符串(包括’\0’)
函数原型
char* strcat ( char* destination, const char* source )
char* 是函数的返回值,返回的是目标空间的起始地址
source 是要追加的字符串
destination 是目标空间(被追加的字符串)
函数使用
#include <stdio.h>
#include <string.h>
int main()
{
char arr1[12] = "hello ";
char arr2[] = "world";
strcat(arr1, arr2);
printf("%s\n", arr1);
return 0;
}
模拟实现
#include <stdio.h>
#include <assert.h>
//字符串追加
char* my_strcat(char* dest, const char* src)
{
//检查指针有效性
assert(dest && src);
char* ret = dest; //记录目标空间的起始地址
//找到目标空间的末尾'\0'
while (*dest !='\0')
{
dest++;
}
//拷贝字符串
while (*dest++ = *src++)
{
;
}
return ret;
}
注意事项
源字符串必须以 ‘\0’ 结束
目标空间必须有足够的大,能容纳下源字符串的内容
目标空间必须可修改
字符串不能自己给自己追加(追加会覆盖末尾的’\0’,从而导致死循环)
strcmp
函数功能
字符串比较,以字节为单位比较(比较ASCII值)两个字符串的大小
函数原型
int strcmp ( const char* str1, const char* str2 )
int 函数的返回值 : >0 : str > str2
=0 : str1 = str2
<0 : str1 < str2
str1 str2 用于比较的两个字符串
函数的使用
#include <stdio.h>
#include <string.h>
int main()
{
char arr1[] = "abcdef";
char arr2[] = "abcdf";
int ret = strcmp(arr1, arr2);
printf("%d\n", ret);
return 0;
}
模拟实现
方法1
#include <stdio.h>
#include <assert.h>
//比较字符串大小
int my_strcmp(const char* str1, const char* str2)
{
//检查指针有效性
assert(str1 && str2);
//字符相等一直继续比较
while (*str1 == *str2)
{
//大小相等且*str1或str2为'\0',说明都为'\0',则说明相等
if (*str1 == '\0')
return 0;
str1++;
str2++;
}
//不相等则返回差值
return (*str1 - *str2);
}
方法2
#include <stdio.h>
#include <assert.h>
//比较字符串大小
int my_strcmp(const char* str1, const char* str2)
{
//检查指针有效性
assert(str1 && str2);
//相等且不为'\0'指针一直往后走
while (*str1 == *str2 && *str1 != '\0' && *str2 != '\0')
{
str1++;
str2++;
}
//此时情况为字符不相等(*str1=='\0' || *str2 =='\0' ||
*str1 == *str2 =='\0' || * str1 != str2且不为'\0')则可以返回差值
return *str1 - *str2;
注意事项
标准规定:
第一个字符串大于第二个字符串,则返回大于0的数字
第一个字符串等于第二个字符串,则返回0
第一个字符串小于第二个字符串,则返回小于0的数字
对每一对字符进行比较,直到遇到不相等的字符或者都到字符串的末尾时函数结束
比较的每是一对字符的ASCII值
长度受限制的字符串函数
由于strcpy,strcat,strcmp等字符串函数存在安全隐患(目标空间小于源空间等问题),C语言针对这些问题提供了几个相对安全的字符串函数,即strncpy,strncat,strncmp,这些字符串函数相对于strcpy,strcat,strcmp函数多了一个参数,用于指定操作的字节数。但是注意的是,strncpy,strncat,strncmp函数只是相对安全,并不是绝对安全,多一个参数只是起到了提醒的作用。
strncpy
函数功能
字符串拷贝,把一个字符串的num个字节的内容拷贝到另一个字符串中。
函数原型
char* strncpy ( char* destination, const char* source, size_t num )
char* 是函数的返回值,返回的是目标空间的起始地址
source是要拷贝的字符串
destination是目标空间
num是要拷贝的字节数
函数使用
#include <stdio.h>
#include <string.h>
int main()
{
char arr1[] = "abcdef";
char arr2[10] = { 0 };
strncpy(arr2, arr1, 6);
printf("%s\n", arr2);
return 0;
}
模拟实现
方法1
#include <stdio.h>
#include <assert.h>
//字符串拷贝
char* my_strncpy(char* dest, const char* src, int num)
{
//检查指针的有效性
assert(dest && src);
char* ret = dest; //记录目标空间的起始地址
while (num--)
{
*dest = *src;
//如果*dest为'\0',说明src的结束字符赋值给了dest,这种情况下源字符串的长度小于num
if (*dest == '\0')
{
//如果源字符串的长度小于num,则在目标空间的后面追加'\0',直到num个
while (num--)
{
*dest++ = '\0';
}
break;
}
dest++;
src++;
}
*dest = '\0'; //在dest的末尾补上'\0'
return ret;
}
方法2
#include <stdio.h>
#include <assert.h>
//字符串拷贝
char* my_strncpy(char* dest, const char* src, int num)
{
//检查指针的有效性
assert(dest && src);
char* ret = dest; //记录目标空间的起始地址
//拷贝字符串直到'\0'
while ((*dest = *src) != '\0' && num)
{
dest++;
src++;
num--;
}
//如果源字符串的长度小于num,则在目标空间的后面追加'\0',直到num个
if (num)
{
while (--num)
{
*dest++ = '\0';
}
}
*dest = '\0'; //在dest的末尾补上'\0'
return ret;
}
注意事项
拷贝num个字节的内容到目标空间,如果源字符串小于num,则拷贝源字符串后在目标空间追加0,直到num个
src和dest所指内存区域不可以重叠且dest必须有足够的空间来容纳num的长度和’\0’
strncat
函数功能
字符串追加,把一个字符串中的num个字节的内容追加到另一个字符串的末尾,并在最后加上’\0’
函数原型
char* strncat ( char* destination, const char* source, size_t num )
char* 是函数的返回值,返回目标空间的起始地址
destination 是目标空间
source 是要追加的字符串
num是要追加的字节数
函数使用
#include <stdio.h>
#include <string.h>
int main()
{
char arr1[12] = "hello ";
char arr2[] = "world";
strncat(arr1, arr2, 5);
printf("%s", arr1);
return 0;
}
模拟实现
#include <stdio.h>
#include <assert.h>
//字符串追加
char* my_strncat(char* dest, const char* src, size_t num)
{
//检查指针的有效性
assert(dest && src);
char* ret = dest; //记录目标空间的起始地址
//找到目标空间的末尾'\0'
while (*dest != '\0')
{
dest++;
}
//拷贝字符串
while (num--)
{
*dest = *src;
//如果源字符串的长度小于num,则在目标空间的后面追加'\0',直到num个
if (*dest == '\0')
{
while (num--)
{
*dest++ = '\0';
}
}
dest++;
src++;
}
*dest = '\0'; //在dest的末尾补上'\0'
return ret;
}
注意事项
将源字符串中的num个字节的内容追加到目标字符串的末尾,并在末尾加’\0’
如果源字符串的长度小于num,则只复制到终止空字符的内容
strncmp
函数功能
字符串比较,比较两个字符串前num个字节的大小
函数原型
int strncmp ( const char* str1, const char* str2, size_t num )
int 函数的返回值 : >0 : str > str2
=0 : str1 = str2
<0 : str1 < str2
str1 str2 用于比较的两个字符串
num是要比较的字节数
函数使用
#include <stdio.h>
#include <string.h>
int main()
{
char arr1[] = "abcdef";
char arr2[] = "abc";
int ret = strncmp(arr1, arr2, 3);
printf("%d\n", ret);
return 0;
}
模拟实现
#include <stdio.h>
#include <assert.h>
//字符串比较
int my_strncmp(const char* str1, const char* str2, size_t num)
{
//检查指针的有效性
assert(str1 && str2);
//不相等或者num为0时结束
while (num && *str1 == *str2)
{
str1++;
str2++;
num--;
}
//比较了num个且相等
if (num == 0)
{
return 0;
}
//不相等则返回差值
return (*str1 - *str2);
}
注意事项
标准规定:
第一个字符串大于第二个字符串,则返回大于0的数字
第一个字符串等于第二个字符串,则返回0
第一个字符串小于第二个字符串,则返回小于0的数字
对每一对字符进行比较,直到遇到不相等的字符或者都到字符串的末尾时函数结束
比较的每是一对字符的ASCII值
字符串查找函数
strstr
函数功能
查找子串,查找一个字符串是否包含子串
函数原型
const char* strstr ( const char* str1, const char* str2 )
char* 是函数的返回值,返回的是字符串子串的起始位置,若找不到,则返回NULL
str1是要搜索的字符串
str2为子串
函数使用
#include <stdio.h>
#include <string.h>
int main()
{
char arr1[] = "abbcdef";
char arr2[] = "bbc";
char* ret = strstr(arr1, arr2);
printf("%s\n", ret);
return 0;
}
模拟实现
#include <stdio.h>
#include <assert.h>
//查找子字符串
char* my_strstr(const char* str1, const char* str2)
{
//检查指针的有效性
assert(str1 && str2);
//如果中途匹配失败需要返回str2的起始位置,所以用其他变量保存str2,保证str2的首地址不会丢失
const char* s1 = str1;
const char* s2 = str2;
//用来记录每次第一个字符匹配成功的位置
const char* p = str1;
while (*p != '\0')
{
s1 = p; //从p处开始往后匹配
s2 = str2; //每次匹配后s2回到str2起始位置
while (*s1 != '\0' && *s2 != '\0' && *s1 == *s2)
{
s1++;
s2++;
//如果不相等处s2为'\0'则说明匹配成功
if (*s2 == '\0')
{
return (char*)p;
}
}
//否则这一次匹配失败,则需要从下一个位置重新开始匹配
p++;
}
//字符串都找完了,说明没有找到则返回NULL
return NULL;
}
注意事项
被查找的字符串和子串都不能为空字符串,且都以’\0’结尾
如果查找成功,返回字符串中子字符串的起始地址,如果查找失败则返回NULL
strtok
函数功能
字符串分割,把一个字符串按照分割标志分割为几个字符串
函数原型
char* strtok ( char* str, const char* sep )
char* 函数返回值,strtok函数会找到str中的下一个标记,并将用’\0’结尾,返回一个指向这个标记的指针
char* str 被分割的字符串,包含了0个或多个由delimiters字符串中的一个或者多个分割字符的标记
sep 一个字符串,做分割字符的集合
函数使用
#include <stdio.h>
#include <string.h>
int main()
{
char sep[] = "@.";
char email[] = "123456789@qq.com";
//因为切割之后源字符串会改变,所以我们用一个临时字符串代替
char cp[20] = { 0 };
strcpy(cp, email);
//第一次传递被切割字符串的首地址
char* ret = strtok(cp, sep);
if (ret != NULL)
printf("%s\n", ret);
//第二次及以后第一个参数传递空指针NULL(strtok会记住上一次切割的位置)
ret = strtok(NULL, sep);
if (ret != NULL)
printf("%s\n", ret);
ret = strtok(NULL, sep);
if (ret != NULL)
printf("%s\n", ret);
return 0;
}
这个我们知道目标字符串会被分割成3个字符串,所以我们调用了三次strtok函数,如果我们不知道源字符串会被分割成多少段字符串的时候,我们这个方法显然就不适用了。
我们知道,strtok函数第一次调用时需要传递目标字符串的地址,之后只需要传递空指针即可,那么我们就可以利用这个特点结合for循环的特征来使用trtok函数。
#include <stdio.h>
#include <string.h>
int main()
{
char sep[] = "@.";
char email[] = "123456789@qq.com";
//因为切割之后源字符串会改变,所以我们用一个临时字符串代替
char cp[20] = { 0 };
strcpy(cp, email);
//用来保存strtok函数的返回值
char* ret = NULL;
for (ret = strtok(cp, sep); //初始化部分,第一次传递被切割字符串的地址
ret != NULL; //判断部分,只要strtok函数的返回值不为空,说明没有分割完毕
ret = strtok(NULL, sep)) //调整部分,第二次级以后传递NULL
{
//ret不为空,说明字符串没有分割完毕,继续打印
printf("%s\n", ret);
}
return 0;
}
注意事项
1.sep参数是个字符串,定义了用作分隔符的字符集合
2.第一个参数指定一个字符串,它包含了0个或者多个由sep字符串中一个或者多个分隔符分割的标记
3.strtok函数找到str中的下一个标记,并将其用 \0 结尾,返回一个指向这个标记的指针。(注:strtok函数会改变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容并且可修改。)
4.strtok函数的第一个参数不为 NULL ,函数将找到str中第一个标记,strtok函数将保存它在字符串中的位置。
5.strtok函数的第一个参数为 NULL ,函数将在同一个字符串中被保存的位置开始,查找下一个标记。
6.如果字符串中不存在更多的标记,则返回 NULL 指针
strerror
函数功能
C语言有一系列的库函数,当这些库函数调用失败时会返回相应的错误码,而我们仅知道错误码还是不能够知道错误的原因,而sreerror函数就能够获取错误码对应的错误信息的首地址,让我们知道程序发生错误的原因
函数原型
char* strerror ( int errnum )
char* 函数返回值,返回错误码对应错误信息的首地址
errnum 错误码
函数使用
#include <stdio.h>
#include <string.h>
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));
printf("%s\n", strerror(5));
return 0;
}
这里有个问题,C语言有那么多的错误信息,需要我们一个一个的去记住错误信息对应的错误码吗,其实并不用,C语言设置了一个全局的用于存放错误码的变量errno,只要调用C语言库函数发生错误,那么errno就会记录相应的错误码,所以strerror函数一般和errno配合使用,errno需要包含头文件<errno.h>
#include <stdio.h>
#include <string.h>
#include <errno.h> //errno对应头文件
int main()
{
FILE* pf = fopen("test.txt", "r");
if (pf == NULL)
{
printf("%s\n", strerror(errno));
return 1;
}
else
{
printf("文件打开成功\n");
}
return 0;
}
字符函数
字符分类函数
函数 | 如果他的参数符合下列条件就返回真 |
---|---|
iscntrl | 任何控制字符 |
isspace | 空白字符:空格‘ ’,换页‘\f’,换行’\n’,回车‘\r’,制表符’\t’或者垂直制表符’\v’ |
isdigit | 十进制数字 0~9 |
isxdigit | 十六进制数字,包括所有十进制数字,小写字母af,大写字母AF |
islower | 小写字母a~z |
isupper | 大写字母A~Z |
isalpha | 字母az或AZ |
isalnum | 字母或者数字,az,AZ,0~9 |
ispunct | 标点符号,任何不属于数字或者字母的图形字符(可打印) |
isgraph | 任何图形字符 |
isprint | 任何可打印字符,包括图形字符和空白字符 |
字符转换函数
函数 | 返回值 |
---|---|
tolower | 返回对于小写字母的ASCII值 |
toupper | 返回对于小写字母的ASCII值 |
内存操作函数
前面我们学习的strcpy,strcat,strcmp,strncpy,strncat,strncmp等函数都是字符串函数,只能对字符串进行相关的操作,而对于整形,浮点型,结构体等等类型的数据就需要使用到内存操作函数,常见的内存操作函数有memcpy,memmove,memset,memcmp
memcpy
函数功能
内存拷贝,将一块内存中的num个字节的内容拷贝到另一块内存中常用来处理不重叠内存数据的拷贝
函数原型
void* memcpy ( void* destination, const void* source, size_t num )
void* 函数返回值,返回dest内存空间的地址
void* destination 目标内存空间地址
void* source 源空间地址
size_t num 要拷贝的字节数
函数使用
#include <stdio.h>
#include <string.h>
int main()
{
int arr1[10] = { 1,2,3,4,5,6,7,8,9,10 };
int arr2[10] = { 0 };
memcpy(arr2, arr1, 40);
for (int i = 0; i < 10; i++)
{
printf("%d ", arr2[i]);
}
printf("\n");
return 0;
}
模拟实现
#include <stdio.h>
#include <assert.h>
//内存拷贝
void* my_memcpy(void* dest, const void* src, size_t num)
{
//检查指针有效性
assert(dest && src);
void* ret = dest; //记录目标空间的起始地址
//以字节为单位进行拷贝
while (num--)
{
//强转为char*后进行赋值
*(char*)dest = *(char*)src;
//这里不要写成(char*)dest++,在某些编译器会报错,因为强制类型转换是一种临时效果
dest = (char*)dest + 1;
src = (char*)src + 1;
}
return ret;
}
注意事项
在C语言标椎中,memcpy只负责处理不重叠的内存拷贝,内存重叠的拷贝是由memmove实现的,但在有些编译器可能memcpy也实现了memmove的功能,但在某些编译器下memcpy可能就没有实现memmove的功能,所以我们在使用内存重叠的数据拷贝时尽量使用memmove函数,避免发生错误
函数memcpy从source的位置开始向后复制num个字节的数据到destination的内存位置
这个函数在遇到 ‘\0’ 的时候并不会停下来
如果source和destination有任何的重叠,复制的结果都是未定义的
memmove
函数功能
内存移动,将一块内存数据中的内容移动覆盖到另一块内存数据,常用来处理重叠内存数据的拷贝
函数原型
void* memmove ( void* destination, const void* source, size_t num )
void* 函数返回值,返回dest内存空间的地址
void* destination 目标内存空间地址
void* source 源空间地址
size_t num 要拷贝的字节数
函数使用
#include <stdio.h>
#include <string.h>
int main()
{
int arr1[10] = { 1,2,3,4,5,6,7,8,9,10 };
memmove(arr1 + 2, arr1, 20);
for (int i = 0; i < 10; i++)
{
printf("%d ", arr1[i]);
}
printf("\n");
return 0;
}
模拟实现
思路分析
dest和src的地址大小不同,如果我们统一像memcpy一样从前向后拷贝就会出现内存覆盖的情况,因此我们分为两种情况进行讨论:
1.dest的地址小于src的地址
如图,如果此时我们从后往前覆盖,7覆盖到5的位置,6覆盖到4的位置,但是当原来的5要覆盖到3的位置时,5已经被7覆盖,所以我们从前往后进行覆盖。
2.dest的地址大于src的地址
如图,如果我们从前往后覆盖的话,1覆盖到3,2覆盖到4,但是当原来的3要覆盖到5的时候,3的位置已经被覆盖成了1,从而造成结果不正确,所以我们从后往前覆盖。
代码实现:
#include <stdio.h>
#include <assert.h>
//内存移动
void* my_memmove(void* dest, 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;
}
注意事项
和memcpy的差别就是memmove函数处理的源内存块和目标内存块是可以重叠的
如果源空间和目标空间出现重叠,就得使用memmove函数处理
memset
函数功能
内存设置,把一块内存中的num个字节的内容设置为指定的数据
函数原型
void* memset ( void* ptr, int value, size_t num )
void* 函数返回值,返回目标空间的地址
void* ptr 函数参数,目标字符串的起始地址
int value 函数参数,指定初始化的数
size_t num 函数参数,要初始化的字节数
函数使用
#include <stdio.h>
#include <string.h>
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
memset(arr + 5, 0, 20);
for (int i = 0; i < 10; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
模拟实现
#include <stdio.h>
#include <assert.h>
//内存设置
void* my_memset(void* ptr, int val, size_t num)
{
assert(ptr);
void* ret = ptr; //记录目标空间的起始地址
//一个字节一个字节的进行赋值
while (num--)
{
*(char*)ptr = val;
ptr = (char*)ptr+1;
}
return ret;
}
memcmp
函数功能
内存比较,比较两块内存中前num个字节的大小
函数原型
int memcmp ( const void * ptr1, const void * ptr2, size_t num )
int 函数的返回值 : >0 : str > str2
=0 : str1 = str2
<0 : str1 < str2
str1 str2 用于比较的两个字符串
num是要比较的字节数
函数使用
#include <stdio.h>
#include <string.h>
int main()
{
int arr1[] = { 1,2,3,4,5, };
int arr2[] = { 1,2,5 };
int ret = memcmp(arr1, arr2, 12);
printf("%d\n", ret);
return 0;
}
模拟实现
#include <stdio.h>
#include <assert.h>
//内存比较
int my_memcmp(const void* str1, const void* str2, size_t num)
{
//检查指针的有效性
assert(str1 && str2);
//不相等或者num为0时结束
while (*(char*)str1 == *(char*)str2 && num)
{
str1 = (char*)str1 + 1;
str2 = (char*)str2 + 1;
num--;
}
//比较了num个且相等
if (num == 0)
{
return 0;
}
//不相等则返回差值
return *(char*)str1 - *(char*)str2;
}
//记录目标空间的起始地址
//一个字节一个字节的进行赋值
while (num–)
{
(char)ptr = val;
ptr = (char*)ptr+1;
}
return ret;
}