二十、函数(三)

1、函数重载

函数重载技术运行我们创建函数名称相同的函数,但是为了编译器能够正确的编译这个程序,这些函数需要采用不同的参数列表来区分(即函数名相同,参数类型不同)。C语言中无法进行函数重载

1)项目设计

①设计一个函数,能够使得函数求出两个int值的平均值

②设计一个函数,能够使得函数求出三个int值的平均值

③设计一个函数,能够使得函数求出三个float值的平均值

//不使用函数重载实现上述功能
#include <iostream>

int Ave_1(int a, int b)
{
    return (a + b) / 2;
}

int Ave_2(int a, int b, int c)
{
    return (a + b + c) / 3;
}

float Ave_float(float a, float b,float c)
{
    return (a + b + c) / 3;
}

int main()
{
    std::cout << Ave_1(2, 3) << std::endl;
    std::cout << Ave_2(2, 3, 4) << std::endl;
    std::cout << Ave_float(2.2, 3.3, 4.4) << std::endl;
}
//使用函数重载实现上述功能
#include <iostream>

int Ave(int a, int b)
{
    return (a + b) / 2;
}

int Ave(int a, int b, int c)
{
    return (a + b + c) / 3;
}

float Ave(float a, float b, float c)
{
    return (a + b + c) / 3;
}

int main()
{
    std::cout << Ave(2, 3) << std::endl;
    std::cout << Ave(2, 3, 4) << std::endl;
    std::cout << Ave(2.2f, 3.3f, 4.4f) << std::endl;
}

2)尽管函数名称相同,但是再我们调用函数时,是通过函数参数的不同,编译器还是能够确定我们调用的是哪一个函数,因此函数能够准确的编译。但是编译器无法单纯的通过返回值确定你要返回的函数,比如

float Ave(int a,intb)和int Ave(int a,int b)是无法重载的

3)思考如下两个函数能否重载

/*不允许重载,因为数组也相当于指针
#include <iostream>

int Ave(int* pa, int count)
{
    return 0;
}

int Ave(int pa[], int count)
{
    return 0;
}
*/
//以下情况也不允许重载,编译不报错,但是使用函数时报错
#include <iostream>

int Ave(int& a, int& b)  //传入引用参数
{
    return (a + b) / 2;
}

int Ave(int a, int b)
{
    return (a + b) / 3;
}

int main()
{
    int a = 100;
    int b = 100;
    //std::cout << Ave(a, b) << std::endl;   //报错
    std::cout << Ave(2, 3) << std::endl; //不出错,临时的常量不存在引用,所以
}


//以下情况也不允许重载
#include <iostream>
int ave(int& a, int& b)
{
    return a + b;
}
float ave(float a, float b)
{
    return a * 2 + b;
}

int main()
{
    char a{ 100 }, b{ 200 };
   //std::cout << ave((int)a, (int)b)<< std::endl;  //转化后的int类型的a和b是一个临时的变量,而临时变量没有引用,即没有固定的内存地址,所以无法转化
}
//无法重载
#include <iostream>

int ave(int a, int b)  //参数为变量
{
    return a + b;
}
int ave(const int  a, const int b)  //参数为常量
{
    return a * 2 + b;
}

int main()
{
    int a{ 100 }, b{ 200 };  //不管a和b是什么类型,都无法影响到函数中的值的变化,因为传递的是值

    //std::cout << ave(a,b)<< std::endl;    //编译报错,因为传递的是值
}
//可以重载
#include <iostream>

int ave(int& a, int& b)  //参数为变量的引用
{
    return a + b;
}
int ave(const int&  a, const int& b)  //参数为常量的引用
{
    return a * 2 + b;
}

int main()
{
    int a{ 100 }, b{ 200 };  
    std::cout << ave(a,b)<< std::endl;    //实现了重载,调用的是参数为变量的引用的函数
    const int c{ 100 }, d{ 200 };
    std::cout << ave(c, d) << std::endl; //实现了重载,调用的是参数为常量的引用的函数
}
//不可以重载
#include <iostream>

int ave(int a=150, int b=250)  //函数有默认参数
{
    return a + b;
}
int ave(float a=300.0f, float b=150.0f)  //函数有默认参数
{
    return a * 2 + b;
}

int main()
{
    //std::cout << ave() << std::endl;    //编译出错,因为直接调用ave(),不带参数,无法确定到底调用哪个函数
}
2、函数模板

1)引出函数模板:统一一个函数模板将功能系相同参数不同的函数统一表示

int ave(int a,int b, int c)
{
    return (a+b+c)/3;
}
float ave(float a,float b,float c)
{
    return (a+b+c)/3;
}

//int  ave(int a,int b,int c)和float ave(float a,float b,float c)除了类型不同外,函数的运算逻辑都一样,这样的函数可以利用函数模板技术来生成对应的函数

2)函数模板语法

//函数模板语法
template <typename type1>type1 ave(type1 a,type1 b)
{
    return (a+b)/2;
}

int a=ave(1,2); //ave相当于int ave(int a,int b);
char =ave(1,2); //ave相当于char ave(char a,char b;)
//函数模板定义及用法
#include <iostream>

template <typename type1>type1 
ave(type1 a, type1 b, type1 c)  //定义一个函数模板type1,可支持各种类型
{
    return a + b + c;
}

int main()
{
    std::cout << ave(100, 200, 300) << std::endl;
    std::cout << ave(100.1f, 200.2f, 300.3f) << std::endl;
    std::cout << ave((char)100, (char)200, (char)300) << std::endl;
}

3)函数模板类型参数

//可以将type1当作一种类型,可以用来声明变量
#include <iostream>

template <typename type1>type1
ave(type1 a, type1 b, type1 c)  //可以把type1当作一种类型
{
    type1 x = a;   //可以通过type1申明变量
    return a + b + c + x;
}

int main()
{
    std::cout << ave(100, 200, 300) << std::endl;
    std::cout << ave(100.1f, 200.2f, 300.3f) << std::endl;
    std::cout << ave((char)100, (char)200, (char)300) << std::endl;
}

4)指定函数模板参数

//显示的为函数模板指定一个类型
template <typename type1>type1 ave(type1 a,type1 b)
{
    return (a+b)/2;
}

ave<int>(192.0f,153,1f);  //相当于执行int ave(int a,int b);而不是float ave(float a,float b);
#include <iostream>

template <typename type1>type1
ave(type1 a, type1 b, type1 c)  
{
    type1 x = a;   
    return a + b + c + x;
}

int main()
{
    std::cout << ave(100, 200, 300) << std::endl;
    std::cout << ave<int>(100.1f, 200.2f, 300.3f) << std::endl;   //强制指定函数模板参数,所有参数一律使用int
    std::cout << ave((char)100, (char)200, (char)300) << std::endl;
}

5)指针在函数模板中常见的问题

#include <iostream>

template <typename type1>type1
bigger(type1 a, type1 b)
{
    return a > b ? a : b;
}

int main()
{
    int a{ 100 };
    int b{ 200 };
    int c{};
    c = *bigger(&a, &b); //此处将a和b的地址传入了函数模板,比较的是a和b的地址,而不是a和b的值
    std::cout << c << std::endl;   //输出100,因为a的地址大于b的地址
}
3、函数模板和重载

1)函数模板的例外处理

//通过template<>定义一种函数模板的例外情况
template <typename type1>type1 ave(type1 a,type1 b)
{
    return (a+b)/2;
}
template <>float ave(float a,float b)
{
    return (a+b-100.0f)/2;
}

//函数模板例外处理用法
#include <iostream>

template <typename type1>type1
bigger(type1 a, type1 b)
{
    return a > b ? a : b;
}

template <>                       //定义一种例外情况
int* bigger(int* a, int* b)
{
    return *a > *b ? a : b;
}

int main()
{
    int a{ 100 };
    int b{ 200 };
    int c{};
    c = *bigger(&a, &b);  //用指针来传递参数
    std::cout << c << std::endl;   
}
//注:此种方法只是定义了int类型指针的情况

2)函数模板和函数重载

int ave(int a,int b)
{
    return a+b;
}
template <typename type1>type1 ave(type1 a,type1 b)
{
    return (a+b)/2;
}
//函数重载执行顺序优先于函数模板
//函数重载执行顺序优先于函数模板
#include <iostream>

template <typename type1>type1
bigger(type1 a, type1 b)
{
    return a > b ? a : b;
}

int bigger(int a, int b)
{
    return a > b ? a : b;
}

float* bigger(float* a, float* b)
{
    return *a > *b ? a : b;
}

int main()
{
    int a{ 100 };
    int b{ 200 };
    int c{};
    c = bigger(a, b);  //用指针来传递参数
    std::cout << c << std::endl;
}

3)函数模板的重载

//函数模板的重载也是通过参数来区分的
#include <iostream>

template <typename type1>type1
ave(type1 a, type1 b)          //函数模板重载,2个参数
{
    return a+b;
}

template <typename type1>type1
ave(type1 a, type1 b,type1 c)  //函数模板重载,3个参数
{
    return a+b+c;
}

int main()
{
    int a{ 100 };
    int b{ 200 };
    int c{300};
    std::cout << ave(a, b) << std::endl;
    std::cout << ave(a,b,c) << std::endl;
}
4、auto->decltype

1)atuo关键字

auto可以声明一个变量,让编译器根据变量的值来推断变量的类型,例如auto a{123};相当于int a{123};

利用auto的这一特性,可以创建一个函数

auto ave(int a,int b)
{
    return a+b;         //根据返回值确定auto的类型
}
int ave(int a,int b)
{
    return a+b;
}
//注:以上并非auto的最恰当用法,不管是函数还是变量,都不推荐使用auto来声明

2)auto关键字特性

①auto不保留const属性

const int a{};
auto b{a};  //此处的b为int类型,而不是const类型

②auto推断一个类型时,会优先推断为值类型,而非引用类型

int a{1500};
const int& la{a};  //定义一个a的引用
auto d=la;  //此处d为int类型,而不是int&

③auto利用函数返回值来确定类型的时候,函数会执行

auto ave(int a,int b)
{
    return a+b;         //根据返回值确定auto的类型
}

auto c = ave(a,b);  //c的类型依据ave()函数返回值的类型来确定

3)auto关键字和拖尾函数

//实现比较两个数,将大的数修改为500
#include <iostream>

int& bigger(int& a, int& b)
{
    return a > b ? a : b;
}

int main()
{
    int a{ 100 };
    int b{ 200 };
    bigger(a, b) = 500;
    std::cout << a << '\n' << b << std::endl;
}

因为auto会优先把值匹配成值类型,而不是引用类型,所以就上述案例,如果直接将函数的返回值设置为auto类型,达不到上述案例的效果,此时可以引用拖尾函数

//拖尾函数
#include <iostream>

auto bigger(int& a, int& b)->int&        //拖尾函数,通过->指定auto的类型
{
    return a > b ? a : b;
}

int main()
{
    int a{ 100 };
    int b{ 200 };
    bigger(a, b) = 500;
    std::cout << a << '\n' << b << std::endl;
}

4)decltype关键字

decltype关键字可以得出一个表达式的类型

//decltype语法
语法:delctype(表达式) 声明变量;     //表达式可以是

int a{100};
unsigned b{200};
decltype(a-b) x;    //相当于unsigned x; 依据a-b的类型,推断出x的类型

1)decltype是如何得出表达式的数据类型的

①如果decltype内的表达式没经历任何运算,那么得出的数据类型同表达式内的数据类型,并且decltype可以保留const和引用类型。例如

    int a;
    const int b;
    int& la{a};
    int* pa{&a};
    
    decltype(a) x; //相当于int x;
    decltype(b) x; //相当于const int x;
    decltype(la) x=a; //相当于int& x;   //即将x的类型设置为la的类型,必须得初始化
    decltype(pa) x; //相当于 int* x;

②如果decltype表达式经历了运算,那么得出得数据类型是根据运算结果是否有固定得内存地址(左值)来决定得,如果有固定得内存地址则得出得类型为该类型得引用类型,如果没有固定得内存地址,则得出得类型为该结果得类型。例如

    int a{100},b{200};
    int* pa{&a};
    
    decltype(a+b) x; //相当于int x;              //a+b没有固定得内存地址
    decltype(*pa) x; //相当于 int& x;            //*pa也是运算
    decltype(pa[0]) x; //相当于int& x;          //pa[0]也有固定得内存地址,也进行了计算

③如果decltype内得表达式是一个函数,那么得出得数据类型是根据函数得返回类型来确定得,例如

    int ave(int a,int b)
    {
        return a+b;
    }
    
    decltype(ave(100,200)) x;  //相当于int x       //判断x类型是,并不会直接执行函数ave()函数
    
    注:如果是auto,则会执行一次函数,如auto xauto=ave(a,b);  //会执行一次ave函数

总结:decltype并不会真得执行表达式,只是根据表达式来推断运算结果得类型

5)auto->decltype

auto bigger(int& a,int& b)->int&
{
    return a>b?a:b;
}

//可以利用decltype得特性,把函数写为:
auto bigger(int& a,int& b)->decltype(a>b?a:b)
{
    return a>b?a:b;
}

//C++14以后,可以写为如下方式
decltype(auto) bigger(int& a,int& b)
{
    return a>b?a:b;
}