一、gets() 函數詳解
gets()函數用來從 標准輸入設備(鍵盤)讀取字符串直到 回車結束,但回車符('\n')不屬於這個字符串。
調用格式為:
gets(str);
其中str為字符串變量(字符串數組名或字符串指針)。
gets(str) 函數與 scanf("%s", &str) 相似,但不完全相同,使用 scanf("%s", &str) 函數輸入字符串時存在一個問題,就是如果輸入了 空格 會認為輸入字符串結束。
空格后的字符將作為下一個輸入項處理, 但 gets() 函數將接收輸入的整個字符串直到 回車 為止。
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char **argv)
{
char s[20], *f;
printf("input sth\n");
gets(s); // 等待輸入字符串直到回車結束
puts(s); // 將輸入的字符串輸出
puts("input sth\n");
f = malloc(sizeof(f));
gets(f);
puts(f);
free(f);
return 0;
}
gets()函數詳解 和 缺陷
1、基本信息
原型:
char *gets( char *buffer);
功能描述:
gets()函數從標准輸入中讀取一行文本,並存儲於作為參數傳遞給它的數組中。一行文本由一串字符組成,以一個換行符('\n')結尾。在返回之前,gets()函數丟棄換行符('\n'),取而代之的是以'\0'結尾。
返回值:
讀取成功,函數返回與buffer相同的指針。讀入過程中遇到EOF或發生錯誤,函數返回NULL指針。當返回值為NULL指針時可以用 feof() 和 ferror() 來判斷函數是遇到EOF還是發生錯誤。
例如:
char str[10];
if (gets(str) != NULL) // Danger!
{
printf("str = %s\n", str);
}
2、函數缺陷
gets函數沒有限制它所讀取長度,程序員應該保證buffer有足夠的空間,否則buffer可能無法容納gets所讀取的內容,從而導致堆棧溢出。如果溢出,多出來的字符將被寫入到堆棧中,這就覆蓋了堆棧原先的內容,破壞一個或多個不相關變量的值。

圖1 溢出提示
為了安全起見,可以使用 gets_s() 函數。
二、fgets() 函數詳解
fgets()函數,從流中讀一行或指定個字符
1、基本信息
原型:
char *fgets(char *s, int n, FILE *stream);
功能描述:
從流中讀入 n-1 個字符放入 s 為起始地址的空間內。如果在未讀滿 n-1 個字符之時,已讀到一個換行符('\n')或一個EOF(文件結束標志),則結束本次讀操作,讀入的字符串中最后包含讀到的換行符('\n')。因此,確切地說,調用 fgets() 函數時,最多只能讀入 n-1 個字符。讀入結束后,系統將自動在最后加 '\0',並以 s 作為函數值返回。
形參注釋:
*s:結果數據的首地址;
n-1:一次讀入數據塊的長度,其默認值為1k,即1024;
stream:文件指針
例:如果一個文件的當前位置的文本如下
Love ,I Have
But ........
如果用
fgets(str1, 4, file1);
則執行后 str1="Lov",讀取了 4-1=3 個字符,
而如果用
fgets(str1, 23, file1);
則執行后 str="Love ,I Have",讀取了一行(包括行尾的'\n', 並自動加上字符串結束符'\0')。
2、特別說明
該函數從stream所指的文件中讀取以'\n'結尾的一行(包括'\n'在內)存到緩沖區s中,並且在該行末尾添加一個 '\0'組成完整的字符串。
如果文件中的一行太長,fgets從文件中讀了 n-1 個字符還沒有讀到 '\n',就把已經讀到的 n-1 個字符和一個 '\0' 字符存入緩沖區,文件中剩下的半行可以在下次調用 fgets() 時繼續讀。
如果一次 fgets() 調用在讀入若干個字符后到達文件末尾,則將已讀到的字符串加上 '\0' 存入緩沖區並返回,如果再次調用 fgets() 則返回 NULL,可以據此判斷是否讀到文件末尾。
注意:
對於
fgets()來說,'\n'是一個特別的字符,而'\0'並無任何特別之處,如果讀到'\0'就當作普通字符讀入。如果文件中存在
'\0'字符(或者說0x00字節),調用fgets()之后就無法判斷緩沖區中的'\0'究竟是從文件讀上來的字符還是由fgets()自動添加的結束符,所以fgets()只適合讀文本文件而不適合讀二進制文件,並且文本文件中的所有字符都應該是可見字符,不能有'\0'。
三、scanf() 函數詳解
1、基本信息
函數名:scanf
功 能:執行格式化輸入
用 法:int scanf(char *format[,argument,...]);
scanf()函數是通用終端格式化輸入函數,它從標准輸入設備(鍵盤) 讀取輸入的信息。可以讀入任何固有類型的數據並自動把數值變換成適當的機內格式。
其調用格式為:
scanf("<格式化字符串>", <地址表>);
scanf()函數返回成功賦值的數據項數,出錯時則返回 EOF。
其 控制串 由三類字符構成:
- 格式化說明符;
- 空白符;
- 非空白符;
(A)格式化說明符
格式字符 說明
%a 讀入一個浮點值(僅C99有效)
%A 同上
%c 讀入一個字符
%d 讀入十進制整數
%i 讀入十進制,八進制,十六進制整數
%o 讀入八進制整數
%x 讀入十六進制整數
%X 同上
%c 讀入一個字符
%s 讀入一個字符串
%f 讀入一個浮點數
%F 同上
%e 同上
%E 同上
%g 同上
%G 同上
%p 讀入一個指針
%u 讀入一個無符號十進制整數
%n 至此已讀入值的等價字符數
%[] 掃描字符集合
%% 讀%符號
附加格式說明字符表
修飾符 說明
L/l 長度修飾符 輸入"長"數據
h 長度修飾符 輸入"短"數據
W 整型常數 指定輸入數據所占寬度
* 星號 空讀一個數據
hh,ll同上h,l但僅對C99有效。
(B)空白字符
空白字符會使scanf()函數在讀操作中略去輸入中的一個或多個空白字符,空白符可以是space,tab,newline等等,直到第一個非空白符出現為止。
(C)非空白字符
一個非空白字符會使scanf()函數在讀入時剔除掉與這個非空白字符相同的字符。
注:scanf()控制串知識就介紹到這里(應該比較齊全了^_^),如有遺漏下次補上。下面將結合實際例程,一一闡述.
2、應用舉例
例1.
#include "stdio.h"
int main(int argc, char **argv)
{
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
printf("%d,%d,%d\n", a, b, c);
return 0;
}
運行時按如下方式輸入三個值:
3□4□5 ↙(輸入a,b,c的值)
3,4,5 (printf輸出的a,b,c的值)
(1)&a、&b、&c中的&是地址運算符,分別獲得這三個變量的內存地址。
(2)"%d%d%d"是按十進值格式輸入三個數值。輸入時,在兩個數據之間可以用一個或多個 空格、tab鍵、回車鍵 分隔。
以下是合法輸入方式:
① 3□□4□□□□5↙
② 3↙
4□5↙
③ 3(tab鍵)4↙
5↙
例2.
#include "stdio.h"
int main(int argc, char **argv)
{
int a, b, c;
scanf("%d,%d,%d", &a, &b, &c);
printf("%d,%d,%d\n", a, b, c);
return 0;
}
運行時按如下方式輸入三個值:
3,4,5 ↙(輸入a,b,c的值)
或者
3,□4,□5 ↙(輸入a,b,c的值)
3,□□□4,□5 ↙(輸入a,b,c的值)
......
都是合法的,但是","一定要跟在數字后面,如:
3□,4,□5 ↙就非法了,程序出錯。(解決方法與原因后面講)
3、注意事項
(1)sacnf()中的變量必須使用地址。
int a, b;
scanf("%d%d", a, b); // 錯誤
scanf("%d%d", &a, &b);
(2)scanf()的格式控制串可以使用其它非空白字符,但在輸入時必須輸入這些字符。
例:
scanf("%d,%d", &a, &b);
輸入: 3,4 ↙(逗號與"%d,%d"中的逗號對應)
scanf("a=%d,b=%d", &a, &b);
輸入: a=3,b=4 ↙("a=","b=",逗號與"%d,%d"中的"a=","b="及逗號對應)
(3)在用"%c"輸入時,空格和“轉義字符”均作為有效字符。
例:
scanf("%c%c%c", &c1, &c2, &c3);
輸入:a□b□c↙
結果:a→c1,□→c2,b→c3 (其余被丟棄)
scanf()函數接收輸入數據時,遇以下情況結束一個數據的輸入:(不是結束該scanf函數,scanf函數僅在每一個數據域均有數據,並按回車后結束)。
① 遇空格、“回車”、“跳格”鍵。
② 遇寬度結束。
③ 遇非法輸入。
4、常見問題
問題一:scanf()函數不能正確接受有空格的字符串?如: I love you!
#include "stdio.h"
int main(int argc, char **argv)
{
char str[80];
scanf("%s", str);
printf("%s", str);
return 0;
}
輸入:I live you!
輸出:I
scanf()函數接收輸入數據時,遇以下情況結束一個數據的輸入:(不是結束該scanf函數,scanf函數僅在每一個數據域均有數據,並按回車后結束)。
① 遇空格、“回車”、“跳格”鍵。
② 遇寬度結束。
③ 遇非法輸入。
所以,上述程序並不能達到預期目的,scanf()掃描到"I"后面的空格就認為對str的賦值結束,並忽略后面的"love you!"。這里要注意是"love you!"還在鍵盤緩沖區(關於這個問題,網上我所見的說法都是如此,但是,我經過調試發現,其實這時緩沖區字符串首尾指針已經相等了,也就是說緩沖區清空了,scanf()函數應該只是掃描stdin流,這個殘存信息是在stdin中)。我們改動一下上面的程序來驗證一下:
#include <unistd.h>
#include "stdio.h"
int main(int argc, char **argv)
{
char str[80];
char str1[80];
char str2[80];
scanf("%s", str); // 此處輸入:I love you!
printf("%s", str);
sleep(5); // 這里等待5秒, 告訴你程序運行到什么地方
scanf("%s", str1); // 這兩句無需你再輸入, 是對鍵盤盤緩沖區再掃描
scanf("%s", str2); // 這兩句無需你再輸入, 是對鍵盤盤緩沖區再掃描
printf("\n%s", str1);
printf("\n%s", str2);
return 0;
}
輸入:I love you!
輸出:I
love
you!
好了,原因知道了,那么scanf()函數能不能完成這個任務?回答是:能!別忘了scanf()函數還有一個 %[] 格式控制符(如果對%[]不了解的請查看本文的上篇),請看下面的程序:
#include <unistd.h>
#include "stdio.h"
int main(int argc, char **argv)
{
char string[50];
//scanf("%s", string); //不能接收空格符
scanf("%[^\n]", string);
printf("%s\n", string);
return 0;
}
問題二:鍵盤緩沖區殘余信息問題
#include "stdio.h"
int main(int argc, char **argv)
{
int a;
char c;
do
{
scanf("%d", &a);
scanf("%c", &c);
printf("a=%d c=%c\n", a, c);
//printf("c=%d\n", c);
}
while (c != 'N');
return 0;
}
scanf("%c", &c);這句不能正常接收字符,什么原因呢?我們用 printf("c=%d\n", c); 將C用int表示出來,啟用 printf("c=%d\n", c); 這一句,看看 scanf() 函數賦給c到底是什么,結果是 c=10,ASCII值為10是什么?換行即\n。對了,我們每擊打一下"Enter"鍵,向鍵盤緩沖區發去一個“回車”(\r),一個“換行"(\n),在這里\r被 scanf() 函數處理掉了(姑且這么認為吧^_^),而\n被scanf()函數“錯誤”地賦給了c。
解決辦法:
可以在兩個 scanf() 函數之后加個 fflush(stdin);,還有加 getch(); getchar(); 也可以,但是要視具體 scanf() 語句加那個,這里就不分析了,讀者自己去摸索吧。但是加fflush(stdin);不管什么情況都可行。
函數名: fflush
功 能: 清除一個流
用 法: int fflush(FILE *stream);
#include "stdio.h"
int main(int argc, char **argv)
{
int a;
char c;
do
{
scanf("%d", &a);
fflush(stdin);
scanf("%c", &c);
fflush(stdin);
printf("a=%d c=%c\n", a, c);
}
while (c != 'N');
return 0;
}
這里再給一個用“空格符”來處理緩沖區殘余信息的示例:
運行出錯的程序:
#include <stdio.h>
int main(int argc, char **argv)
{
int i;
char j;
for (i = 0; i < 10; i++)
{
scanf("%c", &j); // 這里%前沒有空格
}
return 0;
}
使用了空格控制符后:
#include <stdio.h>
int main(int argc, char **argv)
{
int i;
char j;
for (i = 0; i < 10; i++)
{
scanf(" %c", &j); // 注意這里%前有個空格
}
return 0;
}
可以運行看看兩個程序有什么不同。
問題三:如何處理scanf()函數誤輸入造成程序死鎖或出錯?
#include <stdio.h>
int main(int argc, char **argv)
{
int a, b, c; // 計算a+b
scanf("%d,%d", &a, &b);
c = a + b;
printf("%d+%d=%d", a, b, c);
return 0;
}
如上程序,如果正確輸入a,b的值,那么沒什么問題,但是,你不能保證使用者每一次都能正確輸入,一旦輸入了錯誤的類型,你的程序不是死鎖,就是得到一個錯誤的結果,呵呵,這可能所有人都遇到過的問題吧?
解決方法:
scanf()函數執行成功時的返回值是成功讀取的變量數,也就是說,你這個scanf()函數有幾個變量,如果scanf()函數全部正常讀取,它就返回幾。但這里還要注意另一個問題,如果輸入了非法數據,鍵盤緩沖區就可能還個有殘余信息問題。
正確的例程:
#include <stdio.h>
int main(int argc, char **argv)
{
int a, b, c; // 計算a+b
while (scanf("%d,%d", &a, &b) != 2)
{
fflush(stdin);
}
c = a + b;
printf("%d+%d=%d", a, b, c);
return 0;
}
