size_t strlen ( const char * str );
strlen函数是用来求字符串长度的库函数,它的参数是被求长度的字符串的其实地址,返回值是一个无符号整型。
注意
使用方式如下: arr是数组名也就是该数组的起始地址,我们定义一个size_t的变量接收strlen函数求出的字符串的长度。
因为strlen函数是计算\0之前的字符,所以我们定义一个char指针替我们挨个寻找字符串直至找到\0,再定义一个变量count,初始化为0,每当char指针找到一个字符串,count便++。这样就可以计算出字符串的长度。
#include<stdio.h>
//计数器
int my_strlen(const char*str)
{
int count = 0;
while (*str)
{
str++;
count++;
}
return count;
}
int main() {
char arr[] = { "abcdefg" };
int len=my_strlen(arr);
printf("%d " , len);
return 0;
}
首先判断传入指针所指向的内容是否为\0,为0直接返回为0,否则就返回1 + my_strlen(str + 1),递归调用直至遇到\0递推结束,然后回归将值一步步返回。
//递归计算
int my_strlen(const char*str)
{
if (*str == '\0') {
return 0;
}
else {
return 1 + my_strlen(str + 1);
}
}
int main()
{
char arr[] = { "abcdefg" };
int len = my_strlen(arr);
printf("%d ", len);
return 0;
}
首先明确,指针-指针的差的绝对值是两个指针之间的元素个数。在函数体内,我们定义一个指针变量ps记录字符串的起始位置,然后让指针变量不断后移,直至遇到\0才停止,我们返回的值就是传入字符串的起始位置(str)与ps的差值。
//指针-指针
int my_strlen(const char*str)
{
char* ps = str;
while (*ps != '\0')
{
ps++;
}
return ps - str;
}
int main()
{
char arr[] = { "abcdefg" };
int len = my_strlen(arr);
printf("%d ", len);
return 0;
}
char* strcpy(char * destination, const char * source );
strcpy函数是用来拷贝字符串,即将一个字符串的内容拷贝到另一个字符串,它的参数都是两个指针,第一个参数为目标空间的起始位置(拷贝的所在位置),第二个参数是源字符串内容的起始位置,即被拷贝的字符串。返回值是目标空间的起始位置,便于链式访问。
注意
源字符没有以‘\0’结尾,程序会直接挂掉
错误使用一
#include<stdio.h>
#include<string.h>
int main()
{
char arr[] = { 'a','c','c' };
char arr2[] = { 'a','a','a'};
printf("%s", strcpy(arr, arr2));
return 0;
}
举个正确使用的例子
#include<stdio.h>
#include<string.h>
int main()
{
char arr[20] = { "abcdexxxx" };
char arr2[] = { "lkjj" };
printf("%s", strcpy(arr, arr2));
return 0;
}
运行结果:
内存分布图
由图可知strcpy会把‘\0’一并拷贝过去。
错误使用二(目标空间过小)
#include<stdio.h>
#include<string.h>
int main()
{
char arr[] = { "abc" };
char arr2[] = { "lkjjfg" };
printf("%s", strcpy(arr, arr2));
return 0;
}
运行结果图
由图可知虽然成功拷贝了,但是由于目标空间过小而造成非法访问,导致报错。
模拟的strcpy在函数参数和返回值上设计是一致的,在函数体内,我们先定义一个char *指针变量存放目标空间的起始位置,最后用于返回。如此便可以实现链式访问。函数的核心部分实现则是,将源字符串中的内容一一赋值给目标空间,直至遇到‘\0’,将‘\0’赋值过去终止循环,再把目标空间的起始位置返回。
#include<assert.h>
char* my_strcpy( char *dest, char*sour)
{
assert(dest && sour);
char* ret = dest;
while (*dest++= *sour++) {
;
}
return ret;
}
int main()
{
char arr[] = { "absdfasdfasrtfsa" };
char arr2[] = { "hello bit" };
printf("%s", my_strcpy(arr, arr2));
return 0;
}
char * strcat ( char * destination, const char * source );
strcat函数将源字符串追加到目标字符串的后面。它的两个参数依旧是两个指针,第一个指针指向的是目标字符串的起始位置,第二个指针指向的是源字符串的起始位置。返回值为目标空间的起始位置。
注意
int main()
{
char arr[20] = "abcdef" ;
char arr2[] = "abdd" ;
printf("%s", strcat(arr, arr2));
return 0;
}
追加后的结果arr中的内容变成“abcdefabdd”
在函数体内先定义一个指针变量存放目标空间的起始位置,用于返回。然后使用循环让dest不断的后移直至找到‘\0’,然后从‘\0’的位置将源字符串中的内容一一追加到目标空间里去,直至直至指针sour遇到‘0’,待把’\0’赋值过去终止循环,最后把目标空间的起始位置返回。
char * my_strcat( char *dest, char *sour)
{
char* ps = dest;
while (*dest)
{
dest++;
}
while (*dest++ = *sour++)
{
;
}
return ps;
}
int main()
{
char arr1[20] = {"abcdefg"};
char arr2[20] = " bit";
printf("%s\n", strcat(arr2, arr2));
}
函数介绍
int strcmp ( const char * str1, const char * str2 );
strcmp函数是用于比较两个字符串内容的函数,它的两个参数都是指针,两个指针分别指向待比较的起始位置,返回值为int的一个数字,当string1大于string2的时候返回一个大于0的数,小于则返回一个小于0的数,等于则返回0.
注意:
举个例子,将arr中的字符串与arr2中的字符串进行比较。
int main()
{
char arr[] = "abcd";
char arr2[] = "abcdef";
printf("%d", strcmp(arr, arr2));
return 0;
}//在VS2019中小于返回-1,大于返回1.等于为0
比较这连个字符串的时候前面几个字母的ASCII值都一样,当比到e和arr中的‘\0’时,发现e的ASCII的值比’\0’大,因此aar小于arr2,所以返回-1。
进入函数体内,直接比较两个字符串的起始位置,如果相同且不为‘\0’,则继续比较下一对字符,,如果相同且等于’\0’直接返回0,如果目标空间的字符ASCII值大于源字符串中字符的ASCII值则返回1,否则返回-1。
#include<assert.h>
int my_strcmp(char *dest,char *str)
{
assert(dest && str);
while (*dest==*str)
{
if (*dest == '\0')
{
return 0;
}
dest++;
str++;
}
if (*dest > *str)
{
return 1;
}
else {
return -1;
}
}
int main()
{
char arr[20] = "abcde";
char arr2[] = "abcdef";
printf("%d", my_strcmp(arr, arr2));
return 0;
}
我们可以发现在使用strcpy的时是将一个字符串的所有内容拷贝到另一个字符串,strcmp也是比较两个字符串的所有字符,我们将此类函数称为长度不受限制的字符串函数。但是这样的函数是有一定的风险,比如char arr[]=“abcd”;, char arr2[]=“rh”;,我们将arr中的字符串拷贝到arr2中,arr明显无法存储,但是会依旧会拷贝,只不过会引起非法访问而报错。为了解决这个问题,库函数中的strncpy,strncmp,strncat便孕育而生。
strncpy与strcpy比较在参数上多了一个参数,这个参数就是一个限制一个规则,这样我们就可以指定拷贝字符的个数。
注意
char * strncpy ( char * destination, const char * source, size_t num );
举例说明:
#include<stdio.h>
#include<string.h>
int main()
{
char arr[] = "xxxxxxxxxxxxxx";
char arr2[] = "fyfydg";
printf("%s", strncpy(arr, arr2, 7));
return 0;
}
将arr2中的7个字符拷贝到aarr中然而arr2中不足7个字符,直接补‘\0’,最终结果为fyfydg 。如果值拷贝三个结果为fyfxxxxxxxxxxx。
strncat与strcat比较在参数上多了一个参数,而这个参数就是指定操作字符的个数。
char * strncat ( char * destination, const char * source, size_t num );
注意
举个例子
int main()
{
char arr[10] = "asdg\0xxxxx";
char arr2[] = "adsg";
printf("%s", strncat(arr, arr2, 2));
return 0;
}
strncmp与strcmp比较在参数上多了一个参数,而这个参数就是指定需要比较字符的个数
举个例子
int main()
{
char arr[10] = "asdg";s
char arr2[] = "adsg";
printf("%d", strncmp(arr, arr2, 2));
return 0;
}
当操作数为2时,只比较arr和arr2前2个字符,比较规则和strcmp一致,所以返回1。
strstr函数可以在一个字符串1中查找另一个字符串2(子串),如果字符串2能在字符串1中找到,那么就返回字符串2在字符串1中出现的起始位置,否则就返回空指针。它的第一个参数是字符串1的起始位置,第二个参数是字符串2的起始位置。
注意:
如果字符串2为空字符串,则返回的是字符串1的起始位置。
举个例子,在arr中查找abc。
int main()
{
char arr [] = "abcabc";
char arr2[] = "abc";
char* ret = strstr(arr, arr2);
if (ret != NULL)
{
printf("找到了");
}
else
{
printf("没有找到");
}
return 0;
}
strstr函数的返回值是abc在字符串1中第一次出现的起始位置。只要找到了就会返回不会管后面是否还有。
strstr函数的模拟实现是比较麻烦的,我们需要创建三个辅助指针,来实现strstr函数。
指针cur:记录开始匹配时的起始位置,当从该位置便能找到目标字符串则直接返回cur,如果没有找到则cur++,从这个位置开始匹配直至找到。
str1指针和str2指针:对str1和str2解引用后判断每个字符是否相等,相等则两个指针都后移指向下一对,如果失败str1则回到cur指向的位置,str2匹配到最后一个字符依旧没找到便回到开始的位置。
举个例子将在arr中的字符串查找abc。
初始三个指针的位置
当s1与s2开始匹配,由图可知不匹配,所以cur此时从指向的位置不能找到我们要往后移动。字符串,故cur++。cur++后s1也要往后移动进行下一轮匹配。
此时s1与s2指向的字符不相等,则说明从这里开始匹配无法找到我们要查找的字符串,所以cur++后赋值给s1
此时s1与s2指向的字符相等,则cur记录这个位置不动,s1和s2继续往后移,直至遇到的字符不相等。
此时s1与s2指向的内容不相等,而s2已经走到最后,我们还是没有找到我们要查找的子串,所以我们需要重置s2与s1,即cur++后赋值给s1,str2赋值给s2.
然后s1与s2不断匹配,失败后cur++再次赋值给s1,s1与s2再次参与匹配。
此时s1与s2不断匹配就只能成功了,最后匹配成功返回cur。
strstr模拟代码
r#include<string.h>
#include<stdio.h>
#include<assert.h>
char* my_strstr(const char* str1,const char* str2)
{
assert(str1 && str2);//str1和str2为空指针时报错
const char* s1 = str1;
const char* s2 = str2;
const char* cur = str1;
while (*cur)
{
s1 = cur;
s2 = str2;
while (*s1&&*s2&&(*s1 == *s2))
{
s1++;
s2++;
}
cur++;
if (*s2 == '\0')//目标字符串已经找到
{
return (char*)cur;
}
}
return NULL;//找不到目标字符串
}
int main()
{
char arr [] = "abcabc";
char arr2[] = "abc";
char* ret = my_strstr(arr, arr2);
if (ret != NULL)
{
printf("找到了");
}
else
{
printf("没有找到");
}
return 0;
}
sretok函数通过给定的分隔符的字符集合中的字符去把字符串分割成若干个子字符串,注意如果分隔符的字符集合中的字符不是待分割字符串的字符,是无法分割。所以分割符的字符集合必须是字符串中有的字符。它的第一参数是需要被分割的字符串的首地址,第二个参数也是字符串的首地址,该字符串是作分割符的字符集合。返回值是查找到分隔符之前字符串的首地址。
char * strtok ( char * str, const char * sep );
注意:
举个例子,比如我们把30776675259@qq.com以“@”和“.”分割
#include<stdio.h>
#include<string.h>
#include<stdio.h>
#include<string.h>
int main()
{
char arr[] = "3076675259@qq.com";
char* sep = "@.";
char arr2[20] = { 0 };
strcpy(arr2, arr);
char* ret = strtok(arr2, sep);
while (ret != NULL)
{
printf("%s\n", ret);
ret = strtok(NULL, sep);
}
return 0;
}
当函数strtok找到第一个分隔符会把这哥哥分割符改成‘\0’,并且将分隔符之前的字符串首元素的地址返回开始打印,第二次查找将从@开始往后查找下一个分隔符,依次打印,如果像com的后面已经没有分割符直接返回com的首地址并打印,因为com后面本身就有“\0”。
strerror
char * strerror ( int errnum );
返回错误码,所对应的错误信息。
比如向内存申请空间失败
#include<stdio.h>
#include<string.h>
#include<limits.h>
#include<errno.h>
int main()
{
int* p = (int*)malloc(INT_MAX);//向堆内存申请空间
if (p == NULL)
{
//printf("%s\n", strerror(errno));
perror("malloc");
return 1;
}
return 0;
}
使用strerror函数的报错信息
使用perror函数的报错信息
这两个函数都是将错误码转换成人们能看的懂的信息,区别就是strerror是将错误码转换成错误信息后不打印,而perror会打印,而且perror中的字符内容是自己指定 的最好是写出现问题的函数,这样可读性高。
函数介绍
mencpy函数是拷贝两块无关的内存区域数据的函数,它会从源数据中的起始位置拷贝num个字节的数据到目标空间里去,并返回目标空间的首地址。
void * memcpy ( void * destination, const void * source, size_t num );
注意
如我们将arr中的前5个数拷贝到arr2中去
#include<stdio.h>
int main()
{
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
int arr2[20] = { 0 };
int* ret = (int*)memcpy(arr2, arr, 20);//返回值的类型为void*,所以强转。
for (int i = 0; i < 5; i++) {
printf("%d ", *(ret+i));
}
return 0;
}
内存分布图
因为我们一个整型4个字节,而我们要拷贝5个数,所以传参的第三个参数为20.
#include<stdio.h>
#include<string.h>
#include<assert.h>
void* my_memcpy(void* dest, void* sour, size_t count)
{
assert(dest && sour);
void* ret = dest;
while (count--)
{
*(char*)dest = *(char*)sour;
dest = (char*)dest + 1;
sour = (char*)sour + 1;
}
return (char*)ret;
}
int main()
{
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
int arr2[20] = { 0 };
int* ret = (int*)my_memcpy(arr2, arr, 20);
for (int i = 0; i < 5; i++) {
printf("%d ", *(ret + i));
}
return 0;
}
menmvoe函数和menecpy函数的参数和返回值是一模一样的,memmvoe函数和memcpy函数最大的区别就是memmove函数操作的源内存块和目标空间的内存块是可以重叠的而memcpy函数的源内存块和目标空间的内存块是不能重叠。
void * memmove ( void * destination, const void * source, size_t num );
注意
举个例子将arr中的12345,从arr+3的位置开始拷贝。
#include<stdio.h>
#include<string.h>
int main()
{
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
int* ret = (int*)memmove(arr+3 , arr, 20);
for (int i = 0; i <=6; i++) {
printf("%d ", *(ret + i));
}
return 0;
}
当源内存块与目标空间内存块重登时是无法像mencpy一样从前往后拷贝,在此我们需要分类讨论。
由图我们将拷贝情况分为三种
我们拷贝分为两种情况,直接划分从前往后拷和从后往前拷。
#include<assert.h>
void* my_memmove(void* dest, const void* sour, size_t count)
{
assert(dest && sour);
void* ret = dest;
if (dest < sour)
{
while (count--)
{
*(char*)dest = *(char*)(sour);
dest = (char*)dest + 1;
sour = (char*)sour + 1;
}
}
else
{
while (count--)
{
*((char*)dest + count) = *((char*)sour + count);
}
}
return ret;
}
int main()
{
int arr1[10] = { 1,2,3,4,5,6,7,8,9,10 };
my_memmove(arr1 + 2, arr1, 16);
int i = 0;
int sz = sizeof(arr1) / sizeof(arr1[0]);
for (i = 0; i < sz; i++)
{
printf("%d ", arr1[i]);
}
return 0;
}
mencmp函数是比较两个内存块大小的函数,它会比较ptr1和ptr2开始的num个字节,当ptr1>ptr2时,返回一个大于0的数;当ptr1<ptr2时,返回一个小于0的数;相等时则返回0;
int memcmp ( const void * ptr1, const void * ptr2, size_t num );
举个例子比较arr和arr2中前4个数
#include<stdio.h>
#include<string.h>
int main()
{
int arr[] = { 1,2,3,4,5 };
int arr2[] = { 1,2,3,4,0x1122334455 };
int ret = memcmp(arr, arr2, 16);
printf("%d\n", ret);
return 0;
}
在VS中内存分布采用小端字节序arr和arr2在内存中的存储形式
因为我们传入的是16也就是比较16个字节的内容,由图可知是相等的,但是如果传入的是17输出的结果为-1,因为05<55。
memset函数可以将内存块中的的某一部分修改为指定的字符。三个参数,第一个参数是目标的起始位置,第二个参数是指定的修改内存区域的字符,第三个参数是从起始位置开始设置的内存的字节个数。memset是以字节为单位来初始化内存单元的。
void *menmset(void* dest ,int num,size_t count)
举例将arr中hello在内存中修改为6.
int main()
{
char arr[] = "hello world" ;
memset(arr, 6, 5);
return 0;
}