จะอ่านบรรทัดจากคอนโซลใน C ได้อย่างไร?


108

วิธีที่ง่ายที่สุดในการอ่านบรรทัดเต็มในโปรแกรมคอนโซล C คืออะไรข้อความที่ป้อนอาจมีความยาวผันแปรและเราไม่สามารถตั้งสมมติฐานเกี่ยวกับเนื้อหาได้


คุณช่วยชี้แจงได้ไหม ดังที่ @Tim กล่าวไว้ด้านล่างทำให้สับสนในสิ่งที่คุณต้องการ :)
warren

คำตอบ:


81

คุณต้องมีการจัดการหน่วยความจำแบบไดนามิกและใช้fgetsฟังก์ชันเพื่ออ่านบรรทัดของคุณ อย่างไรก็ตามดูเหมือนว่าจะไม่มีทางที่จะเห็นว่ามันอ่านกี่ตัวอักษร ดังนั้นคุณใช้ fgetc:

char * getline(void) {
    char * line = malloc(100), * linep = line;
    size_t lenmax = 100, len = lenmax;
    int c;

    if(line == NULL)
        return NULL;

    for(;;) {
        c = fgetc(stdin);
        if(c == EOF)
            break;

        if(--len == 0) {
            len = lenmax;
            char * linen = realloc(linep, lenmax *= 2);

            if(linen == NULL) {
                free(linep);
                return NULL;
            }
            line = linen + (line - linep);
            linep = linen;
        }

        if((*line++ = c) == '\n')
            break;
    }
    *line = '\0';
    return linep;
}

หมายเหตุ : Never use gets! ไม่ทำการตรวจสอบขอบเขตและสามารถล้นบัฟเฟอร์ของคุณได้


ข้อแม้ - ต้องตรวจสอบผลการจัดสรรใหม่ที่นั่น แต่ถ้าล้มเหลวแสดงว่ามีปัญหาที่แย่กว่านั้นมากที่สุด
ทิม

4
คุณสามารถปรับปรุงประสิทธิภาพได้เล็กน้อยโดยทำ fgets ด้วยบัฟเฟอร์และตรวจสอบว่าคุณมีอักขระขึ้นบรรทัดใหม่ในตอนท้ายหรือไม่ หากไม่เป็นเช่นนั้นให้จัดสรรบัฟเฟอร์การสะสมของคุณใหม่คัดลอกลงในบัฟเฟอร์และสร้างใหม่อีกครั้ง
Paul Tomblin

3
ฟังก์ชันนี้ต้องการการแก้ไข: บรรทัด "len = lenmax;" หลังจาก realloc ควรนำหน้า realloc หรือควรเป็น "len = lenmax >> 1;" - หรือสิ่งอื่นที่เทียบเท่าซึ่งอธิบายถึงความจริงที่ว่ามีการใช้ความยาวครึ่งหนึ่งแล้ว
Matt Gallagher

1
@Johannes เพื่อตอบคำถามของคุณวิธีการของ Paul อาจเร็วกว่าในการใช้งาน libc ส่วนใหญ่ (เช่น reentrant) เนื่องจากวิธีการของคุณล็อค stdin โดยปริยายสำหรับทุกอักขระในขณะที่เขาล็อคหนึ่งครั้งต่อบัฟเฟอร์ คุณสามารถใช้อุปกรณ์พกพาได้น้อยลงfgetc_unlockedหากความปลอดภัยของเธรดไม่ได้เป็นปัญหา แต่ประสิทธิภาพนั้น
vladr

3
โปรดทราบว่าสิ่งนี้getline()แตกต่างจากgetline()ฟังก์ชันมาตรฐาน POSIX
Jonathan Leffler

28

หากคุณกำลังใช้ไลบรารี GNU C หรือไลบรารีที่สอดคล้องกับ POSIX อื่นคุณสามารถใช้getline()และส่งผ่านstdinไปยังไลบรารีสำหรับสตรีมไฟล์ได้


16

การใช้งานที่ง่ายมาก แต่ไม่ปลอดภัยในการอ่านบรรทัดสำหรับการจัดสรรแบบคงที่:

char line[1024];

scanf("%[^\n]", line);

การใช้งานที่ปลอดภัยยิ่งขึ้นโดยไม่มีความเป็นไปได้ที่จะเกิดบัฟเฟอร์ล้น แต่มีความเป็นไปได้ที่จะไม่อ่านทั้งบรรทัดคือ:

char line[1024];

scanf("%1023[^\n]", line);

ไม่ใช่ 'ความแตกต่างโดยหนึ่ง' ระหว่างความยาวที่ระบุประกาศตัวแปรและความยาวที่ระบุในสตริงรูปแบบ เป็นสิ่งประดิษฐ์ทางประวัติศาสตร์


14
แบบนี้ไม่ปลอดภัยเลย มันต้องทนทุกข์ทรมานจากปัญหาเดียวกันว่าทำไมจึงgetsถูกลบออกจากมาตรฐานทั้งหมด
Antti Haapala

6
หมายเหตุผู้ดำเนินรายการ:ความคิดเห็นข้างต้นอ้างถึงการแก้ไขคำตอบก่อนหน้านี้
Robert Harvey

13

ดังนั้นหากคุณกำลังมองหาข้อโต้แย้งคำสั่งลองดูคำตอบของ Tim หากคุณต้องการอ่านบรรทัดจากคอนโซล:

#include <stdio.h>

int main()
{
  char string [256];
  printf ("Insert your full address: ");
  gets (string);
  printf ("Your address is: %s\n",string);
  return 0;
}

ใช่มันไม่ปลอดภัยคุณสามารถทำบัฟเฟอร์โอเวอร์รันได้ไม่ตรวจสอบจุดสิ้นสุดของไฟล์ไม่รองรับการเข้ารหัสและสิ่งอื่น ๆ อีกมากมาย อันที่จริงฉันไม่คิดด้วยซ้ำว่ามันทำอะไรได้บ้าง ฉันยอมรับว่าฉันเมาแล้ว :) แต่ ... เมื่อฉันเห็นคำถามเช่น "จะอ่านบรรทัดจากคอนโซลใน C ได้อย่างไร" ฉันคิดว่าคน ๆ หนึ่งต้องการอะไรง่ายๆเช่น gets () ไม่ใช่ 100 บรรทัดของรหัส เหมือนข้างบน. อันที่จริงฉันคิดว่าถ้าคุณพยายามเขียนโค้ด 100 บรรทัดในความเป็นจริงคุณจะทำผิดพลาดมากกว่าที่คุณจะทำถ้าคุณได้รับ;)


1
ไม่อนุญาตให้มีสายยาว ... - ซึ่งฉันคิดว่าเป็นประเด็นสำคัญของคำถามของเขา
ทิม

2
-1 ไม่ควรใช้ gets () เนื่องจากไม่ได้ทำการตรวจสอบขอบเขต
ผ่อนคลาย

7
ในทางกลับกันหากคุณกำลังเขียนโปรแกรมสำหรับตัวคุณเองและเพียงแค่ต้องอ่านข้อมูลนี้ก็ใช้ได้ดี ความปลอดภัยที่โปรแกรมต้องการมากน้อยเพียงใดก็ตรงตามข้อกำหนด - คุณไม่จำเป็นต้องให้ความสำคัญกับทุกครั้ง
Martin Beckett

4
@Tim - ฉันต้องการเก็บประวัติทั้งหมดไว้ :)
Paul Kapustin

4
โหวตลดลง getsไม่มีอีกแล้วจึงใช้ไม่ได้ใน C11
Antti Haapala

11

คุณอาจต้องใช้อักขระทีละอักขระ (getc ()) ลูปเพื่อให้แน่ใจว่าคุณไม่มีบัฟเฟอร์ล้นและไม่ตัดทอนอินพุต


9

getline ตัวอย่างที่รันได้

กล่าวถึงคำตอบนี้แต่นี่คือตัวอย่าง

มันคือPOSIX 7จัดสรรหน่วยความจำให้เราและนำบัฟเฟอร์ที่จัดสรรมาใช้ซ้ำบนลูปอย่างดี

Pointer newbs อ่านสิ่งนี้: เหตุใดอาร์กิวเมนต์แรกของ getline จึงเป็นตัวชี้ไปที่ตัวชี้ "char **" แทนที่จะเป็น "char *"

#define _XOPEN_SOURCE 700

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

int main(void) {
    char *line = NULL;
    size_t len = 0;
    ssize_t read = 0;
    while (read != -1) {
        puts("enter a line");
        read = getline(&line, &len, stdin);
        printf("line = %s", line);
        printf("line length = %zu\n", read);
        puts("");
    }
    free(line);
    return 0;
}

การใช้งาน glibc

ไม่มี POSIX? บางทีคุณอาจต้องการที่จะดูที่การดำเนินงาน glibc 2.23

มันแก้ไขเป็นgetdelimซึ่งเป็นส่วนเหนือ POSIX แบบง่ายของgetlineตัวยุติบรรทัดโดยพลการ

เพิ่มหน่วยความจำที่จัดสรรเป็นสองเท่าเมื่อจำเป็นต้องเพิ่มและดูปลอดภัย

ต้องมีการขยายมาโคร แต่คุณไม่น่าจะทำได้ดีกว่านี้


จุดประสงค์ของlenที่นี่คืออะไรเมื่ออ่านแล้วให้ความยาวด้วย
อับดุล

@ อับดุลดูman getline. lenคือความยาวของบัฟเฟอร์ที่มีอยู่0เป็นเวทมนตร์และบอกให้จัดสรร Read คือจำนวนตัวอักษรที่อ่าน readขนาดบัฟเฟอร์อาจจะมีขนาดใหญ่กว่า
Ciro Santilli 郝海东冠状病六四事件法轮功

6

หลายคนเช่นฉันมาที่โพสต์นี้พร้อมกับชื่อที่ตรงกับสิ่งที่ค้นหาแม้ว่าคำอธิบายจะบอกเกี่ยวกับความยาวผันแปร ส่วนใหญ่เราจะทราบความยาวล่วงหน้า

หากคุณทราบความยาวก่อนลงมือลองด้านล่าง:

char str1[1001] = { 0 };
fgets(str1, 1001, stdin); // 1000 chars may be read

แหล่งที่มา: https://www.tutorialspoint.com/c_standard_library/c_function_fgets.htm


5

ตามที่แนะนำคุณสามารถใช้ getchar () เพื่ออ่านจากคอนโซลได้จนกว่าจะส่งคืน end-of-line หรือ EOF โดยสร้างบัฟเฟอร์ของคุณเอง การเพิ่มบัฟเฟอร์แบบไดนามิกอาจเกิดขึ้นได้หากคุณไม่สามารถกำหนดขนาดเส้นสูงสุดที่เหมาะสมได้

คุณสามารถใช้ fgets เป็นวิธีที่ปลอดภัยในการรับบรรทัดเป็นสตริงที่สิ้นสุดด้วย C null:

#include <stdio.h>

char line[1024];  /* Generously large value for most situations */

char *eof;

line[0] = '\0'; /* Ensure empty line if no input delivered */
line[sizeof(line)-1] = ~'\0';  /* Ensure no false-null at end of buffer */

eof = fgets(line, sizeof(line), stdin);

หากคุณใช้อินพุตคอนโซลหมดหรือหากการดำเนินการล้มเหลวด้วยเหตุผลบางประการ eof == NULL จะถูกส่งกลับและบัฟเฟอร์บรรทัดอาจไม่เปลี่ยนแปลง (ซึ่งเป็นเหตุผลว่าทำไมการตั้งค่า char ตัวแรกเป็น '\ 0' จึงมีประโยชน์)

fgets จะไม่เติมบรรทัด [] มากเกินไปและจะทำให้แน่ใจว่ามีค่าว่างหลังอักขระที่ยอมรับครั้งสุดท้ายในการส่งคืนที่สำเร็จ

หากถึงจุดสิ้นสุดของบรรทัดอักขระที่นำหน้า "\ 0" จะเป็น "\ n"

หากไม่มีการยุติ "\ n" ก่อนที่จะสิ้นสุด "\ 0" อาจเป็นไปได้ว่ามีข้อมูลเพิ่มเติมหรือคำขอถัดไปจะรายงานจุดสิ้นสุดของไฟล์ คุณจะต้องทำ fgets อื่นเพื่อดูว่าอันไหน (ในเรื่องนี้การวนลูปด้วย getchar () จะง่ายกว่า)

ในโค้ดตัวอย่าง (อัปเดต) ด้านบนถ้าบรรทัด [sizeof (line) -1] == '\ 0' หลังจากสร้าง fget สำเร็จคุณจะทราบว่าบัฟเฟอร์ถูกเติมเต็มแล้ว หากตำแหน่งนั้นดำเนินต่อไปโดย "\ n" คุณรู้ว่าคุณโชคดี มิฉะนั้นจะมีข้อมูลเพิ่มเติมหรือจุดสิ้นสุดของไฟล์อยู่ข้างหน้าใน stdin (เมื่อเติมบัฟเฟอร์ไม่สมบูรณ์คุณยังสามารถอยู่ที่จุดสิ้นสุดของไฟล์ได้และอาจไม่มี '\ n' ที่ท้ายบรรทัดปัจจุบันเนื่องจากคุณต้องสแกนสตริงเพื่อค้นหาและ / หรือกำจัด '\ n' ใด ๆ ก่อนสิ้นสุดสตริง ('\ 0' ตัวแรกในบัฟเฟอร์) ฉันชอบใช้ getchar () เป็นอันดับแรก)

ทำในสิ่งที่คุณต้องทำเพื่อจัดการกับมันยังคงมีบรรทัดมากกว่าจำนวนที่คุณอ่านเป็นชิ้นแรก ตัวอย่างของบัฟเฟอร์ที่เพิ่มขึ้นแบบไดนามิกสามารถทำให้ทำงานได้กับ getchar หรือ fgets มีกรณีขอบที่ยุ่งยากบางอย่างที่ต้องระวัง (เช่นอย่าลืมให้อินพุตถัดไปเริ่มจัดเก็บที่ตำแหน่งของ '\ 0' ที่สิ้นสุดอินพุตก่อนหน้าก่อนที่บัฟเฟอร์จะขยายออกไป)


2

จะอ่านบรรทัดจากคอนโซลใน C ได้อย่างไร?

  • การสร้างฟังก์ชันของคุณเองเป็นวิธีหนึ่งที่จะช่วยให้คุณอ่านบรรทัดจากคอนโซลได้สำเร็จ

  • ฉันใช้การจัดสรรหน่วยความจำแบบไดนามิกเพื่อจัดสรรจำนวนหน่วยความจำที่ต้องการที่ต้องการ

  • เมื่อเรากำลังจะหมดหน่วยความจำที่จัดสรรเราจะพยายามเพิ่มขนาดหน่วยความจำเป็นสองเท่า

  • และที่นี่ฉันใช้ลูปเพื่อสแกนอักขระแต่ละตัวของสตริงทีละตัวโดยใช้getchar()ฟังก์ชันจนกว่าผู้ใช้จะป้อน'\n'หรือEOFอักขระ

  • ในที่สุดเราก็ลบหน่วยความจำที่จัดสรรเพิ่มเติมก่อนที่จะกลับบรรทัด

//the function to read lines of variable length

char* scan_line(char *line)
{
    int ch;             // as getchar() returns `int`
    long capacity = 0;  // capacity of the buffer
    long length = 0;    // maintains the length of the string
    char *temp = NULL;  // use additional pointer to perform allocations in order to avoid memory leaks

    while ( ((ch = getchar()) != '\n') && (ch != EOF) )
    {
        if((length + 1) >= capacity)
        {
            // resetting capacity
            if (capacity == 0)
                capacity = 2; // some initial fixed length 
            else
                capacity *= 2; // double the size

            // try reallocating the memory
            if( (temp = realloc(line, capacity * sizeof(char))) == NULL ) //allocating memory
            {
                printf("ERROR: unsuccessful allocation");
                // return line; or you can exit
                exit(1);
            }

            line = temp;
        }

        line[length] = (char) ch; //type casting `int` to `char`
    }
    line[length + 1] = '\0'; //inserting null character at the end

    // remove additionally allocated memory
    if( (temp = realloc(line, (length + 1) * sizeof(char))) == NULL )
    {
        printf("ERROR: unsuccessful allocation");
        // return line; or you can exit
        exit(1);
    }

    line = temp;
    return line;
}
  • ตอนนี้คุณสามารถอ่านทั้งบรรทัดได้ดังนี้:

    char *line = NULL;
    line = scan_line(line);

นี่คือตัวอย่างโปรแกรมที่ใช้scan_line()ฟังก์ชัน:

#include <stdio.h>
#include <stdlib.h> //for dynamic allocation functions

char* scan_line(char *line)
{
    ..........
}

int main(void)
{
    char *a = NULL;

    a = scan_line(a); //function call to scan the line

    printf("%s\n",a); //printing the scanned line

    free(a); //don't forget to free the malloc'd pointer
}

อินพุตตัวอย่าง:

Twinkle Twinkle little star.... in the sky!

ตัวอย่างผลลัพธ์:

Twinkle Twinkle little star.... in the sky!

0

ฉันเจอปัญหาเดียวกันเมื่อไม่นานมานี้นี่คือวิธีแก้ปัญหาของฉันหวังว่ามันจะช่วยได้

/*
 * Initial size of the read buffer
 */
#define DEFAULT_BUFFER 1024

/*
 * Standard boolean type definition
 */
typedef enum{ false = 0, true = 1 }bool;

/*
 * Flags errors in pointer returning functions
 */
bool has_err = false;

/*
 * Reads the next line of text from file and returns it.
 * The line must be free()d afterwards.
 *
 * This function will segfault on binary data.
 */
char *readLine(FILE *file){
    char *buffer   = NULL;
    char *tmp_buf  = NULL;
    bool line_read = false;
    int  iteration = 0;
    int  offset    = 0;

    if(file == NULL){
        fprintf(stderr, "readLine: NULL file pointer passed!\n");
        has_err = true;

        return NULL;
    }

    while(!line_read){
        if((tmp_buf = malloc(DEFAULT_BUFFER)) == NULL){
            fprintf(stderr, "readLine: Unable to allocate temporary buffer!\n");
            if(buffer != NULL)
                free(buffer);
            has_err = true;

            return NULL;
        }

        if(fgets(tmp_buf, DEFAULT_BUFFER, file) == NULL){
            free(tmp_buf);

            break;
        }

        if(tmp_buf[strlen(tmp_buf) - 1] == '\n') /* we have an end of line */
            line_read = true;

        offset = DEFAULT_BUFFER * (iteration + 1);

        if((buffer = realloc(buffer, offset)) == NULL){
            fprintf(stderr, "readLine: Unable to reallocate buffer!\n");
            free(tmp_buf);
            has_err = true;

            return NULL;
        }

        offset = DEFAULT_BUFFER * iteration - iteration;

        if(memcpy(buffer + offset, tmp_buf, DEFAULT_BUFFER) == NULL){
            fprintf(stderr, "readLine: Cannot copy to buffer\n");
            free(tmp_buf);
            if(buffer != NULL)
                free(buffer);
            has_err = true;

            return NULL;
        }

        free(tmp_buf);
        iteration++;
    }

    return buffer;
}

1
รหัสของคุณจะง่ายขึ้นมากหากคุณใช้gotoเพื่อจัดการกรณีข้อผิดพลาด อย่างไรก็ตามคุณไม่คิดว่าจะสามารถนำกลับมาใช้ใหม่tmp_bufได้แทนที่จะใช้mallocมันด้วยขนาดเดียวกันซ้ำแล้วซ้ำเล่า?
Shahbaz

การใช้ตัวแปรส่วนกลางเดียวhas_errเพื่อรายงานข้อผิดพลาดทำให้ฟังก์ชันนี้ไม่ปลอดภัยและใช้งานน้อยกว่าความสะดวกสบาย อย่าทำแบบนั้น คุณระบุข้อผิดพลาดแล้วโดยส่งคืนค่า NULL นอกจากนี้ยังมีพื้นที่ที่จะคิดว่าข้อความแสดงข้อผิดพลาดที่พิมพ์ออกมาไม่ใช่ความคิดที่ดีในฟังก์ชันไลบรารีที่ใช้งานทั่วไป
Jonathan Leffler

0

ในระบบ BSD และ Android คุณยังสามารถใช้fgetln:

#include <stdio.h>

char *
fgetln(FILE *stream, size_t *len);

ชอบมาก:

size_t line_len;
const char *line = fgetln(stdin, &line_len);

lineจะไม่เป็นโมฆะยกเลิกและมี\n(หรือสิ่งที่แพลตฟอร์มของคุณใช้) ในท้ายที่สุด จะไม่ถูกต้องหลังจากการดำเนินการ I / O ถัดไปบนสตรีม


ใช่ฟังก์ชันนี้มีอยู่ ข้อแม้ที่ว่ามันไม่ได้จัดเตรียมสตริงที่สิ้นสุดด้วย null นั้นมีขนาดใหญ่เพียงพอและมีปัญหาว่ามันอาจจะดีกว่าที่จะไม่ใช้มัน - มันอันตราย
Jonathan Leffler

0

สิ่งนี้:

unsigned int getConsoleInput(char **pStrBfr) //pass in pointer to char pointer, returns size of buffer
{
    char * strbfr;
    int c;
    unsigned int i;
    i = 0;
    strbfr = (char*)malloc(sizeof(char));
    if(strbfr==NULL) goto error;
    while( (c = getchar()) != '\n' && c != EOF )
    {
        strbfr[i] = (char)c;
        i++;
        strbfr = (void*)realloc((void*)strbfr,sizeof(char)*(i+1));
        //on realloc error, NULL is returned but original buffer is unchanged
        //NOTE: the buffer WILL NOT be NULL terminated since last
        //chracter came from console
        if(strbfr==NULL) goto error;
    }
    strbfr[i] = '\0';
    *pStrBfr = strbfr; //successfully returns pointer to NULL terminated buffer
    return i + 1; 
    error:
    *pStrBfr = strbfr;
    return i + 1;
}

0

วิธีที่ดีที่สุดและง่ายที่สุดในการอ่านบรรทัดจากคอนโซลคือการใช้ฟังก์ชัน getchar () ซึ่งคุณจะจัดเก็บทีละอักขระในอาร์เรย์

{
char message[N];        /* character array for the message, you can always change the character length */
int i = 0;          /* loop counter */

printf( "Enter a message: " );
message[i] = getchar();    /* get the first character */
while( message[i] != '\n' ){
    message[++i] = getchar(); /* gets the next character */
}

printf( "Entered message is:" );
for( i = 0; i < N; i++ )
    printf( "%c", message[i] );

return ( 0 );

}


-3

ฟังก์ชันนี้ควรทำในสิ่งที่คุณต้องการ:

char* readLine( FILE* file )
 {
 char buffer[1024];
 char* result = 0;
 int length = 0;

 while( !feof(file) )
  {
  fgets( buffer, sizeof(buffer), file );
  int len = strlen(buffer);
  buffer[len] = 0;

  length += len;
  char* tmp = (char*)malloc(length+1);
  tmp[0] = 0;

  if( result )
   {
   strcpy( tmp, result );
   free( result );
   result = tmp;
   }

  strcat( result, buffer );

  if( strstr( buffer, "\n" ) break;
  }

 return result;
 }

char* line = readLine( stdin );
/* Use it */
free( line );

ฉันหวังว่านี่จะช่วยได้.


1
คุณควรจะทำไม่ได้fgets( buffer, sizeof(buffer), file ); เว้นที่ว่างไว้สำหรับการยุติโมฆะ sizeof(buffer)-1fgets
user102008

โปรดทราบว่าwhile (!feof(file))ผิดเสมอและนี่เป็นเพียงอีกหนึ่งตัวอย่างของการใช้งานที่ผิดพลาด
Jonathan Leffler
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.