Types of Files


Computers only provide two types of files: text files and binary files. Programs use text files to store text, HTML, XML, C++ source code, and many other types of information. They use binary files to store word processing documents, spreadsheets, images, sounds, and so forth.

Games use both text and binary files. Therefore, the remainder of this chapter demonstrates how to read and write information using both text and binary files.

Text Files

A text file is just what it sounds like. It's a file that can only hold text. These files can't hold just any character; the text must be from the set of characters specified by the ASCII character set. The ASCII character set is a standard set of characters used by all computer manufacturers. ASCII stands for the American Standard Code for Information Exchange. Because it's one of the oldest standardized character sets, all computers recognize the ASCII character set, which you'll find on the CD on the main HTML page that is displayed when you insert it into your CD-ROM drive.

The ASCII character set contains characters for all the letters of the English alphabet. In addition, it has characters for the numeric digits 09, some punctuation marks, and other common characters.

Note

The mainframe computers made by IBM do not use the ASCII character set. Mainframes are huge computers that are used for business computing, not games. Therefore, we won't deal with them in this book.


Writing to Text Files

Back in chapter 2, "Writing C++ Programs," I introduced the notion of streams. Recall that the C++ Standard Library defines the streams cin and cout for you. The C++ Standard Library also provides stream classes for file input and output. However, unlike cin and cout, streams for files are not created automatically. Your program must create them itself.

To create a stream for text file output, your program must have access to the class ofstream. It can get access by including the C++ Standard Library header file fstream. Your program then declares a variable of type ofstream, which stands for output file stream. After the stream has been created, your program can write to it using the insertion operator. In that respect, it's just like writing to cout. Listing 12.1 demonstrates how to write to a text file.

Listing 12.1. Using streams to output text

 1     #include <cstdlib> 2     #include <iostream> 3     #include <fstream> 4 5     using namespace std; 6 7     int main(int argc, char *argv[]) 8     { 9         std::string textArray[] = 10        { 11           "Jeeves--my man, you know--is really a most extraordinary ", 12           "chap. So capable. Honestly, I shouldn't know              what to do ", 13           "without him. On broader lines he's like those chappies who", 14           " sit peering sadly over the marble battlements at the ", 15           "Pennsylvania Station in the place marked \"Inquiries.\" ", 16           "You ,know the Johnnies I mean. You go up to them and say:", 17           " \"When's the next train for Melonsquashville, ", 18           "Tennessee?\" and they reply, without stopping to think,", 19           " \"Two-forty-three, track ten, change at San Francisco.\" ", 20           "And they're right every time. Well, Jeeves gives you just ", 21           "the same impression of omniscience.", 22           "My Man Jeeves, P.G. Wodehouse, 1919" 23      }; 24 25      cout << "Opening file..."; 26      std::ofstream outputFile("Jeeves.txt"); 27      if (outputFile) 28      { 29          cout << "Done" << endl; 30 31          cout << endl << "Writing text to the file Jeeves. txt..."; 32          int i = 0; 33          while (i<12) 34          { 35              outputFile << textArray[i] << endl; 36              i++; 37          }; 38          cout << "Done" << endl; 39 40          cout << endl << "Closing Jeeves.txt..."; 41          outputFile.close(); 42 43          cout << "Done" << endl << endl; 44     } 45     else 46     { 47         cout << "Could not open file." << endl; 48     } 49 50     system("PAUSE"); 51     return EXIT_SUCCESS; 52   } 

Near the beginning of the program in Listing 12.1 (see line 3), the program includes fstream, which is provided by the C++ Standard Libraries. On lines 923, the program declares and initializes an array of strings named textArray.

After the program declares the string array, it outputs a message to the screen on line 25. It then declares a variable of type ofstream on line 26. In the declaration, it passes the name of the file to open to the ofstream constructor. This program opens a file called Jeeves.txt. When you run this program, it creates the file on your hard drive in the same folder that the executable version of this program (Prog_12_01.exe) resides in.

Factoid

Most text files have the filename extension .txt. However, nothing forces them to have that extension. They can also have extensions such as .xml, .html, .cpp, .h, .ini, .cfg, or anything else you think is appropriate; a text editor can open them all.


It is possible for the file to fail to be created and opened. The typical reasons for this failure are that the disk is either full or damaged, or that you do not have write permissions. If you're running this program from a hard drive, it is not likely that the disk is full. It is either damaged or you do not have write permissions.

It is also quite unlikely for the creation of this file to fail due to a hard drive failure. More typically, it is because you don't have write permissions. The most common reason for that is trying to write to a CD or DVD. CDs and DVDs are read-only disks. That means you can't create files on them after they have been burned.

For example, if you run this program from the CD that comes with this book, the program will try to create Jeeves.txt on the CD. That will fail. When it does, the variable outputFile declared on line 26 will evaluate to NULL. Therefore, you can use it as the condition in an if statement. That's exactly what happens on line 27. If the value of outputFile is not NULL, the if statement evaluates to TRue. In that case, it executes the body of the if statement on lines 2844.

The body of the if statement begins by outputting status messages on lines 2931. On lines 3237, it uses a while loop to write each string in the array to a text file. The statement that does the actual writing appears on line 35. Notice that it is very similar to writing text to the screen. You simply use the insertion operator. The only real difference is that you don't write to cout. Instead, you write to the ofstream variable outputFile.

When the loop on lines 3237 is finished, the program outputs some status messages to the screen on lines 3840. It then uses the close() function, which is a member of the ofstream class, to close the stream. After that, your program can't write to the file any more without opening the stream again.

Beyond English Characters

It is possible to create text files that use additional characters that are not defined in the ASCII character set. This capability is absolutely essential for games produced in languages other than English. Note that the A in ASCII stands for American. The first computer manufacturers were all located in the United States. When they got together and defined the ASCII set, they included only characters for English.

These days, computers handle a much broader set of characters than just the ASCII characters. For example, there are kinds of text files that can hold characters for virtually any kind of writing system. This includes alphabets that use Roman characters, such as English, French, German, or Spanish, and characters that are completely unlike Western writing, such as Chinese or Japanese characters.

Having said all that, the most basic kind of text file holds only characters in the ASCII set. Because this is an introductory book, that's all I'll cover. After you've mastered the essentials of file I/O, learning to use a broader range of character sets is not difficult. The information on how to do it is available on the Web for free. The best place to start looking for information on standard international character sets is at www.wikipedia.org. Do a search on the keyword "Unicode" and you'll get a good introduction.


If the file could not be created, the program executes the else statement beginning on line 45. The else statement contains a statement that outputs an error message to the screen.

You may ask whether you can only write strings to text files. The answer is no. You can write any type of data that can be converted to strings. For example, you could write the contents of an int or float variable to a text file. The insertion operator knows how to convert integers and floating-point numbers to text. That's what happens when you write integers and floating-point numbers to the screen. Anything you can write to the screen with the insertion operator, you can write to a text file with the insertion operator.

Warning

Always close all streams that you open. If you don't, it is possible to lose data on some devices. This won't happen on personal computers, but it can happen on some smaller game machines such as consoles or handheld devices. It's safest to just get into the habit of closing all streams you open.


Reading from Text Files

As with writing to text files, your programs use streams to read from text files. They create input streams by declaring variables of type ifstream, which stands for input file stream. Listing 12.2 shows how to declare and use an input stream.

Listing 12.2. Using streams to input text

 1     #include <cstdlib> 2     #include <iostream> 3     #include <fstream> 4 5     using namespace std; 6 7     int main(int argc, char *argv[]) 8     { 9        ifstream inputFile; 10 11       cout << "Opening input file..."; 12       inputFile.open("Input.txt"); 13 14       if (inputFile.is_open()) 15       { 16           cout << "Done" << endl << endl; 17 18           char inputString[80]; 19 20           cout << "Reading input file..." << endl; 21           cout << endl; 22           int i=0; 23           while (!inputFile.eof()) 24           { 25              inputFile.getline(inputString,80,'\n'); 26              cout << inputString << endl; 27              i++; 28           } 29           cout << endl; 30        } 31        else 32        { 33           cout << "Could not open input file." << endl; 34        } 35        system("PAUSE"); 36        return EXIT_SUCCESS; 37   } 

Like the program in Listing 12.1, the program in Listing 12.2 declares a stream variable. The ifstream variable inputFile enables the program to open and read a text file.

To demonstrate an alternate method of opening the file, I did not pass the file name to the ifstream constructor. Instead, the program calls the open() function on line 12 of Listing 12.2. This function exists in both the ifstream and ofstream classes, so you can call it to open either input or output files. When your program calls the open() function, it passes the filename in the parameter list.

Note

Some compilers require an explicit conversion from char * to string. But that's not something I want to get into.


In Listing 12.1, the program tested the stream to see if it was opened by testing the variable name. The C++ I/O classes provide another way to accomplish the same thing. On line 14 of Listing 12.2, the program calls the is_open() function on the input stream. Output streams also provide this function. So whether your program is testing input or output streams, it can see if they're open by calling is_open(). If the program was able to open the stream, is_open() returns true. If not, it returns false.

If the file was opened, the program prints a status message on line 16 and then declares an array of characters. C++ uses character arrays as strings. This is a hold-over from the C language.

In most cases, using the C++ Standard Library's string class is preferable to using character arrays. However, in some cases, you must use character arrays. This happens to be one of them. A look at line 25 shows why.

On line 25, you can see that the program does not use the extraction operator to read input from the file. Instead, it uses an ifstream member function named getline(). The getline() function reads characters into the character array specified in its first parameter. Because this parameter requires a character array, you cannot use variables of type string with the getline() function.

Factoid

The C++ programming language is derived from the older C programming language.


Note

There is a way to make the getline() function work with variables of type string, but explaining it would take us too far off our current subject. You can find information on it in the C++ programming books in the list of suggested reading on the CD.


Character arrays have a specific size. In the program in Listing 12.2, the array is 80 characters long. Because you use character arrays with getline(), you must specify the maximum number characters to read for each line. Otherwise, getline() could potentially read more characters than will fit in the array. That is likely to cause your program to crash. The 80 on line 25 ensures that getline() reads no more than 80 characters. The last one is always a null character, which is represented in C++ by the '\0' character. The null character is considered one character even though you use both a backslash and a zero to represent it. Because getline() always puts a '\0' character at the end of the input string, it always reads one less input character than you tell it to. Specifying 80 as the second parameter to getline() makes getline() read 79 characters and append a '\0' at the end.

Note

In the C programming language, all strings had to have the '\0' character at the end to mark the end of the string. That convention is often followed in C++ as well.


The third parameter to the getline() function is a delimiter character. When getline() finds the delimiter character in the text it's reading, it stops reading input and returns. The character '\n' is actually one character, not two. When you're specifying one literal character, like an 'A', you always use single quotes rather than double quotes (double quotes are for strings). In any case, the character '\n' stands for a newline, which is the same as an endline. Using the '\n' character as the delimiter means that the getline() function reads until it encounters the end of the line. Every line of text in a text file contains a '\n' character at the end. The combination of the second and third parameters to the getline() function means that getline() will read text from the text file until it either reads 80 characters or encounters the end of the line. If either one of those conditions occurs, it stops reading and returns.

If you wanted, you could change line 25 to read as follows:

 inputFile >> inputString; 


If you did this, the program would execute very differently. When your program uses the extraction operator to read from text files, it stops any time it encounters white space. White space includes endlines, spaces, and tabs. If the words in your text file are separated by spaces, the extraction operator will read one word at a time rather than one line of text at a time. This is a very important difference to most games. It's much more typical for games to read one line at a time rather than one word at a time.

Factoid

The character '\n', which stands for newline, is as another name for an endline. It's a holdover from the C language.


Binary Files

The other type of file is called a binary file. Binary files can contain any type of data in binary format. As you may recall, I mentioned binary numbers briefly in chapter 1, "What It Takes to Be a Game Programmer." I explained that you do not have to learn the binary number system to write games (although it does help if you do). The same is true of using binary files. You do not have to learn binary to read from or write to binary files.

Unlike text files, binary files can contain any kind of data. Any information you can store in memory can be stored in a binary file. This includes simple data types such as integers and floating-point numbers, or complex types such as objects. That's rightyou can save entire objects to binary files.

Because binary files can hold any type of data, the information they store tends to be more complex than the information stored in text files. Games typically use binary files for their level and save files. They may use text files for their configuration files.

Writing to Binary Files

When your program writes data to binary files, it treats the data it's writing as a group of bytes. To write a group of bytes to a file, your program must call the write() function, which is a member of the ofstream class, and pass it a pointer to the data to be written. It must also tell the write() function how many bytes to write. Listing 12.3 gives a short example of how to do this.

Listing 12.3. Writing a binary save game file

 1    #include <cstdlib> 2    #include <iostream> 3    #include <fstream> 4 5    using namespace std; 6 7    class game_save_info 8    { 9    public: 10       game_save_info(); 11 12       void CurrentLevel(int levelNumber); 13       int CurrentLevel(); 14 15       void CurrentScore(int score); 16       int CurrentScore(); 17 18       void PlayerName(char name[80]); 19       std::string PlayerName(); 20 21   private: 22       int currentLevel; 23       int currentScore; 24       char playerName[80]; 25   }; 26 27 28   game_save_info::game_save_info() 29   { 30      currentLevel = 0; 31      currentScore = 0; 32  } 33 34  void game_save_info::CurrentLevel(int levelNumber) 35  { 36     currentLevel = levelNumber; 37  } 38 39  int game_save_info::CurrentLevel() 40  { 41     return (currentLevel); 42  } 43 44  void game_save_info::CurrentScore(int score) 45  { 46     currentScore = score; 47  } 48 49  int game_save_info::CurrentScore() 50  { 51    return (currentScore); 52  } 53 54  void game_save_info::PlayerName(char name[80]) 55  { 56     strcpy(playerName, name); 57  } 58 59  std::string game_save_info::PlayerName() 60  { 61    return (playerName); 62  } 63 64  int main(int argc, char *argv[]) 65  { 66    game_save_info saveGame; 67 68    saveGame.CurrentLevel(3); 69    saveGame.CurrentScore(11042); 70    saveGame.PlayerName("Ozymandius Jones"); 71 72    cout << "Opening save game file..."; 73    ofstream saveFile; 74    saveFile.open("save.dat", ios::binary); 75    if (saveFile.is_open()) 76    { 77       cout << "Done." << endl; 78 79       cout << "Writing save game info to file..."; 80       saveFile.write( 81           (char *)(&saveGame), 82            sizeof(game_save_info)); 83       cout << "Done." << endl; 84     } 85 86 87    system("PAUSE"); 88    return EXIT_SUCCESS; 89  } 

The first thing that the program in Listing 12.3 does is define a class called game_save_info. This is a simplified version of a class you might create to write information to a file that enables players to save their progress in a game. This simple class has only a few items of member data, a constructor, and some functions for accessing the member data.

Notice that the player name is a character array rather than an object of type string. When you write data to files, it is a much simpler process if you write things that have a fixed size. A string object does not. The size of a string object depends on how many characters it holds. The character array defined on line 24 is always exactly 80 characters long. Even if the string it holds is shorter, the array is still 80 characters long. Because it's a known, fixed size, it's easier to write to a file than a string object would be.

Tip

It is not uncommon for C++ programs to require the use of functions from the old C Standard Library. As you become more proficient in C++ programming, you may want to get a good reference book on C programming. Alternatively, you could use the C/C++ Language Reference on the Web at www.msdn.microsoft.com.


Because the game_save_info object contains a character array, it must use the old C Standard Library strcpy() function in the PlayerName() function on lines 5962. The name "strcpy" stands for "string copy." The strcpy() function, which was heavily used in C programs, copies the contents of one character array to another. In the case of line 56, it copies the characters in the parameter name to the data member playerName. Both of these are arrays of no more than 80 characters.

In the main() function, this program declares an object of type save_game_info on line 66. It then stores some information in the object. It will write that information to a file shortly.

After outputting a status message, the program declares a variable of type ofstream on line 73. In the program back in Listing 12.1, I passed the output file name to the ofstream constructor. Just to demonstrate that you can also use the open() function with output streams, this program calls open() on line 74. Notice that it passes two parameters to open() this time. The first is a string containing the file name. The second is a constant that is defined in the C++ Standard Library. The name of the constant is ios::binary. Passing the constant ios::binary to the open() function tells open() to create a binary file rather than a text file.

On line 75, the program tests whether the file was successfully opened by calling the is_open() function. If it was, the program outputs some status messages to the screen.

Next, the program calls the ofstream class's write() function on line 80 to write the contents of the variable saveGame to the binary file. The first parameter to the write() function is always a pointer to characters. C++ treats characters as bytes, so a pointer to characters is another way of saying a pointer to bytes. The variable saveGame is not a pointer to characters. It is an object of type save_game_info. To pass the information in saveGame to the write() function, the program must convert saveGame to a pointer to characters. It does that in specific steps. First, it puts the variable name in parentheses and puts the ampersand in front of it. Recall from our discussion of pointers in chapter 11 that the ampersand operator takes the address of a variable. In other words, the statement &saveGame obtains a pointer to the variable saveGame.

Whenever you pass write() a variable, rather than a pointer, you must use the ampersand operator to convert the variable to a pointer. The type of the variable does not matter. This rule holds true for simple types such as int or classes such as save_game_info.

Note

The formal name for converting one type to another by inserting the destination type in parentheses (as shown on line 81 of Listing 12.3) is type casting.


Using the ampersand with the variable name obtains the address of the variable. Another way of saying that is that it gets a pointer to the variable. In order to pass the pointer to the write() function, the program must convert the pointer to a character pointer. To do so, it uses the statement (char *) at the beginning of line 81. So the (&saveGame) gets a pointer to the type game_save_info and the (char *) converts it to a character pointer for that statement only. Therefore, write() receives a pointer to a group of characters (which it treats as a pointer to a group of bytes) as its first parameter. That is exactly what it needs.

The second parameter to the write() function appears on line 82. The write() function requires an integer as its second parameter that specifies how many bytes to write to the file. To figure that out, the program uses the C++ sizeof() operator. The sizeof() operator takes a type name in its parentheses. It calculates how many bytes a variable of that type occupies in memory. In the case of line 82, it calculates the number of bytes required for a variable of type game_save_info. When you write an object to a file, only the data gets written, not the member functions. So the sizeof() operator simply adds up the sizes of the member data to get the final total.

This program doesn't provide a way to check that everything was written to the file properly. The only way we can really verify that is to read the contents of the file back into memory and print them to the screen. That's the subject of the next section.

Note

Did you spot the mistake in the program in Listing 12.3? It's a small one, but I put it in as a reminder. The mistake is that the program never closes the file it opens. This is poor programming practice and should never be done in a professional program.


Reading from Binary Files

Programs reading data from binary files by calling the ifstream::read() function. The read() function works nearly identically to the ofstream::write() function except that the data flows into the program instead of out to a file. Listing 12.4 shows how to use it.

Listing 12.4. Using the read() function to read binary data

 1     #include <cstdlib> 2     #include <iostream> 3     #include <fstream> 4 5     using namespace std; 6 7     class game_save_info 8     { 9     public: 10        game_save_info(); 11 12        void CurrentLevel(int levelNumber); 13        int CurrentLevel(); 14 15        void CurrentScore(int score); 16        int CurrentScore(); 17 18        void PlayerName(char name[80]); 19        std::string PlayerName(); 20 21    private: 22        int currentLevel; 23        int currentScore; 24        char playerName[80]; 25   }; 26 27 28   game_save_info::game_save_info() 29   { 30      currentLevel = 0; 31      currentScore = 0; 32   } 33 34   void game_save_info::CurrentLevel(int levelNumber) 35   { 36      currentLevel = levelNumber; 37   } 38 39   int game_save_info::CurrentLevel() 40   { 41      return (currentLevel); 42   } 43 44   void game_save_info::CurrentScore(int score) 45   { 46       currentScore = score; 47   } 48 49   int game_save_info::CurrentScore() 50   { 51      return (currentScore); 52   } 53 54   void game_save_info::PlayerName(char name[80]) 55   { 56      strcpy(playerName, name); 57   } 58 59   std::string game_save_info::PlayerName() 60   { 61     return (playerName); 62   } 63 64   int main(int argc, char *argv[]) 65   { 66      game_save_info saveGame; 67 68      cout << "Opening save game file..."; 69      ifstream saveFile; 70      saveFile.open("save.dat", ios::binary); 71      if (saveFile.is_open()) 72      { 73         cout << "Done." << endl; 74 75         cout << "Reading save game info from file..."; 76         saveFile.read( 77             (char *)(&saveGame), 78              sizeof(game_save_info)); 79         cout << "Done." << endl; 80 81         cout << endl; 82         cout << "Player Name: " << saveGame.PlayerName(); 83         cout << endl; 84         cout << "Player Score: " << saveGame. CurrentScore(); 85        cout << endl; 86        cout << "Current Level: " << saveGame. CurrentLevel(); 87        cout << endl; 88 89        saveFile.close(); 90     } 91 92 93     system("PAUSE"); 94     return EXIT_SUCCESS; 95   } 

This program is very similar to the one in Listing 12.3. The difference is in the main() function. After declaring a variable to hold the data that will come in from the file, the main() function opens the data file written by the program in Listing 12.3. If the file is opened successfully, the program uses the ifstream::read() function on lines 7678.

As with the ofstream::write() function, the first parameter to ifstream:: read() is a character pointer. The pointer is the address of the memory that will receive the data to be read from the file. The second parameter to the read() function is the number of bytes to read. Because the program is reading an object of type game_save_info, it uses the statement sizeof(game_save_info). This tells the program the number of bytes that a game_save_info object requires.

That is exactly the number of bytes the program should read from the file. When the read() function executes, it reads the number of bytes specified in its second parameter into the memory location contained in its first parameter.

After the program reads the data back into memory, it prints the data to the screen on lines 8187. It then closes the data file before it exits. Figure 12.1 shows what the output looks like.

Figure 12.1. The contents of the object read in from the file Save.dat.




Creating Games in C++(c) A Step-by-Step Guide
Creating Games in C++: A Step-by-Step Guide
ISBN: 0735714347
EAN: 2147483647
Year: N/A
Pages: 148

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net