【问题标题】:Pointers and std::string - Strange behavior - C++指针和 std::string - 奇怪的行为 - C++
【发布时间】:2018-02-04 14:59:24
【问题描述】:

我提前道歉,因为我在之前的帖子中问过同样的问题,但正如有人正确指出的那样,我没有发布真正的代码。因此,我再次问同样的问题,试图比以前更清楚。

作为练习,我正在创建一个操作字符串的程序。特别是,我想删除包含在 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' 并执行相同的过程会导致程序崩溃。

然后我想出了 2 个假设

1 - 可能我无法使用 char 指针管理字符串对象,即使我只是管理给定字符串的 字符 .但是,如果我将指针初始化为字符串的现有地址,我可以很容易地显示短语的字符。

2 - 问题与我也在处理空字符有关,例如“空格”等。因此,问题出在for循环n.2或n.3(参考代码)。

我知道我可以用 char[] 数组来处理问题,并且我知道用字符串函数来处理这个问题会更好,但我想要解决这个问题是为了充分理解字符串对象和字符指针之间的相关性。所以我只是寻求帮助以在此代码中找到错误;我不想要一个新代码(因为这对你来说是浪费时间,而且这意味着以某种方式利用你,即使我们正在谈论一个独立的练习)。我希望我清楚地解释了这个问题。提前谢谢你。

编辑:我忘了指出,我也认为问题可能与大小有关,以字节为单位的 int 值,而我将其视为字符串中的槽数包含一个字符。我认为这些信息会很有用,但我不确定它的出席率。

编辑 2:@lilscent 解决了与引用空指针相关的问题。我已经更改了代码并将pa1指针和pa2指针初始化为

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

编辑 3:按照建议,我删除了布尔变量,并在第一个循环中使用了 break。我还更改了最后一个 for 循环,因为代码现在可以工作,但没有做它应该做的事情。我还编辑了第二个循环添加和其他:

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. 的帖子。他修改了部分程序,提高了效率,解决了大部分问题。再次感谢!

【问题讨论】:

  • 在新程序中优先使用nullptr 而不是NULL
  • 什么是size?请确保您发布的代码可以编译。
  • 你正在取消引用一个空指针:if(*pa1 == '*')
  • 不要调试消息,而是使用真正的调试器。一步步跟踪执行,观察局部变量。
  • @King - 您还应该考虑到std::string 具有find and erase 的功能,可以帮助您在没有循环的情况下进行编辑。

标签: c++ string pointers


【解决方案1】:

循环中的逻辑不起作用。循环条件说:

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

所以这将运行只要i &lt; size - 1stop == true。但是,您想要的是循环在stop == true 时停止,而不是继续运行。所以你需要:

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

请注意,它是i &lt; size,而不是i &lt; size - 1std::string::size() 不包括终止符 \0

在循环中,你有:

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

如果找到*,您正在检查pa1 是否指向星号。但是,这将导致空指针取消引用,因为pa1 被初始化为空。相反,您应该做的只是测试 pa1 是否仍然是空指针。如果是,那么这意味着您还没有找到第一个*。所以改为这样做:

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 是否不为空。如果它仍然为空,则循环可以继续运行。

总的来说:

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&amp; 而不仅仅是 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* 部分,您只需将位置pa2 的字符复制到位置pa1 并调整frase 的大小:

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

【讨论】:

  • @KingPowa 如果只有一个星号,我更改了代码以删除星号。在这种情况下,您只需设置pa2 = pa1,这样while 循环就会覆盖星号。
  • @KingPowa 顺便说一句,这种 C++ 代码不是一个好主意。您实际上不应该使用这种指针访问来操作数据。我认为这只是为了学习目的。在真正的代码中,你不应该这样做:-)
  • 当然应该是 i &lt; size - 1 &amp;&amp; !stop; 否则将 stop 设置为 true 不会停止循环。 break 比使用布尔变量控制循环要清晰得多。
  • @john 哎呀。复制和意大利面。固定。
  • @KingPowa 谢谢。这看起来是正确的。 string::size() 不包括终止符 \0,因此不需要 -1。
猜你喜欢
  • 1970-01-01
  • 2012-09-21
  • 2016-03-02
  • 1970-01-01
  • 2020-11-28
  • 2012-05-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多