柚子快報邀請碼778899分享:c語言 字符串函數(shù)(2)
柚子快報邀請碼778899分享:c語言 字符串函數(shù)(2)
目錄
前言1. strlen1.1 strlen函數(shù)的理解和使用1.2 strlen函數(shù)的模擬實現(xiàn)
2. strcpy2.1 strcpy函數(shù)的理解和使用2.2 strcpy函數(shù)的模擬實現(xiàn)
3.strcat3.1 strcat函數(shù)的理解和使用3.2 strcat 函數(shù)的模擬實現(xiàn)
前言
在上一篇文章中,我們對字符分類函數(shù)和字符轉(zhuǎn)換函數(shù)進行了學(xué)習(xí),部分小伙伴們可能會想字符串函數(shù)在哪里呢?其實是我沒有寫完啦!在本篇文章中,我會對該內(nèi)容進行詳細講解。
1. strlen
1.1 strlen函數(shù)的理解和使用
字符串函數(shù)的使用需要包含頭文件string.h。strlen的返回值的類型是size_t(無符號整型)。strlen函數(shù)是求字符串的長度的。 例如:
工作原理是: 當我們有一個字符串時,里面除了我們沒看到的以外,還存放著\0。我們給strlen函數(shù)傳了數(shù)組名arr。數(shù)組名是數(shù)組首元素的地址,strlen從給定的起始位置開始,向后統(tǒng)計\0之前字符的個數(shù)。 注意: 一定是從起始位置開始,我們現(xiàn)在可以測試一下別的寫法。 arr是數(shù)組名,代表首元素的地址,如果傳arr,則從a開始數(shù)。如果傳arr+1,則跳過一個元素,從b開始數(shù)。 前面我們說過strlen的返回值的類型是size_t類型的,這一部分我們需要加深理解,現(xiàn)在我們舉一個例子進行理解。 按照我們的想法來看,strlen(arr2)和strlen(arr1)的輸出結(jié)果分別為3和6,3減6的值等于-3,應(yīng)該打印<=。但是運行的結(jié)果并不是我們所想,因為strlen的返回值的類型是size_t , size_t類型的值相減也是size_t,此時計算出的-3會被作為一個無符號的整型來處理,此時的-3就沒有了符號位。 在內(nèi)存中存放著-3的補碼,當-3為無符號整型時,最前面的1就不是符號位了,此時就不存在原反補了。計算的結(jié)果將是一個非常大的正數(shù),所以輸出的結(jié)果是>。我們現(xiàn)在對代碼進行修改。
#include
#include
int main()
{
char arr1[] = "abcdef";
char arr2[] = "abc";
if (strlen(arr2) > strlen(arr1) )
printf(">\n");
else
printf("<=\n");
return 0;
}
或者:
#include
#include
int main()
{
char arr1[] = "abcdef";
char arr2[] = "abc";
if ((int)strlen(arr2) - (int)strlen(arr1) > 0)
printf(">\n");
else
printf("<=\n");
return 0;
}
這兩個代碼都能實現(xiàn)我們原來的目的。
1.2 strlen函數(shù)的模擬實現(xiàn)
我們采用遞歸的方式進行實現(xiàn)。 現(xiàn)在我們有一個字符數(shù)組里面存放著abcdef?,F(xiàn)在我們需要寫一個自己的函數(shù)來計算字符串的長度。仿照著strlen把arr傳過去,并讓其返回一個size_t的值,當然int也行,只是字符串的長度不會為負值,選擇size_t比較合適。 函數(shù)的參數(shù)部分需要接受arr(即首元素的地址),我們采用char*類型的str進行接收,同時我們不希望str去改變字符串,使用可以使用const來修飾。 遞歸的思想是大化小,我們把字符串中的第一個字符拿出來,不是\0說明長度至少是1,1加上后面的長度就是總長度,按照這個原理我們不斷的向后拿取,如圖 代碼:
#include
size_t my_strlen(const char* str)
{
if (*str == '\0')
return 0;
else
return 1 + my_strlen(str + 1);
}
int main()
{
char arr[] = "abcdef";
size_t len = my_strlen(arr);
printf("%zd\n", len);
return 0;
}
運行結(jié)果:
現(xiàn)在可能還有部分小伙伴沒有理解,我們可以縮短字符串的長度(改為abc)來理解:
我們要計算abc的長度,此處我們調(diào)用了自己寫的函數(shù)來完成。我們將a的地址傳了過去,給了str,此時str向后就可以看到a b c \0。 剛開始時,* sr 為a,a并不等于\0,我們就走else這條路,然后再對my_strlen進行調(diào)用。第二次調(diào)用時,這里的str指向b,b不等于\0,走else的路線,這里我們還需要對my_strlen進行調(diào)用,第三次調(diào)用時,這里的str指向c,c不等于\0,走else的路線,最后一次調(diào)用my_strlen時,str指向了\0。此時我們的遞推就結(jié)束了,將開始回歸。1就會不斷的相加到3,最后返回去,此時len就為3. 注意: 在這里的 1 + my_strlen(str + 1)沒有返回時,是不會計算的。 代碼:
#include
size_t my_strlen(const char* str)
{
if (*str == '\0')
return 0;
else
return 1 + my_strlen(str + 1);
}
int main()
{
char arr[] = "abc";
size_t len = my_strlen(arr);
printf("%zd\n", len);
return 0;
}
2. strcpy
2.1 strcpy函數(shù)的理解和使用
該函數(shù)是用來進行字符串拷貝的。我們現(xiàn)在來看函數(shù)的基本情況。 第一個參數(shù)的名字是destination(目的地)。第二個參數(shù)是source(源頭),即將源頭的數(shù)據(jù)拷貝到destination里面去。例如: 理解: arr1數(shù)組里面放了hello world\0 .我們把arr1和arr2分別傳給了我們剛才說的source和destination,我們認為它是一個個字符進行拷貝的,把\0拷貝完成就停止。 這里我們可以看看到底有沒有將\0拷貝過去,如下: 此時我們可以看到\0也被拷貝過去了。既然我們需要將\0拷貝過去,那么我們源頭中的字符串也需要有\(zhòng)0。如果我們?nèi)サ鬨0會發(fā)生什么呢?如下:
拷貝將不會有停下的動作,一直進行拷貝,甚至?xí)浇缤筮M行修改。 注意:
在拷貝時,我們的目標空間必須足夠大,只有足夠大,才能放下從源頭拷貝過來的數(shù)據(jù)目標空間必須可修改,如圖: 這里的p是常量字符串,不能進行修改。
2.2 strcpy函數(shù)的模擬實現(xiàn)
我們首先創(chuàng)建arr1,里面放上abcdef,再創(chuàng)建一個arr2,里面可以放上20個元素。根據(jù)剛才學(xué)到的strcpy的參數(shù)部分,我們也可以將my_strlen中的參數(shù)設(shè)置為char * dest 和char * src。暫時我們把返回這里寫為void。 我們將arr2和arr1傳過去之后,dest和src分別指向如下位置: 這里我們需要將字符一個個拷貝過去,即我們需要進行多次拷貝,我們可以寫一個while循環(huán),在里面使dest和src++來不停的往后。當\0也拷貝下來時循環(huán)結(jié)束,所以我們把條件寫為 * src! = ’\0’,此時\0并沒有拷貝下來,我們添加 *dest = * src即可(停下來時 * src為\0,我們此時相當于把‘\0’拷貝過去)。此時代碼就完成了。
代碼:
#include
void my_strcpy(char* dest, char* src)
{
while (*src != '\0')
{
*dest = *src;
src++;
dest++;
}
*dest = *src;
}
int main()
{
char arr1[] = "abcdef";
char arr2[20] = { 0 };
my_strcpy(arr2, arr1);
printf("%s\n",arr2);
return 0;
}
運行結(jié)果: 現(xiàn)在我們進行優(yōu)化,把代碼的改為后置++,用完后再++,然后我們的 * dest++ = * src++和 * dest = * src是有一點重復(fù)的,我們需要改為如下寫法:
#include
void my_strcpy(char* dest, char* src)
{
while (*dest++ = *src++)
{
;
}
}
int main()
{
char arr1[] = "abcdef";
char arr2[20] = { 0 };
my_strcpy(arr2, arr1);
printf("%s\n",arr2);
return 0;
}
理解: 第一次 * src是a,我們將a賦值過去,此時整個表達式的值就為a,a的ASCII碼值不為0,為真,進入循環(huán),因為賦值完成以后進行++,則下一次 * src拿到的值就是b,然后再進行該過程,直到 * src指向\0,將\0賦值過去,此時表達式的結(jié)果為\0,\0的ASCII碼為0,0為假,跳出循環(huán)。我們會發(fā)現(xiàn)這里我們既可以賦值,賦值產(chǎn)生的值有可以進行判斷。 我們還可以對代碼進行調(diào)整,我們的目的是將src指向的內(nèi)容拷貝到dest指向的空間里面去,我們不希望src指向的空間被修改。這里可以加const進行修飾,但是解引用之前我們害怕為空指針,我們可以利用assert斷言進行處理。
代碼:
#include
#include
void my_strcpy(char* dest, const char* src)
{
assert(dest && src);
while (*dest++ = *src++)
{
;
}
}
int main()
{
char arr1[] = "abcdef";
char arr2[20] = { 0 };
my_strcpy(arr2, arr1);
printf("%s\n",arr2);
return 0;
}
但是strcpy的返回值的類型是char*,與我們自己寫的函數(shù)是不同的,那使用char * 有什么好處嗎? 函數(shù)是將原字符串拷貝放到目標空間里面去,我們是期望目標空間發(fā)生變化來進行觀察。所以函數(shù)最終返回目標空間的起始位置。但是我們這里不能直接return dest。因為這里dest不斷地++以后,dest指向的不再是起始地址,解決方式是我們創(chuàng)建一個 char * 的ret把未進行改動的dest存一份,最后return ret。
代碼:
#include
#include
char* my_strcpy(char* dest, const char* src)
{
char* ret = dest;
assert(dest && src);
while (*dest++ = *src++)
{
;
}
return ret;
}
int main()
{
char arr1[] = "abcdef";
char arr2[20] = { 0 };
char* ret = my_strcpy(arr2, arr1);
printf("%s\n",ret);
return 0;
}
運行結(jié)果: 此時我們的函數(shù)更加靈活,my_strcpy的返回值也可以作為其他函數(shù)的參數(shù),實現(xiàn)鏈式訪問。
代碼:
#include
#include
#include
char* my_strcpy(char* dest, const char* src)
{
char* ret = dest;
assert(dest && src);
while (*dest++ = *src++)
{
;
}
return ret;
}
int main()
{
char arr1[] = "abcdef";
char arr2[20] = { 0 };
/*char* ret = my_strcpy(arr2, arr1);*/
size_t len = strlen(my_strcpy(arr2, arr1));
printf("%zd\n",len);
return 0;
}
運行結(jié)果:
3.strcat
3.1 strcat函數(shù)的理解和使用
該函數(shù)的作用是字符串的追加,我們直接上實例進行理解:
我們現(xiàn)在有一個數(shù)組arr1,里面存放著hello。我們想在后面追加world。可能有同學(xué)會想用strcpy,但是它會將hello給覆蓋。所以以我們會使用strcat這個函數(shù)。
現(xiàn)在我們研究一下這個函數(shù): 我們不難發(fā)現(xiàn)這與我們剛才看到的strcpy的參數(shù)是一樣的,第一個參數(shù)是char * destination,是我們需要追加的對象,第二個參數(shù)是const char * source,是需要追加的內(nèi)容。這個函數(shù)的返回值的路線是char *,返回的是目標空間的起始地址(與我們之前講的strcpy一樣)。 我們現(xiàn)在看看是如何追加的: 這里是在第一個\0的位置開始追加world,我們不免會想這里我們是將world的\0追加過來了,還是原來在arr1中的?我們可以測試看看,我們可以在原arr1中添加一個\0并觀察試試。 兩張圖對比我們可以發(fā)現(xiàn),world從hello后面的\0開始追加,world還把它原來的\0也帶了過來。
strcat的原理:
找到目標空間中的第一個\0然后從這個\0的位置開始追加源頭字符串源頭字符串的內(nèi)容,包括\0都會追加到目標空間
注意: 在追加時目標空間的大小要足夠長,能夠放下我們要追加的數(shù)據(jù)。其次目標空間要可修改,以便追加數(shù)據(jù)。因為會將源頭字符串字符串的\0追加過去,所以我們的源頭字符串要有\(zhòng)0。目標字符串也要有\(zhòng)0,不然不知道從哪里開始追加。
3.2 strcat 函數(shù)的模擬實現(xiàn)
我們之前觀察過它的參數(shù)和返回類型,這些部分是和strcpy一樣的。我們就可以仿照來寫代碼。 我們首先需要在目標空間中找到\0再進行追加,這里我們可以利用while循環(huán)往后找,如果 * dest不等于\0,我們就讓dest++,直到dest指向\0跳出循環(huán)。 之后我們需要將world這個數(shù)據(jù)拷貝過去 ,這個拷貝數(shù)據(jù)的代碼我們觀察已經(jīng)學(xué)習(xí)過了,我們可以照搬過來。剛才dest已經(jīng)指向了\0,現(xiàn)在我們進行拷貝就會將\0覆蓋。 接下來我們要返回char * 的數(shù)據(jù),即返回目標空間的起始地址,但是我們的dest通過++已經(jīng)走很遠了,我們可以仿照剛才的ret來存放目標空間的起始地址,最后返回ret。當然如果擔心我們的指針有效性也可以采用assert斷言一下。
代碼:
#include
#include
char* my_strcat(char* dest, const char* src)
{
char* ret = dest;
assert(dest && src);
while (*dest != '\0')
dest++;
while (*dest++ = *src++)
{
;
}
return ret;
}
int main()
{
char arr1[20] = "hello ";
my_strcat(arr1, "world");
printf("%s\n", arr1);
return 0;
}
運行結(jié)果: 思考: 我們能不能自己給自己追加? 進行傳參以后我們的dest和src指向的位置為:
程序開始運行以后我們的dest會一直往后找,直到找到\0,之后開始拷貝,此時h就會將原來的\0覆蓋,沒有\(zhòng)0以后拷貝的這個循環(huán)是停不下來的,就會造成死循環(huán)的問題。 所以我們通常是不會用這個函數(shù)進行對自己追加的操作的。strcat在進行自己給自己追加時會有一定的概率會成功,我們?nèi)绻胍约航o自己追加可以使用strncat。 好了今天知識就學(xué)習(xí)到這里,我們下期blog再見!如果文章內(nèi)容有誤,請大佬在評論區(qū)斧正!謝謝大家!
柚子快報邀請碼778899分享:c語言 字符串函數(shù)(2)
精彩文章
本文內(nèi)容根據(jù)網(wǎng)絡(luò)資料整理,出于傳遞更多信息之目的,不代表金鑰匙跨境贊同其觀點和立場。
轉(zhuǎn)載請注明,如有侵權(quán),聯(lián)系刪除。