Professional Documents
Culture Documents
PBO
Function Overloading
(Continuing)
function overloading provides a mechanism to create and resolve function calls to multiple
functions with the same name, so long as each function has a unique function prototype.
This allows you to create variations of a function to work with different data types, without
having to think up a unique name for each variant.
https://www.learncpp.com/cpp-tutorial/overloading-the-subscript-operator/
Overloading unary operators +, -, and !
#include <iostream>
Unlike the operators you’ve seen so far that is binary, the
positive (+), negative (-) and logical not (!) operators all are class Cents
unary operators, which means they only operate on one { Coba 1, run code ini bahas hasilnya
operand. private:
int m_cents {};
Because they only operate on the object they are applied to,
typically unary operator overloads are implemented as public:
member functions. Cents(int cents): m_cents(cents) {}
All three operands are implemented in an identical manner.
Let’s take a look at how we’d implement operator- on the // Overload -Cents as a member function
Cents class we used in a previous example: Cents operator-() const;
if (corolla != camry) bool operator!= (const Car& c1, const Car& c2)
std::cout << "a Corolla and Camry are not the same.\n"; {
return (c1.m_make != c2.m_make ||
return 0; c1.m_model != c2.m_model);
} }
Here’s a different example overloading all 6 logical comparison operators:
#include <iostream> bool operator== (const Cents& c1, const Cents& c2) int main()
{ {
class Cents Coba 4, run code return c1.m_cents == c2.m_cents; Cents dime{ 10 };
{ ini bahas hasilnya } Cents nickel{ 5 };
private:
int m_cents; bool operator!= (const Cents& c1, const Cents& c2) if (nickel > dime)
{ std::cout << "a nickel is greater than a dime.\n";
public: return c1.m_cents != c2.m_cents; if (nickel >= dime)
Cents(int cents) : m_cents{ cents } { } } std::cout << "a nickel is greater than or equal to a dime.\n";
if (nickel < dime)
friend bool operator== (const Cents& c1, const Cents& c2); bool operator< (const Cents& c1, const Cents& c2) std::cout << "a dime is greater than a nickel.\n";
friend bool operator!= (const Cents& c1, const Cents& c2); { if (nickel <= dime)
return c1.m_cents < c2.m_cents; std::cout << "a dime is greater than or equal to a nickel.\n";
friend bool operator< (const Cents& c1, const Cents& c2); } if (nickel == dime)
friend bool operator> (const Cents& c1, const Cents& c2); std::cout << "a dime is equal to a nickel.\n";
bool operator> (const Cents& c1, const Cents& c2) if (nickel != dime)
friend bool operator<= (const Cents& c1, const Cents& c2); { std::cout << "a dime is not equal to a nickel.\n";
friend bool operator>= (const Cents& c1, const Cents& c2); return c1.m_cents > c2.m_cents;
}; } return 0;
}
bool operator<= (const Cents& c1, const Cents& c2)
{
return c1.m_cents <= c2.m_cents;
}
Minimizing comparative redundancy bool operator== (const Cents& c1, const Cents& c2) bool operator== (const Cents& c1, const Cents& c2)
{ {
In the example above, note how similar the return c1.m_cents == c2.m_cents; return c1.m_cents == c2.m_cents;
implementation of each of the overloaded comparison } }
operators are.
bool operator!= (const Cents& c1, const Cents& c2) bool operator!= (const Cents& c1, const Cents& c2)
Overloaded comparison operators tend to have a high { {
degree of redundancy, and the more complex the return c1.m_cents != c2.m_cents; return !(operator==(c1, c2)); // pakai yg sdh ada
implementation, the more redundancy there will be. } }
Fortunately, many of the comparison operators can be bool operator< (const Cents& c1, const Cents& c2) bool operator< (const Cents& c1, const Cents& c2)
implemented using the other comparison operators: { {
return c1.m_cents < c2.m_cents; return c1.m_cents < c2.m_cents;
• operator!= can be implemented as !(operator==) } }
• operator> can be implemented as operator< with the
order of the parameters flipped bool operator> (const Cents& c1, const Cents& c2) bool operator> (const Cents& c1, const Cents& c2)
• operator>= can be implemented as !(operator<) { {
• operator<= can be implemented as !(operator>) return c1.m_cents > c2.m_cents; return operator<(c2, c1); // balik argumen
} }
This means that we only need to implement logic for
operator== and operator<, and then the other four bool operator<= (const Cents& c1, const Cents& c2) bool operator<= (const Cents& c1, const Cents& c2)
comparison operators can be defined in terms of those { {
two! return c1.m_cents <= c2.m_cents; return !(operator>(c1, c2)); // operasi ! Dari (c1 > c2)
} }
Here’s an updated Cents example illustrating this:
bool operator>= (const Cents& c1, const Cents& c2) bool operator>= (const Cents& c1, const Cents& c2)
This way, if we ever need to change something, we only { {
need to update operator== and operator< instead of all return c1.m_cents >= c2.m_cents; return !(operator<(c1, c2)); // operasi ! Dari (c1 < C2)
six comparison operators! } }
#include <iostream> int main()
#include <numeric> // for std::gcd Coba 6 ?? Quiz : Add the six {
class Fraction comparison operators to the Fraction f1{ 3, 2 };
{ Fraction class so that the Fraction f2{ 5, 8 };
private: following program compiles, std::cout << f1 << ((f1 == f2) ? " == " : " not == ") << f2 <<
int m_numerator{}; dan bahas hasilnya '\n';
int m_denominator{}; std::cout << f1 << ((f1 != f2) ? " != " : " not != ") << f2 << '\n';
std::cout << f1 << ((f1 < f2) ? " < " : " not < ") << f2 << '\n';
public: std::cout << f1 << ((f1 > f2) ? " > " : " not > ") << f2 << '\n';
Fraction(int numerator = 0, int denominator = 1) : std::cout << f1 << ((f1 <= f2) ? " <= " : " not <= ") << f2 <<
m_numerator{ numerator }, m_denominator{ denominator } '\n';
{ std::cout << f1 << ((f1 >= f2) ? " >= " : " not >= ") << f2 <<
// We put reduce() in the constructor '\n';
// to ensure any new fractions we make get reduced! return 0;
// Any fractions that are overwritten will need to be re-reduced }
Coba 7 Quiz : Add an overloaded operator<< and operator< to the Car class
reduce(); at the top of the lesson so that the following program compiles, dan bahas
} hasilnya
Overloading the increment (++) and decrement (--) operators is pretty straightforward, with one small exception. There are actually two
versions of the increment and decrement operators: a prefix increment and decrement (e.g. ++x; --y;) and a postfix increment and
decrement (e.g. x++; y--;).
Because the increment and decrement operators are both unary operators and they modify their operands, they’re best overloaded as
member functions. We’ll tackle the prefix versions first because they’re the most straightforward.
1. Note that we’ve distinguished the prefix from the postfix operators by providing an int dummy parameter on the postfix.
2. Because the dummy parameter is not used in the function implementation, we have not even given it a name. This tells the
compiler to treat this variable as a placeholder, which means it won’t warn us that we declared a variable but never used it.
3. Note that the prefix and postfix operators do the same job -- they both increment or decrement the object. The difference
between the two is in the value they return.
The overloaded prefix operators return the object after it has been incremented or decremented. Consequently, overloading
these is fairly straightforward. We simply increment or decrement our member variables, and then return *this.
The postfix operators, on the other hand, need to return the state of the object before it is incremented or decremented. This
leads to a bit of a conundrum -- if we increment or decrement the object, we won’t be able to return the state of the object
before it was incremented or decremented. On the other hand, if we return the state of the object before we increment or
decrement it, the increment or decrement will never be called.
The typical way this problem is solved is to use a temporary variable that holds the value of the object before it is incremented or
decremented. Then the object itself can be incremented or decremented. And finally, the temporary variable is returned to the
caller. In this way, the caller receives a copy of the object before it was incremented or decremented, but the object itself is
incremented or decremented. Note that this means the return value of the overloaded operator must be a non-reference,
because we can’t return a reference to a local variable that will be destroyed when the function exits. Also note that this means
the postfix operators are typically less efficient than the prefix operators because of the added overhead of instantiating a
temporary variable and returning by value instead of reference.
Finally, note that we’ve written the post-increment and post-decrement in such a way that it calls the pre-increment and pre-
decrement to do most of the work. This cuts down on duplicate code, and makes our class easier to modify in the future.
Overloading the subscript operator
When working with arrays, we typically use the subscript operator ([]) While this works, it’s not particularly user friendly. Consider the following
to index specific elements of an array: example:
myArray[0] = 7; // put the value 7 in the first element of the array int main()
{
However, consider the following IntList class, which has a member variable IntList list{};
that is an array: list.setItem(2, 3);
Now, whenever we use the subscript operator ([]) on an object of our class, Consider what would happen if operator[] returned an integer by value
the compiler will return the corresponding element from the m_list member instead of by reference. list[2] would call operator[], which would return
variable! This allows us to both get and set values of m_list directly: the value of list.m_list[2]. For example, if m_list[2] had the value of 6,
operator[] would return the value 6. list[2] = 3 would partially evaluate to
This is both easy syntactically and from a comprehension standpoint. When 6 = 3, which makes no sense! If you try to do this, the C++ compiler will
list[2] evaluates, the compiler first checks to see if there’s an overloaded complain:
operator[] function. If so, it passes the value inside the hard braces (in this
case, 2) as an argument to the function. C:VCProjectsTest.cpp(386) : error C2106: '=' : left operand must be l-value
Note that although you can provide a default value for the function
parameter, actually using operator[] without a subscript inside is not
considered a valid syntax, so there’s no point.
#include <iostream> Coba 12 run dan bahas hasilnya
Dealing with const objects class IntList
{
In the above IntList example,
private: int m_list[10]{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; // give this class some initial state for this example
operator[] is non-const, and we can
use it as an l-value to change the public:
state of non-const objects. However, int& operator[] (int index);
what if our IntList object was const? const int& operator[] (int index) const;
In this case, we wouldn’t be able to };
call the non-const version of
operator[] because that would allow
int& IntList::operator[] (int index) // for non-const objects: can be used for assignment
us to potentially change the state of
a const object. {
return m_list[index];
The good news is that we can define }
a non-const and a const version of
operator[] separately. The non-const const int& IntList::operator[] (int index) const // for const objects: can only be used for access
version will be used with non-const
{
objects, and the const version with
const-objects. return m_list[index];
}
int main()
{
IntList list{};
list[2] = 3; // okay: calls non-const version of operator[]
std::cout << list[2] << '\n';
In the above example, we have used the assert() function (included in the
cassert header) to make sure our index is valid. If the expression inside int main()
the assert evaluates to false (which means the user passed in an invalid {
index), the program will terminate with an error message, which is much IntList* list{ new IntList{} };
better than the alternative (corrupting memory). This is probably the most (*list)[2] = 3; // get our IntList object, then call overloaded operator[]
common method of doing error checking of this sort. delete list;
return 0;
Pointers to objects and overloaded operator[] don’t mix }
If you try to call operator[] on a pointer to an object, C++ will assume
you’re trying to index an array of objects of that type. This is ugly & error prone. Better yet, don’t set pointers to your objects if you don’t have to.
Consider the following example: Rule : Make sure you’re not trying to call an overloaded operator[] on a pointer to an object.
The function parameter does not need to be an Overloading the parenthesis operator
integer
All of the overloaded operators you have seen so far let you define the type of
As mentioned above, C++ passes what the user types between the operator’s parameters, but not the number of parameters (which is fixed
the hard braces as an argument to the overloaded function. In based on the type of the operator). For example, operator== always takes two
most cases, this will be an integer value. However, this is not parameters, whereas operator! always takes one. The parenthesis operator
required -- and in fact, you can define that your overloaded (operator()) is a particularly interesting operator in that it allows you to vary
operator[] take a value of any type you desire. You could define both the type AND number of parameters it takes.
your overloaded operator[] to take a double, a std::string, or
whatever else you like. There are two things to keep in mind: first, the parenthesis operator must be
implemented as a member function. Second, in non-object-oriented C++, the ()
As a ridiculous example, just so you can see that it works: operator is used to call functions. In the case of classes, operator() is just a
#include <iostream> Coba 14 run dan bahas hasilnya normal operator that calls a function (named operator()) like any other
#include <string>
overloaded operator.
class Stupid
An example
{
Let’s take a look at an example that lends itself to overloading this operator:
private:
public: void operator[] (const std::string& index); class Matrix
}; {
private:
// It doesn't make sense to overload operator[] to print something double data[4][4] { };
// but it is the easiest way to show that the function parameter can be a non-integer };
void Stupid::operator[] (const std::string& index)
{ Matrices are a key component of linear algebra, and are often used to do
std::cout << index; geometric modeling and 3D computer graphics work. In this case, all you need to
} recognize is that the Matrix class is a 4 by 4 two-dimensional array of doubles.
int main() Will display Hello, world! In the lesson on overloading the subscript operator, you learned that we could
{
overload operator[] to provide direct access to a private one-dimensional array.
Stupid stupid{};
However, in this case, we want access to a private two-dimensional array. Because
stupid["Hello, world!"];
operator[] is limited to a single parameter, it is not sufficient to let us index a two-
return 0;
dimensional array.
}
However, because the () operator can take as many Now, let’s overload the () operator again, this time in a way that takes no
parameters as we want it to have, we can declare a version parameters at all:
of operator() that takes two integer index parameters, and
#include <cassert> // for assert() And here’s our new example:
use it to access our two-dimensional array. Here is an
class Matrix
example of this: Coba 15 run dan bahas hasilnya { #include <iostream>
#include <cassert> // for assert() private: double m_data[4][4] {};
class Matrix public: int main()
{ double& operator()(int row, int col); {
private: double m_data[4][4] { }; double operator()(int row, int col) const; Matrix matrix{};
public: void operator()(); matrix(1, 2) = 4.5;
double& operator()(int row, int col); }; matrix(); // erase matrix
double operator()(int row, int col) const; // for const objects std::cout << matrix(1, 2) << '\n';
}; double& Matrix::operator()(int row, int col)
{ return 0;
double& Matrix::operator()(int row, int col) assert(col >= 0 && col < 4); }
{ assert(row >= 0 && row < 4);
assert(col >= 0 && col < 4); return m_data[row][col]; which produces the result: 0
assert(row >= 0 && row < 4); }
return m_data[row][col]; Persoalan :
} double Matrix::operator()(int row, int col) const Write a class that holds a string. Overload
{ operator() to return the substring that
double Matrix::operator()(int row, int col) const assert(col >= 0 && col < 4); starts at the index of the first parameter.
{ assert(row >= 0 && row < 4); The length of the substring should be
assert(col >= 0 && col < 4); return m_data[row][col]; defined by the second parameter.
assert(row >= 0 && row < 4); } Hint: You can use array indices to access
return m_data[row][col]; individual chars within the std::string
} void Matrix::operator()() Hint: You can use operator+= to append
{ something to a string
Now we can declare a Matrix & access its elements like // reset all elements of the matrix to 0.0 The following code should run:
this: for (int row{ 0 }; row < 4; ++row)
#include <iostream> { int main() Coba 17 bahas hasilnya
int main() for (int col{ 0 }; col < 4; ++col) {
{ { Mystring string{ "Hello, world!" };
Matrix matrix; m_data[row][col] = 0.0; // start at index 7 and return 5 characters
matrix(1, 2) = 4.5; } std::cout << string(7, 5) << '\n’;
std::cout << matrix(1, 2) << '\n'; } // This should print : world
}
Coba 16 run dan bahas hasilnya return 0;
return 0;
} }
which produces the result: 4,5
In the following example, we have to use getCents() to convert our Cents variable
Overloading typecasts back into an integer so we can print it using printInt():
you learned that C++ allows you to convert one data type to another. void printInt(int value) If we have already written a lot of
The following example shows an int being converted into a double: { functions that take integers as
std::cout << value; parameters, our code will be littered with
int n{ 5 }; } calls to getCents(), which makes it more
auto d{ static_cast<double>(n) }; // int cast to a double messy than it needs to be.
int main()
C++ already knows how to convert between the built-in data types. { To make things easier, we can provide a
However, it does not know how to convert any of our user-defined Cents cents{ 7 }; user-defined conversion by overloading
classes. That’s where overloading the typecast operators comes into printInt(cents.getCents()); // print 7 the int typecast. This will allow us to cast
play. std::cout << '\n'; our Cents class directly into an int. The
return 0; following example shows how this is
User-defined conversions allow us to convert our class into another } Ini cara biasa
done:
data type. Take a look at the following class:
This allows us to convert a Dollars object directly into a Cents object! This int main()
allows you to do something like this: {
Dollars dollars{ 9 };
which makes sense, since 9 dollars is 900 cents! printCents(dollars); // dollars will be implicitly cast to a Cents here
std::cout << '\n'; Coba 19 run dan bahas hasilnya
return 0;
}