Outline
Other Material
We say a term is overloaded if it has two or more meanings.
Most words in natural languages are overloaded, and confusion is resolved by means of context.
Same is true of OO languages. There are two important classes of context that are used to resolve overloaded names
A name scope defines the portion of a program in which a name can be used, or the way it can be used. Scopes are introduced using lots of different mechanisms:
An advantage of scopes is that the same name can appear in two or more scopes with no ambiguity.
Procedure Friend.sendFlowersTo (anAddress : address); begin go to florist; give florist message sendFlowersTo(anAddress); end; Procedure Florist.sendFlowersTo (anAddress : address); begin if address is nearby then make up flower arrangement tell delivery person sendFlowersTo(anAddress); else look up florist near anAddress phone florist give florist message sendFlowersTo(anAddress) end;
This type of overloading is resolved by looking at the type of the receiver.
Allows the same name to be used in unrelated classes.
Since names need not be distinct, allows short, easy to remember, meaningful names.
A different type of overloading allows multiple implementations in the same scope to be resolved using type signatures.
class Example { // same name, three different methods int sum (int a) { return a; } int sum (int a, int b) { return a + b; } int sum (int a, int b, int c) { return a + b + c; } }
A type signature is the combination of argument types and return type. By looking at the signature of a call, can tell which version is intended.
Note that resolution is almost always performed at compile time, based on static types, and not dynamic values.
class Parent { ... }; class Child : public Parent { ... }; void Test(Parent * p) { cout << "in parent" << endl; } void Test(Child * c) { cout << "in child" << endl } Parent * value = new Child(); Test(value);
Example will, perhaps surprizingly, execute parent function.
Stream output in C++ is a good example of the power of overloading. Every primitive type has a different stream output function.
ostream & operator << (ostream & destination, int source); ostream & operator << (ostream & destination, short source); ostream & operator << (ostream & destination, long source); ostream & operator << (ostream & destination, char source); ostream & operator << (ostream & destination, char * source); // ... and so on double d = 3.14; cout << "The answer is " << d << '\n';
Since output uses overloading, it is very easy to extend to new types.
class Fraction { public: Fraction (int top, int bottom) { t = top; b = bottom; } int numerator() { return t; } int denominator() { return b; } private: int t, b; }; ostream & operator << (ostream & destination, Fraction & source) { destination << source.numerator() << "/" << source.denominator(); return destination; } Fraction f(3, 4); cout << "The value of f is " << f << '\n';
When one adds conversions into the mix, resolving overloaded function or method calls can get very complex. Many different types of conversions:
See text for illustration of the complex rules.
A redefinition occurs when a child class changes the type
signature of a method in the parent class.
Two different types of rules are used to resolve name:
The following example will illustrate the difference in these two models.
class Parent { public void example (int a) { System.out.println("in parent method"); } } class Child extends Parent { public void example (int a, int b) { System.out.println("in child method"); } } Child aChild = new Child(); aChild.example(3);
Will execute parent method in Java and C# (Merge model) and give error in C++ (hierarchical model). Delphi allows programmer control over this.
Some languages allow the programmer to create optional parameters, usually only at the end of the parameter list:
function Count (A, B : Integer; C : Integer = 0; D : Integer = 0); begin (* Result is a pseudo-variable used *) (* to represent result of any function *) Result := A + B + C + D; end begin Writeln (Count(2, 3, 4, 5)); // can use four arguments Writeln (Count(2, 3, 4)); // or three Writeln (Count(2, 3)); // or two end
Such a program will have more than one type signature.
The language C# has an interesting way to include arbitrary number of arguments:
class ParamsExample { public void Write (int x) { // use this with one argument WriteString("Example one "); WriteString(x.ToString()); } public void Write (double x, int y) { // use this with two arguments WriteString("Example two "); WriteString(x.ToString()); WriteString(y.ToString()); } public void Write (params object [ ] args) { // use this with any other combination WriteString("Example three "); for (int i = 0; i < args.GetLength(0); i++) WriteString(args[i].ToString()); } } | ParamsExample p; p.Write(42); Example one 42 p.Write(3.14,159); Example two 3.14159 p.Write(1,2,3,4); Example three 1234 p.Write(3.14); Example three 3.14 p.Write(3,"abc"); Example three 3abc |
In this chapter we have looked at various aspects of overloading