文章目錄
- 🍳 前言
- 一、求字串長度
- 💦 strlen
- 二、長度不受限制的字串函式
- 💦 strcpy
- 💦 strcat
- 💦 strcmp
- 三、長度受限制的字串函式
- 💦 strncpy
- 💦 strncat
- 💦 strncmp
- 四、字串查找
- 💦 strstr
- 💦 strtok
- 五、錯誤資訊報告
- 💦 strerror
- 💦 perror
- 六、字符操作函式
- 1、字符分類函式
- 💦 isdigit
- 💦 islower
- 2、字符轉換函式
- 💦 tolower
- 七、記憶體操作函式
- 💦 memcpy
- 💦 memmove
- 💦 memset
- 💦 memcmp
- 八、函式的模擬實作
- 💦 strlen
- 1、計數器的版本(需要臨時變數)
- 2、遞回版本(不使用臨時變數)
- 3、指標版本(指標-指標)
- 💦 strcpy
- 💦 strcat
- 💦 strstr
- 💦 strcmp
- 💦 memcpy
- 💦 memmove
🍳 前言
C語言對于字符和字串的處理是很頻繁的,但是C語言本身沒有字串型別,字串通常放在常量字串或者字符陣列中,
一、求字串長度
💦 strlen
🔗 函式原型和頭:

🔗 函式的回傳值:

🔗 功能:

#include<stdio.h>
#include<string.h>
int main()
{
char arr1[] = "abc";
char arr2[] = { 'a', 'b', 'c' };
printf("arr1的長度是:%d\n", strlen(arr1));
printf("arr2的長度是:%d\n", strlen(arr2));
return 0;
}
? 輸出結果:

📝 分析結果:
??? “abc” 這種型別的字串有4個元素 - > ‘a’ ‘b’ ‘c’ ‘\0’
??這里說明了strlen這個函式在求字串長度時計算的是 ‘\0’ 之前的字符,且以 ‘\0’ 為字串的結束標志
??? { ‘a’, ‘b’, ‘c’ } 這種型別的字串只有3個元素 -> ‘a’, ‘b’, ‘c’
??這里的結果是一個亂數,因為字串里并沒有 ‘\0’ 作為結束標志,所以它會繼續往下數
? 觀察以下代碼,輸出的結果是什么
#include<string.h>
#include<stdio.h>
int main()
{
if(strlen("abc") - strlen("abcdef") > 0)
{
printf(">\n");
}
else
{
printf("<\n");
}
return 0;
}
? 輸出結果:

📝 分析結果:

strlen函式的回傳值型別無符號的
💨 總結:
? 字串以 ‘\0’ 作為結束標志,strlen函式的回傳值是在字串中 ‘\0’ 前面出現的字符個數(不包含 ‘\0’ )
? 引數指向的字符必須要以 ‘\0’ 結束
? 注意函式的回傳值為size_t,是無符號的(易錯)
二、長度不受限制的字串函式
💦 strcpy
🔗 函式原型和頭:

🔗 函式的回傳值:

🔗 功能:

#include<stdio.h>
int main()
{
char arr[20] = { 0 };
strcpy(arr, "hello");
printf("%s\n", arr);
return 0;
}
? 輸出結果:hello
📝 分析結果:strcpy會把源字串中的內容拷貝到目標空間,并回傳目標空間的起始位置
? strcpy在拷貝的程序中是以 ‘\0’ 為結束標志嗎
?
📐 驗證如下
#include<stdio.h>
#include<string.h>
int main()
{
char arr1[20] = "##########";
char arr2[] = "ab\0cdef";
strcpy(arr1, arr2);
return 0;
}
? 結果:

📝 分析:
strcpy在拷貝字串時是以’\0’為標志的,且會將’\0’也拷貝
? 當原字串要拷貝的空間比目標字串的空間大時
#include<stdio.h>
#include<string.h>
int main()
{
char arr[5] = { 0 };
char* p = "hello world";
strcpy(arr, p);
printf("%s\n", arr);
return 0;
}
? 結果:

? 目標空間為常量字串時
#include<stdio.h>
#include<string.h>
int main()
{
char* str = "xxxxxxxxxx";
char* p = "hello world";
strcpy(str, p);
printf("%s\n", str);
return 0;
}
? 結果:

💨 總結:
? 源字串必須以 ‘\0’ 結束
? strcpy在拷貝的程序中是以 ‘\0’ 為結束標志,且會將 ‘\0’ 也拷貝
? 目標空間必須足夠大,以確保能存放源字串
? 目標空間必須可變
💦 strcat
🔗 函式原型和頭:

🔗 函式的回傳值:

🔗 功能:

#include<stdio.h>
int main()
{
char arr1[20] = "hello";
char arr2[] = "bit";
strcat(arr1, arr2);
printf("%s\n", arr1);
return 0;
}
? 輸出結果:

📝 分析結果:
從結果可以知道源字串在追加時會找到目標字串最后的 ‘\0’ 并將它給覆寫,然后開始追加
? 在源字串追加給目標字串時,源字串的 ‘\0’ 會不會也追加
?
📐 驗證如下
#include<stdio.h>
int main()
{
char arr1[20] = "hello\0xxxxxxxxxx";
char arr2[] = "bit";
strcat(arr1, arr2);
printf("%s\n", arr1);
return 0;
}
? 結果:

? strcat能不能自己給自己追加
#include<stdio.h>
int main()
{
char arr[20] = "abcd";
strcat(arr, arr);//?
printf("%s\n", arr);
return 0;
}
? 結果:

📝 分析:

💨 總結:
? 源字串必須以 ‘\0’ 結束
? 目標空間必須足夠大,以足以容納字串
? 目標字串必須可修改
? 不能自己追加自己
💦 strcmp
🔗 函式原型和頭:

🔗 函式的回傳值:

🔗 功能:

? 字串的比較
#include<stdio.h>
#include<string.h>
int main()
{
char* p1 = "def";
char* p2 = "abc";
if(p1 > p2)
printf(">\n");
else
printf("<\n");
return 0;
}
📝 分析:

? 字串的比較
#include<stdio.h>
#include<string.h>
int main()
{
char* p1 = "def";
char* p2 = "abcdef";
printf("%d\n", strcmp(p1, p2));
return 0;
}
📝 分析:

三、長度受限制的字串函式
💦 strncpy
🔗 函式原型和頭:

🔗 函式的回傳值:

🔗 功能:

#include<stdio.h>
#include<string.h>
int main()
{
char arr1[20] = "abcdef";
char arr2[] = "qwer";
strncpy(arr1, arr2, 2);
printf("%s\n", arr1);
return 0;
}
? 結果:

? 當指定的個數num大于源字串時
#include<stdio.h>
#include<string.h>
int main()
{
char arr1[20] = "abcdef";
char arr2[] = "qwe";
strncpy(arr1, arr2, 6);
printf("%s\n", arr1);
return 0;
}
? 結果:

💨 總結:
? 拷貝num個字符從源字串到目標空間
? 如果源字串的長度小于num,則拷貝完源字串之后,在目標的后面追加0,直到num個
💦 strncat
🔗 函式原型和頭:

🔗 函式的回傳值:

🔗 功能:

#include<stdio.h.>
#include<string.h>
int main()
{
char arr1[20] = "hello ";
char arr2[] = "world";
strncat(arr1, arr2, 3);
printf("%s\n", arr1);
return 0;
}
? 結果:

? strncat能否自己給自己追加
#include<stdio.h.>
#include<string.h>
int main()
{
char arr1[20] = "hello";
strncat(arr1, arr1, 6);
printf("%s\n", arr1);
return 0;
}
? 結果和分析:

💦 strncmp
🔗 函式原型和頭:

🔗 函式的回傳值:

🔗 功能:

#include<stdio.h>
#include<string.h>
int main()
{
char* p1 = "abcdef";
char* p2 = "abcpdf";
int ret = strncmp(p1, p2, 4);
printf("%d\n", ret);
return 0;
}
? 結果:

四、字串查找
💦 strstr
🔗 函式原型和頭:

🔗 函式的回傳值:

🔗 功能:

#include<stdio.h>
#include<string.h>
int main()
{
char arr1[] = "abcdefabcdef";
char arr2[] = "bcd";
char* ret = strstr(arr1, arr2);
if(ret == NULL)
printf("沒找到\n");
else
printf("找到了:%s\n", ret);
return 0;
}
? 結果:

📝 分析:
strstr在目標字串中查找子串,如果找到了就回傳目標字串中首次出現的源字串的地址,否則回傳一個空指標
💦 strtok
🔗 函式原型和頭:

🔗 函式的回傳值:

🔗 功能:

?
🔗 函式詳解:
? strDelimit的引數是一個字串,定義了用作分隔符的字符集合
? strToken指定一個字串,它包含了0個或者多個由strDelimit串中一個或者多個分隔符分割的標記
? strtok函式找到strToken中的下一個標記,并將其用 \0 結尾,回傳一個指向這個標記的指標(注:strtok函式會改變被操作的字串,所以在使用strtok函式切分的字串一般都是臨時拷貝的內容并且可修改)
? strtok函式的第一個引數不為NULL,函式將找到strToken中的第一個標記,strtok函式將保存它在字串中的位置
? strtok函式的第一個引數為NULL,函式將在同一個字串中被保存的位置開始,查找下一個標記
? 如果字串中不存在更多的標記,則回傳NULL指標
? strtok函式被呼叫一次,只會切割一次,所以想要切割多次,就需要多次呼叫
#include<stdio.h>
#include<string.h>
int main()
{
char arr[] = "wanghong@deboke.csdn";
char* p = "@.";
char temp[30] = { 0 };//拷貝一份arr于temp,讓temp去切割
strcpy(temp, arr);
char* ret = NULL;
ret = strtok(temp, p);
printf("%s\n", ret);
ret = strtok(NULL, p);
printf("%s\n", ret);
ret = strtok(NULL, p);
printf("%s\n", ret);
return 0;
}
? 結果與分析:

? 但是我們通常會將多次分割的這個操作使用回圈來實作,非常的妙啊 !!!
#include<stdio.h>
#include<string.h>
int main()
{
char arr[] = "wanghong@deboke.csdn";
char* p = "@.";
char temp[30] = { 0 };
strcpy(temp, arr);
char* ret = NULL;
for(ret = strtok(temp, p); ret != NULL; ret = strtok(NULL, p))
{
printf("%s\n", ret);
}
return 0;
}
五、錯誤資訊報告
💦 strerror
🔗 函式原型和頭:

🔗 函式的回傳值:
🔗 功能:

🔗 函式詳解:
在呼叫庫函式失敗時,都會設定錯誤碼
C語言中有一個全域的錯誤碼 -> int errno ,只要呼叫庫函式發生了錯誤,就會把錯誤碼放到errno里去
這里strerror就會把錯誤碼翻譯成對應的錯誤資訊,然后再把錯誤資訊以字串首地址回傳回來
通常strerror都會和errno一起使用
使用errno需要頭檔案errno.h
#include<stdio.h>
#include<string.h>
int main()
{
printf("%s\n", strerror(0));
printf("%s\n", strerror(1));
printf("%s\n", strerror(2));
printf("%s\n", strerror(3));
printf("%s\n", strerror(4));
printf("%s\n", strerror(5));
return 0;
}
? 結果與分析:

? 具體是怎么用的
注:以下所使用的對檔案操作的一些函式會在后面進行了解
#include<stdio.h>
#include<string.h>
#include<errno.h>
int main()
{
//以讀的形式打開test.txt檔案,這個檔案如果不存在就會打開失敗,然后回傳一個空指標
FILE* pf = fopen("test.txt", "r");
//失敗就輸出失敗的原因,也就題錯誤資訊
if(pf == NULL)
{
printf("%s\n", strerror(errno));
return 1;
}
//...
fclose(pf);//關閉檔案
pf == NULL;
return 0;
}
? 結果:

💦 perror
🔗 函式原型和頭:

🔗 函式的回傳值:
🔗 功能:

#include<stdio.h>
#include<stdio.h>
int main()
{
FILE* pf = fopen("test.txt", "r");
if(pf == NULL)
{
perror("fopen");
return 1;
}
//...
fclose(pf);
pf == NULL;
return 0;
}
? 結果與分析:

六、字符操作函式
1、字符分類函式

🧿 注:簡單介紹幾個,其余的函式可以照貓畫虎
💦 isdigit
🔗 函式原型和頭:

🔗 函式的回傳值:

🔗 功能:

#include<stdio.h>
#include<ctype.h>
int main()
{
char ch = '@';
//如果是數字字符回傳非0的值,否則回傳0
int ret = isdigit(ch);
printf("%d\n", ret);
return 0;
}
💦 islower
🔗 函式原型和頭:

🔗 函式的回傳值:

🔗 功能:

#include<stdio.h>
#include<ctype.h>
int main()
{
char ch = 'A';
//如果是小寫字母回傳非0的值,否則回傳0
int ret = islower(ch);
printf("%d\n", ret);
return 0;
}
2、字符轉換函式
| 函式 | 功能 |
|---|---|
| tolower | 大寫轉小寫 |
| toupper | 小寫轉大寫 |
💦 tolower
🔗 函式原型和頭:

🔗 函式的回傳值:

🔗 功能:

#include<stdio.h>
#include<ctype.h>
int main()
{
char arr[20] = { 0 };
scanf("%s", arr);
int i = 0;
while(arr[i] != '\0')
{
printf("%c ", tolower(arr[i]));
i++;
}
return 0;
}
? 結果:

七、記憶體操作函式
💦 memcpy
🔗 函式原型和頭:

🔗 函式的回傳值:

🔗 功能:

#include<stdio.h>
#include<string.h>
int main()
{
int arr1[10] = { 1,2,3,4,5,6,7,8,9,10 };
int arr2[20] = { 0 };
memcpy(arr2, arr1, 20);//注意第3個引數的單位是位元組
return 0;
}
? 結果:

? 我們注意到memcpy的前2個引數是void*型別的,那是不是說它可以拷貝不同的資料
#include<stdio.h>
#include<string.h>
int main()
{
char arr1[] = "abcdef";
char arr2[20] = { 0 };
memcpy(arr2, arr1, 6);
printf("%s\n", arr2);
return 0;
}
? 結果:

? 對于strcpy函式在拷貝字串時,如果遇到’\0’它會停止拷貝,那么思考memcpy在拷貝字串時遇到’\0’是否也會停止
#include<stdio.h>
#include<string.h>
int main()
{
char arr1[] = "abc\0def";
char arr2[20] = { 0 };
memcpy(arr2, arr1, 6);
printf("%s\n", arr2);
return 0;
}
? 結果:

這里有一個場景 ?

#include<stdio.h>
#include<string.h>
int main()
{
int arr1[10] = { 1,2,3,4,5,6,7,8,9,10 };
memcpy(arr1 + 2, arr1, 20);
return 0;
}
? 結果與分析:

💦 memmove
🔗 函式原型和頭:

🔗 函式的回傳值:

🔗 功能:

#include<stdio.h>
#include<string.h>
int main()
{
int arr1[10] = { 1,2,3,4,5,6,7,8,9,10 };
memmove(arr1 + 2, arr1, 20);
return 0;
}
? 結果與分析:

💦 memset
🔗 函式原型和頭:

🔗 函式的回傳值:

🔗 功能:

#include<stdio.h>
#include<string.h>
int main()
{
int arr[10] = { 0 };
memset(arr, 1, 20);
return 0;
}
? 結果與分析:

? 使用memset設定一個10個元素的陣列的元素為1
#include<stdio.h>
#include<string.h>
int main()
{
int arr[10] = { 0 };
int i = 0;
for (i = 0; i < 10; i++)
{
memset(&arr[i], 1, 1);
}
return 0;
}
? 結果:

💦 memcmp
🔗 函式原型和頭:

🔗 函式的回傳值:

🔗 功能:

#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);//注意第3個引數的單位是位元組
printf("%d\n", ret);
return 0;
}
八、函式的模擬實作
💦 strlen
1、計數器的版本(需要臨時變數)
#include<stdio.h>
int my_strlen(char* str)
{
int count = 0;
while(*str != '\0')
{
count++;
str++;
}
return count;
}
int main()
{
char arr[] = "abc";
int len = my_strlen(arr);
printf("%d\n", len);
return 0;
}
? 缺陷和不足:
? 在my_strlen函式里對指標直接進行解參考這是不安全的,因為指標一定得是有效的
? my_strlen函式的功能是求字串的長度,而這個my_strlen函式有權限去改變字串里的內容,因此這是不安全的
? my_strlen這個函式的回傳值是int,如果回傳的是一個負數,那可能就當場懵逼
💯優化
? 使用斷言assert,需要引頭檔案<assert.h>
? 使用const對str進行限制
? 將函式的回傳值int更改為size_t
? 對代碼整體進行更簡潔的優化
#include<stdio.h>
#include<assert.h>
size_t my_strlen(const char* str)
{
assert(str != NULL);
int count = 0;
while (*str++)
count++;
return count;
}
int main()
{
char arr[] = "abc";
printf("%d\n", my_strlen(arr));
return 0;
}
2、遞回版本(不使用臨時變數)
#include<stdio.h>
#include<assert.h>
int my_strlen(const char* str)
{
assert(str);
if(*str)
return 1 + my_strlen(str+1);
else
return 0;
}
int main()
{
char arr[] = "abc";
printf("%d\n", my_strlen(arr));
return 0;
}
3、指標版本(指標-指標)
字串長度 = '\0’的地址 - 首元素的地址
#include<stdio.h>
#include<assert.h>
int my_strlen(char* first, char* end)
{
assert(first);
assert(end);
return end - first;
}
int main()
{
char arr[] = "abc";
int left = 0;
int right = sizeof(arr)/sizeof(arr[0]) - 1;
printf("%d\n", my_strlen(&arr[left], &arr[right]));
return 0;
}
💦 strcpy
#include<stdio.h>
#include<assert.h>
void my_strcpy(char* dest, char* src)
{
while(*src != 0)
{
*dest = *src;
dest++;
src++;
}
src = 0;//將str1的最后一個元素拷貝為0
}
int main()
{
char arr1[20] = { 0 };
char arr2[] = "hello bit";
my_strcpy(arr1, arr2);
printf("%s\n", arr1);
return 0;
}
? 缺陷和不足:
? 在my_strcpy函式里對指標直接進行解參考這是不安全的,因為指標一定得是有效的
?對于my_strcpy這個函式的2個引數,其一是目標字串,其二是源字串,目標字串必須保證可被修改,而源字串不能被修改
? 這里是先把除 ‘\0’ 其它字符先拷貝,再拷貝 ‘\0’
? 這個函式的回傳值是void,而在庫里是char*,回傳的是目標字串的首地址 :

💯優化
? 使用斷言assert,需要引頭檔案<assert.h>
? 使用const對str2進行限制
? 將 ‘\0’ 和其它字符一起進行拷貝
? 將函式的回傳值設定為char*
#include<stdio.h>
#include<assert.h>
char* my_strcpy(char* dest, const char* src)
{
assert(dest);
assert(src);
char* temp = dest;//備份一份首地址
while (*dest++ = *src++)
{
;
}
return temp;//回傳備份的首地址
}
int main()
{
char arr1[20] = { 0 };
char arr2[] = "hello bit";
printf("%s\n", my_strcpy(arr1, arr2));
return 0;
}
💦 strcat
#include<stdio.h>
#include<assert.h>
char* my_strcat(char* dest, const char* src)
{
assert(dest && src);
char* temp = dest;//備份一份首地址
//找到目標字串中的'\0'
while(*dest)
{
dest++;
}
while(*dest++ = *src++)
{
;
}
return temp;//回傳首地址
}
int main()
{
char arr1[20] = "hello";
char arr2[] = "bit";
printf("%s\n", my_strcat(arr1, arr2));
return 0;
}
💦 strstr
#include<stdio.h>
#include<assert.h>
char* my_strstr(const char* father, const char* son)
{
assert(father && son);
const char* f1 = NULL;
const char* s2 = NULL;
const char* ret = father;
while(*ret)
{
f1 = ret;
s2 = son;
if(*son == '\0')
{
return (char*)father;
}
while(*f1 && *s2 && (*f1 == *s2))
{
f1++;
s2++;
}
if(*s2 == '\0')
{
return (char*)ret;
}
ret++;
}
return NULL;
}
int main()
{
char arr1[] = "abbbcdef";
char arr2[] = "bbc";
char* ret = my_strstr(arr1, arr2);
if(ret == NULL)
{
printf("沒找到\n");
}
else
{
printf("找到了:%s\n", ret);
}
return 0;
}
? 分析:

? 考慮到如果查找失敗的情況,不一定是找不到子串,所以要從新再匹配直到*father == '\0’就找不到子串
? 需要定義2個指標f1(指向father首地址)和s2(指向son首地址)來幫我們往下去匹配
? 如果匹配失敗指標要回到該回到的地方
對于s2:s2 = son
對于f1:這里就還需要另1個指標ret(指向fther首地址)來記錄,每一次匹配失敗,都讓ret++,然后f1 = ret
🍳拓展:KMP演算法 - 字串查找演算法 - 大家可以了解一下
💦 strcmp
#include<stdio.h>
#include<assert.h>
int my_strcmp1(const char* str1, const char* str2)
{
assert(str1 && str2);
while(1)
{
if(*str1 > *str2)
return 1;
else if(*str1 < *str2)
return -1;
else
{
if(*str1 == *str2 && *str1 == '\0')
return 0;
else
{
str1++;
str2++;
}
}
}
}
int my_strcmp2(const char* str1, const char* str2)
{
assert(str1 && str2);
while(*str1 == *str2)
{
if(*str1 == '\0')
return 0;
str1++;
str2++;
}
if(*str1 > *str2)
return 1;
else
return -1;
}
int main()
{
char* p1 = "def";
char* p2 = "abcdef";
//int ret = my_strcmp1(p1, p2);//版本1
int ret = my_strcmp2(p1, p2);//版本2
if(ret > 0)
printf("p1 > p2\n");
else if (ret < 0)
printf("p1 < p2\n");
else
printf("p1 = p2\n");
return 0;
}
? 優化冗余:
#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* p1 = "def";
char* p2 = "abcdef";
int ret = my_strcmp(p1, p2);
if(ret > 0)
printf("p1 > p2\n");
else if (ret < 0)
printf("p1 < p2\n");
else
printf("p1 = p2\n");
return 0;
}
💦 memcpy
#include<stdio.h>
#include<assert.h>
void* my_memcpy(void* dest, const void* src, size_t num)
{
assert(dest && src);
void* ret = dest;
while(num--)
{
/*
*(char*)dest = *(char*)src;
dest = (char*)dest + 1;
src = (char*)src + 1;
*/
*((char*)dest)++ = *((char*)src)++;
}
return ret;
}
int main()
{
int arr1[10] = { 1,2,3,4,5,6,7,8,9,10 };
int arr2[20] = { 0 };
my_memcpy(arr2, arr1, 20);
}
? 注意:

? my_memcpy能否拷貝重疊的記憶體空間
#include<stdio.h>
#include<assert.h>
void* my_memcpy(void* dest, const void* src, size_t num)
{
assert(dest && src);
void* ret = dest;
while(num--)
{
*(char*)dest = *(char*)src;
dest = (char*)dest + 1;
src = (char*)src + 1;
}
return ret;
}
int main()
{
int arr1[10] = { 1,2,3,4,5,6,7,8,9,10 };
my_memcpy(arr1 + 2, arr1, 20);
}
? 結果:

💦 memmove
? 相信小伙伴們都很好奇memmove是怎么處理記憶體重疊的情況的
🔎 分析一波

#include<stdio.h>
#include<assert.h>
void* my_memmove(void* dest, const void* src, size_t num)
{
assert(dest && src);
void* ret = dest;
if(dest < src)
{
//從前向后拷貝
while(num--)
{
*(char*)dest = *(char*)src;
dest = (char*)dest + 1;
src = (char*)src + 1;
}
}
else
{
//從后向前拷貝
while(num--)
{
*((char*)dest + num) = *((char*)src + num);
}
}
return ret;
}
int main()
{
int arr1[10] = { 1,2,3,4,5,6,7,8,9,10 };
my_memmove(arr1 + 2, arr1, 20);
return 0;
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/287427.html
標籤:其他
下一篇:C語言案例教程詳解
