Як читати рядок з консолі на 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;
}

Примітка : Ніколи не використовуйте! Це не робить перевірку меж і може переповнити ваш буфер


Caveat - там потрібно перевірити результат realloc. Але якщо це не вдається, то, швидше за все, є гірші проблеми.
Тім

4
Можливо, ви могли б трохи покращити ефективність, роблячи fgets з буфером та перевіряючи, чи є у вас символ нового рядка в кінці. Якщо цього не зробіть, перерозподіліть буфер накопичення, скопіюйте його та знову з’єднайте.
Пол Томблін

3
Ця функція потребує корекції: рядок "len = lenmax;" після realloc має або передувати realloc, або бути "len = lenmax >> 1;" - або якийсь інший еквівалент, який пояснює той факт, що половина довжини вже використовується.
Метт Галлахер

1
@Johannes, у відповідь на ваше запитання, @ підхід Павла БУДЕ швидше в більшості (тобто реєрентних) реалізацій libc, оскільки ваш підхід неявно блокує stdin для кожного персонажа, тоді як його блокується один раз на буфер. Ви можете використовувати менш портативний, fgetc_unlockedякщо безпека ниток не викликає занепокоєння, але ефективність.
vladr

3
Зауважте, що це getline()відрізняється від стандартної getline()функції POSIX .
Джонатан Леффлер

28

Якщо ви використовуєте бібліотеку GNU C або іншу бібліотеку, сумісну з POSIX, ви можете використовувати getline()та переходити stdinдо неї для потоку файлів.


16

Дуже проста, але небезпечна реалізація для читання рядка для статичного розподілу:

char line[1024];

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

Більш безпечна реалізація, без можливості переповнення буфера, але з можливістю не читати весь рядок:

char line[1024];

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

Не "різниця на одиницю" між вказаною довжиною, що декларує змінну, і довжиною, вказаною в рядку формату. Це історичний артефакт.


14
Це зовсім не безпечно. Вона страждає точно від тієї ж проблеми, чому getsїї взагалі зняли зі стандарту
Антті Хаапала

6
Примітка модератора: вищевказаний коментар стосується попереднього перегляду відповіді.
Роберт Харві

13

Отже, якщо ви шукали аргументи команд, подивіться на відповідь Тіма. Якщо ви просто хочете прочитати рядок з консолі:

#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?", Я припускаю, що людині потрібно щось просте, як, наприклад, get (), а не 100 рядків коду як вище. Насправді, я думаю, якщо ви спробуєте насправді написати ці 100 рядків коду, ви зробили б набагато більше помилок, ніж ви зробили б, якби вибрали;)


1
Це не дозволяє довгих рядків ... - що, на мою думку, є сутністю його питання.
Тім

2
-1, get () не слід використовувати, оскільки він не робить перевірку меж.
розмотується

7
З іншого боку, якщо ви пишете програму для себе і вам просто потрібно прочитати вхід, це цілком чудово. Скільки потрібно програмі, що відповідає програмі, - це не кожен раз, коли ви ставите її пріоритетною.
Мартін Бекетт

4
@Tim - Я хочу зберегти всю історію :)
Павло Капустін

4
Захищений. getsбільше не існує, тому це не працює в C11.
Антті Хаапала

11

Можливо, вам доведеться використовувати цикл символів (getc ()), щоб переконатися, що у вас немає переповнення буфера та не врізати вхід.


9

getline приклад для виконання

Згаданий у цій відповіді, але ось приклад.

Це POSIX 7 , виділяє нам пам’ять і добре використовує виділений буфер на циклі.

Покажчики новичок, прочитайте це: Чому перший аргумент 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тут, коли читати також визначає довжину
Абдул

@Abdul див man getline. len- це довжина існуючого буфера, 0є магічним і підказує йому виділяти. Читання - кількість прочитаних символів. Розмір буфера може бути більшим, ніж 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 () для читання з консолі, поки не повернеться кінцева лінія чи EOF, будуючи власний буфер. Динамічне зростання буфера може виникати, якщо ви не можете встановити розумний максимальний розмір рядка.

Ви також можете використовувати fgets як безпечний спосіб отримати рядок у вигляді рядка з нульовим завершенням:

#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 повертається, а буфер лінії може бути незмінним (саме тому встановлення першого знака на "\ 0" зручно).

fgets не заповнить рядок [], і це забезпечить наявність нуля після останнього прийнятого символу при успішному поверненні.

Якщо було досягнуто кінця рядка, символом, що передує завершенню "\ 0", буде "\ n".

Якщо до кінця "\ 0" немає закінчення "\ n", можливо, більше даних або про наступний запит буде повідомлено про закінчення файлу. Вам доведеться зробити ще один фетт, щоб визначити, що це таке. (У зв'язку з цим циклічне використання за допомогою getchar () простіше.)

У наведеному вище (оновленому) прикладі коду, якщо рядок [sizeof (рядок) -1] == '\ 0' після успішних фетів, ви знаєте, що буфер був заповнений повністю. Якщо цю позицію виконує "\ 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щоб застосовувати його з однаковим розміром у петлі?
Шахбаз

Використання єдиної глобальної змінної has_errдля повідомлення про помилки робить цю функцію нитки небезпечною і менш комфортною у використанні. Не роби це так. Ви вже вказуєте на помилку, повертаючи NULL. Існує також можливість подумати, що надруковані повідомлення про помилки не є гарною ідеєю для функції бібліотеки загального призначення.
Джонатан Леффлер

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(або що б не використовувала ваша платформа) врешті-решт. Він стає недійсним після наступної операції вводу / виводу в потоці.


Так, функція існує. Застереження про те, що він не забезпечує нульову кінець рядка, є досить великим і проблематичним, що, мабуть, краще не використовувати його - це небезпечно.
Джонатан Леффлер

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)-1. fgetsзалишає простір для закінчення нуля.
user102008

Зауважте, що while (!feof(file))це завжди неправильно, і це лише ще один приклад помилкового використання.
Джонатан Леффлер
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.