字符串即内存操作函数
本篇博客将介绍一些字符串及内存标准库函数,这些函数都是标准库中提前准备好了的,而我们需要做的是:
①能够熟练使用
②能够深度使用(了解内部的一些坑,并能规避)
③能够扩展(了解内部实现)
当我们能够做到这些的时候,我们就可以在编写代码的时候如鱼得水,如虎添翼。需要注意的是,在我们使用这些标准库函数时,需要包含相应的头文件。
参数合法性检验
由于无法保证调用者传入的是一个什么样的参数,比如极端情况下传入一个非法地址;所以我们必须要让我们的代码有较强的健壮性,应该在函数的入口处加上对于参数合法性的校验,这样可以规避许多常见的错误。
参数合法性检验的两种风格:
1.if else;
2.assert (表达式);断言,写一个表达式,如果表达式为真,断言没有任何效果,代码继续执行;如果表达式为假,程序就会直接崩溃。使用的时候要包含<assert.h>头文件。
参数合法性判定的注意事项:
1.一般拿参数和空指针判定,虽然没啥卵用,但是聊胜于无。(有些合法性检验没啥用,但是必须写,要让别人知道你有参数合法性检验这个意识)。
2.判定的时候有两种风格,该怎么选?
assert:是一种比较严厉的处理方式,一旦触发,程序就会崩溃,如果当前场景是比较严重的问题,适合使用assert;例如:服务器启动,加载数据发现数据丢失了;在进行金钱结算的时候出现的问题等等。
if:如果当前场景没那么严重的问题,就不应该使用assert,而应该使用if,来自行定义问题处理方式。
字符串函数
下面来介绍一些字符串操作函数,掌握了这些库函数,那么我们在操作字符串的时候,就不需要再写一些循环呀、函数呀什么的来操作字符串,这样会很方便我们写代码。要使用这些函数就要包含<string.h>头文件。
strlen()
size_t strlen ( const char * str );
字符串以 '\0' 作为结束标志,strlen函数返回的是在字符串中 '\0' 前面出现的字符个数(不包含 '\0' )。参数指向的字符串必须要以 '\0' 结束。
注意函数的返回值为size_t,是无符号的,这点不小心就很容易出错。而我们在实际开发的过程中,除非必要,尽量不要使用无符号类型,因为这会是个隐患,你不知道什么时候就会出错,所以慎用。
下面是strlen()函数的简单实现:
//计数器方式 int my_strlen(const char * str) { int count = 0; while(*str) { count++; str++; } return count; } //不能创建临时变量计数器 int my_strlen(const char * str) { if(*str == '\0') return 0; else return 1+my_strlen(str+1); } //指针-指针的方式 int my_strlen(char *s) { char *p = s; while(*p != ‘\0’ ) p++; return p-s; }
strcpy()
char* strcpy(char * destination, const char * source );
源字符串必须以 '\0' 结束,因为在将源字符串拷贝到目标字符串时,会将源字符串中的 '\0' 拷贝到目标空间,拷贝结束之后,目标字符串才是一个完整的c风格字符串;另外目标空间必须足够大,以确保能存放源字符串。
我们可以注意到,strcpy()的返回值是char*类型的,这样的返回类型是为了可以链式调用这个函数,格式如下:
strcpy(str1,strcpy(str2,"zhangsan"));
下面是strcpy()函数的简单实现:
//1.参数顺序 //2.函数的功能,停止条件 //3.assert //4.const修饰指针 //5.函数返回值 //6.题目出自《高质量C/C++编程》书籍最后的试题部分 char *my_strcpy(char *dest, const char*src) { char *ret = dest; assert(dest != NULL); assert(src != NULL); while((*dest++ = *src++)) { ; } return ret; }
strncpy()
char * strncpy ( char * destination, const char * source, size_t num );
使用规则:拷贝num个字符从源字符串到目标空间。如果源字符串的长度小于num,则拷贝完源字符串之后,在目标的后边追加0,直到num个。
下面是strncpy()函数的简单实现:
char* Strncpy(char* destination, const char* source, size_t num) { assert(destination != NULL); assert(source != NULL); size_t i = 0; while(i < num && source[i] != '\0') { destination[i] = source[i]; i++; } if(i == num) { return destination; } if(source[i] == '\0') { for(; i < num; i++) { destination[i] = '\0'; } return destination; } }
strcat()
char * strcat ( char * destination, const char * source );
源字符串必须以 '\0' 结束,因为在将源字符串追加到到目标字符串时,会将源字符串中的 '\0' 也追加到到目标空间,追加结束之后,目标字符串才是一个完整的c风格字符串;另外目标空间必须足够大,以确保能存放源字符串。
我们可以注意到,strcat()的返回值是char*类型的,这样的返回类型是为了可以链式调用这个函数,格式如下:
strcat(str1,strcat(str2,"zhangsan"));
下面是strcat()函数的简单实现:
char *my_strcat(char *dest, const char*src) { char *ret = dest; assert(dest != NULL); assert(src != NULL); while(*dest) { dest++; } while((*dest++ = *src++)) { ; } return ret; }
strncat()
char * strncat ( char * destination, const char * source, size_t num );
使用规则:追加num个字符从源字符串到目标空间。如果源字符串的长度小于num,则追加完源字符串之后,在目标的后边追加0,直到num个。
下面是strncat()函数的简单实现:
char* Strncat(char* destination, const char* source, size_t num) { assert(destination != NULL); assert(source != NULL); size_t i = 0; while(destination[i] != '\0') { i++; } size_t j = 0; while(j < num && source[j] != '\0') { destination[i] = source[j]; j++; i++; } destination[i] = '\0'; return destination; }
strcmp()
int strcmp ( const char * str1, const char * str2 );
这个是字符串比较函数,比较前后两个字符串的大小。比较字符串的时候则是通过两个字符串从前至后每个字符串字符的ASCII码的大小来进行比较的,比较到出现两个字符不一样或者一个字符串结束,然后返回一个整数结果。
标准规定:第一个字符串大于第二个字符串,则返回大于0的数字;第一个字符串等于第二个字符串,则返回0;第一个字符串小于第二个字符串,则返回小于0的数字。
下面是strcmp()函数的简单实现:
int my_strcmp (const char * src, const char * dst) { int ret = 0 ; assert(src != NULL); assert(dest != NULL); while( ! (ret = *(unsigned char *)src - *(unsigned char *)dst) && *dst) ++src, ++dst; if ( ret < 0 ) ret = -1 ; else if ( ret > 0 ) ret = 1 ; return( ret ); }
strncmp()
int strncmp ( const char * str1, const char * str2, size_t num );
比较到出现另个字符不一样或者一个字符串结束或者num个字符全部比较完。结果返回一个整数。
比较的结果和strcmp()一样,第一个字符串大于第二个字符串,则返回大于0的数字;第一个字符串等于第二个字符串,则返回0;第一个字符串小于第二个字符串,则返回小于0的数字。
下面是strncmp()函数的简单实现:
int Strncmp(const char* str1, const char* str2, size_t num) { assert(str1 != NULL); assert(str2 != NULL); size_t i = 0; while(str1[i] == str2[i] && i < num && str1[i] != '\0' && str2[i] != '\0') { i++; } if(i == num) { return 0; } if(str1[i] == '\0' && str2[i] == '\0') { return 0; } if(str1[i] > str2[i]) { return 1; } if(str1[i] < str2[i]) { return -1; } }
strstr()
char * strstr ( const char *, const char * );
这个函数的功能相比之前的就较为复杂,是在str1查找为str2的子串。这里的内容与数据结构略有相关。简单来说就是在第一个字符串中查找是否存在一串和第二个字符串完全一致的串,如果存在则返回第一次出现的首字符的指针,如果不存在就返回NULL。
我们可以注意到,strstr()的返回值是char*类型的,这样的返回类型是为了可以链式调用这个函数,格式如下:
strstr(str1,strstr(str2,str3));
下面是strstr()函数的简单实现:
char *my_strstr(const char* str1, const char* str2 ) { assert(str1); assert(str2); char *cp = (char*)str1; char *substr = (char *)str2; char *s1 = NULL; if(*str2 == '\0') return NULL; while(*cp) { s1 = cp; substr = str2; while(*s1 && *substr && (*s1 == *substr)) { s1++; substr++; } if(*substr == '\0') return cp; cp++; } }
strtok()
char * strtok ( char * str, const char * sep );
sep参数是个字符串,定义了用作分隔符的字符集合。第一个参数指定一个字符串,它包含了0个或者多个由sep字符串中一个或者多个分隔符分割的标记。strtok函数找到str中的下一个标记,并将其用 \0 结尾,返回一个指向这个标记的指针。(注:strtok函数会改变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容并且可修改。) strtok函数的第一个参数不为 NULL ,函数将找到str中第一个标记,strtok函数将保存它在字符串中的位置。 strtok函数的第一个参数为 NULL ,函数将在同一个字符串中被保存的位置开始,查找下一个标记。 如果字符串中不存在更多的标记,则返回 NULL 指针。
下面演示一下如何使用strtok()函数:
//第一种: #include <stdio.h> #include <string.h> int main () { char str[] ="- This, a sample string."; char * pch; printf ("Splitting string \"%s\" into tokens:\n",str); pch = strtok (str," ,.-"); while (pch != NULL) { printf ("%s\n",pch); pch = strtok (NULL, " ,.-"); } return 0; } //第二种: #include <stdio.h> int main() { char *p = "zhangpengwei@bitedu.tech"; const char* sep = ".@"; char arr[30]; char *str = NULL; strcpy(arr, p);//将数据拷贝一份,处理arr数组的内容 for(str=strtok(arr, sep); str != NULL; str=strtok(NULL, sep)) { printf("%s\n", str); } }
strerror()
char * strerror ( int errnum );
strerror()是错误信息报告函数,根据错误返回的错误码可以将其对应的错误信息打印。下面举个例子:
#include <stdio.h> #include <stdlib.h> #include <errno.h>//要使用错误码我们需要引入这个头文件 #include <string.h> int main() { FILE* file; file = fopen("1.txt", "r");//打开这个文件实际上我的目录下并没有这个文件因此就会产生错误, if(file == NULL) { printf("%s\n", strerror(errno));//打印最后一个错误码的错误信息 } } //输出:No such file or directory
打印的错误信息和我们编译错误信息十分相似,我们可以通过这种方式提醒用户哪里出现错误,但是目前这种报错方式十分落后。
字符串分类函数
下面这些函数,如果它的参数符合下列对应的条件,就返回真。使用时要包含<ctype.h>头文件。
iscntrl //任何控制字符 isspace //空白字符:空格‘ ’,换页‘\f’,换行'\n',回车‘\r’,制表符'\t'或者垂直制表符'\v' isdigit //十进制数字 0~9 isxdigit //十六进制数字,包括所有十进制数字,小写字母a~f,大写字母A~F islower //小写字母a~z isupper //大写字母A~Z isalpha //字母a~z或A~Z isalnum //字母或者数字,a~z,A~Z,0~9 ispunct //标点符号,任何不属于数字或者字母的图形字符(可打印) isgraph //任何图形字符 isprint //任何可打印字符,包括图形字符和空白字符
字符转换
int tolower ( int c ); int toupper ( int c );
C 库函数 int tolower(int c) 把给定的字母转换为小写字母。
#include <stdio.h> #include <ctype.h> int main() { int i = 0; char c; char str[] = "RUNOOB"; while( str[i] ) { putchar(tolower(str[i])); i++; } return(0); } //输出结果:runoob
C 库函数 int toupper(int c) 把给定的字母转换为大写字母。
#include <stdio.h> #include <ctype.h> int main() { int i = 0; char c; char str[] = "runoob"; while(str[i]) { putchar (toupper(str[i])); i++; } return(0); } //输出结果:RUNOOB
内存操作函数
下面来介绍一些内存操作相关的函数,不过需要注意的是,在C语言中要使用这些库函数,可并不是包含<memory.h>这个头文件(虽然存在这个头文件),而是还要包含<string.h>这个头文件,这是C语言中的一个bug,需要大家注意。
memcpy()
void * memcpy ( void * destination, const void * source, size_t num );
这个函数与strncpy()十分类似,不过此时复制的已经不仅仅只能是字符串了,而是任何类型的数据都可以进行复制;函数memcpy()从source的位置开始向后复制num个字节的数据到destination的内存位置;这个函数在遇到 '\0' 的时候并不会停下来;如果source和destination的内存空间有任何的重叠,复制的结果都是未定义的。
我们可以注意到,memcpy()的返回值是void*类型的,这样的返回类型是为了可以链式调用这个函数,格式如下:
memcpy(arr1,memcpy(arr2,arr3,num),num);
下面是memcpy()函数的简单实现:
void * memcpy ( void * dst, const void * src, size_t count) { void * ret = dst; assert(dst); assert(src); /* * copy from lower addresses to higher addresses */ while (count--) { *(char *)dst = *(char *)src; dst = (char *)dst + 1; src = (char *)src + 1; } return(ret); }
memmove()
void * memmove ( void * destination, const void * source, size_t num );
和memcpy的差别就是memmove函数处理的源内存块和目标内存块是可以重叠的。 如果源空间和目标空间出现重叠,就得使用memmove函数处理。
我们可以注意到,memmove()的返回值是void*类型的,这样的返回类型是为了可以链式调用这个函数,格式如下:
memmove(arr1,memmove(arr2,arr3,num),num);
下面是memmove()函数的简单实现:
void * memmove ( void * dst, const void * src, size_t count) { void * ret = dst; if (dst <= src || (char *)dst >= ((char *)src + count)) { /* * Non-Overlapping Buffers * copy from lower addresses to higher addresses */ while (count--) { *(char *)dst = *(char *)src; dst = (char *)dst + 1; src = (char *)src + 1; } } else { /* * Overlapping Buffers * copy from higher addresses to lower addresses */ dst = (char *)dst + count - 1; src = (char *)src + count - 1; while (count--) { *(char *)dst = *(char *)src; dst = (char *)dst - 1; src = (char *)src - 1; } } return(ret); }
memcmp()
int memcmp ( const void * ptr1,const void * ptr2,size_t num );
比较从ptr1和ptr2指针开始的num个字节,比较的结果和strcmp()一样,第一个字段大于第二个字段,则返回大于0的数字;第一个字段等于第二个字段,则返回0;第一个字段小于第二个字段,则返回小于0的数字。由于传入的参数并不一定是字符串,类型有很多种,所以比较大小的规则也是按照字典序来排列的,具体实现就不在这演示了。举个例子演示一下:
#include <stdio.h> #include <string.h> int main () { char buffer1[] = "DWgaOtP12df0"; char buffer2[] = "DWGAOTP12DF0"; int n; n=memcmp ( buffer1, buffer2, sizeof(buffer1) ); if (n>0) printf ("'%s' is greater than '%s'.\n",buffer1,buffer2); else if (n<0) printf ("'%s' is less than '%s'.\n",buffer1,buffer2); else printf ("'%s' is the same as '%s'.\n",buffer1,buffer2); return 0; }
memset()
void *memset(void *str, int c, size_t n)
C 库函数 void *memset(void *str, int c, size_t n) 复制字符 c(一个无符号字符)到参数 str 所指向的字符串的前 n 个字符。该值返回一个指向存储区 str 的指针。
这个函数也不做具体实现演示,简单举个例子:
#include <stdio.h> #include <string.h> int main () { char str[50]; strcpy(str,"This is string.h library function"); printf("%s\n",str); memset(str,'$',7); printf("%s\n",str); return(0); } //This is string.h library function //$$$$$$$ string.h library function
浙公网安备 33010602011771号