重点介绍处理字符和字符串的库函数的使用和注意事项。C语言中对字符和字符串的处理很是频繁,但是C语言本身是没有字符串类型的,字符串通常放在常量字符串中或者字符数组中。字符串常量适用于那些对它不做修改的字符串函数。
- 字符串以 '\0' 作为结束标志,strlen 函数返回的是在字符串中 '\0' 前面出现的字符个数(不包含 '\0' )。
- 参数指向的字符串必须要以 '\0' 结束。
- (易错点):注意函数的返回值为 size_t,是无符号(unsigned)的。
strlen - C++ Reference (cplusplus.com)
#include
// 1、计数器方式
size_t my_strlen(const char* str)
{
size_t count = 0;
while (*str)
{
count++;
str++;
}
return count;
}
// 2、不能创建临时变量计数器
size_t my_strlen(const char* str)
{
if(*str == '\0')
{
return 0;
}
else
{
return 1 + my_strlen(str+1);
}
}
// 3、指针-指针的方式
size_t my_strlen(char* str)
{
char *p = str;
while(*p != '\0' )
{
p++;
}
return p-str;
}
int main()
{
char arr[] = "hello world";
size_t count = my_strlen(arr);
printf("%zu\n", count);
return 0;
}
Copies the C string pointed by source into the array pointed by destination, including the terminating null character (and stopping at that point).
- 源字符串 src 必须以 '\0' 结束。
- 会将源字符串 src 中的 '\0' 拷贝到目标空间 dest。
- 目标空间必须足够大,以确保能存放源字符串。
- 目标空间必须可变,即目标空间 dest 不可以被 const 声明。
strcpy - C++ Reference (cplusplus.com)
#include
#include
char* my_strcpy(char* str2, const char* str1)
{
assert(str1 && str2);
char* ret = str2;
while (*str2++ = *str1++)
{
;
}
return ret;
}
int main()
{
char s1[] = "hello world";
char s2[20] = { 0 };
char* ret = my_strcpy(s2, s1);
printf("%s\n", ret);
return 0;
}
Appends a copy of the source string to the destination string. The terminating null character in destination is overwritten by the fifirst character of source, and a null-character is included at the end of the new string formed by the concatenation of both in destination.
- 源字符串 src 必须以 '\0' 结束。
- 将源字符串 src 中的 '\0' 一同拷贝到目标空间 dest ,并删除 *dest 原来末尾的 '\0'。
- 目标空间必须有足够大,能容纳下源字符串的内容。
- 目标空间必须可修改,即目标空间 dest 不可以被 const 声明。
strcat - C++ Reference (cplusplus.com)
字符串自己给自己追加,会如何?
当使用 strcat 函数将一个字符串追加到自身时,可能会导致未定义的行为。strcat 函数的工作原理是在源字符串的结尾处追加目标字符串的内容,并在结尾加上空字符 '\0'。当源字符串和目标字符串是同一个字符串时,追加操作会导致源字符串的内容被破坏,因为在追加过程中,源字符串的内容会被覆盖,最终结果会是一个不可预测的字符串。因为根据不同的编译器和库的版本,strcat 函数在某些情况下可能不会导致未定义行为。但是将一个字符串追加到自身仍然是一个不好的编程实践,因为它容易引发错误和混乱。
因此,不推荐使用 strcat 函数将字符串追加到自身。如果需要将一个字符串复制到另一个字符串末尾,可以使用 strcpy 函数进行复制操作。
#include
#include
char* my_strcat(char* dest, const char* src)
{
char* tmp = dest;
assert(dest && src);
while (*dest)
{
dest++;
}
while (*dest = *src)
{
dest++;
src++;
}
return tmp;
}
int main()
{
char s1[20] = "hello";
char s2[] = " world";
char* ret = my_strcat(s1, s2);
printf("%s\n", ret);
return 0;
}
This function starts comparing the fifirst character of each string. If they are equal to each other, it continues with the following pairs until the characters diffffer or until a terminating null-character is reached.
⚪标准规定:
- 第一个字符串大于第二个字符串,则返回大于 0 的数字。
- 第一个字符串等于第二个字符串,则返回 0。
- 第一个字符串小于第二个字符串,则返回小于 0 的数字。
strcmp - C++ Reference (cplusplus.com)
那么如何判断两个字符串?
返回值只需要满足要求即可,比如大于 0 的数字,不一定是1,只需满足条件即可。strcmp 函数的比较是基于字符的 ASCII 码进行的。它从两个字符串的第一个字符开始逐个比较,直至找到不相等的字符或者其中一个字符串的结束符 '\0'。在比较的时候,它会将两个字符的 ASCII 码进行减法运算,返回结果作为比较的结果。
需要注意的是,strcmp 函数是区分大小写的。也就是说,大写字母和小写字母被认为是不同的字符。如果需要不区分大小写的字符串比较,可以使用 strcasecmp 函数(在某些编程环境中可能被称为_stricmp)。
#include
#include
int my_strcmp(const char* str1, const char* str2)
{
assert(str1 && str2);
while (*str1 == *str2)
{
if (*str1 == '\0')
{
return 0;
}
str1++;
str2++;
}
return (*str1 - *str2);
}
int main()
{
char s1[] = "abcdef";
char s2[] = "abcq";
int ret = my_strcmp(s1, s2);
if (ret > 0)
{
printf(">\n");
}
else if (ret == 0)
{
printf("=\n");
}
else
{
printf("<\n");
}
return 0;
}
⚠ 注意:根据编译器的不同,返回的结果也不同。
在 VS2019 中,大于返回 1,等于返回 0,小于返回 -1。但在 Linux-gcc 中,大于返回正数,等于返回0,小于返回负数。
// 推荐
if(strcmp(p1, p2) > 0)
{
printf("p1 > p2");
}
else if(strcmp(p1, p2 == 0))
{
printf("p1 == p2");
}
else if(strcmp(p1, p2) < -1)
{
printf("p1 < p2");
}
// 不推荐
if(strcmp(p1, p2) == 1)
{
printf("p1 > p2");
}
else if(strcmp(p1, p2 == 0))
{
printf("p1 == p2");
} else if(strcmp(p1, p2) == -1)
{
printf("p1 < p2");
}
Copies the fifirst num characters of source to destination. If the end of the source C string (which is signaled by a null-character) is found before num characters have been copied, destination is padded with zeros until a total of num characters have been written to it.
- 拷贝 count 个字符从源字符串到目标空间。
- 如果源字符串的长度小于 count,则拷贝完源字符串之后,在目标的后边追加 0,直到 count 个。
dest 和 src 不应该重叠(重叠时可以用更安全的 memmove 替代)。
目标空间 dest 必须足够大,以确保能够存放源字符串。
目标空间 dest 必须可变,即目标空间 dest 不可以被 const 声明。
strncpy - C++ Reference (cplusplus.com)
#include
#include
char* my_strncpy(char* dest, const char* src, size_t count)
{
assert(dest && src);
char* cur = dest;
while (count--)
{
if ((*dest++ = *src++) == '\0')
{
break;
}
}
if (count != 0)
{
while (count--)
{
*dest++ = '\0';
}
}
return cur;
}
int main()
{
char s1[20] = { 0 };
char s2[] = "hello world";
int sz = sizeof(s2) / sizeof(s2[0]);
printf("%s\n", my_strncpy(s1, s2, sz));
return 0;
}
Appends the fifirst num characters of source to destination, plus a terminating null-character. If the length of the C string in source is less than num, only the content up to the terminating null-character is copied.
- 如果源字符串的长度小于 count,则只复制 '\0' 之前的内容。
strncat - C++ Reference (cplusplus.com)
#include
#include
char* my_strncat(char* dest, const char* src, size_t count)
{
assert(dest && src);
char* cur = dest;
while (*dest)
{
dest++;
}
while (count--)
{
if ((*dest++ = *src++) == '\0')
{
return cur;
}
}
*dest = '\0';
return cur;
}
int main()
{
char s1[20] = "hello";
char s2[] = " world";
size_t sz = sizeof(s2) / sizeof(s2[0]);
printf("%s\n", my_strncat(s1, s2, sz)); // 从s2中取sz个追加到s1中
return 0;
}
- 比较到出现另个字符不一样或者一个字符串结束或者 count 个字符全部比较完。
strncmp - C++ Reference (cplusplus.com)
#include
#include
int main()
{
const char* p1 = "abczdef";
const char* p2 = "abcqwer";
int ret1 = strncmp(p1, p2, 1);
int ret2 = strncmp(p1, p2, 4);
printf("%d %d\n", ret1, ret2);
return 0;
}
Returns a pointer to the fifirst occurrence of str2 in str1, or a null pointer if str2 is not part of str1.
- 返回字符串中首次出现子串的地址。若 str2 是 str1 的子串,则返回 str2 在 str1 中首次出现的地址。如果 str2 不是 str1 的子串,则返回 NULL 。
strstr - C++ Reference (cplusplus.com)
#include
#include
char* my_strstr(const char* str1, const char* str2)
{
assert(str1 && str2);
if (*str2 == '\0')
{
return (char*)str1;
}
char* cur = (char*)str1;
char* s1, *s2;
while (*cur != '\0')
{
s1 = cur;
s2 = (char*)str2;
while (*s1 && *s2 && (*s1 == *s2))
{
s1++;
s2++;
}
if (*s2 == '\0')
{
return cur;
}
cur++;
}
return NULL;
}
int main()
{
char s1[] = "abbcde";
char s2[] = "bcd";
char s3[] = "abcd";
char* ret1 = my_strstr(s1, s2);
char* ret2 = my_strstr(s1, s3);
if (ret1 == NULL)
{
printf("未找到匹配的子串!\n");
}
else
{
printf("%s\n", ret1);
}
if (ret2 == NULL)
{
printf("未找到匹配的子串!\n");
}
else
{
printf("%s\n", ret2);
}
return 0;
}
- sep(delimit) 参数是个字符串,定义了用作分隔符的字符集合。
- 第一个参数指定一个字符串,它包含了 0 个或者多个由 sep 字符串中一个或者多个分隔符分割的标记。
- strtok 函数找到 str 中的下一个标记,并将其用 '\0' 结尾,返回一个指向这个标记的指针。(注:strtok 函数会改变被操作的字符串,所以在使用 strtok 函数切分的字符串一般都是临时拷贝的内容并且可修改。)
- strtok 函数的第一个参数不为 NULL ,函数将找到 str 中第一个标记,strtok 函数将保存它在字符串中的位置。
- strtok 函数的第一个参数为 NULL ,函数将在同一个字符串中被保存的位置开始,查找下一个标记。
- 如果字符串中不存在更多的标记,则返回 NULL 指针。
strtok - C++ Reference (cplusplus.com)
⚠ 注意:strtok 会破坏原字符串,分割后原字符串保留第一个分割符前的字符。
#include
#include
int main()
{
char arr[] = "[email protected]";
printf("原字符串: %s\n", arr);
const char* sep = "@."; // 创建sep
char arr1[30];
char* ret = NULL;
strcpy(arr1, arr); // 将数据拷贝一份,保留arr数组的内容
// 分行打印切割内容
for (ret = strtok(arr, sep); ret != NULL; ret = strtok(NULL, sep))
{
printf("%s\n", ret);
}
printf("分割后原字符串被破坏: %s\n", arr); // 分割后原字符串保留第一个分割符前的字符
return 0;
}
- 返回错误码,所对应的错误信息。
- errno 是一个全局的错误码变量。当 C 语言的库函数在执行过程中,发生了错误后就会把对应的错误码赋值到errno中。
strerror - C++ Reference (cplusplus.com)
#include
#include
#include
// 错误码 错误信息
// 0 - No error
// 1 - Operation not permitted
// 2 - No such file or directory
//
int main()
{
char* str = strerror(errno);
printf("%s\n", str);
return 0;
}
#include
#include
int main()
{
char ch1 = 'a';
int ret = islower(ch1); // 判断ch1是否为小写 -- 非0为真
printf("%d\n", ret);
char ch2 = 'B';
int res = islower(ch2); // 判断ch2是否为小写 -- 0为假
printf("%d\n", res);
return 0;
}
⚠ 注意:需引入头文件 ctype.h 头文件。
int tolower ( int c );
int toupper ( int c );
#include
int main()
{
char ch = tolower('Q'); // 大写转小写
putchar(ch);
return 0;
}
- 函数 memcpy 从 src 的位置开始向后复制 count 个字节的数据到 dest 的内存位置。
- 这个函数在遇到 '\0' 的时候并不会停下来。
- 如果 src 和 dest 有任何的重叠,复制的结果都是未定义的。
memcpy - C++ Reference (cplusplus.com)
#include
#include
void* my_memcpy(void* dest, const void* src, size_t count)
{
assert(dest && src);
void* ret = dest;
while (count--)
{
*(char*)dest = *(char*)src;
dest = (char*)dest + 1;
src = (char*)src + 1;
}
return ret;
}
int main()
{
char s1[] = "abcdefgh";
char s2[20] = "xxxxxxxxxx";
my_memcpy(s2, s1, 5);
printf("%s\n", s2);
return 0;
}
// 拷贝结构体
#include
#include
struct S
{
char name[20];
int age;
};
int main()
{
struct S arr3[] = { {"张三", 20}, {"李四", 30} };
struct S arr4[3] = { 0 };
memcpy(arr4, arr3, sizeof(arr3));
return 0;
}
- 和 memcpy 的差别就是 memmove 函数处理的源内存块和目标内存块是可以重叠的。
- 如果源空间和目标空间出现重叠,就得使用 memmove 函数处理。
C语言标准要求:
memcpy 用来处理不重叠的内存拷贝,而 memmove 用来处理重叠内存的拷贝。
memmove - C++ Reference (cplusplus.com)
#include
#include
void* my_memmove(void* dest, const void* src, size_t count)
{
assert(dest && src);
void* ret = dest;
//从前->后
if (dest <= src)
{
while (count--)
{
*(char*)dest = *(char*)src;
dest = (char*)dest + 1;
src = (char*)src + 1;
}
}
//从后->前
else
{
dest = (char*)dest + count - 1;
src = (char*)src + count - 1;
while (count--)
{
*(char*)dest = *(char*)src;
dest = (char*)dest - 1;
src = (char*)src - 1;
}
}
return ret;
}
int main()
{
char arr[] = "abcdefgh";
my_memmove(arr, arr + 3, 2);
printf("%s\n", arr);
return 0;
}
- memset 是以字节为单位设置内存的。
memset - C++ Reference (cplusplus.com)
#include
#include
int main()
{
int arr[10] = { 0 };
memset(arr, 1, 20); // 将前20个字节全部设置为1
return 0;
}
- 比较从 buf1 和 buf2 指针开始的 count 个字节。
⚠ 注意:memcmp 不同于 strcmp,memcmp 遇到 '\0' 不会停止比较。
memcmp - C++ Reference (cplusplus.com)
#include
#include
int main()
{
float arr1[] = { 1.0, 2.0, 3.0, 4.0 };
float arr2[] = { 1.0, 3.0 };
int ret = memcmp(arr1, arr2, 8); // arr1是否比arr2大,比较8个字节
printf("%d\n", ret);
return 0;
}