6

I need to read a string char by char in order to perform some controls on it. Is it possible to do that? Have I necessarily got to convert it to a char array? I tried to point at single chars with string_to_control[i] and then increase i to move, but this doesn't seem to work. As an example, I post a piece of the code for the control of parenthesis.

bool Class::func(const string& cont){
    const string *p = &cont;
    int k = 0;
    //control for parenthesis
    while (p[k].compare('\0') != 0) {
        if (p[k].compare("(") == 0) { ap++; };
        if (p[k].compare(")") == 0) { ch++; };
        k++;
    };
    //...
};

The string is copied alright, but as soon as I try the first comparison an exception is thrown.

EDIT: I add that I would like to have different copies of the initial string cont (and move on them, rather than on cont directly) in order to manipulate them (later on in the code, I need to verify that certain words are in the right place).

A O
  • 65
  • 6
  • 1
    Add your code to question. – Evgeny Jan 18 '20 at 08:51
  • Minor improvement suggestions: _1_: if you are sticking with doing `if`s: Add `else` before the second `if`. _2_: There's no reason to make this a class member function. Make it a free function instead. – Ted Lyngmo Jan 18 '20 at 10:44
  • Can you convert `std::string` to C string using `c_str()`? read this: https://stackoverflow.com/questions/7416445/what-is-use-of-c-str-function-in-c – Soumya Kanti Feb 14 '20 at 12:38

7 Answers7

6

The simplest way to iterate through a string character by character is a range-for:

bool Class::func(const string& cont){
    for (char c : cont) {
        if (c == '(') { ap++; }
        if (c == ')') { ch++; }
    }
    //...
};

The range-for syntax was added in C++11. If, for some reason, you're using an old compiler that doesn't have C++11 support, you can iterate by index perfectly well without any casts or copies:

bool Class::func(const string& cont){
    for (size_t i = 0; i < cont.size(); ++i) {
        if (cont[i] == '(') { ap++; }
        if (cont[i] == ')') { ch++; }
    }
    //...
};
Miles Budnek
  • 28,216
  • 2
  • 35
  • 52
  • 1
    Safer perhaps to use `std::string::at()` in preference to `std::string::operator[]`, and arguably a `std::string::begin()`/`end()` iterator for the pre-C++11 version. – Clifford Jan 18 '20 at 09:20
  • @Clifford I don't see any safety gain using `at()` instead of `operator[]` here. It'd only make it potentially slower. If `size()` may change between the `for` predicate and the `if` it would mean that another thread made the change and using `at()` would not help. – Ted Lyngmo Jan 18 '20 at 10:57
4

If you just want to count the opening and closing parentheses take a look at this:

bool Class::func(const string& cont) {
    for (const auto c : cont) {
        switch (c) {
            case '(': ++ap; break;
            case ')': ++ch; break;
        }
    }
    // ...
}
Doeus
  • 430
  • 1
  • 3
  • 7
4
const string *p = &cont;
int k = 0;
while (p[k].compare('\0') != 0)

Treats p as if it were an array, as p only points to a single value your code has undefined behaviour when k is non-zero. I assume what you actually wanted to write was:

bool Class::func(const string& cont){
    while (cont[k] != '\0') {
        if (cont[k] == '(') { ap++; };
        if (cont[k] == ') { ch++; };
        k++;
    };
};

A simpler way would be to iterate over std::string using begin() and end() or even more simply just use a range for loop:

bool Class::func(const string& cont){
    for (char ch : cont) {
        if (ch == '(') { ap++; };
        if (ch == ')') { ch++; };
    };
};

If you want to copy your string simply declare a new string:

std::string copy = cont;
Alan Birtles
  • 32,622
  • 4
  • 31
  • 60
  • 1
    I think if the argument of `operator[]` equals the length of the string the null-character is returned and it's not treated as out-of-bounds access. So `while (cont[k] != '\0')` should not cause undefined behaviour. – Lukas-T Jan 18 '20 at 09:21
  • You're right, it changed in c++11 now that strings are guaranteed to always be null terminated: https://en.cppreference.com/w/cpp/string/basic_string/operator_at – Alan Birtles Jan 18 '20 at 09:22
  • @churill : It is perhaps not wise to assume the code will never be compiled on an older compiler. I suspect the C++11 change was to support old code that already made this error, and to formalise implementations that added a nul to avoid breaking such code gratuitously, and perhaps to make `string::c_str()` simpler to implement. It remains an inelegant way of detecting the end of a `std::string`. – Clifford Jan 18 '20 at 09:53
  • 1
    @Clifford actually for the constant `[]` overload this was always supported – Alan Birtles Jan 18 '20 at 09:55
2

If you don't want to use iterators std::string also overloads operator[], so you can access the chars like you would do with a char[].

cont[i] will return the character at index i for example, then you can use == to compare it to another char:

bool Class::func(const string& cont){
    int k = 0;

    while (k < cont.length()) {
        if (cont[k] == '(') { ap++; };
        if (cont[k] == ')') { ch++; };
        k++;
    };
};
Lukas-T
  • 11,133
  • 3
  • 20
  • 30
2

The std::string::operator[] overload allows expressions such as cont[k]. Your code treats p as an array of std::string rather then an array of characters as you intended. That could be corrected by:

const string &p = cont;

but is unnecessary since you can already access cont directly.

cont[k] has type char so calling std::string::compare() is not valid. You can compare chars in the normal manner:

cont[k] == '('` 

You should also be aware that before C++11 the end of a std::string is not delimited by a \0 like a C string (there may happen to be a NUL after the string data, but that is trusting to luck). C++11 does guarantee that, but probably only to "fix" older code that made the assumption that it was.

If you use std::string::at rather then std::string::operator[] an exception will be thrown if you exceed the bounds. But you should use either range-based for, a std::string::iterator or std::string::length() to iterate a string to the end.

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
Clifford
  • 88,407
  • 13
  • 85
  • 165
2

To count parentheses, you can use std::count algorithm from the standard library:

/* const */ auto ap = std::count(cont.begin(), cont.end(), '(');
/* const */ auto ch = std::count(cont.begin(), cont.end(), ')');

The string will be traversed twice.

For single traversal you can implement a generic function (requires C++17):

template<class C, typename... Ts>
auto count(const C& c, const Ts&... values) {
    std::array<typename C::difference_type, sizeof...(Ts)> counts{};
    for (auto& value : c) {
        auto it = counts.begin();
        ((*it++ += (value == values)), ...);
    }
    return counts;
}

and then write

/* const */ auto [ap, ch] = count(cont, '(', ')');
Ted Lyngmo
  • 93,841
  • 5
  • 60
  • 108
Evg
  • 25,259
  • 5
  • 41
  • 83
  • 1
    I made a single traversal version, based on yours, using iterators (https://godbolt.org/z/dpb55x) that may come in handy too. – Ted Lyngmo Jan 18 '20 at 14:29
-5

First convert the string to a char array like this:

bool Class::func(const string& cont){

    char p[cont.size() + 1];
    strcpy(p, cont.c_str());

    int k = 0;
    //control for parenthesis
    while (p[k].compare('\0') != 0) {
        if (p[k].compare("(") == 0) { ap++; };
        if (p[k].compare(")") == 0) { ch++; };
        k++;
    };
    //...
};

You could do what you want with an algorithm, which means you can avoid the array conversion:

#include <iostream>
#include <string>
#include <cstring>
#include <algorithm>    // std::count

int main()
{
    std::string s = "hi(there),(now))";

    int ap = std::count (s.c_str(), s.c_str()+s.size(), '(');
    int ch = std::count (s.c_str(), s.c_str()+s.size(), ')');

    std::cout << ap <<  "," <<  ch << '\n'; // prints 2,3

    return 0;
}
Phillip Ngan
  • 15,482
  • 8
  • 63
  • 79
  • 3
    did you try compiling your code? Why do you convert a perfectly good string to a char array just to access the characters? – Alan Birtles Jan 18 '20 at 09:07
  • 1
    Also `char p[...]` is a VLA, which is not standard C++. – Lukas-T Jan 18 '20 at 09:08
  • 1
    The line `char p[cont.size() + 1];` is not correct: the compiler says that 'function call must have a constant value in constant expression'. – A O Jan 18 '20 at 09:13
  • 1
    @AnnaOriglia Because it's not part of the standard, some compilers accept Variable Length Arrays as a non-standard extension. – Lukas-T Jan 18 '20 at 09:16
  • 2
    `[]` is already overloaded for `std::string` the copying of the string to a `char[]` is pointless. – Clifford Jan 18 '20 at 09:16