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 call

Function 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 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:
        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/6

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

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/2

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.
Fraction a(1, 2);
Fraction b(2, 3);
b += a;
b.operator+=(a); // equivalent

friend 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 this pointer is passed to the friend function.
    • Can be placed in both private and public section of the class, it doesn’t matter.
  • 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::)
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, cout is the ostream object.
    • To modify the cout object? No!
    • To add a new functionality to the cout object? Yes!
  • 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
    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 compiler

If 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 called

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

    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 another type.
// 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 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.
MyTime(int m): hours(0), minutes(m) {
    this->hours += this->minutes / 60;
    this->minutes %= 60;
}

To use the converting constructor:

MyTime t = 120; // 2 hours

Assignment 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 called

Be careful with converting constructors and assignment operators

Mytime t1 = 120; // converting constructor

Mytime t2;
t2 = 120; // assignment operator

Increment/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: []