Search code examples
c++filecontactsdev-c++

Number getting stored as special character in C++


#include<fstream>
#include<string.h>
#include<iostream>
using namespace std;
class contact
{
    long long ph;
    unsigned char name[20],add[50],email[30];
    public:
    void create_contact()
    {
        cout<<"Phone: ";
        cin>>ph;
        cout<<"Name: ";
        cin.ignore();
        cin>>name;
        cout<<"Address: ";
        cin.ignore();
        cin>>add;
        cout<<"Email address: ";
        cin.ignore();
        cin>>email;
        cout<<"\n";
    }
    void show_contact()
    {
        cout<<endl<<"Phone Number: "<<ph;
        cout<<endl<<"Name: "<<name;
        cout<<endl<<"Address: "<<add;
        cout<<endl<<"Email Address : "<<email;
    }
    long long getPhone()
    {
        return ph;
    }
    unsigned char* getName()
    {
        return name;
    }
    unsigned char* getAddress()
    {
        return add;
    }
    unsigned char* getEmail()
    {
        return email;
    }
};
fstream fp;
contact cont;
void save_contact()
{
    fp.open("contactBook.txt",ios::out|ios::app);
    cont.create_contact();
    fp.write((char*)&cont,sizeof(contact));
    fp.close();
    cout<<endl<<endl<<"Contact Has Been Sucessfully Created...";
    getchar();

Hey there, I am new to C++ as well as this community and in this is the code that I have been working on, the phone number of the contact is getting saved as random special characters. This is half of the code where I think the problem occurs Any ideas on how I could fix it? It would be of much help. Thanks!


Solution

  • I take it you expected to see the phone number written out in your text file as something like "15551234567." However, long long is not stored in this form in memory. It's actually stored as a 64-bit binary integer. The special characters you describe are likely the encoded version of that integer. If you read the data back in, you should find that it is still an integer.

    However, there is one remaining issue. You are missing ios::binary on the fstream open command. Each of the ios flag imbues the stream with a particular behavior:

    • ios::out - indicates that this stream should be an output stream that you can write bytes to
    • ios::app - indicates that this stream should be opened in "append" mode. This means that it will not erase the contents of the file every time you open it, and any bytes outputted to the stream are appended to the end of the file.
    • ios::binary - opens the file in binary mode, which is needed when you want to input/output binary data, rather than just text.

    You want to open the file with ios::out | ios::app | ios::binary. Forgetting binary is going to lead to very difficult to debug errors.

    Now binary mode is a bit of a pest. Sorry for this being a long read, but its a lot easier to come to grips with this flag if you understand the history behind it.

    Way back in the early days of computing, there was a disagreement about how to write a newline into a file. This way the days of typewriters, where starting a new line was broken into two actions. There was "carriage return" which moved the sliding bit of the typewriter back to the start of the line (this was the loud part of the motion), and there was a "line feed" which moved the paper up one spot. Each of these were separate actions, so they were given separate characters in ASCII, one of the the definitive ways to write text as a string of bytes. The 8-bit number 10 encoded a line feed (aka LF), and the 8-bit number 13 encoded a carriage return (aka CR). This would permit one to do things like overtyping, a trick where one types one character (like a letter) and then goes back to add another over the top (like an accent). You might write à by first typing a, doing a "carriage return" and then writing a `, just like you did on a typewriter.

    Some operating systems (such as Windows) encoded the start of the next line as both of these characters, so you'd see CR LF in a text file. Other operating systems (such as Unix) decided that it wasn't worth wasting a precious byte at the end of every line, so they chose to represent the start of a new line just with a LF. Others (such as Macintosh), decided to represent the new line as CR. Nobody could agree.

    To deal with this, many file reading/writing APIs treat these characters specially. fopen and fstream follow a pattern where if they see a CR LF or a CR in a text file, they silently turn it into a LF character when read. This lets you read every file type. Likewise, if it sees a LF character when writing, it expands it to whatever the platform specified a new line should look like. This lets you write cross-platform code which writes text files without having to pay attention to which new line character is used on each platform!

    However, this causes huge problems for binary data. Consider the number 302,844,416 written as a 32 bit number. In hexadecimal, we would write that as 0x120D0A00 (hex is a popular way to write numbers in programming because every byte can be written as 2 characters in hex). The issue is the middle two bytes of the number, 0x0D and 0x0A. In decimal, these are 13 and 10, which you should recognize as the same bytes as CR and LF.

    If the program tries to read that number in "text mode," it will see the CR LF pair, and turn it into just a single LF, per the C rules. Now, instead of our number being 0x120D0A00, its 0x120A00XX, where the XX is whatever the next byte was in the file. Very bad things! Not only is this data corrupted, but you probably needed the next byte for whatever came next in the file!

    ios::binary and the "b" flag for fopen resolve this. They tell C/C++ that the data is going to be binary. There wont be any new lines to convert. If you write bytes to a binary stream, they get written directly to the file, without any clever attempts to handle new lines.

    Your phone number is stored as a long long, which is a binary integer format. Without ios::binary, you run the risk of the number just happening to have a CR LF pair in it, and fstream will corrupt your data. ios::binary tells fstream to not mess with the data in that way.