Learning C++ with help of Bruce Eckel "Thinking in C++". Stuck in exercise 05 of chapter "Iostreams":
Text of exercise
We know that setw( ) allows for a minimum of characters read in, but what if you wanted to read a
maximum? Write an effector that allows the user to specify a maximum number of characters to
extract. Have your effector also work for output, in such a way that output fields are truncated, if
necessary, to stay within width limits.
I understand how to create manipulators both without and with parameter (which one is called effectors in the book terminology). But do not understand how to limit maximum number of characters to extract. std::ios_base::width specifies the minimum number of characters.
Shoud I do some tricks with underlying streambuf object?
#include <iostream>
#include <iomanip>
#include <string>
#include <cstring>
using namespace std;
class fixW{
char* chrP;
char str[1024];
size_t Max;
public:
fixW(char* p,size_t m=25):chrP(p),Max(m){}
friend istream& operator >>(istream& is,fixW fw){
is >>fw.str;
size_t n=strlen(fw.str);
cout <<" n= "<<n << endl;
if(n>=25){
fw.str[fw.Max]='\0';
}
strcpy(fw.chrP,fw.str);
return is;
}
friend ostream& operator<<(ostream& os, fixW fw){
for(size_t i= 0; i<fw.Max; ++i){
fw.str[i] = fw.chrP[i];
}
fw.str[fw.Max]='\0';
return os <<fw.str;
}
};
int main(){
char s[80];
cin >> fixW(s,25);
cout << s << endl;
cout << fixW(s,10)<<endl;
cout << s <<endl;
return 0;
}
Its not a perfect solution (but I can't think of another way at the moment without reading the iostream library).
Say you manipulator is:
class MaxFieldSize {/*STUFF*/};
When you write the stream operator(s) you write a slightly funky one that does not return an actual stream (but rather returns a stream with a wrapper around it).
MaxFieldWdithStream operator<<(std::ostream&, MaxFieldSize const& manip);
Now you overload all the stream operator of this class to truncate their input before returning a normal stream object.
class MaxFieldWithStream { std::ostream& printTruncatedData(std::string& value);};
Then all you need is the generic overloads:
template<typename T>
std::ostream& operator<<(MaxFieldWithStream& mfwstream, T const& value)
{
std::stringstream trunStream;
trunStream << value;
return mfwstream.printTruncatedData(trunStream.substr(0, mfwstream.widthNeeded));
}
// You will probably need another overload for io-manipulators.
I would also add a conversion operator that converts MaxFieldWithStream to std::iostream automatically so that if it is passed to a function it still behaves like a stream (though it will loose its max width property).
class MaxFieldWithStream
{
std::ostream& printTruncatedData(std::string& value);};
operator st::ostream&() const { return BLABLAVLA;}
};
Related
I am currently trying to create a candlestick chart representing high and low temperatures for my C++ class. In this assignment, we are provided a txt file with two data columns in the following format:
Average Monthly Low High Temperatures(F)
X Y
X Y
X Y
I have successfully been able to read the txt file, but am confused as how to select specific data. I would like to essentially skip the first sentence and store the remaining variables to create the graph. This is what I am having trouble with.
In the end, I need to display the first line of the text file, as well as display the graph. I would very much like your help.
If there is a more efficient way of doing this, I would love to learn more!
You read a line in C++ with std::getline(). Note that it is good practice to check the return value of all input functions in order to detect faulty or missing data or media problems etc. getline() as well as the input operator>>() return the input stream. Streams have a handy conversion to bool: When an error has been encountered (read error, end of file), the conversion yields false; if the stream is OK, it is true. This conversion with these semantics has been designed exactly so that it is convenient to check the success of an input operation:
std::string header;
if(!std::getline(infile, header)) {
std::cerr << "Couldn't read first line\n";
exit(1);
}
// keep line for later output.
// Now read the number pairs. The numbers must be pairs but they actually don't
// have to be on a single line (but it doesn't hurt either).
int a, b;
// The read could fail for other reasons as well;
// a more robust program would look at the status bits of the stream
// and, if an error occurred, print an error message.
while (infile >> a >> b) { // will be false when EOF is reached
{
// print your tree or whatever
}
You should split the big problem into smaller problems and then solve the smallest problem first. Additionally, you should make use of buildinC++ functionality. An,in C++ you should use an object oriented approach.
That means, store data and their related functions in one object. So, the data and the methods, operating on this data.
I would propose the build 2 classes (or structs). On holds just one pair of low and high temperature.
The second class, holds the header line and a list (implemented as std::vector) of low-high-temperature pairs.
The methods that we needin our example are extraction from and insertion into a stream.
For this we usethe well known operators << and >> and add them to our class. Here we will do all necessary IO-operations.
So, if we split the big problem into smaller parts according to the above approach, then we will get easier to understand and better readable code.
Last but not least, we will use existing functions from the standard library, like std::copy.
The istream_iterator and std::ostream_iterator will simply call the underlying extratcor operator >> and inserter operator <<.
An example for a complete program (one of many possible soultions) can be seen below:
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <algorithm>
#include <iterator>
// A data struct that can hold a low and a high temperatur and knows, how reaad and write its data
struct LowHighTemperature {
// The data
double low{};
double high{};
// The extractor operator for reading values from a stream
friend std::istream& operator >> (std::istream& is, LowHighTemperature& lht) {
return is >> lht.low >> lht.high;
}
// The inserter operator for writing values to a stream
friend std::ostream& operator << (std::ostream& os, const LowHighTemperature& lht) {
return os << lht.low << '\t' << lht.high;
}
};
// A list with high and low temperatures and a header line
struct TemperatureList {
// The data
std::string header{};
std::vector<LowHighTemperature> lowHighTemperature{};
// The extractor operator for reading values from a stream
friend std::istream& operator >> (std::istream& is, TemperatureList& tl) {
// Delete potentioally pre existing data
tl.lowHighTemperature.clear();
// Read the header line
if (std::getline(is, tl.header))
// Now read all temperatures
std::copy(std::istream_iterator<LowHighTemperature>(is), {}, std::back_inserter(tl.lowHighTemperature));
return is;
}
// The inserter operator for writing values to a stream
friend std::ostream& operator << (std::ostream& os, const TemperatureList& tl) {
// Show header
os << tl.header << '\n';
// Show temperatures
std::copy(tl.lowHighTemperature.begin(), tl.lowHighTemperature.end(), std::ostream_iterator<LowHighTemperature>(os, "\n"));
return os;
}
};
// Please store the path to your temperatures source file here
const std::string temperatureFileName{ "r:\\temperatures.txt" };
int main() {
// Open source file and check, if it is open
if (std::ifstream temperaturFileStream{ temperatureFileName }; temperaturFileStream) {
// Here we will store the list with all temperatures and the header
TemperatureList temperatureList{};
// Read all data from file stream
temperaturFileStream >> temperatureList;
// For debug purposes, you show the result on the screen
std::cout << temperatureList;
}
else {
std::cerr << "\n\nError: Could not open '" << temperatureFileName << "'\n";
}
return 0;
}
You can add whatever other methods you need for you own calculations.
You can access the first temperature pair via temperatureList.lowHighTemperature[0]
I hope you got the idea.
I am new to c++ and specifically file handling.
I made this code.
#include <iostream>
#include <fstream>
using namespace std;
class student
{
public:
char s;
int age;
};
int main (void)
{
student a;
a.s = 'a';
a.age = 1;
ofstream myfile;
myfile.open("a1.txt",ios::app);
myfile.write(reinterpret_cast<char*>(&a),sizeof(student));
return 0;
}
When I opened the file a1.txt, it had the character a correctly in there, but for the integer it had some weird encoding and a message that if you continue to edit this file, it will be corrupted. Can't I write an object to a file containing an integer and a character or a string as well?
You're writing the binary representation of student into the file. The character will come out as expected; but the int will be the bytes used to represent the value, not a readable number.
If you want the output to be formatted as readable text, use formatted output:
myfile << a.s << ' ' << a.age << '\n';
For convenience, you could overload the operator for your class:
ostream & operator<<(ostream & os, student const & a) {
return os << a.s << ' ' << a.age;
}
myfile << a << '\n';
For more complex structures, you might consider the Boost.Serialization library. Or you might do what I tend to do, with tuples instead of plain structures, and variadic templates to read and write them, but that might be rather more fiddly than you'd like.
I would define your own << operator that handles your custom type.
#include <iostream>
#include <fstream>
using namespace std;
class student
{
char s;
int age;
};
ostream& operator<<(ostream &output, const student &o)
{
output << o.s << " " << o.age;
return output;
}
int main (void)
{
student a;
a.s = 'a';
a.age = 1;
ofstream myfile;
myfile.open("a1.txt",ofstream::app);
ofstream << a;
return 0;
}
When you called myfile.write(reinterpret_cast<char*>(&a),sizeof(student)); it was not converting your struct to a human-readable string then writing it to file. In reality, it was interpreting the memory of your struct as a series of characters then writing it to file.
You actually did write the int to the file, but not readable (123 => '1' '2' '3')
but the 4 (or 8) byte of that int. Your program can read the value back too,
the only probem is that we humans can´t read that form well.
Concering Strings:Just writing the whole struct will probably fail
(depending on the exact type of the string variable etc.), because the
"string" often stores only a pointer in the struct (which points to some
memory elsewhere, and this other memory isn´t written automatically to the file)
To be safe, write each variable of the struct explicitely (and handle different
var type appropiately) instead of writing them all together.
This way, things like different variable ordering and struct padding
can´t cause problems too. Other pitfalls to remember are different int
sizes and endianess on different computers... serialization isn´t trivial.
How can I effectively insert data from one stream into another stream of different type?
I have tried the following:
#include <iostream>
#include <sstream>
using namespace std;
int main(void)
{
basic_stringstream<unsigned short> uss;
stringstream cs;
unsigned short val = 0xffff;
uss.write(&val, 1); // write value to 'uss'
uss.read(&val, 1); // read data from 'uss' into 'val'
cout << hex << val << endl; // gives 0xffff
cs << uss.rdbuf(); // copy 'uss' contents into 'cs'
cs.read((char*) &val, 2); // read data from 'cs' into 'val'
cout << hex << val << endl; // gives 0x3030 ?
return 0;
}
First, as noted in this question, you can't instantiate basic_strings and streams with types like unsigned short without writing a hell lot of custom template specializations.
Second, this line
cs << uss.rdbuf();
doesn't do what you think it does. basic_ostream's operator<< that takes a basic_streambuf is
basic_ostream<charT,traits>& operator<< (basic_streambuf<char_type,traits>* sb);
where char_type is a typedef for charT. In other words, the character types must match.
In your case, they don't match, so you end up calling operator<<(const void *) instead, and just printing out the address. When I tested this on coliru, it printed out 7830 instead, for the characters 0x.
Suppose I wanted operator>> to extract entire lines from an istream instead of whitespace-separated words. I was surprised to see that this, although horrible, actually worked:
#include <iostream>
#include <string>
namespace std {
istream &operator>>(istream &is, string &str) {
getline(is, str);
}
}
int main() {
std::string line;
std::cin >> line;
std::cout << "Read: '" << line << "'\n";
}
If I type multiple words into stdin, it actually does call my operator and read an entire line.
I would expect this definition of operator>> to conflict with the official one, producing a link error. Why doesn't it?
Edit: I thought maybe the real operator>> was a template, and non-template functions take precedence, but this still works just as well:
namespace std {
template<typename charT>
basic_istream<charT> &operator>>(basic_istream<charT> &is, basic_string<charT> &s) {
getline(is, s);
}
}
It happens to work because the official template is less specific (there are additional template arguments).
However it is Undefined Behaviour™. You are only permitted to provide overloads of standard library symbols for your own types. If you come across another standard library that will define extra overloads (it may), it will stop working and you won't know why.
I assigned myself some homework over the summer, and the project I am 98% finished with has come to a standstill due to this one problem.
I have a class called Mixed. It contains member data for a whole number, a numerator, and a denominator. I need to overload all of the common operators to allow multiplication, addition, comparison and streaming of objects of type Mixed. I have all the operators overloaded except for >> (the extraction operator).
All mixed numbers read in will be of format:
whole numerator/denominator
ex: 1 2/3, 0 7/8, -3 18/5, 0 -1/89
Header: friend istream& operator>> (istream &, Mixed);
CPP file: istream& operator>> (istream &in, Mixed m) {...}
For the assignment, I am limited to the iostream and iomanip libraries. My plan was to read in the values from the stream and assign them to temporary int variables (w, n, d) which I would then use with the Mixed constructor to create object m. Unfortunately, I cannot think of a way to separate the numerator and denominator. They are both ints, but they have a char (/) between them.
I cannot use getline() with its delimiter, because it assigns data to a char array, which I do not believe I can convert to an int without another library.
I cannot use a char array and then segment it for the same reason.
I cannot use a while loop with get() and peek() because, again, I do not think I will be able to convert a char array into an int.
I cannot use a string or c-string and then segment it because that requires external libraries.
Once again, I need to split a value like "22/34" into 22 and 34, using only iostream and iomanip. Is there some fairly obvious method I am overlooking? Is there a way to implicitly convert using pointers?
You could first extract the nominator, then the separating character, and then the denominator.
Example for illustration:
istream& operator>> (istream &in, Mixed &m) {
int num, denom;
char separ;
in >> num;
in.get(separ);
if (separ != '/')
in.setstate(ios::failbit);
in >> denom;
if (in) {
// All extraction worked
m.numerator = num;
m.denominator = denom;
}
return in;
}
Once again, I need to split a value like "22/34" into 22 and 34, using
only iostream and iomanip.
Couldn't you just read in the first integer, use get to get the next character, and then read the second integer? Something like this:
#include <iostream>
int main() {
using std::cin;
using std::cout;
int num;
int den;
while(cin) {
cin >> num;
if (cin.get() != '/') {
// handle error
}
cin >> den;
cout << num << "/" << den << std::endl;
}
return 0;
}
You can then make sure that the character read between the two integers was a '/' and handle appropriately if it isn't.