指针与std::string - 奇怪的行为 - C++

4

提前致歉,因为我之前在另一篇帖子中问了同样的问题,但是有人正确指出,我没有发布真正的代码。因此,我再次问同样的问题,试图比以前更清晰。

作为练习,我正在创建一个操作字符串的程序。特别地,我想删除在2个'*'之间包含的字符串部分。需要强调的是,我已经成功地使用库字符串的函数创建了相同的程序;实际上,该问题涉及使用char指针对给定字符串进行操作。我将发布完整的代码并进行深入讨论。

#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;
}

在发布帖子之前,我做了一些努力去了解问题的本质。我看到了一个意外的行为:如果我将指针初始化为一个内存地址,例如:

pa1 = &frase[i];

该代码段不包含任何星号,然后在for循环n.1的‘if’条件下将其地址更改为第一个星号后,我尝试通过以下方式(忽略其余代码)进行可视化:

cout << *pa1;

程序不会崩溃并输出星号。然而,使用pa2并创建带有2个星号的短语仍会导致程序崩溃。将指针pa1初始化为“NULL”并执行相同的过程会导致程序崩溃。
然后我想出了两个假设:
1 - 可能无法使用char指针管理字符串对象,即使我只是处理给定字符串的字符。但是,如果将指针初始化为现有地址,则可以轻松显示短语的字符,感谢它们。
2 - 问题与我处理空字符(如“空格”等)有关。因此,问题位于第2或第3个循环中(参考代码)。
我知道我可以使用char []数组解决问题,并且最好使用字符串函数来解决问题,但我想解决这个问题,以便完全理解字符串对象和char指针之间的关系。因此,我只是寻求帮助找到此代码中的错误;我不想要新代码(因为这对您来说是浪费时间,而且在某种程度上意味着利用您,尽管我们正在谈论一个独立的练习)。提前感谢您的帮助。
编辑:我忘记指出,我还认为问题可能与大小相关,即“int值以字节为单位”,而我将其视为包含字符的字符串中的插槽数。我认为这个信息会很有用,但我不确定它是否可行。
编辑2:@lilscent解决了与空指针的延迟引用相关的问题。我已更改代码并将pa1指针和pa2指针初始化为
pa1 = &frase[0];
pa2 = nullptr;

编辑 3:如建议所示,我删除了布尔变量,并在第一个循环中使用了 break。我还更改了最后一个 for 循环,因为代码现在能运行但未能实现其预期功能。我还编辑了第二个循环,添加了 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 = ' ';
        }
    }

编辑4:现在程序已经完全可用。我修改了最后一个循环:

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

感谢您的帮助和建议!我将代码保留原样,以便帮助其他遇到同类型问题的人。

最终编辑:请参考NikosC.的帖子。他修改了程序的一部分并提高了效率,解决了大部分问题。再次感谢!


1
在新的程序中,建议使用 nullptr 而不是 NULL - Jesper Juhl
1
什么是 size?请确保您发布的代码可以编译。 - llllllllll
2
您正在取消引用空指针:if(*pa1 == '*') - llllllllll
1
@KingPowa 不是的。当你第一次遇到'*'时,它是一个空指针。 - llllllllll
2
@King - 你还应该考虑到std::string有一些函数find和erase,这些函数可以帮助你在不使用循环的情况下完成编辑。 - Bo Persson
显示剩余10条评论
1个回答

3
循环中的逻辑不起作用。循环条件为:
for (int i = 0; i < size - 1 || stop == true; i++)

这将一直运行,直到i < size - 1stop == true。然而,你希望循环在stop == true时停止,而不是继续运行。所以你需要:

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

注意,应该是i < size而不是i < size - 1。因为std::string::size()不包括终止的\0字符。
循环内部应该是:
if (frase[i] == '*') {
    if (*pa1 == '*') {
        pa2 = &frase[i];
        stop = true;
    }
    pa1 = &frase[i];
}

如果发现一个星号*,则需要检查pa1是否指向星号。然而,这将导致空指针引用,因为pa1被初始化为null。相反地,你应该简单地测试pa1是否仍然是一个null指针。如果是,那就意味着你还没有找到第一个*。所以请改成这样:
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;
}

这种新的逻辑允许您以不需要stop的方式重写循环条件。您只需检查pa2是否为null。如果仍然为null,则循环可以继续运行。

因此,总体而言:

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];
    }
}

(此外,最好使用nullptr而不是NULL。这将防止某些错误,否则在使用NULL时会隐藏这些错误。)
然而,您可以进一步简化上述内容,使用基于范围的for循环,这是迭代容器所有元素的推荐方法。您需要在迭代中使用引用(auto&而不仅仅是auto),因为我们需要取实际元素的地址而不是元素副本的地址:
for (auto& i : frase) {
    if (i == '*') {
        if (pa1 == nullptr) {
            pa1 = &i;
        } else {
            pa2 = &i;
            break; // stop the loop since we found the second *
        }
    }
}

接下来,您将看到尝试打印结果的代码:
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);
    }
}

这是行不通的,因为你试图解引用pa1,即使它可能为空。在我看来,你想要做的只是输出一个错误消息,指出没有星号,或者如果只有一个星号,就将该星号删除:

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;
}

最后一部分,要去掉字符串中的*text*部分,只需将位置为pa1pa2的字符复制并重新调整frase的大小:

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

1
肯定应该是 i < size - 1 && !stop; 否则将 stop 设置为 true 将无法停止循环。使用 break 比使用布尔变量控制循环更清晰明了。 - john
1
@john 抱歉,复制粘贴错误了。已修复。 - Nikos C.
1
@KingPowa 谢谢,看起来是正确的。string::size()不包括终止符\0,因此不需要减1。 - Nikos C.
1
@KingPowa 我还添加了一个基于范围的for循环版本,使它变得更简单,更难出错。 - Nikos C.
1
@KingPowa 看到C++仍然被反向教授,总是有点烦人...许多老师从低级指针语义和算术开始,而实际上他们应该先教授容器,甚至在“高级”课程中也不要提及指针。啊,好吧。 - Nikos C.
显示剩余10条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接