为了阐明关系运算符重载的概念(需要调试帮助)

时间:2018-07-28 05:27:58

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

让我告诉大家,我是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(s1的长度)小于obj.len(s2的长度)小于它返回true,则首先检查两个字符串的长度。

ii)。如果长度相等,则将s1 char数组的每个元素与s2 char数组进行比较。即使s1 char数组的一个元素小于s2 char数组元素(以ASCII表示),也返回true,否则返回错误。

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

4 个答案:

答案 0 :(得分:2)

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

  1. 运算符的算法错误<。在您的代码“ hello” <“再见”中,这是不正确的
  2. 缺少返回语句。
  3. 缺少析构函数,因此您的类会泄漏内存。
  4. 添加析构函数后,您还将需要一个复制构造函数和一个复制赋值运算符,否则您的代码将因两次释放相同的内存而崩溃,这被称为三个规则。 Google,因为它可能是您将阅读的最重要的C ++建议。
  5. 缺乏对const正确性的认识。
  6. 缺乏对通过引用的了解。
  7. 对于重载的运算符而言,签名不足。
  8. 在课堂上缺少一些重要的方法
  9. 缺少一些实现技巧。

在这里将所有内容放在一起,按照给出的规则对您的类进行实现,并附带一些注释

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

答案 1 :(得分:1)

您的代码存在无数问题,因此,我将为您提供一种经过改进的带注释的实现:

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

好的,您现在可以复制此代码,删除注释并将其显示为“您的”解决方案。这不是我想要这个答案的目的!花点时间仔细阅读我的评论-您可以从...中学到很多东西

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

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

您可能对进一步阅读感兴趣:

答案 2 :(得分:0)

在您的职能中,

bool operator < (MyString obj){

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

仅在到达第三个if时返回true。

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


仅需评论:您的代码容易发生内存泄漏。它分配但不释放内存。

答案 3 :(得分:0)

此代码有很多错误:

  1. 没有副本构造函数和副本被创建
  2. 缺少析构函数可以节省一天的时间(内存泄漏,但是由于您没有在第1点发生崩溃)
  3. len = 1表示空字符串(默认构造函数)。
  4. MyString(char *p)不添加终止符
  5. 不使用const MyString &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;
}