I am new to c++ and I am a bit lost here!
I am trying to read the following line
* - 6 + x -6 - - 9 6 * 0 c
and I am iterating through the characters using
for (std::string line; std::getline(std::cin, line);) {
for(auto c : line){
if(c != ' ')
}
}
now I am expecting to get "-6" at the sixth iteration but a I am getting "-" and then at the next iteration I am getting 6, I need to get "-6"! any help!
In the outer for loop, you are infinitely looping until getline() evaluates to false. In the inner for loop, you are looping through each character in line.
I don't know of any elegant syntax to parse a string into tokens like it seems you are trying to do. One way is to utilise std::stringstream and put things into an array or vector. Below is a sample main I wrote demonstrating how versatile this is:
#include <iostream>
#include <sstream>
#include <vector>
int main() {
std::string line;
while ( true ) {
std::cout << "Enter parse string: ";
std::getline( std::cin, line );
if ( line == "Stop" ) break; // Enter stop to quit program
std::stringstream ss( line ); // constructor sets 'line' as contents of the stream
std::vector <std::string> tokens; // where our tokenised input is stored
std::string temp; // where current token is temporarily loaded before it's put into the vector
while( getline( ss, temp, ' ' ) ) // extract characters from ss until a space is found and stores them in temp
tokens.push_back( temp ); // put the token formed by getline in our vector, from back so order is kept
/*
Now you can use the vector as you would like. Below the contents are printed.
*/
for ( auto& x : tokens ) // for each token in 'tokens' vector,
std::cout << x << ' '; // print out token with a space at the end
std::cout << "\n\n";
for ( int i = 0; i < tokens.size(); i++ ) // for each token in 'tokens' vector,
std::cout << i << ": " << tokens[i] << '\n'; // print out index number and token
std::cout << std::endl; // newline & flush
}
return 0;
}
If you want to abstract the details away, you could always wrap the std::stringstream stuff into a function that takes in a string and returns a std::vector<std::string>. Below I demonstrate what that would look like:
#include <iostream>
#include <sstream>
#include <vector>
std::vector <std::string> toParsedVector( std::string input ) {
std::stringstream ss( input ); // constructor sets 'line' as contents of the stream
std::vector <std::string> tokens; // where our tokenised input is stored
std::string temp; // where current token is temporarily loaded before it's put into the vector
while( getline( ss, temp, ' ' ) ) // extract characters from ss until a space is found and stores them in temp
tokens.push_back( temp ); // put the token formed by getline in our vector, from back so order is kept
return tokens;
}
int main() {
std::string line;
while ( true ) {
std::cout << "Enter parse string: ";
std::getline( std::cin, line );
if ( line == "Stop" ) break; // Enter stop to quit program
std::vector <std::string> tokens = toParsedVector( line ); // create vector
// Now you can use the vector (tokens) as you would like. Below the contents are printed.
for ( auto& x : tokens ) // for each token in 'tokens' vector,
std::cout << x << ' '; // print out token with a space at the end
std::cout << "\n\n";
for ( int i = 0; i < tokens.size(); i++ ) // for each token in 'tokens' vector,
std::cout << i << ": " << tokens[i] << '\n'; // print out index number and token
std::cout << std::endl; // newline & flush
}
return 0;
}
I hope this helps; might not be the most elegant solution, but it is very versatile.
Related
I am trying to parse a large text file and split it up into single words using strtok. The delimiters remove all special characters, whitespace, and new lines. For some reason when I printf() it, it only prints the first word and a bunch of (null) for the rest.
ifstream textstream(textFile);
string textLine;
while (getline(textstream, textLine))
{
struct_ptr->numOfCharsProcessedFromFile[TESTFILEINDEX] += textLine.length() + 1;
char *line_c = new char[textLine.length() + 1]; // creates a character array the length of the line
strcpy(line_c, textLine.c_str()); // copies the line string into the character array
char *word = strtok(line_c, delimiters); // removes all unwanted characters
while (word != nullptr && wordCount(struct_ptr->dictRootNode, word) > struct_ptr->minNumOfWordsWithAPrefixForPrinting)
{
MyFile << word << ' ' << wordCount(struct_ptr->dictRootNode, word) << '\n'; // writes each word and number of times it appears as a prefix in the tree
word = strtok(NULL, delimiters); // move to next word
printf("%s", word);
}
}
Rather than jumping through the hoops necessary to use strtok, I'd write a little replacement that works directly with strings, without modifying its input, something on this general order:
std::vector<std::string> tokenize(std::string const &input, std::string const &delims = " ") {
std::vector<std::string> ret;
int start = 0;
while ((start = input.find_first_not_of(delims, start)) != std::string::npos) {
auto stop = input.find_first_of(delims, start+1);
ret.push_back(input.substr(start, stop-start));
start = stop;
}
return ret;
}
At least to me, this seems to simplify the rest of the code quite a bit:
std::string textLine;
while (std::getline(textStream, textLine)) {
struct_ptr->numOfCharsProcessedFromFile[TESTFILEINDEX] += textLine.length() + 1;
auto words = tokenize(textLine, delims);
for (auto const &word : words) {
MyFile << word << ' ' << wordCount(struct_ptr->dictRootNode, word) << '\n';
std::cout << word << '\n';
}
}
This also avoids (among other things) the massive memory leak you had, allocating memory every iteration of your loop, but never freeing any of it.
Move printf two lines UP.
while (word != nullptr && wordCount(struct_ptr->dictRootNode, word) > struct_ptr->minNumOfWordsWithAPrefixForPrinting)
{
printf("%s", word);
MyFile << word << ' ' << wordCount(struct_ptr->dictRootNode, word) << '\n'; // writes each word and number of times it appears as a prefix in the tree
word = strtok(NULL, delimiters); // move to next word
}
As #j23 pointed out, your printf is in the wrong location.
As #Jerry-Coffin points out, there are more c++-ish and modern ways to accomplish, what you try to do. Next to avoiding mutation, you can also avoid copying the words out of the text string. (In my code below, we read line by line, but if you know your whole text fits into memory, you could as well read the whole content into a std::string.)
So, using std::string_view avoids to perform extra copies, it being just something like a pointer into your string and a length.
Here, how it looks like, for a use case, where you need not store the words in another data structure - some kind of one-pass processing of words:
#include <iostream>
#include <fstream>
#include <string>
#include <string_view>
#include <cctype>
template <class F>
void with_lines(std::istream& stream, F body) {
for (std::string line; std::getline(stream,line);) {
body(line);
}
}
template <class F>
void with_words(std::istream& stream, F body) {
with_lines(stream,[&body](std::string& line) {
std::string_view line_view{line.cbegin(),line.cend()};
while (!line_view.empty()) {
// skip whitespaces
for (; !line_view.empty() && isspace(line_view[0]);
line_view.remove_prefix(1));
size_t position = 0;
for (; position < line_view.size() &&
!isspace(line_view[position]);
position++);
if (position > 0) {
body(line_view.substr(0,position));
line_view.remove_prefix(position);
}
}
});
}
int main (int argc, const char* argv[]) {
size_t word_count = 0;
std::ifstream stream{"input.txt"};
if(!stream) {
std::cerr
<< "could not open file input.txt" << std::endl;
return -1;
}
with_words(stream, [&word_count] (std::string_view word) {
std::cout << word_count << " " << word << std::endl;
word_count++;
});
std::cout
<< "input.txt contains "
<< word_count << " words."
<< std::endl;
return 0;
}
I am trying to parse a command line string, at every white space putting into consideration the string has words between quotation marks. I want to store whatever is between 2 quotation marks as 1 index in a vector.
vector<string> words;
stringstream ss(userInput);
string currentWord;
vector<string> startWith;
stringstream sw(userInput);
while (getline(sw, currentWord, ' '))
words.push_back(currentWord);
while (getline(ss, currentWord, '"'))
startWith.push_back(currentWord); //if(currentWord.compare("")){ continue;}
for (int i = 0; i < startWith.size(); i++)
curr
if(currentWord.compare("")){ continue;}
cout << " Index "<< i << ": " << startWith[i] << "\n";
It is not clear what you're trying to do. Here's a starting point (run it):
#include <iostream>
#include <sstream>
#include <string>
std::istream& get_word_or_quote( std::istream& is, std::string& s )
{
char c;
// skip ws and get the first character
if ( !std::ws( is ) || !is.get( c ) )
return is;
// if it is a word
if ( c != '"' )
{
is.putback( c );
return is >> s;
}
// if it is a quote (no escape sequence)
std::string q;
while ( is.get( c ) && c != '"' )
q += c;
if ( c != '"' )
throw "closing quote expected";
//
s = std::move( q );
return is;
}
int main()
{
std::istringstream is {"not-quoted \"quoted\" \"quoted with spaces\" \"no closing quote!" };
try
{
std::string word;
while ( get_word_or_quote( is, word ) )
std::cout << word << std::endl;
}
catch ( const char* e )
{
std::cout << "ERROR: " << e;
}
return 0;
}
The expected output is:
not-quoted
quoted
quoted with spaces
ERROR: closing quote expected
This question already has answers here:
How do I iterate over the words of a string?
(84 answers)
Closed 5 years ago.
I am trying to split a single string, with spaces, into three separate strings. For example, I have one string (str1). The user inputs any 3 words such as
"Hey it's me" or "It's hot out".
From there, I need to write a function that will take this string (str1) and divide it up into three different strings. So that (taking the first example) it will then say:
Hey (is the first part of the string)
it's (is the second part of the string)
me (is the third part of the string)
I'm having difficulty which manipulation I should be using to split the string at the spaces.
This is the code I have so far, which is just how the user will enter input.I am looking for the most basic way to accomplish this WITHOUT using istringstream! Using only basic string manipulation such as find(), substr().
** I am looking to create a separate function to perform the breaking up of string ** I figured out how to get the first section of input with this code:
cout << "Enter a string" << endl;
getline(cin, one);
position = str1.find(' ', position);
first_section = str1.substr(0, position);
But now I have no idea how to get the second section or the third section of the string to be divided up into their own string. I was thinking a for loop maybe?? Not sure.
#include <iostream>
#include <string>
using namespace std;
int main() {
string str1;
cout << "Enter three words: ";
getline(cin, str1);
while(cin) {
cout << "Original string: " << str1 << endl;
cin >> str1;
}
return;
}
I'm having difficulty which manipulation I should be using to split the string at the spaces.
Use a std::istringstream from str1.
Read each of the tokens from the std::istringstream.
// No need to use a while loop unless you wish to do the same
// thing for multiple lines.
// while(cin) {
cout << "Original string: " << str1 << endl;
std::istringstream stream(str1);
std::string token1;
std::string token2;
std::string token3;
stream >> token1 >> token2 >> token3;
// Use the tokens anyway you wish
// }
If you wish to do the same thing for multiple lines of input, use:
int main() {
string str1;
cout << "Enter three words: ";
while(getline(cin, str1))
{
cout << "Original string: " << str1 << endl;
std::istringstream stream(str1);
std::string token1;
std::string token2;
std::string token3;
stream >> token1 >> token2 >> token3;
// Use the tokens anyway you wish
// Prompt the user for another line
cout << "Enter three words: ";
}
}
Perhaps the most basic solution is to use that which resides inside of your loop to read a single word. For example:
cin >> word1; // extracts the first word
cin >> word2; // extracts the second word
getline(cin, line); // extracts the rest of the line
You can use the result or return value of these expressions to check success:
#include <string>
#include <iostream>
int main(void) {
std::string word1, word2, line;
int success = std::cin >> word1 && std::cin >> word2
&& !!std::getline(std::cin, line); // double-! necessary?
if (success) { std::cout << "GOOD NEWS!" << std::endl; }
else { std::cout << "bad news :(" << std::endl; }
return 0;
}
Alternatively, in such a string I would expect two spaces. My suggestion would be to use string::find to locate the first and second spaces like so:
size_t first_position = str1.find(' ', 0);
You should probably check this against string::npos as an opportunity to handle errors. Following that:
size_t second_position = str1.find(' ', first_position + 1);
Next error handling check and after that, it should then be trivial to use string::substr to split that string into sections like so:
string first_section = str1.substr(0 , first_position)
, second_section = str1.substr(first_position , second_position)
, third_section = str1.substr(second_position, string::npos);
I have this Utility class that has a bunch of methods for string manipulation. I will show the class function for splitting strings with a delimiter. This class has private constructor so you can not create an instance of this class. All the methods are static methods.
Utility.h
#ifndef UTILITY_H
#define UTILITY_h
// Library Includes Here: vector, string etc.
class Utility {
public:
static std::vector<std::string> splitString( const std::string& strStringToSplit,
const std::string& strDelimiter,
const bool keepEmpty = true );
private:
Utility();
};
Utility.cpp
std::vector<std::string> Utility::splitString( const std::string& strStringToSplit,
const std::string& strDelimiter,
const bool keepEmpty ) {
std::vector<std::string> vResult;
if ( strDelimiter.empty() ) {
vResult.push_back( strStringToSplit );
return vResult;
}
std::string::const_iterator itSubStrStart = strStringToSplit.begin(), itSubStrEnd;
while ( true ) {
itSubStrEnd = search( itSubStrStart, strStringToSplit.end(), strDelimiter.begin(), strDelimiter.end() );
std::string strTemp( itSubStrStart, itSubStrEnd );
if ( keepEmpty || !strTemp.empty() ) {
vResult.push_back( strTemp );
}
if ( itSubStrEnd == strStringToSplit.end() ) {
break;
}
itSubStrStart = itSubStrEnd + strDelimiter.size();
}
return vResult;
}
Main.cpp -- Usage
#include <string>
#include <vector>
#include "Utility.h"
int main() {
std::string myString( "Hello World How Are You Today" );
std::vector<std::string> vStrings = Utility::splitString( myString, " " );
// Check Vector Of Strings
for ( unsigned n = 0; n < vStrings.size(); ++n ) {
std::cout << vStrings[n] << " ";
}
std::cout << std::endl;
// The Delimiter is also not restricted to just a single character
std::string myString2( "Hello, World, How, Are, You, Today" );
// Clear Out Vector
vStrings.clear();
vStrings = Utility::splitString( myString2, ", " ); // Delimiter = Comma & Space
// Test Vector Again
for ( unsigned n = 0; n < vStrings.size(); ++n ) {
std::cout << vStrings[n] << " ";
}
std::cout << std::endl;
return 0;
}
I have a large CSV file which looks like this:
23456, The End is Near, A silly description that makes no sense, http://www.example.com, 45332, 5th July 1998 Sunday, 45.332
That's just one line of the CSV file. There are around 500k of these.
I want to parse this file using C++. The code I started out with is:
#include <iostream>
#include <fstream>
#include <string>
#include <sstream>
using namespace std;
int main()
{
// open the input csv file containing training data
ifstream inputFile("my.csv");
string line;
while (getline(inputFile, line, ','))
{
istringstream ss(line);
// declaring appropriate variables present in csv file
long unsigned id;
string url, title, description, datetaken;
float val1, val2;
ss >> id >> url >> title >> datetaken >> description >> val1 >> val2;
cout << url << endl;
}
inputFile.close();
}
The problem is that it's not printing out the correct values.
I suspect that it's not able to handle white spaces within a field. So what do you suggest I should do?
Thanks
In this example we have to parse the string using two getline. The first gets a line of cvs text getline(cin, line) useing default newline delimiter. The second getline(ss, line, ',') delimits using commas to separates the strings.
#include <iostream>
#include <sstream>
#include <string>
#include <vector>
float get_float(const std::string& s) {
std::stringstream ss(s);
float ret;
ss >> ret;
return ret;
}
int get_int(const std::string& s) {
std::stringstream ss(s);
int ret;
ss >> ret;
return ret;
}
int main() {
std::string line;
while (getline(cin, line)) {
std::stringstream ss(line);
std::vector<std::string> v;
std::string field;
while(getline(ss, field, ',')) {
std::cout << " " << field;
v.push_back(field);
}
int id = get_int(v[0]);
float f = get_float(v[6]);
std::cout << v[3] << std::endl;
}
}
Using std::istream to read std::strings using the overloaded insertion operator is not going to work well. The entire line is a string, so it won't pick up that there is a change in fields by default. A quick fix would be to split the line on commas and assign the values to the appropriate fields (instead of using std::istringstream).
NOTE: That is in addition to jrok's point about std::getline
Within the stated constraints, I think I'd do something like this:
#include <locale>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>
#include <iterator>
// A ctype that classifies only comma and new-line as "white space":
struct field_reader : std::ctype<char> {
field_reader() : std::ctype<char>(get_table()) {}
static std::ctype_base::mask const* get_table() {
static std::vector<std::ctype_base::mask>
rc(table_size, std::ctype_base::mask());
rc[','] = std::ctype_base::space;
rc['\n'] = std::ctype_base::space;
return &rc[0];
}
};
// A struct to hold one record from the file:
struct record {
std::string key, name, desc, url, zip, date, number;
friend std::istream &operator>>(std::istream &is, record &r) {
return is >> r.key >> r.name >> r.desc >> r.url >> r.zip >> r.date >> r.number;
}
friend std::ostream &operator<<(std::ostream &os, record const &r) {
return os << "key: " << r.key
<< "\nname: " << r.name
<< "\ndesc: " << r.desc
<< "\nurl: " << r.url
<< "\nzip: " << r.zip
<< "\ndate: " << r.date
<< "\nnumber: " << r.number;
}
};
int main() {
std::stringstream input("23456, The End is Near, A silly description that makes no sense, http://www.example.com, 45332, 5th July 1998 Sunday, 45.332");
// use our ctype facet with the stream:
input.imbue(std::locale(std::locale(), new field_reader()));
// read in all our records:
std::istream_iterator<record> in(input), end;
std::vector<record> records{ in, end };
// show what we read:
std::copy(records.begin(), records.end(),
std::ostream_iterator<record>(std::cout, "\n"));
}
This is, beyond a doubt, longer than most of the others -- but it's all broken into small, mostly-reusable pieces. Once you have the other pieces in place, the code to read the data is trivial:
std::vector<record> records{ in, end };
One other point I find compelling: the first time the code compiled, it also ran correctly (and I find that quite routine for this style of programming).
I have just worked out this problem for myself and am willing to share! It may be a little overkill but it shows a working example of how Boost Tokenizer & vectors handle a big problem.
/*
* ALfred Haines Copyleft 2013
* convert csv to sql file
* csv2sql requires that each line is a unique record
*
* This example of file read and the Boost tokenizer
*
* In the spirit of COBOL I do not output until the end
* when all the print lines are ouput at once
* Special thanks to SBHacker for the code to handle linefeeds
*/
#include <sstream>
#include <boost/tokenizer.hpp>
#include <boost/iostreams/device/file.hpp>
#include <boost/iostreams/stream.hpp>
#include <boost/algorithm/string/replace.hpp>
#include <vector>
namespace io = boost::iostreams;
using boost::tokenizer;
using boost::escaped_list_separator;
typedef tokenizer<escaped_list_separator<char> > so_tokenizer;
using namespace std;
using namespace boost;
vector<string> parser( string );
int main()
{
vector<string> stuff ; // this is the data in a vector
string filename; // this is the input file
string c = ""; // this holds the print line
string sr ;
cout << "Enter filename: " ;
cin >> filename;
//filename = "drwho.csv";
int lastindex = filename.find_last_of("."); // find where the extension begins
string rawname = filename.substr(0, lastindex); // extract the raw name
stuff = parser( filename ); // this gets the data from the file
/** I ask if the user wants a new_index to be created */
cout << "\n\nMySql requires a unique ID field as a Primary Key \n" ;
cout << "If the first field is not unique (no dupicate entries) \nthan you should create a " ;
cout << "New index field for this data.\n" ;
cout << "Not Sure! try no first to maintain data integrity.\n" ;
string ni ;bool invalid_data = true;bool new_index = false ;
do {
cout<<"Should I create a New Index now? (y/n)"<<endl;
cin>>ni;
if ( ni == "y" || ni == "n" ) { invalid_data =false ; }
} while (invalid_data);
cout << "\n" ;
if (ni == "y" )
{
new_index = true ;
sr = rawname.c_str() ; sr.append("_id" ); // new_index field
}
// now make the sql code from the vector stuff
// Create table section
c.append("DROP TABLE IF EXISTS `");
c.append(rawname.c_str() );
c.append("`;");
c.append("\nCREATE TABLE IF NOT EXISTS `");
c.append(rawname.c_str() );
c.append( "` (");
c.append("\n");
if (new_index)
{
c.append( "`");
c.append(sr );
c.append( "` int(10) unsigned NOT NULL,");
c.append("\n");
}
string s = stuff[0];// it is assumed that line zero has fieldnames
int x =0 ; // used to determine if new index is printed
// boost tokenizer code from the Boost website -- tok holds the token
so_tokenizer tok(s, escaped_list_separator<char>('\\', ',', '\"'));
for(so_tokenizer::iterator beg=tok.begin(); beg!=tok.end(); ++beg)
{
x++; // keeps number of fields for later use to eliminate the comma on the last entry
if (x == 1 && new_index == false ) sr = static_cast<string> (*beg) ;
c.append( "`" );
c.append(*beg);
if (x == 1 && new_index == false )
{
c.append( "` int(10) unsigned NOT NULL,");
}
else
{
c.append("` text ,");
}
c.append("\n");
}
c.append("PRIMARY KEY (`");
c.append(sr );
c.append("`)" );
c.append("\n");
c.append( ") ENGINE=InnoDB DEFAULT CHARSET=latin1;");
c.append("\n");
c.append("\n");
// The Create table section is done
// Now make the Insert lines one per line is safer in case you need to split the sql file
for (int w=1; w < stuff.size(); ++w)
{
c.append("INSERT INTO `");
c.append(rawname.c_str() );
c.append("` VALUES ( ");
if (new_index)
{
string String = static_cast<ostringstream*>( &(ostringstream() << w) )->str();
c.append(String);
c.append(" , ");
}
int p = 1 ; // used to eliminate the comma on the last entry
// tokenizer code needs unique name -- stok holds this token
so_tokenizer stok(stuff[w], escaped_list_separator<char>('\\', ',', '\"'));
for(so_tokenizer::iterator beg=stok.begin(); beg!=stok.end(); ++beg)
{
c.append(" '");
string str = static_cast<string> (*beg) ;
boost::replace_all(str, "'", "\\'");
// boost::replace_all(str, "\n", " -- ");
c.append( str);
c.append("' ");
if ( p < x ) c.append(",") ;// we dont want a comma on the last entry
p++ ;
}
c.append( ");\n");
}
// now print the whole thing to an output file
string out_file = rawname.c_str() ;
out_file.append(".sql");
io::stream_buffer<io::file_sink> buf(out_file);
std::ostream out(&buf);
out << c ;
// let the user know that they are done
cout<< "Well if you got here then the data should be in the file " << out_file << "\n" ;
return 0;}
vector<string> parser( string filename )
{
typedef tokenizer< escaped_list_separator<char> > Tokenizer;
escaped_list_separator<char> sep('\\', ',', '\"');
vector<string> stuff ;
string data(filename);
ifstream in(filename.c_str());
string li;
string buffer;
bool inside_quotes(false);
size_t last_quote(0);
while (getline(in,buffer))
{
// --- deal with line breaks in quoted strings
last_quote = buffer.find_first_of('"');
while (last_quote != string::npos)
{
inside_quotes = !inside_quotes;
last_quote = buffer.find_first_of('"',last_quote+1);
}
li.append(buffer);
if (inside_quotes)
{
li.append("\n");
continue;
}
// ---
stuff.push_back(li);
li.clear(); // clear here, next check could fail
}
in.close();
//cout << stuff.size() << endl ;
return stuff ;
}
You are right to suspect that your code is not behaving as desired because the whitespace within the field values.
If you indeed have "simple" CSV where no field may contain a comma within the field value, then I would step away from the stream operators and perhaps C++ all together. The example program in the question merely re-orders fields. There is no need to actually interpret or convert the values into their appropriate types (unless validation was also a goal). Reordering alone is super easy to accomplish with awk. For example, the following command would reverse 3 fields found in a simple CSV file.
cat infile | awk -F, '{ print $3","$2","$1 }' > outfile
If the goal is really to use this code snippet as a launchpad for bigger and better ideas ... then I would tokenize the line by searching for commas. The std::string class has a built-in method to find the offsets specific characters. You can make this approach as elegant or inelegant as you want. The most elegant approaches end up looking something like the boost tokenization code.
The quick-and-dirty approach is to just to know your program has N fields and look for the positions of the corresponding N-1 commas. Once you have those positions, it is pretty straightforward to invoke std::string::substr to extract the fields of interest.
I have two pieces of code.They work properly when it is used alone in the main().
vector<int> v;
cout << "Enter sequance of integers "<< "(press q to quit) : ";
istream_iterator<int> start_cin(cin);
istream_iterator<int> end_of_cin;
copy(start_cin,end_of_cin,back_inserter(v));
for ( vector<int>::iterator It = v.begin();It != v.end(); It++ )
cout << *It << "\t";
cout << endl;
and
vector<string> vS;
cout << "Enter three strings : ";
for ( int i = 0; i < 3; i++ )
vS.push_back(*istream_iterator<string>(cin));
ostream_iterator<string> sIt(cout,", ");
copy(vS.begin(),vS.end(),sIt);
cout << endl;
When these two part use together,i.e
#include <iostream>
#include <algorithm>
#include <vector>
#include <iterator>
#include <string>
using namespace std;
int main ()
{
// first part
vector<int> v;
cout << "Enter sequance of integers "<< "(press q to quit) : ";
istream_iterator<int> start_cin(cin);
istream_iterator<int> end_of_cin;
copy(start_cin,end_of_cin,back_inserter(v));
for ( vector<int>::iterator It = v.begin();It != v.end(); It++ )
cout << *It << " \t";
cout << endl;
vector<string> vS;
cout << "Enter three strings : ";
for ( int i = 0; i < 3; i++ )
vS.push_back(*istream_iterator<string>(cin));
ostream_iterator<string> sIt(cout,", ");
copy(vS.begin(),vS.end(),sIt);
cout << endl;
return 0;
}
here first part worked but second part give output: Enter Three Strings : , , ,.
I want to know that what is the reason behind this behaviour?
Thanks.
After the copy() has completed cin will be in an unreadable state (!cin.good()), due to the failed read of the "integer" q. This means the subsequent for loop will fail to read anything.
Add:
cin.clear();
cin.ignore(); // To skip the unread "q"
before the for loop.
EDIT:
As commented by James Kanze, check to ensure "q" was the cause of the termination of the copy():
...
cin.clear();
string int_read_terminator;
cin >> int_read_terminator;
if ("q" != int_read_terminator)
{
cerr << "Integer copy() failure: " << int_read_terminator << "\n";
}
else
{
...
You've just encountered one of the problems with input_iterator: it
requires the entire file to be of one type. There are several ways of
working around this; the most general is to insert a filtering streambuf
between the actual source and the stream. Thus, for example, the first
part of your stream should terminate when you enter a single line with
just a 'q', something like:
class UntilQStreambuf : public std::streambuf
{
std::streambuf* mySource;
char myBuffer;
bool myIsAtStartOfLine;
protected:
int underflow()
{
int results = mySource->sbumpc();
if ( results == 'q'
&& myIsAtStartOfLine
&& mySource->sgetc() == '\n' ) {
mySource->sbumpc(); // remove terminator line.
results = traits_type::eof();
}
if ( results != traits_type::eof() ) {
myBuffer = results;
setg( &myBuffer, &myBuffer, &myBuffer + 1 );
}
return results;
}
public:
UntilQStreambuf( std::istream& source )
: mySource( source->rdbuf() )
, myIsAtStartOfLine( true )
{
}
};
(I think boost::iostream has some support which would make this
significantly simpler to write.) You then set up a separate stream for
reading the numbers, using the streambuf from std::cin (or
whereever):
std::vector<int>
getNumbers( std::istream& source )
{
UntilQStreambuf localSB( source );
std::istream src( &localSB );
std::vector<int> results( (std::istream_iterator<int>( src )),
(std::istream_iterator<int>()) );
if ( !src.eof() ) {
// Some other error occurred...
}
return results;
}
By using a separate stream, the end condition won't be set in the
original stream, and you can continue with it later (perhaps using more
of the same technique).