蹉跎莫遣韶光老,人生唯有读书好。这篇文章主要讲述C语言进阶—— 字符操作函数+内存操作函数详解(吐血爆肝 !!!)相关的知识,希望能为你提供帮助。
@toc
? 前言 ?本篇文章将会详细介绍和学习字符串操作函数,重点介绍处理字符和字符串的库函数和字符操作分类改写函数,内存操作函数的使用和注意事项,还有自己模拟实现该函数的代码练习。
文章图片
?本章重点求字符串长度
- strlen
- strcpy
- strcat
- strcmp
- strncpy
- strncat
- strncmp
- strstr
- strtok
- strerror
内存操作函数
- memcpy
- memmove
- memset
- memcmp
图解:
文章图片
重点内容:
strlen函数的模拟实现三种方法:
#include<
stdio.h>
#include<
assert.h>
//1.计数器实现求字符串长度函数
int my_strlen1(const char* str)//整个过程不改变指针指向内容,加上const
{
assert(str != NULL);
//加上断言,防止接收空指针
int count = 0;
while (*str != \'\\0\')//也可以直接用while(*str)
{
count++;
str++;
}
return count;
}//2.递归实现求字符串长度,不用创建临时变量
int my_strlen2(const char* str)
{
assert(str != NULL);
if (*str != \'\\0\')//也可以直接用if(*str)
{
return 1 + strlen(str + 1);
//不能直接使用str++,可以使用++str,建议直接用str+1
}
else
return 0;
}//3.指针-指针得到中间元素的个数,实现求字符串长度
int my_strlen3(const char* str)
{
assert(str != NULL);
const char* tmp = str;
//创建临时指针变量保存str起始值
while (*str != \'\\0\')
{
str++;
}
return str - tmp;
}int main()
{
char arr[] = "abcdefgh";
int ret1 = my_strlen1(arr);
//1.计数器方法
int ret2 = my_strlen2(arr);
//2.递归方法
int ret3 = my_strlen3(arr);
//3.指针 - 指针方法
printf("%d\\n", ret1);
printf("%d\\n", ret2);
printf("%d\\n", ret3);
return 0;
}
文章图片
?二、长度不受限制的字符串函数 1.strcpy如果我们要
将一个字符串的内容拷贝到另外一个字符串空间中
时,需要使用字符串拷贝- -strcpy函数,在正式介绍strcpy函数
之前,我们先看一段代码:#include<
stdio.h>
#include<
string.h>
int main()
{
char arr1[] = "abcdefghik";
char arr2[] = "hello";
//将字符串arr2拷贝给arr1
//不能些arr1 = arr2 因为数组名表示首元素地址
strcpy(arr1, arr2);
//用调试窗口观察arr1变化
return 0;
}
文章图片
strcpy函数进行字符串拷贝的算法分析:
文章图片
重点内容:
模拟实现strcpy
#include<
stdio.h>
#include<
assert.h>
char* my_strcpy(char* dest, const char* src)
{
assert(dest != NULL);
assert(src != NULL);
char* ret = dest;
//储存原字符串首字符地址//拷贝src指向字符串的内容到dest指向的空间,包括\'\\0\'
//方法一:常规思路版
while (*src != \'\\0\')
{
*dest = *src;
dest++;
src++;
}
*dest = *src;
//方法二:代码精简版
while (*dest++ = *src++)//遇到‘/0’为0,循坏停止
{
;
}
//返回目的空间起始位置
return ret;
}
int main()
{
char arr1[] = "abcdefghik";
char arr2[] = "hello";
my_strcpy(arr1, arr2);
//模拟实现strcpy函数
printf("%s", arr1);
return 0;
}
文章图片
2.strcat如果我们要
将一个字符串的内容追加到另外一个字符串的末尾空间中
时,需要使用字符串拷贝-- - strcat函数,在正式介绍strcat函数
之前,我们先看一段代码:#include<
stdio.h>
#include<
string.h>
int main()
{
char arr1[30] = "hello";
//arr1空间需要足够大来接收追加过来的内容
//否则会造成越界访问
char arr2[] = "world";
//将字符串arr2内容追加给arr1
strcat(arr1, arr2);
printf("%s", arr1);
return 0;
}
文章图片
strcat在进行追加的时候是否会将’\\0’追加过去?
文章图片
strcat函数进行字符串追加的算法分析:
文章图片
重点内容:
的模拟实现strcat
#include<
stdio.h>
#include<
assert.h>
char* my_strcat(char* dest, const char* src)
{
assert(dest != NULL);
assert(src != NULL);
char* dest_start = dest;
//1.找到目的空间中的\'\\0\'
while (*dest != \'\\0\')//跳过不是\'\\0\'的字符
{
dest++;
}
//2.追加
while (*dest++ = *src++)
{
;
}
return dest_start;
}int main()
{
char arr1[30] = "hello";
char arr2[] = "world";
my_strcat(arr1, arr2);
//模拟实现strcat
printf("%s", arr1);
return 0;
}
文章图片
思考:strcat为什么不能自己追加自己?
3.strcmp如果我们
要比较两个字符串的是否相等,或者比较字符串大小,不能用操作符 == 来直接进行判断
,而是需要用到字符串比较函数strcmp
strcmp函数进行字符串追加的算法分析:
文章图片
重点内容:
模拟实现strcmp
#include<
stdio.h>
#include<
assert.h>
int my_strcmp(const char* p1, const char* p2)
{
assert(p1 &
&
p2);
while (*p1 == *p2)
{
if (*p1 == \'\\0\')
{
return 0;
}
p1++;
p2++;
}//方法一:vs实现的方式
if (*p1 >
*p2)
{
return 1;
}
else
{
return -1;
}//方法二:linux下gcc实现方式
int my_strcmp(const char* p1, const char* p2)
{
return *p1 - *p2;
}int main()
{
char* p1 = "abc";
char* p2 = "ab";
int ret = my_strcmp(p1, p2);
printf("ret = %d", ret);
return 0;
}
文章图片
?三、长度不限制的字符串函数 1.strncpy
strncpy与strcpy相比较多了一个字母n
,这个n代表的是需要拷贝字符的个数
,也就是说strncpy需要关注拷贝字符的个数
,而不是像strcpy那样关注’\\0’。举例:
#include<
stdio.h>
#include<
string.h>
int main()
{
char arr1[10] = "abcdefg";
char arr2[] = "1234";
int len = 0;
//len是拷贝字节的个数
scanf("%d", &
len);
strncpy(arr1, arr2, len);
printf("%s", arr1);
return 0;
}
算法图解分析:
文章图片
文章图片
重点内容
模拟实现strncpy
#include<
stdio.h>
#include<
assert.h>
//模拟实现strncpy
//方法一
//char* my_strncpy(char* dest, const char* src, size_t n)
//{
//char* dest_start = dest;
//while ((n >
0) &
&
(*src != \'\\0\'))
//{
//*dest = *src;
//dest++;
//src++;
//n--;
//}
//while (n >
0)
//{
//*dest = \'\\0\';
//dest++;
//n--;
//}
//return dest_start;
//}//方法二
char* my_strncpy(char* dest, const char* src, size_t count)
//count比n更有实际意义
{
assert(dest != NULL);
//引用断言
assert(src != NULL);
char* start = dest;
while (count &
&
(*dest++ = *src++) != \'\\0\')
{
count--;
}
if (count)
{
while (count--)
{
*dest++ = \'\\0\';
}
}
return start;
}int main()
{
char arr1[10] = "abcdefg";
char arr2[] = "1234";
size_t len = 0;
scanf("%d", &
len);
my_strncpy(arr1, arr2, len);
printf("%s", arr1);
return 0;
}
文章图片
2,strncatstrcat函数是字符串追加,在使用的时候以src的’\\0’作为追加结束标志,因此在使用strcat来追加一个字符串数组本身的时候,会因\\0被提前覆盖而无法追加成功。
文章图片
strncat与strcat相比多了一个字母n,这个n代表的是需要追加字符的个数,也就是说
strncat需要关注追加字符的个数,而不是像strcat那样关注’\\0’。
文章图片
strncat在追加字符串的时候,会自动在末尾处添加字符串结束标志’\\0’。(这也是我们在追加的时候,不用关注原dest, src中’\\0’,仅需关注追加字符的个数的原因)
#include<
stdio.h>
#include<
string.h>
int main()
{
char arr1[15] = "12345\\0xxxxxxx";
char arr2[] = "abcd";
size_t count = 0;
scanf("%d", &
count);
strncat(arr1, arr2, count);
printf("%s", arr1);
return 0;
}
strncat在追加字符串的时候,会自动在末尾处添加字符串结束标志’\\0’。(这也是我们在追加的时候,不用关注原dest, src中’\\0’,仅需关注追加字符的个数的原因)
#include<
stdio.h>
#include<
string.h>
int main()
{
char arr1[15] = "12345\\0xxxxxxx";
char arr2[] = "abcd";
size_t count = 0;
scanf("%d", &
count);
strncat(arr1, arr2, count);
printf("%s", arr1);
return 0;
}
文章图片
算法图解分析:
文章图片
情况二程序演示效果:
文章图片
重点内容:
模拟实现strncpy
#include<
stdio.h>
#include<
assert.h>
//方法一
//char* my_strncat(char* dest, const char* src, size_t count)
//{
//char* start = dest;
////dest找到\\0的位置
//while (*dest!=\'\\0\')
//{
//dest++;
//}
//while (count)
//{
////将src中的字符追加给dest
//*dest = *src;
//dest++;
//src++;
////如果src以及指向\\0的位置,提前结束循环
//if (*src =https://www.songbingjia.com/android/= /'\\0\')
//{
//break;
//}
//count--;
//}
//*dest = \'\\0\';
//字符个数追加完毕后,再单独追加\'\\0\'
//return start;
//}//方法二
char* my_strncat(char* dest, const char* src, size_t count)
{
assert(dest != NULL &
&
src != NULL);
char* start = dest;
while (*dest++)
;
dest--;
while (count--)
if ((*dest++ = *src++) == \'\\0\')
return start;
*dest = \'\\0\';
return start;
}
int main()
{
char arr1[15] = "12345\\0xxxxxxx";
char arr2[] = "abcd";
size_t count = 0;
scanf("%d", &
count);
my_strncat(arr1, arr2, count);
printf("%s", arr1);
return 0;
}
3.strncmpstrcmp用来比较两个字符串的大小,其算法思想如下:
文章图片
而strncmp与strcmp相比多了一个字母n,这个n代表的是需要比较字符的个数,也就是说strncmp需要关注比较字符的个数,而不是像strcmy那样仅关注’\\0’。
strncmp的返回结果与strcmp一样,返回 > 0, == 0, < 0 的整数。
以下面代码为例:
#include<
stdio.h>
#include<
string.h>
int main()
{
char arr1[] = "abcdef";
char arr2[] = "abcd12";
int ret = 0;
size_t count = 0;
scanf("%d", &
count);
ret = strncmp(arr1, arr2, count);
if (ret >
0)
{
printf("arr1 >
arr2\\n");
}
else if (ret == 0)
{
printf("arr1 = arr2\\n");
}
else
{
printf("arr1 <
arr2\\n");
}
return 0;
}
算法图解分析:
文章图片
重点内容:
文章图片
库函数参考:
int __cdecl strncmp
(
const char* first,
const char* last,
size_tcount
)
{
size_t x = 0;
if (!count)
{
return 0;
}
/*
* This explicit guard needed to deal correctly with boundary
* cases: strings shorter than 4 bytes and strings longer than
* UINT_MAX-4 bytes .
*/
if (count >
= 4)
{
/* unroll by four */
for (;
x <
count - 4;
x += 4)
{
first += 4;
last += 4;
if (*(first - 4) == 0 || *(first - 4) != *(last - 4))
{
return(*(unsigned char*)(first - 4) - *(unsigned char*)(last - 4));
}
if (*(first - 3) == 0 || *(first - 3) != *(last - 3))
{
return(*(unsigned char*)(first - 3) - *(unsigned char*)(last - 3));
}
if (*(first - 2) == 0 || *(first - 2) != *(last - 2))
{
return(*(unsigned char*)(first - 2) - *(unsigned char*)(last - 2));
}
if (*(first - 1) == 0 || *(first - 1) != *(last - 1))
{
return(*(unsigned char*)(first - 1) - *(unsigned char*)(last - 1));
}
}
}
/* residual loop */
for (;
x <
count;
x++)
{
if (*first == 0 || *first != *last)
{
return(*(unsigned char*)first - *(unsigned char*)last);
}
first += 1;
last += 1;
}
return 0;
}
?四、字符串查找 1.strstr
查找字符串,找子字符串
找到字符串返回的是子字符串的地址,找不到返回空指针。
#include<
stdio.h>
#include<
string.h>
int main()
{
char arr1[] = "abbbcdefbcd";
char arr2[] = "bcd";
char* ret = strstr(arr1, arr2);
if (ret == NULL)
{
printf("Can not find!\\n");
}
else
{
printf("%s\\n", ret);
}
return 0;
}
文章图片
算法图解分析:
文章图片
重点内容:
模拟实现strstr:(
KMP算法
)#define _CRT_SECURE_NO_WARNINGS 1
#include <
stdio.h>
#include <
string.h>
#include <
cassert>
char* my_strstr(const char* p1, const char* p2)
{
assert(p1 != NULL);
//断言
assert(p2 != NULL);
char* s1 = NULL;
char* s2 = NULL;
char* cur = p1;
if (*p2 == \'\\0\')//如果p2中只放了\'/0\',返回一个字符串
{
return (char*)p1;
}while (*cur != \'\\0\')
{
s1 = cur;
s2 = p2;
while ((*s2 != \'\\0\') &
&
(*s1 != \'\\0\') &
&
(*s1 == *s2))
{
s1++;
s2++;
}
if (*s2 == \'\\0\')
{
return cur;
//找到字串的情况
}
cur++;
}
return NULL;
//找不到字符的情况
}int main()
{
char* str1 = "abbbchjdfdj";
char* str2 = "bbc";
char* ret = my_strstr(str1, str2);
if (ret == NULL)
{
printf("找不到\\n");
}
else
{
printf("%s", ret);
}return 0;
}
文章图片
其实库函数的实现逻辑跟我们实现的方式是一样的,两者有所区别的是代码风格,库函数的代码风格更
加简练,需要有一定的编程功底!
库函数源代码参考:
char* _cdecl strstr
(
const char* str1,
const char* str2
)
{
char* cp = (char*)str1;
char* s1, * s2;
if (!*str2)
return((char*)str1);
while (*cp)
{
s1 = cp;
s2 = (char*)str2;
while (*s1 &
&
*s2 &
&
!(*s1 - *s2))
s1++, s2++;
if (!*s2)
return(cp);
cp++;
}
return(NULL);
}
2.strtok应用举例:
int main()
{
//192.168.31.121网络ip地址 --点分十进制
//192 168 31 121---strtok.
//student_zhang@whu.edu 邮箱地址
//student_zhang whu edu --- @ .
char arr[] = "student_zhang@whu.edu";
char* p = "@.";
//分割的字符集合char buf[100] = { 0 };
char* ret = 0;
//用于接收分割后标记的位置
strcpy(buf, arr);
//分割字符串之前先进行临时拷贝//分割buf中的字符串//一次只能分割一次,三段分割一共要调用三次,太挫了这种写
ret = strtok(buf, p);
printf("%s\\n", ret);
ret = strtok(NULL, p);
printf("%s\\n", ret);
ret = strtok(NULL, p);
printf("%s\\n", ret);
//一次性打印
for (ret = strtok(buf, p);
ret != NULL;
ret = strtok(NULL, p))
{
printf("%s\\n", ret);
}return 0;
}
文章图片
?五、误信息报告函数 1.strerror错误码-- - 所对应的错误信息:
文章图片
实际在使用的时候,错误码并非由我们来控制的,而是
接收系统返回的错误信息
printf("%s\\n", strerror(errno));
//errno是一个全局的错误码的变量
//当C语言的库函数在执行的过程中发生了错误,就会把对应的错误码,赋值到errno中
//errno需要引用头文件 errno.h
实际使用举例:
打开文件
#include<
stdio.h>
#include<
errno.h>
int main()
{
FILE* pf = fopen("test.txt", "r");
//当前路径下是没有test.txt文件的,所以应该会打开文件失败if (pf == NULL)//fopen函数返回指针,打开文件失败返回空指针
{
printf("%s\\n", strerror(errno));
}
else
{
printf("Open file success!\\n");
}
return 0;
}
示例:找不到文件
文章图片
?六、字符操作 1.字符分类函数
文章图片
2.字符转换函数:
文章图片
举例:
大写字母转小写
#include <
stdio.h>
#include <
ctype.h>
int main()
{
int i = 0;
char str[] = "I AM A STUDENT ";
while (str[i])
{if (isupper(str[i])) //判断是否是大写
{
str[i] = tolower(str[i]);
//转为小写
}
i++;
}
printf("%s\\n", str);
return 0;
}
文章图片
?七、内存操作函数 1.memcpy在之前的学习中,我们知道字符串拷贝可以使用strcpy函数,但是,
当我们拷贝的数据不是字符串的时候,比如说int类型、double类型,还能使用strcpy函数吗?
strcpy函数在拷贝的时候是以\\0为字符串拷贝的结束标志,那么在拷贝其它类型数据的时候,拷贝该结束的时候不一定存在\\0。所以使用strcpy函数肯定是行不通的。那怎么办呢?
实际上
我们可以使用memcpy函数-- - 内存拷贝函数,用来拷贝任意类型数据。
举例:
#include<
stdio.h>
#include<
string.h>
int main()
{
int arr1[] = { 0,1,2,3,4 };
int arr2[5] = { 0 };
memcpy(arr2, arr1, sizeof(arr1));
return 0;
}
文章图片
拷贝前:
文章图片
拷贝后:
文章图片
算法分析 + 图解:
文章图片
重点内容:
模拟实现函数:
#include<
stdio.h>
#include<
assert.h>
#include<
string.h>
void* my_memcpy(void* dest, const char* src, size_t num)
{//void* 可接受所有类型指针,但不能解引用操作和运算,那肯定要使用强制类型转换
void* dest_start = dest;
//存储地址assert(dest &
&
src);
while (num--)
{
//*(char*)dest = *(char*)src;
//++(char*)dest
//++(char*)src*((char*)dest)++ = *((char*)src)++;
}
return dest_start;
}
int main()
{
int arr1[10] = { 1,2,3,4,5,6,7,8,9,10 };
int arr2[10] = { 0 };
my_memcpy(arr2, arr1, 20);
//20是字节,强制类型转换为char*步长为1
return 0;
}
文章图片
2.memmove假设我们有一个整型数组 1 2 3 4 5 6 7 8 9 10 ,如果我们想要将前5个数字拷贝到第3 - 8个位置上,也就是:
文章图片
如果我们通过my_memcpy可以做到吗?试验以下就知道了:
执行前:
文章图片
执行后:
文章图片
得到的结果是:1 2 1 2 1 2 1 8 9 10,并不是我们想要的 1 2 1 2 3 4 5 8 9 10
为什么呢?
文章图片
分析后,可以发现从后向前的拷贝方式是可以的,不会出现后面需要被拷贝的数据提前被覆盖的情况。
但是,如果我们拷贝 3 4 5 6 7 到 1 2 3 4 5的位置上,那么从前往后的拷贝方式还行得通吗?
是不是发现从后向前拷贝会出现数据提前被覆盖的情况。所以这时候我们需要进行分情况讨论:
文章图片
文章图片
文章图片
为了方便我们实际编程,我们可以将情况二、情况三合并起来,这样就得到:
文章图片
重点内容
模拟实现memmove函数
#include<
stdio.h>
#include<
assert.h>
#include<
string.h>
void* my_memmove(void* dest, const void* src, size_t num)
{
void* dest_start = dest;
assert(dest &
&
src);
if (dest <
src)
{
//从前向后拷贝
while (num--)
{
*(char*)dest = *(char*)src;
++(char*)dest;
//dest = (char*)dest + 1;
++(char*)src;
//src = https://www.songbingjia.com/android/(char*)src + 1;
}
}
else
{
//从后向前拷贝
while (num--)
{
*((char*)dest + num) = *((char*)src + num);
//+num字节找到最后的字节就实现从后往前拷贝
}
}
return dest_start;
}
int main()
{
int arr1[10] = { 1,2,3,4,5,6,7,8,9,10 };
int arr2[10] = { 0 };
my_memmove(arr1 + 2, arr1, 20);
return 0;
}
【C语言进阶—— 字符操作函数+内存操作函数详解(吐血爆肝 !!!)】
文章图片
3.memcmp(简单了解)
文章图片
举例:
#include<
stdio.h>
#include<
string.h>
int main()
{
int arr1[4] = { 1,2,3,5 };
int arr2[4] = { 1,2,3,4 };
int ret = memcmp(arr1, arr2, sizeof(arr1));
if (ret >
0)
{
printf("arr1 >
arr2");
}
else if (ret == 0)
{
printf("arr1 == arr2");
}
else
{
printf("arr1 <
arr2");
}
return 0;
}
文章图片
4.memset(简单了解)相关信息:
文章图片
举例:
#include<
stdio.h>
#include<
string.h>
int main()
{
char arr[] = "abcdefg";
memset(arr, \'*\', 4);
printf("%s", arr);
return 0;
}
文章图片
文章图片
推荐阅读
- 鸿蒙轻内核 M 核源码分析(数据结构之任务排序链表)
- 能否与安卓iOS抗衡(鸿蒙OS+华为P40第一时间上手体验)
- 中断 Hwi(提高鸿蒙轻内核系统实时性及执行效率的秘密武器)
- 基于鸿蒙OS的按键驱动
- 鸿蒙开源第三方组件——自定义图片缩放组件PinchImageView-ohos
- 鸿蒙第三方组件——SwipeCaptcha滑动拼图验证组件
- 鸿蒙编译构建丨hb工具分析
- 鸿蒙内核源码分析(源码注释篇) | 鸿蒙必定成功,也必然成功 | 百篇博客分析HarmonyOS源码
- 鸿蒙开源第三方组件——日期和时间处理组件JodaTime-ohos