Cpp Chapter 11: Working with Classes Part1

11.1 Operator overloading

) Operator overloading is an example of C++ polymorphism. It allows you to extend operator overloading to user-defined types, to use the + symbol to add two objects.
) To overload an operator, you use a special function called operator function:

operatorop(argument-list);

op here is the symbol of the operator. For example, functions like operator+() or operator*()
) If you have overloaded + for a class, you can write this kind of assignment:

class thing
{
    ...
}
thing a, b, c;
a = b + c; // !!

The compiler will translate a = b + c to:

a = b.operator+(c)

11.2 Time on our hands: developing an operator overloading example

) First, go through a Time class adding code without operator overloading:

#ifndef MYTIME0_H_INCLUDED
#define MYTIME0_H_INCLUDED

class Time
{
private:
    int hours;
    int minutes;
public:
    Time();
    Time(int h, int m = 0);
    void AddMin(int m);
    void AddHr(int h);
    void Reset(int h = 0, int m = 0);
    Time Sum(const Time & t) const;
    void Show() const;
};

#endif // MYTIME0_H_INCLUDED
// mytime0.cpp -- implementing Time methods
#include <iostream>
#include "mytime0.h"

Time::Time()
{
    hours = minutes = 0;
}

Time::Time(int h, int m)
{
    hours = h;
    minutes = m;
}

void Time::AddMin(int m)
{
    minutes += m;
    hours += minutes / 60;
    minutes %= 60;
}

void Time::AddHr(int h)
{
    hours += h;
}

void Time::Reset(int h, int m) // default arguments only written in the prototype
{
    hours = h;
    minutes = m;
}

Time Time::Sum(const Time & t) const
{
    Time sum;
    sum.minutes = minutes + t.minutes;
    sum.hours = hours + t.hours + sum.minutes / 60;
    sum.minutes %= 60;
    return sum;
}

void Time::Show() const
{
    std::cout << hours << " hours, " << minutes << " minutes";
}
// usetime0.cpp -- using the first draft the Time class
// compile with mytime0.cpp
#include <iostream>
#include "mytime0.h"

int main()
{
    using std::cout;
    using std::endl;
    Time planning;
    Time coding(2, 40);
    Time fixing(5, 55);
    Time total;

    cout << "planning time = ";
    planning.Show();
    cout << endl;

    cout << "coding time = ";
    coding.Show();
    cout << endl;

    cout << "fixing time = ";
    fixing.Show();
    cout << endl;

    total = coding.Sum(fixing);
    cout << "coding.Sum(fixing) = ";
    total.Show();
    cout << endl;

    return 0;
}

Noteworthy:
1 Member functions should not return reference to temporary variables or objects
2 passing a const & object to a function is more efficient than passing a copy of the object(no need of creating another object)


11.2.1 Adding an addition operator

) Now replace the previous Sum() with operator+() function:

#ifndef MYTIME1_H_INCLUDED
#define MYTIME1_H_INCLUDED

class Time
{
private:
    int hours;
    int minutes;
public:
    Time();
    Time(int h, int m = 0);
    void AddMin(int m);
    void AddHr(int h);
    void Reset(int h = 0, int m = 0);
    Time operator+(const Time & t) const;
    void Show() const;
};

#endif // MYTIME1_H_INCLUDED
// mytime1.cpp -- implementing Time methods
#include <iostream>
#include "mytime1.h"

Time::Time()
{
    hours = minutes = 0;
}

Time::Time(int h, int m)
{
    hours = h;
    minutes = m;
}

void Time::AddMin(int m)
{
    minutes += m;
    hours += minutes / 60;
    minutes %= 60;
}

void Time::AddHr(int h)
{
    hours += h;
}

void Time::Reset(int h, int m) // default arguments only written in the prototype
{
    hours = h;
    minutes = m;
}

Time Time::operator+(const Time & t) const
{
    Time sum;
    sum.minutes = minutes + t.minutes;
    sum.hours = hours + t.hours + sum.minutes / 60;
    sum.minutes %= 60;
    return sum;
}

void Time::Show() const
{
    std::cout << hours << " hours, " << minutes << " minutes";
}
// usetime1.cpp -- using the second draft of the Time class
// compile usetime1.cpp and mytime1.cpp together
#include <iostream>
#include "mytime1.h"

int main()
{
    using std::cout;
    using std::endl;
    Time planning;
    Time coding(2,40);
    Time fixing(5,55);
    Time total;

    cout << "planning time = ";
    planning.Show();
    cout << endl;

    cout << "coding time = ";
    coding.Show();
    cout << endl;

    cout << "fixing time = ";
    fixing.Show();
    cout << endl;

    total = coding + fixing;
    cout << "coding + fixing = ";
    total.Show();
    cout << endl;

    Time morefixing(3,28);
    cout << "more fixing time = ";
    morefixing.Show();
    cout << endl;
    total = morefixing.operator+(total);
    cout << "morefixing.operator+(total) = ";
    total.Show();
    cout << endl;

    return 0;
}

Noteworthy:
1 Two notations of invoking the operator+() function:

total = coding.operator+(fixing);
total = coding + fixing;

The object to the left of the operator is the invoking object, and the object to the right is the one passed as an argument
2 t4 = t1 + t2 + t3 is valid, the same as:

t4 = t1.operator+(t2.operator+(t3));

11.2.2 Overloading restrictions

) Overloaded operators don't necessarily have to be member functions
) Restrictions on operator overloading:
1 The overloaded operator must have at least one operand that is a user-defined type
Thus, you can't overload operators for built-in types, which preserves program sanity
2 You can't use an operator in a manner that violates its original syntax:

int x;
Time shiva;
% x; // invalid for modulus operator
% shiva; // invalid for overloaded operator
Can't alter operator precedence. Can't change the required number of operands for a operator(in this example)

3 You can't create new operator symbols

operator**(); // invalid!

4 You can't overload these:

. .* :: ?: typeid const_cast dynamic_cast reinterpret_cast static_cast

5 Overloading these operators, you must use member functions:

= () [] ->

11.2.3 More overloading operators

Here comes an example using operator-() and operator*():

#ifndef MYTIME2_H_INCLUDED
#define MYTIME2_H_INCLUDED

class Time
{
private:
    int hours;
    int minutes;
public:
    Time();
    Time(int h, int m = 0);
    void AddMin(int m);
    void AddHr(int h);
    void Reset(int h = 0, int m = 0);
    Time operator+(const Time & t) const;
    Time operator-(const Time & t) const;
    Time operator*(double n) const;
    void Show() const;
};

#endif // MYTIME2_H_INCLUDED
// mytime2.cpp -- implementing Time methods
#include <iostream>
#include "mytime2.h"

Time::Time()
{
    hours = minutes = 0;
}

Time::Time(int h, int m)
{
    hours = h;
    minutes = m;
}

void Time::AddMin(int m)
{
    minutes += m;
    hours += minutes / 60;
    minutes %= 60;
}

void Time::AddHr(int h)
{
    hours += h;
}

void Time::Reset(int h, int m) // default arguments only written in the prototype
{
    hours = h;
    minutes = m;
}

Time Time::operator+(const Time & t) const
{
    Time sum;
    sum.minutes = minutes + t.minutes;
    sum.hours = hours + t.hours + sum.minutes / 60;
    sum.minutes %= 60;
    return sum;
}

Time Time::operator-(const Time & t) const
{
    Time diff;
    int tot1, tot2;
    tot1 = minutes + hours * 60;
    tot2 = t.minutes + t.hours * 60;
    diff.minutes = (tot1 - tot2) % 60;
    diff.hours = (tot1 - tot2) / 60;
    return diff;
}

Time Time::operator*(double n) const
{
    Time result;
    long totalminutes = hours * n * 60 + minutes * n;
    result.hours = totalminutes / 60;
    result.minutes = totalminutes % 60;
    return result;
}

void Time::Show() const
{
    std::cout << hours << " hours, " << minutes << " minutes";
}
// usetime2.cpp -- using the third draft of the Time class
// compile usetime2.cpp and mytime2.cpp together
#include <iostream>
#include "mytime2.h"

int main()
{
    using std::cout;
    using std::endl;
    Time weeding(4, 35);
    Time waxing(2, 47);
    Time total;
    Time diff;
    Time adjusted;

    cout << "weeding time = ";
    weeding.Show();
    cout << endl;

    cout << "waxing time = ";
    waxing.Show();
    cout << endl;

    cout << "total work time = ";
    total = weeding + waxing;
    total.Show();
    cout << endl;

    diff = weeding - waxing;
    cout << "weeding time - waxing time = ";
    diff.Show();
    cout << endl;

    adjusted = total * 1.5;
    cout << "adjusted work time = ";
    adjusted.Show();
    cout << endl;

    return 0;
}

11.3 Introducing friends

) In C++ classes, public class methods serve as the only access to private data members.But C++ provides another form of access: the friend:
1 Friend functions
2 Friend classes
3 Friend member function
By making a function a friend to a class, you allow the function the same access as a member function of the class
) Problem arises in the following code:

A = B * 2.75; // valid, A = B.operator*(2.75);
A = 2.75 * B; // invalid! no match function

You could use nonmember functions to solve this, where are both the object and the double are explicitly passed and used. But nonmember functions couldn't access the private data of B, hence the need of friend functions


11.3.1 Creating friends

) There are 2 steps of creating friend functions:
1 place a prototype in the class declaration and prefix it with keyword friend:

friend Time operator*(double m, const Time & t);

Although operator() here is in the class declaration, it is not a member function and not invoked by "."
Although operator
() is not a member function, it has the same access rights as a member function
2 write the function definition, note that don't use Time:: qualifier and don't write friend here(only in prototype)

Time operator*(double m, const Time & t)
{
    Time result;
    long totalminutes = t.hours * m * 60 + t.minutes * m;
    result.minutes = totalminutes % 60;
    result.hours = totalminutes /  60;
    return result;
}

Actually, the friend function above could also be written like this:

Time operator*(double m, const Time & t)
{
    return t * m;
}

Seems like in this way, the nonmember function doesn't have to access private data members of the t object, but you'd better make it friend function anyway
) This example illustrates that while overloading an operator, if you want to use the operator with a non-class term as the first operand, you use a friend function to reverse the order


11.3.2 A common kind of friend: overloading the << operator

) Code like this is desirable:

Time t;
cout << t; // make cout recognize class Time objects

You use the class Time to teach the Time class how to use cout in this general manner, also remember to make this function a friend to class Time:

ostream & operator<<(ostream & os, const c_name & obj)
{
    os <<  ... ;
    return os;
}

Noteworthy:
1 As you often use cout with cout << something, the operands of operator << is first cout, which is an ostream class object, then something, which is a given class member. So the argument-list would be (ostream & os, const c_name & obj), which in this case is (ostream & os, const Time & t)
2 As overloading operator << involves access to private class data members, you should make the function a friend to class c_name(prototype):

friend ostream & operator<<(ostream & os, const c_name & obj);

As you generally use os as a whole, you don't need to make this function a friend to the class ostream, which saves much trouble
3 Why return ostream &? Consider code like this:

Time t;
cout << "Total time: " << t << endl;

When using cout to handle built-in type, C++ handles it like this:

cout << a << b;
(cout << a) << b;

The operator << originally means bit manipulation operators, C++ has already overloaded it to tackle built-in types. C++ make cout << .. return the cout object again, which means that (cout << a) << b; is equivalent to cout << b; after the process of a by cout. So, in order to be consistent and let your self-overloaded version of << to cope with multiple << in one line, make your overloading function return the reference to the ostream object cout, thus making the code above "cout << "Total time: " << t;" equivaleng to "(cout << "Total time: ") << t;", which is "cout << t" eventually
4 The ostream class object is not restricted to cout, others:
cerr: put content to the standard error stream. fout: put content to a file rather than the console window(implemented by the feature of inheritance)

Here comes example of using the overloaded << for output and friend function operator*() for reversed-order calculation:

#ifndef MYTIME3_H_INCLUDED
#define MYTIME3_H_INCLUDED
#include <iostream>

class Time
{
private:
    int hours;
    int minutes;
public:
    Time();
    Time(int h, int m = 0);
    void AddMin(int m);
    void AddHr(int h);
    void Reset(int h = 0, int m = 0);
    Time operator+(const Time & t) const;
    Time operator-(const Time & t) const;
    Time operator*(double n) const;
    friend Time operator*(double n, const Time & t)
        {return t * n;}
    friend std::ostream & operator<<(std::ostream & os, const Time & t);
    void Show() const;
};

#endif // MYTIME3_H_INCLUDED
// mytime3.cpp -- implementing Time methods
#include <iostream>
#include "mytime3.h"

Time::Time()
{
    hours = minutes = 0;
}

Time::Time(int h, int m)
{
    hours = h;
    minutes = m;
}

void Time::AddMin(int m)
{
    minutes += m;
    hours += minutes / 60;
    minutes %= 60;
}

void Time::AddHr(int h)
{
    hours += h;
}

void Time::Reset(int h, int m) // default arguments only written in the prototype
{
    hours = h;
    minutes = m;
}

Time Time::operator+(const Time & t) const
{
    Time sum;
    sum.minutes = minutes + t.minutes;
    sum.hours = hours + t.hours + sum.minutes / 60;
    sum.minutes %= 60;
    return sum;
}

Time Time::operator-(const Time & t) const
{
    Time diff;
    int tot1, tot2;
    tot1 = minutes + hours * 60;
    tot2 = t.minutes + t.hours * 60;
    diff.minutes = (tot1 - tot2) % 60;
    diff.hours = (tot1 - tot2) / 60;
    return diff;
}

Time Time::operator*(double n) const
{
    Time result;
    long totalminutes = hours * n * 60 + minutes * n;
    result.hours = totalminutes / 60;
    result.minutes = totalminutes % 60;
    return result;
}

std::ostream & operator<<(std::ostream & os, const Time & t)
{
    os << t.hours << " hours, " << t.minutes << " minutes";
    return os;
}

void Time::Show() const
{
    std::cout << hours << " hours, " << minutes << " minutes";
}
// usetime3.cpp -- using the fourth draft of the Time class
// compile usetime3.cpp and mytime3.cpp together
#include <iostream>
#include "mytime3.h"

int main()
{
    using std::cout;
    using std::endl;
    Time aida(3, 35);
    Time tosca(2, 48);
    Time temp;

    cout << "Aida and Tosca:
";
    cout << aida << "; " << tosca << endl;
    temp = aida + tosca;
    cout << "Aida + Tosca: " << temp << endl;
    temp = aida * 1.17;
    cout << "Aida * 1.17: " << temp << endl;
    cout << "10.0 * Tosca: " << 10.0 * tosca << endl;
    return 0;
}

11.4 Overloaded Operators: Member Versus Nonmember Functions

) In many situations, you could use both member and nonmember functions for operator overloading:

Time operator+(const Time & t) const; // member version
friend Time operator+(const Time & t1, const Time & t2); // nonmember version

Differences:
1 nonmember version is a friend function to class Time, while member version is not
2 nonmember version takes both operands as parameters, but member version only takes one as argument, leaving the other implicitly passed by this pointer
) Advantages:
Often it doesn't make much difference, sometimes the nonmember version has an advantage when conversions between classes are implemented


11.5 More overloading: a vector class

) Here comes an example of the declaration and definition of a vector class, capable of handing both rectangle mode and polar mode:

// vect.h -- vector class with <<, mode state
#ifndef VECT_H_INCLUDED
#define VECT_H_INCLUDED
#include <iostream>
namespace VECTOR
{
    class Vector
    {
    public:
        enum Mode {RECT, POL};
    private:
        double x;
        double y;
        double mag;
        double ang;
        Mode mode;
        void set_mag();
        void set_ang();
        void set_x();
        void set_y();
    public:
        Vector();
        Vector(double n1, double n2, Mode form = RECT);
        void reset(double n1, double n2, Mode form = RECT);
        ~Vector();
        double xval() const {return x;}
        double yval() const {return y;}
        double magval() const {return mag;}
        double angval() const {return ang;}
        void polar_mode();
        void rect_mode();
        Vector operator+(const Vector & b) const;
        Vector operator-(const Vector & b) const;
        Vector operator-() const; // return reverse of vector
        Vector operator*(double n) const;
        friend Vector operator*(double n, const Vector & a);
        friend std::ostream & operator<<(std::ostream & os, const Vector & v);
    };
}

#endif // VECT_H_INCLUDED
// vect.cpp -- methods for the Vector class
#include <cmath>
#include "vect.h"
using std::sqrt;
using std::sin;
using std::cos;
using std::atan;
using std::atan2;
using std::cout;

namespace VECTOR
{
    const double Rad_to_deg = 45.0 / atan(1.0);

    void Vector::set_mag()
    {
        mag = sqrt(x * x + y * y);
    }

    void Vector::set_ang()
    {
        if (x == 0.0 && y == 0.0)
            ang = 0.0;
        else
            ang = atan2(y, x);
    }

    void Vector::set_x()
    {
        x = mag * cos(ang);
    }

    void Vector::set_y()
    {
        y = mag * sin(ang);
    }

    Vector::Vector()
    {
        x = y = ang = mag = 0.0;
        mode = RECT;
    }

    Vector::Vector(double n1, double n2, Mode form)
    {
        mode = form;
        if (form == RECT)
        {
            x = n1;
            y = n2;
            set_mag();
            set_ang();
        }
        else if (form == POL)
        {
            mag = n1;
            ang = n2 / Rad_to_deg;
            set_x();
            set_y();
        }
        else
        {
            cout << "Incorrect 3rd argument to Vector() -- ";
            cout << "vector set to 0
";
            x = y = mag = ang = 0.0;
            mode = RECT;
        }
    }

    void Vector::reset(double na, double n2, Mode form)
    {
        mode = form;
        if (form == RECT)
        {
            x = n1;
            y = n2;
            set_mag();
            set_ang();
        }
        else if (form == POL)
        {
            mag = n1;
            ang = n2 / Rad_to_deg;
            set_x();
            set_y();
        }
        else
        {
            cout << "Incorrect 3rd argument to Vector() -- ";
            cout << "vector set to 0
";
            x = y = mag = ang = 0.0;
            mode = RECT;
        }
    }

    Vector::~Vector()
    {
    }

    void Vector::polar_mode()
    {
        mode = POL;
    }

    void Vector::rect_mode()
    {
        mode = RECT;
    }

    Vector Vector::operator+(const Vector & b) const
    {
        return Vector(x + b.x, y + b.y);
    }

    Vector Vector::operator-(const Vector & b) const
    {
        return Vector(x - b.x, y - b.y);
    }

    Vector Vector::operator-() const
    {
        return Vector(-x, -y);
    }

    Vector Vector::operator*(double n) const
    {
        return Vector(n * x, n * y);
    }

    Vector operator*(double n, const Vector & a)
    {
        return a * n;
    }

    std::ostream & operator<<(std::ostream & os, const Vector & v)
    {
        if (v.mode == Vector::RECT)
            os << "(x,y) = (" << v.x << ", " << v.y << ")";
        else if (v.mode == Vector::POL)
        {
            os << "(m,a) = (" << v.mag << ", " << v.ang * Rad_to_deg << ")";
        }
        else
            os << "Vector object mode is invalid";
        return os;
    }
}

) The design follows the OOP tradition of having interface concentrate on the essentials while hiding the details


11.5.1 Using a state member

) The code below

class Vector
{
public:
    enum Mode {RECT, POL};
private:
    Mode mode;
    ...
}

By using enumeration, it specified a state member called mode, representing whether RECT mode or POLAR mode is used. It also uses this mode to specify the way of output, the way of initiallization and so on. Basically, you could store many representations of the same thing in a class and establish a state member to show which state it is in now. Apart from that, you could also only store 1 type and make other representations available by calculations or other functions.


11.5.2 Overloading arithmetic operators for the Vector Class

binary operator

an operator which has two operands

unary operator

an operator which has only one operand

The "-" has two modes in C++, one as binary operator for subtraction, one as unary operator for negative mark. In this example, it overloads the overloaded operator "-' to make it both available as binary or unary operator for the class Vector

原文地址:https://www.cnblogs.com/fsbblogs/p/9759432.html