你不見得會計算C字串長度

FooBoy發表於2020-02-26

我個人一直比較喜歡 C 這門程式語言,大學的時候學習了微控制器,剛開始學的時候一臉懵逼,特別是那個組合語言學得簡直讓人懷疑人生。後面開始接觸使用 C 語言編寫微控制器程式,然後就這麼一直愛著它了。平時工作中用到的 C 程式語言不算多,但是我一直沒有放棄它,而是在默默的學習它、研究它,人嗎怎的有個小愛好吧?!

C 字串

在 C 語言中,字串實際上是使用字元 '\0' 終止的一維字元陣列。

以下幾種方式表示的都是 C 字串的正確表達方式。

// 要以 '\0' 結尾
char greeting[6] = {'H', 'e', 'l', 'l', 'o', '\0'};

// 要以 '\0' 結尾
char greeting[] = {'H', 'e', 'l', 'l', 'o', '\0'};

// 預設會在末尾增加'\0'
char greeting[] = {"Hello"};

// 上面的簡寫形式
char greeting[] = "Hello";

// 預設會在末尾增加'\0'
char *greeting = "Hello";
複製程式碼

看下面另外一種宣告方式:

char greeting[] = {'h', 'e', 'l', 'l', 'o'};

printf("greeting: %s\n", greeting);
複製程式碼

輸出結果:

greeting: hello\376
複製程式碼

這個結果在不同編譯器下面可能還會不一樣,總之輸出都不是我們想要的結果。這種方式建立的字串沒有 '\0',不算是真正的 C 字串,所以建議大家在宣告 C 字串的時候使用字元指標(char *)的方式。

string.h 裡面宣告瞭很多關於操作 C 字串的庫函式。

字串長度

這裡在說計算字串長度的前提是字元編碼都是按照UTF-8(中文佔用3個位元組,英文佔用1個位元組)的編碼形式為前提的。我們先來看下面這個例子,如下:

char *greeting1 = "hello";
char greeting2[] = {'h', 'e', 'l', 'l', 'o'};
char greeting3[] = {'h', 'e', 'l', 'l', 'o', '\0'};
char greeting4[] = "hello";
    
printf("greeting1 sizeOf: %ld, strlen: %ld\n", sizeof(greeting1), strlen(greeting1));

printf("greeting2 sizeOf: %ld, strlen: %ld\n", sizeof(greeting2), strlen(greeting2));

printf("greeting3 sizeOf: %ld, strlen: %ld\n", sizeof(greeting3), strlen(greeting3));

printf("greeting4 sizeOf: %ld, strlen: %ld\n", sizeof(greeting4), strlen(greeting4));
複製程式碼

如果你能說出上面 printf 的結果,基本上關於計算字串長度的問題就迎刃而解了。

按照 UTF-8 編碼,上面例子的輸出結果如下所示:

greeting1 sizeOf: 8, strlen: 5
greeting2 sizeOf: 5, strlen: 7
greeting3 sizeOf: 6, strlen: 5
greeting4 sizeOf: 6, strlen: 5
複製程式碼

如果輸出結果令你無法相信,可以選擇繼續往下看或者你自己寫程式碼試試。

sizeof、strlen

linux.die 可以查到 strlen 的說明,如下:

Synopsis:
#include <string.h>
size_t strlen(const char *s);

Description:
The strlen() function calculates the length of the string s, excluding the terminating null byte (aq\0aq).

Return Value:
The strlen() function returns the number of bytes in the string s.
複製程式碼

函式 strlen 返回字串裡的字元數,不包括終止字元 '\0',這裡注意 strlen 是一個 C 的函式,而 sizeof 只是一個操作符。

我們知道,sizeof 操作符的引數可以是陣列、指標、型別、物件、函式等,函式 strlen 的引數只能是字串。

對於 sizeof, 其引數不同時,其返回的值也不一樣,如下:

1、陣列:編譯時分配的陣列空間大小; 2、指標:儲存該指標所用的空間大小(32位機器上是4,64位機器上是8); 3、型別:該型別所佔的空間大小; 4、物件:物件的實際佔用空間大小(這個指的是在 C++ 中); 5、函式:函式的返回型別所佔的空間大小。函式的返回型別不能是 void 型別;

那我們再回頭看看上面的例子,我把要說明的寫在註釋上面了。

// 注意這裡是指標
char *greeting1 = "hello";

// 沒有結束符 '\0',其 strlen 結果不確定
char greeting2[] = {'h', 'e', 'l', 'l', 'o'};

char greeting3[] = {'h', 'e', 'l', 'l', 'o', '\0'};
char greeting4[] = "hello";
    
/* 結果是 8、5 */
/* greeting1是指標,sizeOf計算的是其儲存該指標所用的空間大小,因為我使用的是64位 macOS,所以輸出是8 */
/*strlen 計算的是字元個數但是不包括結束符 '\0'*/
printf("greeting1 sizeOf: %ld, strlen: %ld\n", sizeof(greeting1), strlen(greeting1));

/* 結果是 5、7 */
/* sizeof 計算的是編譯時分配的陣列空間大小,這裡是5 */
/* greeting2沒有結束符,strlen 的計算結果不確定 */
printf("greeting2 sizeOf: %ld, strlen: %ld\n", sizeof(greeting2), strlen(greeting2));

/* 結果是 6、5 */
/* sizeof 計算的是編譯時分配的陣列空間大小,這裡是6,因為多了結束符 */
/*strlen 計算的是字元個數但是不包括結束符 '\0'*/
printf("greeting3 sizeOf: %ld, strlen: %ld\n", sizeof(greeting3), strlen(greeting3));

/* 結果是 6、5,這裡類似上面的情況,不再贅述 */
printf("greeting4 sizeOf: %ld, strlen: %ld\n", sizeof(greeting4), strlen(greeting4));
複製程式碼

小結

1、sizeof 是一個操作符,而 strlen 是 C 語言的庫函式。

2、sizeof 的引數可以是任意資料型別或者表示式,而 strlen 只能以結尾為 '\0' 的字串作引數。

3、sizeof 的結果在編譯時就計算出了,而 strlen 必須在執行時才能計算出來。

4、sizeof 計算資料型別佔記憶體的大小,strlen 計算字串實際長度,要記住 strlen 計算出來的結果不包括結束符 '\0'

5、sizeof 反應的並非真實字串長度而是所佔空間大小,所以memset 初始化字串的時候用 sizeof 較好。

6、系統函式返回值是 char * (字元指標)型別的會在末尾加上結束符 '\0'

7、無論是 sizeof 還是 strlen 計算結果的單位都是位元組。

我們還需要注意一點,strlen 函式,當陣列名作為引數傳入時,實際上陣列就退化成指標了。舉個例子,如下圖所示:

你不見得會計算C字串長度
可以看出傳入進來的引數會被退化為指標。

探索無止境

在文章的開始,我給出了幾種 C 字串的正確表達方式,那我們再來看另外一種。

char greeting[4] = "blog";
複製程式碼

這種方式看起來好像很完美的樣子,其實是不對的,寫個例子給大家,如下:

int main(int argc, const char *argv[])
{
	char greeting[4] = "blog";
	size_t len = strlen(greeting);
	printf("greeting len: %ld\n", len);
	printf("greeting: %s\n", greeting);
	
	return 0;
}
複製程式碼

編譯執行,結果如下:

greeting len: 10
greeting: blog\330\365\277\357\376
複製程式碼

蒼天呀,這結果讓人無語。。。

對於 char greeting[4] = "blog" 其實是定義一個長度為 4 的字元陣列,但是字串 "blog" 實際是要包括結束符 \0 的,也就是說下面的程式碼

char greeting[4] = "blog";
複製程式碼

本質和下面程式碼是一樣的,如下:

char greeting[] = {'b', 'l', 'o', 'g'};
複製程式碼

顯然是不正確的,那我們修改一下程式碼,如下:

int main(int argc, const char *argv[])
{
	// 注意這裡是 5
	char greeting[5] = "blog";
	size_t len = strlen(greeting);
	printf("greeting len: %ld\n", len);
	printf("greeting: %s\n", greeting);
    
	return 0;
}
複製程式碼

或者這樣寫:

int main(int argc, const char *argv[])
{
	// 這裡乾脆不要寫數字
	char greeting[] = "blog";
  	size_t len = strlen(greeting);
	printf("greeting len: %ld\n", len);
	printf("greeting: %s\n", greeting);
    
    return 0;
}
複製程式碼

這樣修改後,再編譯執行結果就對了,如下:

greeting len: 4
greeting: blog
複製程式碼

我們知道的東西是有限的,我們不知道的東西則是無窮的。

在這裡插入圖片描述

相關文章