【CPP】函数重载、模版

1-Default Arguments

Default arguments

  • A feature in C++ (not C)
  • To call a function without providing one or more trailing arguments

default-argument.cpp

#include <iostream>
#include <cmath>
using namespace std;

float norm(float x, float y, float z);
float norm(float x, float y, float z = 0);
float norm(float x, float y = 0, float z);

int main()
{
    cout << norm(3.0f) << endl;
    cout << norm(3.0f, 4.0f) << endl;
    cout << norm(3.0f, 4.0f, 5.0f) << endl;
    return 0;
}

float norm(float x, float y, float z)
{
    return sqrt(x * x + y * y + z * z);
}

在这里插入图片描述

#include <iostream>
#include <cmath>
using namespace std;

float norm(float x, float y, float z);
float norm(float x, float y = 0, float z);

int main()
{
    return 0;
}

float norm(float x, float y, float z)
{
    return sqrt(x * x + y * y + z * z);
}

在这里插入图片描述
缺失默认参数

默认参数要从尾部开始定义

重复定义默认参数也会报错:

#include <iostream>
#include <cmath>
using namespace std;

float norm(float x, float y, float z);
float norm(float x, float y, float z = 0);
float norm(float x, float y = 0, float z = 4);

int main()
{
    return 0;
}

float norm(float x, float y, float z)
{
    return sqrt(x * x + y * y + z * z);
}

在这里插入图片描述

2-Function-Overloading

Why to overload?

  • C99
<math.h>

double round (double x);
float roundf (float x);
long double roundl (long double x);
  • C++11
<cmath>

double round (double x);
float round (float x);
long double round (long double x);
  • which one do you prefer?

Function overloading

  • Which function to choose? The compiler will perform name lookup
  • Argument-dependent lookup, also known as ADL
  • The return type will not be considered in name lookup
#include <iostream>

using namespace std;

int sum(int x, int y)
{
    cout << "sum(int, int) is called" << endl;
    return x + y;
}
float sum(float x, float y)
{
    cout << "sum(float, float) is called" << endl;
    return x + y;
}
double sum(double x, double y)
{
    cout << "sum(double, double) is called" << endl;
    return x + y;
}

// //Is the following definition correct?
// double sum(int x, int y)
// {
//     cout << "sum(int, int) is called" << endl;
//     return x + y;
// }

int main()
{

    cout << "sum = " << sum(1, 2) << endl;
    cout << "sum = " << sum(1.1f, 2.2f) << endl;
    cout << "sum = " << sum(1.1, 2.2) << endl;

    //which function will be called?
    //cout << "sum = " << sum(1, 2.2) << endl;

    return 0;
}

在这里插入图片描述

//Is the following definition correct?
double sum(int x, int y)
{
    cout << "sum(int, int) is called" << endl;
    return x + y;
}

在这里插入图片描述

两个函数如果只是返回值不同,是不可以被重载的

#include <iostream>

using namespace std;

int sum(int x, int y)
{
    cout << "sum(int, int) is called" << endl;
    return x + y;
}
float sum(float x, float y)
{
    cout << "sum(float, float) is called" << endl;
    return x + y;
}
double sum(double x, double y)
{
    cout << "sum(double, double) is called" << endl;
    return x + y;
}
int main()
{

    //which function will be called?
    cout << "sum = " << sum(1, 2.2) << endl;

    return 0;
}

在这里插入图片描述

参数可以匹配多个函数,有歧义,报错

#include <iostream>

using namespace std;

int sum(int x, int y)
{
    cout << "sum(int, int) is called" << endl;
    return x + y;
}

int main()
{

    //which function will be called?
    cout << "sum = " << sum(1, 2.2) << endl;

    return 0;
}

在这里插入图片描述

warning: implicit conversion from 'double' to 'int' changes value from 2.2 to 2 [-Wliteral-conversion]
    cout << "sum = " << sum(1, 2.2) << endl;
                        ~~~    ^~~
1 warning generated.

进行了隐式类型转换

在这里插入图片描述

3-Function Templates

Why function templates

  • The definitions of some overloaded functions may be similar

Explicit Instantiation

  • A function template is not a type, or a function, or any other entity
  • No code is generated from a source file that contains only template definitions
  • The template arguments must be determined, then the compiler can generate an actual function

实例化

template<typename T>
T sum(T x, T y)
{
    cout << "The input type is " << typeid(T).name() << endl;
    return x +
     y;
}
// Explicitly instantiate
template double sum<double>(double, double);

// instantiates sum<char>(char, char), template argument deduced
template char sum<>(char, char);

// instantiates sum<int>(int, int), template argument deduced
template int sum(int, int);

template1.cpp

#include <iostream>
#include <typeinfo>
using namespace std;

template<typename T>
T sum(T x, T y)
{
    cout << "The input type is " << typeid(T).name() << endl;
    return x +
     y;
}
// Explicitly instantiate
template double sum<double>(double, double);

int main()
{
    auto val = sum(4.1, 5.2);
    cout << val << endl;
    return 0;
}

The input type is d
9.3
#include <iostream>
#include <typeinfo>
using namespace std;

template<typename T>
T sum(T x, T y)
{
    cout << "The input type is " << typeid(T).name() << endl;
    return x +
     y;
}
// Explicitly instantiate
template float sum<float>(float, float);

int main()
{
    auto val = sum(4.1, 5.2);
    cout << val << endl;
    return 0;
}

The input type is d
9.3

为什么还是调用了类型为double的函数?

为什么把函数删掉依然能够编译?

其实是因为函数可以做隐式实例化

Implicit Instantiation

  • Implicit instantiation occurs when a function template is not explicitly instantiated

隐式实例化

template<typename T>
T sum(T x, T y)
{
    cout << "The input type is " << typeid(T).name() << endl;
    return x + y;
}
// Implicitly instantiates product<int>(int, int)
cout << "sum = " << sum<int>(2.2f, 3.0f) << endl;
// Implicitly instantiates product<float>(float, float)
cout << "sum = " << sum(2.2f, 3.0f) << endl;

template2.cpp

#include <iostream>
#include <typeinfo>
using namespace std;

template<typename T>
T sum(T x, T y)
{
    cout << "The input type is " << typeid(T).name() << endl;
    return x + y;
}

int main()
{
    // Implicitly instantiates product<int>(int, int)
    cout << "sum = " << sum<int>(2.2f, 3.0f) << endl;
    // Implicitly instantiates product<float>(float, float)
    cout << "sum = " << sum(2.2f, 3.0f) << endl;

    return 0;
}

sum = The input type is i
5
sum = The input type is f
5.2

Function template specialization

  • We have a function template
template<typename T> T sum(T x, T y)
  • If the input type is Point
struct Point
{
  int x;
  int y;
};
  • But no + operator for Point;
  • We need to give a special definition for this case

specialization.cpp

#include <iostream>
#include <typeinfo>
using namespace std;

template<typename T>
T sum(T x, T y)
{
    cout << "The input type is " << typeid(T).name() << endl;
    return x + y;
}

struct Point
{
    int x;
    int y;
};


int main()
{
    //Explicit instantiated functions
    cout << "sum = " << sum(1, 2) << endl;
    cout << "sum = " << sum(1.1, 2.2) << endl;

    Point pt1 {1, 2};
    Point pt2 {2, 3};
    Point pt = sum(pt1, pt2);
    cout << "pt = (" << pt.x << ", " << pt.y << ")" << endl;
    return 0;
}
specialization.cpp:9:14: error: invalid operands to binary expression ('Point' and 'Point')
    return x + y;
           ~ ^ ~
specialization.cpp:38:16: note: in instantiation of function template specialization 'sum<Point>' requested here
    Point pt = sum(pt1, pt2);

不合法的操作

专门去定义Point结构体模版

#include <iostream>
#include <typeinfo>
using namespace std;

template<typename T>
T sum(T x, T y)
{
    cout << "The input type is " << typeid(T).name() << endl;
    return x + y;
}

struct Point
{
    int x;
    int y;
};

// Specialization for Point + Point operation
template<>
Point sum<Point>(Point pt1, Point pt2)
{
    cout << "The input type is " << typeid(pt1).name() << endl;
    Point pt;
    pt.x = pt1.x + pt2.x;
    pt.y = pt1.y + pt2.y;
    return pt;
}


int main()
{
    //Explicit instantiated functions
    cout << "sum = " << sum(1, 2) << endl;
    cout << "sum = " << sum(1.1, 2.2) << endl;

    Point pt1 {1, 2};
    Point pt2 {2, 3};
    Point pt = sum(pt1, pt2);
    cout << "pt = (" << pt.x << ", " << pt.y << ")" << endl;
    return 0;
}

sum = The input type is i
3
sum = The input type is d
3.3
The input type is 5Point
pt = (3, 5)

如果只是template 是实例化,如果加了template<> 是特例化。

4- Function Pointers and References

Function pointers

  • norm_ptr is a pointer, a function pointer
  • The function should have two float parameters, and returns float
#include <iostream>
#include <cmath>
using namespace std;

float norm_l1(float x, float y); //declaration
float norm_l2(float x, float y); //declaration
float (*norm_ptr)(float x, float y); //norm_ptr is a function pointer

int main()
{
    norm_ptr = norm_l1; //Pointer norm_ptr is pointing to norm_l1
    cout << "L1 norm of (-3, 4) = " << norm_ptr(-3.0f, 4.0f) << endl;

    norm_ptr = &norm_l2; //Pointer norm_ptr is pointing to norm_l2
    cout << "L2 norm of (-3, 4) = " << (*norm_ptr)(-3.0f, 4.0f) << endl;

    return 0;
}

float norm_l1(float x, float y)
{
    return fabs(x) + fabs(y);
}

float norm_l2(float x, float y)
{
    return sqrt(x * x + y * y);
}
L1 norm of (-3, 4) = 7
L2 norm of (-3, 4) = 5

函数指针:指向函数的指针,要求指向的函数要跟指针的类型完全相同,也就是说指针指向的函数应该要有两个参数,且这两个参数的类型都应该是float,返回值也是float

  • A function pointer can be an argument and pass to a function
<stdlib.h>

void qsort(void *ptr, size_t count,  size_t size, int(*comp)(const void*, const void*));
  • To sort some customized types, such as
struct Point
struct Person

Function references

function-reference.cpp

#include <iostream>
#include <cmath>
using namespace std;

float norm_l1(float x, float y); //declaration
float norm_l2(float x, float y); //declaration
float (&norm_ref)(float x, float y) = norm_l1; //norm_ref is a function reference

int main()
{
    cout << "L1 norm of (-3, 4) = " << norm_ref(-3, 4) << endl;
    return 0;
}

float norm_l1(float x, float y)
{
    return fabs(x) + fabs(y);
}

float norm_l2(float x, float y)
{
    return sqrt(x * x + y * y);
}
L1 norm of (-3, 4) = 7

5- Recursive Functions

Recursive Functions

  • A simple example

recursion.cpp

#include <iostream>
using namespace std;

void div2(double val);

int main()
{
    div2(1024.); // call the recursive function
    return 0;
}

void div2(double val)
{

    cout << "Entering val = " << val << endl;
    if (val > 1.0)
        div2( val / 2); // function calls itself
    else
        cout << "--------------------------" << endl;
    
    cout << "Leaving  val = " << val << endl;
}

Entering val = 1024
Entering val = 512
Entering val = 256
Entering val = 128
Entering val = 64
Entering val = 32
Entering val = 16
Entering val = 8
Entering val = 4
Entering val = 2
Entering val = 1
--------------------------
Leaving  val = 1
Leaving  val = 2
Leaving  val = 4
Leaving  val = 8
Leaving  val = 16
Leaving  val = 32
Leaving  val = 64
Leaving  val = 128
Leaving  val = 256
Leaving  val = 512
Leaving  val = 1024
  • Pros:
  1. Good at tree traversal
  2. Less lines of source code
  • Cons:
  1. Consume more stack memory
  2. May be slow
  3. Difficult to implement and debug