C'de konsoldan bir satır nasıl okunur?


108

Bir C konsol programında tam bir satırı okumanın en basit yolu nedir Girilen metnin uzunluğu değişken olabilir ve içeriği hakkında herhangi bir varsayımda bulunamayız.


açıklar mısın lütfen? @Tim'in aşağıda söylediği gibi, ne istediğinizi kafa karıştırıcı :)
warren

Yanıtlar:


81

Dinamik bellek yönetimine ihtiyacınız var ve fgetssatırınızı okumak için işlevi kullanın. Ancak, kaç karakter okuduğunu görmenin bir yolu yok gibi görünüyor. Yani fgetc kullanıyorsunuz:

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;
}

Not : Asla alır! Sınır kontrolü yapmaz ve arabelleğinizden taşabilir


Uyarı - orada yeniden ayırmanın sonucunu kontrol etmeniz gerekiyor. Ancak bu başarısız olursa, büyük olasılıkla daha kötü sorunlar vardır.
Tim

4
Muhtemelen arabellek ile fgets yaparak ve sonunda yeni satır karakterinin olup olmadığını kontrol ederek verimliliği biraz artırabilirsiniz. Yapmazsanız, biriktirme tamponunuzu yeniden tahsis edin, içine kopyalayın ve tekrar fgets.
Paul Tomblin

3
Bu işlevin düzeltilmesi gerekiyor: "len = lenmax;" satırı yeniden ayırmadan sonra, yeniden ayırmadan önce gelmeli veya "len = lenmax >> 1;" - veya uzunluğun yarısının halihazırda kullanıldığı gerçeğini açıklayan başka bir eşdeğer.
Matt Gallagher

1
@Johannes, sorunuza yanıt olarak, @ Paul'un yaklaşımı çoğu (yani evresel) libc uygulamasında daha hızlı OLABİLİR, çünkü yaklaşımınız her karakter için stdin'i örtük olarak kilitlerken, onun her tamponda bir kez kilitler. İş fgetc_unlockedparçacığı güvenliği önemli değilse de performans önemliyse daha az taşınabilir olanı kullanabilirsiniz.
vladr

3
Bunun getline()POSIX standart getline()işlevinden farklı olduğunu unutmayın .
Jonathan Leffler

28

GNU C kitaplığını veya POSIX uyumlu başka bir kitaplığı kullanıyorsanız, kullanabilir getline()ve geçebilirsiniz.stdin , dosya akışı için ona .


16

Statik ayırma satırını okumak için çok basit ama güvenli olmayan bir uygulama:

char line[1024];

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

Arabellek taşması olasılığı olmayan, ancak tüm satırı okumama olasılığı olan daha güvenli bir uygulama:

char line[1024];

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

Değişkeni bildiren uzunluk ile biçim dizesinde belirtilen uzunluk arasındaki 'bir fark' değil. Tarihi bir eserdir.


14
Bu hiç güvenli değil. Niçin getsstandarttan tamamen çıkarıldığının tam olarak aynı sorundan muzdarip
Antti Haapala

6
Moderatör notu: Yukarıdaki yorum , yanıtın önceki bir revizyonuna
Robert Harvey

13

Öyleyse, komut argümanları arıyorsanız, Tim'in cevabına bir bakın. Sadece konsoldan bir satır okumak istiyorsanız:

#include <stdio.h>

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

Evet, güvenli değil, arabellek taşması yapabilirsiniz, dosyanın sonunu kontrol etmez, kodlamaları ve diğer pek çok şeyi desteklemez. Aslında bunlardan HERHANGİ BİRİNİ yapıp yapmadığını bile düşünmedim. Katılıyorum, bir nevi batırdım :) Ama ... "C'de konsoldan bir satır nasıl okunur?" Gibi bir soru gördüğümde, bir kişinin 100 satır kod yerine gets () gibi basit bir şeye ihtiyacı olduğunu varsayıyorum. yukarıdaki gibi. Aslında, bence bu 100 satırlık kodu gerçekte yazmaya çalışırsanız, seçmiş olsaydınız yapacağınızdan çok daha fazla hata yaparsınız;)


1
Bu uzun dizelere izin vermiyor ... - bence sorusunun özü bu.
Tim

2
-1, gets () sınır kontrolü yapmadığı için kullanılmamalıdır.
gevşeyin

7
Öte yandan, kendiniz için bir program yazıyorsanız ve sadece bir girdiyi okumanız gerekiyorsa, bu tamamen iyidir. Bir programın ihtiyaç duyduğu güvenlik miktarı spesifikasyonla aynıdır - bunu her seferinde öncelik olarak koymak zorunda değilsiniz.
Martin Beckett

4
@Tim - Tüm geçmişi saklamak istiyorum :)
Paul Kapustin

4
Olumsuz oy verildi. getsartık yok, bu nedenle bu C11'de çalışmıyor.
Antti Haapala

11

Arabellek taşması olmadığından ve girdiyi kesmediğinizden emin olmak için karakter karakter (getc ()) döngüsü kullanmanız gerekebilir.


9

getline çalıştırılabilir örnek

Bu cevapta bahsedildi ama işte bir örnek.

Bu ise POSIX 7 , bizim için ayırır bellek ve güzel bir şekilde bir döngü tahsis arabelleği yeniden.

İşaretçi newbs, şunu okuyun: getline'ın ilk argümanı neden "char *" yerine "char **" göstericisine bir gösterici?

#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 uygulaması

POSIX yok mu? Belki glibc 2.23 uygulamasına bakmak istersiniz .

Rasgele bir satır sonlandırıcı ile getdelimbasit bir POSIX üst kümesi olan olarak çözülür getline.

Her artış gerektiğinde ayrılan belleği iki katına çıkarır ve iş parçacığı açısından güvenli görünür.

Biraz makro genişletme gerektirir, ancak daha iyisini yapma olasılığınız düşüktür.


lenBuradaki amaç nedir, okumak da uzunluğu sağlar
Abdul

@Abdul bakın man getline. lenmevcut tamponun uzunluğu 0, sihirlidir ve ona ayırmasını söyler. Okuma, okunan karakter sayısıdır. Arabellek boyutu daha büyük olabilir read.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功


5

Önerildiği gibi, bir satır sonu veya bir EOF döndürülene kadar konsoldan okumak için getchar () kullanabilir ve kendi tamponunuzu oluşturabilirsiniz. Makul bir maksimum satır boyutu ayarlayamazsanız, dinamik olarak büyüyen tampon oluşabilir.

Fgets'ı C boş sonlu dizge olarak bir satırı elde etmenin güvenli bir yolu olarak da kullanabilirsiniz:

#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);

Konsol girişini bitirdiyseniz veya işlem herhangi bir nedenle başarısız olduysa, eof == NULL döndürülür ve satır arabelleği değişmemiş olabilir (bu nedenle ilk karakterin '\ 0' olarak ayarlanması kullanışlıdır).

fgets, [] satırını aşmaz ve başarılı bir dönüşte son kabul edilen karakterden sonra bir boş değer olmasını sağlar.

Satırın sonuna ulaşılmışsa, '\ 0' ile biten karakterin önündeki karakter '\ n' olacaktır.

'\ 0' bitiminden önce sonlandırıcı bir '\ n' yoksa, daha fazla veri olabilir veya bir sonraki istek dosya sonunu bildirebilir. Hangisinin hangisi olduğunu belirlemek için başka bir fget yapmanız gerekecek. (Bu bağlamda getchar () ile döngü yapmak daha kolaydır.)

Yukarıdaki (güncellenmiş) örnek kodda, başarılı fgets'den sonra satır [sizeof (line) -1] == '\ 0' ise, arabelleğin tamamen dolduğunu bilirsiniz. Bu pozisyon bir '\ n' ile ilerletilirse, şanslı olduğunuzu bilirsiniz. Aksi takdirde, stdin'de ya daha fazla veri ya da ileride bir dosya sonu vardır. (Arabellek tamamen doldurulmadığında, hala bir dosyanın sonunda olabilirsiniz ve mevcut satırın sonunda da bir '\ n' olmayabilir. Bulmak için dizeyi taramanız gerektiğinden ve / veya dizenin sonundan önceki herhangi bir '\ n' yi ortadan kaldırın (tampondaki ilk '\ 0'), ilk etapta getchar () kullanmayı tercih etme eğilimindeyim.)

Hala ilk yığın olarak okuduğunuz miktardan daha fazla satır olmasıyla başa çıkmak için yapmanız gerekenleri yapın. Dinamik olarak büyüyen bir tamponun örnekleri getchar veya fgets ile çalışmak üzere yapılabilir. Dikkat edilmesi gereken bazı zorlu uç durumlar vardır (bir sonraki girdinin, arabellek uzatılmadan önceki girdiyi sonlandıran '\ 0' konumunda depolamaya başlaması gibi).


2

C'de konsoldan bir satır nasıl okunur?

  • Kendi işlevinizi oluşturmak, konsoldan bir satır okumanıza yardımcı olacak yollardan biridir.

  • Gereken bellek miktarını ayırmak için dinamik bellek ayırma kullanıyorum

  • Ayrılan belleği tüketmek üzereyken, bellek boyutunu iki katına çıkarmaya çalışırız.

  • Ve burada getchar(), kullanıcı '\n'veya EOFkarakter girene kadar dizenin her karakterini işlevi kullanarak tek tek taramak için bir döngü kullanıyorum.

  • Son olarak, satırı döndürmeden önce ek olarak ayrılmış bellekleri kaldırıyoruz

//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;
}
  • Şimdi tam bir satırı şu şekilde okuyabilirsiniz:

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

İşte işlevi kullanan örnek bir programscan_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
}

örnek giriş:

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

örnek çıktı:

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

0

Bir süre önce aynı problemle karşılaştım, bu benim çözümümdü, umarım yardımcı olur.

/*
 * 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
gotoHata durumunu işlemek için kullanırsanız kodunuz ÇOK daha basit hale gelir . Yine de, aynı boyutta döngü içinde tekrar tekrar kullanmak tmp_bufyerine yeniden kullanabileceğinizi düşünmüyor musunuz malloc?
Shahbaz

has_errHataları bildirmek için tek bir global değişken kullanmak , bu işlevi iş parçacığını güvensiz ve kullanımı rahat hale getirir. Bunu bu şekilde yapma. NULL döndürerek zaten bir hatayı belirtiyorsunuz. Ayrıca, yazdırılan hata mesajlarının genel amaçlı bir kütüphane işlevinde iyi bir fikir olmadığını düşünmeye de yer vardır.
Jonathan Leffler

0

BSD sistemlerinde ve Android'de şunları da kullanabilirsiniz fgetln:

#include <stdio.h>

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

Şöyle:

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

lineBoş sonlandırılmış değildir ve içerdiği \n(veya platformu kullanarak ne olursa olsun) sonunda. Akıştaki bir sonraki G / Ç işleminden sonra geçersiz hale gelir.


Evet, işlev var. Boş sonlandırılmış bir dizge sağlamadığı uyarısı yeterince büyük ve sorunludur, muhtemelen kullanmamak daha iyidir - tehlikelidir.
Jonathan Leffler

0

Bunun gibi bir şey:

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

Bir konsoldan bir satırı okumanın en iyi ve en basit yolu getchar () işlevini kullanmaktır, böylece bir dizide her seferinde bir karakter saklayacaksınız.

{
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

Bu işlev istediğinizi yapmalıdır:

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 );

Umarım bu yardımcı olur.


1
Sen yapmalıyım fgets( buffer, sizeof(buffer), file );değil sizeof(buffer)-1. fgetssonlandırıcı null için boşluk bırakır.
user102008

Bunun while (!feof(file))her zaman yanlış olduğunu ve bunun hatalı kullanımın sadece bir örneği olduğunu unutmayın.
Jonathan Leffler
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.