Advanced Input/Output

Simple stream input/output covers much of what we need for basic programs, but as we move to more sophisticated codes we may find that we need more control, especially for reading files.

Stringstreams

An input/output stream is a sequence of characters. A string is also a sequence of characters. With the right definitions and methods, we can thus treat a string as a stream. C++ does this with the stringstream class. In particular, the insertion and extraction operators are defined on the stringstream object.

Stringstreams are very commonly used to convert from a string to a different type, often a numerical type, and vice versa. As long as >> and << know how to do the conversion for the target type, as they must for an ordinary istream or ostream objects, the operators can perform the same conversion for a stringstream. Since the >> operator breaks on whitespace, it can also be used to split a string.

To convert from string to numeric, we create a new stringstream from the string. We now have a string buffer holding the string. We now read from that buffer into the numeric variable using the extraction operator.

   std::stringstream ss(num_str) 
   float num;
   ss>>num;

To go the other direction we write the number into an empty stringstream buffer. We must then extract the string using a built-in method.

   std::stringstream st;
   float num=10.4;
   st<<num;
   std::string num_str=st.str();

Putting this together gives us this example:

#include <iostream>
#include <sstream>
#include <string>
#include <vector>

int main() {

    std::string num_str;
    float number;

    num_str="17.1";
    std::stringstream ss(num_str);
    ss>>number;
    std::cout<<"The number is "<<number<<std::endl;

    std::string another_str;
    float another_num=11.3;
    std::stringstream st;
    st<<another_num;
    std::cout<<"As a string "+st.str()<<"\n";

    std::stringstream line("This is an example.");
    std::string word;
    std::vector<std::string> words;
    while ( line >> word ) {
       words.push_back(word);
    }
    
    for (std::size_t i=0; i < words.size(); i++ ) {
        std::cout<<words[i]<<":";
    }
    std::cout<<std::endl;

    return 0;

}
    

Reading from the Command Line

Command-line options are strings that follow the name of the executable.

./myexec first second 10

The command line is contained in a two-dimensional character array (one dimension for the characters, the other for multiple character groups). It is called argv. The element argv[0] is the name of the executable. The integer argc is the length of argv. These variables must be specified as arguments to main if you wish to read command-line arguments.

We can read strings only. You must convert if necessary to a numerical type using stringstreams.

#include <iostream>
#include <string>
#include <sstream>

int main(int argc, char **argv) {

  std::string filename;
  float value;

  if (argc>2) {
     filename=argv[1];
     std::stringstream inputValue;
     inputValue<<argv[2];
     inputValue>>value;
     std::cout<<"Input is "<<filename<<" "<<value<<"\n";
   } else {
     std::cout<<"Usage: filename number\n";
     exit(1);
   }
   return 0;
}

Getline

The getline function reads an entire line at once, as a single string. This means that we will need to handle the input ourselves, converting as appropriate.

#include <fstream>
#include <iostream>
#include <string>

int main() {

  std::string line;
  //note implicit open
  std::ifstream mystream("data.txt");
  if (mystream.is_open()) {
      while (std::getline(mystream,line)) {
      //do something with line
      std::cout<<line<<"\n";
      }
  }
  else {
     std::cout<<"Unable to open file\n";
  }

}
      


Getline’s name is a little misleading. Getline actually reads to a delimiter. The default delimiter is newline \n.

   getline(istream,string)  // reads to newline
   getline(istream,string,delim) // reads to delimiter character

The delimiter character is discarded from the string.

Getline can also be used for standard input. Example:

  cout<<"Enter your name:";
  getline(cin,name);

Reading a CSV file

  • We often need to read files where each line contains several fields separated by a comma or other delimiter. For example: read four values from each line for 200 lines, ignoring the second column values.
#include <iostream>
#include <fstream>
#include <sstream>

using namespace std;

int main() {

    const int nobs=200;
    float bf[nobs],wt[nobs],ht[nobs];
    string line;

    int lineCount=0;
    ifstream fin("datafile.txt");
    if (fin.is_open()) {
         while (getline(fin,line) && lineCount<200) {
             stringstream lineStream(line);
             string *linevals=new string[4];
             int index=0;
             while (getline(lineStream,linevals[index],',') ) {
                 ++index;
             }
             stringstream ssbf,sswt,ssht;
             ssbf<<linevals[0];
             ssbf>>bf[lineCount];
             sswt<<linevals[2];
             sswt>>wt[lineCount];
             ssht<<linevals[3];
             ssht>>ht[lineCount];
             lineCount++;
         }
    } else {
        cout<<"Unable to open file";
        return 1;
    }

    return 0;

}


Getline is used twice, once to read the line as a string and again to split the line on commas. In this case we know that we have four fields in each line so we declare an array of strings. More generally, we could use a vector and push_back after getline reads the next chunk to the delimiter. To read the subunits of the line, we declare a stringstream and use that as the stream buffer, rather than a file descriptor.

Exercises

  1. Download the file cpi.csv. Examine the file. Write a program that will read the file. Store the first column in a vector year and the second column in another vector cpi. Be sure to skip the header line. It is not necessary to read any data from the header.

As an alternative to converting with a stringstream, you can convert a C++ string to a C-style string with the c_str() method.

mystr.c_str()

You can then use the atoi and atof functions that operate on C strings to convert to integer and float respectively.

Example Solution

#include <iostream>
#include <fstream>
#include <string>
#include <sstream>
#include <vector>

using namespace std;

float inflationFactor(float* cpi, int baseYear, int compYear1, int compYear2=1980);

int main(int argc, char **argv)
{
    //Declare Variables
    ifstream fin;
    string line;

    vector<int> year;
    vector<float> cpi;

    fin.open("cpi.csv");

    getline(fin,line);
    while ( getline(fin,line) ) {
        stringstream lineStream(line);
        string *lineVals=new string[2];
        int index=0;
        while ( getline(lineStream, lineVals[index],',') ) {
            ++index;
        }
        year.push_back(atoi(lineVals[0].c_str()));
        cpi.push_back(atof(lineVals[1].c_str()));
    }

    return 0;
}

  1. Write a program that creates a file mydata.txt containing four rows consisting of
1, 2, 3
4, 5, 6
7, 8, 9
10, 11, 12

Rewind the file and read the data back. Write a loop to add 1 to each value and print each row to the console.

Example Solution

#include <iostream>
#include <fstream>
#include <sstream>
#include <vector>

using namespace std;

int main() {
    fstream myfile;
    myfile.open("mydata.txt", ios::out);

    for (int i=1; i<=12; ++i) {
        myfile << i;
        if (i%3==0) {
            myfile << "\n";
        } else {
            myfile << ", ";
        }
    }
    myfile.close();

    myfile.open("mydata.txt", ios::in);

    float n[4][3];
    if (myfile.is_open()) {
        string line;
        string lineval;
	int lineCount=0;
        while (getline(myfile,line)) {
            stringstream lineStream(line);
            vector<string> linevals;
            while (getline(lineStream,lineval,',') ) {
                linevals.push_back(lineval);
            }
            stringstream ssbf,sswt,ssht;
            ssbf<<linevals[0];
            ssbf>>n[lineCount][0];
            sswt<<linevals[1];
            sswt>>n[lineCount][1];
            ssht<<linevals[2];
            ssht>>n[lineCount][2];
	    lineCount++;
        }
    } else {
        cout<<"Unable to open file";
        return 1;
    }

    for (int i=0; i<4; ++i) {
	for (int j=0; j<3; ++j) {
            n[i][j]++;
	}
    }
    for (int i=0; i<4; ++i) {
	for (int j=0; j<3; ++j) {
            cout << n[i][j] << " ";
        }
        cout << "\n";
    }
}

Previous
Next