I have to implement polymorphism in my project. I have a virtual class called "Account". Then there are 4 subclasses: USD, EUR, GBP, and CHF.
I need to read the current balance from a text file like this:
USD 50
CHF 80
GBP 10
EUR 90
and make a subclass depending on the currency.
Every currency should have its own object. Later in the program, I will implement currency exchange, exchange rates will be read from the file. I don't have any idea of how to start with these classes. What should I learn?
My code so far:
class Account{
std::string currency;
public:
virtual void balance() = 0;
};
class currencyAcc: public Konto {
std::string currency;
float balance;
walutowe(std::string currency,float balance) {
this->currency= currency;
this->balance= balance;
}
void AccBallance() {
std::cout << balance<< std::endl;
}
};
What should I learn?
Well, if you have covered the basics, you sure need some practice and guidance!
You could have a global function that:
reads a block of text file,
parses and creates the correct object dynamically (based on some condition), and
returns a pointer to the object (cast to the base):
Account * parseOne(std::fstream& file); // Reference to opened file
Even if you just want the code, you will still have to go through an explanation. :)
Let us see it in a general sense.
Read a line
Very simply:
std::getline(file, line);
it. You should also check if the read was successful.
Parse it
You can do this as:
std::stringstream parse_me(line);
parse_me >> some_data_1;
parse_me >> some_data_2;
...
Create your object...
Here, you need to create it on the basis of currency_type. Do:
if(currency_type == "GBP")
{
new_currency_object = new GBP(balance);
}
for each derived class.
...and The Code:
Putting it together:
Account * parseOne(std::fstream& file) // Reference to opened file
{
// To read a line from the file
std::string line;
// If the read failed, return NULL
if(!std::getline(file, line))
{
return 0;
}
// Read success
// Using stringstream so that different types of data can be parsed
std::stringstream line_buffer(line);
// Declare variables to be parsed
std::string currency_type;
float balance;
// Now parse it (note the order!)
line_buffer >> currency_type >> balance;
// Create a pointer to base...
Account * new_currency_object;
// ...and create the object based on the currency_type
if(currency_type == "USD")
{
new_currency_object = new USD(balance);
}
... for each currency
// We are done, return the fruits of our labour
return new_currency_object;
}
(Note that I assume you have a USD(float balance) constructor. If not, set balance yourself)
to be used as:
// open the file
std::fstream currency_file("my_currencies.txt");
// Read a currency
Account * a_currency;
// When the read finishes, we get NULL
while(a_currency = parseOne(currency_file))
{
// do something with a_currency. Maybe:
// list_of_currencies.push_back(a_currency) it?
}
Edit: And be sure to deallocate the memory once done! In fact, use of new and raw pointers are not encouraged anymore. Thanks to this comment for suggesting it.
For further reading, see How to implement the factory method pattern in C++ correctly
.
Good luck!
You need a Currency class that accepts the currency code in the constructor. The object of the Currency class can be part of the account via composition, instead of the current currency with datatype string.
Related
I want to make my code more efficient, specifically the reading of data from a text file. Here is a snapshot of what it looks like now:
values V(name);
V.population = read_value(find_line_number(name, find_in_map(pop, mapping)));
V.net_growth = read_value(find_line_number(name, find_in_map(ngr, mapping)));
... // and so on
Basically, the read_value function creates an ifstream object, opens the file, reads one line of data, and closes the file connection. This happens many times. What I want to do is to open the file once, read every line that is needed into the struct, and then close the file connection.
Here is the creating values struct function with parameters:
static values create_struct(std::string name, std::map<std::string, int> mapping) {
values V(name);
V.population = read_value(find_line_number(name, find_in_map(pop, mapping)), file);
V.net_growth = read_value(find_line_number(name, find_in_map(ngr, mapping)), file);
// more values here
return V;
}
The function that calls create_struct is shown below:
void initialize_data(string name) {
// read the appropriate data from file into a struct
value_container = Utility::create_struct(name, this->mapping);
}
I am thinking of instead defining the ifstream object in the function initialize_data. Given what is shown about my program, would that be the best location to create the file object, open the connection, read the values, then close the connection? Also, would I need to pass in the ifstream object into the create_values struct, and if so, by value, reference or pointer?
The short answer is to create your ifstream object first and pass it as reference to your parser. Remember to seek the stream back to the beginning before you leave your function, or when you start to read.
The RAII thing to do would be to create a wrapper object that automatically does this when it goes out of scope.
class ifStreamRef{
ifStreamRef(std::ifstream& _in) : mStream(_in){}
~ifStreamRef(){mStream.seekg(0);}
std::ifstream& mStream;
}
Then you create a wrapper instance when entering a method that will read the fstream.
void read_value(std::ifstream& input, ...){
ifStreamRef autoRewind(input);
}
Or, since the Ctor can do the conversion...
void read_value(ifStreamRef streamRef, ...) {
streamRef.mStream.getLine(...);
}
std::ifstream itself follows RAII, so it will close() the stream for you when your stream goes out of scope.
The long answer is that you should read up on dependency injection. Don't create dependencies inside of objects/functions that can be shared. There are lots of videos and documents on dependency injection and dependency inversion.
Basically, construct the objects that your objects depend on and pass them in as parameters.
The injection now relies on the interface of the objects that you pass in. So if you change your ifStreamRef class to act as an interface:
class ifStreamRef{
ifStreamRef(std::ifstream& _in) : mStream(_in){}
~ifStreamRef(){mStream.seekg(0);}
std::string getLine(){
// todo : mStream.getLine() + return "" on error;
}
bool eof() { return mStream.eof(); }
std::ifstream& mStream;
}
Then later on you can change the internal implementation that would take a reference to vector<string>& instead of ifstream...
class ifStreamRef{
ifStreamRef(std::vector<string>& _in) : mStream(_in), mCursor(0){}
~ifStreamRef(){}
std::string getLine(){
// todo : mStream[mCursor++] + return "" on error;
}
bool eof() { return mCursor >= mStream.size(); }
std::vector<string>& mStream;
size_t mCursor;
}
I have oversimplified a few things.
My professor gave us an assignment where we have to take a data file and that has information about a phone company and wants us to take the call minutes and find the final charge. I'm pretty sure my function is okay but he didn't really explain how to extract information from a data file. (His examples were very simple).
The columns in the file represent the phone number, company code, local minutes, and long distance minutes respectively. This is the order I made my parameters in my function.
My Code:
int main()
{
int pNumber;
char company;
int localMin;
int longMin;
ifstream infile;
ofstream outfile;
infile.open("e:/C++/Lab Assignments/Lab 6/phoneData.dat");
outfile.open("e:/C++/Lab Assignments/Lab 6/companyComparison.bat");
while(!infile.eof())
{
if(pNumber > 111000)
**infile >> companyA(int pNumber, char company, int localMin, int longMin); **
}
infile.close("e:/C++/Lab Assignments/Lab 6/phoneData.dat");
outfile.close("e:/C++/Lab Assignments/Lab 6/companyComparison.bat");
system("pause");
return 0;
}
I'm getting an error in the while loop in my main function. I put double asterisks around the code line in question. I'm just not sure how to fix this.
You don't "place" data into a function, you pass them. That's what function parameters are for. Besides that,
companyA(int pNumber, char company, int localMin, int longMin);
is a function declaration, which does not have a place here at all.
Here's the edited code that uses the variables you declared, and does not use std::basic_ios::eof:
// read to intermediate variables
while(infile >> pNumber >> company >> localMin >> longMin)
{
if(pNumber > 111000)
// just call the function, passing intermediate variables
companyA(pNumber, company, localMin, longMin);
}
Moreover, std::basic_fstream does not have a close that takes some arguments (path). See the reference and pay attention in the class.
I need to write tests(using google testing framework) for small study program that was written not by me. (it's just small console game which can get modes from command line or just get it in runtime)
There is a problem: I can't change the souce code but there is in almost all methods used cout and cin. and my question is "how to answer on requests (cin) of programm while testing (something like get data for cin from string )?".
Assuming you can control main() (or some other function called before the functions to be tested) you can change where std::cin reads from and where std::cout writes to:
int main(int ac, char* av[]) {
std::streambuf* orig = std::cin.rdbuf();
std::istringstream input("whatever");
std::cin.rdbuf(input.rdbuf());
// tests go here
std::cin.rdbuf(orig);
}
(likewise for std::cout)
This example saves the original stream buffer of std::cin so it can be replaced before leaving main(). It then sets up std::cin to read from a string stream. It can be any other stream buffer as well.
My understanding is you need to perform the following:
Launch / start target executable (the game).
Send test data to target executable.
Obtain output from target executable.
Compare output with expected results.
The standard C++ language has no standard facilities for communicating with other programs. You will need help from the operating system (which you didn't specify).
Using only C++ or without OS specific calls, I suggest:
Writing test input to a file.
Run the executable, piping the test input file as input and piping
the output to a results file.
Read and analyze the result file.
Otherwise, search your OS API to find out how to write to the I/O redirection drivers.
I know you said you can't modify the code, but I'll answer this as if you can. The real world typically allows (small) modifications to accommodate testing.
One way is to wrap your calls that require external inputs (DB, user input, sockets, etc...) in function calls that are virtual so you can mock them out. (Example below). But first, a book recommendation on testing. Working Effectively with Legacy Code is a great book for testing techniques that aren't just limited to legacy code.
class Foo {
public:
bool DoesSomething()
{
string usersInput;
cin >> usersInput;
if (usersInput == "foo") { return true; }
else { return false; }
}
};
Would turn into:
class Foo
{
public:
bool DoesSomething() {
string usersInput = getUserInput();
if (usersInput == "foo") { return true; }
else { return false; }
}
protected:
virtual std::string getUserInput() {
string usersInput;
cin >> usersInput;
return usersInput;
}
};
class MockFoo : public Foo {
public:
void setUserInput(std::string input) { m_input = input }
std::string getUserInput() {
return m_input;
}
};
TEST(TestUsersInput)
{
MockFoo foo;
foo.setUserInput("SomeInput");
CHECK_EQUAL(false, foo.DoesSomething());
foo.setUserInput("foo");
CHECK_EQUAL(true, foo.DoesSomething());
}
You can improve testability of your classes by not using cin and cout directly. Instead use istream& and ostream& to pass in the input source and output sink as parameters. This is a case of dependency injection. If you do that, you can pass in a std::stringstream instead of cin, so that you can provide specified input and get at the output from your test framework.
That said, you can achieve a similar effect by turning cin and cout into stringstreams (at least temporarily). To do this, set up a std::stringbuf (or "borrow" one from a std::stringstream) and use cin.rdbuf(my_stringbuf_ptr) to change the streambuf used by cin. You may want to revert this change in test teardown. To do that you can use code like:
stringbuf test_input("One line of input with no newline", ios_base::in);
stringbuf test_output(ios_base::out);
streambuf * const cin_buf = cin.rdbuf(&test_input);
streambuf * const cout_buf = cout.rdbuf(&test_output);
test_func(); // uses cin and cout
cout.rdbuf(cout_buf);
cin.rdbuf(cin_buf);
string test_output_text = test_output.str();
This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Reading from text file until EOF repeats last line
I am writting data to a file using the following code
//temp is class object
fstream f;
f.open ("file", ios::in|ios::out|ios::binary);
for(i=0;i<number_of_employees ;++i)
{
temp.getdata();
f.write( (char*)&temp,sizeof(temp));
}
f.close();
temp is the object of following class
class employee
{
char eno[20];
char ename[20];
char desg[20];
int bpay;
int ded;
public:
void getdata();
void displaydata();
}
But when i write data using this code i find that the last object written to file gets written two times.
my function to read from file is
fstream f;
f.open ("file", ios::in|ios::out|ios::binary);
while(f)
{
f.read((char*)&temp, sizeof(temp));
temp.displaydata();
}
f.close();
following shows my file when it is read till eof
Number :1
Name :seb
Designation:ceo
Basic Pay :1000
Deductions :100
Number :2
Name :sanoj
Designation:cto
Basic Pay :2000
Deductions :400
Number :2
Name :sanoj
Designation:cto
Basic Pay :2000
Deductions :400
What is the cause of this and how can i solve it?
If the problem is repeated output, it's very likely caused by the way you are looping. Please post the exact loop code.
If the loop is based on the data you receive from getdata(), you'll need to look closely at exactly what you input as well. You might not be receiving what you expect.
Of course, without real code, these are almost just guesses.
The reason for your problem is simple: you're not checking whether the
read has succeeded before using the results. The last read encounters
end of file, fails without changing the values in your variables, and
then you display the old values. The correct way to do exactly what
you're trying to do would be:
while ( f.read( reinterpret_cast<char*>( &temp ), sizeof( temp ) ) ) {
temp.displaydata();
}
Exactly what you're trying to do, however, is very fragile, and could
easily break with the next release of the compiler. The fact that your
code needs a reinterpret_cast should be a red flag, indicating that
what you're doing is extremely unportable and implementation dependent.
What you need to do is first, define a binary format (or use one that's
already defined, like XDR), then format your data according to it into a
char buffer (I'd use std::vector<char> for this), and finally use
f.write on this buffer. On reading, it's the reverse: you read a
block of char into a buffer, and then extract the data from it.
std::ostream::write and std::istream::read are not for writing and
reading raw data (which makes no sense anyway); if they were, they'd
take void*. They're for writing and reading pre-formatted data.
Writing an object to a file with write((char*)object, sizeof(object)) is looking for trouble!
Rather write a dedicated write function for the class:
class employee {
...
void write(ostream &out) {
out.write(eno, sizeof(eno));
out.write(ename, sizeof(ename));
out.write(desg, sizeof(desg));
out.write((char*)&bpay, sizeof(bpay));
out.write((char*)&ded, sizeof(ded));
}
void read(istream &in) {
in.read(&eno, sizeof(eno));
in.read(&ename, sizeof(ename));
...
in.read((char*)&bpay, sizeof(bpay));
in.read((char*)&ded, sizeof(ded));
}
}
ostream &operator <<(ostream &out, employee &e) {
e.write(out);
return out;
}
istream &operator >>(istream &in, employee &e) {
e.read(in);
return in;
}
Once you've done that, you can use:
f << temp;
to write your employee record to the file.
But note that even this isn't great, because at least as far as the integers are concerned, we're becoming very platform dependent, ito the size of an int, and ito the endianness of the int.
I need to parse through a text file that contains something like :
1|Song Title|Release date||"ignore me"|0|0|0|1|1|1|0|0|0|0|0|0|0|0|0|0|0|0|0
which is the song number, followed by the release date, followed by a website that I need to ignore, and followed by a series of 0's and 1's which could represent an vector of genres.
I need a way to separate this data, and ignore the one that say's the website while at the same time creating a new instance of a Song Object which has an : (int songNumber,string songTitle, vector* genres, string releaseDate)
Thanks!
The C++ String Toolkit Library (StrTk) has the following solution to your problem:
#include <string>
#include <deque>
#include "strtk.hpp"
struct song_type
{
unsinged int id;
std::string release_date;
std::string url;
char genre[8];
};
strtk_parse_begin(song_type)
strtk_parse_type(id)
strtk_parse_type(release_date)
strtk_parse_type(url)
strtk_parse_type(genre[0])
strtk_parse_type(genre[1])
strtk_parse_type(genre[2])
strtk_parse_type(genre[3])
strtk_parse_type(genre[4])
strtk_parse_type(genre[5])
strtk_parse_type(genre[6])
strtk_parse_type(genre[7])
strtk_parse_end()
int main()
{
std::deque<song_type> song_list;
strtk::for_each_line("songs.txt",
[&song_list](const std::string& line)
{
song_type s;
if (strtk::parse(line,"|",s))
song_list.push_back(s);
});
return 0;
}
More examples can be found Here
Define a class Song that holds the data in the form you require, as you stated above
implement Song::operator>>(const istream&); to populate the class by parsing the above data from an input stream
read the file line by line using string::getline
for each line, convert to stringstream and then use your operator>> to fill in the fields in an instance of Song.
It's straightforward to tokenize the stringstream with the '|' character as a separator, which would be the bulk of the work.
int main()
{
std::string token;
std::string line("1|Song Title|Release date||\"ignore me\"|0|0|0|1|1|1|0|0|0|0|0|0|0|0|0|0|0|0|0");
std::istringstream iss(line);
while ( getline(iss, token, '|') )
{
std::cout << token << std::endl;
}
return 0;
}
Code lifted from here.
You'd typically do this by overloading operator>> for the type of object:
struct song_data {
std::string number;
std::string title;
std::string release_date;
// ...
};
std::istream &operator>>(std::istream &is, song_data &s_d) {
std::getline(is, s_d.number, '|');
std::getline(is, s_d.title, '|');
std::getline(is, s_d.release_date, '|');
std::string ignore;
std::getline(is, ignore, '|');
// ...
return is;
}
Depending on whether there are more fields you might want to ignore (especially trailing fields) it can sometimes be more convenient to read the entire line into a string, then put that into an istringstream, and parse the individual fields from there. In particular, this can avoid extra work reading more fields you don't care about, instead just going on to the next line when you've parsed out the fields you care about.
Edit: I would probably handle the genres by adding a std::vector<bool> genres;, and reading the 0's and 1's into that vector. I'd then add an enumeration specifying what genre is denoted by a particular position in the vector, so (for example) testing whether a particular song is classified as "country" would look something like:
enum { jazz, country, hiphop, classic_rock, progressive_rock, metal /*, ... */};
if (songs[i].genres[country])
if (songs[i].genres[hiphop])
process_hiphop(songs[i]);
Of course, the exact genres and their order is something I don't know, so I just made up a few possibilities -- you'll (obviously) have to use the genres (and order) defined for the file format.
As far as dealing with hundreds of songs goes, the usual way would be (as implied above) create something like: std::vector<song_data> songs;. Using a stream extraction like above, you can then copy the data from the file to the vector:
std::copy(std::istream_iterator<song_data>(infile),
std::istream_iterator<song_data>(),
std::back_inserter(songs));
If you're likely to look up songs primarily by name (for one example), you might prefer to use std::map<std::string, song_data> songs. This will make it easy to do something like:
songs["new song"].release_date = Today;