I have learned a bit of C in university and I'm now learning C++ independently. I'm trying to print out std::vector<std::string>
char
by char
. I know, that in C, strings are arrays and are terminated by '0円'
terminator. Can I assume the same thing in c++? I use C++14.
This works, but I'm unsure if it's the correct way to do this. I know, I can use getline(std::cin, line)
to read from stream as well. Which one is more preferred? How would you make my code better?
using namespace std;
int main()
{
cout << "Enter text\n";
// Get input
vector<string> str;
for (string temp; cin >> temp; ) {
str.push_back(temp);
// If input ends with enter, break loop
if (cin.peek() == '\n') break;
}
cout << "\n\n";
// Output vector string by string
vector<string>::iterator it;
for (it = str.begin(); it != str.end(); ++it) {
cout << *it << '\n';
}
cout << "\n\n";
// Output vector char by char
it = str.begin();
while (it != str.end()) {
/* Is this (*it)[i] != '0円' correct? Are strings '0円' terminated in
* c++ as well, and are they in vector<string> '0円 terminated? */
for (size_t i = 0; (*it)[i] != '0円' ; ++i) {
cout << (*it)[i];
}
cout << ' ';
++it;
}
cout << "\n\n";
}
3 Answers 3
So the first thing you can do is to use container loops
for (std::string &mystring : str) {
// do someting
}
The second thing would be to have a look at rbegin() and rend, which give you reverse_iterators.
You can find a very good reference here: http://www.cplusplus.com/reference/string/string/rend/
Together you would get something like that
for (std::string &mystring : str) {
for (std::string::reverse_iterator rit=mystring.rbegin(); rit!=mystring.rend(); ++rit) {
std::cout << *rit;
}
}
-
\$\begingroup\$ @miscco- Shouldn't there be
rit != mystrting.rend()
instead ofrit != str.rend()
? And why is that "&" necessary in the firstfor
loop? \$\endgroup\$Name– Name2016年08月28日 16:04:41 +00:00Commented Aug 28, 2016 at 16:04 -
\$\begingroup\$ @Lauri, it is to get a reference to the string element in the
str
. If you wouldn't put&
, it would copy on every cycle, which is really expensive for big loops. Further reading is about range loops. First point is right, it should bemystring.rend()
. \$\endgroup\$Incomputable– Incomputable2016年08月28日 17:09:09 +00:00Commented Aug 28, 2016 at 17:09 -
\$\begingroup\$ I'm wondering: why reverse iterators? OP isn't outputting anything in reverse... Am I missing something? \$\endgroup\$Rakete1111– Rakete11112016年08月28日 17:11:28 +00:00Commented Aug 28, 2016 at 17:11
-
\$\begingroup\$ @Rakete1111, please read comments to the question, I've asked a few questions and what you need is in one of the answers \$\endgroup\$Incomputable– Incomputable2016年08月28日 17:12:29 +00:00Commented Aug 28, 2016 at 17:12
-
\$\begingroup\$ @OlzhasZhumabek Ooh, didn't see his comments. Thanks \$\endgroup\$Rakete1111– Rakete11112016年08月28日 17:13:21 +00:00Commented Aug 28, 2016 at 17:13
Here are a few points:
- I hope you didn't forget to
#include
vector
,string
andiostream
:) - Don't use
using namespace std;
, it is bad practice Instead of using iterators, you can use range based for loops:
for (const auto& value : str) { std::cout << value << '\n'; }
Yes,
std::string
s are null-terminated. But here you can also use range based loops:for (const auto& value : str) { for (auto c : value) std::cout << c; std::cout << ' '; }
- Use better naming: It can be weird if
str
is astd::vector<std::string>
instead of the obviousstd::string
.
Make sure to #include
all libraries used by the code. It's a turn-off for readers/reviewers to have to actually modify your code to make it run.
Do not abuse using
directives and declarations. Importing everything from a namespace into the global namespace causes pollution which could result in name collisions and ambiguity for the compiler (an imported function is found to be more viable even if not correct).
Naming variables appropriately helps with readability. What is the element being read? A word
. What should we call a collection of word
? Pluralization makes sense, so words
.
std::vector<std::string> words;
for (std::string word; std::cin >> word; ) {
words.push_back(word);
}
When we point at the beginning of a range, what are we pointing at? The first
element.
auto first = std::begin(words); // type of first deduced from result of function
Functions that are short, simple, and focused on a single logical operation are easier to understand, test, and reuse. Your program already indicates these logical boundaries (// Get Input
, // Output Vector
), you just need to refactor your code into those abstractions. The C++ standard library comes with some nice abstractions (see <algorithm>
) that you can use as building blocks for more powerful functions.
template <typename InputType>
std::vector<InputType> stream_until_eol(std::istream& in) {
std::vector<InputType> inputs;
// your code that fills input from a stream until eol
return inputs;
}
int main() {
auto words = stream_until_eol<std::string>(std::cin);
std::copy(words.cbegin(), words.cend(),
std::experimental::make_ostream_joiner{std::cout, "\n"});
for (const auto& word : words) {
std::reverse_copy(word.cbegin(), word.cend(),
std::experimental::make_ostream_joiner{std::cout, " "});
}
}
I know, I can use
getline(std::cin, line)
to read from stream as well. Which one is more preferred? How would you make my code better?
If you want to make your code better, make the code readable, understandable, and reviewable. If you can accomplish those, then the code will naturally be maintainable.
As for this problem, consider a third option where you write a specialized std::istream
iterator adaptor called eol_istream_iterator
. It's job is simple, read objects of type T
until the next char is '\n'.
auto words = std::vector<std::string>{eol_istream_iterator<std::string>{std::cin}, {}};
Clear, concise, and reusable with other functions/objects.
/* Is this (*it)[i] != '0円' correct? Are strings '0円' terminated in
* c++ as well, and are they in vector<string> '0円 terminated? */
for (size_t i = 0; (*it)[i] != '0円' ; ++i) {
cout << (*it)[i];
}
std::string
are not null-terminated strings like C-Strings. What you wrote still works because the standard allows it to work. From the C++14 standard (n4140 was the last publicly free draft before standardization)
21.4.5
basic_string
element access [string.access]
const_reference operator[](size_type pos) const;
reference operator[](size_type pos);
\$^1\$ Requires:
pos <= size()
.\$^2\$ Returns:
*(begin() + pos)
ifpos < size()
. Otherwise, returns a reference to an object of typecharT
with valuecharT()
, where modifying the object leads to undefined behavior.\$^3\$ Throws: Nothing.
\$^4\$ Complexity: Constant time.
Using std::string::operator(size_type)
beyond the size of our string returns a value initialized charT()
. The rules of value initialization from the standard:
8.5 Initializers [dcl.init]
\$^8\$ To value-initialize an object of type
T
means:\$^{(8.1)}\$ — if
T
is a (possibly cv-qualified) class type (Clause 9) with either no default constructor (12.1) or a default constructor that is user-provided or deleted, then the object is default-initialized;\$^{(8.2)}\$ — if
T
is a (possibly cv-qualified) class type without a user-provided or deleted default constructor, then the object is zero-initialized and the semantic constraints for default-initialization are checked, and ifT
has a non-trivial default constructor, the object is default-initialized;\$^{(8.3)}\$ — if
T
is an array type, then each element is value-initialized;\$^{(8.4)}\$ — otherwise, the object is zero-initialized.
charT()
value-initialization becomes zero-initialization, which means
8.5 Initializers [dcl.init]
\$^6\$ To zero-initialize an object or reference of type
T
means:\$^{(6.1)}\$ — if
T
is a scalar type (3.9), the object is initialized to the value obtained by converting the integer literal0
(zero) toT
;
So charT()
becomes charT(0)
, which is equivalent to 0円
(the null character).
Since std::string
is not null-terminated, the null-character '0円'
is a valid character to be contained within a std::string
.
std::string str{"ab"};
str.push_back{'0円'};
str += "cd";
assert(5 == str.size()); // true!
Note: std::string
member functions treat C-String arguments like C-Strings.
assert(5 == std::string{"ab0円cd"}.size()); // 5 == 2 ? False!
-
\$\begingroup\$ Is this an answer one gives to a c++ newbie who knows very very little? You might scare the poor guy away :P On a more serious note, what is "stream_until_eol" and "eol_stream_iterator"? Where do you get them from? Google gives me nothing, can't find any information about them. \$\endgroup\$Kodnot– Kodnot2016年08月30日 14:27:29 +00:00Commented Aug 30, 2016 at 14:27
-
\$\begingroup\$ "Where do you get them" - Read the review and not just the code.
stream_until_eol
is just an example of a higher level abstraction that could encapsulate some of the lower-level handwritten code.eol_stream_iterator
is an iterator adaptor concept that is described. \$\endgroup\$Snowhawk– Snowhawk2016年08月30日 17:56:23 +00:00Commented Aug 30, 2016 at 17:56 -
\$\begingroup\$ I asked my question before you updated your review and added the definition of stream_until_eol. Before there was only a single line indicating that the abstraction is user-written, so I must've missed it, sorry \$\endgroup\$Kodnot– Kodnot2016年08月31日 08:04:21 +00:00Commented Aug 31, 2016 at 8:04
word
, then change it todrow
and store it in another string or vector. \$\endgroup\$std::string
returns achar
pointer viac_str()
. However, it also stores the length of the string so you can know exactly how many characters there are without checking for 0. \$\endgroup\$