目录
前言:
函数介绍:
1.1 strlen
1.2 strcpy
1.3 strcat
1.4 strcmp
1.5 strstr
1.6 strtok
1.7 strerror
1.8 perror
2. 字符分类函数
2.1 memcpy
2.2 memmove
C语言中,字符串函数和字符函数的使用是很频繁的,如果我们能够熟练使用,能够帮助我们解决很多的字符问题。
格式:strlen( const char* str )
字符串以 ‘\0’ 作为结束标志,返回的是 ‘\0’ 前面出现的字符个数。(不包括 ‘\0’)
参数指向的字符串必须要以 ‘\0’ 结尾,不然结果就是未知的了。
函数的返回值是 size_t 类型,是无符号的。
#include
#include int main() { char arr1[] = "abcdefg"; printf("%d\n", strlen(arr1)); return 0; }
1、局部变量统计
size_t my_strlen(char* str) { int count = 0; while (*str != '\0') { count++; str++; } return count; }
每走过一个不是 ‘\0’ 的字符,局部变量就+1,最后返回这个值。
2、递归调用
size_t my_strlen(const char* str) { if (*str != '\0') { return 1 + my_strlen(str + 1); } else { return 0; } }
如果不是 ‘\0’ ,那么返回1+my_strlen。否则,就返回0。
3、指针法
size_t my_strlen(const char* str) { const char* temp = str; while (*str != '\0') { str++; } return (str - temp); }
用一个局部变量记录指针初始位置,如果不是遇到 ‘\0’ 指针就一直往后走,最后返回指针-局部变量。(两个指针相减得到之间相差元素个数)
这段代码的结果就是,hehe,因为返回值是无符号整形,而strlen(p1)-strlen(p2)得到的值是负数,自动转为非负的数,最终得到一个正整数。所以还是会打印hehe。所以我们应该避免相减为负数的情况,直接改成 strlen(p2) > strlen(p2) 就好了。
格式:strcpy( char* dest,const char* src )
源字符串必须以 ‘\0’ 结束。
会将源字符串的 ‘\0’ 拷贝到目标空间。
目标空间必须足够大,确保能够放得下源字符串。
目标空间必须可修改。
如果是 char* p=“abcdef”;这样是不可修改的,因为指针指向的是常量字符串
使用:
#include
#include int main() { char arr1[20] = "ABCDEFG"; char arr2[30]; strcpy(arr2, arr1); printf(arr2); return 0; }
char* my_strcpy(char* dest, const char* src) { assert(dest); assert(src); char* temp = dest; while (*dest++ = *src++) { ; } return temp; }
断言assert判断传入指针非空,创建一个临时变量记录木目的空间首地址,然后把src指向的空间中的内容赋值给dest,然后++。使用断言需要包含头文件
注意:由于最后遇到 ‘\0’ 了,它的ASCII码值是0,所以会退出循环。
格式:strcat( char* dest,const char* src )
源字符串必须以 ‘\0’ 结束。
目标空间足够大,能够容纳下源字符串的内容。
目标空间必须可以修改。
字符串是不可以给自己追加的,不然陷入死循环。
#include
#include int main() { char arr1[20] = "hello "; char arr2[20] = "wrold"; strcat(arr1, arr2); printf("%s", arr1); return 0; }
char* my_strcat(char* dest, const char* src) { assert(dest); assert(src); char* temp = dest; while (*dest) { dest++; } while (*dest++ = *src++) { ; } return temp; }
由于不需要对src进行修改,所以加上const修饰,增加代码健壮性。第一个while循环能让指针找到结尾 ‘\0’ 处,然后第二个循环把每一个src处的字符赋值给dest。最后返回dest的首元素地址。
格式:strcmp( const char* str1,const char* str2 )
第一个字符串大于第二个字符串,则返回大于0的数字。
第一个字符串等于第二个字符串,则返回0。
第一个字符串小于第二个字符串,则返回小于0的数字。
判断两个字符是靠他们的ASCII码值来比较的,所以小写字母大于大写字母。
#include
#include int main() { char arr1[20] = "hello "; char arr2[20] = "Hello"; int ret = strcmp(arr1,arr2); printf("%d\n", ret); return 0; }
int my_strcmp(const char* s1, const char* s2) { assert(s1); assert(s2); while (*s1 == *s2) { //让s1和s2比完了就停下来 if (*s1 == '\0') { return 0; } s1++; s2++; } //直接返回他们差值 return *s1 - *s2; }
第一个while循环让两个指针指针指向的字符串逐个字符比较。如果其中*s1= ‘\0’,由于进入循环是要两个比较的字符相等,说明两个都是结尾了,两个字符串比较到结尾说明他们相等,所以return 0。如果退出循环了,就返回 *s1 - *s2,如果小于返回的值就是负数,如果大于返回的值就是正数。
格式:strstr( const char* str1,const char* str2 )
用于查找子集。
第二个字符串如果是第一个字符串的子集,那么返回第二个字符串在第一个字符串中的首元素地址。否则,返回空指针。
#include
#include int main() { char arr1[20] = "helio world"; char arr2[20] = "world"; char* p = strstr(arr1, arr2); printf("%s\n", p); return 0; }
//s2在s1中查找,cp代表每次要查找的起始位置,避免了部分符合回不去的情况. char* my_strstr(const char* str1, const char* str2) { //断言 assert(str1); assert(str2); const char* s1 = str1; const char* s2 = str2; const char* cp = str1; //如果传过来一个空集,一定是子集. if (*str2 == '\0') { return (char*)str1; } while (*cp) { //每次回到有效起始位置 s1 = cp; s2 = str2; //两个都不是末尾,并且相等的时候就都++ while (*s1 && *s2 && *s1 == *s2) { s1++; s2++; } //如果s2到底了,说明查完了 if (*s2 == '\0') { return (char*)cp; } cp++; } //如果都不符合,说明没有查到,返回空指针 return NULL; }
创建指针s1、s2、cp,其中s1和s2用来比较是否字符相等,cp来记录从这一个字符开始的对比是否符合,因为其中可能出现部分符合的情况。
先if判断 *str2 是否为空,如果为空,直接返回str1的首元素地址。
s1每次都回到cp的位置,s2回到第二个字符串首元素位置。然后进入比较,如果两个都不为空并且相等的话,那么s1++,s2++。如果*s2 == ‘\0’ ,那么说明s2比到头了,说明是子集,所以返回cp指向的位置。否则,就是不相等的情况,那么cp++,从下一个元素开始比较。最后如果都找不到,说明不是子集,返回NULL。
#include
#include int main() { char arr1[] = "abc.def@ghijk"; char arr2[] = "@."; char temp[30] = { 0 }; strcpy(temp, arr1); char* p = NULL; for (p = strtok(temp, arr2); p != NULL; p = strtok(NULL, arr2)) { printf("%s\n", p); } return 0; } 结果:abc
def
hijk
这个函数由于会修改第一个参数,所以一般是在临时变量中进行。for循环进入的时候调用一次,传参拷贝过后的temp和arr2,用一个指针p来接收。判断条件为返回的指针 p!=NULL,最后每次执行完都会继续调用,只不过第一个参数改为NULL。
这样的做法比一次一次调用要效率高。
格式: char *strerror( int errnum )
返回错误码所对应的信息
#include
需要手动打印错误信息
格式:perror( const char *string )
相比于strerror,这个更为有效,能够自动打印错误信息。
#include
int main() { FILE* pf = fopen("test.txt", "r"); if (pf == NULL) { perror("打印错误信息"); } else { printf("打印成功\n"); fclose(pf); pf = NULL; } return 0; }
使用需要包含头文件
小写转换大写
#include
#include int main() { char ch = 'a'; if (isalpha(ch)) { printf("%c\n", toupper(ch)); } return 0; } 打印结果:A
格式:memcpy ( void * destination, const void * source, size_t num )
函数memcpy从source的位置开始向后赋值num个字节的数据到destination的内存位置。
这个函数在遇到 ‘\0’ 的时候不会停下来。
如果 source 和 destination 有任何的重叠,复制的结果都是未定义的。
size_t num 是总字节大小,如果是用其他类型的算,就要折算成字节大小。
使用:
#include
#include / int main() { int arr1[20] = { 1,2,3,4,5,6,7,8,9,10 }; int arr2[20] = { 0 }; memcpy(arr1, arr2, 10 * sizeof(int)); for (int i = 0; i < 10; i++) { printf("%d ", arr1[i]); } return 0; } 由于计算的是 int 类型的,所以大小要乘上整形的大小4。
结果:0 0 0 0 0 0 0 0 0 0
void* my_memcpy(void* dest, void* src, size_t count) { assert(dest); assert(src); void* ret = dest; while (count--) { *(char*)dest = *(char*)src; ++(char*)dest; ++(char*)src; } return ret; }
先断言判断非空指针。由于dest和src都是无类型的指针,解引用需要先强制类型转换为char*类型的,因为这个函数要做到所有类型都是用,只能采取最小的计量值1个字节,所以就是char型的。循环中每次把一个字节的值赋值,然后++,这样就把每一个字节的内容都拷贝过去了。
格式:memmove( void *dest, const void *src, size_t count )
#include
int main() { int arr[20] = { 1,2,3,4,5,6,7,8,9,10 }; memmove(arr, arr+1, 4); for (int i = 0; i < 10; i++) { printf("%d ", arr[i]); } return 0; } 结果: 2 2 3 4 5 6 7 8 9 10
void* my_memmove(void* dest, void* src, size_t count) { assert(dest); assert(src); if (dest < src) { //从前向后拷贝 while (count--) { *(char*)dest = *(char*)src; dest = (char*)dest + 1; src = (char*)src + 1; } } else { //从后向前拷贝 while (count--) { *((char*)dest + count) = *((char*)src + count); } } }
这其中涉及到了目标空间在前面还是后面的问题: 如果在前面并且和源头空间有重叠,那么就有可能互相影响导致结果不正确。例如abcdefg,把bcd移动到abc的位置,如果从向前移动,那么c移动到b这里的时候b就改变了,本来要把b移到a就变成了c移动到a。
所以我们的思路应该是:如果目标空间在前面,那么从前往后移动。其他的都从后往前移动。(从前往后指的是从头部移还是先从尾部移)
今天的文章就到此结束啦!感谢各位的观看!
五一快乐!