字串輸入輸出函式
字串輸入
字串輸入首先要考慮的是儲存位置和儲存空間大小。
例子:char *name; scanf("%s", name); 指標未初始化,可能指向記憶體中的任意地方,字串讀入的時候有可能覆蓋記憶體中的關鍵資料,造成程式或機器崩潰。
char name[81]; 這種方式顯式宣告空間並進行初始化。
gets()函式
scanf("%s", str)函式讀入單個word。而gets()可以讀取一整行的字串。它通過換行符讀取整行,丟棄換行符,儲存剩餘的字元,新增空字元('\0')以建立C字串。一般和puts()搭配使用。
示例:
#include<stdio.h> #define STLEM 81 int main(void) { char words[STLEM]; puts("Enter a string, please."); gets(words); printf("Your string twice:\n"); printf("%s\n", words); puts(words); puts("Done."); return 0; }
⚠️:gets函式的問題(int puts(const char *);):雖然在定義字串的時候聲明瞭大小為81,但gets函式無法阻止輸入超過大小的字串長度,編譯執行時將報錯"warning: this program uses gets(), which is unsafe."
gets函式只接收一個引數,char型別的指標。沒有其他引數限定使用者輸入的字串長度。gets()只知道陣列的開始位置,卻不知道陣列的長度。
如果輸入字串太長,則會出現緩衝區溢位,這意味著多餘的字元溢位了指定的目標。 額外的字元可能只是進入未使用的記憶體並且沒有立即出現問題,或者它們可能會覆蓋程式中的其他資料,但這些肯定不是唯一的可能性。
C99已經不建議使用該函式,並建議將gets()從標準庫中移除,C11已經將gets()從標準中移除。但C的標準只是要求編譯器廠商必須符合所要求的標準,而不是限制編譯器廠商不能做什麼。所以,主流的編譯器依然支援gets()函式已確保向後相容。
gets()的替代品fgets()
為了解決gets()函式容易引起的緩衝區溢位問題,出現了fgets()函式。char fgets(char __restrict, int, FILE *);
fgets()與gets的主要不同點為:
- fgets()有第二個引數,是一個int型別,限定輸入的字串最大長度。
- fgets()讀取換行符('\n'),並儲存在字串中,而不是像gets(),丟棄換行符。
- fgets()有第三個引數,預設為檔案輸入。也可以讀取鍵盤輸入,引數為stdin。
示例:
#include<stdio.h> #define STLEN 14 int main(void) { char words[STLEN]; puts("Enter a string, please."); fgets(words, STLEN, stdin); printf("Your string twice (puts(), then fputs()):\n"); puts(words); fputs(words, stdout); puts("Enter another string, please."); fgets(words, STLEN, stdin); printf("Your string twice (puts(), then fputs()):\n"); puts(words); fputs(words, stdout); puts("Done."); return 0; }
輸出:
Enter a string, please.
apple pie
Your string twice (puts(), then fputs()):
apple pie
apple pie
Enter another string, please.
strawberry shortcake
Your string twice (puts(), then fputs()):
strawberry sh
strawberry shDone.
解讀:
- 首先輸入"apple pie",字串長度小於14,使用fgets()時會保留換行符。使用puts()輸出時,會額外新增一個換行符(因為gets()丟棄了換行符),則puts()會輸入"\n\0\n"。fputs()會按words實際儲存的進行輸出。因此,兩個"apple pie"之間的空行就是puts()函式額外新增的。
- 其次輸入"strawberry shortcake",字串長度超過14,則僅保留13個有效當前輸入(額外一個留給'\0')。故使用puts()時輸出"strawberry sh\0\n"('\n'為額外新增)。而使用fputs()輸出時,不會新增'\n',故直接輸出"strawberry sh",緊接著是"Done."。
fgets()函式的返回值為指向char型別的指標。指標指向輸入的字串地址。但是,如果函式遇到檔案結尾,則返回一個稱為空指標的特殊指標。這是一個確保不指向有效資料的指標,因此它可用於指示特殊情況。在程式碼中,該指標可以表示為數字0,或者在C中更通常的表示方法為巨集NULL。
fgets()的輸入問題
由於fgets()會儲存換行符,這就帶來了一些問題。有時,你不想儲存該換行符,而僅僅將使用者輸入的換行符作為buffered I/O的一個指令,通知臨時儲存區的資料可以被fgets()函式使用。
如何丟棄換行符?
while (words[i] != '\n')
i++;
words[i] = '\0';
words為通過fgets()得到的輸入字串,將換行符替換為'\0'。
如果想將超過陣列定義大小的剩餘字串也丟棄掉,以免作為下次的輸入,如何做到呢?
while (getchar() != '\n')
continue;
Null字元和NULL指標
NULL字元或'\0',用來標記字串的結束。程式碼為0的字元,ASCII 為0的字元。
空指標或NULL具有與有效資料地址不對應的值。 它通常由函式使用,否則返回有效地址以指示某些特殊情況,例如遇到檔案結束或未能按預期執行。
另外,空字元是int型別,而NULL指標是指標型別。
gets_s()函式
C11引入了一個新函式gets_s(),類似fgets(),但和fgets也有不同之處。
- gets_s()只讀取標準輸入,因此不需要第三個引數如stdin。
- 如果get_s()讀取到換行符,則丟棄換行符,而不是和fgets()一樣,保留它。
- 如果gets_s()讀取最大字元數並且無法讀取換行符,則需要幾個步驟。 它將目標陣列的第一個字元設定為空字元。 它會讀取並丟棄後續輸入,直到遇到換行符或檔案結尾。 它返回空指標。 它呼叫依賴於實現的“處理程式”函式(或者選擇的函式),這可能導致程式退出或中止。
輪子
char * s_gets(char *st, int n)
{
char *ret_val;
int i = 0;
ret_val = fgets(st, n, stdin);
if (ret_val)
{
while (st[i] != '\n' && st[i] != '\0')
i++;
if (st[i] == '\n')
st[i] = '\0';
else
{
while (getchar() != '\n')
continue;
}
}
return ret_val;
}
s_gets()函式為自定義函式,可以丟棄fgets()函式保留的換行符,同時丟棄超過最大字元數的剩餘輸入。
scanf()函式
一般我們使用scanf()函式和%s格式讀取字串。scanf()更像是讀取一個單詞,而不是一個字串。
示例:
Input Statement | Original Input Queue* | Name Contents | Remaining Quenue |
---|---|---|---|
scanf("%s', name); | Fleebert#Hup | Fleebert | #Hup |
scanf("%5s", name); | Fleebert#Hup | Fleeb | ert#Hup |
scanf("%5s", name); | Ann#Ular | Ann | #Ular |
‘#’代表space character(/t, /b等)。
可以到,scanf會在遇到空白字元或最大字元時讀取結束,其他字元將再下次輸入時讀取。scanf的返回值為成功讀取到的字串個數。