Advertisement
If you have a new account but are having problems posting or verifying your account, please email us on hello@boards.ie for help. Thanks :)
Hello all! Please ensure that you are posting a new thread or question in the appropriate forum. The Feedback forum is overwhelmed with questions that are having to be moved elsewhere. If you need help to verify your account contact hello@boards.ie
Hi there,
There is an issue with role permissions that is being worked on at the moment.
If you are having trouble with access or permissions on regional forums please post here to get access: https://www.boards.ie/discussion/2058365403/you-do-not-have-permission-for-that#latest

C++ stucture skipping a line

  • 26-04-2011 12:04pm
    #1
    Registered Users, Registered Users 2 Posts: 649 ✭✭✭


    Hi, sorry to bother you with my noob question. I'm writing a kind of library structure however my program keeps displaying the "author" and "category" line on the same line, skips the "author" cin and only takes the "category" cin.

    Could it be because I am opening / creating my file in append mode?

    Any help would be appreciated.
    void addrec()
     {
    
      FILE *fp;
    
      struct rec_book{
      char title[20];
      char author[20];
      char category[20];
      char ISBN[10];
      int copies;
      };
    
      rec_book new_book;
    
      fp=fopen("c:\\books.dat","ab");
    
      clrscr();
      cout<<"Please enter the title of the book: ";
      cin>>new_book.title;
    
      cout<<"Please enter the author of the book: ";  //This line
      cin>>new_book.author;
    
      cout<<"What category does the book belong in?: "; //And this line appear 
      cin>>new_book.category;                           //on the same line.
    
      cout<<"Please enter the ISBN of the book: ";
      cin>>new_book.ISBN;
    
      cout<<"Please enter the amount of copies: ";
      cin>>new_book.copies;   //HERE
    
    
      fwrite(&new_book,1,sizeof(new_book),fp);
    
      fclose(fp);
    
      gotoxy(20,10);
      cout<<"Book now on file - press any key for main menu...";
      getch();
    


Comments

  • Registered Users, Registered Users 2 Posts: 240 ✭✭Axe Rake


    Did you try going through the code with the debugger?

    Notice any unexpected behavior?

    Seems strange..


  • Closed Accounts Posts: 4,564 ✭✭✭Naikon


    Hi, sorry to bother you with my noob question. I'm writing a kind of library structure however my program keeps displaying the "author" and "category" line on the same line, skips the "author" cin and only takes the "category" cin.

    Could it be because I am opening / creating my file in append mode?

    Any help would be appreciated.
    void addrec()
     {
    
      FILE *fp;
    
      struct rec_book{
      char title[20];
      char author[20];
      char category[20];
      char ISBN[10];
      int copies;
      };
    
      rec_book new_book;
    
      fp=fopen("c:\\books.dat","ab");
    
      clrscr();
      cout<<"Please enter the title of the book: ";
      cin>>new_book.title;
    
      cout<<"Please enter the author of the book: ";  //This line
      cin>>new_book.author;
    
      cout<<"What category does the book belong in?: "; //And this line appear 
      cin>>new_book.category;                           //on the same line.
    
      cout<<"Please enter the ISBN of the book: ";
      cin>>new_book.ISBN;
    
      cout<<"Please enter the amount of copies: ";
      cin>>new_book.copies;
    
    
      fwrite(&new_book,1,sizeof(new_book),fp);
    
      fclose(fp);
    
      gotoxy(20,10);
      cout<<"Book now on file - press any key for main menu...";
      getch();
    

    The problem is your code is not ANSI/ISO compliant. Use of function calls like gotoxy() and getch() and clrscr() are Microsoft only specifics, thus breaking the standard. It could be a bug with the libraries associated with those calls, but I can't test it right now. Calling clrscr() might be resetting some terminal attributes which could be causing a problem. I have tried the code on my own machine without those functions and there is no skipping, so I think it's an issue with those Microsoft specific function calls. Just remove the non standard bits I mentioned and it should work fine. What compiler/headers are you using? Try replacing clrscr with: system("cls");


  • Registered Users, Registered Users 2 Posts: 15,070 ✭✭✭✭Malice


    Could you use a newline character (like '\n' or 'endl') to move the text down or does the user pressing the Return key to enter values already achieve that?


  • Registered Users, Registered Users 2 Posts: 649 ✭✭✭Steviemoyne


    Thanks for the help guys.

    Axe Rake, done the debugger and it just seems to output both lines and look for the category input completely skipping the author. I had the problem with another program before but unfortunately my tutor never explained how to fix it and just moved on in the module:(.

    The headers and compiler I'm using Naikon are:

    Borland C++ version 5.02 I suspect the compiler may be the issue but I don't have access to another compiler.

    Headers below:

    #include<iostream.h>
    #include<conio.h>
    #include<stdio.h>
    #include<fstream.h>

    I tried what you said and took out the clrscr(); getch(); and gotxy(); and got an error saying: phonebook.cpp (74,24) : Illegal character ' ' (0x1) upon checking the area it brought me to (edited the original message to show where commented as //HERE) and checking the code above to make sure I didn't do something completely stupid like forget to close a statement, I could find nothing.

    Malice I can use both but it does not fix the skipping line problem unfortunately.


  • Registered Users, Registered Users 2 Posts: 649 ✭✭✭Steviemoyne


    The Illegal character error seems to be with the following line:

    cin>>new_book.copies;

    Renaming the variable in the structure template and the line did not help.

    Also when I commented it out the program still skipped the author line.

    EDIT: The illegal characters were invisible ones that Borlands would not display. I had to use a different compiler called Codepad to make these appear.
    http://codepad.org/WiSaxecN

    As you can see there are two lines. One after the cin of title and one on the same line as the cin of copies that look line a small box with 0 and 1 in them. I guessed their position and deleted them and the program ran. However it is still skipping.


  • Advertisement
  • Registered Users, Registered Users 2 Posts: 649 ✭✭✭Steviemoyne


    I decided to check if my inputs for the running program was the problem.

    It seems to be the if I try to enter something like: "ss tt" for the title the program skips author.

    However if I enter "ss" it does not. And this happens with any size input even the maximum 20 I have set. If I do not use a space the program runs fine.

    So I know how to get around the problem so technically the problem is solved. If anyone knows what is causing the error I would be very interested to know.


  • Registered Users, Registered Users 2 Posts: 89 ✭✭tehjimmeh


    The ">>" operator reads all text in an input stream up until a newline or whitespace.

    So when you read a string with spaces from cin with it, it only assigns the portion up until the first space to the variable on the RHS. The next time the program wants to read from cin with ">>", the remainder of the original string is still in the input buffer, so it assigns the rest of the string to the variable on the RHS.

    Try an input with two spaces, "ss tt uu", for example, you'll find that the "category" will be skipped too.

    What you want is getline(). e.g. replace "cin>>new_book.title;" with "cin.getline(new_book.title, 20);". This reads input up to a newline or 20 characters, whichever is hit first.

    Naikon wrote: »
    The problem is your code is not ANSI/ISO compliant.
    Wtf? Standards compliance has absolutely nothing to do with their problem...


  • Registered Users, Registered Users 2 Posts: 2,426 ✭✭✭ressem


    The keyboard is seen as an input stream.
    The cin >> operator treats all whitespace the same way, as an indicator to end (a delimiter). And the remainder is buffered and used with the next cin >> without asking the user.
     // get 19 characters, and append NULL to end string  
    cin.getline(new_book.title,20);
    
    is closer to what you want to use.

    Using cin is a bit of a pain combined with char arrays though.
    You'll see more issues with when mixing inputs, when entering overlong strings etc.

    I'd suggest that if you can't use a library dedicated to handling user input in a reasonable, simple way, then you could add in the function below to clear out the input buffer and status flags before calling cin.getline() or similar functions.

    from http://support.microsoft.com/kb/132422
       int ClearError(istream& isIn)        // Clears istream object
       {
          streambuf*  sbpThis;
          char        szTempBuf[20];
          int         nCount, nRet = isIn.rdstate();
    
          if  (nRet)                        // Any errors?
          {
              isIn.clear();                 // Clear error flags
              sbpThis = isIn.rdbuf();       // Get streambuf pointer
              nCount = sbpThis->in_avail(); // Number of characters in buffer
    
              while (nCount)                // Extract them to szTempBuf
              {
                  if  (nCount > 20)
                  {
                      sbpThis->sgetn(szTempBuf, 20);
                      nCount -= 20;
                  }
                  else
                  {
                      sbpThis->sgetn(szTempBuf, nCount);
                      nCount = 0;
                  }
              }
          }
    
          return  nRet;
       }
    

    So your code would be
      cout<<"Please enter the title of the book: ";
      ClearError(cin);
      cin.getline(new_book.title,20);
    
    

    As an alternative to the above you could use a c++ string to hold the user input, then cut to shape.
    #include <string>
    
    string tempStore="";
    cout<<endl<<"Prompt for input"<<endl; 
    getline(cin,tempStore);
    // 19 chars + NULL in char array. Remainder trimmed or underflow filled with NULLs.
    tempStore.resize(19); 
    strcpy(new_book.title,tempStore.c_str()); // c_str adds the NULL to end.
    


  • Closed Accounts Posts: 4,564 ✭✭✭Naikon


    tehjimmeh wrote: »
    The ">>" operator reads all text in an input stream up until a newline or whitespace.

    So when you read a string with spaces from cin with it, it only assigns the portion up until the first space to the variable on the RHS. The next time the program wants to read from cin with ">>", the remainder of the original string is still in the input buffer, so it assigns the rest of the string to the variable on the RHS.

    Try an input with two spaces, "ss tt uu", for example, you'll find that the "category" will be skipped too.

    What you want is getline(). e.g. replace "cin>>new_book.title;" with "cin.getline(new_book.title, 20);". This reads input up to a newline or 20 characters, whichever is hit first.

    Decent explanation. Didn't know cin terminates after reading a whitespace. I was under the assumption that whitespace isn't a special char. Is there a real reason for this? Just wondering.
    tehjimmeh wrote: »
    Wtf? Standards compliance has absolutely nothing to do with their problem...


    I use fgets() along sscanf() quite regularly in C, but yeah, I think I should have known better than to comment about iostream nuances. I honestly didn't know cin reads up to BOTH the newline char and whitespace. I guess I am pampered too much with C specifics :p

    Here is the code I tested OP. It's not robust by any means.
    #include <stdio.h>
    #include <stdlib.h>
    #define MAX 30
    
    int main(int argc, char **argv)
    {
    
        FILE *fp;
    
        struct rec_book
        {
            char title[20];
            char author[20];
            char category[20];
            char ISBN[10];
            char copies[20];
        };
    
        struct rec_book new_book;
        fp=fopen("c:\\books.dat","ab");
    
        printf("Please enter the title of the book:");
        fgets(new_book.title, MAX, stdin);
    
        printf("Please enter the author of the book:");
        fgets(new_book.author, MAX, stdin);
    
        printf("What category does the book belong in?:");
        fgets(new_book.category, MAX, stdin);
    
        printf("Please enter the ISBN of the book:");
        fgets(new_book.ISBN, MAX, stdin);
    
        printf("Please enter the amount of copies:");
        fgets(new_book.copies, MAX, stdin); //strtol
    
        fwrite(&new_book,1,sizeof(new_book),fp);
        fclose(fp);
    
        printf("Book now on file - press any key for main menu...");
        exit(EXIT_SUCCESS);
    
    }
    


  • Closed Accounts Posts: 4,564 ✭✭✭Naikon


    ressem wrote: »
    The keyboard is seen as an input stream.
    The cin >> operator treats all whitespace the same way, as an indicator to end (a delimiter). And the remainder is buffered and used with the next cin >> without asking the user.
     // get 19 characters, and append NULL to end string  
    cin.getline(new_book.title,20);
    
    is closer to what you want to use.

    Using cin is a bit of a pain combined with char arrays though.
    You'll see more issues with when mixing inputs, when entering overlong strings etc.

    I'd suggest that if you can't use a library dedicated to handling user input in a reasonable, simple way, then you could add in the function below to clear out the input buffer and status flags before calling cin.getline() or similar functions.

    from http://support.microsoft.com/kb/132422
       int ClearError(istream& isIn)        // Clears istream object
       {
          streambuf*  sbpThis;
          char        szTempBuf[20];
          int         nCount, nRet = isIn.rdstate();
    
          if  (nRet)                        // Any errors?
          {
              isIn.clear();                 // Clear error flags
              sbpThis = isIn.rdbuf();       // Get streambuf pointer
              nCount = sbpThis->in_avail(); // Number of characters in buffer
    
              while (nCount)                // Extract them to szTempBuf
              {
                  if  (nCount > 20)
                  {
                      sbpThis->sgetn(szTempBuf, 20);
                      nCount -= 20;
                  }
                  else
                  {
                      sbpThis->sgetn(szTempBuf, nCount);
                      nCount = 0;
                  }
              }
          }
    
          return  nRet;
       }
    

    So your code would be
      cout<<"Please enter the title of the book: ";
      ClearError(cin);
      cin.getline(new_book.title,20);
    
    

    As an alternative to the above you could use a c++ string to hold the user input, then cut to shape.
    #include <string>
    
    string tempStore="";
    cout<<endl<<"Prompt for input"<<endl; 
    getline(cin,tempStore);
    // 19 chars + NULL in char array. Remainder trimmed or underflow filled with NULLs.
    tempStore.resize(19); 
    strcpy(new_book.title,tempStore.c_str()); // c_str adds the NULL to end.
    

    Forgive my potential ignorance, but is it not better to use standard compliant library calls even if performance may or may not be improved? I like to think getting it to work is the first point of call. Make it work, then make it fast by optimizing bits that could use some tweaking. It would be a pain in the ass to have to haul around your own reinventions of the wheel all the time to boot. Granted, the above code is more of a wrapper. Still though. Standards are good.

    strcpy is very unsafe btw :p


  • Advertisement
  • Registered Users, Registered Users 2 Posts: 649 ✭✭✭Steviemoyne


    Well, I don't understand the majority of that apart from that cin>> takes a space as a termination and moves onto the next. Or something along those lines.

    It explains, and solves the problem though so that's great.

    Thank you all for the help. It is very much appreciated.


  • Closed Accounts Posts: 4,564 ✭✭✭Naikon


    Well, I don't understand the majority of that apart from that cin>> takes a space as a termination and moves onto the next. Or something along those lines.

    It explains, and solves the problem though so that's great.

    Thank you all for the help. It is very much appreciated.

    No you are right. cin terminates once it hits a whitespace or newline. C fgets() only counts newlines/EOF marker. If you are using C++ you best stick with cin.


  • Registered Users, Registered Users 2 Posts: 89 ✭✭tehjimmeh


    Naikon wrote: »
    Decent explanation. Didn't know cin terminates after reading a whitespace. I was under the assumption that whitespace isn't a special char. Is there a real reason for this? Just wondering.
    cin doesn't terminate with a whitespace, the ">>" operator does.

    My understanding is that when you want to read something from an input stream in c++, it checks the input buffer for that stream, if it's empty, another line (i.e. text up until a newline character) is read into the buffer.

    cin is just a special input stream where the line read into the input buffer comes from user input.

    When the ">>" operator is used, text in the input stream's input buffer up until a whitespace is read, leaving the rest of the string in the input buffer.

    When getline is used, text in the input stream's input buffer up to a newline is read.

    I think the rationale behind the behaviour of the ">>" operator is to easily facilitate the parsing of space delimited data.


  • Registered Users, Registered Users 2 Posts: 2,426 ✭✭✭ressem


    Naikon wrote: »
    Forgive my potential ignorance, but is it not better to use standard compliant library calls even if performance may or may not be improved? I like to think getting it to work is the first point of call. Make it work, then make it fast by optimizing bits that could use some tweaking. It would be a pain in the ass to have to haul around your own reinventions of the wheel all the time to boot. Granted, the above code is more of a wrapper. Still though. Standards are good.

    strcpy is very unsafe btw :p

    "Standard compliant library calls" is the problem though. This is low level stuff that should have had a standard cross-platform wrapper for eons now.

    Instead every unmentored kid is left searching for functions and libraries to do the basics, like throwing away excessive input.
    It's like the strcpy situation. You have strlcpy and strcpy_s as replacements. I end up just copying the strlcpy into every C prog. And using ncurses library when a C program needs user interaction.

    strcpy is unsafe when you aren't sure that the input has had a null added to it. The c_str() would have to fail for that situation to occur in the post, or to be copy/pasted and the wrong array sizes filled in.

    fgets() with a buffer size exceeding the destination though? (fake horror).
    I'm only kidding, fgets is a better standard way of getting input but it still leaves the Op with the issue of when too much input is provided, then the prompts will be skipped.

    Can we compromise on
    #include <string>
    #define BUFFSIZE 30
    
    ...
       struct rec_book
        {
            char title[BUFFSIZE ];
            char author[BUFFSIZE ];
            char category[BUFFSIZE ];
            char ISBN[BUFFSIZE ];
            char copies[BUFFSIZE ];
        };
    
    
    string tempStore="";
    cout<<endl<<"Prompt for input"<<endl; 
    getline(cin,tempStore);
    // 19 chars + NULL in char array. Remainder trimmed or underflow filled with NULLs.
    tempStore.resize(BUFFSIZE-1 ); 
    strncpy(new_book.title,tempStore.c_str(), BUFFSIZE ); // c_str adds the NULL to end.
    


  • Closed Accounts Posts: 4,564 ✭✭✭Naikon


    ressem wrote: »
    "Standard compliant library calls" is the problem though. This is low level stuff that should have had a standard cross-platform wrapper for eons now.

    Instead every unmentored kid is left searching for functions and libraries to do the basics, like throwing away excessive input.
    It's like the strcpy situation. You have strlcpy and strcpy_s as replacements. I end up just copying the strlcpy into every C prog. And using ncurses library when a C program needs user interaction.

    strcpy is unsafe when you aren't sure that the input has had a null added to it. The c_str() would have to fail for that situation to occur in the post, or to be copy/pasted and the wrong array sizes filled in.

    fgets() with a buffer size exceeding the destination though? (fake horror).
    I'm only kidding, fgets is a better standard way of getting input but it still leaves the Op with the issue of when too much input is provided, then the prompts will be skipped.

    Can we compromise on
    #include <string>
    #define BUFFSIZE 30
    
    ...
       struct rec_book
        {
            char title[BUFFSIZE ];
            char author[BUFFSIZE ];
            char category[BUFFSIZE ];
            char ISBN[BUFFSIZE ];
            char copies[BUFFSIZE ];
        };
    
    
    string tempStore="";
    cout<<endl<<"Prompt for input"<<endl; 
    getline(cin,tempStore);
    // 19 chars + NULL in char array. Remainder trimmed or underflow filled with NULLs.
    tempStore.resize(BUFFSIZE-1 ); 
    strncpy(new_book.title,tempStore.c_str(), BUFFSIZE ); // c_str adds the NULL to end.
    

    Decent solution. I agree with the fact low level issues arise at this level. That is why I tend to advise people to stay away from C/C++ for anything that does not really require performance. Great tools, but you have to figure out what you are trying to solve first. You would be surprised how much you can get away with just using a command shell for daily tasks. The problem I found with programming in college, is the Lecturers overlook safe string handling 100%. It's not ok to allow a user to enter an arbitrary string, cause it could be too long and cause a buffer overflow to potentially allow someone to overwrite/mess around with the local subroutine return address.

    That does not account for the other problems related to type expectations etc. Standard scanf() is a joke if you allow any user access to that call. Seriously bad. I am by no means an expert, but it's always best to simply store the distinct user input in buffers that have nothing to do with internal structs/data structures, parse these unsafe buffers, then go from there. Treat the user as an enemy imo. Some jackass knows a little about shellcode. Boom. Company potentially at risk. Memory protection means nothing if the context is a local function imo.

    That is why I tend to go with a simple fgets() + sscanf() combo, but even then, nothing is perfect. Damn weak typing.

    /very long rant


Advertisement