File I/O
With C++ Fstream
Intro
File handling is as simple as writing in a book, much easier to modify and
find. It's so simple people get confused with it :-). Welcome to the world of file handling.
We will use the c++ fstream classes to do our file handling. So, what is a file? A file is just a bunch of bytes stored on a hardisk. Some have a specific structure others dont. Files are used to save info so that it can be retrived later for use. [I dont think you will want to save 100 people's address in memory will you].
Types of Files
Actually there are only two. Text files and binary files. In text files data is stored as readable chars and binary file are in machine language. So if you output abc123 to a text file you will see abc123 but in a binary file you may see only a bunch of black blocks if you use notepad. The binary files are smaller in size.
Fstream.h
fstream.h provides simultaneous input and output through ifstream, ofstream and fstream.
ifstream - open the file for input
ofstream - open the file for output
fstream - open the file for input/output/both
Writing to a file
Relatively very simple.
Steps:
- Declare an ofstream var.
- Open a file with it.
- Write to the file (there are a couple of ways.)
- Close it.
Eg Program 1.1
#include <fstream.h>
void main
{
ofstream file;
file.open("file.txt"); //open a file
file<<"Hello file\n"<<75; //write to it
file.close(); //close it
}
Methods for Writing to a file
The fstream class is derived from the iostream classes, so you can use ofstream variables exactly how you use cout. So you can use the insertion (<<) operator and the put().
Usages:
file<<"string\n";
file.put('c');
Reading from a file
Almost the same.
- Declare an ifstream var.
- Open a file with it.
- Read from the file(there are a couple of ways.)
- Close it.
Eg Program 1.2
#include <fstream.h>
void main
{
ifstream file;
char output[100];
int x;
file.open("file.txt"); //open a file
file>>output; //write to it
cout<<output; //result = Hello file
file>>x;
cout<<x; //result = 75
file.close(); //close it
}
Methods for Reading a file
The fstream class is derived from the iostream classes, so you can use fstream variables how you use cin.So you can use the extraction (<<) operator and the put().
Usages:
file>>char *; //ie an array
file>>char; //single char
file.get(char); //single char
file.get(char *,int); //read a string
file.getline(char *,int sz);
file.getline(char *,int sz,char eol);
**Notes:***
-
The file handles can be used like:
ofstream file("fl.txt");
ifstream file("fl.txt");
You will use the constructor for opening a file.I would not recommend using this not because it works less well or anything but in most cases it will improve code clarity and prevent errors when you are handling multiple files. If a file handle is used more than once without calling a close() in between them there will be errors. This is just provided for info sake if you need to use it in a hurry or in something small.
-
Never ever declare a fstream variable globally. It is a bad habit. If you forget to close it next time you run the program it will show access errors to the C: drive (or which ever drive you use) and you will have to restart the computer. Declare them within funtions or classes and close them when their use is over.
-
If you are doing databases or any file handling for that matter never put any file i/o into classes. It will simply complicate debugging and you may also open a single file multiple times with difffrent objects at the same time. This is definitely not what we want. Classes are only to be used when you have to minimal file i/o but still I recommend you use normal funtions.
-
ifstream stands for input stream and can only be used for input.
-
ofstream stands for output stream and can only be used for output.
- Any variable declared with ifstream,ofstream or fstream is called a file handle.
That wraps up the simple very stuff. You will not use them much unless you are working with text files or in a small project. Now we will move on to fstream which is more flexible and will be most used. It's easy if you look at it logically.
Dynamic file access
The file streams we discussed have a limitation, they can only do input or output at a time. fstream provides us with a way to read, write randomly without having to close and reopen the file. It has great flexibility involving a bit more work with returns being ten fold. Hey, you cant have everything for free (what fun would it be if everything was handed to you).
Lets look at how a file can be opened with fstream.
Program Example 1.3
void main()
{
fstream file;
file.open("file.ext",iso::in|ios::out)
//do an input or output here
file.close();
}
Notice anything new? The ios::--- are attributes which define how a file should be opened.
List of Attributes
ios::in open file for reading
ios::out open file for writing
ios::app open for writing,add to end of file(append).
ios::binary Binary file
ios::nocreate do not create the file,open only if it exists
ios::noreplace open and create a new file if the specified file does not exist
ios::trunc open a file and empty it.(Boom, all the data is gone,if any)
ios::ate goes to end of file instead of the begining
Notes
-
The default mode is text mode when a file is opened
-
By default if a file does not exist,a new one is created
-
Multiple attributes can be specified at a time seperated by ...|...|...
-
All attributes start with an ios:: (as if you dint notice it,but still let me hammer it)
-
These attributes can be used with ifstream and ofstream but of course ios::in wont work with ofstream and similarly ios::out wont .............
- File byte location start from zero (like in arrays)
Now we know how to open a file with fstream. Cool, now come read and write.
Reading and Writing with fstream
The two funtion are exactly similar in usage and simple to understand
file.write(char *,int); //for writing
file.read(char *,int); //for reading (gee...:D)
Until now we have only been able to use strings and ints to write/read. Most databases will want to store data in structures or classes. If we had to write a seperate function to split and write each member, brrr horrors. C++ goes to be very nice and provides us with a way to write entire classes or structures with little work
struct x
{
int i;
char a;
char s[10];
}data;
file.write((char*)&data,sizeof(x));
file.read((char*)&data,sizeof(x));
Hold on, what the heck is the char * thingy???.
That's called typecasting. Quite an interesting subject actually. It means converting one data type to another. Here it is converting struct x into a char pointer and it's address is passed to the funtion. If anybody wants to know more, well tell!
The rest is simple. You pass the size of the structure which can be found out by using sizeof();
Eg:
cout<<"\nInt:"<<sizeof(int);
cout<<"\nFloat:"<<sizeof(float);
cout<<"\nChar:"<<sizeof(char);
Now instead of going more yack yack I show you an example which should explain a lot more. Sigh "A picture is worth a thousand word, source is worth a million bytes"
Example Program 1.4
#include <fstream.h>
struct contact
{
char name[10];
int age;
}
struct address
{
char city[10];
char country[10];
}
class DtbRec
{
private:
contact cnt;
address adr;
public:
void getdata();
void dispdata();
};
/*
I will give you a bit of work, make the funtions getdata() and dispdata()
It's easy and not worth for me to bother with now :-}
*/
void DtbRec::getdata() //get user input
{
}
void DtbRec::dispdata() //display to screen
{
}
//This program is not tested, have fun fixing errors, if any
//I am a taos programmer so dont expect anything major
//Typing mistakes are not errors
//This was done off the cuff and not even compiled once
void main()
{
DtbRec xRec; //temp rec
fstream fl_h; //file handle
char ch;
int num;
fl_h.open("database.dtb",ios::in|ios::out|ios::binary);
do
{
cout<<"\n\nFstream Dtb\n"
<<"\n1.Add records"
<<"\n2.View records"
<<"\n3.Modify records"
<<"\n4.Exit"
<<"\n\tEnter Choice:";
cin>>ch;
if(ch == '1') //we are dealing with chars not ints
{
//Adding a rec
fl_h.seekp(0,ios::end); //will disscuss this later,this sets the file write pointer to
//the end of a file.
xRec.getdata(); //Get some data from the user
fl_h.write((char*)&xRec,sizeof(DtbRec);
}
else if(ch == '2')
{
//View recs
fl_h.seekg(0,ios::beg); //will disscuss this later,this sets the file read pointer to the
//begining of a file.
num = 0;
while(!fl_h.eof()) //will disscuss this later,it check if the file's end has been reached
{
n++;
fl_h.read((char*)&xRec,sizeof(DtbRec);
cout<<"\nRecord No["<<num<<"]\n";
xRec.dispdata(); //Show the user all the data present
}
}
else if(ch == '3')
{
//Modify me colors ;-)
cout<<"Enter the record no(starts at 0):";
cin>>num;
fl_h.seekg(num*sizeof(DtbRec),ios::beg); //move the read pointer to where the rec is
fl_h.read((char*)&xRec,sizeof(DtbRec); //read it
xRec.dispdata(); //Show the info
xRec.getdata(); //Let the user change the info
fl_h.seekp(num*sizeof(DtbRec),ios::beg); //move the write ponter this time
fl_h.write((char*)&xRec,sizeof(DtbRec); //overwrite with new info
//yahoo,modification done.I have seen too many people who just
//cant get modification .It's so simiple I just cant get why they just cant
//get it ;-)
}
}
while(ch != '4');
fl_h.close(); //close the file
cout<<"\nEnd of Program";
}
Got more doubts now than what was cleared? Good, doubts are the first step towards knowledge. (If you happen to be of an opinion that a teacher should explain everything, sorry it's just not my style. I want thinking students not tape recorders)
Note:
-
Records start at byte zero. I said this once and I will say it again.(Dont forget user friendlyness though.)
-
read() and write() can be used to write both classes and structures to the file in the same way.
-
Always know where you are before you start reading or writing to a file or move the pointer to the area of work.
-
The read pointer and write pointer are separate. So move the correct pointer if you want fstream to work.
-
Always close the file when it's use is over.
-
Always perform checks when necessary or appropriate. fstream has a number of buit-in ones for you :-)
-
Never open a new file with a file handle without calling close().
- Remember, with structs and classes their size is always fixed so finding the exact location of a record can be done mathematically.
------------------------- PART II -------------------------
Random File Access
With fstream we work with 2 file pointers, read and write. By moving these pointers we go access any part of the file at random. See below:
Reading
1.seekg(); //move the read pointer in bytes
2.tellg(); //returns where the read pointer is in bytes
Writing
1.seekp(); //move the write pointer in bytes
2.tellp(); //returns where the write pointer is in bytes
Both tellp() and tellg() don't take any parameters but return where the pointer location in bytes.
Eg.
int rp = file.tellg();
int wp = file.tellp();
The seek pointers take 2 parameters
Eg.
file.seekg(0,ios::beg);
List of seek attributes
ios::beg move from the beginning of the file
ios::end move from the end of the file
ios::cur move from current location of the file
Note:
-
ios::beg is default. I recommend you still pass it where ever you use it.
- Negative numbers can be used with ios::cur & ios::end.(not ios::beg figure it out)
Part II
Databases & Security
(Based on a true story of a database)
Intro
This is another area people find difficulty. I figured it out myself without any help what so ever. I also learned file handling a year before my classmates by looking at an elder's text which contained only vague descriptions so I guess my eagerness to learn had something
to do with it. I have always loved computers and programming. I fell in love with QBasic the moment I saw it. C++ stole my heart then. Ah, programming is a part of my soul.
And even when I got my own text book nothing what so ever was mentioned about random file access except a short and vague description about the 2 functions and the options. There was no reference what so ever about locating individual records. I can't guess why! Reminds me of a saying "when old, one forgets how it is to be young". That maybe a cause :D.
[oh, this reminds me, almost every comp guy/gal I know works late at night. I do too when I don't have much time in the mornings. We should stop this. Never work in the dark without enough light, nor stay up too late. If your eyes feel slightly sleepy,SLEEP. No more than 1:30 AM at the most, I am thankful I been able to do most of this]
Locating Individual Records
Problem area. Let start at the beginnings. Ever seen a graph paper ?.It has a lot of tiny, small, medium and large sized squares. Also each is being made from the tiniest square. What that got a do with files? Well when you use databases you will almost always use structures and they are always the same size (in that particular database). That means like in the program in Part I all the records are with that one class and when you write it to a file it will still be the same size even if you filled it or not. It's like your water bottle. Whether it's full, half or even empty it will always
take up the same space in your bag. Get it?
How do you find the size of any data type ?Easy.
Size_Of_Data_Type = sizeof(Data_Type); //sizeof is a reserved keyword in C++.
Ok, how will that help us find a record? Well all records are of the same size, they start at zero. They can be thought of as an array too and the concept is similar to how you use normal pointers.
Eg. A struct is 20 bytes in size. The first rec starts at byte zero, the second at byte 20, the next at byte 40 ...... and so on
Eg. Program 2.1
file.seekg( Rec_no * sizeof(Data_Type),ios::beg);
file.read((char*)&rec_var,sizeof(Data_Type));
file.seekp( Rec_no * sizeof(Data_Type),ios::beg);
file.write((char*)&rec_var,sizeof(Data_Type));
Simple as that.
Note:
-
This can be use with ios::beg,ios::cur and ios::end
- When read() or write() is called the appropriate pointer is moved automatically to the next record or rather by a number of bytes as passed to it(that's the sizeof(Data_Type))
I think now you get random access. How about finding the total number of records in a database?
Eg. Program 2.2
file.seekg(0,ios::end); //move to the end of the file
int fl_sz = file.tellg(); //tell me where you are, partner( pointer ) !
int total_no_rec = fl_sz/sizeof(Data_Type);
file.seekg(0,ios::beg); //move to the beg of the file
cout<<"There are "<<total_no_rec<<" (s) in this database ";
We divide the total file size by the size of the structure to find the number of record. Simple maths eh?
That's it you have full knowledge of how to handle fstream. Now all you need is creativity, so use it. I won't tell you all, so think. Knowledge can be gained from others but wisdom only from yourself (and from God).
Ah, I love this. Here is some work for you.
- Re-write the example program to include random reading, writing, and modification on multiple databases. [Put in error checks and don't allow the user to read or modify a rec which does not exist]
- Find a way to write a whole single dimensional array of a struct to a file with just one write statement. [No hints, re-read this paper if you have to]
- Deletion. Add a way to allow a person to delete an existing record.
- [One way to do it, copy all the records except the one to be deleted to another temp file, open the database with ios::trunc and copy the records from the other temp file.]
Error Checking
These are some funtions that help to keep track of errors in your database if any
Error Funtions
good() returns true if the file has been opened without problems
bad() returns true if there were errors when opening the file
eof() returns true if the end of the file has been reached
All are member funtions and are used as file_handle.errror_funtion()
Eg.
char ch;
ifstream file("kool.cpp",ios::in|ios::out);
if(file.good()) cout<<"The file has been opened without problems;
else cout<<"An Error has happend on opening the file;
while(!file.eof())
{
file>>ch;
cout<<ch;
}
You should have understood what those funtions are for. Implement them when you do you file handling. good() should be called after opening a file to see it has been opened properly. bad() is the opposite of good().
Encryption
Aha, any decent database program must encrypt its files. Just open up any of the files from the example programs with notepad and you will be able to see the text. We definitely don't want people to see that.
With encryption you can scramble the text so people can read it. Now there are a lot of encryption schemes and a lot of other methods for protecting data. I will just show you a couple of methods.
Binary Shift
The simplest of all. In this you increase or decrease a char by a number.
void b_shif(char *s,int n)
{
for(int i=0;s[i]!=0;i++)
{
s[i] += n;
}
}
XOR
Another type of encryption. Exclusive-OR encryption, is almost unbreakable through brute force methods. It is susceptible to patterns, but this weakness can be avoided through first compressing
the file (so as to remove patterns). This encryption while extremely simple, is nearly unbreakable.
int xor(char *string, char *key)
{
int l=0;
for(int x=0; string[x]!='\0'; x++)
{
string[x]=string[x]^key[l];
l++;
if(l>=strlen(key)) l=0;
}
return 0;
}
Encrypt an entire structure
void struct_enc_xor(char *str,char *key,int sz)
{
int l=0;
for(int x=0; str[x]!='\0'; x++)
{
str[x]=str[x]^key[l];
l++;
if(l>=strlen(key)) l=0;
}
return 0;
}
Use it exactly how you used read().
Eg.
struct_enc_xor((char*)&cRec,"password",sizeof(DtbRec));
Exit
Encryption is a very interesting topic. A lot of research has gone into it. If you are interested there are lot of pages on the web which describe encryption and various schemes. Then there is stenography,
the art of hiding info in any file like text within a bitmap or an mp3. Have fun researching.