CS330 C++ File I/O and Parsing Line Input


Highlights of this lab:


C++ File IO

There are three predefined streams in C++:

Note: make sure you have #include<iostream> and have used "using namespace std;" at the start of the program.

To open a file for reading, you can use code like the following:

ifstream inFile("name", ios::in);

To open a file for output, you can use code like:

ofstream outfile("name", ios::out);

Make sure you have #include<fstream> before using these functions.

To input a character from an input stream, use the get() function:

char ch;
inFile.get(ch);

To output a character, use the put() function:

outFile.put(ch);

Here is a sample program that gets characters from standard input and puts them into the file copy.out

#include <fstream>

int main()
{
   //open a file copy.out for output
   ofstream outFile("copy.out");

   if (! outFile)
   {
      cerr << "cannot open \"copy.out\" for output\n";
      return -1;
   }

   char ch;
   while (cin.get(ch))
      outFile.put(ch);
}
To open a file for input only, an ifstream class object is used.
#include <fstream>

int main()
{
   cout << "filename: ";
   string file_name;
   cin >> file_name;

   //open a file copy.out for input
   ifstream inFile(file_name.c_str() );

   if (! inFile)
   {
      cerr << "unable to open input file: "
	   << file_name << " --bailing out! \n";
      return -1;
   }

   char ch;
   while (inFile.get(ch))
      cout.put(ch);
}

Reading a Line from Input

The above section described single character I/O. Often you want to read a line at a time. The function to do that is getline. There are two versions of getline:

  1. For reading C strings.
    The prototypes are:
    istream& getline (char* s, streamsize n);
    istream& getline (char* s, streamsize n, char delim); 
    For the latter prototype, if you want your lines to be delimited by something other than than a '\n' (the default), then you can specify this as a third, delim, argument. The following is an example that uses the first prototype to read a line at a time of a file and echo it to standard output:
    #include <iostream>
    #include <fstream>
    using namespace std;
    
    const int MAXLINE=256;
    
    int main()
    {
       ifstream inFile ("test.txt");
       char oneline[MAXLINE];
    
       while (inFile)
       {
           inFile.getline(oneline, MAXLINE);
           cout << oneline << endl;
       }
    
       inFile.close();
    
       return 0;
    }
    Notes:
  2. For reading strings.
    The prototypes are:
    istream& getline (istream& is, string& str);
    istream& getline (istream& is, string& str, char delim); 
    Once again, you can specify a delimiter other than '\n'. The following is an example that uses the first prototype to read one line at a time and echo it to standard output:
    #include <iostream>
    #include <fstream>
    #include <string>
    using namespace std;
    
    int main()
    {
        ifstream inFile("test.txt");
        string strOneLine;
    
        while (inFile)
        {
           getline(inFile, strOneLine);
           cout << strOneLine << endl;
        }
    
        inFile.close();
    
        return 0;
    } 
    Notes:

C String Versus String

It's probably been a long time since you've thought about C strings and strings. The following sections is meant to give you a review on these two programming topics.

C Strings

C strings are based on the "old" C way of creating a string. Just because the word old is used doesn't mean that C strings aren't used and useful. There are times and places where you encounter C strings and it's very good to know these foundations.

A C string is an array of characters that is null terminated. Functions that are used with C strings include:

  • strlen(const char *s)
Returns the length of the string s (the length does not include the terminating null character)
  • strcpy(char *s1, const char *s2)
Copies the string s2 into string s1, including the terminating null character. The programmer must ensure that s1 points to enough space to hold the result.
  • strncpy(char *s1, const char *s2, size_t n)
Copies n characters (or until the null terminator has been encountered) from string s2 to string s1. If s2 has less than n characters, then s1 will be padded with '\0' up to n.

The following code provides examples of different ways of creating C strings and the use of the functions listed above:

#include <iostream>
#include  <cstring>
using namespace std;

int main()
{
   char s1[4]="one"; //remember there will be one space for null terminator
   char s2[]="two";
   char s3[4];
   char *s4;         //s4 will point to the beginning of the array of chars
   char s5[6];

   //Assigning characters to s3
   s3[0]='b';
   s3[1]='a';
   s3[2]='t';
   s3[3]='\0';


   //Need to allocate space otherwise get core dump when do strcpy. Two ways:
   //s4=new char[6];               //specify size directly
   s4=new char[strlen("hello") + 1];   //use strlen to determine size

   //Copy the strings
   strncpy(s4,"hello",6);        // specify the size of the string + 1 for null 


   //s5="bye";   // 21: error: ISO C++ forbids assignment of arrays. Instead:
   strncpy(s5, "bye", (strlen("bye") + 1));


   cout << s1 << endl;
   cout << s2 << endl;
   cout << s3 << endl;
   cout << s4 << endl;
   cout << s5 << endl;

   //clean up space allocated for s4
   delete [] s4;

   return 0;
}

The output:

one
two
bat
hello
bye

The following is a diagrammatic representation of the C Strings created in the above program:

A POSIX Shortcut

This snippet from the example above:

   s4=new char[strlen("hello") + 1];   //use strlen to determine size
   //Copy the strings
   strncpy(s4,"hello",6);        // specify the size of the string + 1 for null 

   //...

   delete[] s4;
or the C version:
   s4= (char*)malloc(strlen("hello") + 1);   //use strlen to determine size
   //Copy the strings
   strncpy(s4,"hello",6);        // specify the size of the string + 1 for null

   //...

   free(s4);
is so common that an efficient shortcut was included in the POSIX standards. It is not a part of the C or C++ standard. If you are lucky enough to be programming on a POSIX compliant OS such as Linux (the lab) or Solaris (Hercules) or Mac OS X 10.5 then you can simplify the snippet above with this function:

  • strdup(const char *s)
returns a pointer to a new string that is a duplicate of the string pointed to by s. The returned pointer should be released with free() because the space for the new string is obtained using malloc. If the new string cannot be created, a null pointer is returned.

The above snippets would then look like this:

   s4=strdup("hello");   //make copy of "hello"

   //...

   free(s4);
and because there is only one function call instead of three, the compiled code is likely more efficient.

Notes:

Strings

In C++, strings are objects. As such, the functions associated with strings are member functions (such as str.length()). The following code provides example of the different ways that you can create strings (with different constructors):

#include <iostream>
#include <string>
using namespace std;
// For a list of constructors see:
// http://www.cppreference.com/cppstring/string_constructors.html

int main()
{
   string str1="one";
   string str2("two");
   string str3;
   string str4(str1);
   string str5(8,'a');
   string str6(str2,1);
   string str7(str2,1,1);

   str3="hello";

   //show the use of the length function
   cout << "The length of '" << str3 << "' is: " << str3.length() << endl;

   //show the use of the append function
   cout << "str6 before: " << str6 << endl;
   //The version of append used here is:
   //string& append( const string& str, size_type index, size_type len );
   str6.append(str1,1,1);
   cout << "str6 after: " << str6 << endl;

   //show the use of the + (concatenation) operator
   cout << "str1 before: " << str1 << endl;
   str1=str1 + ", " + str2;
   cout << "str1 after: " << str1 << endl;

   cout << str1 << endl;
   cout << str2 << endl;
   cout << str3 << endl;
   cout << str4 << endl;
   cout << str5 << endl;
   cout << str6 << endl;
   cout << str7 << endl;

   return 0;
}

The output:

The length of 'hello' is: 5
str6 before: wo
str6 after: won

str1 before: one
str1 after: one, two

one, two
two
hello
one
aaaaaaaa
won
w 

Notes:


Splitting C Strings into Tokens

Sometimes you may want to split a line into tokens or words. To do that, there is a C String function called strtok. The prototype is:
char * strtok (char * str, const char * delimiters)
where str is the line (or C string) that you want to split into tokens or words, and delimiters are an array of characters in which any one of the characters delimits or marks the boundaries between words.

The following is an example of using strtok:

#include <iostream>
#include <cstring>
using namespace std;

int main(int argc, char *argv[])
{
   char cstr1[]="This is a sample string. Is it working?";
   char delim[]=" ,.-;!?";
   char *token;

   cout << "cstr1 before being tokenized: " << cstr1 << endl << endl;

   //In the first call to strtok, the first argument is the line to be tokenized
   token=strtok(cstr1, delim);
   cout << token << endl;


   //In subsequent calls to strtok, the first argument is NULL
   while((token=strtok(NULL, delim))!=NULL)
   {
         cout << token << endl;
   }
}

The output:

cstr1 before being tokenized: This is a sample string. Is it working?

This
is
a
sample
string
Is
it
working

There are a couple of "catches" with strtok:

  1. In the first call to strtok, the first argument is the line or C string to be tokenized; in subsequent calls to strtok, the first argument is NULL. Notice the two calls from the lines above:
  2. The original C string is modified when it is tokenized so that delimiters are replaced by null terminators ('\0'). The following represents what the C string in the sample code will look like after tokenizing:

Dynamic Arrays of C Strings

Sometimes you want to have a dynamically created array of C Strings. The following code demonstrates this:

#include <iostream>
#include <cstring>
using namespace std;

int main ()
{
  char **words;
  char endWord[]="330!";

  words = new char *[4]; //allocate pointers to four words

  //words[0] to words[2] are assigned constant strings.  For variable
  //assignment, you should allocate space and then use strncpy 
  //(as done for words[3])
  words[0] = "hello";
  words[1] = "there";
  words[2] = "CS";

  words[3] = new char[strlen(endWord) +1];
  strncpy(words[3],endWord,(strlen(endWord)+1));

  for (int i=0; i<4; i++)
  {
     cout << words[i] << endl;
  }

  //Clean up everything that you allocated:
  delete [] words[3];  // cleans up words[3]= new char[strlen(endWord +1]; 
  delete [] words;     // cleans up words = new char *[4];
}

Review of Creating a Class in C++

In previous computer science classes, you have learned that the typical design of C++ programs is to have two files when you create a class:

Once you have the class defined, then you need a main function to test the code. Good programming practice involves putting the main function in a separate file. This makes a total of three files.

If you have been working in Java or any other programming languages, you may be foggy on the C++ syntax. The following is an example of a Circle class with a main.cpp file used to test this code.

circle.h

#include <iostream>
using namespace std;
#define PI 3.14159

class Circle
{
   public:
      //Constructors
      Circle();
      Circle(double radius);

      //Functions
      double getRadius();
      istream &readRadius(istream& is);
      void setRadius(double radius);
      double getArea();
      double getCircumference();

   private:
      double radius;
};

circle.cpp

#include "circle.h"

//Constructors. If no radius is specified, the default is 5.
Circle::Circle()
{
   radius = 5.0;
}
Circle::Circle(double radius)
{
   //because the data member is also called radius, you need to use "this->"
   this->radius=radius;
}


//Functions
double Circle::getRadius()
{
   return radius;
}

//Read the radius from some input stream (for instance cin or an ifstream object)
istream& Circle::readRadius(istream& is)
{
   is >> radius;
   return is;
}

void Circle::setRadius(double radius)
{
   this->radius=radius;
}

double Circle::getArea()
{
   return (PI*radius*radius);
}

double Circle::getCircumference()
{
   return (2*radius*PI);
}

main.cpp

#include "circle.h"
#include <iostream>
using namespace std;

int main()
{
   Circle c1;
   Circle c2(20.2);
   Circle c3;

   cout << "Please Enter the radius for c3:  ";
c3.readRadius(cin);
cout << endl << "Details of c1:" << endl; cout << "Radius is: " << c1.getRadius() << endl; cout << "Area is: " << c1.getArea() << endl; cout << "Circumference is: " << c1.getCircumference() << endl; cout << endl << "Details of c2:" << endl; cout << "Radius is: " << c2.getRadius() << endl; cout << "Area is: " << c2.getArea() << endl; cout << "Circumference is: " << c2.getCircumference() << endl; cout << endl << "Details of c3:" << endl;
cout << "Radius is: " << c3.getRadius() << endl;
cout << "Area is: " << c3.getArea() << endl;
cout << "Circumference is: " << c3.getCircumference() << endl; return 0; }

To compile and run this code, you can use the following commands on the command-line:

Notes:


References