文章目录
strncpy,strncmp,strncat-可限制的字符串函数
前言
1.C语言中对字符和字符串的处理很是频繁,但是C语言本身是没有字符串类型的,字符串通常放在 常量字符串中或者字符数组中。
2.字符串常量 适用于那些对它不做修改的字符串函数.
函数介绍
strlen-求字符串长度
size_t strlen ( const char * str );
strlen函数是用来求字符串长度的库函数,它的参数是被求长度的字符串的其实地址,返回值是一个无符号整型。
注意
1.参数所指向的字符串必须以\0结束
2.strlen所求的字符串长度是\0之前的字符个数,不把\0纳入计算范围
3.函数的返回值是无符号数(size_t)
使用方式如下: arr是数组名也就是该数组的起始地址,我们定义一个size_t的变量接收strlen函数求出的字符串的长度。
模拟实现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; }
strcpy-字符串拷贝
char* strcpy(char * destination, const char * source );
strcpy函数是用来拷贝字符串,即将一个字符串的内容拷贝到另一个字符串,它的参数都是两个指针,第一个参数为目标空间的起始位置(拷贝的所在位置),第二个参数是源字符串内容的起始位置,即被拷贝的字符串。返回值是目标空间的起始位置,便于链式访问。
注意
1.源字符串必须以‘\0’结束
2.会将源字符串拷贝中的‘\0’拷贝到目标空间
3.目标空间必须足够大,以确保能存放源字符串。
源字符没有以‘\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模拟
模拟的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; }
strcat-字符串追加
char * strcat ( char * destination, const char * source );
strcat函数将源字符串追加到目标字符串的后面。它的两个参数依旧是两个指针,第一个指针指向的是目标字符串的起始位置,第二个指针指向的是源字符串的起始位置。返回值为目标空间的起始位置。
注意
1.源字符串必须以 ‘\0’ 结束。
2.目标空间必须有足够的大,能容纳下源字符串的内容。
3.举例说明,我们将字符创arr2中的内容追加到arr中
int main() { char arr[20] = "abcdef" ; char arr2[] = "abdd" ; printf("%s", strcat(arr, arr2)); return 0; }
追加后的结果arr中的内容变成“abcdefabdd”
strcat模拟实现
在函数体内先定义一个指针变量存放目标空间的起始位置,用于返回。然后使用循环让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)); }
strcmp-字符串比较
函数介绍
int strcmp ( const char * str1, const char * str2 );
strcmp函数是用于比较两个字符串内容的函数,它的两个参数都是指针,两个指针分别指向待比较的起始位置,返回值为int的一个数字,当string1大于string2的时候返回一个大于0的数,小于则返回一个小于0的数,等于则返回0.
注意:
字符串比较的是两个字符串对应的ASCII值而不是字符串的长度。
举个例子,将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。
strcmp模拟实现
进入函数体内,直接比较两个字符串的起始位置,如果相同且不为‘\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,strncmp,strncat-可限制的字符串函数
strncpy
strncpy与strcpy比较在参数上多了一个参数,这个参数就是一个限制一个规则,这样我们就可以指定拷贝字符的个数。
注意
- 如果源字符串的长度小于num,则拷贝完源字符串之后,在目标的后边追加0,直到num个。
- 拷贝num个字符从源字符串到目标空间。num为多少就拷贝多少。
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
strncat与strcat比较在参数上多了一个参数,而这个参数就是指定操作字符的个数。
char * strncat ( char * destination, const char * source, size_t num );
注意
1.如果追加时操作数大于源字符串中字符个数,只会将源字符串的字符个数全部追加完就结束
2.追加时是从目标空间的‘\0’开始追加,追加完后再补一个‘\0’,使之成为字符串
3.可以自己给自己追加
举个例子
int main() { char arr[10] = "asdg\0xxxxx"; char arr2[] = "adsg"; printf("%s", strncat(arr, arr2, 2)); return 0; }
strncmp
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
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函数的模拟实现是比较麻烦的,我们需要创建三个辅助指针,来实现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; }
strtok-字符分割函数
sretok函数通过给定的分隔符的字符集合中的字符去把字符串分割成若干个子字符串,注意如果分隔符的字符集合中的字符不是待分割字符串的字符,是无法分割。所以分割符的字符集合必须是字符串中有的字符。它的第一参数是需要被分割的字符串的首地址,第二个参数也是字符串的首地址,该字符串是作分割符的字符集合。返回值是查找到分隔符之前字符串的首地址。
char * strtok ( char * str, const char * sep );
注意:
strtok函数找到str中的下一个标记,并将其用 \0 结尾,返回一个指向这个标记的指针。
strtok函数的第一个参数不为 NULL ,函数将找到str中第一个标记,strtok函数将保存它在字符串 中的位置。
strtok函数的第一个参数为 NULL ,函数将在同一个字符串中被保存的位置开始,查找下一个标记。
如果字符串中不存在更多的标记,则返回 NULL 指针。
strtok函数会改变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容 并且可修改。
举个例子,比如我们把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-perror-报错函数
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中的字符内容是自己指定 的最好是写出现问题的函数,这样可读性高。
内存函数
memcpy-内存拷贝
函数介绍
mencpy函数是拷贝两块无关的内存区域数据的函数,它会从源数据中的起始位置拷贝num个字节的数据到目标空间里去,并返回目标空间的首地址。
void * memcpy ( void * destination, const void * source, size_t num );
注意
函数memcpy从source的位置开始向后复制num个字节的数据到destination的内存位置。
这个函数在遇到 ‘\0’ 的时候并不会停下来。
如果source和destination有任何的重叠,复制的结果都是未定义的。
如我们将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.
memcpy模拟实现
#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; }
memmove-内存拷贝加强版
menmvoe函数和menecpy函数的参数和返回值是一模一样的,memmvoe函数和memcpy函数最大的区别就是memmove函数操作的源内存块和目标空间的内存块是可以重叠的而memcpy函数的源内存块和目标空间的内存块是不能重叠。
void * memmove ( void * destination, const void * source, size_t num );
注意
- 和memcpy的差别就是memmove函数处理的源内存块和目标内存块是可以重叠的。
- 如果源空间和目标空间出现重叠,就得使用memmove函数处理。
举个例子将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; }
memmove模拟实现
当源内存块与目标空间内存块重登时是无法像mencpy一样从前往后拷贝,在此我们需要分类讨论。
由图我们将拷贝情况分为三种
1.dest指针位于sour内存块的左边,采用从前向后拷贝。
2.dest指针在sour内存块内则采用从后向前拷贝。
3.dest指针与sour内存块位于同一区域,则可以从后往前拷贝也可以从前往后拷。
我们拷贝分为两种情况,直接划分从前往后拷和从后往前拷。
#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; }
memcmp-内存比较
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函数可以将内存块中的的某一部分修改为指定的字符。三个参数,第一个参数是目标的起始位置,第二个参数是指定的修改内存区域的字符,第三个参数是从起始位置开始设置的内存的字节个数。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; }