簡體   English   中英

如何在C中掃描用戶定義的字符串數?

[英]How to scan a user defined number of strings in C?

如果用戶輸入5作為第一個輸入,則程序應繼續掃描5個字符串。

我試過了:

int n;
scanf("%d",&n);
char a[100][10];

for (int i = 0; i < n; i++) {
    scanf("%s", &a[i][10]);
}

但這只會掃描一個單詞然后退出。

考慮到這里給出的大多數答案在某種程度上都有缺陷:
例如,接受的答案指出a[i][10]訪問行中的第十個字符。 這是完全錯誤的。 由於數組的索引為零,因此實際上嘗試訪問行中的第11個字符,這很可能超出范圍。
相同的答案顯示了如何將2D數組分為行和列。 這也是錯誤的。 數組(2D或1D)是內存中的連續塊。 只有一排字符。 數組a[100][10]的實際布局如下所示。 我剛剛注意到答案提到了這一點,但仍然:

|/a[0][0] ===============================\\===============>a[99][9]\|
|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|

連續僅100 * 10個字符。 然后訪問a[1][0]與訪問a[0][10]或實際上是a[10] 如果i為99,則會出現使用a[i][10]風險,因為如您所見,數組中的最后一個char位於a[99][9] 超出該限制的所有內存均無法使用。
給定的示例以行和列格式輸入了a,b和c,則該示例看起來不是這樣的:

_ _ _ _ _ _ _ _ _ a 
_ _ _ _ _ _ _ _ _ b
_ _ _ _ _ _ _ _ _ c

而是,它看起來像這樣:

0 1 2 3 4 5 6 7 8 9 
_ _ _ _ _ _ _ _ _ _
a _ _ _ _ _ _ _ _ _ 
b _ _ _ _ _ _ _ _ _
c _ _ _ _ _ _ _ _ _

@TomFenech的答案要好得多,在這里使用fgets很有意義。 但是,要在i上加點,然后在T上加點:nget終止的char不會被fgets讀取。 該函數最多讀取n-1個字符,其中n是指定的最大長度,如文檔所述:

fgets函數從stream指向的流到s指向的數組中讀取的字符數最多少於n指定的字符數。 在換行符(保留)或文件結束之后,不會讀取其他字符。

但是,只有在遇到斷行或EOF時, fgets才會停止讀取。 遇到NUL字符后,它不會停止。 請記住這一點,但就您而言,不必擔心。

但是,在使用fgets時,可以找到大量的片段,它們稱為fflush(stdin); 預先清潔輸入緩沖區。 但是,這會調用未定義的行為,您可以很好地讀取緩沖區,直到緩沖區為空為止。 湯姆·芬奇(Tom Fench)忽略了這一點,為了完整起見,我也想在這里提及。 除此之外,我會說他的答案是正確的。

因此,考慮到這一點,這是我的建議:

取消使用固定大小的緩沖區。 a將包含多少個字符串取決於用戶輸入。 這意味着數組本身應該是可變長度之一。 幸運的是,C(因為C99)滿足了您的所有需求:可變長度數組-或簡稱為VLA在這里會更可取。 這是我作為概念的快速測試/證明編寫的:

#include <stdio.h>
#include <stdlib.h>

int main ( void )
{
    int i, n;
    puts("How many words do you wish to enter?");
    scanf(" %d", &n);//get size of input array
    while((i=getchar()) != EOF && i != '\n');//"flush" stdin
    char a[n][10];
    printf("Please enter %d words now\n", n);
    for (i=0;i<n;++i)
        fgets(a[i], 9, stdin);//get 9 char input, not 10 (10th char is nul terminator)
    for (i=0;i<n;++i)
        printf("Line %d: %s\n", i+1, a[i]);//print each input line
    return 0;//return
}

您可以使用gcc在* NIX系統上編譯此文件:

$ gcc -std=c99 your_code.c -o progName
$ ./progName

並運行它。 它按預期工作。
請注意,每個條目的輸入緩沖區只有10個字符。 這意味着像“成功”這樣的單詞是不可行的(它的長度為10個字符,但是字符串也需要以NUL結尾的字符)。 如果您是我,我會選擇對實際輸入使用更大的緩沖區。
由於我們使用的是VLA,因此默認情況下我們不再使用1000字節的堆棧空間( a[100][10]使用那么多)。 如果n值為10,我們可以使緩沖區大100個字符,但最終仍不使用比現在更多的內存。 所以也許考慮使用:

char a[n][24];
for (i=0;i<n;++i)
    fgets(a[i], 24, stdin);

理想情況下,您還應該在循環中的每個fgets調用之后清理stdin緩沖區:

int j;//for clearing the buffer
for (i=0;i<n;++i)
{
    fgets(a[i], 24, stdin);
    if (strlen(a[i]) >= 23)//buffer overflow averted, clear stdin
        while ((j = getchar()) != EOF && j != '\n');//clear buffer
}

如果不這樣做,則將緩沖區保留為10,則可能會遇到以下情況:

Please enter 2 words now
successful?
Line 1: successfu
Line 2: l?

完整的代碼現在看起來像這樣:

#include <stdio.h>
#include <stdlib.h>
#include <string.h> //add this

int main ( void )
{
    int i, j, n;//extra temp var
    puts("How many words do you wish to enter?");
    scanf(" %d", &n);
    while((j=getchar()) != EOF && j != '\n');
    char a[n][10];
    printf("Please enter %d words now\n", n);
    for (i=0;i<n;++i)
    {
        fgets(a[i], 10, stdin);
        if (strlen(a[i] >= 9)//<-- added
            while ((j=getchar()) != EOF && j != '\n');//added
    }
    for (i=0;i<n;++i)
        printf("Line %d: %s\n", i+1, a[i]);
    return 0;//return
}

但是,該程序以完全相同的方式編譯和運行。
如果您只想通過兩個字作為輸入,如果您想容納更大的緩沖區,那么您也可以使用VLA:

#include <stdio.h>
#include <stdlib.h>
#include <string.h> //add this

//max buffer total 1000 chars is what you are using with a[100][10]
#define MAX_TOTAL_BUFFER 1000
//don't allow more than 80 chars per entry
#define MAX_SINGLE_BUFFER 80
//added for safety
#define MIN_SINGLE_BUFFER 10

int main ( void )
{
    int i, j, n. buf_len;
    puts("How many words do you wish to enter?");
    scanf(" %d", &n);
    buf_len = MAX_TOTAL_BUFFER/n; // int/int yields int 1000/3 == 333
    if (buf_len > MAX_SINGLE_BUFFER)
        buf_len = MAX_SINGLE_BUFFER;
    else if (buf_len < MIN_SINGLE_BUFFER) //mind you, risk of stack overflow here
        buf_len = MIN_SINGLE_BUFFER;
    while((j=getchar()) != EOF && j != '\n');
    char a[n][buf_len];
    printf("Please enter %d words (max %d chars long)\n", n, buf_len-1);
    for (i=0;i<n;++i)
    {
        fgets(a[i], buf_len, stdin);
        if (strlen(a[i] >= buf_len)//<-- added
            while ((j=getchar()) != EOF && j != '\n');//added
    }
    for (i=0;i<n;++i)
        printf("Line %d: %s\n", i+1, a[i]);
    return 0;//return
}

嘗試使用緩沖區的最大值和最小值,並查看可以將其推送多遠,直到堆棧最終溢出為止。 從用戶那里獲得n后,還要檢查它的值。 如果用戶傳遞0 ,則該程序無法正確處理。 再次提示用戶輸入正n,使用默認值,或退出。
如果n為負,則使用其絕對值(或乘以-1),再次提示或退出...所有這些都為交互式CLI C程序中的良好練習提供了幫助。 分離邏輯,並編寫函數將有助於將該代碼段轉換為實際的有用代碼。

&a[i][10]的計算結果為第i個陣列中的11要素的地址a -然而,由於你的數組只有十個元素這將最終出界。

您需要第i個數組的地址。 你可以用a[i]來得到。

在這種情況下, scanf很危險,因為所輸入的字符串可能長於數組的長度。 我建議使用fgets

#include <stdio.h>

int main()
{
  int n;
  scanf("%d ",&n); // note the added space
  char a[100][10];
  for (int i = 0; i < n; i++) {
      fgets(a[i], 10, stdin);
  }
  return 0;
}

fgets的中間參數指定要讀取的最大字符數。 fgets max - 1讀取max - 1字符,並在字符串末尾添加\\0 (空字節)。 使用這種方法可以防止緩沖區溢出,並確保您以有效的字符串結尾。

scanf增加的空間是為了使數字后的換行符也被吞下。

char a[100][10];
_ _ _ _ _ _ _ _ _ _ 
_ _ _ _ _ _ _ _ _ _
_ _ _ _ _ _ _ _ _ _
         |
         |
    100 rOWS and 10 columns
         |
         |
_ _ _ _ _ _ _ _ _ _
_ _ _ _ _ _ _ _ _ _
_ _ _ _ _ _ _ _ _ _

當您執行scanf("%s", &a[i][10]); ,您將僅從stdin輸入每行的tenth字符。

例如,如果n=3且輸入為a,b,c,則邏輯上將看起來像這樣(內存是連續的,但僅出於理解目的,我正在使用行和列)。

_ _ _ _ _ _ _ _ _ a 
_ _ _ _ _ _ _ _ _ b
_ _ _ _ _ _ _ _ _ c

因此,要輸入整個字符串,您應該只給出row的基地址。

scanf("%s", &a[i]);

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM