Quelle est la manière la plus simple de lire une ligne complète dans un programme de console C Le texte saisi peut avoir une longueur variable et nous ne pouvons faire aucune hypothèse sur son contenu.
pourriez-vous clarifier, s'il vous plaît? comme @Tim l'a dit ci-dessous, ce que vous demandez est déroutant :)
warren
Réponses:
81
Vous avez besoin d'une gestion dynamique de la mémoire et utilisez la fgetsfonction pour lire votre ligne. Cependant, il ne semble y avoir aucun moyen de voir combien de caractères il a lu. Donc, vous utilisez 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;}
Remarque : n'utilisez jamais gets! Il ne vérifie pas les limites et peut déborder de votre tampon
Mise en garde - besoin de vérifier le résultat de la réallocation là-bas. Mais si cela échoue, il y aura probablement des problèmes pires.
Tim
4
Vous pourriez probablement améliorer un peu l'efficacité en faisant des fgets avec un tampon et en vérifiant si vous avez le caractère de nouvelle ligne à la fin. Si vous ne le faites pas, réallouez votre tampon d'accumulation, copiez-y et recommencez.
Paul Tomblin
3
Cette fonction nécessite une correction: la ligne "len = lenmax;" après la réallocation doit soit précéder la réallocation, soit être "len = lenmax >> 1;" - ou tout autre équivalent qui tient compte du fait que la moitié de la longueur est déjà utilisée.
Matt Gallagher
1
@Johannes, en réponse à votre question, l'approche de @ Paul POURRAIT être plus rapide sur la plupart des implémentations de libc (c'est-à-dire réentrantes), puisque votre approche verrouille implicitement stdin pour chaque caractère alors que son verrouillage le verrouille une fois par tampon. Vous pouvez utiliser le moins portable fgetc_unlockedsi la sécurité des threads n'est pas un problème mais les performances le sont.
vladr
3
Notez que ceci getline()est différent de la getline()fonction standard POSIX .
Jonathan Leffler
28
Si vous utilisez la bibliothèque GNU C ou une autre bibliothèque compatible POSIX, vous pouvez l'utiliser getline()et la transmettre stdinpour le flux de fichiers.
Une implémentation très simple mais non sécurisée pour lire la ligne pour l'allocation statique:
char line[1024];
scanf("%[^\n]", line);
Une implémentation plus sûre, sans possibilité de débordement de tampon, mais avec la possibilité de ne pas lire toute la ligne, est:
char line[1024];
scanf("%1023[^\n]", line);
Pas la «différence de un» entre la longueur spécifiée déclarant la variable et la longueur spécifiée dans la chaîne de format. C'est un artefact historique.
Donc, si vous cherchiez des arguments de commande, jetez un œil à la réponse de Tim. Si vous voulez juste lire une ligne depuis la console:
#include<stdio.h>int main(){char string [256];
printf ("Insert your full address: ");
gets (string);
printf ("Your address is: %s\n",string);return0;}
Oui, ce n'est pas sécurisé, vous pouvez faire un dépassement de tampon, cela ne vérifie pas la fin du fichier, il ne prend pas en charge les encodages et bien d'autres choses. En fait, je ne me suis même pas demandé si ça faisait quoi que ce soit. Je suis d'accord, j'ai un peu foiré :) Mais ... quand je vois une question comme "Comment lire une ligne de la console en C?", Je suppose qu'une personne a besoin de quelque chose de simple, comme gets () et non 100 lignes de code comme ci-dessus. En fait, je pense que si vous essayez d'écrire ces 100 lignes de code en réalité, vous feriez beaucoup plus d'erreurs que vous n'auriez fait si vous aviez choisi get;)
Cela ne permet pas de longues chaînes ... - ce qui, je pense, est au cœur de sa question.
Tim du
2
-1, gets () ne doit pas être utilisé car il ne vérifie pas les limites.
détendez-vous
7
D'un autre côté, si vous écrivez un programme pour vous-même et que vous avez juste besoin de lire une entrée, c'est parfaitement bien. Le niveau de sécurité dont un programme a besoin dépend de la spécification - vous n'avez pas à le mettre en priorité à chaque fois.
Martin Beckett
4
@Tim - Je veux garder toute l'histoire :)
Paul Kapustin
4
Voté contre. getsn'existe plus, donc cela ne fonctionne pas en C11.
Antti Haapala le
11
Vous devrez peut-être utiliser une boucle caractère par caractère (getc ()) pour vous assurer qu'il n'y a pas de débordement de tampon et de ne pas tronquer l'entrée.
Quel est le but de lenici, lorsque lu fournit la longueur aussi
Abdul
@Abdul voir man getline. lenest la longueur du tampon existant, 0est magique et lui dit d'allouer. La lecture correspond au nombre de caractères lus. La taille du tampon peut être supérieure à read.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
6
Beaucoup de gens, comme moi, viennent à cet article avec le titre correspondant à ce qui est recherché, bien que la description parle de longueur variable. Dans la plupart des cas, nous connaissons la longueur à l'avance.
Si vous connaissez la longueur à l'avance, essayez ci-dessous:
char str1[1001]={0};
fgets(str1,1001, stdin);// 1000 chars may be read
Comme suggéré, vous pouvez utiliser getchar () pour lire depuis la console jusqu'à ce qu'une fin de ligne ou un EOF soit renvoyé, en construisant votre propre tampon. Une croissance dynamique du tampon peut se produire si vous ne parvenez pas à définir une taille de ligne maximale raisonnable.
Vous pouvez également utiliser des fgets comme moyen sûr d'obtenir une ligne sous forme de chaîne C terminée par un 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);
Si vous avez épuisé l'entrée de la console ou si l'opération a échoué pour une raison quelconque, eof == NULL est renvoyé et le tampon de ligne peut rester inchangé (c'est pourquoi définir le premier caractère sur '\ 0' est pratique).
fgets ne remplira pas trop la ligne [] et s'assurera qu'il y a un nul après le dernier caractère accepté lors d'un retour réussi.
Si la fin de ligne a été atteinte, le caractère précédant le «\ 0» de fin sera un «\ n».
S'il n'y a pas de fin '\ n' avant la fin '\ 0', c'est peut-être qu'il y a plus de données ou que la prochaine requête signalera la fin de fichier. Vous devrez faire un autre fgets pour déterminer lequel est lequel. (À cet égard, faire une boucle avec getchar () est plus facile.)
Dans l'exemple de code (mis à jour) ci-dessus, si la ligne [sizeof (line) -1] == '\ 0' après des fgets réussis, vous savez que le tampon a été complètement rempli. Si cette position est précédée d'un '\ n', vous savez que vous avez eu de la chance. Sinon, il y a soit plus de données, soit une fin de fichier en avance dans stdin. (Lorsque le tampon n'est pas complètement rempli, vous pouvez toujours être à une fin de fichier et il se peut qu'il n'y ait pas non plus de «\ n» à la fin de la ligne actuelle. Puisque vous devez analyser la chaîne pour trouver et / ou éliminer tout '\ n' avant la fin de la chaîne (le premier '\ 0' du tampon), j'ai tendance à préférer utiliser getchar () en premier lieu.)
Faites ce que vous devez faire pour faire en sorte qu'il y ait encore plus de lignes que le montant que vous lisez comme premier morceau. Les exemples de croissance dynamique d'un tampon peuvent être conçus pour fonctionner avec getchar ou fgets. Il y a quelques cas de bord délicats à surveiller (comme se souvenir que l'entrée suivante commence à stocker à la position du '\ 0' qui a mis fin à l'entrée précédente avant l'extension du tampon).
Lorsque nous sommes sur le point d'épuiser la mémoire allouée, nous essayons de doubler la taille de la mémoire
Et ici, j'utilise une boucle pour scanner chaque caractère de la chaîne un par un en utilisant la getchar()fonction jusqu'à ce que l'utilisateur entre '\n'ou EOFcaractère
enfin nous supprimons toute mémoire supplémentaire allouée avant de renvoyer la ligne
//the function to read lines of variable lengthchar* scan_line(char*line){int ch;// as getchar() returns `int`long capacity =0;// capacity of the bufferlong length =0;// maintains the length of the stringchar*temp = NULL;// use additional pointer to perform allocations in order to avoid memory leakswhile(((ch = getchar())!='\n')&&(ch != EOF)){if((length +1)>= capacity){// resetting capacityif(capacity ==0)
capacity =2;// some initial fixed length else
capacity *=2;// double the size// try reallocating the memoryif((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 memoryif((temp = realloc(line,(length +1)*sizeof(char)))== NULL ){
printf("ERROR: unsuccessful allocation");// return line; or you can exit
exit(1);}
line = temp;return line;}
Vous pouvez maintenant lire une ligne complète de cette façon:
char*line = NULL;
line = scan_line(line);
Voici un exemple de programme utilisant la scan_line()fonction:
#include<stdio.h>#include<stdlib.h>//for dynamic allocation functionschar* 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}
J'ai rencontré le même problème il y a quelque temps, c'était ma solution, j'espère que cela aide.
/*
* Initial size of the read buffer
*/#define DEFAULT_BUFFER 1024/*
* Standard boolean type definition
*/typedefenum{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;}
Votre code deviendrait BEAUCOUP plus simple si vous l'utilisiez gotopour gérer le cas d'erreur. Néanmoins, ne pensez-vous pas que vous pourriez le réutiliser tmp_buf, au lieu de mallocle réutiliser avec la même taille encore et encore dans la boucle?
Shahbaz
L'utilisation d'une seule variable globale has_errpour signaler les erreurs rend cette fonction peu sûre pour les threads et moins confortable à utiliser. Ne fais pas ça de cette façon. Vous indiquez déjà une erreur en renvoyant NULL. Il est également possible de penser que les messages d'erreur imprimés ne sont pas une bonne idée dans une fonction de bibliothèque à usage général.
Jonathan Leffler
0
Sur les systèmes BSD et Android, vous pouvez également utiliser fgetln:
Le linen'est pas terminé par null et contient \n(ou ce que votre plate-forme utilise) à la fin. Il devient invalide après la prochaine opération d'E / S sur le flux.
Oui, la fonction existe. La mise en garde qu'elle ne fournit pas de chaîne terminée par un nul est suffisamment grande et problématique pour qu'il vaut probablement mieux ne pas l'utiliser - c'est dangereux.
Jonathan Leffler
0
Quelque chose comme ça:
unsignedint getConsoleInput(char**pStrBfr)//pass in pointer to char pointer, returns size of buffer{char* strbfr;int c;unsignedint 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 consoleif(strbfr==NULL)goto error;}
strbfr[i]='\0';*pStrBfr = strbfr;//successfully returns pointer to NULL terminated bufferreturn i +1;
error:*pStrBfr = strbfr;return i +1;}
Le moyen le meilleur et le plus simple de lire une ligne à partir d'une console est d'utiliser la fonction getchar (), dans laquelle vous stockerez un caractère à la fois dans un tableau.
{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);
Réponses:
Vous avez besoin d'une gestion dynamique de la mémoire et utilisez la
fgets
fonction pour lire votre ligne. Cependant, il ne semble y avoir aucun moyen de voir combien de caractères il a lu. Donc, vous utilisez fgetc:Remarque : n'utilisez jamais gets! Il ne vérifie pas les limites et peut déborder de votre tampon
la source
fgetc_unlocked
si la sécurité des threads n'est pas un problème mais les performances le sont.getline()
est différent de lagetline()
fonction standard POSIX .Si vous utilisez la bibliothèque GNU C ou une autre bibliothèque compatible POSIX, vous pouvez l'utiliser
getline()
et la transmettrestdin
pour le flux de fichiers.la source
Une implémentation très simple mais non sécurisée pour lire la ligne pour l'allocation statique:
Une implémentation plus sûre, sans possibilité de débordement de tampon, mais avec la possibilité de ne pas lire toute la ligne, est:
Pas la «différence de un» entre la longueur spécifiée déclarant la variable et la longueur spécifiée dans la chaîne de format. C'est un artefact historique.
la source
gets
été complètement supprimé de la normeDonc, si vous cherchiez des arguments de commande, jetez un œil à la réponse de Tim. Si vous voulez juste lire une ligne depuis la console:
Oui, ce n'est pas sécurisé, vous pouvez faire un dépassement de tampon, cela ne vérifie pas la fin du fichier, il ne prend pas en charge les encodages et bien d'autres choses. En fait, je ne me suis même pas demandé si ça faisait quoi que ce soit. Je suis d'accord, j'ai un peu foiré :) Mais ... quand je vois une question comme "Comment lire une ligne de la console en C?", Je suppose qu'une personne a besoin de quelque chose de simple, comme gets () et non 100 lignes de code comme ci-dessus. En fait, je pense que si vous essayez d'écrire ces 100 lignes de code en réalité, vous feriez beaucoup plus d'erreurs que vous n'auriez fait si vous aviez choisi get;)
la source
gets
n'existe plus, donc cela ne fonctionne pas en C11.Vous devrez peut-être utiliser une boucle caractère par caractère (getc ()) pour vous assurer qu'il n'y a pas de débordement de tampon et de ne pas tronquer l'entrée.
la source
getline
exemple exécutableMentionné sur cette réponse mais voici un exemple.
C'est POSIX 7 , nous alloue de la mémoire, et réutilise joliment le tampon alloué sur une boucle.
Pointer newbs, lisez ceci: Pourquoi le premier argument de getline est-il un pointeur vers le pointeur "char **" au lieu de "char *"?
implémentation de la glibc
Pas de POSIX? Peut-être voulez-vous regarder l' implémentation de la glibc 2.23 .
Il résout en
getdelim
, qui est un simple sur-ensemble POSIXgetline
avec un terminateur de ligne arbitraire.Il double la mémoire allouée chaque fois qu'une augmentation est nécessaire et semble sûr pour les threads.
Cela nécessite une certaine expansion macro, mais il est peu probable que vous fassiez beaucoup mieux.
la source
len
ici, lorsque lu fournit la longueur aussiman getline
.len
est la longueur du tampon existant,0
est magique et lui dit d'allouer. La lecture correspond au nombre de caractères lus. La taille du tampon peut être supérieure àread
.Beaucoup de gens, comme moi, viennent à cet article avec le titre correspondant à ce qui est recherché, bien que la description parle de longueur variable. Dans la plupart des cas, nous connaissons la longueur à l'avance.
Si vous connaissez la longueur à l'avance, essayez ci-dessous:
source: https://www.tutorialspoint.com/c_standard_library/c_function_fgets.htm
la source
Comme suggéré, vous pouvez utiliser getchar () pour lire depuis la console jusqu'à ce qu'une fin de ligne ou un EOF soit renvoyé, en construisant votre propre tampon. Une croissance dynamique du tampon peut se produire si vous ne parvenez pas à définir une taille de ligne maximale raisonnable.
Vous pouvez également utiliser des fgets comme moyen sûr d'obtenir une ligne sous forme de chaîne C terminée par un null:
Si vous avez épuisé l'entrée de la console ou si l'opération a échoué pour une raison quelconque, eof == NULL est renvoyé et le tampon de ligne peut rester inchangé (c'est pourquoi définir le premier caractère sur '\ 0' est pratique).
fgets ne remplira pas trop la ligne [] et s'assurera qu'il y a un nul après le dernier caractère accepté lors d'un retour réussi.
Si la fin de ligne a été atteinte, le caractère précédant le «\ 0» de fin sera un «\ n».
S'il n'y a pas de fin '\ n' avant la fin '\ 0', c'est peut-être qu'il y a plus de données ou que la prochaine requête signalera la fin de fichier. Vous devrez faire un autre fgets pour déterminer lequel est lequel. (À cet égard, faire une boucle avec getchar () est plus facile.)
Dans l'exemple de code (mis à jour) ci-dessus, si la ligne [sizeof (line) -1] == '\ 0' après des fgets réussis, vous savez que le tampon a été complètement rempli. Si cette position est précédée d'un '\ n', vous savez que vous avez eu de la chance. Sinon, il y a soit plus de données, soit une fin de fichier en avance dans stdin. (Lorsque le tampon n'est pas complètement rempli, vous pouvez toujours être à une fin de fichier et il se peut qu'il n'y ait pas non plus de «\ n» à la fin de la ligne actuelle. Puisque vous devez analyser la chaîne pour trouver et / ou éliminer tout '\ n' avant la fin de la chaîne (le premier '\ 0' du tampon), j'ai tendance à préférer utiliser getchar () en premier lieu.)
Faites ce que vous devez faire pour faire en sorte qu'il y ait encore plus de lignes que le montant que vous lisez comme premier morceau. Les exemples de croissance dynamique d'un tampon peuvent être conçus pour fonctionner avec getchar ou fgets. Il y a quelques cas de bord délicats à surveiller (comme se souvenir que l'entrée suivante commence à stocker à la position du '\ 0' qui a mis fin à l'entrée précédente avant l'extension du tampon).
la source
Construire votre propre fonction est l'un des moyens qui vous aiderait à lire une ligne depuis la console
J'utilise l' allocation de mémoire dynamique pour allouer la quantité de mémoire requise
Lorsque nous sommes sur le point d'épuiser la mémoire allouée, nous essayons de doubler la taille de la mémoire
Et ici, j'utilise une boucle pour scanner chaque caractère de la chaîne un par un en utilisant la
getchar()
fonction jusqu'à ce que l'utilisateur entre'\n'
ouEOF
caractèreenfin nous supprimons toute mémoire supplémentaire allouée avant de renvoyer la ligne
Vous pouvez maintenant lire une ligne complète de cette façon:
Voici un exemple de programme utilisant la
scan_line()
fonction:exemple d'entrée:
exemple de sortie:
la source
J'ai rencontré le même problème il y a quelque temps, c'était ma solution, j'espère que cela aide.
la source
goto
pour gérer le cas d'erreur. Néanmoins, ne pensez-vous pas que vous pourriez le réutilisertmp_buf
, au lieu demalloc
le réutiliser avec la même taille encore et encore dans la boucle?has_err
pour signaler les erreurs rend cette fonction peu sûre pour les threads et moins confortable à utiliser. Ne fais pas ça de cette façon. Vous indiquez déjà une erreur en renvoyant NULL. Il est également possible de penser que les messages d'erreur imprimés ne sont pas une bonne idée dans une fonction de bibliothèque à usage général.Sur les systèmes BSD et Android, vous pouvez également utiliser
fgetln
:Ainsi:
Le
line
n'est pas terminé par null et contient\n
(ou ce que votre plate-forme utilise) à la fin. Il devient invalide après la prochaine opération d'E / S sur le flux.la source
Quelque chose comme ça:
la source
Le moyen le meilleur et le plus simple de lire une ligne à partir d'une console est d'utiliser la fonction getchar (), dans laquelle vous stockerez un caractère à la fois dans un tableau.
}
la source
Cette fonction doit faire ce que vous voulez:
J'espère que ça aide.
la source
fgets( buffer, sizeof(buffer), file );
passizeof(buffer)-1
.fgets
laisse de l'espace pour le null de fin.while (!feof(file))
c'est toujours faux et ce n'est qu'un autre exemple d'utilisation erronée.