简体   繁体   中英

Pointers and std::string - Strange behavior - C++

I apologise in advance since I've asked the same question in a previous post but, as someone correctly pointed out, I didn't post the real code. Thus I'm asking the same question again, trying to be clearer than previously.

I'm creating, as an exercise, a program that manipulate a string. In particular, I want to delete part of the string enclosed between 2 '*'. I've to underline that I've successfully created the same program with the function of the library string; in fact the problem concerns the manipulation of the given string with char pointers. I will post the full code and discuss in depth.

#include <iostream>
#include <string>
using namespace std;

int main() {

    string frase;
    getline (cin, frase); // Takes as input the phrase
    int size = frase.size();

    cout << frase[0]; // <- this line is not even processed (I've used it to test the problem) However, if I put it before the first if, it will be sent in output.

    char* pa1 = NULL; // The pointer which will "point" to the first *
    char* pa2 = NULL; // The pointer which will "point" to the second *
    bool stop = false; // When the pointers find 2 asterisk, stop = true
    for(int i = 0; i < size - 1 || stop == true; i++){ // FOR LOOP n.1
        if(frase[i] == '*'){
            if(*pa1 == '*'){
                pa2 = &frase[i];
                stop = true;
            }
            pa1 = &frase[i];
        }
    }

 // I've debugged the program and find that the problem is before this line, probably
 // linked to the address of pointers. I will explain later what I mean.
 // I've came up with this conclusion after trying to ignore part of the program and processing it in another file.
 // However, I'm not fully sure with this result, since the problem regards the visualization of the content of the pointers.

    if(pa2 == NULL){ // if it's a null pointer, this means  that second asterisk has not been found.
        if(pa1 == NULL){// if also this is a null pointer, there is no asterisk at all
            cout << "Non ci sono asterischi. Non verrà eliminata nessuna parola.\n\n";
        }
        cout << "C'è un solo asterisco. Verrà eliminato unicamente l'asterisco.\n\n";
        for(; pa1 < &frase[size - 1]; pa1++){ // FOR LOOP n.2
            *pa1 = *(pa1 + 1);
        }
    }

    else{
        for(; pa1 < pa2 + 1; pa1++){ // this removes asterisk and 
        //the phrase between them, by overwriting the existing characters. FOR LOOP n.3
            *pa1 = *(pa1 + 1);

        }
    }

cout << "La frase dopo l'eliminazione è:\n" << frase;
return 0;
}

Before posting, I made some effort to understand the nature of the problem. I saw an unexpected behavior: if I initialize the pointers to a memory address, such as:

pa1 = &frase[i];

which does not contain any asterisk, and then, (thanks to the 'if' condition in the for loop n.1) after changing its address to the first asterisk, I try to visualize it (ignoring the rest of the code) by writing:

cout << *pa1;

the program does not crash and output the asterisk. However, doing the same with pa2 and creating a phrase with 2 asterisk causes the program to crash anyway. Initializing the pointer pa1 to 'NULL' and doing the same procedure causes the program to crash.

I've then came up with 2 hypotesis :

1 - Probably I can not manage a string object with char pointers , even though I'm just managing characters of the given string. However, I can easily show a character of the phrase thanks to them, if I initialize the pointer to an existing address of the string.

2 - The problem is linked to the fact I'm dealing also with empty characters , such as "space" and alike. Thus, the problem is located in the for loop n.2 or n.3 (refers to the code).

I know I could deal with the problem with char[] array , and I know it would be better to deal with the problem with string functions , but I want to solve this problem in order to fully understand the correlations between string objects and char pointers. So I'm just asking help to find the error within this code; I do not want a new code (since it would be a waste of time for you, and moreover it would mean in some way to exploit you, even though we are talking about an indipendent exercise). I hope I explained the problem clearly. Thank you in advance.

EDIT: I've forgot to point out, I also think the problem is probably linked to size being a int value in bytes , whereas I treat it as the number of slot in the string which contains a character. I think this information would be useful, but I'm not sure about it's attendability.

EDIT 2: @lilscent solved the problem linked to the deference of a null pointer. I've changed the code and initialized the pa1 pointer and pa2 pointer to

pa1 = &frase[0];
pa2 = nullptr;

EDIT 3: As suggested, I deleted the boolean variable and I used break instead in the first loop. I'm also changing the last for loop, since the code now works but fails to do what it should do. I've also edited the second loop adding and else:

if(pa2 == nullptr){

        if(pa1 == &frase[0]){
            cout << "Non ci sono asterischi. Non verrà eliminata nessuna parola.\n\n";
        }
        else{
            cout << "C'è un solo asterisco. Verrà eliminato unicamente l'asterisco.\n\n";
            for(; pa1 < &frase[size - 1]; pa1++){
                *pa1 = *(pa1 + 1);
            }
            *pa1 = ' ';
        }
    }

EDIT 4: Now the program fully works. I've edited the last loop:

else{
        *pa2 = ' ';
        pa2+= 2;
        for(; pa1 < pa2 + 1 && pa2 < &frase[size]; pa1++, pa2++){
            *pa1 = *pa2;
            *pa2 = ' ';
        }
        *pa2 = ' ';
    }

Thanks for the help and all the suggestion! I've left the code as the it was to help others with the same type of problem.

FINAL EDIT: Refer to NikosC.'s post. He modified part of the program and improved its efficiency, solving most of the problems. Thanks again!

The logic in the loop doesn't work. The loop condition says:

for (int i = 0; i < size - 1 || stop == true; i++)

So this will run for as long as either i < size - 1 or stop == true . However, what you want is for the loop to stop when stop == true , not keep running. So you need:

for (int i = 0; i < size && !stop; i++)

Note that it's i < size , not i < size - 1 . std::string::size() does not include the terminating \\0 character.

Inside the loop, you have:

if (frase[i] == '*') {
    if (*pa1 == '*') {
        pa2 = &frase[i];
        stop = true;
    }
    pa1 = &frase[i];
}

If a * is found, you're checking if pa1 points to an asterisk. However, this will result in a null pointer dereference, since pa1 is initialized to null. What you should do instead, is simply test if pa1 is still a null pointer. If it is, then that means you didn't find the first * yet. So do this instead:

if (pa1 == nullptr) {
    // Since pa1 is still null, this is the first '*' we encountered.
    pa1 = &frase[i];
} else  {
    // pa1 was not null, so this means we just found the second '*'.
    pa2 = &frase[i];
    stop = true;
}

This new logic allows you to rewrite the loop condition in a way that does not need stop anymore. You can simply check if pa2 is not null. If it's still null, then the loop can keep running.

So overall:

char* pa1 = nullptr; // The pointer which will "point" to the first *
char* pa2 = nullptr; // The pointer which will "point" to the second *
for (int i = 0; i < size && pa2 == nullptr; i++) {
    if (frase[i] == '*') {
        if (pa1 == nullptr)
            pa1 = &frase[i];
        else
            pa2 = &frase[i];
    }
}

(Also, prefer using nullptr instead of NULL . It will prevent certain kinds of errors that otherwise are hidden when using NULL .)

However, you can further simplify the above using a range-based for loop, which is the recommended way to iterate over all elements of a container. You need to use a reference for the iteration ( auto& instead of just auto ), since we need to take the address of the actual elements and not the address of copies of the elements:

for (auto& i : frase) {
    if (i == '*') {
        if (pa1 == nullptr) {
            pa1 = &i;
        } else {
            pa2 = &i;
            break; // stop the loop since we found the second *
        }
    }
}

Next up, you have the code that tries and print the result:

if (pa2 == NULL) {
    if (pa1 == NULL) {
        cout << "Non ci sono asterischi. Non verrà eliminata nessuna parola.\n\n";
    }
    cout << "C'è un solo asterisco. Verrà eliminato unicamente l'asterisco.\n\n";
    for ( ; pa1 < &frase[size - 1]; pa1++) {
        *pa1 = *(pa1 + 1);
    }
}

This cannot work, since you are trying to derefence pa1 even though it might be null. It seems to me here that what you want to do, is simply give out an error message that states that there's no asterisks, or if there's only one, just remove that asterisk:

if (pa1 == nullptr) {
    cout << "C'è un solo asterisco. Verrà eliminato unicamente l'asterisco.\n";
    return 0;
}

if (pa2 == nullptr) {
    cout << "Non ci sono asterischi. Non verrà eliminata nessuna parola.\n";
    pa2 = pa1;
}

And for the last part, to get rid of the *text* part of the string, you just need to copy the characters at position pa2 to position pa1 and resize frase :

while (pa2 < &frase[size]) {
    ++pa2;
    *pa1 = *pa2;
    pa1++;
}
frase.resize(size - 1 - (pa2 - pa1));
cout << "La frase dopo l'eliminazione è: " << frase << '\n';

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM