Can I do something like the code below to get a persons name into the firstname string?
printf("First Name? ");
scanf("%s", &firstname[11]);
yup. This stores the input in firstname, starting at index 11.
No. You should not do it this way. It is unsafe, in exactly the same way gets is unsafe.
You should instead do something like this. (I am assuming that you really do want to write to firstname starting at character position 11, which is what &firstname[11] does. If firstname is 11 bytes long and you want to write starting at position 0, you would simply use firstname and remove the various occurrences of 11 + below.)
char inbuf[80];
size_t n;
fputs("First Name? ", stdout);
fgets(inbuf, sizeof inbuf, stdin);
n = strlen(inbuf);
if (n == 0) {
fputs("No name entered\n", stderr);
exit(1);
}
if (inbuf[n-1] != '\n') {
fputs("Name too long\n", stderr);
exit(1);
}
inbuf[--n] = '\0';
if (n == 0) {
fputs("No name entered\n", stderr);
exit(1);
}
if (11 + n >= sizeof firstname) {
fputs("Name too long\n", stderr);
exit(1);
}
memcpy(&firstname[11], inbuf, n);
firstname[11 + n] = '\0';
If you reaction is that that looks like a giant pain in the behind, all I can say is, welcome to C programming. All of that is in fact necessary for robustness in the face of arbitrarily malformed input.
You should also reflect upon Falsehoods Programmers Believe About Names and then redesign your database accordingly. (Most importantly in this context: people do not necessarily divide their names into "first" and "last" and "middle" components; many people have first names that require more than 10 bytes to represent.)
EDIT: Bugs in example code should now all be corrected. Serve me right for doing memory arithmetic in my head without testing it.
The short answer to your question is, yes you can.
The long answer (and questions that go with it)...
What's the declaration of firstname?
What's in the first 11 places of filename?
Are you trying to limit the the limit the length of the user input to 11 characters and that was the syntax you thought would work?
I suspect that you are trying to limit the firstname to be at most 11 characters. The way to solve this is:
char firstname[12];
scanf("%11s", firstname);
However, as Zach pointed out, this will be problematic if you have a first name like "Mary Kay". scanf will stop at the first white space and you will end up with a first name that contains just "Mary". The better approach is to use fgets.
fgets(firstname, 12, stdin);
That should work if there is nothing else in a line.
If you expect to read the data from a file and there are other fields in a line, then you will have to deal with that additional complexity. Say you have first name, last name, and age in a line. Your input file could look something like:
John, Deer, 59
Mary Kay, Smith, 42
If your input file contains data like above, you can't use fgets to pick out the first name using fgets(firstname, 12, infile);. You will have to use fgets to read the entire line, then parse the line to extract all the relevant data.
Related
I’m a beginner at programming with C. I’m here because I need help from professional programmers. So, I have to do a simple program called “Lessons”. In this program, I need to use structure, an array of structure and use files to save all records.
For now, I have a struct
typed struct lessons{
char LessonName [30],
char TeacherName [20],
char TeacherLastName[20],
int numberOfStudents
} lessons
And array
lessons info[10]
As far I can understand I have an array called “info” which can handle 10 lessons information. Right?
And now I face up with my biggest problem. How should I “play” with all records?
Should I create a txt file and fill it up with some information or I should add the lesson’s information with code help?
Can anybody explain, give some examples of how should I put the new record to a static array when the user enters all information about the lesson?
And also, how to scan all records from (txt or bin) file and show it on console?
To make work easier I can give examples of records:
Physical education Harry Pleter 32
History Emily Shelton 12
If all string fields of the struct are a single word, you can do it like this:
for (int i = 0; i < sizeof info / sizeof info[0]; i++)
{
scanf("%29s %19s %19s %d", info[i].LessonName, info[i].TeacherName, info[i].TeacherLastName, &info[i].numberOfStudents);
}
This will work perfectly with the string "History Emily Shelton 12", but will however fail to work with the string "Physical education Harry Pleter 32", because Physical education is 2 words.
Reading more than one word per field
Reading more than 1 word per field can be done very similarly, but you will have to decide which is the character that separates the fields. In this example, I used comma ,:
for (int i = 0; i < sizeof info / sizeof info[0]; i++)
{
scanf("%29[^,] %19[^,] %19[^,] %d", info[i].LessonName, info[i].TeacherName, info[i].TeacherLastName, &info[i].numberOfStudents);
}
This will correctly parse the string: "Physical education, Harry, Pleter, 32". The commas are needed because the program needs to know when to stop reading a field and continue with the next one.
How should I “play” with all records?
How about 1 line of the file == 1 record?
Code needs 2 functions:
void lessons_write(FILE *destination, const lessons *source);
// return 1: success, EOF: end-of-file, 0: fail
int lessons_read(FILE *source, lessons *destination);
A key consideration: Input may not be formatted correctly and so calling code needs to know when something failed.
Consider comma separated values CSV.
void lessons_write(FILE *destination, const lessons *source) {
// Maybe add tests here to detect if the record is sane.
fprintf(destination, "%s, %s, %s, %d\n",
source->LessonName,
source->TeacherName,
source->TeacherLastName,
source->numberOfStudents);
}
int lessons_read(FILE *source, lessons *destination) {
// Use a buffer large enough for any reasonable input.
char buf[sizeof *destination * 2];
// Use fgets to read **1 line**
if (fgets(buf, sizeof buf, source) == NULL) {
return EOF;
}
// Use %n to detect end of scanning. It records the offset of the scan.
int n = 0;
sscanf(buf, " %29[^,], %19[^,], %19[^,], %d %n",
destination->LessonName,
destination->TeacherName,
destination->TeacherLastName,
&destination->numberOfStudents, &n);
// If scan did not complete or some junk at the end ...
if (n == 0 || buf[n] != '\0') {
return 0;
}
// Maybe add tests here to detect if the record is sane.
return 1;
}
Now armed with basics, consider more advanced issues:
Can a course name or teachers name contain a ',' or '\n' as that will foal up the reading? Perhaps form a bool Lessons_Valid(const lessons *source) test?
Can a course name or teachers name begin, contain or end with spaces? Or control characters?
What range is valid for # of students? Perhaps 0-9999.
How about long names?
A key to good record handling is the ability to detect garbage input.
give some examples of how should I put the new record to a static array when the user enters all information about the lesson?
How about some pseudo code to not take all the learning experience away?
open file to read, successful?
Set N as 10
lessons abc[N]
for up to N times
read record and return success
Was that the last (EOF)?
Was is a bad record (Error message)
else save it and increment read_count
close input
for read_count
print the record to stdout
I've read a file called names.txt which contains firstname and secondname. I printed the names out, just to see, if it's working
for (int counter = 0; counter < 10; counter ++) {
fscanf(names, "%s %s\n", firstname, secondname);
printf("%s%s\n", firstname, secondname);
}
I tried to access a specific string with firstname[x] but this gets me the single char in the firstname
is it possible now, to only print let's say the 7th firstname and secondname of the red file names.txt?
for (int counter = 0; counter < 10; counter ++)
{
fscanf(names, "%s %s\n", firstname, secondname);
if(counter==6)
printf("%s%s\n", firstname, secondname);
}
This code will now print only 7th firstname and secondname,
Yes, if you just want to print the 7th name, put a condition around your printf:
if (counter == 6)
printf("%s%s\n", firstname, secondname);
Now just some general comments on other problems with your approach. Firstly, you are doing no bounds checking on your inputs, which can potentially cause a buffer overflow.
The general recommendation for reading strings from the file is using fgets. That reads an entire line, provided your buffer is large enough. You can then split that line into names using strtok or simply finding the first space with strchr.
Another issue is you're not testing whether the input succeeds. fscanf returns the number of elements successfully read. If that is not equal to 2, you should probably abandon your loop.
Last of all, you're looping exactly 10 times, which makes a somewhat bold assumption as to the contents of the file. Perhaps you want to exit the loop after reading the 7th string. Who knows? But either do that, or loop until reading a line fails.
Something that I thought that would be pretty basic has me stumped.
If I have a char array char menuInput[3]; and do fgets(menuInput,3,stdin);, I can easily check if they entered nothing:
if(menuInput[0]=='\n')
{
printf("******You made no selection.******");
}
Or if they entered too many (in this case, more than one) characters:
else if(menuInput[1]!='\n')
{
printf("******Invalid Selection: Please enter a single digit option.******");
break;
}
In that case, the user is entering either no characters, exactly the right amount (one), or too many.
What is giving me trouble is checking when they could either enter no characters, up to n amount, or too many.
If I have char surname[SURNAME_MAX + 1];, and use fgets(surname,SURNAME_MAX + 1,stdin); where SURNAME_MAX is 12, I can't work out how to check whether the input falls within the 'acceptable' range.
I can easily see if the user has inputted anything at all (if surname[0]='\n') and if they have entered exactly 12 characters (with the 13th being the '\n').
I think the crux of things is here is just making sure that, somewhere, surname[SURNAME_MAX + 1] contains '\n', but I don't know how to do that in C.
Regards,
Doug
EDIT: Ok, I'm going with #R Sahu's answer, but am having trouble making it work properly.
I'm expecting, at most, the 12 characters defined by SURNAME_MAX.
This should mean that, by that answer, my array should be surname[SURNAME_MAX+3] ("need at least SURNAME_MAX+2 just to store the newline and the terminating null character")
Then I fgets(surname,SURNAME_MAX + 3,stdin); and then is it supposed to be:
while(strlen(surname)>SURNAME_MAX+2);
{
printf("Input is too long!\n");
printf("Surname (1-12 characters): ");
fgets(surname,SURNAME_MAX + 2,stdin);
}
?
Is that the correct way of implementing the answer?
It looks right to me, but, no matter what I enter, I'm getting the "Input is too long!" message.
EDIT 2: My bad, had some code in the wrong place, all good now.
You said:
What is giving me trouble is checking when they could either enter no characters, up to n amount, or too many.
My suggestion: If you are expecting to see at most N characters, create an array whose size is larger than N+2. You need at least N+2 just to store the newline and the terminating null character, and use fgets on that string. If the length of the string is greater than N+1, then you know they entered too many characters. Detecting whether they entered too few characters is simple. I think you will be able to figure it out.
For finding character in string, you can use strchr method:
char *pch;
pch=strchr(SURNAME,'\n');
if (pch != NULL)
{
// ok we've got \n in string
}
But I'm not sure it is the best way to solve this problem at all.
Suggest separating user input from variable checking/assignment.
First read in user input
char buf[100];
if (fgets(buf, sizeof buf, stdin) == NULL) {
// EOF or IO error occurred.
return;
}
Now qualify input; various methods exist.
int len = strlen(buf);
// lop off potential \n
if (buf > 0 && buf[len-1] == '\n') buf[--len] = 0;
if (len >= sizeof surname) {
// Input too long
return;
}
memcpy(surname, buf, len + 1);
I'm working on a project and I just encountered a really annoying problem. I have a file which stores all the messages that my account received. A message is a data structure defined this way:
typedef struct _message{
char dest[16];
char text[512];
}message;
dest is a string that cannot contain spaces, unlike the other fields.
Strings are acquired using the fgets() function, so dest and text can have "dynamic" length (from 1 character up to length-1 legit characters). Note that I manually remove the newline character after every string is retrieved from stdin.
The "inbox" file uses the following syntax to store messages:
dest
text
So, for example, if I have a message from Marco which says "Hello, how are you?" and another message from Tarma which says "Are you going to the gym today?", my inbox-file would look like this:
Marco
Hello, how are you?
Tarma
Are you going to the gym today?
I would like to read the username from the file and store it in string s1 and then do the same thing for the message and store it in string s2 (and then repeat the operation until EOF), but since text field admits spaces I can't really use fscanf().
I tried using fgets(), but as I said before the size of every string is dynamic. For example if I use fgets(my_file, 16, username) it would end up reading unwanted characters. I just need to read the first string until \n is reached and then read the second string until the next \n is reached, this time including spaces.
Any idea on how can I solve this problem?
#include <stdio.h>
int main(void){
char username[16];
char text[512];
int ch, i;
FILE *my_file = fopen("inbox.txt", "r");
while(1==fscanf(my_file, "%15s%*c", username)){
i=0;
while (i < sizeof(text)-1 && EOF!=(ch=fgetc(my_file))){
if(ch == '\n' && i && text[i-1] == '\n')
break;
text[i++] = ch;
}
text[i] = 0;
printf("user:%s\n", username);
printf("text:\n%s\n", text);
}
fclose(my_file);
return 0;
}
As the length of each string is dynamic then, if I were you, I would read the file first for finding each string's size and then create a dynamic array of strings' length values.
Suppose your file is:
A long time ago
in a galaxy far,
far away....
So the first line length is 15, the second line length is 16 and the third line length is 12.
Then create a dynamic array for storing these values.
Then, while reading strings, pass as the 2nd argument to fgets the corresponding element of the array. Like fgets (string , arrStringLength[i++] , f);.
But in this way you'll have to read your file twice, of course.
You can use fgets() easily enough as long as you're careful. This code seems to work:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
enum { MAX_MESSAGES = 20 };
typedef struct Message
{
char dest[16];
char text[512];
} Message;
static int read_message(FILE *fp, Message *msg)
{
char line[sizeof(msg->text) + 1];
msg->dest[0] = '\0';
msg->text[0] = '\0';
while (fgets(line, sizeof(line), fp) != 0)
{
//printf("Data: %zu <<%s>>\n", strlen(line), line);
if (line[0] == '\n')
continue;
size_t len = strlen(line);
line[--len] = '\0';
if (msg->dest[0] == '\0')
{
if (len < sizeof(msg->dest))
{
memmove(msg->dest, line, len + 1);
//printf("Name: <<%s>>\n", msg->dest);
}
else
{
fprintf(stderr, "Error: name (%s) too long (%zu vs %zu)\n",
line, len, sizeof(msg->dest)-1);
exit(EXIT_FAILURE);
}
}
else
{
if (len < sizeof(msg->text))
{
memmove(msg->text, line, len + 1);
//printf("Text: <<%s>>\n", msg->dest);
return 0;
}
else
{
fprintf(stderr, "Error: text for %s too long (%zu vs %zu)\n",
msg->dest, len, sizeof(msg->dest)-1);
exit(EXIT_FAILURE);
}
}
}
return EOF;
}
int main(void)
{
Message mbox[MAX_MESSAGES];
int n_msgs;
for (n_msgs = 0; n_msgs < MAX_MESSAGES; n_msgs++)
{
if (read_message(stdin, &mbox[n_msgs]) == EOF)
break;
}
printf("Inbox (%d messages):\n\n", n_msgs);
for (int i = 0; i < n_msgs; i++)
printf("%d: %s\n %s\n\n", i + 1, mbox[i].dest, mbox[i].text);
return 0;
}
The reading code will handle (multiple) empty lines before the first name, between a name and the text, and after the last name. It is slightly unusual in they way it decides whether to store the line just read in the dest or text parts of the message. It uses memmove() because it knows exactly how much data to move, and the data is null terminated. You could replace it with strcpy() if you prefer, but it should be slower (the probably not measurably slower) because strcpy() has to test each byte as it copies, but memmove() does not. I use memmove() because it is always correct; memcpy() could be used here but it only works when you guarantee no overlap. Better safe than sorry; there are plenty of software bugs without risking extras. You can decide whether the error exit is appropriate — it is fine for test code, but not necessarily a good idea in production code. You can decide how to handle '0 messages' vs '1 message' vs '2 messages' etc.
You can easily revise the code to use dynamic memory allocation for the array of messages. It would be easy to read the message into a simple Message variable in main(), and arrange to copy into the dynamic array when you get a complete message. The alternative is to 'risk' over-allocating the array, though that is unlikely to be a major problem (you would not grow the array one entry at a time anyway to avoid quadratic behaviour when the memory has to be moved during each allocation).
If there were multiple fields to be processed for each message (say, date received and date read too), then you'd need to reorganize the code some more, probably with another function.
Note that the code avoids the reserved namespace. A name such as _message is reserved for 'the implementation'. Code such as this is not part of the implementation (of the C compiler and its support system), so you should not create names that start with an underscore. (That over-simplifies the constraint, but only slightly, and is a lot easier to understand than the more nuanced version.)
The code is careful not to write any magic number more than once.
Sample output:
Inbox (2 messages):
1: Marco
How are you?
2: Tarma
Are you going to the gym today?
I am getting the user to input 4 numbers. They can be input: 1 2 3 4 or 1234 or 1 2 34 , etc. I am currently using
int array[4];
scanf("%1x%1x%1x%1x", &array[0], &array[1], &array[2], &array[3]);
However, I want to display an error if the user inputs too many numbers: 12345 or 1 2 3 4 5 or 1 2 345 , etc.
How can I do this?
I am very new to C, so please explain as much as possible.
//
Thanks for your help.
What I have now tried to do is:
char line[101];
printf("Please input);
fgets(line, 101, stdin);
if (strlen(line)>5)
{
printf("Input is too large");
}
else
{
array[0]=line[0]-'0'; array[1]=line[1]-'0'; array[2]=line[2]-'0'; array[3]=line[3]-'0';
printf("%d%d%d%d", array[0], array[1], array[2], array[3]);
}
Is this a sensible and acceptable way? It compiles and appears to work on Visual Studios. Will it compile and run on C?
OP is on the right track, but needs adjust to deal with errors.
The current approach, using scanf() can be used to detect problems, but not well recover. Instead, use a fgets()/sscanf() combination.
char line[101];
if (fgets(line, sizeof line, stdin) == NULL) HandleEOForIOError();
unsigned arr[4];
int ch;
int cnt = sscanf(line, "%1x%1x%1x%1x %c", &arr[0], &arr[1], &arr[2],&arr[3],&ch);
if (cnt == 4) JustRight();
if (cnt < 4) Handle_TooFew();
if (cnt > 4) Handle_TooMany(); // cnt == 5
ch catches any lurking non-whitespace char after the 4 numbers.
Use %1u if looking for 1 decimal digit into an unsigned.
Use %1d if looking for 1 decimal digit into an int.
OP 2nd approach array[0]=line[0]-'0'; ..., is not bad, but has some shortcomings. It does not perform good error checking (non-numeric) nor handles hexadecimal numbers like the first. Further, it does not allow for leading or interspersed spaces.
Your question might be operating system specific. I am assuming it could be Linux.
You could first read an entire line with getline(3) (or readline(3), or even fgets(3) if you accept to set an upper limit to your input line size) then parse that line (e.g. with sscanf(3) and use the %n format specifier). Don't forget to test the result of sscanf (the number of read items).
So perhaps something like
int a=0,b=0,c=0,d=0;
char* line=NULL;
size_t linesize=0;
int lastpos= -1;
ssize_t linelen=getline(&line,&linesize,stdin);
if (linelen<0) { perror("getline"); exit(EXIT_FAILURE); };
int nbscanned=sscanf(line," %1d%1d%1d%1d %n", &a,&b,&c,&d,&lastpos);
if (nbscanned>=4 && lastpos==linelen) {
// be happy
do_something_with(a,b,c,d);
}
else {
// be unhappy
fprintf(stderr, "wrong input line %s\n", line);
exit(EXIT_FAILURE);
}
free(line); line=NULL;
And once you have the entire line, you could parse it by other means like successive calls of strtol(3).
Then, the issue is what happens if the stdin has more than one line. I cannot guess what you want in that case. Maybe feof(3) is relevant.
I believe that my solution might not be Linux specific, but I don't know. It probably should work on Posix 2008 compliant operating systems.
Be careful about the result of sscanf when having a %n conversion specification. The man page tells that standards might be contradictory on that corner case.
If your operating system is not Posix compliant (e.g. Windows) then you should find another way. If you accept to limit line size to e.g. 128 you might code
char line[128];
memset (line, 0, sizeof(line));
fgets(line, sizeof(line), stdin);
ssize_t linelen = strlen(line);
then you do append the sscanf and following code from the previous (i.e. first) code chunk (but without the last line calling free(line)).
What you are trying to get is 4 digits with or without spaces between them. For that, you can take a string as input and then check that string character by character and count the number of digits(and spaces and other characters) in the string and perform the desired action/ display the required message.
You can't do that with scanf. Problem is, there are ways to make scanf search for something after the 4 numbers, but all of them will just sit there and wait for more user input if the user does NOT enter more. So you'd need to use gets() or fgets() and parse the string to do that.
It would probably be easier for you to change your program, so that you ask for one number at a time - then you ask 4 times, and you're done with it, so something along these lines, in pseudo code:
i = 0
while i < 4
ask for number
scanf number and save in array at index i
E.g
#include <stdio.h>
int main(void){
int array[4], ch;
size_t i, size = sizeof(array)/sizeof(*array);//4
i = 0;
while(i < size){
if(1!=scanf("%1x", &array[i])){
//printf("invalid input");
scanf("%*[^0123456789abcdefABCDEF]");//or "%*[^0-9A-Fa-f]"
} else {
++i;
}
}
if('\n' != (ch = getchar())){
printf("Extra input !\n");
scanf("%*[^\n]");//remove extra input
}
for(i=0;i<size;++i){
printf("%x", array[i]);
}
printf("\n");
return 0;
}