補遺篇之C字符串

    C並沒有字符串類型,C字符串是一個以null('\0')結尾的字符數組,用null標識字符串結束。如{'a','b','c','d','\0'},只有包含這個'\0’纔算C字符串。注意null也好,'\0'也罷,都只是0的不同表達,如:

    char a[] = “ABCDEF”;

    printf(“a=%s\n”,a);

    a[2]=0;

    printf(“a=%s\n”, a);

    運行看看,明白什麼是null以及它的作用了吧!

字符串與字符

    要注意區分'a'和"ABCDEF"這兩種形式。單引號修飾的單字符實際代表一個整數,其值爲該字符在編譯器默認字符集中(一般爲ascii)的映射值。如char x =’a’;ascii表,等價於char x = 0x61。而用雙引號修飾的字符序列,即字符串常量,可看作無名字符數組,該數組被雙引號中的字符集和一個隱含的'\0’初始化。

字符串與字符數組

    結尾是否有'\0'是字符串和一般字符數組的唯一區別,{'a','b','c','d’,'\0'}是字符串,而{'a','b','c','d’}是字符數組。這兩種形式很好區分,但定義太繁瑣,於是C爲了簡便,引入另一種字符串定義形式,即"abcd”,它等價於{'a’,'b’,'c’,'d’,'\0’}。這種定義更簡單,但外表看不到null(儘管編譯器知道),需要不斷自我提醒"abcd”是字符串,結尾隱含'\0’。可看不見摸不到總是容易混淆。如:

    char a[] = “ABCDEF”;    

    char b[] = {'A','B','C','D','E’,'F'};

    敘述正確的是:A)ab完全相同  B)ab長度相同  C)ab都是字符串 D)a數組比b數組長

    只有D。因爲"ABCDEF"等價{'A','B’,'C’,'D’,'E’,'F’,'\0'},因此ab並不相同,a長度爲7b長度爲6a爲字符串,b只是普通字符數組。

編程中幾乎所有和C字符串相關的錯誤都和這個隱含的'\0’相關。

strxxxmemxxx

    除標準字符串處理函數外,還有一些函數也能操作字符串,如memcpy代替strcpymemcmp代替strcmp等。這兩套怎麼選擇?

    a. 首先,非字符串操作,不能用strxxx,如:

    char a[4],b[4],c[4];

    a[0]=0; a[1]=2; a[2]=0; a[3]=1;

    memcpy(b, a, 4);

    strncpy(c, a, 4);

    源數據a[4]中間有0strncpy會把0當成字符串結束符而提前終止拷貝。而memcpy執行內存拷貝,不受內容影響。

    b. 對於字符串操作,都可以用。strxxx專門針對字符串特性,遇到null自動結束,使用起來更方便。

'\0’的代價

    字符串默認以'\0’結尾,標識字符流結束,區分字符串和一般字符數組,表達形式更自然。但作爲代價,'\0’也引出很多問題:

    a. 字符串中除結尾外不能包含任何0,所以它不能保存二進制數據。

    b. 要搞清哪些strxxx結尾自動補'\0’,哪些不補。如strncpy(dst, src, n)不自動補0,如果dst在操作前未清零,而nsrc只填充部分dst,那dst就可能因缺少結束符而成爲非法字符串。

    c. 而strcpy(dst,src)中,如果src是未置'\0’的字符數組,strcpy就找不到結束符而死循環。兩害擇其輕,最好strnxxx()代替strxxx()

    d. strxxx每次都判斷是否null,性能較差且隨字串長度變化(油漆工的故事)。如下strcat:

    void strcat( char* dest, char* src )

    {

     while (*dest) dest++;

     while (*dest++ = *src++);

    }

    char longstr[1000];

    longstr[0] = '\0';

    strcat(longstr,"John, ");

    strcat(longstr,"Paul, ");

    strcat每次都要掃描整個dst字符串,尋找尾部'\0’,字符串越長,性能越差。需要注意字符串操作的性能不確定性。

避免字符串硬編碼

    字符串一般不是程序的關鍵角色,程序員往往直接把它們隨意寫到代碼裏,如:

    if (..) {  MessageBox(_T("1.0.0.1, xxx Company");  }

    但是,這種字符串硬編碼會帶來如下問題:

    a. 可讀性不好且無法集中修改:裸字符串和奇異數一樣,含義不明確,如果分散寫在代碼裏,難以統一維護。最好用define或全局const變量來表示,即反邊統一修改維護,也能明確顯示含義,如:

#define SOFTWARE_VERSION_INFO ("1.0.0.1, xxx Company ")或const char VersionInfo[] = "1.0.0.1, xxx Company ";

    b. 難以統計資源佔用:字符串常量佔用靜態只讀內存,如果某程序中字符串很多,需要統計資源佔用,把它們提取出來專門放置,比分散在源碼裏更方便統計和優化。

    c. 配置多語言版本:如果字符串硬編碼,要出多語言版,必須一個個去修改字符串,再重新編譯,導致一種語言一份代碼。可事先把所有與顯示相關的字符串放到資源配置文件中(自定義的保存資源的配置文件),配合編譯開關選擇切換不同語言的字符串。

油漆工的笑話:

    某人得到一份油漆工作,負責在馬路中間噴塗畫線。第一天,他帶着一罐漆來到他負責的路段,噴塗了300碼長的線。“幹得不錯!”老闆稱讚道,“真是一位麻利的工匠”,然後賞給他一個硬幣。

    第二天,他只噴塗了150碼。“雖然不如昨天,但仍然算得上一位麻利的工匠!150碼也不錯,”老闆又賞給他一個硬幣。

    然而第三天,他只噴塗了30碼長的馬路。“才30碼!”老闆吼道。“這太令人難以接受了!第一天你的工作量是今天的10倍!怎麼回事?”

   “我盡力了,”油漆工哭喪着臉。“可一天一天下來,我離油漆罐越來越遠!”

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章