Linux C 字符串輸入函數 gets()、fgets()、scanf() 詳解


一、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;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM