-1

Let me tell you guys I am beginner to c++.
For educational and learning purpose I have created my own string class named MyString. As per instructions from my instructor I am not allowed to use standard library functions to compare two strings. MyString class contains char type pointer and integer type variable that holds length of string i.e:

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

Here is main

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

My algo for comparing two strings is:

i).First check length of two strings if len(length of s1) is less than obj.len(length of s2) than it returns true.

ii).if lengths are equal, compare for each element of s1 char array with s2 char array.Even if one of element of s1 char array is less than that s2 char array element (in ASCII) than return true otherwise return false.

The problems is whenever program executes, on console it shows "s1< s2" no matter if two strings passed are equal .

curiousguy
  • 7,344
  • 2
  • 37
  • 52
  • Are you aware that this comparison will result in `"xyz"` being smaller than `"abcd"`? Is this really intended? – Aconcagua Jul 28 '18 at 05:39
  • 1
    Please don't include tags for languages that aren't relevant. This is not C. – David Heffernan Jul 28 '18 at 05:42
  • 2
    `bool operator < (MyString obj){` -- I guess your instructor never mentioned about the "Rule of 3". That one line of code can introduce a double deletion error and memory corruption. Also, if you're going to be taught your own string class, at least the teacher(s) should give you one that actually works, not one full of memory leaks and bugs. – PaulMcKenzie Jul 28 '18 at 05:44
  • 1
    Off-topic: Don't use `if` for checking complements of previous if: `if(condition) if(!condition)` (bad) or variant of: `if(condition) else if(!condition)`. Just have simple else instead: `if(condition) else`; applies for your equality check as well, if neither < nor > applies then equality *does* apply, so: `if() else` (no `==` needed any more - unless you have comparisons that can result in being neither < nor > nor ==). – Aconcagua Jul 28 '18 at 05:44
  • Have you noticed that in second constructor, you don't copy the terminating null character? You need one additional offset... Additionally, you can have the counting *much* simpler: `for(len = 0; p[len] != '\0'; len++);` Starting with `len = 1` then will automatically include the null character as well. – Aconcagua Jul 28 '18 at 05:47
  • 2
    The real issue is attempting to create classes that seem obvious to a beginner to figure out how to create, but are not really so obvious to create correctly. Your class lacks a destructor, thus a memory leak. If you add a destructor, that line of code that passes `MyString` by value now causes memory corruption, etc. etc. – PaulMcKenzie Jul 28 '18 at 05:49
  • Also, you don't need to rewrite operator `>` from scratch if you already coded `operator `: `return obj < *this;` – PaulMcKenzie Jul 28 '18 at 05:52
  • @Aconcagua yes, "xyz" should be smaller than "abcd"because of string length but what i intend to do is to show "xyz" greater than "abc" when both strings are of same length,comparision made should be character wise. – Muzahir Hussnain Jul 28 '18 at 06:00
  • 1
    I'd add as advise that you should learn how to enable warnings when compiling. There are functions that fail to return what they promised. That should have generated a warning to help you fix your code. Also, please read [mcve], if you only have a problem with `operator==`, anything not necessary should have been stripped. – Ulrich Eckhardt Jul 28 '18 at 06:13
  • 1
    You have no destructor freeing the memory you allocate. You are leaking memory. – Jesper Juhl Jul 28 '18 at 11:17

4 Answers4

2

You're trying to write simple classes that allocate resources. This is a very important skill to have. What you've written so far has some good code, but also a lot of mistakes. The main errors are

  1. Wrong algorithm for operator<. In your code "hello" < "goodbye" which is incorrect
  2. Missing return statements.
  3. Lack of destructor, so your class leaks memory.
  4. Once you add a destructor you will also need a copy constructor and copy assignment operator otherwise your code will crash by freeing the same memory twice, this is known as the rule of three. Google it as it's perhaps the most important piece of C++ advice you'll read.
  5. Lack of awareness of const correctness.
  6. Lack of awareness of pass by reference.
  7. Less than ideal signatures for your overloaded operators.
  8. Missing some important methods on your class
  9. Missing a few implementation tricks.

Putting all that together here an implementation of your class following the rules you've been given, with a few comments as well

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);
}
john
  • 71,156
  • 4
  • 49
  • 68
  • @curiousguy pretty much everyone who uses Latin script I think – john Jul 28 '18 at 07:02
  • Latin script specifies the ordering of String class? OK – curiousguy Jul 28 '18 at 14:08
  • I'm not aware of any system of ordering strings in which the length of a string is considered more significant than the letters within the string. It's not the case in the C standard, the C++ standard, or in the ordering of words in any language I'm know of. Maybe you know better, if so tell me about it. Make a positive statement instead of just sniping. – john Jul 28 '18 at 15:53
  • The point is that it doesn't have to be standardized by a "system", by a tradition, or a formal standard body. An order is what you define it to be, if you can define it consistently. Unless you can find something absurd in that definition (beyond a bug in the posted code, that's easily corrected), it's a valid, untraditional order. – curiousguy Jul 28 '18 at 16:48
  • @john That list of 9 reasons is evidence that creating string classes needs to be actually taught, not sent out to students with no knowledge of these certain aspects and have them implement such classes blindly. Once taught, then future assignments involving classes such as a string class become straightforward as to what to implement. – PaulMcKenzie Jul 28 '18 at 18:01
1

There are countless issues with your code, so I'll provide an improved, commented implementation for you:

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

OK, you now could just copy this code, strip the comments and present it as "your" solution. This is not what I intended this answer for! Take your time and read my comments carefully – you can learn quite a bit from...

Finally: there is yet another improvement possible: Before C++11, you could only copy data, if passing objects by value. Since C++, we additionally can move data from one object into another one – however, the type needs to support move semantics. You can do so by additionally providing a move constructor and copy assignment:

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

You might be interested in further reading:

Aconcagua
  • 19,952
  • 4
  • 31
  • 51
0

In your function,

bool operator < (MyString obj){

I see no way to return false at the end!

It only returns true if it reach the 3rd if.

In addition, as others mentioned, the length does not imply the comparison in the way that you implemented.


Just a comment: Your code is prone to memory leak. It allocates but does not release the memory.

Arash
  • 1,984
  • 9
  • 15
0

This code has lots of mistakes:

  1. No copy constructor and copy is created
  2. Missing destructor saves the day (memory leak, but thanks to that you do not have a crash for point 1)
  3. len = 1 for empty string (default constructor).
  4. MyString(char *p) do not add terminating character
  5. not using const MyString &obj (unneeded copies).
  6. missing return values at the end of various branches of methods
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;
}
Marek R
  • 23,155
  • 5
  • 37
  • 107