Function2 & Class2
Default arguments
To call a function without providing the required arguments, you can use default arguments.
void print_message(std::string message = "Hello, World!");
(); // Hello, World!
print_message("Hello, C++"); // Hello, C++ print_message
After a parameter has a default value, all subsequent parameters must
- have default values
void print_message(std::string message = "Hello, World!", int times); // Error
void print_message(std::string message = "Hello, World!", int times = 1); // OK
(); // Hello, World!
print_message("Hello, C++"); // Hello, C++
print_message("Hello, C++", 3); // Hello, C++ Hello, C++ Hello, C++ print_message
- have a previous declaration supplies a default argument in the same scope.
void print_message(std::string message, int times = 1);
void print_message(std::string message = "Hello, World!", int times);
Function overloading
Why overloading?
Consider the following functions in C:
// math.h
double round(double x);
float roundf(float x);
long double roundl(long double x);
In C++, a single function name can be used for different types of arguments.
// <cmath>
double round(double x);
float round(float x);
long double round(long double x);
Overloading rules
The compiler will perform a name lookup to find the best match for the function call.
- Argument-based lookup: The compiler will look for the best match for the function call.
- The return type is not considered in the argument-based lookup.
int sum(int a, int b) return a + b;
float sum(float a, float b) return a + b;
double sum(double a, double b) return a + b;
int a = sum(1.2, 2.3); // Error: ambiguous call
Function overloading in class
class Account {
public:
(double balance = 0.0) : m_balance(balance) {}
Account
double Deposit(double dAmount, const char *szPassword);
private:
double Deposit(double dAmount) {
m_balance += dAmount;
return m_balance;
}
bool Validate(const char *szPassword) const {
// For illustration, assume password is always valid
return true;
}
double m_balance;
};
Operator overloading
String class
std::string
is a class implemented in STL. When we use std::string
, we can add characters to it.
std::string s = "Hello";
+= " World";
s std::cout << s << std::endl; // Hello World
The way std::string
implements the +=
operator is by calling the append
method.
In C++, we can customize the behavior of operators for our own classes. This is called operator overloading.
Operator overloading
class Fraction {
private:
int numerator;
int denominator;
public:
(int numerator, int denominator);
Fractionoperator+(const Fraction& other) const;
Fraction operator+=(const Fraction& other);
Fraction void simplify();
void print() const;
};
The operator+
function is a member function of the Fraction
class.
::operator+(const Fraction& other) const {
Fraction Fractionreturn Fraction(this->numerator * other.denominator
+ other.numerator * this->denominator,
this->denominator * other.denominator);
}
Next we overload the +=
operator.
::operator+=(const Fraction& other) {
Fraction Fractionthis->numerator = this->numerator * other.denominator
+ other.numerator * this->denominator;
this->denominator = this->denominator * other.denominator;
();
simplifyreturn *this;
}
After overloading the operator+
and operator+=
, we can add two Fraction
objects.
(1, 2);
Fraction a(1, 3);
Fraction b= a + b;
Fraction c .print(); // 5/6
c+= a;
c .print(); // 17/6 c
Operator overloading for built-in types
If one operand is not a class, and is an int
. We want do allow the operation “Fraction + int”.
The function can be
operator+(int i) const {
Fraction return Fraction(this->numerator + i * this->denominator, this->denominator);
}
Another way to implement this is to use our overloaded operator+
.
operator+(int i) const {
Fraction return *this + Fraction(i);
}
(1, 2);
Fraction a= a + 1;
Fraction b .print(); // 3/2 b
The overloaded operator are functions with special names.
- The name of the operator function is
operator
followed by the operator symbol. - The operator function is a member function of the class.
- The operator function takes at least one argument, which is the other operand.
(1, 2);
Fraction a(2, 3);
Fraction b+= a;
b .operator+=(a); // equivalent b
friend
functions
Why friend
functions?
The previous overloaded operators are quite helpful. However, they are member functions.
(1,2);
Fraction a20 + a; // how to define this?
If we want the operator can support int + Fraction
, we need to define a friend
function.
friend
function
- Friend functions are not member functions of the class.
- No
this
pointer is passed to the friend function. - Can be placed in both private and public section of the class, it doesn’t matter.
- No
- Friend functions are granted access to the private members of the class.
- The definition of the
friend
function is placed in the global scope.- No class prefix is needed. (in our case,
Fraction::
)
- No class prefix is needed. (in our case,
class Fraction {
private:
int numerator;
int denominator;
public:
(int numerator, int denominator);
Fractionfriend Fraction operator+(int i, const Fraction& f);
// other members
};
operator+(int i, const Fraction& f) {
Fraction return Fraction(f.numerator + i * f.denominator, f.denominator);
}
Overloading <<
operator
Instead of using print
method, we can use <<
operator to print the Fraction
object.
- In
cout << a
,cout
is theostream
object.- To modify the
cout
object? No! - To add a new functionality to the
cout
object? Yes!
- To modify the
- We can use
friend
function to overload the<<
operator.
friend std::ostream& operator<<(std::ostream& os, const Fraction& f) {
// simplified version, can detect if the denominator is 1 first
<< f.numerator << "/" << f.denominator;
os return os;
}
Return by reference
Why return by reference?
Functions can be declared to return a reference type. There are two reasons to make such a declaration:
- The information being returned is a large enough object that returning a reference is more efficient than returning a copy.
- The referred-to object will not go out of scope when the function returns.
It’s useful when we want to return a reference to a member of the class or in the overloaded operators.
Example
class Point {
public:
unsigned &x();
unsigned &y();
private:
unsigned obj_x;
unsigned obj_y;
};
unsigned &Point ::x() { return obj_x; }
unsigned &Point ::y() { return obj_y; }
int main() {
;
Point ThePoint.x() = 7;
ThePoint.y() = 9;
ThePoint
<< "x = " << ThePoint.x() << "\n"
cout << "y = " << ThePoint.y() << "\n";
}
Some default operations
Default constructor
- Default constructor is a constructor that takes no arguments.
If you don’t define any constructor, the compiler will automatically generate a default constructor.
::Fraction() {} // implicitly defined by compiler Fraction
If you define a constructor, the compiler will not automatically generate a default constructor.
class Fraction {
public:
() : numerator(0), denominator(1) {}
Fraction// other members
};
Avoid ambiguous default constructor
If you define two constructors below:
class Fraction {
public:
(): numerator(0), denominator(1) {}
Fraction(int n = 1, int d = 1): numerator(n), denominator(d) {}
Fraction};
The compiler will not be able to determine which constructor to call when creating a Fraction
object without any arguments.
Implicitly defined default destructor
- If no destructor is defined, the compiler will implicitly define a default destructor.
- Memory allocated in constructor is normally released in a destructor.
- Only stack-allocated objects are automatically released.
Copy constructor
- Copy constructor: Copy an object to another object.
- Only one parameter
- Or multiple parameters, but all but the first parameter have default values.
(const Fraction & f); Fraction
(1, 2);
Fraction f1(f1); // copy constructor called
Fraction f2= f1; // copy constructor called Fraction f3
Default copy constructor
If no copy constructor is defined, the compiler will implicitly define a default copy constructor.
The default copy constructor will copy all non-static members from the source object to the destination object.
Recall that
static
members are shared by all objects of the class.
Default copy assignment
Assignment operators:
=
,+=
,-=
,*=
,/=
,%=
, etc.Copy assignment operator calls when
(1, 2); Fraction f1; // default constructor called Fraction f2= f1; // copy assignment operator called f2
Default copy assignment
- If no user-defined copy assignment is defined, the compiler will implicitly define a default copy assignment.
- It will copy all non-static members from the source object to the destination object.
User-defined Type Conversion
MyTime
class
class MyTime
{
private:
int hours;
int minutes;
public:
(): hours(0), minutes(0){}
MyTime(int h, int m): hours(h), minutes(m){}
MyTime
operator+(const MyTime & t) const
MyTime {
;
MyTime sum.minutes = this->minutes + t.minutes;
sum.hours = this->hours + t.hours;
sum
.hours += sum.minutes / 60;
sum.minutes %= 60;
sum
return sum;
}
& operator+=(const MyTime & t)
MyTime {
this->minutes += t.minutes;
this->hours += t.hours;
this->hours += this->minutes / 60;
this->minutes %= 60;
return *this;
}
std::string getTime() const
{
return std::to_string(this->hours) + " hours and "
+ std::to_string(this->minutes) + " minutes.";
}
};
Operator type()
- Overload the
()
operator to convert a type to anothertype
.
// allow implicit/explicit conversion to int
operator int() const {
return this->hours * 60 + this->minutes;
}
// must be explicit conversion
operator float() const {
return float(this->hours) + float(this->minutes) / 60.0;
}
When using the conversion operator:
(1, 30);
MyTime tint minutes = t; // 90, implicit conversion
float hours = t; // error, try if removing the explicit keyword
float hours2 = (float)t; // 1.5, explicit conversion
Converting constructor
Sometimes we want to allow user to convert int
to MyTime
.
- A converting constructor is a constructor that takes a single argument.
(int m): hours(0), minutes(m) {
MyTimethis->hours += this->minutes / 60;
this->minutes %= 60;
}
To use the converting constructor:
= 120; // 2 hours MyTime t
Assignment operator operator=
- Overload the assignment operator to convert a type to another
type
.
& operator=(int m) {
MyTime this->hours = 0;
this->minutes = m;
this->hours += this->minutes / 60;
this->minutes %= 60;
return *this;
}
The assignment operator calls when we use =
to assign a value to a MyTime
object.
; // default constructor called
MyTime t= 120; // assignment operator called t
Be careful with converting constructors and assignment operators
= 120; // converting constructor
Mytime t1
;
Mytime t2= 120; // assignment operator t2
Increment/Decrement operators
Increment
Two operators: prefix increment ++
and postfix increment ++
.
Example:
int i = 0;
= ++i; // a = 1, i = 1
a = i++; // a = 1, i = 2 a
& operator++() { // prefix increment
MyTimethis->minutes += 1;
this->hours += this->minutes / 60;
this->minutes %= 60;
return *this;
}
operator++(int) { // postfix increment
MyTime = *this;
MyTime old operator++();
return old;
}
Decrement
Decrement operators are similar to increment operators, except that they decrement the value instead of incrementing it.
The example is left as an exercise.
Operators
The following operators can be overloaded, see the reference.
- Arithmetic operators:
+
,-
,*
,/
,%
- Relational operators:
==
,!=
,<
,>
,<=
,>=
- Logical operators:
&&
,||
,!
- Bitwise operators:
&
,|
,~
,^
,<<
,>>
- Assignment operators:
=
,+=
,-=
,*=
,/=
,%=
,&=
,|=
,^=
,<<=
,>>=
- Address-related operators:
&
,*
- Subscript operator:
[]