繁体   English   中英

来自字符串的 C++ 子字符串

[英]C++ substring from string

我对 C++ 很陌生,我需要创建 MyString 类,以及从另一个子字符串创建新 MyString 对象的方法,但是在创建类时以及使用我的方法打印它时,选择的子字符串会发生变化。

这是我的代码:

#include <iostream>
#include <cstring>

using namespace std;

class MyString {
public:
    char* str;

    MyString(char* str2create){
        str = str2create;
    }

    MyString Substr(int index2start, int length) {
        char substr[length];
        int i = 0;
        while(i < length) {
            substr[i] = str[index2start + i];
            i++;
        }
        cout<<substr<<endl; // prints normal string
        return MyString(substr);
    }

    void Print() {
        cout<<str<<endl;
    }
};

int main() {
    char str[] = {"hi, I'm a string"};
    MyString myStr = MyString(str);
    myStr.Print();

    MyString myStr1 = myStr.Substr(10, 7);
    cout<<myStr1.str<<endl;
    cout<<"here is the substring I've done:"<<endl;
    myStr1.Print();

    return 0;
}

这是输出:

嗨,我是一个字符串

细绳

斯特里

这是我完成的子字符串:

您的函数Substr通过在返回值MyString对象中存储指向它的指针来间接返回局部变量substr的地址。 一旦超出范围,取消引用指向局部变量的指针是无效的。

我建议您决定您的类是包装外部字符串,还是拥有自己的字符串数据,在这种情况下,您需要将输入字符串复制到成员缓冲区。

必须通过这个来解释正确的地方出了什么问题,所以请耐心等待。

int main() {
    char str[] = {"hi, I'm a string"};

分配一个 17 个字符的临时数组(16 个字母加上一个终止空值),在其中放置字符“嗨,我是一个字符串”,并以空值结束。 临时意味着它听起来像什么。 当函数结束时, str消失了。 任何指向str东西现在都指向垃圾。 在被重用和覆盖之前,它可能会蹒跚而行,并提供一些表面上的生命,但实际上它是一个僵尸,只能信任杀死您的程序并吃掉它的大脑。

    MyString myStr = MyString(str);

创建另一个临时变量 myStr。 使用字符数组调用构造函数。 那么让我们来看看构造函数:

MyString(char* str2create){
    str = str2create;
}

取一个指向字符的指针,在这种情况下,它将有一个指向 main 的str的第一个元素的指针。 该指针将分配给 MyString 的str 没有复制“嗨,我是一个字符串”。 mains 的str和 MyString 的str指向内存中的同一位置。 这是一种危险的情况,因为对一个的改变会影响另一个。 如果一个str消失,另一个str消失。 如果一个str被覆盖,另一个str也会被覆盖。

构造函数应该做的是:

MyString(char* str2create){
    size_t len = strlen(str2create); // 
    str = new char[len+1]; // create appropriately sized buffer to hold string
                           // +1 to hold the null
    strcpy(str, str2create); // copy source string to MyString
}

一些警告:这真的很容易破解。 例如,传入一个永远不会结束的 str2create,并且 strlen 将旋转到未分配的内存中,结果将是不可预测的。

现在我们假设没有人特别恶意并且只会输入好的值,但这在现实世界中已被证明是非常糟糕的假设。

这也强制要求析构函数释放str使用的内存

virtual ~MyString(){
    delete[] str;
}

它还增加了对复制和移动构造函数以及复制和移动赋值运算符的要求,以避免违反三/五规则

回到 OP 的代码...

strmyStr指向内存中的同一个位置,但这还不错。 因为这个程序是微不足道的,所以它永远不会成为问题。 myStrstr都在同一点到期,并且都不会再次修改。

myStr.Print();

将正确打印,因为strmyStr没有任何变化。

    MyString myStr1 = myStr.Substr(10, 7);

需要我们查看 MyString::Substr 来看看会发生什么。

MyString Substr(int index2start, int length) {
    char substr[length];

创建一个长度为长度的临时字符数组。 首先,这是非标准的 C++。 它不会在很多编译器下编译,首先不要这样做。 第二,是暂时的。 当函数结束时,这个值是垃圾。 不要使用任何指向substr指针,因为它的存在时间不足以使用它们。 第三,没有为终止空值保留空间。 这个字符串将是一个等待发生的缓冲区溢出。

    int i = 0;
    while(i < length) {
        substr[i] = str[index2start + i];
        i++;
    }

好的,这很好。 从源复制到目标。 它缺少的是空终止,因此 char 数组的用户知道它何时结束。

    cout<<substr<<endl; // prints normal string

那个缓冲区溢出等待发生? 刚发生。 哎呀。 你很不走运,因为它看起来像它工作而不是崩溃并让你知道它没有。 在内存中的正确位置必须是空值。

    return MyString(substr);

这创建了一个新的 MyString 指向substr 就在substr到达函数末尾并死亡之前。 这个新的 MyString 几乎立即指向垃圾。

}

Substr 应该做什么:

MyString Substr(int index2start, int length)
{
    std::unique_ptr<char[]> substr(new char[length + 1]);
    // unique_ptr is probably paranoid overkill, but if something does go 
    // wrong, the array's destruction is virtually guaranteed
    int i = 0;
    while (i < length)
    {
        substr[i] = str[index2start + i];
        i++;
    }
    substr[length] = '\0';// null terminate
    cout<<substr.get()<<endl; // get() gets the array out of the unique_ptr
    return MyString(substr.get()); // google "copy elision" for more information 
                                   // on this line.
}

回到 OP 的代码中,随着返回到主函数, substr开始被重用和覆盖。

cout<<myStr1.str<<endl;

打印myStr1.str并且我们已经可以看到其中一些已被重用和销毁。

cout<<"here is the substring I've done:"<<endl;
myStr1.Print();

更多的死亡,更多的破坏,更少的绳索。

以后不要做的事情:

共享数据应该被复制的指针。

指向临时数据的指针。

非空终止字符串。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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