Reputation: 117
I have the class date
defined as follows. I have programed the function write(ostream &o)
to write the date in this format 27/May/2020
so its easily readable to the user. Now i want to read from a file containing the date in aforementioned format. So how do i write a function in C++ that would ignore the '/'
character and store the value 27
for example in int day
, the value "May"
in string month
and the value 2010
in int year
. Also how can i design a function to ignore a certain character and be able to store the values in various data types in C++?
class date{
private :
int day;
string month;
int year;
public:
date()
{
day=0;
month="NULL";
year=2020;
}
void getdate()
{
cout<<"Enter Date : ";
cin>>day>>month;
}
void write(ostream &o) const // to write text file or screen
{
o<<day<<'/'<<month.c_str()<<'/'<<year;
}
};
Upvotes: 0
Views: 515
Reputation: 15277
You could use a very simple approach, using the property of stream extraction/insertion operators return a reference to the stream. With that you can chain extraction statement like in your example cin>>day>>month;
.
With that you can do a one liner, like in the following example:
#include <iostream>
#include <string>
#include <sstream>
std::istringstream iss{"27/May/2020"};
int main() {
int day{};
std::string month{};
int year{};
char slash{};
if ((std::getline(iss >> day >> slash, month, '/') >> year) && (slash == '/')) {
std::cout << day << ' ' << month << ' ' << year << '\n';
}
else {
std::cerr << "\nError: Wrong input format\n";
}
return 0;
}
EDIT
OP asked for more explanation for the one liner.
if ((std::getline(iss >> day >> slash, month, '/') >> year) && (slash == '/')) {
OK. As you know, nested functions will be evaluated from inside out. If you have for example: pow(sqrt(x+100),y+2)
, then at first x+100, then sqrt then y+2 and finally pow will be evaluated. Good, understood.
Looking at our one liner, it means, first iss >> day
will be evaluated. In the open stream iss (you can also use std::cin
or an std::ifstream
or basically any istream
) we have at the beginning: "27/May/2020". The above extraction operation will extract the digits 2 and 7 and convert it to the integer 27. That is our day.
Now, the input stream contains "/May/2020" because the 2 and the 7 have already been extracted.
The operation iss >> day
is finished now. The overloaded extractor operator >>
will always return a reference to the istream
with which it was invoked. Meaning, iss >> day
will return "iss".
The prototype of such a operator is usually defined as
std::istream& operator >> (std::istream& is, SomeClass& sc) {
// Do something with SomeClass sc . . .
return is; }
You can see, that the stream is returned. And that gives you the possibility to chain the extractor operator. After this first iss >> day
statement has been evaluated, the rest of the line would look like:
if ((std::getline(iss >> slash, month, '/') >> year) && (slash == '/'))
because iss >> day
has been done and returned iss. Remember that our stream contains "/May/2020" now. And, next we will do: iss >> slash
. Meaning, we will extract the slash from the stream and store it in the variable slash. The extractor operator will again return iss. And the data in the stream is now "May/2020". The new one liner will be:
if ((std::getline(iss, month, '/') >> year) && (slash == '/'))
This we can understand. std::getline will extract a string from the stream, until it sees a slash. That string is stored in the variable month. The data in the stream will now be "2020". And guest what?
std::getline```` will also return a reference to the given stream. With that the one liner will now be
if ((iss >> year) && (slash == '/'))
We know, what will come now: iss >> year
will be evaluated. "2020" will be converted to the integer 2020. The stream is now empty. Everything has been extracted. The operator >>
will again return iss. Giving us:
if ((iss) && (slash == '/'))
Hm, if
expects a boolean expression, how does this work with iss? Can be explained: If expects a boolean expression and, very smart, a stream as an overloaded bool operator. Please read here. And this operator returns, if the stream is in good state or failed.
If some extraction operation in the whole chain would have failed, then the bool operator would return false. And then we would show the error message.
As an additional check, we validate the contents of the variable "slash".
So, that's it. I hope, i could explain it in an understandable way.
End EDIT
But maybe it is more safe, if you first read a complete line (std::string
with std::getline
and then split this line.
I will show you some example code for this as well.
See some common patterns for splitting a string:
Splitting a string into tokens is a very old task. There are many many solutions available. All have different properties. Some are difficult to understand, some are hard to develop, some are more complex, slower or faster or more flexible or not.
Alternatives
std::strtok
function. Maybe unsafe. Maybe should not be used any longerstd::getline
. Most used implementation. But actually a "misuse" and not so flexiblePlease see 4 examples in one piece of code.
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <regex>
#include <algorithm>
#include <iterator>
#include <cstring>
#include <forward_list>
#include <deque>
using Container = std::vector<std::string>;
std::regex delimiter{ "," };
int main() {
// Some function to print the contents of an STL container
auto print = [](const auto& container) -> void { std::copy(container.begin(), container.end(),
std::ostream_iterator<std::decay<decltype(*container.begin())>::type>(std::cout, " ")); std::cout << '\n'; };
// Example 1: Handcrafted -------------------------------------------------------------------------
{
// Our string that we want to split
std::string stringToSplit{ "aaa,bbb,ccc,ddd" };
Container c{};
// Search for comma, then take the part and add to the result
for (size_t i{ 0U }, startpos{ 0U }; i <= stringToSplit.size(); ++i) {
// So, if there is a comma or the end of the string
if ((stringToSplit[i] == ',') || (i == (stringToSplit.size()))) {
// Copy substring
c.push_back(stringToSplit.substr(startpos, i - startpos));
startpos = i + 1;
}
}
print(c);
}
// Example 2: Using very old strtok function ----------------------------------------------------------
{
// Our string that we want to split
std::string stringToSplit{ "aaa,bbb,ccc,ddd" };
Container c{};
// Split string into parts in a simple for loop
#pragma warning(suppress : 4996)
for (char* token = std::strtok(const_cast<char*>(stringToSplit.data()), ","); token != nullptr; token = std::strtok(nullptr, ",")) {
c.push_back(token);
}
print(c);
}
// Example 3: Very often used std::getline with additional istringstream ------------------------------------------------
{
// Our string that we want to split
std::string stringToSplit{ "aaa,bbb,ccc,ddd" };
Container c{};
// Put string in an std::istringstream
std::istringstream iss{ stringToSplit };
// Extract string parts in simple for loop
for (std::string part{}; std::getline(iss, part, ','); c.push_back(part))
;
print(c);
}
// Example 4: Most flexible iterator solution ------------------------------------------------
{
// Our string that we want to split
std::string stringToSplit{ "aaa,bbb,ccc,ddd" };
Container c(std::sregex_token_iterator(stringToSplit.begin(), stringToSplit.end(), delimiter, -1), {});
//
// Everything done already with range constructor. No additional code needed.
//
print(c);
// Works also with other containers in the same way
std::forward_list<std::string> c2(std::sregex_token_iterator(stringToSplit.begin(), stringToSplit.end(), delimiter, -1), {});
print(c2);
// And works with algorithms
std::deque<std::string> c3{};
std::copy(std::sregex_token_iterator(stringToSplit.begin(), stringToSplit.end(), delimiter, -1), {}, std::back_inserter(c3));
print(c3);
}
return 0;
}
Upvotes: 1