目录
C语言中对字符和字符串的处理很频繁,但是C语言本身没有字符串类型,字符串通常放在常量字符串或字符数组中。常量字符串适用于那些对它不做修改的字符串函数
/*
* 字符串
* - 以'\0'结尾的一串字符。0与'\0'相同,但是与'0'不同。
* - \0标志着字符串的结束,但其不是字符串的一部分。
* 计算字符串长度的时候,就不包含'\0'
* - 字符串多以数组形式存在,以数组或指针的形式访问
* 更多是以指针的形式。
* - string.h有很多处理字符串的函数。
*/
-
字符串特性
int main() { //C语言中,如果两个相邻的字符串中间什么都没有,那么就会被看作是一个字符串。 printf("abc""def\n");//abcdef char a[] = "a""c"; printf("%s",a);//ac // 这里字符串中的\,表示换行。换行后字符串前的空格也算入字符串中 printf("abc\ def\n");//acabc def printf("abc\ def");//acabcdef }
-
字符串常量
int main() { int i; char *s = "hello world"; char *s2 = "hello world"; //s与s2都是指针,初始化为指向一个字符串常量。 //由于这个常量所在的地方,所以实际上s与s2是const char*。但是如果不写const,编译器也不报错。 //所以我们就需要自己注意,不能去修改常量 //*s = 'B'; //该操作是要去修改常量,常量不允许修改,所以编译报错。 //我们发现字符串常量存放的位置,与局部变量存放的位置不同。 //局部变量的地址与字符串常量的地址,相距很远。 //并且s与s2指向了同一个字符串常量。这是因为字符串常量是存在于代码段中。 //并且对于代码段中的数据,我们只有读取的权限,没有写入的权限,这就是为什么常量一旦定义之后就不可以修改的原因。 //因为s于s2指向相同的字符串常量,所以他们实际上是指向了同一个字符串常量。 printf("%p\n",&i); //000000A53B9FF72C printf("%p\n",s); //00007FF7967BA000 printf("%p\n",s2); //00007FF7967BA000 //如果定义的字符串需要修改,则应使用数组 char s3[] = "Hello world!"; return 0; }
-
char*是字符串吗?
/* * 1.字符串可以表达为char*的形式 * 2.char*不一定是字符串 * char*本意上是指向字符的指针,可能指向的是字符数组 * 只是说char*所指向的字符数组有字符串结束符时,才能说是指向的是字符串。 */
-
空字符串
//""是一个空字符串,空字符串以\0结尾,也就是说buffer[0] = '\0' char buffer[100] = ""; //该数组长度是1,其中存储一个字符串结束符\0 char buffer[] = "";
-
main()函数的参数有两个
//表示数组argv有argc个元素。 int main(int argc,char const* argv[]) { //argv[0]是命令本身。当使用Unix的符号链接时,反应符号链接的名字。 int i; for(i=0 ; i<argc ; i++) { printf("%d——%s\n",i,argv[i]); //windows下:0——D:\Program Files (x86)\CLion 2021.3.2\Project\First\cmake-build-debug\First.exe } } //实际上用于Linux系统下,输入指令运行该程序时,所有输入的内容,都以空格隔开,存储在argv()数组中 //例如输入指令调用以上程序(以上程序写入文件,文件名是a.out,linux下可执行程序以.out为后缀)。 //指令: ./a.out 123 abc haha hehe //程序执行输出:0——./a.out 1——123 2——abc 3——haha 4——hehe
求字符串长度函数
strlen()函数
-
函数原型
/* * strlen()函数 * 函数原型:size_t strlen(const char* str); * 作用:返回字符串str的长度。字符串的长度由终止空字符'\0'决定。只计算字符的个数,直到出现\0。长度不包括结束符'\0'。 * 参数 —— str:要计算长度的字符串。 * 返回值:返回字符串的长度。 * 注意:参数指向的字符串必须要以\0结束。如果没有\0,则会一直向后取,直到碰到\0位置,因为其后出现\0的位置并不能确定,所以此时的字符串长度是个随机值。 * size_t是服务号整型 */
-
strlen()函数使用
#include <string.h> #include <stdio.h> int main() { //arr1数组中实际上有四个元素:a b c \0 char arr1[] = "abc"; //arr2数组中没有结束符\0 char arr2[] = {'a','b','c'}; int len1 = strlen(arr1); int len2 = strlen(arr2); printf("%d\n",len1);//3 //此时的长度是个随机值,因为没有碰到\0会一直向后取。 printf("%d\n",len2);//6 return 0; }
-
strlen()函数模拟实现
计数器方式,使用中间变量
#include <string.h> #include <stdio.h> #include <assert.h> int my_strlen(const char * str) //因为只是计算长度,所以为str指针增加一个不可修改的属性。 { assert(str); //str不是空指针的时候执行程序 int count = 0; while(*str != '\0') { count++; str++; } return count; } int main() { char arr1[] = "abcdddef"; char arr2[] = {'a','b','c'}; //模拟实现strlen函数 int len1 = my_strlen(arr1); int len2 = my_strlen(arr2); printf("%d\n",len1);//8 printf("%d\n",len2);//11,这个11是个随机值 return 0; }
递归实现
/* * 以下程序执行过程: * - main()函数调用,char数组创建,其中存储数据dog,printf("%d\n",my_strLen(arr));执行 * - my_strLen(arr)执行,其中存储的是数组首元素的内存地址,其中存储的'd'!='\0',输出,并执行return 1+my_strLen(str+1); * 注意这里传进来的是str,也就是dog'\0',比较的时候用的指针变量,*str则表示其是首元素地址,也就是第一个字符。 * - 1+my_strLen(str+1)执行,str+1表示’d‘的下一个内存地址。此时函数内的str就变成了og'\0',*str是'o'!='\0' * 输出并执行return 1+my_strLen(str+1); * - 1+my_strLen(str+1)执行,str+1表示’o‘的下一个内存地址。此时函数内的str就变成了g'\0',*str是'g'!='\0' * 输出并执行return 1+my_strLen(str+1); * - 1+my_strLen(str+1)执行,str+1表示’g‘的下一个内存地址。此时函数内的str就变成了'\0',*str是'\0'='\0' * 此时if中条件为0,不再进行函数递归。return0 * - return 0+1之后,再次return 1+1,再次return 1+2,就返回3。 */ #include <stdio.h> //因为是计算数组中字符的个数,所以应该是返回一个数。因为是要读取数组本身,所以参数要传入指针变量。 int my_strLen(char* str) { //如果传递进来的这个地址对应的不是'\0'字符串结尾字符,就递归调用 //*str就表示我们传递进来的元素的内存地址,这里如果写成str,就 if(*str != '\0') { //因为str是一个指针变量,其中存储的是数组首元素的内存地址 //又因为数组中存储的元素内存地址都是连续的,所以str+1就表示下一个元素的内存地址。 //这里不是*str,是因为str+1本身就是地址,我们传入之后,函数直接操作就可以。 return 1+my_strLen(str+1); } //如果str中读取的是\n,也就是说读取到字符串末尾了,则返回0 return 0; } int main() { char arr[] = "dog"; printf("%d\n",my_strLen(arr)); }
指针-指针
#include <stdio.h> //数组指针相减求长度的方法 int myStrLen(char * str) { //将数组首元素的地址保存到s中 char * s = str; //如果不是\0,就一直循环,直到str对应的地址是最后一个元素的地址,也就是'\0'的地址 while(*str != '\0') { str++; } //最后一个元素的地址-第一个元素的地址,就是这两个元素之间的个数,也就是字符串的长度 return str-s; } int main() { char arr[] ="abcdef"; int len = myStrLen(arr); printf("%d\n",len); return 0; }
-
需要注意的地方:strlen()函数返回size_t类型,是无符号整型
#include <string.h> #include <stdio.h> int main() { //因为strlen的返回类型是size_t,这两个无符号类型的值进行加减,返回的也是一个无符号类型的数。 //第一个长度是3,第二个长度是6。3-6本来应该是-3。但是因为是无符号整型,所以最高位1被解析为数值位。 //-3原码:10000000 00000000 00000000 00000011 //-3反码:11111111 11111111 11111111 11111100 //-3补码:11111111 11111111 11111111 11111101 //strlen("abc")-strlen("abcdef")表达式执行后,其结果最后为size_t无符号整型,所以一定是比0大的。 //-3在内存中存储为补码,如果是无符号整型,则最高位1被解析为数值位。就是4294967293。这个数是远大于0的,所以输出大于。 if(strlen("abc")-strlen("abcdef") >0) { //%u以无符号整形打印。 printf("%u\n",strlen("abc")-strlen("abcdef"));//4294967293 printf("大于"); } else { printf("小于"); } }
不可设置长度的字符串函数
strcpy()函数
-
函数原型
/* * strcpy()函数 * 函数原型:char *strcpy(char *dest,const char *src) * 作用:将src指向的C字符串复制到dest指向的数组中,包括终止的空字符(并在复制终止符后停止)。 * 参数:dest——用于复制内容的目标数组; src——要复制的字符串 * 需要注意: * - 如果目标数组的dest不够大,而src中的字符串长度又太长,可能会造成缓冲溢出的情况。要保证dest足够大,以确保能放下源字符串 * - 目标空间必须可变。如:dest是一块数组空间,不能是指向常量字符串的指针,因为常量字符串是常量,不可修改。 * - 目标数组中如果有内容,则会被源字符串覆盖掉。 * - 因为遇到'\0'才会停止拷贝,所以源字符串必须以'\0'结束。如果没有'\0',则可能得到意料之外的结果。 * 返回值:返回一个指向最终的目标字符串dest的指针 */
-
使用
#include <stdio.h> #include <string.h> int main() { //数组拷贝 char arr1[20] = {0}; char arr2[] = "hello world!"; strcpy(arr1,arr2); printf("%s\n",arr1);//hello world! //字符串拷贝 char arr3[20] = {0}; // char* p ="abcdefg"; // strcpy(arr3,p); //可以简化为: strcpy(arr3,"you are good"); printf("%s\n",arr3);//you are good //程序会挂掉,因为常量字符串不能修改。 // char* str = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxx"; // char* p ="abcd"; // strcpy(str,p); // printf("%s\n",str); //目标字符串中如果有内容,则会被源字符串覆盖掉。 char arr4[20] = {'c','c','c'}; char arr5[] = {'a','b','c'}; //源字符串如果没有以'\0'结尾,则会一直拷贝,直到碰到\0。 strcpy(arr4,arr5); printf("%s\n",arr4);//abcabc,如果c后有\0,则正常拷贝应该是abc,但是没有\0,所以出现了abcabc。abc后是随机字符。 return 0; }
-
strcpy()函数模拟实现
#include <stdio.h> void myStrCpy(char* dest,char* src) { //当不是字符串结尾的时候拷贝 while(*src != '\0') { *dest++ = *src++; } //执行到这里,*src中存储的就是\0了,*dest也++了,虽然此时循环停止了,但是我们可以手动拷贝\0到dest中 *dest =*src; } int main() { char arr1[20] = "abcdefgh"; char arr2[] = "hello"; myStrCpy(arr1,arr2); printf("%s",arr1); return 0; }
简化
#include <stdio.h> void myStrCpy(char* dest,char* src) { //当没有到达字符串结尾的时候,循环一直进行。这里是后置++,先赋值再运算。 //()中表达式的结果就是*src的结果。例如:b=3,a=b这个表达式的结果应该是a,但是a=b,也就是说b就是这个表达式的结果。 //当src指向的是'h'时,它对应的ASCII码是:104。非零条件为真,执行空语句。此时后置++生效,继续判断()中的结果 //只有当src='\0'的时候,也就是dest='\0',字符串结尾符'\0'拷贝了过去,字符串拷贝完成。而'\0'的ascii码就是0,也就是条件为假,循环结束。 while(*dest++ = *src++); //参考: //int i = 1; //printf("%d\n",i='\0'); //0 } int main() { char arr1[20] = "abcdefgh"; char arr2[] = "hello"; myStrCpy(arr1,arr2); printf("%s",arr1); return 0; }
使用断言,并使用const修饰
#include <stdio.h> #include <assert.h> //库函数中的字符串拷贝函数是char * strcpy ( char * destination, const char * source ); //它的形参在源数组前加上了一个const,为什么要加这个东西呢? //我们拷贝是要把src指向的内容放入dest指向的空间中,从本质上讲,是希望dest指向的内容被修改,而src指向的内容不修改。 //也就是*src不能被修改,所以在*的左边加上了const,表示*src不能被修改。 void myStrCpy(char* dest,const char* src) { //当程序执行到断言的位置时,对应的断言应该为真。若断言不为真时,程序会中止执行,并给出错误信息。 //当传入的两个数组中有一个是空指针,则输出错误信息 assert(dest != NULL); assert(src != NULL); //const表示被修饰的变量不可被修改。防止我们在编写的过程中写反。 // 也就是说如果我们这里写反了,那么编译都不会过去,报错:assignment of read-only location '*src++' //while(*src++ = *dest++); while(*dest++ = *src++); } int main() { char arr1[20] = "abcdefgh"; char arr2[] = "hello"; myStrCpy(arr1,arr2); printf("%s",arr1); return 0; }
最终版
#include <stdio.h> #include <assert.h> //库函数中的字符串拷贝函数是char * strcpy ( char * destination, const char * source ); //库函数返回类型是一个指针,返回的是目标的起始地址。 char* myStrCpy(char* dest,const char* src) { assert(dest); assert(src); //我们定义一个指针,接收传入函数的目标起始地址,保存到变量中。 //使用ret进行拷贝地址的变化。 char* ret = dest; while(*ret++ = *src++); //拷贝完成后返回起始地址。 return dest; } int main() { char arr1[20] = "abcdefghjklmn"; char arr2[] = "hi baby!"; printf("%s",myStrCpy(arr1,arr2)); return 0; }
strcat()函数
-
函数原型
/* * 字符串拼接函数strcat() * 函数原型:char * strcat(char* dest,const char* src); * 作用:把src指向的字符串,追加到dest所指向的字符串的结尾。dest字符串中“第一个字符串结束符”被src第一个字符覆盖。 * 将整个源字符串,包括结束符\0,都拼接到dest字符串后。如果拼接的源字符串没有结束符\0,则程序会down掉。 * 按理说:如果一个数组,自己拷贝自己。数组中存放的字符串结束符\0就会被替换掉,但是在运行的时候,并没有被替换。而是正常拷贝,并且拷贝后的字符串有结束符。但是如果拷贝的是一个没有结束符的字符数组,则程序就会down掉。 * 参数:dest:指向目标数组,该数组中包含了一个C字符串,并且足够容纳追加后的字符串。src:要追加的字符串,该字符串不会覆盖目标字符串。 * 返回值:返回dest字符串指针 * 注意:dest必须可以修改,不能是是指向常量字符串的指针。源字符串必须有字符串结束符\0 */
-
使用
#include <string.h> #include <stdio.h> int main() { char arr1[20] = "hello \0aaa"; strcat(arr1,"word!"); printf("%s\n",arr1);//hello word! return 0; }
-
strcat()函数的模拟实现
#include <string.h> #include <stdio.h> char* my_strcat(char* dest,const char* src) { //因为要返回目标指针,所以我们在附加之前就保存其指针。 char* ret = dest; //1.找字符串dest中的\0 while(*dest) //当*dest中不是\0的时候,循环就一直运行。当其指向了\0时,*dest=\0,这个表达式的结果就是0,循环结束。 { dest++; } //追加字符串,包含'\0',当*src='\0'时,*dest就='\0',而\0就是0,循环结束。当拷贝了字符串结束符之后,循环也停止。 while(*dest++ = *src++); return ret; } int main() { char arr1[20] = "hello \0"; printf("%s\n",my_strcat(arr1,"world!"));//hello word! return 0; }
strcmp()函数
-
函数原型
/* * strcmp()函数 * - 用于对两个字符串进行比较 * - 语法: int strcmp(const char* str1 , const char* str2); * 参数str1与参数str2是参与比较的两个字符串 * - strcmp()会根据ASCII编码依次比较str1和str2中的每一个字符,直到出现不到的字符,或者达到字符串末尾(遇到\0) * - 返回值: * ——如果返回值 < 0 ,则表示str1 < str2 * ——如果返回值 > 0 ,则表示str1 > str2 * ——如果返回值 = 0 ,则表示str1 = str2 * - 比较这两个字符串相同位置的字符的ASCII码,一旦出现不匹配的,就比较这两个字符。ASCII码低的就小,高的就大。 * - 如abcq与adc进行比较,两个字符串第一个字符相同,比较第二个字符,d比b大,所以adc>abcq。比较的是对应位置的字符的大小,而不是比较字符串长度。 */
-
使用
#include <stdio.h> #include <string.h> int main() { char* p = "obc"; char* q = "abcdef"; //字符串比较不能使用运算符比较,要使用相应的字符串函数。 //比较两个字符串对应位置的字符的ASCII码。 int ret = strcmp(p,q); if(ret > 0) { printf("p大"); } else if(ret < 0) { printf("q大"); } else { printf("p=q"); } return 0; }
-
strcmp()函数模拟实现
#include <stdio.h> #include <string.h> #include <assert.h> int my_strcmp(const char* s1 ,const char* s2) { assert(s1 && s2);//如果s1或s2有一个是空指针,程序就不会继续执行。 //如果对应位置的字符相等,那么地址+1,指向下一个字符。直到这两个字符不相等,或比较完。 while(*s1 == *s2) { //如果其中一个是字符串结束符,那么说明这两个字符串完全相等。相等返回0。 if(*s1 == '\0') { return 0; } s1++; s2++; } //如果是abc与abcd比较,那么c比完之后。就是d与\0比较,d肯定比0大,所以是abcd>abc //如果指向的字符不相等,则进行比较,看哪个大。 //s1比s2大,就返回>0的数。//如果s1<s2,就返回<0的数。 return *s1 - *s2; } int main() { char* p = "abce"; char* q = "abcdef"; //比较两个字符串对应位置的字符的ASCII码。 int ret = my_strcmp(p,q); if(ret > 0) { printf("p大"); } else if(ret < 0) { printf("q大"); } else { printf("p=q"); } return 0; }
可设置长度的字符串函数
strncpy()函数
-
函数原型
/* * strcnpy()函数 * 函数原型 char* strncpy(char* dest,const char* src,size_t num); * 作用:把src的前num个字符复制到dest。如果num>src长度,多余的用\0填充,直到总共写入了num个字符。如果num<sre,则不会在目标末尾隐式追加空字符。 * 参数: * - dest:指向要存储复制内容的目标数组指针。 * - src:要复制的字符串 * - num:要从src复制的最大字符数。size_t是无符号整型。 * 注意: * - dest与src不能重叠。也就是说不能自己复制自己。 * - 如果dest中本来就有内容,并且src中没有空字符、src>num,此时dest前num个字符是从src中复制过来的,剩下的是dest中的内容。 * 除非从src中复制了空字符\0,此时dest中长于src的部分才不会显示。 * 返回值:返回dest */
-
使用
#include <string.h> #include <stdio.h> int main() { char arr1[20] = "abcdef"; char arr2[] = "qwer"; //复制qw到arr2中,因为没有空字符,所以arr1前两个字符就变为:qw,接着arr1中本来就有的cdef。就成为:qwcdef strncpy(arr1,arr2,2); printf("%s\n",arr1); //qwcdef return 0; }
-
strncpy()函数模拟实现
#include <stdio.h> char* my_strncpy(char* dest,const char* src,size_t num) { //将dest保存,最后返回 char* start = dest; //条件:当num不为0、*src没有到\0的时候进行拷贝 //情况1:当num不为0,当*src='\0'时,*dest = '\0',\0就是0,此时循环停止。//因为拷贝\0也是一次拷贝,而拷贝了一次,没有执行循环,num少减了一次1。 //情况2:当*src不为0,而num为0时,说明要拷贝的次数已经完成。 //后置++,每次拷贝完之后,dest与src都向后指一个位置,方便进行拷贝。 while(num && (*dest++ = *src++)) { num--;//循环每执行一次,num-1 } //num为0后,说明次数拷贝完了,不用再做操作 //而如果num不为0,而*src为字符串结束符\0了。此时num为几,就要在后面追加几个\0。 //1、如果num不为0,而我们上面的程序num少减了一次1,如果使用后置++,则会多追加一个\0 //2、使用前置++,先减1再执行程序,为dest之后追加0。这样看似合理。但是当num=0时,0-1=-1,因为num是无符号整型。-1就被解析为一个很大的数,循环一直追加\0,此时程序就会陷入死循环。 //所以我们不能直接进行操作,而是要操作前判断一下num是不是为0,如果不为0再进行操作 //while(++num){*dest++ = '\0';} if(num) { //如果num不为0,则继续为dest追加空字符 while(--num){ //此时是num不为0,而*dest=*src=\0的情况,因为是后置++,此时的dest指向的就是拷贝的\0的后面,直接*进行追加\0操作 //而后置++,同样是为了下一次追加更方便。 *dest++ = '\0'; } } return start; } int main() { char arr1[20] = "abcdefg"; char arr2[] = "qwe"; //复制qw到arr2中,因为没有空字符,所以arr1前两个字符就变为:qw,接着arr1中本来就有的cdef。就成为:qwcdefg my_strncpy(arr1,arr2,2); printf("%s\n",arr1); //qwcdefg //当num>src时,如果想要验证拷贝的\0个数是否正确,可以加断点,Debug进行查看。 return 0; }
strncat()函数
-
函数原型
/* * 指定长度字符串拼接函数strncat() * 函数原型:char * strncat(char* dest,const char* src,size_t num); * 作用:将src的前num个字符附加到dest结尾。dest字符串中第一个的字符串结束符被src第一个字符覆盖。并且在拼接后的字符串末尾加上字符串结束符 * 如果src中字符串的长度小于num,则仅复制直到终止空字符串的内容(包括终止字符串)。 * 注意:因为当长度<num时,strncat并不会为其添加结束符,只有碰到结束符才会结束。此时如果追加的字符数组没有结束符\0时,在追加了其长度的字符之后的内容,都是随机的,因为只有碰到结束符\0才会结束。所以我们在为其追加时,src字符串一定要带有\0。 * 参数: * - dest:指向目标数组,应该包含一个C字符串,且足够容纳追加后的字符串,包括额外的空字符 * - src:要追加的字符串 * - num:要附加的最大字节数。size_t无符号整型。 * 返回值:返回一个只想最终目标字符串dest的指针。 *
-
使用
#include <string.h> #include <stdio.h> int main() { char arr1[20] = "hello "; char arr2[] ="world!"; //将arr2的前3个字符附加到arr1结尾。arr1中第一个字符串结束符被arr2第一个字符覆盖。并且在拼接后的字符串末尾加上字符串结束符\0 strncat(arr1,arr2,3); printf("%s\n",arr1); //hello wor char arr3[20] = "hi "; char arr4[] ="baby!"; //arr4中字符串的长度小于20,则仅复制直到终止空字符串的内容(包括终止字符串)。 strncat(arr3,arr4,20); printf("%s\n",arr3); //hi baby! return 0; }
-
strncat()函数模拟实现(模拟库函数实现方法实现)
#include <stdio.h> char* my_strncat(char* dest, char* src, size_t num) { //保存dest,最后返回 char* start = dest; //首先将dest定位到其字符串结束符\0的位置。 //*dest=\0时,条件为0,循环结束。后置++,此时dest指向\0后的位置。 while(*dest++); //dest-1,让dest指向字符串结束符\0。 dest--; //num为几就循环几次 while(num--) { //进行拷贝。 //*dest++ = *src++; //如果num>src长度,那么如何结束呢?加入判断,在*src == \0的时候,结束方法 //if(*src == '\0'){return start;} //简化:只有当拷贝到字符串结束符的时候结束结束方法。 //情况1:num<src长度时,追了num个字符,循环结束会结束。结束后会为其添加字符串结束符。 //情况2:num>src长度,在*src == \0的时候,结束方法。 if((*dest++ = *src++) == '\0') { return start; } } //为结尾添加字符串结束符\0 *dest = '\0'; //返回dest。 return start; } int main() { char arr1[20] = "hello "; char arr2[] ="world!"; //将arr2的前3个字符附加到arr1结尾。arr1中第一个字符串结束符被arr2第一个字符覆盖。并且在拼接后的字符串末尾加上字符串结束符\0 my_strncat(arr1,arr2,3); printf("%s\n",arr1); //hello wor char arr3[20] = "hi "; char arr4[] ="baby!"; //arr4中字符串的长度小于20,则仅复制直到终止空字符串的内容(包括终止字符串)。 my_strncat(arr3,arr4,20); printf("%s\n",arr3); //hi baby! return 0; }
实现注意:
/* * - 因为当长度<num时,strncat并不会为其添加结束符,只有碰到结束符才会结束。 * 此时如果追加的字符数组没有结束符\0时,在追加了其长度的字符之后的内容,都是随机的,因为只有碰到结束符\0才会结束。 * 所以我们在为其追加时,src字符串一定要带有\0。 * - 解决:重新实现strncat * * - 实现思路:增加一个参数,用于传递字符数组元素个数/字符串的长度。 * 这样在实现的时候,加入判断。 * - 如果长度>指定追加字符数num,那么就以num--为条件进行循环 * - 如果长度<num,那么我们以(长度--)为条件进行循环,然后循环结束后为其添加结束符\0。 * * - char * my_strncat(char* dest,const char* src,size_t length,size_t num); * dest:指向目标数组,应该包含一个C字符串,且足够容纳追加后的字符串,包括额外的空字符 * src:要追加的字符串/字符 * length:字符串/字符数组长度 * num:要附加的最大字节数。size_t无符号整型。 * 返回值:返回一个只想最终目标字符串dest的指针。 */
strncmp()函数
-
函数原型
/* * strncmp()函数 * 函数原型:int strncmp(const char* str1,const char* str2,size_t num); * 作用:比较str1与str2的前n个字符。比较每个字符串的对应字符,如果相等,则比较下一个位置的字符,直到字符不同/到达终止空字符/到达第num个字符。 * 参数:str1 —— 要比较的字符串; str2 —— 要比较的字符串; num —— 要比较的最大字符数,size_t是无符号整型。 * 返回值: * - 如果返回值 < 0 ,表示第一个不匹配的字符在str1中的值低于在str2中的值。str1 < str2 * - 如果返回值 > 0 ,表示第一个不匹配的值在str1中的值大于在str2中的值。str1 > str2 * - 如果返回值 = 0 ,表示两个字符串前num个字符相等。str1 = str2 */
-
使用
#include <string.h> #include <stdio.h> int main() { char* p = "abcdef"; char* q ="abcqwe"; int ret = strncmp(p,q,3); printf("%d\n",ret);//0 表示前三个字符相等 int ret1 = strncmp(p,q,4); printf("%d\n",ret1);//-1,表示i前三个字符,p<q char* p1 = "abc"; char* q1 ="abc"; int ret2 = strncmp(p1,q1,6); printf("%d\n",ret2);//0,表示相等。num>长度,比较到字符串结束符就停止。 char* p2 = "aec"; char* q2 ="abc"; int ret3 = strncmp(p2,q2,6); printf("%d\n",ret3);//1,表示p2>q2。虽然num为6,但是在比较到第二个字符时,出现了不匹配的字符。比较出了大小,所以停止。 return 0; }
字符串中查找字符
strchr()
/*
* strchr()函数
* 函数原型:csont char* strchr(const char* str,int char);
* 作用:定位字符串中第一次出现的字符
* - 返回指向C 字符串str中第一次出现的字符的指针。
* 终止的空字符被认为是 C 字符串的一部分。因此,也可以定位它以检索指向字符串末尾的指针。
* 参数:
* - str:C字符串
* - char:要定位的字符,作为int整型提升传递,但在内部转换为char进行比较。
* 返回值:指向str中第一次出现该字符的指针。如果未找到该字符,则该函数返回一个空指针。
*
*/
int main()
{
char s[] = "hello";
//查找第一个l
char *p = strchr(s,'l');
printf("%s\n",p);//llo
//查找第二个l
// p = strchr(p+1,'l');
// printf("%s\n",p);//lo
//拷贝找到的字符之后的所有字符到另一个字符串
char *t = (char*)malloc(strlen(s)+1);
strcpy(t,p);
printf("%s\n",t);//llo
//拷贝找到的字符之前的所有字符到另一个字符串
*p = '\0';
strcpy(t,s);
printf("%s\n",t);//he
return 0;
}
strrchr()
/*
* strrchr()
* 函数原型:const char* strrchr(const char* str,int char);
* 作用:查找字符串中最后一次出现的字符
* - 返回指向字符串str中最后一次出char字符的指针。
* 终止空字符被认为是C字符串中的一部分。因此,也可以定位它来检索指向字符串结尾的指针。
* 参数:
* - str:字符串
* - char:要定位的字符,作为int整型提升传递,但在内部转换为char进行比较。
* 返回值:指向str中最后一次出现该字符的指针。如果未找到该字符,则该函数返回一个空指针。
*/
int main()
{
char s[] = "world dlrow";
char *p = strrchr(s,'r');
printf("%s\n",p);//row
return 0;
}
字符串查找函数
strstr函数
-
函数原型
/* * 定位子串函数strstr() * 函数原型:const char* strstr(const char* str1,const char* str2); * 或char* strstr(char* str1,const char* str2); * 作用:在str1中查找第一次出现str2的指针,如果str2不是str1的一部分,则返回空指针。匹配过程中不包括字符串结束符'\0',但它会停在那里。 * 参数:- str1:要扫描的长字符串。 -str2:短字符串,在str1中查找是否出现的字符串 * 返回值:该函数返回str2第一次出现在str1中的指针。如果str2不在str2中,也就是未在str1中找到str2,就返回NULL。 */
-
使用
#include <string.h> #include <stdio.h> int main() { char arr1[] = "abcdefabcdef"; char arr2[] = "bcd"; char* ret = strstr(arr1,arr2); ret == NULL? printf("没找到"): printf("找到了:%s\n",ret); return 0; }
-
strstr函数的模拟实现
#include <stdio.h> #include <assert.h> char* my_strstr(const char* str1,const char* str2) { assert(str1 && str2); //strstr规定,如果要在某个字符串中查找空字符串(""):其中存储了字符串结束符,那么就返回str1字符串 if(*str2 == '\0') { return (char*)str1; } /* * 比较逻辑: * - 需要把短的字符串(bbc)第一个字符与长字符串(acbbbcdef)的第一个字符比较:a≠b。指针向后指1 * - bbc第一个字符与acbbbcef中第二个字符比较:b≠c。指针向后指1 * - bbc第一个字符与acbbbcef中第三个字符比较:b=b;指针向后指1 * 继续比较:bbc第二个字符与acbbbcef中第四个字符比较,b=b;指针向后指1 * 继续比较:bbc第三个字符与acbbbcef中第五个字符比较,c≠b。指针返回指向开始比较时的地址。 * 注意这里:acbbbcef指向第五个字符的指针,此时需要指向的是第四个字符。也就是说,我们需要有一个指针保存str1在每次比较前所指向的位置。 * - bbc第一个字符与acbbbcef中第四个字符比较:b=b;指针向后指1 * 继续比较:bbc第二个字符与acbbbcef中第五个字符比较,b=b;指针向后指1 * 继续比较:bbc第三个字符与acbbbcef中第六个字符比较,c=c;指针向后指1 * 继续比较:bbc中的结束符与acbbbcef中第六个字符比较,此时返回acbbbcef中第四个指针的地址。 */ //当不匹配时用于每次向后走1个位置,匹配时使用s1进行比较。 //如果只是前几个字符匹配,出现不匹配字符的时候,将repeat指向下一个位置,并且赋给s1。 //如果全部匹配,则说明此时repeat指向的位置就是str2在str1中首次出现的位置,返回即可。 const char* repeat = str1; //因为查找子串,最后需要返回子串的指针。所以我们将其赋给s1、s2。操作s1、s2,这样str1、str2中的值就不会被改变了。 const char* s1 = str1;//长字符串指针,用于每次比较 const char* s2 = str2;//短字符串指针,要用于每次比较。 //repeat每次向后指1,直到长字符串匹配完(匹配到终止空字符)时循环结束 while(*repeat) { //每次循环,s1中指向的是repeat指向的位置。 s1 = repeat; //s2指向的就是短字符串。 s2 = str2; //进行匹配,如果*s1=*s2,则s1与s2都指向下一个位置。 //情况1:在abcd中找cd。c=c,d=d,\0=\0,此时s1与s2++之后都指向\0之后的内容了。加入判断 //情况2:在abcde中找efg,e=e,\0≠f,此时跳出循环repeat++,直到repeat=\0循环停止。 //我们可以加上判断:如果s1=\0了,就返回NULL。这样就不用判断之后的了。 // while(*s1 == *s2) // { // s1++; // s2++; // } //s2++后,如果s2是字符串结束符,说明s1中包含了s2,那么我们在循环后加条件让其返回repeat指针 /* while(*s2 && *s1 == *s2) { s1++; s2++; } if(*s2 == '\0') { return repeat; }*/ while(*s1 == *s2) { s1++; s2++; //当*s1=*s2时,如果s2++为空,说明s2已经到了字符串结束符。 //也就是说,此时repeat中存储的指针就是s1中第一次出现s2的位置。 /* * 注意:这个判断要在循环内。 * 例如:当abcd中找cd时,如果判断在循环外。则c=c,d=d,\0=\0,然后s1与s2++,此时他们都指向字符串结束符之后的位置 * - 等循环出来,s2指向的已经不是结束符的位置了。所以判断要放在循环内。 */ if(*s2 == '\0') { return repeat; } } /* * - 情况1:abcd中找cd,s1与s2都为\0,但是s2为\0的时候就已经返回了指针,不考虑这种情况。 * - 情况2:abcd中找def,d=d,\0≠e,跳出循环之后进入判断,因为s1已经找完字符串结尾,在这之后肯定不可能找到def字符串了 * 所以我们直接返回NULL。加这个判断,只是为了提升效率。也可以不加,跳出循环后repeat+1,然后传入s1。只是会多判断几次再返回NULL */ if(*s1 == '\0') { return NULL; } repeat++; } return NULL; } int main() { char arr1[] = "acbbbcef"; char arr2[] = "bbc"; char* ret = my_strstr(arr1,arr2); ret == NULL? printf("没找到"): printf("找到了:%s\n",ret); return 0; }
strtok()函数
-
函数原型
/* * strtok()函数 * 函数原型:char* strtok(char* str,const char* delimiters); * 作用:使用标记拆分str字符串。这些标记由delimiters(分隔符)中的任何字符分割的连续字符序列。 * - 在第一次调用时,该函数需要一个字符串作为str的参数,str的第一个字符用做扫描的起始位置。 * - 函数首先从起始位置扫描不是分隔符的第一个字符,然后从此字符开始扫描分隔符。 * 如:##abc#def#ggg#haha\0中以#为分隔符分割字符串,那么会从a开始扫描其后的分隔符。 * - 在其后扫描到的第一个分隔符,该字符被替换为字符串结束符\0,并且起始扫描位置由函数返回;或者扫描到终止空字符,扫描也会停止。 * 如:##abc#def#ggg#haha\0以#为分隔符分割字符串,从a开始扫描分隔符,扫描到c后面的#,会将其替换为\0,成为:##abc\0def#ggg#haha\0 * 此时扫描的起始位置由函数返回,也就是指向str的第一个非分隔符字符'a'的指针。 * - 如果扫描到分割符,那么在之后的调用中,该函数需要一个空指针,并使用前一个分隔符之后的位置作为新的扫描位置。 * 例:也就是##abc\0def#ggg#haha\0中,c后面的那个空字符\0之后的位置,会保存到函数中,以便在下次调用中使用(不需要特定的库实现,来避免数据争用) * - 如果扫描到了分隔符,那么在之后的调用中,strtok函数的第一个参数为NULL,函数将在同一个字符串中被保存的位置开始,查找下一个分隔符。 * 如:##abc\0def#ggg#haha\0,继续调用strtok(NULL,"#"),此时会从c后的\0后的d开始扫描分隔符,返回指向d的指针。 * 扫描到f后#的位置,将其替换为空字符\0,成为:##abc\0def\0ggg#haha\0。然后f后\0之后的位置,会保存在函数中,以便于在下次调用中使用。 * ......再经历一次strtok(NULL,"#"),成为:##abc\0def\0ggg\0haha\0,g后的\0之后的位置被保存到函数中。 * - 一旦找到str终止空字符,对该函数的所有后续调用(以空指针作为第一个参数)都会返回空指针。 * 如:##abc\0def\0ggg\0haha\0,从h开始扫描,扫描到haha后的\0,返回指向h的指针,扫描停止。 * 在此之后所有对strtok(NULL,"#")的调用,都将返回空指针。 * * 注意: * - strtok会改变被操作的字符串,所以使用此函数分割字符串时,一般是拷贝源字符串后,对拷贝出的字符串进行函数调用。 * - strtok函数的第一个参数不为NULL时,函数在str中找到第一个分隔符,并保存它在字符串中的位置。 * 是在函数内部,有一个静态变量来保存其位置。在下一次调用的时候,使用这个位置后的那个位置作为开始扫描的位置,继续扫描分隔符。 * - 也可以使用空格作为分隔符。strtok(str," "); * 但是不能使用\0作为分隔符,因为碰到\0strtok函数会以为遇到了字符串结束符,会下一次调用就会返回空指针。 * * 参数: * - str:要被分解的字符串。注意:这个字符串是通过拆分成更小的字符串(标记)来修改的。或者可以指定空指针,在这种情况下,函数继续扫描先前成功调用结束的位置。 * - delimiters:包含分割字符的C字符串。 * * 返回值: * - 该函数返回被分解的第一个子字符串,如果没有可检索的字符串,则返回空指针。 * 如果找到标记,则会显示指向该标记开头的指针;否则返回空指针。在扫描的字符串中,到达字符串结束符时,始终返回空指针。 */
-
使用
#include <string.h> #include <stdio.h> int main() { char arr[] = "##abc#def#ggg#haha"; char* p = "#"; //strtok会改变被操作的字符串,所以使用此函数分割字符串时,我们将arr数组拷贝进tmp数组,对tmp进行函数调用。 char tmp[20] = {}; strcpy(tmp,arr); //strtok函数返回每一个分割后的字符串的起始字符的指针,我们使用char*类型指针变量q来接收 char* q = NULL; // q = strtok(tmp,p); // printf("%s\n",q);//abc // // q = strtok(NULL,p); // printf("%s\n",q);//def // // q = strtok(NULL,p); // printf("%s\n",q);//ggg // // q = strtok(NULL,p); // printf("%s\n",q);//haha //简化 //初始化:q=strtok(tmp,p) //判断条件:当q不为空指针时,说明还没有分隔到字符串结尾。 //更新:每次打印了分割的字符串之后,进行更新 q=strtok(NULL,p) //当遇到tem中的字符串结束符\0时,扫描停止,下一次strtok(NULL,p)调用返回空指针。此时q为空指针,循环结束。 for(q=strtok(tmp,p) ; q!=NULL; q=strtok(NULL,p)) { printf("%s\n",q); } //循环中更新表达式已经执行,但是条件为假循环结束。此时我们打印查看发现q确实已经为为空指针 //printf("%s\n",q);//(null) return 0; }
错误信息报告
strerror()函数
-
函数原型
/* * strerror()函数 * 函数原型:char* strerror(int errnum); * * 作用: * - 使用库函数的时候,如果库函数调用失败,都会设置错误码:其函数中有一个全局变量int errno; * - strerror()从内部数组中搜索错误号errnum,生成带有描述错误信息的字符串,strerror生成的错误字符串取决于开发平台和编译器。 * 返回的指针指向一个静态分配的字符串,程序不能修改。对该函数的进一步调用可能会覆盖其内容(不需要特定的库实现来避免数据争用) * 参数: * - errnum:错误号,通常传入的是errno。 * - error是一个全局变量,C语言定义好的一个全局变量,所有人都可以使用。需要引入头文件<error.h> * 返回值:返回指向描述错误号errnum的错误字符串的指针。 */
-
使用
#include <stdio.h> #include <string.h> #include <errno.h> int main() { // printf("%s\n",strerror(0));//No error —— 没有错误 // printf("%s\n",strerror(1));//Operation not permitted —— 不允许操作:操作被拒绝 // printf("%s\n",strerror(2));//No such file or directory —— 没有这个文件/文件夹 // printf("%s\n",strerror(3));//No such process —— 没有这个进程 // printf("%s\n",strerror(4));//Interrupted function call —— 函数调用被中断 // printf("%s\n",strerror(5));//Input/output error —— 输入/输出错误 //fopen()函数用于打开文件,test.txt是文件名,r表示以只读形式打开。 FILE* pf = fopen("test.txt","r"); if(pf == NULL) { //error是一个全局错误码,是一个全局变量。当fopen函数调用失败之后,会将error设置为对应值。 //我们将error传入strerror()函数,就会打印对应的错误信息。 //因为我们打开文件传入的文件名是一个没有创建的文件,所以错误信息是:没有这个文件 printf("%s\n",strerror(errno));//No such file or directory } return 0; }
perror()函数
-
函数原型
/* * perror()函数与strerror()函数的区别: * - strerror()函数是把错误码转换为错误信息,需要的时候才打印其对应的错误信息 * - perror()会直接打印错误信息,并且还可以选择在其信息之前加上执行的自定义信息。 * perror()函数 * 函数原型:void perror(const char* str); * 作用: * - 将error的值解释为错误信息,并将其打印到标准错误输出流,通常是打印到控制台。可以选择在其前面加上str中指定的自定义消息。 * - errno是一个整型变量,其值描述了调用库函数产生的错误条件或诊断信息。 * C标准库的任何函数都可以为errno设置值(即使没有明确指定,即使没有发生错误)。 * - 产生的错误信息与使用的开发平台及编译器有关。 * - 如果str传入空指针NULL,则会直接打印错误信息,错误信息后跟换行符\n。 * 如果str不是空指针,则打印str指向的字符串,后跟一个英文冒号(:),再跟一个空格( ),然后加上错误信息,再加上换行符\n。 * - perror应该在错误信息产生之后立即调用,否则error可能会在调用其他库函数时被覆盖。 * * 参数 —— str:要打印在错误信息之前的自定义的字符串。如果传入空指针,则无自定义信息,直接打印错误信息。 * 返回值:无返回值。 */
-
使用
#include <stdio.h> int main() { //fopen()函数用于打开文件,test.txt是文件名,r表示以只读形式打开。 FILE* pf = fopen("test.txt","r"); if(pf == NULL) { //因为我们打开文件传入的文件名是一个没有创建的文件,所以错误信息是:没有这个文件 //如果传入的是空指针,则直接打印错误信息,后跟换行符\n。 perror(NULL);//No such file or directory //如果是传入一个字符串,则是str+冒号+空格+错误信息+换行符\n perror("fopen");//fopen: No such file or directory } 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() | 是否为可打印字符,包括图形字符和空白字符 |
-
简单使用
#include <stdio.h> #include <ctype.h> int main() { //isdigit(),如果是数字字符返回非0的值,如果不是数字字符,返回0。 char ch = '1'; printf("%d\n",isdigit(ch));//1 //islower(),如果是小写字母字符返回非0的值,如果不是,返回0。 ch = 'a'; printf("%d\n",islower(ch));//2 return 0; }
字符转换函数
-
函数原型
/* * tolower()函数 * 函数原型:int tolower(int c) * 作用: * - 如果c是大写字母,并具有其小写字母,则将c转换为对应的小写字母。 * 如果不能进行这种转换,则返回的值c不变 * - 注意:被视为字母的内容可能取决于使用的语言环境。在默认的”C“语言环境中。 * 大写字母可以是以下任意一种:A B C D E F G H J K L M N O P Q R S T U V W X Y Z * 它们分别转换为:a b c d e f g h i j k l m n o p q r s t u v w x y z * - 在其他语言环境中,如果一个大写字符有多个对应的小写字符,则此函数总是为相同的c值返回相同的字符。 * * 参数c:要被转换为小写的字符。 * 返回值:如果c有对应的小写字母,则函数返回c的小写字母,否则返回未更改的c。该值作为int值返回,可以隐式转换为char类型 */
-
使用
#include <stdio.h> #include <ctype.h> int main() { char arr[20] = {0}; scanf("%s",arr); int i =0; //可以加一个判断,如果其为大写字母,则转换为小写,并放入数组。 // while(arr[i] != '\0') // { // if(isupper(arr[i])) // { // arr[i] = tolower(arr[i]); // } // printf("%c ",arr[i]); // i++; // } //如果其值如果不能被转换为小写,或已经是小写,则不会改变其值。所以我们也可以省略判断。 while(arr[i] != '\0') { arr[i] = tolower(arr[i]); printf("%c ",arr[i]); i++; } printf("%s",arr); return 0; }
-
函数原型
/* * toupper()函数 * 函数原型:int toupper(int c) * 作用: * - 如果c是小写字母,并具有其大写字母,则将c转换为对应的大写字母。 * 如果不能进行这种转换,则返回的值c不变 * - 注意:被视为字母的内容可能取决于使用的语言环境。在默认的”C“语言环境中。 * 小写字母是:a b c d e f g h i j k l m n o p q r s t u v w x y z * 它们分别转换为:A B C D E F G H J K L M N O P Q R S T U V W X Y Z * - 在其他语言环境中,如果一个小写字符有多个对应的大写字符,则此函数总是为相同的c值返回相同的字符。 * * 参数c:要被转换为大写的字符。 * 返回值:如果c有对应的大写字母,则函数返回c的大写字母;否则返回未更改的c。该值作为int值返回,可以隐式转换为char类型 */
-
使用
#include <stdio.h> #include <ctype.h> int main() { char arr[20] = "aAbBcCdDEe12EeDdgG"; int i =0; //如果其值如果不能被转换为大写,或已经是大写,则不会改变其值。所以我们也可以省略其是否小写字母,如果是再转换的判断。 while(arr[i] != '\0') { arr[i] = toupper(arr[i]); i++; } printf("%s",arr);//AABBCCDDEE12EEDDGG return 0; }
内存函数
memcpy()函数
-
函数原型
/* * memcpy()函数 * 函数原型:void* memcpy(void* dest,const void* src,size_t num); * 作用: * - 将src中前num个字节复制到dest指向的内存块。指针指向的数据的类型与函数无关。 * - 该函数不检查源中的任何终止空字符,他总是精确复制num个字节。为了避免溢出,dest与src指向的数组大小应该至少为num个字节。 * 并且dest与src不能指向同一内存块。对于重叠的内存块,memmove是一种更安全的方法。 * * 参数: * - dest:指向存储复制内容的目标数组指针,类型强制转换为void*指针。 * - src:只想要复制的数据源的指针,类型强制转换为void*指针。 * - num:要复制的字节数。size_t是无符号整型。 * * 返回值:该函数返回指向dest的指针。 */
-
使用
#include <memory.h> int main() { int arr1[10] = {0,1,2,3,4,5,6,7,8,9}; int arr2[10] = {0}; // //memcpy(arr2,arr1,20); //指定断点,调试查看执行结果 return 0; }
-
模拟实现
#include <assert.h> void* my_memcpy(void* dest,const void* src,size_t num) { assert(dest && src); void* ret = dest; while(num--) { //void*是无具体类型,需要将其强制转换为char*类型,然后解引用。一个字节一个字节拷贝。 *(char*)dest = *(char*)src; //拷贝后,指针向后走一个字节,就需要先转换为char*类型,然后+1。 dest = (char*)dest + 1; src = (char*)src + 1; } return ret; } int main() { int arr1[10] = {0,1,2,3,4,5,6,7,8,9}; int arr2[10] = {0}; my_memcpy(arr2,arr1,20); //将arr1中前5个元素0,1,2,3,4放到2,3,4,5,6的位置,期望结果:0,1,0,1,2,3,4,7,8,9 my_memcpy(arr1+2,arr1,20);//0,1,0,1,0,1,0,7,8,9 ,如果库函数中也是我们这样实现的,那么在使用时,dest与src就不能指向同一个数组。 //指定断点,调试查看执行结果 return 0; }
memmove()函数
-
函数原型
/* * memmove()函数 * 函数原型:void* memmove(void* dest,const void* src,size_t num); * 作用:将src中的前num个字节复制到dest指向的内存块。复制就像使用中间缓冲区一样进行。允许src与dest重叠。 * 参数: * 参数: * - dest:指向存储复制内容的目标数组指针,类型强制转换为void*指针。 * - src:只想要复制的数据源的指针,类型强制转换为void*指针。 * - num:要复制的字节数。size_t是无符号整型。 * * 返回值:该函数返回指向dest的指针。 */
-
使用
#include <string.h> int main() { int arr[10] = {1,2,3,4,5,6,7,8,9,10}; //将arr数组中,1,2,3,4,5放到3,4,5,6,7的位置 memmove(arr+2,arr,20); //加断点,调试查看结果 //1,2,1,2,3,4,5,8,9,10 return 0; }
-
memmove()函数模拟实现
#include <assert.h> /* * 实现思路: * - dest与src是不重叠,则怎么拷贝都可以。 * dest与src指向同一个数组的不同位置,并且src的前num个字节包含了dest指向的位置,此时就需要注意将src内容拷贝到dest时,是不是修改了src中靠后的几个字节的内容。 * 如:将a[10]中前5个元素a[0]~a[4]放到a[2]~a[6]的位置, * 如果是从前往后拷贝:a[2]=a[0],a[3]=a[1],a[4]应该=a[2]原来的值,但是因为a[2]=a[0]了, * src中的内容还没有被拷贝到dest中,就被改变了,结果就不是我们想要的结果了。 * - dest与src重叠,dest<src,并且src的前num个字节有与dest重叠的部分。 * 如:1 2 3 4 5 6 7 8 9中将6 7 8拷贝到5 6 7的位置,从前往后拷贝 * 6放入5的位置,成为:1 2 3 4 6 6 7 8 9 * 7放入6的位置,,成为:1 2 3 4 6 7 7 8 9 * 8放到7的位置,成为:1 2 3 4 6 7 8 8 9 * -dest与src重叠,src<dest,并且src的前num个字节有与dest重叠的部分。 * 如:1 2 3 4 5 6 7 8 9中将5 6 7拷贝到6 7 8的位置,期望结果:1 2 3 5 5 6 7 9 * 此时如果从前向后拷贝,5放到6的位置成为:1 2 3 4 5 5 7 8 9 ( src中的6还没有拷贝就被修改了。 ) * 6位置的数据放入7位置:1 2 3 4 5 5 5 8 9 * 7位置的数据放入8位置:1 2 3 4 5 5 5 5 9 ,与七万结果不同,src中的数据还未拷贝到dest中对应位置就被修改了。 * 如果我们从后往前拷贝,也就是将7放入8的位置:1 2 3 4 5 6 7 7 9 * 6放入7的位置:1 2 3 4 5 6 6 7 9 * 5放入6的位置:1 2 3 4 5 5 6 7 9 ,与期望结果相同 * - 如果是dest与src重叠,dest<src或dest>src。但src的前num个字节有与dest没有重叠的部分。 * 此时不管从前向后或从后向前拷贝,都可以。因为没有重叠的部分,所以src中的内容不会被修改,可以完全拷贝到dest中 * - 如果dest与src指向不同的数组,则前向后或后向前都可以。 * * 结论: * - dest<src时,从前往后拷贝即可。也就是*(char*)dest=*(char*)src,拷贝完后dest与src向后走一个字节,然后继续拷贝.... * - src<=dest时,从后往前拷贝,也就是*((char*)dest+num) = *((char*)src+num),拷贝完之后dest与src向前走一个字节,然后继续拷贝... */ char* my_memmove(void* dest,const 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--) { //每次拷贝完,向前走一个字节,就是-1。但是num每次就减1,所以不用再做操作。 *((char *) dest + num) = *((char *) src + num); } } return ret; } int main() { int arr[10] = {1,2,3,4,5,6,7,8,9,10}; my_memmove(arr+2,arr,20); //加断点,调试查看结果。 return 0; }
memcmp()函数
-
函数原型
/* * memcmp()函数 * 函数原型:int memcmp(const void* ptr1,const void* ptr2,size_t num); * 作用:将prt1与prt2的前num个字节进行比较。与strcmp不同,该函数找到空字符后不会停止比较。 * 参数: * - ptr1:指向内存块的指针。 * - ptr2:指向内存块的指针。 * - num:要比较的字节数 * 返回值: * - 返回值<0,表示两个内存块中不匹配的第一个字节,在prt1中值低于ptr2,也就是ptr1 < ptr2。 * - 返回值等于0,两个内存块的前num个字节相同。 * - 返回值>0,表示两个内存块中不匹配的第一个字节,在prt1中值大于ptr2,也就是ptr1 > ptr2。 */
-
使用
#include <stdio.h> #include <string.h> int main() { int arr1[] = {1,2,3}; int arr2[] = {1,3}; printf("%d\n",memcmp(arr1,arr2,4)); printf("%d\n",memcmp(arr1,arr2,5)); return 0; }
memset()函数
-
函数原型
/* * memset()函数 * 函数原型:void * memset(void * ptr,int value,size_t num); * 作用:将ptr指向的内存块的前num字节设置为指定值。 * - ptr:指向要填充的内存块的指针 * - value:要设置的值。该值作为int传递,但该函数使用该值的无符号字符转换填充内存块。 * - num:被设置为value值的字节数。size_t是无符号整数类型。 */
-
使用
#include <string.h> #include <stdio.h> int main() { char arr[] ="hello world"; //将arr数组的前5个字节替换为x memset(arr,'x',5); printf("%s",arr); //xxxxx world int arr1[10] = {0}; memset(arr1,1,20);//以字节为单位设置内存 //调试查看arr数组在内存中的存储,会发现arr数组的前20个字节,每个字节都是01 return 0; }