1. 程式人生 > >字串輸入輸出函式

字串輸入輸出函式

字串輸入

字串輸入首先要考慮的是儲存位置和儲存空間大小。
例子: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的返回值為成功讀取到的字串個數。

字串輸出