Handout 3 - Operator Overloading

  • November 2019
  • PDF

This document was uploaded by user and they confirmed that they have the permission to share it. If you are author or own the copyright of this book, please report to us by using this DMCA report form. Report DMCA


Overview

Download & View Handout 3 - Operator Overloading as PDF for free.

More details

  • Words: 3,013
  • Pages: 11
1

Mekelle University Faculty of Business & Economics Computer Science Department ICT122: Object-Oriented Programming Handout 3 – Operator Overloading

Handout Overview This handout covers the topic of operator overloading. The concept of an operator is defined, then it is shown how to redefine (overload) the standard C++ operators to perform user-defined functions. The this pointer is described, and its relevance and usefulness to operator overloading described. 1. What is an Operator? Operators available in C++ include +, -, *, /, ++, -- and the array subscript operator []. An operator is similar to a function: it takes an argument or arguments and returns a value as its result. However, the way in which operators are used differs from the way functions are used. For example, if we were to write a normal C++ function plus to perform addition, it would have to be used as follows: x = plus (y, 2); However, using the built-in addition operator, we can perform the same function as follows: x = y + 2; In other words, the operator symbol appears between the two arguments, instead of before them. The +, -, * and / operators are all binary operators. Binary operators take two arguments and return a single value. The ++ and -- operators are unary operators: this means that they take a single argument and return a single value. 2. What is Operator Overloading? The built-in operators in C++ are defined only for the simple data types, i.e. int, char, float, long int, short, double. This means that if we define a new class, it will not be possible to use these operators with objects of our new class. For example, suppose we define a new class called Rational, to store and perform calculations with rational numbers (i.e. numbers that can be written as the ratio of two

2 integers, for example 3/7). It would be nice if we could make use of the built-in arithmetic operators to perform these calculations, as in the following code: Rational r1 (2, 5); //define rational number 2/5 Rational r2 (5, 7); //define rational number 5/7 Rational r3; r3 = r1 * r2; //use built-in multiply operator However, since the built-in operators are only defined for the built-in simple data types, this is not possible. Operator overloading is a way of allowing the use of built-in operators for classes that you have written yourself. 3. Example – A Rational Number Class As an example we will present an implementation of a rational number class. The code listed below is divided into 3 source files: “Rational.h” contains the class definition for the Rational class; “Rational.cpp” contains the member function bodies; and “RationalTest.cpp” contains a main function that reads in two rational numbers, and displays their product and sum. Look first at the class definition in “Rational.h”. To simplify the code, all data members and member functions have been made public, so we do not need to worry about information hiding in this example. The class contains just two data members, representing the numerator and denominator of the rational number. There are two constructors: the default constructor that will be used if no arguments are supplied at declaration; and a second constructor that will be invoked if the programmer provides two integer arguments when they declare the object. For example, the declarations of r1 and r2 in the main function will use this second constructor. The Rational class includes a single member function to display the number to the screen. The “Rational.h” file also contains the function prototypes for the two overloaded operator functions (for the + and * operators). Notice first that they do not need to be member functions of the class. Secondly, notice the names of the functions: you always define an overloaded operator function using the word operator followed by the symbol for the operator you want to overload, e.g. operator+ for the addition operator, operator[] for the array subscript operator, etc. The addition and multiplication operators are both binary operators; therefore both overloading functions take two arguments, both of type Rational. Since the arguments should not be modified as a result of the operation, both are specified as const arguments. They are defined as const reference parameters (i.e. const Rational& instead of const Rational) for efficiency reasons. (If they were value parameters, then copies of the objects would be created every time the operator was used.) Finally, both functions return a single value of type Rational.

3 The function bodies in “Rational.cpp” are fairly straightforward. The overloaded operator functions perform the simple calculation necessary for the addition and multiplication of rational numbers. Now in the main function in “RationalTest.cpp” you can see that we have used the + and * operators with objects of the Rational class, which would not normally have been possible without operator overloading. This helps to make the code easier to read and understand. ”RationalTest.cpp” #include #include "Rational.h" main () { int d1, d2, n1, n2; cout << "Enter numerator then denominator for 1st number:\n"; cin >> n1 >> d1; cout << "Enter numerator then denominator for 2nd number:\n"; cin >> n2 >> d2; Rational r1 (n1, d1); Rational r2 (n2, d2); cout << "Numbers:" << endl; r1.Print(); r2.Print(); Rational r3 = r1 * r2; //use overloaded multiply operator cout << "Product = "; r3.Print(); Rational r4 = r1 + r2; //use overloaded addition operator cout << "Sum = "; r4.Print(); } “Rational.h” class Rational { public: //constructors Rational (); //default constructor Rational (int, int); //initialising constructor //data members int numerator; int denominator; //member functions void Print () const; }; Rational operator+ (const Rational&, const Rational&); Rational operator* (const Rational&, const Rational&);

4 “Rational.cpp” #include #include "Rational.h" //default constructor Rational::Rational() { numerator = 0; denominator = 0; } //constructor that initialises values Rational::Rational(int n, int d) { numerator = n; denominator = d; } void Rational::Print() const { cout << numerator << "/" << denominator << endl; } Rational operator+ (const Rational& v1, const Rational& v2) { // returns value of (a/b) + (c/d) int a = v1.numerator; int b = v1.denominator; int c = v2.numerator; int d = v2.denominator; return Rational (a*d + b*c, b*d); } Rational operator* (const Rational& v1, const Rational& v2) { // returns value of (a/b) * (c/d) int a = v1.numerator; int b = v1.denominator; int c = v2.numerator; int d = v2.denominator; return Rational (a*c, b*d);

} 4. Overloading the Assignment Operator The assignment operator, unlike most other operators, is already defined for classes that you define yourself. For example, if we use the assignment operator on objects of the Rational class defined above, it will work fine. Whenever you use the assignment operator on a class you have defined yourself it will perform an assignment on each data member individually, therefore so long as assignment is defined for the types of the data member of the class, you can assign objects of the class to each other using the = operator. However, such member-by-member assignment is not appropriate for classes that have one or more pointers as data members. Recall that if you use assignment on pointer variables it simply makes both pointers point to the same object; it does not make a separate copy of the object (see Figure 1). Similarly, if a class contains a

5 pointer data members (for example a char*), then assignment of objects of this class will not make a separate copy of the data that the pointer points to; it will just make both data members point to the same data. Modification of one object will therefore lead to modification of the other.

Figure 1 – Assignment with pointer variables: (a) initial state; (b) after assignment y = x; To avoid the problem described above, it is necessary to overload the assignment operator so that separate copies of pointer data members are made. 4.1. A Simple Example To illustrate overloading of the assignment operator, we will consider a simple example. The code in the file “Person.cpp” listed below defines a class to store some basic data about people: their name, their age and their height. The Person class includes an empty default constructor, and a second constructor that initialises the values of the three data members. Note that one of the data members, name, is a pointer type (char*), so the data that it points to is allocated using the new statement. The last member function of Person is the overloading of the assignment operator. Notice the type of this function: Person& operator= (const Person& p) The assignment operator is a binary operator (it takes two arguments). However, it has to change the value of one of these arguments (the one on the left of the assignment). Whenever you overload an operator that changes one of its arguments the overloaded operator function must be a member function of the class. In the statement above, the single argument of the operator= function represents the right side of the assignment operator, and the Person object itself is the left side of the assignment. Remember that every statement in C++ returns a value: whenever you write an assignment statement in C++ the statement returns the value of the left side of the operator. For example the statement x = y;

6 returns the value of x after the assignment. This is useful because it makes statements such as x = y = z; valid. First z is assigned to y, and the value returned by the statement is y; next y is assigned to x, and the value returned is x. Because we want the assignment statement to return a value, the return type of the operator= function is Person& (i.e. a reference to a Person). The body of the operator= function uses the strcpy function to copy the string pointed to by the name variable into the current object. The other two data members can be assigned directly as they are not pointers. “Person.cpp” #include //class definition class Person { public: //data members char *name; int age; float height; //member functions Person() {}; //default constructor Person (char *n, int a, float h) { name = new char[strlen(n) + 1]; //create space for name strcpy (name, n); //copy name age = a; //copy age height = h; //copy height } void Print() const { cout << "Name =" << name << endl; cout << "Age =" << age << endl; cout << "Height=" << height << endl; } Person& operator= (const Person& p) { strcpy (name, p.name); age = p.age; height = p.height; return *this; } }; //main program main () { Person x("Solomon", 26, 1.78); Person y; y = x; strcpy (y.name, "Frehiwot"); //only change name of person y x.Print(); y.Print(); }

7 4.2. The this Pointer The final line of the operator= function requires some further explanation: return *this; Remember that each object of a class has its own copies of each of the data members, but there is only one copy of each of the member functions. When a member function is being executed, it knows which object it is operating on by means of a special system pointer, called the this pointer. Within the body of any member function, this always points to the current object of that class. When you compile your C++ code, the compiler will always precede each reference to a class data member by this->, to indicate precisely which instance of the data member is referred to. As a programmer, you can also make use of the this pointer. In fact, if you wanted to, you could always precede data members by this->. For example you could write the Print() function of the Person class as void Print() { cout << "Name =" << this->name << endl; cout << "Age =" << this->age << endl; cout << "Height=" << this->height << endl; } This code would compile and execute correctly. However, this is unnecessary since the compiler will do this for you. There is one case in which you need to make use of the this pointer, and that is when you are overloading the assignment operator. As explained above, the return value of an assignment statement should always be the value on the left side of the operator. When overloading the assignment operator, the left side of the operator is the current object, therefore the overloaded operator= function should return the value of the current object. The only way for a programmer to access this value is by using the this pointer. If this always points to the current object, then *this is the value of the current object (* dereferences the pointer). Read through the “Person.cpp” code and make sure you understand it. It may also help if you type the code in (or copy it from the FBE network) and run the program. Try deleting (or commenting out) the overloaded operator= function, and see what difference it makes to the output of the program. 5. Rules for Operator Overloading Operator overloading can be a very useful tool when you are developing reasonably complex classes in C++. You can overload (almost) any of the built-in operators in C++. These are listed in Table 1 below.

8 Operator

Meaning

Operator

Meaning

+ * / ++ -= () [] -> % || |

Addition Subtraction Multiplication Division Increment Decrement Assignment Function call Array subscript Indirect member Modulo Logical OR Bitwise OR

&& ! != > >= < <= == += -= *= /=

Logical AND Not Not equal to Greater than Greater than or equal to Less than Less then or equal to Equal to Add to Subtract from Multiply by Divide by

Table 1 – Operators that can be overloaded Most overloaded operators should be defined outside of the class, i.e. they should not be member functions of the class. However, remember that if the data members they need to access are protected or private they should be made friends of the class to gain access to the required data members. Also, the following operators must always be made members of the class: = () [] -> In addition, operators that change the values of one of their arguments are normally made member functions of the class, e.g. += -= *= /= ++ -Table 2 summarises the function prototypes necessary to overload some of the more common operators. The fact that the << and >> operators can be overloaded is the main reason why C++ introduced the new cin and cout statements. Although the scanf and printf statements provided by the C language work perfectly well for most input/.output tasks, they can not be overloaded in the way that the << and >> operators can. When overloading operators, you can never change the unary/binary nature of the operator. For example, the < operator is a binary operator (it takes two arguments). Therefore if you overload the < operator you must define it as a binary operator. However, remember that the + and – operators have both unary and binary forms, e.g. the following statements are all valid in C++: x = y + 2; //binary addition operator x = +2; //unary addition operator x = y – 2; //binary subtraction operator x = -y; //unary subtraction operator Finally, recall that there are built-in rules for operator precedence in C++: for example the * operator will always be evaluated before the + or – operators. Remember that it is never possible to override these predefined operator precedence rules.

9

Operator s

Function Prototype

Class Member?

+ * / = << >> += -= *= /= == > >= < <= []

Type operator+ (const Type&, const Type&) Type operator- (const Type&, const Type&) Type operator* (const Type&, const Type&) Type operator/ (const Type&, const Type&) Type& operator= (const Type&) ostream& operator<< (ostream&, const Type&) istream& operator>> (istream&, Type&) Type& operator+= (const Type&) Type& operator-= (const Type&) Type& operator*= (const Type&) Type& operator/= (const Type&) int operator== (const Type&, const Type&) int operator> (const Type&, const Type&) int operator>= (const Type&, const Type&) int operator< (const Type&, const Type&) int operator<= (const Type&, const Type&) Type& operator[] (int)

N N N N Y N N Y Y Y Y N N N N N Y

Table 2 – Overloaded function prototypes for common operators

10 Summary of Key Points • • • • •

• • • • • • •

A unary operator is one that takes a single argument, e.g. x++ A binary operator is one that takes two arguments, e.g. a * b Most standard C++ operators (e.g. +, -, %, []) are not defined for userdefined classes. However, these operators can be overloaded to perform user-defined functions in a class. When you overload an operator that does not change the value of one of its arguments, the overloaded function should not be a member function of the class. However, it may need to be a friend of the class if it needs to access private or protected members. Whenever you overload an operator that changes the value of one of its arguments (e.g. [], =), it should be a member function of the class. The assignment operator is defined for user-defined classes: it will perform member-by-member assignment If you do not want the assignment operator to perform member-by-member assignment (e.g. if you have pointer data members), you can overload the assignment operator. The this pointer always points to the current object of a class. When overloading an operator that changes the values of an argument, the overloaded function should return a reference to the current object (the this pointer should be used for this purpose). When overloading operators, you can never change the unary/binary nature of the operator. You cannot change the built-in rules for operator precedence.

Note: The full source code listings for the examples in this handout can be found on the FBE network server: to access them, open My Network Places, double-click on MU-FBE, then FBE-SERVER and then browse to: Courses\ICT122 – Object-Oriented Programming\src\Handout 3 Notes prepared by: FBE Computer Science Department.

11

Related Documents

C++ Operator Overloading
April 2020 13
Operator
May 2020 27
Method Overloading
November 2019 9