C Hi, I'm new to coding, I wanted to know why "str.replace" didn't work in the "for(char& i : str)" loop but it did in the "for (int i = 0; i <= str.length(); i )" loop.
#include <iostream>
#include <string>
using namespace std;
int main()
{
string str = "aNo more 'a'";
int sum;
for (char& i : str){
if (i == 'a'){
sum = 1;
}
}
cout << str << " " << sum;
return 0;
}
Output: "aNo more 'a' 2" This code seems to work fine.
#include <iostream>
#include <string>
using namespace std;
int main()
{
string str = "aNo more 'a'";
int sum;
for (char& i : str){
if (i == 'a'){
str.replace(i,1,"");
}
}
cout << str << endl;
return 0;
}
Error: Terminate called after throwing an instance of 'std::out_of_range' what(): basic_string::replace: __pos (which is 97) > this->size() (which is 12)
#include <iostream>
#include <string>
using namespace std;
int main()
{
string str = "aNo more 'a'";
int sum;
for (int i = 0; i <= str.length(); i ){
if (str[i] == 'a'){
str.replace(i,1,"");
}
}
cout << str << endl;
return 0;
}
Output: "No more '' "
This code works fine because it's not running into the range error listed above. I don't know why that is. Can someone help me understand why the "for(char& i : str)" doesn't work?
CodePudding user response:
A range based for-loop like this:
for (char& i : str) {
if (i == 'a') {
str.replace(i, 1, "");
}
}
Is roughly the same as:
for(auto it = std::begin(str), end = std::end(str); it != end; it) {
if (*it == 'a') {
str.replace(*it, 1, "");
}
}
- It'll use the character value (
i
in your code or*it
in my loop) as the position in the string where to start replacing. The character'a'
has the ASCII value 97 which explains the exception you get since 97 is clearly out of bounds. - If you change the length of
str
inside the loop,end
will be invalidated andit
will run out of bounds.
You may find using std::erase(std::string)
easier. This will remove all 'a'
s from str
:
std::erase(str, 'a');
CodePudding user response:
When using a range-for in C , the current element (i
in this case) is not actually an iterator but rather is the element itself, thus i
would have the value of the current element. Here you've declared i
as char&
meaning it is a reference to a character in the collection.
When you passed i
to std::string::replace()
it expected the first argument to be a value indicating the position you want to replace at. Because char
is a smaller integral type it gets promoted to the std::string::size_type
::replace()
expects for its first argument, for example 'a' would become 97. Because this is technically valid to request it tries to replace at that position but because 97 is out of the strings range it throws an exception, as it should.
It is considered bad practice to try and modify a container directly in a for loop, particularly a range-for loop as the end of the container becomes invalidated if reallocation occurs, which erasure and replacement often do.
Instead, to achieve the desired outcome, one can utilize a combination of the algorithms std::remove
and std::string::erase
or if you can use C 20 std::erase_if
std::string str = "aNo more 'a'";
auto end_no_as = std::remove(std::begin(str), std::end(str), 'a');
str.erase(end_no_as, std::end(str));
/// or
std::erase_if(str, [](char& c){ return c == 'a'; });