Essential C++ # 04. Object-Based Programming
Chapter 4. Object-Based Programming
📌What should a class consist of?
A class consists of 2 parts:
public
set of operations and operators (also called member functions which represents the public interface of the class)private
implementation
📌Why Class is important to C++?
Designing and implementing classes are the primary activities of C++ programmers.
4.1. Implement a Class
The following section will include some codes which mimic the behavior of stack(computer).
📌Class definition in C++
x/********************************/
/************Stack.h*************/
/********************************/
using namespace std;
class Stack
{
private:
vector<string> _stack;
public:
bool push(const string& );
bool pop(string &elem);
bool peek(string &elem);
bool empty();
bool full();
int size() {return _stack.size()};
};
Things to noticed:
1️⃣ all functions must be declared inside public
if you want to use with an instance. e.g. bool push/pop/peek/empty/full
2️⃣ functions declared+defined inside the class definition is inline
function. e.g. int size()
📌Implement Class Definition
For those non-inline member functions, they are defined within a program text file. Normally suffix with .cpp, .c, .cc
xxxxxxxxxx
inline bool
Stack::empty()
{
return _stack.empty();
}
bool
Stack::pop(string &elem)
{
if(empty())
{
return false;
}
elem = _stack.back();
_stack.pop_back();
return true;
}
inline bool
Stack::full()
{
return _stack.size() == _stack.max_size();
}
bool
Stack::peek(string &elem)
{
if(empty())
{
return false;
}
elem = _stack.back();
return true;
}
bool
Stack::push(const string &elem)
{
if(full())
{
return false;
}
_stack.push_back(elem);
return true;
}
The preceding syntax ::
is called the class scope operator.
📌Conclusion of .h
and .cpp
Header file .h
contains:
- the definition of the class
- the declaration of
public
andprivate
functions - if the function is also defined here, it is
inline
function
Program text file .cpp
contains:
- implement the function declare in
.h
- use
inline
keyword the specify the function to be aninline
function
4.2. Constructor and Destructor
📌Regular Constructor in C++
Suppose we have a Person
class. (eheww... Again it is Person
class🙂)
xxxxxxxxxx
class Person
{
private:
public:
int id;
string name;
Person(); //default constructor
Person(string _name); //constructor with 1 variable
Person(string _name, int _id); //constructor with 2 variables
};
📌Default Constructor
From my perspective, I think there are 2 ways of default constructors.
1️⃣No arguments, pure pure pure default constructor.
2️⃣Constructor with arguments with default values
xxxxxxxxxx
1️⃣
/*******Person.h*******/
Person();
/******Person.cpp******/
Person::Person()
{
name = "Eric";
id = 4;
}
2️⃣
/*******Person.h*******/
Person(string _name = "Ada", int _id = 0);
/******Person.cpp******/
Person::Person(string _name, int _id)
{
id = _id;
name = _name;
}
📌Unusual Initialization
For a C# programmer, the following initialization is kind of...🤣
xxxxxxxxxx
/*******Person.h*******/
Person(int _id);
/******Person.cpp******/
Person::Person(int _id)
{
id = _id;
}
/*******main.cpp******/
Person person5 = 9; //WTF!! 😲
cout << "Person 5 Id: " << person5.id << endl;
// OUTPUT: "Person 5 Id: 9"
📌Member Initialization List
Suppose you have the following:
xxxxxxxxxx
class Triangle
{
private:
string _name;
int _id, _length;
public:
Triangle(int len, int id); // Here!✋
Triangle(const Triangle &rhs); // Here!✋
~Triangle();
};
You can use member initialization list to construct as followed:
xxxxxxxxxx
// version of assigning variable
Triangle::Triangle(int len, int id)
: _name("new"), _id(id), _length(len)
{
}
// version of pass by reference
Triangle::Triangle(const Triangle &rhs)
: _name(rhs._name), _id(rhs._id), _length(rhs._length)
{
}
📌Why do we need Member Initialization List?
OK, now you can smell a bit about member initialization list. But why should we use it?🤔
xxxxxxxxxx
// ver1
Triangle::Triangle(int len, int id)
: _name("new"), _id(id), _length(len)
{
}
// ver2
Triangle::Triangle(int len, int id)
{
_name = "new";
_length = len;
_id = id;
}
What is the difference in the preceding codes??🤔😵
The difference is that ver1
only initialize once!!! Therefore, you should use member initialization list for performance benefits sake!
xxxxxxxxxx
/******License.cpp******/
License::License()
{
cout << "Init a license..." << endl;
}
License::License(int _order)
{
order = _order;
cout << "Init a license with Id: " << order << endl;
}
The preceding code I make 2 constructors. And suppose License
instance is a member of Person
.
1️⃣Not using member initialization list❌
xxxxxxxxxx
Person::Person(string _name, int _id)
{
id = _id;
name = _name;
license = License(23);
}
The output:
xxxxxxxxxx
Person person1("Tim", 3);
// OUTPUT
// Init a license...
// Init a license with Id: 23
You see the License
instance is init once in the member list and init second times in the Person
constructor.
2️⃣Using member initialization list✔
xxxxxxxxxx
Person::Person(string _name, int _id)
: license(License(23))
{
id = _id;
name = _name;
}
The output:
xxxxxxxxxx
Person person1("Tim", 3);
// OUTPUT
// Init a license with Id: 23
📌Use of Destructor
The primary use of a destructor is to free resources. The destructor cannot be reloaded!
xxxxxxxxxx
class Matrix
{
private:
int _row, _col;
double *_pmat;
public:
Matrix(int row, int col);
~Matrix();
};
Matrix::Matrix(int row, int col)
: _row(row), _col(col)
{
// constructor allocates a resource
_pmat = new double[row * col];
}
Matrix::~Matrix()
{
// destructor frees the resource
delete [] _pmat;
}
📌When should we use destructor?
When the data members are store by value, there is no need to use destructor.
📌Difference of class between C++ and C#⭐
Everything works fine here. But!⚠⚡ The class is so heck different from class in C#.
xxxxxxxxxx
// class in C++ 🔵
Person person1("Tim", 3);
Person person2 = person1; // here pass by value
person1.id = 13;
cout << "Person 2 ID: " << person2.id << endl;
// OUTPUT would be 3!!
The preceding code demonstrates that the class
in C++ is not "reference type" in C#. The following is C#.
xxxxxxxxxx
// Class in C# 🟣
Person person = new Person() { Name = "Tim", Id = 3 };
Person person1 = person; // here pass by reference
person1.Id = 13;
Console.WriteLine(person.Id);
// OUTPUT would be 13!!
📌The Mechanism Behind the Difference between C++ and C#
In the preceding code, we are realized that the class is copy by value. (Yes and No...) The truth is that only the "value type" are pass by value!
xxxxxxxxxx
// Taking the preceding Matrix class as example...
Matrix mat1(4, 4);
{
// default constructor, member-wise copy here
Matrix mat2 = mat1;
// suppose you use mat2 here..
// mat2 destructor applied here before leaving the bracket
// destruct the pointer as well!!
}
// use mat1 here...
// mat1 destructor applied here...
The line Matrix mat2 = mat2;
indicates that:
xxxxxxxxxx
mat2._pmat = mat._pmat; // 2 instance of _pmat address the same array in the heap memory
⚠⚠⚠!!! Serious shit happens here!!! When the destructor applied to mat2
, the _pmat
is deallocated and mat1
can't use it!!
📌Define a Full Copy Constructor
xxxxxxxxxx
Matrix::Matrix(const Matrix &rhs)
: _row(rhs._row), _col(rhs._col)
{
// create a "deep copy" of the array addressed by rhs._pmat
int elem_cnt = _row * _col;
_pmat = new double[elem_cnt];
for (int ix = 0; ix < elem_cnt; ix++)
{
_pmat[ix] = rhs._pmat[ix];
}
}
📌No Absolute Correct Way Designing a C++ Class
In the preceding, you see that C++ class are with more freedom to do so. While in C#, the instance is only reference type. When we design a class, we must ask ourselves whether the default member-wise behavior is adequate for the class?
- If yes🙂, we need not provide an explicit copy constructor.
- If not🙁, we MUST define an explicit instance and within it implement the correct initialization semantics.
4.3. mutable
and const
📌const
in Function Declaration
In short, a const
function ensures the body code would NOT CHANGE the data member of the class.
📌Example of const
and non-const
Function
Suppose you have a class, the class is sort of storing data members. And you can maintain and read the data inside of it by next
. (You know what it is..)
xxxxxxxxxx
/**************Triangular.h**************/
using namespace std;
class Triangular
{
private:
int _length; // number of elements
int _begin_pos; // beginning position of range
int _next; // next element to iterate over
static vector<int> _elems; // static vector to store the elments
public:
//✋ const member functions
int length() const {return _length;} // return the length of _elems
int begin_pos() const {return _begin_pos;} // return the _begin_pos
int elem(int pos) const; // query the pos(th) element in _elems
//✋ non-const member functions
bool next(int &val);
void next_reset() {_next = _begin_pos - 1};
Triangular();
~Triangular();
};
OK, let's take a look here.
📌How does the Compiler check if it ACTUALLY is const
?
In short, the compiler will look at the function which is decorated with const
. And check every functions inside that function if it is decorated with const
....
This is a
const
function and it can be compiled.✔ Because every member function here isconst
.
xxxxxxxxxx
int sum(const Triangular &trian)
{
int beg_pos = trian.begin_pos();
int length = trian.length();
int sum = 0;
for (int ix = 0; ix < length; ix++)
{
sum += trian.elem(beg_pos + ix);
}
return sum;
}
This is NOT a
const
function and it cannot be compiled.❌Because it modifies the member.
xxxxxxxxxx
bool Triangular::next(int &value) const
{
if (_next < _begin_pos + _length - 1)
{
//ERROR: modifying _next
value = _elems[next++];
return true;
}
return false;
}
📌Class Designer MUST consider the "constness"
The class designer must tell the compiler by labeling as const
each member function that does not modify the class object.
📌const
keyword should be both in declaration and definition
Please notice that the const
is appear in both .h
and .cpp
files.
xxxxxxxxxx
/**************Triangular.h**************/
class Triangular
{
private:
// data member
// .....
public:
// const member functions
// ...
int elem(int pos) const;
// non-const member functions
// ......
};
/*************Triangular.cpp*************/
int Triangular::elem(int pos) const
{
return _elems[pos - 1];
}
📌A Complicated Example
The following code will be compiled with errors.
xxxxxxxxxx
class val_class
{
private:
BigClass _val;
public:
val_class(const BigClass &v)
: _val(v){}
BigClass& val() const {return _val;} // ERROR!!
};
class BigClass
{
};
TODO - Understand why.
The correct one should be this:
xxxxxxxxxx
const BigClass& val() const {return _val;}
📌What is mutable
?
In English, mutable
is defined as:
mutable: able or likely to change
In C++, we decorate member acted as iterator to mutable
. For example, the _next
variable. Therefore, the Triangular
class can be modified as followed:
xxxxxxxxxx
// OLD
int _next;
// NEW
mutable int _next;
With decorated mutable
, the const
function now can be compiled with no error!✔ (even though the _next
is modified...)
4.4. this
Pointer
📌What is this
keyword?
In C#, this
refer to the current instance of such class. There is no difference in C++. It also points to the current instance. Suppose we have a deep copy function to copy
the Triangular
object.
xxxxxxxxxx
Triangular& Triangular::copy(const Triangular &rhs)
{
// ⭐ when you copy a class object with another, it is a good practice to
// first check that 2 objects are not the same
if (this != &rhs)
{
this->_length = rhs._length;
this->_begin_pos = rhs._length;
this->_next = rhs._next;
}
return *this;
}
Few things need to be noticed: different meanings of &
- 1️⃣
Triangular&
in the return type means the function is to return who else called this function.⭐⭐⭐ The&
here means pass the object invoked by reference.1 - 2️⃣
&rhs
in the function declaration means therhs
is pass by reference (speed up) - 3️⃣
&rhs
in the control flow means taking the address ofrhs
and compared withthis
.
📌Other Examples
1️⃣this
cannot be assigned🙅♂️
The following method is designed to change current object to point to d
object. It is with ERROR.❌
xxxxxxxxxx
void Dog::change(Dog &d)
{
if (this != &d)
{
this = &d; // ERROR!
}
}
2️⃣this
cannot be used in static
function🙅♂️
The this
can only exist in an instance.(class object)
xxxxxxxxxx
static void fun2()
{
cout << "Inside fun2()";
this->fun1(); // ERROR!!
}
3️⃣cannot use after delete
🙅♂️
You can release manually by calling a function. (like GC
in C#)
xxxxxxxxxx
class Point
{
private:
public:
void destroy() { delete this; }
void print() { cout << "Hello World!" << endl; }
};
int main()
{
Point obj;
obj.destroy();
obj.print(); // ERROR!!
return 0;
}
4️⃣Don't forget to &
return type if you want to modify itself
The following is a bad example.
xxxxxxxxxx
class Point
{
private:
int x, y;
public:
Point (int x = 0, int y = 0) { this->x = x; this->y = y; }
Point setX(int a) { x = a; return *this; }
Point setY(int b) { y = b; return *this; }
void print() { cout << "x = " << x << " y = " << y << endl; }
};
int main()
{
Point obj1;
obj1.setX(10).setY(20);
obj1.print();
return 0;
}
The result is:
xxxxxxxxxx
x = 10 y = 0
But if we modify the function to:
xxxxxxxxxx
Point& setX(int a) { x = a; return *this; }
Point& setY(int b) { y = b; return *this; }
The output is correct:
xxxxxxxxxx
x = 10 y = 20
4.5. static
Class Member
📌static
in C++
The declaration and definition of static
functions and members(fields) are both explicitly and separately.
Suppose you have a member static vector<int>
and a function static bool is_elem(int)
:
xxxxxxxxxx
/******Triangular.h******/
class Triangular
{
private:
// data member
static vector<int> _elems;
public:
// ...
static bool is_elem(int);
};
The preceding is just the declaration. You have to define it:
1️⃣Either in Triangular.h
like so:
xxxxxxxxxx
// field
vector<int> Triangular::_elems;
// function
bool Triangular::is_elem(int ival)
{
//.. here is the function body
}
2️⃣Or in Triangular.cpp
like so:
xxxxxxxxxx
// field
vector<int> Triangular::_elems;
// function
bool Triangular::is_elem(int ival)
{
//.. here is the function body
}
📌static
stuffs example
It's very much the same as with C#.
xxxxxxxxxx
/******Triangular.h*****/
class Triangular
{
private:
// ...other members
static vector<int> _elems;
static int _start_pos;
public:
// ...other functions
static bool is_elem(int);
// ...
Triangular();
~Triangular();
};
/*****Triangular.cpp*****/
vector<int> Triangular::_elems;
bool Triangular::is_elem(int pos)
{
// definition
// ...
}
You can use it as the following:
xxxxxxxxxx
int ival;
cout << "Please enter a number: " << endl;
cin >> ival;
// example using static member
ival = ival > Triangular::start_pos ? ival : Triangular::start_pos;
// example using static function
bool is_elem = Triangular::is_elem(ival);
📌static
in C++ and C#
It is interesting to see the common and difference between these 2 languages.
C++ | C# | |
---|---|---|
Declaration | in class | in class |
Definition | must outside of class declaration(outside of the class {} ) | can also in class declaration |
Access Modifier | :: | . |
4.6. Iterator Class
Finally! We are going to implement our own iterator class!
📌User Story on Iterator
Iterate the Triangular
sequence using iterator.
xxxxxxxxxx
Triangular trian(1, 8);
Triangular::iterator
it = trian.begin(),
end_it = trian.end();
while( it != end_it)
{
cout << *it << ' ';
++it;
}
📌First Look on Iterator
xxxxxxxxxx
class Triangular_iterator
{
private:
void check_integrity() const;
int _index;
public:
Triangular_iterator(int index)
: _index(index - 1) {}; // set index - 1, therefore no need to -1 everytime using it
// operator overloading
bool operator==(const Triangular_iterator&) const;
bool operator!=(const Triangular_iterator&) const;
int operator*() const;
int& operator++(); // prefix version
int operator++(int); // postfix version
~Triangular_iterator();
};
//TODO What is operator*
exactly?
📌Equality and Inequality as an Example
1️⃣ Let's implement the ==
operator first:
The _index
is exactly for checking equality. Therefore, the code could be something like the following:
xxxxxxxxxx
inline bool Triangular_iterator::operator==(const Triangular_iterator& rhs) const
{
bool flag = this->_index == rhs._index;
return flag;
}
For a better format and indentation:
xxxxxxxxxx
inline bool Triangular_iterator::
operator==(const Triangular_iterator& rhs) const
{
bool flag = this->_index == rhs._index;
return flag;
}
The this
pointer implicitly represents the left operand.
2️⃣ The following is the example of how to use it
xxxxxxxxxx
// for class object
if (trian1_it == trian2_it) ...
// for pointer
if (*ptr1_it == *ptr2_it) ...
Why the pointers require dereference?🤔 Please take a look at the function declaration! It's because the type is Triangular_iterator
!
xxxxxxxxxx
inline bool Triangular_iterator::operator==(const Triangular_iterator& rhs) const;
3️⃣ Implement "inequality"
The complement of an operator is typically implemented with its associated operator2.
xxxxxxxxxx
inline bool Triangular_iterator::
operator!=(const Triangular_iterator&rhs) const
{
return !(*this == rhs); // smart move
}
You see? It's so simple! 😍 Because in *this == rhs
means:
- dereference current object pointer
- since lhs and rhs are both object
- apply
==
operator - use
!
to take its complement
📌Things to Notice when Overloading Operators🙅♂️
1️⃣There are 4 operators that can't be overloaded:
.
.*
::
?:.
2️⃣The arity of existing operator can't be overloaded
For example, if you overloaded ==
operator, then you must put 2 operands.
3️⃣ At least 1 class type as argument
We cannot refine operators for nonclass types, e.g. pointers.
4️⃣ The precedence can't be overwritten
e.g. /
always takes precedence over +
📌Difference between member operator and non-member operator
Member operator function:
xxxxxxxxxx
inline int Triangular_iterator::
operator*()const
{
check_integrity();
return Triangular::_elems[_index];
}
Nonmember operator function:
xxxxxxxxxx
inline int
operator* (const Triangular_iterator &rhs)
{
rhs.check_integrity();
return Triangular::_elems[rhs._index];
}
//TODO - figure this out
📌Overload prefix and postfix
It means implementing:
- prefix -
++it
- postfix -
it++
prefix:
xxxxxxxxxx
inline int& Triangular_iterator::
operator++()
{
// prefix instance
++_index;
check_integrity();
return Triangular::_elem[_index];
}
postfix:
xxxxxxxxxx
inline int Triangular_iterator::
operator++(int)
{
// post instance
check_integrity();
return Triangular::_elems[_index++];
}
How does it work?
- For prefix, the
_index
is implemented before accessing the_elem
- For postfix, the
_index
is implemented after accessing the_elem
Please take a look the operator declaration:
xxxxxxxxxx
operator++() // prefix
operator++(int) // postfix
The prefix has no parameter, postfix has parameter. Why?🤔 Because each overloaded operator MUST have a unique parameter list. The single int
in postfix will be handled by the compiler. So no worries.
//TODO figure out why there should be a int&
in prefix.
📌Nested Types
It uses typedef
:
xxxxxxxxxx
typedef existing_type new_name;
The existing_type
can be built-in, compound, or class type. We can take advantage of this to implement the last piece of our iterator
.
xxxxxxxxxx
using namespace std;
class Triangular
{
private:
int _length;
int _begin_pos;
// ...
public:
// this shields users from having to know
// the actual name of the iterator class
typedef Triangular_iterator iterator;
Triangular_iterator begin() const
{
return Triangular_iterator(_begin_pos);
}
Triangular_iterator end() const
{
return Triangular_iterator(_begin_pos + _length);
}
// ...
};
With the preceding implementation, we could access Triangular_iterator
of Triangular
object simply by iterator
.
✔
xxxxxxxxxx
Triangular::iterator it = trian.begin();
❌
xxxxxxxxxx
iterator it = trian.begin(); // ERROR!!
Because we typedef
in Triangular
class, therefore you have to use the class scope operator ::
to access it. And that's the reason and mechanism behind the container class.
xxxxxxxxxx
Fibonacci::iterator fit = fib.begin();
vector<int>::iterator vit = _elem.begin();
4.7. friend
in C++
📌What is friend
?
In short, it makes the collaboration between classes available! Friendship is generally required for performance reasons, such as the multiplication of a Point and Matrix in a nonmember operator function.
📌Friendship in Functions
xxxxxxxxxx
/******Triangular.h******/
class Triangular
{
friend int operator*(const Triangular_iterator &rhs);
// ...
};
/******Triangular_iterator.h******/
class Triangular_iterator
{
friend int operator*(const Triangular_iterator &rhs);
// ...
};
With the preceding declaration, you can have the following:
xxxxxxxxxx
inline int operator*(const Triangular_iterator &rhs)
{
rhs.check_integrity();
return Triangular::_elems[rhs.index()];
}
📌Friendship in Class
Define Triangular_iterator
as a friend in Triangular
is more efficient.
xxxxxxxxxx
class Triangular
{
friend class Triangular_iterator;
// ...
}
4.8. Copy Assignment Operator
📌Use =
to assign objectA to objectB
In 4.2. we mentioned that the following class cannot be copied easily.
xxxxxxxxxx
class Matrix
{
private:
int _row, _col;
double *_pmat;
public:
Matrix(int row, int col);
~Matrix();
};
Matrix::Matrix(int row, int col)
: _row(row), _col(col)
{
// constructor allocates a resource
_pmat = new double[row * col];
}
Matrix::~Matrix()
{
// destructor frees the resource
delete [] _pmat;
}
The problem may occur in the following block of code:
xxxxxxxxxx
Matrix mat1(4, 4);
{
Matrix mat2 = mat1;
}
// ERROR!! if we manipulate mat1, because _pmat was destroyed by mat2
While we can overload =
operator to solve this problem.
xxxxxxxxxx
Matrix& Matrix::
operator=(const Matrix &rhs)
{
if (this != &rhs)
{
this->_col = rhs._col;
this->_row = rhs._row;
int elem_cnt = this->_col * this->_row;
delete [] _pmat;
_pmat = new double[elem_cnt];
for (int ix = 0; ix < elem_cnt; ix++)
{
_pmat[ix] = rhs._pmat[ix];
}
}
return *this;
}
Few things to notice:
- 1️⃣
this != &rhs
is to check the address are different - 2️⃣ //TODO I don't quite understand why should we
delete [] _pmat
- 3️⃣ In the end, return
*this
as itself toMatrix&
- 4️⃣ Warning! The preceding implementation is not exception-safe.
4.9. Implementing a Function Object
📌What is Function Object?
It is a class that provides an overloaded instance of the function call operator.
📌Parameter List of Function Object
The function call operator can take any number of parameters: none, one, two, and so on.
📌Example: LessThan Function Class
You can define as the following:
xxxxxxxxxx
class LessThan
{
private:
int _val;
public:
LessThan(int val)
: _val(val) {}
int comp_val() const {return _val;}
void comp_val(int nval) {_val = nval;}
bool operator() (int value) const;
};
// Key Function!
inline bool LessThan::
operator() (int value) const
{
return value < _val;
}
You can use it like:
xxxxxxxxxx
// Function to count the amount of elements less than `comp`
int count_less_than(const vector<int> &vec, int comp)
{
LessThan less_than(comp);
int count = 0;
for (int ix = 0; ix < vec.size(); ix++)
{
if (less_than(vec[ix])) // here invoke bool operator() (int value) const;
{
++count;
}
}
return count;
}
// Function to print the element less than `comp`
void print_less_than(const vector<int> &vec, int comp, ostream &os = cout)
{
LessThan less_than(comp);
vector<int>::const_iterator iter = vec.begin();
vector<int>::const_iterator it_end = vec.end();
os << "elements less than " << less_than.comp_val() << endl;
while ( (iter = find_if(iter, it_end, less_than)) != it_end) // here invoke bool operator() (int value) const;
{
os << *iter << ' ';
++iter;
}
}
The example code would be like:
xxxxxxxxxx
int ia[16] = { 17, 12, 44, 9, 18, 45, 6,
14, 23, 67, 9, 0, 27, 55, 8, 16};
vector<int> vec(ia, ia+16);
int comp_val = 20;
cout << "Number of elements less than "
<< comp_val << " are "
<< count_less_than(vec, comp_val) << endl;
print_less_than(vec, comp_val);
4.10. iostream
operator for Class instance
📌ostream
operator <<
The ostream
operator is very much like the .ToString()
member function in C#.
xxxxxxxxxx
ostream& operator<<(ostream &os, const Triangular &rhs)
{
os << "(" << rhs.beg_pos() << ", "
<< rhs.length() << ") ";
rhs.display(rhs.length(), rhs.beg_pos(), os);
return os;
}
Few things to notice:
1️⃣ rhs
is declared both with const
and &
. That is to speed up the process by pass by reference. And in the meantime, the rhs
is not modified.
2️⃣ the return type ostream&
is not declared with const
since each output operation modifies the internal state of the ostream
object.
3️⃣ the operator is not declared as member function. Why? Because a member function requires that its left operand be an object of that class.
xxxxxxxxxx
// member function overloaded
// very confusing!! 😵
tri << cour << '\n';
📌istream
operator >>
xxxxxxxxxx
istream& operator>>(istream &is, Triangular &rhs)
{
char ch1, ch2;
int b_pos, len;
// suppose the user write the input: "(3, 6) 6 10 15 121 28 36"
// ch1=='(' , b_pos==3, ch2==',' , len==6
is >> ch1 >> b_pos >> ch2 >> len;
rhs.beg_pos(b_pos);
rhs.length(len);
rhs.next_reset();
return is;
}
Input operators are more complicated to implement because of the possibility of invalid data being read!