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!");
print_message(); // Hello, World!
print_message("Hello, C++"); // Hello, C++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
print_message(); // Hello, World!
print_message("Hello, C++"); // Hello, C++
print_message("Hello, C++", 3); // Hello, C++ Hello, C++ Hello, C++- 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 callFunction overloading in class
class Account {
public:
Account(double balance = 0.0) : m_balance(balance) {}
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";
s += " World";
std::cout << s << std::endl; // Hello WorldThe 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:
Fraction(int numerator, int denominator);
Fraction operator+(const Fraction& other) const;
Fraction operator+=(const Fraction& other);
void simplify();
void print() const;
};The operator+ function is a member function of the Fraction class.
Fraction Fraction::operator+(const Fraction& other) const {
return Fraction(this->numerator * other.denominator
+ other.numerator * this->denominator,
this->denominator * other.denominator);
}Next we overload the += operator.
Fraction Fraction::operator+=(const Fraction& other) {
this->numerator = this->numerator * other.denominator
+ other.numerator * this->denominator;
this->denominator = this->denominator * other.denominator;
simplify();
return *this;
}After overloading the operator+ and operator+=, we can add two Fraction objects.
Fraction a(1, 2);
Fraction b(1, 3);
Fraction c = a + b;
c.print(); // 5/6
c += a;
c.print(); // 17/6Operator 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
Fraction operator+(int i) const {
return Fraction(this->numerator + i * this->denominator, this->denominator);
}Another way to implement this is to use our overloaded operator+.
Fraction operator+(int i) const {
return *this + Fraction(i);
}Fraction a(1, 2);
Fraction b = a + 1;
b.print(); // 3/2The overloaded operator are functions with special names.
- The name of the operator function is
operatorfollowed 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.
Fraction a(1, 2);
Fraction b(2, 3);
b += a;
b.operator+=(a); // equivalentfriend functions
Why friend functions?
The previous overloaded operators are quite helpful. However, they are member functions.
Fraction a(1,2);
20 + 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
thispointer 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
friendfunction 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:
Fraction(int numerator, int denominator);
friend Fraction operator+(int i, const Fraction& f);
// other members
};
Fraction operator+(int i, const Fraction& f) {
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,coutis theostreamobject.- To modify the
coutobject? No! - To add a new functionality to the
coutobject? Yes!
- To modify the
- We can use
friendfunction to overload the<<operator.
friend std::ostream& operator<<(std::ostream& os, const Fraction& f) {
// simplified version, can detect if the denominator is 1 first
os << f.numerator << "/" << f.denominator;
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;
ThePoint.x() = 7;
ThePoint.y() = 9;
cout << "x = " << ThePoint.x() << "\n"
<< "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::Fraction() {} // implicitly defined by compilerIf you define a constructor, the compiler will not automatically generate a default constructor.
class Fraction {
public:
Fraction() : numerator(0), denominator(1) {}
// other members
};Avoid ambiguous default constructor
If you define two constructors below:
class Fraction {
public:
Fraction(): numerator(0), denominator(1) {}
Fraction(int n = 1, int d = 1): numerator(n), denominator(d) {}
};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.
Fraction(const Fraction & f);
Fraction f1(1, 2);
Fraction f2(f1); // copy constructor called
Fraction f3 = f1; // copy constructor calledDefault 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
staticmembers are shared by all objects of the class.
Default copy assignment
Assignment operators:
=,+=,-=,*=,/=,%=, etc.Copy assignment operator calls when
Fraction f1(1, 2); Fraction f2; // default constructor called f2 = f1; // copy assignment operator called
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:
MyTime(): hours(0), minutes(0){}
MyTime(int h, int m): hours(h), minutes(m){}
MyTime operator+(const MyTime & t) const
{
MyTime sum;
sum.minutes = this->minutes + t.minutes;
sum.hours = this->hours + t.hours;
sum.hours += sum.minutes / 60;
sum.minutes %= 60;
return sum;
}
MyTime & operator+=(const MyTime & t)
{
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:
MyTime t(1, 30);
int minutes = t; // 90, implicit conversion
float hours = t; // error, try if removing the explicit keyword
float hours2 = (float)t; // 1.5, explicit conversionConverting constructor
Sometimes we want to allow user to convert int to MyTime.
- A converting constructor is a constructor that takes a single argument.
MyTime(int m): hours(0), minutes(m) {
this->hours += this->minutes / 60;
this->minutes %= 60;
}To use the converting constructor:
MyTime t = 120; // 2 hoursAssignment operator operator=
- Overload the assignment operator to convert a type to another
type.
MyTime & operator=(int m) {
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.
MyTime t; // default constructor called
t = 120; // assignment operator calledBe careful with converting constructors and assignment operators
Mytime t1 = 120; // converting constructor
Mytime t2;
t2 = 120; // assignment operatorIncrement/Decrement operators
Increment
Two operators: prefix increment ++ and postfix increment ++.
Example:
int i = 0;
a = ++i; // a = 1, i = 1
a = i++; // a = 1, i = 2
MyTime& operator++() { // prefix increment
this->minutes += 1;
this->hours += this->minutes / 60;
this->minutes %= 60;
return *this;
}
MyTime operator++(int) { // postfix increment
MyTime old = *this;
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:
[]