I've seen other people reporting this problem with cin.ignore() and getline(). I understand it's some problem involving newlines, but I'm not entirely sure how to debug this with >>. I'm trying to implement a gradebook that takes in a student name and test grades and outputs their name (and eventually, course grade) [from Chapter 4 of Accelerated C++]. I'm having trouble even outputting the names properly, though.
// Student.cpp
#include "Student.h"
#include <iostream>
#include <vector>
istream& read(istream& in, Student& s) {
in >> s.name >> s.midterm;
read_hw(in, s.homework);
return in;
}
istream& read_hw(istream& in, vector<double>& hw) {
if (in) {
hw.clear();
double x;
while (in >> x)
hw.push_back(x);
in.clear();
}
return in;
}
And here I try to test it with my main function:
int main() {
vector<Student> students;
Student curr_student;
while (read(cin, curr_student)) {
cout << curr_student.name;
students.push_back(curr_student);
cout << students.size() << endl;
}
cout << students.size() << endl;
for (int i = 0; i < students.size(); i++) {
cout << students[i].name << endl;
}
return 0;
}
When I input something into the command line, though, the output of the student names after the first one are cut off:
Input in terminal:
Alice 50 50 50 50 (<enter>)
Bob 100 100 100 100 (<enter>)
Carl 50 50 50 50 (<enter>)
(<Ctrl-D>)
And then it outputs:
Alice
ob
rl
From the look of the result the grades are read using hex format: B, C, and a are vaild hex digits. That shouldn't be happen according to your cide, though.
In any case note that formatted reading will normally skip all leading white space, including newlines. There are a few approaches to deal with line ends. The usual one is to resd lines into a std::string and use the string to initialize an std::istringstream.
An alternative approach would be the use of a custom definition of line ends by specializing the std::ctype<char> facet to not consider newline a space character. Another approach is using a manipulator which consumes whitespace but sets std::ios_base::failbit upon encountering a newline. For example:
std::istream& skip(std::istream& in) {
if (std::istream::sentry kerberos{in, true}) {
std::istreambuf_iterator<char> it(in), end;
if (end != (it = std::find_if(it, end, [](unsigned char c){
return !std::isspace(c) || char(c) == '\n'; }))
&& *it == '\n') {
++it;
in.setstate(std::ios_base::failbit);
}
}
return in;
}
// ...
if (in) {
while (in >> skip >> x) {
hw.push_back(x);
}
if (!hw.empty()) {
in.clear();
}
}
While I don't think I'd be able that the original code actually reproduces the problem I'm fairly sure that the above approach does fix it!
Related
I have a file with lines in the format:
firstword;secondword;4.0
I need to split the lines by ;, store the first two words in char arrays, and store the number as a double.
In Python, I would just use split(";"), then split("") on the first two indexes of the resulting list then float() on the last index. But I don't know the syntax for doing this in C++.
So far, I'm able to read from the file and store the lines as strings in the studentList array. But I don't know where to begin with extracting the words and numbers from the items in the array. I know I would need to declare new variables to store them in, but I'm not there yet.
I don't want to use vectors for this.
#include <iomanip>
#include <fstream>
#include <string>
#include <stdlib.h>
#include <iostream>
using namespace std;
int main() {
string studentList[4];
ifstream file;
file.open("input.txt");
if(file.is_open()) {
for (int i = 0; i < 4; i++) {
file >> studentList[i];
}
file.close();
}
for(int i = 0; i < 4; i++) {
cout << studentList[i];
}
return 0;
}
you can use std::getline which support delimiter
#include <string>
#include <sstream>
#include <iostream>
int main() {
std::istringstream file("a;b;1.0\nc;d;2.0");
for (int i = 0; i < 2; i++){
std::string x,y,v;
std::getline(file,x,';');
std::getline(file,y,';');
std::getline(file,v); // default delim is new line
std::cout << x << ' ' << y << ' ' << v << '\n';
}
}
C++ uses the stream class as its string-handling workhorse. Every kind of transformation is typically designed to work through them. For splitting strings, std::getline() is absolutely the right tool. (And possibly a std::istringstream to help out.)
A few other pointers as well.
Use struct for related information
Here we have a “student” with three related pieces of information:
struct Student {
std::string last_name;
std::string first_name;
double gpa;
};
Notice how one of those items is not a string.
Keep track of the number of items used in an array
Your arrays should have a maximum (allocated) size, plus a separate count of the items used.
constexpr int MAX_STUDENTS = 100;
Student studentList[MAX_STUDENTS];
int num_students = 0;
When adding an item (to the end), remember that in C++ arrays always start with index 0:
if (num_students < MAX_STUDENTS) {
studentList[num_students].first_name = "James";
studentList[num_students].last_name = "Bond";
studentList[num_students].gpa = 4.0;
num_students += 1;
}
You can avoid some of that bookkeeping by using a std::vector:
std::vector <Student> studentList;
studentList.emplace_back( "James", "Bond", 4.0 );
But as you requested we avoid them, we’ll stick with arrays.
Use a stream extractor function overload to read a struct from stream
The input stream is expected to have student data formatted as a semicolon-delimited record — that is: last name, semicolon, first name, semicolon, gpa, newline.
std::istream & operator >> ( std::istream & ins, Student & student ) {
ins >> std::ws; // skip any leading whitespace
getline( ins, student.last_name, ';' ); // read last_name & eat delimiter
getline( ins, student.first_name, ';' ); // read first_name & eat delimiter
ins >> student.gpa; // read gpa. Does not eat delimiters
ins >> std::ws; // skip all trailing whitespace (including newline)
return ins;
}
Notice how std::getline() was put to use here to read strings terminating with a semicolon. Everything else must be either:
read as a string then converted to the desired type, or
read using the >> operator and have the delimiter specifically read.
For example, if the GPA were not last in our list, we would have to read and discard (“eat”) a semicolon:
char c;
ins >> student.gpa >> c;
if (c != ';') ins.setstate( std::ios::failbit );
Yes, that is kind of long and obnoxious. But it is how C++ streams work.
Fortunately with our current Student structure, we can eat that trailing newline along with all other whitespace.
Now we can easily read a list of students until the stream indicates EOF (or any error):
while (f >> studentList[num_students]) {
num_students += 1;
if (num_students == MAX_STUDENTS) break; // don’t forget to watch your bounds!
}
Use a stream insertion function overload to write
’Nuff said.
std::ostream & operator << ( std::ostream & outs, const Student & student ) {
return outs
<< student.last_name << ";"
<< student.first_name << ";"
<< std::fixed << std::setprecision(1) << student.gpa << "\n";
}
I am personally disinclined to modify stream characteristics on argument streams, and would instead use an intermediary std::ostreamstream:
std::ostringstream oss;
oss << std::fixed << std::setprecision(1) << student.gpa;
outs << oss.str() << "\n";
But that is beyond the usual examples, and is often unnecessary. Know your data.
Either way you can now write the list of students with a simple << in a loop:
for (int n = 0; n < num_students; n++)
f << studentList[n];
Use streams with C++ idioms
You are typing too much. Use C++’s object storage model to your advantage. Curly braces (for compound statements) help tremendously.
While you are at it, name your input files as descriptively as you are allowed.
{
std::ifstream f( "students.txt" );
while (f >> studentList[num_students])
if (++num_students == MAX_STUDENTS)
break;
}
No students will be read if f does not open. Reading will stop once you run out of students (or some error occurs) or you run out of space in the array, whichever comes first. And the file is automatically closed and the f object is destroyed when we hit that final closing brace, which terminates the lexical context containing it.
Include only required headers
Finally, try to include only those headers you actually use. This is something of an acquired skill, alas. It helps when you are beginning to list those things you are including them for right alongside the directive.
Putting it all together into a working example
#include <algorithm> // std::sort
#include <fstream> // std::ifstream
#include <iomanip> // std::setprecision
#include <iostream> // std::cin, std::cout, etc
#include <string> // std::string
struct Student {
std::string last_name;
std::string first_name;
double gpa;
};
std::istream & operator >> ( std::istream & ins, Student & student ) {
ins >> std::ws; // skip any leading whitespace
getline( ins, student.last_name, ';' ); // read last_name & eat delimiter
getline( ins, student.first_name, ';' ); // read first_name & eat delimiter
ins >> student.gpa; // read gpa. Does not eat delimiters
ins >> std::ws; // skip all trailing whitespace (including newline)
return ins;
}
std::ostream & operator << ( std::ostream & outs, const Student & student ) {
return outs
<< student.last_name << ";"
<< student.first_name << ";"
<< std::fixed << std::setprecision(1) << student.gpa << "\n";
}
int main() {
constexpr int MAX_STUDENTS = 100;
Student studentList[MAX_STUDENTS];
int num_students = 0;
// Read students from file
std::ifstream f( "students.txt" );
while (f >> studentList[num_students])
if (++num_students == MAX_STUDENTS)
break;
// Sort students by GPA from lowest to highest
std::sort( studentList, studentList+num_students,
[]( auto a, auto b ) { return a.gpa < b.gpa; } );
// Print students
for(int i = 0; i < num_students; i++) {
std::cout << studentList[i];
}
}
The “students.txt” file contains:
Blackfoot;Lawrence;3.7
Chén;Junfeng;3.8
Gupta;Chaya;4.0
Martin;Anita;3.6
Running the program produces the output:
Martin;Anita;3.6
Blackfoot;Lawrence;3.7
Chén;Junfeng;3.8
Gupta;Chaya;4.0
You can, of course, print the students any way you wish. This example just prints them with the same semicolon-delimited-format as they were input. Here we print them with GPA and surname only:
for (int n = 0; n < num_students; n++)
std::cout << studentList[n].gpa << ": " << studentList[n].last_name << "\n";
Every language has its own idiomatic usage which you should learn to take advantage of.
I am writing a program that can do operations on complex numbers. I have a class called ComplexNumber that has the overloaded operators in it. My program takes input from a file in the form of complex *operator* complex. So, for example an input would look like 3+4i + 2+3i. I have written my >> operator so this works fine.
The issue arises when the input looks like 3i + 1+2i. We have to validate the input so it works when the complex number is missing parts. It can be just a real number, or just an imaginary number.
The functions in the ComplexNumber class that relate to this issue are as follows:
ComplexNumber::ComplexNumber(double r,double i)
{
realNum = r;
imaginaryNum = i;
}
istream& operator>>(istream &input , ComplexNumber& other) //Overloaded >> operator
{
char filter = 0;
double r =0;
double i = 0;
input >> r >> i >> filter;
other.setR(r);
other.setI(i);
return input;
}
And the way I am reading in the input in my main class is as follows:
void input(ifstream &in)
{
ComplexNumber a,b;
in >> a;
in.get();
string op;
getline(in,op,' ');
in >> b;
cout << a << " " << op << " " << b << endl;
}
int main()
{
ifstream in("complex.txt");
if(!in) cout << "failed to open file." << endl;
while(!in.eof()){
input(in);
}
return 0;
}
For my operators to work, I need to set the missing part of the input as 0 in the object. So if the input was 3i the variables in the object would be realNum = 0, imaginaryNum = 3 How can I achieve this?
How can I check the input on the line to decide how it should be read in? At the moment, it is expecting the complex number to have both a real and imaginary part to it.
I also wrote an overloaded constructor for cases where the complex number only has one of the parts to it, but I am unsure how to use it. The function is as follows:
ComplexNumber::ComplexNumber(double in, string r_i) //Overloaded constructor
{
if(r_i == "r"){realNum = in; imaginaryNum = 0;}
else{imaginaryNum = in; realNum = 0;}
}
Beyond this issue, we also have to check to make sure that the input has no invalid characters eg. j or ! but i feel that if I get help with this first problem, I can use the information given to solve this second problem.
I realize that this may not be worded in the best way, I just hope you understand what I am trying to achieve. I really appreciate any help with this. Thanks.
Normally I'd do this with a state machine. Never done it with C++ streams before. Bit sneakier than it looked, but basically the same. Commentary on the whats and whys embedded as comments in the code.
#include <string>
#include <iostream>
#include <sstream>
#include <cmath>
#include <cctype>
// Made this really dumb for ease of writing example
struct ComplexNumber
{
double realNum;
double imaginaryNum;
};
// splitting the guts of the parsing off into its own function made writing
// operator>> dead easy
bool parsecomplex(std::istream &input,
double & real,
double & imag)
{
char filter;
double temp;
char next;
if (input >> temp)// read a double. No clue if it's the real or imaginary part yet.
{
next = input.peek(); // check the next character, but do not extract
if (next != 'i') // not imaginary
{
real = temp; // store as real
if (next == '+' || next == '-') // do we stop here or is there an imaginary?
{
if (input >> imag >> filter // read imaginary
&& filter == 'i') // and ensure trailing i
{
return true;
}
}
else
{
return true;
}
}
else
{ // just an imaginary
imag = temp;
input >> filter; // remove the i. we already know it's an i
return true;
}
}
return false;
}
std::istream& operator>>(std::istream &input,
ComplexNumber& other)
{
double real = 0.0;
double imag = 0.0;
if (parsecomplex(input, real, imag))
{ // OK so we got a good complex number.
other.realNum = real;
other.imaginaryNum = imag;
input.clear(); // may have read eof
return input;
/* This next bit is a deviation from normal stream parsing. Typically 3j
would be read and store of 3 as real and j stays in the stream for the
next read. OP sounds like they might need to be a bit more anal. If so,
replace the above with
char next = input.peek();
if (std::isspace(next) || next == std::char_traits<char>::eof())
{
other.realNum = real;
other.imaginaryNum = imag;
input.clear(); // may have read eof
return input;
}
The Law of Least Surprise says you should go with the expected parsing
behaviour so as to not leave a trail of confused and angry programmers
in your wake. */
}
input.setstate(std::ios::failbit);
return input;
}
// quick test harness
void test(const char * str)
{
ComplexNumber cnum;
std::stringstream input(str);
if (input >> cnum)
{
std::string remaining;
std::getline(input, remaining);
std::cout << str << " is " << cnum.realNum <<","<< cnum.imaginaryNum
<< " still in stream: " << remaining << std::endl;
}
else
{
std::cout << "Invalid: " << str << std::endl;
}
}
int main()
{
test("3-3i");
test("3");
test("-3i");
test(" 3-3i");
test("3-3i ");
test("3 ");
test("-3i ");
test("3-3i 3-3i");
test("3 -3i");
test("j3+3i");
test("3j3i");
test("3+3j");
test("3+3ij");
test("3j");
test("-3j");
test("-3ij");
test("");
test("DETHTONGUE!");
}
Output:
3-3i is 3,-3 still in stream:
3 is 3,0 still in stream:
-3i is 0,-3 still in stream:
3-3i is 3,-3 still in stream:
3-3i is 3,-3 still in stream:
3 is 3,0 still in stream:
-3i is 0,-3 still in stream:
3-3i 3-3i is 3,-3 still in stream: 3-3i
3 -3i is 3,0 still in stream: -3i
Invalid: j3+3i
3j3i is 3,0 still in stream: j3i
Invalid: 3+3j
3+3ij is 3,3 still in stream: j
3j is 3,0 still in stream: j
-3j is -3,0 still in stream: j
-3ij is 0,-3 still in stream: j
Invalid:
Invalid: DETHTONGUE!
I have a text file, that looks like this:
Name_of_st1 67 5 87 4 78 4
Name_of_st2 89 5 56 3 79 4
...
I have written a program that reads the data and creates a vector of students with its own vector of subjects. The program reads the first line correctly, but when i ask it to write out name of second student, std::cout displays garbage. Here is the code:
struct Subject {
int mark0;
int mark1;
};
struct Student {
char name[50];
char surname[50];
vector<Subject>my_marks;
};
istream& operator>>(istream& is, Subject& sub);
istream& operator>>(istream& is, Student& st);
int main()
try
{
ifstream ifs("Text.txt");
vector<Student>group;
Student s1, s2;
ifs >> s1;
group.push_back(s1);
getline(ifs, temp);
ifs >> s2;
group.push_back(s2);
cout << group.size();
cout << group[0].name;
cout << group[0].surname;
cout << group[1].name;
cout << group[0].my_marks[1].mark0;
}
catch (exception& e){
cerr << e.what() << endl;
return 1;
}
catch (...)
{
cerr << "exception \n";
return 2;
}
istream& operator>>(istream& is, Subject& sub)
{
int m0, m1;
is >> m0;
if (!is) return is;
is>> m1;
sub.mark0 = m0;
sub.mark1 = m1;
return is;
}
istream& operator>>(istream& is, Student& st)
{
char n1[50];
char n2[50];
is >> n1;
is >> n2;
strcpy_s(st.name, n1);
strcpy_s(st.surname, n2);
if (!is) return is;
while (true)
{
Subject sub;
if (!(is >> sub)) break;
st.my_marks.push_back(sub);
}
return is;
}
I have tried this, but still the same:
string temp
ifs >> s1;
getline(ifs, temp);
group.push_back(s1);
And this:
char n1[50];
char n2[50];
for (int i = 0;; ++i)
{
char n0;
is.get(n0);
if (isspace(n0)) break;
n1[i] = n0;
}
for (int i = 0;; ++i)
{
char n0;
is.get(n0);
if (isspace(n0)) break;
n2[i] = n0;
}
if (!is) return is;
strcpy_s(st.name, n1);
strcpy_s(st.surname, n2);
Then i added ':' after every Name of Student and used is.get(n1, 100, ':').
It still reads the first line correctly, but refuses to read the next. Whatever i did, i can`t move text cursor to the beginning of new line. If anyone can help me, i would appreciate it.
Your second student is not being read because you put the stream in a bad state reading the first student.
if (!(is >> sub)) break;
You basically read subjects until the input breaks (which is when you hit a name). Since you don't reset the state of the stream to good any further attempts to read will be ignored.
You have two choices.
Set the state to good after you have finished reading the subjects.
This has an issue in that you will probably go into a infinite loop as your checking of the stream state is spotty currently.
Read a line then parse the subject information from the line.
I would go for option 2.
// After you have read the name.
// The rest of the line is scores. So read a whole line.
std::string line;
std::getline(is, line);
// convert the line into a stream.
std::stringstream linestream(line);
// Now parse the stream for subject.
Subject sub;
while(linestream >> sub) { // Note this reads an item and checks the
// state of the stream. The while body
// is only entered on a successful read.
st.my_marks.push_back(sub);
}
I have a .txt file with names and grades such as "emiltytaco 56". After the last name, there are 3 blank lines which should not be inserted into my trie and heap. However the code is Aborted and dumped when hitting the blank lines. This is the insert function
while(myfile.good())
{
getline(myfile, line);
name = line.substr(0,line.find("\t"));
stringstream convert((line.substr(line.find("\t")+1)));
convert >> grade;
if(name.at(0) > 96 && name.at(0) < 123)
{
insert(name, grade);
cout << name << " " << grade << endl;
}
}
myfile.close();
should the .close be part of an "else" statement with the if? a friend of mine has this exact thing but his does not abort.
First point, change your loop to something like:
while (getline(myfile, line)) {
// ...
}
Second, it's probably a lot simpler to feed the whole line to the stringstream, and read both pieces from there:
stringstream convert(line);
std::getline(convert, name, '\t');
convert >> grade;
Third, if you want to check for lower-case letters, you're much better off with islower than comparisons to magic numbers like 96 and 123. I wouldn't do the check that way though. If you want to check for a blank line, it would be better to do that directly, with line.empty() immediately after you read it. Using that, you end up with something like:
while (getline(myfile, line))
if (!line.empty()) {
std::istringstream convert(line);
std::getline(convert(line, name);
convert >> grade;
insert(name, grade);
std::cout << name << " " << grade << "\n";
}
There is one more step beyond that though: since the name and grade are (apparently) related, I'd probably define a class for them, and write an extractor to get an object of that type from a stream:
class whatever {
std::string name;
int grade;
friend std::istream &operator>>(std::istream &is, whatever &w) {
std::getline(is, w.name, '\t');
is >> w.grade;
is.ignore(4096, '\n');
return is;
}
};
With that in place, we can read the data from the file quite a bit more simply:
whatever w;
while (myfile >> w) {
insert(w);
std::cout << w.name << " " << w.grade << "\n";
}
Note that in this case, we don't have to check for the blank lines explicitly at all -- we just check whether we could successfully read and convert a whatever from the stream, which will fail for a blank line.
I'm writing a very simple program where I want to get user input from the standard input stream (keyboard) and then do something based on what input I encountered. However, the problem is that sometimes the input will be a number (double) while othertimes it'll be a string. I'm not sure exactly what methods calls I need in order to parse it properly (perhaps something similar to Integer.parseInt in java).
Here is some pseduocode of what I would like to do:
cin >> input
if(input is equal to "p") call methodA;
else if(input is a number) call methodB;
else call methodC;
I think this is what you need:
#include <iostream>
#include <sstream>
#include <string>
using namespace std;
void a(string& s){ cout << "A " << s << endl; }
void b(double d){ cout << "B " << d << endl; }
void c(string& s){ cout << "C " << s << endl; }
int main()
{
std::string input;
cin >> input;
if (input == "p")
a(input);
else
{
istringstream is;
is.str(input);
double d = 0;
is >> d;
if (d != 0)
b(d);
else
c(input);
}
return 0;
}
Hope this helps ;)
std::string input;
std::cin >> input;
if(input =="p") f();
else if(is_number(input)) g();
else h();
Now implement is_number() function:
bool is_number(std::string const & s)
{
//if all the characters in s, are digits, then return true;
//else if all the characters, except one, in s are digits, and there is exactly one dot, then return true;
//else return false
}
Implement this function yourself, as it seems to be homework. You can also consider case like the number may begin with sign + or -.
The usual solution I use is to read the input as a line (using
std::getline rather than >>), and parse it as I would in any
language—boost::regex is very useful here; if you are sure that
you can count on C++11, it's std::regex (which I think is almost
identical to Boost). So you end up with something like:
std::string line;
if ( ! std::getline( std::cin, line ) ) {
// Error reading line (maybe EOF).
} else {
if ( regex_match( line, firstFormat) ) {
processFirstFormat( line );
} else if ( regex_match( line, secondFormat) ) {
processSecondFormat( line ) ;
} ...
}