字串輸出入處理

一個程式中字串處理是最重要的,往往因為字串處理的關係造成程式當掉,或是用指標取得的資料不是你所要的,又或是邊界條件的關係,讓你Debug搞了好久,只是錯在一點點小地方,後面我會加上自己修正stdio版本的函式,讓使用更方便。

※ scanf從鍵盤輸入中格式讀取資料

我想scanf()應該大家最常用到的輸入資料的函數了,所以這邊就不多講了,但是它卻具有很特別的妙用,scanf是格式化輸入,所以可以你訂出來的格式,別人套用你的格式來輸入資料。

scanf中空格的意義

使用%s%d%f等等的格式輸入,在讀到目標之前有遇到空白或是TAB或是換行字元,都會自動略過,直遇到整數或是字串或是浮點數,但是如果是用%c的話,那是讀取stdin中第一個字元,那如果是空白列,那可就讀到空白了,因此可改成scanf(" %c")表示要讀取一個字元之前,自動略過前面有空白或是TAB或是換行的字元。

scanf中的略過「*」和集合「[]」

* %*s (跳過讀入一個字串)
* %*d (跳過讀入一個數值)
* %[] (讀取字串,但只讀取包含於集合中的字元)
* %[^] (讀取字串,直讀到包含於集合中出現的字元)
* []的正規表示式,可用「-」來表示從那裡到那裡
* %[a-z]讀取字串中的字元只含a-z的字元,若讀到別的字元或是數字就停止
* %[^0-3]讀取一字串,直讀到遇到0123字元的出現就停止讀取。
* 程式回傳讀入成功的數目

Example:
輸入2007/6/6要取得年月日的話

if (scanf("%d%*[ \n\t/]%d%*[ \n\t/]%d", &a, &b, &c) == 3)
{
printf("year: %d, month: %d, day %d", a, b, c);
}

or

scanf("%d %*c%d %*c%d", &a, &b, &c);

其中%*[ \n\t/]指跳過「空白」「Tab」「換行」「/」這些字元,或是利用scanf中的空白字元,讓讀入自動會跳過空白或TAB或是換行。

由上程式我們可以由自訂的格式輸入來把year, month, day全都讀入,而別人如果沒有造你的格式輸入的話,資料回傳就不會剛剛好等於3,那就是代表資料輸入有錯誤。

Example:
輸入(13, 22, 41)要把數字取出來的話

scanf("%*[ \t\n(]%d%*[ \t\n,]%d%*[ \t\n,]%d%*[ \t\n)]", &a, &b, &c);

仔細的分解一下,就漸漸可以了解這種格式輸入要怎麼活用,如果可以活用的話,那取資料的時候就可以大大減少自己在那邊拆來拆去,拿出自己要的東西了。

※ fgets從鍵盤一次讀入一行

本來應該使用gets的,但是因為gets沒有長度限制,所以你的string buffer要是不夠大的話,輸入只要超過你的buffer那資料就會覆寫到別得資料空間,所以說是非常可怕的,因此使用fgets,可以限定輸入的資料不可以超過你的buffer大小,fgets還有些要注意的。

* 只會讀到n-1個字元,或是讀到'\n'(換行字元)就停了
* 輸入超過buffer大小時,結尾不含'\n'
* 輸入不到buffer大小時,結尾自動會補上'\n'

Example:
修正fgets讓不管是輸入超過或是輸入不到,都不會有換行字元

char* fix_gets(char* buf, int num, FILE* fp)
{
char* find = 0;
fgets(buf, num, fp);
if (find = strrchr(buf, '\n'))
{
*find = '\0';
}
else
{
while (fgetc(fp) != '\n');
}
return buf;
}

如此我從fix_gets讀入到buffer中的資料不會再有的時候有'\n',又有的時候沒有'\n'的問題了,而且也不用管它怎麼做,只要把 buffer的大小告訴它,就自動幫我做的好好的了。而當stream中資料實在太多超過buf的時候,會自動把buf不要取得的資料刪掉。

Example:
呼叫使用修正過的fix_gets函數

char buffer[21];
fix_gets(buffer, 21, stdin);

※ sscanf從陣列中格式化讀取資料

和scanf並沒有兩樣,只是讀取資料是從陣列中取出而已,通常用在我們有一些固定格式的資料,放在這個陣列裡面,而我們要抽離出裡面某些值的時候,可以使用sscanf,而它也具有scanf格式化的特性,如此就不用自己寫一堆判斷式了。

Example:
從buffer格式化讀出需要的資料

char str[] = "2007/3/25";
if (sscanf(str, "%d%*c%d%*c%d", &a, &b, &c) == 3)
{
printf("%d %d %d\n", a, b, c);
}

※ printf格式化資料輸出

我想printf應該是寫C第一個用到的函式吧!除了該有的格式以外,它格式化輸出,有兩種修飾字「旗標」和「數字」還有「.數字」,以下就來解釋一下這幾個東西分別的意義。

旗標

* - 靠左對齊 ex: %-20s
* + 若是正數就加正號,負數就加負號 ex: %+6.2f
* # 輸出8或16進位時,會加上0(八進制)或是0x(十六進制) ex: %#x
* 0 實際數值前的位置全部放0,而非空白字元 ex: %010d

數字

* 設定最少使用長度,如果顯示的數字或是字串超過的話,會使用更多的位數 ex: %4d

. 數字

* 對%f, %e,它代表小數點右邊有幾個數字 ex: %.2f (小數點後2位)
* 對%s,它代表最多的輸出字元數 ex: %.5s (只輸出字串前5個字)
* 對%d,它代表最少要出現幾個數字,不夠的自動補0 ex: %.10d (輸出數字不滿10位,則前面自動補0)

Example:
各種printf的格式化輸出

printf("%10d", 123); output: _______123
printf("%-10d", 123); output: 123_______
printf("%10.3f", 3345.67); output: __3345.670
printf("%010.2f", 3345.67); output: 0003345.67
printf("%5.3d", 8); output: __008
printf("%10.5s", "hello world"); output: _____hello
printf("%-10.5s", "hello world"); output: hello_____

※ puts將字串輸出且自動加上換行符號

使用puts(data);相同於使用printf("%s\n", data);但是使用puts比較簡短方便,而且會自動加上換行字元,真的是非常的棒,所以單一要輸出一個字串不混合輸出的時候,使用puts比較方便。

※ sprintf格式化的把資料寫入buffer中

sprintf是非常好用的函式,最常拿來使用型別轉換了,因為要把數字或是浮點數轉換成字串,在標準函式庫裡面是沒有的,因此使用sprintf最好用了,只要指一下格式,就可以把資料寫到我們的buffer中了。

Example:
將100數值轉成字串

char* itoa(char* to, int from, int num)
{
char tmp[11]; (int最大數表示為2147483648剛好10位要加上'\0')
sprintf(tmp, "%d", from);
fix_strcpy(to, tmp, size); (修正過指定大小的strcpy下面會說)
return to;
}

※ strcmp比較兩字串是否相同

比較兩字串是否相同,如果相同的話回傳0,不同的話可能是正值也可能是負值,不過最常使用的就是看有沒有回傳0了,因此程式可以寫成這樣。

Example:
比較兩字串是否相等

const char* str1 = "hello world";
const char* str2 = "helloworld";
if (!strcmp(str1, str2))
{
do something...
}

※ strncpy複製字串到buffer中

本來應該使用strcpy的但是因為strcpy沒有限字元數,因此如果buffer不夠大的話,複製過去多的字元可能會覆寫到其它的記憶體,造成程式當掉,因此轉而使用strncpy比較安全。

* 會複製n個字元,或是讀到'\0'(空字元)為止
* 複製來源少於目標時,連'\0'都會複製
* 複製來源多於目標時,'\0'要自行加上去

Example:
修正strncpy讓複製n-1字元過去,而且都會自動加上'\0'

char* fix_strcpy(char* to, const char* from, int num)
{
int size = num-1;
strncpy(to, from, size);
if (strlen(from) >= size)
{
to[size] = '\0';
}
return to;
}

如此我們在使用上,就不用管它怎麼做的了,只需要將我們的buffer的大小直接傳給它,就會自動幫我做的好好的了。

Example:
使用修正版的strcpy對字串做複製

char buffer[11];
const char* str = "hello world!!";
fix_strcpy(buffer, str, 11);

※ strlen計算一字串的元字數

strlen計算字元數可以用於指標或是陣列,只要它最後面有'\0'空字元者都可以使用,而回傳的是不含空字元的數量。

1 意見:

    On 2014年10月24日 上午2:54 匿名 提到...

    請問在fix_fgets的範例中,

    char* find = 0;

    這樣的寫法是正確的嗎?

    執行上沒有問題.