
目錄
- 前言
- C語言的庫函式分類
- 求字串長度
- strlen
- strlen函式的模擬實作
- 非遞回寫法
- 遞回寫法
- 指標相減法
- 長度不受限制的字串函式
- strcpy
- 舉例1、
- 舉例2、
- 舉例3、
- strcat
- strcmp
- 長度受限制的字串函式介紹
- strncpy
- strncat
- strncmp
- 字串查找
- strstr
- strtok
- 錯誤資訊報告
- strerror
- 記憶體操作函式
- memcpy
- memcpy記憶體重疊問題繼續探討,memmove的引入
- memmove
- memmove的模擬實作
- memset
- memcmp
前言
C語言中對字符和字串的處理很是頻繁,但是C語言本身是沒有字串型別的,字串通常放在 常量字串 中 或者 字符陣列 中, 字串常量, 適用于那些對它不做修改的字串函式
C語言的庫函式分類
-
IO函式 prntf、scanf
字符函式
字串函式
記憶體函式
時間日期函式
數學函式
重點會講解常用的,本次的內容主要在介紹記憶體函式和字串函式
求字串長度
strlen

-
字串已經 ‘\0’ 作為結束標志,strlen函式回傳的是在字串中 ‘\0’ 前面出現的字符個數(不包含 ‘\0’ ),
引數指向的字串必須要以 ‘\0’ 結束,
注意函式的回傳值為size_t,是無符號的( 易錯 )
學會strlen函式的模擬實作

char arr[] = "hello Mozart";
size_t len = strlen()
strlen的一些小細節
void functest()
{
char arr1[] = "abdc";
char arr2[] = "abdef";
if (strlen(arr1) - strlen(arr2) > 0)
{
printf("hehe\n");
}
else
{
printf("haha\n");
}
}
會列印hehe,因為回傳值型別是size_t,是一個無符號型別的結果,(并不會存在負數),當然強制型別轉換就會得到一個有符號型別的結果(存在負數)
void functest()
{
char arr1[] = "abdc";
char arr2[] = "abdde";
if ((int)strlen(arr1) - (int)strlen(arr2) > 0)
{
printf("hehe\n");
}
else
{
printf("haha\n");
}
}
strlen函式的模擬實作
非遞回寫法
//非遞回寫法
int my_strlen(char* str)
{
int count = 0;
while('\0' != *str)
{
count++;
str++;
}
return count;
}
遞回寫法
//遞回寫法
nt my_strlen(char *str)
{
if('\0' == *str)
return 0;
else
return 1+my_strlen(1+str);
}
指標相減法
int my_strlen(char* str)
{
char *dest = str;
while(*dest++)
{
;
}
//除去‘\0’
return dest - str - 1;
}
長度不受限制的字串函式
strcpy

使用時注意事項
-
源字串必須以 ‘\0’ 結束,
會將源字串中的 ‘\0’ 拷貝到目標空間,
目標空間必須足夠大,以確保能存放源字串,
目標空間必須可變,
學會模擬實作,
舉例1、
void functest()
{
char arr1[] = "xxxxxxxxxxxx";
char arr2[] = "abcd";
strcpy(arr1,arr2);
printf("%s ",arr1);
}
會將字串的結束標記 ’ \0’也給拷貝進去,直到遇到 ’ \0 ',strcpy的作業也就結束了

舉例2、
目標空間足夠大
依然是使用原來的代碼,方便大家理解,在這里可以看到要想將arr2的內容拷貝至arr1,arr1的空間必須足夠大,實際上并沒有給出一塊合適的空間來預存arr2的內容,那么后果也是可想而知
void functest()
{
char arr1[] = "xxx";
char arr2[] = "abcd";
strcpy(arr1,arr2);//err
printf("%s ",arr1);
}

舉例3、
目標空間必須可變,在學習指標的時候還記得字符指標用來存放字符首地址嗎
char *ptr = "hello Mozart"
為了保護字串習慣于在前面加const
const char *ptr = "hello Mozart"
代碼可不能這樣寫哦,受const保護的字串是不允許被修改的
void functest()
{
const char* ptr1 = "xxxxxxx";
char* ptr2 = "abc";
strcpy(ptr1,ptr2);//err
printf("%s ",ptr1);
}
strcpy模擬實作
char *my_strcpy(char *dest,const char *src)
{
assert(dest && src);
char *tmp = dest;
while (*tmp ++ = *src++)
{
;
}
return dest;
}
strcat
又叫字串鏈接和字串追加

-
源字串必須以 ‘\0’ 結束,
目標空間必須有足夠的大,能容納下源字串的內容,
目標空間必須可修改,
字串自己給自己追加,如何?
使用示范
void func3()
{
char arr[20] = "abcdef";
//char arr[] = "abcdef"; err,空間不夠
strcat(arr,"def");
printf("%s ",arr);
}
int main()
{
func3();
return 0;
}
如果你在使用strcat的時候遇到過這種情況,只需要在程式最開始的位置處加一個宏定義


錯誤示范
void func3()
{
char arr1[20] = "abcdef";
char arr2[20] = {'a','b','c'}; //字符陣列末尾沒加‘\0’
strcat(arr1, arr2);
printf("%s ",arr1);
}
int main()
{
func3();
char arr;
scanf("%s ",&arr);
return 0;
}
當想將一個字符陣列arr2的內容追加到另外一個字符陣列arr1中去,strcat是遇到 ‘\0’ 才會停止追加字符的,而arr2中并未出現
‘\0’,由于在記憶體中‘\0’的出現時機并不是很明確,可能在‘\0’還未出現之前,追加過去的字符內容就已經足以導致arr1越界了,可能存在越界,但是程式是明顯崩了,這一點請務必注意
模擬實作strcat
整體思路就是找到源字串的末尾位置,存放‘\0’的位置處,從這里開始將待追加字串全部拷貝過去

char * my_strcat(char *s1, const char * s2)
{
//防止對空指標解參考
assert(s1 && s2);
char *dest = s1;
while (*dest)
{
dest++;
}
while (*dest++ = *s2++)
{
;
}
// strcat回傳的是目標空間的起始地址
return s1;
}
strcmp
是比較字串的內容(Ascii碼值),并不是比較長度

-
標準規定:
第一個字串大于第二個字串,則回傳大于0的數字
第一個字串等于第二個字串,則回傳0
第一個字串小于第二個字串,則回傳小于0的數字
那么如何判斷兩個字串?
可以通過strcmp的回傳值來比較字串的大小
void func3()
{
char arr1[] = "abdc";
char arr2[] = "abdc";
int ret = strcmp(arr1,arr2);
if (ret > 0)
printf(">\n");
else if (ret == 0)
printf("==\n");
else
printf("<\n");
}
模擬實作strcmp
//模擬實作strcmp
int my_strcmp(const char *s1, const char * s2)
{
//防止對空指標解參考
assert(s1 && s2);
while (*s1 == *s2)
{
if (!(*s1))
return 0;
++s1;
++s2;
}
return *s1 - *s2;
}
長度受限制的字串函式介紹
相對于上面的函式使用起來會更安全,但是功能上是一樣的
strncpy

-
拷貝num個字符從源字串到目標空間,
如果源字串的長度小于num,則拷貝完源字串之后,在目標的后邊追加0,直到num個
void func3()
{
char arr1[12] = "abdc";
char arr2[] = "abdc";
strncpy(arr1,"xxxxxxxxxxx",11);
printf("%s ",arr1);
}
源字串的長度剛好等于num
當源字串的長度剛好小于num,在字串后面補0
strncat

-
將源的第一個num字符附加到目標,再加上一個結束的空字符,
如果source中的C字串長度小于num,則只復制到終止空字符’\0’之前的內容
可以指定追加num個字符
void func3()
{
char arr1[] = "abcd\0xxxxxxxxxx";
char arr2[] = "defg";
strncat(arr1, arr2,3);
printf("%s ",arr1);
}
可以看出是在目標字串的‘\0’位置開始追加3個字符,末尾自動補‘\0’

strncmp

- 比較到出現另個字符不一樣或者一個字串結束或者num個字符全部比較完
指定比較兩個字串出現的前num個字符
void func3()
{
char arr1[] = "abcdxxxxxxxxxx";
char arr2[] = "abcd";
//比較字串的內容
if(!strncmp(arr1, arr2, 4))
printf("相等\n");
else
printf("不相等\n");
}
字串查找
strstr

- 回傳一個指向str1中第一次出現的str2的指標,如果str2不是str1的一部分,則回傳一個空指標,
void func3()
{
char arr1[] = "abcdxxxxxxxxxx";
char *ret = strstr(arr1,"abcd");
if (ret == NULL)
printf("找不到");
else
//找到了就列印該字串
printf("%s ", ret);
}
模擬實作
基本思路:備份兩個字串的起始地址,s1表示字串的首地址,s2表示子串的首地址,
1、s1和s2同時走的情況,*s1 == *s2
2、如果 * s1 != * s2,那么cp++,因為前一個字符都已經不相等了,就可以從后面的字符中開始找字串
3、s1找著找著中途不與s2相等了,那么s1回到它的開始處查找(cp的位置),而s2依然是從它的起始位置開始,s1和s2繼續比較
char *my_strstr(const char *str1, const char *str2)
{
assert(str1 && str2);
char *cp = str1, *s1 ,*s2;
while (*cp)
{
//cp記錄str1起始位置
s1 = cp;
//s2記錄sr2的起始位置
s2 = str2;
while (*s1 != '\0' && *s2 != '\0' && *s1 == *s2)
{
s1++;
s2++;
}
//找到的情況
if (*s2 == '\0')
return cp;
//如果沒有找到cp++
cp++;
}
return NULL;
}
比較的好的情況

不好的情況

strtok

-
sep引數是個字串,定義了用作分隔符的字符集合
第一個引數指定一個字串,它包含了0個或者多個由sep字串中一個或者多個分隔符分割的標記,
strtok函式找到str中的下一個標記,并將其用 \0 結尾,回傳一個指向這個標記的指標,(注:strtok函式會改
變被操作的字串,所以在使用strtok函式切分的字串一般都是臨時拷貝的內容并且可修改,)strtok函式的第一個引數不為 NULL ,函式將找到str中第一個標記,strtok函式將保存它在字串中的位置,
strtok函式的第一個引數為 NULL ,函式將在同一個字串中被保存的位置開始,查找下一個標記,
如果字串中不存在更多的標記,則回傳 NULL 指標
int main()
{
char arr1[] = "CSDN@IT莫扎特.Mozart";
char arr2[40] = { 0 };
// 臨時拷貝的內容
strcpy(arr2,arr1);
//用作分隔符的字符集合
char tmp[] = "@.";
for (char *ret = strtok(arr2, tmp); ret != NULL; ret = strtok(NULL, tmp))
{
//列印回傳的字串
printf("%s \n",ret);
}
}
使用效果還是很明顯的

希望我梳理的流程圖對你有所幫助

錯誤資訊報告
strerror

解釋errnum的值,生成一個帶有描述錯誤條件的訊息的字串,就像被庫的函式設定為errno一樣,
回傳的指標指向一個靜態分配的字串,程式不能修改該字串,對這個函式的進一步呼叫可能會覆寫它的內容(不需要特定的庫實作來避免資料競爭),
strerror產生的錯誤字串可能是特定于每個系統和庫實作的,
c語言會將庫函式的錯誤資訊存放到errnum
這個變數當中,每個錯誤碼都會有一條它的具體資訊,strerror可以回傳c語言錯誤碼對應的資訊,
以下舉得幾個例子就當對這個函式的了解,有興趣可以自己去研究研究
printf("%s \n", strerror(0));
printf("%s \n", strerror(1));
printf("%s \n", strerror(2));
printf("%s \n", strerror(3));

int main()
{
FILE *pf = fopen("test.txt","r");
if(pf == NULL)
{
printf("%s \n",strerror(errno));
寫成下面這個函式也是可以
//perror("錯誤資訊:\n");
}
else
{
printf("檔案打開成功\n");
}
return 0;
}
記憶體操作函式
memcpy

官方檔案
-
將num位元組的值從source所指向的位置直接復制到destination所指向的記憶體塊,
源指標和目標指標所指向的物件的底層型別與此函式無關;結果是資料的二進制副本,
該函式不檢查source中是否有任何終止的空字符——它總是精確地復制num位元組,
為了避免溢位,目標和源引數所指向的陣列的大小必須至少為num位元組,并且不能重疊(對于重疊的記憶體塊,memmove是一種更安全的方法),
C語言中文網解讀
memcpy() 用來復制記憶體,其原型為:
void * memcpy ( void * dest, const void * src, size_t num );memcpy() 會復制 src 所指的記憶體內容的前 num 個位元組到 dest 所指的記憶體地址上,
memcpy() 并不關心被復制的資料型別,只是逐位元組地進行復制,這給函式的使用帶來了很大的靈活性,可以面向任何資料型別進行復制,
需要注意的是: dest 指標要分配足夠的空間,也即大于等于 num 位元組的空間,如果沒有分配空間,會出現斷錯誤, dest 和 src
所指的記憶體空間不能重疊(如果發生了重疊,使用 memmove() 會更加安全),
稍后再來解決memcpy函式記憶體重疊的問題與 strcpy() 不同的是,memcpy() 會完整的復制 num 個位元組,不會因為遇到“\0”而結束,
【回傳值】回傳指向 dest 的指標,注意回傳的指標型別是 void,使用時一般要進行強制型別轉換,
舉例使用
將一個陣列中的內容拷貝到另一個陣列中去
int arr1[10] = {9,8,7,6,5,4,3,2,1,0};
int arr2[10] = { 0 };
memcpy(arr2,arr1,sizeof(int) * 10);
for (int i = 0; i < 10; i++)
{
printf("%d ",arr2[i]);
}
模擬實作
思路:
原陣列拷貝到目標陣列中,拷貝的是資料,而資料又分型別,為了更方便的達到實作的目的,將使用一個位元組一個位元組地拷貝
一個位元組一個位元組拷貝
void * my_memcpy(void * dest, const void * src, size_t num)
{
assert(dest && src);
void * tmp = dest;
while (num--)
{
//一個位元組拷貝
*(char *)dest = *(char *)src;
//dest指向下一個位元組
dest = (char *)dest + 1;
//src指向下一個位元組
src = (char *)src + 1;
}
return tmp;
}
int main()
{
int arr1[10] = {0,1,2,3,4,5,6,7,8,9};
int arr2[10] = { 0 };
my_memcpy(arr2,arr1,sizeof(int) * 10);
for (int i = 0; i < 10; i++)
{
printf("%d ",arr2[i]);
}
}
最終的效果

memcpy記憶體重疊問題繼續探討,memmove的引入
這里會有一個記憶體被覆寫的問題
int arr[10] = {0,1,2,3,4,5,6,7,8,9};
my_memcpy(arr + 2,arr,16);
for (int i = 0; i < 10; i++)
{
printf("%d ",arr[i]);
}
從起始地址arr每每間隔兩個整形(arr + 2),每次拷貝一位元組,回圈16次(4 * 4)int * 4,相當于用前兩個整形元素覆寫掉后面兩個整形元素
執行結果也確是如此要想解決這個問題就得引入memmove
memmove

-
和memcpy的差別就是memmove函式處理的源記憶體塊和目標記憶體塊是可以重疊的,
如果源空間和目標空間出現重疊,就得使用memmove函式處理
使用memmove后,可以看出效果還是很明顯的不會發生記憶體重疊
int main()
{
int arr[10] = {0,1,2,3,4,5,6,7,8,9};
memmove(arr + 2,arr,16);
for (int i = 0; i < 10; i++)
{
printf("%d ",arr[i]);
}
return 0;
}
memmove的模擬實作
思路:要想實作記憶體不重疊,將原資料src拷貝到目標資料dest,那么前提是從前往后拷貝呢?還是從后往前拷貝?

從前往后拷貝
思考:可以看出如果是從前往后拷貝,那么3 4就已經被覆寫了,原本的資料也被改成了1 2,再將資料拷貝到5 6的時候那么就是1 2 1 2 1
2 7 8 9 10了,很明顯不符合我們想達到的預期結果

從后往前拷貝
思考:那么這種情況就很符合我們的預期結果了

但僅僅是這樣思考 還是不夠
思考:如果dest和src的位置互換了之后呢? 即使是從后向前拷貝資料還是被覆寫了

解決方案: dest 在 src的左邊,從前往后拷貝

src在dest的左邊,從后向前拷貝

重疊的情況,從后向前

dest 在 src的右邊,從后往前拷貝,從后往前拷貝都可以

梳理邏輯

實作代碼
void *my_memove(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
{
//從后向前拷貝
while (count--) //count的大小決定了指標偏移到了哪個位置
{
*((char*)dest + count) = *((char*)src + count);
}
}
return ret;
}
memset

將ptr指向的記憶體塊的第一個num位元組設定為指定的值(解釋為unsigned char),

int arr[5] = { 1,2,3,4,5 };
memset(arr,0,20);
memcmp

比較ptr1指向的記憶體塊的第一個num位元組和ptr2指向的第一個num位元組,如果它們都匹配則回傳0,如果不匹配則回傳一個不同于0的值,表示哪個值更大,
注意,與strcmp不同,該函式在找到空字符后不會停止比較,
記憶體比較函式,比較的是記憶體中的內容
int arr1[10] = { 0,1,2,3,4,5,6,7,8,9 };// 00 00 00 00 01 00 00 00 02 00 00 00
int arr2[10] = { 0,1,2,3,4,5,6,7,8,9 };//00 00 00 00 01 00 00 00 02 00 00 00
if (memcmp(arr1, arr2, 13) == 0)
printf("相等\n");
else
printf("不相等\n");
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/301332.html
標籤:其他
上一篇:Postman介面測驗-基礎教程








