4
\$\begingroup\$

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";
}
\$\endgroup\$
  • \$\begingroup\$ Why would you want to output char by char? I believe you know that it is probably much slower than string by string? \$\endgroup\$ – Incomputable Aug 28 '16 at 11:37
  • \$\begingroup\$ Because I need to reverse the words, ex if I have a word, then change it to drow and store it in another string or vector. \$\endgroup\$ – Name Aug 28 '16 at 11:40
  • \$\begingroup\$ Why would you print it out then? You could use standard library to do the reverse as well. \$\endgroup\$ – Incomputable Aug 28 '16 at 11:42
  • 1
    \$\begingroup\$ std::string returns a char pointer via c_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\$ – D. Jurcau Aug 28 '16 at 13:15
  • 4
    \$\begingroup\$ For someone self teaching from C to C++ pretty good. \$\endgroup\$ – pacmaninbw Aug 28 '16 at 15:08
6
\$\begingroup\$

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;
    }
}
\$\endgroup\$
  • \$\begingroup\$ @miscco- Shouldn't there be rit != mystrting.rend() instead of rit != str.rend()? And why is that "&" necessary in the first for loop? \$\endgroup\$ – Name Aug 28 '16 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 be mystring.rend(). \$\endgroup\$ – Incomputable Aug 28 '16 at 17:09
  • \$\begingroup\$ I'm wondering: why reverse iterators? OP isn't outputting anything in reverse... Am I missing something? \$\endgroup\$ – Rakete1111 Aug 28 '16 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 Aug 28 '16 at 17:12
  • \$\begingroup\$ @OlzhasZhumabek Ooh, didn't see his comments. Thanks \$\endgroup\$ – Rakete1111 Aug 28 '16 at 17:13
6
\$\begingroup\$

Here are a few points:

  1. I hope you didn't forget to #include vector, string and iostream :)
  2. Don't use using namespace std;, it is bad practice
  3. Instead of using iterators, you can use range based for loops:

    for (const auto& value : str) {
        std::cout << value << '\n';
    }
    
  4. Yes, std::strings 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 << ' ';
    }
    
  5. Use better naming: It can be weird if str is a std::vector<std::string> instead of the obvious std::string.
\$\endgroup\$
4
\$\begingroup\$

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) if pos < size(). Otherwise, returns a reference to an object of type charT with value charT(), 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 if T 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 literal 0 (zero) to T;

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{"ab\0cd"}.size()); // 5 == 2 ? False!
\$\endgroup\$
  • \$\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 Aug 30 '16 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 Aug 30 '16 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 Aug 31 '16 at 8:04

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.