2.16. Operator Overloading

Operator overloading allows us to provide class-specific instances of the existing operators, such as addition, multiplication, equality, and so on. For example, rather than providing a named function, such as multiplyByDouble(), for a Matrix class, we can overload the multiply operator to perform the same task. That is, rather than writing

Matrix newMat = Matrix.multiplyByDouble( mat, dval );

the user can write the more intuitive

Matrix newMat = mat * dval;

Here is how we might implement this instance of the multiply operator:

public class Matrix
{
   public static Matrix operator*(Matrix mat, double dval)
   {
         Matrix result = new Matrix( mat.rows, mat.cols );

         for ( int ix = 0; ix < mat.rows; ix++ )
                for ( int iy = 0; iy < mat.cols; iy++ )
                       result[ix,iy] = mat[ix,iy] * dval;

                return result;
   }

   // ... rest of the Matrix class
}

All operator functions must be declared public and static. The operator keyword is followed by the operator symbol we wish to overload. At least one of the parameters of the operator must be a class instance. In this case, we overload the multiplication operator to accept a Matrix object and a value of type double, and the operator returns a new object of type Matrix.

The operator is applied directly to the class object. The use of the operator looks no different from the use of the built-in operators. When the compiler encounters an expression, such as mat*dval, it resolves which instance of the multiplication operator to invoke on the basis of the types of the operands. In this case, it invokes our overloaded Matrix instance.

What happens if we write the following?

mat *= dval;

Since we have not explicitly provided an overloaded instance of the compound assignment operator for multiplication, does this statement result in a compile-time error? No. Support of the compound operator is automatic when we provide an overloaded instance of an operator for which there exists a compound assignment operator, such as addition, subtraction, multiplication, and so on. In fact, providing an explicit overloaded instance of a compound assignment operator results in a compile-time error.

What about when we reverse the operands, as in

Matrix newMat = dval * mat;

In this case, the compiler flags the expression as invalid. It doesn't know how to multiply a double by a Matrix; it knows only how to multiply a Matrix by a double. We have to provide an explicit second instance in order for this to work. In this case, the implementation simply invokes the first instance:

public static Matrix
operator*( double dval, Matrix m ){ return m*dval; }

What if we multiply a Matrix object by an int or a float? Since our operator expects an operand of type double, does an error result?

No. If a single implicit conversion can match the type of the formal parameter, everything is fine.

The parameters to the overloaded operator cannot be either ref or out. Nor can we change the predefined arity of an operator (i.e., the number of arguments that an operator can take). For example, we cannot define a division operator that accepts only one operand. Similarly, we cannot change the precedence of an operator. For example, the multiplication operator always has a higher precedence than that of the addition operator. (See Section 1.18.4 for a discussion of operator precedence.)

The language requires that at least one parameter of the operator be of the class type to which the operator belongs. This restriction prevents us from redefining existing operators such as the int addition operator.

Eight unary operators can be overloaded:

// the overloadable unary operators
+, -, !, ~, ++, --, true, false

The single parameter must be of the class type to which the operator is a member. Three unary operators are not permitted to be overloaded: the selection operator (.), the call operator (()), and new.

If we define a true operator, we must also define a false operator. Both must return a bool value. For example, our StringNode class might define true to mean that its string member is not null and false to mean that it is:

class StringNode
{
    private string text;
    public  string Text{ get{ return text; }};

    private StringNode front_link;
    public  StringNode Next{ get{ return front_link; }};

    public static bool
        operator true(StringNode sn) {return sn.text != null;}

    public static bool
        operator false(StringNode sn){return sn.text == null;}
}

A StringNode object might then be used as follows:

static public void display( StringNode sn )
{
    while ( sn != null )
    {
        // invokes: bool operator true( StringNode )
        if ( sn )
           Console.WriteLine( sn.Text );
        sn = sn.Next;
    }
}

The increment and decrement operators (++,--) must return an object of the class for which they are members. The language does not support prefix and postfix forms of the operator.

The following binary operators can be overloaded:

// the overloadable binary operators
+, -, *, /, %, &, |, ^, <, >, <<, >>, ==, |=, <=, >=

At least one of the two parameters must be of the type of the class to which the operator belongs. The following binary operators cannot be overloaded:

&&, ||, =, ?:, +=, -=, *=, /=, %=, &=, |=, ^=, <<=, >>=

In the following operator pairs, both members must be overloaded:

(==, |=); (<, >); (<=, >=)

If we overload the equality operator but not the inequality operator, for example, the compiler flags the absence of the inequality instance as an error.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.144.94.190