重點介紹處理字符和字符串的庫函數的使用和注意事項。C語言中對字符和字符串的處理很是頻繁,但是C語言本身是沒有字符串類型的,字符串通常放在常量字符串中或者字符數組中。字符串常量適用于那些對它不做修改的字符串函數。
一、求字符串長度?strlen?
- 字符串以?'\0' 作為結束標志,strlen?函數返回的是在字符串中 '\0' 前面出現的字符個數(不包含 '\0' )。
- 參數指向的字符串必須要以 '\0' 結束。
- (易錯點):注意函數的返回值為?size_t,是無符號(unsigned)的。
strlen - C++ Reference (cplusplus.com)
?【模擬實現】(三種不同方法)
#include <stdio.h>// 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;
}
二、長度不受限制的字符串函數
1、strcpy
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 <stdio.h>
#include <assert.h>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;
}
2、strcat
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 <stdio.h>
#include <assert.h>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;
}
3、strcmp
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 <stdio.h>
#include <assert.h>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");
}
三、長度受限制的字符串函數介紹
1、strncpy
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 <stdio.h>
#include <assert.h>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;
}
2、strncat
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 <stdio.h>
#include <assert.h>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;
}
3、strncmp
- 比較到出現另個字符不一樣或者一個字符串結束或者 count?個字符全部比較完。
strncmp - C++ Reference (cplusplus.com)
【代碼演示】?
#include <stdio.h>
#include <string.h>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;
}
四、字符串查找
1、strstr
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 <stdio.h>
#include <assert.h>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;
}
2、strtok
- sep(delimit)?參數是個字符串,定義了用作分隔符的字符集合。
- 第一個參數指定一個字符串,它包含了?0?個或者多個由?sep?字符串中一個或者多個分隔符分割的標記。
- strtok?函數找到?str?中的下一個標記,并將其用 '\0'?結尾,返回一個指向這個標記的指針。(注:strtok?函數會改變被操作的字符串,所以在使用 strtok?函數切分的字符串一般都是臨時拷貝的內容并且可修改。)
- strtok?函數的第一個參數不為 NULL ,函數將找到?str?中第一個標記,strtok?函數將保存它在字符串中的位置。
- strtok?函數的第一個參數為 NULL ,函數將在同一個字符串中被保存的位置開始,查找下一個標記。
- 如果字符串中不存在更多的標記,則返回 NULL 指針。
strtok - C++ Reference (cplusplus.com)
? 注意:strtok 會破壞原字符串,分割后原字符串保留第一個分割符前的字符。?
【代碼演示】
#include <stdio.h>
#include <string.h>int main()
{char arr[] = "3031899646@qq.com";printf("原字符串: %s\n", arr);const char* sep = "@."; // 創建sepchar 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;
}
五、錯誤信息報告?strerror
- 返回錯誤碼,所對應的錯誤信息。
- errno 是一個全局的錯誤碼變量。當 C 語言的庫函數在執行過程中,發生了錯誤后就會把對應的錯誤碼賦值到errno中。
strerror - C++ Reference (cplusplus.com)
?【模擬實現】?
#include <stdio.h>
#include <string.h>
#include <errno.h>// 錯誤碼 錯誤信息
// 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;
}
六、字符操作
1、字符分類函數
?【代碼演示】
#include <stdio.h>
#include <ctype.h>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 頭文件。?
2、字符轉換
int tolower ( int c );
int toupper ( int c );
?【代碼演示】?
#include <stdio.h>int main()
{char ch = tolower('Q'); // 大寫轉小寫putchar(ch);return 0;
}
七、內存操作函數
1、memcpy
?
- 函數?memcpy?從?src?的位置開始向后復制 count?個字節的數據到?dest?的內存位置。
- 這個函數在遇到 '\0' 的時候并不會停下來。
- 如果?src?和?dest?有任何的重疊,復制的結果都是未定義的。
?memcpy - C++ Reference (cplusplus.com)
?【模擬實現】?
#include <stdio.h>
#include <assert.h>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 <stdio.h>
#include <string.h>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;
}
?
2、memmove
- 和?memcpy?的差別就是?memmove?函數處理的源內存塊和目標內存塊是可以重疊的。
- 如果源空間和目標空間出現重疊,就得使用?memmove?函數處理。
C語言標準要求:
memcpy 用來處理不重疊的內存拷貝,而 memmove 用來處理重疊內存的拷貝。
memmove - C++ Reference (cplusplus.com)
?【模擬實現】
#include <stdio.h>
#include <assert.h>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;
}
3、memset
?
- memset 是以字節為單位設置內存的。
memset - C++ Reference (cplusplus.com)
?【代碼實現】?
#include <stdio.h>
#include <string.h>int main()
{int arr[10] = { 0 };memset(arr, 1, 20); // 將前20個字節全部設置為1return 0;
}
4、memcmp
- 比較從 buf1?和 buf2?指針開始的 count?個字節。
? 注意:memcmp 不同于 strcmp,memcmp 遇到 '\0' 不會停止比較。
memcmp - C++ Reference (cplusplus.com)
?【代碼演示】
#include <stdio.h>
#include <string.h>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;
}