修補fgets單行讀入不足
2008年9月8日 星期一 by Anati
一般我們在設計中會要求使用者輸入一行資料,但是往往使用者就是不聽話,會乩打都是不是有心的打錯,因此會造成資料不容易取得,今天就來討論看看如何處理這樣的問題,即使資料輸入錯誤可以要求重新輸入。
※ 資料讀入的問題
我想在文字方面從要求一個字元輸入,到要求字串輸入,到要求可以有空白的字串輸入,每種都有不一樣的方法,但是卻沒有一個很好的解決方法,在可以有空白字串的輸入一般在書上是使用gets它可以讀入一行,但是有缺點,也是不安全性的地方,那就是它會一直讀入直到'\n'換行的出現,那如果我的 buffer根本不夠大,那會怎麼樣呢?會就造成記憶體溢位,程式就當了,所以它已經被fgets取代掉了。
※ gets和fgets的比較
* 兩者都是讀到'\n'就會停止,而fgets有限制大小所以大小一到就立刻停
* 使用fgets讀入一行字串,其輸入字串如果小於buffer大小的話會含有'\n',但是使用gets則不會
* gets會一直讀到'\n'才會停止,如果buffer不夠大會造成程式當掉
* fgets可以用在任何的檔案指標且可以用在stdin,但是gets只用在stdin
* fgets可以使資料輸入限定多少字,但gets不行
* 使用fgets讀取stream,如果資料超過你buffer大小,則會取出buffer大小的資料,而剩下的資料會留著給下一次資料做讀取
Example:
char buffer[5];
gets(buffer);
puts(buffer);
輸入:12345678944646
輸出:當掉
輸入:123
輸出:123 (正常)
fgets(buffer, sizeof(buffer), stdin);
puts(buffer);
輸入:123456123456
輸出:1234 (正常空字元占一格)
輸入:12
輸出:12\n (多了換行字元)
while (fgets(buffer, sizeof(buffer), stdin))
{
puts(buffer);
}
輸入:12345678912
輸出:1234 (正常)
輸出:5678 (正常)
輸出:912\n (多了換行字元)
由例子中看到fgets最麻煩的就是換行字元,只要資料輸入不足buffer大小就會出現換行字元'\n'所以要先除掉它,第二就是資料輸入那麼長,可能只要前面的5個字元就好了,後面的字串不想響影下次輸入,這樣的情形以下的想法可能會發生,今天你要取得名字10個字元的字串,班級也是10個字元的字串,現在要開始取資料了,使用者一開始就輸入超過10個字元的字串,這個沒有問題我們的fgets有限大小,所以也讀了10個字元,那剩下那多輸入的列,就留在stream裡面了,這些值是無用值,會造成後面的輸入不正確取得,等你要取班級的10個字元的字串時,你取的是之前留在stream裡面無效用的值,所以我們要清掉我們的stream讓後面不可以一再錯下去。
※ fgets的修正和增加
* 修正取完資料中當資料不足時產生的 '\n'換行字元
* 增加是否要順便清掉stream使得不會讓無用值被下一次取用的時候做為輸入
* 適用在檔案指標或是stdin,若在檔案指標,有可能已檔案最後一行沒有'\n'所以要加上EOF的判斷
Example:
char* gear_fgets(char* buf, int num, FILE* fp, bool ignore)
{
char* find = 0;
if (!fgets(buf, num, fp))
{
return NULL;
}
if ((find = strrchr(buf, '\n')))
{
*find = '\0';
}
else if (ignore)
{
char ch;
while (((ch = fgetc(fp)) != EOF) && (ch != '\n'));
}
return buf;
}
使用和fgets完全一樣的介面所以用法也完全一樣,只是我們修正和增加它一些功能,當取完,如果有錯誤就回傳錯誤,代表fgets不成功,如果成功取得,先看看buffer後端有沒有'\n'如果有的話,取代成'\0'這樣意思為使用者輸入資料沒有超過buffer那就不用為是否要清空 stream做打算,但是如果後端沒有'\n'的話表示資料超過我們的buffer大小,超過的話剩下沒有用的資料會影響下次的取得,所以考慮要不要清掉,這就留給程式設計階段來決定。
※ 如何使用新的fgets
Example:
char buffer[5];
gear_fgets(buffer, sizeof(buffer), fp, false);
輸入:123456789
輸出:1234 (含空字元)
stream:56789\n
gear_fgets(buffer, sizeof(buffer), fp, true);
輸入:123456789
輸出:1234 (含空字元)
stream:null
while (gear_fgets(buffer, sizeof(buffer), fp, true) && buffer[0] != '\0')
{
puts(buffer);
}
資料可以一直輸入,直到輸入空白行停止,用於要多次取得使用者資料。
※ 資料讀入的問題
我想在文字方面從要求一個字元輸入,到要求字串輸入,到要求可以有空白的字串輸入,每種都有不一樣的方法,但是卻沒有一個很好的解決方法,在可以有空白字串的輸入一般在書上是使用gets它可以讀入一行,但是有缺點,也是不安全性的地方,那就是它會一直讀入直到'\n'換行的出現,那如果我的 buffer根本不夠大,那會怎麼樣呢?會就造成記憶體溢位,程式就當了,所以它已經被fgets取代掉了。
※ gets和fgets的比較
* 兩者都是讀到'\n'就會停止,而fgets有限制大小所以大小一到就立刻停
* 使用fgets讀入一行字串,其輸入字串如果小於buffer大小的話會含有'\n',但是使用gets則不會
* gets會一直讀到'\n'才會停止,如果buffer不夠大會造成程式當掉
* fgets可以用在任何的檔案指標且可以用在stdin,但是gets只用在stdin
* fgets可以使資料輸入限定多少字,但gets不行
* 使用fgets讀取stream,如果資料超過你buffer大小,則會取出buffer大小的資料,而剩下的資料會留著給下一次資料做讀取
Example:
char buffer[5];
gets(buffer);
puts(buffer);
輸入:12345678944646
輸出:當掉
輸入:123
輸出:123 (正常)
fgets(buffer, sizeof(buffer), stdin);
puts(buffer);
輸入:123456123456
輸出:1234 (正常空字元占一格)
輸入:12
輸出:12\n (多了換行字元)
while (fgets(buffer, sizeof(buffer), stdin))
{
puts(buffer);
}
輸入:12345678912
輸出:1234 (正常)
輸出:5678 (正常)
輸出:912\n (多了換行字元)
由例子中看到fgets最麻煩的就是換行字元,只要資料輸入不足buffer大小就會出現換行字元'\n'所以要先除掉它,第二就是資料輸入那麼長,可能只要前面的5個字元就好了,後面的字串不想響影下次輸入,這樣的情形以下的想法可能會發生,今天你要取得名字10個字元的字串,班級也是10個字元的字串,現在要開始取資料了,使用者一開始就輸入超過10個字元的字串,這個沒有問題我們的fgets有限大小,所以也讀了10個字元,那剩下那多輸入的列,就留在stream裡面了,這些值是無用值,會造成後面的輸入不正確取得,等你要取班級的10個字元的字串時,你取的是之前留在stream裡面無效用的值,所以我們要清掉我們的stream讓後面不可以一再錯下去。
※ fgets的修正和增加
* 修正取完資料中當資料不足時產生的 '\n'換行字元
* 增加是否要順便清掉stream使得不會讓無用值被下一次取用的時候做為輸入
* 適用在檔案指標或是stdin,若在檔案指標,有可能已檔案最後一行沒有'\n'所以要加上EOF的判斷
Example:
char* gear_fgets(char* buf, int num, FILE* fp, bool ignore)
{
char* find = 0;
if (!fgets(buf, num, fp))
{
return NULL;
}
if ((find = strrchr(buf, '\n')))
{
*find = '\0';
}
else if (ignore)
{
char ch;
while (((ch = fgetc(fp)) != EOF) && (ch != '\n'));
}
return buf;
}
使用和fgets完全一樣的介面所以用法也完全一樣,只是我們修正和增加它一些功能,當取完,如果有錯誤就回傳錯誤,代表fgets不成功,如果成功取得,先看看buffer後端有沒有'\n'如果有的話,取代成'\0'這樣意思為使用者輸入資料沒有超過buffer那就不用為是否要清空 stream做打算,但是如果後端沒有'\n'的話表示資料超過我們的buffer大小,超過的話剩下沒有用的資料會影響下次的取得,所以考慮要不要清掉,這就留給程式設計階段來決定。
※ 如何使用新的fgets
Example:
char buffer[5];
gear_fgets(buffer, sizeof(buffer), fp, false);
輸入:123456789
輸出:1234 (含空字元)
stream:56789\n
gear_fgets(buffer, sizeof(buffer), fp, true);
輸入:123456789
輸出:1234 (含空字元)
stream:null
while (gear_fgets(buffer, sizeof(buffer), fp, true) && buffer[0] != '\0')
{
puts(buffer);
}
資料可以一直輸入,直到輸入空白行停止,用於要多次取得使用者資料。