C语言字符串函数及内存函数
大家可以在下面这个网站上学习C以及C++,里面内容很多,很详细。
https://legacy.cplusplus.com/
使用下面的函数需要包含一个头文件string.h
一、字符串函数
以下是一些比较常用的函数,都是对字符串进行操作的,除了要运用它们,还可以模拟实现一下,加深理解和记忆。
1.strlen
这个函数可以计算一个字符串的长度,但不包含’\0’。
函数原型如图
函数参数是一个char*类型的指针,返回值是一个整数。
可以计算整个串的长度,也可以计算子串的长度,只要参数合理即可。
下面是模拟实现的代码。
size_t my_strlen(const char* str)
{
assert(str); //保证参数的有效性
const char* begin = str;
while (*str) //字符不为‘0’时,进入循环
{
str++;
}
return (str-begin); //两个指针相减,得到的数是两个指针间的元素的个数
}
方法不止一种,也可以选择创建一个计数的变量来计数。
由于字符个数最少是0,所以返回值的类型为size_t,在函数内部不希望修改字符串的内容,所以函数参数类型为const char*
2.strcpy
这个函数可以复制一个字符串。
函数原型如下
该函数有两个参数,source指针指向的是源字符串,destination指针指向的是目标字符串,将源字符串的内容拷贝到目标字符串。如果待拷贝的字符串太长,则会出现错误,这就要求目标字符串的空间必须足够大。返回的是目标字符串的地址。
#include<stdio.h>
#include<string.h>
int main()
{
char ch[] = "4568215";
char ch1[5] = "0";
printf("%s\n",strcpy(ch1, ch)); //错误,因为ch1空间太小
printf("%s",strcpy(ch1, ch+3)); //正确,因为只需要复制ch后四个字符
return 0;
}
在复制时,会将源字符串的\0一起复制过去。
下面是模拟实现的代码
char* my_strcpy(char* DesStr, const char* SourceStr)
{
assert(DesStr && SourceStr); //保证两个指针的有效性
int length = my_strlen(SourceStr); //计算源字符串的长度
for (int i = 0; i <= length; ++i) //由于字符0不能进行解引用操作,这里选择
//循环赋值
{
DesStr[i] = SourceStr[i];
}
return DesStr;
}
由于字符串会包含一个隐藏的\0,所以实际长度是length+1个
字符串“123456789”可以看成这样的一个数组,赋值时,需要进行length+1次,这样将最后的\0也复制进去了,并且没有改变指针的指向,直接返回目标字符串的地址即可。
3.strcmp
这个函数可以比较两个字符串的大小,如果两个字符串的长度相同且对应字符一一相同,则两个字符串相同。在比较时,比较的不是两个串的长度,而是对应字符的ASCII码值。
函数原型如下
函数参数是待比较的两个字符串的地址,返回值是一个整数,如果str1指向的串更大,则返回大于0的数字,如果str2指向的串更大,则返回小于0的数字,如果两个串一样大,就返回0。
#include<stdio.h>
#include<string.h>
int main()
{
char ch[] = "ABCD";
char ch1[] = "ABCD";
char ch2[] = "aBCD";
char ch3[] = "ABC";
printf("%d\n", strcmp(ch, ch3)); //打印1 因为D的ASCII码值大,\0的ASCII码值是0
printf("%d\n", strcmp(ch, ch1)); //打印0 因为两个串完全相同
printf("%d", strcmp(ch, ch2)); //打印-1 因为a的ASCII码值大
return 0;
}
模拟实现代码如下
int my_strcmp(const char* ch1, const char* ch2)
{
assert(ch1 && ch2); //保证两个指针的有效性
while (*ch1 == *ch2) //字符相同则进入循环
{
if (*ch1 == '\0')//两个字符相等,,而且走完了,说明两个串完全相同,返回0
return 0; //否则指针向后走一步,比较下一个字符
ch1++;
ch2++;
}
if (*ch1 > *ch2) //字符不相等,比较字符的ASCII值
return 1;
return -1;
}
4.strcat
这个函数可以追加一个字符串到另一个字符串末尾。
函数原型如下
同样,函数参数是两个指针,返回值为目标字符串的地址。
由于需要添加字符到目标字符串末尾,则目标字符串需要有足够的空间。
#include<stdio.h>
#include<string.h>
int main()
{
char ch[] = "123456";
char ch1[20] = "ABCDEF";
printf("%s\n", strcat(ch1, ch));//打印ABCDEF123456
printf("%s\n", strcat(ch1 + 4, ch + 3));//打印EF123456456
return 0;
}
追加字符串时,从目标字符串的\0位置处开始追加。
模拟实现代码如下
char* my_strcat(char* des, const char* source)
{
assert(des && source); //保证两个指针的有效性
char* ret = des; //创建一个指针保存目标字符串地址
while (*des) //寻找目标字符串的\0
{
des++;
}
while (*source) //进行追加
{
*des = *source;
des++;
source++;
}
return ret;
}
5.strstr
函数原型如下
函数参数是两个指针,功能为在str1指向的字符串找str2指向的串,如果不存在这样的串,返回NULL,如果存在,则返回第一次出现这个串的地址。
对于函数功能下图可以帮助理解下。
#include<stdio.h>
#include<string.h>
int main()
{
char ch[15] = "123456789789";
printf("%s\n", strstr(ch, "123"));//打印123456789
printf("%s\n", strstr(ch, "135"));//找不到字串,打印null
printf("%s\n", strstr(ch, "89")); //打印89789
return 0;
}
模拟实现strstr函数,代码如下
char* my_strstr(const char* ch1, const char* ch2)
{
char* ch = (char*)ch1;
char* s1, * s2;
if (!*ch2) //如果ch2是空串,直接返回ch1
{
return (char*)ch1;
}
int len1 = strlen(ch1); //ch1指向的字符串的长度
int len2 = strlen(ch2); //ch2指向的字符串的长度
while (len1 >= len2) //串ch1余下的长度大于等于ch2的长度,说明余下的部分可
{ //能存在该字串
s1 = ch;
s2 = (char*)ch2;
while (!(*s1 - *s2)) //两个字符相等,则进行迭代
{
s1++;
s2++;
}
if (!*s2) //待查找的字串已经走完了,直接返回ch
{
return ch;
}
ch++; //代码走到这里时,说明从ch位置开始,找不到字串,则ch往后走一步
//继续找
len1--; //ch1的长度减1
}
return NULL;
}
如果余下的ch1的长度比待查找的字串的长度小,则不可能出现该字串,直接返回NULL
6.strncpy
函数原型如下
这个函数可以从source位置处拷贝num个字符到des位置处,如果num太大,则将source拷贝完后,需要额外拷贝\0,直到num个字符。
返回值是des位置处的地址。另外需要注意,两个指针指向的空间不能有重叠的部分,拷贝含有重叠部分时,会用到memmove函数。
#include<stdio.h>
#include<string.h>
int main()
{
char ch[20] = "1234";
char ch1[10] = "ABC";
printf("%s\n", strncpy(ch, ch1, 2)); //打印AB34
printf("%s\n", strncpy(ch, ch1+2, 10)); //打印C
printf("%s\n", strncpy(ch+3, ch1, 2)); //打印AB
return 0;
}
这个函数和strcpy函数相似,只是拷贝的字符数收到了限制
模拟实现代码如下
char* my_strncpy(char* des, const char* source, size_t num)
{
assert(des && source); //保证指针有效性
char* cur = des;
while (num--) //拷贝num次
{
if (*source != '\0') //拷贝source指向的字符串
{
*cur = *source;
}
else //source指向的字符串拷贝完了,需要额外补充\0
{
*cur = '\0';
}
cur++;
source++;
}
return des; //返回起始地址
}
函数参数和返回值都和库函数保持一致。
7.strncat
这个函数可以追加num个字符到目标字符串末尾
函数原型如下
同strcat一样,这里就不举例了,直接模拟实现一下
代码如下
char* my_strncat(char* des, const char* source, size_t num)
{
assert(des && source); //保证指针有效性
char* cur = des;
while (*cur) //寻找\0
{
cur++;
}
while (num) //开始迭代追加
{
if (*source) //source指向的串没有追加完
{
*cur = *source;
cur++;
source++;
}
else //追加完之后,需要追加一个\0
{
*cur = '\0';
break; //跳出循环
}
num--;
}
return des;
}
如果source指向的字符长度小于num,则追加完后,需要添加一个\0
二、内存函数
1.memcpy
函数原型如下
这个函数可以从source位置处拷贝num个字节到des位置处,函数参数和返回值类型都为void*,说明这个函数可以拷贝任意类型的数据。
比如整型数组,结构体类型…
如下代码
#include<stdio.h>
#include<string.h>
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int arr1[10] = { 0 };
char ch[10] = "ABCDEF";
char ch1[10] = "0";
memcpy(arr1, arr, sizeof(int) * 10);
memcpy(ch1, ch, 10);
for (int i = 0; i < 10; i++)
printf("%d ", arr[i]);
printf("\n%s", ch1);
return 0;
}
打印结果分别是1,2,3,4…10和ABCDEF
函数的返回值为des指向的地址,在拷贝时,仍需要注意内存重叠的问题。
模拟实现代码如下
void* my_memcpy(void* des, void* source, size_t num)
{
void* ret = des;
assert(des && source); //保证指针有效性
for (size_t i = 0; i < num; i++) //num次
{
*(char*)des = *(char*)source; //将指针强制转换为char*类型,在进行复制
((char*)des)++; //一次拷贝一个字节,指针一次走一个字节的长度
((char*)source)++;
}
return ret;
}
由于不知道需要拷贝的数据的类型,但是直到需要拷贝的字节数,所以可以选择一个字节一个字节的拷贝。
2.memmove
函数原型如下
这个函数的功能也是拷贝,但是允许源空间和目标空间重叠
比如下面这段代码
int main()
{
int arr[10] = { 1,5,4,2,3,5,6,9,8,744 };
char arr1[20] = "ABCDEFG1234567";
memmove(arr+2, arr, 5*sizeof(int));
memmove(arr1 + 2, arr1, 7);
for(int i=0;i<10;i++)
printf("%d ", arr[i]);
//打印结果1,5,1,5,4,2,3,9,8,744
printf("\n%s", arr1);
//打印结果为ABABCDEFG34567
return 0;
}
第一个memmove将arr中的1,5,4,2,3拷贝给4,2,3,5,6,存在内存重叠。
第二个memmove将arr1中的ABCDEFG拷贝给CDEFG12,也存在内存重叠。
但是结果都没有错误。
这个函数的返回值是目标空间的起始地址。
由于存在内存重叠,这里需要判断从后往前拷贝或者从前往后拷贝。
由于数组在内存中存储是连续的,随着下标的增加,地址由低到高,所以根据图中结果,如果存在内存重叠且源数据的地址小于目标数据的地址,应该从后往前拷贝。
下面是模拟实现的代码。
void* my_memmove(void* des,const void* source,size_t num)
{
void* ret = des;
assert(des && source); //保证指针有效性
if (des < source) //源数据地址大,选择正向拷贝
{
while (num--)
{
*(char*)des = *(char*)source;
((char*)des)++;
((char*)source)++;
}
}
else //源数据地址小与或者等于目标地址,逆向拷贝
{
while (num--)
{
*((char*)des + num) = *((char*)source + num);
//从最后一个字节开始拷贝,随着num--,实现从后往前拷贝。
}
}
return ret;
}
由于不知道拷贝的数据的类型,但是知道总字节数,根据指针的加法操作,找到末尾数据进行拷贝。
关于部分字符串函数和内存函数的介绍,到这里就结束了。大家可以在网站上进行更深入的学习。
https://legacy.cplusplus.com/
如果有不足或错误的地方,欢迎指出,如果有什么不懂的地方,也欢迎提问,拜拜!