【问题标题】:To clarify concept of relational operator overloading (Debugging help needed)澄清关系运算符重载的概念(需要调试帮助)
【发布时间】:2018-07-28 05:27:58
【问题描述】:

让我告诉你们,我是 C++ 的初学者。
出于教育和学习的目的,我创建了自己的字符串类,名为MyString。根据我的导师的指示,我不允许使用标准库函数来比较两个字符串。 MyString 类包含 char 类型指针和保存字符串长度的整数类型变量,即:

class MyString{
    char *str; int len;

public:
    MyString(){
        len = 1;
        str = new char[len];
        str[len - 1] = '\0';
    }

    MyString(char *p){
        int count = 0;
        for (int i = 0; p[i] != '\0'; i++){
            count++;
        }
        len = count;
        str = new char[len];
        for (int i = 0; i < len; i++){
            str[i] = p[i];
        }
    }

    int length(){
        return len;
    }

    bool operator < (MyString obj){
        char temp;

        if (len < obj.len){ return true; }
        if (len>obj.len){ return false; }
        if (this->len == obj.len){
            for (int i = 0; i < len; i++){
                if (this->str[i] < obj.str[i])
                {
                    return true;
                }
            }
        }
    }

    bool operator > (MyString obj) {
        if (len > obj.len) {
            return true;
        }
        if (len<obj.len) {
            return false;
        }
        if (this->len == obj.len)
        {
            for (int i = 0; i < this->len; i++) {
                if (this->str[i] > obj.str[i]) {
                    return true;
                }
            }
        } 
    }

    bool operator == (MyString obj) {
        int count = 0;
        if (this->len == obj.len){
            for (int i = 0; i < this->len; i++) {
                if (this->str[i] == obj.str[i]) {
                    count++;
                }
            }
            if (count == len) {
                return true;
            }
        }
    }

    char & operator[](int i) {
        return str[i];
    }
};

这里是主要的

int main()
{
    char arr1[30], arr2[30];
    cout << "Enter first MyString: ";
    cin.get(arr1, 30);
    cin.ignore();
    cout << "Enter second MyString: ";
    cin.get(arr2, 30);
    MyString s1(arr1);    //parametrized constructor
    MyString s2(arr2);

    cout << "Length of s1:" << s1.length() << endl;
    cout << "Length of s2:" << s2.length() << endl;

    if (s1<s2)           // < operator overloaded
        cout << "s1 < s2" << endl;
    else if (s1>s2)      // > operator overloaded
        cout << "s1 > s2" << endl;
    else if (s1 == s2)     // == operator overloaded
        cout << "s1 == s2" << endl;

    return 0;
}

我比较两个字符串的算法是:

i).首先检查两个字符串的长度,如果 len(length of s1) 小于 obj.len(length of s2) 则返回 true。

ii).如果长度相等,则将 s1 char 数组的每个元素与 s2 char 数组进行比较。即使 s1 char 数组的元素之一小于 s2 char 数组元素(ASCII 格式),则返回 true 否则返回假的。

问题是每当程序执行时,无论传递的两个字符串是否相等,在控制台上都会显示“s1

【问题讨论】:

  • 请不要包含不相关语言的标签。这不是 C。
  • bool operator &lt; (MyString obj){ -- 我猜你的导师从未提到过“3 规则”。那一行代码可能会引入双重删除错误和内存损坏。此外,如果您要学习自己的字符串课程,至少老师应该给您一个真正有效的课程,而不是充满内存泄漏和错误的课程。
  • 题外话:不要使用if 来检查先前如果:if(condition) if(!condition)(坏)或变体:if(condition) else if(!condition) 的补码。只需简单的 else 代替:if(condition) else;也适用于您的相等检查,如果 都不适用,则相等 确实 适用,因此:if(&lt;) else if(&gt;) else(不再需要 == - 除非您有可能导致两者都不是的比较 也不 ==)。
  • 真正的问题是尝试创建对初学者来说似乎很容易弄清楚如何创建的类,但正确创建却不是那么明显。您的类缺少析构函数,因此存在内存泄漏。如果您添加析构函数,那么按值传递MyString 的那行代码现在会导致内存损坏等。
  • 我建议您学习如何在编译时启用警告。有些功能无法返回他们承诺的内容。这应该会生成一个警告来帮助您修复代码。另外,请阅读minimal reproducible example,如果您只是对operator== 有问题,则应该删除任何不必要的内容。

标签: c++ string memory-management operator-overloading


【解决方案1】:

您正在尝试编写分配资源的简单类。这是一个非常重要的技能。到目前为止,您所写的代码很好,但也有很多错误。主要错误是

  1. 运算符
  2. 缺少返回语句。
  3. 缺少析构函数,因此您的类会泄漏内存。
  4. 添加析构函数后,您还需要复制构造函数和复制赋值运算符,否则您的代码会因两次释放相同的内存而崩溃,这被称为 三规则。谷歌一下,因为它可能是你会读到的最重要的 C++ 建议。
  5. 缺乏对 const 正确性的认识。
  6. 缺乏通过引用传递的意识。
  7. 对于您的重载运算符而言,签名不太理想。
  8. 在您的课程中缺少一些重要的方法
  9. 缺少一些实现技巧。

把所有这些放在一起,按照你给定的规则实现你的类,还有一些 cmets

class MyString {
    char *str; int len;

public:
    // default constructor should create a empty string, i.e. a zero length string
    MyString() {
        len = 0;
        str = new char[len];
    }

    // contents of p are not changed so make it const
    MyString(const char *p) {
        int count = 0;
        for (int i = 0; p[i] != '\0'; i++){
            count++;
        }
        len = count;
        str = new char[len];
        for (int i = 0; i < len; i++){
            str[i] = p[i];
        }
    }

    // destructor, frees memory
    ~MyString() {
        delete[] str;
    }

    // copy constructor, similar to the above except it starts from a MyString
    MyString(const MyString& o) {
        len = o.len;
        str = new char[len];
        for (int i = 0; i < len; i++){
            str[i] = o.str[i];
        }
    }

    // swap method, efficient exchange of two strings
    void swap(MyString& o) 
    {
        int t1 = o.len;
        o.len = len;
        len = t1;
        char* t2 = o.str;
        o.str = str;
        str = t2;
    }

    // assignment operator, uses copy and swap idiom
    MyString& operator=(MyString o) {
        swap(o);
        return *this;
    }

    // length does not modify the string, so it should be decalred const
    int length() const {
        return len;
    }

    char& operator[](int i) {
        return str[i];
    }

    // need a const version of operator[] as well, otherwise you won't be able to do [] on a const string
    char operator[](int i) const {
        return str[i];
    }
};

// operator< should be a function not a class method. This is the only way to get
// C++ to treat the two arguments symmetrically. For instance with your version
// "abc" < str is not legal, but str < "abc" is. This oddity is because C++ will
// not implicitly create a MyString object to call a MyString method but it will implicitly
// create a MyString object to pass a parameter. So if operator< is a function you will
// get implicit creation of MyString objects on either side and both "abc" < str and 
// str < "abc" are legal.
// You also should pass to parameters by const reference to avoid unnecessary
// copying of MyString objects.
// Finally this uses the conventional algorithm for operator<
bool operator<(const MyString& lhs, const MyString& rhs) {
    for (int i = 0; ; ++i)
    {
        if (i == rhs.length())
            return false;
        if (i == lhs.length())
            return true;
        if (lhs[i] > rhs[i])
            return false;
        if (lhs[i] < rhs[i])
            return true;
    }
}

// This is the easy way to write operator>
bool operator>(const MyString& lhs, const MyString& rhs) {
    return rhs < lhs;
}

// This is the easy way to write operator<=
bool operator<=(const MyString& lhs, const MyString& rhs) {
    return !(rhs < lhs);
}

// This is the easy way to write operator>=
bool operator>=(const MyString& lhs, const MyString& rhs) {
    return !(lhs < rhs);
}

// operator== is a function not a method for exactly the same reasons as operator<
bool operator==(const MyString& lhs, const MyString& rhs) {
    if (lhs.length() != rhs.length())
        return false;
    for (int i = 0; i < lhs.length(); ++i)
        if (lhs[i] != rhs[i])
            return false;
    return true;
}

// this is the easy way to write operator!=
bool operator!=(const MyString& lhs, const MyString& rhs) {
    return !(lhs == rhs);
}

【讨论】:

  • @curiousguy 我认为几乎所有使用拉丁文字的人
  • 拉丁文指定String类的顺序?好的
  • 我不知道任何排序字符串的系统,其中字符串的长度被认为比字符串中的字母更重要。在 C 标准、C++ 标准或我所知道的任何语言中的单词顺序中都不是这种情况。也许你知道得更好,如果知道的话,请告诉我。做出积极的声明,而不仅仅是狙击。
  • 重点是它不必由“系统”、传统或正式的标准机构来标准化。如果您可以一致地定义它,那么订单就是您定义的。除非你能在该定义中发现一些荒谬的东西(除了发布的代码中的错误,这很容易纠正),否则它是一个有效的、非传统的命令。
  • @john 列出的 9 个原因证明了创建字符串课程需要实际教授,而不是发送给不了解这些特定方面的学生,让他们盲目地实施此类课程。一旦被教授,那么未来涉及诸如字符串类之类的类的分配就变得很简单了。
【解决方案2】:

你的代码有无数的问题,所以我会为你提供一个改进的、注释的实现:

class MyString
{

    char* str;
    unsigned int len; // strings can't have negative length; using unsigned reflects this better
                      // even better: use size_t; this is the type for the concrete system
                      // able to cover any allocatable memory size
public:
    MyString()
        : str(new char[1]), len(1) // prefer initializer list
    {
        str[0] = 0; // does not matter if you use 0 or '\0', just my personal preference...
    }

    MyString(char const* p)
    // you make a copy of, so have a const pointer (you can pass both const and non-const to)
    {
        for(len = 1; p[len] != 0; ++len);
        //        ^ make sure to copy the terminating null character as well!
        str = new char[len];
        for (unsigned int i = 0; i < len; i++)
        {
            str[i] = p[i];
        }
        // or use memcpy, if allowed
    }

    // OK, above, you allocated memory, so you need to free it again:
    ~MyString() // if you want to be able to inherit from, it should be virtual;
                // strings, though, most likely should not be inherited from...
    {
        delete[] str;
    }

    // C++ creates a default copy constructor; this one, however, just copies all members by value
    // i. e. copies the POINTER str, but not the memory pointed to, i. e. does not perform a deep copy
    // which is what you need, however, to avoid double deletion:
    MyString(MyString const& other)
        : str(new char[other.len]), len(other.len)
    {
        for (unsigned int i = 0; i < len; i++)
        {
            str[i] = other.str[i];
        }
    }

    // similar for assignment; I'm using copy and swap idiom to reduce code duplication here:
    MyString& operator=(MyString other)
    {
        swap(other);
        return *this;
    }

    void swap(MyString& other)
    {
        char* str = this->str;
        unsigned int len = this->len;
        this->str = other.str;
        this->len = other.len;
        other.str = str;
        other.len = len;
    }

    unsigned int length() const
    //                    ^^^^^  allows to retrieve length from a
    //                           const MyString as well!
    {
        return len;
    }

    // fine, you can change the character within the string
    char& operator[](unsigned int i)
    {
        return str[i];
    }
    // but what, if you have a const MyString???
    // solution:
    char operator[](unsigned int i) const
    //                              ^^^^^
    {
      return str[i];
    }
    // you could alternatively return a const reference,
    // but char is just too small that a reference would be worth the effort
    // additionally: a reference could have the const casted away by user
    // which is not possible by returning a copy, so we gain a little of safety as well...

    bool operator<(MyString const& other) const
    //                      ^^^^^^
    // we don't need a copy and don't want a copy(it would just costs runtime and memory for nothing)!
    // -> pass by const reference
    // additionally, we want to be able to do comparison on const this as well (see length)
    // 
    {
        // have you noticed that you have one and the same code in all of your comparison operators???
        // only the comparison itself changes lets have it just a little bit cleverer:
        return compare(other) < 0;
    }
    bool operator>(MyString const& other) const
    {
        return compare(other) > 0;
    }
    bool operator==(MyString const& other) const
    {
        return compare(other) == 0;
    }
    // and for completeness:
    bool operator<=(MyString const& other) const
    {
        return compare(other) <= 0;
    }
    bool operator>=(MyString const& other) const
    {
        return compare(other) >= 0;
    }
    bool operator!=(MyString const& other) const
    {
        return compare(other) != 0;
    }
    // the upcoming space ship operator (<=>) will simplify this, well, OK, but for now, we don't have it yet...

    int compare(MyString const& other) const
    {
        // I decided to compare "abcd" smaller than "xyz" intentionally
        // for demonstration purposes; just place your length checks
        //  back to get your original comparison again
        unsigned int pos = 0;

        // EDIT: "stealing" john's implementation, as superior to
        // mine (with minor adaptions) ... 
        for (unsigned int pos = 0; ; ++pos)
        {
            ///////////////////////////////////////////////////
            // if you have your original length checks placed back above,
            // just have the following check instead of the active one:
            // if(pos == len) return 0;
            if (pos == len)
            {
                return pos == other.len ? 0 : -pos - 1;
            }
            if (pos == other.len)
            {
                return pos + 1;
            } 
            ///////////////////////////////////////////////////
            if(str[pos] < other.str[pos])
            {
                return -pos - 1;
            }
            if(str[pos] > other.str[pos])
            {
                return pos + 1;
            }
        }
        return 0;
    }
    // WARNING: above code has yet an issue! I wanted to allow (for demonstration)
    // to return positional information so that we not only see the result of comparison
    // but can conclude to at WHERE the two strings differ (but need 1-based offset for to
    // distinguish from equality, thus addition/subtraction of 1);
    // however, on VERY large strings (longer than std::numeric_limits<int>::max()/-[...]::min()), we get
    // signed integer overflow on the implicit cast, which is undefined behaviour
    // you might want to check against the limits and in case of overflow, just return the limits
    // (leaving this to you...)
    // alternative: just return -1, 0, +1, the issue is gone as well...
};

好的,您现在可以复制此代码,剥离 cmets 并将其呈现为“您的”解决方案。这不是我想要这个答案的目的!花点时间仔细阅读我的 cmets - 你可以从中学到很多...

最后:还有另一个可能的改进:在 C++11 之前,如果按值传递对象,则只能复制数据。从 C++ 开始,我们还可以将数据从一个对象移动到另一个对象——但是,该类型需要支持移动语义。您可以通过另外提供移动构造函数和复制赋值来做到这一点:

MyString(MyString&& other)
    : str(nullptr), len(0)
{
    // delete[]'ing nullptr (in other!) is OK, so we don't need
    // to add a check to destructor and just can swap again...
    swap(other);
}

MyString& operator=(MyString&& other)
{
    // and AGAIN, we just can swap;
    // whatever this contained, other will clean it up...
    swap(other);
    return *this;
}

您可能有兴趣进一步阅读:

【讨论】:

    【解决方案3】:

    在你的函数中,

    bool operator < (MyString obj){
    

    我看不到最后返回 false 的方法!

    只有到达第三个if才返回true。

    此外,正如其他人所提到的,长度并不意味着以您实现的方式进行比较。


    只是评论:您的代码容易出现内存泄漏。它分配但不释放内存。

    【讨论】:

    • 这是众多错误之一。为什么要突出这一点。
    • @DavidHeffernan,我相信这是最重要的。因为(s1&lt;s2) 总是返回true。
    • 是说较短的字符串在概念上是劣等的吗?
    • @curiousguy,对与错取决于您的定义。如果我们以strcmp为标准,那就错了。
    【解决方案4】:

    这段代码有很多错误:

    1. 没有复制构造函数并创建了副本
    2. 缺少析构函数可以节省时间(内存泄漏,但由于第 1 点没有崩溃)
    3. len = 1 用于空字符串(默认构造函数)。
    4. MyString(char *p)不要添加终止符
    5. 不使用const MyString &amp;obj(不需要的副本)。
    6. 在各个方法分支的末尾缺少返回值
    bool operator < (const MyString &obj) {
        if (len < obj.len) {
           return true;
        }
        if (len>obj.len) {
            return false;
        }
        for (int i = 0; i < len; i++) {
            if (this->str[i] != obj.str[i]) {
                return this->str[i] < obj.str[i];
            }
        }
        return false;
    }
    

    【讨论】:

    • 谢谢!!你刚刚拯救了我的一天
    • 这不是operator&lt; 的正常实现。 "z"
    • @john 谁定义了“正常”?
    • @curiousguy OK 常规
    • 是的,他的比较算法不匹配普通降神会,但让他发现这个问题。我知道长度不应该是比较的首要标准。
    猜你喜欢
    • 2013-04-12
    • 2018-09-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-08-17
    • 1970-01-01
    • 1970-01-01
    • 2013-07-22
    相关资源
    最近更新 更多