Parent directory
Makefile
grades-malformed.txt
grades.txt
io1.cpp
io2.cpp
io3.cpp
CC = g++
CXX = g++
CFLAGS = -g -Wall
CXXFLAGS = -g -Wall -std=c++17
executables = io1 io2 io3
.PHONY: default
default: $(executables)
io1: io1.o
io2: io2.o
io3: io3.o
.PHONY: clean
clean:
rm -f *~ a.out core *.o $(executables)
.PHONY: all
all: clean default
"Edgar Allan Poe" , 88.8
"Mark Twain",, 66.6
"Herman Melville",77.7
"Edgar Allan Poe" , 88.8
"Mark Twain", 66.6
"Herman Melville",77.7
#include <iostream>
#include <iomanip>
void print_io_states(std::ios& io) {
std::cout << '\t';
std::cout << " fail()==" << io.fail();
std::cout << " bad()==" << io.bad();
std::cout << " eof()==" << io.eof();
std::cout << '\n';
}
int sum_ints(std::istream& is) {
int i, sum = 0;
while (is >> i) {
sum += i;
std::cout << "current sum: " << std::setw(4) << sum;
print_io_states(is);
}
return sum;
}
int main() {
int sum = sum_ints(std::cin);
std::cout << " final sum: " << std::setw(4) << sum;
print_io_states(std::cin);
}
/* Lecture outline:
1. Code overview
- I/O Stream Library class hierarchy
- https://en.cppreference.com/w/cpp/io
- std::ios, std::istream, std::cin
- istream& istream::operator>>( int& value );
- ios::operator bool() const;
- same as !ios::fail()
- I/O manipulator
- "out << setw(n)" will invoke "out.width(n)"
2. ios_base::iostate flags and ios accessors
- https://en.cppreference.com/w/cpp/io/ios_base/iostate
- ios_base::iostate
- failbit: formatting or parsing error
- badbit: irrecoverable I/O error
- eofbit: end-of-file (EOF) is reached
- ios accessor functions:
- fail(): failbit or badbit are set - should've been fail_or_bad()...
- bad(): badbit is set
- eof(): eofbit is set
- operator bool(): equivalent to !fail()
3. Examples runs:
$ echo "10 2 3 4" | ./io1
current sum: 10 fail()==0 bad()==0 eof()==0
current sum: 12 fail()==0 bad()==0 eof()==0
current sum: 15 fail()==0 bad()==0 eof()==0
current sum: 19 fail()==0 bad()==0 eof()==0
final sum: 19 fail()==1 bad()==0 eof()==1
- Note that echo "10 2 3 4" adds '\n' at the end,
so 4 was read without hitting EOF.
$ echo "10 abc 3 4" | ./io1
current sum: 10 fail()==0 bad()==0 eof()==0
final sum: 10 fail()==1 bad()==0 eof()==0
- "abc" failed to parse.
$ echo -n "10 2 3 4" | ./io1
current sum: 10 fail()==0 bad()==0 eof()==0
current sum: 12 fail()==0 bad()==0 eof()==0
current sum: 15 fail()==0 bad()==0 eof()==0
current sum: 19 fail()==0 bad()==0 eof()==1
final sum: 19 fail()==1 bad()==0 eof()==1
- echo -n does not add '\n' at the end,
so when 4 was read, we hit EOF.
*/
#include <iostream>
#include <iomanip>
void print_io_states(std::ios& io) {
std::cout << '\t';
std::cout << " fail()==" << io.fail();
std::cout << " bad()==" << io.bad();
std::cout << " eof()==" << io.eof();
std::cout << '\n';
}
int sum_ints(std::istream& is) {
int i, sum = 0;
while (!is.eof()) {
is >> i;
if (is.bad()) {
throw std::runtime_error{"bad istream"};
} else if (is.fail()) {
// fail(), but not bad()
if (is.eof()) {
// failbit && eofbit: this is normal (trailing whitespace)
break;
}
// failbit && !eofbit: probably non-digit; just skip it
is.clear();
char c;
is.get(c);
std::cout << " skipped: " << c << '\n';
} else {
// read i successfully;
// we could have hit eof or not (depends on trailing whitespace)
sum += i;
std::cout << "current sum: " << std::setw(4) << sum;
print_io_states(is);
}
}
return sum;
}
int main() {
int sum = sum_ints(std::cin);
std::cout << " final sum: " << std::setw(4) << sum;
print_io_states(std::cin);
}
/* Lecture outline:
1. sum_ints() rewritten to recover from error
$ echo "10 abc 3 4" | ./io2
current sum: 10 fail()==0 bad()==0 eof()==0
skipped: a
skipped: b
skipped: c
current sum: 13 fail()==0 bad()==0 eof()==0
current sum: 17 fail()==0 bad()==0 eof()==0
final sum: 17 fail()==1 bad()==0 eof()==1
2. sum_ints() code walk-through
- careful coverage of every possible case
- ios::clear(), istream::get()
3. C++ I/O streams vs. C standard I/O
By default, std::cin, std::cout, and std::err are synchronized with stdin,
stdout, and stderr. This means that the C++ streams are unbuffered, and
each I/O operation on a C++ stream is immediately applied to the
corresponding C stream's buffer. This makes it possible to freely mix C++
and C I/O.
You can turn off this behavior by detaching C++ streams from C streams:
std::ios_base::sync_with_stdio(false);
Possible benefits of detaching C++ streams:
- The C++ streams are allowed to buffer their I/O independently,
which may increase performance.
But you lose the following benefits when you detach C++ streams:
- You can no longer mix C & C++ I/O.
- C++ streams will no longer be thread-safe.
- '\n' may not flush standard output connected to terminal.
Recommendation:
- Keep the default behavior (i.e. C++ & C I/O synchronized) unless
performance measurement reveals significant overhead.
- Use '\n' instead of std::endl for terminal output.
*/
#include <iostream>
#include <iomanip>
#include <fstream>
#include <sstream>
using namespace std;
static ostream& operator<<(ostream& os, const pair<string,double>& e) {
return os << "[" << e.first << "] (" << e.second << ")";
}
static istream& operator>>(istream& is, pair<string,double>& e) {
char c;
if (is >> c && c == '"') { // read opening quote, skipping whitespace
string student;
while (is.get(c) && c != '"') { // read all chars till closing quote
student += c;
}
if (c == '"') { // proceed if we read the name properly
if (is >> c && c == ',') { // read a comma, skipping whitespace
double grade;
if (is >> grade) {
e = { student, grade };
return is;
}
}
}
}
// if we are here, we could not read "student name", grade
is.setstate(ios_base::failbit);
return is;
}
int f1(istream& is);
int f2(istream& is);
int f3(const char *filename);
int main(int argc, char **argv) {
try {
return f1(cin);
//return f2(cin);
//return f3(argv[1]);
} catch (const exception& x) {
cerr << x.what() << '\n';
}
}
int f1(istream& is) {
pair<string,double> e;
while (is >> e) {
cout << e << '\n';
}
// istream::operator bool() returns false if fail() is true,
// which means failbit == 1 or badbit == 1
if (is.bad()) {
throw runtime_error{"bad istream"};
} else if (!is.eof()) {
// failbit is set but we are not at the end, so parse error
throw invalid_argument{"bad grade format"};
} else {
return 0;
}
}
int f2(istream& is) {
int r;
string str;
while (getline(is, str)) {
istringstream iss(str);
try {
r = f1(iss);
} catch (const invalid_argument& x) {
cerr << x.what() << ": " << str << '\n';
}
}
return r;
}
int f3(const char *filename) {
ifstream ifs { filename };
if (!ifs) {
if (filename)
throw runtime_error{"can't open file: "s + filename};
else
throw runtime_error{"no file name"};
}
return f2(ifs);
}
/* Lecture outline:
1. Code walk-through
- Demo (f1 version)
- std::pair class template
- f1()
- operator>>()
2. String stream
- Revisit I/O Stream Hierarchy: https://en.cppreference.com/w/cpp/io
- f2():
- getline() reads a line from an istream
- istringstream is an istream whose character source is a string
- f2() calls f1() not with cin, but with an istringstream
- Error recovery is achieved without modifying f1()
3. File stream
- f3():
- ifstream is an istream whose character source is a file
- f3() calls f2() not with cin, but with an ifstream
- Reading from a file without redirection is achieved without
modifying f2()
- Also note:
- RAII paradigm of ifstream: the constructor opens the file
and the destructor closes it
- failbit is set if it fails to open the file
- ""s denotes an std::string literal
*/