Wednesday, January 16, 2013

Simple File Manipulation in a Console Application with C++

The goal of this tutorial is to show you how to make a C++ console application that will read data from a file called "input.txt" that's formatted like this (with each new entry on its own line):

1234567
1234567
1234567

and output it to a file called "output.txt" that is formatted like this (standard CSV (Comma Separated Values) format, except with 25 columns per row maximum):

1234567,1234567,1234567,...(25 times)
1234567,1234567,1234567,...(25 times)
1234567,1234567,1234567,...(18 times)

First things first - you'll need:
  1. Visual Studio installed - click here for a tutorial with help/instructions for that.
  2. To create a new, empty C++ console project in VS - click here for a tutorial with instructions for that.

Here's a screenshot of our fully finished program working:


and here are some useful links for you in case you want them:

Code file ("main.cpp"): http://thecodingwebsite.com/tutorials/csvmaker/main.cpp
Code file as a ".txt" file ("main.txt") so you can view it in your browser: http://thecodingwebsite.com/tutorials/csvmaker/main.txt
Finished program: http://thecodingwebsite.com/tutorials/csvmaker/CSVMaker.exe
Sample input file ("input.txt"): http://thecodingwebsite.com/tutorials/csvmaker/input.txt
Sample output file ("output.txt") (what the output should look like after running the program): http://thecodingwebsite.com/tutorials/csvmaker/output.txt


From this point on, I'll assume that you have your project created successfully in C++, it's opened in VS, and you're viewing "main.cpp" (or whatever you chose to name your code file).


The first code we want in our program is the basic structure - some include statements, a using statement, and our main function:

#include <iostream>
#include <fstream>

using namespace std;

void main()
{

}

Include statements tell it to include external files in the program. In this case, we're using "iostream" and "fstream", which we will use for outputting text in the console and reading from and writing to files, respectively.

We then tell it that we're using the "std" (Standard Library) namespace. I won't go into much detail about it here, but basically it makes it easier to read and write the code for our program.

Then we declare a function called "main" that returns nothing (hence the "void" return parameter type specified). This function currently does nothing because there is no code in between the {} brackets. It's important to note here that the "main" function is special in C++ because that's the function that gets called as soon as a program is run - it's the starting point.


From this point on, I'll only show the "main" function and all of its code as we develop it, because the rest of the code will go in the main function:

void main()
{
 //Open "input.txt" for reading.
 fstream fin("input.txt");

 //Open "output.txt" for writing - the ios::out parameter is required. This will overwrite if applicable.
 fstream fout("output.txt", ios::out);

 //Close the files.
 fout.close();
 fin.close();

 system("pause");
}

This code will open the file "input.txt" (must be in the same directory as the program) for reading. Then it will open the file "output.txt" for writing, creating it if it doesn't exist and overwriting it if it does.

In a moment we'll read from the input file and write to the output file (using the "fin" and "fout" objects we just created), but for now we're just going to close both files.

Lastly, we call a system "pause", which tells the program to wait for the user to press a key before it continues. This is necessary if we want the program's console text output to be visible before the program ends.


For now, don't worry about where the following code will go...


We want to read entire lines from the input file, no matter how short or long they are. There's actually a character called a new line character that separates lines within a file. In C++ and many other languages, you can specify it using "\n":

//Outputs two new line characters to the console using "cout" (console output).
cout << "\n\n";

However, when reading from a file, we just want to use the fstream's "getline" function. In order to use getline, we'll need a character array to store all of the line's characters in:

//This will store each line of characters that we read - we'll set the max size of each line (column) to 100.
 char nextLine[100] = {0};

Then we can use "fin.getline" to read each line from the file using an infinite while loop and check to see if we've reached the end of the file after each line read:

//This is an infinite loop - it will only break (exit) from the loop when we've reached the end of the file.
  while (true)
  {
   //Read the next line from the file.
   fin.getline(nextLine, 100);
   
   //Check to see if we've reached the end of the file. If so, break out of the reading/writing while loop.
   if (!fin)
   {
    break;
   }
  }

If the value of our "fin" object is false, we've reached the end of the "input.txt" file (there is nothing more to read), so we break out of the infinite while loop. To output text to a file, we'll use this code:

fout << "Hello.";

So, if we want to place a comma in between each line read in our file output, we would simply do this:

fout << nextLine;
fout << ",";

This is almost exactly what we want, except for one stipulation: we don't want more than 25 columns per row in our output file. So, we're going to have to keep track of the current column we're on in each row:

//These variables are used to separate every so many columns with a new line character (e.g. 25 columns per row).
 int columnCount = 0, columnMax = 25;

This is what the code will need to look like now to limit it to 25 columns per row maximum:

//Output the next column.
   fout << nextLine;

   //Another column is being added.
   ++columnCount;

   //Output a new line character after so many (columnMax) columns are outputted.
   if (columnCount >= columnMax)
   {
    //Output a new line character.
    fout << "\n";

    //Reset the column count for the next row.
    columnCount = 0;
   }
   else
   {
    //Make sure we don't put a comma at the end of the file.
    if (fin)
    {
     //Output a comma.
     fout << ",";
    }
   }

If you read the comments, they pretty much explain what the above code does. Basically, we increment the current column number by 1 each time around, and if it's greater than or equal to 25 we output a new line character and reset the column count. Because we start counting at 0, we actually want the column count to stop at 24 - that's why it checks for greater than or equal to rather than just equal to, etc. Here is the finished program's code:

#include <iostream>
#include <fstream>

using namespace std;

void main()
{
 //Tell them we're converting.
 cout << "Converting...\n\n";

 //Open "input.txt" for reading.
 fstream fin("input.txt");

 //Open "output.txt" for writing - the ios::out parameter is required. This will overwrite if applicable.
 fstream fout("output.txt", ios::out);

 //This will store each line of characters that we read - we'll set the max size of each line (column) to 100.
 char nextLine[100] = {0};

 //These variables are used to separate every so many columns with a new line character (e.g. 25 columns per row).
 int columnCount = 0, columnMax = 25;

 //Make sure "input.txt" and "output.txt" were opened properly.
 if (fin && fout)
 {
  //This is an infinite loop - it will only break (exit) from the loop when we've reached the end of the file.
  while (true)
  {
   //Read the next line from the file.
   fin.getline(nextLine, 100);
   
   //Check to see if we've reached the end of the file. If so, break out of the reading/writing while loop.
   if (!fin)
   {
    break;
   }

   //Output the next column.
   fout << nextLine;

   //Another column is being added.
   ++columnCount;

   //Output a new line character after so many (columnMax) columns are outputted.
   if (columnCount >= columnMax)
   {
    //Output a new line character.
    fout << "\n";

    //Reset the column count for the next row.
    columnCount = 0;
   }
   else
   {
    //Make sure we don't put a comma at the end of the file.
    if (fin)
    {
     //Output a comma.
     fout << ",";
    }
   }
  }

  //Assume success since there were no file opening errors.
  cout << "Conversion successful! Check \"output.txt\".";
 }
 else
 {
  //The files were not opened properly - let the user know that it was unsuccessful.
  cout << "Error either opening \"input.txt\" for reading or opening \"output.txt\" for writing.";
 }

 //Close the files.
 fout.close();
 fin.close();

 //Output some new line characters before the program asks for a key press.
 cout << "\n\n";

 system("pause");
}

I added a few cout's here and there to make it more user friendly. If you have any questions about the code, make sure to read my comments carefully as they should explain it pretty well. If you still have questions, feel free to ask them below!

No comments:

Post a Comment