C语言中对字符和字符串的处理很是频繁,但是C语言本身是没有字符串类型的,字符串通常放在
常量字符串
中或者字符数组
中。
字符串常量
适用于那些对它不做修改的字符串函数
本章的主要内容为介绍常见字符串函数以及模拟实现
- strlen
- strcpy
- strcat
- strcmp
- strncpy
- strncmp
- strncat
- strstr
- strchr
- strrchr
- strtok
- strerror
int main()
{char str[] = "abc\0ab";printf("%zd\n", strlen(str));//统计'\0'前面的字符个数
}
1.计数器
size_t my_strlen(const char* pstr) {int cnt = 0;while (*pstr++) cnt++;return cnt; }
2.指针-指针
size_t my_strlen(const char* pstr) { assert(pstr); char* cur = pstr; while (*cur++);return cur - 1 -pstr; }
3.递归
size_t my_strlen(const char* pstr){assert(pstr);if (*pstr) return 1 + my_strlen(pstr + 1);else return 0; }
目标空间必须可变
pdest指向的是数据段上的字符串常量,字符串常量不可以修改,因此目标空间是不可变得,无法使用strcpy函数
目标空间必须足够大
dest得空间只有7个字节,但是src得空间有9个字节,拷贝时会提示非法访问dest周围的空间
char* my_strcpy(char* dst, const char* src)
{char* cur = dst;assert(dst && src);while (*dst++ = *src++);return cur;
}
原字符串必须以’\0’结束
strcpy函数需要将src数组结束符前所有字符包括结束符拼接到dest数组中,所以需要给src数组定义结束符
目标空间必须足够大
同strcpy,目标空间不足够容纳时会发生非法访问
目标空间必须可以修改
同strcpy,目标空间指向常量字符串时不可以使用strcat函数
不可以自己追加自己
当追加第一个dest元素’a’时,‘a’会覆盖dest的结束符’\0’,后续dest数组中不在存在结束符’\0’,因此strcat函数会重复追加a b c d e f再dest数组后面,始终没有结束符,最后会导致越界非法访问
在某些编译器上,可能使用strcat函数可以实现自己追加自己(如MSVC),但是标准C没有规定可以这么做,所以取决于编译器自己,这样的程序可能在不同编译器上的结果不同,可移植性不高
char* my_strcat(char* dest, const char* src)
{assert(dest && src);char* cur = dst;while (*dest++);dest--;while (*dest++ = *src++);return cur;
}
标准规定:
strcmp比较的是2个字符串的大小,不是字符串的长度
标准只规定str1>str2时返回>0的数,str1,并没有规定返回具体的数,所以再判断字符串大小时应当用函数的返回值是否>0或者<0带判断而不是返回值是否等于某个具体的数
int my_strcmp(const char* str1, const char* str2)
{assert(str1 && str2);while (*str1 && *str2){if (*str1 != *str2)return *str1 - *str2;else{str1++;str2++;}}return *str1 - *str2;
}
上面介绍的函数strcat,strcpy都有越界的风险,因此在很多情况下,认为它们是不安全的,我们下面来介绍几个相对来说更加安全的函数
num>src的大小,不足用’\0’补
num
char* my_strncpy(char* dest, const char* src, size_t num)
{assert(dest && src);char* cur = dest;size_t len = strlen(src);//num大于src的大小if (num > len){for (size_t i = 0; i < num && *src != 0; i++){*dest++ = *src++;}for (size_t i = 0; i < num - len; i++) *dest++ = '\0';}//num小于src的大小else{for (size_t i = 0; i < num; i++){*dest++ = *src++;}}return cur;
}
会在追加最后一个元素的后面加上结束符
src的个数不足num个,只追加src个
因为strncat会根据num的个数在最后追加的字符后面加上结束符,所以用strncat追加自己并不会因为dest数组中的结束符被覆盖而导致程序崩溃,自己追加自己时应使用strncat函数
char* my_strncat(char* dest, const char* src, size_t num)
{assert(dest && src && num > 0);char* cur = dest;while (*dest++);dest--;while (num-- && (*src)){*dest++ = *src++;}*dest = '\0';return cur;
}
函数和功能strcmp类似,只不过比较的是前num个元素
//strncmp example
//寻找具有相同的前缀
int main()
{char str[][5] = { "R2D2", "C3PO", "R2A6" };int n;puts("Looking for R2 astromech droids...");for (n = 0; n < 3; n++){if (strncmp(str[n], "R2xx", 2) == 0){printf("Found %s\n", str[n]);}}return 0;
}
int my_strncmp(const char* str1, const char* str2, size_t num)
{assert(str1 && str2);while (num--){if (*str1 == '\0' || *str2 == 0)return *str1 - *str2;else if (*str1 != *str2){return *str1 - *str2;}else{str1++;str2++;}}return *--str1 - *--str2;
}
strstr在C语言中是第二种形式,第一种形式是C++的重载
NULL
/* strstr example */
/*此示例在 str 中搜索“simple”子字符串并将该词替换为“sample”*/
int main()
{char str[] = "This is a simple string";char* pch;pch = strstr(str, "simple");if (pch != NULL)strncpy(pch, "sample", 6);puts(pch);return 0;
}
//方法1:借助strncmp
char* my_strstr(const char* str, const char* find)
{assert(str && find);while (*str){if (strncmp(str, find, strlen(find)) == 0){return str;}else str++;}return NULL;
}//方法2
char* my_strstr(const char* str, const char* find)
{assert(str && find);char* str1 = (char*)str;char* str2 = (char*)find;char* cur = (char*)str;while (*str1){//有可能匹配if (*str1 == *str2){cur = str1;//记录可能的返回位置while (*str1 == *str2){str1++;str2++;if (*(str2 + 1) == '\0')//结束符之前的位置匹配成功return cur;}//匹配失败str1 = cur + 1;//str1回到前面的位置str2 = find;//str2回到前面的位置}//没有可能匹配else{str1++;}}return NULL;
}
C语言中只支持第二种函数原型
/*strchr example*/
/*用来寻找字符串中所有s出现的位置*/
int main()
{char str[] = "This is a sample string";char* pch;printf("Looking for the 's' character in \"%s\"...\n", str);pch = strchr(str, 's');while (pch != NULL){printf("found at %d\n", pch - str + 1);pch = strchr(pch + 1, 's');}return 0;
}
char* my_strchr(const char* str, int character)
{while (*str){char* cur = str;if (*str == character){return cur;}str++;}if (character == '\0')return str;return NULL;
}
strrstr在C语言中是第二种形式
/* strrchr example */
//寻找字符最后一次出现的位置
int main()
{char str[] = "This is a sample string";char* pch;pch = strrchr(str, 's');printf("Last occurence of 's' found at %d \n", pch - str + 1);return 0;
}
char* my_strrchr(const char* str, int character)
{int len = strlen(str);char* cur = str + len;while (cur - len >= 0){if (*cur == character)return cur;cur--;}return NULL;
}
delimiter
参数是个字符串,定义了用作分隔符的字符集合delimiter
字符串中一个或者多个分隔符分割的标记。第一个标记
,strtok函数将保存它在字符串中的位置。 NULL
指针。函数较复杂,这里不再模拟,以后有机会的话在出文章专门实现strtok
errno
一样strerror
产生的错误字符串可能特定于每个系统和库实现。errno
是定义在头文件errno.h中的变量,用于接收库函数的错误码errno
的值在程序开始时默认为0(因此错误码为0代表没有错误),后续由库函数修改errno
的值总是最后一个错误码的值/* strerror example : error list */
int main ()
{FILE * pFile;pFile = fopen ("unexist.ent","r");if (pFile == NULL)printf ("Error opening file unexist.ent: %s\n",strerror(errno));return 0;
}
错误的原因是当前路径地下没有对应的文件,因此库函数fopen打开文件失败,此时会生成一个错误码,错误码的值赋给
errno
,这是系统完成的,然后通过strerror
将errno
的值解读为错误的原因
可以认为perror
是printf
函数加上strerror
函数